Matthias Nott
2026-03-08 dbb6bf09b013937b5903844f8aa567ef4ee69555
contexts/ChatContext.tsx
....@@ -146,6 +146,7 @@
146146 export function ChatProvider({ children }: { children: React.ReactNode }) {
147147 const [sessions, setSessions] = useState<WsSession[]>([]);
148148 const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
149
+ const activeSessionIdRef = useRef<string | null>(null);
149150 const [latestScreenshot, setLatestScreenshot] = useState<string | null>(null);
150151 const needsSync = useRef(true);
151152
....@@ -187,9 +188,7 @@
187188 if (active) {
188189 setActiveSessionId((prev) => {
189190 if (prev !== active.id) {
190
- if (prev) {
191
- messagesMapRef.current[prev] = messages;
192
- }
191
+ // No need to save prev — messagesMapRef is kept in sync by all mutators
193192 const all = messagesMapRef.current[active.id] ?? [];
194193 const page = all.length > PAGE_SIZE ? all.slice(-PAGE_SIZE) : all;
195194 setMessages(page);
....@@ -201,10 +200,11 @@
201200 return next;
202201 });
203202 }
203
+ activeSessionIdRef.current = active.id;
204204 return active.id;
205205 });
206206 }
207
- }, [messages]);
207
+ }, []);
208208
209209 // On connect: ask gateway to sync sessions. If we already had a session
210210 // selected, tell the gateway so it preserves our selection instead of
....@@ -212,7 +212,8 @@
212212 useEffect(() => {
213213 if (status === "connected") {
214214 needsSync.current = true;
215
- sendCommand("sync", activeSessionId ? { activeSessionId } : undefined);
215
+ const id = activeSessionIdRef.current;
216
+ sendCommand("sync", id ? { activeSessionId: id } : undefined);
216217 } else if (status === "disconnected") {
217218 setIsTyping(false);
218219 }
....@@ -223,38 +224,34 @@
223224 const addMessageToActive = useCallback((msg: Message) => {
224225 setMessages((prev) => {
225226 const next = [...prev, msg];
226
- setActiveSessionId((id) => {
227
- if (id) {
228
- messagesMapRef.current[id] = next;
229
- debouncedSave(messagesMapRef.current);
230
- }
231
- return id;
232
- });
227
+ const id = activeSessionIdRef.current;
228
+ if (id) {
229
+ messagesMapRef.current[id] = next;
230
+ debouncedSave(messagesMapRef.current);
231
+ }
233232 return next;
234233 });
235234 }, []);
236235
237236 // Helper: add a message to a specific session (may not be active)
238237 const addMessageToSession = useCallback((sessionId: string, msg: Message) => {
239
- setActiveSessionId((currentActive) => {
240
- if (sessionId === currentActive) {
241
- setMessages((prev) => {
242
- const next = [...prev, msg];
243
- messagesMapRef.current[sessionId] = next;
244
- debouncedSave(messagesMapRef.current);
245
- return next;
246
- });
247
- } else {
248
- const existing = messagesMapRef.current[sessionId] ?? [];
249
- messagesMapRef.current[sessionId] = [...existing, msg];
238
+ const currentActive = activeSessionIdRef.current;
239
+ if (sessionId === currentActive) {
240
+ setMessages((prev) => {
241
+ const next = [...prev, msg];
242
+ messagesMapRef.current[sessionId] = next;
250243 debouncedSave(messagesMapRef.current);
251
- setUnreadCounts((u) => ({
252
- ...u,
253
- [sessionId]: (u[sessionId] ?? 0) + 1,
254
- }));
255
- }
256
- return currentActive;
257
- });
244
+ return next;
245
+ });
246
+ } else {
247
+ const existing = messagesMapRef.current[sessionId] ?? [];
248
+ messagesMapRef.current[sessionId] = [...existing, msg];
249
+ debouncedSave(messagesMapRef.current);
250
+ setUnreadCounts((u) => ({
251
+ ...u,
252
+ [sessionId]: (u[sessionId] ?? 0) + 1,
253
+ }));
254
+ }
258255 }, []);
259256
260257 const updateMessageStatus = useCallback(
....@@ -272,13 +269,11 @@
272269 const next = prev.map((m) =>
273270 m.id === id ? { ...m, content } : m
274271 );
275
- setActiveSessionId((sessId) => {
276
- if (sessId) {
277
- messagesMapRef.current[sessId] = next;
278
- debouncedSave(messagesMapRef.current);
279
- }
280
- return sessId;
281
- });
272
+ const sessId = activeSessionIdRef.current;
273
+ if (sessId) {
274
+ messagesMapRef.current[sessId] = next;
275
+ debouncedSave(messagesMapRef.current);
276
+ }
282277 return next;
283278 });
284279 }, []);
....@@ -324,13 +319,15 @@
324319 timestamp: Date.now(),
325320 status: "sent",
326321 };
322
+ const isForActive = !data.sessionId || data.sessionId === activeSessionIdRef.current;
327323 if (data.sessionId) {
328324 addMessageToSession(data.sessionId, msg);
329325 } else {
330326 addMessageToActive(msg);
331327 }
332328 notifyIncomingMessage("PAILot", data.content ?? "Voice message");
333
- if (msg.audioUri && canAutoplay()) {
329
+ // Only autoplay if this voice note is for the currently viewed session
330
+ if (msg.audioUri && canAutoplay() && isForActive) {
334331 playAudio(msg.audioUri).catch(() => {});
335332 }
336333 break;
....@@ -487,26 +484,22 @@
487484 const deleteMessage = useCallback((id: string) => {
488485 setMessages((prev) => {
489486 const next = prev.filter((m) => m.id !== id);
490
- setActiveSessionId((sessId) => {
491
- if (sessId) {
492
- messagesMapRef.current[sessId] = next;
493
- debouncedSave(messagesMapRef.current);
494
- }
495
- return sessId;
496
- });
487
+ const sessId = activeSessionIdRef.current;
488
+ if (sessId) {
489
+ messagesMapRef.current[sessId] = next;
490
+ debouncedSave(messagesMapRef.current);
491
+ }
497492 return next;
498493 });
499494 }, []);
500495
501496 const clearMessages = useCallback(() => {
502497 setMessages([]);
503
- setActiveSessionId((id) => {
504
- if (id) {
505
- messagesMapRef.current[id] = [];
506
- clearPersistedMessages(id);
507
- }
508
- return id;
509
- });
498
+ const id = activeSessionIdRef.current;
499
+ if (id) {
500
+ messagesMapRef.current[id] = [];
501
+ clearPersistedMessages(id);
502
+ }
510503 }, []);
511504
512505 // --- Session management ---
....@@ -516,16 +509,10 @@
516509
517510 const switchSession = useCallback(
518511 (sessionId: string) => {
519
- setActiveSessionId((prev) => {
520
- if (prev) {
521
- messagesMapRef.current[prev] = messages;
522
- debouncedSave(messagesMapRef.current);
523
- }
524
- return prev;
525
- });
512
+ // messagesMapRef is already kept in sync by all mutators — no need to save here
526513 sendCommand("switch", { sessionId });
527514 },
528
- [sendCommand, messages]
515
+ [sendCommand]
529516 );
530517
531518 const renameSession = useCallback(
....@@ -559,20 +546,18 @@
559546 }, [sendCommand]);
560547
561548 const loadMoreMessages = useCallback(() => {
562
- setActiveSessionId((sessId) => {
563
- if (!sessId) return sessId;
564
- const all = messagesMapRef.current[sessId] ?? [];
565
- setMessages((current) => {
566
- if (current.length >= all.length) {
567
- setHasMoreMessages(false);
568
- return current;
569
- }
570
- const nextSize = Math.min(current.length + PAGE_SIZE, all.length);
571
- const page = all.slice(-nextSize);
572
- setHasMoreMessages(nextSize < all.length);
573
- return page;
574
- });
575
- return sessId;
549
+ const sessId = activeSessionIdRef.current;
550
+ if (!sessId) return;
551
+ const all = messagesMapRef.current[sessId] ?? [];
552
+ setMessages((current) => {
553
+ if (current.length >= all.length) {
554
+ setHasMoreMessages(false);
555
+ return current;
556
+ }
557
+ const nextSize = Math.min(current.length + PAGE_SIZE, all.length);
558
+ const page = all.slice(-nextSize);
559
+ setHasMoreMessages(nextSize < all.length);
560
+ return page;
576561 });
577562 }, []);
578563