GCWing / GCWing/OpenBitFun

[Bug]: Transcript re-measures every frame while a thinking card grows, causing stutter during reasoning

Abierto
#3,069 0 comentarios 0 reacciones 1 asignado Reclamado por @wsp1911 Ver en GitHub
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

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.