## 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`