HatchedDocs
Platform

Contributing

Branch model, commit conventions, code standards, and the PR checklist for contributing to Hatched.

Branch model

  • main is the always-deployable trunk. CI runs against every PR; a green build is a prerequisite for merge.
  • Feature branches: feat/<scope>-<short-description>, e.g. feat/badges-daily-limit.
  • Fix branches: fix/<scope>-<short-description>.
  • Chore / docs: chore/<...>, docs/<...>.

Commit conventions

Short, imperative, lowercase scope prefix. One change per commit when practical; a refactor that spans 20 files and has no logical single-commit unit is fine as a single commit if it's genuinely atomic.

feat(badges): add weekly earning cap
fix(rule-engine): serialize custom-counter writes
chore(ci): drop --passWithNoTests

Reference issues / PRs in the body, not the subject.

Code standards

  • TypeScript strict across all projects. Don't any escape.
  • No backwards-compat hacks — the project has zero users. Delete code that won't be missed; renaming a variable is cheaper than aliasing it forever.
  • No half-finished implementations — merge only what is wired up end to end.
  • Don't narrate what code does in comments. Comments explain why a non-obvious choice was made (a subtle invariant, a workaround, an external constraint). Remove dead // TODO comments — open a ticket instead.
  • Entity → DTO boundary is enforced. Controllers must not import entity classes; they return DTOs produced by a module-local mapper.

Tests are the contract

  • Every new feature that touches money (coins, tokens, badges), auth, or the rule-engine requires an e2e test under apps/api/test/.
  • Invariant tests (invariant-idempotency, invariant-tenant-isolation, invariant-concurrency) are load-bearing — do not weaken them. If a PR has to touch them, add a commit that explains the new invariant first.
  • Widget changes get a build-level assertion (widgets are produced) plus a manual click-through in apps/demo.
  • Dashboard CRUD pages ship with a Playwright smoke.

Running the gates locally

pnpm install
pnpm lint                              # fail on any warning
pnpm build                             # every workspace builds
pnpm --filter @hatched/api test        # unit tests
pnpm --filter @hatched/api test:e2e    # e2e (needs local Postgres + Redis)

CI runs exactly these. If they are red locally, don't push.

Capability naming

When adding a new capability / feature gate:

  1. Add the key to CustomerFeatures in @hatched/shared/types.
  2. Add a default in apps/api/src/database/migrations/…_default_feature_flags.sql (or equivalent default provider).
  3. Add a label + description in apps/dashboard/src/components/capability-disabled-state.tsx.
  4. Update the settings page capability toggles.
  5. Add the capability to AudienceBrief per-audience overrides if it applies per-segment.

Always use the word capability in UI copy; feature belongs to internal code paths.

PR checklist

Before requesting review:

  • pnpm lint && pnpm build green.
  • New behavior has an e2e or unit test.
  • If the change affects a public contract (API DTO, widget prop, SDK method), the type is sourced from @hatched/shared and all call sites compile.
  • If the change introduces an error path, it throws a subclass of HatchedException with a stable code.
  • Migration files are numbered strictly ascending and unique.
  • No secrets or hex-looking literals committed in source.
  • Docs updated when behavior in README, ARCHITECTURE, or OBSERVABILITY goes stale.

PR description should answer why this change, not what — the diff already shows the what.