fix: dismiss chat naming error card before opening settings (#34655)

This commit is contained in:
Jinjing
2026-10-27 18:49:07 -07:00
committed by GitHub
parent 4f9b331570
commit 023c8603e8
4 changed files with 43 additions and 22 deletions
@@ -50,7 +50,11 @@ function ChatTab(): React.JSX.Element {
title="Repair the login flow"
icon={<span />}
programName="Claude"
alert={naming ? <NativeChatNamingErrorBlock naming={naming} /> : undefined}
alert={
naming
? (dismiss) => <NativeChatNamingErrorBlock naming={naming} onDismiss={dismiss} />
: undefined
}
>
<button>chat tab</button>
</TabHoverCard>
@@ -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)
}
}
)
})
@@ -13,9 +13,11 @@ function regenerateLabel(naming: NativeChatNamingFailureControls): string {
}
export function NativeChatNamingErrorBlock({
naming
naming,
onDismiss
}: {
naming: NativeChatNamingFailureControls
onDismiss?: () => void
}): React.JSX.Element {
return (
<div className="space-y-1 border-t border-border pt-2" data-tab-hover-card-naming-error>
@@ -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' })
@@ -223,12 +223,8 @@ export default function SortableTab({
dragListeners?.onPointerDown as ((event: React.PointerEvent<Element>) => 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 ? <NativeChatNamingErrorBlock naming={naming} /> : undefined}
alert={
naming
? (dismiss) => <NativeChatNamingErrorBlock naming={naming} onDismiss={dismiss} />
: undefined
}
>
{tabRoot}
</TabHoverCard>
@@ -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 (
<Tooltip disableHoverableContent={!interactive}>
<Tooltip open={open} onOpenChange={setOpen} disableHoverableContent={!interactive}>
<TooltipTrigger
asChild
onFocus={(event) => {
@@ -61,7 +65,7 @@ export function TabHoverCard({
</span>
<span className="min-w-0 break-all">{programName}</span>
</div>
{alert}
{typeof alert === 'function' ? alert(dismiss) : alert}
</div>
</TooltipContent>
</Tooltip>