diff --git a/components/vibn-chat/chat-panel.tsx b/components/vibn-chat/chat-panel.tsx index 734ecb59..2d941f03 100644 --- a/components/vibn-chat/chat-panel.tsx +++ b/components/vibn-chat/chat-panel.tsx @@ -143,6 +143,10 @@ export function ChatPanel() { return localStorage.getItem("vibn-chat-open") !== "false"; }); const [threads, setThreads] = useState([]); + // threadsLoaded flips to true after the FIRST loadThreads() resolves. + // Used to gate the auto-create effect — without it we race the fetch + // and spawn an empty thread before history loads. + const [threadsLoaded, setThreadsLoaded] = useState(false); const [activeThread, setActiveThread] = useState(null); const [messages, setMessages] = useState([]); const [toolEvents, setToolEvents] = useState([]); @@ -188,7 +192,9 @@ export function ChatPanel() { const res = await fetch(`/api/chat/threads?workspace=${encodeURIComponent(workspace)}`); const data = await res.json(); setThreads(data.threads || []); - } catch { /* silent */ } + } catch { /* silent */ } finally { + setThreadsLoaded(true); + } }, [workspace, status]); useEffect(() => { @@ -225,14 +231,34 @@ export function ChatPanel() { } catch { /* silent */ } }, []); - // Auto-create first thread + // Auto-resume previous thread (or create a fresh one if the user has + // never chatted in this workspace). We MUST wait for threadsLoaded + // before deciding — otherwise we race the fetch and spawn an empty + // thread before history arrives. Last-active thread is restored from + // localStorage so a page reload (deploy, refresh) lands the user back + // in the conversation they were in. useEffect(() => { - if (open && status === "authenticated" && workspace && threads.length === 0 && !activeThread) { + if (!open || status !== "authenticated" || !workspace) return; + if (!threadsLoaded) return; + if (activeThread) return; + + if (threads.length === 0) { newThread(); - } else if (open && threads.length > 0 && !activeThread) { - loadThread(threads[0].id); + return; } - }, [open, status, workspace, threads.length, activeThread, newThread, loadThread]); + + const savedKey = `vibn-chat-active-thread:${workspace}`; + const saved = typeof window !== "undefined" ? localStorage.getItem(savedKey) : null; + const target = saved && threads.some((t) => t.id === saved) ? saved : threads[0].id; + loadThread(target); + }, [open, status, workspace, threadsLoaded, threads, activeThread, newThread, loadThread]); + + // Persist active thread so reload re-opens the same conversation. + useEffect(() => { + if (typeof window === "undefined" || !workspace) return; + const savedKey = `vibn-chat-active-thread:${workspace}`; + if (activeThread) localStorage.setItem(savedKey, activeThread); + }, [activeThread, workspace]); useEffect(() => { scrollToBottom(); }, [messages, toolEvents, scrollToBottom]);