Referenta

These rules apply equally to human contributors and AI agents. They exist because each one corresponds to an incident — something that got broken once, took a while to figure out, and is now cheap to avoid by following a small convention.

Do not hand-edit generated artifacts

Anything below is produced by a tool. If the output looks wrong, fix the input and regenerate; don't patch the output. supabase/migrations/*.sql is the one entry with a documented exception — see below.

*.sql — generated by pnpm db:diff
database.types.ts — generated by pnpm db:types*
.env.development.local — auto-written by supabase/dev.sh
next-env.d.ts — regenerated by Next.js

Default to the generated migration: run pnpm db:diff and commit its output. Hand-writing or hand-editing a file in supabase/migrations/ is allowed only when db:diff can't express the change safely — and only if you mirror the same change into supabase/schemas/ so a follow-up pnpm db:diff produces an empty diff (zero drift). The schema stays the source of truth; the migration must converge to it. Everything else in the list above has no such exception: fix the input and regenerate.

Schemas-first for the database

supabase/schemas/ is the source of truth for the database. supabase/migrations/ is generated history. The full rules and rationale are in supabase/migrations/README.md; the short version is:

  1. Edit the file under supabase/schemas/ that owns the affected product domain.
  2. If you added a new schema file, register it in supabase/config.toml under [db.migrations].schema_paths.
  3. Run pnpm db:diff <short_snake_case_name> to generate a timestamped migration.
  4. Inspect the migration. Prefer fixing the schema and rerunning db:diff over patching the migration by hand. Hand-edit only when db:diff can't express the change safely — and then mirror the same change into the schema so a follow-up db:diff produces an empty diff (zero drift).
  5. Regenerate types with pnpm db:types:local and commit schema + migration + regenerated types in the same PR.

See Database Workflow for the schema file layout and the standard flow.

Dedupe-first when bumping cross-package dependencies

pnpm hashes peer-dependency variants. If two workspace packages depend on, say, react@19.2.4 and react@19.2.5, pnpm will install two copies — and any library that relies on module-level singletons (Sonner toasts, React contexts, Zustand stores, etc.) will silently break because each package gets a different singleton.

Before bumping a shared dep in one app, check whether any other app or workspace package depends on it:

pnpm why <pkg>

If multiple workspace entries pin to different versions, either bump them all in lockstep or align the new app to the existing pin. Don't introduce a fresh version just because the install resolves cleanly — the consequence shows up at runtime as features that visibly don't work and no error in the console.

Sonner is a known landmine. If apps/admin and packages/ui resolve to different sonner copies, the toast() calls in admin point at one singleton and the <Toaster /> mounted in admin (via the ui package) listens to another — toasts silently fail. Keep React and Sonner versions identical across every app that consumes @referenta/ui.

Regenerate derived artifacts after their source changes

ChangedRegenerate with
supabase/schemas/*.sqlpnpm db:types:local (regenerates apps/dashboard/database.types.ts). For apps/admin, also run pnpm --filter admin db:types:local.
apps/cms content schemapnpm --filter cms strapi generate if you edited content types via the file system; the Strapi UI handles it otherwise.
Service-role keys / Supabase project URLsRewrite the relevant .env.local. Restart any running dev server — Next.js reads env at process start.

If the schema change adds or renames columns, type regeneration must happen in the same PR as the schema change. A merged migration with stale types will break type-checking on the next person's branch.

Don't bypass git hooks

lefthook + biome enforce formatting and lint on every commit. If a hook blocks a commit, fix the underlying issue — don't pass --no-verify. The same applies to --no-gpg-sign and amending published commits.

If a hook is wrong (not the code), fix the hook in lefthook.yml and ship it as a separate small PR.

Don't redeclare env vars unnecessarily

pnpm env:pull writes hosted-dev credentials to apps/dashboard/.env. The local Supabase wrapper (supabase/dev.sh) writes the local Supabase + service URLs to apps/dashboard/.env.development.local. Next.js precedence makes .env.development.local win for local dev, so the two coexist cleanly.

If you find yourself adding the same key to multiple .env* files, stop and check which one Next.js actually loads first (precedence: .env.local.env.development.local.env.development.env). Redeclaring a key in a higher-precedence file is a deliberate override, not a default.

On this page