First slice of the Plan 8 deferred doc-update checklist: - CLAUDE.md Key Conventions: factory-function use cases/controllers, entities/models/<x>.ts paths, .toDynamicValue DI bindings, direct injection in tests - docs/architecture/overview.md data-flow box updated to factory style (controller resolved via container.get<IXController>; use case factory takes deps as args) - docs/decisions/adr-012-lazar-conformance.md created — records the conformance decision and four intentional divergences - docs/superpowers/plans/2026-05-05-plan-7-tdd-foundation.md and the matching spec annotated with a "pre-Plan-8 layout" note pointing at the refactor log Remaining Plan 8 doc-update items (root AGENTS.md, per-feature AGENTS.md, adding-a-feature.md, tdd-workflow.md, testing-strategy.md, vertical-feature-spec.md §6/§10, core-testing AGENTS.md) intentionally paused — Plan 9 (input/output unification) will change overlapping content, so resuming after Plan 9 lands avoids double-churn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.2 KiB
Architecture Overview
A vertical-feature monorepo. Business capabilities are top-level packages; non-business foundations are core-*.
Package map
packages/
# Foundation (no business logic)
core-shared/ Generic primitives — Payload field/block helpers, tRPC init/context, lib utilities
core-cms/ Composition only: assembles feature CMS exports into one Payload config
core-api/ Composition only: aggregates feature tRPC routers into one appRouter
core-trpc/ Frontend tRPC client + per-framework providers (Next.js, TanStack)
core-ui/ Design-system primitives (atoms, molecules, generic organisms, templates)
# Business capabilities
auth/ Users + sign-in/sign-up/sign-out + session/cookie domain
blog/ Articles collection + publishing flow
media/ Media upload collection (skeleton; expand with optimization, CDN, etc.)
marketing-pages/ Pages collection + SiteSettings global
navigation/ Header global + menu items
# Tooling
core-eslint/ Shared ESLint flat config + boundary rules
core-typescript/ Shared tsconfig + vitest base
Data flow
React component
↓ useQuery(trpc.blog.articleBySlug.queryOptions(...)) ← ui/query.ts (typed tRPC client)
HTTP /api/trpc
↓
tRPC procedure ← integrations/api/router.ts
↓ container.get<IXController>(SYMBOL)
Controller factory (Zod safeParse) ← interface-adapters/controllers/<verb-noun>.controller.ts
↓ (useCase) => async (input) => result
Use case factory ← application/use-cases/<verb-noun>.use-case.ts
↓ (deps) => async (input) => result; deps injected by container
Repository implementation ← infrastructure/repositories/<noun>.repository.ts
↓ getPayload({ config })
Payload Local API → Postgres
Use cases and controllers are factory functions — they take their dependencies
as arguments and return the callable. The container wires them via
.toDynamicValue((ctx) => factoryFn(ctx.container.get(...))). Each exports
export type I*UseCase = ReturnType<typeof xUseCase> (and the analogous
I*Controller) so consumers can depend on the type without importing the impl.
Controllers are one per use case — no multi-method controller files.
Three enforcement layers
package.jsondeps — only declare allowed depsexportsmap — each package exposes a small public surface (.,./cms,./api,./di/bind-production)- Two parallel automated checks:
- ESLint
eslint-plugin-boundaries(lint-time) — enforces boundary rules at linting - Turborepo
boundaries(build-graph time) — validates entire workspace dependency graph, including transitive reaches
- ESLint
Both use the same five-tag model; see "Five tags" section below.
Five tags
The workspace is organized into five mutually exclusive tags:
- app (4 packages):
apps/cms,apps/web-next,apps/web-tanstack,apps/storybook - core-composition (3 packages):
packages/core-api,packages/core-cms,packages/core-trpc - core (2 packages):
packages/core-shared,packages/core-ui - feature (5 packages):
packages/auth,packages/blog,packages/media,packages/marketing-pages,packages/navigation - tooling (2 packages):
packages/core-eslint,packages/core-typescript
Allowed dependency directions:
| Tag | May depend on |
|---|---|
| app | app, core, core-composition, feature, tooling |
| core-composition | core, core-composition, feature, tooling |
| core | core, core-composition, tooling |
| feature | core, tooling |
| tooling | tooling |
Composition exceptions:
core-apimay import from@repo/<feature>/apisubpath exports onlycore-cmsmay import from@repo/<feature>/cmssubpath exports onlycore-trpcreaches features transitively throughcore-api'sAppRoutertype
Per-feature DI containers
Each feature owns its own InversifyJS Container + symbol table. No shared symbols, no cross-feature DI coupling. Tests rebind per feature without touching others. Apps call bindProduction*(config) per feature at boot to swap the default mock implementations for Payload-backed ones.
Spec reference
docs/architecture/vertical-feature-spec.md is the canonical design.