glass lints: native popup hosts and popup page roots must show the glass
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 onGlass.makeOverlayPanel) to carry icons and submenus and route this menu there under Chromium. #23066 changesWebPopupHost.swiftand the row views, so this waits for it. - (b) a new native rich-menu host.
- (c) keep the agent chat on WebKit (
agent.chromiumoff). This flips a default.
#23066 keeps the menu in the page and moves it to a neutral--surface-menutint, 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: newpopup-hostrule. A native class named for a menu, picker, popover, popup or palette that is a panel or window, aNativePopupHost(panel:), anadoptPopupStyle(card:)or anNSPopover()must buildGlass.makeOverlayPanel(orGlass.makeMenuPanel) in its own brace-matched body (glass in a sibling or nested type, or elsewhere in the file, does not count), or the typeGLASS_FROMnames for it in a content file must build it in its own body. StaleGLASS_FROMand exemption entries fail. Clean on base. New cases are intests/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.tsis left alone because #23066 changes it):- Every Swift file that floats a page with
requiresWebKit: .transparentOverlayhas to be mapped to its document. Today that isIconPickerService→ icon-picker andUpdateSurfaceIsland→ 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.
- Every Swift file that floats a page with
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.tsgives 7 pass, 0 fail.vp checkis 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 incheck-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](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
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.pygains apopup-hostrule: native panel/window classes named for menus, pickers, popovers, popups, or the palette, plusNativePopupHost(panel:),adoptPopupStyle(card:), andNSPopover(), must buildGlass.makeOverlayPanel(orGlass.makeMenuPanel) in their own body — the host class and its extensions in the file, the call's enclosing type, and the panel classNativePopupHost(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 inGLASS_FROM; stale entries fail.check-glass-never-opaque.tsgainsfindOpaquePopupPageRoots, flagging a popup document whose root paints an opaque background or whose html never paints itself transparent.- New
popup-page-root-glass.test.tsmaps every SwiftrequiresWebKit: .transparentOverlayhost 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.