Commit Graph

3 Commits

Author SHA1 Message Date
Julien Gautier 4ce22d6d05 feat(shared-charts): foundations + bar / donut / stacked-bar components
CI / commits (pull_request) Successful in 3m31s
CI / scan (pull_request) Successful in 4m28s
CI / check (pull_request) Failing after 8m9s
CI / a11y (pull_request) Successful in 3m56s
Docs site / build (pull_request) Successful in 4m59s
CI / perf (pull_request) Successful in 10m8s
Implementation of ADR-0023 (PR 1). The next chantier wires these
components into the audit-log page; this PR delivers the lib + the
three starter chart types + the a11y contract.

Deps:
  * d3 + @types/d3 (top-level)
  * d3-shape + @types/d3-shape (donut's pie/arc generators)
  * d3-scale-chromatic + @types/d3-scale-chromatic (palettes)
  * @observablehq/plot (bar + stacked-bar)

Lib `libs/shared/charts/`:
  * `_internal/palette.ts` — single source of palettes. Sequential
    (Viridis / Cividis for dark mode) + categorical (ColorBrewer
    Set2). Colour-blind-safe per the ColorBrewer review.
  * `_internal/a11y.ts` — chartId generator, SVG <title>/<desc>
    injector, `findChartSvg` for Plot's legend-wrapped output,
    prefers-reduced-motion guard, theme resolver.
  * `_internal/chart-envelope.scss` — shared figure/caption/
    fallback rules consumed via `@use` by each chart's .scss.
    Extracted at the third consumer per CLAUDE.md's "three similar
    things" rule.
  * `_internal/chart-types.ts` — common `ChartBaseInputs<T>`
    interface every component extends.

Three Plot-or-D3-backed components:
  * `<lib-bar-chart>` — Plot.barY, categorical-by-default palette.
  * `<lib-donut-chart>` — raw d3-shape (pie + arc); Plot has no
    donut mark so we reach for D3 directly. Each slice carries its
    own <title> child for SVG-tooltip accessibility.
  * `<lib-stacked-bar-chart>` — Plot.barY with `fill` set to the
    series key (auto-stacked), legend on.

Each component honours ADR-0023's six commitments:
  1. <figure role="img" aria-labelledby aria-describedby>
  2. SVG <title> + <desc> as first two children (injected post-
     render because Plot doesn't emit them)
  3. <details> tabular fallback rendering every data point
  4. Palette from the canonical list (no ad-hoc colours)
  5. AA-contrast text on axis ticks (theme-aware grayscale via
     the .chart-canvas svg text rule)
  6. prefers-reduced-motion → data-no-transitions marker on the
     SVG, CSS strips animations.

A custom ESLint rule in `libs/shared/charts/eslint.config.mjs`
bans direct imports of `d3-scale-chromatic` outside
`_internal/palette.ts`, so a future contributor can't bypass the
colour-blind-safe contract.

Tests: 13 specs across the three components. Each component covers
the figure ARIA contract, the SVG title/desc injection, the
tabular fallback, and data-reactivity. Donut spec additionally
asserts per-slice <title> labels for screen readers.

Verification:
  * `pnpm nx lint test build --projects=shared-charts,portal-shell,portal-admin`
    — 12/12 tasks green.
  * `pnpm nx build portal-shell --configuration=production` —
    i18n-strict prod build clean (the lib ships no i18n marks
    since axis labels are caller-supplied).
2026-05-16 21:48:47 +02:00
Julien Gautier 8de19320c5 chore: generate shared and feature libs with module boundaries per ADR-0003
Generate the four phase-4 libraries:

- libs/shared/tokens (project name shared-tokens) - plain TS lib via
  @nx/js:library; will host the a11y design tokens (palette,
  contrast tiers, spacing, motion) once Tailwind lands in phase 5;
  consumable by both apps; tagged scope:shared, type:shared.
- libs/shared/util (shared-util) - plain TS lib for cross-cutting
  utility code; tagged scope:shared, type:shared.
- libs/shared/ui (shared-ui) - Angular standalone library that will
  host the spartan-ng components copy-pasted in phase 5; Angular-only
  so tagged scope:portal-shell, type:shared. unitTestRunner=
  vitest-analog because vitest-angular requires a buildable lib.
- libs/feature/auth (feature-auth) - placeholder Angular standalone
  feature lib to demonstrate the type:feature pattern; tagged
  scope:portal-shell, type:feature.

@nx/enforce-module-boundaries depConstraints replaced (root
eslint.config.mjs) with the rules from ADR-0003:

  scope:portal-shell -> scope:portal-shell, scope:shared
  scope:portal-bff   -> scope:portal-bff,   scope:shared
  scope:shared       -> scope:shared
  type:app           -> type:feature, type:shared
  type:feature       -> type:feature, type:shared
  type:shared        -> type:shared

This forbids portal-shell from importing portal-bff code (and vice
versa) and prevents shared libs from depending on feature libs.

Project names follow the convention of ADR-0003 (feature-<name> /
shared-<scope>) by passing --name explicitly to the generator; the Nx
22 default takes only the last directory segment.

Sanity check: pnpm nx run-many -t lint and -t test pass for the 8
projects (4 apps/e2e + 4 libs).

Side effects from the generators: tsconfig.base.json paths populated
with the lib import aliases; nx.json gains vite/playwright plugin
entries; .gitignore picks up vitest.config.*.timestamp* (Vitest temp
files); package.json gains @analogjs/vitest-angular and related
devDeps; pnpm-lock.yaml regenerated. Two eslint-disable comments in
portal-bff-e2e support files were trimmed by lint --fix - those files
already lint clean without the directive.
2026-04-30 17:37:29 +02:00
Julien Gautier bea5e1954f chore: generate portal-shell and portal-bff apps per ADR-0004 / ADR-0005
Add the @nx/angular, @nx/nest, @nx/vite, @nx/eslint plugins, then
generate the two apps. Adjust the empty-template tsconfig.base.json
to be Angular-compatible (drop project references and customConditions
that the empty-template defaults to but Angular doesn't support; keep
the strict-TS extensions from ADR-0004).

apps/portal-shell (Angular 21):
- standalone APIs, routing, SCSS, esbuild
- vitest-angular as unitTestRunner, playwright for e2e
- strict mode
- tags scope:portal-shell, type:app
- app.config.ts wired with provideZonelessChangeDetection() per
  ADR-0004 (Angular 21 + Nx 22 generates without zone.js by default)

apps/portal-bff (NestJS 11):
- Express adapter (default per ADR-0005)
- Jest as unitTestRunner
- tags scope:portal-bff, type:app
- main.ts wired with a global ValidationPipe configured
  whitelist + forbidNonWhitelisted + transform per ADR-0005
- Phase-2 security additions (helmet, CORS, sessions, CSRF, rate
  limit, auth guards, error filter) deferred to their respective
  ADRs - placeholder comment in main.ts

Workspace dependencies: class-validator + class-transformer added
(required by NestJS ValidationPipe at runtime). Nx-generated
.gitignore additions (.angular, __screenshots__) merged into ours.
.vscode/extensions.json and launch.json added by Nx are kept (do not
override our existing settings.json).
2026-04-30 16:12:42 +02:00