From dbb6bf09b013937b5903844f8aa567ef4ee69555 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Sun, 08 Mar 2026 06:45:10 +0100
Subject: [PATCH] fix: cross-session message routing — stale closure + autoplay bugs
---
contexts/ChatContext.tsx | 133 +++++++++++++++++++------------------------
1 files changed, 59 insertions(+), 74 deletions(-)
diff --git a/contexts/ChatContext.tsx b/contexts/ChatContext.tsx
index be47100..5269516 100644
--- a/contexts/ChatContext.tsx
+++ b/contexts/ChatContext.tsx
@@ -146,6 +146,7 @@
export function ChatProvider({ children }: { children: React.ReactNode }) {
const [sessions, setSessions] = useState<WsSession[]>([]);
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
+ const activeSessionIdRef = useRef<string | null>(null);
const [latestScreenshot, setLatestScreenshot] = useState<string | null>(null);
const needsSync = useRef(true);
@@ -187,9 +188,7 @@
if (active) {
setActiveSessionId((prev) => {
if (prev !== active.id) {
- if (prev) {
- messagesMapRef.current[prev] = messages;
- }
+ // No need to save prev — messagesMapRef is kept in sync by all mutators
const all = messagesMapRef.current[active.id] ?? [];
const page = all.length > PAGE_SIZE ? all.slice(-PAGE_SIZE) : all;
setMessages(page);
@@ -201,10 +200,11 @@
return next;
});
}
+ activeSessionIdRef.current = active.id;
return active.id;
});
}
- }, [messages]);
+ }, []);
// On connect: ask gateway to sync sessions. If we already had a session
// selected, tell the gateway so it preserves our selection instead of
@@ -212,7 +212,8 @@
useEffect(() => {
if (status === "connected") {
needsSync.current = true;
- sendCommand("sync", activeSessionId ? { activeSessionId } : undefined);
+ const id = activeSessionIdRef.current;
+ sendCommand("sync", id ? { activeSessionId: id } : undefined);
} else if (status === "disconnected") {
setIsTyping(false);
}
@@ -223,38 +224,34 @@
const addMessageToActive = useCallback((msg: Message) => {
setMessages((prev) => {
const next = [...prev, msg];
- setActiveSessionId((id) => {
- if (id) {
- messagesMapRef.current[id] = next;
- debouncedSave(messagesMapRef.current);
- }
- return id;
- });
+ const id = activeSessionIdRef.current;
+ if (id) {
+ messagesMapRef.current[id] = next;
+ debouncedSave(messagesMapRef.current);
+ }
return next;
});
}, []);
// Helper: add a message to a specific session (may not be active)
const addMessageToSession = useCallback((sessionId: string, msg: Message) => {
- setActiveSessionId((currentActive) => {
- if (sessionId === currentActive) {
- setMessages((prev) => {
- const next = [...prev, msg];
- messagesMapRef.current[sessionId] = next;
- debouncedSave(messagesMapRef.current);
- return next;
- });
- } else {
- const existing = messagesMapRef.current[sessionId] ?? [];
- messagesMapRef.current[sessionId] = [...existing, msg];
+ const currentActive = activeSessionIdRef.current;
+ if (sessionId === currentActive) {
+ setMessages((prev) => {
+ const next = [...prev, msg];
+ messagesMapRef.current[sessionId] = next;
debouncedSave(messagesMapRef.current);
- setUnreadCounts((u) => ({
- ...u,
- [sessionId]: (u[sessionId] ?? 0) + 1,
- }));
- }
- return currentActive;
- });
+ return next;
+ });
+ } else {
+ const existing = messagesMapRef.current[sessionId] ?? [];
+ messagesMapRef.current[sessionId] = [...existing, msg];
+ debouncedSave(messagesMapRef.current);
+ setUnreadCounts((u) => ({
+ ...u,
+ [sessionId]: (u[sessionId] ?? 0) + 1,
+ }));
+ }
}, []);
const updateMessageStatus = useCallback(
@@ -272,13 +269,11 @@
const next = prev.map((m) =>
m.id === id ? { ...m, content } : m
);
- setActiveSessionId((sessId) => {
- if (sessId) {
- messagesMapRef.current[sessId] = next;
- debouncedSave(messagesMapRef.current);
- }
- return sessId;
- });
+ const sessId = activeSessionIdRef.current;
+ if (sessId) {
+ messagesMapRef.current[sessId] = next;
+ debouncedSave(messagesMapRef.current);
+ }
return next;
});
}, []);
@@ -324,13 +319,15 @@
timestamp: Date.now(),
status: "sent",
};
+ const isForActive = !data.sessionId || data.sessionId === activeSessionIdRef.current;
if (data.sessionId) {
addMessageToSession(data.sessionId, msg);
} else {
addMessageToActive(msg);
}
notifyIncomingMessage("PAILot", data.content ?? "Voice message");
- if (msg.audioUri && canAutoplay()) {
+ // Only autoplay if this voice note is for the currently viewed session
+ if (msg.audioUri && canAutoplay() && isForActive) {
playAudio(msg.audioUri).catch(() => {});
}
break;
@@ -487,26 +484,22 @@
const deleteMessage = useCallback((id: string) => {
setMessages((prev) => {
const next = prev.filter((m) => m.id !== id);
- setActiveSessionId((sessId) => {
- if (sessId) {
- messagesMapRef.current[sessId] = next;
- debouncedSave(messagesMapRef.current);
- }
- return sessId;
- });
+ const sessId = activeSessionIdRef.current;
+ if (sessId) {
+ messagesMapRef.current[sessId] = next;
+ debouncedSave(messagesMapRef.current);
+ }
return next;
});
}, []);
const clearMessages = useCallback(() => {
setMessages([]);
- setActiveSessionId((id) => {
- if (id) {
- messagesMapRef.current[id] = [];
- clearPersistedMessages(id);
- }
- return id;
- });
+ const id = activeSessionIdRef.current;
+ if (id) {
+ messagesMapRef.current[id] = [];
+ clearPersistedMessages(id);
+ }
}, []);
// --- Session management ---
@@ -516,16 +509,10 @@
const switchSession = useCallback(
(sessionId: string) => {
- setActiveSessionId((prev) => {
- if (prev) {
- messagesMapRef.current[prev] = messages;
- debouncedSave(messagesMapRef.current);
- }
- return prev;
- });
+ // messagesMapRef is already kept in sync by all mutators — no need to save here
sendCommand("switch", { sessionId });
},
- [sendCommand, messages]
+ [sendCommand]
);
const renameSession = useCallback(
@@ -559,20 +546,18 @@
}, [sendCommand]);
const loadMoreMessages = useCallback(() => {
- setActiveSessionId((sessId) => {
- if (!sessId) return sessId;
- const all = messagesMapRef.current[sessId] ?? [];
- setMessages((current) => {
- if (current.length >= all.length) {
- setHasMoreMessages(false);
- return current;
- }
- const nextSize = Math.min(current.length + PAGE_SIZE, all.length);
- const page = all.slice(-nextSize);
- setHasMoreMessages(nextSize < all.length);
- return page;
- });
- return sessId;
+ const sessId = activeSessionIdRef.current;
+ if (!sessId) return;
+ const all = messagesMapRef.current[sessId] ?? [];
+ setMessages((current) => {
+ if (current.length >= all.length) {
+ setHasMoreMessages(false);
+ return current;
+ }
+ const nextSize = Math.min(current.length + PAGE_SIZE, all.length);
+ const page = all.slice(-nextSize);
+ setHasMoreMessages(nextSize < all.length);
+ return page;
});
}, []);
--
Gitblit v1.3.1