Commit Graph
10 Commits
Author SHA1 Message Date
nesquena-hermesandallenliang2022 9fd349898f fix(chat): defer mobile overflow-anchor suppression (re-arm reachable + independent hard cap) #4856
Clean rebase of allenliang2022's #5392 (rebase-first).

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-07-02 04:01:38 +00:00
allenliang2022 7db6fd8650 fix(chat): keep overflow-anchor suppressed across the async post-render settle window (mobile jump-back)
The sync-frame guards (_fixMobileScrollJank / _suppressBrowserOverflowAnchor)
only cover the render frame itself. postProcessRenderedMessages() — syntax
highlight, inline diff/csv/pdf/html/excalidraw, katex/mermaid — is scheduled a
FRAME LATER via requestAnimationFrame(), after those guards have released. Each
of those can change the height of rows ABOVE the viewport; on mobile
(overflow-anchor:auto) the browser's native anchor engine then compensates
scrollTop a SECOND time in that unguarded frame, yanking an unpinned reader to
another turn (the residual mobile 往回大跳).

Wrap all three deferred post-process dispatches (fast-path cache branch, main
render tail, live-tool remount) in _postProcessWithAnchorSuppression(), which
routes through the shared _suppressBrowserOverflowAnchor() and holds suppression
one extra frame so late media/layout reflow is covered too. Desktop rests at
overflow-anchor:none so the wrapper is a verified no-op there.

Reproduced on an isolated debug instance with a cloned 1179-message session:
above-viewport +350px during the async settle window jumped scrollTop +350 on
mobile (auto) and 0 with the wrapper; desktop (none) 0 both ways. static/ui.js
only.
2026-07-01 17:46:03 +00:00
Frank SongandPaperclip 71a458784a fix(chat): guard live worklog manual scroll intent
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-30 10:52:11 +08:00
allenliang2022 e2ea0789b3 fix(chat): do not treat Space on transcript controls as scroll intent (#4970 review)
Maintainer/Codex found one narrow edge in the keyboard intent stamp: because the
listener runs capture-phase and accepts any focused descendant of #messages,
Space/Spacebar on an in-transcript control (tool-card toggles, copy buttons,
role buttons, links/tabs) stamped _lastMessageKeyScrollIntentMs before the
control handler could preventDefault/stopPropagation. That made a button
activation look like a scroll intent and could mask a legitimate post-render
artifact.

Fix by excluding Space/Spacebar when the event target or active element is an
interactive transcript control: button, a[href], select, summary, role=button,
role=tab, role=menuitem, or contenteditable. Keep PageUp/PageDown/arrows/Home/End
stamping unchanged, and keep the existing INPUT/TEXTAREA/contenteditable composer
exclusion.

Add a behavioral node-harness test: Space on a focused transcript button leaves
the key-scroll stamp at -Infinity (serialized null), while PageUp on the pane
still stamps 1234. test_4856 + test_4295 green (20 passed), node --check clean.
2026-06-26 13:29:14 +00:00
allenliang2022 6825bc03b4 fix(chat): record keyboard message-pane scroll as intent (#4970 greptile P1)
Keyboard scrolling of the message pane (PageUp/PageDown, Arrow keys, Space,
Home/End) fires a native scroll event with no wheel/touch/scrollbar/non-message
intent. Inside the 1400ms post-render artifact window the suppression branch
then returned before movedUp could unpin, so a keyboard scroll-up was swallowed
and live-follow snapped the reader back to the bottom.

- Add _lastMessageKeyScrollIntentMs + _recentMessageKeyScrollIntent(), stamped
  by a capture-phase keydown listener on the scroll keys, gated to when the
  message pane is the scroll target (focused/contains focus/hovered) and not an
  editable field (composer/input/contenteditable).
- Gate the post-render suppression on !_recentMessageKeyScrollIntent() (typeof
  guard keeps the #4295 node harness inert).
- Clear the stamp in both _resetScrollDirectionTracker() and
  _resetStreamScrollFollow() (same stale-state hygiene as the wheel stamp).

Tests: keyboard-scroll-inside-window-still-unpins behavioral regression (harness
extended with injected _recentMessageKeyScrollIntent), plus a source lock for the
helper/keydown-stamp/suppression-gate/both-resets. test_4856 + test_4295 green
(19 passed), node --check clean.
2026-06-26 13:29:14 +00:00
allenliang2022 67d9580825 fix(chat): reset wheel intent on session/stream resets + gate suppression on scrollbar drag (#4970 review)
Maintainer dual-gate found two stale-state leaks in the new
_lastMessageWheelIntentMs and one adjacent pre-existing gap; all three fixed:

MUST-FIX 1 — _resetScrollDirectionTracker() (session switch) did not clear
_lastMessageWheelIntentMs, so a gentle wheel in chat A left
_recentMessageWheelIntent() true into chat B's first post-render window,
under-suppressing the artifact and falsely unpinning. Now reset to -Infinity.

MUST-FIX 2 — _resetStreamScrollFollow() (fresh stream) had the same leak: a
gentle upward wheel within 1200ms of a new stream could silently disable live
follow. Now reset to -Infinity.

SHOULD-FIX 3 — the suppression branch ignored _scrollbarDragActive, so a manual
scrollbar-drag upward scroll inside the 1400ms window was swallowed. Gate the
branch on (typeof _scrollbarDragActive==='undefined' || !_scrollbarDragActive);
typeof guard keeps the #4295 node harness inert.

Tests: scrollbar-drag-inside-window-still-unpins behavioral regression (harness
extended with injected _scrollbarDragActive), plus source locks for both resets
and the scrollbar-drag gate. test_4856 + test_4295 green (17 passed),
node --check clean.
2026-06-26 13:29:14 +00:00
allenliang2022 2ce0e731ec fix(chat): scope post-render suppression to true no-intent artifacts (#4970 review)
Maintainer dual-gate (Codex) found the suppression could swallow a genuine
low-delta trackpad wheel scroll-up for ~1.4s after a render:
_recordNonMessageScrollIntent() only recorded message-pane wheel intent at
deltaY<-30, so a gentle deltaY:-5 left both intent helpers false and the
post-render branch returned before movedUp set _messageUserUnpinned.

- Track recent low-delta upward message-pane wheel intent separately
  (_lastMessageWheelIntentMs / _recentMessageWheelIntent), recorded for any
  upward wheel (deltaY<0). The decisive deltaY<-30 sticky-unpin is unchanged.
- Require !_recentMessageWheelIntent() before suppressing the artifact, so a
  real gentle scroll-up inside the window still unpins.
- Add behavioral node-harness regressions: gentle wheel inside the window
  unpins; no-intent artifact inside the window stays suppressed; outside the
  window unpins. Plus a source lock that low-delta intent is tracked.
2026-06-26 13:29:14 +00:00
allenliang2022 f9a952f085 fix(chat): suppress post-render scroll artifacts 2026-06-26 13:29:14 +00:00
allenliang2022 ef6901c947 fix(chat): ignore DOM-wipe scroll clamp during render 2026-06-25 20:19:00 +00:00
Rod Boev 0668cfa77c fix(#4856): suppress Chromium scroll-anchor re-selection during mobile DOM rebuild 2026-06-25 01:06:09 +00:00