- 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>
11 KiB
AGENTS.md — marketing-pages
Pages collection + SiteSettings global for site-wide metadata. Provides marketing/landing page content, SEO settings, and site configuration via Payload.
Overview
@repo/marketing-pages owns: Page and SiteSettings domain models, marketing-pages-scoped errors, IPagesRepository + ISiteSettingsRepository interfaces, two use cases, two controllers, real Payload-backed repositories, and the tRPC marketingPagesRouter. Query builders live in ./ui.
Layer responsibilities
| Layer | Key files |
|---|---|
| entities/models | page.ts (Page, PageStatus, Hero), site-settings.ts (SiteSettings) — Zod schemas + types |
| entities/errors | page.ts (PageNotFoundError), common.ts (InputParseError) |
| application/use-cases | get-page-by-slug.use-case.ts, get-site-settings.use-case.ts — factory functions + exported schemas |
| application/repositories | pages.repository.interface.ts, site-settings.repository.interface.ts |
| infrastructure/repositories | pages.repository.ts, site-settings.repository.ts (real Payload-backed); pages.repository.mock.ts, site-settings.repository.mock.ts |
| interface-adapters/controllers | get-page-by-slug.controller.ts, get-site-settings.controller.ts — one file per use case |
| di | symbols.ts (MARKETING_PAGES_SYMBOLS), module.ts, container.ts, bind-production.ts |
| integrations/api | procedures.ts (marketingPagesProcedure), router.ts (marketingPagesRouter) |
| integrations/cms | collections/pages.ts, globals/site-settings.ts — Payload definitions |
| ui | src/ui/index.ts — re-exports pageBySlugQuery and siteSettingsQuery |
Public exports
| Subpath | Contents |
|---|---|
. |
Page, PageStatus, Hero, SiteSettings types; PageNotFoundError, InputParseError; all use-case schemas + input/output types + IXUseCase aliases; IXController type aliases; MarketingPagesRouter type |
./ui |
pageBySlugQuery, siteSettingsQuery — React Query option builders |
./api |
marketingPagesRouter (tRPC router) |
./cms |
Payload Pages collection + SiteSettings global |
./di/bind-production |
bindProductionMarketingPages(ctx: BindProductionContext) — swaps mock impls for real Payload-backed ones at app boot |
./di/bind-dev-seed |
bindDevSeedMarketingPages(ctx: BindContext) — replaces the default empty mocks with populated ones for dev / Storybook |
./di/container |
marketingPagesContainer — the per-feature inversify container (consumed by e2e tests + production Payload event-tasks) |
./di/symbols |
MARKETING_PAGES_SYMBOLS — DI symbol registry (consumed by e2e tests + production Payload event-tasks) |
./services/mailer |
IMailerService — interface for the welcome-email seam (ADR-015 proof-of-life) |
./services/recording-mailer |
RecordingMailerService — test / dev-seed mailer that records calls instead of sending |
Use-case + controller patterns
See CLAUDE.md Key Conventions and docs/architecture/overview.md for the canonical factory templates.
Use cases
| Use case | Input schema | Output schema | Notes |
|---|---|---|---|
getPageBySlugUseCase |
getPageBySlugInputSchema — { slug } |
getPageBySlugOutputSchema — pageSchema | undefined |
Returns undefined for missing pages (preserves existing semantics); PageNotFoundError mapping is forward-compatible for if/when the use case changes to throw |
getSiteSettingsUseCase |
getSiteSettingsInputSchema — z.object({}).strict() (void input) |
getSiteSettingsOutputSchema — siteSettingsSchema |
Takes _input: GetSiteSettingsInput parameter; always returns settings or throws |
Controllers
Both controllers use identity presenters — function presenter(value: XOutput) { return value; }. getPageBySlugController return type is ReturnType<typeof presenter> | undefined (preserves the missing-page semantics). Both accept unknown input and safeParse with the use-case's xInputSchema, throwing InputParseError on failure.
Errors → tRPC codes
| Error class | tRPC code | Thrown by |
|---|---|---|
InputParseError |
BAD_REQUEST |
controllers (safeParse failure) |
PageNotFoundError |
NOT_FOUND |
forward-compat; current getPageBySlugUseCase returns undefined instead of throwing |
Defined in src/integrations/api/procedures.ts via marketingPagesProcedure = t.procedure.use(defineErrorMiddleware([...])).
Tests
- Factories:
src/__factories__/page.factory.ts,src/__factories__/site-settings.factory.ts - Contract suites:
src/__contracts__/pages-repository.contract.ts,src/__contracts__/site-settings-repository.contract.ts - Unit tests: colocated
*.test.tsnext to each source file - Feature integration:
tests/page-by-slug.feature.test.ts— full slice: tRPC caller → controller → use case → mock repo - R25 (output validation):
get-page-by-slug.use-case.test.tshas a test injecting a malformed page repository mock asserting.rejects.toBeInstanceOf(ZodError).get-site-settings.use-case.test.tsuses an inline malformed repository mock (e.g.,{ siteName: "" }failingmin(1)) to assert ZodError. - R26 (router error mapping):
router.test.tsassertsBAD_REQUESTon empty input forpageBySlug; confirmsundefinedreturn for missing slug (use case does not throwPageNotFoundErrortoday). - R27/R28 (presenter shape): both controllers use identity presenters — no reshape test obligation.
pnpm test --filter @repo/marketing-pages
pnpm test --filter @repo/marketing-pages -- --watch
See docs/guides/tdd-workflow.md for the full cycle.
Directory structure
src/
entities/
models/
page.ts
site-settings.ts
errors/
page.ts # PageNotFoundError
common.ts # InputParseError
application/
repositories/
pages.repository.interface.ts
site-settings.repository.interface.ts
use-cases/
get-page-by-slug.use-case.ts
get-site-settings.use-case.ts
infrastructure/
repositories/
pages.repository.ts # real Payload-backed
pages.repository.mock.ts
site-settings.repository.ts # real Payload-backed
site-settings.repository.mock.ts
interface-adapters/
controllers/
get-page-by-slug.controller.ts
get-site-settings.controller.ts
integrations/
api/
procedures.ts # marketingPagesProcedure
router.ts # marketingPagesRouter
cms/
collections/
pages.ts
globals/
site-settings.ts
index.ts
di/
symbols.ts
module.ts
container.ts
bind-production.ts
ui/
index.ts # pageBySlugQuery, siteSettingsQuery
query.ts
index.ts
__factories__/
page.factory.ts
site-settings.factory.ts
__contracts__/
pages-repository.contract.ts
site-settings-repository.contract.ts
tests/
page-by-slug.feature.test.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