CmuxNext: solid popup surface for menus, popovers and tooltips

Merged
TE
25d ago
+854-258

Popovers, menus and tooltips were glass at about 92% with a blur, so the text under them showed through. The New Tab folder popover over the Chats list read as text on text. Every overlay that carries text is now one solid, unblurred theme surface. Native and web share the exact color.

One surface token

  • Native: Palette.popupSurface is the theme's opaqueOverlayFill(lift:).

    • Glass.makeOverlayPanel pins OverlaySurfaceView to it. That covers every caller: the palette and its menus, dialogs, hover cards, find and prompt bars, page info, the cookie card and notices, group editors, the quick composer, the restart notice and shortcut-hint chips.
    • The themed tooltip, the icon picker panel (formerly popover vibrancy), the update sheet (formerly Glass.makePanel) and the Share cmux dialog use it too.
    • The omnibar's suggestion card and the field's editing card it joins use it.
    • The bookmark bubble and the update card's highlights popover are system NSPopovers. Their content is now filled with it (NSView.fillWithPopupSurface(themeOf:)).
    • Overlays without text (drop overlay, dock and feed backdrops) keep this Mac's material. Toasts were made solid in #19759.
  • Light and dark: on a light theme the popup lifts toward white, so a white page gets a white popup set apart by its hairline and shadow, like native light menus. Before, it was a 14% gray box. On a dark theme it lifts toward the text. The lift stops where secondary text would drop below 4.5:1 on a hovered row: at the old 14%, dark themes measured about 3.9:1 there.

  • Web: --surface-popup in desktop.css takes the host's new --cmux-popup-surface (WebTheme, the same color as Palette.popupSurface). It is used by:

    • .ui-popup and the page menu;
    • every acpmux menu and popover;
    • the icon picker's pill;
    • markdown link cards and the link popover;
    • the editor's suggest and hover widgets;
    • the home-channels message toolbar and switcher.

    None of them set backdrop-filter. The agent pane's dev previews now load desktop.css for the token. .ui-sheet is a page sheet, not a popup, so it keeps the elevated surface.

Tooltips match the native one

  • .ui-tooltip and the title tooltip share one rule: the popup surface, primary ink, 5px radius, 4×8 padding, a --cmux-separator hairline, a subtle shadow, the caption font and a 320px max width.
  • The shortcut hint is drawn in the secondary ink (<Tooltip shortcut>, or data-tooltip-shortcut via tooltipProps). The chat header's Changes, Terminal and Browser buttons, the dictation button and the New Tab tools now pass their keycaps as the hint instead of baking them into the title.
  • Truncated text: a title tooltip counts as truncated when its element or any descendant clips (scrollWidth > clientWidth + 1, as in BranchBasePicker rows, where the title is on the row and the label clips). It shows the full text, wrapping, up to the row's width (or 480px for a narrow one). It is at least as wide as the line unless the full text is shorter, and then just the text's width. This is tooltipWidth({width, truncated, view, text}).

Model marks

  • The "Vercel AI Gateway/Apex" chip looked up the profile's own id, which names no brand, so it drew a placeholder square.
  • Marks are now the first known of icon, family and id (knownMark). A switch draws its target's mark (switching.mark, else that harness's own).
  • An unknown mark draws nothing: no placeholder, ever. List rows (model picker search and starred rows, Settings > Agents) keep an empty slot so names line up. The rail shows the harness's initial.

Testing

Each round put its failing tests in their own commit before the fix:

  • Round one: tests c4abc7851f7d, fix b10e6983fbbf.

  • Review gaps: tests 11aa6b0f0c57, fix 76afbfef590f.

  • The head ce7c0a8e4c98 merges feat-cmux-next with #19759. RefusalHUD is a toast there, so the refusal-pill change and its test were dropped.

  • Swift, on the fleet (run-swift-testing-suites.sh Packages/macOS/CmuxNext):

    • Red at c4abc78: PopupSurfaceTests and ThemedTooltipTests failed (the panel was .liquidGlass, there was no --cmux-popup-surface, and the tooltip fill was elevatedBackground).
    • Red at 11aa6b0: PopupSurfaceTests failed with light popups darker than the page (white gave #DBDBDB) and secondary text at 3.86 to 4.08:1 on hovered rows. PopupSurfaceCallersTests failed with the Share dialog and refusal pill on .liquidGlass and the omnibar card on elevatedBackground. No other suite failed in either run.
    • Green at ce7c0a8: all 148 suites passed (cmux-ci step e567936ff7d9eb5e775978b7), including PopupSurfaceTests, PopupSurfaceCallersTests and ThemedTooltipTests.
  • Web, locally:

    • src/ui/titleTooltips.test.ts: red 2 failing (child clipping, text-width min), green 5/5.
    • test/model-picker-mark.test.tsx: red 3 failing (family fallback, switch mark, row slot), green 6/6.
    • test/chat-header-tooltips.test.tsx: red, green.
    • test/popup-surface-dev.test.ts: red, green.
    • CMUX_BROWSER_TESTS=1 bun test test/popup-surface.test.ts in Chromium: red on markdown, editor and home-channels popups at the translucent elevated color, green 3/3. WebKit can't launch on this machine, so the WebKit cases run only in CI's browser lane.
    • bun run check passes. Also passing: ui-rules, gallery-coverage, gallery-play, theme-colors, stylesheets, check-layers, model-picker-composition, harnesses-page and the settings harness suites.
  • Swift guards match base: check-concurrency (1 base hit), crash_ratchet (5 base modules), check-theme-scope (16 base hits, none in touched files), check-system-blue, check-no-godfiles (nothing new) and lint-ios-package-conventions (2 base errors).

  • No test covers the two NSPopover content fills (bookmark bubble, update card highlights) or the update sheet; showing them needs a live window. I didn't take a live capture.

Gallery: agent-pane.model-picker has a profile-without-mark variant. Tooltips are on the gallery-coverage allowlist.

Localization: there are no new user-facing strings. Keycaps moved from the title text into the hint, and the Debug Settings help for glass.opaqueOverlayLift changed (English-only).

Changelog

Changed: menus, popovers, hover cards and tooltips are solid theme surfaces, so text underneath no longer shows through. On light themes they are near-white, and a tooltip for truncated text is as wide as its row.

🤖 Generated with Claude Code