Contributing
This guide covers the mechanics of making a change to Surplus — getting set up, meeting the quality bar, and getting your work validated. If you're brand new, start with the Quickstart.
Before you start
Make sure you can run the stack and that a clean checkout passes:
bun install
bun run typecheck # types across every package
bun run lint # Biome check + autofixBoth should pass on main before you begin, so you know any failures afterward are yours.
Make your change
Surplus rewards working with its structure rather than around it:
- Follow the conventions. camelCase, no
any, semantic UI tokens, stable element IDs, transactions for multi-step writes. These aren't style preferences — they're what keeps the codebase navigable and the tests reliable. - Adding or changing an endpoint? Touch the four layers in order — contract, service function, service class, route handler — as described in Request lifecycle. The contract change is what keeps the client and server in sync.
- Changing the database? Edit the Drizzle schema in
packages/postgres/src/schema/(the source of truth) and apply it with the Drizzle push workflow. Don't hand-write ad-hoc SQL for routine schema changes. - Reuse types. Derive from
@surplus/typeswith.pick(),.omit(),.partial(), and.extend()instead of redefining fields.
Validate locally
Before opening a pull request, run the full set of checks:
bun run typecheck
bun run lint
bun run test # Playwright end-to-end suite (ephemeral infra)The end-to-end suite spins up a throwaway Neon branch and Upstash instance, so it's safe to run repeatedly. See Testing for how to add coverage for your change — new behavior should come with a test.
Continuous integration
Pull requests targeting main and staging automatically run the end-to-end suite in GitHub Actions (.github/workflows/e2e.yml). CI injects configuration through the Railway CLI, so it runs against the same kind of ephemeral infrastructure you use locally. Get CI green before requesting a merge.
Commit messages
Keep commits focused and write messages in the imperative, describing the change and the why — for example, "Add multi-distribution batch allocate for hub inventory" or "Fix logger not visible in Apitally." Group related work into a single logical commit rather than many noisy ones.
A quick checklist
-
bun run typecheckpasses -
bun run lintis clean -
bun run testpasses, and new behavior is covered - The change follows the conventions
- New endpoints go through the contract and the four layers
- Schema changes are made in Drizzle, not ad-hoc SQL
Security issues
Found a vulnerability? Please don't open a public issue. See Vulnerability disclosure for how to report it responsibly.