diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 6739acb..cee1daa 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -2102,23 +2102,29 @@ registerProcessor('whisper-capture', WhisperCaptureProcessor);
`;
let whisperCaptureWorkletReady = false;
-let whisperCaptureWorkletLoading = false;
+let _whisperCaptureLoadPromise = null;
+/* RACE FIX: previous version returned Promise.resolve(false) for any
+ * SECOND concurrent caller — startSelfCapture and startCaptureForUuid
+ * both fire on toggle-on and the loser got "not ready" instead of
+ * awaiting the in-flight load. Fox 2026-06-05 surfaced this as
+ * "self capture: worklet module load failed." Cache the loading
+ * promise so every caller awaits the same one. */
function loadWhisperCaptureWorklet(ctx){
- if (whisperCaptureWorkletReady || whisperCaptureWorkletLoading) return Promise.resolve(whisperCaptureWorkletReady);
- whisperCaptureWorkletLoading = true;
+ if (whisperCaptureWorkletReady) return Promise.resolve(true);
+ if (_whisperCaptureLoadPromise) return _whisperCaptureLoadPromise;
const blob = new Blob([WHISPER_CAPTURE_WORKLET_CODE], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
- return ctx.audioWorklet.addModule(url).then(() => {
+ _whisperCaptureLoadPromise = ctx.audioWorklet.addModule(url).then(() => {
URL.revokeObjectURL(url);
whisperCaptureWorkletReady = true;
- whisperCaptureWorkletLoading = false;
return true;
}).catch(e => {
URL.revokeObjectURL(url);
- whisperCaptureWorkletLoading = false;
+ _whisperCaptureLoadPromise = null; /* allow retry on next call */
logLine('err', 'whisper-capture worklet load: '+e.message);
return false;
});
+ return _whisperCaptureLoadPromise;
}
/* Whisper inference runs in a dedicated Web Worker so ONNX Runtime
@@ -7420,8 +7426,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');