Thanks for helping out. Here's how to do it well.
Before diving in, read docs/PHILOSOPHY.md — the beliefs behind every decision in this codebase. The workflow below will make more sense once you understand the why.
If you're working with an AI agent on this project, also read AGENTS.md.
This is a GitHub template repo. You already have your own copy.
pnpm setup # Install dependencies + generate .env
docker compose up -d
pnpm db:migrate
pnpm devBackend on localhost:9999/docs. Frontend on localhost:5173.
git checkout dev
git pull origin dev
git checkout -b feature/your-feature-nameNever work directly on main or dev. Every change gets its own branch.
| Type | Branch pattern |
|---|---|
| New feature | feature/<name> |
| Bug fix | fix/<name> |
| Refactor | refactor/<name> |
| Chore / deps | chore/<name> |
| Docs | docs/<name> |
A feature, a fix, a refactor — not all three at once. If you notice something adjacent that needs fixing, note it and address it separately. Mixing concerns makes every change harder to review, harder to revert, and harder to understand later.
For anything non-trivial: think through the approach before writing code. A five-minute outline is faster than a misdirected hour of implementation.
Tests are part of the feature, not something you add at the end. Before you commit:
pnpm test # All tests
pnpm typecheck # TypeScript
pnpm lint # BiomeCI runs these on every PR. It will catch it — fix it locally first.
Conventional commits:
<type>(<scope>): <what changed>
<why it changed, if not obvious>
Types: feat, fix, refactor, chore, docs, test, perf
Scopes: backend, frontend, shared, db, auth, jobs, scripts
# Good
git commit -m "feat(backend): add pagination to posts endpoint"
git commit -m "fix(auth): handle expired refresh token on re-login"
# Bad
git commit -m "fixed stuff"
git commit -m "wip"
git commit -m "added the feature and also fixed a bug and updated docs"If the message needs "and" to describe it, it's probably two commits.
PR per coherent unit. Not per day, not per session. One change, one PR.
Keep it reviewable in under 30 minutes. A 1000-line diff is probably two PRs.
Use this template:
## What
Brief description. One to three sentences.
## Why
The user problem or technical need. Link to issue if one exists.
## How
Key decisions made. What alternatives were considered.
## Testing
How to verify this works. Specific steps.
## Notes
Technical debt introduced. Follow-up work needed. Anything a reviewer should know.Respond to all comments within 24 hours — even a simple "done" or "disagree because X". If you disagree, explain why. Reviews are collaborative, not adversarial.
Mark conversations as resolved when addressed. Request re-review when ready.
Monitor your changes in production for at least an hour. Delete the feature branch. Close related issues.
- Errors are values. Use-cases and repositories return
Result<T, E>— never throw. Seeapps/backend/docs/DECISIONS.md. - Handlers are thin. They read input, call the repository or use-case, map the Result to HTTP. No business logic in handlers.
- Infrastructure has one catch boundary. All DB/Redis calls go through
tryInfra. That's the onlytry/catchin repository code.
// Good
const result = await findUserById(id);
return match(result, {
ok: (user) => c.json(success(user), 200),
err: (e) => c.json(failure({ code: "NOT_FOUND" }), 404),
});
// Bad
try {
const user = await db.query.users.findFirst(...);
return c.json(user, 200);
} catch (e) {
return c.json({ error: "unknown" }, 500);
}- Server state lives in React Query. UI state lives in Zustand. Don't create a context for server data.
- Auth guards go in
beforeLoad, not inside the component. No flash of unauthenticated content. - Components wrap Base UI primitives. Style with design tokens (
--color-primary, etc.) not hardcoded Tailwind colors.
- No
any. Configure abiome-ignorecomment with a reason if you absolutely need one. - Name things clearly.
getUserByIdnotgetorfetchData. - Delete dead code. Don't comment it out.
- Leave the codebase better than you found it — but save it for its own commit.
pnpm new:module postsThen register in apps/backend/src/routes/index.ts. See the README for the full walkthrough.
# Edit packages/db/src/schema/your-table.ts
pnpm db:generate # Generates migration
pnpm db:migrate # Applies itnpx @better-auth/cli generate # Updates schema from auth config
pnpm db:generate
pnpm db:migrateCreate apps/frontend/src/routes/your-page.tsx. TanStack Router picks it up automatically.
You're done with a unit when:
- It works as intended
- Tests cover the new behavior
- Lint and typecheck pass
- The commit message describes what and why
- A stranger could understand the PR in five minutes
Finish what you start. Shipping three complete things beats ten half-done ones every time.
Ask in the internal chat or ping a tech lead directly.