Drop a sidebar workspace onto a pane to merge its tabs
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, withmergeWorkspacestubbed tofalseand 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 commit2bfe8cdd0dediffers 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 hitutimensat: Operation not permittedwhile 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
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.