Files
brain-of-reese/.agent/user_stories/loading-feedback.md
T
ducoterra 022da8e2bc feat: scaffold Brain of Reese — FastAPI RAG chat over Postgres 17 + pgvector
Foundation (phase 01, verified):
- FastAPI app: /api/health, /api/suggestions, /api/chat (placeholder),
  static frontend served locally (no CDN)
- Postgres 17 + pgvector via db/Containerfile + compose.yaml
  (podman compose up -d db), Alembic initial migration (documents,
  chunks with vector(768), query_log)
- LLM client targeting https://aipi.reeseapps.com/v1 (turbo/embed);
  scripts/llm_probe.py verified models + 768-dim embeddings live
- Conditional debugpy: imported only when DEBUGPY=1 (attach on demand,
  :5678); logging config for clean single-line logs
- Frontend shell: mobile-first chat + Sources pages, tokens, a11y baselines
- Tests: 24 unit+integration (99% coverage on app/), ruff + pyright clean,
  Playwright smoke E2E (3 tests) against a deterministic mock LLM
- Planning: .agent/PLAN.md (architecture + LOCKED decisions), AGENTS.md,
  6 user stories, 7 phase files (one story / one phase / one Playwright
  suite each)
2026-08-21 13:42:21 -04:00

62 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story: Loading Feedback & Progress
**Phase:** `06_story_loading_feedback.md` · **E2E:** `tests/e2e/test_loading_feedback.py`
## Narrative
As **a user**, local LLM answers can take 10–30+ seconds. I want to *always*
know Brain is working — a clear "thinking" state, live progress as tokens
arrive, and a definitive end — so I never stare at a stale Send button
wondering if it's stuck.
- **Given** I submit a question
- **When** the answer is in flight (pre-token, streaming, or erroring)
- **Then** the UI shows an unambiguous in-progress state, transitions
cleanly to done/error, and the send button is never left in a zombie state.
## Acceptance criteria
1. **Pre-token:** typing-indicator bubble (3 animated dots, `role="status"`,
`aria-label="Brain of Reese is thinking"`) + send button disabled with
spinner and label "Thinking…".
2. **Streaming:** first delta replaces the typing indicator; text appends
live; button stays busy until `done`.
3. **Done:** button re-enabled, label "Send", input focused back.
4. **Error paths:** (a) LLM/DB error → red banner `role="alert"` with retry
hint, button re-enabled; (b) **120s client timeout** → same error state
(guard against a hung stream); (c) page reload mid-stream loses the
stream but the composer is usable again (state is turn-local).
5. **Slow-model E2E:** the mock LLM's 3s warm-up (message containing
"pretend to think slowly") must show the typing indicator for ≥2s before
any text appears.
6. Server side: per-turn log includes `embed_ms` / total `total_ms` (PLAN
§9) so "slow" is diagnosable.
7. `prefers-reduced-motion`: dots/spinner still visible (slower/static) —
feedback is never removed, only calmed.
## UI Visualization & Structure
- State machine (single source of truth in `app.js`):
`idle → thinking → streaming → done | error → idle`.
- Typing indicator: 8px dots, `--ink-soft`, staggered 1.2s bounce; inside a
normal brain bubble (same geometry as answers) so the layout doesn't jump.
- Send button busy style: `background: #a5b4fc` (disabled contrast still
fine — it's a disabled state), 16px spinner (2.5px ring, white top
arc), label swap "Send" ↔ "Thinking…".
- Error banner: `--err-bg/--err-ink/--err-line`, top of chat shell,
`role="alert"`, includes the actionable hint ("Try again — if this
persists, check the LLM is reachable").
- Elapsed-time hint: after 10s still pre-token, the typing bubble's aria
label becomes "…still thinking (12s)" — SR users are never left guessing.
## Playwright Mapping Rule
**Test Scenario → `tests/e2e/test_loading_feedback.py`** (mock LLM):
1. `test_typing_indicator_during_slow_think` — ask "pretend to think slowly
then tell me about kubernetes"; assert `#typing-indicator` visible within
500ms of submit, still visible at ~2s, gone by the time the answer text
is present.
2. `test_button_state_machine` — during the in-flight turn: `#send-btn`
disabled + label "Thinking…"; after done: enabled + "Send".
3. `test_streaming_appends_live` — capture bubble text at two timestamps
during the stream; second length > first (progress is visible).
4. `test_error_banner_on_llm_down` (fixture stops the mock) — submit;
assert `role=alert` banner visible and button re-enabled within timeout.