From 023c8603e8e1f34589fd64ef6b9855e0d789dfd1 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:49:07 -0700 Subject: [PATCH] fix: dismiss chat naming error card before opening settings (#34655) --- .../tab-bar/NativeChatNamingFailure.test.tsx | 34 +++++++++++++------ .../tab-bar/NativeChatNamingFailure.tsx | 5 ++- .../src/components/tab-bar/SortableTab.tsx | 14 ++++---- .../src/components/tab-bar/TabHoverCard.tsx | 12 ++++--- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx b/src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx index ffdd413048a..cff0db2c76b 100644 --- a/src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx +++ b/src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx @@ -50,7 +50,11 @@ function ChatTab(): React.JSX.Element { title="Repair the login flow" icon={} programName="Claude" - alert={naming ? : undefined} + alert={ + naming + ? (dismiss) => + : undefined + } > @@ -95,15 +99,6 @@ describe('chat tab naming failure', () => { expect(screen.getAllByRole('button', { name: 'view error' }).length).toBeGreaterThan(0) const settingsButtons = screen.queryAllByRole('button', { name: 'Settings' }) expect(settingsButtons.length > 0).toBe(!web) - if (!web) { - fireEvent.click(settingsButtons[0]) - expect(settings.openSettingsPage).toHaveBeenCalledOnce() - expect(settings.openSettingsTarget).toHaveBeenCalledExactlyOnceWith({ - pane: 'chat', - repoId: null, - sectionId: 'chat-names' - }) - } const regenerate = screen.queryAllByRole('button', { name: 'Regenerate name' }) expect(regenerate.length > 0).toBe(supported) if (supported) { @@ -122,6 +117,25 @@ describe('chat tab naming failure', () => { await act(async () => acceptance.resolve(true)) expect(screen.queryAllByRole('button', { name: 'Regenerating…' })).toHaveLength(0) } + if (!web) { + settings.openSettingsPage.mockImplementationOnce(() => { + expect(screen.queryByText("Couldn't name this chat")).toBeNull() + }) + fireEvent.click(settingsButtons[0]) + expect(screen.queryByText("Couldn't name this chat")).toBeNull() + expect(settings.openSettingsPage).toHaveBeenCalledOnce() + expect(settings.openSettingsTarget).toHaveBeenCalledExactlyOnceWith({ + pane: 'chat', + repoId: null, + sectionId: 'chat-names' + }) + fireEvent.pointerLeave(screen.getByRole('button', { name: 'chat tab' })) + fireEvent.pointerMove(screen.getByRole('button', { name: 'chat tab' }), { + pointerType: 'mouse' + }) + act(() => vi.advanceTimersByTime(TAB_TOOLTIP_DELAY_MS)) + expect(screen.getAllByText("Couldn't name this chat").length).toBeGreaterThan(0) + } } ) }) diff --git a/src/renderer/src/components/tab-bar/NativeChatNamingFailure.tsx b/src/renderer/src/components/tab-bar/NativeChatNamingFailure.tsx index a998b910e30..858cf965e1e 100644 --- a/src/renderer/src/components/tab-bar/NativeChatNamingFailure.tsx +++ b/src/renderer/src/components/tab-bar/NativeChatNamingFailure.tsx @@ -13,9 +13,11 @@ function regenerateLabel(naming: NativeChatNamingFailureControls): string { } export function NativeChatNamingErrorBlock({ - naming + naming, + onDismiss }: { naming: NativeChatNamingFailureControls + onDismiss?: () => void }): React.JSX.Element { return (
@@ -57,6 +59,7 @@ export function NativeChatNamingErrorBlock({ variant="ghost" size="chip" onClick={() => { + onDismiss?.() const store = useAppStore.getState() store.openSettingsPage() store.openSettingsTarget({ pane: 'chat', repoId: null, sectionId: 'chat-names' }) diff --git a/src/renderer/src/components/tab-bar/SortableTab.tsx b/src/renderer/src/components/tab-bar/SortableTab.tsx index df8bca0ec35..e038ce6d7c2 100644 --- a/src/renderer/src/components/tab-bar/SortableTab.tsx +++ b/src/renderer/src/components/tab-bar/SortableTab.tsx @@ -223,12 +223,8 @@ export default function SortableTab({ dragListeners?.onPointerDown as ((event: React.PointerEvent) => void) | undefined ) }} - onMouseDown={(e) => { - // Why: block middle-click auto-scroll; don't close here — removing the element pre-mouseup triggers a Linux X11 paste. - if (e.button === 1) { - e.preventDefault() - } - }} + // Why: block middle-click auto-scroll; don't close here — removing the element pre-mouseup triggers a Linux X11 paste. + onMouseDown={preventMiddleButtonDefault} onMouseUp={preventMiddleButtonDefault} onAuxClick={(e) => { if (isEditing) { @@ -390,7 +386,11 @@ export default function SortableTab({ progress={programProgress} /> } - alert={naming ? : undefined} + alert={ + naming + ? (dismiss) => + : undefined + } > {tabRoot} diff --git a/src/renderer/src/components/tab-bar/TabHoverCard.tsx b/src/renderer/src/components/tab-bar/TabHoverCard.tsx index 0068112123d..18c10a5466b 100644 --- a/src/renderer/src/components/tab-bar/TabHoverCard.tsx +++ b/src/renderer/src/components/tab-bar/TabHoverCard.tsx @@ -1,4 +1,5 @@ -import type { ReactNode, SyntheticEvent } from 'react' +import { useState, type ReactNode, type SyntheticEvent } from 'react' +import { flushSync } from 'react-dom' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' export function TabHoverCard({ @@ -15,11 +16,14 @@ export function TabHoverCard({ programName: string description?: string /** Controls below the program row; the card then stays open under the pointer. */ - alert?: ReactNode + alert?: ReactNode | ((dismiss: () => void) => ReactNode) }): React.JSX.Element { + const [open, setOpen] = useState(false) + // Remove the portal before navigation can start rendering a slower destination. + const dismiss = () => flushSync(() => setOpen(false)) const interactive = alert !== undefined && alert !== null return ( - + { @@ -61,7 +65,7 @@ export function TabHoverCard({ {programName}
- {alert} + {typeof alert === 'function' ? alert(dismiss) : alert}