fix(web): narrow chat scroll provenance

This commit is contained in:
kingwl
2026-08-02 18:06:15 +08:00
committed by imccyu
parent 8de33e42f7
commit 9b91e5312c
5 changed files with 97 additions and 669 deletions
@@ -14,7 +14,7 @@ Status: implemented
会话统计挂在 `'conversation.composer.dock'`(位于 `'conversation.input.dock'` 之上)。InputBar 的 textarea 在宿主内以 `{ passive: false }` 链式处理 `wheel`:在限高 textarea 仍能沿该方向滚动时保留原生手势;仅在自身边缘才 `preventDefault` 并将 `deltaY` 施加到宿主。
Chat 历史前插通过稳定的已渲染 node/call 身份跟随读者意图,而不是使用整个滚动容器的高度差。分页开始时,`ChatView` 记录第一个可见的 `data-chat-anchor-key` 及其相对滚动容器的顶部位置;请求在途期间,每次读者滚动都会重新选择当前可见的稳定锚点;页面到达后则按该行矩形的前后差值补偿。到达底部或追加读者自己的消息会取消分页锚点,因此迟到的页面不能把视图从最新内容拉走。贴底跟随的意图归读者掌控,与瞬时几何状态相互独立:原始滚动事件绝不改变所有权,因为程序化写入、浏览器执行的钳制或锚定,以及读者滚动,都会产生形态相同的事件。`ChatView` 会根据以下来源激活读者手势:抵达宿主的滚轮/触控板输入、直接通过指针或触控进行的滚动、原生滚动容器自身收到的指针输入,以及纵向滚动键或焦点导航键;嵌套 overflow owner 若能沿该方向滚动,就不会为 transcript 激活手势。滚轮处理保持 passive,并以主线程最近一次收到滚动事件或程序化写入后的 `scrollTop` 作为输入前基线,因为 Chromium 可能先推进 compositor 几何状态,再投递 passive wheel 事件。只有该手势期间的宿主滚动才能在跟随与阅读之间切换;连续两个空闲的动画帧后,手势结束;新的末端高度会从读者位移中排除其强制钳制量,而阅读状态只有在读者朝底部移动时才能重新取得跟随所有权。并非由读者引起的滚动漂移在跟随状态下会重新贴底,在阅读状态下则只刷新保存的语义位置。语义化历史位置恢复与前插校正会先取消任何活跃的读者手势,再应用并保存各自明确的所有权状态。`ChatView` 的单个 `ResizeObserver` 统一负责消息列与 sticky 编辑器高度变化的贴底跟随决策:只有在贴底所有权仍保持且没有活跃的读者手势时才跟随流式输出、工具展开与草稿尺寸变化,且每个 chunk 不会触发第二次滚动写入。
Chat 历史前插通过稳定的已渲染 node/call 身份跟随读者意图,而不是使用整个滚动容器的高度差。分页开始时,`ChatView` 记录第一个可见的 `data-chat-anchor-key` 及其相对滚动容器的顶部位置;请求在途期间,每次读者滚动都会重新选择当前可见的稳定锚点;页面到达后则按该行矩形的前后差值补偿。到达底部或追加读者自己的消息会取消分页锚点,因此迟到的页面不能把视图从最新内容拉走。贴底跟随采用存储状态,而不是原始滚动几何状态。passive wheel 监听器以最近一次由主线程交付或由程序写入的 `scrollTop` 作为输入前基线,因为 Chromium 可能先推进合成器几何状态,之后才交付事件;当前使用的非负下限不会将并发的布局钳制计入读者移动。没有对应滚轮/触控板输入位移的滚动,在跟随状态下会重新贴底,在阅读状态下则只刷新语义位置。`ChatView` 的单个 `ResizeObserver` 只会在贴底所有权仍保持时跟随流式输出、工具展开与草稿尺寸变化,且每个 chunk 不会触发第二次滚动写入。
## Alternatives considered
@@ -26,8 +26,8 @@ Chat 历史前插通过稳定的已渲染 node/call 身份跟随读者意图
**把 StatsLine 留在 ChatView 消息列下方。** 否决:落在 sticky 编辑器之外会随内容滚走,而输入区仍钉在底部。
**程序化贴底写入后,忽略下一个滚动事件或按目标位置匹配该事件。** 否决:未改变位置的写入可能不触发事件,重复写入可能合并;流收尾阶段还可能先让滚动容器收缩并钳制位置,再使其重新增长,最后由一个延迟事件报告不同于上一目标的位置。若没有输入来源信息,该事件与读者滚动无法区分。
**为每一种浏览器滚动输入来源建模。** 此次窄范围修复不采用:已复现的桌面端路径使用滚轮/触控板输入。指针/触控滚动、拖动原生滚动条、键盘滚动、焦点导航与嵌套 overflow 所有权仍不纳入输入来源模型,也不为此新增通用输入状态机。
## Consequences
在页脚上滚轮会滚动 transcript;可见布局是固定标题栏、可滚动 transcript 与 sticky 底部编辑器。统计出现在每一个活跃视图标签上。宿主下的嵌套视图 scroller 被抑制,因而 Trajectory 的 sticky Turn 标题贴在列宿主上。并发历史加载、流式输出、工具展开与编辑器重排会保留读者更新的滚动决定;在没有活跃读者手势时,延迟到达的程序化滚动事件不能改变跟随所有权。hero → active 保持同一 textarea DOM 节点(assembled slash-flow 快照)以及 InputHub 草稿。
在页脚上滚轮会滚动 transcript;可见布局是固定标题栏、可滚动 transcript 与 sticky 底部编辑器。统计出现在每一个活跃视图标签上。宿主下的嵌套视图 scroller 被抑制,因而 Trajectory 的 sticky Turn 标题贴在列宿主上。并发历史加载、流式输出、工具展开与编辑器重排会保留滚轮/触控板的滚动决定,包括 Chromium 先推进合成器几何状态再交付事件,以及流收尾阶段滚动位置受钳制后滚动容器重新增长的情况。在这条窄范围的输入来源规则下,其他浏览器滚动输入不会改变贴底跟随所有权。hero → active 保持同一 textarea DOM 节点(assembled slash-flow 快照)以及 InputHub 草稿。