refactor: move the portal home into its own overzicht context (RD-03)

The dashboard page lived inside registratie, a context with no permitted
outbound edges. Two of its sections do cross-context navigation: action
links to herregistratie/intake/brief/concepts, and an admin links section
that imported ADMIN_LINKS directly from the app shell.

Move the page and these two navigation sections into a new overzicht
context. Declare overzicht: ['registratie'] as the one sanctioned edge, so
the four data sections that stay in registratie/ui/dashboard/ remain
reachable. The route stays at /dashboard: it is a user-visible URL and four
e2e specs assert it, so only the import path and class name change.

beheer-links.section.ts now injects HEADER_ADMIN_LINKS, the same token the
site header uses, instead of importing the app shell's nav config directly.

Used npm run gen:context and corrected its three known-wrong outputs: the
overzicht: [] edge, the scaffolded overzicht.page.ts (the real page is the
moved dashboard.page.ts), and the scaffolded path: 'overzicht' route.

Verified the boundary is enforced, not decorative: a temporary import of a
herregistratie file into overzicht.page.ts makes dep:check fail, as
expected, then reverted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-04 16:26:11 +02:00
co-authored by Claude Sonnet 5
parent b9d572cfdc
commit b9fd411283
8 changed files with 187 additions and 13 deletions
@@ -0,0 +1,168 @@
# RD-03 — Move the portal home into its own `overzicht` context
Status: done
Source: PLAN.md Phase 0
## Why
The dashboard is the portal home, but it lives inside `registratie` — a context that
`.dependency-cruiser.ssp.js` declares as `registratie: []`, permitted to import no other
context. Three concrete symptoms:
- Its six sections span four concerns: registratie data (3), aanvragen (1), cross-context
action links to `/herregistratie` `/intake` `/brief` `/concepts` (1), and admin links to
`/beheer/*` (1).
- The cross-context coupling is **invisible to the linter**, because
`wat-wilt-u-doen.section.ts` links by route _string_. `npm run dep:check` passes and gives
false assurance on exactly this file.
- `beheer-links.section.ts:6` imports `ADMIN_LINKS` from `../../../shell/nav.config` — a
context reaching into the app frame.
Splitting the dashboard into sections is what makes this fixable at all. That is the
refactor's first real payoff.
## Read first
- `apps/ssp/src/app/registratie/ui/dashboard.page.ts` (42 lines, pure composition)
- `apps/ssp/src/app/registratie/ui/dashboard/` — the six sections
- `.dependency-cruiser.ssp.js` — the `CONTEXT_ALLOWED` map (5 lines)
- `libs/shared/src/layout/site-header/nav-config.ts` — the `HEADER_ADMIN_LINKS` token
- `libs/shared/src/layout/site-header/site-header.component.ts:99` — how to inject it
- `apps/ssp/src/app/app.config.ts:72` — where `ADMIN_LINKS` is already provided to the token
- `plopfile.mjs`, the `context` generator
- CLAUDE.md decision 1 (DDD contexts and layers)
## Decisions (pre-made, don't relitigate)
1. **New context name: `overzicht`.** Dutch, per CLAUDE.md ("domain contexts are Dutch"), and
it matches the page's own title, "Mijn overzicht".
2. **Only the page and the two _navigation_ sections move.** These three:
- `dashboard.page.ts``overzicht/ui/overzicht.page.ts` (class `OverzichtPage`, selector
`app-overzicht-page`)
- `dashboard/wat-wilt-u-doen.section.ts``overzicht/ui/wat-wilt-u-doen.section.ts`
- `dashboard/beheer-links.section.ts``overzicht/ui/beheer-links.section.ts`
3. **The four data sections STAY in `registratie/ui/dashboard/`**`mijn-aanvragen`,
`wat-moet-ik-regelen`, `mijn-registratie`, `specialismen`. They render registratie data and
belong beside `BigProfileStore`/`AanvragenStore`. Do not move them, and do not move their
three story files.
4. **Declare `overzicht: ['registratie']`** in `.dependency-cruiser.ssp.js` — the second
sanctioned cross-feature edge, mirroring `herregistratie: ['registratie']`. The generator
inserts `overzicht: []`; you must change it.
5. **`beheer-links.section.ts` injects `HEADER_ADMIN_LINKS`**, exactly as
`site-header.component.ts:99` does. Drop the `shell/nav.config` import entirely.
`app.config.ts:72` already provides `ADMIN_LINKS` to that token, and the `AdminLink` doc
comment already says it is "Consumed by the site header's admin nav AND (per app) a
dashboard's own Beheer section". The direct import was always the anomaly.
6. **KEEP the route at `/dashboard`.** Four e2e specs assert it (`e2e/smoke.spec.ts:18`,
`e2e/brief-v2.spec.ts:33`, `e2e/error-state.spec.ts:16,18`), and it is a user-visible URL.
Only the `loadComponent` import path and the class name change. Add a one-line comment in
`app.routes.ts` saying the path stays `dashboard` deliberately while the context is
`overzicht`, so the mismatch reads as a decision and not an oversight.
7. **Use `npm run gen:context`, then correct three things.** The generator gets the folders,
the `@overzicht/*` tsconfig alias, and the dep-cruiser entry right. It gets three things
wrong for this case:
- it writes `overzicht: []` → change to `['registratie']` (decision 4)
- it scaffolds a **new** `overzicht.page.ts` from a template → delete it, the real page is
the moved `dashboard.page.ts`
- it adds a **new** `path: 'overzicht'` route → delete that block, and instead repoint the
existing `dashboard` route (decision 6)
8. **Delete the empty layer folders the generator creates.** `overzicht` has no `domain/`,
`application/`, `infrastructure/` or `contracts/` — it is composition only. Remove those
four `.gitkeep` directories rather than keeping scaffolding for work that does not exist.
9. **The page keeps its 8 imports.** CLAUDE.md has no import-count rule; one `import` per
rendered section is exactly right. Do not introduce an `OVERZICHT_SECTIONS` const to get
the number down — that trades a self-documenting array for an indirection, and this repo
deliberately has no barrels.
## Files
Move (use `git mv` so the rename is visible in review):
| From | To |
| ---------------------------------------------------------------------- | ---------------------------------------------------------- |
| `apps/ssp/src/app/registratie/ui/dashboard.page.ts` | `apps/ssp/src/app/overzicht/ui/overzicht.page.ts` |
| `apps/ssp/src/app/registratie/ui/dashboard/wat-wilt-u-doen.section.ts` | `apps/ssp/src/app/overzicht/ui/wat-wilt-u-doen.section.ts` |
| `apps/ssp/src/app/registratie/ui/dashboard/beheer-links.section.ts` | `apps/ssp/src/app/overzicht/ui/beheer-links.section.ts` |
Edit: `apps/ssp/tsconfig.json` (alias), `.dependency-cruiser.ssp.js` (edge),
`apps/ssp/src/app/app.routes.ts` (import path + class + comment),
`apps/ssp/src/app/overzicht/ui/beheer-links.section.ts` (token),
`apps/ssp/src/app/overzicht/ui/overzicht.page.ts` (class, selector, section import paths).
## Steps
1. Run `npm run gen:context` and answer `overzicht`.
2. Delete the generated `overzicht/ui/overzicht.page.ts` and the four empty layer folders.
3. Delete the generated `path: 'overzicht'` route block from `app.routes.ts`.
4. Change the generated `overzicht: []` to `overzicht: ['registratie']`.
5. `git mv` the three files per the table above.
6. Rename the page's class to `OverzichtPage` and its selector to `app-overzicht-page`; fix
its six section import paths (four now via `@registratie/ui/dashboard/…`, two local).
7. Repoint the existing `dashboard` route to `@overzicht/ui/overzicht.page` / `OverzichtPage`
and add the comment from decision 6.
8. Rewrite `beheer-links.section.ts` to inject `HEADER_ADMIN_LINKS` per decision 5.
9. Update this ticket's `Status:` to `done` and the README's RD-03 row to `done`.
10. Commit all of it together.
## Acceptance criteria
```bash
npm run dep:check # exits 0 — proves overzicht: ['registratie'] is accepted
npm run ci # exits 0
```
Then prove the boundary is real, not decorative:
```bash
# Add a temporary import of a herregistratie file to overzicht/ui/overzicht.page.ts.
# `npm run dep:check` MUST fail (overzicht may reach registratie only). Then revert.
```
And prove the section still renders for an admin:
```bash
grep -n "HEADER_ADMIN_LINKS" apps/ssp/src/app/overzicht/ui/beheer-links.section.ts # present
grep -rn "shell/nav.config" apps/ssp/src/app/overzicht/ # no match
```
## Verification
`npm run ci`. No story or `.mdx` file moves in this ticket — the three section stories stay
in `registratie` — so `--full` is not strictly required. Run it once anyway, because this is
a structural move and Storybook globs the app's stories by path.
Manual check, since e2e is not in the local GREEN one-liner: `npm start`, open
`http://localhost:4200/dashboard`, confirm all six sections render, then `?role=admin` and
confirm the Beheer section appears (and is absent without it).
## Out of scope
- Renaming the route to `/overzicht`. It needs a redirect and touches four e2e specs.
Recorded in PLAN.md as deliberately out of scope.
- Moving the four data sections (decision 3).
- Story titles — that is RD-04.
- Turning `wat-wilt-u-doen`'s action list into a token beside `NAV_ITEMS`. PLAN.md Phase 0
step 6 lists it as a "consider"; leave it for now, since route strings on a landing page
are legitimate once the page lives in a context whose job is cross-context linking.
## Risks
- **The generator's three wrong outputs** (decision 7). If you skip the corrections,
`dep:check` still passes with the boundary silently missing — the exact failure mode
`plopfile.mjs`'s own comment warns about for `create-frontend.mjs`.
- **`messages.xlf` / `messages.en.xlf` carry `<context context-type="sourcefile">` paths**
that these moves make stale. Nothing checks them and `$localize` ids are the identity, so
`ng build --localize` stays green. Do **not** hand-edit the xlf files to chase paths.
- **Do not change any `$localize` id.** Every id in the three moved files stays byte
identical; only the file's location changes. A changed id needs a new English `<target>` or
the localize gate fails.
- **`git mv`, not delete-and-create**, or the review loses the rename and the diff looks like
300 new lines.