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:
@@ -0,0 +1,31 @@
|
||||
# Task 01 — The Retry button (redo in place)
|
||||
|
||||
**Phase:** `49_retry_answer` · **Source:** `TODO.md:4` — "Need a retry button to retry the last answer, like a redo button"
|
||||
**Story:** n/a (TODO-derived)
|
||||
|
||||
## Objective
|
||||
`handleSend` is split so a question can be re-run without re-adding it, and a Retry button on the last brain bubble performs that redo: old answer gone (DOM + storage), fresh answer streaming into its place.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/app.js`:
|
||||
- **Extract `runTurn(text, { reask = false })`** from `handleSend`: everything from the `setUiState(thinking)` / `armTurnTimeout` point through the `finally` settle moves into `runTurn`; the `reask` flag skips (a) the `addMessage("user", …)` append and (b) the `conversation.push({ who: "user", … })` + `saveConversation()` (the question is already in both). `handleSend` keeps the form-level pre-work — the `!text` guard, the **in-flight guard that calls `stopTurn()`** (phase 48), `clearErrorBanner()`, the input clear + autoGrow — then appends + persists the user message and calls `runTurn(text, { reask })`. The turn-local resets (`acc`, `thinkingAcc`, `sawThinking`, `sawDone`, `toolAcc`, `stoppedByUser`, `turnAbort`) stay turn-scoped exactly as phase 48 left them. **No behavior drift for the normal send path**: a plain send must produce byte-identical DOM/SSE/persistence behavior to today (the regression suite is the proof).
|
||||
- **`appendRetryButton(wrap)`** — the house `appendTuneButton` pattern for the meta row (reuse the `.msg-meta` row; `role=list` → `role=listitem` where the row is a list; one per bubble; an inline redo-glyph SVG + "Retry" text — the text is the accessible name). **Not** admin-gated (owner-locked: all visitors). Click handler `retryLastTurn(wrap)`:
|
||||
- in-flight guard: if `uiState` is thinking/streaming → no-op (owner-locked).
|
||||
- find the record: the last brain record in `conversation` (and that `wrap` is the rendered wrap of that record — the button only ever sits on the last bubble, but the guard keeps a stale click harmless).
|
||||
- **redo in place:** pop the brain record from `conversation`; `saveConversation()` immediately (a crash between the pop and the fresh `done` must never resurrect the replaced answer — what the user saw, the removed answer, is what is stored; the question remains); remove `wrap` from the DOM; locate the preceding **user** record's `text` (the record immediately before the popped one — invariant: every brain record follows its user record); call `runTurn(text, { reask: true })`. No `sendStatus` banner; no scroll (phase-42 contract — no auto-scroll; the fresh bubble lands where the old one was).
|
||||
- **Last-bubble-only management:** a helper `markLastRetryable()` — removes any existing `.retry-btn` from every rendered `.msg-meta`, then appends the Retry button to the last brain bubble (only when it has a preceding user record — always true in practice). Call sites: on `done` (after `appendTuneButton`), on the empty-answer fallback path, on the **stop finalize** path (phase 48 — the stopped partial is the prime retry candidate), and once at the end of `restoreConversation()` (after all records are rendered). `startNewChat`'s list reset removes everything anyway — no change there.
|
||||
- **Header comment:** note the Retry contract (2026-08-29, `TODO.md` L4).
|
||||
2. `frontend/assets/styles.css` — `.retry-btn`: the exact visual family of `.tune-btn` (same size/spacing/focus-visible/hover; ≥44px comfortable via the meta-row padding, as Tune has) with the redo glyph in the phase-08 palette (ink-soft → ink on hover), so the two meta actions read as a pair.
|
||||
3. `frontend/index.html` — no markup change (the button is JS-injected like Tune); update the messages-section comment to mention the meta-row actions (Tune — admin; Retry — everyone).
|
||||
4. Frontend source pins (house pattern, extend `tests/unit/test_frontend_feedback.py` or a sibling): the `runTurn` signature + the `reask` skips (no user append/push when reask); `appendRetryButton`'s no-admin-gate + one-per-bubble; `markLastRetryable`'s remove-then-append; the pop → save → rerun order in `retryLastTurn`; the in-flight no-op.
|
||||
|
||||
- ASSUMPTION (owner-locked 2026-08-29): redo-in-place — the old answer is replaced (DOM + persisted record), the question is re-sent without duplication; only the last brain bubble carries the button; available to all visitors; inert while a turn is in flight.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: source pins as above; full suite green.
|
||||
- Coverage: **>90%** on `app/` (unchanged — frontend-only).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The plain-send path is byte-identical to pre-task behavior (regression: `test_chat_rag.py` E2E green in isolation).
|
||||
- [ ] Retry on a completed / deflected / stopped answer redoes in place (unit-pinned now, E2E in task 02).
|
||||
- [ ] `uv run pytest` green; `uv run ruff check . && uv run pyright` clean.
|
||||
Reference in New Issue
Block a user