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.
4.0 KiB
Phase 51 — Share a Chat by Link (Anonymous View)
Source: TODO.md L6 — "Need a way to share a chat with a link so others can see it anonymously."
Story: n/a (TODO-derived — owner roadmap confirmation 2026-08-29)
Context: Builds on phase 50's saved_chats rows: sharing is a token on a saved chat. The public surface is a new anonymous page /shared/<token> (a real route — the static mount cannot serve a dynamic path) rendering a read-only copy of the conversation through the same record shape (thinking block, tool lines, source chips, stopped note) — no composer, no controls. The documents API is admin-only (phase 16), so a guest's source chips are plain text (owner-locked). The cache-busting middleware treats known HTML paths (the HTML_PAGES tuple in app/core/caching.py) as revalidate + ?v=-rewrite pages; the shared page joins that contract by path prefix.
Objective
The owner can turn a saved chat into a public link (/shared/<token>); anyone with the link sees the conversation read-only, anonymously; unsharing revokes it.
Dependencies
50_chat_history(todo) — thesaved_chatsrow, the Save flow, and the History table the share actions extend.
Tasks
01_share_token.md— migration0009_saved_chat_share_token+ the share/unshare/public-read API + the/shared/<token>page route + the middleware prefix.02_share_ui.md— the Share button (chat page, save-then-share in one action) + the History table's share column (create/copy/unshare).03_shared_page.md—shared.html+shared.js: the anonymous read-only rendering.04_e2e_share_chat.md— the story Playwright suite + regressions + commit.
Testing & Quality
- Integration: share/unshare/public-read contract (token shape, idempotent share, unshare revokes, a wrong token 404s, no admin needed to read,
updated_atuntouched by share/unshare) +tests/integration/test_migration_0009.py. - Coverage: >90% on
app/. - E2E (mandatory, A16):
tests/e2e/test_share_chat.py, run in isolation.
Completion Criteria
- Admin: the Share button on the chat page (saved or unsaved conversation) and the History row both produce/copy the
/shared/<token>link; an unsaved conversation is saved + shared in one action. - A fresh anonymous context opening
/shared/<token>sees the full conversation read-only (thinking collapsed, tool lines, the stopped note where present, source chips as plain text) with no composer, Save, Share, Tune, or Retry anywhere; a wrong/revoked token shows the "invalid or revoked" state. - Unshare revokes: the same URL shows the invalid state afterwards; the served shared page carries the cache-busting contract (no-cache +
?v=rewrite). uv run pytestgreen; coverage TOTAL >90%.uv run pytest tests/e2e/test_share_chat.py -v --no-covgreen in isolation (DB up).- Regression E2E suites green in isolation:
test_chat_history.py,test_chat_persistence.py,test_smoke.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 (2026-08-29, roadmap confirmation): share links are public by design (128-bit
uuid4token; unshare revokes); the shared page renders the full conversation read-only (thinking collapsed) with zero interactive controls; source chips are plain text (guests cannot open documents — the documents API is admin-only); clipboard copy with an inline-link fallback (a homelab http origin may not be a secure context). - The A10 extension (phase 50, owner permission 2026-08-29) unchanged — sharing reuses the already-stored row; no new storage beyond the token column.
- 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): share a chat by link — anonymous read-only /shared/<token> page, share/unshare"