From 14f6f2f0dc076b42706f2c61b8297a95bb149c40 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Thu, 20 Aug 2026 06:14:12 +0200 Subject: [PATCH] fix: make the progress export actually reach the disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Export is the whole of this app's backup story. Progress, streaks, ignores and every game and event the reader typed in live in one browser and nowhere else — no account, no server, nothing to restore from — so the download either happens or the data was never backed up. Two things made it a coin toss. The anchor was never in the document, which is not reliably clickable, and the object URL was revoked in the same task as the click, which can pull the blob away before the download has read it. Both fail silently and look exactly like a successful export. Co-Authored-By: Claude Opus 5 (1M context) --- src/client/App.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/client/App.tsx b/src/client/App.tsx index 44c0d5e..171417f 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -866,8 +866,17 @@ function exportProgress( const a = document.createElement("a"); a.href = url; a.download = `event-clock-progress-${new Date().toISOString().slice(0, 10)}.json`; + // In the document, and revoked on a later task. This is the only copy of + // everything the reader typed, ticked and marked — there is no account and no + // server that has ever seen it — so a download that quietly does not happen is + // the lossy backup this function exists to prevent. A detached anchor is not + // reliably clickable, and revoking the URL in the same task can pull the blob + // out from under a download that had not started reading it yet. + a.style.display = "none"; + document.body.append(a); a.click(); - URL.revokeObjectURL(url); + a.remove(); + setTimeout(() => URL.revokeObjectURL(url), 0); } async function importProgress(