Files
apf_portal/CLAUDE.md
T
Julien Gautier ea852b3239 docs: add ADR-0017 for performance budgets (Core Web Vitals + Lighthouse CI + Angular budgets + BFF SLOs)
Pin the perf framework. Front-end metrics tracked at Google Core Web
Vitals 'Good' thresholds (LCP <= 2.5s, INP <= 200ms, CLS <= 0.1, plus
TBT <= 200ms, TTFB <= 800ms) and Lighthouse Performance >= 90 on
critical routes. Lighthouse CI (@lhci/cli) enforces them in CI with
median-of-3 runs to mitigate runner variance, on a curated
critical-routes list (login, home, accessibility statement, flagship
features as they land). Wires into the perf gate slot from ADR-0015.

Bundle budgets enforced at nx build via Angular's project.json budgets
array, type 'error': initial <= 300 KB gzip, lazy chunks <= 100 KB
gzip, per-component CSS <= 6 KB. source-map-explorer wired as an Nx
analyze target for diagnosis on budget breaches.

Back-end SLOs documented per endpoint family (p95/p99) and observed
via the OpenTelemetry spans already shipped by ADR-0012 - advisory in
CI (load profile unrepresentative), alerting in production. Quarterly
review tightens budgets when achievable.

Scheduled weekly Lighthouse run on the prod env via the existing
security-scheduled.yml workflow extends coverage beyond PR-time.

Explicit a11y/perf trade-off rule: when they conflict, a11y wins (per
APF's mission, ADR-0016). The perf budget is then re-evaluated at the
next quarterly review.

No browser-side RUM SDK in v1 - the OTel browser tracing from ADR-0012
plus scheduled prod Lighthouse runs cover the gap. RUM revisited in
v2 if a real incident escapes the existing signal.

decisions/README.md index updated. CLAUDE.md gains an explicit
'Performance budgets' line pointing to ADR-0017.
2026-04-30 14:22:36 +02:00

11 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project rules (durable)

These constraints were set by the project lead at kickoff. They apply to every change.

  • Scale & quality bar. Treat this as a large-scale portal for a sizable organization, not a prototype. No bricolage, no exotic stacks. Default to stable, recognized, battle-tested choices. Cutting-edge / "à la pointe" alternatives must always be evaluated alongside the stable option, but are only adopted when the trade-off is captured in an ADR (drivers, risk, exit strategy). Pre-1.0 dependencies and one-maintainer projects are rejected unless an ADR justifies the exception.
  • Security, performance, accessibility. All three are first-class concerns from day one — never bolted on. Architecture, dependency, and feature decisions must explicitly consider their impact on these axes and document the trade-offs.
  • Project name. Currently apf_portal, provisional. Do not hardcode it outside repo/workspace-level metadata so a rename stays a one-line change.
  • Language. All code, identifiers, comments, documentation, commit messages, and PR descriptions are written in English. (Conversation with the project lead happens in French — but artifacts shipped in the repo are English-only.)
  • Commits / PRs. Never add a Co-Authored-By: Claude trailer or a 🤖 Generated with Claude Code footer to commits or PR bodies.
  • Be a peer, not a typist. Challenge requests when a better approach exists; surface trade-offs frankly. Don't silently execute a suboptimal directive — propose, then execute the agreed plan.

Documentation

  • All documentation lives in .md files under docs/, indexed by docs/README.md. The index is maintained automatically whenever a doc is added, renamed, or removed — no need to be asked.
  • Documentation is written proactively whenever it is genuinely useful (architecture, runbooks, onboarding, security/perf/a11y rationales). It is not created for trivial things just to tick a box.
  • The folder notes/ is the project lead's personal scratchpad — git-ignored and not part of project artifacts. Never write project documentation there.

Architectural Decision Records (ADRs)

  • Format: MADR 4.0.0 (https://adr.github.io/, https://github.com/adr/madr). Template at decisions/template.md.
  • Location: flat folder decisions/, indexed by decisions/README.md.
  • Filename convention: NNNN-kebab-title.md with globally sequential 4-digit numbers. Numbers are never reset and never reused — even when an ADR is superseded or deprecated.
  • Categorization: via the tags: array in the MADR frontmatter (e.g. [frontend, security]). The canonical tag vocabulary lives in decisions/README.md; never invent ad-hoc tags inline.
  • Proactivity. Any non-trivial development decision (tool/library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change) warrants proposing an ADR before implementation. Don't wait to be asked. Update the index in the same change.

Architecture (decided in phase-1 ADRs)

The structural choices are recorded as ADRs and summarized below. Any change to these requires updating the corresponding ADR.

  • Workspace: Nx monorepo with the apps preset, managed by pnpm — see ADR-0002.
  • Naming: workspace apf-portal; apps portal-shell (frontend) and portal-bff (backend); libs feature-<name> and shared-<scope> — see ADR-0003.
  • Frontend (portal-shell): Angular at the latest LTS major — standalone APIs, zoneless change detection, Signals, CSR only (no SSR), Vitest, SCSS — see ADR-0004.
  • Backend (portal-bff): NestJS at the latest stable major, mounted on the Express adapter (Fastify adapter swappable later) — see ADR-0005.
  • Persistence: PostgreSQL (latest stable major) via Prisma — see ADR-0006.
  • Sessions: opaque session id in __Host-portal_session, payload in self-hosted Redis (Sentinel HA in prod, single node in dev), tokens encrypted at rest with AES-256-GCM, idle 30 min sliding + absolute 12 h — see ADR-0010.
  • MFA: enforced by Entra ID Conditional Access (org-side policy, P1 licensing required); BFF sanity-checks the amr claim at session creation; @RequireMfa() decorator and freshness-based step-up are designed-in for future sensitive routes (no v1 consumer) — see ADR-0011.
  • Identity: multi-tenant Microsoft Entra ID with B2B invitation for workforce in v1, dual-audience design ready for future External ID activation — see ADR-0008.
  • Authentication flow: OIDC Authorization Code + PKCE via @azure/msal-node, executed entirely on the BFF; SPA never holds tokens; __Host- prefixed cookies, double-submit CSRF, RP-initiated logout — see ADR-0009.
  • Observability: Pino + nestjs-pino for structured JSON logs, OpenTelemetry SDK + auto-instrumentations for traces, W3C Trace Context propagation across SPA → BFF → DB → Redis, nestjs-cls for request-scoped context (trace_id, session_id, user_id_hash, audience), 100 % sampling at the app with tail sampling deferred to the OTel Collector, stdout + OTLP shipping — see ADR-0012.
  • Audit trail: dedicated audit.events schema in the same Postgres instance, append-only by Postgres role grants (audit_writer INSERT, audit_reader SELECT, audit_archiver DELETE older than retention; no UPDATE/TRUNCATE to anyone); 365-day retention default; cross-referenced with app logs via trace_id and actor_id_hash (same salt); blocking writes (no audit ⇒ no action) — see ADR-0013.
  • Downstream API access: unified DownstreamApiClient (@nestjs/axios + cockatiel), per-service DownstreamApiConfig; default auth strategy is OBO via MSAL Node for Entra-protected APIs (downstream-scoped tokens cached in Redis with AES-256-GCM under a dedicated key); fallback strategy is service credential + signed X-User-Assertion JWT (BFF JWKS at /.well-known/jwks.json); per-call audience pre-check; no axios/fetch outside src/downstream/ — see ADR-0014.
  • CI/CD: Gitea Actions (level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months). Trunk-based with squash-merge, branch protection on main, all CI gates blocking. Thin YAML — orchestration logic lives in package.json scripts (ci:check, ci:scan, ci:commits) and Nx targets, runnable locally. Gates: format / lint / type-check / test / build / audit / secret-scan / commit-lint, plus a11y (per ADR-0016) and future perf. Self-hosted act_runner on-prem. Conventional Commits validated locally (hook) and in CI (defense in depth). Required reviewer count = 0 in v1, raised to ≥1 once a second contributor joins. Signed commits recommended, revisited at GitLab migration — see ADR-0015.
  • Accessibility: WCAG 2.2 AA baseline + targeted AAA on criteria with high impact for APF's user base (1.4.6 Contrast Enhanced, 2.2.3 No Timing, 2.3.3 Animation, 3.1.5 Reading Level, 1.4.8 Visual Presentation, 2.4.9 Link Purpose, 3.3.5 Help). RGAA 4.1 alignment for French audit. UI stack: Angular CDK + spartan-ng + Tailwind (no Angular Material; no React libs). User-preferences panel (contrast / text size / motion / spacing / cognitive simplification / reading focus) persisted in session. Tooling: @angular-eslint/template/* lint, @axe-core/playwright e2e (blocking on critical/serious), token-contrast CI check, touch-target check (44×44 min). Manual testing cadence with APF's internal user panel before each major release. Public accessibility statement page at /accessibility and /accessibilite — see ADR-0016.
  • Performance budgets: Core Web Vitals at Google "Good" thresholds (LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1, TBT ≤ 200 ms, TTFB ≤ 800 ms), Lighthouse Performance ≥ 90 on critical routes. Lighthouse CI (@lhci/cli) runs in CI with median-of-3 mitigation, blocking on threshold breach. Angular bundle budgets (type: "error"): initial ≤ 300 KB gzip, lazy chunks ≤ 100 KB gzip. BFF p95/p99 SLOs per endpoint family observed via OTel (advisory in CI, alerting in prod). Weekly scheduled Lighthouse run on prod env. a11y wins over perf when they conflict — see ADR-0017.
  • Local quality gates: Husky + lint-staged + commitlint with Conventional Commits — see ADR-0007.
  • Runtime: Node.js latest LTS major.

Repository status

The Nx workspace is not yet bootstrapped — there is no package.json, no source code, no tests. Phase-1 ADRs (0001 → 0006) have fixed the structural choices; the next step is to scaffold the workspace per a revised version of docs/setup/03-angular-nx-monorepo.md (the existing file still carries the original placeholder values and needs an update to match the ADRs before it is followed).

If asked to "build", "test", or "run" anything, first verify whether the workspace exists; if not, the right response is to scaffold it (or update the setup doc), not to invent commands.

Commands once the workspace exists

App-scoped — <app> is one of portal-shell, portal-bff:

pnpm nx serve <app>      # dev server
pnpm nx build <app>
pnpm nx test <app>       # Vitest, all tests for the app
pnpm nx lint <app>

Run a single test file:

pnpm nx test <app> --testFile=path/to/file.spec.ts

Workspace-wide:

pnpm nx run-many -t lint test build
pnpm nx affected -t lint test build   # only projects affected by current changes
pnpm nx format:check

Environment conventions

  • Never install Angular globally. Use pnpm dlx for one-off CLI invocations and project-local pnpm nx ... for everything else — versions stay pinned per project.
  • Work inside the WSL filesystem (~/dev/...), never under /mnt/c — the latter has severe I/O penalties that break Nx caching and dev-server reload times.
  • pnpm is mandatory (activated via corepack enable); do not introduce npm or yarn lockfiles.
  • Prettier config target: singleQuote: true, semi: true, printWidth: 100.