Repository navigation
fix(next palette): the focus overlay goes on the window the palette opens over - #26951
Conversation
…pens over The palette panel is a child of the window it opens over, but its focus overlay went on `windows.active`: the window an input event came from, else the key window's, else the last active one. Those disagree with the panel's parent whenever the palette opens over a window that is not the active one (an explicit `relativeTo:`, or a press in one window while another holds the keys). The overlay then landed on a window the palette was not over, and the window under the panel kept an empty overlay stack, so it went on treating its terminal as the keyboard target (input-spec.md W5, W6, F1). The palette now reports the window it parented to, and the app opens the overlay on that window's controller. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c8241b2aa98359e57bc6
|
Owned by an agent session · session |
|
Bead |
Independent review at this exact head: SHIPTwo independent Codex SHIP. Its findings:
Checks at this head
The only follow-up raised is item 4's unowned-window test, which covers pre-existing fallback behaviour this PR preserves unchanged, so it is not a blocker here. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 13531e3 to compare; do not merge until green; see lane-rules The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/41004307929 at 8e7537c failed: cmux-next checks (god files, concurrency, crash safety, l10n). |
|
Review (codex gpt-6-astra high, next-review): SHIP at ec6d8ce No blocking findings. |
|
Orphaned: no activity for 14 h, and no lane holds a lease on it. Adopted by the orphan sweep: merged |
|
Found 1 test failure on Blacksmith runners: Failure
|
|
Review (codex gpt-6-astra high, next-review): SHIP at 978a05e No blocking findings. |
A newer verdict blocks 6c26512 (no independent SHIP at the head).
|
Review (codex gpt-6-astra high, next-review): SHIP at 6c26512 No blocking findings. The callback reports the resolved parent before key-window changes, and the regression test checks overlay placement and cleanup. Reviewed the supplied diff and files; tests were not run. |
|
Shipped in:
|
|
Shipped in:
|
|
Shipped in:
|
The slice head 2b2fc8e (= 43037147a0b's tree: slice + trunk sync c09bb74) merges the feat tip, 130 new commits. 58 files conflicted: - 0 reuse the previous round's resolutions: this round's base is the previous feat tip a14725a, so no hunk had all three sides equal to 5f39f9e's inputs. - Regenerated, not hand-merged: the 4 CmuxMessagesLab patches (Fixture, Layout, RowDrawing, WindowView) from the merged vendored sources against the pinned upstream reconstructed from the side whose patch was consistent (feat's for 3, perf's for WindowView; feat 65de604 edited WindowView.swift without rewriting its patch). - 54 resolved by porting feat's change into perf's shape: - Package.swift (CmuxNext: Floating tests get CmuxNextDesign with perf's Wakeups deps; CmuxMobileKit: Model gets feat's Resources with perf's CmuxSafeConcurrency). - Lineage parents #24136: SidebarSessionTreeFeed (recordedParents and per-host links on perf's ServiceObservation), RemoteAcpmuxWire (host/linksAlias notes posted on perf's mainWork; test-only inits take them too), AgentTabRemoteHost, AgentTabs+Wiring, AgentSessionWorkspace (the record is one completion task on main), tests. - Artifact sandbox #26216: perf's page-host PageArtifactFrame already serves the sandboxed endpoint; feat's navigation tests ported to PageNavigation, frame-ancestors expectation added, artifactFrameURL keeps only the page host, webviews tests on perf's URLs. - pane.commandAvailability (#25405) and chief.face (#27914) beside perf's keyToPaint and Wiring init. - State files off main #27749: AgentUnsentDrafts (perf's ObservedMap) and ScrollAnchors (blockingDecode) on AgentPaneStateFile. - AgentPaneTransport.close: perf's close reason plus feat's host-request failure and trust reset. - CloudService: perf's file, plus #27104 signedOutCreation (scope, policy, stop, signOut in +Machines) and #26740 autosize/sizePick; listIsCurrent moved to +AppServer as feat did. - debug.object_menu into perf's DebugDriverControl; Home fixture presence/faces beside perf's field. - Palette overlay window #26951 into perf's paletteBuilt. - Per-window Home #27880 in perf's dockTab helper; active team (1bb1b96) on perf's cloudAuth. - Busy refusal (a930474, 2a9b813) in perf's LineTransport split (+State, +Route, framed write). - Launch snapshot sidebar doc (dc38061): DaemonLaunchSnapshot.sidebarDoc, shown once settled (whenSettled), its connect follow a ServiceObservation; the Rust test moved to launch_snapshot_tests.rs. - Hover card corners (bc66475) with perf's interactive flag; Chief pet state (#25671) on perf's ServiceObservation and AppearanceSignals; Chief picture crop (#27933) on perf's lanes and optional store; Home presence (#25578) as a FrameObservation; sidebar tab look (#27530) via SidebarLookFollower; settleOpenings (#24374) with perf's snap; Omnibar imports. - acpmux: inbox digest senders without slicing; withdraw_queued async with from_agents_only; cmux-tui-core and platform: connection leases beside perf's modules and InlineSocket (bounded_outbound.rs takes back the `platform::transport` import perf had dropped, which feat's claim_watched_connection names). - webviews: App/NewTab/NewTabScreen (retry, slash commands, at), settings keys moved to cardRows.ts. - Feat's tests that call perf-changed inits fixed: TabConcentricCornerTests (input:), HomeChatPresenceFacesTests (deadlines:, frame delivery), RemoteChildSessionsTests (main run loop for posted notes). Generators: cargo metadata in cmux-tui, cmux-tui/fuzz, Native/DiffSidecar and optchat-chief changed no lock; gen-features.py --check, gen-chromium-host-wire.py --check and --check-ops, sync-resource-api.py --check pass. webviews: bun run check passes. schemas/settings/settings-schema.json stays perf's (generated; the autofix regenerates it): until then feat's chiefLook.test.tsx misses the home.bubbleColors rows.
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Fixes #4303.
What was wrong
#24814 fixed the responder handoff inside the panel (
onBecomeKey→focusField). The remaining hole is one level up: the window under the panel is not reliably told the palette is open.The palette panel is a child of the window it opens over (
PaletteController.present, which resolvesrelativeTo:??NSApp.keyWindow??NSApp.mainWindow, then walks up to the top-level parent). Its focus overlay, though, went onwindows.active— a different resolution: the window an input event came from, else the key window's owner, else the last active one.Those disagree whenever the palette opens over a window that is not the active one:
relativeTo:that is notwindows.active;windows.activeis the event's window,presentparents to the key window;owner(of:)is nil andactivefalls back tolastActive.When they disagree the overlay lands on a window the palette is not over, and the window under the panel keeps an empty overlay stack. That window's
FocusCoordinatortherefore goes on treating its terminal as the keyboard target andFocusEffectApplierkeeps re-seating the terminal surface as its first responder — the "keystrokes leak to the terminal" shape of this bug. It breaks three stated invariants inplans/cmux-next/input-spec.md: W5 ("panel has the keys but the window's overlay stack is empty"), W6, and F1 ("while an overlay is open it is the keyboard target").W6's check only counts overlays (
palettes.count != 1), so an overlay on the wrong window passed it; only W5 catches it, and only in a verification build, and it reports rather than repairs.The fix
PaletteController.onVisibilityChangenow reports the window the panel actually parented to, andAppServices.observePaletteForFocusopens the overlay on that window's controller instead of guessing fromwindows.active.parentWindowis assigned before the callback so the reported window and the panel's parent cannot drift. The close path is unchanged — it already closes the overlay on whichever window holds it.Test
PaletteOverlayWindowTests.theOverlayGoesOnTheWindowThePaletteOpensOveropens two windows, pointskeyWindowSourceat the second sowindows.activeis the one the palette is not over, presents over the first, and asserts the overlay is on the first and not the second. It fails on the parent commit and passes here.observePaletteForFocushad no test at all before this.Deliberately out of scope
An independent read of the whole key path also found that
ActiveAppKeyPanel.canBecomeKey == isAppActive()means a palette opened while the app is inactive (a CLI/MCP open, aCMUX_NEXT_NO_ACTIVATE=1run) draws without taking the keys. That is the panel's documented contract — it exists so a non-activating panel never captures what the user is typing into their frontmost app — so changing it is a product decision, not a bug fix, and it is not touched here. The Cmd+P path cannot hit it: a key event reaching the app means the app is active.No UI change, so no before/after capture applies.
Risk
Low and contained: one callback signature with a single consumer, and one window lookup. The only behaviour change is which window's overlay stack records an open palette, and only in the cases where the two resolutions disagreed.
Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes the command palette focus leak: when the palette opened over a window that wasn't
windows.active, its focus overlay went on the wrong window, leaving the window under the panel with an empty overlay stack so keystrokes went to the terminal instead of the palette.PaletteControllernow reports the window the panel actually parents to, andobservePaletteForFocusopens the overlay on that window's controller instead of guessing fromwindows.active. Adds a test (PaletteOverlayWindowTests) that fails on the old behavior and passes here.No UI change; only the overlay placement logic is affected.
Written for commit 6c26512. Summary will update on new commits.
No capture: the change only moves the palette's focus overlay onto the window the palette opens over; nothing drawn changes.