Skip to content

Add split pane for terminal/changes area#743

Merged
selfcontained merged 15 commits into
mainfrom
agt_f5f162f0530b/agent-f0530b
Jul 9, 2026
Merged

Add split pane for terminal/changes area#743
selfcontained merged 15 commits into
mainfrom
agt_f5f162f0530b/agent-f0530b

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Summary

  • Add split pane feature allowing terminal and changes to be viewed side-by-side via drag-and-drop
  • Resizable divider with unsplit button that tracks the resize bar in real-time via ResizeObserver
  • Per-agent state persisted in localStorage via Jotai atomFamily; disabled on mobile
  • 6 E2E tests covering persistence, unsplit, tab hiding, tab switching, mobile, and drag-to-split

Details

Entering split mode: Drag an inactive tab from the center tab bar into a left or right drop zone. Only inactive tabs are draggable (active tab drag is a no-op).

Exiting split mode: Click the unsplit button on the divider, or click any tab in the center tab bar while split.

Unsplit button: Positioned absolutely outside the ResizableHandle to avoid cursor conflicts with the resize library. Uses a ResizeObserver on the left panel for pixel-accurate tracking during drag. Vertically centered in the 32px pane header row.

Tab centering: Header uses CSS grid (grid-cols-[1fr_auto_1fr]) so tabs stay centered regardless of left/right icon counts.

Safety: Document-level dragend listener prevents stuck drop zone overlays from browser DnD quirks.

Files changed

  • agents-view.tsx — Main split pane integration, ResizeObserver tracking, drag state
  • center-pane-tab-bar.tsx — Draggable tabs, split-aware filtering, typographic minus
  • split-drop-zones.tsx — Left/right drop zone overlays (new)
  • use-split-pane.ts — Core hook: enter/exit/update/drop with duplicate-pane guard (new)
  • store.ts — CenterTab type, SplitPaneState, atomFamily with localStorage (new types)
  • resizable.tsx — shadcn wrapper for react-resizable-panels v4 (new)
  • split-pane.spec.ts — 6 E2E tests (new)
  • store.test.ts — Unit tests for split pane state atoms (new)

Test plan

  • Type check passes (pnpm run check)
  • Production build passes (pnpm run finalize:web)
  • 6/6 split pane E2E tests pass
  • Full E2E suite passes (132 passed, 1 flaky pre-existing)
  • 195/195 unit tests pass
  • Frontend UX persona review approved (R1 + R2)
  • Manual browser verification of cursor, centering, and live resize tracking

🤖 Generated with Claude Code

selfcontained and others added 15 commits July 9, 2026 00:22
- Install react-resizable-panels v4.12.1 dependency
- Create Resizable component wrapping Group, Panel, and Separator
- Exports ResizablePanelGroup, ResizablePanel, ResizableHandle for split-pane UI

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Adds per-agent split-pane state (mode, left/right CenterTab, sizes)
backed by localStorage, following the existing atomWithLocalStorage +
atomFamily + reconcile*Storage pattern used for media sidebar and diff
view state. Also adds the useSplitPane hook that later tasks (tab bar,
agents-view integration) will consume for drag-to-split behavior.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Replace INACTIVE_ATOM (localStorage-backed with empty string key) with
inactiveSplitPaneStateAtom (plain in-memory atom) to avoid unnecessary
persistence. Add guards to exitSplit and updateSizes for null agentId.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- CenterPaneTabBar: tabs are now draggable (application/x-dispatch-tab
  MIME) and tabs currently shown in split panes are hidden from the bar.
- New SplitDropZones overlay component providing left/right drop targets.
- agents-view.tsx: pass placeholder isSplit/splitState/isMobile props to
  CenterPaneTabBar so the repo-wide typecheck (enforced by the pre-commit
  hook) stays green; Task 4 will replace these with real useSplitPane
  wiring and mount SplitDropZones.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Wires useSplitPane into AgentsView: renders ResizablePanelGroup with
two panes and per-pane headers when split, tracks tab-drag state to
show SplitDropZones, moves ChangesSettingsPopover into the pane header
in split mode, and exits split mode when a tab is clicked in the
center tab bar. Also reconciles split-pane localStorage against live
agent ids alongside the existing media/diff-view reconciliation.
onLayoutChange fires on every pointer-move during resize, causing a
synchronous localStorage write per mousemove. Switch to onLayoutChanged,
which only fires when the drag gesture ends, per the library's docs.

Also extract the duplicated TerminalPane/ChangesTab JSX (previously
repeated across the left split pane, right split pane, and single-pane
mode) into terminalElement/changesElement locals computed once per
render.
Cover split state persistence across reload, unsplit button exit,
tab bar hiding during split, post-unsplit tab switching, and
drag-and-drop entry via synthesized DragEvents.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Move unsplit button above resize handle (no cursor conflict)
- Use Split icon instead of Columns2
- Bigger button with proper padding
- Add left padding to pane headers to clear unsplit button
- Disable drag on active tab (no-op drop)
- Center tab bar with grid layout (immune to icon changes)
- Remove committed plan and spec files

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Move unsplit button outside ResizableHandle to fix cursor showing
resize icon instead of pointer. Position button absolutely using
ResizeObserver on the left panel for real-time tracking during
resize drag. Button is vertically centered in the pane header row.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Add document-level dragend listener to reset isDraggingTab state,
preventing drop zones from staying visible if a drag operation
ends without a clean dragleave event (browser quirk edge case).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Resolve conflicts in agents-view.tsx (add useNavigate + Split imports,
keep split pane code with new onReviewSubmitted prop) and
use-media-sidebar-state.ts (add both reconcile functions).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@selfcontained
selfcontained merged commit d94223b into main Jul 9, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_f5f162f0530b/agent-f0530b branch July 9, 2026 20:33
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