[Bug]: Transcript re-measures every frame while a thinking card grows, causing stutter during reasoning
- Lenguaje dominante
- Rust
- Estrellas
- 2.2k
- Forks
- 229
- Merge medio
- 2 h 46 min
- PR fusionados (30 d)
- 577
Descripción
### Summary
While the model is reasoning, the thinking card grows from zero up to its `max-height: 300px` cap. **Every streamed line changes the card’s outer height**, and that height is transcript layout — so the virtualized message list re-measures and re-commits on each of those frames.
Expected instead: the card’s own growth should not cost the message list a re-measure every frame.
Measured on an ordinary reasoning round (**no user input at all**), `tailFollow.thinking` reported **13–52 list commits per second**, with the tail follow falling **up to 1034px** behind the content and then catching that up in a single visible move. To the reader this reads as the transcript sticking, then juddering.
This happens with **no scrolling by the reader**. It is independent of the `follow-output-owns-the-viewport` refusals logged during streaming — that line is the register recording our own placement, not a refused gesture.
### Area
Web UI
### Reproduction or evidence
**Steps**
1. Open a session and send a prompt that makes the model reason at length (a long chain-of-thought, not merely a long answer).
2. Do **not** touch the scrollbar or wheel — just watch the transcript while the thinking card streams.
3. Observe: the transcript hitches, and the follow visibly lags behind the streaming text and then jumps to catch up.
**Evidence — the repo already emits the numbers**
`tailFollow.thinking` / `tailFollow.list` come from `noteTailFollowStep` (`src/web-ui/src/infrastructure/diagnostics/flowChatTailFollowDiagnostics.ts`), sampled by `ModelThinkingDisplay.tsx` (subject `thinking`) and `useFlowChatFollowOutput.ts` (subject `list`). One reasoning round, before the fix below:
tailFollow.thinking windows=55
steps = 1995
snaps = 1248 # steps that changed the card outer height
innerScrollSteps = 751 # steps that scrolled inside a capped card (38%)
listCommits = 1425 # list re-measure + React commit
max listCommitsPerSec = 50.8
maxLagPx = 1034
travelPx = 5216
**Root cause**
`ModelThinkingDisplay.scss` sets `max-height: 300px; overflow-y: auto;`, so the card only starts scrolling internally **after** it has grown to 300px. Before that, every streamed line changes the card outer height. The component own comment acknowledges the cost:
> Below the card `max-height` the box is still growing, so each of these steps also costs the list a re-measure. Above it, none do.
`innerScrollSteps` being only 38% of `steps` is that gap.
**Proposed fix**
Pin the box to the cap for the whole stream, so the height stops changing and the remaining scrolling happens inside the card:
```scss
--thinking-content-cap: 300px;
max-height: var(--thinking-content-cap);
.thinking-content[data-streaming="true"] {
height: var(--thinking-content-cap);
}
```
Plus one line in `ModelThinkingDisplay.tsx` because the typewriter reveal also drains content over its own frames: `data-streaming` on the content element changes from `isActive` to `isVisuallyStreaming` (`FlowTextBlock.tsx` and `ModelRoundItem.tsx` already use it).
**After the change** (same instrumentation, same machine):
| metric (tailFollow.thinking) | before | after |
| --- | --- | --- |
| snaps | 1248 | 126 (0 in settled windows) |
| innerScrollSteps share | 751/1995 (38%) | 939/1065 (88%) |
| listCommits | 1425 | 163 |
| max listCommitsPerSec | 50.8 | 10 |
| maxLagPx | 1034 | 47 |
**Tradeoff**: the card now occupies its full 300px cap from the first streamed line, so a round whose reasoning is shorter than 300px leaves blank space under the text until the card collapses.
**Related but distinct**: #2891 pauses the tail follow on a reader upward scroll — that is about the reader gesture; this is about the cost of the card own growth, which happens with no gesture at all.
### Environment, if relevant
OpenBitFun version/commit: 1.0.0, local dev build from commit bb64534
OS: macOS (arm64)
Browser/device: Tauri desktop (WebKit)
Model/provider: DeepSeek V4 Pro
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.