Skip to content

cmux NEXT theme: Grass and Hot Dog Stand cards stay translucent with AA text (ink takes the higher least contrast) - #23441

Merged
teamleaderleo merged 2 commits into
feat-cmux-nextfrom
cmux/card-never-opaque
Oct 21, 2026
Merged

teamleaderleo merged 2 commits into
feat-cmux-nextfrom
cmux/card-never-opaque

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

On the Grass and Hot Dog Stand themes, every floating glass card (hover cards, popovers, the palette, the update card) drew an opaque fill, and its text still missed 4.5:1. CardMaterial lays the card fill on the glass at the least alpha below 1 that keeps primary and secondary text at 4.5:1 over black and over white. When no alpha does, it falls back to exactly 1, and the glass turns solid. Glass is never opaque, and card text keeps WCAG AA (Lawrence, 2026-10-20). This follows #23366 (the no-opaque-glass guard) and covers every theme cmux ships.

The cause was in ThemeTokens.derive. When a foreground didn't read, the code walked it toward white or toward black, picking by a 0.18 luminance cutoff of the pressed row, and checked the result only on that row.

  • Grass (#FFF0A5 on #13773D): the walk went to near-black, which reads on the pressed row but gets 3.73:1 on the background and on the card.
  • Hot Dog Stand (white on #EA3323): the text became a dark grey that held on the lighter pressed row but not on the red. White itself only reaches 4.20:1 on that red.

Two fixes:

  • Ink choice (ThemeTokens.inks): a foreground that already reads keeps its color. Otherwise it is walked toward both white and black until it reads on the pressed row and on the background, and the candidate whose primary and secondary text keep the higher least contrast wins. The score is capped at 4.5:1, so when both candidates read, the one toward the pole that reads better on the pressed row stays first. Grass now takes white and Hot Dog Stand dark text.
  • Card lift (opaqueOverlayFill): a dark theme's card fill was lifted toward its text. With dark text that darkens the card, so when the text is darker than the page the fill now lifts toward white, as on a light theme.

CardMaterial's opacity rule, ChromeTunables, theme files and ChiefPetBubble.swift are unchanged.

What changes among the 622 bundled themes: only Grass (ink, and so its card fill, through the unchanged dark lift) and Hot Dog Stand (ink, and its card fill through the white lift). onlyGrassAndHotDogStandChange re-derives every theme with the previous rule and asserts that this is the whole changed set.

One exemption: Grass's pressed and selected rows keep 4.41:1. No single ink reads on both its background and its pressed rows: white gets 5.62:1 on the background and 4.41:1 on a pressed row, black 4.76:1 on a pressed row and 3.73:1 on the background. The test exempts Grass by name at that measured ratio.

Tests

CardNeverOpaqueTests (CmuxNextDesignTests) reads every theme file the app bundles (Resources/ghostty/themes, Resources/cmux-themes) and checks:

  • the card stays below alpha 1;
  • primary and secondary text keep 4.5:1 on the card over the worst backdrop (TooltipSurface.worstContrast, as CardMaterial measures) and on the pressed row, with the one Grass exemption;
  • only Grass and Hot Dog Stand change, against the previous rule;
  • the ink choice on the two fills (Grass light, Hot Dog Stand dark, with their ratios);
  • Hot Dog Stand's white lift;
  • a grey-on-grey fixture that the checker must report, so the sweep can't pass by checking nothing.

Command (fleet):

cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref <sha> [--red] \
  --arg=suite --arg=Packages/macOS/CmuxNext --arg=CmuxNextDesignTests.CardNeverOpaqueTests \
  --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app

Red at the test commit, green at head

  • Red at the test commit 86316da761b7 (fleet step 44b8003d6f549348e6f4e45c, --red, CardNeverOpaqueTests): 4 of 5 tests fail; the fixture test passes. Failure lines:
    • opaque → ["Resources/ghostty/themes/Grass [dark] (fill #13773D, opacity 1.0)", "Resources/ghostty/themes/Hot Dog Stand [dark] (fill #EA3323, opacity 1.0)"]).isEmpty → false
    • cardInk → [Grass primary 3.53, Grass secondary 3.53, Hot Dog Stand primary 3.94, Hot Dog Stand secondary 3.89]
    • changed → [:]: at this commit the test's copy of the previous rule matches derive exactly on all 622 themes, which is what the green changed-set check is measured against.
    • grass.textPrimary.relativeLuminance → 0.0029 > 0.1367 (Grass's text was near-black), and hotDog.opaqueOverlayFill → #EA3323 where the white lift gives #ED5042.
  • Green at head 50ea1b1f6c6d (fleet step 92ab4af6606130e29bc68d3d): all 20 suites passed, listed below.
    • The new suite: CardNeverOpaqueTests.
    • Card, popup and tooltip contrast: CardMaterialLegibilityTests, WebCardMaterialTests, PopupSurfaceTests, SeeThroughInkTests, ReadabilityOverArtTests, TooltipSurfaceTests.
    • Web theme: WebThemeSolidSurfaceTests, WebThemeCaretSelectionTests, WebThemeTerminalTests.
    • Theme and accent: BundledCmuxThemesTests, ThemeAccentEditTests, GroupHeaderThemeLookTests.
    • Glass, selection and cards: FocusedPaneTabCueTests, WindowGlassTests, DragGlassTests, SidebarSelectionLookTests, UpdateFeedCardTests.
    • Palette chips: PaletteScopeChipContrastTests, PaletteScopeChipSnapshotTests.
  • Fleet compile check at head: success, https://github.com/manaflow-ai/cmux/actions/runs/39609156884.

Observed / Not run

  • Observed: the fleet runs above, and both captures below (each read before upload).
  • Not run: the full CmuxNext package test suite. Only the 20 suites listed ran. The previous-rule comparison covers inks and card fills only; it does not compare derived colors that depend on the primary ink, such as highlightText and onAccent.
  • For a user theme (not bundled), the ink choice can now land on the other pole when that pole reads on both the pressed row and the background and the old pole doesn't. That is intended. Among bundled themes, the changed-set check shows it happens only for Grass and Hot Dog Stand.

Changelog

  • Fixed: text on the Grass and Hot Dog Stand themes' glass cards now meets contrast and the card stays translucent.

Captures

The tab hover-card gallery (debug.hover_cards gallery tabs) at head, with appearance.theme set to each theme. Both theme names are single themes, so the light and dark stills show the same theme. The sidebar gallery was refused by the privacy check, because one of its fixture cards names a real machine.

Grass:

Capture steps-8a83f81d4d at 50ea1b1f6c6d (fleet build, capture mini, text checked for private data)

Light Dark
light dark

Hot Dog Stand:

Capture steps-007fc7d0ba at 50ea1b1f6c6d (fleet build, capture mini, text checked for private data)

Light Dark
light dark

Agent session: cosmic-gray

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 21, 2026 06:32
…d its card text AA

CardMaterial draws a card's fill on its glass at the least alpha below 1 that
keeps primary and secondary text at 4.5:1 over black and white, and at exactly 1
when none does, which makes the glass solid. Check every bundled theme
(Resources/ghostty/themes and Resources/cmux-themes) at the shipped lift and
minimum opacity: the card stays below alpha 1, its primary and secondary text
keep 4.5:1 on the card and on the pressed row (Grass exempted at its measured
4.41:1, where no single ink reads on both its background and its pressed rows),
and only Grass and Hot Dog Stand derive other inks or card fills than the
previous rule. Grass should take the light ink, Hot Dog Stand the dark ink with
a white-lifted card. The fixture proves the checker reports an opaque card.

Red on this commit: Grass and Hot Dog Stand fall back to an opaque card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fdc6-7623-8ee3-fa94b3900ccd&host=cosmic-gray
… ink lifts the card toward white

ThemeTokens.derive walked an unreadable foreground toward white or black by a
0.18 luminance cutoff of the pressed row and checked it on that row only. On
Grass (#13773D) that gave near-black text at 3.73:1 on the background, and on
Hot Dog Stand (#EA3323) a dark grey that read on the lighter pressed row but
not on the red, so both cards fell back to an opaque fill. Walk the foreground
toward both poles until it reads on the pressed row and on the background, and
keep the candidate whose primary and secondary keep the higher least contrast
(capped at 4.5:1, so a reading ink toward the pole that reads better on the row
stays first). Grass now takes white, Hot Dog Stand dark text.

A dark theme's card fill lifted toward its text; with Hot Dog Stand's dark text
that darkened the card under it. opaqueOverlayFill lifts toward white, as on a
light theme, when the text is darker than the page.

Only Grass and Hot Dog Stand change among the 622 bundled themes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fdc6-7623-8ee3-fa94b3900ccd&host=cosmic-gray
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by palette-audit on cosmic-gray · session

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 50ea1b1: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23441 -f ref=cmux/card-never-opaque -f platform=macos -f backend_mode=local

Tour of 50ea1b1f6c (run)

The path-selected recorder failed for core; see the run artifact and failed manifests.

Path-selected interactions

Interaction Changed paths
core Packages/macOS/CmuxNext/Sources/CmuxNextDesign/OverlayMaterial.swift
Packages/macOS/CmuxNext/Tests/CmuxNextDesignTests/CardNeverOpaqueTests.swift

Core UI (before): PASS — 19 ok

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 A command reports progress: tab loading spinner ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "clear; printf '\\033]9;4;3\\007'; sleep 900\r"
5 Split right ok cmux action run splitRight
6 Split down ok cmux action run splitDown
7 New terminal tab ok cmux action run newSurface
8 Browser tab ok cmux tab new-browser --url https://example.com
9 Hide the sidebar ok cmux action run toggleSidebar
10 Show the sidebar ok cmux action run toggleSidebar
11 Notification ok cmux notify --title Tour --body "Hello from CI"
12 Notifications panel ok cmux action run showNotifications
13 Agent chat ok cmux agent new-chat
14 Agent workspace ok cmux workspace new --name Agent
15 Agent starts ok cmux workspace name:Agent screen current pane current tab current terminal current write --text "clear; printf '\\033]7501;state=working\\033\\\\'; sleep 8; printf '\\033]7501;state=done\\033\\\\'; sleep 900\r"
16 An agent works: row working spinner ok cmux workspace new --name Notes
17 The agent finishes: done mark in the spinner's place ok
18 Session tree: keycaps on the first rows ok cmux settings set sidebar.sessionTree true
19 Session tree back off for later tours ok cmux settings set sidebar.sessionTree false
Screenshots (15)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. A command reports progress: tab loading spinner
A command reports progress: tab loading spinner

5. Split right
Split right

6. Split down
Split down

7. New terminal tab
New terminal tab

8. Browser tab
Browser tab

9. Hide the sidebar
Hide the sidebar

11. Notification
Notification

12. Notifications panel
Notifications panel

13. Agent chat
Agent chat

16. An agent works: row working spinner
An agent works: row working spinner

17. The agent finishes: done mark in the spinner's place
The agent finishes: done mark in the spinner's place

18. Session tree: keycaps on the first rows
Session tree: keycaps on the first rows

### Core UI \(after\): PASS — 19 ok

tour

Video (mp4)

# Step Result Command
1 Launch ok
2 New workspace ok cmux workspace new --name Tour
3 Type in the terminal ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "echo hello from the cmux-next tour\r"
4 A command reports progress: tab loading spinner ok cmux workspace name:Tour screen current pane current tab current terminal current write --text "clear; printf '\\033]9;4;3\\007'; sleep 900\r"
5 Split right ok cmux action run splitRight
6 Split down ok cmux action run splitDown
7 New terminal tab ok cmux action run newSurface
8 Browser tab ok cmux tab new-browser --url https://example.com
9 Hide the sidebar ok cmux action run toggleSidebar
10 Show the sidebar ok cmux action run toggleSidebar
11 Notification ok cmux notify --title Tour --body "Hello from CI"
12 Notifications panel ok cmux action run showNotifications
13 Agent chat ok cmux agent new-chat
14 Agent workspace ok cmux workspace new --name Agent
15 Agent starts ok cmux workspace name:Agent screen current pane current tab current terminal current write --text "clear; printf '\\033]7501;state=working\\033\\\\'; sleep 8; printf '\\033]7501;state=done\\033\\\\'; sleep 900\r"
16 An agent works: row working spinner ok cmux workspace new --name Notes
17 The agent finishes: done mark in the spinner's place ok
18 Session tree: keycaps on the first rows ok cmux settings set sidebar.sessionTree true
19 Session tree back off for later tours ok cmux settings set sidebar.sessionTree false
Screenshots (15)

1. Launch
Launch

2. New workspace
New workspace

3. Type in the terminal
Type in the terminal

4. A command reports progress: tab loading spinner
A command reports progress: tab loading spinner

5. Split right
Split right

6. Split down
Split down

7. New terminal tab
New terminal tab

8. Browser tab
Browser tab

9. Hide the sidebar
Hide the sidebar

11. Notification
Notification

12. Notifications panel
Notifications panel

13. Agent chat
Agent chat

16. An agent works: row working spinner
An agent works: row working spinner

17. The agent finishes: done mark in the spinner's place
The agent finishes: done mark in the spinner's place

18. Session tree: keycaps on the first rows
Session tree: keycaps on the first rows

Tours are data in scripts/cmux-next/media/tours: add a step or a tour file to show your feature.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp40rfq6c3/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpgzdkcmza/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-v98s7 created for this PR by the cmux coordinator.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / defaults-snapshot is red on 19 of 20 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

teamleaderleo added a commit that referenced this pull request Oct 26, 2026
… written by the spec tool (#25438)

First half of #23517, rebased onto feat-cmux-next. schemas/theme/theme-ink-vectors.json
holds what Swift derives for each of the 622 bundled themes (text inks and the
popup surface at the shipped lift). derive_theme_tokens.py takes #23441's ink and
popup rules and writes the file (--theme-ink-vectors) or checks it
(--check-theme-ink-vectors); ThemeInkVectorsTests fails when the file is not
Swift's output. The Rust and web ports follow in the second half.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 26, 2026
…pup rules, checked against the Swift vectors

Second half of #23517. cmux-theme-tokens (inks, readable_over,
opaque_overlay_fill), the web gallery tokens (inks, the see-through surfaces,
opaqueOverlayFill) and the agent pane harness take the rules #23441 gave Swift,
and each is checked against schemas/theme/theme-ink-vectors.json: Rust
spec::tests::derive_matches_the_swift_ink_vectors, and
webviews/test/theme-ink-vectors.test.ts for the gallery, the harness and
derive_theme_tokens.py --check-theme-ink-vectors. The spec prose states the
new rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495
teamleaderleo added a commit that referenced this pull request Oct 26, 2026
…pup rules, checked against the Swift vectors (#25444)

Second half of #23517. cmux-theme-tokens (inks, readable_over,
opaque_overlay_fill), the web gallery tokens (inks, the see-through surfaces,
opaqueOverlayFill) and the agent pane harness take the rules #23441 gave Swift,
and each is checked against schemas/theme/theme-ink-vectors.json: Rust
spec::tests::derive_matches_the_swift_ink_vectors, and
webviews/test/theme-ink-vectors.test.ts for the gallery, the harness and
derive_theme_tokens.py --check-theme-ink-vectors. The spec prose states the
new rules.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-3891b16636fa80ffc495

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant