SsuperslateDocs
Build with the boilerplate

Add a domain end to end

Build a durable product capability from contract and migration through UI and verification.

Golden path: add a domain end to end

When to use

Use this path when a product concept needs a new durable resource or business capability across Postgres, the Hono API, shared contracts, the typed web client, and the React UI. Use a smaller path for a presentation-only view, an application-only refactor, or a field on an existing resource.

Before editing, write the resource owner, allowed actors, lifecycle, invariants, public fields, failure semantics, and whether unauthorized callers may learn that a record exists. Avoid adding organizations, roles, collaboration, billing, uploads, or background work unless the PRD requires and verifies them.

Files and boundaries

  • apps/server/migrations/*.sql: durable schema, foreign keys, constraints, indexes, and reversal.
  • packages/contracts/src/<domain>.ts and index.ts: Zod request/query schemas and public response types; the server and web must not redeclare them.
  • apps/server/src/domains/<domain>/repository.ts: SQL and database row mapping only.
  • apps/server/src/domains/<domain>/service.ts: ownership-aware business rules, transactions, and stable domain errors.
  • apps/server/src/domains/<domain>/routes.ts: authentication, validation, HTTP mapping, and status codes; no SQL.
  • apps/server/src/domains/<domain>/types.ts: internal types only when they are not wire contracts.
  • apps/server/src/app.ts: one mounted domain route.
  • apps/web/src/services/<domain>.service.ts: typed Hono calls, React Query keys, mutations, and precise invalidation.
  • apps/web/src/modules/<domain>/: accessible loading, empty, error, success, and mutation UI.
  • apps/web/src/AppRouter.tsx, utils/routes.ts, and navigation only when the domain has a page.
  • contract, repository/service, HTTP/auth, web utility/component, and route tests.

Routes translate HTTP. Services own business decisions. Repositories own SQL. Shared contracts own wire shapes. UI components do not call fetch or duplicate the server cache outside TanStack Query.

Procedure

  1. Write a short acceptance table covering authenticated owner success, invalid input, unauthenticated access, cross-owner access, missing record, conflict/idempotency where relevant, loading, empty, error, and narrow/keyboard UI behavior.
  2. Add request and query Zod schemas plus public response types in packages/contracts. Parse representative valid/invalid fixtures. Do not expose a database row merely because it exists.
  3. Follow Add a database migration. Use users(id) for personal ownership, define delete behavior deliberately, and index every ownership/list predicate. A unique invariant belongs in Postgres, not only in the UI.
  4. Implement repository functions with explicit inputs and mapped outputs. Every read/update/delete of a user-owned record includes the owner predicate in the SQL statement; do not fetch globally and authorize afterward.
  5. Implement services that express product behavior and translate absent/forbidden resources into stable application errors. Prefer the same not-found result for a missing record and another user record when revealing existence is unnecessary.
  6. Implement routes with sessionAuth, requireUserId, and sValidator. Pass the authenticated identity into the service; never accept owner identity from JSON, query, headers, or URL params. Use sendSuccess, sendPaginated, or sendOk consistently.
  7. Mount the route once in apps/server/src/app.ts. Run the server and contracts typechecks before writing the web service so Hono RPC exposes the new route accurately.
  8. Add one service module in the web app. Define stable query-key factories, use api with rpc or rpcPaginated, pass inferred contract values, and invalidate the narrowest list/detail keys after mutations.
  9. Add a lazy route and page module. Preserve semantic form labels, keyboard operation, focus, actionable errors, loading state, empty state, mutation pending state, and narrow viewport behavior. Do not add a second state or styling system.
  10. Add tests at the lowest valuable layers:
    • contract parsing and transforms;
    • real Postgres ownership and constraints;
    • authenticated HTTP success, invalid input, no session, and cross-owner denial;
    • query-key/route utilities and meaningful UI behavior.
  11. Prove the new migration from empty with up → down → up, run the DB-enabled tests, then run the full repository checks and build.
  12. Generate a custom-scope/custom-brand buyer, repeat the change or replay its commit, and execute the evaluation harness from its clean tagged start. Record every intervention and architecture violation.

Verification

From repository root, with an exact disposable database:

vp check
vp run --filter @app/contracts test
vp run --filter @app/server typecheck
vp run --filter @app/web typecheck
RUN_DB_INTEGRATION_TESTS=1 \
  DATABASE_URL='postgres://localhost:5432/app_domain_proof?sslmode=disable' \
  bun test --cwd apps/server src/domains/<domain>
vp run -r test
vp run -r build
git diff --check

Also assert that:

  • route files do not import bun:sql or infra/db;
  • application code outside apps/server/src/config/env.ts does not add process.env reads;
  • existing released migrations are unchanged;
  • the web imports request/response types from shared contracts or infers them from Hono RPC;
  • unauthenticated calls return 401 and cross-owner detail/update/delete cannot reveal or mutate data;
  • a second clean install can understand the changed boundary without the author transcript.

Use docs/evaluations/fixtures/domain-golden-path.json for the representative bookmarks proof.

Security constraints

  • Resolve identity exclusively from the Better Auth session.
  • Scope ownership in the SQL statement for reads and writes; a client-provided user_id is never authorization.
  • Validate every untrusted body, query, and identifier before the service boundary.
  • Use indistinguishable missing/cross-owner responses unless disclosure is an explicit requirement.
  • Review mass assignment, URL/HTML rendering, uniqueness races, foreign-key deletion, pagination bounds, and sensitive fields in responses/logs.
  • Keep secrets, cookies, auth links, credentials, private URLs, and customer fixtures out of commits, evaluation specs, command logs, and issue comments.

Failure modes

  • Duplicating a request interface in the web app and drifting from the server schema.
  • Putting SQL or business branching in Hono routes to save one file.
  • Fetching a record by ID and checking user_id later, which leaks timing/existence and invites a missed authorization branch.
  • Trusting owner identity from the request body or an ad hoc header.
  • Invalidating every React Query cache entry after each mutation.
  • Rendering only the populated happy path with no loading, empty, error, or pending state.
  • Treating TypeScript inference as runtime input validation.
  • Adding product-unrelated collaboration, admin, billing, queue, or provider abstractions.
  • Marking the workflow complete after source tests without a generated-buyer execution.

Rollback and diagnosis

If the HTTP type is missing in the web client, verify the route is mounted in the fluent createApp chain, contracts export once, and both server and web typechecks see the same workspace package. Do not add a manual client interface as a workaround.

For incorrect ownership, stop before deployment. Add cross-owner tests at repository and HTTP boundaries, move the owner predicate into the SQL mutation, and inspect every list/detail/update/ delete function. Follow the failed-migration path for schema errors. Roll back UI and API code in compatibility order; use the migration down section only when its data effect is safe.

Acceptance criteria

  • One short PRD maps to an explicit owner, lifecycle, invariants, and failure contract.
  • Shared Zod contracts parse valid input and reject invalid input without duplicate wire types.
  • The migration passes fresh up → down → up and real Postgres tests.
  • Repository, service, routes, app mounting, typed web service, route, and UI each retain their boundary.
  • Owner success, invalid input, unauthenticated access, cross-owner access, and missing records are tested.
  • UI loading, empty, error, success, pending, keyboard, and narrow-viewport behavior is addressed.
  • Root verification and a custom generated-buyer evaluation pass without architecture violations.
  • Any human intervention or remaining unsupported behavior is recorded rather than hidden.

Agent prohibitions

  • Do not put SQL in routes or HTTP objects/status codes in repositories.
  • Do not duplicate shared request or response types.
  • Do not accept user_id, role, entitlement, or ownership claims from the client.
  • Do not authorize only in the UI or only after an unscoped database lookup.
  • Do not mutate a released migration or forge migration history.
  • Do not add a second API client, server-state cache, styling system, or authentication mechanism.
  • Do not weaken validation, auth, ownership, constraints, or tests to make the fixture pass.
  • Do not claim cold or independent execution when the authoring agent performed the run.

On this page