Files
atomic-design-poc/docs/project/readable-codebase/RD-38-wizard-container-readability.md
T
ehoandClaude Opus 5 fc2a3c348b refactor: one member order for the 3 wizard containers (RD-38)
RD-22 and RD-23 brought the wizard containers under the 250-line budget, so
`max-lines` reports nothing. The files still read badly. Line count was never
the problem.

Fix three things in all three containers:

1. The member order was scrambled, and it differed per file. `registratie`
   declared `draftSync` in the middle of a run of `computed`s; `herregistratie`
   read `this.stepLabels.length` seven lines before `stepLabels` existed; the
   three files put the copy arrays in three different places. All three now use
   one nine-section order, so they compare side by side.
2. Pure logic sat in the container. Extract `digitalDocumentIds` into
   `upload.machine.ts` — the "digital and finished uploading" filter was
   written out four times, and it removes a `documentId!` assertion from both
   containers. Extract `diplomaMsg` into a sibling of the step files.
3. Comments carried archaeology. Drop the three RD-05 references and keep the
   rule. Drop "replaces sessionStorage" and the note about focus management that
   moved to the shell. Fix `intake`'s class comment, which claimed answers
   persist to sessionStorage and was contradicted 30 lines below.

`phase` deliberately stays in all three: it cannot live in `domain/`, and three
siblings plus three specs is a worse trade than 17 readable lines. The store ⇄
`draftSync` cycle also stays — both callbacks are deferred, so it is safe, and
one comment now names it.

No behaviour change. Member lists and every `private`/`protected`/`readonly`
modifier are unchanged, which the showcase depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:16:23 +02:00

5.8 KiB

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 computeds.
  • 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 // --- <name> --- 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.