ADR-0032: Frontend realization — Quinoa, Angular Material, i18n & E2E¶
- Status: accepted
- Refined by: ADR-0050 (SSE state push, CodeMirror 6 editor, draft presence)
- Date: 2026-07-01
- Deciders: Nicolas Burri
- Refines: ADR-0007 (Angular; server-side-only enforcement)
- Relates to: ADR-0011 / ADR-0028 (BFF encrypted-cookie session), ADR-0005 (modular monolith), ADR-0004 (catalog translations)
Context¶
ADR-0007 chose Angular as a separate app consuming the REST API, with the frontend never an enforcement point and the E2E suite serving as validation evidence. It explicitly left open, "at scaffolding time": how the app is built and served, the component library, the i18n mechanism, and the E2E tool. This ADR fixes those so the first screens can be built consistently.
Decisions¶
-
Build & serve: Quarkus Quinoa. Quarkus builds the Angular app (via npm) and serves the SPA same-origin in production (SPA at
/, REST under a path prefix), proxying to the Angular dev server in development. Same-origin is the decisive factor: the BFF encrypted-cookie session (ADR-0028) works with no CORS and no cross-site-cookie concessions. One Gradle build produces one artifact, one deployment. Angular remains a separate application (ADR-0007) — Quinoa governs only build+serve, not the architecture. The web app lives in theapimodule undersrc/main/webui(Quinoa's convention), sinceapiis the HTTP edge. -
Component library: Angular Material. Official, versioned and released in lockstep with Angular itself → a single, LTS-aligned dependency, the smallest SOUP surface of the realistic options (the CSV/GAMP 5 lens of ADR-0007). Strong forms, tables, dialogs and accessibility for a form-heavy enterprise QMS UI. (PrimeNG — richer but an independently-churning third party; plain CSS — least dependency, most effort — both rejected on the SOUP/effort trade.)
-
i18n:
ngx-translate(runtime). Per-user language switching at runtime from JSON catalogs, vs Angular's built-in compile-time i18n (one build per locale). A QMS serving Swiss/EU users who switch language without a rebuild favours runtime translation; content-level translations already live in the catalog (ADR-0004). Designed in from the first screen (ADR-0007) — no hard-coded strings. -
E2E: Playwright. The modern default (fast, reliable auto-waiting, multi-browser, first-class CI). The E2E suite is validation evidence (ADR-0007): stable
data-testidselectors, seedable data, deterministic flows. -
Angular major pinned to 20 (environment-driven). The build environment runs Node 20.19; the latest Angular (22) requires Node ≥22.22. Angular 20 is the newest major that runs on Node 20.19 and pairs with Angular Material 20. Pinning a known-good major also fits ADR-0007's plannable-LTS posture. Revisit when the environment's Node is upgraded (then a planned Angular major bump per the configuration-management upgrade windows).
Consequences¶
- The Gradle
apibuild gains an npm/Angular build step (Quinoa). CI must have Node available; the npm dependency set joins the SOUP list (ADR-0007), its churn batched into Angular's LTS windows. - First vertical slice (chosen at the checkpoint): the document-lifecycle loop — BFF login → browse a scope's documents → create/edit a draft → submit → approve/release → view the released version — exercising auth, documents, lifecycle and review together against the existing REST API.
- The API must expose the caller's effective permissions so the UI can reflect (never enforce)
them (ADR-0007 consequence) — a small
GETfor the current user's permissions per scope is a near-term backend follow-up. - Frontend defects remain non-compliance-relevant (enforcement is server-side), keeping per-change frontend re-validation light.