fix(ui): animated background actually animates — grid drift and glow breathe per the phase-08 design

This commit is contained in:
2026-08-24 22:16:41 -04:00
parent 22a6121411
commit 0adc9b5801
24 changed files with 797 additions and 183 deletions
@@ -1,104 +0,0 @@
# Phase 21 — Thinking Window: No Scroll Back, Just Live
**Source:** `TODO.md` L4 — *"Disable scroll in the thinking window.
Users don't need to scroll back through thinking, just see it live."*
**Story:** `.agent/user_stories/thinking-no-scroll.md` (created by task 02)
**Context:** `frontend/assets/styles.css` —
`details.thinking .thinking-text` (today: `max-height: 320px;
overflow-y: auto;`, i.e. a user-scrollable 320px window, ~line 444);
`frontend/assets/app.js` — the phase-17 thinking block (`ensureThinkingBlock`,
the streaming `thinking` branch that appends chunks and already pins the
stream to the bottom: `textEl.scrollTop = textEl.scrollHeight`, ~line 886),
and the phase-14 restore path (stored `thinking` re-renders a
**collapsed** block).
## Objective
The live Thinking block is a scratchpad, not a transcript. The user must
not be able to scroll back through it — the 320px window always shows the
**live tail** of the reasoning stream (the existing per-chunk
bottom-pinning stays). Wheel, drag, and keyboard scrolling on
`.thinking-text` stop working; the stream itself keeps pinning to the
bottom as chunks arrive.
## Owner-confirmed (2026-08-24, roadmap A2)
1. **Keep the 320px clip** — "just see it live" means the window stays a
fixed 320px viewport showing the newest lines; no auto-height growth,
no "↓ more" affordance.
2. **The answer bubble is untouched** — final answers keep their existing
scroll behavior (phase 11 long answers).
3. **Restored (collapsed) Thinking blocks are untouched** — the phase-14
restore renders them collapsed, where overflow is moot.
## Design
- **CSS (the whole functional change):**
`details.thinking .thinking-text` — `overflow-y: auto` →
`overflow-y: hidden`; keep `max-height: 320px`.
`overflow: hidden` still permits **programmatic** scrolling
(`scrollTop`), so the phase-17 pin
(`textEl.scrollTop = textEl.scrollHeight` on every thinking chunk)
keeps the window glued to the live tail — no JS change needed.
Add a CSS comment: *no user scroll back (owner choice 2026-08-24):
the window is a live tail only — the JS bottom-pin is the sole
scroller*.
- **No JS change** — the pin already exists; nothing else touches
`.thinking-text` scroll.
- **Non-goals:** no change to the answer bubble, the collapsed restore
state, the summary/chevron, or the auto-collapse on first delta
(phase 17).
## Dependencies
- `17_thinking_display` (complete) — the block, the pin, the restore.
- `18_follow_bottom_scroll` (complete) — no overlap (chat-page scroll
gate only; the thinking window is a separate inner element).
- `11_long_answers` (complete) — the untouched answer-bubble behavior.
## Tasks
1. `01_disable_thinking_scroll.md` — the CSS change + source-level unit
pins.
2. `02_e2e_story_suite_commit.md` — `tests/e2e/test_thinking_no_scroll.py`
(the story gate, isolated), regression suites, story file, final
validation, the single atomic commit, phase move to `complete/`.
## Locked decisions
- **A11 untouched** — no CDN, pure CSS. **A16 honored** — one new story
E2E suite + adapted regressions. No anchor changed.
## Testing & Quality
- **Unit (source-level, new `tests/unit/test_thinking_no_scroll.py`,
repo source-pin pattern):** `styles.css` carries
`details.thinking .thinking-text` with `overflow-y: hidden` and
`max-height: 320px`; the phase-17 pin line
(`textEl.scrollTop = textEl.scrollHeight`) still present in `app.js`
(the live-tail mechanism must not be lost).
- **Integration:** none (no `app/` changes).
- **Coverage:** frontend-only; the >90% `app/` gate is unaffected.
- **E2E:** `tests/e2e/test_thinking_no_scroll.py` (task 02), green
**in isolation** (prereq `podman compose up -d db`).
- **Lint/types:** `uv run ruff check . && uv run pyright` clean.
## Completion Criteria
- [ ] With a long thinking stream, wheel/mouse-drag/keyboard on
`.thinking-text` do **not** move it; the visible content is always
the live tail (`scrollTop === scrollHeight` after each chunk,
within 1px).
- [ ] Computed style: `overflow-y: hidden`, `max-height: 320px`.
- [ ] A long **answer** bubble still scrolls normally; a restored
collapsed Thinking block still renders (phase 17 regression).
- [ ] `uv run pytest` green; `uv run pytest --cov=app
--cov-report=term-missing` ≥ today's number.
- [ ] `uv run pytest tests/e2e/test_thinking_no_scroll.py -v --no-cov`
green in isolation; regressions green in isolation:
`test_thinking_display.py`, `test_long_answers.py`.
- [ ] `uv run ruff check . && uv run pyright` clean.
- [ ] UI Structure Check (AGENTS.md rule 5): no new surface; the block
keeps its focus-visible summary, aria contract, and reduced-motion
behavior.
- [ ] `.agent/user_stories/thinking-no-scroll.md` exists.
- [ ] One `--no-gpg-sign` commit (below);
`.agent/phases/todo/21_thinking_no_scroll/` moved to
`.agent/phases/complete/`.
## Commit
```bash
git add -A .agent/ frontend/ tests/ && git commit --no-gpg-sign -m "fix(ui): thinking window no longer scrolls — live 320px view pinned to the stream tail"
```
@@ -1,70 +0,0 @@
# Task 02 — E2E story suite, story file, validation, commit
**Phase:** `21_thinking_no_scroll` · **Source:** `TODO.md` L4
## Objective
The story gate: `tests/e2e/test_thinking_no_scroll.py` proves the window
can't be user-scrolled but always tracks the live tail, plus regressions,
story file, final validation, and the single atomic commit.
## Work
1. `tests/e2e/test_thinking_no_scroll.py` (new — reuse
`test_thinking_display.py`'s mock-LLM streaming scaffolding; the mock
must stream a **long** thinking body, in many chunks, so
`.thinking-text` overflow exceeds its 320px box). Tests:
1. `test_thinking_window_not_user_scrollable` — open the block, wait
until `scrollHeight > clientHeight`; focus `.thinking-text`
(`el.focus()`), dispatch mouse wheel over it
(`page.mouse.wheel(0, -200)` after moving the mouse over the
element) and press `Home`/`ArrowUp`: `scrollTop` must not decrease
(assert `scrollTop` unchanged within 1px between actions).
2. `test_thinking_window_tracks_live_tail` — while chunks stream,
after the 2nd-to-last and last chunk:
`scrollTop === scrollHeight` (within 1px) — the visible window is
the live tail; the **last** chunk's text is within the visible
rectangle (its offsetTop + scrollTop geometry check, or
`elementFromPoint` at the box's bottom).
3. `test_thinking_window_css_contract` — computed style of
`.thinking-text`: `overflow-y === "hidden"`,
`max-height === "320px"`.
4. `test_answer_bubble_still_scrollable` (regression, phase 11) — a
long answer (use the long-answer mock from
`test_long_answers.py`): the answer bubble is still
user-scrollable (scrollTop moves on wheel) and
`overflow-y` is not `hidden` there.
5. `test_restored_collapsed_thinking_unaffected` (regression,
phase 17) — a turn with stored `thinking`, reload: the collapsed
Thinking block renders with its text (existing pin from
`test_thinking_display.py` — replicate, don't duplicate the file).
2. `.agent/user_stories/thinking-no-scroll.md` (new) — story file per
the repo format: goal, the bug report verbatim from `TODO.md` L4, the
owner-confirmed A2 decisions from `00_phase.md`, E2E mapping table.
3. Run the suite **in isolation** (prereq `podman compose up -d db`):
`uv run pytest tests/e2e/test_thinking_no_scroll.py -v --no-cov`.
4. Regressions, in isolation, one command each:
- `uv run pytest tests/e2e/test_thinking_display.py -v --no-cov`
- `uv run pytest tests/e2e/test_long_answers.py -v --no-cov`
5. Final validation: `uv run pytest` green; `uv run pytest --cov=app
--cov-report=term-missing` ≥ today's number (>90% gate);
`uv run ruff check . && uv run pyright` clean.
6. **UI Structure Check** (AGENTS.md rule 5): no new surface; the block
keeps its summary chevron, focus-visible ring, aria-live/label
contract, and the reduced-motion stillness (styles.css ~line 686).
7. Write the phase report (`.agent/reports/21_thinking_no_scroll/`).
8. Commit (one atomic commit) and move the phase:
```bash
git add -A .agent/ frontend/ tests/
git commit --no-gpg-sign -m "fix(ui): thinking window no longer scrolls — live 320px view pinned to the stream tail"
mv .agent/phases/todo/21_thinking_no_scroll .agent/phases/complete/
```
## Testing & Quality
- Story suite green **in isolation**; both regression suites green in
isolation; full unit+integration suite green; `app/` coverage at or
above today's number (>90%); ruff + pyright clean.
## Completion Criteria
- [ ] `test_thinking_no_scroll.py` 5/5 in isolation.
- [ ] Regressions (thinking display, long answers) green in isolation.
- [ ] Story file + phase report exist.
- [ ] One `--no-gpg-sign` commit; phase directory in `complete/`.