# 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 frontend` → `pnpm 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.