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:
@@ -2,6 +2,7 @@
|
||||
// scaffolding one (see `gen:context`, WP-44).
|
||||
module.exports = require('./.dependency-cruiser.base.js')(
|
||||
{
|
||||
overzicht: ['registratie'],
|
||||
auth: [],
|
||||
registratie: [],
|
||||
herregistratie: ['registratie'], // the one sanctioned cross-feature edge
|
||||
|
||||
@@ -14,9 +14,11 @@ export const routes: Routes = [
|
||||
loadComponent: () => import('@auth/ui/login.page').then((m) => m.LoginPage),
|
||||
},
|
||||
{
|
||||
// Path stays 'dashboard' on purpose: it is a user-visible URL and four e2e
|
||||
// specs assert it. The context is `overzicht`; only the path string differs.
|
||||
path: 'dashboard',
|
||||
canActivate: [authGuard],
|
||||
loadComponent: () => import('@registratie/ui/dashboard.page').then((m) => m.DashboardPage),
|
||||
loadComponent: () => import('@overzicht/ui/overzicht.page').then((m) => m.OverzichtPage),
|
||||
},
|
||||
{
|
||||
path: 'registratie',
|
||||
|
||||
+3
-2
@@ -3,7 +3,7 @@ import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
import { ApplicationListComponent } from '@shared/ui/application-list/application-list.component';
|
||||
import { ApplicationLinkComponent } from '@shared/ui/application-link/application-link.component';
|
||||
import { AccessStore } from '@shared/application/access.store';
|
||||
import { ADMIN_LINKS } from '../../../shell/nav.config';
|
||||
import { HEADER_ADMIN_LINKS } from '@shared/layout/site-header/nav-config';
|
||||
|
||||
/** Section: "Beheer" — the admin pages the current principal may reach, capability-
|
||||
gated (never role-derived), the same source + filter the site header uses.
|
||||
@@ -31,5 +31,6 @@ import { ADMIN_LINKS } from '../../../shell/nav.config';
|
||||
})
|
||||
export class BeheerLinksSection {
|
||||
private access = inject(AccessStore);
|
||||
protected adminLinks = computed(() => ADMIN_LINKS.filter((l) => this.access.can(l.cap)));
|
||||
private rawAdminLinks = inject(HEADER_ADMIN_LINKS);
|
||||
protected adminLinks = computed(() => this.rawAdminLinks.filter((l) => this.access.can(l.cap)));
|
||||
}
|
||||
+10
-9
@@ -1,17 +1,18 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';
|
||||
import { MijnAanvragenSection } from './dashboard/mijn-aanvragen.section';
|
||||
import { WatMoetIkRegelenSection } from './dashboard/wat-moet-ik-regelen.section';
|
||||
import { MijnRegistratieSection } from './dashboard/mijn-registratie.section';
|
||||
import { SpecialismenSection } from './dashboard/specialismen.section';
|
||||
import { WatWiltUDoenSection } from './dashboard/wat-wilt-u-doen.section';
|
||||
import { BeheerLinksSection } from './dashboard/beheer-links.section';
|
||||
import { MijnAanvragenSection } from '@registratie/ui/dashboard/mijn-aanvragen.section';
|
||||
import { WatMoetIkRegelenSection } from '@registratie/ui/dashboard/wat-moet-ik-regelen.section';
|
||||
import { MijnRegistratieSection } from '@registratie/ui/dashboard/mijn-registratie.section';
|
||||
import { SpecialismenSection } from '@registratie/ui/dashboard/specialismen.section';
|
||||
import { WatWiltUDoenSection } from './wat-wilt-u-doen.section';
|
||||
import { BeheerLinksSection } from './beheer-links.section';
|
||||
|
||||
/** Page: "Mijn overzicht" — the portal home, following the NL Design System "Mijn
|
||||
omgeving" pattern. Composition only: each section below answers its own data
|
||||
question (own store, own async state) — see `ui/dashboard/*.section.ts`. */
|
||||
question (own store, own async state) — four sections stay in `registratie/ui/dashboard/`
|
||||
(they render registratie data), two live here (cross-context navigation). */
|
||||
@Component({
|
||||
selector: 'app-dashboard-page',
|
||||
selector: 'app-overzicht-page',
|
||||
imports: [
|
||||
PageShellComponent,
|
||||
MijnAanvragenSection,
|
||||
@@ -39,4 +40,4 @@ import { BeheerLinksSection } from './dashboard/beheer-links.section';
|
||||
</app-page-shell>
|
||||
`,
|
||||
})
|
||||
export class DashboardPage {}
|
||||
export class OverzichtPage {}
|
||||
@@ -6,6 +6,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@overzicht/*": ["src/app/overzicht/*"],
|
||||
"@shared/*": ["../../libs/shared/src/*"],
|
||||
"@beheer/*": ["../../libs/beheer/src/*"],
|
||||
"@auth/*": ["src/app/auth/*"],
|
||||
|
||||
@@ -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.
|
||||
@@ -97,7 +97,7 @@ two. Note that RD-15 exists because 22 abandoned agent worktrees are still on di
|
||||
| ----- | ---------------------------------------------------------------------------- | ---------- | --------- | ------ |
|
||||
| RD-01 | Scaffold this backlog: README, PLAN, ticket template | — | | done |
|
||||
| RD-02 | `max-lines` rule + `reportUnusedDisableDirectives` + 7 disables | 01 | | done |
|
||||
| RD-03 | `overzicht` context: page + 2 nav sections, boundary edge, admin-links token | 02 | yes | todo |
|
||||
| RD-03 | `overzicht` context: page + 2 nav sections, boundary edge, admin-links token | 02 | yes | done |
|
||||
| RD-04 | Story titles to `Domein/<Context>/<Name>`; add the missing stories | 03 | yes | todo |
|
||||
| RD-05 | `createStore` gains the effect map + specs | 02 | | todo |
|
||||
| RD-06 | **Bug fix:** 2 single-step forms to the effect map + retry affordance | 05 | yes | todo |
|
||||
|
||||
Reference in New Issue
Block a user