Conventions
Code style
- Prettier for all formatting (config:
packages/config/prettier.js, defaults). - ESLint 10 flat config (
packages/config/eslint.config.js) withtypescript-eslintandeslint-plugin-import-x. - TypeScript strict mode — no
any, no implicitany, no!non-null asserts unless justified. - Comments follow the CLAUDE.md rule: default to none, write only when the why isn't obvious from names.
Commit messages
Conventional-Commits style, matching what already exists in git log:
feat(api): add PIX payment routes and drain webhook handling
fix(api): regenerate worker types before typecheck
docs(openspec): archive pix-payment-flow change
chore(api): drop redundant @cloudflare/workers-types dep
Scopes: api, mobile, web, docs, shared, config, openspec, ci.
Zod schemas
- Every request and response for every API endpoint has a Zod schema in
packages/shared/src/<domain>/schemas.ts. - The API imports and validates with
.parse()on requests; responses are typed off the inferred schema. - Mobile imports the same schema for typed API calls — one source of truth.
Naming
- Files: kebab-case (
pix-payments.ts, notpixPayments.ts). - Zod schemas:
PascalCaseSchemasuffix (CreatePixPaymentRequestSchema). - Inferred types: same name, no
Schemasuffix (CreatePixPaymentRequest). - DB tables: snake_case in the DB, camelCase in Drizzle (
pixPaymentsin TS →pix_paymentsin Postgres).
Errors
- API returns
{ error: string, code?: string }with a 4xx status for user errors, 5xx for our fault. - Bridge errors bubble as
BridgeApiError— the client (apps/api/src/bridge/client.ts) parses status, code, and body. - Never leak Bridge API keys or full error bodies to mobile — surface the user-facing message only.
Feature flags
Not currently used. When we need them, keep them close to the code they gate — no central registry.
OpenSpec
- Use OpenSpec for anything user-visible or architectural. Bug fixes and refactors don't need a spec.
- Archive completed changes with
/opsx:archive— this moves them underopenspec/changes/archive/YYYY-MM-DD-<name>/.