# Phase 14 — Chat Survives a Refresh (localStorage) **Story:** `.agent/user_stories/chat-persistence.md` **Context:** owner report 2026-08-22 — "the chat disappears as soon as the browser refreshes. It should use local storage to track previous sessions." ## Goal The conversation is a **durable local session**: refresh, tab close, or a trip to Sources and back — the chat comes back exactly as left. ## Design - Storage key `bor.chat.v1` (versioned; a format bump = clean start). - Value: `{v: 1, messages: [{who: "user"|"brain", text, sources?, deflected?, suggestions?}]}` — **raw text** (re-rendered through the existing escape-first markdown on restore; never stored HTML). - Save points: user message on send; brain message on `done` (with sources/deflected/suggestions). A failed turn keeps the user message (the question is not lost) — consistent with the "never stale" contract. - Restore on load: re-render messages (user bubble; brain bubble with source chips, `is-deflected` styling, maybe-try chips), hide the empty state when non-empty. - Size bound: if the serialized state exceeds ~700k chars, drop oldest messages until it fits (localStorage quota is ~5MB; stay well under). - Failure-safe: every `localStorage` access in try/catch (private mode, quota) — chat keeps working with in-memory state only. - **"New chat"** button in the chat header (`#new-chat-btn`, ghost pill like a nav link, ≥44px, accessible name): clears the key + the message list, restores the empty state with suggestions. ## Implementation steps 1. `frontend/assets/app.js`: conversation model + save/restore/clear as above; wire into `handleSend` (push+save user on send; push+save brain on `done`); "New chat" handler. 2. `frontend/index.html`: `#new-chat-btn` in `.header-inner` after the nav (chat page only); live-region text reuse for clear confirmation. 3. `frontend/assets/styles.css`: `.new-chat-btn` (Phase-08 tokens, focus-visible, ≥44px, hover like `.nav-link`). 4. **PLAN §7.5:** new component ids (`#new-chat-btn`); §7.4 note: persistence is local-only (A10 stateless API unchanged — no server session). ## Locked decisions A10 (stateless API) untouched — persistence is browser-local only. A11 untouched (no library — raw `localStorage` JSON). ## Testing & Quality - E2E: `tests/e2e/test_chat_persistence.py` per the story mapping (fresh page fixture = fresh context, so tests are isolated by construction). - Regression: `test_chat_rag.py`, `test_suggestion_chips.py`, `test_loading_feedback.py` green in isolation (fresh contexts start with the empty state exactly as before). - Coverage gate unchanged (frontend-only phase). ## Commit ```bash git add -A .agent/ frontend/ tests/e2e/test_chat_persistence.py && git commit --no-gpg-sign -m "feat(ui): persist the chat conversation in localStorage — survives refresh and navigation, with a New chat reset" ```