Make Separate All Panes and Separate Current Pane keybindable commands - #231
Closed
thdxg wants to merge 1 commit into
Closed
Make Separate All Panes and Separate Current Pane keybindable commands#231thdxg wants to merge 1 commit into
thdxg wants to merge 1 commit into
Conversation
thdxg
force-pushed
the
claude/pr-228-pane-to-sidebar
branch
2 times, most recently
from
August 7, 2026 09:40
088d6e2 to
75f866e
Compare
thdxg
force-pushed
the
claude/pr-228-pane-to-sidebar
branch
3 times, most recently
from
August 7, 2026 10:10
eacf46e to
a4da8fb
Compare
thdxg
force-pushed
the
claude/pr-228-pane-to-sidebar
branch
2 times, most recently
from
August 7, 2026 15:47
ecdc830 to
55826e0
Compare
Separate Panes existed only in the tab context menu, and dragging a pane's grab handle to the sidebar was tried as the per-pane counterpart but is not implementable: the sidebar column's SwiftUI hierarchy never receives the grab handle's AppKit NSDraggingSession. Every public drop mechanism was tested against the live app — row-, ForEach-, and header-level dropDestination, row/header/List-level onDrop, and a target outside the List — and none ever got a callback, while the same delegate fires fine in the workspace column. So both operations become AppCommands instead: palette rows plus rebindable hotkeys, unbound by default like the other tree-rewriting actions. - Separate All Panes: the context-menu action, now invocable on the active tab from the palette or a binding. - Separate Current Pane: new — splits just the focused pane out of the active tab into its own tab inserted right after it, via AppState.separatePane: removePane's tree/zoom/focus repair minus the surface teardown, so the pane object (surface and running shell) survives the trip; crossing projects rebinds routing identity the way moveTab does. Covered by new AppStateTests. MovablePane stays as the shared drag-pasteboard decode for the workspace's LeafDropDelegate, which previously unpacked the UUID bytes inline.
thdxg
force-pushed
the
claude/pr-228-pane-to-sidebar
branch
from
August 7, 2026 15:54
55826e0 to
6e078b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #230 (which stacks on #228) — retarget as the PRs beneath merge.
Separate Panes existed only in the tab context menu. This PR makes it and a new per-pane sibling first-class commands — palette rows plus rebindable hotkeys (unbound by default, matching the other tree-rewriting actions like Apply/Save Layout):
Why commands and not a sidebar drag
Dragging a pane's grab handle onto the sidebar was tried first (twice) and is not implementable with public SwiftUI: the sidebar column's hierarchy never receives the grab handle's AppKit
NSDraggingSession. Tested against the live app with synthesized drags — row-, ForEach-, and header-leveldropDestination; row-, header-, and List-levelonDrop; and a target outside the List — none ever got a single delegate callback, while the identical delegate fires fine in the workspace column (the same drag session reshapes splits there). The finding is recorded onMovablePane's doc comment so nobody retries it blind.How it works
AppState.separatePanedoesremovePane's tree/zoom/focus repair minus the surface teardown, so thePaneobject (surface and running shell) survives the trip; crossing projects rebinds routing identity the waymoveTabdoes. The destination workspace resolves before detaching, so a failure can't leave the pane belonging to no tab.AppCommandcases (palette picks them up viaallCases),HotkeyActioncases, and routing through theAppCommand.action(in:)single source of truth inResponders.swift, so palette, menu, and binding can't drift. Enablement guards make them no-ops on single-pane tabs.MovablePanestays as the shared drag-pasteboard decode for the workspace'sLeafDropDelegate, which previously unpacked the UUID bytes inline.Testing
AppStateTests: pane lands in its own tab with source-tab zoom/focus repair, insertion at a slot, cross-project rebind + destination activation, only-pane / unknown-pane no-ops. Full suite passes;format/lintclean.🤖 Generated with Claude Code