docs: repoint fp-tea paths at the monorepo tree (RD-32)
The learning guide fp-tea-atomic-design.md still cited the pre-monorepo tree. Every path started with src/app/, which no directory has used since WP-67 split the app into apps/ssp and libs/shared. Rewrite the 11 stale paths per the ticket's mapping rules: a context path becomes apps/ssp/src/app/<context>/..., a shared path becomes libs/shared/src/.... Fix the broken anchor at Part 5b to match the current ARCHITECTURE.md §1 heading. Fix the "Where" guidance in Recipe A to name the atomic layer folder under libs/shared/src/ui/. The teaching prose is unchanged; only addresses moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, Uren> {
|
||||
@@ -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<Model, Msg> {
|
||||
@@ -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/<atoms|molecules|organisms>/` 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<AdresValue>();
|
||||
errors = input<AdresErrors>({});
|
||||
@@ -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[] {
|
||||
|
||||
Reference in New Issue
Block a user