Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.6 KiB
WP-67 — Merge behandelportal into this repo as a monorepo
Status: done Phase: 11 — Behandelportal
Why
WP-61 bootstrapped behandelportal as a separate sibling repo, per ADR-0002's original
"separate frontend application" reading taken literally as "separate git repository." That
produced real, measured friction: a hand-vendored, manually-kept-in-sync copy of the
backend's OpenAPI doc instead of a live-generated one; shared/ui+shared/layout forked at
WP-61 and already silently diverging by the time this WP checked (7 files differed); a
beheer (admin/stamdata) context and the styles.scss token bridge duplicated byte-for-byte
across both repos; a second CI/lint/CLAUDE.md to hand-maintain. The user asked to collapse
this into one repo so the two apps share one CI, one shared UI library, and one generated API
client — the standard monorepo payoff, now that a second real frontend exists.
Read first
- ADR-0002 — its "Amendment (WP-67)" section records exactly what changed and why the underlying actor/bounded-context reasoning didn't.
- dependencies.md — the per-app dependency-cruiser split this WP introduced.
behandelportal-bootstrapmemory (prior sessions) for WP-61/62's own decisions.
Decisions (made during this WP, not pre-made — see the plan file for the questions asked)
- Unify
libs/sharedinto one library both apps import (not two copies) — the user's explicit call, given the two trees had already started diverging. - Restructure into
apps/ssp/+apps/behandelportal/(not an Angular-CLIprojects/addition next to an untouchedsrc/app/) — the user's call, accepting the larger diff. - Leave the old sibling repo (
/home/eho/repos/behandelportal) untouched — no deletion. - Bring the content in as a fresh commit, no git-history import — that repo is itself a fork of this repo's own pre-WP-61 history, so a subtree merge would fight to reconcile two copies of the same ancestor commits for one commit's worth of real new content.
- Course corrections found during execution (squarely within "unify shared," not
separately asked):
libs/beheer(admin/stamdata — already identical in both apps, not actor-specific) andlibs/shared/styles.scss(the token bridge, also byte-identical) were folded in alongsidelibs/shared/ui.authwas deliberately not unified — ADR-0002 models it as actor-specific (differentPrincipalvariants), so today's accidental similarity is expected to diverge, not something to force together. - Two Storybook instances, not one (
.storybook-ssp/,.storybook-behandelportal/**) — a structural necessity, not a simplification choice: both apps'auth(and other) context aliases share the name@auth/*but resolve to different physical directories, so no single tsconfig can compile both apps' stories in one pass. - Each app's own
shell/nav.config.tssupplies its primary nav + admin links to the sharedSiteHeaderComponentvia two new injection tokens (HEADER_NAV_ITEMS,HEADER_ADMIN_LINKS) rather than the component hardcoding one app's routes — the same "shared component takes copy as an input, the domain caller supplies it" idiom CLAUDE.md already used forshared/uicopy, extended to injection tokens for this DI-shaped case. The dev-only "state" panel (DebugStateComponent) moved out oflibs/sharedintoapps/sspentirely (it's coupled toBigProfileStore, an ssp-only store) and is now hosted by the sharedShellComponentvia aDEBUG_PANELinjection token — no provider, no panel.
Files
Nearly the whole repo, mechanically (git mv), plus:
angular.json— rewritten for 4 projects:ssp,behandelportal(real apps),shared,beheer(library projects whose only real purpose is givingng testabuildTargetto satisfy — seelibs/*/src/test-entry.ts's comment).tsconfig.json(root, no morepaths— see its comment) + a newtsconfig.jsonper app/library declaring that project's own full alias map..dependency-cruiser.base.js(rule factory) +.dependency-cruiser.ssp.js+.dependency-cruiser.behandelportal.js(replacing the single.dependency-cruiser.js).eslint.config.mjs,package.jsonscripts,docker-compose.yml,.github/workflows/ci.yml,scripts/ci-local.sh,scripts/check-tokens.sh,scripts/gen-snippets.mjs,scripts/serve-i18n.mjs,scripts/dep-graph.sh,plopfile.mjs,nswag.json— all re-pointed at the new paths / made per-app aware.CLAUDE.md,ARCHITECTURE.md,dependencies.md, ADR-0002 — updated for the new layout.
Steps
- Branch, then
git mvthe SSP'ssrc/app/*(minusshared/) →apps/ssp/src/app/, plusmain.ts/index.html/locale//proxy.conf.json/tsconfig.app.json/tsconfig.spec.json. git mv src/app/shared→libs/shared/src;git mv src/app/beheer→libs/beheer/src; fold inenvironments/and the Storybookdocs/*.mdx(both found byte-identical between the two repos — same treatment asshared/ui).- Copy (not
git mv— a different repo) behandelportal'sauth/behandeling+main.ts/index.html/locale/intoapps/behandelportal/src/. - Rewrite
angular.jsonfor the two real projects + two library test-only projects; fix every relative import that broke (environments/environment→@shared/environments/environment; MDX docs'../app/shared/...→../src/...). - Split
.dependency-cruiser.jsinto a base factory + one config per app; broadeneslint.config.mjs'sfilesglob. - Rewrite
package.jsonscripts,docker-compose.yml(added aweb-behandelportalservice),.github/workflows/ci.yml+scripts/ci-local.sh(both apps built/tested, path filters widened), the fourscripts/*.mjs/*.shhelpers,plopfile.mjs's three generators (value-object/form-machine paths now resolveshared/beheertolibs/, thecontextgenerator's tsconfig/dep-cruiser/routes edits re-anchored on the ssp config). - Split
.storybook/into.storybook-ssp/+.storybook-behandelportal/(forced by the@auth/*alias collision); moved the CIBG token bridge (styles.scss) and the generateddocumentation.jsonto avoid two more collisions. - Add
HEADER_NAV_ITEMS/HEADER_ADMIN_LINKS/DEBUG_PANELinjection tokens toSiteHeaderComponent/ShellComponent; give each app its ownshell/nav.config.tsand (ssp only)shell/debug-state/. - Update
CLAUDE.md,ARCHITECTURE.md,dependencies.md, and amend ADR-0002.
Acceptance criteria
npm run cigreen (lint, dep:check ×2, format:check, check:tokens, test:coverage ×4 projects,ng build --localize×2, npm audit, backend test, snippets drift, api-client drift).- Both apps' dev servers run against the one shared backend
(
npm start/npm run start:behandelportal, ports 4200/4201, backend on 5000). - Both apps' production + localized builds succeed.
- Both Storybook instances build (
build-storybook[:behandelportal]). npm run gen:apigenerates the one client intolibs/sharedwith zero drift.docker compose upserves both apps + the shared backend.
Verification
npm run ci; npx ng build ssp --localize && npx ng build behandelportal --localize;
npx ng run ssp:build-storybook && npx ng run behandelportal:build-storybook; manual:
npm start on :4200 and npm run start:behandelportal on :4201 against
dotnet run --project backend/src/BigRegister.Api, log in on both.
Out of scope
- Deleting or archiving the old sibling repo (
/home/eho/repos/behandelportal) — left untouched per the user's explicit choice. scripts/create-frontend.mjs/ thenew-sspskill (bootstrapping a third portal as a fresh standalone repo) — whether future portals should also join this monorepo is a separate decision.- Extending the
storybook-a11yCI job to behandelportal's own Storybook instance — it still only covers ssp's, unchanged from before this WP. - Reconciling
authbetween the two apps — left duplicated, deliberately (see Decisions).
Risks
The libs/shared/libs/beheer "library" Angular projects exist solely to give the
unit-test builder a buildTarget to resolve (@angular/build:unit-test always requires
one, even for a project that's only ever tested) — test-entry.ts + tsconfig.app.json in
each are a ponytail:-flagged workaround, not a real buildable/publishable library. If
either library ever needs to actually build (e.g. an ng-packagr distributable), replace this
with a real library target then.