Files
agentic-dev/CLAUDE.md
Danijel Martinek 8f34daca36 docs(dev-seed): canonical doc updates + refactor-log entry
- CLAUDE.md Key Conventions: 'App bootstrap' rule rewritten as 'Three
  binding modes per feature' — describes USE_DEV_SEED + NODE_ENV
  resolution order and the new ./di/bind-dev-seed export.
- AGENTS.md (root): exports list now mentions ./ui + ./di/bind-dev-seed;
  Per-feature public-API surface table gains a row; Apps section shows
  the bindAll() dispatcher with three-rule logic.
- docs/architecture/vertical-feature-spec.md §6: file shape now
  includes bind-dev-seed.ts, bind-dev-seed.test.ts, __seeds__/dev.ts;
  package.json exports list updated to include ./di/bind-dev-seed.
- docs/architecture/data-flow-explainer.html: anatomy tree gains
  __seeds__/ row; LAYERS.di description updated with new binders +
  cross-link to di-explainer.html; new LAYERS.seeds entry; public-
  surface card expanded to six subpaths.
- docs/superpowers/refactor-logs/2026-05-06-input-output-unification.md
  §7: new 'Post-Plan-9: dev-seed binders' entry summarizing the rollout
  (commits, per-feature additions, app wiring, tests, turbo, docs).
- bind-production.test.ts: dispatcher tests use vi.stubEnv (typesafe
  way to test process.env in TypeScript 5+ with @types/node read-only
  process.env types). 4 dispatcher tests + 2 bindAllProduction tests
  = 7 tests total.
2026-05-06 19:49:58 +02:00

5.7 KiB

Clean Architecture Monorepo Template

Quick Start

pnpm install          # Install all dependencies
pnpm dev              # Start all dev servers
pnpm build            # Build all packages
pnpm test             # Run all tests
pnpm turbo boundaries # Validate workspace dependency graph
docker compose up -d  # Start PostgreSQL

TDD

pnpm test --watch --filter @repo/<feature>   # watch one feature
pnpm test -- --coverage                      # full run with coverage
pnpm test:stories                            # Storybook smoke tests
pnpm test:e2e                                # Playwright e2e

See docs/guides/tdd-workflow.md for the full cycle.

Project Overview

Turborepo + pnpm monorepo organized by vertical features. Each feature (auth, blog, media, marketing-pages, navigation) owns its Clean Architecture layers. Core packages (core-shared, core-cms, core-api, core-trpc, core-ui) provide foundation. Two tooling packages (core-eslint, core-typescript) provide shared configs. Workspace boundaries are enforced by ESLint (lint-time) and Turborepo (build-graph time). Supports Next.js and TanStack Start as frontend frameworks, Payload CMS for content management, and comprehensive agent-optimized documentation.

Read First

  • AGENTS.md — Package map, boundary rules, per-package conventions
  • docs/architecture/overview.md — High-level architecture and package responsibilities
  • docs/architecture/vertical-feature-spec.md — Design spec with rationale and decision log
  • docs/guides/adding-a-feature.md — End-to-end new feature walkthrough

Key Conventions

  • Relative imports in src/ — Source files use relative paths (../repositories/...), not @/ alias
  • @/ alias in tests — Test files (*.test.ts) use @/ to import from src/
  • vitest.config.ts — Every package must define resolve.alias: { "@": path.resolve(__dirname, "./src") }
  • tsconfig.json rootDir — Set "rootDir": "." so TypeScript finds both src/ and test files
  • Lazar-conformant file layout — Entities live at entities/models/<x>.ts; errors at entities/errors/<domain>.ts + entities/errors/common.ts; mock siblings use the .mock.ts suffix (<x>.repository.mock.ts); real repository impls drop the payload- prefix (<x>.repository.ts); interface filenames are dot-separated (<x>.repository.interface.ts)
  • Factory-function use cases & controllers — Every use case and controller is (deps) => async (input) => result; each exports export type I*UseCase = ReturnType<typeof xUseCase> (and the analogous I*Controller); one controller per use case (no multi-method controllers)
  • DI uses .toDynamicValue() for factoriesbind<IXUseCase>(SYMBOL).toDynamicValue((ctx) => xUseCase(ctx.container.get(...))); mocks remain the default binding
  • Tests inject mocks directly — Construct MockXRepository and pass into the factory: signInUseCase(mockUsers, mockAuth)(input). No container rebinding in unit tests
  • Schemas in the use-case file — Every use case exports xInputSchema (a z.ZodObject with .strict(); z.object({}).strict() for void inputs) and, for non-void use cases, xOutputSchema. Types: XInput = z.infer<typeof xInputSchema> and XOutput. Use case body ends with xOutputSchema.parse(result) before returning (runtime guarantee against malformed repository data)
  • Controllers receive unknown + presenter — Controllers safeParse(xInputSchema) from the use-case file and throw InputParseError on failure. Non-void controllers define a top-level function presenter(value: XOutput) and return Promise<ReturnType<typeof presenter>> (identity is fine — return value); void controllers return Promise<void> with no presenter
  • Feature-scoped tRPC error mapping — Each feature has integrations/api/procedures.ts exporting xProcedure = t.procedure.use(defineErrorMiddleware([[Ctor, "TRPC_CODE"], ...])) from @repo/core-shared/trpc/define-error-middleware. Routers use xProcedure.input(xInputSchema) — schemas are imported from the use-case file, never redefined inline. core-shared never enumerates feature error classes
  • Public surface split — Feature root (.) exports contracts only: types, errors, schemas, IUseCase / IController aliases, router type, constants. UI artifacts (query builders, components) live behind ./ui (src/ui/index.ts). Apps import queries from @repo/<feature>/ui, schemas/types from @repo/<feature>
  • Payload repositories via constructor — Feature packages receive Payload config at constructor time, not as a direct dependency
  • Three binding modes per feature — Each feature exports two binders: ./di/bind-production (real Payload) and ./di/bind-dev-seed (populated mock). The app's bindAll() dispatcher in apps/web-next/src/server/bind-production.ts picks one by env: USE_DEV_SEED="true" → dev seed; NODE_ENV="production" → production; otherwise → dev seed (developer default so pnpm dev boots without Payload). Dev seed lives in src/__seeds__/dev.ts as a lazy buildDev<Entities>() function that uses the feature's existing factory
  • App bootstrap — Each app calls bindAll() from a server entry point (page server component, route handler) before resolving any feature controller. The dispatcher is idempotent

MCP Servers

Start Storybook before UI work: pnpm dev --filter @repo/storybook

Storybook MCP available at http://localhost:6006/mcp — use list-all-documentation to discover existing components before creating new ones.

Key Ports

Service Port
Next.js 3000
Payload CMS 3001
TanStack Start 3002
PostgreSQL 5432
Storybook 6006