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