WFDASH-WEBAPP/docs/theme-preview/README.md
Dave 9ce6c97772 feat(theme): the picker reads the real palette, not a hand-kept copy
The theme registry carried a hand-maintained set of hex swatches for
every palette, and they had drifted away from the CSS the app actually
paints. The picker previewed colours the product never showed.

The registry now holds only what cannot be read from CSS — id, label,
tagline, fonts, radius — and the picker, the Settings swatches and the
preview cards resolve --background, --primary and --foreground off the
theme itself. A preview can no longer disagree with the screen it
previews.

themes.css is rewritten so every palette carries a real dark surface,
and the palettes that are dark by nature stay dark whichever way the
mode switch is set. The pre-hydration script validates the stored theme
id against the registry, so a retired id falls back to WFDASH instead of
painting an unstyled page, and the old "warm" mode migrates to Light.

The legacy text-{warn,success,info}-foreground classes now resolve to
the semantic text colour and switch to the filled ink only on an exact
solid fill, so one class reads correctly on both a tinted badge and a
filled button. A solid focus outline replaces the faint ring, and the
charts and quote status badges take their colours from tokens rather
than hard-coded dark strokes.

Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the
storage-blocked path; e2e/themes.spec.ts walks every palette in both
modes checking surface contrast, tinted and solid semantic pairs, focus
colour, chart tokens and the destructive hover, and asserts that the
picker preview matches the applied theme.
2026-11-05 00:40:40 +02:00

3.3 KiB

WFDASH themes

The current theme picker is Settings → Appearance (/settings). The historical themes.html mockup in this directory predates the current registry and is not a preview of the shipping palettes.

src/lib/themes.ts lists the selectable themes. src/styles/themes.css is the single source for their colours; picker cards and the About swatches use those same CSS tokens rather than maintaining a second set of hex approximations.

  • data-theme="<id>" selects a palette on <html>.
  • light / dark classes select the mode through next-themes.
  • Choices persist per browser under wfdash-theme and wfdash-mode.
  • The default is WFDASH + Light. Old warm modes migrate to light and retired or invalid theme IDs fall back to WFDASH before hydration.
  • Standard palettes provide both light and dark surfaces. The palettes marked category: "dark" are intentionally dark in either mode.
  • Preview elements scope the same tokens through .theme-preview[data-theme]; their mode follows the mode selected in Settings.

Adding or adjusting a palette

  1. Add its metadata to THEMES in src/lib/themes.ts. Preserve existing IDs; MiniMax is intentionally case-sensitive for saved preferences.
  2. Define its tokens in src/styles/themes.css using :is(:root, .theme-preview)[data-theme="<id>"]. For a standard palette, also define a .dark variant with sufficient specificity to override its light surface. A bare .dark selector loses to the base palette selector.
  3. Define background/foreground pairs, muted text, semantic status colours, input/focus, sidebar, radius and fonts. Shared chart and AI tokens have defaults in globals.css. AI purple must follow the actual surface: include any new natively dark palette in the dark AI selector there. Legacy text-{warn,success,info}-foreground utilities adapt to tinted/plain text and exact solid fill classes; new tinted badges should use the semantic text class directly. These custom text utilities replace the generic Tailwind aliases to avoid duplicate declarations. The inverse info background/border utilities remain available for selected-customer checkmark bubbles.
  4. Keep business meaning consistent: success green, warnings amber/orange, destructive red, price checks blue, AI purple. Foreground tokens accompany solid fills; semantic text colours must also be readable on tinted badges.
  5. Verify in the actual app. The login page and business chart series retain their intentional brand/data colours; chart surfaces use theme tokens.

Verification

npm run test:run -- src/components/theme-bootstrap.test.tsx
npm run e2e -- e2e/themes.spec.ts

The browser matrix checks all registered themes in both modes for dark surfaces, text contrast, filled and tinted semantic pairs, primary button hover labels, focus-colour contrast and chart token coverage. It uses the shared CSS from the public login route, so this part requires no PocketBase fixture.

For authenticated preview/persistence and dialog/mobile checks, supply E2E_TEST_EMAIL / E2E_TEST_PASSWORD, or a Playwright E2E_STORAGE_STATE file for the test origin. These checks skip when no browser fixture is supplied. Use isolated browser storage; never commit credentials or storage-state files.