Skip to content

fix(scroll): preserve user position during live stream reattach on session return - #3045

Closed
starship-s wants to merge 4 commits into
nesquena:masterfrom
starship-s:fix/scroll-jump-live-reattach-20260528
Closed

starship-s wants to merge 4 commits into
nesquena:masterfrom
starship-s:fix/scroll-jump-live-reattach-20260528

Conversation

@starship-s

Copy link
Copy Markdown
Contributor

Thinking Path

  • Hermes WebUI needs to support scrolling up to read history while a response is still generating, including after the user switches away from and back to the conversation.
  • The recent SSE reattach and stream ownership work (8408a3d + ffd9f33) correctly increased the frequency of loadSession → renderMessages() + attachLiveStream({reconnecting:true}) when returning to sessions that still have an active generation.
  • Two active-stream paths in loadSession were still calling renderMessages() without the existing preserveScroll contract.
  • Live token rendering paths continued to call scrollIfPinned() without consulting the upward scroll intent window that the scroll listener was already tracking.
  • Result: users who scrolled up after switching back to an in-progress chat were repeatedly yanked to the bottom on the next token batch.

What Changed

  • In static/sessions.js, the two live/reattach render sites inside loadSession now pass {preserveScroll:true} so the established snapshot/restore logic in _scrollAfterMessageRender can preserve the user’s position.
  • In static/ui.js, scrollIfPinned() now also short-circuits when _recentMessageUpwardIntent() is true. This makes the intent tracking (already computed by the wheel/scroll listener) authoritative for live token delivery.

Net change: 3 lines. No new state, no timeouts, no reattach-specific branches.

Why It Matters

Users can scroll up to review earlier turns while a response is generating, even after switching sessions. The previous behavior made this unreliable when returning to an in-progress chat.

Verification

  • Branch created directly from latest origin/master and pushed for review.
  • The two live/reattach renderMessages({preserveScroll:true}) call sites and the strengthened scrollIfPinned() guard were confirmed present.
  • Manual reproduction: long chat with active generation → switch away → return → immediately scroll up (wheel and trackpad momentum) while tokens continue to arrive.

Risks / Follow-ups

  • Low risk. All changes reuse long-standing scroll-pinning machinery (preserveScroll, _recentMessageUpwardIntent, _scrollAfterMessageRender).
  • No behavior change for users already pinned at the bottom or for historical (non-live) session loads.
  • Future live DOM mutation sources (compression cards, handoff summaries, etc.) should continue to be checked against the same user scroll intent rules.

Model Used

xAI / Grok Build 0.1

…h on session return

Recent SSE reattach improvements (8408a3d et al.) increased live render frequency
when returning to conversations with active generations.

Two active-stream paths in loadSession were still calling renderMessages()
without the preserveScroll contract. This caused the viewport to reset to
bottom (or near-bottom) right as token-driven scrollIfPinned() calls fired,
producing the "jumps back while scrolling up" symptom.

This change makes the remaining call site consistent with the other two
(using the mature {preserveScroll:true} + snapshot/restore path) and ensures
the existing upward-intent guards in scrollIfPinned() remain authoritative
during live token delivery.

The ui.js strengthening for _recentMessageUpwardIntent() was already present
on current master.

Cleanly based on origin/master (5528e2c).

Refs: 8408a3d, hermes-webui-operations skill (live-stream-reattach-scroll-pinning-regressions.md)
…oad path

The previous change had over-scoped the preserveScroll:true updates
to the non-live historical path in loadSession (the }else{ branch).

This path has long intentionally forced scrollToBottom() for finished
conversations on normal session switches. Changing it was outside the
scope of the live reattach scroll-jump regression fix.

Kept the two live/reattach paths (INFLIGHT reattach + activeStreamId)
with preserveScroll:true, plus the ui.js upward intent guard.

This addresses the scoping concern from code review.
… preserveScroll change

The tests were doing brittle string searches for the literal
'renderMessages();' call in the INFLIGHT branch.

Updated them to expect the new (correct) call with
{preserveScroll:true} that is now used for live reattach paths.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants