fix(editor): drop the load-error Close action; closing stays with the tab strip

The Close button routed through `requestEditorFileClose`, which skips the
pinned-tab and shared-reference checks the tab strip applies, has no
listener outside the Terminal workbench (floating editor panels), and on
the conflict-review overview could target an unrelated open tab whose id
is the same absolute path as a synthesized inline row. Rather than
reimplement the tab strip's close semantics in a second place, the error
view keeps Retry and its copy points the user at closing the tab.
This commit is contained in:
Neil
2026-09-17 23:09:45 -07:00
parent 9458eb6082
commit 9229b8b2dc
6 changed files with 14 additions and 48 deletions
@@ -7,7 +7,6 @@ import type { GitStatusEntry } from '../../../../shared/git-status-types'
import { ConflictBanner, ConflictPlaceholderView, ConflictReviewPanel } from './ConflictComponents'
import { ImageViewer, MonacoEditor } from './editor-lazy-views'
import { EditorFileLoadErrorView } from './EditorFileLoadErrorView'
import { requestEditorFileClose } from './editor-autosave'
import type { FileContent } from './editor-panel-content-types'
import { translate } from '@/i18n/i18n'
import type { EditorConflictNavigation } from './useEditorConflictNavigation'
@@ -128,16 +127,12 @@ export function EditorConflictReviewSurface({
)
}
if (fileContent.loadError) {
// Why: inline overview rows are synthesized per entry and are not tabs, so the
// close queue would drop the request; only a real open tab gets a Close action.
const isOpenTab = openFiles.some((file) => file.id === contentFile.id)
return (
<div className={className}>
<EditorFileLoadErrorView
message={fileContent.loadError}
code={fileContent.loadErrorCode}
onRetry={() => reloadContent(contentFile)}
onClose={isOpenTab ? () => requestEditorFileClose(contentFile.id) : undefined}
/>
</div>
)
@@ -8,7 +8,6 @@ import { EditorConflictReviewSurface } from './EditorConflictReviewSurface'
import { EditorDiffFileSurface } from './EditorDiffFileSurface'
import { EditorEditFileSurface } from './EditorEditFileSurface'
import { EditorFileLoadErrorView } from './EditorFileLoadErrorView'
import { requestEditorFileClose } from './editor-autosave'
import type { FileContent } from './editor-panel-content-types'
import { translate } from '@/i18n/i18n'
import { useEditorConflictNavigation } from './useEditorConflictNavigation'
@@ -191,7 +190,6 @@ export function EditorContent({
message={fileContent.loadError}
code={fileContent.loadErrorCode}
onRetry={() => reloadContent(activeFile)}
onClose={() => requestEditorFileClose(activeFile.id)}
/>
)
}
@@ -13,7 +13,6 @@ import {
} from './editor-lazy-views'
import type { EditorConflictNavigation } from './useEditorConflictNavigation'
import { EditorFileLoadErrorView } from './EditorFileLoadErrorView'
import { requestEditorFileClose } from './editor-autosave'
import type { FileContent } from './editor-panel-content-types'
import { ExternalFileChangeBanner } from './ExternalFileChangeBanner'
import type { useMarkdownDocuments } from './useMarkdownDocuments'
@@ -104,7 +103,6 @@ export function EditorEditFileSurface({
message={fileContent.loadError}
code={fileContent.loadErrorCode}
onRetry={() => reloadContent(activeFile)}
onClose={() => requestEditorFileClose(activeFile.id)}
/>
)
}
@@ -10,28 +10,17 @@ import {
describe('EditorFileLoadErrorView', () => {
afterEach(cleanup)
it('offers Close only when the caller can route it, and never closes on its own', () => {
it('offers Retry as its only action', () => {
// Why: closing must stay with the tab strip, whose path carries the pin, shared-
// reference, and unsaved-changes checks; a second close control here would not.
const onRetry = vi.fn()
const onClose = vi.fn()
render(
<EditorFileLoadErrorView message="selector_not_found" onRetry={onRetry} onClose={onClose} />
)
render(<EditorFileLoadErrorView message="selector_not_found" onRetry={onRetry} />)
screen.getByText('selector_not_found')
expect(screen.getAllByRole('button')).toHaveLength(1)
fireEvent.click(screen.getByRole('button', { name: 'Retry' }))
expect(onRetry).toHaveBeenCalledOnce()
expect(onClose).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('button', { name: 'Close tab' }))
expect(onClose).toHaveBeenCalledOnce()
})
it('renders no Close action without a handler', () => {
render(<EditorFileLoadErrorView message="ENOENT" onRetry={vi.fn()} />)
screen.getByRole('button', { name: 'Retry' })
expect(screen.queryByRole('button', { name: 'Close tab' })).toBeNull()
})
it('localizes the host-unresolved state by its sentinel code, not by the stored text', () => {
@@ -16,19 +16,18 @@ function localizeFileLoadError(message: string, code: string | undefined): strin
return message
}
// Why no Close action here: this view renders for real tabs and for synthesized inline
// conflict rows alike, and only the tab strip's own close path carries the pin, shared-
// reference, and unsaved-changes semantics. The copy points the user at that path instead
// of adding a second one that would have to reimplement it (#21041).
export function EditorFileLoadErrorView({
message,
code,
onRetry,
onClose
onRetry
}: {
message: string
code?: string
onRetry: () => void
// Why: a tab whose read reached a terminal state must not vanish on its own — the user
// decides whether to keep retrying or close it (#21041). Callers route the close through
// the unsaved-changes queue so a dirty draft is still confirmed, never silently dropped.
onClose?: () => void
}): React.JSX.Element {
return (
<div className="flex h-full items-center justify-center bg-editor-surface p-6 text-sm text-muted-foreground">
@@ -39,20 +38,10 @@ export function EditorFileLoadErrorView({
{translate('auto.components.editor.EditorContent.39f018b052', 'Unable to load file')}
</div>
<div className="mt-1 break-words">{localizeFileLoadError(message, code)}</div>
<div className="mt-3 flex items-center gap-2">
<Button type="button" variant="outline" size="sm" onClick={onRetry}>
<RefreshCw className="size-3.5" />
{translate('auto.components.editor.EditorContent.2a512bb46a', 'Retry')}
</Button>
{onClose ? (
<Button type="button" variant="ghost" size="sm" onClick={onClose}>
{translate(
'auto.components.editor.EditorFileLoadErrorView.296b59cd29',
'Close tab'
)}
</Button>
) : null}
</div>
<Button type="button" variant="outline" size="sm" className="mt-3" onClick={onRetry}>
<RefreshCw className="size-3.5" />
{translate('auto.components.editor.EditorContent.2a512bb46a', 'Retry')}
</Button>
</div>
</div>
</div>
-3
View File
@@ -15344,9 +15344,6 @@
"a0af0198aa": "Large diffs are not rendered by default.",
"c3d9f4a712": "This diff's size isn't known yet, so it loads on request.",
"f7fa7a40d0": "Load diff"
},
"EditorFileLoadErrorView": {
"296b59cd29": "Close tab"
}
},
"diff": {