From 5f86585c34da736904a7f2a9610041636795dc1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Mon, 20 Apr 2026 20:40:39 +0800 Subject: [PATCH] Preserve tab reordering without losing title-bar drag fallback The recent macOS title-bar drag fix marked the entire session tab strip as a native drag region. That restored window movement in the overlay title area, but it also intercepted the tab component's own HTML drag events and regressed manual tab reordering. This narrows the native drag surface to a small gutter beside the tabs while keeping the explicit empty-space startDragging fallback, and adds a focused regression test for drag-to-reorder. Constraint: Must preserve window dragging from non-tab title-bar space on macOS overlay windows Rejected: Keep the whole tab bar as a drag region | conflicts with tab-level drag-and-drop reordering Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not reapply data-tauri-drag-region to the full tab strip unless tab reordering is redesigned around a different drag mechanism Tested: bun run test -- src/components/layout/TabBar.test.tsx; bun run lint; bun run build Not-tested: Manual desktop runtime verification after this commit --- desktop/src/components/layout/TabBar.test.tsx | 38 ++++++++++++++++++- desktop/src/components/layout/TabBar.tsx | 10 ++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/desktop/src/components/layout/TabBar.test.tsx b/desktop/src/components/layout/TabBar.test.tsx index 1ff869c5..1a288d19 100644 --- a/desktop/src/components/layout/TabBar.test.tsx +++ b/desktop/src/components/layout/TabBar.test.tsx @@ -149,7 +149,8 @@ describe('TabBar', () => { render() }) - expect(screen.getByTestId('tab-bar')).toHaveAttribute('data-tauri-drag-region') + expect(screen.getByTestId('tab-bar')).not.toHaveAttribute('data-tauri-drag-region') + expect(screen.getByTestId('tab-bar-drag-gutter')).toHaveAttribute('data-tauri-drag-region') }) it('starts dragging when clicking the empty tab-bar gutter', async () => { @@ -214,4 +215,39 @@ describe('TabBar', () => { expect(startDraggingMock).not.toHaveBeenCalled() }) + + it('reorders tabs via drag and drop', async () => { + const { TabBar } = await import('./TabBar') + const { useTabStore } = await import('../../stores/tabStore') + const { useChatStore } = await import('../../stores/chatStore') + + useTabStore.setState({ + tabs: [ + { sessionId: 'tab-1', title: 'First Session', type: 'session', status: 'idle' }, + { sessionId: 'tab-2', title: 'Second Session', type: 'session', status: 'idle' }, + ], + activeTabId: 'tab-1', + }) + useChatStore.setState({ + sessions: {}, + disconnectSession: vi.fn(), + } as Partial>) + + await act(async () => { + render() + }) + + const firstTab = screen.getByText('First Session').closest('[draggable="true"]') + const secondTab = screen.getByText('Second Session').closest('[draggable="true"]') + + expect(firstTab).toBeTruthy() + expect(secondTab).toBeTruthy() + + fireEvent.dragStart(firstTab!) + fireEvent.dragOver(secondTab!) + fireEvent.drop(secondTab!) + fireEvent.dragEnd(firstTab!) + + expect(useTabStore.getState().tabs.map((tab) => tab.sessionId)).toEqual(['tab-2', 'tab-1']) + }) }) diff --git a/desktop/src/components/layout/TabBar.tsx b/desktop/src/components/layout/TabBar.tsx index b4089000..5b6595f6 100644 --- a/desktop/src/components/layout/TabBar.tsx +++ b/desktop/src/components/layout/TabBar.tsx @@ -169,7 +169,6 @@ export function TabBar() { return (
@@ -202,6 +201,15 @@ export function TabBar() { ))}
+ {isTauri && ( +