LC-566: six-palette theme system foundation (redesign P1) #528

Merged
vas2000-work merged 18 commits from docs/LC-541-redesign-p1-foundation into main 2026-07-09 14:27:38 +02:00
Owner

Summary

Phase 1 (Foundation) of the Let's Chat UI/UX redesign (umbrella LC-541, this phase LC-566): a six-palette theme system on an orthogonal data-theme (palette) + data-mode (light/dark/hc-light/hc-dark) model, with a live appearance picker. No product surface is redesigned in P1; the default look (Blue Harbor) is unchanged and existing users keep their theme with zero data transform.

What changed

  • Schema: rename users.theme to users.theme_mode and add users.theme_palette (migration 0037); existing values map straight across, palette defaults to blue-harbor.
  • Selection model B: data-theme = palette (blue-harbor default plus cobalt, ink-ice, arctic, deep-sea, royal-navy), data-mode = light/dark/hc-light/hc-dark. Tailwind darkMode keyed on [data-mode$="dark"].
  • Tokens: main.css split into a palette-constant layer (status + actor-badge, keyed by mode) and a palette-varying layer (surface/content/border/accent/ring/rail/sidebar, keyed by palette x mode) = 24 blocks. Status and actor-badge colors stay palette-independent.
  • Two-axis no-flash bootstrap in base.html resolves palette and mode before paint; adds __lcSetPalette/__lcSetMode; keeps the sidebar quick-toggle, density, and sidebar-collapse bootstraps.
  • Cookie sync (lc-mode/lc-palette) in the locale middleware; routes post_appearance (mode + palette + density) and post_palette (fire-and-forget from the picker); the sidebar quick-toggle writes lc-mode.
  • Appearance picker: six palette swatches (persist on select, cross-device) plus a mode select; en and es locale parity.
  • Dev-only /dev/theme-gallery (release-gated to 404) renders all 24 combos; a WCAG contrast script (server/scripts/contrast-check.mjs) verifies AAA for every high-contrast block and AA for every light/dark block.
  • Components: add the tokenized .lc-table family; conservative radius and card-shadow retune.

Verification (automated, all green)

  • just check (both feature sets, clippy -D warnings, fmt) clean; just test 1456 passed / 0 failed / 1 ignored.
  • Contrast script: 72/72 pass (AAA all high-contrast, AA all light/dark).
  • Migration preserves existing prefs; dev gallery 404s in release builds; route inputs validated against fixed allowlists; a final review found no critical or high issues.

Needs manual (browser) verification before merge

  • No flash-of-wrong-theme on hard reload across palette/mode combos.
  • Instant picker apply with no reload; cross-device persistence after selecting a palette or mode.
  • All 24 combos render legibly on the gallery plus a real room view and Settings; the component retune matches the mockups.
  • Decision to confirm: arctic and deep-sea LIGHT-mode accents were darkened one shade (to sky-700 / cyan-700) so white button text meets WCAG AA; the alternative is to keep the brighter cyan with near-black text. Please confirm the preference.

Design docs

  • Spec: docs/superpowers/specs/2026-07-09-lets-chat-redesign-p1-foundation-design.md
  • Plan: docs/superpowers/plans/2026-07-09-lets-chat-redesign-p1-foundation.md

Tracked as LC-566 (relates LC-541). Later phases (P2 core chat pixel-match, P3 utility-page migration, P4 remaining-surface sweep, P5 public/brand, P6 accessibility regression) get their own specs and PRs.

## Summary Phase 1 (Foundation) of the Let's Chat UI/UX redesign (umbrella LC-541, this phase LC-566): a six-palette theme system on an orthogonal `data-theme` (palette) + `data-mode` (light/dark/hc-light/hc-dark) model, with a live appearance picker. No product surface is redesigned in P1; the default look (Blue Harbor) is unchanged and existing users keep their theme with zero data transform. ## What changed - Schema: rename `users.theme` to `users.theme_mode` and add `users.theme_palette` (migration 0037); existing values map straight across, palette defaults to blue-harbor. - Selection model B: `data-theme` = palette (blue-harbor default plus cobalt, ink-ice, arctic, deep-sea, royal-navy), `data-mode` = light/dark/hc-light/hc-dark. Tailwind darkMode keyed on `[data-mode$="dark"]`. - Tokens: `main.css` split into a palette-constant layer (status + actor-badge, keyed by mode) and a palette-varying layer (surface/content/border/accent/ring/rail/sidebar, keyed by palette x mode) = 24 blocks. Status and actor-badge colors stay palette-independent. - Two-axis no-flash bootstrap in `base.html` resolves palette and mode before paint; adds `__lcSetPalette`/`__lcSetMode`; keeps the sidebar quick-toggle, density, and sidebar-collapse bootstraps. - Cookie sync (`lc-mode`/`lc-palette`) in the locale middleware; routes `post_appearance` (mode + palette + density) and `post_palette` (fire-and-forget from the picker); the sidebar quick-toggle writes `lc-mode`. - Appearance picker: six palette swatches (persist on select, cross-device) plus a mode select; en and es locale parity. - Dev-only `/dev/theme-gallery` (release-gated to 404) renders all 24 combos; a WCAG contrast script (`server/scripts/contrast-check.mjs`) verifies AAA for every high-contrast block and AA for every light/dark block. - Components: add the tokenized `.lc-table` family; conservative radius and card-shadow retune. ## Verification (automated, all green) - `just check` (both feature sets, clippy `-D warnings`, fmt) clean; `just test` 1456 passed / 0 failed / 1 ignored. - Contrast script: 72/72 pass (AAA all high-contrast, AA all light/dark). - Migration preserves existing prefs; dev gallery 404s in release builds; route inputs validated against fixed allowlists; a final review found no critical or high issues. ## Needs manual (browser) verification before merge - No flash-of-wrong-theme on hard reload across palette/mode combos. - Instant picker apply with no reload; cross-device persistence after selecting a palette or mode. - All 24 combos render legibly on the gallery plus a real room view and Settings; the component retune matches the mockups. - Decision to confirm: arctic and deep-sea LIGHT-mode accents were darkened one shade (to sky-700 / cyan-700) so white button text meets WCAG AA; the alternative is to keep the brighter cyan with near-black text. Please confirm the preference. ## Design docs - Spec: `docs/superpowers/specs/2026-07-09-lets-chat-redesign-p1-foundation-design.md` - Plan: `docs/superpowers/plans/2026-07-09-lets-chat-redesign-p1-foundation.md` Tracked as LC-566 (relates LC-541). Later phases (P2 core chat pixel-match, P3 utility-page migration, P4 remaining-surface sweep, P5 public/brand, P6 accessibility regression) get their own specs and PRs.
Approved Phase 1 (Foundation) design for the Let's Chat UI/UX redesign: a six-palette token system (Blue Harbor, Cobalt, Ink+Ice, Arctic, Deep Sea, Royal Navy) across light/dark/hc-light/hc-dark, selected via an orthogonal data-theme(palette) + data-mode model, with a two-axis no-flash bootstrap, a users.theme -> theme_mode rename plus a new theme_palette column, a live appearance picker, a component-base retune, and a theme/component gallery as the proof. Part of the phased redesign program tracked under LC-541; later phases (core chat pixel-match, utility migration, surface sweep, brand, a11y) get their own specs.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-by-task TDD plan for Phase 1: the six-palette theme system (data-theme
palette + data-mode), migration, two-axis no-flash bootstrap, appearance
picker, component retune, and the theme/component gallery. Executes the
approved P1 spec under LC-541.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename users.theme -> users.theme_mode and add users.theme_palette (NULL = blue-harbor). Groundwork for the six-palette theme system; existing rows keep their mode and default the palette in.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename User.theme -> theme_mode, add theme_palette, with theme_mode_or_system()/theme_palette_or_default(), updated row hydration, and the settings template call.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename set_user_theme -> set_user_theme_mode (UPDATE theme_mode), add set_user_theme_palette, and update the settings route call sites.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
post_appearance persists mode + palette + density; add POST /settings/palette with an lc-palette cookie; the sidebar quick-toggle now writes lc-mode.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolve_locale mirrors the mode and palette prefs into the lc-mode / lc-palette cookies (plus density) so a fresh device gets the saved theme no-flash.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Constant status/badge tokens keyed by data-mode; blue-harbor varying tokens under [data-theme="blue-harbor"][data-mode]. Pure restructure, values unchanged.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add cobalt, ink-ice, arctic, deep-sea, royal-navy varying-token blocks for light, dark, hc-light, hc-dark. Status/badge tokens stay palette-independent; HC surfaces inherit blue-harbor HC with per-palette accents.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve data-theme (palette) and data-mode (light/dark/hc) independently before paint; add __lcSetPalette/__lcSetMode, keep the quick-toggle and density/sidebar bootstraps; re-key the HC focus ring to data-mode.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the theme select with a six-palette swatch radiogroup wired to __lcSetPalette plus a mode select wired to __lcSetMode; add swatch styles and locale strings. The old __lcSetTheme call site is gone.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The appearance picker added eight en catalog keys (settings-palette, settings-mode, and the six palette names); mirror them in es so the i18n_catalog parity gate stays green.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a debug-only GET /dev/theme-gallery route that renders every shared component (buttons, input, card, alerts, table row, actor badge) across all six palettes and four modes (light / dark / hc-light / hc-dark) as 24 themed cells, so a palette edit can be eyeballed against the token contract in one page.

The handler returns 404 in release builds via cfg!(debug_assertions) and is never user-facing; it is registered unconditionally so it compiles in every profile. The template is a standalone HTML document (it does not extend base.html) that loads the same tailwind-built.css + main.css the app uses. The view struct supplies palettes and modes as slices because Askama cannot iterate a literal array in a template.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add server/scripts/contrast-check.mjs, a WCAG verifier that parses main.css, resolves each palette x mode combo to its varying token block, and checks three pairs (content/surface, content-muted/surface, accent-content/accent) at AAA (>= 7:1) for the hc-light/hc-dark modes and AA (>= 4.5:1) for light/dark. It prints a row per combo+pair and exits non-zero on any failure. Run it with `bun server/scripts/contrast-check.mjs`.

Revert the high-contrast --ring for the five non-default palettes back to the max-contrast #000000 (hc-light) / #ffffff (hc-dark) that Blue Harbor already uses, undoing Task 8 which had set them to the palette accent and lowered focus-ring contrast in HC.

Nudge the flagged HC accents so accent-content reaches AAA while keeping each palette hue: darken every hc-light accent that failed (Blue Harbor blue-700 was only 6.70:1 despite the comment claiming AAA, plus cobalt, arctic, deep-sea, royal-navy) and lighten cobalt's hc-dark accent. Fix the now-stale Blue Harbor HC comment.

The script also flagged two LIGHT (non-HC) accents on the approved stubs: arctic sky-600 (4.10:1) and deep-sea cyan-600 (3.68:1) both fail AA for white button text, so darken them to sky-700 / cyan-700. All 72 checks (24 combos x 3 pairs) now pass.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the tokenized .lc-table family for tables, and nudge .card/.btn/.input radius plus a subtle card shadow toward the mockup look. No color changes.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mermaid (and any other client JS reading the mode off data-theme) now read data-mode, treating dark and hc-dark as dark, since data-theme holds the palette. Also convert stray em-dashes in touched files to hyphens.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(theme): persist palette on select; darkMode hc-dark
All checks were successful
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 4s
check-secrets / TruffleHog (pull_request) Successful in 5s
check-secrets / Nosey parker (push) Successful in 1m24s
check-secrets / Kingfisher (push) Successful in 1m25s
check-secrets / TruffleHog (push) Successful in 1m25s
Check / clippy + fmt + tests (pull_request) Successful in 2m56s
Create release / Create release from merged PR (pull_request) Has been skipped
e0fd465ce6
Wire the appearance palette swatches to fire-and-forget POST /settings/palette so palette persists cross-device on select (not only on Save); fix the Tailwind darkMode selector to $= so dark: also fires on hc-dark; correct stale single-axis comments.

#LC-541

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vas2000-work deleted branch docs/LC-541-redesign-p1-foundation 2026-07-09 14:27:38 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!528
No description provided.