Skip to content

fix(web): thread post-list 闪动 — keyed-diff renderPosts + SSE-aware poll#106

Merged
github-actions[bot] merged 1 commit into
mainfrom
judy/fix-thread-flicker
Jun 25, 2026
Merged

fix(web): thread post-list 闪动 — keyed-diff renderPosts + SSE-aware poll#106
github-actions[bot] merged 1 commit into
mainfrom
judy/fix-thread-flicker

Conversation

@SymbolStar

Copy link
Copy Markdown
Owner

问题

thread message 页面经常肉眼可见闪动(mermaid / markdown / 滚动条都会抖)。

根因调查(见 thread th_19efda72d29_0c1061):

  1. renderPosts 每次 refresh 都 innerHTML='' 然后整列重建。
  2. 四条管道并行触发 refreshCurrentThread → renderDetail:SSE 推送、8s poll、BroadcastChannel 跨 tab、scheduleRefIndexRefresh 兜底。彼此没去重,新 post 到来 200ms 内常见重复刷新 2-3 次。
  3. 每次重建后 mermaid 块 / code highlight / chip 都要重新计算布局,renderDetail 末尾还会基于瞬时归零的 scrollHeight 调 scrollTop。

修复

数据流单源(signature short-circuit)

  • 新增 _postSig(post, prevPid)_threadSig(thread),把所有影响渲染输出的字段(speaker/phase/edited_at/parent/reactions/inlineDuration/content/前一条 pid…)拼成 monotonic 签名。
  • refreshCurrentThread 拿到新 thread 后先比 sig,相等直接 return,不进 renderDetail。SSE+poll 重叠那批冗余刷新全部 no-op。

增量 DOM(keyed diff renderPosts)

  • 维护 Map<post_id, {node, sig}> 缓存上一轮 DOM 节点。
  • 按 target 列表对齐 postList 子节点:sig 相同复用(mermaid SVG / code highlight / event handlers / scroll anchor 全部保留),不同才重建,尾部多余节点 remove。
  • 切换 thread 或 settings 强制重画时传 force: true 清缓存。

SSE-aware 降级

  • SSE hello / message 触发后 state.threadEventHealthy = true
  • startPolling 只在 SSE 不健康时对当前 thread 跑 refetch(unread sweep 不动),消除 SSE+poll 200ms 内打架。

验证

  • node --check web/app.js 通过。
  • 现有 SSE / poll / BroadcastChannel / chip collapse timer / scheduleRefIndexRefresh 这些路径全部继续走 refreshCurrentThread,被 sig 短路。
  • 切换 thread / settings 改 avatar 之类需要全量重画的场景通过 force 显式触发。

后续

  • renderPostNode 内部 header/body/footer 分区 patch(hash markdown body)。
  • /api/threads/<id>/events 改推 patch payload(post added / phase changed),前端直接 apply 不再 refetch。

renderPosts 之前每次 refresh 都 innerHTML='' 然后整列重建,叠加 SSE +
8s poll + BroadcastChannel + scheduleRefIndexRefresh 四条管道几乎同时
触发 refreshCurrentThread → renderDetail,导致 thread message 页面经常
肉眼可见闪动(mermaid/markdown/滚动条都会抖一下)。

彻底修复方案:

1. _postSig + _threadSig:把 post 渲染输出依赖的所有字段(speaker /
   phase / edited_at / parent / superseded / reactions / inlineDuration
   / content / 前一条 pid 等)拼成 signature。
2. refreshCurrentThread 收到新 thread 后先比 _threadSig,相等就直接
   return,不走 renderDetail。SSE 跟 poll 重叠那批冗余刷新全部 noop。
3. renderPosts 改成 keyed diff:用 Map<post_id, {node, sig}> 缓存上一
   轮的 DOM 节点。新一轮按 target 列表对齐 postList 子节点 —— sig
   相同就复用节点(mermaid SVG / code highlight / event handlers /
   scroll anchor 全部保留),不同才重建,多余的尾部节点 remove。
4. renderDetail 接受 force:true,settings 改 avatar/color 之类需要全
   量重画的场景传 force 清缓存;其它路径走 diff。
5. SSE 收到 hello/event 后 state.threadEventHealthy=true,startPolling
   只在 SSE 不健康时对当前 thread 跑 refetch(unread sweep 不动),
   消除 SSE+poll 200ms 内打架。

后续可以再做 renderPostNode 内部 header/body/footer 分区 patch、以及
让 SSE 直接推 patch payload(post added / phase changed),那是后续 PR
的事。
@github-actions

Copy link
Copy Markdown

🤖 bot-review (comment-only · phase 1)

Diff: 2 files changed, 218 insertions(+), 41 deletions(-) @ f1a63dd

Red-line checks:

  • ✅ A-7.5: no new 'forbidden' code in xiaof

Phase 2: auto-approve + auto-merge fire only when red-lines are clean, author is internal, and no needs-human path is touched. Block with no-auto-merge label or [no-auto-merge] in title.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved by bot-review: red-line checks clean, internal author, no needs-human paths.

@github-actions github-actions Bot enabled auto-merge (squash) June 25, 2026 11:41
@github-actions github-actions Bot merged commit 63e4f12 into main Jun 25, 2026
7 checks passed
github-actions Bot pushed a commit that referenced this pull request Jun 26, 2026
PR #93 把 #post-list 的 scrollbar 改成 hover 时显示,通过 hover 切换
scrollbar-width: none → auto 实现。但这个切换在 Firefox 和 Chromium
都会真正占走 ~10px 布局宽度,鼠标移进 post-list 内容左移 10px、移走
再右移回来,肉眼可见的抖动。

修法:scrollbar-gutter: stable 永远预留 gutter,bar 仍然只在 hover
时画。视觉上跟之前完全一致(默认空、hover 出现),但布局宽度恒定,
鼠标进出零 reflow。

(原本是跟 PR #106 keyed-diff 一起修的,但 #106 在 scrollbar 改动 push
上去之前就 merge 了,所以这里单独再开一个 PR。)

Co-authored-by: judy <judy@symbolstar.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant