Skip to content

feat(floating-terminal): consolidate tab model — drag-to-reorder + drag-to-split#6469

Draft
wolfiesch wants to merge 5 commits into
stablyai:mainfrom
wolfiesch:consolidate/floating-terminal
Draft

feat(floating-terminal): consolidate tab model — drag-to-reorder + drag-to-split#6469
wolfiesch wants to merge 5 commits into
stablyai:mainfrom
wolfiesch:consolidate/floating-terminal

Conversation

@wolfiesch

@wolfiesch wolfiesch commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the floating terminal's tab handling to parity with the main workspace by consolidating it onto the shared unified-tab model:

  • Drag-to-reorder tabs within the floating terminal tab strip.
  • Drag-to-split with live hover preview — drag a tab onto a pane edge to split the floating workspace into a side-by-side / stacked layout, using the same preview overlay as the main workspace.
  • Floating editor close requests route into the panel's own save-dialog queue; floating popup/close shortcuts route correctly.

The floating panel now renders the shared TabGroupSplitLayout (tab strip, panes, reorder/drag wiring) instead of owning a bespoke single-group surface. The earlier no-split invariant was removed from both the tab-move-to-pane-column UI guard and the dropUnifiedTab store boundary, and the floating worktree seeds layoutByWorktree.

Screenshots

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test (affected: 7 files, 173 tests green)
  • pnpm build
  • Added/updated tests: floating panel tests now assert delegation to TabGroupSplitLayout rather than TabBar internals; PTY-exit / hidden-visibility coverage moved to TerminalPaneOverlayLayer.test.tsx; dnd-wiring (enabled/disabled, exact sensor/handler passthrough) covered in TabGroupSplitLayout.test.ts

Targeted gates run locally: typecheck:web clean, oxlint clean on all changed files, full affected suite 173/173.

AI Review Report

Reviewed the drag-split consolidation for: (1) correctness of removing the no-split invariant at both the UI guard and store boundary — confirmed the floating worktree now seeds layoutByWorktree so the split layout has a tree to paint and tabs aren't orphaned; (2) test quality — confirmed panel tests assert delegation (component boundary) rather than reaching into TabBar internals, reducing brittleness; (3) the untracked src/preload/api-types.d.ts was identified as an accidental tsc emit of the type-checked api-types.ts (source-of-truth since #1197) and is not committed — the stale !src/preload/api-types.d.ts allow-list entry was removed from .gitignore to prevent recurrence.

Cross-platform: changes are renderer-only (React/zustand/dnd-kit) with no shell, path, or Electron-main behavior touched; no OS-specific code paths introduced. Drag activation uses pointer sensors already in use across platforms.

Security Audit

No new input handling, command execution, path handling, auth, secrets, or IPC surfaces. Changes are confined to renderer tab-model state and drag wiring. The .gitignore change keeps a generated .d.ts emit out of source control (no secret exposure). No dependency changes.

Notes

The floating terminal reused the shared TabBar/SortableTab but rendered
the strip bare, without the DndContext/sensors the main workspace gets
from TabGroupSplitLayout. dnd-kit's useSortable never activated, so tabs
could not be dragged to reorder.

Wrap the floating tab strip in TabDragProvider -> DndContext -> DragOverlay
driven by the existing useTabDragSplit hook (keyed to the floating
worktree). Reordering flows through the shared reorderUnifiedTabs/tabOrder
model with no new state. Broaden the titlebar no-drag selector to
[data-tab-id] so dragging browser/markdown tabs reorders instead of
starting a window move.

Also guard moveTabToNewPaneColumn for the floating worktree: the floating
panel renders a single group with no split layout, so a "Move Tab to
Split" would orphan the tab into a group that never renders (pre-existing
latent bug for terminal/browser/markdown tabs).
- Normalize the titlebar no-drag target so SVG icons and text nodes
  nested inside a [data-tab-id] tab root yield the pointer to dnd-kit
  instead of starting a floating-window move. Add SVG- and text-node
  regression tests asserting setPointerCapture is not called.
- Enforce the floating-worktree no-split invariant at the store
  boundary: dropUnifiedTab now rejects a split drop for the floating
  worktree before creating any group/layout, complementing the existing
  tab-move-to-pane-column UI guard. Add a direct store test.
- Strengthen FloatingTerminalPanel drag wiring tests via an inspectable
  hoisted useTabDragSplit mock: assert the hook is called with
  { worktreeId, enabled }, DndContext receives the exact sensors/handler
  references, the drag root uses setDragRootNode as ref, TabBar receives
  the exact hoveredTabInsertion, plus a closed-panel enabled:false case.
- Reword comments that claimed the disabled state registers no sensors;
  the hook uses an impossible activation distance to prevent drag
  activation while hidden.
Scale the shared tab drag-split system out to the floating terminal so a
dragged tab can be dropped onto a pane edge to split the floating
workspace, with the same live hover preview used in the main workspace.

- Render TabGroupSplitLayout in FloatingTerminalPanel and remove the
  earlier no-split invariant from both the tab-move-to-pane-column UI
  guard and the dropUnifiedTab store boundary; the floating worktree now
  seeds layoutByWorktree and routes its tab strip, panes, and drag wiring
  through the shared split layout.
- Rewrite the floating panel tests to assert delegation to
  TabGroupSplitLayout instead of reaching into TabBar internals, and move
  PTY-exit / hidden-visibility coverage into the new
  TerminalPaneOverlayLayer.test.tsx and TabGroupSplitLayout.test.ts
  (dnd-wiring enabled/disabled, exact sensor/handler passthrough).
- Drop the stale !src/preload/api-types.d.ts gitignore allow-list entry
  left over from stablyai#1197 (api-types.d.ts -> type-checked api-types.ts), so
  the accidental tsc emit stays ignored.
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.

2 participants