diff --git a/docs/project/readable-codebase/RD-32-fp-tea-paths.md b/docs/project/readable-codebase/RD-32-fp-tea-paths.md new file mode 100644 index 0000000..09b6f22 --- /dev/null +++ b/docs/project/readable-codebase/RD-32-fp-tea-paths.md @@ -0,0 +1,106 @@ +# RD-32 — `fp-tea-atomic-design.md` cites the pre-monorepo tree + +Status: done +Phase: 5 — fix the docs that describe this flow + +## Why + +`docs/reference/fp-tea-atomic-design.md` is the learning guide. CLAUDE.md names it as one of +the three documents that hold the _why_. It still addresses the tree from before the monorepo +(WP-67): every path starts `src/app/`, which no directory has done since that work landed. + +A reader learning the codebase from this guide cannot open a single file it names. The prose is +correct; only the addresses are wrong. + +## Read first + +- `docs/project/readable-codebase/PLAN.md`, phase 5 item 3 (line 833). +- `docs/reference/fp-tea-atomic-design.md` — the whole file. + +## Decisions (pre-made, do not relitigate) + +1. **Two mapping rules cover all 11 paths.** A context path becomes + `apps/ssp/src/app//…`. A `shared` path becomes `libs/shared/src/…` — note that + `app/shared/` collapses to `libs/shared/src/`, it does not become `libs/shared/src/app/`. +2. **Follow the file's own precedent.** Line 338 already reads + `libs/shared/src/application/store.ts`. Line 223 names the same file the old way. Make 223 + match 338, not the reverse. +3. **Fix the broken anchor at line 435.** It reads + `#1-the-big-picture-three-contexts-four-layers`. The heading is now "1. The big picture: two + apps, cross-app libraries, "contexts", "layers"", so the anchor is + `#1-the-big-picture-two-apps-cross-app-libraries-contexts-layers`. +4. **Correct line 513's "Where" guidance.** It says `shared/ui/`. After RD-27 a shared block + lands in `libs/shared/src/ui//`. Name the layer folder; the whole + point of RD-27 is that the folder is the layer. +5. **Change addresses only.** Do not rewrite the teaching. This guide's prose survived the + refactor because it teaches ideas, not file layout. + +## The facts, measured against the current tree + +All 11 occurrences of `src/app/`, and where each file lives now: + +| Line | Cited | Actual | +| -------- | --------------------------------------------------------- | ------------------------------------------------------------------ | +| 72 | `src/app/registratie/domain/value-objects/uren.ts` | `apps/ssp/src/app/registratie/domain/value-objects/uren.ts` | +| 98, 255 | `src/app/herregistratie/domain/herregistratie.machine.ts` | `apps/ssp/src/app/herregistratie/domain/herregistratie.machine.ts` | +| 223 | `src/app/shared/application/store.ts` | `libs/shared/src/application/store.ts` | +| 299 | `src/app/shared/kernel/fp.ts` | `libs/shared/src/kernel/fp.ts` | +| 305 | `src/app/herregistratie/ui/herregistratie-wizard/…` | `apps/ssp/src/app/herregistratie/ui/herregistratie-wizard/…` | +| 381 | `src/app/showcase/concepts.page.ts` | `apps/ssp/src/app/showcase/concepts.page.ts` | +| 388 | `src/app/shared/infrastructure/scenario.ts` | `libs/shared/src/infrastructure/scenario.ts` | +| 414, 523 | `src/app/registratie/ui/address-fields/…` | `apps/ssp/src/app/registratie/ui/address-fields/…` | +| 588 | `src/app/herregistratie/domain/intake.machine.ts` | `apps/ssp/src/app/herregistratie/domain/intake.machine.ts` | + +Every target exists. This is a rename, not a hunt. + +**One correction to `PLAN.md`.** It says one path points at a deleted file, +`submit-herregistratie.ts`. That reference is no longer in the document — +`grep -n submit-herregistratie docs/reference/fp-tea-atomic-design.md` returns nothing. Do not +go looking for it. Record the correction in the ticket; leave `PLAN.md` itself alone, because +it is a design record of what was true when it was written. + +Line 523 sits inside a code comment, not prose. Update it anyway; a stale path in an example is +read as a real path. + +## Files + +- `docs/reference/fp-tea-atomic-design.md`. + +## Steps + +1. Rewrite the 11 paths per the table. +2. Fix the anchor at line 435. +3. Fix the "Where" guidance at line 513. +4. `grep -n "src/app/" docs/reference/fp-tea-atomic-design.md` — every remaining hit must start + `apps/ssp/src/app/`. +5. `npm run ci`. + +## Acceptance criteria + +- [x] No path in the file starts with a bare `src/app/`. +- [x] Every cited path resolves. Verify each with `ls`. +- [x] The §1 anchor matches the current heading. +- [x] `npm run ci` is green. + +## Verification + +1. `npm run ci` — the format check covers the file. +2. `grep -oE '\`[^\`]_src/[^\`]_\.ts\`' docs/reference/fp-tea-atomic-design.md | tr -d '\`' | + sort -u | xargs ls` — every path must resolve, with no error. +3. Open `ARCHITECTURE.md` and confirm the §1 heading text still produces the anchor you wrote. + The slug is the heading in lower case, with punctuation removed and spaces turned into + hyphens. + +## Out of scope + +- `ARCHITECTURE.md`. RD-31 owns it. +- CLAUDE.md and `atomic-design.mdx`. RD-33 owns them. +- The teaching content, the diagrams, and the code examples. + +## Risks + +1. **`app/shared/` is not `libs/shared/src/app/`.** The `app` segment disappears for the + library. Getting this wrong produces a path that looks right and resolves to nothing. +2. **The anchor is easy to half-fix.** GitHub drops the quotation marks around "contexts" and + "layers" and the colon, and turns each space into one hyphen. Verify by clicking, not by + reasoning. diff --git a/docs/project/readable-codebase/README.md b/docs/project/readable-codebase/README.md index 1889897..ac8efac 100644 --- a/docs/project/readable-codebase/README.md +++ b/docs/project/readable-codebase/README.md @@ -126,7 +126,7 @@ two. Note that RD-15 exists because 22 abandoned agent worktrees are still on di | RD-29 | The 3 atomic-ladder rules in dependency-cruiser | 27 | | done | | RD-30 | Archive the finished backlogs (16,300 lines) + an archive README | 01 | | done | | RD-31 | `ARCHITECTURE.md` section 6a: symbols not lines, 2 dead paths, new names | 03, 08, 16 | | done | -| RD-32 | `fp-tea-atomic-design.md`: 11 broken paths + the broken anchor | 27 | | todo | +| RD-32 | `fp-tea-atomic-design.md`: 11 broken paths + the broken anchor | 27 | | done | | RD-33 | CLAUDE.md + `atomic-design.mdx` + the `ui-component` skill | 03, 27, 29 | yes | todo | | RD-34 | _(optional)_ `NO_SUBORGS`/`NO_TABLES` become `RemoteData.Empty` | 11 | | todo | | RD-35 | _(optional, last, alone)_ upload `type:` discriminant to `tag:` | 27 | | todo | diff --git a/docs/reference/fp-tea-atomic-design.md b/docs/reference/fp-tea-atomic-design.md index 610e355..13214e5 100644 --- a/docs/reference/fp-tea-atomic-design.md +++ b/docs/reference/fp-tea-atomic-design.md @@ -69,7 +69,7 @@ add a b = a + b ``` In this app, the parsers and reducers are pure. For example -(`src/app/registratie/domain/value-objects/uren.ts`): +(`apps/ssp/src/app/registratie/domain/value-objects/uren.ts`): ```ts export function parseUren(raw: string): Result { @@ -95,7 +95,7 @@ copies the old fields and overrides one. ``` This app's reducers always return a fresh object — e.g. -`src/app/herregistratie/domain/herregistratie.machine.ts`: +`apps/ssp/src/app/herregistratie/domain/herregistratie.machine.ts`: ```ts export function setField(s: WizardState, key: keyof Draft, value: string): WizardState { @@ -220,7 +220,7 @@ store. Cross-page state that _must_ be shared lives in one root singleton ### 4a. The store — TEA's runtime in ~10 lines -`src/app/shared/application/store.ts`: +`libs/shared/src/application/store.ts`: ```ts export interface Store { @@ -252,7 +252,7 @@ to change it — it runs the pure `update` and `set`s the new value. ### 4b. Model + Msg + reduce Mapping the four TEA pieces to real code, using the herregistratie wizard (the smallest -machine) as the example — `src/app/herregistratie/domain/herregistratie.machine.ts`: +machine) as the example — `apps/ssp/src/app/herregistratie/domain/herregistratie.machine.ts`: - **Model** → `WizardState` (the discriminated union from §2d). - **Msg** → `WizardMsg`, every event as one union: @@ -296,13 +296,13 @@ export function reduce(s: WizardState, m: WizardMsg): WizardState { } ``` -`assertNever` (`src/app/shared/kernel/fp.ts`) makes the switch **exhaustive**: add a new +`assertNever` (`libs/shared/src/kernel/fp.ts`) makes the switch **exhaustive**: add a new `Msg` variant and forget to handle it, and the build fails. (`intake.machine.ts` and `registratie-wizard.machine.ts` have larger unions, same exact shape.) ### 4c. view → template + `computed()` + `dispatch` -The container component (`src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts`) +The container component (`apps/ssp/src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts`) creates the store and derives view values with `computed()`: ```ts @@ -378,14 +378,14 @@ doesn't loop on its own write — see the BRP prefill and policy-threshold effec Each wizard exposes `state` as a **read-only signal**, deliberately public so the teaching page can highlight the live state. See it on the in-app showcase -(`src/app/showcase/concepts.page.ts`, route `/concepts`): section 4 lights up the +(`apps/ssp/src/app/showcase/concepts.page.ts`, route `/concepts`): section 4 lights up the current `WizardState` among `Editing → Submitting → Submitted/Failed` as you drive the form, and section 5 shows the intake steps re-deriving as you type. > **Discrepancy with the PRD — open question.** The PRD refers to a dedicated "state > debug view" / inspector. **No such feature exists** in the code today. What exists is > the `/concepts` showcase (live state highlight) and the `?scenario=slow|loading|empty|error` -> interceptor (`src/app/shared/infrastructure/scenario.ts`) for exercising async states. +> interceptor (`libs/shared/src/infrastructure/scenario.ts`) for exercising async states. > A JSON state inspector _would be trivial here_ — single one-way state means you could > render `JSON.stringify(state())` in a panel and watch every transition — precisely > because of everything in Part 6. Treat building one as a future task, not documented @@ -411,7 +411,7 @@ In this codebase the form **atoms** (`text-input`, `radio-group`) are thin wrapp the design system. They take config via `input()` and — because they implement Angular's `ControlValueAccessor` — emit changes through `[ngModel]` / `(ngModelChange)`. The `form-field` **molecule** composes a label + projected control + error. The -`address-fields` **organism** (`src/app/registratie/ui/address-fields/address-fields.component.ts`) +`address-fields` **organism** (`apps/ssp/src/app/registratie/ui/address-fields/address-fields.component.ts`) composes three `form-field`s and emits with `output()`: ```ts @@ -432,7 +432,7 @@ Molecules compose atoms; organisms compose molecules — exactly like composing functions, where the composite is still pure. `address-fields` is pure because the `form-field` and `text-input` it's built from are pure. Each atomic level only uses the level(s) below it (see the hierarchy diagram in -[`ARCHITECTURE.md` §1](./architecture/ARCHITECTURE.md#1-the-big-picture-three-contexts-four-layers)). +[`ARCHITECTURE.md` §1](./architecture/ARCHITECTURE.md#1-the-big-picture-two-apps-cross-app-libraries-contexts-layers)). ### 5c. Pages / containers are the TEA runtime (the shell) @@ -510,9 +510,9 @@ Each recipe follows the existing pattern and naming, and ends with the same remi **When:** you genuinely need a new building block (not a one-off; reuse must earn it — see [CLAUDE.md §2](../../CLAUDE.md)). -**Where:** `shared/ui/` if generic; a context's `ui/` if domain-specific. Pick the level -by composition: composes nothing → **atom**; composes atoms → **molecule**; composes -molecules into a domain block → **organism**. +**Where:** `libs/shared/src/ui//` if generic; a context's `ui/` +if domain-specific. Pick the level by composition: composes nothing → **atom**; composes +atoms → **molecule**; composes molecules into a domain block → **organism**. **Steps:** build it **pure/presentational** — `input()`s for data/config, `output()`s for events, `computed()` for derived display; **no inject, no state, no effects**. Theme @@ -520,7 +520,7 @@ only with design tokens (no hardcoded hex — CI checks via `npm run check:token Add a co-located `*.stories.ts` titled `Layer/Name`. ```ts -// shape — see src/app/registratie/ui/address-fields/address-fields.component.ts +// shape — see apps/ssp/src/app/registratie/ui/address-fields/address-fields.component.ts export class AddressFieldsComponent { value = input.required(); errors = input({}); @@ -585,7 +585,7 @@ _This is the same loop, again — the rule is just another pure function._ **Steps:** compose A–C. Model the step's state in the Model; **derive** the visible steps rather than storing "next" — copy `visibleSteps(answers)` from -`src/app/herregistratie/domain/intake.machine.ts`: +`apps/ssp/src/app/herregistratie/domain/intake.machine.ts`: ```ts export function visibleSteps(a: Answers): StepId[] {