chore(agent): track .agent/ planning tree in git
Build and Push Containers / build-and-push-app (push) Successful in 12s
Build and Push Containers / build-and-push-db (push) Successful in 10s

Remove the blanket .agent/ gitignore so the phase roadmap, user
stories, reports, and PLAN.md are versioned with the code. Only
runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log)
remain ignored. Update AGENTS.md git protocol rule to match.
This commit is contained in:
2026-09-01 10:18:22 -04:00
parent 5fa620fde5
commit 4971e2859d
818 changed files with 23964 additions and 4 deletions
@@ -0,0 +1,42 @@
# 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) — the `saved_chats` row, the Save flow, and the History table the share actions extend.
## Tasks
1. `01_share_token.md` — migration `0009_saved_chat_share_token` + the share/unshare/public-read API + the `/shared/<token>` page route + the middleware prefix.
2. `02_share_ui.md` — the Share button (chat page, save-then-share in one action) + the History table's share column (create/copy/unshare).
3. `03_shared_page.md` — `shared.html` + `shared.js`: the anonymous read-only rendering.
4. `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_at` untouched 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 pytest` green; coverage TOTAL >90%.
- [ ] `uv run pytest tests/e2e/test_share_chat.py -v --no-cov` green 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 pyright` clean.
- [ ] One `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/`.
## Locked decisions
- **Owner-locked (2026-08-29, roadmap confirmation):** share links are public by design (128-bit `uuid4` token; 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
```bash
git add -A .agent/ 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"
```