Files
brain-of-reese/.agent/phases/complete/06_story_loading_feedback.md
T

2.7 KiB

Phase 06 — Story: Loading Feedback & Progress

Story: .agent/user_stories/loading-feedback.md Context: .agent/PLAN.md §7.4 ("never stale" contract), §9

Goal

An unambiguous state machine — idle → thinking → streaming → done | error → idle — so the user always knows what's happening, and a stale Send button is impossible.

Implementation steps

  1. app.js — formalize the state machine (single setUiState(state) function driving: typing indicator, send button disabled/spinner/label, #send-status live text). Replace ad-hoc busy handling from Phase 03.
  2. Pre-token: typing indicator (role="status", aria-label="Brain of Reese is thinking"); after 10s pre-token, update the label with elapsed seconds (setInterval, cleared on state change).
  3. Streaming: first delta removes the typing indicator and starts appending to the answer bubble; button stays busy.
  4. Error paths: {"type":"error"} SSE event, non-2xx response, or 120s client-side timeout (clear on first delta) → red banner role="alert" ("Try again — if this persists, check the LLM is reachable") + state → idle.
  5. prefers-reduced-motion: CSS already slows animations — verify; add a static fallback for the dots if needed.
  6. Server: confirm the per-turn log line includes embed_ms and total_ms (add if Phase 03 omitted it).

UI Verification

Walk the full state machine by hand (dev server + mock LLM slow path): submit → indicator + "Thinking…" disabled button → live tokens → done (enabled, focused input). Kill the mock mid-stream → banner + recovery. Contrast of disabled button + spinner OK; reduced-motion pass.

Testing & Quality

  • Unit/integration: SSE error event serialization; timeout constant exported/testable; (JS logic is E2E-covered).
  • Coverage: >90% on app/.

Playwright Execution Phase

Run ONLY this story's suite:

uv run pytest tests/e2e/test_loading_feedback.py -v --no-cov

Implements the story mapping (mock LLM's 3s "pretend to think slowly" warm-up + a fixture that stops the mock): typing indicator visible during pre-token and gone by answer; button disabled→"Thinking…"→enabled "Send"; streaming appends (two-timestamp length check); LLM-down ⇒ role=alert banner + button recovered.

Success criteria

  • every in-flight state has a visible indicator; button never zombies
  • error + 120s timeout paths both recover cleanly
  • reduced-motion respected
  • unit + integration green, coverage >90%
  • story E2E green in isolation
  • committed

Commit

git add -A && git commit --no-gpg-sign -m "feat(ui): explicit chat state machine — typing indicator, streaming progress, timeout and error recovery"