chore(agent): track .agent/ planning tree in git
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:
@@ -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.
|
||||
Reference in New Issue
Block a user