chore(agent): track .agent/ planning tree in git
Remove the blanket .agent/ gitignore so the phase roadmap, user stories, reports, and PLAN.md are versioned with the code. Only runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log) remain ignored. Update AGENTS.md git protocol rule to match.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# Story: Thinking Display
|
||||
|
||||
**Phase:** `17_thinking_display` · **E2E:** `tests/e2e/test_thinking_display.py`
|
||||
|
||||
## Narrative
|
||||
|
||||
As **a user**, my local reasoning model *thinks* before it answers — and
|
||||
today those 10–30 seconds are pure silence. I want to *see* Brain think:
|
||||
its reasoning streaming live, tucked away once the answer starts — so long
|
||||
turns feel transparent instead of frozen.
|
||||
|
||||
- **Given** I submit a question the model reasons about
|
||||
- **When** the turn streams reasoning ahead of (or around) the answer
|
||||
- **Then** a collapsible "Thinking" block streams open above the answer
|
||||
bubble, auto-collapses on the first answer token, and stays
|
||||
user-toggleable afterwards — while turns that emit no reasoning render
|
||||
exactly as before.
|
||||
|
||||
## Acceptance criteria
|
||||
1. Turns whose model stream carries reasoning show a "Thinking" block
|
||||
(collapsible, above the answer bubble) that streams open and
|
||||
auto-collapses on the first answer token; always user-toggleable
|
||||
afterwards.
|
||||
2. Turns without reasoning render exactly as before (no block, no layout
|
||||
shift).
|
||||
3. Thinking-without-answer (reasoning exhausted the token budget) shows
|
||||
the existing empty-answer fallback with the thinking block preserved.
|
||||
4. Thinking persists with the message (phase 14) and restores collapsed
|
||||
after reload; "New chat" clears it with everything else.
|
||||
5. Deflected turns show the amber bubble + "Maybe try" chips alongside the
|
||||
thinking block (honesty gate untouched).
|
||||
6. A stream that dies mid-thinking/mid-answer ends in the error state
|
||||
(retry hint) — never a silent half bubble.
|
||||
7. `BOR_STREAM_THINKING=0` suppresses `thinking` events server-side (log
|
||||
line still counts `thinking_chars`).
|
||||
|
||||
## UI Visualization & Structure
|
||||
- **DOM contract** (built dynamically — `index.html` is unchanged): inside
|
||||
`.msg-body`, **before** `.bubble`:
|
||||
```html
|
||||
<details class="thinking" open>
|
||||
<summary>Thinking</summary>
|
||||
<div class="thinking-text"></div>
|
||||
</details>
|
||||
```
|
||||
The block is created **open** at the first `thinking` event and
|
||||
auto-collapses at the first answer token; a late/interleaved `thinking`
|
||||
event only appends to the block — it never reopens once the answer
|
||||
started. Restored messages (phase 14) render the block **collapsed**.
|
||||
- **Styling (Phase-08 tokens):** `details.thinking` — `--surface`
|
||||
background, 1px `--line` border, 3px `--brand-soft` left accent,
|
||||
`--radius-sm`; `summary` — `--brand-ink` on surface (≈**8.7:1**), 0.9rem,
|
||||
**min-height 44px** touch target, CSS chevron `▸` rotating 90° when open
|
||||
(0.15s transform, stills under `prefers-reduced-motion`),
|
||||
`:focus-visible` 3px `--brand` outline; `.thinking-text` — `--ink-soft`
|
||||
on surface (≈**6.9:1**), 0.875rem/1.55, **max-height 320px** with
|
||||
`overflow-y: auto` (long scratchpads scroll instead of pushing the
|
||||
answer off-screen) and tightened paragraph/list margins.
|
||||
- **Accessibility:** native `<details>/<summary>` — a real,
|
||||
keyboard-focusable control with native open/closed announcements. **No
|
||||
live region on the scratchpad** (announcing every reasoning chunk would
|
||||
be hostile to screen readers); the existing `#send-status` region
|
||||
announces turn state as before.
|
||||
- **Feedback contract:** while reasoning streams, the block *is* the
|
||||
visible "thinking" feedback (the typing dots are removed); the UI state
|
||||
stays `thinking` (button disabled with "Thinking…", status text
|
||||
unchanged). The 120s pre-token guard clears on the first `thinking`
|
||||
**or** `delta` event.
|
||||
- **Stream-drop guard:** a stream that ends without a `done` event after
|
||||
at least one thinking/delta frame settles into the error state ("The
|
||||
stream ended before my answer finished — try again?") — never a silent
|
||||
half bubble.
|
||||
|
||||
## Playwright Mapping Rule
|
||||
**Test Scenario → `tests/e2e/test_thinking_display.py`** (mock LLM,
|
||||
`"think out loud"` trigger):
|
||||
1. `test_thinking_block_streams_open_then_collapses` — submit
|
||||
`THINK_QUESTION`. Assert `details.thinking` inside the last
|
||||
`.msg.brain` attaches within 10s (it appears at the first `thinking`
|
||||
event); immediately after attach, assert it is open (the mock's
|
||||
~800-char thinking stream gives a multi-second open window — see the
|
||||
suite's determinism note) and `.thinking-text` is non-empty; once
|
||||
`.bubble` text is non-empty, assert the block is **closed**; at
|
||||
settle: `.thinking-text` contains `Step 2: Check my notes`, the bubble
|
||||
contains the mock's deterministic answer sentence, `.source-chip`
|
||||
count ≥ 1, send button re-enabled.
|
||||
2. `test_thinking_toggle_after_done` — after a settled `THINK_QUESTION`
|
||||
turn, the block is closed; click `summary` → `details[open]` and the
|
||||
full thinking text is visible; click again → closed. (Real
|
||||
keyboard-focusable control.)
|
||||
3. `test_thinking_restored_after_reload` — settle a `THINK_QUESTION`
|
||||
turn; capture the thinking text; `page.reload()`; the restored
|
||||
conversation contains the brain message with a **closed**
|
||||
`details.thinking` whose `.thinking-text` matches the captured text,
|
||||
and the answer bubble + source chips are intact (phase-14 restore
|
||||
path).
|
||||
4. `test_no_thinking_block_without_trigger` — submit `PLAIN_QUESTION`; at
|
||||
settle: `page.locator("details.thinking")` count is 0 (a model that
|
||||
doesn't think renders exactly as before — no layout regression).
|
||||
5. `test_thinking_with_deflection` — submit `THINK_DEFLECT_QUESTION`; at
|
||||
settle: the brain message has `.is-deflected`, a `.maybe-try` group
|
||||
with chips, and a closed `details.thinking` whose text contains
|
||||
`Step 2: Check my notes` (thinking and the honesty gate coexist).
|
||||
Reference in New Issue
Block a user