glass lints: native popup hosts and popup page roots must show the glass

Merged
TE
11d ago
+791-1

Leo: "why is this not glassed and themed right" (the "Choose a folder" picker and the chat ⋮ menu).

Leo-ask: ask-7dccca6526

What each surface is

"Choose a folder" picker: already fixed by #23801 (merged 0b71d5ea), reaches Leo with the next nightly.
It is the native palette (PalettePanel → PaletteContentView, a CmuxPicker page), built on Glass.makeOverlayPanel. The installed nightly was built at ee6f052, before #23801, when cardFill defaulted to true and painted the nearly opaque card material over the glass. Real-screen grabs of a fleet build of feat-cmux-next (12ad3f4) over a wallpaper show the picker as glass, with the wallpaper through it, in Catppuccin Mocha and GitHub Light Default. This is a native fix, so it ships in a build, not over the air.

Chat ⋮ menu: not fixed here; needs a product call.
It is the in-page Base UI menu in ChatHeaderTools.tsx (.ui-popup.ui-glass.ui-menu). The agent chat renders in Chromium (agent.chromium defaults on; debug.agent_pane engine returns cef). A windowed CEF page is an opaque child window, so the menu's CSS backdrop blur only samples the opaque page, and it reads as a flat panel. A CSS change can't fix this. The ways out are:

  • (a) extend WebPopupHost (native rows on Glass.makeOverlayPanel) to carry icons and submenus and route this menu there under Chromium. #23066 changes WebPopupHost.swift and the row views, so this waits for it.
  • (b) a new native rich-menu host.
  • (c) keep the agent chat on WebKit (agent.chromium off). This flips a default.
    #23066 keeps the menu in the page and moves it to a neutral --surface-menu tint, so it neither fixes the Chromium opacity nor gives the theme's popup tint the ask wants.

Icon picker page (same class of bug): covered by #23066. Its document never clears html, so the page base paints the opaque theme surface over the popover's glass. #23066 adds html, body { background: transparent }.

What this PR changes (enforcement only, no UI change)

  • scripts/cmux-next/check-overlay-glass.py: new popup-host rule. A native class named for a menu, picker, popover, popup or palette that is a panel or window, a NativePopupHost(panel:), an adoptPopupStyle(card:) or an NSPopover() must build Glass.makeOverlayPanel (or Glass.makeMenuPanel) in its own brace-matched body (glass in a sibling or nested type, or elsewhere in the file, does not count), or the type GLASS_FROM names for it in a content file must build it in its own body. Stale GLASS_FROM and exemption entries fail. Clean on base. New cases are in tests/check-overlay-glass.test.sh.
  • webviews/scripts/check-glass-never-opaque.ts: findOpaquePopupPageRoots(files) flags a popup document whose root (html, body, :root, #root) paints an opaque background (Reduce Transparency blocks are allowed), or whose html never paints itself transparent.
  • webviews/test/popup-page-root-glass.test.ts (new; popup-surface.test.ts is left alone because #23066 changes it):
    • Every Swift file that floats a page with requiresWebKit: .transparentOverlay has to be mapped to its document. Today that is IconPickerService → icon-picker and UpdateSurfaceIsland → changelog #/surface.
    • Every mapped document passes the scan. The icon picker is listed as pending #23066, so this test is green on base and stays green once #23066 lands.
    • A fixture case covers the opaque, Canvas, never-cleared, scoped and Reduce Transparency forms.

Verification

  • check-overlay-glass.py: ok. check-overlay-glass.test.sh: ok.
  • huge-blue: bun test test/popup-page-root-glass.test.ts test/glass-never-opaque.test.ts gives 7 pass, 0 fail. vp check is clean on the changed files. The icon-picker CSS fails the scan on base and passes with #23066's version.
  • python3 scripts/cmux-next/prepush-gate.py: no new violations.
  • pr-overlap: #23970 and #24022 (ours) touch other rules in the same lint files; #24032 (ours) touches another function in check-glass-never-opaque.ts. No duplicate fix.

No capture: lint and test only, no UI change. The before grabs of both surfaces on a fleet build are kept privately with the lane's captures.

Agent session: cosmic-gray

🤖 Generated with Claude Code


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


Summary by cubic

Adds lint rules enforcing that native popup hosts and popup page roots use the shared glass surface, so menus and pickers can't accidentally render as opaque panels. Enforcement only; no UI change.

  • check-overlay-glass.py gains a popup-host rule: native panel/window classes named for menus, pickers, popovers, popups, or the palette, plus NativePopupHost(panel:), adoptPopupStyle(card:), and NSPopover(), must build Glass.makeOverlayPanel (or Glass.makeMenuPanel) in their own body — the host class and its extensions in the file, the call's enclosing type, and the panel class NativePopupHost(panel:) constructs. Glass in a sibling or nested type doesn't count. Files that build the glass elsewhere list that file and the building type in GLASS_FROM; stale entries fail.
  • check-glass-never-opaque.ts gains findOpaquePopupPageRoots, flagging a popup document whose root paints an opaque background or whose html never paints itself transparent.
  • New popup-page-root-glass.test.ts maps every Swift requiresWebKit: .transparentOverlay host to its document and verifies the roots pass the scan; the icon picker is listed pending #23066.

Scope

  • The native folder-picker palette already lands glassed via #23801; the chat ⋮ menu is a Chromium child-window page, so a CSS change can't fix it and it's not addressed here.