cmux NEXT glass: one overlay surface token set (SurfaceRole), native and web

Merged
TE
5d ago
+521-25

Leo: "I don't like the non-glassness". The standing rule: Glass, never opaque.

What and why

Popovers, cards, menus, toasts and sheets drew their glass from tokens scattered across several places: FloatingGlassStyle's three hand-written looks, the OverlayTone passed at each call site, and the web's glass.css. No single place said what each role is, what it looks like, or what it turns into under Reduce Transparency. This PR adds that one place, built on the existing pieces (FloatingGlassStyle, OverlayTone, Glass.makeOverlayPanel, OverlayMaterial, glass.css); it is not a parallel set:

  • schemas/surface-roles/surface-roles.json is the one source. It defines six roles. For each role it records the tone (the theme's glass tint, or the shared neutral glass), the veil and rim, the radius, the shadow, and the Reduce Transparency fallback. It also records nearOpaque (0.9): at or above that share, a fill on an overlay reads as solid. That is the threshold the glass lints fail at (#27824).
  • scripts/cmux-next/check-surface-roles.py holds two hand-kept copies to the JSON. It writes nothing, because the generated-files guard refuses new committed generated outputs. It fails when either copy differs, and --print shows the text each copy should have:
    • CmuxNextDesign/FloatingGlass/SurfaceRole.swift: SurfaceRole (tone, neutralStyle, cornerRadius, castsShadow, reduceTransparencyFallback, nearOpaque), plus FloatingGlassStyle.toast, .dialog and .card. Those three move out of FloatingGlassStyle.swift with unchanged values, and SurfaceRoleTests pins them.
    • The surface-roles block of webviews/src/ui/glass/glass.css (--surface-<role>-*) (radius, veil and rim per neutral role, plus --surface-near-opaque).
    • The check runs in CI (cmux-next.yml, "Surface roles", a style check like Motion scale and Menu metrics) and in check:pieces. It also refuses a neutral veil at or above nearOpaque.
  • Glass.makeSurface(role, ...) is makeOverlayPanel in the role's tone. The dialog (.sheet), toast (.toast tone), tooltip (.tooltip) and the sidebar card's style (.card) now go through the roles. Each of these is a mechanical change that draws the same pixels.
  • check-overlay-glass.py now counts Glass.makeSurface(...) as the shared glass, for both the hover-window rule and the popup-host rule, with tests.
RoleForGlassReduce Transparency
popoverpopovers, pickers, hover cards, find and prompt bars, page info, the palettetheme glass tint, panel radius (web --ui-popup-radius)opaque popup surface with its hairline
menumenus, context menus, dropdownstheme glass tint, panel radiusopaque popup surface with its hairline
cardthe sidebar's cards (Update Ready, the Updated card, notices, tips)neutral veil 22% dark / 45% light, light rim, no shadow, 10 ptopaque elevated fill with a hairline
toasttoastsneutral veil 42% / 55%, rim, shadow, 9 pt (never a capsule)opaque popup surface, no rim
sheetdialogs, confirms, prompt bars, sheetsneutral veil 48% / 62%, rim, shadow, 14 ptopaque popup surface, no rim
tooltiptooltipsthe frost (TooltipSurface), 5 ptopaque popup surface

Before / after

No surface should change how it looks: every routed call site resolves to the same tone, radius and style it had before. SurfaceRoleTests.theNeutralLooksKeepTheirValues pins the moved values, and the existing FloatingGlassStyleTests and CmuxToastSurfaceTests still cover the toast and dialog.

Captured with cmux-ci capture <sha> --state tour:stop-all-toast on fleet minis: the toast (SurfaceRole.toast) over the window, with the sidebar's tip card (CardSurfaceView, SurfaceRole.card).

Before (base 15ba750)After (f396b52)
Lightbefore lightafter light
Darkbefore darkafter dark

Viewed after image: yes
Visual verdict: Yes; the toast and the sidebar tip card are the same translucent glass as before, so it sits next to ChatGPT or Claude exactly as it did.

I viewed the after images. The toast glass, its rim and radius, and the tip card match the before images in both appearances. The one difference is the toast's message text, caught mid-crossfade in the before light still (its Restart button is crisp); it is shot timing, not this change. Two earlier after runs caught the window mid appearance switch (a greyed sidebar in dark, and one dark still that was all light); a third run on the same mini was clean, and nothing in this PR touches appearance.

Risk

Low. The Swift changes are a move and a rename that keep the same values, and the CSS block only adds custom properties no existing rule reads. A fleet compile check of the CmuxNext package passed at the first head. Follow-up: once #27824 lands, both glass lints read nearOpaque from this JSON instead of their own 0.9.

Agent session: agent session

šŸ¤– Generated with Claude Code