phase: 111_chat_banner_retry
Build and Push Containers / build-and-push-app (push) Successful in 2m24s
Build and Push Containers / build-and-push-db (push) Successful in 14s

## Phase 111 Completion Report

**Implemented/Verified:**
- `#kb-banner` contains a `<button type="button" class="banner-retry" id="banner-retry">` (hidden by default, Retry label + SVG)
- `showErrorBanner(detail, opts)` reveals the button only when `opts.retryable` is true AND `lastBrainWrap` exists
- Turn-error path passes `{ retryable: true }`; all non-turn callers (share, save-doc, stale) remain text-only
- `clearErrorBanner()` re-hides the button
- `ERROR_HINT` changed from "Try again — …" to "If this persists, check the LLM is reachable."
- `.banner-retry` CSS styled as a pill (matching `.stale-regenerate` family)
- 12 source-assertion unit tests in `tests/unit/test_frontend_banner_retry.py`

**Test / Lint / Coverage:**
- `uv run pytest tests/unit/test_frontend_banner_retry.py -v --no-cov` → 12 passed
- `uv run pytest --cov=app --cov-report=term-missing` → 2362 passed, 99% coverage
- `uv run ruff check .` → All checks passed
- `uv run pyright` → 0 errors
- `tests/e2e/test_llm_retry.py` → 4 passed (in isolation)
- `tests/e2e/test_smoke.py` → 3 passed (in isolation)

**Completion Criteria:**
- ✅ Retry button visible after failed chat turn, re-runs last question
- ✅ Non-turn callers show text-only banner (no button)
- ✅ pytest green, coverage >90%, ruff + pyright clean
- ✅ Phase dir to be moved by pipeline gate

**Next pending phase:** `112_honesty_gate_weak_hits`
This commit is contained in:
2026-09-14 23:07:56 -04:00
parent f37c517590
commit 2683128876
17 changed files with 697 additions and 4 deletions
@@ -0,0 +1,42 @@
# Phase 111 — Chat error banner: a real Retry button (TODO L1)
**Source:** `TODO.md` L3–21 — "L1 — Chat error banner: 'Try again' is plain text, not a button (2026-09-15, brain-of-reese interactive test)"
**Story:** n/a (interactive-test follow-up fix; extends the phase-06 loading-feedback and phase-49/53 retry assets).
**Context:** `frontend/index.html:112` renders `#kb-banner` (chat page only — the Sources/document pages do not render it) with `#kb-banner-text`; `frontend/assets/app.js` — `showErrorBanner(detail)` (L2084) writes `${detail} ${ERROR_HINT}` as **plain text**; `ERROR_HINT` (L362) begins "Try again — …" so "Try again" reads as a clickable action but is not. `retryLastTurn(wrap)` (L2131) re-asks the last question in place (phase 49); `#stale-regenerate` (index.html:134, handler at app.js ~L1898) is the existing banner-button → `retryLastTurn` pattern.
## Objective
Give the chat-view error banner a real Retry control after a failed/dropped turn: the banner shows a visible Retry button that re-runs the last question without re-typing (reusing the phase-49 redo-in-place and the stale-banner button pattern). The banner text stops mimicking a button, and every existing `showErrorBanner` caller (share, save-doc, stale chat) keeps working text-only — the Retry button appears only on failed chat turns.
## Dependencies
- `110_fix_sse_db_pool_exhaustion` (complete) — pipeline predecessor (execution order) only; no code dependency (this phase touches `frontend/index.html`, `frontend/assets/app.js`, `frontend/assets/styles.css`, and frontend unit tests).
## Design (shared by all tasks — the executor reads this, not the chat)
- **Banner button (task 01):** add `<button type="button" class="banner-retry" id="banner-retry" hidden>` inside `#kb-banner` (after `#kb-banner-text`), mirroring the `#stale-regenerate` markup (same refresh SVG + visible "Retry" label). Hidden by default; `showErrorBanner(detail, opts)` gains an optional second arg — when the caller flags the error as a **failed chat turn** (the UI state-machine path at app.js:1281 `if (state === UI_STATE.error) showErrorBanner(errorDetail)`), the button is revealed and wired to `retryLastTurn(lastBrainWrap)` — the same last-brain-bubble targeting the `#stale-regenerate` handler uses. No retryable brain bubble → no button.
- **Copy:** `ERROR_HINT` becomes "If this persists, check the LLM is reachable." — the "Try again —" prefix moves to the button (the text must no longer read as a fake control).
- **Non-turn callers** (share failures L1857/L1870/L1892, save-doc L735/L742, stale L1620/L1950/L1962, …) pass no opts → text-only banner, no button — no behavior change for them.
- **CSS:** `.banner-retry` in `styles.css` reuses the `.stale-regenerate` pill look (same component family); the banner keeps `role="alert"`.
- **NOT touched:** the per-answer Retry pill (phase 49), the stale banner, `retryLastTurn` itself, the server, and the RAG/document views (no `#kb-banner` there — no split needed, ASSUMPTION in task 01).
## Tasks
1. `01_banner_retry_button.md` — banner Retry button markup + handler + hint-copy fix.
2. `02_banner_retry_tests.md` — frontend unit tests for the button's presence/handler + stale hint-copy assertions updated.
## Testing & Quality
- Unit: `tests/unit/test_frontend_banner_retry.py` (new, task 02) — house-style source assertions: `#banner-retry` exists in the `#kb-banner` markup (hidden by default, `type="button"`); `showErrorBanner` wires the click → `retryLastTurn`; the button is revealed only on the turn-error path; `clearErrorBanner` re-hides it; `ERROR_HINT` no longer starts with "Try again".
- E2E: no new file — the turn-error path is exercised by the existing `tests/e2e/test_llm_retry.py` and `tests/e2e/test_smoke.py` suites, which must stay green (no banner behavior change for non-turn callers).
- Coverage: **>90%** on `app/` (validate.sh gate; the frontend JS is pinned by the source-assertion unit tests — no app/ code changes in this phase).
## Completion Criteria
- [ ] After a dropped/failed chat turn, `#kb-banner` shows a visible Retry button; clicking it re-runs the last question without re-typing.
- [ ] Share/save-doc/stale-chat errors show a text-only banner (no button) — unchanged.
- [ ] `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` TOTAL >90%; `uv run ruff check . && uv run pyright` clean.
- [ ] One `--no-gpg-sign` commit; phase dir moved to `.agents/phases/complete/` by the pipeline gate.
## Locked decisions
- **A1 — Retry = re-ask the last question in place via the existing `retryLastTurn` (owner-confirmed 2026-09-14, roadmap confirmation).** No new retry mechanism; the phase-49 redo-in-place is reused.
- **A2 — the button is offered only on the UI state-machine's turn-error path (a dropped/failed chat turn); all other banner callers stay text-only (owner-confirmed 2026-09-14).** Matches L1's acceptance ("after a dropped/failed turn …").
## Commit
```bash
git add frontend/ tests/ .agents/phases/ && git commit --no-gpg-sign -m "fix(ui): give the chat error banner a real Retry button that re-asks the last question"
```