- Rename docs/decisions/adr-012-lazar-conformance.md → adr-012-feature-conventions.md - Strip "Lazar", "Plan 8/9/10/11", "refactor-logs" refs from all ADRs, architecture docs, HTML explainers, and feature/core AGENTS.md files - Update all incoming links in docs/, packages/*/AGENTS.md, HTML explainers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.5 KiB
AGENTS.md — navigation
Header global for main site navigation. Provides the Header Payload global and tRPC procedures for dynamic navigation content.
Overview
@repo/navigation owns: Header and HeaderItem domain models, navigation-scoped errors, the IHeaderRepository interface, one use case, one controller, a real Payload-backed repository, and the tRPC navigationRouter. The headerQuery React Query builder lives in ./ui.
Layer responsibilities
| Layer | Key files |
|---|---|
| entities/models | header.ts — Header, HeaderItem Zod schemas + types |
| entities/errors | header.ts (HeaderNotFoundError), common.ts (InputParseError) |
| application/use-cases | get-header.use-case.ts — factory function + exported schemas |
| application/repositories | header.repository.interface.ts — IHeaderRepository |
| infrastructure/repositories | header.repository.ts (real Payload-backed), header.repository.mock.ts (in-memory) |
| interface-adapters/controllers | get-header.controller.ts — one file per use case |
| di | symbols.ts (NAVIGATION_SYMBOLS), module.ts, container.ts, bind-production.ts |
| integrations/api | procedures.ts (navigationProcedure), router.ts (navigationRouter) |
| integrations/cms | globals/header.ts — Payload Header GlobalConfig |
| ui | src/ui/index.ts — re-exports headerQuery |
Public exports
| Subpath | Contents |
|---|---|
. |
Header, HeaderItem types; HeaderNotFoundError, InputParseError; getHeaderInputSchema, getHeaderOutputSchema, GetHeaderInput, GetHeaderOutput, IGetHeaderUseCase; IGetHeaderController type alias; NavigationRouter type |
./ui |
headerQuery — React Query option builder |
./api |
navigationRouter (tRPC router) |
./cms |
Payload Header global definition |
./di/bind-production |
bindProductionNavigation(ctx: BindProductionContext) — swaps mock impls for real Payload-backed ones at app boot |
./di/bind-dev-seed |
bindDevSeedNavigation(ctx: BindContext) — replaces the default empty mock with a populated one for dev / Storybook |
Use-case + controller patterns
See CLAUDE.md Key Conventions and docs/architecture/overview.md for the canonical factory templates.
Use case
| Use case | Input schema | Output schema | Notes |
|---|---|---|---|
getHeaderUseCase |
getHeaderInputSchema — z.object({}).strict() (void input) |
getHeaderOutputSchema — = headerSchema |
Takes _input: GetHeaderInput; throws HeaderNotFoundError when repository returns falsy; ends with getHeaderOutputSchema.parse(header) |
Controller
getHeaderController uses an identity presenter — function presenter(value: GetHeaderOutput) { return value; } — and returns Promise<ReturnType<typeof presenter>>. Accepts unknown input and safeParse with getHeaderInputSchema, throwing InputParseError on failure.
Errors → tRPC codes
| Error class | tRPC code | Thrown by |
|---|---|---|
InputParseError |
BAD_REQUEST |
controller (safeParse failure; also triggers on strict() rejecting unknown keys) |
HeaderNotFoundError |
NOT_FOUND |
getHeaderUseCase when repository returns falsy |
Defined in src/integrations/api/procedures.ts via navigationProcedure = t.procedure.use(defineErrorMiddleware([...])).
Tests
- Factories:
src/__factories__/header.factory.ts,src/__factories__/nav-item.factory.ts - Contract suite:
src/__contracts__/header-repository.contract.ts— runs against mock and realHeaderRepository - Unit tests: colocated
*.test.tsnext to each source file - R25 (output validation):
get-header.use-case.test.tshas a test using an inline malformed repository mock (e.g.,{ items: [{ label: "", href: "/", external: false }] }, label failingmin(1)) to assert.rejects.toBeInstanceOf(ZodError). - R26 (router error mapping):
router.test.tsassertsBAD_REQUESTwhen input has extra unknown keys (strict mode rejection → InputParseError), andNOT_FOUNDvia an inlineNullHeaderRepositoryrebind causingHeaderNotFoundError.
pnpm test --filter @repo/navigation
pnpm test --filter @repo/navigation -- --watch
See docs/guides/tdd-workflow.md for the full cycle.
Directory structure
src/
entities/
models/
header.ts # Header, HeaderItem schemas + types
errors/
header.ts # HeaderNotFoundError
common.ts # InputParseError
application/
repositories/
header.repository.interface.ts
use-cases/
get-header.use-case.ts
infrastructure/
repositories/
header.repository.ts # real Payload-backed
header.repository.mock.ts
interface-adapters/
controllers/
get-header.controller.ts
integrations/
api/
procedures.ts # navigationProcedure
router.ts # navigationRouter
cms/
globals/
header.ts
index.ts
di/
symbols.ts
module.ts
container.ts
bind-production.ts
ui/
index.ts # headerQuery
query.ts
index.ts
__factories__/
header.factory.ts
nav-item.factory.ts
__contracts__/
header-repository.contract.ts
What it must NOT import
- Any other feature package (
@repo/auth,@repo/blog, etc.) - Any app package
@repo/core-api,@repo/core-cms,@repo/core-trpc,@repo/core-uidirectly; only@repo/core-sharedNote:
@repo/core-trpcand@repo/core-uiare optional packages scaffolded viapnpm turbo gen core-package trpc/ui. If not present, these constraints still apply to any future installation.
Cross-links
- ADR-012 (
docs/decisions/adr-012-feature-conventions.md) — factory-style use cases, per-use-case controllers, file-naming conventions - ADR-013 (
docs/decisions/adr-013-input-output-unification.md) — schemas-in-use-case, presenter,./uisubpath, error middleware