注释:为会话滚动锚点的全表量测补 TODO 性能分析
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m55s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 4m18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m33s
Project CI / Backend tests (pull_request) Successful in 3m58s
Project CI / Repository checks (pull_request) Successful in 3m4s
Project CI / Frontend tests (pull_request) Successful in 3m25s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m14s
Project CI / Native shell tests (pull_request) Successful in 5m49s

- conversationScrollAnchor.ts 的 readTopVisibleTurnAnchor 循环上方新增 // TODO(perf): 注释,仅注释、无行为变化
- 记录量测口径:最坏 O(锚点之前的块数) 次 getBoundingClientRect,同帧首次调用后布局已干净、后续不再各自触发 reflow,属 low,不是振荡源
- 记录 rAF 节流与「只在补偿前才读」都省不掉这次遍历,故不采纳
- 记录二分 / 续扫被收起 <details> 的 0 矩形打断单调性,需先定「隐藏块怎么参与」
- 首选后续方向:缓存上次命中的块身份并从附近续扫,不破坏单调性假设

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-10-02 22:34:07 +08:00
parent 0ae94caf16
commit 1874a6e9ec
@@ -84,6 +84,14 @@ export function readTopVisibleTurnAnchor(
): ConversationTurnAnchor | null {
const listTop = list.getBoundingClientRect().top;
// TODO(perf): 这个循环最坏是 O(锚点之前的块数) 次 `getBoundingClientRect()`,调用点是 `onScroll`
// (离底时)与每次 `compensateLayout`,超长历史里属于 low 级别的量测开销——同一帧里第一次调用
// 之后布局已经干净,后续调用不会各自再触发一次 reflow,所以不是「每帧几百次强制布局」的振荡源。
// 真要省掉遍历只能按「块在文档顺序里位置单调递增」做二分、或从上次命中处续扫;但被折进**收起**
// `<details>` 的块矩形全 0、同样在候选集合里,会打断这个单调性,二分前得先定「隐藏块怎么参与」,
// 而给锚点集合加「只收可见块」的规则又要每次子元素变化先量一遍,等于把省下的遍历又搬回来。
// 若真机在超长历史里量到滚动手感问题,先走「缓存上次命中的块身份 + 从它附近续扫」这条不破坏
// 单调性假设的路,而不是 rAF 节流(滚动事件本就按帧派发,同一帧去重收益极小)。
for (const block of collectTurnBlocks(list)) {
const rect = block.getBoundingClientRect();
// 折进收起 `<details>` 的块在真实浏览器里没有布局盒(矩形全 0):它既不是用户正在读的