From d5122123c2ce08f6651e051868c230ea6275d7bc Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sun, 29 Mar 2026 12:53:16 +0000 Subject: [PATCH] fix: preserve task lane answers across page reload and browser close Two issues fixed: 1. TaskLane useEffect on [questions, actions] was resetting all tasks to pending with empty values, wiping restored user answers. Now checks sessionStorage for saved state before resetting. 2. selectChat was setting activeQuestions/activeActions before writing responses to sessionStorage, causing a race where TaskLane mounted with new props but empty sessionStorage. Now writes responses to sessionStorage first so TaskLane can restore them on prop change. The backend saveTaskLane debounce (2s) persists responses to the DB, and selectChat restores them via pending_task_lane.responses. This chain now survives browser close. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/components/assistant/TaskLane.tsx | 12 ++++++++++-- frontend/src/pages/AssistantChatPage.tsx | 9 +++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/assistant/TaskLane.tsx b/frontend/src/components/assistant/TaskLane.tsx index e361b66e..152890fc 100644 --- a/frontend/src/components/assistant/TaskLane.tsx +++ b/frontend/src/components/assistant/TaskLane.tsx @@ -147,8 +147,16 @@ export function TaskLane({ questions, actions, sessionId, onSubmit, onClose, loa return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current) } }, [sessionId, tasks]) // eslint-disable-line react-hooks/exhaustive-deps - // Reset when new tasks come in from AI response + // Reset when new tasks come in from AI response — but preserve saved state useEffect(() => { + if (sessionId) { + const saved = loadTaskState(sessionId) + if (saved && saved.length > 0) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: syncs derived state from prop changes + setTasks(saved) + return + } + } // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: syncs derived state from prop changes setTasks([ ...questions.map((q): QuestionResponse => ({ @@ -158,7 +166,7 @@ export function TaskLane({ questions, actions, sessionId, onSubmit, onClose, loa type: 'action', label: a.label, command: a.command, description: a.description, state: 'pending', value: '', })), ]) - }, [questions, actions]) + }, [questions, actions]) // eslint-disable-line react-hooks/exhaustive-deps const updateTask = (idx: number, updates: Partial) => { setTasks(prev => prev.map((t, i) => i === idx ? { ...t, ...updates } as TaskResponse : t)) diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index dc6fcb7b..ad6d41c1 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -232,16 +232,17 @@ export default function AssistantChatPage() { const q = detail.pending_task_lane.questions || [] const a = detail.pending_task_lane.actions || [] if (q.length > 0 || a.length > 0) { - setActiveQuestions(q) - setActiveActions(a) - setShowTaskLane(true) - // Pre-load user's saved responses into sessionStorage so TaskLane restores them + // Pre-load user's saved responses into sessionStorage BEFORE setting props + // so TaskLane can restore them on mount/prop-change const responses = (detail.pending_task_lane as Record).responses as unknown[] | undefined if (responses && responses.length > 0) { try { sessionStorage.setItem(`rf-tasklane-state:${chatId}`, JSON.stringify(responses)) } catch { /* ignore */ } } + setActiveQuestions(q) + setActiveActions(a) + setShowTaskLane(true) } } } catch {