Files
atomic-design-poc/docs/project/backlog/WP-67-monorepo-behandelportal.md
T
ehoandClaude Sonnet 5 e7156c5132 feat(WP-67): merge behandelportal into this repo as a monorepo
Restructures into apps/ssp + apps/behandelportal (two Angular projects)
plus libs/shared + libs/beheer (cross-app libraries), replacing WP-61's
separate sibling repo. That split had already produced real drift: a
hand-vendored copy of the backend's OpenAPI doc, a shared/ui+layout tree
forked and silently diverging (7 files), and beheer + the styles.scss
token bridge duplicated byte-for-byte across both repos.

- git mv the SSP's src/app/* into apps/ssp/; fold shared/, beheer/,
  environments/, the Storybook docs/*.mdx, and styles.scss into
  libs/shared + libs/beheer (all confirmed identical between the two
  repos before merging). auth stays deliberately duplicated per
  ADR-0002 (actor-specific, expected to diverge) - amended there.
- One generated API client (libs/shared), no more vendored swagger.json.
- .dependency-cruiser split into a base factory + one config per app,
  and Storybook into .storybook-ssp/.storybook-behandelportal - both
  forced by the @auth/* alias resolving to different directories per app.
- SiteHeaderComponent/ShellComponent gained HEADER_NAV_ITEMS/
  HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens so each app supplies
  its own nav/admin-links/dev-panel instead of one being hardcoded.
- CLAUDE.md, ARCHITECTURE.md, dependencies.md, and ADR-0002 updated;
  WP-67 backlog entry documents the full decision trail.

npm run ci green (lint, dep:check x2, 360 tests across ssp/
behandelportal/shared/beheer, both localized builds, backend tests,
snippet + api-client drift); both dev servers, both Storybook
instances, and docker compose verified working.

The old sibling repo (/home/eho/repos/behandelportal) is left
untouched, not deleted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 21:01:57 +02:00

8.6 KiB
Raw Blame History

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-bootstrap memory (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/shared into 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-CLI projects/ addition next to an untouched src/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) and libs/shared/styles.scss (the token bridge, also byte-identical) were folded in alongside libs/shared/ui. auth was deliberately not unified — ADR-0002 models it as actor-specific (different Principal variants), 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.ts supplies its primary nav + admin links to the shared SiteHeaderComponent via 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 for shared/ui copy, extended to injection tokens for this DI-shaped case. The dev-only "state" panel (DebugStateComponent) moved out of libs/shared into apps/ssp entirely (it's coupled to BigProfileStore, an ssp-only store) and is now hosted by the shared ShellComponent via a DEBUG_PANEL injection 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 giving ng test a buildTarget to satisfy — see libs/*/src/test-entry.ts's comment).
  • tsconfig.json (root, no more paths — see its comment) + a new tsconfig.json per 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.json scripts, 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

  1. Branch, then git mv the SSP's src/app/* (minus shared/) → apps/ssp/src/app/, plus main.ts/index.html/locale//proxy.conf.json/tsconfig.app.json/tsconfig.spec.json.
  2. git mv src/app/shared → libs/shared/src; git mv src/app/beheer → libs/beheer/src; fold in environments/ and the Storybook docs/*.mdx (both found byte-identical between the two repos — same treatment as shared/ui).
  3. Copy (not git mv — a different repo) behandelportal's auth/behandeling + main.ts/index.html/locale/ into apps/behandelportal/src/.
  4. Rewrite angular.json for 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/...).
  5. Split .dependency-cruiser.js into a base factory + one config per app; broaden eslint.config.mjs's files glob.
  6. Rewrite package.json scripts, docker-compose.yml (added a web-behandelportal service), .github/workflows/ci.yml + scripts/ci-local.sh (both apps built/tested, path filters widened), the four scripts/*.mjs/*.sh helpers, plopfile.mjs's three generators (value-object/form-machine paths now resolve shared/beheer to libs/, the context generator's tsconfig/dep-cruiser/routes edits re-anchored on the ssp config).
  7. Split .storybook/ into .storybook-ssp/ + .storybook-behandelportal/ (forced by the @auth/* alias collision); moved the CIBG token bridge (styles.scss) and the generated documentation.json to avoid two more collisions.
  8. Add HEADER_NAV_ITEMS/HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens to SiteHeaderComponent/ShellComponent; give each app its own shell/nav.config.ts and (ssp only) shell/debug-state/.
  9. Update CLAUDE.md, ARCHITECTURE.md, dependencies.md, and amend ADR-0002.

Acceptance criteria

  • npm run ci green (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:api generates the one client into libs/shared with zero drift.
  • docker compose up serves 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 / the new-ssp skill (bootstrapping a third portal as a fresh standalone repo) — whether future portals should also join this monorepo is a separate decision.
  • Extending the storybook-a11y CI job to behandelportal's own Storybook instance — it still only covers ssp's, unchanged from before this WP.
  • Reconciling auth between 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.