cmux-next: overlay host panels close with their window
Leo: "But why do you guys not just clean up after yourselves, or at least do your best to?" (on Swift suites that leave windows open for the suites after them)
What leaked and why
CmuxNextDesign.OverlayHostPanel: one per main window (the WindowOverlayHost panel that draws overlays above Chromium pages). The census from #27003 found it open after tests in ten suites at base (37 panels in total, listed below). There were two causes:
- When a window closed,
WindowOverlayHost.tearDown()only ordered the panel out. An ordered-out panel stays inNSApp.windows, open, for as long as anything holds the host. tearDown()also dropped the host from its map. So anyhost(for:)call that reached the closed window later made a new host and a new panel. That host never got the window'swillClose, so nothing ever closed its panel. One such late caller isWindowController.teardown()touching the lazily made overlay layer.
The fix
tearDown()closes the panel.OverlayHostPanelalready setsisReleasedWhenClosed = false.- A closed window keeps its torn-down host.
host(for:)returns that host while the window is not visible, and a torn-down host never attaches its panel again.present()on it returns a handle that is already dismissed and installs no views, observers, modal state or Escape monitor. A window shown again gets a fresh host, so its overlays (dialogs, toasts, hover cards) work as before.existingHost(for:)returns nil for a torn-down host. Entries whose window was deallocated are pruned whenever a new host is made. - New
WindowOverlayHost.windowIsClosing(_:).WindowController.teardown()calls it last, so a host made during that teardown closes too. - The app host (
appHost(), no window) is untouched.
No capture: no visible change. An open window looks and behaves the same; only a closed window's hidden overlay panel now closes, and nothing presented on a closed window shows (it never showed before either: the panel was ordered out).
Tests
New CmuxNextDesignTests.WindowOverlayHostCloseTests, run on the fleet:
- At base sources (probe step 027d8641): 3 of its tests fail. These are
closingTheWindowClosesItsHostPanel,aHostAskedForAfterTheCloseMakesNoNewPanelandaHostMadeDuringTheCloseClosesWithIt. - At this head: all pass, and the suite leaves 0 windows open.
Performance
Windows the suites below left open at process exit, from the fleet test census. Before is step fd82dc62 at the #27003 head; after is step c7a807e6 at this head. Both runs used the same filter list.
| metric | before | after | delta | harness |
|---|---|---|---|---|
| OverlayHostPanels left open, 10 suites (Toast, DialogFade, AgentCursor x2, MultiWindowInputRouting, MenuKeyRouting, BackgroundAccessoryPolicy, PageGeometryPost, StripScrollbarAbovePage, FullScreenTitlebarPageMode) | 37 | 12 | -25 (-68%) | package-test-lane.sh suite, steps fd82dc62 / c7a807e6 |
| MultiWindowInputRoutingTests | 9 | 0 | -9 | same |
| CmuxToastSurfaceTests | 8 | 6 | -2 | same |
| DialogFadeTests | 5 | 1 | -4 | same |
The panels still left open come from tests that never close their own window (defer { window.orderOut(nil) }). The suite fixes that follow close those.
Risk
This is product code every window uses. A torn-down host shows nothing that is presented on it late. That is the intended behavior: the window is gone, and the overlay used to land in a panel nothing showed. A window that is closed and shown again gets a fresh host. An ordered-out window and the app host are unaffected. BackgroundAccessoryPolicyTests.aWindowClosedBeforeTheRestoreFinishesStaysClosed fails the same way at base (two expectations at the same lines, same messages). It is being compared on one worker separately.
Agent session: agent session
š¤ Generated with Claude Code