feat: improve voice recording UX and realtime state reconciliation
All checks were successful
CI / test (push) Successful in 20s

This commit is contained in:
2026-03-08 12:44:15 +03:00
parent d7160af908
commit 2af4588688
2 changed files with 103 additions and 8 deletions

View File

@@ -25,6 +25,7 @@ export function useRealtime() {
const manualCloseRef = useRef(false);
const notificationPermissionRequestedRef = useRef(false);
const reloadChatsTimerRef = useRef<number | null>(null);
const reconcileIntervalRef = useRef<number | null>(null);
const wsUrl = useMemo(() => {
return accessToken ? buildWsUrl(accessToken) : null;
@@ -66,11 +67,7 @@ export function useRealtime() {
ws.close();
}
}, 15000);
const store = useChatStore.getState();
void store.loadChats();
if (store.activeChatId) {
void store.loadMessages(store.activeChatId);
}
void reconcileState();
if ("Notification" in window && Notification.permission === "default" && !notificationPermissionRequestedRef.current) {
notificationPermissionRequestedRef.current = true;
void Notification.requestPermission();
@@ -198,6 +195,10 @@ export function useRealtime() {
window.clearInterval(watchdogIntervalRef.current);
watchdogIntervalRef.current = null;
}
if (reconcileIntervalRef.current !== null) {
window.clearInterval(reconcileIntervalRef.current);
reconcileIntervalRef.current = null;
}
if (manualCloseRef.current) {
return;
}
@@ -209,9 +210,24 @@ export function useRealtime() {
ws.onerror = () => {
ws.close();
};
if (reconcileIntervalRef.current !== null) {
window.clearInterval(reconcileIntervalRef.current);
}
reconcileIntervalRef.current = window.setInterval(() => {
void reconcileState();
}, 60000);
};
const onFocusOrVisible = () => {
if (document.visibilityState === "visible") {
void reconcileState();
}
};
connect();
window.addEventListener("focus", onFocusOrVisible);
document.addEventListener("visibilitychange", onFocusOrVisible);
return () => {
manualCloseRef.current = true;
@@ -223,6 +239,10 @@ export function useRealtime() {
window.clearInterval(watchdogIntervalRef.current);
watchdogIntervalRef.current = null;
}
if (reconcileIntervalRef.current !== null) {
window.clearInterval(reconcileIntervalRef.current);
reconcileIntervalRef.current = null;
}
if (reconnectTimeoutRef.current !== null) {
window.clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
@@ -235,9 +255,20 @@ export function useRealtime() {
wsRef.current = null;
typingByChat.current = {};
useChatStore.setState({ typingByChat: {} });
window.removeEventListener("focus", onFocusOrVisible);
document.removeEventListener("visibilitychange", onFocusOrVisible);
};
}, [wsUrl, meId]);
async function reconcileState() {
const storeBefore = useChatStore.getState();
await storeBefore.loadChats();
const storeAfter = useChatStore.getState();
if (storeAfter.activeChatId) {
await storeAfter.loadMessages(storeAfter.activeChatId);
}
}
function scheduleReloadChats() {
if (reloadChatsTimerRef.current !== null) {
return;