From d31a345b174c1ec65359e89290675e6ac73b0dcd Mon Sep 17 00:00:00 2001 From: allenliang2022 Date: Mon, 6 Jul 2026 20:42:58 +0800 Subject: [PATCH] fix(webui): show new-message cue after unpinned mid-stream scroll restore Address greptile review: the new S.activeStreamId restore branch omitted the _maybeShowNewMessageScrollCue call that the preserveScroll and idle-unpin restore branches both make. An unpinned reader whose position is restored mid-stream should still see the new-message cue, so add it for consistency. Update the behavioral test to assert the cue fires and the test_issue1690 source-lock for the new branch body. --- static/ui.js | 1 + tests/test_issue1690_scroll_completion.py | 2 +- tests/test_wipe_rows0_streaming_unpinned_restore.py | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/static/ui.js b/static/ui.js index a84673a2..1801ce02 100644 --- a/static/ui.js +++ b/static/ui.js @@ -13440,6 +13440,7 @@ function _scrollAfterMessageRender(preserveScroll, scrollSnapshot){ // the no-op. Pinned/tail-following readers keep scrollIfPinned() (correct live-follow). if(_messageUserUnpinned && scrollSnapshot){ _restoreMessageScrollSnapshot(scrollSnapshot); + _maybeShowNewMessageScrollCue(scrollSnapshot); return; } scrollIfPinned(); diff --git a/tests/test_issue1690_scroll_completion.py b/tests/test_issue1690_scroll_completion.py index 44073847..f7940e4a 100644 --- a/tests/test_issue1690_scroll_completion.py +++ b/tests/test_issue1690_scroll_completion.py @@ -72,7 +72,7 @@ def test_render_messages_preserve_scroll_option_uses_user_pin_state_not_stream_l # browser's scrollTop clamp from the inner.innerHTML='' wipe, so it stranded the # reader at the top. Pinned/tail-following readers still take scrollIfPinned(). assert "if(S.activeStreamId){" in scroll_helper - assert "if(_messageUserUnpinned && scrollSnapshot){\n _restoreMessageScrollSnapshot(scrollSnapshot);\n return;\n }\n scrollIfPinned();\n return;\n }" in scroll_helper + assert "if(_messageUserUnpinned && scrollSnapshot){\n _restoreMessageScrollSnapshot(scrollSnapshot);\n _maybeShowNewMessageScrollCue(scrollSnapshot);\n return;\n }\n scrollIfPinned();\n return;\n }" in scroll_helper def test_cached_render_path_uses_same_scroll_policy_as_fresh_render(): diff --git a/tests/test_wipe_rows0_streaming_unpinned_restore.py b/tests/test_wipe_rows0_streaming_unpinned_restore.py index cb4e9a2a..35aea51e 100644 --- a/tests/test_wipe_rows0_streaming_unpinned_restore.py +++ b/tests/test_wipe_rows0_streaming_unpinned_restore.py @@ -103,7 +103,7 @@ const performance = { now: performanceNow }; function scrollIfPinned(){ calls.push('scrollIfPinned'); } function scrollToBottom(){ calls.push('scrollToBottom'); } function _followMessagesAfterDomReplace(){ calls.push('follow'); return true; } -function _maybeShowNewMessageScrollCue(){ } +function _maybeShowNewMessageScrollCue(){ calls.push('newMessageCue'); } // _restoreMessageScrollSnapshot is what the FIX must call for an unpinned reader. // Model its real effect: it moves scrollTop back toward the captured snapshot.top. function _restoreMessageScrollSnapshot(snap){ @@ -149,6 +149,11 @@ console.log(JSON.stringify({ calls, finalTop: el.scrollTop })); "after restore, scrollTop must return to the reader's pre-wipe position " f"(3000), not stay at the browser-clamped 0; got {m['finalTop']}" ) + assert "newMessageCue" in m["calls"], ( + "after restoring an unpinned reader mid-stream, the new-message cue must be " + "shown (consistent with the preserveScroll and idle-unpin restore branches); " + f"recorded calls={m['calls']!r}" + ) def test_streaming_pinned_reader_still_follows_via_scrollifpinned():