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.
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 beyondapp.js; ordering keeps the chat UI stable while the save/load plumbing lands.14_chat_persistence(complete) — the record shape + therenderStoredMessagerestore a saved chat reuses.16_admin_auth+34_consistent_navbar(complete) — therequire_admingate + the one-bar nav contract.- Owner permission (2026-08-29): the A10 extension — a new
saved_chatstable for explicitly saved conversations (see Context).
Tasks
01_saved_chat_model.md— theSavedChatmodel + migration0008_saved_chats(+ migration test).02_chats_api.md— admin-only CRUD under/api/chats+ schemas + integration tests.03_save_chat_ui.md— the chat page: Save button,?chat=<id>load, upsert semantics, live-region feedback, absent for anonymous.04_history_page.md—history.html+history.js(full-width table, Open + two-step Delete), the#nav-historyadmin-only nav link on every page, the cache-busting page registration, CSS.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 fromtest_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.htmlcarry the cache-busting contract (no-cache +?v=rewrite). - Anonymous: no Save button, no History nav link,
/api/chats*→ 403,/history.htmlshows the gated state without fetching/api/chats. uv run pytestgreen; coverage TOTAL >90%.uv run pytest tests/e2e/test_chat_history.py -v --no-covgreen 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 pyrightclean.- One
--no-gpg-signcommit; phase dir moved to.agents/phases/complete/.
Locked decisions
- Owner-locked extension (2026-08-29, recorded per AGENTS.md rule 3):
saved_chatsin Postgres stores only conversations the owner explicitly saves;/api/chatstays 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 (nowindow.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"