# RD-38 — One member order for the three wizard containers Status: done Source: user report — "the registratie-wizard component still doesn't look readable" ## Why RD-22 and RD-23 split the intake and registratie wizards into step components. That brought every container under the 250-line budget, so `max-lines` reports nothing. The user read the result and still called it unreadable. Line count was never the problem. Three problems remained, and the user named all three: 1. **The member order is scrambled, and it differs per file.** You bounce up and down to follow one thread, and the three containers cannot be compared side by side. 2. **Pure logic sits in the container**, where it has no test. 3. **Comments carry archaeology** — ticket numbers, history, and one comment that describes code which is no longer in the file. RD-02 measures a file. This ticket is about what a file reads like at a fixed size. ## Evidence, before the change - `registratie` declared `stepLabels`/`stepTitles` between the `seed` input and `state`/`dispatch`; `intake` declared them near the bottom; `herregistratie` declared them after `draftSync`. Three files, three orders for the same three roles. - `registratie` declared `draftSync` in the middle of a run of `computed`s. - `herregistratie`'s `snapshot` read `this.stepLabels.length` seven lines before `stepLabels` was declared. - `intake`'s class comment claimed answers persist to `sessionStorage`. Thirty lines below, another comment said draft-sync replaced it. The class comment was false. - All three cited "RD-05's tag-transition rule" in an identical sentence. ## Decisions (pre-made, don't relitigate) 1. **One nine-section member order, identical in all three files.** Injected stores → inputs → the store (`createStore`, `state`, `dispatch`) → static copy → state projections → controllers → shell wiring → event handlers → constructor and `restart`. Each section carries a `// --- ---` header. Section 7's header already existed in all three files, so it is reused byte for byte. 2. **The store ⇄ `draftSync` cycle stays.** The store's effect map calls `this.draftSync`, and `draftSync`'s `snapshot` calls `this.state()`. Both are arrow functions that run after construction, so there is no temporal-dead-zone hazard; the cycle cannot be removed by reordering. One comment at the effect map names it. 3. **Extract `digitalDocumentIds` into `libs/shared/src/domain/upload.machine.ts`.** The "digital and finished uploading" filter was written out four times, in two shapes: mapped to ids in the two container snapshots, and as `.some(...)` inside two machines' `hasProgress`. One function serves all four, and it removes the `r.documentId!` non-null assertion from both containers. It joins an existing file beside `deliveryRefs`, and its spec joins the existing `deliveryRefs` block. 4. **Extract `diplomaMsg` into a new sibling of the step files.** `onDiplomaKeuze` was the fattest member in the three containers and had no test. It is pure: a `DuoLookupDto` and a selected id in, a machine message or `null` out. `HANDMATIG` does not move — the new file is its sibling and imports it exactly as the container did. 5. **`phase` stays in all three containers.** It maps this machine's tags onto the shell's `WizardPhase` vocabulary and composes a `$localize` failure message. That is a container's job. It cannot move to `domain/`: `WizardPhase` comes from an Angular component, and `domain/` points inward only. Moving it to a per-wizard sibling would add three files and three specs to relocate 17 readable lines each. Decision 1 already fixes what was wrong with it — it belongs in the shell-wiring section, and only two of three files had it there. 6. **The four `err*` computeds in `herregistratie` stay.** They are one-line projections feeding four template bindings. Folding them into one `errors()` would edit the template, which is behaviour-shaped work this ticket does not do. 7. **Comment policy, four rules.** Delete ticket references and keep the sentence (RD-18 decision 1 stripped `WP-`/`RB-` for the same reason; `RD-` is the same debt). Delete history — a comment says what the code does now. Delete a comment that describes code which is not in the file. Keep a comment that states a current why: the `untracked` loop-avoidance notes, the `IntakePolicy.RejectIncompleteScholing` seam pointer, and the `demo-*` preview note all stay. ## Traps - **`messages.en.xlf`.** Every `$localize` id in these files is translated. The reorder moves the copy arrays; it must not touch an id or its source text. - **`enabled: () => this.seed() === initial`** is a reference-identity check against the exported `initial` singleton. Never clone or rebuild it — breaking the identity turns draft-sync on inside Storybook and the tests. - **Public members are load-bearing.** `showcase/vragenlijst.section.ts` reads `IntakeWizardComponent.steps`; `showcase/form-machine.section.ts` reads `HerregistratieWizardComponent.state`. No `private`/`protected`/`readonly` modifier changes. - **`intake.machine.ts` carries `#region showcase:steps` markers** that feed `gen:snippets`. This ticket does not touch them. - **New specs change a generated document.** `scripts/ci-local.sh` regenerates `libs/shared/docs/behaviour-spec.mdx` and diffs it. Four new `it()` titles land there, so the regenerated file belongs in the same commit. ## Acceptance - `npm run ci` passes. - Each container's member list and every modifier are unchanged: `diff <(git show HEAD:$f | grep -oE '^ (private|protected|readonly)? ?[a-zA-Z]+ *[=(]' | sort) <(...)` reports no difference for all three files. - `git grep -nE "\bRD-[0-9]+" -- 'apps/ssp/src/app/*/ui/*wizard*'` returns nothing. - `git grep -n "sessionStorage" -- 'apps/ssp/src/app/herregistratie/ui'` returns nothing.