This PR now covers the full Phase 11 (Behandelportal) arc, not just the original monorepo merge — it closes out the entire showcase-hardening backlog (WP-01 through WP-67, all done).
WP-67: merge the behandelportal sibling repo into this repo as a monorepo (apps/ssp + apps/behandelportal sharing libs/shared + libs/beheer + one CI/backend).
WP-64: behandelportal's first real screen — the werkvoorraad (queue) of aanvragen needing treatment, gated by the medewerker aanvraag:beoordelen capability (not the admin role).
WP-65a: beoordeling detail (read) screen + a fix for an unreachable medewerker login path.
WP-65b: beoordeling besluit — the behandelaar's decision write (Goedkeuren/Afwijzen/MeerInfoOpvragen), advancing the WP-63 status lifecycle locally.
WP-66: wires that decision into OpenZaak — IZaakSource.RecordBesluit (mirroring WP-50's CreateZaak write pattern) POSTs a new Statussen entry to the zaak when Zgw:Enabled=true; a failure is flagged via RecordZgwDivergence, the same way submit's create-zaak/document writes are (closing WP-60's "second write pair" gap). LocalZaakSource stays a no-op, so the behandelportal is unchanged when ZGW is off.
Two housekeeping commits: a CI fix (test-storybook config-dir + missing behandelportal a11y coverage) and a docs fix (stale WP-65 status in the backlog README).
Test plan
npm run ci green (lint, dep:check ×2, both apps' localized builds, both Storybook builds, tests, backend tests, api-client drift)
cd backend && dotnet test — 204/204 passing
New unit tests for OpenZaakZaakSource.RecordBesluit against a stub HttpMessageHandler (posts the last statustype with besluit+toelichting; no-ops when the aanvraag has no zaak; throws on an unconfigured zaaktype)
Manual: npm start (ssp :4200) and npm run start:behandelportal (:4201) against the shared backend, logged in on both
Residual risk (documented in WP-66): no live-harness (OpenZaakIntegrationTests) round-trip added for the besluit write yet — only stub-based unit tests, same gap WP-50 originally shipped with before WP-54's harness caught a real bug (Content-Crs header). Worth closing before relying on this in a real deployment.
## Summary
This PR now covers the full Phase 11 (Behandelportal) arc, not just the original monorepo merge — it closes out the entire showcase-hardening backlog (WP-01 through WP-67, all `done`).
- **WP-67**: merge the behandelportal sibling repo into this repo as a monorepo (`apps/ssp` + `apps/behandelportal` sharing `libs/shared` + `libs/beheer` + one CI/backend).
- **WP-64**: behandelportal's first real screen — the werkvoorraad (queue) of aanvragen needing treatment, gated by the medewerker `aanvraag:beoordelen` capability (not the admin role).
- **WP-65a**: beoordeling detail (read) screen + a fix for an unreachable medewerker login path.
- **WP-65b**: beoordeling besluit — the behandelaar's decision write (Goedkeuren/Afwijzen/MeerInfoOpvragen), advancing the WP-63 status lifecycle locally.
- **WP-66**: wires that decision into OpenZaak — `IZaakSource.RecordBesluit` (mirroring WP-50's `CreateZaak` write pattern) POSTs a new Statussen entry to the zaak when `Zgw:Enabled=true`; a failure is flagged via `RecordZgwDivergence`, the same way submit's create-zaak/document writes are (closing WP-60's "second write pair" gap). `LocalZaakSource` stays a no-op, so the behandelportal is unchanged when ZGW is off.
- Two housekeeping commits: a CI fix (test-storybook config-dir + missing behandelportal a11y coverage) and a docs fix (stale WP-65 status in the backlog README).
## Test plan
- [x] `npm run ci` green (lint, dep:check ×2, both apps' localized builds, both Storybook builds, tests, backend tests, api-client drift)
- [x] `cd backend && dotnet test` — 204/204 passing
- [x] New unit tests for `OpenZaakZaakSource.RecordBesluit` against a stub `HttpMessageHandler` (posts the last statustype with besluit+toelichting; no-ops when the aanvraag has no zaak; throws on an unconfigured zaaktype)
- [x] Manual: `npm start` (ssp :4200) and `npm run start:behandelportal` (:4201) against the shared backend, logged in on both
- [ ] Residual risk (documented in WP-66): no live-harness (`OpenZaakIntegrationTests`) round-trip added for the besluit write yet — only stub-based unit tests, same gap WP-50 originally shipped with before WP-54's harness caught a real bug (`Content-Crs` header). Worth closing before relying on this in a real deployment.
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>
New GET /werkvoorraad endpoint lists aanvragen still open (Ingediend/InBehandeling),
gated by the medewerker capability (CanBeoordelen) rather than the admin role — reuses
the existing ApplicationSummaryDto, no new DTO. GET /me now surfaces aanvraag:beoordelen
for a behandelaar so the FE can gate with the same AccessStore/capabilityGuard idiom
every other page uses.
FE: a behandeling domain type deliberately narrower than ssp's full AanvraagStatus
union (only the two open tags — illegal states unrepresentable), composed into a
werkvoorraad-list organism from existing shared/ui molecules. Replaces WP-61's
scaffold placeholder as the app's real landing page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New GET /beoordeling/{id} shows one aanvraag's status, linked documents, and a
canBesluiten decision flag, gated by the same CanBeoordelen capability as the
werkvoorraad list. Reads through IZaakSource.ListCases rather than a new seam
method (WP-66 needs one anyway for the real write); owner BSN is masked.
Fixes a real gap found while wiring this up: the behandelportal's login was still
WP-61's copied citizen/BSN DigiD flow, so nothing ever sent X-Medewerker and the
werkvoorraad screen (WP-64) always denied in a real browser. A dev-only
medewerkerInterceptor (mirrors the existing ?role= stand-in as ?rollen=) fixes that.
WP-65's own Risks note authorized splitting read from write across sessions given
its size; this is the read half. The decision-recording mutation is next (65b).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds POST /beoordeling/{id}/besluit: a Besluit enum (Goedkeuren/Afwijzen/
MeerInfoOpvragen) backed by new Aanvraag.BesluitStatus/BesluitToelichting
columns, gated by the same BeoordelingRules.CanDecide the read side's
canBesluiten flag already uses (409 on an illegal transition, 400 on a
missing required toelichting). Mappers.ToStatusDto gains the "a recorded
decision wins" branch. FE: besluit.machine.ts + besluit-form organism
(same form idiom as change-request-form), wired into the beoordeling page
behind the server's canBesluiten flag.
Completes WP-65 (65a + 65b) — verified end-to-end against a running
backend (werkvoorraad -> beoordeling -> besluit -> status reflected back).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WP-65's summary-table row still said "in progress (65a done)" from before
65b shipped, and that one oversized cell was forcing prettier to want to
re-pad the entire ~65-row table. Correcting it to "done" (its actual
status, per the WP-65 file's own outcome notes) removes the outlier —
npm run format:check is green again with zero other rows touched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
WP-67's monorepo split renamed .storybook to .storybook-ssp/
.storybook-behandelportal, but test-storybook still defaulted to the
(now nonexistent) plain .storybook dir -- it loads <config-dir>/main.js
for test-runner hooks even in --url mode, so every invocation failed
with "Could not load main.js in .storybook".
Also: test-storybook:ci only ever built+served+tested the ssp instance.
Since the split, behandelportal's stories (werkvoorraad, beoordeling,
besluit-form, ...) were never axe-tested in CI at all. Added the
:behandelportal siblings (mirroring the existing storybook/
build-storybook naming) and wired them into ci-local.sh's --full step
and the storybook-a11y GitHub Actions job.
Verified directly: ssp 62/180 stories green, behandelportal 45/112
green (including the new besluit-form story), full `ci-local.sh --full`
green end-to-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extends IZaakSource with RecordBesluit, mirroring WP-50's CreateZaak write
pattern: OpenZaakZaakSource POSTs a new Statussen entry (highest-volgnummer
statustype, since the harness catalogus has no per-outcome besluittype),
carrying the besluit + toelichting in statustoelichting; LocalZaakSource
no-ops. The beoordeling endpoint calls it after the local decision commits,
flagging a failure via RecordZgwDivergence the same way submit's
create-zaak/document writes do — closing WP-60's "second write pair" gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
This PR now covers the full Phase 11 (Behandelportal) arc, not just the original monorepo merge — it closes out the entire showcase-hardening backlog (WP-01 through WP-67, all
done).apps/ssp+apps/behandelportalsharinglibs/shared+libs/beheer+ one CI/backend).aanvraag:beoordelencapability (not the admin role).IZaakSource.RecordBesluit(mirroring WP-50'sCreateZaakwrite pattern) POSTs a new Statussen entry to the zaak whenZgw:Enabled=true; a failure is flagged viaRecordZgwDivergence, the same way submit's create-zaak/document writes are (closing WP-60's "second write pair" gap).LocalZaakSourcestays a no-op, so the behandelportal is unchanged when ZGW is off.Test plan
npm run cigreen (lint, dep:check ×2, both apps' localized builds, both Storybook builds, tests, backend tests, api-client drift)cd backend && dotnet test— 204/204 passingOpenZaakZaakSource.RecordBesluitagainst a stubHttpMessageHandler(posts the last statustype with besluit+toelichting; no-ops when the aanvraag has no zaak; throws on an unconfigured zaaktype)npm start(ssp :4200) andnpm run start:behandelportal(:4201) against the shared backend, logged in on bothOpenZaakIntegrationTests) round-trip added for the besluit write yet — only stub-based unit tests, same gap WP-50 originally shipped with before WP-54's harness caught a real bug (Content-Crsheader). Worth closing before relying on this in a real deployment.New GET /beoordeling/{id} shows one aanvraag's status, linked documents, and a canBesluiten decision flag, gated by the same CanBeoordelen capability as the werkvoorraad list. Reads through IZaakSource.ListCases rather than a new seam method (WP-66 needs one anyway for the real write); owner BSN is masked. Fixes a real gap found while wiring this up: the behandelportal's login was still WP-61's copied citizen/BSN DigiD flow, so nothing ever sent X-Medewerker and the werkvoorraad screen (WP-64) always denied in a real browser. A dev-only medewerkerInterceptor (mirrors the existing ?role= stand-in as ?rollen=) fixes that. WP-65's own Risks note authorized splitting read from write across sessions given its size; this is the read half. The decision-recording mutation is next (65b). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Adds POST /beoordeling/{id}/besluit: a Besluit enum (Goedkeuren/Afwijzen/ MeerInfoOpvragen) backed by new Aanvraag.BesluitStatus/BesluitToelichting columns, gated by the same BeoordelingRules.CanDecide the read side's canBesluiten flag already uses (409 on an illegal transition, 400 on a missing required toelichting). Mappers.ToStatusDto gains the "a recorded decision wins" branch. FE: besluit.machine.ts + besluit-form organism (same form idiom as change-request-form), wired into the beoordeling page behind the server's canBesluiten flag. Completes WP-65 (65a + 65b) — verified end-to-end against a running backend (werkvoorraad -> beoordeling -> besluit -> status reflected back). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Monorepo: merge behandelportal + WP-64 werkvoorraad screento Behandelportal: monorepo merge + WP-64..67 backoffice arc (OpenZaak write closes it out)