Files
brain-of-reese/.agents/phases/complete/50_chat_history/00_phase.md
T
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

5.3 KiB

Phase 50 — Save & View Chat History

Source: TODO.md L5 — "Need a way to save and view chat history in a new page, then return to that history with a click" Story: n/a (TODO-derived — owner roadmap confirmation 2026-08-29) Context: A10 (revised, phase 16) keeps the public chat API stateless, and phase 14 locked conversation persistence to browser-local localStorage (bor.chat.v1). This phase records an owner-locked extension (AGENTS.md rule 3, owner permission 2026-08-29): conversations the owner explicitly Saves are stored in a new Postgres saved_chats table — /api/chat itself stays stateless, and nothing is stored about a conversation that was not saved. The admin gate is app/core/auth.require_admin (phase 16, the steering router's pattern). The localStorage record shape ({who, text, sources?, deflected?, suggestions?, thinking?, tools?, stopped?}) is the stored messages payload, so a saved chat restores pixel-identical through the existing renderStoredMessage path. The new page follows the phase-34 shared-header contract (every page carries the identical nav block; admin-only links ship hidden and header.js reveals them) and AGENTS.md rule 5 (a full-width table — no skinny wasted-space list).

Objective

The owner can Save the current conversation, see every saved chat on a new History page (full-width table), click one to return to the chat with that conversation loaded, and delete a saved chat.

Dependencies

  • 48_stop_generation / 49_retry_answer (todo, sequential) — no shared-file conflicts beyond app.js; ordering keeps the chat UI stable while the save/load plumbing lands.
  • 14_chat_persistence (complete) — the record shape + the renderStoredMessage restore a saved chat reuses.
  • 16_admin_auth + 34_consistent_navbar (complete) — the require_admin gate + the one-bar nav contract.
  • Owner permission (2026-08-29): the A10 extension — a new saved_chats table for explicitly saved conversations (see Context).

Tasks

  1. 01_saved_chat_model.md — the SavedChat model + migration 0008_saved_chats (+ migration test).
  2. 02_chats_api.md — admin-only CRUD under /api/chats + schemas + integration tests.
  3. 03_save_chat_ui.md — the chat page: Save button, ?chat=<id> load, upsert semantics, live-region feedback, absent for anonymous.
  4. 04_history_page.md — history.html + history.js (full-width table, Open + two-step Delete), the #nav-history admin-only nav link on every page, the cache-busting page registration, CSS.
  5. 05_e2e_chat_history.md — the story Playwright suite + regressions + commit.

Testing & Quality

  • Integration: tests/integration/test_chats_api.py (the CRUD contract: 403 anonymous, create auto-title, list order, get, put replacement, delete 404/204, message-shape validation).
  • Integration: tests/integration/test_migration_0008.py (the house migration-test pattern from test_migration_0007.py).
  • Coverage: >90% on app/.
  • E2E (mandatory, A16): tests/e2e/test_chat_history.py, run in isolation.

Completion Criteria

  • Admin: Save on the chat page stores the conversation (auto-title = first question, 120-char cap); re-Save on the same conversation updates the same row; New chat unlinks.
  • /history.html (admin) lists saved chats in a full-width table (Title, Messages, Updated, Actions); a row's title opens /?chat=<id> and the chat renders the stored conversation (sources, thinking, stopped notes, deflection chips — pixel-identical to the local restore); a subsequent Save updates that row.
  • Delete removes the row (inline two-step confirm, no window.confirm); an unknown id 404s; GET /api/chats + the served /history.html carry the cache-busting contract (no-cache + ?v= rewrite).
  • Anonymous: no Save button, no History nav link, /api/chats* → 403, /history.html shows the gated state without fetching /api/chats.
  • uv run pytest green; coverage TOTAL >90%.
  • uv run pytest tests/e2e/test_chat_history.py -v --no-cov green in isolation (DB up).
  • Regression E2E suites green in isolation: test_chat_persistence.py, test_nav_consistency.py, test_shared_header.py, test_admin_auth.py, test_cache_busting.py.
  • uv run ruff check . && uv run pyright clean.
  • One --no-gpg-sign commit; phase dir moved to .agents/phases/complete/.

Locked decisions

  • Owner-locked extension (2026-08-29, recorded per AGENTS.md rule 3): saved_chats in Postgres stores only conversations the owner explicitly saves; /api/chat stays stateless; phase 14's local persistence is unchanged (the localStorage session keeps working exactly as before — saving is an additional, explicit action).
  • Owner-locked (2026-08-29): Save/History is admin-only (no account system — anonymous rows would be unfindable); absent-not-hidden for anonymous (phase 16); auto-title, no rename UI in v1 (the schema still accepts an optional title); re-Save = upsert of the same row; ?chat=<id> replaces the local conversation and links it; inline two-step delete confirm (no window.confirm).
  • A16/A17 honoured — one story E2E suite, one atomic commit.

Commit

git add -A .agents/ app/ alembic/versions/ frontend/ tests/ && git commit --no-gpg-sign -m "feat(chat): save and view chat history — admin-only saved_chats, History page, open-a-chat return"