chore(agent): phase roadmap from TODO.md, 1 phase (161 reasoning bubble)
LLM-Generated: true
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Task 01 — The live reasoning container
|
||||
|
||||
**Phase:** `161_reasoning_bubble` · **Source:** `TODO.md:3 — "The reasoning process should be one collapsible section that **stays open** during the reasoning process and shows all thinking and tool calls in order. Right now the thinking, tool calling, thinking, tool calling blocks take up a ton of space and obscure the answer. We need to condense this while still letting the user watch the thinking process happen so the latency doesn't feel super low. This new thinking/tool calling bubble should only collapse once the final response has been returned and the session has stopped."`
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
One `details.reasoning` container per brain message wraps the phase-158
|
||||
per-run timeline live: created on the turn's first thinking/tool frame,
|
||||
`open` until the turn settles (done / stop / failed-finalize), condensed
|
||||
in a bounded 320px live window with pinned-tail follow, the summary
|
||||
carrying the D6 status literals; the per-run blocks are re-anchored into
|
||||
the container (their structure and open/fold behavior are unchanged).
|
||||
|
||||
## Work
|
||||
1. `frontend/assets/app.js` — `createReasoningWrap()` (beside
|
||||
`createThinkingBlock` ~L1068): createElement only (innerHTML ABSENT)
|
||||
— `details` (class `reasoning`) + `summary` (class
|
||||
`reasoning-summary`, `textContent = "Reasoning"`, the ONLY new static
|
||||
literal — D6) + `span` (class `reasoning-status`, empty until a frame
|
||||
sets it) + `div` (class `reasoning-body`); return the `details`
|
||||
element. The docstring cites this phase + D1/D6.
|
||||
2. `frontend/assets/app.js` — module scope `reasoningWrapEl = null`
|
||||
(beside `segRuns`/`currentRun` ~L1464–1465) + `ensureReasoningWrap
|
||||
(wrap)` (get-or-create: on the turn's first thinking/tool frame —
|
||||
`createReasoningWrap()`, `open = true` (D1), inserted `before`
|
||||
`wrap.querySelector(".msg-body").querySelector(".bubble")`, stored in
|
||||
`reasoningWrapEl`); reset in `runTurn` alongside `segRuns = []`
|
||||
(~L3304 — the pagehide partial reads the live container, so the
|
||||
module scope is readable there like `segRuns`).
|
||||
3. `frontend/assets/app.js` — re-anchor the per-run inserts: in
|
||||
`ensureThinkingRun` (~L1103) and `ensureToolRun` (~L1122), the
|
||||
`body.insertBefore(block, body.querySelector(".bubble"))` line becomes
|
||||
`ensureReasoningWrap(wrap).querySelector(".reasoning-body")
|
||||
.appendChild(block)` (the `wrap` param — both functions already
|
||||
receive it). EVERYTHING ELSE in both functions is UNCHANGED (the
|
||||
`closeCurrentRun()` call, the `open = true`, the `segRuns.push`,
|
||||
`currentRun`). The container is created by the FIRST run's
|
||||
`ensureReasoningWrap` call — no other call site.
|
||||
4. `frontend/assets/app.js` — `settleReasoning(wrap)` (beside
|
||||
`closeAllRuns` ~L1149):
|
||||
`wrap?.querySelector("details.reasoning")?.removeAttribute("open")`
|
||||
(idempotent; a no-op without a container — the plain-answer turn
|
||||
settles byte-identically). Called alongside `closeAllRuns(wrap)` at
|
||||
the THREE settle sites: the `done` branch (~L3570), the STOP path
|
||||
(~L3752), `finalizeFailedTurn` (~L3218). The `delta` branch
|
||||
(~L3553) is UNCHANGED — `closeCurrentRun()` folds the active run;
|
||||
the container STAYS OPEN (D2 — the TODO's core contract).
|
||||
5. `frontend/assets/app.js` — the D6 status: in the thinking frame
|
||||
branch (~L3381, after `ensureThinkingRun`), set
|
||||
`reasoningWrapEl?.querySelector(".reasoning-status")?.textContent =
|
||||
"Thinking…"`; in the tool frame branch (~L3501, after
|
||||
`ensureToolRun`), set `"Calling " + name + "…"` (the `name` the
|
||||
`appendToolLine` call already receives — the phase-70 legacy-name
|
||||
mapping already produced it); at the three settle sites (alongside
|
||||
`settleReasoning`), set `"${segRuns.length} steps"` (the live `N` —
|
||||
D6; the settle sites read `segRuns` — module scope).
|
||||
6. `frontend/assets/app.js` — the D3 live follow: the
|
||||
`REASONING_NEAR_BOTTOM_PX = 32` constant (beside the phase-17
|
||||
`THINKING_NEAR_BOTTOM_PX`) + the follow helpers —
|
||||
`isReasoningPinned(el)` (the pre-update capture — the phase-17
|
||||
pre-measure convention: `el.scrollTop + el.clientHeight >=
|
||||
el.scrollHeight - REASONING_NEAR_BOTTOM_PX`) and
|
||||
`scrollReasoningToBottom(el)` (the post-append apply: `el.scrollTop
|
||||
= el.scrollHeight`). Called from BOTH the thinking frame branch (pre:
|
||||
after the existing `isThinkingNearBottom` capture; post: after the
|
||||
block re-render) and the tool frame branch (pre: before
|
||||
`appendToolLine`; post: after it) — `el` =
|
||||
`reasoningWrapEl?.querySelector(".reasoning-body")` (a no-op when
|
||||
absent — the plain-answer turn never creates one). The EXISTING
|
||||
per-block `isThinkingNearBottom(textEl)` follow is UNCHANGED
|
||||
(harmless — task 03 makes the inner `.thinking-text`
|
||||
non-scrollable inside the container, so it no-ops there). The
|
||||
phase-87 `armToolLineClock`/`settleToolLine` wrap-scoped queries are
|
||||
DESCENDANT-scoped — UNCHANGED (they match the nested runs).
|
||||
7. `tests/unit/test_reasoning_bubble.py` (new) — the source-level pins:
|
||||
`createReasoningWrap`'s createElement shape (the `reasoning` /
|
||||
`reasoning-summary` / `reasoning-status` / `reasoning-body` classes,
|
||||
the `Reasoning` literal, innerHTML ABSENT), `ensureReasoningWrap`'s
|
||||
get-or-create + `open = true` + the reset in `runTurn`, the per-run
|
||||
inserts append into `.reasoning-body` (the `insertBefore(…bubble)`
|
||||
per-run anchor is GONE from `ensureThinkingRun`/`ensureToolRun`),
|
||||
`settleReasoning` defined + called at the THREE settle sites and NOT
|
||||
at the `delta` branch, the status literals (`Thinking…` / `Calling `
|
||||
/ ` steps`), the `REASONING_NEAR_BOTTOM_PX = 32` constant + the
|
||||
pre/post split, and the pagehide partial's readability (the module
|
||||
scope reset pattern).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_reasoning_bubble.py` (above) — all pins green.
|
||||
- Coverage: **>90%** on this task's new/modified code (frontend — the
|
||||
`app/` gate is untouched; held by the suite).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `tests/unit/test_reasoning_bubble.py` green (`uv run pytest
|
||||
tests/unit/test_reasoning_bubble.py -v --no-cov`)
|
||||
- [ ] the full test suite green (`uv run pytest`)
|
||||
- [ ] no behavior change in completed work beyond the documented
|
||||
anchor supersession (the per-run blocks' structure + open/fold
|
||||
behavior is byte-identical; only their parent changed)
|
||||
Reference in New Issue
Block a user