cmux NEXT glass: one overlay surface token set (SurfaceRole), native and web
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.jsonis 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 recordsnearOpaque(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.pyholds 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--printshows the text each copy should have:CmuxNextDesign/FloatingGlass/SurfaceRole.swift:SurfaceRole(tone,neutralStyle,cornerRadius,castsShadow,reduceTransparencyFallback,nearOpaque), plusFloatingGlassStyle.toast,.dialogand.card. Those three move out ofFloatingGlassStyle.swiftwith unchanged values, andSurfaceRoleTestspins them.- The
surface-rolesblock ofwebviews/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 incheck:pieces. It also refuses a neutral veil at or abovenearOpaque.
Glass.makeSurface(role, ...)ismakeOverlayPanelin the role's tone. The dialog (.sheet), toast (.toasttone), 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.pynow countsGlass.makeSurface(...)as the shared glass, for both the hover-window rule and the popup-host rule, with tests.
| Role | For | Glass | Reduce Transparency |
|---|---|---|---|
| popover | popovers, pickers, hover cards, find and prompt bars, page info, the palette | theme glass tint, panel radius (web --ui-popup-radius) | opaque popup surface with its hairline |
| menu | menus, context menus, dropdowns | theme glass tint, panel radius | opaque popup surface with its hairline |
| card | the sidebar's cards (Update Ready, the Updated card, notices, tips) | neutral veil 22% dark / 45% light, light rim, no shadow, 10 pt | opaque elevated fill with a hairline |
| toast | toasts | neutral veil 42% / 55%, rim, shadow, 9 pt (never a capsule) | opaque popup surface, no rim |
| sheet | dialogs, confirms, prompt bars, sheets | neutral veil 48% / 62%, rim, shadow, 14 pt | opaque popup surface, no rim |
| tooltip | tooltips | the frost (TooltipSurface), 5 pt | opaque 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) | |
|---|---|---|
| Light | ![]() | ![]() |
| 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



