Skip to content

Give every window its own independent Dock - #7144

Merged
austinywang merged 47 commits into
mainfrom
issue-7142-per-window-dock
Jul 2, 2026
Merged

austinywang merged 47 commits into
mainfrom
issue-7142-per-window-dock

Conversation

@austinywang

@austinywang austinywang commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #7142 — every cmux window now hosts its own independent Dock. Opening a second window no longer shows the "Global Dock is active in another window" placeholder: each window's right sidebar mounts its own live DockSplitStore, so N windows can run N Docks (terminals + browsers) simultaneously.

Behavior

  • Per-window store, fresh seed. A window's Dock is created lazily the first time that window shows the Dock and is seeded from the Dock config exactly like a fresh app launch (~/.config/cmux/dock.json, .cmux/dock.json semantics unchanged). No state migrates between windows.
  • Dies with its window. Closing a window tears down that window's Dock through closeAllPanels() (terminals, browsers, portals — no leaked PTYs). The old app-lifetime persistence of the single Global Dock is intentionally retired. Quit and last-window close go through the same unregister path.
  • No render-host gating. DockInactiveHostView, renderHostId, and its first-claim/handoff logic are removed (dead code under per-window stores). The visibleUIHostIds union survives to keep visibility stable across transient SwiftUI remounts within a window. The removed dock.inactiveHost.title string is deleted from Localizable.xcstrings.

Design: owner id == window id

Each per-window store's workspaceId is its owning window's windowId (registry: AppDelegate.windowDocksById, see AppDelegate+WindowDock.swift). That makes Dock-scoped CLI results self-describing (workspace_id names the window whose Dock was hit) and makes routing a dictionary lookup.

CLI/socket compatibility:

  • The legacy Global Dock constant (D0CCD0CC-…) keeps routing as an alias meaning "the Dock of the routed window" (explicit window_id, else the caller's window), so existing scripts keep working.
  • A Dock owner id in workspace_id routes commands to that window (resolveTabManager prefers the owning window over the caller).
  • Surface/pane containment scans every window Dock, so surface.focus, surface.close, browser commands, etc. resolve the owning window cross-window.

Call-site audit (every user of the old app.globalDock / existingGlobalDock / isGlobalDockOwnerId)

Area Decision
RightSidebarPanelView Mounts app.windowDock(for: tabManager) — the window's own store.
dockSurfaceCreate / dockPaneCreate (--placement dock) Create in the routed window's Dock.
windowDockForRouting (was globalDockForRouting) Owner id → that Dock; alias → routed window's Dock; else containment.
Shortcut routing (focusedDockStoreForShortcut) + focused close (closeFocusedDockPanelForCommand) Use the focused window's own Dock (context.windowId).
Drag/drop (dockForPane, locateDockSurface, moveSurfaceIntoDock, moveDockSurfaceTo*) Scan all window Docks; Dock↔main and Dock↔Dock moves work across windows.
dockReferenceTabManager Window Dock → its owning window (fallback: active window mid-teardown).
Browser dock resolvers (v2ResolveWindowDockBrowser*) Thread tabManager so the alias resolves per window.
resolvedSurfaceIdForClose dock branch Removed — unreachable now that windowDockForRouting handles owner-id routing first (comment explains).
Workspace.openDockBrowserLinkInNewTabIfNeeded Finds the window Dock containing the panel.

Workspace-scoped Docks (Workspace.dockSplit, project .cmux/dock.json trees) are untouched.

Tests

  • New cmuxTests/WindowDockLifecycleTests.swift (wired into project.pbxproj): independent store per window (identity, owner ids, routing ids), teardown on window unregister (registry drop + panels closed + not visible), and both windows' Docks visible/portal-active simultaneously (the exact regression the render-host gate used to cause).
  • DockSocketLifecycleTests updated to the new semantics: results report the owning window id; owner-id routing for list/current/close/pane-mutation/browser commands; a dedicated legacy-alias compat test; the two-window focused-close test now proves Cmd+W in window B closes B's Dock panel while window A's Dock is untouched.
  • Not unit-testable (stated per repo policy): the pure SwiftUI rendering of two live Docks side-by-side and portal reattachment on window recreation; covered indirectly by the portal-visibility assertions above.

Localization audit

Removed surface only: DockInactiveHostView and its dock.inactiveHost.title key (en + ja) deleted from Resources/Localizable.xcstrings. No new user-facing strings were introduced (verified via rg over the touched Swift files for new Text(/String(localized: usages — none added). docs/dock.md (English-only CLI doc) gained a per-window paragraph; the localized web docs catalogs (web/messages/*) contain no window-sharing claims, so no catalog changes were needed.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Replaces the single app-wide Dock with independent per-window Docks. Each window owns, renders, and tears down its Dock seeded from ~/.config/cmux/dock.json; routing, shortcuts, browser/socket commands, and quit/close flows now target the owning window. Fixes #7142.

  • New Features

    • Per-window Dock lifecycle: created on first show; torn down on window unregister; never created for closed windows; no “Global Dock is active in another window” placeholder.
    • Owner-anchored routing: a Dock’s workspace_id equals its window_id; the legacy global‑dock alias means “the Dock of the routed window”; explicit window_id/pane_id are honored; surface/pane containment and drag/move scan across windows; browser shortcuts route to the window Dock.
    • Quit and close confirmations: busy window‑Dock terminals count toward quit warnings; close‑confirmation routing always uses the owner window.
  • Bug Fixes

    • Conflicting or unresolved Dock selectors fail closed with a localized “Conflicting Dock routing selectors” error.
    • Focus/refresh/close act on the Dock’s owning window; Ghostty runtime close and child‑exit now close window‑Dock terminals.
    • Guard against moving a window’s last main panel into its own Dock to avoid destroying the moved surface.

Written for commit 0c1c671. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Each window now has its own Dock, with independent terminal and browser panel state.
    • Dock-aware control and browser socket routing now targets the owning window’s Dock.
  • Bug Fixes
    • Fixed Dock creation, closing, focus, and move actions so they stay within the correct window’s Dock.
    • Dock runtime closes now prefer the owning window’s Dock when applicable.
    • Quit confirmation now includes close-confirmation-required window Docks.
    • Improved dock routing conflict handling with localized error messaging.
  • Documentation
    • Updated Dock docs/comments to reflect per-window Dock behavior.
  • Tests
    • Expanded window-Dock lifecycle and routing coverage for multi-window scenarios.

Replace the single app-wide Global Dock with per-window DockSplitStores:
each main window lazily creates its own Dock (seeded fresh from
~/.config/cmux/dock.json, owner id == window id), renders it live in its
right sidebar, and tears it down — panels, PTYs, portals — when the
window unregisters. No window ever shows the "Global Dock is active in
another window" placeholder; DockInactiveHostView and the renderHostId
first-claim gating are removed.

CLI/socket routing resolves per window: a Dock-scoped workspace_id names
the owning window (results are self-describing), the legacy global-dock
constant keeps routing as an alias for "the Dock of the routed window",
and surface/pane containment scans every window Dock. Shortcut routing,
focused-close, drag/drop moves, and browser tab commands all target the
window's own store.

Fixes #7142

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 2, 2026 5:38pm
cmux-staging Building Building Preview, Comment Jul 2, 2026 5:38pm

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR replaces the shared Dock with per-window Dock storage, updates rendering and lifecycle cleanup to use window-scoped Dock instances, and rewires shortcut, control, browser, and socket routing to resolve and close the correct window Dock. Tests, docs, localization, and project membership were updated to match.

Changes

Per-window Dock lifecycle and cleanup

Layer / File(s) Summary
Window Dock registry and lifecycle
Sources/AppDelegate+WindowDock.swift, Sources/AppDelegate.swift, Sources/AppDelegate+RecoverableMainWindowRoutes.swift, Sources/DockSplitStore.swift, Sources/DockSplitStore+CloseConfirmation.swift, Sources/QuitConfirmationAlertPresenter.swift, Sources/GhosttyTerminalView.swift
Per-window Dock storage, teardown, lookup, recovery, close confirmation, and runtime close paths now use window Dock ownership and cleanup.
Dock rendering and visibility
Sources/DockPanelView.swift, Sources/RightSidebarPanelView.swift, Sources/DockScope.swift, Sources/DockSplitStore+Config.swift, Sources/DockSplitStore+SurfaceTransfer.swift, Sources/DockInactiveHostView.swift, docs/dock.md, Resources/Localizable.xcstrings
Right-sidebar Dock rendering, visibility, scope docs, browser lookup comments, documentation, and localization text are updated for per-window Dock semantics.

Routing and control paths

Layer / File(s) Summary
Shortcut routing and Dock ownership
Sources/AppDelegate+DockShortcutRouting.swift, Sources/KeyboardShortcutContext.swift, Sources/Workspace+DockBrowserLookup.swift, Sources/AppDelegate+DockSurfaceMove.swift
Keyboard shortcut focus, browser panel lookup, focused close, and Dock move checks resolve the current window Dock instead of the removed global Dock.
CLI control-pane and surface Dock routing
Sources/TerminalController+ControlSurfaceContext.swift, Sources/TerminalController+ControlSurfaceContext2.swift, Sources/TerminalController+ControlSurfaceContext3.swift, Sources/TerminalController+ControlSurfaceDock.swift, Sources/TerminalController.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Pane/ControlPaneCreateResolution.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Pane/ControlCommandCoordinator+Pane.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlSurfaceCreateResolution.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Surface/ControlCommandCoordinator+Surface.swift
Control-surface and control-pane routing now resolve window Dock ownership, compute dock-derived window IDs, and surface conflicting-selector failures through new resolution cases and error mapping.
Browser CLI Dock routing
Sources/TerminalController+WindowDockBrowserRouting.swift, Sources/TerminalController.swift
Browser socket routing, payload construction, tab operations, and close handling now target window Dock browser panels and tab stores.

Tests and wiring

Layer / File(s) Summary
Test suite updates and project wiring
cmux.xcodeproj/project.pbxproj, cmuxTests/AppDelegateMainWindowTestingSupport.swift, cmuxTests/WindowDockLifecycleTests.swift, cmuxTests/WindowDockRoutingSocketTests.swift, cmuxTests/DockSocketLifecycleTests.swift, cmuxTests/AppDelegateRenameShortcutContextTests.swift, cmuxTests/DockTerminalReattachTests.swift
Project membership and tests were updated to cover per-window Dock lifecycle, routing, shortcut behavior, and reattach assertions.
Related test support
cmuxTests/AppDelegateMainWindowTestingSupport.swift
Test-only main-window registration and teardown helpers support the new Dock lifecycle and routing tests.

Estimated code review effort: 5 (Critical) | ~120 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error Sources/KeyboardShortcutContext.swift:305-330 and TerminalController+ControlSurfaceDock.swift:438-466 add hot fallback scans over all docks/workspaces/panels with no bound or benchmark. Add a reverse index (panelId/webView → BrowserPanel, paneId → Dock) or cache dock lookups so shortcut/socket fallbacks don’t rescan every window/workspace panel set.
Cmux No Ambient Global State ❌ Error Sources/DockSplitStore.swift:67 adds a module-wide live-store registry (static let liveStoresTable/liveStores), which is mutable ambient state instead of injected ownership. Move the registry into an owned, injectable DockRegistry (e.g. on AppDelegate or a dedicated service) and pass it to DockSplitStore/routing helpers.
Linked Issues check ⚠️ Warning [#7142] is satisfied, but [#39] release items (shortcut remaps, update pill move, update relaunch menu) are not implemented. If #39 is meant to be in scope, add the v1.31.0 shortcut remaps, sidebar-only update pill, and Install Update and Relaunch menu item changes; otherwise unlink #39.
✅ Passed checks (22 passed)
Check name Status Explanation
Title check ✅ Passed Matches the main change: moving cmux from a single app-wide Dock to per-window independent Docks.
Description check ✅ Passed Covers summary, behavior, design, tests, and localization, though the template's demo video/review trigger/checklist sections are omitted.
Out of Scope Changes check ✅ Passed All visible changes support the per-window Dock refactor; no clear unrelated feature work stands out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Cmux Swift Actor Isolation ✅ Passed New Dock helpers stay on @MainActor AppDelegate/MainWindowContext/TerminalController; the only nonisolated addition is a UUID constant, and Dock store access remains actor-confined.
Cmux Swift Blocking Runtime ✅ Passed Production Swift diff adds no new blocking/timing sync primitives; zero added matches for waits, sleeps, syncs, or locks in changed Sources/Packages files.
Cmux Browser Automation Off-Main ✅ Passed Wait-heavy browser verbs stay in socketWorkerMethods and are test-covered; worker handlers wrap WebKit/AppKit access in v2MainSync, so no off-main regression.
Cmux Expensive Synchronous Load ✅ Passed New dock-routing/close/shortcut/socket paths add no expensive agent-history loads; existing RestorableAgentSessionIndex.load() sites are preexisting cold-cache fallbacks.
Cmux Cache Substitution Correctness ✅ Passed No persistence/history/undo/snapshot path swaps a fresh read for a stale cache; new WindowDock lookups are live UI/routing helpers with creation or graceful fallback.
Cmux No Hacky Sleeps ✅ Passed No covered non-Swift runtime files changed; submodule diffs only touch Zig/Swift, and no sleep/timer/polling patterns appear in changed non-Swift files.
Cmux Swift Concurrency ✅ Passed PASS: touched production files add no new DispatchQueue/Combine/completion-handler patterns; only test-target serialization uses withCheckedContinuation/Task, which the rule allows.
Cmux Swift @Concurrent ✅ Passed No touched Swift file adds misused @concurrent or nonisolated async work; new async test helpers are UI-bound or explicitly hop to a worker/main actor.
Cmux Swift File And Package Boundaries ✅ Passed PASS: Changes are focused app-target Dock/glue and tests; the only new prod Swift file is cohesive and under the 500-line reference threshold, and oversized files were only lightly touched.
Cmux Swiftpm Lockfiles ✅ Passed No cmux-owned .gitignore ignores Package.resolved; the only match is vendored vendor/bonsplit. I found no package-reference or lockfile omission in the touched files.
Cmux Swift Logging ✅ Passed No added or modified production logging was found in the touched dock-routing files; remaining NSLog/Logger uses are pre-existing or debug-only.
Cmux User-Facing Error Privacy ✅ Passed New user-facing error text is generic (“Conflicting Dock routing selectors”) and exposes no vendor, secret, or internal payload details.
Cmux Full Internationalization ✅ Passed New user-facing Dock error copy uses String(localized:); the xcstrings key has translated entries for all 20 catalog locales, and no uncataloged UI text was added.
Cmux Swiftui State Layout ✅ Passed Touched SwiftUI files add no GeometryReader, lazy/list rows, or new @Published/ObservableObject; changes are branch/removal only, with existing AppKit bridge state untouched.
Cmux Architecture Rethink ✅ Passed Dock ownership is centralized in MainWindowContext with teardown on unregister; the renderHostId gate is removed and no new symptom-patch timing/observer workaround was added.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR only retargets main-window/right-sidebar Dock routing and adds test-only NSWindow fixtures; no new standalone user-visible cmux window lacks a stable cmux.* id or shared close-shortcut owner...
Cmux Source Artifacts ✅ Passed Changed paths are intentional source/docs/config/localization plus submodule refs; no logs, caches, temp dirs, or build artifacts appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR moved test helpers into cmuxTests; changed Sources/ code is product routing/teardown logic with no new ForTesting/debug-only seam.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-7142-per-window-dock

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the single app-wide globalDock with independent per-window DockSplitStore instances, each tied to its owning MainWindowContext and torn down via closeAllPanels() when the window unregisters. Routing, shortcuts, drag-and-drop, browser commands, and quit-confirmation dirty checks are all threaded through the owning window, with the legacy D0CCD0CC-… alias preserved for CLI back-compat.

  • Lifecycle: each window's Dock is created lazily on first sidebar render and torn down unconditionally on window close; teardownWindowDock() fires before the context is removed from mainWindowContexts so async Ghostty callbacks queued during teardown can no longer re-create the store.
  • Routing: windowDockForRouting uses the legacy alias, owner-id (window-id == dock-owner-id), and surface/pane containment in strict fail-closed order; windowDockRoutingConflicts rejects contradictory explicit selectors before any panel is created or mutated.
  • Test seams: registerMainWindowContextForTesting and unregisterMainWindowContextForTesting moved from #if DEBUG in production Sources/ to cmuxTests/AppDelegateMainWindowTestingSupport.swift, reaching internal AppDelegate state via @testable import.

Confidence Score: 5/5

This PR is safe to merge. The per-window Dock lifecycle is correctly scoped to MainWindowContext, teardown is unconditional and ordered (nil before closeAllPanels), and all routing paths are fail-closed on contradictory selectors.

The core lifecycle invariant — one DockSplitStore per window, created lazily, torn down on unregister — is upheld at every call site. Routing conflict detection, browser-command resolution, quit-confirmation, and Ghostty runtime-close callbacks are all updated consistently. The test seams were correctly moved to the test target per the canonical pattern. No correctness bugs were found in routing, teardown ordering, or notification cleanup.

No files require special attention. DockScope.swift carries an acknowledged naming mismatch (the global case now represents a per-window store) but the comment is clear and the raw value is stable for coding compatibility.

Important Files Changed

Filename Overview
Sources/AppDelegate+WindowDock.swift New file centralising all per-window Dock lifecycle: lazy creation, owner-id routing, teardown-on-close, and containment helpers. Design is sound; teardownWindowDock correctly nils the store before calling closeAllPanels to prevent re-entry.
Sources/TerminalController+ControlSurfaceDock.swift Replaces global-dock routing with per-window routing; windowDockForRouting is fail-closed on owner-id with no existing dock; conflict detection via windowDockRoutingConflicts is thorough.
Sources/TerminalController+WindowDockBrowserRouting.swift New file for browser-socket resolution through per-window Docks. windowDockSelectorsConflict correctly rejects non-alias workspace IDs that contradict a Dock found via surface/pane. All resolution happens inside v2MainSync.
Sources/AppDelegate.swift notifyMainWindowContextsDidChange and discardOrphanedMainWindowContext widened to internal for @testable access from the test target (canonical fix); #if DEBUG registerMainWindowContextForTesting removed from production source.
Sources/QuitConfirmationAlertPresenter.swift Adds per-window Dock dirty-terminal check to hasQuitConfirmationDirtyWorkspaces; correctly gates quit confirmation on busy window-Dock terminals.
Sources/GhosttyTerminalView.swift Both Ghostty runtime-close callbacks now call closeWindowDockRuntimeSurface before the workspace path, correctly handling surfaces whose tab-id is a window-Dock owner.
Resources/Localizable.xcstrings Adds fully-translated dock.error.conflictingRoutingSelectors across all supported locales; removes dock.inactiveHost.title cleanly.
cmuxTests/WindowDockLifecycleTests.swift New suite covering identity, owner-id routing, teardown-on-unregister, and simultaneous multi-window Dock visibility.
cmuxTests/WindowDockRoutingSocketTests.swift Comprehensive socket routing tests covering owner-id routing, legacy alias, conflict detection, cross-window focused-close, and browser command routing.
Sources/DockScope.swift The global case now represents a per-window Dock; doc comment explains naming is kept for coding/raw-value compatibility. Semantic mismatch is acknowledged.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant W as NSWindow
    participant AD as AppDelegate
    participant MWC as MainWindowContext
    participant DSS as DockSplitStore
    participant SW as Socket Worker

    W->>AD: registerMainWindowContext
    AD->>MWC: "create (windowDock=nil, lazy)"

    W->>MWC: RightSidebar renders dock mode
    MWC->>DSS: windowDockStore() creates store
    Note over DSS: workspaceId == windowId

    SW->>AD: socket command with routing selectors
    AD->>AD: windowDockForRouting
    AD->>DSS: route to owning store
    DSS-->>SW: "result (workspace_id = windowId)"

    W->>AD: window closes
    AD->>MWC: teardownWindowDock
    MWC->>DSS: "windowDock=nil then closeAllPanels"
    AD->>AD: remove context from mainWindowContexts
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant W as NSWindow
    participant AD as AppDelegate
    participant MWC as MainWindowContext
    participant DSS as DockSplitStore
    participant SW as Socket Worker

    W->>AD: registerMainWindowContext
    AD->>MWC: "create (windowDock=nil, lazy)"

    W->>MWC: RightSidebar renders dock mode
    MWC->>DSS: windowDockStore() creates store
    Note over DSS: workspaceId == windowId

    SW->>AD: socket command with routing selectors
    AD->>AD: windowDockForRouting
    AD->>DSS: route to owning store
    DSS-->>SW: "result (workspace_id = windowId)"

    W->>AD: window closes
    AD->>MWC: teardownWindowDock
    MWC->>DSS: "windowDock=nil then closeAllPanels"
    AD->>AD: remove context from mainWindowContexts
Loading

Reviews (32): Last reviewed commit: "Align Dock conflict tests with read-rout..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 26 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/TerminalController+ControlSurfaceContext.swift
Comment thread Sources/AppDelegate.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Workspace+DockBrowserLookup.swift (1)

80-89: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not close the legacy workspace Dock from a focused window-Dock shortcut.

If the right sidebar is in Dock mode but the per-window store has not been registered yet, this fallback can close selectedWorkspace’s old Dock state. Use the window-owned Dock as the single target and no-op when it has no focused panel.

As per path instructions, Dock/window ownership must use one authoritative routing path.

Proposed fix
-        if let windowDock = existingWindowDock(forWindowId: context.windowId) {
-            guard let panelId = windowDock.focusedPanelId else { return true }
-            if windowDock.closePanel(panelId, force: false) {
-                notificationStore?.clearNotifications(forTabId: windowDock.workspaceId, surfaceId: panelId)
-            }
-            return true
-        }
-        guard let workspace = context.tabManager.selectedWorkspace,
-              let panelId = workspace.focusedDockPanelId else { return true }
-        _ = workspace.closeDockPanelAndClearNotifications(panelId, force: false)
+        let windowDock = windowDock(forWindowId: context.windowId)
+        guard let panelId = windowDock.focusedPanelId else { return true }
+        if windowDock.closePanel(panelId, force: false) {
+            notificationStore?.clearNotifications(forTabId: windowDock.workspaceId, surfaceId: panelId)
+        }
         return true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Workspace`+DockBrowserLookup.swift around lines 80 - 89, The fallback
in Workspace+DockBrowserLookup’s focused-window Dock handling is incorrectly
closing selectedWorkspace’s legacy Dock state when the per-window store is
missing. Update the logic so the shortcut only targets the window-owned Dock via
existingWindowDock(forWindowId:) and returns immediately if that Dock has no
focusedPanelId; do not consult tabManager.selectedWorkspace or call
closeDockPanelAndClearNotifications in this path.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/AppDelegate`+DockShortcutRouting.swift:
- Around line 24-28: Stop falling back to the legacy workspace Dock in the dock
shortcut routing path. Update the Dock resolution in
AppDelegate+DockShortcutRouting so the focused window always uses the
window-owned DockSplitStore via existingWindowDock(forWindowId:) and, if none
exists, returns nil instead of selectedWorkspace?.dockSplit; keep the change
localized to the dock lookup helper used for Dock shortcuts.

In `@Sources/AppDelegate`+WindowDock.swift:
- Around line 91-98: The dock lookup in dockReferenceTabManager(for:) currently
falls back from tabManagerFor(windowId:) to
preferredRegisteredMainWindowContext()?.tabManager when dock.scope is .global,
which can route a move into the wrong window’s Dock. Update this method to fail
closed by returning nil when the owning window context cannot be resolved, and
keep the tabManagerFor(tabId:) path unchanged for non-global docks.

In `@Sources/TerminalController.swift`:
- Around line 5276-5278: The window-Dock routing in
v2ResolveWindowDockBrowserPanelContext and v2ResolveWindowDockBrowserTabStore
currently accepts mixed selectors and then prefers dockBySurface/dockByPane over
dockByOwner, which can route commands to the wrong Dock. Add the same
resolvedDocks agreement check in both helpers so workspace_id, surface_id,
pane_id, and owner-derived resolution must all agree before treating the request
as handled. If they disagree, fail closed by returning an unhandled/error result
instead of overriding the caller’s tabManager or context.

In `@Sources/TerminalController`+ControlSurfaceContext.swift:
- Line 86: The routing helpers are allowing a Dock found by surfaceID/paneID to
be acted on even when it belongs to a different resolved window/workspace than
the one selected by resolveTabManager. Update
windowDockForRouting(_:tabManager:) and the related windowDockContainingPanel
paths to verify the Dock’s owner matches the resolved tab manager/window
identity before mutating or focusing it, using the existing
v2ResolveWindowId(tabManager:) / explicit windowID sources as the authority. If
the resolved owner does not match, skip that Dock and continue routing so
actions stay scoped to the correct window.

In `@Sources/TerminalController`+ControlSurfaceContext2.swift:
- Around line 425-447: The close flow in
TerminalController+ControlSurfaceContext2 should reject cases where the Dock
resolved from surfaceID disagrees with the explicitly resolved tab/window
context. In the windowDock close branch and the other close branch at the
referenced close helpers, add a guard that compares Dock ownership and surface
identity before calling closePanel, and return a failure if the
windowDock/tabManager sources do not match. Ensure the returned closed payload
uses a windowID derived from the same resolved window/workspace context used to
close the panel.

---

Outside diff comments:
In `@Sources/Workspace`+DockBrowserLookup.swift:
- Around line 80-89: The fallback in Workspace+DockBrowserLookup’s
focused-window Dock handling is incorrectly closing selectedWorkspace’s legacy
Dock state when the per-window store is missing. Update the logic so the
shortcut only targets the window-owned Dock via existingWindowDock(forWindowId:)
and returns immediately if that Dock has no focusedPanelId; do not consult
tabManager.selectedWorkspace or call closeDockPanelAndClearNotifications in this
path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3aac6ffb-fe22-4c96-a39d-aed17bd1ad2c

📥 Commits

Reviewing files that changed from the base of the PR and between 6985a83 and 1bb00fc.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (25)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+DockShortcutRouting.swift
  • Sources/AppDelegate+DockSurfaceMove.swift
  • Sources/AppDelegate+GlobalDock.swift
  • Sources/AppDelegate+WindowDock.swift
  • Sources/AppDelegate.swift
  • Sources/DockInactiveHostView.swift
  • Sources/DockPanelView.swift
  • Sources/DockScope.swift
  • Sources/DockSplitStore+Config.swift
  • Sources/DockSplitStore+SurfaceTransfer.swift
  • Sources/DockSplitStore.swift
  • Sources/RightSidebarPanelView.swift
  • Sources/TerminalController+ControlPaneContext.swift
  • Sources/TerminalController+ControlPaneDock.swift
  • Sources/TerminalController+ControlSurfaceContext.swift
  • Sources/TerminalController+ControlSurfaceContext2.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+ControlSurfaceDock.swift
  • Sources/TerminalController.swift
  • Sources/Workspace+DockBrowserLookup.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/DockSocketLifecycleTests.swift
  • cmuxTests/WindowDockLifecycleTests.swift
  • docs/dock.md
💤 Files with no reviewable changes (3)
  • Sources/DockInactiveHostView.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+GlobalDock.swift

Comment thread Sources/AppDelegate+DockShortcutRouting.swift Outdated
Comment thread Sources/AppDelegate+WindowDock.swift
Comment thread Sources/TerminalController.swift
Comment thread Sources/TerminalController+ControlSurfaceContext.swift
Comment thread Sources/TerminalController+ControlSurfaceContext2.swift Outdated
Review follow-ups: the Dock registry is the single source of truth for
which window renders a Dock surface, so Dock-scoped commands now focus,
reveal, and report the dock's OWNING window (owner id == window id)
instead of whatever window the caller's routed context resolved.
Explicit selectors naming two different windows' Docks fail closed with
invalid_params. The focused-Dock shortcut path no longer falls back to
the never-rendered workspace Dock, dockReferenceTabManager fails closed
instead of retargeting the active window, and per-window dock stores
now live in a dedicated WindowDockRegistry so all lifecycle mutation is
centralized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/TerminalController+ControlSurfaceDock.swift (1)

100-117: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail closed when Dock routing selectors disagree.

windowDockForRouting returns the owner/alias Dock before checking surfaceID/paneID containment, so calls that don’t later guard with containsPanel/containsPane can return the wrong Dock snapshot for mixed selectors. Resolve all Dock candidates first, let the legacy alias stay non-specific, and return nil when explicit owner/surface/pane selectors point at different window Docks.

Proposed fix
 func windowDockForRouting(_ routing: ControlRoutingSelectors, tabManager: TabManager) -> DockSplitStore? {
-    if let workspaceID = routing.workspaceID {
-        if workspaceID == AppDelegate.windowDockAliasWorkspaceId {
-            return AppDelegate.shared?.windowDock(for: tabManager)
-        }
-        if let dock = AppDelegate.shared?.existingWindowDock(forWindowId: workspaceID) {
-            return dock
-        }
-    }
+    let dockByOwner: DockSplitStore? = routing.workspaceID.flatMap { workspaceID in
+        guard workspaceID != AppDelegate.windowDockAliasWorkspaceId else { return nil }
+        return AppDelegate.shared?.existingWindowDock(forWindowId: workspaceID)
+    }
+    let dockBySurface = routing.surfaceID.flatMap { windowDockContainingPanel($0) }
+    let dockByPane = routing.paneID.flatMap { windowDockContainingPane($0) }
+
+    let resolved = [dockByOwner, dockBySurface, dockByPane].compactMap { $0 }
+    if let first = resolved.first, resolved.contains(where: { $0 !== first }) {
+        return nil
+    }
+    if let dock = resolved.first {
+        return dock
+    }
+    if routing.workspaceID == AppDelegate.windowDockAliasWorkspaceId {
+        return AppDelegate.shared?.windowDock(for: tabManager)
+    }
-    if let surfaceID = routing.surfaceID,
-       let dock = windowDockContainingPanel(surfaceID) {
-        return dock
-    }
-    if let paneID = routing.paneID,
-       let dock = windowDockContainingPane(paneID) {
-        return dock
-    }
     return nil
 }

As per path instructions, correctness-critical routing must avoid disagreeing sources of truth and fail closed when authoritative mappings disagree.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TerminalController`+ControlSurfaceDock.swift around lines 100 - 117,
windowDockForRouting currently returns the workspace/alias Dock before fully
validating surfaceID and paneID, which can let mixed routing selectors resolve
to the wrong Dock. Update
TerminalController+ControlSurfaceDock.windowDockForRouting to gather the
candidate Docks for workspaceID, surfaceID, and paneID first, treat
AppDelegate.windowDockAliasWorkspaceId as the only non-specific legacy case, and
compare the explicit owner/surface/pane results. If the explicit selectors
disagree, return nil instead of picking one arbitrarily; only return a Dock when
the selectors consistently identify the same window Dock.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/TerminalController`+ControlSurfaceDock.swift:
- Around line 100-117: windowDockForRouting currently returns the
workspace/alias Dock before fully validating surfaceID and paneID, which can let
mixed routing selectors resolve to the wrong Dock. Update
TerminalController+ControlSurfaceDock.windowDockForRouting to gather the
candidate Docks for workspaceID, surfaceID, and paneID first, treat
AppDelegate.windowDockAliasWorkspaceId as the only non-specific legacy case, and
compare the explicit owner/surface/pane results. If the explicit selectors
disagree, return nil instead of picking one arbitrarily; only return a Dock when
the selectors consistently identify the same window Dock.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5a6ec9b8-6e9e-4d8a-ba5b-7ab8749c2237

📥 Commits

Reviewing files that changed from the base of the PR and between 1bb00fc and 80e1d2b.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (10)
  • Sources/AppDelegate+DockShortcutRouting.swift
  • Sources/AppDelegate+WindowDock.swift
  • Sources/AppDelegate.swift
  • Sources/TerminalController+ControlPaneContext.swift
  • Sources/TerminalController+ControlSurfaceContext.swift
  • Sources/TerminalController+ControlSurfaceContext2.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+ControlSurfaceDock.swift
  • Sources/TerminalController.swift
  • cmuxTests/DockSocketLifecycleTests.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/TerminalController.swift Outdated
austinywang and others added 3 commits July 1, 2026 15:59
…routing

Busy window-Dock terminals now make the quit / last-window-close warning
appear, matching workspace Docks (the retired Global Dock was never
counted). An explicit window_id that contradicts the Dock a command
resolves (by owner workspace_id, surface, or pane) now fails closed in
windowDockForRouting, the browser resolvers' conflict check, and the
focus/close containment branches, so explicit window routing is honored
or rejected — never silently overridden.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…licy

A recoverable (already-closed) window's manager can no longer lazily
create a NEW Dock store through CLI paths — it would have no teardown
owner, leaving headless panels running until quit. Manager-based Dock
creation now requires a live registered window; only an existing store
stays addressable during close races. The unconditional teardown on
window unregister is documented as deliberate: a busy Dock panel does
not veto its window's close, matching the window's workspace surfaces,
while the menu path keeps its unconditional dialog and quit counts
window Docks via hasQuitConfirmationDirtyWorkspaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TerminalController.swift`:
- Around line 5326-5334: The Dock routing conflict check in TerminalController
should also validate the requested workspace scope, not just the resolved Dock
stores. Update the conflict logic around windowDockSelectorsConflict so it takes
requestedWorkspaceID into account and fails closed when workspace_id disagrees
with surface_id/pane_id, except for the legacy alias or the Dock owner ID. Keep
the existing routing flow in TerminalController’s browser-command handling, but
ensure the guard before selecting dockBySurface/dockByPane/dockByOwner rejects
mixed workspace/Dock selectors instead of falling back.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6baab771-d53e-405b-9cb8-64b4603e0ed2

📥 Commits

Reviewing files that changed from the base of the PR and between 80e1d2b and e9dee4f.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (7)
  • Sources/QuitConfirmationAlertPresenter.swift
  • Sources/TerminalController+ControlSurfaceContext.swift
  • Sources/TerminalController+ControlSurfaceContext2.swift
  • Sources/TerminalController+ControlSurfaceDock.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/DockSocketLifecycleTests.swift
👮 Files not reviewed due to content moderation or server errors (4)
  • cmux.xcodeproj/project.pbxproj
  • Sources/QuitConfirmationAlertPresenter.swift
  • Sources/TerminalController+ControlSurfaceDock.swift
  • Sources/TerminalController+ControlSurfaceContext.swift

Comment thread Sources/TerminalController.swift Outdated
…browser resolvers

"Conflicting Dock routing selectors" now goes through
String(localized:) with en/ja entries like the other Dock socket
errors. The browser resolvers' conflict check also fails closed when a
workspace_id names a NON-Dock scope while surface/pane selectors point
into a window Dock (browser CLI commands never inject caller workspace
context, unlike close-surface, so this cannot break first-party flows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
austinywang and others added 3 commits July 1, 2026 16:55
No budget increases: the window-Dock browser resolvers, selector
conflict check, owner-anchored payload, and Dock browser close move to
a new TerminalController+WindowDockBrowserRouting.swift; the shared
focus/reveal owner-anchoring and the Dock close branch move into
TerminalController+ControlSurfaceDock.swift as reusable helpers; the
main-window ForTesting seams move to a dedicated
AppDelegate+MainWindowTestingSupport.swift per the debug-seam policy;
and the cross-window routing socket tests move to a new
WindowDockRoutingSocketTests.swift. Behavior is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Emptying a window's only workspace closes the window, and window close
now tears down its Dock — so accepting that move would destroy the
just-moved surface. Mirrors the existing workspace-Dock self-move
guard; windows with more workspaces are unaffected (only the emptied
workspace closes). Regression-covered in WindowDockLifecycleTests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/RightSidebarPanelView.swift (1)

419-433: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move window-dock creation out of the render path Sources/RightSidebarPanelView.swift:420
AppDelegate.windowDock(for:) lazily creates the DockSplitStore, so this call can mutate registry state during body evaluation. Trigger the first access from an explicit lifecycle hook (.onAppear, mode change, or didSet) and keep the view path read-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/RightSidebarPanelView.swift` around lines 419 - 433, The dock lookup
in dockPanel(windowAppearance:) is mutating state during view rendering because
AppDelegate.windowDock(for:) lazily creates the DockSplitStore; move that
first-access side effect out of the body path. Use an explicit lifecycle trigger
tied to RightSidebarPanelView, such as .onAppear, a mode change handler, or a
didSet on the relevant state, so DockPanelView only reads an already-created
dock and the render path stays read-only.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TerminalController.swift`:
- Around line 3169-3173: The Dock-owner routing in TerminalController should not
depend on an already-created Dock, because first-use requests can miss the
intended window and fall back incorrectly. In the workspace-id handling path,
keep the existing Dock-scoped lookup, but when it fails, resolve the owner
directly through tabManagerFor(windowId:) using the workspace lookup result
before any fallback to the active window. Apply the same fix in both the Dock
owner resolution and dockSurfaceCreate-related paths so tab selection is always
based on the authoritative window identity.

In `@Sources/TerminalController`+WindowDockBrowserRouting.swift:
- Around line 17-19: Explicit Dock routing selectors are currently being treated
as absent when v2UUID(...) returns nil for empty or invalid values, which allows
unintended fallback to the focused/owner Dock. Update the routing validation in
TerminalController+WindowDockBrowserRouting, including the selector handling in
v2ResolveWindowDockBrowserTabStore, so that presence of keys like surface_id,
pane_id, target_surface_id, target_pane_id, and window_id is checked before
parsing and any present-but-invalid UUID returns invalid_params instead of
falling back. Use the existing requestedWorkspaceID, requestedSurfaceID, and
requestedPaneID style variables as the entry points for this fail-closed
validation.

---

Outside diff comments:
In `@Sources/RightSidebarPanelView.swift`:
- Around line 419-433: The dock lookup in dockPanel(windowAppearance:) is
mutating state during view rendering because AppDelegate.windowDock(for:) lazily
creates the DockSplitStore; move that first-access side effect out of the body
path. Use an explicit lifecycle trigger tied to RightSidebarPanelView, such as
.onAppear, a mode change handler, or a didSet on the relevant state, so
DockPanelView only reads an already-created dock and the render path stays
read-only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75bf9968-8c5d-4287-abc2-10b19a66bce5

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6c319 and 00ab8ec.

📒 Files selected for processing (18)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+DockSurfaceMove.swift
  • Sources/AppDelegate+MainWindowTestingSupport.swift
  • Sources/AppDelegate+WindowDock.swift
  • Sources/AppDelegate.swift
  • Sources/DockSplitStore+SurfaceTransfer.swift
  • Sources/RightSidebarPanelView.swift
  • Sources/TerminalController+ControlPaneContext.swift
  • Sources/TerminalController+ControlSurfaceContext.swift
  • Sources/TerminalController+ControlSurfaceContext2.swift
  • Sources/TerminalController+ControlSurfaceContext3.swift
  • Sources/TerminalController+ControlSurfaceDock.swift
  • Sources/TerminalController+WindowDockBrowserRouting.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/DockSocketLifecycleTests.swift
  • cmuxTests/WindowDockLifecycleTests.swift
  • cmuxTests/WindowDockRoutingSocketTests.swift
💤 Files with no reviewable changes (1)
  • cmuxTests/DockSocketLifecycleTests.swift

Comment thread Sources/TerminalController.swift
Comment thread Sources/TerminalController+WindowDockBrowserRouting.swift
austinywang and others added 2 commits July 1, 2026 17:22
Per the debug-seam policy's preferred fix: the register/unregister
ForTesting helpers now live in cmuxTests and reach internal AppDelegate
state via @testable import, instead of shipping in production Sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uting

The CLI injects the caller's CMUX_WORKSPACE_ID unconditionally on the
surface/pane command family even with explicit surface UUIDs, so the
browser-style non-Dock-workspace conflict rule cannot apply here
without breaking Dock surface targeting from main-area terminals.
Encode the invariant where the precedence lives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
austinywang and others added 3 commits July 1, 2026 18:10
A supplied-but-unresolvable surface/pane/workspace/window handle (empty
string, stale ref) no longer degrades to the focused/owner Dock
fallback: both window-Dock browser resolvers now apply the canonical
v2RejectUnresolvedHandles guard once the request routes to a Dock,
matching the methods that already pre-validate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The containment-based focus branch now fails closed when an explicit
Dock-owner workspace_id (or window_id) names a different window's Dock
than the one containing the surface, matching the other window-Dock
resolvers. Non-Dock workspace ids stay non-conflicting for CLI
caller-context injection. Regression-covered in the two-window test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime closes route by the surface's owner id, which for a window Dock
is a window id no TabManager tab matches — the close and child-exit
callbacks silently no-opped (as they already did for the retired Global
Dock's synthetic owner id). Both callbacks now route window-Dock
surfaces to their owning store first, so Ctrl-D and close bindings tear
the panel down instead of leaving a dead surface retained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/TerminalController+ControlSurfaceDock.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread cmuxTests/WindowDockRoutingSocketTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
cmuxTests/WindowDockRoutingSocketTests.swift (1)

249-249: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reuse expectInvalidParams for all fail-closed assertions instead of checking ok==false alone.

Several fail-closed assertions in this test only check envelope["ok"] as? Bool == false (e.g. Lines 249, 297, 320, 325, 330, 335, 343, 352, 357, 362), while the expectInvalidParams helper (Lines 192-196) already exists and is used elsewhere in the same test (Lines 227, 238, 312, 380, 391) to also assert error["code"] == "invalid_params". A prior review on this file flagged a similar weakness (checking the localized message instead of the stable error code), which was fixed for one call site — but these newer conflict assertions reintroduce a related gap: they only prove some failure occurred, not that it's specifically the intended selector-conflict rejection. If an unrelated bug caused a different error path to fire, these assertions would still pass.

♻️ Suggested fix
-            `#expect`(lazyOwnerReadConflict["ok"] as? Bool == false)
+            try expectInvalidParams(lazyOwnerReadConflict)

Apply the same substitution at the other ["ok"] as? Bool == false-only checks (Lines 297, 320, 325, 330, 335, 343, 352, 357, 362).

Also applies to: 293-297, 308-343, 348-362

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/WindowDockRoutingSocketTests.swift` at line 249, The fail-closed
assertions in WindowDockRoutingSocketTests are only checking that the response
is not ok, which doesn’t verify the intended invalid_params failure path.
Replace the remaining envelope["ok"] as? Bool == false-only checks with the
existing expectInvalidParams helper so the tests also assert error["code"] ==
"invalid_params". Use expectInvalidParams consistently at the conflict-selector
call sites in this test, matching the same pattern already used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cmuxTests/WindowDockRoutingSocketTests.swift`:
- Line 249: The fail-closed assertions in WindowDockRoutingSocketTests are only
checking that the response is not ok, which doesn’t verify the intended
invalid_params failure path. Replace the remaining envelope["ok"] as? Bool ==
false-only checks with the existing expectInvalidParams helper so the tests also
assert error["code"] == "invalid_params". Use expectInvalidParams consistently
at the conflict-selector call sites in this test, matching the same pattern
already used elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3b8cf487-9df6-47eb-967d-618a264b42e9

📥 Commits

Reviewing files that changed from the base of the PR and between 75492b2 and 0c1c671.

📒 Files selected for processing (1)
  • cmuxTests/WindowDockRoutingSocketTests.swift

@austinywang
austinywang merged commit 0602ad4 into main Jul 2, 2026
40 of 42 checks passed
@austinywang
austinywang deleted the issue-7142-per-window-dock branch July 2, 2026 13:39
azooz2003-bit added a commit that referenced this pull request Jul 3, 2026
…dowRegistry (dissolved MainWindowContext); wire Dock teardown into context removal (round 41-fix)
azooz2003-bit added a commit that referenced this pull request Jul 3, 2026
… dropped from Workspace.swift during merge (#7144) (round 43-fix)
azooz2003-bit added a commit that referenced this pull request Jul 3, 2026
…kspace terminal-creation chain; wire workspace-Dock teardown (round 44-fix)
azooz2003-bit added a commit that referenced this pull request Jul 3, 2026
… SidebarBonsplitTabDropDelegate (#7144 sidebar tab-move), wire pbxproj (round 46-fix)

This branch was successfully deployed

1 active deployment
Preview – cmux — 0c1c6711 Deployed Jul 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Every cmux window should have its own independent Dock — second window shows "Global Dock is active in another window" instead of a usable Dock

1 participant