Skip to content

Commit 76e496d

Browse files
committed
fix(files): reset the image preview when the file is overwritten
An overwrite preserves the storage key, which is what the parent keys this component on, so only the URL version changes and it never remounts. The previous bytes' outcome therefore stuck, leaving a replaced image parked on 'Preview not available' until something else forced a remount. Reset on URL change during render rather than in an effect — this is derived state, and an effect would render the stale outcome first.
1 parent a83cfce commit 76e496d

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.test.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ afterEach(() => {
4242
container.remove()
4343
})
4444

45-
function render() {
46-
act(() => root.render(<ImagePreview file={file} />))
45+
function render(record: WorkspaceFileRecord = file) {
46+
act(() => root.render(<ImagePreview file={record} />))
4747
}
4848

4949
describe('ImagePreview', () => {
@@ -68,4 +68,20 @@ describe('ImagePreview', () => {
6868
expect(container.querySelector('img')).toBeNull()
6969
expect(container.textContent).toContain('Preview not available')
7070
})
71+
72+
it('retries when the file is overwritten, which keeps the same storage key', () => {
73+
render()
74+
75+
act(() => {
76+
container.querySelector('img')?.dispatchEvent(new Event('error'))
77+
})
78+
expect(container.textContent).toContain('Preview not available')
79+
80+
// An overwrite preserves `file.key` — the parent's key — so only the version
81+
// changes and the component never remounts.
82+
render({ ...file, updatedAt: new Date('2026-02-01T00:00:00Z') })
83+
84+
expect(container.querySelector('img')).not.toBeNull()
85+
expect(container.textContent).not.toContain('Preview not available')
86+
})
7187
})

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/image-preview.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ZoomablePreview } from './zoomable-preview'
88

99
export const ImagePreview = memo(function ImagePreview({ file }: { file: WorkspaceFileRecord }) {
1010
const source = useFileContentSource()
11-
const [status, setStatus] = useState<'loading' | 'loaded' | 'error'>('loading')
1211
// Version the URL on updatedAt: overwrites keep the same storage key, so an unversioned
1312
// URL would resolve to a previously cached copy instead of the rewritten bytes.
1413
// `preview` lets the server substitute a renderable derivative for a HEIC.
@@ -17,6 +16,17 @@ export const ImagePreview = memo(function ImagePreview({ file }: { file: Workspa
1716
preview: true,
1817
})
1918

19+
const [status, setStatus] = useState<'loading' | 'loaded' | 'error'>('loading')
20+
const [loadedUrl, setLoadedUrl] = useState(serveUrl)
21+
22+
// The parent keys this on `file.key`, which an overwrite preserves — only the
23+
// version changes. Without this the outcome of the previous bytes would stick,
24+
// leaving a replaced image permanently on the unsupported state.
25+
if (loadedUrl !== serveUrl) {
26+
setLoadedUrl(serveUrl)
27+
setStatus('loading')
28+
}
29+
2030
// Covers every way the bytes can turn out unrenderable — a derivative the server
2131
// declined to build (too large, undecodable) and a corrupt or truncated image
2232
// alike — rather than leaving a broken image in the viewer.

0 commit comments

Comments
 (0)