Skip to content

Commit a074658

Browse files
sands21claude
andcommitted
fix(web): a failed preview image retries when its URL is renewed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent 5af3c87 commit a074658

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

‎apps/web/src/components/files/AttachmentFilePreview.browser.test.tsx‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2412,6 +2412,7 @@ it("keeps a workspace PDF open across URL renewals and reloads it when the file
24122412
}
24132413
});
24142414
it("keeps a workspace image and its zoom across agent commands and reloads it when the file changes", async () => {
2415+
assetUrlState.mockReset();
24152416
const { WorkspaceImagePreview } = await import("./FilePreviewPanel");
24162417
const original =
24172418
'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"/>';
@@ -2472,6 +2473,39 @@ it("keeps a workspace image and its zoom across agent commands and reloads it wh
24722473
expect(reloaded?.getAttribute("src")?.startsWith(edited)).toBe(true);
24732474
});
24742475
});
2476+
it("retries a failed workspace image when its URL is renewed for the same file", async () => {
2477+
assetUrlState.mockReset();
2478+
const { WorkspaceImagePreview } = await import("./FilePreviewPanel");
2479+
const preview = (mutation: string) => (
2480+
<WorkspaceImagePreview
2481+
environmentId={EnvironmentId.make("test")}
2482+
threadRef={{ environmentId: EnvironmentId.make("test"), threadId: ThreadId.make("test") }}
2483+
draft={false}
2484+
workspaceRoot="/repo"
2485+
absolutePath="/repo/screenshot.svg"
2486+
alt="screenshot.svg"
2487+
workspaceMutationId={mutation}
2488+
/>
2489+
);
2490+
assetUrlState.mockReturnValue({
2491+
_tag: "Success",
2492+
url: "data:image/svg+xml,broken",
2493+
expiresAt: 0,
2494+
fileRevision: "one",
2495+
});
2496+
await using screen = await render(preview("first"));
2497+
await expect.element(screen.getByText("Unable to load image.")).toBeVisible();
2498+
2499+
assetUrlState.mockReturnValue({
2500+
_tag: "Success",
2501+
url: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"/>#renewed',
2502+
expiresAt: 0,
2503+
fileRevision: "one",
2504+
});
2505+
await screen.rerender(preview("second"));
2506+
await expect.element(screen.getByRole("button", { name: "Zoom in" })).toBeEnabled();
2507+
expect(screen.container.textContent).not.toContain("Unable to load image.");
2508+
});
24752509
function planningPdf(
24762510
titles = ["Quarterly outlook", "Delivery milestones", "Budget and risks"],
24772511
firstHeight = 140,

‎apps/web/src/components/files/PreviewImageContent.tsx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export default function PreviewImageContent(props: {
1111
readonly mimeType?: string;
1212
readonly onError?: () => void;
1313
}) {
14-
const [failed, setFailed] = useState(false);
14+
// A renewed URL for the same file retries a failed load without resetting zoom.
15+
const [failedSrc, setFailedSrc] = useState<string | null>(null);
1516
const [expanded, setExpanded] = useState(false);
1617
const [natural, setNatural] = useState({ width: 0, height: 0 });
1718
const [viewport, setViewport] = useState({ width: 0, height: 0 });
@@ -34,7 +35,7 @@ export default function PreviewImageContent(props: {
3435
: 1;
3536
const scale = zoom ?? fit;
3637
const changeZoom = (factor: number) => setZoom(Math.max(0.05, Math.min(8, scale * factor)));
37-
if (failed) return <FileSurfaceFailure message="Unable to load image." />;
38+
if (failedSrc === props.src) return <FileSurfaceFailure message="Unable to load image." />;
3839
return (
3940
<>
4041
<div className="flex min-h-10 shrink-0 flex-wrap items-center justify-between gap-2 border-b border-border/60 px-3 py-1 text-xs text-muted-foreground">
@@ -107,7 +108,7 @@ export default function PreviewImageContent(props: {
107108
})
108109
}
109110
onError={() => {
110-
setFailed(true);
111+
setFailedSrc(props.src);
111112
props.onError?.();
112113
}}
113114
/>

0 commit comments

Comments
 (0)