From a770faf6eb34f2b3de4943571237e6d616e9c33b Mon Sep 17 00:00:00 2001 From: jules Date: Sat, 2 May 2026 19:59:32 +1000 Subject: [PATCH] fix(ai): clear conversation actually clears MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useChat exposes reset() which calls setMessages(opts.initialMessages), and the AI page passes initialLive.current as initialMessages — a ref captured once on mount from localStorage. resetAndClear was calling reset() then clearLive(). Sequence problem: reset() → setMessages(initialLive.current) // populated old array clearLive() → localStorage.removeItem(LIVE_KEY) // does nothing to memory The ref still held the original messages, so reset re-seeded them and the conversation appeared to "come back" the moment you typed anything (or sometimes immediately, depending on render timing). Fix: blank the ref, clear localStorage, and call setMessages([]) directly. reset() is no longer needed at this call site so it's been dropped from the useChat destructure. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/routes/ai.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/routes/ai.tsx b/app/routes/ai.tsx index 34dd09d..343b66e 100644 --- a/app/routes/ai.tsx +++ b/app/routes/ai.tsx @@ -378,7 +378,7 @@ function ChatSurface({ if (initialLive.current === null) { initialLive.current = loadLive() ?? [] } - const { messages, setMessages, send, continueChat, abort, isStreaming, reset } = useChat({ + const { messages, setMessages, send, continueChat, abort, isStreaming } = useChat({ system: systemPrompt, initialMessages: initialLive.current, }) @@ -389,11 +389,20 @@ function ChatSurface({ saveLive(messages) }, [messages]) - // Wrap reset so "Clear conversation" also drops the persisted snapshot. + // "Clear conversation" must drop three things in lockstep: + // 1. The in-memory messages (setMessages([])). + // 2. The persisted live snapshot (clearLive()). + // 3. The initialLive ref — otherwise on the next render or hook + // reconciliation, useChat's reset() would re-seed from the + // captured-at-mount initialMessages and the old conversation + // pops back. (This was the bug.) + // We deliberately don't call useChat's reset() here because reset + // restores to opts.initialMessages, which we want to be empty. const resetAndClear = useCallback(() => { - reset() + initialLive.current = [] clearLive() - }, [reset]) + setMessages([]) + }, [setMessages]) // Auto tool-loop using native function calls. Reads run automatically; // writes are held in `pendingConfirm` until the operator clicks Confirm