From cf14791de0c9f33ee140564093e92108cbac5f81 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Wed, 25 Feb 2026 23:33:51 +0800 Subject: [PATCH] fix: abort active stream on session switch and warn before closing during streaming - Add useEffect cleanup to abort in-flight fetch when ChatView unmounts (e.g., session switch via key={id} remount). This triggers the existing server-side cleanup chain: saves partial message to DB, auto-denies pending permissions. Also resets streamingSessionId and pendingApprovalSessionId global state to prevent stale UI. - Add beforeunload handler during streaming to show browser confirmation dialog when user attempts to close the window/tab, preventing accidental data loss. Listener is automatically removed when streaming ends. Co-Authored-By: Claude Opus 4.6 --- src/components/chat/ChatView.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/chat/ChatView.tsx b/src/components/chat/ChatView.tsx index 68d32fe..6220bef 100644 --- a/src/components/chat/ChatView.tsx +++ b/src/components/chat/ChatView.tsx @@ -66,6 +66,33 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal }, [sessionId]); const abortControllerRef = useRef(null); + // Abort active stream on unmount (e.g., session switch via key={id} remount). + // This triggers the existing server-side cleanup chain: + // fetch abort → request.signal 'abort' → route abortController.abort() + // → collectStreamResponse catch block saves partial message to DB + // → permission-registry abort handler auto-denies pending permissions + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + abortControllerRef.current = null; + } + setStreamingSessionId(''); + setPendingApprovalSessionId(''); + }; + }, [setStreamingSessionId, setPendingApprovalSessionId]); + + // Warn before closing window/tab while streaming to prevent accidental data loss + useEffect(() => { + if (!isStreaming) return; + const handler = (e: BeforeUnloadEvent) => { + e.preventDefault(); + e.returnValue = ''; + }; + window.addEventListener('beforeunload', handler); + return () => window.removeEventListener('beforeunload', handler); + }, [isStreaming]); + // Ref to keep accumulated streaming content in sync regardless of React batching const accumulatedRef = useRef(''); // Refs to track tool data reliably across closures (state reads can be stale)