diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index ed39d848..58d76522 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -97,7 +97,21 @@ export default function AssistantChatPage() { const [logContent, setLogContent] = useState('') const [pendingUploads, setPendingUploads] = useState([]) const [isDragOver, setIsDragOver] = useState(false) + // Task-lane mount restoration is gated on (a) the persisted chatId + // matching whatever activeChatId resolved to, AND (b) the page not being + // entered with a prefill in location.state. The prefill case means we're + // about to create a brand-new session and discard the previous one's + // task lane anyway — restoring it just causes the previous chat's + // questions/actions to flash on the first paint before sendPrefill's + // resetSessionDerivedState clears them. Same logic for the bell-icon + // pickup flow (?pickup=true): the senior is entering an unrelated + // session and any leftover task-lane meta from their own prior chat is + // noise. Both gates collapse to "are we about to leave the previous + // chat behind?" — if yes, start clean. + const incomingPrefill = !!(location.state as { prefill?: string } | null)?.prefill + const skipTaskLaneRestore = incomingPrefill || isPickup const [activeQuestions, setActiveQuestions] = useState(() => { + if (skipTaskLaneRestore) return [] try { const saved = sessionStorage.getItem('rf-tasklane-meta') if (saved) { const d = JSON.parse(saved); if (d.chatId === activeChatId) return d.questions || [] } @@ -105,6 +119,7 @@ export default function AssistantChatPage() { return [] }) const [activeActions, setActiveActions] = useState(() => { + if (skipTaskLaneRestore) return [] try { const saved = sessionStorage.getItem('rf-tasklane-meta') if (saved) { const d = JSON.parse(saved); if (d.chatId === activeChatId) return d.actions || [] } @@ -112,6 +127,7 @@ export default function AssistantChatPage() { return [] }) const [showTaskLane, setShowTaskLane] = useState(() => { + if (skipTaskLaneRestore) return false try { const saved = sessionStorage.getItem('rf-tasklane-meta') if (saved) { const d = JSON.parse(saved); return d.show === true && d.chatId === activeChatId } @@ -479,6 +495,16 @@ export default function AssistantChatPage() { // Phase 9: tab strip reset setChatTab('chat') setScriptBuilderHasProgress(false) + // Belt-and-braces: also wipe the persisted task-lane meta. Without this, + // a remount or page reload before the next AI response can re-hydrate + // the previous session's questions/actions from sessionStorage even + // though the in-memory state has been cleared. The persistence effect + // re-saves on the next state change anyway, so the only window where + // sessionStorage is empty is between this reset and the next response — + // which is exactly the window where stale-tag leakage was happening. + try { + sessionStorage.removeItem('rf-tasklane-meta') + } catch { /* ignore */ } }, []) // Phase 2 facts — fetch + handlers. `refreshFacts` is called from selectChat