feat(popup): add native declarative glass popup host

Merged
TE
5d ago
+4016-18

Web popup overflow currently scrapes hidden DOM rows, which loses rich controls and splits material ownership. Leo requested: “popovers in general need glassier bg blur / try to match liquid glass somehow”. This foundation adds declarative popup descriptions rendered by AppKit using the existing Glass.makeOverlayPanel material and Reduce Transparency fallback. Consumer migrations stack separately.

The local UI protocol supports search/text fields, sections, descriptions, icons, shortcuts, toggles, segmented choices, sliders, accessory actions, submenus, and model-picker rail/footer descriptors. The web hook binds events to popup identity/revision and accepts virtual anchors. Native controls emit typed actions, open instantly, restore keyboard ownership, keep child Escape local, and report root dismissal reasons. Legacy overflow listeners are isolated from native events.

Validation:

  • Remote full web check and two transport tests passed in run 40845240352, covering the final web changes including virtual anchors and dismissal reasons.
  • Native submenu regression reproduced parent dismissal/key restoration failure at e90d829a3d3d9f6ce74a06cbfc476789810cdcce; the same runtime test passed at 13d560310df6f940bc1ca964e7ac63d18522d924 after the fix. Expanded ancestry/dismissal/content regressions are pending at the final head.
  • Exact native compile at 4ea584f480ef238ad4ca34082fca8029b904e67e passed in 263 seconds. Compile/runtime verification and whole-diff independent review for final head 326bf3dd949704be844212b0961e3e1eb83d5e50 are pending; older receipts do not approve it.
  • Previous review findings addressed stale-hide tombstones, search descriptor refresh, child/root dismissal, disabled ancestry, event isolation, IME Escape, and viewport width. Final review remains required.
  • Wire types are hand-written (no generator): Swift in Overlay/PopupProtocol/, TypeScript in webviews/src/ui/popupProtocol.ts. The earlier gen-popup-protocol.py and its 16 committed outputs were refused by cmux-next no new generated files; the files moved unchanged apart from the header line, and scripts/ci/generated_files_guard.py check passes against the base.
  • Keyboard toggle fix: NativePopupContentTests.repeatedKeyboardToggleFlipsTheSwitchEachTime failed at 589a0c31 (fleet step 825ed411, changes == ["true","true"]) and passed at 4df13dbb (fleet step ca1ea5a9; NativePopupContentTests and NativePopupProtocolTests passed).
  • Anchor-gone fix: a popup already shown now hides at its current revision and reports dismiss("anchor-gone") when its anchor goes away; test a shown popup hides when its anchor goes away in nativePopup.lifecycle.test.tsx.
  • Sol review at 4df13dbb: SHIP.

No native capture is claimed yet. Risk: shared popup protocol, focus, and keyboard lifecycle. The protocol and renderer remain together because the wire contract and native state machine must agree; consumer changes are separate.

Changelog: Added the native declarative popup host foundation and shared Liquid Glass material path.

Agent session: agent session