diff --git a/apps/desktop/src/context/CallContext.tsx b/apps/desktop/src/context/CallContext.tsx index fcce19a..ef3a434 100644 --- a/apps/desktop/src/context/CallContext.tsx +++ b/apps/desktop/src/context/CallContext.tsx @@ -2263,10 +2263,21 @@ export function CallProvider({ children }: { children: ReactNode }) { // a call we route through `playSoundboard` so peers hear; outside a call // we fall back to `playSoundboardLocal` which plays through the system // default output only. + // + // We deliberately do NOT depend on `state.kind` in the effect dep array: + // every call state transition (idle → connecting → connected → reconnecting + // → ...) would trigger a full unregister+re-register cycle through IPC, and + // during the 1–50 ms gap the hotkeys are silently dead. Instead we read the + // current call state through a ref that's always kept in sync. + const callStateKindRef = useRef(state.kind); + callStateKindRef.current = state.kind; + const playSoundboardRef = useRef(playSoundboard); + playSoundboardRef.current = playSoundboard; + useEffect(() => { const teardown = startSoundboardHotkeys((id) => { - if (state.kind === 'connected') { - void playSoundboard(id); + if (callStateKindRef.current === 'connected') { + void playSoundboardRef.current(id); } else { void (async () => { const entries = await listSoundboard(); @@ -2276,7 +2287,7 @@ export function CallProvider({ children }: { children: ReactNode }) { } }); return teardown; - }, [state.kind, playSoundboard]); + }, []); // eslint-disable-line react-hooks/exhaustive-deps const setAudioInputDevice = useCallback(async (deviceId: string | null) => { updateAudioSettings({ inputDeviceId: deviceId });