Skip to content

[Feature] Collapse light theme surface system to match dark #840

@Astro-Han

Description

@Astro-Han

Goal

Apply the same 2-surface / minimal-token collapse to light mode that PR #834 (eab4cbfb78) shipped for dark, so the system has a single coherent token contract across both schemes. Light's pain level is lower (no "foggy" perception issue like dark Settings 1.14:1), so this is system-health work rather than a user-visible fix — keep at P2.

Current state (audit 2026-05-22 against packages/ui/src/styles/theme.css light block, L83-164)

Token group Distinct values today Notes
Surface neutral 4 off-whites: #ffffff / #faf9f7 / #f9f9f9 / #f4f0ec --bg-base and --surface-base both #fff (already aliased), but --sidebar #f9f9f9 and --surface-list-hover #f4f0ec each invent their own off-white. Comment says "2 tiers" but the value count is 4.
Fg ink 4 grays + 1 on-brand: #1a1613 / #4a4138 / #8c817a / #b6ada7 / #ffffff fg-base vs fg-weak reads close in body copy size.
Border 3 tokens, mixed alpha + opaque: rgba(0,0,0,0.162) (--border-base) vs #ece6df / #f3ede6 (--border-weak / --border-weaker) Highest-severity finding: the same "weak border" looks different against #ffffff vs #faf9f7 because two tokens are opaque hex while the base is alpha. Dark mode collapsed all three to a single alpha.
Interactive cream 3 cream-y: #fdf6f0 (selected) / #fbece0 (hover) / #f4f0ec (list hover) Comment in theme.css says selected vs list-hover hue split is intentional, but list-hover hex is essentially a desaturated cream of the same neighborhood.
Icon 4 values, --icon-disabled #c7c7c7 is not derived from fg-* Other icon tokens mirror fg-strong / fg-weak / fg-weaker; icon-disabled invented its own neutral grey.

Target after collapse (proposed, not committed):

  • canvas (#ffffff) — --bg-base, --surface-base, --surface-raised
  • raised (#faf9f7 or new mid value) — --surface-sunken, --bg-cream, --sidebar, --surface-list-hover
  • fg (2 tiers) — collapse 4 grays to strong + weak per dark pattern
  • border (single alpha) — rgba(0, 0, 0, 0.10-0.16), drop the opaque #ece6df / #f3ede6 variants
  • interactive cream — keep #fdf6f0 for selected (brand-tinted identity), drop the second cream hover layer

Why this is P2 and not P0

Dark mode had a real "foggy" / 1.14:1 Settings dissolve user pain (see closed #617). Light doesn't — #ffffff background gives huge perceptual headroom and the border-form inconsistency hides because most users never put two surface tiers next to each other.

But the system contract is asymmetric today: dark is 5 tokens, light is 12+. Future visual work (#583 Context tab, #823 Turn Changes visual) will keep paying the asymmetry tax until light is collapsed too.

Scope when picked up

In scope:

  • packages/ui/src/styles/theme.css light block (L83-164) and the corresponding pawwork.json light overrides
  • packages/ui/test/theme-parity.test.ts — keep green; update the contract if collapse forces shape changes
  • Hardcoded light hex in callsites that don't pull from CSS vars (audit similar to the dark side: packages/app/src/index.css desktop shell, theme-color meta tags, switch thumb, message-part user bubble)
  • Snap regression: the new session-turn-changes / sidebar / sidebar-unread / app-shell targets already capture light shots; before/after diffs go in the PR

Out of scope:

  • Reference-product survey + handoff doc + prototype HTML — the dark redesign used those because it had a real perception problem. Light is system-health; the cleanup pattern can be lifted straight from dark.
  • UNREGULATED token groups (avatar palette, markdown palette, diff hidden surfaces, syntax) — they belong to [Task] Design token system debt log (drift, redundancy, dual systems) #642 governance, not this collapse.
  • Touching dark tokens.

Notes from the dark side

  • --shell-border-{base,weak} in packages/app/src/index.css are color-mix chrome inputs (titlebar + frame), not body dividers. They kept two values in dark; light has the same shape (#D6D2CA + #E3DED6). Keep that exemption in light too with a parallel comment.
  • The user-bubble re-identification trade-off in dark (1.46:1 → 1.15:1, mitigated by right-align + asymmetric corner radius) does not need to repeat for light because the light --surface-interactive-base #fdf6f0 is brand-tinted, not neutral cream — keeping its identity through hue, not luminance.

How to verify (once a PR exists)

bun --cwd packages/ui test theme-parity → light overrides match
bun --cwd packages/ui x tsgo --noEmit and bun --cwd packages/app x tsgo --noEmit → clean
bun run snap app-shell, bun run snap sidebar, bun run snap session-turn-changes, bun run snap sidebar-unread → light halves of each grid should still read coherently; before/after PNGs in the PR body

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium priorityenhancementNew feature or requestuiDesign system and user interface

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions