website refactor

This commit is contained in:
2026-01-12 01:01:49 +01:00
parent 5ca6023a5a
commit fefd8d1cd6
294 changed files with 4628 additions and 4991 deletions

View File

@@ -4,6 +4,12 @@ This document defines architecture guardrails that must be enforced via tests +
Authoritative contract: [`WEBSITE_CONTRACT.md`](docs/architecture/website/WEBSITE_CONTRACT.md:1).
Purpose:
- Encode the architecture as *enforceable* rules.
- Remove ambiguity and prevent drift.
- Make it impossible for `page.tsx` and Templates to accumulate business logic.
## 1) RSC boundary guardrails
Fail CI if any `apps/website/app/**/page.tsx`:
@@ -13,6 +19,29 @@ Fail CI if any `apps/website/app/**/page.tsx`:
- calls `Intl.*` or `toLocale*`
- performs sorting/filtering (`sort`, `filter`, `reduce`) beyond trivial null checks
Also fail CI if any `apps/website/app/**/page.tsx`:
- imports from `apps/website/lib/display-objects/**`
- imports from `apps/website/lib/services/**` **that are not explicitly server-safe**
- imports from `apps/website/lib/di/**` (server DI ban)
- defines local helper functions other than trivial `assert*`/`invariant*` guards
- contains `new SomeClass()` (object graph construction belongs in PageQueries)
- contains any of these calls (directly or indirectly):
- `ContainerManager.getInstance()`
- `ContainerManager.getContainer()`
Filename rules (route module clarity):
- Only `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `actions.ts` are allowed under `apps/website/app/**`.
- Fail CI if any file under `apps/website/app/**` matches:
- `*Template.tsx`
- `*ViewModel.ts`
- `*Presenter.ts`
Allowed exception:
- `apps/website/app/<route>/actions.ts` may call services and API clients (server-side), but it must not import ViewModels or Presenters.
## 2) Template purity guardrails
Fail CI if any `apps/website/templates/**`:
@@ -22,12 +51,198 @@ Fail CI if any `apps/website/templates/**`:
- imports from `apps/website/lib/display-objects/*`
- calls `Intl.*` or `toLocale*`
Also fail CI if any Template:
- contains `useMemo`, `useEffect`, `useState`, `useReducer` (state belongs in `*PageClient.tsx` and components)
- calls `.filter`, `.sort`, `.reduce` (derived computations must happen before ViewData reaches Templates)
- imports from:
- `apps/website/lib/page-queries/**`
- `apps/website/lib/services/**`
- `apps/website/lib/api/**`
- `apps/website/lib/di/**`
- `apps/website/lib/contracts/**`
Templates accept ViewData only.
Filename + signature rules:
- Template filenames must end with `Template.tsx`.
- The first parameter type of a Template component must be `*ViewData` (or an object containing only `*ViewData` shapes).
- Templates must not export helper functions.
## 3) Display Object guardrails
Fail CI if any `apps/website/lib/display-objects/**`:
- calls `Intl.*` or `toLocale*`
Also fail CI if any Display Object:
- imports from `apps/website/lib/api/**`, `apps/website/lib/services/**`, or `apps/website/lib/page-queries/**` (no IO)
- imports from `apps/website/lib/view-models/**` (direction must be Presenter/ViewModel -> DisplayObject, not vice versa)
- exports non-class members (Display Objects must be class-based)
Display Objects must be deterministic.
## 4) Page Query guardrails (server composition only)
Fail CI if any `apps/website/lib/page-queries/**`:
- imports from `apps/website/lib/view-models/**`
- imports from `apps/website/lib/display-objects/**`
- imports from `apps/website/lib/di/**` or references `ContainerManager`
- calls `Intl.*` or `toLocale*`
- calls `.sort`, `.filter`, `.reduce` (sorting/filtering belongs in API if canonical; otherwise client ViewModel)
- returns `null` (must return `PageQueryResult` union)
Filename rules:
- PageQueries must be named `*PageQuery.ts`.
- Page DTO types must be named `*PageDto` and live next to their PageQuery.
## 5) Services guardrails (DTO-only, server-safe)
Fail CI if any `apps/website/lib/services/**`:
- imports from `apps/website/lib/view-models/**` or `apps/website/templates/**`
- imports from `apps/website/lib/display-objects/**`
- stores state on `this` other than injected dependencies (services must be stateless)
- uses blockers (blockers are client-only UX helpers)
Naming rules:
- Service methods returning API responses should use variable name `apiDto`.
- Service methods returning Page DTO should use variable name `pageDto`.
## 6) Client-only guardrails (ViewModels, Presenters)
Fail CI if any file under `apps/website/lib/view-models/**`:
- lacks `'use client'` at top-level when it exports a ViewModel class intended for instantiation
- imports from `apps/website/lib/page-queries/**` or `apps/website/app/**` (dependency direction violation)
Fail CI if any Presenter/ViewModel uses:
- HTTP calls (`fetch`, axios, API clients)
## 7) Write boundary guardrails (Server Actions only)
Fail CI if any client module (`'use client'` file or `apps/website/components/**`) performs HTTP writes:
- `fetch` with method `POST|PUT|PATCH|DELETE`
Fail CI if any server action (`apps/website/app/**/actions.ts`):
- imports from `apps/website/lib/view-models/**` or `apps/website/templates/**`
- returns ViewModels (must return primitives / redirect / revalidate)
## 8) Model taxonomy guardrails (naming + type suffixes)
Problem being prevented:
- Calling everything “dto” collapses API Transport DTO, Page DTO, and ViewData.
- This causes wrong-layer dependencies and makes reviews error-prone.
Fail CI if any file under `apps/website/**` contains a variable named exactly:
- `dto`
Allowed variable names (pick the right one):
- `apiDto` (API Transport DTO from OpenAPI / backend HTTP)
- `pageDto` (Page DTO assembled by PageQueries)
- `viewData` (Template input)
- `commandDto` (write intent)
Type naming rules (CI should fail if violated):
1. Any PageQuery output type MUST end with `PageDto`.
- Applies to types defined in `apps/website/lib/page-queries/**`.
2. Any Template prop type MUST end with `ViewData`.
- Applies to types used by `apps/website/templates/**`.
3. API Transport DTO types may end with `DTO` (existing generated convention) or `ApiDto` (preferred for hand-written).
Module boundary reinforcement:
- `apps/website/templates/**` MUST NOT import API Transport DTO types directly.
- Prefer: PageQuery emits `pageDto` → Presenter emits `viewData`.
## 9) Contracts enforcement (mandatory interfaces)
Purpose:
- Guardrails that rely on regex alone will always have loopholes.
- Contracts make the compiler enforce architecture: code must implement the right shapes.
These contracts live under:
- `apps/website/lib/contracts/**`
### 9.1 Required contracts
Fail CI if any of these are missing:
1. PageQuery contract: `apps/website/lib/contracts/page-queries/PageQuery.ts`
- Requires `execute(...) -> PageQueryResult<PageDto>`.
2. Service contract(s): `apps/website/lib/contracts/services/*`
- Services return `ApiDto`/`PageDto` only.
- No ViewModels.
3. Presenter contract: `apps/website/lib/contracts/presenters/Presenter.ts`
- `present(input) -> output` (pure, deterministic).
4. ViewModel base: `apps/website/lib/contracts/view-models/ViewModel.ts`
- ViewModels are client-only.
- Must not expose a method that returns Page DTO or API DTO.
### 9.2 Enforcement rules
Fail CI if:
- Any file under `apps/website/lib/page-queries/**` defines a `class *PageQuery` that does NOT implement `PageQuery`.
- Any file under `apps/website/lib/services/**` defines a `class *Service` that does NOT implement a Service contract.
- Any file under `apps/website/lib/view-models/**` defines a `*Presenter` that does NOT implement `Presenter`.
Additionally:
- Fail if a PageQuery returns a shape that is not `PageQueryResult`.
- Fail if a service method returns a `*ViewModel` type.
Note:
- Enforcement can be implemented as a boundary test that parses TypeScript files (or a regex-based approximation as a first step), but the source of truth is: contracts must exist and be implemented.
## 10) Generated DTO isolation (OpenAPI transport types do not reach UI)
Purpose:
- Generated OpenAPI DTOs are transport contracts.
- UI must not depend on transport contracts directly.
- Prevents “DTO soup” and forces the PageDto/ViewData boundary.
Fail CI if any of these import from `apps/website/lib/types/generated/**`:
- `apps/website/templates/**`
- `apps/website/components/**`
- `apps/website/hooks/**` and `apps/website/lib/hooks/**`
Fail CI if any Template imports from `apps/website/lib/types/**`.
Allowed locations for generated DTO imports:
- `apps/website/lib/api/**` (API clients)
- `apps/website/lib/services/**` (transport orchestration)
- `apps/website/lib/page-queries/**` (Page DTO assembly)
Enforced flow:
- Generated `*DTO` -> `apiDto` (API client/service)
- `apiDto` -> `pageDto` (PageQuery)
- `pageDto` -> `viewData` (Presenter)
Rationale:
- If the API contract changes, the blast radius stays in infrastructure + server composition, not in Templates.