Some checks failed
CI / lint (pull_request) Successful in 1m12s
CI / build (pull_request) Successful in 53s
CI / unit (pull_request) Successful in 1m3s
CI / frontend (pull_request) Successful in 1m47s
CI / mutation (pull_request) Successful in 4m2s
CI / verify-stack (pull_request) Failing after 7m51s
The walking-skeleton e2e timed out waiting for the Keycloak login form (`#username`). Root cause: in the compose network the portal is served over plain HTTP on a non-localhost origin (http://self-service), which is not a secure context, so Web Crypto (`crypto.subtle`) is undefined. angular-auth- oidc-client needs SubtleCrypto to build the PKCE code challenge, so `authorize()` threw ("Cannot read properties of undefined (reading 'digest')") and the login redirect never fired. Production serves the portal over HTTPS, where this works. Instead of terminating TLS in the throwaway e2e stack, tell Chromium to treat the origin as secure via --unsafely-treat-insecure-origin-as-secure. The flag is only honoured by the full Chromium build (new headless), not Playwright's default headless-shell, so pin channel: 'chromium'. Verified against a minimal in-network stack (keycloak + self-service): login redirect now reaches the Keycloak form, and the full login → token exchange → authenticated portal renders with no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.0 KiB
7.0 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.yamlunderallowBuilds(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(thevitest-angularrunner). Angular Testing Library is added for component tests when the first real components land (S-08c); the S-08a placeholder uses a plainTestBedrender assertion. - Lint: ESLint (flat config,
@nx/eslint). - Nx is scoped to
apps/+libs/only. The@nx/dockerand@nx/dotnetplugins are not installed — the .NET services are built bydotnet/the Makefile, and@nx/dockerwould otherwise infer everyservices/*/Dockerfileas an unnamed Nx project and break the project graph. - No Nx Cloud.
nxCloudIdis stripped fromnx.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
frontendjob (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, unlikeopenapi-generator), so it runs in the pnpm/Node CI lane. It emits an injectableBffApiV1Serviceusing Angular'sHttpClient— which means the DigiD bearer token can be attached by anHttpInterceptor(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 intosrc/lib/generated/,clean: true, prettier). Regenerate withnx run api-client:generateafter the BFF spec changes; the output is deterministic (idempotent), andsrc/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 inapps/self-service/src/styles.css). Utrecht is NL DS's reference Angular implementation. Its v3 components are NgModule-based, not standalone, solibs/uire-exportsUtrechtComponentsModule(and the component classes, so the AOT compiler resolves the template directives through the barrel); standalone components consume it viaimports: [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 Keycloakdigidrealm (public clientbig-portal). A smallAuthServiceabstraction (bsn / isAuthenticated / login) wraps the library so components and theauthenticatedGuarddepend on a mockable surface; a tokenHttpInterceptorattaches the bearer to BFF calls (secure route). The OIDCauthority/secureApiOriginare dev defaults inapp.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) withAuthServiceand the api-client mocked; the axe (vitest-axe) check runs scoped to WCAG 2.1 AA tags (wcag2a/2aa/21a/21aa) with the documentlangset, 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-serviceimage serves the built app and reverse-proxies/self-service/*+/openbaar/*to thebffservice. 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 (aresolver+ variableproxy_pass) so it starts before the BFF is up. - Runtime config. The app fetches
/config.jsonbefore bootstrap (main.ts);appConfigis a factory. The dev default (public/config.json) points atlocalhost: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.shruns Playwright in anodecontainer oncg, so the browser reaches Keycloak askeycloak: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 asverify-e2ein theverify-stackCI job. - e2e treats the portal origin as secure. In-network the portal is served over plain HTTP on a
non-localhost origin (
http://self-service), which is not a secure context, so Web Crypto (crypto.subtle) is unavailable. angular-auth-oidc-client needs it for the PKCE code challenge, soauthorize()throws and the login redirect never fires. Production runs behind HTTPS where this is a non-issue; rather than terminate TLS in the throwaway stack, the Playwright config passes--unsafely-treat-insecure-origin-as-secure(honoured only by the fullchannel: 'chromium'build, not the default headless-shell). This emulates the production HTTPS secure context without touching the app or its production config. tests/e2eis a standalone Playwright project (its ownpackage.json), not an Nx project — it's a live-stack check like the otherverify-*runners, not part of thefrontendunit lane.