Files
register-referentie/docs/frontend-decisions.md
Niek Otten d3f23a4da3
Some checks failed
CI / lint (pull_request) Successful in 1m5s
CI / build (pull_request) Successful in 52s
CI / unit (pull_request) Successful in 1m2s
CI / frontend (pull_request) Successful in 1m31s
CI / mutation (pull_request) Successful in 3m55s
CI / verify-stack (pull_request) Has been cancelled
docs(portal-self-service): serving/e2e decisions + walking-skeleton demo note (refs #68)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:09:04 +02:00

6.3 KiB

Frontend decisions

A running log of frontend tooling and component decisions (CLAUDE.md §10). One entry per decision; record why, and note any deviation from NL Design System.


Workspace & tooling (S-08a, #65)

The portals live in an Nx monorepo at the repository root, alongside the .NET services/.

  • Package manager: pnpm. Native build scripts are approved explicitly in pnpm-workspace.yaml under allowBuilds (pnpm 11 fails the install otherwise). Node 24, pnpm 11.
  • Angular, standalone components + signals, no NgModules (§10). Apps are generated with @nx/angular:application.
  • Unit tests: Vitest via Angular's built-in @angular/build:unit-test (the vitest-angular runner). Angular Testing Library is added for component tests when the first real components land (S-08c); the S-08a placeholder uses a plain TestBed render assertion.
  • Lint: ESLint (flat config, @nx/eslint).
  • Nx is scoped to apps/ + libs/ only. The @nx/docker and @nx/dotnet plugins are not installed — the .NET services are built by dotnet/the Makefile, and @nx/docker would otherwise infer every services/*/Dockerfile as an unnamed Nx project and break the project graph.
  • No Nx Cloud. nxCloudId is stripped from nx.json; remote caching would depend on an external service, and the repo is Gitea-only (§8.7). Nx's "configure-ai-agents" additions (.claude/settings.json, a CLAUDE.md section referencing a GitHub marketplace) are not committed for the same reason.
  • CI: a frontend job (make frontendpnpm install --frozen-lockfile + nx run-many -t lint test build) runs on pnpm + Node, with pinned action URLs (§15).

NL Design System: not yet introduced — the S-08a app is a placeholder. NL DS components arrive with the submit form (S-08c, #67); any deviation from NL DS will be recorded here.


API client generator (S-08b, #66)

libs/api-client is generated from services/bff/openapi.json — never hand-written (§10).

  • Generator: orval (client: 'angular'), a node-based generator (no Java, unlike openapi-generator), so it runs in the pnpm/Node CI lane. It emits an injectable BffApiV1Service using Angular's HttpClient — which means the DigiD bearer token can be attached by an HttpInterceptor (S-08c), the idiomatic Angular approach; a fetch-based SDK would bypass the interceptor pipeline.
  • Config: libs/api-client/orval.config.ts (single-file output into src/lib/generated/, clean: true, prettier). Regenerate with nx run api-client:generate after the BFF spec changes; the output is deterministic (idempotent), and src/lib/generated/ is never hand-edited.
  • Tested against a mocked BFF via HttpClientTesting (libs/api-client/src/lib/bff-api.spec.ts).
  • The BFF endpoints carry no operationId, so orval synthesises method names (postSelfServiceRegistrations, getOpenbaarRegister); adding explicit operation ids to the BFF is a possible later polish.

Self-service form: NL DS, DigiD auth, testing (S-08c, #67)

  • NL Design System via @utrecht/component-library-angular (libs/ui) + @utrecht/design-tokens (imported once in apps/self-service/src/styles.css). Utrecht is NL DS's reference Angular implementation. Its v3 components are NgModule-based, not standalone, so libs/ui re-exports UtrechtComponentsModule (and the component classes, so the AOT compiler resolves the template directives through the barrel); standalone components consume it via imports: [UtrechtComponentsModule]. §10's "no NgModules in new code" governs our code — consuming a third-party module is fine.
  • DigiD login via angular-auth-oidc-client (libs/auth): auth-code + PKCE against the Keycloak digid realm (public client big-portal). A small AuthService abstraction (bsn / isAuthenticated / login) wraps the library so components and the authenticatedGuard depend on a mockable surface; a token HttpInterceptor attaches the bearer to BFF calls (secure route). The OIDC authority/secureApiOrigin are dev defaults in app.config.ts; the compose-served app overrides them (S-08d), and the browser-vs-container issuer alignment is handled there (ADR-0010).
  • Testing: component tests use @testing-library/angular (§10) with AuthService and the api-client mocked; the axe (vitest-axe) check runs scoped to WCAG 2.1 AA tags (wcag2a/2aa/21a/21aa) with the document lang set, asserting zero violations on the submit page. The real DigiD browser round-trip is exercised in S-08d (Playwright).
  • Module boundaries: replaced the demo eslint depConstraints (scope:shop/scope:shared, left over from the Nx angular template) with a permissive * default; scope/type tags can be introduced when the portal set grows.

Serving + e2e (S-08d, #68)

  • Served by nginx, same-origin as the BFF. The compose self-service image serves the built app and reverse-proxies /self-service/* + /openbaar/* to the bff service. Because the api-client uses relative URLs, the browser calls the app's own origin → nginx forwards to the BFF: no CORS, and the DigiD token (same-origin) is attached by the interceptor. nginx resolves the BFF at request time (a resolver + variable proxy_pass) so it starts before the BFF is up.
  • Runtime config. The app fetches /config.json before bootstrap (main.ts); appConfig is a factory. The dev default (public/config.json) points at localhost:8180; the Docker image bakes the compose value (keycloak:8080). One build, per-environment OIDC authority.
  • e2e runs inside the compose network. infra/run-e2e-check.sh runs Playwright in a node container on cg, so the browser reaches Keycloak as keycloak:8080 — the same issuer the BFF validates against (resolves the browser-vs-container mismatch, ADR-0010). Chromium is installed at runtime, so there's no Playwright-image-version pinning to keep in sync. The spec is copied in (docker cp), not mounted, so it leaves nothing root-owned on the host. Wired as verify-e2e in the verify-stack CI job.
  • tests/e2e is a standalone Playwright project (its own package.json), not an Nx project — it's a live-stack check like the other verify-* runners, not part of the frontend unit lane.