diff --git a/src/__tests__/renderer/components/TabBar.test.tsx b/src/__tests__/renderer/components/TabBar.test.tsx index 393a7f67..7e1b38a7 100644 --- a/src/__tests__/renderer/components/TabBar.test.tsx +++ b/src/__tests__/renderer/components/TabBar.test.tsx @@ -1581,6 +1581,56 @@ describe('TabBar', () => { rafSpy.mockRestore(); }); + + it('scrolls active tab into view when its name changes (e.g., after auto-generation)', async () => { + // Mock requestAnimationFrame + const rafSpy = vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { + cb(0); + return 0; + }); + + const tabs = [ + createTab({ id: 'tab-1', name: null }), // Tab without name initially + createTab({ id: 'tab-2', name: 'Tab 2' }), + ]; + + const { rerender, container } = render( + + ); + + // Simulate the active tab's name being updated (e.g., auto-generated name) + // This should trigger a scroll to ensure the now-wider tab is still visible + const updatedTabs = [ + createTab({ id: 'tab-1', name: 'A Much Longer Auto-Generated Tab Name' }), + createTab({ id: 'tab-2', name: 'Tab 2' }), + ]; + + rerender( + + ); + + // The scroll behavior uses getBoundingClientRect which returns 0s in JSDOM, + // so we just verify the effect runs without error and the tab renders with new name + const activeTab = container.querySelector('[data-tab-id="tab-1"]'); + expect(activeTab).toBeTruthy(); + expect(screen.getByText('A Much Longer Auto-Generated Tab Name')).toBeTruthy(); + + rafSpy.mockRestore(); + }); }); describe('styling', () => { diff --git a/src/renderer/components/TabBar.tsx b/src/renderer/components/TabBar.tsx index 4c9a27af..ad076427 100644 --- a/src/renderer/components/TabBar.tsx +++ b/src/renderer/components/TabBar.tsx @@ -1599,7 +1599,14 @@ function TabBarInner({ const tabRefs = useRef>(new Map()); const [isOverflowing, setIsOverflowing] = useState(false); - // Ensure the active tab is fully visible (including close button) when activeTabId or activeFileTabId changes, or filter is toggled + // Get active tab's name to trigger scroll when it changes (e.g., after auto-generated name) + const activeTab = tabs.find((t) => t.id === activeTabId); + const activeTabName = activeTab?.name ?? null; + + // Ensure the active tab is fully visible (including close button) when: + // - activeTabId or activeFileTabId changes (new tab selected) + // - activeTabName changes (tab renamed, so width may have changed) + // - filter is toggled useEffect(() => { // Double requestAnimationFrame ensures the DOM has fully updated after React's state changes // First rAF: React has committed changes but browser hasn't painted yet @@ -1634,7 +1641,7 @@ function TabBarInner({ } }); }); - }, [activeTabId, activeFileTabId, showUnreadOnly]); + }, [activeTabId, activeFileTabId, activeTabName, showUnreadOnly]); // Can always close tabs - closing the last one creates a fresh new tab const canClose = true;