Matthias Nott
2026-03-08 d832f656599b153be8826bc43e1832209b2a1bf6
contexts/ChatContext.tsx
....@@ -116,6 +116,12 @@
116116
117117 // --- Context ---
118118
119
+interface IncomingToast {
120
+ sessionId: string;
121
+ sessionName: string;
122
+ preview: string;
123
+}
124
+
119125 interface ChatContextValue {
120126 messages: Message[];
121127 sendTextMessage: (text: string) => void;
....@@ -136,6 +142,8 @@
136142 loadMoreMessages: () => void;
137143 hasMoreMessages: boolean;
138144 unreadCounts: Record<string, number>;
145
+ incomingToast: IncomingToast | null;
146
+ dismissToast: () => void;
139147 latestScreenshot: string | null;
140148 requestScreenshot: () => void;
141149 sendNavKey: (key: string) => void;
....@@ -156,8 +164,11 @@
156164 const [messages, setMessages] = useState<Message[]>([]);
157165 // Unread counts for non-active sessions
158166 const [unreadCounts, setUnreadCounts] = useState<Record<string, number>>({});
159
- // Typing indicator from server
167
+ // Per-session typing indicator (sessionId → boolean)
168
+ const typingMapRef = useRef<Record<string, boolean>>({});
160169 const [isTyping, setIsTyping] = useState(false);
170
+ // Toast for other-session incoming messages
171
+ const [incomingToast, setIncomingToast] = useState<{ sessionId: string; sessionName: string; preview: string } | null>(null);
161172 // PAI projects list
162173 const [projects, setProjects] = useState<PaiProject[]>([]);
163174 // Pagination: does the active session have more messages in storage?
....@@ -199,6 +210,9 @@
199210 delete next[active.id];
200211 return next;
201212 });
213
+ // Sync typing indicator for the new active session
214
+ const activeTyping = typingMapRef.current[active.id] ?? false;
215
+ setIsTyping(activeTyping);
202216 }
203217 activeSessionIdRef.current = active.id;
204218 return active.id;
....@@ -251,6 +265,16 @@
251265 ...u,
252266 [sessionId]: (u[sessionId] ?? 0) + 1,
253267 }));
268
+ // Show toast for other-session messages (assistant only, skip system noise)
269
+ if (msg.role === "assistant") {
270
+ setSessions((prev) => {
271
+ const session = prev.find((s) => s.id === sessionId);
272
+ const name = session?.name ?? sessionId.slice(0, 8);
273
+ const preview = msg.type === "voice" ? "🎤 Voice note" : msg.type === "image" ? "📷 Image" : (msg.content ?? "").slice(0, 60);
274
+ setIncomingToast({ sessionId, sessionName: name, preview });
275
+ return prev;
276
+ });
277
+ }
254278 }
255279 }, []);
256280
....@@ -359,26 +383,12 @@
359383 break;
360384 }
361385 case "session_switched": {
362
- const msg: Message = {
363
- id: generateId(),
364
- role: "system",
365
- type: "text",
366
- content: `Switched to ${data.name}`,
367
- timestamp: Date.now(),
368
- };
369
- addMessageToActive(msg);
386
+ // Just refresh session list — no system message needed
370387 sendCommand("sessions");
371388 break;
372389 }
373390 case "session_renamed": {
374
- const msg: Message = {
375
- id: generateId(),
376
- role: "system",
377
- type: "text",
378
- content: `Renamed to ${data.name}`,
379
- timestamp: Date.now(),
380
- };
381
- addMessageToActive(msg);
391
+ // Just refresh session list — no system message needed
382392 sendCommand("sessions");
383393 break;
384394 }
....@@ -388,7 +398,11 @@
388398 break;
389399 }
390400 case "typing": {
391
- setIsTyping(data.typing);
401
+ const typingSession = (data.sessionId as string) || activeSessionIdRef.current || "_global";
402
+ typingMapRef.current[typingSession] = !!data.typing;
403
+ // Only show typing indicator if it's for the active session
404
+ const activeTyping = typingMapRef.current[activeSessionIdRef.current ?? ""] ?? false;
405
+ setIsTyping(activeTyping);
392406 break;
393407 }
394408 case "status": {
....@@ -545,6 +559,10 @@
545559 sendCommand("projects");
546560 }, [sendCommand]);
547561
562
+ const dismissToast = useCallback(() => {
563
+ setIncomingToast(null);
564
+ }, []);
565
+
548566 const loadMoreMessages = useCallback(() => {
549567 const sessId = activeSessionIdRef.current;
550568 if (!sessId) return;
....@@ -595,6 +613,8 @@
595613 loadMoreMessages,
596614 hasMoreMessages,
597615 unreadCounts,
616
+ incomingToast,
617
+ dismissToast,
598618 latestScreenshot,
599619 requestScreenshot,
600620 sendNavKey,