Records the decision to use D3 + Observable Plot, wrapped in a new `libs/shared/charts/` lib, as the chart toolkit shared by `portal-shell` and `portal-admin`. Implementation lands as a separate chantier (`pnpm add` + `libs/shared/charts/` foundations + 3 starter components + audit-page integration). The user's original ask cited D3 directly. The ADR honours that preference but pairs D3 with Observable Plot — the same team's higher-level layer (Mike Bostock / Observable Inc.). Plot turns "standard charts" (bar / donut / line / stacked-bar / scatter) into ~50 LOC per type instead of the ~250 LOC each that raw D3 would take. The shared lib's component contract stays uniform whether the underlying technique is Plot or raw D3 — `<lib-bar-chart>`, `<lib-heatmap>`, all expose the same `[data] / [caption] / [description] / [ariaLabel] / [colorScheme]` signal-based shape. Accessibility is enforced at the lib level (not bolted on per chart): SVG `<title>` + `<desc>`, `<details>`-based tabular fallback, colour-blind-safe palettes (Viridis / Cividis for sequential, ColorBrewer Set2 for categorical), AA-contrast text, `prefers-reduced-motion` gating. Six commitments, each unit-tested so a sloppy contributor can't drop one silently. Bundle impact (~65 KB gzip for the v1 vocabulary loaded on a chart-bearing lazy chunk) stays well under ADR-0017's 100 KB lazy- chunk cap via per-`d3-*` module imports. Considered + rejected: * D3 alone — 250 LOC per chart × 4-5 charts × consistent a11y posture across them all = sustained code investment before the first dashboard ships. * Apache ECharts — 600 KB minified + canvas rendering with an `aria` plugin afterthought + JSON-config idiom is the furthest from the Angular-Signals direction the workspace runs on. * Chart.js — narrower vocabulary than Plot, canvas-rendered with weaker out-of-the-box a11y, dark-mode integration brittle. CLAUDE.md and `docs/decisions/README.md` updated in the same change. ADR-0017 (perf budgets) and ADR-0016 (a11y baseline) are referenced as the binding constraints; this ADR doesn't supersede either, it operationalises both for the chart surface.
9.7 KiB
Architectural Decision Records
This project records architecturally-significant decisions as ADRs in the MADR 4.0.0 format. References: adr.github.io.
Why ADRs
ADRs capture the why behind a decision — context, drivers, options considered, trade-offs accepted — at the moment the decision is made. They make architecture reviewable, onboarding faster, and prevent the same debate from being re-litigated later.
Conventions
- Format: MADR 4.0.0. Start from template.md.
- Filename:
NNNN-kebab-case-title.md, e.g.0007-adopt-tailwind-for-design-tokens.md. - Numbering: globally sequential 4-digit prefix. Numbers never reset, never get reused — even when an ADR is superseded or deprecated.
- Layout: flat folder. ADRs are not nested into category subfolders; topical organization happens via tags.
- Tags: every ADR carries a
tags:array in the MADR frontmatter, drawn from the tag vocabulary below. An ADR may carry several tags. Propose new tags (or renames) in the same PR that needs them; never invent ad-hoc tags inline. - Status lifecycle:
proposed→accepted→ optionallydeprecatedorsuperseded by [ADR-NNNN](NNNN-other.md). Update the YAML frontmatter; never delete an ADR. - Index maintenance: every ADR addition or status change must update the Index below in the same commit.
When to write an ADR
Write one whenever a development decision is non-trivial: tool or library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change, or any choice that future contributors would benefit from understanding the why of.
Tag vocabulary
The vocabulary below is the source of truth. It is intentionally coarse — propose extensions only when an existing tag genuinely doesn't fit, and avoid overly narrow tags.
| Tag | Scope |
|---|---|
frontend |
UI, Angular, components, design system, client-side state |
backend |
API, BFF, server-side services |
security |
AuthN, AuthZ, sessions, CSP, dependency scanning, secret management |
performance |
Perf budgets, caching, bundle size, Lighthouse |
accessibility |
WCAG, a11y testing, keyboard, ARIA, contrast |
infrastructure |
CI/CD, hosting, deployment, runtime |
observability |
Logs, metrics, traces, correlation IDs, monitoring |
data |
Persistence, schemas, migrations, data flow |
process |
Team conventions, workflows, repo policy |
Status: starter vocabulary, to be refined as ADRs accumulate. Update this table whenever a tag is added, renamed, or retired.
Index
ADRs are listed in numerical order. To slice by topic, filter on the Tags column.
| # | Title | Status | Tags | Date |
|---|---|---|---|---|
| 0001 | Use ADRs to record architectural decisions | accepted | process |
2026-04-29 |
| 0002 | Adopt Nx monorepo with the apps preset |
accepted | infrastructure, frontend, backend |
2026-04-29 |
| 0003 | Workspace and app naming convention | accepted | process |
2026-04-29 |
| 0004 | Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest | accepted | frontend |
2026-04-29 |
| 0005 | Backend stack — NestJS over Express, Fastify, Hono | accepted | backend |
2026-04-29 |
| 0006 | Persistence — PostgreSQL with Prisma | accepted | data, backend |
2026-04-29 |
| 0007 | Pre-commit hooks and Conventional Commits | accepted | process |
2026-04-29 |
| 0008 | Identity model — multi-tenant Entra ID for workforce, dual-audience design for future External ID | accepted | security, data |
2026-04-29 |
| 0009 | Authentication flow — OIDC Authorization Code + PKCE via MSAL Node, BFF session pattern | accepted | security, backend |
2026-04-29 |
| 0010 | Session management — opaque session IDs in cookies, payload in self-hosted Redis with AES-GCM at rest | accepted | security, backend, infrastructure |
2026-04-29 |
| 0011 | MFA enforcement — Entra ID Conditional Access baseline, BFF claim sanity-check, step-up hooks designed-in | accepted | security |
2026-04-29 |
| 0012 | Observability — Pino structured logs + OpenTelemetry tracing, W3C Trace Context propagation, stdout + collector | accepted | observability, backend, frontend |
2026-04-29 |
| 0013 | Audit trail — separated append-only Postgres schema, decoupled from app logs | accepted | security, observability, data |
2026-04-29 |
| 0014 | Downstream API access — On-Behalf-Of pattern, unified DownstreamApiClient, audience-aware authorization |
accepted | security, backend |
2026-04-29 |
| 0015 | CI/CD pipeline — Gitea Actions, trunk-based + squash-merge, thin YAML over portable scripts | accepted | infrastructure, process |
2026-04-30 |
| 0016 | Accessibility baseline — WCAG 2.2 AA + targeted AAA, Angular CDK + spartan-ng + Tailwind, APF panel testing | accepted | accessibility, frontend, process |
2026-04-30 |
| 0017 | Performance budgets — Core Web Vitals + Lighthouse CI gates, bundle budgets, BFF p95/p99 SLOs | accepted | performance, frontend, backend, process |
2026-04-30 |
| 0018 | Environment configuration — Angular environment.ts, BFF env vars, audit pool split |
accepted | frontend, backend, infrastructure, process |
2026-05-10 |
| 0019 | Internationalisation — @angular/localize, build-time per-locale bundles, /fr + /en path-based routing |
accepted | frontend, accessibility, performance, process |
2026-05-11 |
| 0020 | portal-admin — dedicated SPA for portal administration, sharing the existing BFF |
accepted | frontend, backend, security, infrastructure, process |
2026-05-11 |
| 0021 | Phase-2 security baseline — helmet, CORS allowlist, double-submit CSRF, rate limiting, structured error envelope | accepted | security, backend |
2026-05-13 |
| 0022 | Documentation site — VitePress + Mermaid plugin, separate static deployment | accepted | process, infrastructure |
2026-05-15 |
| 0023 | Charts + dashboards — D3 + Observable Plot wrapped in libs/shared/charts |
accepted | frontend, accessibility, performance |
2026-05-16 |