@co0ontty/wand 1.31.1 → 1.31.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -106,6 +106,13 @@
106
106
  terminalHealthTimer: null,
107
107
  lastTerminalResyncAt: 0,
108
108
  terminalAutoFollow: true,
109
+ // 程序触发的滚动(wand 主动 scrollTo / wterm 内部因 _shouldScrollToBottom=true
110
+ // 拽 scrollTop=scrollHeight)落到 scroll handler 时会被误判为"用户滚回严格
111
+ // 底部",把 autoFollow 反转回 true,把用户刚 wheel 上滚的意图吞掉。
112
+ // 存"窗口截止时间戳"而非"开始时间戳":不同调用方按各自动画长度延长窗口
113
+ // (瞬时 120ms 覆盖一次 rAF + 事件分发;smooth 500ms 覆盖 Chromium smooth
114
+ // scroll 动画),多次调用用 Math.max 合并、不会被短窗口缩短。
115
+ terminalProgrammaticScrollUntil: 0,
109
116
  terminalScrollIdleTimer: null,
110
117
  terminalScrollIdleMs: 1800,
111
118
  terminalScrollThreshold: 12,
@@ -7081,6 +7088,16 @@
7081
7088
  if (!state.terminal) return;
7082
7089
  var viewport = getTerminalViewport();
7083
7090
  if (!viewport) return;
7091
+ // 打"程序触发滚动"窗口:紧跟着的 scroll 事件是 wand 自己拽出来的,
7092
+ // scroll handler 在窗口内跳过 autoFollow 修改,避免"程序拽底 →
7093
+ // scroll 事件 → handler 看到在底 → autoFollow=true"的反馈环把
7094
+ // 用户刚 wheel 上滚的意图覆盖掉。smooth 模式 Chromium 滚动动画约
7095
+ // 300-500ms,瞬时滚动只需覆盖一次 rAF + 事件分发延迟。
7096
+ var windowMs = smooth ? 500 : 120;
7097
+ state.terminalProgrammaticScrollUntil = Math.max(
7098
+ state.terminalProgrammaticScrollUntil,
7099
+ Date.now() + windowMs
7100
+ );
7084
7101
  if (smooth) {
7085
7102
  viewport.scrollTo({ top: viewport.scrollHeight, behavior: "smooth" });
7086
7103
  } else {
@@ -7487,7 +7504,36 @@
7487
7504
  if (!state.wideParserState) state.wideParserState = createWideParserState();
7488
7505
  var padded = widePadAnsi(data, state.wideParserState);
7489
7506
  var framed = processSyncOutputFraming(padded);
7507
+ // wterm.write 内部用 5px 阈值判定"在底部",下一帧 _doRender 据此强制
7508
+ // scrollTop = scrollHeight。这与 wand 的 autoFollow("真正到底"才为
7509
+ // true,2px 阈值)独立,会把用户主动向上滚的几像素吞掉。覆写为 wand
7510
+ // 的 autoFollow 状态,让 autoFollow 成为唯一真相。
7511
+ //
7512
+ // 时序关键:必须在 terminal.write() 之前先覆写一次,否则 wterm 在 write
7513
+ // 内部解析 chunk 时可能同步触发 _doRender → 提前完成 scrollTop=scrollHeight,
7514
+ // write 之后再覆写就晚了一帧,用户上滚位置已被吞。write 之后再覆写一次
7515
+ // 兜底:wterm 在解析 newline / cursor move / scroll region 时可能把
7516
+ // _shouldScrollToBottom 改回 true。
7517
+ var follow = state.terminalAutoFollow !== false;
7518
+ if ("_shouldScrollToBottom" in terminal) {
7519
+ terminal._shouldScrollToBottom = follow;
7520
+ }
7490
7521
  if (framed) terminal.write(framed);
7522
+ if ("_shouldScrollToBottom" in terminal) {
7523
+ terminal._shouldScrollToBottom = follow;
7524
+ }
7525
+ // wterm 按 follow=true 真的 scrollTop=scrollHeight 时会触发一次程序性的
7526
+ // scroll 事件 — 打窗口,让 scroll handler 不要误判为"用户滚回底部"。
7527
+ // **只在 follow=true 时打**:follow=false 时 wterm 不会拽底,没有程序事件
7528
+ // 要过滤;如果这里也打标,Claude 流式输出 chunk <120ms 一个会让窗口永
7529
+ // 不过期,scroll handler 永远 early return,用户哪怕滚回严格底部 autoFollow
7530
+ // 也回不到 true,再也走不出"上滚阅读"模式。
7531
+ if (follow) {
7532
+ state.terminalProgrammaticScrollUntil = Math.max(
7533
+ state.terminalProgrammaticScrollUntil,
7534
+ Date.now() + 120
7535
+ );
7536
+ }
7491
7537
  // R6: 在 chunk 热路径上识别原地重绘序列(CSI nA/B/C/D/f/H/J/K),
7492
7538
  // 节流安排一次 softResync 兜底。Claude 用相对光标位移重画菜单时,
7493
7539
  // 如果 NEW-A 的 sync output buffer 因某种原因没拦截到完整帧(比如
@@ -7495,13 +7541,6 @@
7495
7541
  // 错位。此 fallback 仅在真出现错位序列时触发,正常输出零开销。
7496
7542
  // 与 R2 策略 A 配合:移除被动 5 处触发后,这是唯一的主动救场路径。
7497
7543
  maybeScheduleResyncForChunk(data);
7498
- // wterm.write 内部用 5px 阈值判定"在底部",下一帧 _doRender 据此强制
7499
- // scrollTop = scrollHeight。这与 wand 的 autoFollow("真正到底"才为
7500
- // true,2px 阈值)独立,会把用户主动向上滚的几像素吞掉。覆写为 wand
7501
- // 的 autoFollow 状态,让 autoFollow 成为唯一真相。
7502
- if ("_shouldScrollToBottom" in terminal) {
7503
- terminal._shouldScrollToBottom = state.terminalAutoFollow !== false;
7504
- }
7505
7544
  }
7506
7545
 
7507
7546
  function resetWideParserState() {
@@ -7919,6 +7958,16 @@
7919
7958
  var viewport = getTerminalViewport();
7920
7959
  if (viewport) {
7921
7960
  state.terminalViewportScrollHandler = function() {
7961
+ // 程序触发的 scroll(wand 主动 scrollTo / wterm 内部
7962
+ // _doRender 因 _shouldScrollToBottom=true 拽 scrollTop=scrollHeight)
7963
+ // 也会进这里。如果不过滤,handler 会看到 isTerminalAtBottom()=true
7964
+ // 把 autoFollow 反转回 true,把用户刚上滚的意图吞掉,下一帧 chunk
7965
+ // 到达又被拽底,形成"上滚→拽底"反馈环。窗口长度由调用方按
7966
+ // 各自动画长度决定(瞬时 120ms / smooth 500ms)。
7967
+ if (Date.now() < state.terminalProgrammaticScrollUntil) {
7968
+ updateTerminalJumpToBottomButton();
7969
+ return;
7970
+ }
7922
7971
  // 严格"真正到底"才恢复 autoFollow:避免 wheel 设 false 后被
7923
7972
  // 紧接着的 scroll 事件因"接近底部 12px"而反转回 true。
7924
7973
  if (isTerminalAtBottom()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.31.1",
3
+ "version": "1.31.2",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {