From c05eeff64452282635572c07bb36f8277016bb99 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: Tue, 21 Apr 2026 18:38:31 +0800 Subject: [PATCH] Restore tab close affordance after drag migration The pointer-drag rewrite left the tab close control without its hover trigger, so tabs could still be closed logically but no longer exposed the affordance. This restores the hover group, trims the close icon to a lighter visual weight, and adds a regression test to keep close-click behavior from colliding with drag. Constraint: Tab close must remain compatible with the custom pointer-drag reorder flow Rejected: Reintroduce a larger hoverable close button | made the tab chrome look visually heavy Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep the close control visually subordinate to the tab label and verify drag-click interactions before changing tab hit areas Tested: cd desktop && bun run test -- TabBar; cd desktop && bun run lint Not-tested: Full desktop app manual visual QA in Tauri runtime --- desktop/src/components/layout/TabBar.test.tsx | 38 +++++++++++++++++++ desktop/src/components/layout/TabBar.tsx | 8 ++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/desktop/src/components/layout/TabBar.test.tsx b/desktop/src/components/layout/TabBar.test.tsx index 01a6b6e1..79984964 100644 --- a/desktop/src/components/layout/TabBar.test.tsx +++ b/desktop/src/components/layout/TabBar.test.tsx @@ -295,4 +295,42 @@ describe('TabBar', () => { expect(useTabStore.getState().tabs.map((tab) => tab.sessionId)).toEqual(['tab-1', 'tab-2']) expect(useTabStore.getState().activeTabId).toBe('tab-1') }) + + it('closes a tab from the close button without activating drag behavior', async () => { + const { TabBar } = await import('./TabBar') + const { useTabStore } = await import('../../stores/tabStore') + const { useChatStore } = await import('../../stores/chatStore') + + const disconnectSession = vi.fn() + + 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-2', + }) + useChatStore.setState({ + sessions: {}, + disconnectSession, + } as Partial>) + + await act(async () => { + render() + }) + + const firstTab = screen.getByText('First Session').closest('.tab-bar-hit-area') + const closeButton = screen.getByLabelText('Close First Session') + + expect(firstTab).toHaveClass('group') + + fireEvent.mouseDown(closeButton, { button: 0, clientX: 20, clientY: 10 }) + fireEvent.click(closeButton) + fireEvent.mouseMove(window, { clientX: 260, clientY: 10 }) + fireEvent.mouseUp(window) + + expect(disconnectSession).toHaveBeenCalledWith('tab-1') + expect(useTabStore.getState().tabs.map((tab) => tab.sessionId)).toEqual(['tab-2']) + expect(useTabStore.getState().activeTabId).toBe('tab-2') + }) }) diff --git a/desktop/src/components/layout/TabBar.tsx b/desktop/src/components/layout/TabBar.tsx index f9fc3a96..87cb0e8f 100644 --- a/desktop/src/components/layout/TabBar.tsx +++ b/desktop/src/components/layout/TabBar.tsx @@ -374,7 +374,7 @@ const TabItem = forwardRef )