Skip to content

feat(desktop): Add drag-and-drop panes to tab bar#856

Merged
Kitenite merged 3 commits intomainfrom
drag-pane
Jan 21, 2026
Merged

feat(desktop): Add drag-and-drop panes to tab bar#856
Kitenite merged 3 commits intomainfrom
drag-pane

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 20, 2026

Summary

  • Allow dragging panes to existing tabs in the tab bar to move them
  • Allow dragging panes to empty tab bar space to create a new tab
  • Visual feedback with accent highlighting during drag-over

Test plan

  • Split a terminal into multiple panes
  • Drag a pane by its toolbar to another tab - verify pane moves to that tab
  • Drag a pane to empty space in the tab bar - verify new tab is created
  • Verify visual feedback (highlight) appears when dragging over valid drop targets
  • Verify dropping a pane on its own tab does nothing

Summary by CodeRabbit

New Features

  • Drag-and-drop panes across the workspace: drag a pane and drop it onto tabs to reorganize layouts.
  • Drop onto a tab moves the pane into that tab; dropping on the strip can create a new tab for the pane.
  • Visual feedback highlights valid drop targets and the strip while dragging.
  • Drag state is cleaned up automatically when dragging ends to keep behavior consistent.

✏️ Tip: You can customize this high-level summary in your review settings.

Allow dragging panes from the mosaic layout to the tab bar:
- Drop on existing tab to move pane to that tab
- Drop on empty tab bar space to create new tab with pane

Uses MosaicWindow's onDragStart/onDragEnd callbacks to track the
currently dragged pane, since react-mosaic's drag item doesn't
include the pane ID.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 20, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds drag-and-drop for panes into tab groups: introduces a dragging-pane Zustand store, wires drag start/end on pane windows, and implements drop targets on group items and the strip to move panes between tabs or create new tabs.

Changes

Cohort / File(s) Summary
Dragging State Store
apps/desktop/src/renderer/stores/tabs/dragging-pane.ts
New Zustand store useDraggingPaneStore tracking draggingPaneId and draggingTabId with setDraggingPane.
Pane Window Drag Handlers
apps/desktop/src/renderer/screens/main/components/.../TabView/components/BasePaneWindow/BasePaneWindow.tsx
Adds onDragStart/onDragEnd handlers that call useDraggingPaneStore.setDraggingPane to set/clear drag context and focus.
Tab Group Item Drop Target
apps/desktop/src/renderer/screens/main/components/.../GroupStrip/GroupItem.tsx
Adds react-dnd useDrop accepting MosaicDragType.WINDOW; reads store state at drop, invokes new onPaneDrop?: (paneId: string) => void prop, and applies drag-over visual styling.
Tab Strip Drop Handling & Moves
apps/desktop/src/renderer/screens/main/components/.../GroupStrip/GroupStrip.tsx
Implements strip-level useDrop, resolves target pane/tab, calls store methods to move panes (movePaneToTab, movePaneToNewTab), passes onPaneDrop into GroupItem children, and clears dragging state on completion.
Tab Store API Additions
apps/desktop/src/renderer/stores/tabs/store
Adds movePaneToTab(paneId: string, targetTabId: string) and movePaneToNewTab(paneId: string) methods referenced by drop handlers.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant PaneWindow as Pane Window
    participant DragStore as Dragging Pane Store
    participant GroupStrip as GroupStrip
    participant GroupItem as GroupItem (Tab)
    participant TabStore as Tab Store

    User->>PaneWindow: drag start (pointer down + move)
    PaneWindow->>DragStore: setDraggingPane(paneId, tabId)
    DragStore-->>PaneWindow: dragging state set

    User->>GroupItem: drag over
    GroupItem->>DragStore: read draggingPaneId/draggingTabId (canDrop)
    GroupItem-->>User: show highlight (isOver/canDrop)

    User->>GroupItem: drop
    GroupItem->>DragStore: get fresh draggingPaneId & clear dragging state
    GroupItem->>GroupStrip: onPaneDrop(paneId) / notify strip
    GroupStrip->>TabStore: movePaneToTab(paneId, targetTabId) or movePaneToNewTab(paneId)
    TabStore-->>GroupStrip: pane relocated

    User->>PaneWindow: drag end
    PaneWindow->>DragStore: setDraggingPane(null, null)
    DragStore-->>All: dragging cleared
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Poem

🐰
I hopped along the code today,
Carried panes in merry play,
Tabs blinked bright, a gentle shove,
New homes found with rabbit love,
Hooray — the panes all rolled away!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature added: drag-and-drop pane functionality in the tab bar.
Description check ✅ Passed The description provides a clear summary of changes and a comprehensive test plan with checkboxes, though it doesn't follow the provided template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx`:
- Around line 37-58: The canDrop logic is too permissive: tighten canDrop and
the drop handler in the useDrop call so they inspect the dragged item (via
monitor.getItem()) and ensure the pane's source tab/workspace differ (or
otherwise meet your valid-target rules) before returning true or invoking
onPaneDrop; update both the canDrop function and the drop callback to read the
dragged item's metadata (e.g., paneId/tabId/workspaceId) and compare it against
this group's identifiers (use the component props/IDs that identify the current
tab/workspace) and only allow drop/trigger onPaneDrop when the checks pass.

In `@apps/desktop/src/renderer/stores/tabs/dragging-pane.ts`:
- Around line 3-13: Change setDraggingPane to accept a single params object
instead of positional arguments: update the DraggingPaneState type so
setDraggingPane: (args: { paneId: string | null; tabId: string | null }) =>
void, and update the useDraggingPaneStore initializer to implement
setDraggingPane: ({ paneId, tabId }) => set({ draggingPaneId: paneId,
draggingTabId: tabId }). Rename parameters in the implementation as shown and
then find and update all call sites that invoke
useDraggingPaneStore().setDraggingPane(paneId, tabId) to the new form
setDraggingPane({ paneId, tabId }); keep the property names paneId and tabId
exactly to match the type.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (1)

124-134: Use a params object for handlePaneDropToTab.

This new helper has 2 parameters; adopt a single params object to match project conventions and improve readability. As per coding guidelines, ...

♻️ Proposed refactor
-	const handlePaneDropToTab = (paneId: string, targetTabId: string) => {
+	const handlePaneDropToTab = ({
+		paneId,
+		targetTabId,
+	}: {
+		paneId: string;
+		targetTabId: string;
+	}) => {
 		const pane = panes[paneId];
 		if (!pane || pane.tabId === targetTabId) return;
@@
 		movePaneToTab(paneId, targetTabId);
 	};
-	onPaneDrop={(paneId) => handlePaneDropToTab(paneId, tab.id)}
+	onPaneDrop={(paneId) =>
+		handlePaneDropToTab({ paneId, targetTabId: tab.id })
+	}

Also applies to: 189-196

Comment on lines +3 to +13
interface DraggingPaneState {
draggingPaneId: string | null;
draggingTabId: string | null;
setDraggingPane: (paneId: string | null, tabId: string | null) => void;
}

export const useDraggingPaneStore = create<DraggingPaneState>((set) => ({
draggingPaneId: null,
draggingTabId: null,
setDraggingPane: (paneId, tabId) =>
set({ draggingPaneId: paneId, draggingTabId: tabId }),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use a params object for setDraggingPane to avoid positional arguments.

This API now has 2+ parameters and should follow the project convention for self-documentation and future extensibility. Update call sites accordingly. As per coding guidelines, ...

♻️ Proposed refactor
 interface DraggingPaneState {
 	draggingPaneId: string | null;
 	draggingTabId: string | null;
-	setDraggingPane: (paneId: string | null, tabId: string | null) => void;
+	setDraggingPane: (params: {
+		paneId: string | null;
+		tabId: string | null;
+	}) => void;
 }

 export const useDraggingPaneStore = create<DraggingPaneState>((set) => ({
 	draggingPaneId: null,
 	draggingTabId: null,
-	setDraggingPane: (paneId, tabId) =>
-		set({ draggingPaneId: paneId, draggingTabId: tabId }),
+	setDraggingPane: ({ paneId, tabId }) =>
+		set({ draggingPaneId: paneId, draggingTabId: tabId }),
 }));
-	setDraggingPane(paneId, tabId);
+	setDraggingPane({ paneId, tabId });
-	setDraggingPane(null, null);
+	setDraggingPane({ paneId: null, tabId: null });
🤖 Prompt for AI Agents
In `@apps/desktop/src/renderer/stores/tabs/dragging-pane.ts` around lines 3 - 13,
Change setDraggingPane to accept a single params object instead of positional
arguments: update the DraggingPaneState type so setDraggingPane: (args: {
paneId: string | null; tabId: string | null }) => void, and update the
useDraggingPaneStore initializer to implement setDraggingPane: ({ paneId, tabId
}) => set({ draggingPaneId: paneId, draggingTabId: tabId }). Rename parameters
in the implementation as shown and then find and update all call sites that
invoke useDraggingPaneStore().setDraggingPane(paneId, tabId) to the new form
setDraggingPane({ paneId, tabId }); keep the property names paneId and tabId
exactly to match the type.

@Kitenite
Copy link
Copy Markdown
Collaborator Author

Closing because this is buggy

@Kitenite Kitenite closed this Jan 20, 2026
@Kitenite Kitenite deleted the drag-pane branch January 20, 2026 22:02
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 20, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

@Kitenite Kitenite restored the drag-pane branch January 20, 2026 22:26
@Kitenite Kitenite reopened this Jan 20, 2026
@Kitenite Kitenite merged commit 1f70a04 into main Jan 21, 2026
6 checks passed
Kitenite added a commit that referenced this pull request Jan 21, 2026
Kitenite added a commit that referenced this pull request Jan 21, 2026
@Kitenite Kitenite deleted the drag-pane branch January 22, 2026 17:48
peterpipes776 pushed a commit to peterpipes776/superset that referenced this pull request Mar 23, 2026
Fetch greptile-apps[bot] inline review comments and extract the structured
"Prompt To Fix With AI" sections (wrapped in 5-backtick markdown blocks).
These contain file path, line number, issue description, and fix suggestion
ready to feed to the auto-fix bot. Falls back to full comment bodies and
Greptile PR body section for score/issues.

Tested against Alike-AI/arch-one PR superset-sh#856 — extracts all 4 prompts correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
peterpipes776 pushed a commit to peterpipes776/superset that referenced this pull request Mar 23, 2026
Greptile embeds the "Prompt To Fix All With AI" section in the PR description
(body), not in the review body (which the API returns as empty). Extract it
from pr.body using the <details><summary> + 5-backtick markdown pattern.
Falls back to individual inline comment prompts if not found in PR body.

Verified against Alike-AI/arch-one PR superset-sh#856 — extracts 2700 chars of fix prompts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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