From 990f04489f5b32bc22ee1de940386d6fe4d0d844 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 6 Apr 2026 20:17:39 +0000 Subject: [PATCH] fix: prevent TaskLane showing stale data when starting new chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three race conditions in AssistantChatPage: 1. handleNewChat cleared showTaskLane/activeQuestions/activeActions AFTER the createChatSession await — old lane was visible during the network call. Moved clears before the await. 2. handleResumeNew never cleared old TaskLane state at all. Added upfront clears before the first await. 3. handleSend and handleTaskSubmit had no stale-session guard. If the user switched chats while sendChatMessage was in flight, the response would set showTaskLane on the wrong session. Added sentForChatId snapshot + currentChatRef guard (same pattern already used in selectChat). Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/pages/AssistantChatPage.tsx | 34 +++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index c1ad8308..1f675727 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -259,6 +259,11 @@ export default function AssistantChatPage() { }, []) const handleNewChat = async () => { + // Clear stale state immediately — don't wait for API to return + setShowTaskLane(false) + setActiveQuestions([]) + setActiveActions([]) + setMessages([]) try { const session = await aiSessionsApi.createChatSession({ intake_type: 'free_text', @@ -275,11 +280,6 @@ export default function AssistantChatPage() { currentChatRef.current = session.session_id setChats(prev => [chatItem, ...prev]) setActiveChatId(session.session_id) - setMessages([]) - // Clear TaskLane from previous session - setShowTaskLane(false) - setActiveQuestions([]) - setActiveActions([]) } catch { toast.error('Failed to create chat') } @@ -315,11 +315,14 @@ export default function AssistantChatPage() { setMessages(prev => [...prev, { role: 'user', content: userMessage }]) setLoading(true) + const sentForChatId = activeChatId try { const response = await aiSessionsApi.sendChatMessage(activeChatId, { message: userMessage, upload_ids: completedUploadIds.length > 0 ? completedUploadIds : undefined, }) + // Guard: discard if user switched to a different chat while this was in flight + if (currentChatRef.current !== sentForChatId) return analytics.aiFeatureUsed({ feature: 'assistant_chat' }) setMessages(prev => [ ...prev, @@ -327,20 +330,20 @@ export default function AssistantChatPage() { ]) setChats(prev => prev.map(c => - c.id === activeChatId + c.id === sentForChatId ? { ...c, message_count: c.message_count + 2, title: c.message_count === 0 ? userMessage.slice(0, 100) : c.title, updated_at: new Date().toISOString() } : c ) ) // Load branches if fork was created - if (response.fork && activeChatId) { - branching.loadBranches(activeChatId) + if (response.fork && sentForChatId) { + branching.loadBranches(sentForChatId) } // Show task lane if AI sent questions or actions const hasQuestions = response.questions && response.questions.length > 0 const hasActions = response.actions && response.actions.length > 0 if (hasQuestions || hasActions) { - if (activeChatId) clearTaskState(activeChatId) + clearTaskState(sentForChatId) setActiveQuestions(response.questions || []) setActiveActions(response.actions || []) setShowTaskLane(true) @@ -377,19 +380,22 @@ export default function AssistantChatPage() { setMessages(prev => [...prev, { role: 'user', content: userMessage }]) setLoading(true) + const sentForChatId = activeChatId try { const response = await aiSessionsApi.sendChatMessage(activeChatId, { message: userMessage }) + // Guard: discard if user switched to a different chat while this was in flight + if (currentChatRef.current !== sentForChatId) return setMessages(prev => [ ...prev, { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows, fork: response.fork, actions: response.actions, questions: response.questions }, ]) - if (response.fork && activeChatId) { - branching.loadBranches(activeChatId) + if (response.fork && sentForChatId) { + branching.loadBranches(sentForChatId) } // Update task lane based on AI response const hasQuestions = response.questions && response.questions.length > 0 const hasActions = response.actions && response.actions.length > 0 - if (activeChatId) clearTaskState(activeChatId) + clearTaskState(sentForChatId) if (hasQuestions || hasActions) { setActiveQuestions(response.questions || []) setActiveActions(response.actions || []) @@ -430,6 +436,10 @@ export default function AssistantChatPage() { } const handleResumeNew = async (summary: string) => { + // Clear stale state immediately — don't wait for API to return + setShowTaskLane(false) + setActiveQuestions([]) + setActiveActions([]) try { const resumePrompt = `I'm continuing a previous troubleshooting session. Here's where we left off:\n\n${summary}\n\nPlease review this context and help me continue from where we stopped.` const session = await aiSessionsApi.createChatSession({