Files
ducoterra dbf2af26c6 refactor(agents): migrate .agent/ planning tree to .agents/
Standardize on the .agents/ directory (shared with project skills):
phases/, user_stories/, reports/, screenshots/, validate.sh, and
phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves
history; runtime artifacts move alongside).

Updates every reference in AGENTS.md, README.md, .gitignore, app
docstrings, and test story headers. Historical KB content in data/
and the runtime pipeline.log transcript are left untouched.
2026-09-05 10:57:07 -04:00

2.9 KiB

Phase 14 — Chat Survives a Refresh (localStorage)

Story: .agents/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

git add -A .agents/ 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"