#180: Dark mode - Light/Dark/System setting with token-based dark palette #181

Merged
stwaidele merged 4 commits from feat/180-dark-mode into main 2026-07-29 07:56:30 +02:00
Collaborator

Implements issue #180 (first stage of the ADR 0018 theming roadmap). Three commits:

  1. Token migration - the ~10 remaining hardcoded colors in base.css become tokens (badge trio, danger hover, notification badge, overdue date); the phantom --color-primary is replaced by the real --color-accent, the three danger reds are unified on --color-danger. Light rendering visually unchanged.
  2. Dark mode - dark palette as a single :root[data-theme='dark'] block; theme/theme.ts + pre-paint public/theme-init.js (external file, prod CSP forbids inline scripts) always resolve the stored ui.theme.mode to a concrete data-theme; System follows live OS changes via matchMedia. color-scheme flips per theme, paired theme-color metas track the effective theme. New Appearance settings section (native radios, de+en, device-local like #170).
  3. Tests + docs - theme-contrast.test.ts parses tokens.css and mechanically asserts every real UI color pairing for BOTH palettes (4.5:1 text / 3:1 UI, ADR 0017); a11y pack now scans all four core screens in light AND dark (0 axe violations); new theme.spec.ts covers instant apply, reload persistence, live OS follow, override-beats-OS; ADR 0018 records the theming model (modes now, accent derivation later).

Verified locally against a fresh e2e environment (dorfteich_e2e_180): typecheck, eslint+prettier, i18n:check, 119 web unit tests, a11y pack 8/8, theme pack 1/1, dark screenshots of settings/read/editor/graph/admin/system + 320px reflow. html-shell.ts needed no change - its #167 dark colors already match the new tokens.

Note: do not auto-close - issue #180 will be closed manually after a green pipeline (project convention).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX

Implements issue #180 (first stage of the ADR 0018 theming roadmap). Three commits: 1. **Token migration** - the ~10 remaining hardcoded colors in base.css become tokens (badge trio, danger hover, notification badge, overdue date); the phantom `--color-primary` is replaced by the real `--color-accent`, the three danger reds are unified on `--color-danger`. Light rendering visually unchanged. 2. **Dark mode** - dark palette as a single `:root[data-theme='dark']` block; `theme/theme.ts` + pre-paint `public/theme-init.js` (external file, prod CSP forbids inline scripts) always resolve the stored `ui.theme.mode` to a concrete `data-theme`; System follows live OS changes via matchMedia. `color-scheme` flips per theme, paired `theme-color` metas track the effective theme. New Appearance settings section (native radios, de+en, device-local like #170). 3. **Tests + docs** - `theme-contrast.test.ts` parses tokens.css and mechanically asserts every real UI color pairing for BOTH palettes (4.5:1 text / 3:1 UI, ADR 0017); a11y pack now scans all four core screens in light AND dark (0 axe violations); new `theme.spec.ts` covers instant apply, reload persistence, live OS follow, override-beats-OS; ADR 0018 records the theming model (modes now, accent derivation later). **Verified locally** against a fresh e2e environment (`dorfteich_e2e_180`): typecheck, eslint+prettier, i18n:check, 119 web unit tests, a11y pack 8/8, theme pack 1/1, dark screenshots of settings/read/editor/graph/admin/system + 320px reflow. `html-shell.ts` needed no change - its #167 dark colors already match the new tokens. Note: do not auto-close - issue #180 will be closed manually after a green pipeline (project convention). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
fable-5 added 3 commits 2026-07-28 20:51:50 +02:00
New tokens (light values, dark arrives with the theme block): surface-muted
(was fallback-only), danger-contrast, danger-strong, badge ok/error/warn
pairs, chip-outline. Replace the phantom --color-primary with the real
--color-accent and unify the three danger reds (#a02818/#b91c1c/#ab091e)
on --color-danger. attachments-panel maps to bg-subtle, matching its old
6%-grey fallback. Light rendering is visually unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
The dark palette lives as a single :root[data-theme='dark'] block in
tokens.css; theme.ts and the pre-paint public/theme-init.js (external file
because the prod CSP forbids inline scripts) always resolve the stored
ui.theme.mode to a concrete data-theme, so 'system' needs no @media
duplicate and follows live OS changes via matchMedia. color-scheme flips
per theme (native controls/scrollbars), paired theme-color metas track the
effective theme, and the new Appearance settings section offers the
three-way choice as native radios (device-local, like #170). Label chips
gain a chip-outline ring so arbitrary user colors stay separated on the
dark canvas; useEffectiveTheme() is exported for the later pond-scoped
theming stage (ADR 0018).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
#180: dark-mode test fence, both-scheme a11y pack, theme e2e, ADR 0018
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m41s
CI / Build container images (pull_request) Successful in 3m57s
CI / Auth e2e pack (pull_request) Failing after 5m26s
CI / Import/export fidelity gate (pull_request) Has been skipped
4e0ad82220
theme-contrast.test.ts parses tokens.css and asserts every real UI colour
pairing (4.5:1 text, 3:1 UI) for BOTH palettes, so palette drift fails
unit tests instead of review. theme.test.ts covers resolve/apply logic
(Node >= 22 ships a shadowing undefined localStorage global — the test
brings its own in-memory storage). The a11y pack now runs its four scans
in light AND dark via emulateMedia; the new theme pack exercises the
three-way switch end to end (instant apply, reload persistence, live OS
follow in system mode, override beats OS). ADR 0018 records the theming
model broadly: modes now, accent themes by derivation later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
fable-5 added 1 commit 2026-07-28 21:58:44 +02:00
#180: settings jump-nav fence — nine sections since the Appearance section
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m38s
CI / Build container images (pull_request) Successful in 1m29s
CI / Auth e2e pack (pull_request) Successful in 7m44s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 2m36s
CD / Promote to Int (push) Successful in 21s
CI / Lint, typecheck, test (push) Successful in 4m46s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 9m18s
CI / Import/export fidelity gate (push) Successful in 56s
77df813f16
CI run 453 caught it: settings-nav.spec.ts pins the user-settings section
count, which #180's Appearance section raised from 8 to 9. Verified
locally against a fresh e2e environment (both pack tests green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
stwaidele merged commit 77df813f16 into main 2026-07-29 07:56:30 +02:00
Sign in to join this conversation.
No description provided.