fix(streaming): restore mobile scroll-jank guard during live renders

A later streaming parse-cache change dropped the #MOBILESCROLL guard from
the live-stream render tick while leaving the helper and CSS in place.
On iOS PWA, Safari can paint a transient scrollTop=0 frame during streamed
DOM rebuilds, forcing the reader to the first/oldest message.

Re-add _fixMobileScrollJank() before streaming DOM work begins and pin it
with a regression test so the third layer of the mobile scroll fix cannot
be silently dropped again.
This commit is contained in:
Brett Yelverton
2026-06-24 16:36:32 +00:00
committed by nesquena-hermes
parent c70105cf35
commit 2a40ca4240
2 changed files with 26 additions and 0 deletions
+3
View File
@@ -3844,6 +3844,9 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
_renderPending=false;
// Guard: a pending setTimeout+rAF can outlive stream finalization.
if(_streamFinalized) return;
// Mobile scroll-jank guard: enable overflow-anchor before DOM writes so
// the browser preserves scroll position during streaming content growth.
if(typeof window._fixMobileScrollJank==='function') window._fixMobileScrollJank();
_lastRenderMs=performance.now();
const parsed=_cachedParsed&&_cachedParsedText===assistantText&&_cachedParsedReasoning===liveReasoningText ? _cachedParsed : _parseStreamState();
_cachedParsed=null;
@@ -4,6 +4,7 @@ from pathlib import Path
REPO = Path(__file__).parent.parent
UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
MESSAGES_JS = (REPO / "static" / "messages.js").read_text(encoding="utf-8")
STYLE_CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
@@ -43,6 +44,28 @@ def test_messages_scroller_uses_overflow_anchor_auto_on_mobile():
)
def test_streaming_render_enables_mobile_scroll_jank_guard_before_dom_writes():
"""Streaming must re-enable mobile scroll anchoring before every DOM write.
Regression: #MOBILESCROLL originally added _fixMobileScrollJank() in the
live-stream render tick, but a later streaming parse-cache change dropped
that call while leaving the helper/CSS in place. On iOS PWA this lets Safari
paint a transient scrollTop=0 frame during streamed DOM rebuilds.
"""
guard_idx = MESSAGES_JS.find("window._fixMobileScrollJank")
assert guard_idx != -1, (
"attachLiveStream() must call window._fixMobileScrollJank() during the "
"streaming render tick, before DOM writes, so iOS PWA does not jump to "
"the first/oldest message while assistant output streams."
)
render_idx = MESSAGES_JS.find("_lastRenderMs=performance.now()")
assert render_idx != -1, "streaming render timestamp not found"
assert guard_idx < render_idx, (
"The mobile scroll-jank guard must run before streaming DOM work begins."
)
def test_scroll_repin_dead_zone_is_wider_for_mac_app_windows():
assert "clientHeight<250" in UI_JS or "bottomDistance<250" in UI_JS, (
"The near-bottom re-pin threshold should be at least 250px so small "