Skip to content

refactor(ui): hover/selected state + tab component governance (post #496 color migrate) #499

Description

@Astro-Han

Background

The #496 color migration (sage green → logo blue) surfaced that hover and selected/active states are not governed by any enforced contract: docs/design-system.md defines a 5-state contract on paper but no test enforces it, so ~20 surfaces drifted to inline oklch / static --foreground-N mixes, and a hardcoded --color-state-selected mint block survived the migration untouched. The same scan also surfaced that tabs have no spec at all — 4 hand-written implementations, one of which bypasses the Base UI Tabs primitive entirely. This issue tracks both governance gaps. The color values themselves are in #496.

Verified against main at 226f31a5 on 2026-07-04 (hover/selected/tab drift is pre-existing; the blue token values land in #496).

Scope

Color values are handled in #496. This issue is about interaction-state governance (hover / selected / pressed) and tab component governance — enforcing the existing 5-state contract, retiring drift, and establishing a tab spec that does not exist yet. Not a visual redesign; the target direction was aligned while doing #496 — list/nav/panel selection = neutral wash + bold (no color, no checkmark), menus/dropdowns keep a checkmark. design-system.md + the settings nav and plan/skill tabs already follow this; the rest of the app does not.

CSS cascade/layer debt is tracked in #476; React architecture debt in #477.

P0 — hover/active state tokens are documented but unenforced; ~20 surfaces drifted

docs/design-system.md:56 defines a 5-state contract (default / hover · focus / active · pressed); :115-116,121 assign --hover (sidebar row / ghost button hover fill, "don't use as active") and --active (sidebar row active / button :active, "don't use as hover"). No contract test enforces this — design-system-governance-406-contract.test.ts only gates var(--accent) usage, not --hover / --active.

Only ~8 sites use --hover/--active. The rest drift across three notations:

NotationDistinct valuesRepresentative sites
inline oklch(from var(--foreground) l c h / X)8 opacities (0.025/0.04/0.045/0.05/0.06/0.065/0.07/0.08)sidebar.css:738 (0.06), nav-sidebar.css:203 (0.065), sidebar.css:762 (0.07), daily-review.css:463 (0.045)
static --foreground-N mix tokens (2/3/5/8/10)5 tokens at ~20 hover sitesnav-sidebar.css:193 (--foreground-5), tool-output.css:213 (--foreground-3), models.css:160 (--foreground-10), model-switcher.css:44 (--foreground-8)
--hover / --active tokens4% / 8%maka-tokens.css:932,1721 (--hover), :934,1722 (--active) — the canonical pattern, underused

--foreground-N is color-mix static real color, not alpha overlay. composer.css:82-86 already documents the bug: --foreground-3 (≈#f7f7f7) is "indistinguishable from the gray plate (≈#ebebeb)" on glass — yet 16 sites still use --foreground-5 and 4 use --foreground-3 as hover backgrounds. frontend-css-governance.md §5 bans hardcoded colors, so the inline oklch hover values technically violate governance, but nothing enforces it.

Impact: hover visibility is inconsistent (some hovers invisible on glass), selection has 5 mechanisms, and any new surface will hand-roll again.

Direction: Add a contract test that enforces the 5-state contract — hover/active backgrounds for list/nav/button-ghost surfaces must resolve to --hover/--active (or a new --state-hover-bg/--state-selected-bg pair), with an allowlist for genuine exceptions (brand CTAs, destructive). Repoint the ~20 drift sites. Retire --foreground-N as hover/selected backgrounds.

P0 — selected/active state has 5 mechanisms and a platform split

  • Sidebar: theme-glass.css:117 (darwin) uses --color-state-selected solid color block; non-darwin sidebar.css:762 uses 7% neutral wash. Platform split.
  • --color-state-selected (theme-glass.css:92,107) is an orphan token — only the sidebar darwin override uses it; the comment claims it will be "reused for chip/tab/nav selected states later" but it never was.
  • Settings nav: nav-sidebar.css:197 uses 6.5% neutral wash + weight 500 (comment: "keep selected row neutral. A leading accent rail reads like a stray status marker in the Settings nav").
  • But the same-class surfaces use brand color instead: search results sidebar.css:464, command palette chat-header.css:850, artifact rows models.css:609, daily-review archive daily-review.css:151, memory preview tool-stream.css:158 all use --nav-active brand color for selected — inconsistent with the settings-nav "neutral" decision.
  • Tabs: see next P0.

Impact: "selected" reads 5 different ways; the colored-vs-neutral boundary is arbitrary per surface.

Direction: Adopt the aligned direction — list/nav/panel selection = neutral wash + bold (no color, no checkmark); menus/dropdowns = checkmark (model switcher, permission mode already do this). Remove --color-state-selected + the darwin override (collapse the platform split). Retire the colored-selected uses on list/panel items (search/palette/artifact/daily-review/memory) to neutral, or document an allowlist if any must stay colored.

P0 — tabs have no spec; 4 hand-written implementations

There is a shared primitive packages/ui/src/primitives/tabs.tsx (Base UI Tabs, default/underline variants + tailwind), but no surface follows it cleanly:

  • plan/skill tabs (module-pages/plan-reminders.css:424, module-pages/skills.css:225): use TabsList/TabsTrigger but hand-write neutral-underline CSS over it.
  • catalog tabs (settings/models.css:164, settings/provider-editor.css:37): use PrimitiveTabsList but display:none the Base UI sliding indicator (models.css:170-176 comment: "the pill background is the single source of the active state") and hand-write brand-color pill.
  • daily-review range tab (daily-review-panel.tsx:398): fully hand-rolled <nav><button className="maka-daily-review-range-tab">, does not use the Tabs primitive at all.

No shared maka-tab base class, no --tab-* token. design-system.md has no tab/segmented-control component section; frontend-css-governance.md doesn't mention tabs; the roadmap doesn't list a tab spec. Only notes/reference-atlas.md:300 mentions a workbench-tab | Tab control variant entry (upstream reference, not Maka's spec).

Impact: 4 visual languages for tabs; new tab surfaces hand-roll again; the primitive exists but is bypassed/overridden.

Direction: Decide one tab spec (variant system — e.g., underline vs pill as intentional documented variants, not per-surface hand-writing), expose it through the primitive + shared class/token, migrate the 4 surfaces, add a contract test that bans hand-written tab active/hover CSS outside the shared spec.

P1 — design-system.md color section is stale post-migration

docs/design-system.md:118 still references "accent 绿…2.46:1" and the old --action/--control green semantics. The #496 migration moved accent/action/control to logo blue (hue 250), but the doc wasn't updated.

Impact: the spec doc contradicts the shipped tokens; future work referencing the doc gets wrong color guidance.

Direction: update the color section to the blue tokens (accent/action/control/foreground values + WCAG ratios). Can ride #496 or this issue's first PR.

P2 — press (:active) scale is not converged

:active press feedback uses 5 scale values: 0.92 (chat-header.css:592 toast-close), 0.97 (global base.css:94 + most), 0.98 (sidebar.css:749 list-row, models.css:605 artifact-row), 0.985 (nav-sidebar.css:630 settings nav/back), 0.99 (daily-review.css:466, sidebar.css:945 prompt-chip) plus translate variants. docs/design-refinement-roadmap-2026-07.md:61 says ":active scale(0.97) 已 shipped" as the standard.

Impact: press feel is subtly inconsistent; no contract guards it.

Direction: converge to 0.97 with a documented allowlist for genuine exceptions; optional contract test.

Suggested PR order

  1. Color doc sync (P1) — update design-system.md color section to blue; can ride feat(design-system): migrate default color system from sage green to logo blue #496.
  2. Hover/active state governance (P0) — consolidate on --hover/--active (or --state-hover-bg/--state-selected-bg), repoint ~20 drift sites, add the 5-state contract test, retire --color-state-selected + the darwin override, unify list/nav/panel selection to neutral + menus to checkmark.
  3. Tab spec (P0) — design the variant system, migrate the 4 surfaces, add a tab contract test. Largest; do after state governance lands.

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions