Files
ehoandClaude Opus 5 25a5d415a5 docs(adr): land ADR-C-001, ADR-C-003, ADR-C-007 and ADR-C-009
The architect approved the four ADR-fix tickets. All four change what the
architecture documents claim. No code changes.

ADR-0001, ADR-C-001: the worked example claimed the POC has no real backend.
It rewrites against `backend/src/BigRegister.Api`. Every path it named is
repointed. The out-of-scope list drops two discharged bullets: 33 `parse*`
boundaries exist, and `npm run gen:api` is real.

ADR-0001, ADR-C-003: a new section states that the generated client is the wire
contract. A hand-written `contracts/*.dto.ts` is the exception for two cases
only. The four survivors stay, because NSwag emits every property as optional
and flattens `RegistrationStatusDto` into five optional strings. The `parse*`
trust boundary stays mandatory, because a generated type is a compile-time
claim about the wire and not a runtime guarantee.

ADR-0003, ADR-C-007: four paths moved in WP-67 and are repointed. Point 4 kept
the principle and changed its example to `skeleton` and `spinner`. Two of its
claims were false and the amendment says so: `app-alert` wraps the vendored
`.feedback` classes, and `site-header` composes the vendored `.titlebar`.

ADR-0004, ADR-C-009: the exception section states a four-part test instead of
one named exception. `OrgTemplateStore` and `FeatureFlagStore` both pass it. RB-07
gated this ticket, because clause 4 needs an audited allow path. RB-07 landed
that, so the ADR does not ratify a control that the code lacks.

Three tickets need a matching CLAUDE.md correction in the same diff. CLAUDE.md
section 2 loses the false `alert` example. Section 4 gets the generated-client
rule and the four-part test.

Two findings were wrong. ADR-C-001 asked to keep an out-of-scope bullet that
reads "SessionStore is in-memory". The session persists to `localStorage` now,
so the bullet covers multi-tab sync only. ADR-C-007 flagged one half of point 4
and missed that the other half is equally false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:29:05 +02:00

5.2 KiB

ADR-0003 — Adopt the CIBG Huisstijl (Bootstrap 5.2) as the design system

Status: Accepted · Date: 2026-07-02

Context

The portal must adhere visually to the CIBG design system (designsystem.cibg.nl) — CIBG runs the real BIG-register. Until now the app was themed with Rijkshuisstijl-Community / Utrecht (@rijkshuisstijl-community/components-css + design-tokens), emitting utrecht-*/rhc-* classes and consuming a --rhc-* CSS-custom-property token set.

CIBG Huisstijl (@cibg/huisstijl, v3.22.0) is a customized build of Bootstrap 5.2.0: standard Bootstrap CSS classes (btn, form-control, card, table, breadcrumb) themed via SCSS variable overrides, with a full --bs-* runtime variable surface. For SPA frameworks it is CSS-only — no Bootstrap JS (the framework drives interactivity). The two systems share neither class names nor tokens, so adopting CIBG is a re-skin of the shared component layer plus a replacement of the token layer — not a palette swap.

Decision

  1. Vendor the package under public/cibg-huisstijl/ (not an npm dep — it was delivered as files), loaded via a <link> in each app's index.html (apps/ssp/src/index.html and apps/behandelportal/src/index.html — two since WP-67) so the CSS's relative url(../fonts|icons|images) references resolve at runtime. public/ stays at the repo root and both apps' angular.json targets copy it. Both Storybook instances serve the same via staticDirs: ['../public'].

  2. Token bridge over token rewrite. libs/shared/styles.scss — one copy, both apps' angular.json point at it (WP-67) — redefines the app's ~54 --rhc-* tokens onto CIBG values (--bs-* where one exists, CIBG palette hex otherwise). The --rhc-* names are now an internal alias set; the values are CIBG. This avoided rewriting 300+ token references and keeps the "components reference tokens" convention intact. (libs/shared/styles.scss is exempt from check:tokens, so palette hex lives in that one file only.)

  3. Re-skin atoms, keep their input() APIs. Each shared/ui atom now emits Bootstrap/CIBG classes (app-buttonbtn btn-primary, text-inputform-control, radio/checkbox → form-check-*); domain pages compose the same atoms and barely changed.

  4. Hand-roll what CIBG's build drops, and mark it. Where the vendored build has no class for a concept, the component is a small token-styled surface carrying a // CIBG-GAP EXTENSION: marker. The clearest live examples are skeleton and spinner: CIBG documents "Laadindicatie" but the vendored build ships no loading-skeleton or loading-spinner class, so both are built from the token bridge. Local class names that collide with Bootstrap components were renamed (.card.app-card, badge → .status-badge).

    Two claims this point used to make were wrong and are corrected here. .alert is not a gap: app-alert is a thin wrapper over the vendored .feedback feedback-* classes — the design system owns surface and icon, and the component adds only the icon's a11y label and a flex fix. It carries no gap marker, correctly. The header is not hand-rolled either: site-header composes the vendored .titlebar and .logo__* classes and leaves the robijn fill (--ro-layout) untouched. The shell template's .layout/.main/.content classes are page scaffolding, not a substitute for a missing design-system component, so they carry no marker either.

  5. System-font stack; no licensed fonts. --bs-font-sans-serif is overridden to system-ui; the licensed RO/Rijks text woffs are removed from the vendored copy (CIBG icon font kept). Logo stays a text wordmark. Interactivity stays Angular-driven (no Bootstrap JS).

Consequences

  • Wiring the design system touches libs/shared/styles.scss (token bridge), both apps' index.html, both angular.json targets (public/ already copied), and both Storybook config dirs (.storybook-ssp/ and .storybook-behandelportal/ — separate since WP-67, because a single merged tsconfig cannot resolve both apps' @auth/* at once) — plus the class strings in ~40 libs/shared/ui + libs/shared/layout + a few domain components. The @rijkshuisstijl-community/* deps are dropped.
  • check:tokens still guards raw hex in components; the token bridge + hand-rolled surfaces comply.
  • Known benign build warning: "Unable to locate stylesheet: /cibg-huisstijl/css/huisstijl.min.css" — Angular's index optimizer doesn't process a public/ stylesheet at build time. The asset is copied and the link is preserved (verified: served 200, .btn-primary present); the build exits green. The alternative (adding the CSS to angular.json styles) would force-bundle the licensed fonts we intentionally dropped, so we accept the warning.
  • Renaming the internal token names from --rhc-* to --app-* is possible later but out of scope.
  • Hand-rolled components (point 4) are tracked in the CIBG gap register (libs/shared/docs/cibg-gaps.mdx, Storybook "Foundations/CIBG Gap Register"): every deviation from the design system carries a // CIBG-GAP EXTENSION: marker so it's auditable rather than silently drifting.