From 052f70e871be56df0e3368713c38ca79c0e4a0e7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 5 Apr 2026 20:28:37 +0200 Subject: [PATCH] fix(transcription): use navigator.sendBeacon for beforeunload save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace async executeSave in beforeunload handler with navigator.sendBeacon — synchronous and reliable for page unload. Sends pending text as JSON blob to the block update endpoint. Fixes @Sara: "beforeunload handlers cannot reliably await async" Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/TranscriptionEditView.svelte | 15 +++++++++++++-- frontend/src/routes/documents/[id]/+page.svelte | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte index f4f57c51..e30d10f5 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte +++ b/frontend/src/lib/components/TranscriptionEditView.svelte @@ -7,13 +7,14 @@ import type { TranscriptionBlockData } from '$lib/types'; type SaveState = 'idle' | 'saving' | 'saved' | 'fading' | 'error'; type Props = { + documentId: string; blocks: TranscriptionBlockData[]; onBlockFocus: (blockId: string) => void; onSaveBlock: (blockId: string, text: string) => Promise; onDeleteBlock: (blockId: string) => Promise; }; -let { blocks, onBlockFocus, onSaveBlock, onDeleteBlock }: Props = $props(); +let { documentId, blocks, onBlockFocus, onSaveBlock, onDeleteBlock }: Props = $props(); let activeBlockId: string | null = $state(null); let saveStates = new SvelteMap(); @@ -115,9 +116,19 @@ function handleDelete(blockId: string) { onDeleteBlock(blockId); } +function flushViaBeacon() { + for (const [blockId, text] of pendingTexts) { + clearDebounce(blockId); + const url = `/api/documents/${documentId}/transcription-blocks/${blockId}`; + const body = JSON.stringify({ text }); + navigator.sendBeacon(url, new Blob([body], { type: 'application/json' })); + pendingTexts.delete(blockId); + } +} + $effect(() => { function onBeforeUnload() { - flushAllPending(); + flushViaBeacon(); } window.addEventListener('beforeunload', onBeforeUnload); diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index e71593e0..fac7d987 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -199,6 +199,7 @@ onMount(() => { {#if transcribeMode}