Conventions and contributing
Task briefs
Section titled “Task briefs”Work is organised as briefs under docs/tasks/, one file per unit of work, written
to be handed to an agent cold: the files to open, the API surface to add, the tests
that must pass, and what is out of scope. Each opens with frontmatter that is the
single source of truth for status, and npm run roadmap generates docs/roadmap.html
from it. Understood-but-undecided work lives in docs/gaps/, deliberately outside
the numbering, so listing it does not claim a plan that does not exist.
docs/tasks/README.md holds the invariants; read it before
any brief.
Conventions to match
Section titled “Conventions to match”- Validation lives in
shared/as zod schemas, imported by both sides of the wire. Input schemas go inshared/api.ts(orshared/orders.tsfor order shapes), never inline in a route. - Routes stay thin. Parse, call a repository function, respond. SQL lives in
db/*-repository.ts. - Errors use
httpError(status, message); messages are user-facing. - Client mutations go through
csrfPost/csrfPut/csrfDelete, wrapped in a hook insrc/admin/queries.tsthat invalidates its own key and the public store key. - Comments explain why, not what. The codebase comments decisions and the mistakes they head off. Match that register.
- Money is integer cents, and CSV columns are
*_centsfor the same reason.
Commands
Section titled “Commands”npm run typecheck # tsc -bnpm run lint # eslintnpm test # vitest: unit, component, and both database dialectsnpm run test:e2e # playwrightnpm run db:generate # after editing BOTH schema filesnpm run db:migrate # applyThings that look like bugs and are not
Section titled “Things that look like bugs and are not”- A fresh checkout has no store in it.
npm testseeds per suite, butnpm run test:e2edrives the real app againstdata/beluga.sqlite. In a new clone or worktree the whole suite fails on a missing heading, which reads as a broken storefront and is an empty database.npm run db:migrate && npm run db:seedonce per checkout. .envis irrelevant to e2e. Playwright pointsENV_FILEat a file that does not exist, on purpose.- Postgres skips silently in
db/dialect.test.tsifembedded-postgrescannot start. Confirm with--reporter=verboseand look forrepository on postgres.
Definition of done
Section titled “Definition of done”npm run typecheck && npm run lint && npm testpass.- New routes are in
MUTATIONSorREADSinserver/security.test.ts. - New schema fields are in both dialect files and both migration folders.
- The README section for the area is updated if behaviour changed.
- No
console.logleft behind except deliberate operator-facing lines. - The brief’s frontmatter says
donewith a date and a PR number, andnpm run roadmapoutput is committed.
Branching
Section titled “Branching”The base branch is main. Branch from it, open pull requests against it, and
delete the branch when it lands. Several sessions may be working in the
repository at once, so always create a branch before starting.