docs: teach overzicht, max-lines and the step contract (RD-33)
CLAUDE.md did not name the overzicht context that RD-03 created, and it did not name the max-lines budget that RD-02 enforces. An agent that follows it writes a long page into the wrong context, and meets a red build with no warning. CLAUDE.md now names the context, the @overzicht/* alias, the overzicht -> registratie arrow, the 250-line budget and its glob, and the step-component contract. layers.mdx gains the same arrow. atomic-design.mdx credited eslint.config.mjs with the layer rules. dependency-cruiser enforces them. Each tool is now named for what it does. The page also gains the layer-folder table and the step contract. Four paths were pre-monorepo: three example paths in the ui-component skill, and two citations of libs/shared/src/ui/async, which RD-27 moved to libs/shared/src/ui/molecules/async. npm run ci --full passes: 67 and 45 storybook suites, 306 axe tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -81,10 +81,11 @@ session protocol is the worked example of this in practice.
|
||||
|
||||
`apps/<app>/src/app/<context>/<layer>/` for an app-local context; `libs/<lib>/src/<layer>/`
|
||||
for a cross-app library (WP-67). Two apps today: `apps/ssp` (Zorgverlener self-service —
|
||||
contexts `auth`, `registratie`, `herregistratie`, `brief` (letter-composition teaching
|
||||
slice), `showcase` (teaching page, not a feature; **sanctioned** to read every context in
|
||||
its own app — nothing imports it)) and `apps/behandelportal` (Behandelaar backoffice,
|
||||
ADR-0002 — contexts `auth`, `behandeling`). Two cross-app libraries: `libs/shared` (the
|
||||
contexts `auth`, `overzicht` (the portal home; composes `registratie`'s dashboard sections
|
||||
plus its own cross-context nav sections), `registratie`, `herregistratie`, `brief`
|
||||
(letter-composition teaching slice), `showcase` (teaching page, not a feature; **sanctioned**
|
||||
to read every context in its own app — nothing imports it)) and `apps/behandelportal`
|
||||
(Behandelaar backoffice, ADR-0002 — contexts `auth`, `behandeling`). Two cross-app libraries: `libs/shared` (the
|
||||
design system + kernel + generated API client — no business logic) and `libs/beheer` (the
|
||||
admin/stamdata context, identical for both apps today — WP-67 folded a silently-diverging
|
||||
duplicate copy back into one). `auth` is deliberately **not** shared even though today it's
|
||||
@@ -106,10 +107,11 @@ depend on `libs/beheer` either — it stays the base). `ui`/`layout` never impor
|
||||
lint-enforced (per app, since each app is cruised against its own tsconfig — WP-67's
|
||||
`.dependency-cruiser.base.js` + one thin `.dependency-cruiser.<app>.js` per app). An app
|
||||
may not import the other app's source directly. Cross-context only
|
||||
`herregistratie → registratie → libs/shared|beheer`, `auth → libs/shared|beheer`,
|
||||
`brief → libs/shared|beheer` (ssp); `behandeling → libs/shared|beheer`, `auth →
|
||||
libs/shared|beheer` (behandelportal). Imports use aliases as direction statements:
|
||||
`@shared/* @beheer/* @auth/* @registratie/* @herregistratie/* @brief/*` (ssp) —
|
||||
`overzicht → registratie → libs/shared|beheer`, `herregistratie → registratie →
|
||||
libs/shared|beheer`, `auth → libs/shared|beheer`, `brief → libs/shared|beheer` (ssp);
|
||||
`behandeling → libs/shared|beheer`, `auth → libs/shared|beheer` (behandelportal). Imports use
|
||||
aliases as direction statements:
|
||||
`@shared/* @beheer/* @auth/* @overzicht/* @registratie/* @herregistratie/* @brief/*` (ssp) —
|
||||
`@shared/* @beheer/* @auth/* @behandeling/*` (behandelportal); each app's own
|
||||
`tsconfig.json` declares its full map (the root `tsconfig.json` intentionally has no
|
||||
`paths` — see its comment). `domain/` imports nothing from Angular.
|
||||
@@ -129,6 +131,18 @@ the design system does the visuals. (Where CIBG lacks a class — e.g. `skeleton
|
||||
`// CIBG-GAP EXTENSION:` marker; see ADR-0003. `alert` is **not** such a case: it wraps the
|
||||
vendored `.feedback feedback-*` classes.)
|
||||
|
||||
**The step-component contract.** A wizard step follows the same rule as
|
||||
`address-fields.component.ts`: values in, events out, no internal state. Three clauses:
|
||||
|
||||
1. **Inputs down.** A step reads its data only from `input()`s the container passes it.
|
||||
2. **One narrow output up.** A step emits one specific event, not the container's whole
|
||||
`dispatch`.
|
||||
3. **`dispatch` is never passed down.** The container owns the Model and decides what a
|
||||
step's event means; a step never calls `dispatch` itself.
|
||||
|
||||
Corollary: a step gets **no** story of its own. The wizard's own story already mounts every
|
||||
step, because it seeds the machine.
|
||||
|
||||
### 3. State: make illegal states unrepresentable
|
||||
|
||||
Default reflex — **if you're about to add a second/third boolean to track state,
|
||||
@@ -136,7 +150,7 @@ model a discriminated union instead.** Three tools, all in `libs/shared/src/appl
|
||||
|
||||
- **`RemoteData<E,T>`** (`remote-data.ts`) — `Loading | Empty | Failure{error} | Success{value}`.
|
||||
Combine sources with `map`/`map2`/`andThen` (Failure > Loading > Success).
|
||||
Render it via the `<app-async>` molecule (`libs/shared/src/ui/async`) — one of four
|
||||
Render it via the `<app-async>` molecule (`libs/shared/src/ui/molecules/async`) — one of four
|
||||
templates, mutually exclusive by construction. Default loading spinner/skeleton
|
||||
is delay-gated (~250ms) so fast connections don't flash.
|
||||
- **Elm-style store** (`store.ts` → `createStore(initial, reduce)`) — all state in
|
||||
@@ -242,7 +256,7 @@ organism doesn't get its own `Organisms/` bucket).
|
||||
(`` $localize`:@@context.key:Tekst` ``). Source locale is `nl`; a second locale is a
|
||||
translation file, not a code change (the seam). Shared/English components must **not**
|
||||
hardcode Dutch — expose copy as `input()`s with localizable defaults; the domain caller
|
||||
supplies the text (see `libs/shared/src/ui/async`). Format-validation messages in
|
||||
supplies the text (see `libs/shared/src/ui/molecules/async`). Format-validation messages in
|
||||
`domain/value-objects/` stay co-located but are still `$localize`-wrapped.
|
||||
- **Forms = one idiom.** Any form with validation or submission uses a `*.machine.ts`
|
||||
(Model/Msg/reduce) + value objects + a `submit-*` command returning `Result` — the
|
||||
@@ -272,7 +286,10 @@ organism doesn't get its own `Organisms/` bucket).
|
||||
- Prettier; `.editorconfig`. tsconfig: `noImplicitReturns`,
|
||||
`noPropertyAccessFromIndexSignature`, `noFallthroughCasesInSwitch`, `isolatedModules`.
|
||||
- **Enforced, not just hoped-for:** `npm run lint` (`eslint.config.mjs`, scoped to
|
||||
`{apps,libs}/**`) fails the build on `any`; `npm run dep:check`
|
||||
`{apps,libs}/**`) fails the build on `any`; the same config's `max-lines` rule caps every
|
||||
`{apps,libs}/**/*.{page,component,section,step}.ts` file at 250 lines
|
||||
(`skipBlankLines: true`, `skipComments: true`). 250 is reachable, not a style-guide
|
||||
default — the dashboard page lands at 42 lines. `npm run dep:check`
|
||||
(`.dependency-cruiser.base.js` + one `.dependency-cruiser.<app>.js` per app, WP-67) fails
|
||||
on illegal imports — `domain/` importing Angular, a context importing "upward" (the
|
||||
`herregistratie → registratie → shared`, `auth → shared` direction), an app importing the
|
||||
|
||||
Reference in New Issue
Block a user