Files
agentic-dev/packages/marketing-pages/AGENTS.md
Danijel Martinek 841655573b docs(adr): rename ADR-012 — drop Lazar; update title + content + cross-refs
- 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>
2026-05-13 10:07:37 +02:00

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 } getPageBySlugOutputSchemapageSchema | undefined Returns undefined for missing pages (preserves existing semantics); PageNotFoundError mapping is forward-compatible for if/when the use case changes to throw
getSiteSettingsUseCase getSiteSettingsInputSchemaz.object({}).strict() (void input) getSiteSettingsOutputSchemasiteSettingsSchema 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.ts next 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.ts has a test injecting a malformed page repository mock asserting .rejects.toBeInstanceOf(ZodError). get-site-settings.use-case.test.ts uses an inline malformed repository mock (e.g., { siteName: "" } failing min(1)) to assert ZodError.
  • R26 (router error mapping): router.test.ts asserts BAD_REQUEST on empty input for pageBySlug; confirms undefined return for missing slug (use case does not throw PageNotFoundError today).
  • 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-ui directly; only @repo/core-shared

    Note: @repo/core-trpc and @repo/core-ui are optional packages scaffolded via pnpm turbo gen core-package trpc / ui. If not present, these constraints still apply to any future installation.

  • 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, ./ui subpath, error middleware