diff --git a/static/ui.js b/static/ui.js index e5483f489..90c70f5c8 100644 --- a/static/ui.js +++ b/static/ui.js @@ -13641,8 +13641,13 @@ function _restoreMessageScrollSnapshotSameFrame(snapshot){ const s=el.querySelector('[data-virtual-spacer="before"]'); return s?(parseFloat(s.style.height||'0')||0):NaN; })(); - const _padBefore=Number(snapshot.anchor&&snapshot.anchor.topPadBefore); - if(Number.isFinite(_padNow)&&Number.isFinite(_padBefore)){ + const _padBeforeRaw=snapshot.anchor&&snapshot.anchor.topPadBefore; + const _padBefore=Number(_padBeforeRaw); + // Require an ACTUAL captured topPadBefore (not null/undefined): Number(null) is 0, + // which would otherwise add the ENTIRE current spacer height to scrollTop and fling + // the reader far from their content (greptile P1). Only apply when it was really + // captured; else keep the raw fallback target. + if(_padBeforeRaw!=null&&Number.isFinite(_padNow)&&Number.isFinite(_padBefore)){ _fbTarget=Math.max(0,Math.min(el.scrollTop+(_padNow-_padBefore), maxTop)); } // else: no measurable anchor and no topPad geometry -> keep raw target. diff --git a/tests/test_issue5637_stale_anchor_guard.py b/tests/test_issue5637_stale_anchor_guard.py index 9deb839e1..3b83c0944 100644 --- a/tests/test_issue5637_stale_anchor_guard.py +++ b/tests/test_issue5637_stale_anchor_guard.py @@ -407,6 +407,23 @@ def test_realign_genuinely_gone_anchor_no_geometry_keeps_raw_desktop(): assert m["writes"] == [1200] +def test_realign_gone_anchor_null_toppadbefore_does_not_fling_desktop(): + """greptile P1: anchor row gone, a virtual top-spacer IS present (padNow=1300), but the + snapshot carries NO captured topPadBefore (null). Number(null) is 0, so a naive + isFinite(padBefore) check would treat padBefore as 0 and add the ENTIRE 1300px spacer + to scrollTop (1000 -> 2300), flinging the reader far from their content. The guard + requires an ACTUAL captured topPadBefore, so with null it keeps the raw snapshot.top. + Mutation: drop the `_padBeforeRaw!=null` term and this FAILS (writes 2300, the fling).""" + m = json.loads(_run_node(_fallback_harness( + snapshot_scroll_height=90000, cur_scroll_height=90500, snapshot_top=1200, + active_intent=False, touch_like=False, + anchor={"key": "gone", "sessionIdx": 999, "topOffset": 1000, "topPadBefore": None}, + anchor_row_content_pos=None, container_top=0, init_scroll_top=1000, + top_pad_now=1300, + ))) + assert m["writes"] == [1200] + + def _predicate_harness(*, pointer_coarse, computed_overflow_anchor, has_matchmedia=True,