fix(transcription): use navigator.sendBeacon for beforeunload save

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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-05 20:28:37 +02:00
parent a3fbcf346b
commit 052f70e871
2 changed files with 14 additions and 2 deletions

View File

@@ -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<void>;
onDeleteBlock: (blockId: string) => Promise<void>;
};
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<string, SaveState>();
@@ -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);

View File

@@ -199,6 +199,7 @@ onMount(() => {
{#if transcribeMode}
<div class="w-[400px] shrink-0 border-l border-line lg:w-[480px]">
<TranscriptionEditView
documentId={doc.id}
blocks={transcriptionBlocks}
onBlockFocus={handleBlockFocus}
onSaveBlock={saveBlock}