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>
This commit is contained in:
@@ -117,6 +117,7 @@ for its existing violations, so every WP ends green.
|
||||
| [WP-64](WP-64-behandelportal-werkvoorraad.md) | Behandelportal: werkvoorraad (queue) screen | 11 · Behandelportal | todo |
|
||||
| [WP-65](WP-65-behandelportal-beoordeling.md) | Behandelportal: zaak detail + beoordeling (decision) screen | 11 · Behandelportal | todo |
|
||||
| [WP-66](WP-66-behandelportal-openzaak-write.md) | Wire the decision into OpenZaak | 11 · Behandelportal | todo |
|
||||
| [WP-67](WP-67-monorepo-behandelportal.md) | Merge behandelportal into this repo as a monorepo | 11 · Behandelportal | done |
|
||||
|
||||
Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn);
|
||||
03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# 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](../reference/architecture/0002-user-groups-and-bounded-contexts.md) — its
|
||||
"Amendment (WP-67)" section records exactly what changed and why the underlying
|
||||
actor/bounded-context reasoning didn't.
|
||||
- [dependencies.md](../reference/architecture/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
|
||||
|
||||
- [x] `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).
|
||||
- [x] Both apps' dev servers run against the one shared backend
|
||||
(`npm start` / `npm run start:behandelportal`, ports 4200/4201, backend on 5000).
|
||||
- [x] Both apps' production + localized builds succeed.
|
||||
- [x] Both Storybook instances build (`build-storybook[:behandelportal]`).
|
||||
- [x] `npm run gen:api` generates the one client into `libs/shared` with zero drift.
|
||||
- [x] `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.
|
||||
@@ -21,7 +21,9 @@ Getting this wrong is expensive: split the code by role and every feature smears
|
||||
|
||||
Confirmed constraints (with the product owner):
|
||||
|
||||
- The backoffice is a **separate frontend application**, own audience, own deployment.
|
||||
- The backoffice is a **separate frontend application** (own audience, own deployable —
|
||||
see the [WP-67 amendment](#amendment-wp-67-2026-08-01-one-repo-not-two) below for where
|
||||
its source actually lives).
|
||||
- The groups **authenticate differently**: Zorgverlener via DigiD/BSN; Behandelaar via employee SSO.
|
||||
- Both act on the **same underlying aggregate** — the aanvraag/registration — but see different views.
|
||||
|
||||
@@ -43,9 +45,11 @@ Confirmed constraints (with the product owner):
|
||||
The same real-world thing is described in two different languages:
|
||||
|
||||
- **Zelfbediening (SSP)** — the Zorgverlener: _"ik vraag herregistratie aan"_ — eligibility, fill in
|
||||
my data, upload documents, submit. **This repo.**
|
||||
my data, upload documents, submit. **`apps/ssp` in this repo** (was "this repo" itself
|
||||
before WP-67 turned it into a monorepo).
|
||||
- **Behandeling (backoffice)** — the Behandelaar: _"ik beoordeel de aanvraag"_ — werkvoorraad,
|
||||
beoordeling, besluit, meer-info-opvragen, SLA, audit. **A sibling application**, not a folder here.
|
||||
beoordeling, besluit, meer-info-opvragen, SLA, audit. **`apps/behandelportal`** — a
|
||||
separate Angular _project_, not a separate _repo_ (see the amendment below).
|
||||
|
||||
Diverging verbs over the same noun is the textbook signal for **two bounded contexts**.
|
||||
|
||||
@@ -60,16 +64,16 @@ contract_ between the two contexts:
|
||||
Ingediend → In behandeling → (Meer info gevraagd ⇄) → Goedgekeurd / Afgewezen
|
||||
```
|
||||
|
||||
The Behandeling context **advances** this lifecycle; the SSP **reads** it. Today the SSP already holds
|
||||
the seed of it — `pendingHerregistratie` in `big-profile.store.ts:53` is the first, coarsest read of
|
||||
that status ("in behandeling"). As the backoffice appears, that single boolean grows into a real
|
||||
status the backend publishes.
|
||||
The Behandeling context **advances** this lifecycle; the SSP **reads** it. WP-63 published the
|
||||
full lifecycle enum backend-side (`AanvraagStatusTag`); the SSP's dashboard `pendingHerregistratie`
|
||||
signal (`big-profile.store.ts`) turned out to be a pure client-side optimistic flag, not a read of
|
||||
any backend field — WP-65 is where a behandelaar action first reaches `Ingediend`/`MeerInfoGevraagd`.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph FE["Frontend bounded contexts (separate apps)"]
|
||||
SSP["<b>Zelfbediening (SSP)</b><br/>Zorgverlener · DigiD/BSN<br/><i>this repo</i>"]
|
||||
BO["<b>Behandeling (backoffice)</b><br/>Behandelaar · employee SSO<br/><i>sibling app</i>"]
|
||||
subgraph FE["Frontend bounded contexts (two Angular projects, one repo — WP-67)"]
|
||||
SSP["<b>Zelfbediening (SSP)</b><br/>Zorgverlener · DigiD/BSN<br/><i>apps/ssp</i>"]
|
||||
BO["<b>Behandeling (backoffice)</b><br/>Behandelaar · employee SSO<br/><i>apps/behandelportal</i>"]
|
||||
end
|
||||
BE["<b>Backend domain</b><br/>aanvraag aggregate (system of record)<br/>status lifecycle · authorization"]
|
||||
SSP -- "reads aanvraag status<br/>(decision DTOs, ADR-0001)" --> BE
|
||||
@@ -115,14 +119,45 @@ not merely a new login.
|
||||
|
||||
## Consequences
|
||||
|
||||
- This repo **stays the pure SSP**. No backoffice code leaks in; no role-named folders appear.
|
||||
- The backoffice ships as a **separate app** against the same backend and the same OpenAPI contract.
|
||||
- `apps/ssp` **stays the pure SSP**. No backoffice code leaks in; no role-named folders appear.
|
||||
- The backoffice ships as a **separate Angular project** (`apps/behandelportal`, WP-67 —
|
||||
originally a separate repo, see the amendment below) against the same backend and the same
|
||||
OpenAPI contract.
|
||||
- The one concrete FE change when actor #2 lands is `Session → Principal` in the `auth` context; the
|
||||
`authGuard`/`SessionStore` seams already localise that (`auth.guard.ts`, `session.store.ts`).
|
||||
- The backend becomes the authority for the **aanvraag status lifecycle** and for **authorization**,
|
||||
publishing both as decision DTOs — a natural extension of ADR-0001, not a new pattern.
|
||||
- `pendingHerregistratie` is understood as a _temporary stand-in_ for a real, backend-owned status.
|
||||
|
||||
## Amendment (WP-67, 2026-08-01): one repo, not two
|
||||
|
||||
WP-61 initially built `apps/behandelportal` as a **separate sibling repo**
|
||||
(`/home/eho/repos/behandelportal`), taking this ADR's "separate frontend application" literally
|
||||
as "separate git repository." That produced real friction WP-67 then undid: a hand-vendored,
|
||||
manually-kept-in-sync copy of the backend's OpenAPI doc instead of a live-generated one, a
|
||||
`shared/ui`+`shared/layout` tree forked at WP-61 and already silently diverging by the time
|
||||
WP-67 checked (7 files), a `beheer` (admin/stamdata) context and `styles.scss` token bridge
|
||||
duplicated byte-for-byte across both repos, and a second CI/lint/CLAUDE.md to hand-maintain.
|
||||
|
||||
**The bounded-context reasoning above is unchanged** — it never depended on repo count. What
|
||||
changes is purely the _packaging_:
|
||||
|
||||
- Two Angular CLI projects in one workspace: `apps/ssp`, `apps/behandelportal` — each still
|
||||
its own deployable, its own `angular.json` build/serve/test targets, its own port.
|
||||
- `libs/shared` (design system + kernel + the one generated API client) and `libs/beheer`
|
||||
(admin/stamdata — genuinely identical for both apps, not actor-specific) are cross-app
|
||||
libraries. `auth` stays **duplicated**, not unified — per §3 above, it's expected to diverge
|
||||
(Zorgverlener DigiD/BSN vs. Behandelaar employee SSO), so unifying it now would be forcing
|
||||
today's accidental similarity into a shape that fights tomorrow's real difference.
|
||||
- One backend, one OpenAPI doc, one generated client — the vendored-swagger workaround is
|
||||
gone; `npm run gen:api` regenerates the live doc straight into `libs/shared`.
|
||||
- Each app still needs its **own** Storybook instance (`.storybook-ssp/`,
|
||||
`.storybook-behandelportal/`) — `@auth/*` (and other context aliases) resolve to different
|
||||
physical directories per app, so one merged tsconfig can't serve both at once. This is a
|
||||
real, structural constraint, not a leftover of the old two-repo split.
|
||||
- The old sibling repo was left untouched (not deleted) when this migration landed — a
|
||||
redundant clone, safe to archive once the monorepo version is verified in daily use.
|
||||
|
||||
## Out of scope here (next steps, not built)
|
||||
|
||||
- Building the Behandeling backoffice application.
|
||||
|
||||
@@ -16,24 +16,33 @@ re-registration — "herregistratie").
|
||||
|
||||
---
|
||||
|
||||
## 1. The big picture: six "contexts", five "layers"
|
||||
## 1. The big picture: two apps, cross-app libraries, "contexts", "layers"
|
||||
|
||||
The code is split first by **business area** (a "bounded context" in DDD terms),
|
||||
then inside each area by **layer**.
|
||||
Two Angular projects share one backend and two cross-app libraries (WP-67 — see
|
||||
ADR-0002's amendment for why this is a monorepo, not two repos). Inside each app (and
|
||||
each library), the code is split first by **business area** (a "bounded context" in DDD
|
||||
terms), then inside each area by **layer**.
|
||||
|
||||
```
|
||||
src/app/
|
||||
shared/ things every context reuses (no business logic of its own)
|
||||
auth/ logging in / the current session
|
||||
registratie/ the user's BIG registration + personal data
|
||||
herregistratie/ the re-registration application flow
|
||||
brief/ letter-composition teaching slice
|
||||
showcase/ a teaching page; not a real feature (may read every context)
|
||||
apps/
|
||||
ssp/src/app/ Zorgverlener self-service (ADR-0002) — this doc's main subject
|
||||
auth/ logging in / the current session
|
||||
registratie/ the user's BIG registration + personal data
|
||||
herregistratie/ the re-registration application flow
|
||||
brief/ letter-composition teaching slice
|
||||
showcase/ a teaching page; not a real feature (may read every ssp context)
|
||||
behandelportal/src/app/ Behandelaar backoffice (ADR-0002) — a sibling app, not covered here
|
||||
auth/ its own login (employee SSO, not DigiD/BSN)
|
||||
behandeling/ werkvoorraad, beoordeling (WP-64/65)
|
||||
libs/
|
||||
shared/src/ things every app reuses (no business logic of its own)
|
||||
beheer/src/ admin/stamdata — a real bounded context, used identically by both apps
|
||||
```
|
||||
|
||||
`showcase/` is a **sanctioned exception** to the direction rules: its whole point is
|
||||
showing multiple contexts side by side, so it may import any context. Nothing imports
|
||||
`showcase`. (Enforced in `eslint.config.mjs`; same precedent as the `debug-state` panel.)
|
||||
`showcase/` is a **sanctioned exception** to the direction rules, scoped to `apps/ssp`:
|
||||
its whole point is showing multiple ssp contexts side by side, so it may import any of
|
||||
them. Nothing imports `showcase`. (Enforced per-app in `.dependency-cruiser.<app>.js`;
|
||||
same precedent as the `debug-state` panel — see below.)
|
||||
|
||||
### The atomic-design hierarchy, visualised
|
||||
|
||||
@@ -70,25 +79,32 @@ questions:
|
||||
| `ui/` | How does it look? | Yes (components) | `dashboard.page.ts` |
|
||||
|
||||
**The one rule that keeps it sane: dependencies only point _inward_.** UI may use
|
||||
application, application may use domain, everyone may use `shared`. Never the
|
||||
other way around. In particular **`ui/` and `layout/` never import `infrastructure/`
|
||||
directly** — they reach data through an application store or command (lint-enforced).
|
||||
application, application may use domain, everyone (in either app) may use `libs/shared`
|
||||
and `libs/beheer`. Never the other way around — `libs/shared` may not depend on
|
||||
`libs/beheer` either (it stays the base), and an app may not import the other app's
|
||||
source. In particular **`ui/` and `layout/` never import `infrastructure/`
|
||||
directly** — they reach data through an application store or command (lint-enforced,
|
||||
per app — a single merged tsconfig can't resolve both apps' `@auth/*` alias at once, so
|
||||
each app is cruised separately against its own `.dependency-cruiser.<app>.js`).
|
||||
The `domain/` layer imports nothing from Angular, so the business rules are plain
|
||||
functions you can read and test in isolation.
|
||||
|
||||
Allowed direction: `herregistratie → registratie → shared`, `auth → shared`,
|
||||
`brief → shared` (`showcase` may read every context; see above).
|
||||
Allowed direction (within `apps/ssp`): `herregistratie → registratie → libs/shared|beheer`,
|
||||
`auth → libs/shared|beheer`, `brief → libs/shared|beheer` (`showcase` may read every ssp
|
||||
context; see above). `apps/behandelportal` has its own analogous rule for `behandeling`/`auth`.
|
||||
|
||||
### Why the `shared/` kernel is split too
|
||||
### Why `libs/shared` is split into layers too
|
||||
|
||||
- `shared/kernel/` — tiny generic helpers (no Angular).
|
||||
- `shared/application/` — generic state tools (RemoteData, the store).
|
||||
- `shared/ui/` — the atomic-design building blocks (buttons, inputs, the async renderer). These know nothing about BIG-register.
|
||||
- `shared/layout/` — page chrome (header, footer, shells).
|
||||
- `shared/infrastructure/` — the demo HTTP interceptor.
|
||||
- `libs/shared/src/kernel/` — tiny generic helpers (no Angular).
|
||||
- `libs/shared/src/application/` — generic state tools (RemoteData, the store).
|
||||
- `libs/shared/src/ui/` — the atomic-design building blocks (buttons, inputs, the async renderer). These know nothing about BIG-register.
|
||||
- `libs/shared/src/layout/` — page chrome (header, footer, shells) — takes each app's own nav/copy via `input()`s or an injection token rather than hardcoding one app's content.
|
||||
- `libs/shared/src/infrastructure/` — the demo HTTP interceptor + the one generated API client both apps import.
|
||||
|
||||
Imports use path aliases so they read as direction statements:
|
||||
`@shared/*`, `@auth/*`, `@registratie/*`, `@herregistratie/*`, `@brief/*`.
|
||||
`@shared/*`, `@beheer/*`, `@auth/*`, `@registratie/*`, `@herregistratie/*`, `@brief/*`
|
||||
(ssp) — `@shared/*`, `@beheer/*`, `@auth/*`, `@behandeling/*` (behandelportal); each
|
||||
app's own `tsconfig.json` declares its full alias map.
|
||||
|
||||
---
|
||||
|
||||
@@ -150,7 +166,7 @@ Three signals = eight combinations, and most are nonsense (loading **and** has
|
||||
data **and** has an error?). You end up writing defensive `if`s everywhere.
|
||||
|
||||
Instead we use **one** value that is _exactly one of_ four shapes
|
||||
(`shared/application/remote-data.ts`):
|
||||
(`libs/shared/src/application/remote-data.ts`):
|
||||
|
||||
```ts
|
||||
type RemoteData<E, T> =
|
||||
@@ -166,7 +182,7 @@ Notice the data lives _on_ the shape — you literally cannot read `.value` unle
|
||||
you're in the `Success` case, so "loaded but no data" can't be written down.
|
||||
|
||||
To use it, you handle every case once. The `<app-async>` component
|
||||
(`shared/ui/async/async.component.ts`) does this for you: you give it a
|
||||
(`libs/shared/src/ui/async/async.component.ts`) does this for you: you give it a
|
||||
`RemoteData` (or a raw `httpResource`) and four templates, and it shows exactly
|
||||
one. There's also `foldRemote(rd, { loading, empty, failure, success })` for
|
||||
doing the same in TypeScript — the compiler makes you cover all four.
|
||||
@@ -277,7 +293,7 @@ function reduce(state, msg) {
|
||||
```
|
||||
|
||||
The component (`herregistratie-wizard.component.ts`) wires it to a signal with
|
||||
the tiny helper in `shared/application/store.ts`:
|
||||
the tiny helper in `libs/shared/src/application/store.ts`:
|
||||
|
||||
```ts
|
||||
private store = createStore(initial, reduce);
|
||||
@@ -341,7 +357,7 @@ A common assumption is "the form saves on blur." It doesn't. **Blur only marks a
|
||||
_touched_** so validation can show; it never writes the value or hits the network. In the
|
||||
shared atoms, `(blur)="onTouched()"` is the `ControlValueAccessor` touched callback and
|
||||
nothing more; the value is pushed on `(input)`, every keystroke
|
||||
([`text-input.component.ts`](../../../src/app/shared/ui/text-input/text-input.component.ts):
|
||||
([`text-input.component.ts`](../../../libs/shared/src/ui/text-input/text-input.component.ts):
|
||||
`(input)` L29 → `onChange` L62, vs `(blur)="onTouched()"` L30).
|
||||
|
||||
The real flow has two stages, neither keyed on focus:
|
||||
@@ -349,20 +365,20 @@ The real flow has two stages, neither keyed on focus:
|
||||
1. **Keystroke → Model.** A field binds `(ngModelChange)`/`(input)` and dispatches
|
||||
`{ tag: 'SetField', key, value }`. The pure reducer stores it immediately — so the
|
||||
Model is always current, on every keystroke, while editing.
|
||||
([`herregistratie-wizard.component.ts`](../../../src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts)
|
||||
L78 → [`herregistratie.machine.ts`](../../../src/app/herregistratie/domain/herregistratie.machine.ts)
|
||||
([`herregistratie-wizard.component.ts`](../../../apps/ssp/src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts)
|
||||
L78 → [`herregistratie.machine.ts`](../../../apps/ssp/src/app/herregistratie/domain/herregistratie.machine.ts)
|
||||
L138-142, `setField`.)
|
||||
2. **Model → backend (600 ms debounce).** A signal `effect` tracks the machine
|
||||
`snapshot()`; each change resets a 600 ms timer whose callback does I/O **only** (it
|
||||
never dispatches, so it can't livelock the store). On the first save it lazily creates
|
||||
the application and stamps `?aanvraag=<id>` into the URL, so a reload resumes the draft.
|
||||
([`draft-sync.ts`](../../../src/app/registratie/application/draft-sync.ts):
|
||||
([`draft-sync.ts`](../../../apps/ssp/src/app/registratie/application/draft-sync.ts):
|
||||
`DEBOUNCE_MS` L34, `effect` L102-108, `flush` L88-98 → `ApplicationsAdapter.syncDraft`.)
|
||||
|
||||
The **brief** context uses the same 600 ms idiom in its own store: `edit()` applies the
|
||||
edit optimistically in the reducer and records an undo step, then `scheduleSave()` →
|
||||
`flushSave()` flips a `saveState` (Saving/Saved/Error) and calls `adapter.save`
|
||||
([`brief.store.ts`](../../../src/app/brief/application/brief.store.ts) L157-166, L192-209).
|
||||
([`brief.store.ts`](../../../apps/ssp/src/app/brief/application/brief.store.ts) L157-166, L192-209).
|
||||
|
||||
So it _feels_ like save-on-blur only because you usually stop typing when you leave a
|
||||
field, and the debounce fires ~600 ms later. The trigger is **"stopped changing," not
|
||||
@@ -370,7 +386,7 @@ field, and the debounce fires ~600 ms later. The trigger is **"stopped changing,
|
||||
|
||||
**The last-mile guard (leaving mid-debounce).** A debounce means an edit made in the final
|
||||
<600 ms before you leave hasn't been written yet. Two seams close that window
|
||||
([`pending-saves.ts`](../../../src/app/shared/application/pending-saves.ts)): every autosave
|
||||
([`pending-saves.ts`](../../../libs/shared/src/application/pending-saves.ts)): every autosave
|
||||
owner (the brief/org-template root stores and each wizard's `draft-sync`) registers in a
|
||||
`PendingSaves` registry, and
|
||||
|
||||
@@ -418,7 +434,7 @@ baked into the type. Same idea for `Uren` and `BigNummer`.
|
||||
3. **Application.** If there's state to coordinate, add/extend a store
|
||||
(`providedIn: 'root'` if it must be shared across pages). Model state as a
|
||||
discriminated union; change it only through a pure `update`/`reduce`.
|
||||
4. **UI last.** Build the page/organism from `shared/ui` atoms. Render async
|
||||
4. **UI last.** Build the page/organism from `libs/shared/ui` atoms. Render async
|
||||
state through `<app-async>`. Send messages; don't mutate.
|
||||
|
||||
If you're tempted to add a third boolean to track state — stop and model it as a
|
||||
@@ -527,7 +543,7 @@ Practical notes, kept lazy:
|
||||
|
||||
The sketch above is the _rationale_; the shipped shape has since firmed up. The contract is
|
||||
no longer hand-written DTOs — it's an **NSwag-generated typed client**
|
||||
([`api-client.ts`](../../../src/app/shared/infrastructure/api-client.ts), regenerate with
|
||||
([`api-client.ts`](../../../libs/shared/src/infrastructure/api-client.ts), regenerate with
|
||||
`npm run gen:api` per [`nswag.json`](../../../nswag.json)) — and the boundary is a
|
||||
`parse*` returning `Result` rather than `httpResource({ parse })`. End to end:
|
||||
|
||||
@@ -541,25 +557,25 @@ no longer hand-written DTOs — it's an **NSwag-generated typed client**
|
||||
`HttpClient` by `httpClientFetch` — the one place cross-cutting concerns live:
|
||||
`X-Correlation-Id` on every call, `Idempotency-Key` on non-GETs, a 10 s timeout, and
|
||||
GET-only retry. Routing through `HttpClient` is exactly what lets the interceptors see API
|
||||
traffic. ([`api-client.provider.ts`](../../../src/app/shared/infrastructure/api-client.provider.ts):
|
||||
traffic. ([`api-client.provider.ts`](../../../libs/shared/src/infrastructure/api-client.provider.ts):
|
||||
`httpClientFetch` L47-82, `provideApiClient` L86-92; registered in
|
||||
[`app.config.ts`](../../../src/app/app.config.ts) L37.)
|
||||
[`app.config.ts`](../../../apps/ssp/src/app/app.config.ts) L37.)
|
||||
- **Interceptors (dev-only, stripped in prod).** `scenario.interceptor.ts` (the `?scenario=`
|
||||
toggle) and `role.interceptor.ts` (`X-Role` on role-aware endpoints).
|
||||
|
||||
**A read (dashboard):** `<app-async [data]="store.profile()">` →
|
||||
[`BigProfileStore`](../../../src/app/registratie/application/big-profile.store.ts) →
|
||||
[`BigProfileStore`](../../../apps/ssp/src/app/registratie/application/big-profile.store.ts) →
|
||||
`DashboardViewAdapter.dashboardViewResource()` = `resource({ loader: () =>
|
||||
client.dashboardView() })`
|
||||
([`dashboard-view.adapter.ts`](../../../src/app/registratie/infrastructure/dashboard-view.adapter.ts))
|
||||
([`dashboard-view.adapter.ts`](../../../apps/ssp/src/app/registratie/infrastructure/dashboard-view.adapter.ts))
|
||||
→ GET `/api/v1/dashboard-view` → `httpClientFetch` → proxy → backend → back through the
|
||||
`parseDashboardView(json): Result` trust boundary → `RemoteData<DashboardView>` → rendered.
|
||||
|
||||
**A write (change address):** `runIfSubmitting()` (§2d) → `createSubmitChangeRequest`
|
||||
([`submit-change-request.ts`](../../../src/app/registratie/application/submit-change-request.ts))
|
||||
([`submit-change-request.ts`](../../../apps/ssp/src/app/registratie/application/submit-change-request.ts))
|
||||
→ `runSubmit` — the one try/catch that mints the `Idempotency-Key` and maps RFC-7807
|
||||
ProblemDetails → string ([`submit.ts`](../../../src/app/shared/application/submit.ts)) →
|
||||
[`change-request.adapter.ts`](../../../src/app/registratie/infrastructure/change-request.adapter.ts)
|
||||
ProblemDetails → string ([`submit.ts`](../../../libs/shared/src/application/submit.ts)) →
|
||||
[`change-request.adapter.ts`](../../../apps/ssp/src/app/registratie/infrastructure/change-request.adapter.ts)
|
||||
→ POST `/api/v1/change-requests` → `ok(referentie)` / `err(detail)` → dispatch
|
||||
`SubmitConfirmed` / `SubmitFailed`.
|
||||
|
||||
|
||||
@@ -1,35 +1,50 @@
|
||||
# Dependencies & boundaries
|
||||
|
||||
How the app's **bounded-context** and **atomic-layer** boundaries are declared, enforced, and
|
||||
visualized (WP-38). One declarative source — `.dependency-cruiser.js` — both **guards** the edges
|
||||
and **draws** the graph, replacing the per-context `no-restricted-imports` blocks that previously
|
||||
had to be hand-copied (and that had left `herregistratie` uncovered).
|
||||
visualized (WP-38, generalized for the two-app monorepo by WP-67). One declarative source —
|
||||
`.dependency-cruiser.base.js` (a rule _factory_) plus one thin `.dependency-cruiser.<app>.js` per
|
||||
app — both **guards** the edges and **draws** the graph, replacing the per-context
|
||||
`no-restricted-imports` blocks that previously had to be hand-copied (and that had left
|
||||
`herregistratie` uncovered). Each app is cruised **separately**, against its own `tsconfig.json`:
|
||||
a single merged tsconfig can't resolve both apps' `@auth/*` alias at once (each points at a
|
||||
different physical directory), so there is no single global config file.
|
||||
|
||||
## The rules (single source: `.dependency-cruiser.js`)
|
||||
## The rules (single source: `.dependency-cruiser.base.js`)
|
||||
|
||||
**Bounded-context direction** — dependencies point inward; everyone may use `shared`, nothing
|
||||
imports `showcase`:
|
||||
**Bounded-context direction** — dependencies point inward; every app context may use
|
||||
`libs/shared` and `libs/beheer`, nothing imports `showcase`:
|
||||
|
||||
| Context | May import |
|
||||
| ---------------- | ------------------------------------- |
|
||||
| `shared` | (base — no feature context) |
|
||||
| `auth` | `shared` |
|
||||
| `registratie` | `shared` |
|
||||
| `herregistratie` | `registratie`, `shared` |
|
||||
| `brief` | `shared` |
|
||||
| `beheer` | `shared` |
|
||||
| `showcase` | everything (sanctioned teaching page) |
|
||||
| Context (apps/ssp) | May import |
|
||||
| ------------------ | --------------------------------------------------- |
|
||||
| `auth` | `libs/shared`, `libs/beheer` |
|
||||
| `registratie` | `libs/shared`, `libs/beheer` |
|
||||
| `herregistratie` | `registratie`, `libs/shared`, `libs/beheer` |
|
||||
| `brief` | `libs/shared`, `libs/beheer` |
|
||||
| `showcase` | everything in `apps/ssp` (sanctioned teaching page) |
|
||||
|
||||
| Context (apps/behandelportal) | May import |
|
||||
| ----------------------------- | ---------------------------- |
|
||||
| `auth` | `libs/shared`, `libs/beheer` |
|
||||
| `behandeling` | `libs/shared`, `libs/beheer` |
|
||||
|
||||
`libs/shared` is the base (no feature context, no `libs/beheer` — that direction is forbidden too,
|
||||
so shared never grows a dependency on a peer library). `libs/beheer` is a real bounded context
|
||||
(admin/stamdata), used identically by both apps — it may depend on `libs/shared`, never the
|
||||
reverse. An app may not import the other app's source directly.
|
||||
|
||||
**Atomic-layer rules:** `domain/` is framework-free (no Angular); `contracts/` import nothing
|
||||
(pure wire DTOs, ADR-0001); `ui/` + `layout/` never import `infrastructure/` directly (reach data
|
||||
through an application store/command — type-only DTO imports are fine); the generated `ApiClient`
|
||||
is a value only inside `infrastructure/` (+ `shared/upload`). Plus **no circular** dependencies.
|
||||
Sanctioned exceptions: `shared/ui/debug-state` (dev panel) and `showcase`.
|
||||
is a value only inside `infrastructure/` (+ `libs/shared/src/upload`). Plus **no circular**
|
||||
dependencies. These apply uniformly across an app's tree and both libraries — no debug-state
|
||||
exception anymore (WP-67 moved the dev panel component out of `libs/shared` into `apps/ssp` since
|
||||
it's genuinely SSP-specific, coupled to `BigProfileStore`; the shared `ShellComponent` hosts
|
||||
whichever app-provided component the `DEBUG_PANEL` injection token supplies, or none).
|
||||
|
||||
## See the graph
|
||||
|
||||
```bash
|
||||
npm run dep:graph # regenerates docs/reference/architecture/dependency-graph.md (mermaid)
|
||||
npm run dep:graph # regenerates docs/reference/architecture/dependency-graph.md (mermaid) — one diagram per app
|
||||
```
|
||||
|
||||
[dependency-graph.md](./dependency-graph.md) is the generated, committed view — contexts × atomic
|
||||
@@ -39,7 +54,7 @@ change.
|
||||
## Enforce
|
||||
|
||||
```bash
|
||||
npm run dep:check # fails on any forbidden edge; part of `npm run ci` and CI
|
||||
npm run dep:check # runs both apps' configs; fails on any forbidden edge; part of `npm run ci` and CI
|
||||
```
|
||||
|
||||
A violation prints the offending `from → to` and the rule name. `dep:check` runs in the local gate
|
||||
@@ -48,10 +63,13 @@ A violation prints the offending `from → to` and the rule name. `dep:check` ru
|
||||
## What still lives in ESLint
|
||||
|
||||
Only the non-dependency rules: `@typescript-eslint/no-explicit-any` and the angular-eslint template
|
||||
accessibility bundle (see `eslint.config.mjs`). Everything about _who may import whom_ is in
|
||||
dependency-cruiser.
|
||||
accessibility bundle (see `eslint.config.mjs`, scoped to `{apps,libs}/**`). Everything about _who
|
||||
may import whom_ is in dependency-cruiser.
|
||||
|
||||
## Adding a context
|
||||
|
||||
Add one `contextRule(...)` entry in `.dependency-cruiser.js` (and the tsconfig path alias + lazy
|
||||
route) — no more hand-copying ESLint blocks. The `new-context` skill covers the full checklist.
|
||||
Add one context entry to the relevant app's object literal in `.dependency-cruiser.ssp.js` (or
|
||||
`.dependency-cruiser.behandelportal.js`) — passed straight into the shared `buildConfig` factory in
|
||||
`.dependency-cruiser.base.js` (and the tsconfig path alias + lazy route) — no more hand-copying
|
||||
ESLint blocks. The `new-context` skill / `gen:context` (`plopfile.mjs`, ssp-only today) covers the
|
||||
full checklist.
|
||||
|
||||
@@ -1,195 +1,516 @@
|
||||
# Dependency graph
|
||||
|
||||
_Generated by `npm run dep:graph` — do not edit by hand._ Nodes are context × atomic
|
||||
layer (`src/app/<context>/<layer>`); edges are real imports. The allowed edges are
|
||||
enforced by `npm run dep:check` (dependency-cruiser); see [dependencies.md](./dependencies.md).
|
||||
layer (`apps/<app>/src/app/<context>/<layer>` or `libs/<lib>/src/<layer>`); edges are
|
||||
real imports. The allowed edges are enforced by `npm run dep:check` (dependency-cruiser,
|
||||
one config per app — see [dependencies.md](./dependencies.md)).
|
||||
|
||||
## ssp
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
||||
subgraph 0["src"]
|
||||
subgraph 1["app"]
|
||||
2["app.config.ts"]
|
||||
3["app.routes.ts"]
|
||||
4["app.ts"]
|
||||
subgraph 5["auth"]
|
||||
6["application"]
|
||||
7["auth.guard.spec.ts"]
|
||||
8["auth.guard.ts"]
|
||||
9["domain"]
|
||||
A["infrastructure"]
|
||||
B["ui"]
|
||||
subgraph 0["apps"]
|
||||
subgraph 1["ssp"]
|
||||
subgraph 2["src"]
|
||||
subgraph 3["app"]
|
||||
4["app.config.ts"]
|
||||
5["app.routes.ts"]
|
||||
6["app.ts"]
|
||||
7["auth"]
|
||||
8["brief"]
|
||||
9["herregistratie"]
|
||||
A["registratie"]
|
||||
B["shell"]
|
||||
C["showcase"]
|
||||
end
|
||||
subgraph C["beheer"]
|
||||
D["application"]
|
||||
E["contracts"]
|
||||
F["domain"]
|
||||
G["infrastructure"]
|
||||
H["ui"]
|
||||
D["main.ts"]
|
||||
end
|
||||
subgraph I["brief"]
|
||||
J["application"]
|
||||
K["domain"]
|
||||
L["infrastructure"]
|
||||
end
|
||||
end
|
||||
subgraph E["libs"]
|
||||
subgraph F["beheer"]
|
||||
subgraph G["src"]
|
||||
H["application"]
|
||||
I["contracts"]
|
||||
J["domain"]
|
||||
K["infrastructure"]
|
||||
L["test-entry.ts"]
|
||||
M["ui"]
|
||||
end
|
||||
subgraph N["herregistratie"]
|
||||
O["application"]
|
||||
P["domain"]
|
||||
Q["infrastructure"]
|
||||
R["ui"]
|
||||
end
|
||||
subgraph S["registratie"]
|
||||
T["application"]
|
||||
U["contracts"]
|
||||
V["domain"]
|
||||
W["infrastructure"]
|
||||
X["ui"]
|
||||
end
|
||||
subgraph Y["shared"]
|
||||
Z["application"]
|
||||
10["domain"]
|
||||
11["infrastructure"]
|
||||
12["kernel"]
|
||||
13["layout"]
|
||||
14["ui"]
|
||||
15["upload"]
|
||||
end
|
||||
subgraph 16["showcase"]
|
||||
17["concepts.page.ts"]
|
||||
18["highlight-ts.spec.ts"]
|
||||
19["highlight-ts.ts"]
|
||||
1A["snippets.generated.ts"]
|
||||
subgraph N["shared"]
|
||||
subgraph O["src"]
|
||||
P["application"]
|
||||
Q["domain"]
|
||||
R["environments"]
|
||||
S["infrastructure"]
|
||||
T["kernel"]
|
||||
U["layout"]
|
||||
V["test-entry.ts"]
|
||||
W["ui"]
|
||||
X["upload"]
|
||||
end
|
||||
end
|
||||
end
|
||||
2-->3
|
||||
2-->6
|
||||
2-->Z
|
||||
2-->11
|
||||
2-->13
|
||||
3-->17
|
||||
3-->8
|
||||
3-->B
|
||||
3-->H
|
||||
3-->M
|
||||
3-->R
|
||||
3-->X
|
||||
3-->Z
|
||||
3-->13
|
||||
6-->9
|
||||
6-->A
|
||||
6-->12
|
||||
7-->6
|
||||
7-->8
|
||||
7-->Z
|
||||
8-->6
|
||||
8-->Z
|
||||
8-->10
|
||||
A-->9
|
||||
subgraph Y["node_modules"]
|
||||
subgraph Z["@angular"]
|
||||
subgraph 10["common"]
|
||||
subgraph 11["fesm2022"]
|
||||
12["common.mjs"]
|
||||
13["http.mjs"]
|
||||
end
|
||||
subgraph 14["locales"]
|
||||
15["en.js"]
|
||||
16["nl.js"]
|
||||
end
|
||||
end
|
||||
subgraph 17["core"]
|
||||
subgraph 18["fesm2022"]
|
||||
19["core.mjs"]
|
||||
1A["rxjs-interop.mjs"]
|
||||
1B["testing.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1C["forms"]
|
||||
subgraph 1D["fesm2022"]
|
||||
1E["forms.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1F["localize"]
|
||||
subgraph 1G["fesm2022"]
|
||||
1H["localize.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1I["platform-browser"]
|
||||
subgraph 1J["fesm2022"]
|
||||
1K["platform-browser.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1L["router"]
|
||||
subgraph 1M["fesm2022"]
|
||||
1N["router.mjs"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1O["@storybook"]
|
||||
subgraph 1P["angular"]
|
||||
subgraph 1Q["dist"]
|
||||
1R["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1S["rxjs"]
|
||||
subgraph 1T["dist"]
|
||||
subgraph 1U["cjs"]
|
||||
1V["index.js"]
|
||||
subgraph 1W["operators"]
|
||||
1X["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1Y["storybook"]
|
||||
subgraph 1Z["dist"]
|
||||
subgraph 20["test"]
|
||||
21["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 22["vitest"]
|
||||
subgraph 23["dist"]
|
||||
24["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
4-->5
|
||||
4-->B
|
||||
4-->12
|
||||
4-->13
|
||||
4-->15
|
||||
4-->16
|
||||
4-->19
|
||||
4-->1N
|
||||
4-->7
|
||||
4-->P
|
||||
4-->S
|
||||
4-->U
|
||||
5-->C
|
||||
5-->1N
|
||||
5-->7
|
||||
5-->M
|
||||
5-->8
|
||||
5-->9
|
||||
5-->A
|
||||
5-->P
|
||||
5-->U
|
||||
6-->19
|
||||
6-->1N
|
||||
7-->19
|
||||
7-->1N
|
||||
7-->P
|
||||
7-->Q
|
||||
7-->T
|
||||
7-->U
|
||||
7-->W
|
||||
7-->1E
|
||||
7-->1B
|
||||
7-->24
|
||||
7-->1R
|
||||
8-->19
|
||||
8-->U
|
||||
8-->W
|
||||
8-->P
|
||||
8-->T
|
||||
8-->X
|
||||
8-->S
|
||||
8-->R
|
||||
8-->1E
|
||||
8-->12
|
||||
8-->1B
|
||||
8-->24
|
||||
8-->1R
|
||||
9-->19
|
||||
9-->1E
|
||||
9-->A
|
||||
9-->P
|
||||
9-->T
|
||||
9-->U
|
||||
9-->W
|
||||
9-->X
|
||||
9-->S
|
||||
9-->24
|
||||
9-->13
|
||||
9-->1R
|
||||
9-->1B
|
||||
A-->T
|
||||
A-->19
|
||||
A-->P
|
||||
A-->S
|
||||
A-->1N
|
||||
A-->12
|
||||
B-->6
|
||||
B-->13
|
||||
B-->14
|
||||
D-->F
|
||||
D-->G
|
||||
D-->Z
|
||||
D-->12
|
||||
F-->12
|
||||
G-->F
|
||||
G-->11
|
||||
G-->12
|
||||
G-->Z
|
||||
H-->D
|
||||
H-->Z
|
||||
H-->13
|
||||
H-->14
|
||||
H-->F
|
||||
J-->K
|
||||
J-->L
|
||||
J-->Z
|
||||
J-->12
|
||||
J-->15
|
||||
K-->12
|
||||
K-->15
|
||||
L-->K
|
||||
L-->Z
|
||||
L-->11
|
||||
L-->12
|
||||
M-->J
|
||||
M-->13
|
||||
M-->14
|
||||
M-->K
|
||||
A-->W
|
||||
A-->U
|
||||
A-->B
|
||||
A-->Q
|
||||
A-->1E
|
||||
A-->X
|
||||
A-->1B
|
||||
A-->24
|
||||
A-->1R
|
||||
A-->13
|
||||
B-->Q
|
||||
B-->U
|
||||
B-->12
|
||||
B-->19
|
||||
B-->7
|
||||
B-->A
|
||||
B-->P
|
||||
B-->S
|
||||
B-->T
|
||||
B-->1R
|
||||
C-->19
|
||||
C-->1E
|
||||
C-->9
|
||||
C-->A
|
||||
C-->T
|
||||
C-->U
|
||||
C-->W
|
||||
C-->24
|
||||
D-->6
|
||||
D-->4
|
||||
D-->1H
|
||||
D-->1K
|
||||
H-->19
|
||||
H-->J
|
||||
H-->K
|
||||
H-->P
|
||||
H-->1B
|
||||
H-->T
|
||||
H-->24
|
||||
J-->T
|
||||
J-->24
|
||||
K-->19
|
||||
K-->J
|
||||
K-->S
|
||||
K-->T
|
||||
K-->P
|
||||
K-->24
|
||||
M-->12
|
||||
M-->Z
|
||||
M-->15
|
||||
O-->P
|
||||
O-->Q
|
||||
P-->V
|
||||
P-->12
|
||||
P-->15
|
||||
Q-->P
|
||||
Q-->11
|
||||
Q-->12
|
||||
R-->P
|
||||
M-->19
|
||||
M-->H
|
||||
M-->P
|
||||
M-->U
|
||||
M-->W
|
||||
M-->J
|
||||
M-->1R
|
||||
P-->19
|
||||
P-->T
|
||||
P-->1N
|
||||
P-->S
|
||||
P-->1A
|
||||
P-->Q
|
||||
P-->1V
|
||||
P-->24
|
||||
P-->1B
|
||||
S-->13
|
||||
S-->19
|
||||
S-->R
|
||||
S-->1V
|
||||
S-->Q
|
||||
S-->T
|
||||
S-->1X
|
||||
S-->24
|
||||
T-->24
|
||||
U-->19
|
||||
U-->1E
|
||||
U-->W
|
||||
U-->Q
|
||||
U-->12
|
||||
U-->1N
|
||||
U-->1A
|
||||
U-->1V
|
||||
U-->P
|
||||
U-->1X
|
||||
U-->1R
|
||||
U-->24
|
||||
W-->19
|
||||
W-->1E
|
||||
W-->X
|
||||
W-->12
|
||||
W-->1N
|
||||
W-->P
|
||||
W-->T
|
||||
W-->1R
|
||||
W-->21
|
||||
W-->24
|
||||
X-->T
|
||||
X-->19
|
||||
X-->P
|
||||
X-->S
|
||||
X-->R
|
||||
X-->24
|
||||
```
|
||||
|
||||
## behandelportal
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
||||
subgraph 0["apps"]
|
||||
subgraph 1["behandelportal"]
|
||||
subgraph 2["src"]
|
||||
subgraph 3["app"]
|
||||
4["app.config.ts"]
|
||||
5["app.routes.ts"]
|
||||
6["app.ts"]
|
||||
7["auth"]
|
||||
8["behandeling"]
|
||||
9["shell"]
|
||||
end
|
||||
A["main.ts"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph B["libs"]
|
||||
subgraph C["beheer"]
|
||||
subgraph D["src"]
|
||||
E["application"]
|
||||
F["contracts"]
|
||||
G["domain"]
|
||||
H["infrastructure"]
|
||||
I["test-entry.ts"]
|
||||
J["ui"]
|
||||
end
|
||||
end
|
||||
subgraph K["shared"]
|
||||
subgraph L["src"]
|
||||
M["application"]
|
||||
N["domain"]
|
||||
O["environments"]
|
||||
P["infrastructure"]
|
||||
Q["kernel"]
|
||||
R["layout"]
|
||||
S["test-entry.ts"]
|
||||
T["ui"]
|
||||
U["upload"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph V["node_modules"]
|
||||
subgraph W["@angular"]
|
||||
subgraph X["common"]
|
||||
subgraph Y["fesm2022"]
|
||||
Z["common.mjs"]
|
||||
10["http.mjs"]
|
||||
end
|
||||
subgraph 11["locales"]
|
||||
12["en.js"]
|
||||
13["nl.js"]
|
||||
end
|
||||
end
|
||||
subgraph 14["core"]
|
||||
subgraph 15["fesm2022"]
|
||||
16["core.mjs"]
|
||||
17["rxjs-interop.mjs"]
|
||||
18["testing.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 19["forms"]
|
||||
subgraph 1A["fesm2022"]
|
||||
1B["forms.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1C["localize"]
|
||||
subgraph 1D["fesm2022"]
|
||||
1E["localize.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1F["platform-browser"]
|
||||
subgraph 1G["fesm2022"]
|
||||
1H["platform-browser.mjs"]
|
||||
end
|
||||
end
|
||||
subgraph 1I["router"]
|
||||
subgraph 1J["fesm2022"]
|
||||
1K["router.mjs"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1L["@storybook"]
|
||||
subgraph 1M["angular"]
|
||||
subgraph 1N["dist"]
|
||||
1O["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1P["rxjs"]
|
||||
subgraph 1Q["dist"]
|
||||
subgraph 1R["cjs"]
|
||||
1S["index.js"]
|
||||
subgraph 1T["operators"]
|
||||
1U["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1V["storybook"]
|
||||
subgraph 1W["dist"]
|
||||
subgraph 1X["test"]
|
||||
1Y["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
subgraph 1Z["vitest"]
|
||||
subgraph 20["dist"]
|
||||
21["index.js"]
|
||||
end
|
||||
end
|
||||
end
|
||||
4-->5
|
||||
4-->9
|
||||
4-->Z
|
||||
4-->10
|
||||
4-->12
|
||||
4-->13
|
||||
4-->16
|
||||
4-->1K
|
||||
4-->7
|
||||
4-->M
|
||||
4-->P
|
||||
4-->R
|
||||
5-->1K
|
||||
5-->7
|
||||
5-->8
|
||||
5-->J
|
||||
5-->R
|
||||
6-->16
|
||||
6-->1K
|
||||
7-->16
|
||||
7-->1K
|
||||
7-->M
|
||||
7-->N
|
||||
7-->Q
|
||||
7-->R
|
||||
7-->T
|
||||
7-->1B
|
||||
7-->18
|
||||
7-->21
|
||||
7-->1O
|
||||
8-->16
|
||||
8-->R
|
||||
9-->R
|
||||
A-->6
|
||||
A-->4
|
||||
A-->1E
|
||||
A-->1H
|
||||
E-->16
|
||||
E-->G
|
||||
E-->H
|
||||
E-->M
|
||||
E-->18
|
||||
E-->Q
|
||||
E-->21
|
||||
G-->Q
|
||||
G-->21
|
||||
H-->16
|
||||
H-->G
|
||||
H-->P
|
||||
H-->Q
|
||||
H-->M
|
||||
H-->21
|
||||
J-->Z
|
||||
J-->16
|
||||
J-->E
|
||||
J-->M
|
||||
J-->R
|
||||
J-->T
|
||||
J-->G
|
||||
J-->1O
|
||||
M-->16
|
||||
M-->17
|
||||
M-->N
|
||||
M-->P
|
||||
M-->1S
|
||||
M-->Q
|
||||
M-->1K
|
||||
M-->21
|
||||
M-->18
|
||||
P-->16
|
||||
P-->N
|
||||
P-->Q
|
||||
P-->10
|
||||
P-->O
|
||||
P-->1S
|
||||
P-->1U
|
||||
P-->21
|
||||
Q-->21
|
||||
R-->16
|
||||
R-->T
|
||||
R-->Z
|
||||
R-->12
|
||||
R-->13
|
||||
R-->14
|
||||
R-->15
|
||||
R-->O
|
||||
R-->V
|
||||
R-->11
|
||||
T-->U
|
||||
T-->V
|
||||
T-->W
|
||||
R-->1K
|
||||
R-->17
|
||||
R-->1S
|
||||
R-->M
|
||||
R-->1U
|
||||
R-->N
|
||||
R-->1O
|
||||
R-->21
|
||||
R-->1B
|
||||
T-->16
|
||||
T-->1B
|
||||
T-->Z
|
||||
T-->11
|
||||
T-->12
|
||||
V-->12
|
||||
V-->15
|
||||
W-->V
|
||||
W-->11
|
||||
W-->12
|
||||
W-->U
|
||||
X-->V
|
||||
X-->14
|
||||
X-->T
|
||||
X-->13
|
||||
X-->Z
|
||||
X-->12
|
||||
X-->10
|
||||
X-->U
|
||||
X-->15
|
||||
X-->11
|
||||
Z-->12
|
||||
Z-->11
|
||||
Z-->10
|
||||
11-->10
|
||||
11-->12
|
||||
13-->14
|
||||
13-->10
|
||||
13-->Z
|
||||
14-->15
|
||||
14-->Z
|
||||
14-->12
|
||||
14-->6
|
||||
14-->9
|
||||
14-->T
|
||||
14-->10
|
||||
14-->11
|
||||
14-->V
|
||||
15-->12
|
||||
15-->Z
|
||||
15-->11
|
||||
17-->19
|
||||
17-->1A
|
||||
17-->R
|
||||
17-->V
|
||||
17-->X
|
||||
17-->12
|
||||
17-->13
|
||||
17-->14
|
||||
18-->19
|
||||
T-->1K
|
||||
T-->M
|
||||
T-->1O
|
||||
T-->1Y
|
||||
T-->Q
|
||||
T-->21
|
||||
T-->U
|
||||
U-->Q
|
||||
U-->16
|
||||
U-->M
|
||||
U-->P
|
||||
U-->O
|
||||
U-->21
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user