Git Workflow
- Create a branch from
main:git checkout -b feat/your-feature - Make changes, commit, push
- Open a pull request against
main - CI runs automatically (lint + test + build)
- Merge when CI passes and review is approved
Branch naming is not enforced by tooling; keep it descriptive.
Code Style
Prettier
Configuration is in .prettierrc:
| Setting | Value |
|---|---|
| Single quotes | true |
| Semicolons | true |
| Trailing commas | ES5 (objects, arrays, function params) |
| Print width | 100 |
| Arrow parens | avoid (omit parens for single-arg arrow fns) |
| Indentation | 2 spaces |
ESLint
Flat config in eslint.config.mjs. Run with pnpm lint.
ESLint and Prettier are separate — ESLint catches logic issues, Prettier handles formatting. Do not add ESLint rules that duplicate Prettier formatting.
Pre-commit Hooks
husky + lint-staged runs prettier --write --ignore-unknown on staged files only. This means:
- Formatting is applied automatically on commit.
- Only staged files are touched — unstaged changes are not affected.
- No lint or typecheck runs on commit. CI catches those.
If a commit is rejected because of a Prettier failure (unusual), run pnpm exec prettier --write <file> and re-stage.
CI Checks
Every push and pull request runs three checks via .github/workflows/ci.yml:
pnpm lint— ESLint must pass cleanlypnpm test:ci— all vitest tests must pass, coverage collectedpnpm build— Next.js build must succeed with the dummy env vars
All three must pass for the PR to be mergeable (if branch protection is configured). See CI/CD for the full workflow breakdown.
Writing Tests
Tests use vitest. Files are colocated with the code they test, e.g. lib/scoring/normalize.test.ts.
Run tests:
pnpm test # interactive watch mode
pnpm test:ci # single run with coverageGuidelines:
- Test actual behavior, not that a function was called
- Do not assert on default configurations that could change without being a bug
- Test conditional branches, edge values, invariants across fields, error handling
- No mocks of business logic — test the real functions with real inputs
Adding Database Migrations
- Create a new file in
supabase/migrations/with the next sequence number:012_your_change.sql - Write idempotent SQL where possible (use
IF NOT EXISTS,DO $$ ... $$) - Apply locally:
npx supabase db push - Commit the migration file alongside the code that depends on it
Do not modify already-applied migrations. All schema changes go in new files.