Skip to content

fix: preserve scroll on stream completion#1709

Merged
1 commit merged intonesquena:masterfrom
Michaelyklam:fix/issue-1690-scroll-completion-20260505085644
May 5, 2026
Merged

fix: preserve scroll on stream completion#1709
1 commit merged intonesquena:masterfrom
Michaelyklam:fix/issue-1690-scroll-completion-20260505085644

Conversation

@Michaelyklam
Copy link
Copy Markdown
Contributor

@Michaelyklam Michaelyklam commented May 5, 2026

Thinking Path

  • Hermes WebUI needs long streamed responses to remain readable while the user reviews earlier content.
  • Issue bug: scroll jumps to bottom when response completes after user scrolls up #1690 reported that manually scrolling up during a long stream still jumped to the bottom when the SSE terminal done render completed.
  • The root cause was that terminal handlers clear S.activeStreamId before their final renderMessages() call, while renderMessages() chose between scrollIfPinned() and scrollToBottom() from stream liveness alone.
  • This PR adds an explicit terminal-render scroll mode so completion/error/cancel rerenders respect the user's current pin state while ordinary session load/switch renders still bottom-pin.
  • The benefit is that users can keep reading mid-response when a long turn finishes, with the scroll-to-bottom affordance still available.

Fixes #1690.

What Changed

  • Added _scrollAfterMessageRender(preserveScroll) in static/ui.js and let renderMessages({preserveScroll:true}) use scrollIfPinned() even after S.activeStreamId has been cleared.
  • Updated stream terminal paths in static/messages.js (done, apperror, cancel, settled restore, connection loss, stale reattach, and background completion) to request scroll preservation for same-pane terminal rerenders.
  • Kept default renderMessages() behavior unchanged for normal idle renders and session load/switch paths so opening a session still bottom-pins.
  • Added issue-specific source regressions in tests/test_issue1690_scroll_completion.py and updated existing scroll/source tests for the new helper-based render policy.
  • Added browser QA media under docs/pr-media/1690/scroll-preserved-after-completion.png.

Why It Matters

A long response can be hundreds of lines. If the user scrolls up while it streams, completion should not yank them back to the bottom and make them lose their place. This keeps the chat interaction predictable: live updates only follow the bottom when the user is pinned, and completed renders preserve manual scroll position.

Verification

/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue1690_scroll_completion.py -q
/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue1690_scroll_completion.py tests/test_issue734_message_windowing.py tests/test_regressions.py::test_done_handler_sets_busy_false_before_renderMessages -q
/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue1690_scroll_completion.py tests/test_issue734_message_windowing.py tests/test_regressions.py tests/test_streaming_race_fix.py tests/test_streaming_markdown.py tests/test_issue856_background_completion_unread.py tests/test_parallel_session_switch.py -q
/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue677.py tests/test_issue1690_scroll_completion.py tests/test_issue734_message_windowing.py tests/test_regressions.py::test_done_handler_sets_busy_false_before_renderMessages tests/test_issue856_background_completion_unread.py::test_hidden_active_done_still_updates_current_pane_but_not_read_state -q
env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/ -q
git diff --check
git diff --cached --check

Result:

RED: tests/test_issue1690_scroll_completion.py initially failed with 3 failures before implementation.
Targeted scroll/source checks: 10 passed, then 176 passed, then post-rebase 20 passed.
Full suite attempt #1: 4506 passed, 2 skipped, 3 xpassed, 1 warning, 8 subtests passed; failed only stale source-string test_issue677 expecting `function renderMessages()` after this PR added an options parameter.
Full suite attempt #2 after updating source tests: 4462 passed, 2 skipped, 3 xpassed, 1 warning, 8 subtests passed; 45 failures from the isolated pytest server/state harness becoming unavailable (`127.0.0.1:24921` connection refused / `webui-test-*` state files missing), not from the scroll-source tests. Targeted post-rebase checks remain green.
GitHub Actions: test (3.11), test (3.12), and test (3.13) all passed on commit `311e69b`.
git diff --check and git diff --cached --check: passed.

Latest local rerun in recovered Kanban run:

/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue1690_scroll_completion.py tests/test_issue677.py tests/test_issue734_message_windowing.py tests/test_issue856_background_completion_unread.py tests/test_regressions.py -q
# 87 passed in 7.40s

/home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue1690_scroll_completion.py tests/test_issue677.py tests/test_issue734_message_windowing.py tests/test_issue856_background_completion_unread.py tests/test_regressions.py tests/test_streaming_race_fix.py tests/test_sprint9.py tests/test_stale_stream_cleanup.py tests/test_streaming_markdown.py tests/test_ui_tool_call_cleanup.py tests/test_inflight_stream_reuse.py tests/test_issue1360_streaming_scroll_hardening.py tests/test_parallel_session_switch.py tests/test_1466_bfcache_inflight_reattach.py -q
# 225 passed in 8.32s

env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/ -q
# 4492 passed, 2 skipped, 15 failed, 3 xpassed, 1 warning, 8 subtests passed in 466.13s; failures were unrelated server/state-harness/session/onboarding tests, while all scroll/streaming targeted suites remained green.

Manual/browser verification:

  • Started an isolated local WebUI server on 127.0.0.1:18790 from this worktree.
  • Injected a long synthetic streaming response, manually moved the viewport away from the bottom, cleared S.activeStreamId, and called renderMessages({preserveScroll:true}) to simulate the terminal done path.
  • Browser state showed scrollTop unchanged at 1352 before/after completion, _scrollPinned=false, S.activeStreamId=null, scroll-to-bottom button still visible, and nearBottom=false.

Evidence / UI Media

Scroll preserved after stream completion

Raw media verified: HTTP 200, 59,886 bytes.

Risks / Follow-ups

  • The fix intentionally introduces an explicit render option instead of inferring terminal behavior from stream liveness; future terminal render paths should use renderMessages({preserveScroll:true}) when they complete the current pane after clearing stream state.
  • Local full-suite verification hit a pytest-server/state-dir instability after the source-string tests were updated; GitHub Actions passed on Python 3.11, 3.12, and 3.13.

Model Used

AI assisted.

  • Provider: OpenAI Codex
  • Model: gpt-5.5
  • Notable tool use: Hermes terminal/file tools, pytest, git/gh, isolated browser QA with synthetic stream completion state.

@nesquena-hermes nesquena-hermes closed this pull request by merging all changes into nesquena:master in 0ea3dfb May 5, 2026
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Closed by the v0.51.5 release in PR #1713 (merged at 0ea3dfb, deployed to production). Thanks!

Live on production: https://github.com/nesquena/hermes-webui/releases/tag/v0.51.5

🚀

githb-ac pushed a commit to githb-ac-org/hermes-webui that referenced this pull request May 5, 2026
githb-ac pushed a commit to githb-ac-org/hermes-webui that referenced this pull request May 5, 2026
4 PRs (1 surface addition, 3 fixes):
- nesquena#1688 VPS resource health Insights panel (@Michaelyklam, closes nesquena#693)
- nesquena#1709 preserve scroll on stream completion (@Michaelyklam, closes nesquena#1690)
- nesquena#1711 hide rename tooltip on folders (@nesquena-hermes, closes nesquena#1710)
- nesquena#1712 guard localStorage.setItem against QuotaExceededError (@24601)

Tests: 4504 → 4527 (+23). Opus: SHIP, 6/6 verification clean.

Held back: nesquena#1686 (Docker enhance) — Opus flagged sibling-repo dep that
breaks standalone clones. Left open for follow-up.

Co-authored-by: Michael Lam <[email protected]>
Co-authored-by: 24601 <[email protected]>
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.

bug: scroll jumps to bottom when response completes after user scrolls up

2 participants