Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat(design-system): migrate default color system from sage green to logo blue by Astro-Han · Pull Request #496 · apache/maka · GitHub
Skip to content

feat(design-system): migrate default color system from sage green to logo blue - #496

Merged
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue
Jul 4, 2026
Merged

feat(design-system): migrate default color system from sage green to logo blue#496
Astro-Han merged 11 commits into
mainfrom
pi/color-to-blue

Conversation

@Astro-Han

Copy link
Copy Markdown
Contributor

Summary

Migrate the default color system from sage green to the logo blue (hue 250), sampled from apps/desktop/assets/icon.png. Direction A: the primary CTA becomes a pale-blue chip with deep-blue text; checked controls use a slightly deeper blue tuned to clear WCAG 1.4.11. Split into two commits by concern.

Why

The product logo is blue; the prior sage-green accent never matched it, and the dark-green primary button read as heavy rather than the "light, fresh" feel the team wanted. Aligning the UI to the logo color is a full color-system repoint, not a single-button tweak.

Owner decision 2026-07-03.

Scope

Changed:

  • --accent and its aliases (--link, --focus-ring, --status-running, --nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default): sage green → logo blue, in :root and .dark.
  • --action → pale-blue chip (oklch 0.85 0.08 250), --action-foreground → deep-blue text (oklch 0.30 0.06 250), in :root and .dark.
  • --controloklch 0.65 0.135 250 (one step below the accent L0.70) so the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo L0.70 only reaches 2.55:1), in :root and .dark.
  • --accent-rgb updated to match (light 77, 169, 220; dark 77, 182, 241).
  • design-system-governance-406-contract.test.ts: action-foreground literal → oklch(0.30 0.06 250); control/control-foreground contrast bar 4.5 → 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical objects, not text; action keeps 4.5).

Not included:

  • The 9 alternative palettes ([data-maka-theme=...]) are untouched; their buttons inherit the new --action via .dark, but their per-theme accents stay.
  • --success stays an independent green (connected/ok semantics).
  • Screenshot baselines under apps/desktop/tests/screenshots-baseline/ are now stale (still show the old green UI); no automated test compares them, so the suite stays green. Regenerating them is a separate follow-up.

Verification

  • Full desktop suite: 1821 pass / 0 fail (npm run -w @maka/desktop test).
  • Storybook (default theme): primary button renders as a pale-blue chip (rgb ≈ 167,210,255) with deep-blue text (rgb ≈ 21,48,76); checked checkbox renders at L0.65 (rgb ≈ 72,147,222).
  • --control contrast verified with the contract test's own oklch→sRGB math: 3.09:1 vs white; action/action-foreground is 8.64:1.

User-facing impact

The whole UI shifts from sage green to logo blue: links, focus rings, active nav, selection, badges, the bot brand mark, the primary button (now a light pale-blue chip instead of a dark-green slab), and checked checkboxes/switches/radio/progress. Connected/success states stay green. CSS token values only; no data migration.

Reviewer notes

  • Two commits, split by rollback reason: (1) decorative accent repoint; (2) CTA + controls + WCAG tuning + the governance test update. Each commit independently builds and passes the suite.
  • The 4.5 → 3.0 threshold change on the control pair is a correction, not a relaxation: control/control-foreground colors graphical objects (checkbox check, switch knob, radio dot, progress fill), so WCAG 1.4.11 non-text (3:1) is the applicable bar, not the 4.5:1 text bar. --control was tuned (L0.65) to clear it; the prior 4.5 only passed because the old green happened to be dark.
  • The 9 alt palettes intentionally keep their decorative accents (mauve/coral/sky/forest…); only the default light/dark identity moves to logo blue.

…o blue
--accent and its aliases (--link, --focus-ring, --status-running,
--nav-active, --toast-accent, --selection, --brand-deep, --bot-brand-default)
move from sage green (oklch 0.70 0.135 152) to the logo blue sampled from
apps/desktop/assets/icon.png (oklch 0.70 0.135 250). --success stays an
independent green, so connected/ok semantics are unchanged. The primary CTA
and checked controls keep green for now; the next commit migrates them to
the blue family with WCAG tuning.
The #406 governance contract still passes: it pins --action/--control
literals and contrasts, which this commit does not touch.
… blue
Direction A: --action becomes a pale blue chip (oklch 0.85 0.08 250) with
deep-blue text (--action-foreground oklch 0.30 0.06 250), a light, fresh
button replacing the dark-green solid. --control moves to oklch 0.65 0.135
250, one step below the accent L0.70, so the near-white glyph clears WCAG
2.1 SC 1.4.11 non-text contrast 3:1 (3.09:1); the logo L0.70 only reaches
2.55:1. Dark mode uses the same action/control values for consistency.
control-foreground stays near-white; --success stays green.
Update the #406 governance contract: action-foreground literal becomes
oklch(0.30 0.06 250), and the control/control-foreground contrast bar moves
4.5 -> 3.0 with a comment citing WCAG 1.4.11 (the pair paints graphical
objects — checkbox check, switch knob, radio dot, progress fill — not text;
action keeps the 4.5:1 text bar). action contrast is 8.64:1.
Verified: full desktop suite 1821 pass / 0 fail; storybook renders the
default primary button as a pale-blue chip + deep-blue text and the
checked checkbox at L0.65.
Two greens survived the accent migration because they were hardcoded
literals, not --accent aliases, so they never tracked the brand color.
- Sidebar selected-row fill (theme-glass.css --color-state-selected):
the darwin glass "QoderWork mint" block was #8ee5a1 (light) /
oklch(0.42 0.075 152) (dark), intentionally separate from --accent.
Repoint to the same L/C at hue 250 — light oklch(0.85 0.09 250),
dark oklch(0.42 0.075 250) — so the selected session reads as the
brand blue while keeping the "color block IS the signal" behavior
(~7:1 dark-foreground contrast). Fixed literals, not var(--accent),
per the #406 governance ban on raw --accent outside token blocks.
- Default palette swatch (theme-preview.css .settingsPaletteSwatch-
default): the swatch color was hand-copied from the old sage accent
oklch(0.70 0.135 152) and never updated when :root's accent moved to
250. Update to oklch(0.70 0.135 250) so the Appearance picker previews
the default palette's real (blue) brand identity. The forest swatch
stays green — forest is a green alt theme.
Verified: full desktop suite 1821 pass / 0 fail; 406 governance
contract passes (no raw var(--accent) introduced).
… gradient
The permission-mode chip mapped the "caution" tone (used by auto-execute
and bypass) to --success (green), which read as "safe" on the two riskiest
modes and never followed the brand migration. Replace with a risk
gradient that matches the modes' actual escalation:
- ask (询问权限) → accent (blue, controlled/safe)
- execute (自动执行) → info (amber, auto but stops for destructive)
- bypass (跳过确认) → destructive (red, skips all confirmation incl. destructive)
composer.tsx: drop 'caution' from the PermissionModeMeta tone union, add
'destructive', repoint execute → 'info' and bypass → 'destructive'.
tool-output.css: replace the dead .maka-composer-mode-chip[data-tone="caution"]
rule (which wired caution → --success) with a [data-tone="destructive"] rule
wired to --destructive; the existing [data-tone="info"] rule now covers
execute. Dropdown items stay neutral (active row = nav-active blue), unchanged.
permission-dialog.tsx keeps its own separate tone system
(info/caution/destructive) for the approval popup; this change is scoped
to the composer chip.
Verified: typecheck @maka/ui clean; full desktop suite 1821 pass / 0 fail.
The color section still described the pre-migration green system: "accent
绿…约 2.46:1 不达标", "主操作用深绿 --action", and the "不 flip 到
--foreground" rationale for the old deep-green solid button.
Rewrite §1.1's color rationale paragraph to Direction A (PR #496):
- --action is now a pale-blue chip oklch(0.85 0.08 250) + deep-blue text
--action-foreground oklch(0.30 0.06 250), 8.64:1 — a light/fresh CTA, not
a dark solid button.
- --control oklch(0.65 0.135 250) is one L-step below the accent L0.70 so
the near-white glyph clears WCAG 1.4.11 non-text 3:1 (3.09:1; the logo
L0.70 only reaches 2.55:1, hence the separate control L).
- --accent is the logo blue oklch(0.70 0.135 250); links/focus ring/live
dots/nav active/toast accent stay on it.
- --success stays an independent green (connected/ok semantics), not tied
to --accent.
No other color prose in the doc was stale; the token usage table (which
describes roles, not values) is unchanged. Verified no remaining
"accent 绿 / 深绿 / 2.46 / flip / sage(mint) / hue 152" references.
The 24 committed stable-scenario baselines (artifact-pane / first-run /
artifact-errors × light/dark × 1280/990 × motion/reduced-motion) still
showed the pre-migration sage-green UI, so `screenshots:diff:stable` would
flag every PNG after PR #496's color migration. Recapture against the blue
renderer dist and update the baseline.
Captured on darwin arm64 (the primary dev platform per ui-quality-plan §8)
via the existing harness (fixed viewport + seed, 60s/capture timeout); all
24 captures passed. Baseline updated with
`diff-screenshots.mjs --update-baseline --subset stable`; manifest.json
refreshed.
--accent-rgb (light 77,169,220 / dark 77,182,241) had two problems: nothing
referenced it (no var(--accent-rgb) call site — only the two definitions
exist), and the values were wrong. Recomputing oklch(0.70 0.135 250) -> sRGB
gives (87, 163, 239) and oklch(0.74 0.15 250) -> (88, 176, 255), not the
values written in 6fe2bd3 (an oklch->sRGB math error there).
Since there are no callers, delete both definitions instead of correcting
them — removes the OKLCH/RGB dual-source inconsistency with no behavior
change. Other *-rgb tokens (used by shadow rings) are untouched.
…406 test
Two related fixes from review:
1. Permission-mode chip + raw --info/--warning text was low-contrast.
--info (L0.75 amber) as the "自动执行" chip text was 2.29:1 on white —
fails WCAG AA text (4.5:1), a regression from 43a6450 (execute
caution->info). Switch the chip text (info/destructive) and the other
raw --info/--warning-as-text sites to the *-text variants (--info-text /
--destructive-text / --warning-text = color-mix 50% with --foreground,
7.25:1 / 10.83:1 / 7.25:1), which clear 4.5:1. Raw tones stay on
borders/fills. Sites: tool-output.css (chip), theme-preview.css,
models.css (needs_reauth, session-branch banner, artifact-list-error
icon), permission-center.css, tool-stream.css. Fills/borders and the
hljs code-highlighting (already calc-darkened) are unchanged.
2. The 406 governance test still asserted the OLD doc rationale
(/不 flip 到 `--foreground`/ and /2.46:1/) that 3343d07 removed from
design-system.md, so the test was failing. Update the assertions to the
new blue rationale (8.64:1 action, 3.09:1 control, WCAG 1.4.11).
Add a precise contrast contract: --info-text / --destructive-text must
clear 4.5:1 vs --background in :root and .dark (computed via color-mix in
oklab), plus a structural check that the chip uses the *-text variants.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…ocs/allowlist
Two follow-ups from the second review pass:
1. The default "询问" permission chip still used raw --nav-active (= --accent,
L0.70) as 11px text — 2.66:1 on white, failing WCAG AA text (4.5:1). Switch
the accent chip text to --foreground-secondary (color-mix foreground 80% +
background 20%, ~8:1 light / ~7.5:1 dark), matching the info/destructive
chips' readable-text pattern; raw accent stays on the border. Extend the
chip contrast contract to all three tones (accent/info/destructive) in
:root and .dark.
2. The design-system doc's 6-color philosophy still listed "accent (purple)"
(stale — accent was green, now logo blue) and the 406 test's
allowedAccentTokenNames still included --accent-rgb after f1803f7 deleted
it. Fix the doc to "accent (logo blue)", drop the dead allowlist entry, and
add an anti-regression assertion that maka-tokens.css must not re-introduce
--accent-rgb.
Verified: full desktop suite 1822 pass / 0 fail; screenshots:diff:stable 0.
…s in maka-tokens.css
The 6-color philosophy comment at the top still said "accent (purple)" — a
placeholder that was never accurate (accent was green, now logo blue) — and
two PR-reference comments still described a "sage-green accent" that the
logo-blue migration replaced. Sync maka-tokens.css to the logo-blue reality:
- line 9: accent (purple) -> accent (logo blue)
- drop the "(replacing the prior sage-green accent)" parenthetical from the
brand-accent note (migration history lives in git/PR #496)
- delete the stale PR-UI-ALIGN-0 dark-mode note (it tied dark accent to the
reference theme's green #5cb870 family, which no longer holds)
docs/design-system.md was already fixed in f5df8aa; this completes the
source-file side. grep "accent (purple)" / "sage-green accent" now returns 0.
… catches deletion/rewire
The chip contrast test computed a theoretical readable color (info/destructive
50% + foreground) and structurally checked the CSS references
var(--info-text)/var(--destructive-text), but never read the actual token
definitions. If a *-text token was deleted or rewired to var(--info), the chip
would silently fall back to the raw 2.29:1 tone while the test still passed.
Add four assertions before the contrast loop: the :root --info-text /
--destructive-text definitions must equal color-mix(in oklab, <tone> 50%,
var(--foreground)), and .dark must not override either (it inherits the :root
formula, re-resolving with dark's tone/foreground). Verified by temporarily
rewiring --info-text to var(--info) — the test fails with ":root --info-text
must be color-mix(info 50%, foreground)"; restoring it passes.
@Astro-Han
Astro-Han merged commit 12ad19a into mainJul 4, 2026
@Astro-Han
Astro-Han deleted the pi/color-to-blue branch July 4, 2026 08:29
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Resolve theme-glass.css conflict: #499 retires the darwin
--color-state-selected token (platform split collapses to neutral
--state-selected-bg); #496 (12ad19a) had repointed it mint→blue. The
token has no consumers (session row moved to --state-selected-bg in
slice 2A), so deleting the orphan blue definition is safe and keeps
#499's darwin-neutral goal.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
Astro-Han added a commit that referenced this pull request Jul 4, 2026
…d neutral, drop press scale (#503)
* refactor(ui): retire --hover/--active for --state-hover-bg/--state-selected-bg
First slice of #499 state governance. Add --state-hover-bg (4% foreground
alpha and --state-selected-bg (6.5%) in maka-tokens.css :root; the
relative-color var(--foreground) substitution auto-follows .dark, matching
how --border/--ring are already defined (no .dark override needed).
Retire --hover/--active: delete the :root definitions, repoint the Tailwind
bridge (--color-hover/--color-active) and all ~11 consumers (sidebar row /
button recipes, theme-preview, models) to the new tokens. No alias.
design-system.md §1.0/§1.1/§3 repoint the token references; the §1.1 table
now splits selected vs pressed semantics.
Add state-token-governance-499-contract.test.ts locking the new definitions,
the full retire (no var(--hover)/var(--active) consumer or definition left),
and the doc registration.
Sidebar row selected converges 8%->6.5% (aligns the settings nav sample).
.maka-button :active keeps its translateY(0.5px) for now; press-scale
removal is a later slice.
The pre-existing foreground-tier-contract fail (--foreground-50 in
base.css:27) is unrelated to this slice and stays for the --foreground-N
retirement slice.
EOF
)
* refactor(ui): retire darwin mint selected block, unify session row to --state-selected-bg
Slice 2 commit A of #499: collapse the platform split in session-list
selection.
- Retire --color-state-selected (light mint / dark deep sage) and the
darwin-only .maka-list-row[data-active] override in theme-glass.css.
Darwin session rows now fall back to the shared .maka-list-row[data-active]
rule, so selected reads the same on macOS glass and non-glass builds.
- sidebar.css session row: hover 6% inline oklch -> var(--state-hover-bg)
(4%), selected 7% inline oklch -> var(--state-selected-bg) (6.5%). Both
were hand-rolled foreground-alpha mixes, not the state tokens.
- design-system.md §3.6: drop the stale "left accent bar" claim (session
rows never had one — selected is background + name weight 600).
- Clean up the now-stale theme-glass comments referencing the mint fill.
* refactor(ui): unify selected surfaces to --state-selected-bg, drop brand rails
Slice 2 commit B of #499: every persistent-selection + keyboard-nav
highlight surface now reads as a neutral wash + bold, no brand color.
- search results (sidebar.css): hover 5% inline -> --state-hover-bg,
selected --nav-active 8% -> --state-selected-bg, drop the inset brand
rail; focus bg 4% inline -> --state-hover-bg.
- command palette (chat-header.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, drop the ::before brand
rail; :active translateY kept for the press-scale removal slice.
- artifact rows (models.css): hover --foreground-5 -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border ->
--border-strong.
- daily-review archive (daily-review.css): hover 4% inline -> --state-hover-bg,
selected --nav-active 10% -> --state-selected-bg, brand border -> --border-strong.
- memory preview + scope (tool-stream.css): selected --nav-active /
--success fills -> --state-selected-bg + --border-strong; the header
span accent -> foreground.
- ModelTable default row (models.css): --control 7% -> --state-selected-bg
(--control stays for checkbox/switch/progress); the enabledDefaultTag
brand pill is kept as a "default model" badge, not a selected background.
- design-system.md §3.10: drop the stale "left accent bar" claim.
- command-palette-a11y-copy-contract.test.ts: repoint the hover/active
assertions from --foreground-5 / brand ::before rail to --state-hover-bg
/ --state-selected-bg (the test was locking the old brand-rail design).
--nav-active is NOT retired (onboarding brand emphasis still uses it);
only selected contexts stop consuming it.
* refactor(ui): drop press scale on neutral surfaces, press via --state-selected-bg
Slice 3 commit C of #499: neutral-surface press feedback stops using a
hand-written scale transform and uses the state-selected background
instead, matching the Base UI data-pressed pattern (item.tsx already
does this).
- base.css: drop the global button:active scale(0.97) + its
prefers-reduced-motion / data-maka-reduced-motion transform:none
fallbacks, and drop transform from the button transition. Button
variants still have their own active:scale (removed in commit D).
- session list row / prompt chip (sidebar.css): :active scale ->
background --state-selected-bg, drop the reduced-motion fallback.
- artifact row (models.css): :active scale -> --state-selected-bg.
- settings nav/back (nav-sidebar.css): :active scale + transition
transform -> --state-selected-bg.
- daily-review list item (daily-review.css): :active scale ->
--state-selected-bg, keep the reduced-motion transition:none for bg.
- toast action/close + palette item (chat-header.css): :active
scale/translateY -> --state-selected-bg, drop the reduced-motion
toast fallback.
- markdown link internal (markdown-link.css): :active translateY ->
--state-selected-bg.
- command-palette-a11y-copy-contract.test.ts: repoint the palette
:active assertion from translateY(1px) to var(--state-selected-bg).
Button variants (.maka-button, buttonVariants) and the solid buttons
(composer send / plan-new-task / chat-jump-bottom / model switcher /
composer tool) still carry their press scale; commit D removes those
and gives each variant an :active background.
* refactor(ui): drop press scale on buttons, press via variant background
Slice 3 commit D of #499: button press feedback stops using a hand-written
scale transform and uses an :active background, matching the Base UI
data-pressed pattern. Hover scale (send 1.03, tool 1.02) stays — that is
hover magnification, not press.
- buttonVariants (ui.tsx): drop the shared active:scale-[0.98] and the
transform transition. Solid variants (default/destructive) get
active:bg-*/90 (= hover state); neutral variants
(secondary/ghost/outline/quiet) get active:bg-[var(--state-selected-bg)].
- .maka-button (maka-tokens.css): :active drops translateY, keeps the
--state-selected-bg background; transition drops transform.
- composer send (composer.css): :active drops scale, keeps the inset
box-shadow as the press cue; hover scale(1.03) stays.
- plan-new-task (plan-reminders.css): :active scale -> bg oklch(0.14)
(deeper than base 0.18, same hardcoded family as the rest of this
button); hover translateY stays.
- chat-jump-bottom (chat-header.css): :active drops scale, keeps
translateX(-50%) positioning, adds --state-selected-bg.
- model-switcher trigger (model-switcher.css): :active scale ->
--state-selected-bg; transition drops transform.
- composer tool button (tool-output.css): :active scale ->
--state-selected-bg; hover scale(1.02) stays.
Toast enter/exit keyframes (chat-header.css scale 0.97/0.98) and modal
exit / pulse keyframes (maka-tokens.css) are decorative/functional
animation, not press feedback — kept.
* fix(ui): use --muted-foreground for lazy-fallback text instead of raw --foreground-50
base.css .maka-lazy-fallback used color: var(--foreground-50), which is a
deleted raw stop (40..95) the foreground-tier contract forbids. Repoint
to the semantic --muted-foreground alias (same 50% ink intent). This
clears the two pre-existing foreground-tier-contract failures.
Rides #499 state governance: the --foreground-N retirement slice.
* refactor(ui): converge hover backgrounds to --state-hover-bg, retire drift
Slice 4 of #499 (P0-1): every :hover background that hand-rolled a
--foreground-N mix or an inline oklch(from var(--foreground) .../0.0X)
alpha now uses the --state-hover-bg token (4%). ~33 sites across the
renderer.
Settings nav/back/form-row, model switcher trigger, first-run checklist
action, health/permission refresh buttons, toast close, chat jump-bottom,
shell topbar button, workspace icon action, search-modal close/clear,
plan template card / card menu trigger / preset, composer mode/model
chip, composer tool button, model catalog pill tabs, browser nav button,
artifact pane collapse / error retry / toolbar button, skill
template/library row, daily-review list item, settings row, theme
option, catalog tab, settings close button, code-block copy, message copy.
Hover magnification transforms (send 1.03, tool 1.02, skill-library
translateX) are hover feedback, not press — kept. The accent border on
theme-option hover is kept (brand accent, not a foreground wash).
This retires --foreground-N and inline foreground-alpha as hover
backgrounds; the remaining --foreground-N uses are card/plate washes
(§1.1 "极淡填充"), not hover state.
* test(ui): lock interaction-state tokens with a storybook story + contract
Slice 4 finish of #499: pin the state-token governance so future PRs
can't silently drift back.
- packages/ui/stories/interaction-states.stories.tsx: a new Design
System story showing list-row and button default/hover/selected/
pressed states side by side, so reviewers can see hover (4%) <
selected (6.5%) and that pressed is a background, not a scale.
- storybook-baseline-contract.test.ts: register the Interaction States
story in the foundation surface list.
- state-token-governance-499-contract.test.ts: add a contract that
:hover backgrounds must use --state-hover-bg, not --foreground-N or
inline oklch(from var(--foreground)...) drift.
- design-system.md §3.1: note that pressed has no scale transform.
* refactor(ui): finish selected neutral on palette icon + memory prompt
Review P2-1: two selected/active surfaces still used --nav-active (brand)
after slice 2B. The command-palette active icon and the memory prompt
active preview kept brand color/border, so users still saw brand-color
selection in those two spots.
- chat-header.css: .maka-palette-item[data-active] .maka-palette-icon
background + color -> --state-selected-bg + --foreground.
- tool-stream.css: .settingsMemoryPromptPreview[data-active] border +
background -> --border-strong + --state-selected-bg; active header
span -> --foreground.
The brand --nav-active pair stays for onboarding emphasis, tone="accent"
status, and the memory pill / prompt-chip brand controls (not selected).
* refactor(ui): drop dead .maka-palette-item::before rail
Review P3-2: after slice 2B removed the brand-color rail, the ::before
pseudo-element kept a transparent background, absolute positioning, a
2px width, and a background-color transition — pure dead CSS. The
parent has no position: relative, so the rail never positioned against
the item anyway. Delete the block.
* refactor(ui): finish selected neutral on composer-mode-menu + lock with contract
Review P2-2: the state-token contract only checked :hover backgrounds, so
selected/active child elements could keep stealing --nav-active (brand).
Add an allowlist contract that selected/active selectors
([data-active]/[data-state="active"]/aria-selected) must not use
--nav-active, except onboarding brand emphasis (onboarding.css) and tab
surfaces pending the tab-spec PR (daily-review-range-tab, catalogTab,
catalogPillTabs, skill-tab, plan-tab).
The contract caught one more sibling: .maka-composer-mode-menu
[data-active="true"] used --nav-active on the active mode label with no
background, so the active item was distinguished only by brand color.
Add an active-item background (--state-selected-bg) and repoint the
label to --foreground, so selection reads as a neutral wash like the
palette and memory prompt.
* test(ui): split Interaction States story into neutral + solid, drop fake inline
Review P3-1: ButtonStates demoed the default (solid) button's hover/pressed
with inline --state-hover-bg/--state-selected-bg, but the real default
variant presses to bg-primary/90 (ui.tsx), so the story misled
maintainers into thinking solid buttons use state tokens.
- Split ButtonStates into NeutralButtonStates (ghost/outline/secondary/
quiet: hover = bg-muted / var(--muted), pressed = --state-selected-bg —
matches the real neutral variants) and SolidButtonStates (default/
destructive: show the default fill and note that press is variant/90,
not a state token — no fake inline).
- ListRowStates unchanged: list rows really do use the state tokens.
- storybook-baseline-contract expected exports updated.
* test(ui): justify chat-header.css palette-input focus !important
Main #496 (12ad19a) added `box-shadow: none !important` on the palette
input focus reset but didn't add the in-file `Justified:` note + ALLOWLIST
entry that renderer-important-audit-contract requires. The audit fails on
main too. Add the justification so the merged PR is green.
* test(ui): cover [data-selected] in state-token selected/active contract
SELECTED_ACTIVE only matched [data-active]/[data-state="active"]/aria-selected,
so .maka-artifact-row[data-selected="true"] in settings/models.css escaped the
--nav-active ban. Add [data-selected to the regex (minimal: only the spelling
that already exists in the codebase). Verified red→green: temporarily repointing
the artifact row selected bg back to --nav-active fails the contract; restoring
--state-selected-bg passes.
* refactor(ui): drop dead press-scale CSS residue (palette/toast/artifact-row)
Slice 3C/3D deleted the press scale but left behind transform scaffolding
with no behavior:
- .maka-palette-item: position: relative (orphaned after ::before was
deleted; JSX children are grid spans, no abs descendants), transform:
translateY(0) (identity), transition: transform 140ms (no transform
target — :hover/[data-active]/:active only change background)
- .maka-toast-action / .maka-toast-close: transition: transform (slice 3D
only touched buttonVariants in ui.tsx, not the hand-written toast CSS)
- .maka-artifact-row: stale PR-ARTIFACT-ROW-TACTILE-0 comment claiming
`:active { scale(0.97) }` (actual :active is background only),
transition: transform (no target), reduced-motion `:active { transform:
none }` (no transform to none)
Sync the command-palette a11y contract: drop the two transform assertions
that pinned the dead scaffold; keep the "pressed feedback via
state-selected background, not a scale transform" assertion (the real
#499 contract). No visual change — the deleted transforms were identity
or had no transition target.
* refactor(ui): route settings nav selected through --state-selected-bg
settingsNavItem[data-active] hand-wrote `oklch(from var(--foreground)
l c h / 0.065)`, which equals --state-selected-bg (maka-tokens.css:165)
but bypasses the token, so changing the selected token later would leave
this row drifting alone. Point it at var(--state-selected-bg).
Drop the stale PR-SETTINGS-NAV-FOCUS-0 comment that referenced the old
--settings-nav-row-selected-bg token + the hand-written 6.5% (kept the
neutral-rail rationale). Fix the PR-UI-11 glyph comment: it claimed the
active glyph was overridden to `--accent`, but the rule is `--foreground`.
Sync settings-form-a11y-contract: assert the bg goes through the token
(fits the anti-drift intent) instead of pinning the literal oklch string.
* refactor(ui): drop dead press-scale residue round 2 (no-op transform + stale comments)
Follow-up to a2fd0b7. The first pass missed several surfaces; this
clears the rest per the audit:
- model-switcher.css: reduced-motion `:active { transform: none }` is a
no-op (:active is background-only)
- daily-review.css: list-item `transition: transform` has no target
(:active/:hover are background-only)
- sidebar.css: drop the stale PR-SESSION-ROW-TACTILE-0 comment claiming
`:active scale(0.98)` (actual :active is background) + the orphaned
"Reduced-motion clause below" (no such clause exists); list-row-main
`transition: transform` has no target (:hover/:active/:focus-visible
never set transform)
- tool-output.css: drop the stale PR-FE-BUG-HUNT-7 comment claiming
"0.97 active" (:active is background; :hover scale(1.02) CSS kept)
- nav-sidebar.css: drop the two PR-SETTINGS-MOTION-TYPO-TOKENS-0 +
self-review comments claiming `:active scale(0.98)` (actual :active on
.settingsBackButton/.settingsNavItem is background-only)
- onboarding.css: list-row-main `:hover { transform: none }` +
`[data-active] { transform: translateX(0) }` are no-ops (list-row-main
base has no transform; no slide-in keyframe)
Audit grep (per review): scale(0.97|0.98) now only on toast keyframes
(animation); transform: none only on real layout resets / default-hidden
actions; transition.*transform only where a transform is actually
animated (chevron rotate, alert hover-lift, skill-action translateX).
* refactor(ui): drop no-op transform in sidebar list-row + settings nav reset
- .maka-list-row transition listed `transform` but :hover/:active/:selected
only change background, so the transform transition had no target
- .settingsSidebar .settingsNavItem reset carried `transform: none` but no
settingsNavItem rule ever sets a transform, so the reset was a no-op
* test(ui): narrow onboarding --nav-active allowlist to selector level
The selected/active contract used ALLOWLIST_FILE = /onboarding\.css$/ and
continue, which skipped the whole file — any selected/active selector in
onboarding.css could use --nav-active without being caught. Drop the
file-level skip and add the real brand-emphasis selectors
(.maka-firstrun-step, .maka-onboarding-setup-steps) to the selector
allowlist instead. Verified red→green: adding a non-allowlisted
[data-active] { background: var(--nav-active) } rule in onboarding.css
fails the contract; removing it passes. Existing brand-emphasis selectors
(firstrun-step / setup-steps li [data-state="active"]) still pass.
* test(ui): match background-color too in hover-bg contract
The :hover background contract only extracted `background:` declarations,
so a `background-color: var(--foreground-5)` on a :hover rule would slip
through (bgMatch null → continue). Match `background(?:-color)?:` so both
spellings are caught, without widening to other color properties. Verified
red→green: a temporary `.test-red:hover { background-color: var(--foreground-5) }`
fails the contract; removing it passes.
* refactor(ui): drop skill-library-row press transform + composer active-scale comment
skill-library-row carried hover/active micro-interaction transforms that
drift from the PR's background-token direction:
- :hover had `transform: translateX(1px)` (hover lift) on top of the
--state-hover-bg background
- :active had `transform: translateY(1px)` (press shift) with no background
- transition listed `transform` to serve both
Drop all three (skill-row state is background-only now, like the other
#499 surfaces) and point :active at --state-selected-bg so the press
signal matches the rest of the app.
composer.css: the send-button comment claimed "0.96→0.97 active" but
:active only changes box-shadow (no scale). Drop the active-scale phrase,
keep the 1.06→1.03 hover-scale note (that transform still exists).
* refactor(ui): route sidebar action-overlay bg through state tokens
.maka-list-row-actions built its hover/active overlay gradient from
--foreground-3 / --foreground-5 instead of the state tokens, so the
action overlay drifted separately from the rest of the #499 surfaces.
Point the default overlay's opaque stops at --state-hover-bg and the
active-row overlay at --state-selected-bg. Values move 0.03->0.04 and
0.05->0.065 (slightly deeper, but now single-sourced with the other
state surfaces).
* test(ui): check every background declaration in a :hover rule, not just the first
body.match(/background(?:-color)?:.../) returned only the first match,
so a later overriding declaration like `background: var(--state-hover-bg);
background-color: var(--foreground-5);` would pass (first match is clean).
Switch to matchAll and flag any background/background-color in the rule
that drifts to --foreground-N or inline oklch. Verified red->green: a
:hover rule with a clean first background but a drifting second
background-color now fails the contract.
* refactor(ui): drop no-op opacity/transform scaffolding on skill-library-action
.maka-skill-library-action kept opacity:1 + transform:none/translateX(0)
on base/hover/focus plus transition opacity/transform, but the action is
always visible (opacity 1 on every state) and the transforms are identity,
so the opacity/transform transitions had no real target. Drop the opacity
and transform declarations and the corresponding transition entries; the
hover/focus change is now background + color only. Same cleanup in the
max-width:820px override.
* test(ui): cover [data-default] in state-token selected/active contract
SELECTED_ACTIVE missed [data-default], so .enabledConnRow[data-default="true"]
in settings/models.css (the ModelTable default-row selection) escaped the
--nav-active ban. Add [data-default to the regex (minimal: only the spelling
already in the codebase). Verified red->green: temporarily repointing the
default-row bg to --nav-active fails the contract; restoring --state-selected-bg
passes.
* docs(design-system): drop stale "微小 translate (PR40)" from SessionRow hover
#499 made .maka-list-row:hover background-only (var(--state-hover-bg), no
transform), but the §3.6 SessionRow state table still listed "hover:
--state-hover-bg 背景 + 微小 translate (PR40)". The doc would mislead a
future implementer into re-adding a row transform. Drop the translate
phrase; hover is --state-hover-bg only.
* test(ui): ban --accent too in selected/active + cover [data-checked]; neutralize theme option
The selected/active contract only banned --nav-active, so a selected
surface using --accent (the same brand color; --nav-active aliases it)
escaped. Ban var(--accent) as well, and add [data-checked to
SELECTED_ACTIVE so checkbox/radio/theme-option checked states are covered.
Audit found one violator: .settingsThemeOption[data-checked] in
theme-preview.css used --accent for bg + border. Theme-option selection
is a persistent choice, not onboarding brand emphasis, so #499's
"selected fully neutral, no exceptions" applies. Repoint to
--state-selected-bg + --border-strong, matching the artifact-row
[data-selected] pattern.
Verified red->green: temporarily restoring the --accent bg fails the
contract; the neutral version passes.
* refactor(ui): neutralize prompt-chip hover (state-hover-bg + border-strong, no translate)
.maka-prompt-chip:hover used --nav-active-derived bg + border plus a
translateY(-1px) translate, but the chip's base is neutral (hairline
--foreground-8 border, transparent bg), so the brand hover tint broke
#499's hover state-token single-sourcing. Repoint hover to
--state-hover-bg + --border-strong (neutral, matching the base) and drop
the translate + transform transition. Update the "accent tint only
appears on hover" comment: hover is now neutral.
* test(ui): ban --nav-active/--accent in hover backgrounds; allowlist base-brand controls
The hover contract only caught --foreground-N / foreground inline oklch
drift, so a :hover background derived from --nav-active or --accent
(brand) escaped. Ban both brand tokens in :hover backgrounds too.
Audit found 5 such hovers; prompt-chip is fixed in the prior commit
(base-neutral -> neutral hover). The other four are base-brand controls
whose hover stays brand (consistent with their brand base):
.maka-chat-header-memory-pill (brand status badge),
.modelTableDefaultHint (brand hint),
.settingsBotAction (outlined brand CTA),
.settingsWechatQrSecondary (outlined brand secondary). Add them as a
selector-level allowlist with a comment stating the base-brand rule:
base-brand controls keep a brand hover; base-neutral controls must use
--state-hover-bg.
Verified red->green: a non-allowlisted --nav-active hover bg fails
(prompt-chip); removing memory-pill from the allowlist fails it too.
* refactor(ui): neutralize 3 base-neutral hovers; ban --toast-accent alias in hover contract
Audit beyond the reviewer's prompt-chip item found 3 more :hover rules
on base-neutral controls using brand tint, breaking #499's hover
single-sourcing:
- .maka-toast-action:hover bg --toast-accent (brand alias) -> --state-hover-bg
- .enabledEmptyAction:hover border --nav-active -> --border-strong
- .settingsThemeOption:hover border --accent -> --border-strong
(bg was already --state-hover-bg; border now matches [data-checked])
Each base is neutral (neutral border + neutral/transparent bg), so hover
stays neutral. Base-brand controls (memory-pill, modelTableDefaultHint,
settingsBotAction, settingsWechatQrSecondary) keep their brand hover via
the existing allowlist.
The hover contract only banned --nav-active/--accent by name, so the
--toast-accent alias (--accent) escaped. Ban var(--toast-accent) too.
Verified red->green: a --toast-accent hover bg fails.
* refactor(ui): neutralize bot-list active row; ban --bot-brand-* in selected/active contract
.settingsBotList button[data-active="true"] used --bot-brand-color (a
per-provider brand color: telegram blue, feishu green, wechat green, etc.)
for both the active background (8% alpha) and a 3px brand rail (::before).
#499's "selected fully neutral, no exceptions" applies: the active row
is a persistent selection, not onboarding guidance, and the row already
shows provider identity via <BotBrandLogo> (first column) + the provider
label, so the brand bg+rail are redundant identity emphasis.
Repoint the active bg to --state-selected-bg; delete the ::before brand
rail. Add var(--bot-brand-color)/var(--bot-brand-default) to the
selected/active contract ban (they are --accent aliases / per-bot brand
variants that escaped the --nav-active/--accent-only ban). Verified
red->green: restoring the --bot-brand-color bg fails the contract.
* test(ui): single-source brand-token ban; cover [data-pressed] in selected/active
The state-token contract banned brand tokens in two separate lists that
drifted: hover banned --nav-active/--accent/--toast-accent (missed
--bot-brand-*), selected/active banned --nav-active/--accent/--bot-brand-*
(missed --toast-accent). Extract one shared BRAND_STATE_TOKEN_RE covering
all five (--nav-active, --accent, --toast-accent, --bot-brand-color,
--bot-brand-default) and reuse it in both layers.
[data-pressed] is a real pressed-state selector (.settingsSegmented
button[data-pressed] in bot.css) but SELECTED_ACTIVE did not cover it, so
a future brand drift on pressed state would escape. Add [data-pressed to
SELECTED_ACTIVE.
Audited other brand aliases for the same gap: --brand-deep /
--brand-deep-hover (hero base + [data-tone="success"] status, not state
surfaces), --status-running (status/onboarding base), --link (link text
:hover color, intentional), --focus-ring (:focus-visible). None appear
in hover/selected/active state surfaces, so they are not added to the ban
(link text hover is intentional and not a state-bg drift).
Verified red->green with the three reviewer cases: :hover bg
var(--bot-brand-color), [data-active] bg var(--toast-accent),
[data-pressed] bg var(--nav-active) all fail the contract; restoring the
neutral versions passes (1842/1842).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han