From 6c34962ced08f0edfc5d3c1c342eca9cd5c35ce6 Mon Sep 17 00:00:00 2001 From: karl Date: Tue, 13 Oct 2026 14:29:49 +0200 Subject: [PATCH] Offer explicit retry after media preview loading failures --- apps/desktop/src/main.ts | 49 ++++++++++++++++++++++------------------ docs/MEDIA-PREVIEW.md | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index bc172e0..9f32c71 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -208,6 +208,24 @@ function clearMedia() { mediaLane.clear(); unloadMedia(); } +function showMediaFailure(request: PreviewRequest, message: string): void { + if (!previewRevision.accepts(request.revision)) return; + const status = element("media-preview-status"); + status.hidden = false; + status.textContent = message; + const retry = document.createElement("button"); + retry.type = "button"; + retry.textContent = "Retry preview"; + retry.addEventListener("click", () => { + const revision = previewRevision.retry(request.revision); + if (revision === undefined) return; + unloadMedia(); + status.hidden = false; + status.textContent = "Preparing media preview…"; + mediaLane.submit({ revision, result: request.result }); + }); + status.append(" ", retry); +} const mediaLane = new LatestLane( async (request) => { const prepared = await invoke<{ @@ -218,10 +236,10 @@ const mediaLane = new LatestLane( const host = element("media-preview"); const status = element("media-preview-status"); const failed = () => { - if (!previewRevision.accepts(request.revision)) return; - status.hidden = false; - status.textContent = - "This format cannot be previewed here. Use Open file to view it in your default application."; + showMediaFailure( + request, + "This preview could not be loaded. Retry or use Open file to view it in your default application.", + ); }; if (prepared.kind === "image") { const image = document.createElement("img"); @@ -262,24 +280,11 @@ const mediaLane = new LatestLane( status.hidden = true; }, (request) => previewRevision.accepts(request.revision), - (request, error) => { - if (!previewRevision.accepts(request.revision)) return; - const status = element("media-preview-status"); - status.hidden = false; - status.textContent = `Preview unavailable: ${String(error)}. Use Open file.`; - const retry = document.createElement("button"); - retry.type = "button"; - retry.textContent = "Retry preview"; - retry.addEventListener("click", () => { - const revision = previewRevision.retry(request.revision); - if (revision === undefined) return; - unloadMedia(); - status.hidden = false; - status.textContent = "Preparing media preview…"; - mediaLane.submit({ revision, result: request.result }); - }); - status.append(" ", retry); - }, + (request, error) => + showMediaFailure( + request, + `Preview unavailable: ${String(error)}. Use Open file.`, + ), ); let displayedRequestId: number | null = null; let expandedError: string | null = null; diff --git a/docs/MEDIA-PREVIEW.md b/docs/MEDIA-PREVIEW.md index f9e410f..34b9c68 100644 --- a/docs/MEDIA-PREVIEW.md +++ b/docs/MEDIA-PREVIEW.md @@ -19,7 +19,7 @@ Arbitrary paths, remote URLs, query strings and traversal are not preview grants The generic Tauri asset protocol is disabled in the new configuration. Index-change notifications also advance the preview selection identity, so a same-path image/recording reloads rather than retaining previously decoded media. -Failed native preview preparation now offers an explicit **Retry preview** +Failed native preview preparation and image/audio/video loading now offer an explicit **Retry preview** button. It invalidates the old request revision and cannot restart a selection that has since changed. There is no automatic retry loop. The added regression passed with all 60 frontend tests, formatting and the production frontend build.