diff --git a/src/components/code-editor/view/subcomponents/CodeEditorMediaPreview.tsx b/src/components/code-editor/view/subcomponents/CodeEditorMediaPreview.tsx index de436837..abb425bb 100644 --- a/src/components/code-editor/view/subcomponents/CodeEditorMediaPreview.tsx +++ b/src/components/code-editor/view/subcomponents/CodeEditorMediaPreview.tsx @@ -46,9 +46,16 @@ export default function CodeEditorMediaPreview({ const [url, setUrl] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); + // Identifies which file the current `url` was loaded for. Rendering is gated on + // this so a blob from a previously-opened file can never show under the new + // file (the editor reuses this component instance across files). + const [loadedKey, setLoadedKey] = useState(null); + const sourceKey = `${projectId ?? ''}:${file.path}:${kind}`; useEffect(() => { if (!projectId) { + setUrl(null); + setLoadedKey(null); setError(labels.error); setLoading(false); return; @@ -97,6 +104,7 @@ export default function CodeEditorMediaPreview({ const typed = outType && outType !== blob.type ? new Blob([blob], { type: outType }) : blob; objectUrl = URL.createObjectURL(typed); setUrl(objectUrl); + setLoadedKey(sourceKey); } catch (loadError: unknown) { if (loadError instanceof Error && loadError.name === 'AbortError') { return; @@ -116,15 +124,19 @@ export default function CodeEditorMediaPreview({ URL.revokeObjectURL(objectUrl); } }; - }, [file.path, projectId, kind, labels.error]); + }, [file.path, file.name, projectId, kind, sourceKey, labels.error]); + + // Only expose the blob once it matches the file currently being shown, so a + // stale URL from the previous file is never rendered during a switch. + const currentUrl = url && loadedKey === sourceKey ? url : null; const renderMedia = () => { - if (!url) return null; + if (!currentUrl) return null; switch (kind) { case 'image': return ( {file.name} @@ -134,10 +146,10 @@ export default function CodeEditorMediaPreview({ // load inside a sandboxed frame (any `sandbox` value yields a broken // viewer). Script execution is instead prevented upstream by validating // the PDF magic bytes and pinning the blob's MIME type to application/pdf. - return