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,36 @@
|
||||
# Phase 42 — No reply autoscroll
|
||||
|
||||
**Source:** `TODO.md` L5 — "Get rid of the chat reply autoscroll, it's breaking things like making it impossible for the user to scroll while a reply generates."
|
||||
**Story:** `.agent/user_stories/no-reply-autoscroll.md`
|
||||
**Context:** Phase 18 ("follow-the-bottom", owner choice 2026-08-23) added `NEAR_BOTTOM_PX = 200` / `isNearBottom()` / `scrollReveal(wrap, behavior, force)` in `frontend/assets/app.js`: the page auto-scrolls on every `thinking` / `tool` / `delta` frame while the user is within 200px of the bottom. The owner now finds that fighting their own scroll. The gate and the per-frame scrolls are **removed**; scrolling happens only on explicit user intent (submit, restore landing).
|
||||
|
||||
## Objective
|
||||
The chat page never auto-scrolls during a turn. The viewport moves only when the user submits (their message is revealed) or when a persisted conversation is restored (one-shot landing) — both user-initiated.
|
||||
|
||||
## Dependencies
|
||||
- `41_sync_fail_fast_models` (todo) — sequential execution only (no code overlap).
|
||||
- `18_follow_bottom_scroll` (complete) — the code being removed; `14_chat_persistence` (complete) — the restore landing that must survive; `17_thinking_display` / `11_long_answers` (complete) — the thinking window-pin and long-answer behavior this phase must not break.
|
||||
|
||||
## Tasks
|
||||
1. `01_remove_autofollow.md` — strip the phase-18 gate + per-frame scrolls from `app.js`; rewrite the unit pin for the new contract.
|
||||
2. `02_no_autoscroll_e2e_and_commit.md` — replace the phase-18 E2E with the inverse-contract suite + regressions + commit.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_frontend_scroll.py` **rewritten** — pins the new contract: no `NEAR_BOTTOM_PX` / `isNearBottom` in `app.js`; the scroll helper scrolls unconditionally (smooth / reduced-motion-aware); the user-submit path scrolls; the thinking/tool/delta handlers contain **no** page-scroll call; the restore landing keeps its one-shot forced scroll.
|
||||
- Coverage: frontend-only — `app/` TOTAL unchanged, >90%.
|
||||
- E2E (mandatory, A16): `tests/e2e/test_no_reply_autoscroll.py`, run in isolation. `tests/e2e/test_follow_bottom_scroll.py` is **deleted** (behavior intentionally removed by owner direction 2026-08-27).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] During thinking / tool / answer streaming, `window.scrollY` is stable (±1px) while the viewport is scrolled up.
|
||||
- [ ] Submit still reveals the user's message; reload still lands one-shot on the latest message.
|
||||
- [ ] `uv run pytest` green; coverage TOTAL unchanged.
|
||||
- [ ] `uv run pytest tests/e2e/test_no_reply_autoscroll.py -v --no-cov` green in isolation (DB up).
|
||||
- [ ] Regression E2E suites green in isolation: `test_chat_rag.py`, `test_thinking_display.py`, `test_chat_persistence.py`, `test_long_answers.py`, `test_smoke.py`.
|
||||
- [ ] `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] One `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/`.
|
||||
|
||||
## Locked decisions
|
||||
- **Owner direction (2026-08-27, roadmap A1)** revises the phase-18 owner choice (2026-08-23): follow-the-bottom auto-follow is removed; submit-reveal + restore-landing are kept. Recorded in the story file and the `app.js` docstring (PLAN.md §7.4's Scroll row is a PLAN-side revision to be noted by the owner — this phase does not edit PLAN.md).
|
||||
- **A15 unchanged** — the SSE contract is untouched; this is pure client-side behavior.
|
||||
- **The thinking window's internal pin** (`textEl.scrollTop`, phase 17) is untouched here — phase 43 reworks it separately.
|
||||
- **A16/A17 honoured** — one story E2E suite (replacing the removed one), one atomic commit.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Task 01 — Remove the auto-follow gate and per-frame scrolls
|
||||
|
||||
**Phase:** `42_no_reply_autoscroll` · **Source:** `TODO.md:5` — "Get rid of the chat reply autoscroll, it's breaking things like making it impossible for the user to scroll while a reply generates."
|
||||
**Story:** `.agent/user_stories/no-reply-autoscroll.md`
|
||||
|
||||
## Objective
|
||||
`app.js` scrolls only on explicit user intent: sending a message and the phase-14 restore landing. No scroll happens anywhere in the streaming path.
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/app.js` —
|
||||
- **Delete** `export const NEAR_BOTTOM_PX = 200` and `function isNearBottom()`.
|
||||
- **Simplify** `scrollReveal(wrap, behavior = SCROLL, force = false)` → an unconditional `wrap.scrollIntoView({ behavior })` (keep the `SCROLL` constant: smooth, `auto` under `prefers-reduced-motion`; keep the "Calm, don't remove" comment). Rename the gate comment block: the phase-18 "follow-the-bottom scroll contract" paragraph is replaced by the new contract — *"No reply autoscroll (owner direction 2026-08-27, `TODO.md` L5): the page never auto-scrolls while a turn streams. The only scroll call sites are the user submit (reveal my message) and the phase-14 restore landing (one-shot, load-time)."*
|
||||
- **`addMessage(who, html, scrollBehavior = SCROLL, force = false)`** → change the signature to `addMessage(who, html, scroll = false)`: the internal `scrollReveal(wrap, scrollBehavior, force)` becomes `if (scroll) scrollReveal(wrap)`. Update the call sites (line numbers are pre-change anchors):
|
||||
- the **user submit** call (`addMessage("user", renderMarkdown(text))`, ~line 896) → `addMessage("user", renderMarkdown(text), true)` (my message must be revealed — the owner-kept behavior);
|
||||
- the **phase-14 restore** calls (~lines 772/775: `addMessage("user", …, "auto", true)` / `addMessage("brain", …, "auto", true)`) → keep the one-shot forced scroll under the new signature (e.g. `addMessage("user", renderMarkdown(m.text), true)` — the "auto" (non-smooth) behavior for the landing is preserved by passing it through if the new signature keeps a behavior param, otherwise the default `SCROLL` is acceptable and must be noted in the docstring);
|
||||
- the brain first-bubble creations in the SSE handlers (`if (!wrap) wrap = addMessage("brain", "")`, ~lines 955/978/995, plus the `"…"` fallback ~1004 and the error fallback ~1046) → `scroll: false` (default) — a streaming turn never scrolls the page;
|
||||
- `addTyping()` (~line 356): the `scrollReveal(wrap)` after `messagesEl.appendChild(wrap)` is **removed** (a typing bubble must not yank the page).
|
||||
- **SSE handlers** — remove the page-scroll calls: in the `thinking` frame drop the `scrollReveal(wrap); // page follows only while pinned (phase 18)` line **but keep** `textEl.scrollTop = textEl.scrollHeight;` (the thinking *window* pin — phase 17, reworked in phase 43); in the `tool` frame drop its `scrollReveal(wrap);`; in the `delta` frame drop its `scrollReveal(wrap);`.
|
||||
- Update the file-top docstring's scroll paragraph (lines ~73–81: "Scroll (phase 18, owner choice…)") to the new contract.
|
||||
2. `tests/unit/test_frontend_scroll.py` — **rewrite** for the new contract (keep the house style — source pins over `app.js`):
|
||||
- `NEAR_BOTTOM_PX` / `isNearBottom` are **absent** from `app.js`;
|
||||
- the scroll helper scrolls unconditionally (no `force`-or-near-bottom condition in its body);
|
||||
- the user-submit `addMessage` call passes the scroll intent; the brain-bubble creation does not;
|
||||
- the `thinking` / `tool` / `delta` handler bodies contain no `scrollReveal` call (the thinking handler's `textEl.scrollTop` pin is still present);
|
||||
- the restore landing still performs its one-shot scroll (pin the marker comment / call);
|
||||
- the `SCROLL` reduced-motion handling is intact.
|
||||
- Delete the now-obsolete phase-18 test functions (the band constant, the gate logic) — do not leave dead pins.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: the rewritten pin file + the full suite green (no `app/` change — coverage TOTAL unchanged).
|
||||
- Coverage: **>90%** on `app/` (unchanged).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] No page scroll happens in the streaming path (grep-verifiable + unit-pinned); submit and restore landing still scroll.
|
||||
- [ ] `uv run pytest` green; the thinking window-pin and all message rendering are byte-identical elsewhere.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Task 02 — No-autoscroll E2E (replaces phase 18) + regressions + commit
|
||||
|
||||
**Phase:** `42_no_reply_autoscroll` · **Source:** `TODO.md:5` — "Get rid of the chat reply autoscroll, it's breaking things like making it impossible for the user to scroll while a reply generates."
|
||||
**Story:** `.agent/user_stories/no-reply-autoscroll.md`
|
||||
|
||||
## Objective
|
||||
Prove the inverse of the phase-18 contract in the browser: no streaming autoscroll, submit-reveal and restore-landing intact — then delete the obsolete phase-18 suite and commit.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_no_reply_autoscroll.py` (new) — mock-only, DB up, per the story's Playwright Mapping Rule:
|
||||
- `test_no_autoscroll_during_long_answer` — `LONG_ANSWER_TRIGGER` question (the mock's ~8 s long answer); once the answer starts streaming, `window.evaluate` a scroll up ~2× the answer's height; sample `window.scrollY` across ≥10 frames (and after `done`): stable within 1px;
|
||||
- `test_no_autoscroll_during_thinking` — `THINKING_TRIGGER` question; scroll up during the ~4.5 s thinking stream; viewport stable across chunks (no per-chunk page follow);
|
||||
- `test_submit_reveals_user_message` — in a populated conversation scrolled to the very top, send a question; after send the user's message is in view (its bounding box within the viewport);
|
||||
- `test_restore_landing_one_shot` — settle a conversation (phase-14 persistence), reload; the page lands on the latest message and stays (no further movement while idle);
|
||||
- `test_answer_content_intact` — the long answer completes with sources; a thinking turn persists + restores (collapsed block, phase 17).
|
||||
2. **Delete** `tests/e2e/test_follow_bottom_scroll.py` (its behavior is intentionally removed — owner direction 2026-08-27; the unit pin was rewritten in task 01).
|
||||
3. Regression pass (isolation runs): `test_chat_rag.py`, `test_thinking_display.py`, `test_chat_persistence.py`, `test_long_answers.py`, `test_smoke.py` — all green; fix only true regressions.
|
||||
4. `uv run pytest` green; `uv run pytest --cov=app --cov-report=term-missing` TOTAL unchanged; `uv run ruff check . && uv run pyright` clean.
|
||||
5. Commit (Conventional Commits, `--no-gpg-sign`), e.g. `fix(chat): stop autoscrolling while a reply streams (owner direction)`, staging this phase's files (including the deleted E2E); move `.agent/phases/todo/42_no_reply_autoscroll/` → `.agent/phases/complete/`.
|
||||
|
||||
## Testing & Quality
|
||||
- E2E: `uv run pytest tests/e2e/test_no_reply_autoscroll.py -v --no-cov` green in isolation.
|
||||
- Coverage: **>90%** on `app/` (unchanged — frontend-only phase).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The new suite passes in isolation; the phase-18 suite is gone; the five regression suites pass in isolation.
|
||||
- [ ] One atomic `--no-gpg-sign` commit; phase dir moved to `.agent/phases/complete/`.
|
||||
Reference in New Issue
Block a user