fix(streaming): show new-message cue when preserving scroll position (#3545)#3631
fix(streaming): show new-message cue when preserving scroll position (#3545)#3631rodboev wants to merge 1 commit into
Conversation
|
| Filename | Overview |
|---|---|
| static/ui.js | Core scroll logic: adds cue helpers, captures scrollHeight in snapshot, replaces _followMessagesAfterDomReplace with explicit _scrollPinned branch — logic is correct and well-guarded |
| static/i18n.js | Adds session_new_message / session_new_message_label keys; all 12 locales updated with count parity |
| static/style.css | Adds --new-message modifier class with pill shape and accent colours; scoped override for .session-jump-btn__text display looks correct |
| tests/test_issue3545_streaming_scroll_cue.py | New static regression test covering cue show/hide invariants, i18n keys, CSS hook — thorough for a static test suite |
| tests/test_issue1690_scroll_completion.py | Updated assertion to match new _scrollAfterMessageRender branch structure; change is mechanically correct |
| tests/test_tars_scroll_reset_regressions.py | Assertions updated to reflect new unpinned-branch inline check; mechanically correct |
| tests/test_issue677.py | Widens search window and updates assertion from btn.style.display to _syncScrollToBottomCue call; correct adaptation to the refactored scroll listener |
| tests/test_session_jump_buttons.py | Updates assertion to match new _syncScrollToBottomCue call in scroll listener; mechanically correct |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[renderMessages preserveScroll=true] --> B[_captureMessageScrollSnapshot\ncaptures top, bottom, scrollHeight, pinned]
B --> C[DOM rebuild]
C --> D[_scrollAfterMessageRender\npreserveScroll=true]
D --> E{_scrollPinned?}
E -- yes --> F[scrollIfPinned\nscroll to bottom]
E -- no --> G[_restoreMessageScrollSnapshot\nrestore scrollTop]
G --> H[_maybeShowNewMessageScrollCue]
H --> I{scrollHeight grew >24px\nAND distance >80px?}
I -- yes --> J[_showNewMessageScrollCue\nshow accented New message pill]
I -- no --> K[_syncScrollToBottomCue\nshow/hide plain End button\nbased on distance]
J --> L[User scrolls near bottom\nor clicks pill]
L --> M[_clearNewMessageScrollCue\nhide cue, restore End button label]
Reviews (2): Last reviewed commit: "fix(streaming): show new-message cue whe..." | Re-trigger Greptile
|
Heads-up: #3401 (live-to-final assistant reply redesign) shipped in v0.51.294, and we're now at v0.51.301. This PR (#3545 (new-message scroll cue)) is on a stale base and no longer applies cleanly onto current The scroll-to-bottom cue functions are largely additive, but the call-site integration conflicts with the rewritten scroll-preservation flow. Could you rebase onto current |
Cluster review re-confirm (#3400 live-to-final) — rework (rebase), NOT supersededRe-checked this against current
No action needed beyond a rebase onto current |
…esquena#3545) # Conflicts: # static/ui.js # tests/test_issue1690_scroll_completion.py # tests/test_tars_scroll_reset_regressions.py
9d1a3d6 to
b041caa
Compare
|
Rebased onto current
Focused verification I ran for this localized rework:
This should be ready for the fresh re-gate you mentioned. |
…) (#3849) * fix(streaming): show new-message cue when preserving scroll position (#3545) # Conflicts: # static/ui.js # tests/test_issue1690_scroll_completion.py # tests/test_tars_scroll_reset_regressions.py * i18n: add missing Polish (pl) translation for session_new_message keys The PR added session_new_message / session_new_message_label to 12 of 13 locales; Polish was missing both, which fails the per-locale parity test. Co-authored-by: rodboev <rodboev@users.noreply.github.com> * fix(streaming): keep forced follow path for pinned users in preserve-scroll branch Codex CORE catch: the PR's preserve-scroll branch used 'if(_scrollPinned) scrollIfPinned()' which skips the synchronous bottom write unless distance>500 and can have its delayed settles cancelled by the DOM-rebuild scroll event — leaving a pinned reader a few lines above the settled final response. Restore master's _followMessagesAfterDomReplace() forced-scrollToBottom() path for pinned/near-bottom users; only genuinely scrolled-up (unpinned, not near bottom) users restore their viewport and get the new-message cue. Updated the 3 structure-pinning tests to assert the corrected (safer) shape while preserving their behavioral intent. Co-authored-by: rodboev <rodboev@users.noreply.github.com> * Release v0.51.334 — Release KX (new-message cue when scrolled up, #3631) New-message cue on the jump-to-bottom button when the user has scrolled up during a live turn (#3545/#3631, @rodboev). Deep-reviewed (Opus+Codex); maintainer fixes during re-gate: (1) restored master's forced follow path for pinned/near-bottom users (Codex CORE: scrollIfPinned could leave a pinned reader short of the settled response) + updated 3 structure-pinning tests to the corrected shape; (2) added missing Polish (pl) i18n keys (PR had 12/13). Full suite 8308, ESLint/scope-undef CLEAN, Opus SHIP-safe, Codex SAFE-TO-SHIP. Co-authored-by: rodboev <rodboev@users.noreply.github.com> --------- Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: Hermes Agent <hermes-agent@nesquena-hermes.local> Co-authored-by: rodboev <rodboev@users.noreply.github.com>
|
Shipped in v0.51.334 (Release KX) via #3849 — thanks @rodboev! 🎉 I deep-reviewed this (Opus + Codex), rebased it onto current master, and shipped it with two fixes. What landed: when you scroll up to read during a live turn, a new message no longer silently lands off-screen nor yanks you to the bottom — the jump-to-bottom button shows a "New message" cue you click to catch up. Pinned/at-bottom readers still auto-follow as before. Fix #1 (Codex CORE catch): the preserve-scroll branch used Fix #2 (i18n): the two new I also independently confirmed the snapshot-shape is contained to a single producer (no spurious-cue path) and the cue state resets on every session/stream boundary. Gate: full suite 8308 passed, ESLint runtime + scope-undef + ruff clean, Opus SHIP-safe, Codex SAFE-TO-SHIP (after fix #1), CI 11/11 green. Co-authored to you. |
Thinking Path
What Changed
static/ui.js: captures scroll height in the preserve-scroll snapshot, detects unpinned renders that add content below the restored viewport, shows a temporary new-message cue, and clears it when the reader jumps to or scrolls back near the bottom.static/style.css: adds a stable visual cue state for the existing scroll-to-bottom pill.static/i18n.js: adds localized text and aria/title keys for the cue.tests/: adds static regression coverage for the preserve-scroll branch, cue clearing, i18n keys, and CSS hook.Why It Matters
Users can keep reading history while a long answer finishes, but they no longer land in an ambiguous state with no indication that the final response is available.
Verification
$env:PYTHONUTF8 = '1'; $env:BROWSER = 'echo'; C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_issue3545_streaming_scroll_cue.py tests/test_issue1690_scroll_completion.py tests/test_session_jump_buttons.py tests/test_tars_scroll_reset_regressions.py tests/test_issue677.py -v --timeout=60 --timeout-method=threadnode --check static/ui.jspytest tests/ -v --timeout=60Risks / Follow-ups
The cue uses a scroll-height growth heuristic rather than a full browser-driven runtime test because the current suite primarily pins these scroll invariants with static tests. A future Playwright regression could exercise the exact mid-stream wheel-scroll flow.
Model Used
GPT-5.5 via Codex CLI