fix(textpreview): say a stat-and-read failure once

A file whose stat and first page both fail — outside the workspace, gone —
showed the same line twice: the metadata bar above an identical centred body
failure. The bar now stands only over pages already loaded, and the centred
retry runs the full reload so one press clears both halves.
This commit is contained in:
Yif
2026-09-09 12:15:00 +08:00
committed by imccyu
parent 284758cbc3
commit c73dcecb96
2 changed files with 27 additions and 2 deletions
@@ -186,10 +186,12 @@ export function TextPreview({
}
return (
<div className={css.preview} data-textpreview-state="text" data-textpreview-url={tab.contentId} data-document-preview={selected.id}>
{meta.failure !== undefined
{meta.failure !== undefined && hasContent
? (
// The file's metadata failed — gone, or its workspace unknown — which
// outranks a pending change; the pages already read stay under it.
// With nothing read the body's own failure already says it, so the
// bar would only repeat the same line.
<p className={css.changed} data-textpreview-meta-failed={meta.failure.code}>
<span>{failureLine(t, meta.failure)}</span>
<button
@@ -301,6 +303,8 @@ export function TextPreview({
</p>
)
: (
// With no content, retry the selected renderer's read; metadata
// observation remains owned by the resource provider.
<div className={css.empty} data-textpreview-failed={current.failure.code}>
<FileTypeIcon kind={classifyFileType(name)} size={36} className={css.emptyIcon} />
<p className={css.emptyLine}>{failureLine(t, current.failure)}</p>
@@ -308,7 +312,7 @@ export function TextPreview({
type="button"
className={css.retry}
data-textpreview-retry
onClick={loadNext}
onClick={reload}
>
<IconRefreshOutline16 size={14} />
{t('retry')}
@@ -396,6 +396,27 @@ describe('TextPreview — the file\'s metadata', () => {
expect(view.container.querySelector('[data-textpreview-changed]')).toBeNull()
})
it('says a metadata-and-read failure once and retries the content read', async () => {
const h = harness({ 1: failure('workspace-file/outside-workspace', { path: PATH }) })
h.setFailure(new RemoteError('workspace-file/outside-workspace', 'outside', { path: PATH }))
const view = render(<TextPreview {...h.props()} />)
await settle()
// With nothing read the body's failure is the whole story: a metadata bar
// above it would repeat the same line.
expect(view.container.querySelector('[data-textpreview-meta-failed]')).toBeNull()
expect(view.container.querySelector('[data-textpreview-failed]')?.getAttribute('data-textpreview-failed'))
.toBe('workspace-file/outside-workspace')
h.script(1, page(1, ['a'], true))
click(view.container, '[data-textpreview-retry]')
await settle()
expect(h.read).toHaveBeenCalledTimes(2)
expect(lines(view.container)).toEqual(['a\n'])
h.setFailure(undefined)
view.rerender(<TextPreview {...h.props()} />)
expect(view.container.querySelector('[data-textpreview-meta-failed]')).toBeNull()
expect(view.container.querySelector('[data-textpreview-failed]')).toBeNull()
})
it('draws a page holding one empty line as one line, and nothing for a page past the end', async () => {
const h = harness({ 1: page(1, [''], false), 2: page(2, [], true) })
const view = render(<TextPreview {...h.props()} />)