Skip to content

Move tabs into new workspaces - #3285

Merged
lawrencecchen merged 9 commits into
mainfrom
feat-detach-tab-to-workspace
Apr 30, 2026
Merged

lawrencecchen merged 9 commits into
mainfrom
feat-detach-tab-to-workspace

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Adds a shared move-tab-to-new-workspace path and wires it into drag/drop, context menus, command palette, socket actions, and CLI.

Follow-up fixes:

  • Bonsplit tab context menu now has a direct Move Tab to New Workspace action.
  • Bonsplit tab drops into the sidebar empty area now show the blue insertion bar.
  • The old Move Tab sheet no longer silently returns if the new-window destination cannot be resolved.

Verification:

  • ./scripts/reload.sh --tag detab2
  • swift test (vendor/bonsplit)
  • xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -configuration Debug -derivedDataPath /tmp/cmux-detab2-unit -only-testing:cmuxTests/AppDelegateMoveTabToNewWorkspaceTests/testMoveSurfaceToNewWorkspaceCreatesSinglePanelWorkspaceFromPanelTitle -only-testing:cmuxTests/AppDelegateMoveTabToNewWorkspaceTests/testMoveSurfaceToNewWorkspaceRejectsOnlyPanel test
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • git diff --check
  • plutil -lint vendor/bonsplit/Sources/Bonsplit/Resources/en.lproj/Localizable.strings vendor/bonsplit/Sources/Bonsplit/Resources/ja.lproj/Localizable.strings

Bonsplit submodule branch: https://github.com/manaflow-ai/bonsplit/tree/feat-detach-tab-to-workspace-menu

Summary by CodeRabbit

  • New Features
    • Added "Move Tab to New Workspace" functionality accessible via context menus in terminal and browser views.
    • Implemented drag-and-drop support for moving tabs to new workspaces from the sidebar.
    • Extended CLI with move-tab-to-new-workspace and detach-tab commands.
    • Added --focus flag support for tab action commands.
    • Expanded localization support (English, Japanese, Korean) for new workspace features.

@vercel

vercel Bot commented Apr 29, 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 Apr 30, 2026 3:04am
cmux-staging Building Building Preview, Comment Apr 30, 2026 3:04am

@coderabbitai

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A comprehensive feature addition implementing "move tab to new workspace" functionality across the application, including CLI command routing, new AppDelegate APIs for workspace/panel management, UI components for drag-drop and context menus, localization entries, TerminalController v2 API support, and accompanying tests.

Changes

Cohort / File(s) Summary
CLI Command Routing & Implementation
CLI/cmux.swift, CLI/CMUXCLI+MoveTabToNewWorkspace.swift
Routes new move-tab-to-new-workspace and detach-tab subcommands, adds --focus flag support to tab-action parsing, builds summary output with created-workspace details via applyTabActionFocusOption and appendCreatedWorkspaceSummaryParts, and exposes parseBoolString, formatHandle, and runTabAction as internal (visibility changed from private).
Localization Resources
Resources/Localizable.xcstrings
Introduces three new localization keys for "Move Tab to New Workspace" UI: browser.contextMenu.moveTabToNewWorkspace, command.moveTabToNewWorkspace.title, and terminalContextMenu.moveTabToNewWorkspace, each with English and Japanese translations; terminal menu item also includes Korean.
Core AppDelegate & Workspace APIs
Sources/AppDelegate+MoveTabToNewWorkspace.swift, Sources/Workspace.swift
Adds SurfaceNewWorkspaceMoveResult struct and @MainActor AppDelegate methods (canMoveSurfaceToNewWorkspace, moveSurfaceToNewWorkspace, moveBonsplitTabToNewWorkspace, canMoveBonsplitTabToNewWorkspace) to create workspaces and move panels. Refactors Workspace tab-move behavior from NSAlert-driven prompts to Bonsplit-integrated destination providers and moveBonsplitTab(...) execution with new adjacent-pane move helpers.
Terminal Controller v2 API
Sources/TerminalController.swift, Sources/TerminalController+MoveTabToNewWorkspace.swift
Extends v2TabAction to support move_to_new_workspace, detach_to_workspace, and detach_to_new_workspace actions. Exposes v2 helper types/functions (V2HandleKind, V2CallResult, v2FocusAllowed, v2OrNull, v2Ref, v2TabRef, v2String, v2Bool) from private to non-private visibility. Adds v2MoveTabToNewWorkspaceActionResult to handle move execution and payload serialization.
Terminal UI & Context Menu
Sources/GhosttyTerminalView.swift, Sources/GhosttyNSView+MoveTabToNewWorkspace.swift
Appends "Move current surface to new workspace" menu item in terminal context menu. New extension wires menu item availability (canMoveCurrentSurfaceToNewWorkspace) and execution (moveCurrentSurfaceToNewWorkspace), delegating to AppDelegate APIs with localized title and SF Symbols image.
Browser Context Menu & Web View
Sources/Panels/BrowserPanel.swift, Sources/Panels/BrowserPanel+MoveTabToNewWorkspace.swift, Sources/Panels/CmuxWebView.swift, Sources/Panels/CmuxWebView+MoveTabToNewWorkspace.swift
Adds optional closures to CmuxWebView (contextMenuCanMoveTabToNewWorkspace, contextMenuMoveTabToNewWorkspace) for menu item availability/execution. New BrowserPanel extension configures these closures to check and invoke AppDelegate move APIs. New CmuxWebView extension builds and appends the context menu item with deduplication and selector-based action routing.
Command Palette & Sidebar Drag-Drop
Sources/ContentView.swift, Sources/ContentView+MoveTabToNewWorkspace.swift
Adds panel.canMoveToNewWorkspace command-palette context key. New extension contributes palette.moveTabToNewWorkspace handler and implements SidebarBonsplitTabNewWorkspaceDropOverlay for drag-drop support with drop-indicator visibility logic. Exposes BonsplitTabDragPayload from private to module-visible.
Tests
cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift, cmuxTests/WorkspaceAdjacentPaneMoveTests.swift
New test suites validating AppDelegate.moveSurfaceToNewWorkspace move success/failure paths, panel removal, workspace creation, and title preservation, plus adjacent-pane tab movement via .moveToRightPane and .moveToLeftPane actions.
Build Configuration & Dependencies
GhosttyTabs.xcodeproj/project.pbxproj, vendor/bonsplit
Registers six new Swift extension files and two test files in Xcode build phases. Updates bonsplit submodule commit reference.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as UI Component<br/>(Menu/Drag-Drop)
    participant AppDelegate
    participant Workspace
    participant TabManager

    User->>UI: Initiate move<br/>(context menu or drag-drop)
    UI->>AppDelegate: Check canMoveSurfaceToNewWorkspace(panelId)
    AppDelegate-->>UI: true/false
    alt Can Move
        UI->>AppDelegate: moveSurfaceToNewWorkspace(panelId, focus, focusWindow)
        AppDelegate->>Workspace: Create new workspace<br/>(with title & placement)
        Workspace-->>AppDelegate: New workspace created
        AppDelegate->>TabManager: Move panel from<br/>source to destination
        TabManager-->>AppDelegate: Panel moved
        AppDelegate-->>UI: SurfaceNewWorkspaceMoveResult
        UI->>User: Display result<br/>(focus/window updates)
    else Cannot Move
        UI->>User: Play system beep
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #2349: Modifies GhosttyTerminalView.swift context menu implementation and adds/updates Localizable.xcstrings entries for terminal menu items.
  • PR #2160: Updates CLI command routing in CLI/cmux.swift and modifies TerminalController workspace creation paths and v2 API support.
  • PR #1503: Extends sidebar drag-drop and drop-indicator visibility logic, touching SidebarTabDropDelegate and overlay behavior.

Poem

🐰 A workspace moves with gentle hop,
Tabs and panels never stop,
New rooms bloom from drag-and-click,
Command palette does the trick,
Focus follows, windows shine—
Workspaces dance in perfect line! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is largely complete, providing a summary of changes and verification steps. However, it lacks structured sections matching the template (Summary, Testing, Demo Video) and does not include a demo video or the Review Trigger checklist. Reformat the description to include all required template sections: Summary (What changed/Why), Testing (How tested/What verified), Demo Video URL, Review Trigger block, and complete the checklist.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Move tabs into new workspaces' is concise and clearly describes the main feature being added across multiple integration points in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-detach-tab-to-workspace

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Introduces a shared moveSurfaceToNewWorkspace path in AppDelegate and wires it into all entry points: drag-and-drop sidebar delegate, terminal and browser context menus, command palette, the existing move-tab dialog, socket tab.action with a new move_to_new_workspace action, and the cmux move-tab-to-new-workspace CLI verb. The implementation correctly guards against moving the only panel in a workspace, cleans up orphaned bootstrap panels after a failed move, and includes two unit tests covering the happy path and the single-panel rejection.

Confidence Score: 4/5

Safe to merge; all findings are P2 style/defensive concerns with no runtime breakage on current call sites.

Only P2 findings: a ?? true fallback that is safe today but fragile for future callers, a confusing property/method name collision, and a minor localization inconsistency. Core move logic, guard conditions, cleanup, and tests look correct.

Sources/Panels/CmuxWebView.swift — ?? true default and property/method naming collision both live here.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds moveSurfaceToNewWorkspace / canMoveSurface* helpers with correct guard logic and orphan-workspace cleanup on move failure.
Sources/ContentView.swift Wires command-palette command, context key, and new SidebarBonsplitTabNewWorkspaceDropDelegate; logic looks correct.
Sources/Panels/CmuxWebView.swift Adds browser context-menu item for the move action; ?? true fallback on contextMenuCanMoveTabToNewWorkspace is a defensive bug risk, and the property/method name collision is confusing.
Sources/Panels/BrowserPanel.swift Hooks up both can-move and move closures to CmuxWebView; correctly sets both closures in tandem.
Sources/GhosttyTerminalView.swift Adds context-menu item and @objc action to terminal NSView; gating via canMoveCurrentSurfaceToNewWorkspace() is consistent.
Sources/TerminalController.swift Adds move_to_new_workspace / detach_to_workspace socket action with rich result payload; delegates to moveSurfaceToNewWorkspace.
Sources/Workspace.swift Replaces inline workspace-creation code in the move-tab dialog with a call to the new shared moveSurfaceToNewWorkspace; also gates the option on canMoveSurfaceToNewWorkspace.
CLI/cmux.swift Adds move-tab-to-new-workspace / detach-tab CLI verbs, --focus flag to tab-action, and updated help text; implementation delegates cleanly to runTabAction.
cmuxTests/WindowAndDragTests.swift Adds two well-structured unit tests for moveSurfaceToNewWorkspace: verifies panel count, title inheritance, and rejection of single-panel workspaces.
Resources/Localizable.xcstrings Adds three new localized keys with EN + JA; one key also has KO but the others do not, creating a minor translation inconsistency.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant Entry as Entry Point<br/>(drag/menu/palette/CLI/socket)
    participant AD as AppDelegate
    participant TM as TabManager
    participant WS as Workspace

    U->>Entry: Trigger "Move Tab to New Workspace"
    Entry->>AD: canMoveSurfaceToNewWorkspace(panelId)
    AD-->>Entry: true/false (panels.count > 1)
    Entry->>AD: moveSurfaceToNewWorkspace(panelId, focus, ...)
    AD->>AD: locateSurface / resolve title
    AD->>TM: addWorkspace(title, select: focus)
    TM-->>AD: destinationWorkspace (+ bootstrapPanelIds)
    AD->>AD: moveSurface(panelId -> destinationWorkspace)
    alt moveSurface fails
        AD->>TM: closeWorkspace(destinationWorkspace)
        AD-->>Entry: nil
    else moveSurface succeeds
        AD->>WS: closePanel(bootstrapPanelId) for each bootstrap panel
        AD-->>Entry: SurfaceNewWorkspaceMoveResult
    end
    Entry->>U: Update UI / return result
Loading

Comments Outside Diff (1)

  1. Resources/Localizable.xcstrings, line 190-212 (link)

    P2 Korean translation only in terminalContextMenu, absent from the other two new keys

    terminalContextMenu.moveTabToNewWorkspace includes a Korean (ko) localization, but neither browser.contextMenu.moveTabToNewWorkspace nor command.moveTabToNewWorkspace.title do. If Korean is intentionally supported it should be added consistently; if it was accidentally included here it should be removed so all three keys stay in sync.

Reviews (1): Last reviewed commit: "Move tabs into new workspaces" | Re-trigger Greptile

Comment thread Sources/Panels/CmuxWebView.swift Outdated
Comment on lines +2140 to +2142
}

if contextMenuMoveTabToNewWorkspace != nil,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 ?? true fallback enables move item when no guard is set

contextMenuCanMoveTabToNewWorkspace?() ?? true means that if contextMenuMoveTabToNewWorkspace is set but contextMenuCanMoveTabToNewWorkspace is left as nil, the "Move Tab to New Workspace" menu item is unconditionally shown — even for a single-panel workspace where the move is invalid. BrowserPanel.swift always sets both closures together, so it doesn't trigger today, but any future callsite that wires only the action closure would silently skip the guard check.

Flipping the default to false is safer:

Suggested change
}
if contextMenuMoveTabToNewWorkspace != nil,
contextMenuCanMoveTabToNewWorkspace?() ?? false,

Comment thread Sources/Panels/CmuxWebView.swift Outdated
Comment on lines +2172 to +2179
}
}

@objc private func contextMenuMoveTabToNewWorkspace(_ sender: Any?) {
_ = sender
guard contextMenuMoveTabToNewWorkspace?() == true else {
NSSound.beep()
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Method and property share the same base name

The @objc private func contextMenuMoveTabToNewWorkspace(_ sender: Any?) method has the same base name as the instance property var contextMenuMoveTabToNewWorkspace: (() -> Bool)? declared a few hundred lines above. Swift disambiguates them by signature, but the collision is confusing to read: inside the method body, contextMenuMoveTabToNewWorkspace?() reads as if it is a recursive call, when it is actually an optional-chained property invocation. Consider renaming the handler to something like contextMenuPerformMoveTabToNewWorkspace(_:) to make the two clearly distinct.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@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/ContentView.swift (1)

6743-6748: ⚠️ Potential issue | 🟡 Minor

Use AppDelegate.canMoveSurfaceToNewWorkspace() to gate the palette command.

panelCanMoveToNewWorkspace duplicates only the final check (workspace.panels.count > 1) while skipping the upstream guards in AppDelegate.canMoveSurfaceToNewWorkspace() (surface locatability, workspace validity, panel existence). To prevent command visibility and execution from drifting, delegate to the app-level helper instead of inline duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 6743 - 6748, The current snapshot
boolean for CommandPaletteContextKeys.panelCanMoveToNewWorkspace is computed
inline as workspace.panels.count > 1 which duplicates only the final check and
skips upstream guards; replace that expression with a call to the app-level
helper AppDelegate.canMoveSurfaceToNewWorkspace(...) passing the panel
identifier (panelId) and the relevant workspace/surface context so the same
locatability, workspace validity, and panel-existence checks are applied
consistently (i.e., stop using workspace.panels.count > 1 and call
AppDelegate.canMoveSurfaceToNewWorkspace with panelId and the
workspace/surface).
🧹 Nitpick comments (1)
Sources/Panels/CmuxWebView.swift (1)

2175-2181: Re-check capability gate at action time before executing move.

Line 2177 calls the move closure directly. If eligibility changes after menu construction (or a stale menu item is invoked), this can run execution without re-validating contextMenuCanMoveTabToNewWorkspace.

Suggested hardening
 `@objc` private func contextMenuMoveTabToNewWorkspace(_ sender: Any?) {
     _ = sender
+    guard contextMenuCanMoveTabToNewWorkspace?() ?? true else {
+        NSSound.beep()
+        return
+    }
     guard contextMenuMoveTabToNewWorkspace?() == true else {
         NSSound.beep()
         return
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/CmuxWebView.swift` around lines 2175 - 2181, Before invoking
the move closure, re-check the capability gate to avoid executing a stale
action: call contextMenuCanMoveTabToNewWorkspace?() inside
contextMenuMoveTabToNewWorkspace(_:) and only call
contextMenuMoveTabToNewWorkspace?() if that returns true; otherwise beep/return
as currently done. Reference the closures contextMenuCanMoveTabToNewWorkspace
and contextMenuMoveTabToNewWorkspace when updating the guard logic so the
capability is validated at action time rather than relying on the menu's
previous state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Sources/ContentView.swift`:
- Around line 6743-6748: The current snapshot boolean for
CommandPaletteContextKeys.panelCanMoveToNewWorkspace is computed inline as
workspace.panels.count > 1 which duplicates only the final check and skips
upstream guards; replace that expression with a call to the app-level helper
AppDelegate.canMoveSurfaceToNewWorkspace(...) passing the panel identifier
(panelId) and the relevant workspace/surface context so the same locatability,
workspace validity, and panel-existence checks are applied consistently (i.e.,
stop using workspace.panels.count > 1 and call
AppDelegate.canMoveSurfaceToNewWorkspace with panelId and the
workspace/surface).

---

Nitpick comments:
In `@Sources/Panels/CmuxWebView.swift`:
- Around line 2175-2181: Before invoking the move closure, re-check the
capability gate to avoid executing a stale action: call
contextMenuCanMoveTabToNewWorkspace?() inside
contextMenuMoveTabToNewWorkspace(_:) and only call
contextMenuMoveTabToNewWorkspace?() if that returns true; otherwise beep/return
as currently done. Reference the closures contextMenuCanMoveTabToNewWorkspace
and contextMenuMoveTabToNewWorkspace when updating the guard logic so the
capability is validated at action time rather than relying on the menu's
previous state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c712f807-dde5-4c6f-9bc6-68936ae27647

📥 Commits

Reviewing files that changed from the base of the PR and between af36cab and 248df2c.

📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/WindowAndDragTests.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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
Sources/ContentView.swift (1)

12210-12210: Route both UTTypes through a single delegate to avoid shadowing.

The chained .onDrop modifiers at lines 12200 and 12210 handle SidebarTabDragPayload and BonsplitTabDragPayload separately. In SwiftUI on macOS, the second modifier overrides the first, leaving only BonsplitTabDragPayload drops active. Combine both UTTypes into a single .onDrop call with a unified delegate, or restructure to separate this into two independent views if the semantics require it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` at line 12210, The two chained .onDrop modifiers
shadow each other so BonsplitTabDragPayload wins and SidebarTabDragPayload drops
are ignored; fix by combining both UTType arrays into a single .onDrop call and
use one delegate (or a new unified delegate) that handles both payload types.
Update the view that currently calls .onDrop(of:
SidebarTabDragPayload.dropContentTypes, delegate:
SidebarTabNewWorkspaceDropDelegate(...)) and .onDrop(of:
BonsplitTabDragPayload.dropContentTypes, delegate:
SidebarBonsplitTabNewWorkspaceDropDelegate(tabManager:selectedTabIds:lastSidebarSelectionIndex:))
to instead call one .onDrop(of: combinedTypes, delegate:
UnifiedSidebarDropDelegate(tabManager:selectedTabIds:lastSidebarSelectionIndex:))
where UnifiedSidebarDropDelegate inspects the incoming UTType and routes to the
existing handling logic for SidebarTabDragPayload and BonsplitTabDragPayload.
Sources/TerminalController.swift (1)

3324-3327: v2TabRef works, but string replacement is a bit brittle.

Consider emitting tab refs directly from a canonical helper (if available) to avoid coupling to "surface:" text shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TerminalController.swift` around lines 3324 - 3327, The v2TabRef
function currently builds a tab reference by replacing "surface:" in the string
returned from v2EnsureHandleRef, which is brittle; change it to produce tab refs
directly by calling a canonical helper instead of string replacement — either
call v2EnsureHandleRef(kind: .tab, uuid: uuid) if that variant exists or add a
small helper (e.g., v2HandleRef(kind: .tab, uuid:)) that returns the correct
"tab:" handle and use that in v2TabRef (keep the same nil-check behavior and
return NSNull() when uuid is nil).
Sources/AppDelegate+MoveTabToNewWorkspace.swift (1)

14-20: Extract shared source-resolution logic to avoid guard drift.

canMoveSurfaceToNewWorkspace(...) and moveSurfaceToNewWorkspace(...) repeat the same locate/workspace/panel validation pattern. A shared helper would keep eligibility checks and execution preconditions aligned.

Also applies to: 57-62

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate`+MoveTabToNewWorkspace.swift around lines 14 - 20,
Extract the repeated locate/workspace/panel validation into a single helper
(e.g., resolveSourceWorkspace(forPanelId:)) that returns an optional tuple
containing the located surface, its sourceWorkspace (the tab matching
surface.workspaceId), and the panel (or nil if not found); then replace the
guard blocks in canMoveSurfaceToNewWorkspace(panelId:) and
moveSurfaceToNewWorkspace(panelId:) to call this helper and unwrap the tuple,
using its values for the subsequent logic (keep the original return
false/early-exit behavior when the helper returns nil and preserve the final
check that the sourceWorkspace.panels.count > 1 in
canMoveSurfaceToNewWorkspace).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift`:
- Around line 29-39: The tests currently call window.orderOut(nil) but never
unregister the registered main-window context, causing leaked mainWindowContexts
across tests; after calling window.orderOut(nil) (or in the existing defer),
call the DEBUG helper unregisterMainWindowContextForTesting(windowId:) with the
same windowId used in registerMainWindow(...) to explicitly remove the context
and clear any active pointers; ensure both tests (the one around line 29 and the
one around lines 73-83) add this teardown so the
TabManager/windowId/registerMainWindow state is fully cleaned up between tests.

In `@Sources/AppDelegate`+MoveTabToNewWorkspace.swift:
- Around line 49-55: The socket handler currently passes the same boolean for
both in-app focus and window activation, causing socket/CLI requests to raise
the app; in the TerminalController+MoveTabToNewWorkspace call to
app.moveSurfaceToNewWorkspace (the invocation that uses v2FocusAllowed(...) and
v2Bool(params, "focus")), stop coupling these by leaving the in-app focus
argument as-is (focus) but always pass focusWindow: false for socket routes so
the window is not activated by default; update the call site that currently uses
focusWindow: focus to use focusWindow: false.

In `@Sources/TerminalController`+MoveTabToNewWorkspace.swift:
- Around line 22-29: The code currently defaults the computed focus to true
which can steal focus; change the default to false by updating the focus
assignment so v2FocusAllowed receives (v2Bool(params, "focus") ?? false) instead
of ... ?? true, and keep passing that focus into app.moveSurfaceToNewWorkspace
(both focus and focusWindow) so CLI/socket commands won't steal focus unless
explicitly requested.

---

Nitpick comments:
In `@Sources/AppDelegate`+MoveTabToNewWorkspace.swift:
- Around line 14-20: Extract the repeated locate/workspace/panel validation into
a single helper (e.g., resolveSourceWorkspace(forPanelId:)) that returns an
optional tuple containing the located surface, its sourceWorkspace (the tab
matching surface.workspaceId), and the panel (or nil if not found); then replace
the guard blocks in canMoveSurfaceToNewWorkspace(panelId:) and
moveSurfaceToNewWorkspace(panelId:) to call this helper and unwrap the tuple,
using its values for the subsequent logic (keep the original return
false/early-exit behavior when the helper returns nil and preserve the final
check that the sourceWorkspace.panels.count > 1 in
canMoveSurfaceToNewWorkspace).

In `@Sources/ContentView.swift`:
- Line 12210: The two chained .onDrop modifiers shadow each other so
BonsplitTabDragPayload wins and SidebarTabDragPayload drops are ignored; fix by
combining both UTType arrays into a single .onDrop call and use one delegate (or
a new unified delegate) that handles both payload types. Update the view that
currently calls .onDrop(of: SidebarTabDragPayload.dropContentTypes, delegate:
SidebarTabNewWorkspaceDropDelegate(...)) and .onDrop(of:
BonsplitTabDragPayload.dropContentTypes, delegate:
SidebarBonsplitTabNewWorkspaceDropDelegate(tabManager:selectedTabIds:lastSidebarSelectionIndex:))
to instead call one .onDrop(of: combinedTypes, delegate:
UnifiedSidebarDropDelegate(tabManager:selectedTabIds:lastSidebarSelectionIndex:))
where UnifiedSidebarDropDelegate inspects the incoming UTType and routes to the
existing handling logic for SidebarTabDragPayload and BonsplitTabDragPayload.

In `@Sources/TerminalController.swift`:
- Around line 3324-3327: The v2TabRef function currently builds a tab reference
by replacing "surface:" in the string returned from v2EnsureHandleRef, which is
brittle; change it to produce tab refs directly by calling a canonical helper
instead of string replacement — either call v2EnsureHandleRef(kind: .tab, uuid:
uuid) if that variant exists or add a small helper (e.g., v2HandleRef(kind:
.tab, uuid:)) that returns the correct "tab:" handle and use that in v2TabRef
(keep the same nil-check behavior and return NSNull() when uuid is nil).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 822680a4-ed2d-42d6-91d1-7e81817195f4

📥 Commits

Reviewing files that changed from the base of the PR and between 248df2c and a4af727.

📒 Files selected for processing (17)
  • CLI/CMUXCLI+MoveTabToNewWorkspace.swift
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+MoveTabToNewWorkspace.swift
  • Sources/ContentView+MoveTabToNewWorkspace.swift
  • Sources/ContentView.swift
  • Sources/GhosttyNSView+MoveTabToNewWorkspace.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanel+MoveTabToNewWorkspace.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/CmuxWebView+MoveTabToNewWorkspace.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/TerminalController+MoveTabToNewWorkspace.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift
✅ Files skipped from review due to trivial changes (3)
  • Resources/Localizable.xcstrings
  • Sources/Panels/CmuxWebView.swift
  • Sources/GhosttyTerminalView.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Panels/BrowserPanel.swift

Comment thread cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift Outdated
Comment thread Sources/AppDelegate+MoveTabToNewWorkspace.swift
Comment thread Sources/TerminalController+MoveTabToNewWorkspace.swift Outdated
@lawrencecchen
lawrencecchen force-pushed the feat-detach-tab-to-workspace branch from a4af727 to 0c35782 Compare April 29, 2026 07:08

@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

♻️ Duplicate comments (3)
Sources/AppDelegate+MoveTabToNewWorkspace.swift (1)

33-35: ⚠️ Potential issue | 🟠 Major

Default focus parameters are unsafe for non-focus callers.

Both move helpers default focus and focusWindow to true. Any socket/CLI caller that omits args can unintentionally change in-app focus and activate windows. Prefer safe defaults (false) and require explicit opt-in where UI focus is intended.

Proposed change
 func moveBonsplitTabToNewWorkspace(
     tabId: UUID,
     destinationManager: TabManager? = nil,
     title: String? = nil,
-    focus: Bool = true,
-    focusWindow: Bool = true,
+    focus: Bool = false,
+    focusWindow: Bool = false,
     placementOverride: NewWorkspacePlacement? = nil
 ) -> SurfaceNewWorkspaceMoveResult? {

 func moveSurfaceToNewWorkspace(
     panelId: UUID,
     destinationManager: TabManager? = nil,
     title: String? = nil,
-    focus: Bool = true,
-    focusWindow: Bool = true,
+    focus: Bool = false,
+    focusWindow: Bool = false,
     placementOverride: NewWorkspacePlacement? = nil
 ) -> SurfaceNewWorkspaceMoveResult? {

Based on learnings: “Socket/CLI commands must not steal macOS app focus … all non-focus commands should preserve current user focus context.”

Also applies to: 53-55

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate`+MoveTabToNewWorkspace.swift around lines 33 - 35, The
default parameters for the move helpers currently default focus and focusWindow
to true, which can unintentionally steal macOS focus for non-UI callers; update
both occurrences of the helper signature in
AppDelegate+MoveTabToNewWorkspace.swift to set focus: false and focusWindow:
false (keep placementOverride: NewWorkspacePlacement? = nil) and ensure callers
that expect UI focus explicitly pass focus: true and/or focusWindow: true to opt
in.
Sources/TerminalController+MoveTabToNewWorkspace.swift (1)

22-29: ⚠️ Potential issue | 🟠 Major

Default focus to false for this socket/CLI action.

Line 22 currently defaults missing focus to true, so tab-action can steal in-app focus without explicit focus intent.

Proposed fix
-        let focus = v2FocusAllowed(requested: v2Bool(params, "focus") ?? true)
+        let focus = v2FocusAllowed(requested: v2Bool(params, "focus") ?? false)

Based on learnings: Socket/CLI commands must not steal macOS app focus unless focus is explicitly requested.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TerminalController`+MoveTabToNewWorkspace.swift around lines 22 - 29,
The code defaults missing focus to true via v2Bool(params, "focus") ?? true and
then passes that through v2FocusAllowed into the focus and focusWindow args of
app.moveSurfaceToNewWorkspace; change the default to false so socket/CLI actions
don't steal app focus by using v2Bool(params, "focus") ?? false (or otherwise
ensure v2FocusAllowed receives false when the param is absent) and keep passing
the resulting focus variable into moveSurfaceToNewWorkspace (referencing v2Bool,
v2FocusAllowed, params, focus, and moveSurfaceToNewWorkspace).
cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift (1)

29-29: ⚠️ Potential issue | 🟠 Major

Unregister main-window context during test teardown.

Line 29 and Line 73 only hide the window; they do not remove the registered main-window context, which can leak AppDelegate state between tests.

Proposed fix
-        defer { window.orderOut(nil) }
+        defer {
+            app.unregisterMainWindowContextForTesting(windowId: windowId)
+            window.orderOut(nil)
+        }
...
-        defer { window.orderOut(nil) }
+        defer {
+            app.unregisterMainWindowContextForTesting(windowId: windowId)
+            window.orderOut(nil)
+        }

Based on learnings: The DEBUG-only helper unregisterMainWindowContextForTesting(windowId:) should mirror production teardown and repoint/clear active pointers to prevent stale test state.

Also applies to: 73-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift` at line 29, The test
only calls window.orderOut(nil) which hides the window but leaves the
AppDelegate main-window context registered; update the teardown to call the
DEBUG helper unregisterMainWindowContextForTesting(windowId:) with the same
window identifier used to register it (mirror production teardown) wherever you
currently hide the window (references: window.orderOut(nil) lines and the DEBUG
helper unregisterMainWindowContextForTesting(windowId:)); ensure this unregister
call is placed in the defer/teardown for both occurrences so the AppDelegate’s
active pointers are repointed/cleared and no stale state leaks between tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLI/cmux.swift`:
- Line 4636: For the "tab.action move-to-new-workspace" path, ensure a non-focus
default is sent when the CLI flag is omitted: after calling
applyTabActionFocusOption(focusOpt, to: &params) (or in that handler), if
focusOpt is nil explicitly set params["focus"] = false so the server doesn't
default to true and steal app focus; reference applyTabActionFocusOption, the
params dictionary, and the tab.action move-to-new-workspace handler when making
this change.

---

Duplicate comments:
In `@cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift`:
- Line 29: The test only calls window.orderOut(nil) which hides the window but
leaves the AppDelegate main-window context registered; update the teardown to
call the DEBUG helper unregisterMainWindowContextForTesting(windowId:) with the
same window identifier used to register it (mirror production teardown) wherever
you currently hide the window (references: window.orderOut(nil) lines and the
DEBUG helper unregisterMainWindowContextForTesting(windowId:)); ensure this
unregister call is placed in the defer/teardown for both occurrences so the
AppDelegate’s active pointers are repointed/cleared and no stale state leaks
between tests.

In `@Sources/AppDelegate`+MoveTabToNewWorkspace.swift:
- Around line 33-35: The default parameters for the move helpers currently
default focus and focusWindow to true, which can unintentionally steal macOS
focus for non-UI callers; update both occurrences of the helper signature in
AppDelegate+MoveTabToNewWorkspace.swift to set focus: false and focusWindow:
false (keep placementOverride: NewWorkspacePlacement? = nil) and ensure callers
that expect UI focus explicitly pass focus: true and/or focusWindow: true to opt
in.

In `@Sources/TerminalController`+MoveTabToNewWorkspace.swift:
- Around line 22-29: The code defaults missing focus to true via v2Bool(params,
"focus") ?? true and then passes that through v2FocusAllowed into the focus and
focusWindow args of app.moveSurfaceToNewWorkspace; change the default to false
so socket/CLI actions don't steal app focus by using v2Bool(params, "focus") ??
false (or otherwise ensure v2FocusAllowed receives false when the param is
absent) and keep passing the resulting focus variable into
moveSurfaceToNewWorkspace (referencing v2Bool, v2FocusAllowed, params, focus,
and moveSurfaceToNewWorkspace).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3b45827-0170-493b-beed-62dc3174f8c9

📥 Commits

Reviewing files that changed from the base of the PR and between a4af727 and 0c35782.

📒 Files selected for processing (18)
  • CLI/CMUXCLI+MoveTabToNewWorkspace.swift
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate+MoveTabToNewWorkspace.swift
  • Sources/ContentView+MoveTabToNewWorkspace.swift
  • Sources/ContentView.swift
  • Sources/GhosttyNSView+MoveTabToNewWorkspace.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanel+MoveTabToNewWorkspace.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/CmuxWebView+MoveTabToNewWorkspace.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/TerminalController+MoveTabToNewWorkspace.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift
  • vendor/bonsplit
✅ Files skipped from review due to trivial changes (5)
  • vendor/bonsplit
  • Sources/Panels/BrowserPanel.swift
  • Resources/Localizable.xcstrings
  • Sources/Panels/CmuxWebView+MoveTabToNewWorkspace.swift
  • Sources/GhosttyTerminalView.swift
🚧 Files skipped from review as they are similar to previous changes (7)
  • Sources/Panels/BrowserPanel+MoveTabToNewWorkspace.swift
  • CLI/CMUXCLI+MoveTabToNewWorkspace.swift
  • Sources/ContentView+MoveTabToNewWorkspace.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/Workspace.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/TerminalController.swift

Comment thread CLI/cmux.swift
params["url"] = urlOpt.trimmingCharacters(in: .whitespacesAndNewlines)
}

try applyTabActionFocusOption(focusOpt, to: &params)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Set a non-focus default when --focus is omitted for move-to-new-workspace.

Line 4636 only applies focus when provided. For tab.action move-to-new-workspace, this means no focus key is sent, and the server path defaults to true, which can steal in-app focus unexpectedly.

Suggested fix
-        try applyTabActionFocusOption(focusOpt, to: &params)
+        if action == "move-to-new-workspace", focusOpt == nil {
+            params["focus"] = false
+        } else {
+            try applyTabActionFocusOption(focusOpt, to: &params)
+        }

Based on learnings: "Socket/CLI commands must not steal macOS app focus ... only explicit focus-intent commands may mutate in-app focus/selection ... all non-focus commands should preserve current user focus context."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try applyTabActionFocusOption(focusOpt, to: &params)
if action == "move-to-new-workspace", focusOpt == nil {
params["focus"] = false
} else {
try applyTabActionFocusOption(focusOpt, to: &params)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` at line 4636, For the "tab.action move-to-new-workspace"
path, ensure a non-focus default is sent when the CLI flag is omitted: after
calling applyTabActionFocusOption(focusOpt, to: &params) (or in that handler),
if focusOpt is nil explicitly set params["focus"] = false so the server doesn't
default to true and steal app focus; reference applyTabActionFocusOption, the
params dictionary, and the tab.action move-to-new-workspace handler when making
this change.

…rkspace

# Conflicts:
#	GhosttyTabs.xcodeproj/project.pbxproj
#	Sources/TerminalController.swift
#	vendor/bonsplit
…rkspace

# Conflicts:
#	GhosttyTabs.xcodeproj/project.pbxproj

@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 the current code and only fix it if needed.

Inline comments:
In `@Sources/Workspace.swift`:
- Around line 13645-13646: The .moveToNewWorkspace branch currently calls
AppDelegate.shared?.moveBonsplitTabToNewWorkspace(...) directly and thus
bypasses the shared helper that handles failures and alerts; change this branch
to call the common mover (moveBonsplitTab(...)) with the appropriate parameters
so the shared logic runs and showMoveTabFailureAlert() will be invoked on
rejected moves (ensure you pass tab.id.uuid, focus: true, focusWindow: false or
the equivalent arguments expected by moveBonsplitTab to preserve behavior).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4912754-9731-4fed-9f7e-38d21814093b

📥 Commits

Reviewing files that changed from the base of the PR and between 0c35782 and 24e7fa5.

📒 Files selected for processing (11)
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/ContentView+MoveTabToNewWorkspace.swift
  • Sources/ContentView.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • cmuxTests/WorkspaceUnitTests.swift
  • vendor/bonsplit
✅ Files skipped from review due to trivial changes (4)
  • vendor/bonsplit
  • Resources/Localizable.xcstrings
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/TerminalController.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • Sources/Panels/BrowserPanel.swift
  • Sources/GhosttyTerminalView.swift
  • CLI/cmux.swift

Comment thread Sources/Workspace.swift
Comment on lines +13645 to +13646
case .moveToNewWorkspace:
_ = AppDelegate.shared?.moveBonsplitTabToNewWorkspace(tabId: tab.id.uuid, focus: true, focusWindow: false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route .moveToNewWorkspace through the shared move helper.

This branch bypasses moveBonsplitTab(...), so failures here skip showMoveTabFailureAlert(). That makes rejected moves, including the only-tab case, silently no-op on this UI path.

Suggested fix
-        case .moveToNewWorkspace:
-            _ = AppDelegate.shared?.moveBonsplitTabToNewWorkspace(tabId: tab.id.uuid, focus: true, focusWindow: false)
+        case .moveToNewWorkspace:
+            _ = moveBonsplitTab(tab.id, toMoveDestination: Self.bonsplitMoveNewWorkspaceDestinationId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case .moveToNewWorkspace:
_ = AppDelegate.shared?.moveBonsplitTabToNewWorkspace(tabId: tab.id.uuid, focus: true, focusWindow: false)
case .moveToNewWorkspace:
_ = moveBonsplitTab(tab.id, toMoveDestination: Self.bonsplitMoveNewWorkspaceDestinationId)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Workspace.swift` around lines 13645 - 13646, The .moveToNewWorkspace
branch currently calls AppDelegate.shared?.moveBonsplitTabToNewWorkspace(...)
directly and thus bypasses the shared helper that handles failures and alerts;
change this branch to call the common mover (moveBonsplitTab(...)) with the
appropriate parameters so the shared logic runs and showMoveTabFailureAlert()
will be invoked on rejected moves (ensure you pass tab.id.uuid, focus: true,
focusWindow: false or the equivalent arguments expected by moveBonsplitTab to
preserve behavior).

@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 the current code and only fix it if needed.

Inline comments:
In `@CLI/CMUXCLI`+MoveTabToNewWorkspace.swift:
- Line 47: The usage synopsis string ("Usage: cmux move-tab-to-new-workspace
[--tab <id|ref>] [--surface <id|ref>] [--workspace <id|ref>] [--title <text>]
[--focus <true|false>]") is inconsistent with the flag docs which allow
"<id|ref|index>"; update the usage line to include "index" for the relevant
flags (--tab, --surface, --workspace) so it reads "<id|ref|index>" to match the
documented flag parsing in this file (CLI/CMUXCLI+MoveTabToNewWorkspace.swift)
and ensure any help/usage generator references the same token set.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6d80599-d587-4bf2-bf27-2dd77e685af0

📥 Commits

Reviewing files that changed from the base of the PR and between 24e7fa5 and 0b8b8b8.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (6)
  • CLI/CMUXCLI+MoveTabToNewWorkspace.swift
  • CLI/cmux.swift
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/TerminalController+MoveTabToNewWorkspace.swift
  • cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift
  • cmuxTests/WorkspaceAdjacentPaneMoveTests.swift
✅ Files skipped from review due to trivial changes (1)
  • GhosttyTabs.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmuxTests/AppDelegateMoveTabToNewWorkspaceTests.swift

Comment thread CLI/CMUXCLI+MoveTabToNewWorkspace.swift Outdated
@lawrencecchen
lawrencecchen merged commit b5adaac into main Apr 30, 2026
19 checks passed
@lawrencecchen
lawrencecchen deleted the feat-detach-tab-to-workspace branch April 30, 2026 03:12

This branch was successfully deployed

1 active deployment
Preview – cmux — fb5f3f41 Deployed Apr 30, 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.

1 participant