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)
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user