Skip to content

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

Closed

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:

Notation Distinct values Representative 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 sites nav-sidebar.css:193 (--foreground-5), tool-output.css:213 (--foreground-3), models.css:160 (--foreground-10), model-switcher.css:44 (--foreground-8)
--hover / --active tokens 4% / 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

Activity

  1. Astro-Han commented on Jul 4, 2026

    @Astro-Han
    ContributorAuthor

    Implementation plan (shape alignment 2026-07-04)

    4 decision points settled via shape; #499 splits into 2 PRs. Color doc rides #496 (already in progress).

    Token model (decision 1)

    • Add --state-hover-bg (oklch 4% foreground alpha) + --state-selected-bg (oklch 6.5% foreground alpha), defined in maka-tokens.css :root and .dark.
    • Retire --hover/--active: delete maka-tokens.css:159-160 definitions, sync the Tailwind bridge --color-hover/--color-active, repoint ~11 references to the new names. Delete the old names outright, no alias.
    • pressed does not take its own bg token; it reuses --state-selected-bg as background, with no scale (see decision 4).

    Selected governance (decision 2, fully neutral, no exceptions)

    • All persistent selection + keyboard nav highlight unify on --state-selected-bg + font-weight 500; brand border/rail removed:
      • persistent selection → neutral: artifact rows (models.css), daily-review archive (daily-review.css), memory preview (tool-stream.css)
      • keyboard nav highlight → neutral: command palette (chat-header.css), search results (sidebar.css)
      • ModelTable: --control 0.07 → --state-selected-bg (--control stays for checkbox/switch/progress)
      • sidebar row: --active 8% → --state-selected-bg 6.5% (align with the settings nav sample)
    • Retire the darwin mint block: delete --color-state-selected (theme-glass.css:92/107) + the darwin override (:117); the platform split collapses.
    • --nav-active is NOT retired (onboarding brand emphasis still uses it); it just stops being used in selected contexts.
    • Drop the "left accent bar" descriptions in design-system.md §3.

    Tab spec (decision 3, single underline variant)

    • Single underline variant, fully neutral: active = var(--foreground) text + font-weight 600 + 2px var(--foreground) under-bar.
    • Primitive refactor (tabs.tsx): drop the default (pill sliding indicator) variant, keep only underline; indicator becomes a foreground neutral under-bar.
    • Migrate 4 surfaces: plan/skill drop the hand-written ::after, catalog drops the brand pill + display:none, daily-review-range migrates onto the primitive (needs a tsx change).
    • Contract test: forbid surfaces from hand-writing tab active/hover CSS; must go through the primitive.

    Press governance (decision 4, delete scale)

    • Delete all hand-written press scale: base.css button:active scale(0.97), buttonVariants active:scale-[0.98], .maka-button translateY(0.5px), the non-button press scales, toast-close 0.92.
    • pressed unifies to :active/data-pressed background --state-selected-bg, no scale. Also drop the reduced-motion transform-none fallback.
    • Rationale: Base UI uses data-pressed background (item.tsx already does); scale is hand-written redundancy, and the three copies disagree with each other.

    Doc + storybook

    PR split + build order

    1. state governance (branch pi/state-governance): token rename + selected neutral + darwin retire + press delete scale + doc state section + storybook + contract extension. First.
    2. tab spec (branch pi/tab-spec): primitive underline + migrate 4 surfaces + contract. Depends on the former; second.

    Out of scope

  2. added 19 commits that reference this issue on Jul 4, 2026
  3. Astro-Han commented on Jul 4, 2026

    @Astro-Han
    ContributorAuthor

    全部 5 部分已落地,关闭本 issue。

    部分 PR 状态
    P0-1 hover/active token 统一(~20 表面漂移) #503 merged
    P0-2 selected/active 5 种机制统一为 neutral + darwin 分叉收口 #503 merged
    P0-3 tab component spec(maka-tab + variant + 4 surface 迁移) #517 merged
    P1 design-system.md 颜色段更新 #496 merged
    P2 press scale 收敛 #503 merged

    P0-3 详情见 #517:一个 tab spec primitive(maka-tab class + underline/pill variant 在 Base UI Tabs 上),active/hover 复用 #503 的 neutral --state-* token(不引专属 tab 颜色 token);4 个手写 tab surface 全迁——plan/skill → 下划线 Tabs+TabsPanel(skill 从手搓 segmented switcher 升级真 tabs,a11y 提升),catalog → pill TabsPanel(active 从 brand 变 neutral),daily-review range → SettingsSegmented(切参数用 ToggleGroup,不是 tabs);顺带清掉 ui.tsx 两套手写 tab 之债(#477)。契约在 tab-spec-499-contract.test.ts(per-value TabsPanel 锁 + 禁手写 tab CSS 防回退)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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