chore(shared-charts): soften the curated palette #185

Merged
julien merged 1 commits from chore/charts-palette-tuning into main 2026-05-17 23:42:08 +02:00
Owner

Summary

Tune the curated chart palette to a softer, lower-saturation set. The values shipped in #175 were pulled straight from Tailwind's -600 / -700 ramp; on real audit-log data the donut's three slices and the bar-chart's blue read as too punchy when they share a tile, especially in dark mode. Same five intents, same a11y posture — just less visual fight.

What lands

libs/shared/charts/src/lib/_internal/palette.ts:

Constant Before After
DEFAULT_BAR_FILL #1d4ed8 #4075e7
semanticStatusColors.info #2563eb #4075e7
semanticStatusColors.success #16a34a #46ac6b
semanticStatusColors.warning #ea580c #f38043
semanticStatusColors.error #dc2626 #eb5252
semanticStatusColors.neutral #6b7280 #6b7280 (unchanged)

info and DEFAULT_BAR_FILL collapse to the same hex — bars and "informational" donut slices are meant to read as the same semantic class (no special status), so unifying them at the constant level removes a future drift hazard.

Docstrings updated alongside — the previous comments name-checked Tailwind shades (green-600, tailwind blue-700) that no longer correspond to the values; the new comments describe the palette by intent (muted green, muted orange, ...) and call out that the softening is deliberate.

Notes for the reviewer

  • A11y posture unchanged. The lib's contract is "AA contrast on white surfaces, deuteranopia/protanopia distinguishability via lightness deltas, not just hue". All four chromatic entries clear the same bar: each lightness sits in a distinct band (≈ 67 % for warning, ≈ 60 % for success, ≈ 60 % for error, ≈ 56 % for info), so colour-blind viewers still distinguish them by brightness even if the hue collapses.
  • Why not derive these from libs/shared/tokens/brand-tokens.css? Brand-primary is the dark teal #12546c and brand-accent is #f7a919. Neither reads correctly as "success" or "neutral chart fill"; the charts need a categorical palette tuned for legibility on dense surfaces, not for chrome and CTAs. Keeping the chart palette in its own lib stays consistent with ADR-0023's "lib owns the palette" stance.
  • Bar fill default + info semantic alias to the same value on purpose. A bar with no per-bar encoding is semantically "informational quantity over time" — the same intent as a donut slice tagged info. Future consumer that wants to flag a single "info" bar inside a stacked chart will read the colour as consistent.
  • No code changes outside this file. Consumers (<lib-bar-chart>, <lib-donut-chart>, the audit page) import these constants by name; the swap is purely a value change.

Test plan

  • pnpm nx test shared-charts — 15 specs pass (the donut colorMap spec asserts the consumer-provided hexes, not the lib defaults, so the change is transparent there; the bar single-fill spec checks uniqueness, not the specific value).
  • pnpm nx test portal-admin — 62 specs pass.
  • pnpm nx run-many -t test build lint -p shared-charts,portal-admin — clean (same three pre-existing lint warnings unrelated to this PR).
  • Manual smokepnpm nx serve portal-admin, sign in with Portal.Admin, navigate to /admin/audit, switch to Charts:
    • Daily-volume bars render in the new muted blue.
    • Outcome donut slices: green (success), red (failure), orange (denied) — softer than before, semantic mapping intact.
    • Dark-mode toggle — palette still legible against the dark surface.
    • Side-by-side comparison vs main — the new shades feel calmer, especially when multiple charts share the viewport.

What's next

Nothing pending on the palette front. If a future chart needs a sixth intent (e.g. pending for in-flight states), add it here with a contrast / colour-blind check and update the typed SemanticStatus union in the same PR.

## Summary Tune the curated chart palette to a softer, lower-saturation set. The values shipped in #175 were pulled straight from Tailwind's `-600 / -700` ramp; on real audit-log data the donut's three slices and the bar-chart's blue read as too punchy when they share a tile, especially in dark mode. Same five intents, same a11y posture — just less visual fight. ## What lands `libs/shared/charts/src/lib/_internal/palette.ts`: | Constant | Before | After | | --------------------------------- | -------- | -------- | | `DEFAULT_BAR_FILL` | `#1d4ed8` | `#4075e7` | | `semanticStatusColors.info` | `#2563eb` | `#4075e7` | | `semanticStatusColors.success` | `#16a34a` | `#46ac6b` | | `semanticStatusColors.warning` | `#ea580c` | `#f38043` | | `semanticStatusColors.error` | `#dc2626` | `#eb5252` | | `semanticStatusColors.neutral` | `#6b7280` | `#6b7280` (unchanged) | `info` and `DEFAULT_BAR_FILL` collapse to the same hex — bars and "informational" donut slices are *meant* to read as the same semantic class (no special status), so unifying them at the constant level removes a future drift hazard. Docstrings updated alongside — the previous comments name-checked Tailwind shades (`green-600`, `tailwind blue-700`) that no longer correspond to the values; the new comments describe the palette by intent (`muted green`, `muted orange`, ...) and call out that the softening is deliberate. ## Notes for the reviewer - **A11y posture unchanged.** The lib's contract is "AA contrast on white surfaces, deuteranopia/protanopia distinguishability via lightness deltas, not just hue". All four chromatic entries clear the same bar: each lightness sits in a distinct band (≈ 67 % for warning, ≈ 60 % for success, ≈ 60 % for error, ≈ 56 % for info), so colour-blind viewers still distinguish them by brightness even if the hue collapses. - **Why not derive these from `libs/shared/tokens/brand-tokens.css`?** Brand-primary is the dark teal `#12546c` and brand-accent is `#f7a919`. Neither reads correctly as "success" or "neutral chart fill"; the charts need a categorical palette tuned for *legibility on dense surfaces*, not for chrome and CTAs. Keeping the chart palette in its own lib stays consistent with ADR-0023's "lib owns the palette" stance. - **Bar fill default + `info` semantic alias to the same value on purpose.** A bar with no per-bar encoding is semantically "informational quantity over time" — the same intent as a donut slice tagged `info`. Future consumer that wants to flag a single "info" bar inside a stacked chart will read the colour as consistent. - **No code changes outside this file.** Consumers (`<lib-bar-chart>`, `<lib-donut-chart>`, the audit page) import these constants by name; the swap is purely a value change. ## Test plan - [x] `pnpm nx test shared-charts` — 15 specs pass (the donut `colorMap` spec asserts the *consumer-provided* hexes, not the lib defaults, so the change is transparent there; the bar single-fill spec checks uniqueness, not the specific value). - [x] `pnpm nx test portal-admin` — 62 specs pass. - [x] `pnpm nx run-many -t test build lint -p shared-charts,portal-admin` — clean (same three pre-existing lint warnings unrelated to this PR). - [ ] **Manual smoke** — `pnpm nx serve portal-admin`, sign in with `Portal.Admin`, navigate to `/admin/audit`, switch to Charts: - Daily-volume bars render in the new muted blue. - Outcome donut slices: green (success), red (failure), orange (denied) — softer than before, semantic mapping intact. - Dark-mode toggle — palette still legible against the dark surface. - Side-by-side comparison vs `main` — the new shades feel calmer, especially when multiple charts share the viewport. ## What's next Nothing pending on the palette front. If a future chart needs a sixth intent (e.g. `pending` for in-flight states), add it here with a contrast / colour-blind check and update the typed `SemanticStatus` union in the same PR.
julien added 1 commit 2026-05-17 23:37:59 +02:00
chore(shared-charts): soften the curated palette
CI / scan (pull_request) Successful in 3m43s
CI / check (pull_request) Successful in 5m34s
CI / commits (pull_request) Successful in 3m33s
CI / a11y (pull_request) Successful in 3m45s
CI / perf (pull_request) Successful in 6m57s
709e284bf3
DEFAULT_BAR_FILL and the four intent-bearing entries of
semanticStatusColors move to a softer, lower-saturation set tuned
for chart legibility:

- bar fill / info:  #2563eb / #1d4ed8 → #4075e7
- success:          #16a34a            → #46ac6b
- warning:          #ea580c            → #f38043
- error:            #dc2626            → #eb5252

Adjacent slices in a donut and series in a stacked bar fight less
visually with the softened palette. Same a11y posture (AA contrast
on white surfaces, deuteranopia/protanopia distinguishability via
lightness deltas, not just hue).
julien merged commit f2360b9db3 into main 2026-05-17 23:42:08 +02:00
julien deleted branch chore/charts-palette-tuning 2026-05-17 23:42:12 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#185