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}