All checks were successful
Anonymous openbaar portal completing the walking skeleton (submit → projection → public visibility). closes #10
8.2 KiB
8.2 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 a container oncg, so the browser reaches Keycloak askeycloak:8080— the same issuer the BFF validates against (resolves the browser-vs-container mismatch, ADR-0010). It uses the officialmcr.microsoft.com/playwright:<version>image with browsers pre-baked, rather than downloading ~150 MB of Chromium on every run (issue #73) — the image tag is kept in lockstep withtests/e2e/package.json's@playwright/testversion. 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.
Openbaar Register portal (S-09, #10)
- Anonymous, no auth. The openbaar register is a public read, so
apps/openbaarhas noangular-auth-oidc-client, no interceptor, and noconfig.json—main.tsbootstrapsappConfigdirectly with justprovideHttpClient+provideRouter. This is the deliberate contrast to self-service and keeps the app trivially cacheable/CDN-able. - Same-origin via nginx, like self-service. The compose
openbaarimage serves the built app and reverse-proxies/openbaarto the BFF; the api-client's relative calls stay same-origin (no CORS). Served on:8141, health-checked over IPv4 (127.0.0.1), no Keycloak dependency. - Public-safe by construction. The portal only ever sees the BFF's
OpenbaarProjection.PublicView(id + status);bsn/naamnever leave the BFF. The e2e asserts the bsn never renders. - Loads on open, filters on search.
RegisterPagefetches the full register on construction and re-queries/openbaar/register?q=on search — no client-side filtering, the BFF owns the query.