[Bug]: Transcript re-measures every frame while a thinking card grows, causing stutter during reasoning
维护者通常 1 天内回复
@wsp1911 已经在做这个了。
开始于 2026年9月17日。
评估
这个 Issue 还没有评估数据。
描述
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
- Open a session and send a prompt that makes the model reason at length (a long chain-of-thought, not merely a long answer).
- Do not touch the scrollbar or wheel — just watch the transcript while the thinking card streams.
- 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-heightthe 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:
--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
- 主要语言
- Rust
- 星标
- 2.3k
- 派生
- 236
- 平均合并
- 3 小时 38 分钟
- 30 天内合并 PR
- 329
环境准备
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
GCWing/OpenBitFun 的其他 Issue
-
难度 2/5 半天 新手友好度 74/100
GCWing/OpenBitFun#3279 ·
维护者通常 1 天内回复
-
[Bug]: 界面写“敏感诊断信息默认关闭”,但后端配置当前实际默认是 true可能已有人在做 @xiechimon 于 17 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 75/100
GCWing/OpenBitFun#3213 · 1 条评论 ·
维护者通常 1 天内回复
-
bug
难度 2/5 1-3 小时 新手友好度 72/100
GCWing/OpenBitFun#2363 ·
维护者通常 1 天内回复
-
question
难度 1/5 1 小时以内 新手友好度 78/100
GCWing/OpenBitFun#2340 ·
维护者通常 1 天内回复
-
难度 2/5 半天 新手友好度 40/100
GCWing/OpenBitFun#3346 ·
维护者通常 1 天内回复
查看 GCWing/OpenBitFun 的全部 Issue
相似的 Issue
-
[Bug]: Web chat input doesn't regain focus after a reply finishes可能已有人在做 @GaijinSystems 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
zeroclaw-labs/zeroclaw#11658 ·
维护者通常 2 天内回复
-
good first issue help wanted
难度 2/5 1-3 小时 新手友好度 72/100
-
documentation
难度 2/5 1-3 小时 新手友好度 70/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
NuSkooler/enigma-bbs#907 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复