Drop a sidebar workspace onto a pane to merge its tabs

Merged
TE
7d ago
+410-6

Summary

Dragging a workspace row from the sidebar onto a pane now moves that workspace's tabs into the pane and closes the emptied workspace. This is the classic-app version of cmux-next's #17542.

  • Pane center: every tab lands at the drop index, in sidebar order.
  • Split edge: the pane splits with the first tab, and the rest follow into the new pane.
  • Multi-tab workspaces merge all their tabs. Each tab goes through the existing cross-workspace moveSurface, so the source closes the way it does after its last tab is dragged out. If it was the window's only workspace, the window closes.
  • Refusals are quiet, with no drop zone: dropping a workspace on itself, a remote tmux mirror on either side, or a tab the target's machine won't accept (acceptsSurface).

The row registers the same opaque TabDragTransferRegistry lease that synthetic tab sources use. Panes therefore show their existing drop zones and preview, and resolve the lease as a new PaneTransferSourceResolver.Source.workspaceMerge. Sidebar destinations (reorder, move into a group, tab-to-workspace) keep reading the row's reorder payload: BonsplitTabDragPayload.transfer ignores a pasteboard that carries one.

Known limitation: over a pane's own tab bar, Bonsplit's insertion indicator can still show for a refused self-drop, and the drop springs back.

Overlap: #15844 changes how remote-backed rows are written to the same pasteboard writer. It is complementary; whichever lands second rebases onto the writer's new merge: parameter.

Testing

cmuxTests/WorkspaceMergeDropTests covers:

  • a single-tab merge, where the workspace closes;
  • a multi-tab merge at index 0, where the source's tab order is kept;
  • an edge split, where every tab ends up in the new pane;
  • the self-merge refusal;
  • the resolver case;
  • the sidebar still reading the reorder payload.

Results, via ./scripts/run-e2e.sh cmuxTests/WorkspaceMergeDropTests:

  • Red: run 37917442253 ran all 6 tests on 15cfe58a83e, with mergeWorkspace stubbed to false and no sidebar guard. The four behavior tests failed (single-tab, multi-tab, edge split, sidebar reorder); the self-merge refusal and resolver tests passed, as they should. The final red commit 2bfe8cdd0de differs only in the multi-tab test, which now reads the source's tab order from the tab bar instead of assuming creation order. Its own focused runs (37919474734, 37920914020) died before any test started: rsync hit utimensat: Operation not permitted while staging the restored product.
  • Green 79e16b31a17: run 37919474321, 6 of 6 tests passed.

python3 scripts/verify-local.py --affected origin/main passed 5/5.

The PR's app-host unit tests (changed suites) lane ran 1052 tests in 77 suites and failed one test both times: RemoteTmuxMirrorSplitRoutingTests.localWorkspaceSplitStillCreatesLocalPanel, where newTerminalSplit returns nil. This change doesn't reach the split path. A split is refused only when splits are disabled, the workspace is retired or a remote tmux mirror, or the window is too narrow. Run on this head after the window-related suites that preceded it, the test passes (run 37970957776). On main it passes after the six suites that preceded it in this lane (run 37968818561). It looks order-dependent (saved window state, as in #15953), but that isn't proven against main with this lane's full suite list. Separately, be81cb5 makes the merge-test fixture restore AppDelegate.shared in tearDown.

No new user-facing strings.

Changelog

Added: Drag a workspace from the sidebar onto a pane, or a pane edge, to move its tabs there

Proof

No capture; the red-then-green focused test is the proof for this drag path.

Checklist

  • Behavior changes have added or updated tests, or Testing says why not
  • Reviewed with a subagent before merge (cmux-review), and all bot and human review comments resolved

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


[!NOTE]
Medium Risk
Changes workspace/tab lifecycle and drag routing across sidebar and panes, but builds on existing moveSurface with explicit guards and focused tests.

Overview
Adds workspace merge via drag-and-drop: dropping a sidebar workspace row onto another workspace’s pane moves all of that workspace’s tabs into the target (in sidebar order) and closes the emptied source, reusing the existing cross-workspace moveSurface path.

Pane behavior: a center drop inserts every tab at the drop index; an edge drop splits the pane with the first tab and stacks the rest in the new pane. Refusals (self-merge, remote tmux mirrors, tabs the target won’t acceptsSurface) stay quiet with no effective drop.

Drag plumbing: workspace rows now export a second pane-merge capability (WorkspaceMergeDragRegistry + synthetic TabDragTransfer) alongside the existing sidebar reorder payload. Panes resolve PaneTransferSourceResolver.Source.workspaceMerge and call AppDelegate.mergeWorkspace; sidebar destinations still read reorder only because BonsplitTabDragPayload.transfer ignores pasteboards that carry sidebar reorder types. The Dock declines merge drags without surfacing ownership errors.

WorkspaceMergeDropTests covers single/multi-tab merges, edge splits, self-merge refusal, resolver wiring, and the reorder-vs-merge pasteboard split.

Reviewed by Cursor Bugbot for commit be81cb511009c5026f36dc8dd4a3413c7361a5a8. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Dropping a sidebar workspace row onto a pane now merges that workspace's tabs into the pane and closes the emptied workspace.

  • A center drop places every tab at the drop index in sidebar order; an edge drop splits the pane with the first tab and the rest follow.
  • Self-merges, remote tmux mirrors, and tabs a target machine won't accept are quiet refusals with no drop zone.
  • Panes resolve the drag through the existing opaque lease, showing their regular drop zones and preview, while sidebar destinations keep reading the row's reorder payload.

Known limitation: over a pane's own tab bar, the insertion indicator can still show for a refused self-drop, and the drag springs back.

WorkspaceMergeDropTests covers single- and multi-tab merges, edge splits, the self-merge refusal, the resolver case, and the sidebar's reorder payload. The test fixture also restores the host app delegate so later suites such as RemoteTmuxMirrorSplitRoutingTests don't run against a bare delegate.

Summary by CodeRabbit

  • New Features
    • Drag a workspace onto a compatible workspace to combine their tabs.
    • Drop in the center to add tabs in order, or at an edge to place them in a new pane.
    • When all tabs move successfully, the emptied source workspace closes.
    • Workspaces cannot be merged into themselves or into incompatible workspaces.
  • Bug Fixes
    • Workspace-row drags are correctly handled as workspace actions rather than pane-tab transfers.