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.
This commit is contained in:
2026-09-05 10:57:07 -04:00
parent 766702c750
commit dbf2af26c6
1118 changed files with 664 additions and 664 deletions
@@ -0,0 +1,31 @@
# Task 02 — Share button (chat) + the History share column
**Phase:** `51_share_chat` · **Source:** `TODO.md:6` — "Need a way to share a chat with a link so others can see it anonymously."
**Story:** n/a (TODO-derived)
## Objective
Two share entry points for the admin: the **Share** button on the chat page (an unsaved conversation is saved + shared in one action) and a Share column on the History table (create link / copy / unshare) — both copy the link with a clipboard + inline-link fallback.
## Work
1. `frontend/index.html` — beside `#save-chat-btn`, `#share-chat-btn` (the same ghost-pill family: an inline link/share SVG + the label "Share", `aria-label="Share chat"`, ships `hidden` — revealed for admin exactly like Save); a comment documenting the save-then-share contract (2026-08-29, `TODO.md` L6).
2. `app/schemas.py` + `app/api/chats.py` (the small server side of this task): `SavedChatCreate` gains `share: bool = False`; the create route, when `share` is true, sets `share_token = uuid.uuid4()` in the same commit and the response carries `share_url`. `SavedChatOut` and `SavedChatRow` each gain `share_url: str | None` (`None` → absent from the JSON) — the list endpoint populates it, so the History column renders from `GET /api/chats` without a second fetch.
3. `frontend/assets/app.js`:
- **`shareCurrentChat()`** — the `#share-chat-btn` handler (the same empty-conversation no-op guard as Save): if `currentChatId` is set → `POST /api/chats/<id>/share`; else → `POST /api/chats` with `{ messages: conversation, share: true }` → `currentChatId = created.id` (one action saves **and** shares — owner-locked). On success: copy the absolute URL of `share_url` — `navigator.clipboard.writeText(...)` in a try; on success the live region reads "Share link copied."; on failure (a non-secure http origin rejects the clipboard) render the **inline fallback**: a transient link field (an `<a>` styled as a select-on-focus field, carrying the full URL) near the status line + the live region "Share link ready — copy it from the field." (owner-locked fallback). On 403/5xx/network: `showErrorBanner` with an actionable line.
- **Reveal on boot:** `#share-chat-btn` joins the same admin-reveal block as `#save-chat-btn`.
- **Header comment:** the share contract (2026-08-29, `TODO.md` L6).
4. `frontend/history.html` + `frontend/assets/history.js` — the **Share column** between Updated and the Actions/Delete cell (the `th` + the per-row cell; the table stays full-width):
- row already shared (`share_url` present): a **Copy** button (the same clipboard + fallback helper — the per-page duplication house style: `history.js` keeps its own ~10-line copy of the helper rather than a new shared module) and an **Unshare** button (inline two-step, the phase-50 Delete-confirm pattern: "Unshare? [Yes] [No]" → `POST /api/chats/<id>/unshare` → the cell re-renders to the unshared state + the live region).
- row unshared: a **Create link** button → `POST /api/chats/<id>/share` → the cell re-renders to the shared state (Copy + Unshare) and the link is offered for copying (same fallback pattern).
5. `frontend/assets/styles.css` — `.share-chat-btn` (the pill family), the `.history-share` cell buttons (the Tune/Retry-family ghost buttons, ≥44px comfortable, focus-visible), `.share-link-fallback` (the inline link field — input-like look, select-on-focus), reusing the phase-50 `.history-confirm` styles for the unshare two-step.
6. `tests/integration/test_chats_api.py` — extend: create-with-share (the response carries `share_url` matching the token shape and the row is immediately publicly readable), create-without-share (no `share_url`), the list rows carry `share_url` when shared and absent otherwise, `GET /{chat_id}` carries it too.
7. Frontend source pins (house pattern): the `shareCurrentChat` branches (linked → POST share; unlinked → create-with-share + `currentChatId` set); the clipboard try → fallback element on rejection; the History cell's three states (unshared / shared / confirming-unshare) + the two-step unshare; `#share-chat-btn` ships hidden and reveals only for admin.
- ASSUMPTION (owner-locked 2026-08-29): the Share button saves + shares an unsaved conversation in one action; the clipboard copy has the inline-link fallback; unshare is inline two-step.
## Testing & Quality
- Integration: as above; full suite green.
- Coverage: **>90%** on `app/` (the create-with-share branch covered).
## Completion Criteria
- [ ] Admin: the Share button works on a saved (linked) and an unsaved conversation; the History column creates / copies / unshares per row.
- [ ] `uv run pytest` green; `uv run ruff check . && uv run pyright` clean.