Files
brain-of-reese/.agent/user_stories/suggestion-chips.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

2.8 KiB
Raw Blame History

Story: Suggestion Chips

Phase: 05_story_suggestion_chips.md · E2E: tests/e2e/test_suggestion_chips.py

Narrative

As a user who opens the chat for the first time (or after a deflection), I want a few concrete example questions right in front of me — so I immediately understand what Brain is good at and can start with zero friction.

  • Given I land on the chat page
  • When the app is healthy
  • Then I see 3–4 suggestion chips drawn from GET /api/suggestions (defaults in settings, tuned to the real Homelab topics), and clicking one fills the composer and submits it.

Acceptance criteria

  1. GET /api/suggestions returns the configured list (settings-driven, overridable via BOR_SUGGESTIONS JSON env).
  2. Chips render in the empty state as <button class="suggestion-chip"> (real buttons, not links/divs) with role="list" container + role="listitem" items; aria-label="Suggested questions" on the group.
  3. Click behavior: fills #message-input, focuses it, and submits (one tap → answer). Keyboard: Tab to chip, Enter activates.
  4. After the first user message the empty state (and its chips) is replaced by the conversation; chips re-appear only on deflection (see honest-deflection story).
  5. If /api/suggestions fails, the chat still works (progressive enhancement — no chips, no error spam).
  6. Mobile: chips become a horizontally scrollable single row (no wrapping into the composer's territory).

UI Visualization & Structure

  • Chips: pill (border-radius: 999px), bg --brand-soft, text --brand-ink (≥6:1), 1px --line border, min-height 44px, comfortable padding 0.55rem 1rem; hover deepens bg; :active scales 0.98.
  • Desktop: flex-wrap: wrap, centered under the empty-state subcopy, gap 0.5rem.
  • Mobile (≤640px): flex-wrap: nowrap; overflow-x: auto single row, scrollbar-width: thin, chips flex: 0 0 auto (thumb-friendly, no accidental double-tap on wrapped lines).
  • Default suggestion copy (tune to real docs in this phase):
    1. "How is my Kubernetes cluster set up?"
    2. "What's my backup strategy?"
    3. "How do I deploy a new service?"
    4. "What's currently running in the homelab?"

Playwright Mapping Rule

Test Scenario → tests/e2e/test_suggestion_chips.py:

  1. test_onboarding_chips_render — goto /, assert ≥3 .suggestion-chip visible inside #suggestions (role=list) with non-empty text.
  2. test_chip_click_submits — click the first chip; assert a user bubble with the chip's exact text appears and the brain reply (mock) follows.
  3. test_chips_keyboard_accessible — Tab from the page start reaches the first chip; Enter submits it.
  4. test_chips_mobile_row — at 375px viewport, the chip row is horizontally scrollable (scrollWidth > clientWidth or single-line height check) and no chip is cut vertically.