Skip to content

fix(chat): 对话区接近底部时正文突然上跳:去掉回贴区吸附并修正末端锚定口径 - #839

Merged
Loosand merged 5 commits into
Stack-Cairn:developfrom
AlphaCatMeow:fix/transcript-bottom-snap-jump-develop
Sep 20, 2026
Merged

Loosand merged 5 commits into
Stack-Cairn:developfrom
AlphaCatMeow:fix/transcript-bottom-snap-jump-develop

Conversation

@AlphaCatMeow

@AlphaCatMeow AlphaCatMeow commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Closes #811

本 PR 是 #812 改 base 到 develop 的重提。4 个提交按序 cherry-pick 到 develop,只有 GatewayConversationPaneHost.tsx 的一处 import 相邻行冲突(develop 新增 cn 导入,本 PR 新增 useScrollFollow 导入),两行都保留即解决;其余改动与 #812 一致。合并后 #812 可关闭。

Summary

WebUI 对话区在接近底部时正文会突然上跳,触发询问用户卡片或工具审批请求时更明显。三处原因,本 PR 逐一处理:

  1. 共享滚动跟随引擎默认的 192px 回贴区:滚轮下行进入该区间的那一 tick 直接把视口钉到底(复现页实测单 tick 视觉位移 231px)。WebUI 主转写区与桌面端 ChatTranscript 都改为 reattachZonePx: 0,只在真正到达底部时恢复跟随;到达底部后向下滚轮、手势落底、指针在底部释放三条路径仍会重新贴底。
  2. virtual-corewasAtEnd 用列表局部距离判"在末端",不含转写 shell 的 18px 顶部 padding 与 194px 底部占位,于是距真实 clamp 约 220px 内都算"在末端",脱离跟随的读者会被任何底部行增高拖走(询问卡片到达 305px、审批请求 63px、流式增高 302px)。改为按 DOM 的 getDistanceFromEnd() 判定,与 followOnAppend 已用的口径一致;两端共用该 fork,桌面端同样受益。
  3. WebUI 工作台背景 Pane 手写的贴底逻辑(距底 48px 即视为跟随、行数一变就写 scrollTop)换成共享引擎,与主 Pane 和桌面端各 Pane 一致。

Change scope

  • Modules: agent-gateway/web、agent-gui、agent-ui、virtual-core
  • Key paths:
    • crates/agent-gateway/web/src/app/GatewayApp.tsx
    • crates/agent-gateway/web/src/app/workbench/GatewayConversationPaneHost.tsx
    • crates/agent-gui/src/pages/chat/transcript/ChatTranscript.tsxcrates/agent-ui/src/lib/chat-scroll/scrollFollowCore.ts(注释)
    • crates/virtual-core/src/index.tscrates/virtual-core/types/index.d.ts
    • 测试:crates/virtual-core/test/end-anchor-distance.test.mjs(harness 新增 extraScrollHeight / maxScrollOffset)、crates/agent-gateway/web/test/transcript-scroll-follow-contract.test.mjscrates/agent-gui/test/chat/chat-transcript-scroller.test.mjs(新增 1 例)
    • 复现页:crates/agent-gateway/web/test/browser/scroll-follow-probe.{html,cdp.mjs,eval.mjs}

Screenshots / preview

2026-09-20_16-07-15
场景 修前 修后
滚轮下行到底 进入 231px 处一步跳到底 每步 100,最后一步 31 到底并恢复跟随
脱离态停在距底 100px,末行增高 470px -302 0
脱离态停在距底 120px,AskUserQuestion 卡片到达 -305 0
脱离态停在距底 120px,工具审批请求到达 -63 0
脱离态停在距底 400px,以上各种变化 0 0
跟随态在底部,卡片/审批到达、审批栏增高 -380 / -63 / -72 不变(贴底本身)

Verification(在 develop 基线上重新跑)

  • pnpm typecheck:virtual-core 通过;pnpm test:virtual-core 34/34
  • crates/agent-gateway/webtsc --noEmit 通过;pnpm test:webui 720/720
  • crates/agent-guitsc --noEmit 通过;chat-transcript-scroller 5/5、scroll-follow-core + use-scroll-follow-native-scrollbar 34/34
  • 浏览器复现方法同 fix(chat): 对话区接近底部时正文突然上跳:去掉回贴区吸附并修正末端锚定口径 #812:web 包 pnpm exec vite --port 5173 + msedge --headless=new --remote-debugging-port=9333node test/browser/scroll-follow-probe.cdp.mjs all|arrive|approve|ask
  • 未运行:桌面端 Tauri 打包构建;触屏与 WebKit

Depends-On: none
Stack-Root: #812

Pre-submit checklist

  • A requirement issue is linked (or this is a trivial fix that needs no issue, as explained in the summary).
  • Synced with the target branch; no merge conflicts.
  • The change is focused, with no unrelated modifications.
  • No secrets, tokens, or personal data included.
  • Docs are updated for changes affecting user behavior, deployment, or configuration.

AlphaCatMeow and others added 4 commits September 20, 2026 14:33
resizeItem 的 wasAtEnd 此前用 getVirtualDistanceFromEnd()(列表局部坐标)判
"在末端"。宿主在虚拟列表下方还渲染了 18px shell padding 与 194px 底部占位,
列表末端离真实 clamp 约 212px,于是脱离跟随、停在距底 220px 内的读者都被
当成"在末端",任何底部行尺寸变化都经 applyScrollAdjustment 直写 scrollTop:
AskUserQuestion 卡片到达视觉位移 305px,审批请求 63px,末行流式增高 302px。

改用 getDistanceFromEnd()(DOM scrollHeight 口径,与 _willUpdate 里
followOnAppend 的 isAtEnd 一致)并加 scrollElement 判空;删除不再使用的私有
getVirtualDistanceFromEnd,types 同步并为 scrollEndThreshold 补口径说明。

测试:harness 新增 extraScrollHeight / maxScrollOffset 模拟列表下方的非虚拟
高度;end-anchor-distance 4 例覆盖带内不写、带边不写、真实 clamp 仍钉底、
默认 harness 语义不变。

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
主转写区的 useScrollFollow 沿用默认 192px 回贴区:滚轮下行进入该区间的那一
tick 直接 pin 到底,读者看到正文突然上跳(复现页实测单 tick 231px)。改为
reattachZonePx: 0,只在真正到达底部(8px 容差)时恢复跟随;到达底部后向下
滚轮、手势落底、指针在底部释放仍会重新贴底。

工作台背景 Pane 原本手写一套贴底:距底不足 48px 即视为跟随,行数或 revision
一变就写 scrollTop,读者停在底部附近会被每次流式增量吸回,且与主 Pane、
桌面端各 Pane 的引擎语义不一致。换成同一 useScrollFollow(zone 0,会话切换
stickToBottom),ScrollArea 根与视口经 callback ref 接入,删除三处 scrollTop
写入。

新增 transcript-scroll-follow-contract 源码契约测试锁住两处配置。

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
scroll-follow-probe.html 挂真实 ScrollArea + GatewayTranscript + useScrollFollow,
拦截 scrollTop/scrollTo 写入并按调用栈归因(follow-engine / virtualizer /
base-ui),用视口顶部那一行的屏幕位移衡量肉眼可见的跳动;?zone= 试回贴区,
?ask=1|2 与 ?approval=1|2 构造挂起的询问卡片与审批请求。
scroll-follow-probe.cdp.mjs 用 Node 内建 WebSocket 直连无头 Chromium 的 CDP,
逐 tick 发滚轮事件记录每步位移与写入方;scroll-follow-probe.eval.mjs 对已
打开的页面即时求值。

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
桌面端 ChatTranscript 的 useScrollFollow 显式传 reattachZonePx: BOTTOM_REATTACH_ZONE_PX,
滚轮下行进入距底 192px 的那一 tick 直接 pin 到底,从本分支起的开发版上仍能看到
"快到最新回复结尾时上跳一下"。与 WebUI 同口径改为 0:只在真正到达底部(8px 容差)
时恢复跟随,到达底部后向下滚轮、手势落底、指针在底部释放仍会重新贴底。

BOTTOM_REATTACH_ZONE_PX 保留:它仍是桌面端底部预留带与 WebUI spacer 的最小值,
scrollFollowCore 的注释改写为现状说明。chat-transcript-scroller 契约测试新增一例
锁住零回贴区配置与预留带取值。

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@StackCairn
StackCairn marked this pull request as draft September 20, 2026 06:36
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

PR governance checks passed. Awaiting human review.

@AlphaCatMeow
AlphaCatMeow marked this pull request as ready for review September 20, 2026 08:07
@Loosand
Loosand merged commit b563784 into Stack-Cairn:develop Sep 20, 2026
9 checks passed
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.

2 participants