Skip to content

UI: harden chat scroll interrupts#72957

Closed
enis-uys wants to merge 5 commits into
openclaw:mainfrom
enis-uys:main
Closed

UI: harden chat scroll interrupts#72957
enis-uys wants to merge 5 commits into
openclaw:mainfrom
enis-uys:main

Conversation

@enis-uys

@enis-uys enis-uys commented Apr 27, 2026

Copy link
Copy Markdown

Summary

Describe the problem and fix in 2-5 bullets:

If this PR fixes a plugin beta-release blocker, title it fix(<plugin-id>): beta blocker - <summary> and link the matching Beta blocker: <plugin-name> - <summary> issue labeled beta-blocker. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.

  • Problem: Chat auto-follow in the Control UI could re-engage too aggressively when the user manually scrolled slightly upward during streaming, because the logic only used a broad "near bottom" threshold.
  • Why it matters: Users trying to read slightly older messages could get pulled back to the bottom, which makes streaming chats feel jumpy and hard to control.
  • What changed: Added an explicit manual-scroll release lock, only reacquire follow when the user is truly back at the bottom, ignore upward wheel intent from nested scrollables, and cover the smooth auto-scroll edge cases in ui/src/ui/app-scroll.test.ts.
  • Rebase note: Rechecked against latest main after feat(ui): add WebChat auto-scroll mode selector #81629 landed the separate persisted WebChat chatAutoScroll mode selector. This PR remains the manual-scroll follow-lock / sticky-bottom bug-fix lane, not a duplicate of the setting work.
  • What did NOT change (scope boundary): No session-management refactors, no tool-call visibility changes, no settings/auth/token behavior changes, and no unrelated UI restructuring.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write N/A. If the cause is unclear, write Unknown.

  • Root cause: The chat scroll state treated "near bottom" as equivalent to "should still auto-follow", so a small upward manual scroll could still satisfy follow conditions and snap the user back down on subsequent updates.
  • Missing detection / guardrail: There was no explicit "user took over scrolling" lock and no test coverage for slight upward manual scrolling during streaming.
  • Contributing context (if known): Smooth auto-scroll progress and user-driven upward scroll were both funneled through the same scroll-state path, which made intent ambiguous.

Regression Test Plan (if applicable)

For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write N/A.

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: ui/src/ui/app-scroll.test.ts
  • Scenario the test should lock in: When the user wheels or scrolls slightly upward from the bottom during chat streaming, auto-follow disengages immediately and does not re-enable until the user actually returns to the bottom.
  • Why this is the smallest reliable guardrail: The behavior is contained in the chat scroll state machine and can be deterministically exercised with direct scroll/wheel events.
  • Existing test that already covers this (if any): Existing tests covered broad near-bottom behavior, but not manual release/reacquire intent or the smooth auto-scroll edge cases.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

  • Chat no longer snaps back to the bottom as easily when the user manually scrolls slightly upward during streaming.
  • Auto-follow resumes only when the user truly returns to the bottom.
  • Wheel events coming from nested scrollable content inside the chat do not incorrectly disable or fight chat scroll state.
  • The persisted chatAutoScroll setting from feat(ui): add WebChat auto-scroll mode selector #81629 is preserved and respected; the follow-lock only tightens the default/manual sticky-bottom state machine around that setting.

Diagram (if applicable)

For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write N/A.

Before:
[user scrolls slightly up] -> [still "near bottom"] -> [next stream update] -> [auto-follow re-engages]

After:
[user scrolls slightly up] -> [follow locked off] -> [next stream update] -> [stays in place]
[user returns to bottom] -> [follow unlocked] -> [next stream update] -> [auto-follow resumes]

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local Node/Vite dev setup
  • Model/provider: N/A
  • Integration/channel (if any): Control UI chat
  • Relevant config (redacted): local gateway on ws://127.0.0.1:18789

Steps

  1. Open the Control UI chat while a response is streaming.
  2. Scroll slightly upward from the bottom or wheel upward during streaming.
  3. Continue streaming, then return fully to the bottom.

Expected

  • Slight upward manual scrolling disengages auto-follow.
  • Streaming updates do not snap the view back down.
  • Auto-follow resumes only after returning to the bottom.

Actual

  • Before fix: chat could snap back down while the user was still trying to read upward.
  • After fix: chat stays released until the bottom is reached again.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Real behavior proof

  • Behavior or issue addressed: Control UI WebChat streaming should not snap back to the bottom after the user manually scrolls slightly upward; follow-lock should release on manual upward intent and reacquire only when the user returns to the bottom.
  • Real environment tested: macOS local OpenClaw checkout, Node 24.14.0, rebased on current main with feat(ui): add WebChat auto-scroll mode selector #81629's persisted chatAutoScroll setting present. The proof command imports the actual ui/src/ui/app-scroll.ts implementation and exercises the WebChat scroll state used by the Control UI.
  • Exact steps or command run after this patch: Ran a local pnpm exec tsx proof command against ui/src/ui/app-scroll.ts with chatAutoScroll: "near-bottom", simulating a user scroll from bottom to scrollTop=1540, a streaming scheduleChatScroll() tick, then returning to bottom at scrollTop=1576.
  • Evidence after fix: Copied terminal output from the local proof command:
{
  "proof": "Control UI WebChat manual-scroll follow-lock after patch",
  "afterManual": {
    "scrollTop": 1540,
    "chatUserNearBottom": false,
    "chatFollowLocked": true,
    "chatNewMessagesBelow": false,
    "chatAutoScroll": "near-bottom"
  },
  "afterStreamTick": {
    "scrollTop": 1540,
    "chatUserNearBottom": false,
    "chatFollowLocked": true,
    "chatNewMessagesBelow": true
  },
  "afterReturnBottom": {
    "scrollTop": 1576,
    "chatUserNearBottom": true,
    "chatFollowLocked": false,
    "chatNewMessagesBelow": false
  }
}
  • Observed result after fix: After manual upward scroll, chatFollowLocked is true and the streaming tick leaves scrollTop at 1540 instead of snapping to 2000; chatNewMessagesBelow becomes true. After returning to bottom, chatFollowLocked clears and chatNewMessagesBelow clears. The persisted chatAutoScroll mode remains near-bottom.
  • What was not tested: Full end-to-end multi-browser matrix, authenticated live gateway streaming, and non-chat surfaces.

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Rebasing against current main kept feat(ui): add WebChat auto-scroll mode selector #81629's chatAutoScroll selector/settings path intact, then ran pnpm test ui/src/ui/app-scroll.test.ts ui/src/ui/app-chat.test.ts.
  • Edge cases checked: Slight upward scroll near bottom, re-enable only at the bottom, nested scrollable wheel intent, post-wheel near-bottom lock behavior, and smooth auto-scroll transition handling.
  • What you did not verify: Full end-to-end multi-browser matrix, full UI suite, and non-chat surfaces.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: The new release/reacquire thresholds could feel slightly different than before for some users near the bottom of the thread.
    • Mitigation: Thresholds are small and targeted, and focused unit tests lock in the intended behavior.
  • Risk: Wheel-based intent handling could interfere with nested scrollable UI inside messages.
    • Mitigation: Nested scrollable ancestors are explicitly ignored and covered by tests.

@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens chat scroll auto-follow in the Control UI by adding an explicit chatFollowLocked state, a chatSmoothAutoScrolling flag, and chatLastScrollTop tracking to distinguish intentional user scroll-up from auto-scroll progress. A new handleChatWheelIntent handler pre-emptively cancels pending auto-scroll when an upward wheel is detected, and the existing handleChatScroll logic now gates follow re-acquisition on a tight FOLLOW_REACQUIRE_THRESHOLD rather than the broader NEAR_BOTTOM_THRESHOLD. Test coverage is comprehensive.

Confidence Score: 4/5

Safe to merge — well-scoped UI-only change with comprehensive test coverage and no regressions to existing behavior.

No P0 or P1 issues found. Logic is correctly implemented: chatFollowLocked, chatSmoothAutoScrolling, and chatLastScrollTop form a sound state machine, all key transitions are covered by deterministic unit tests, and the nested-scrollable ancestor walk correctly guards against false releases. Score reflects P2-only quality with no blocking concerns.

No files require special attention.

Reviews (1): Last reviewed commit: "UI: refine chat wheel follow handling" | Re-trigger Greptile

@clawsweeper

clawsweeper Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: current main already carries the central WebChat backscroll/follow-lock fix from #92622, with regression coverage and beta release notes, so this older branch is no longer the landing path.

Canonical path: Keep the merged current-main follow-lock implementation from #92622; open a fresh narrow PR only if a wheel-specific edge still reproduces after that fix.

So I’m closing this here and keeping the remaining discussion on #92622.

Review details

Best possible solution:

Keep the merged current-main follow-lock implementation from #92622; open a fresh narrow PR only if a wheel-specific edge still reproduces after that fix.

Do we have a high-confidence way to reproduce the issue?

No current-main failing path remains: the original bug is source-reproducible in the latest stable-era helper, but current main now locks follow on upward user scroll and has regression tests for the near-bottom streaming cases.

Is this the best way to solve the issue?

No, this PR is no longer the best way to solve it; the narrower merged replacement changes the central scroll helper and tests without carrying this branch's extra wheel/smooth-scroll path or release-owned changelog entry.

Security review:

Security review cleared: The reviewed diff is limited to Control UI scroll state, tests, app/view wiring, and one changelog line; no dependency, workflow, permission, network, credential, or secret-handling change was found.

AGENTS.md: found and applied where relevant.

What I checked:

  • Repository policy read: The full root AGENTS.md and scoped UI AGENTS.md were read; the review applied the repo rules for PR supersession, whole-path UI review, and release-owned changelog handling. (AGENTS.md:1, b5999bc6a07d)
  • Current main source has the follow lock: Current main gates near-bottom auto-scroll on !host.chatFollowLocked, sets the lock on upward user scroll, clears it only at the bottom threshold, and resets it with chat scroll state. (ui/src/ui/app-scroll.ts:85, b5999bc6a07d)
  • Current main regression coverage: Current main tests the reported near-bottom backscroll cases at scrollTop=1540 and scrollTop=1589, then verifies streaming updates leave the view in place and mark new messages below. (ui/src/ui/app-scroll.test.ts:269, b5999bc6a07d)
  • Merged replacement provenance: The canonical merged replacement is fix(ui): preserve WebChat backscroll during streaming, which changed only the central scroll helper, its tests, and app state wiring. (ui/src/ui/app-scroll.ts:85, d3e7e0366907)
  • Release provenance: GitHub release v2026.6.8-beta.1 notes include the WebChat backscroll fix via fix(ui): preserve WebChat backscroll during streaming #92622, and GitHub compare shows the beta tag target contains the merged fix; latest stable v2026.6.6 predates it. (3c43b5927042)
  • This branch is now overlapping: The open branch still adds a parallel wheel/smooth-scroll implementation plus one direct release changelog entry; after the merged current-main fix, any remaining wheel-specific edge should be retargeted as a fresh narrow PR. (ui/src/ui/app-scroll.ts:44, cf25d824345a)

Likely related people:

  • TurboTheTurtle: Authored the merged replacement PR that preserves WebChat backscroll during streaming and closes the later canonical report. (role: canonical fix contributor; confidence: high; commits: d3e7e0366907; files: ui/src/ui/app-scroll.ts, ui/src/ui/app-scroll.test.ts, ui/src/ui/app.ts)
  • Vincent Koc: Authored the follow-up commit that kept forced initial chat scrolls above the new follow lock in the merged replacement path. (role: recent follow-up contributor; confidence: high; commits: 62be2a28aa14, d3e7e0366907; files: ui/src/ui/app-scroll.ts, ui/src/ui/app-scroll.test.ts)
  • BunsDev: Authored the merged WebChat auto-scroll mode selector work that this follow-lock path must preserve and commented that the follow-lock lane was distinct before the later replacement merged. (role: adjacent auto-scroll settings contributor; confidence: high; commits: 256377c029f6; files: ui/src/ui/app-scroll.ts, ui/src/ui/storage.ts, ui/src/ui/views/chat.ts)
  • Marco Marandiz: Earlier commits added the WebChat user-scroll preservation tests and near-bottom scroll behavior that the current follow-lock fix builds on. (role: prior scroll behavior contributor; confidence: medium; commits: e18f43ddade6, 822388fe9236; files: ui/src/ui/app-scroll.ts, ui/src/ui/app-scroll.test.ts)
  • nickmopen: Authored the programmatic scroll guard work that current main now tightens when a user scrolls upward during the guard window. (role: adjacent guard contributor; confidence: medium; commits: b90f28e89506; files: ui/src/ui/app-scroll.ts, ui/src/ui/app-scroll.test.ts, ui/src/ui/app.ts)

Codex review notes: model internal, reasoning high; reviewed against b5999bc6a07d; fix evidence: release v2026.6.8-beta.1, commit d3e7e0366907.

@enis-uys
enis-uys force-pushed the main branch 2 times, most recently from a21f3a5 to 88360ac Compare May 4, 2026 01:11
@enis-uys

enis-uys commented May 4, 2026

Copy link
Copy Markdown
Author

Thanks for the review. I addressed the missing changelog item in CHANGELOG.md and manually tested the Control UI chat scroll behavior.

Local validation is green for pnpm check:changed.

I also tried to run the requested Testbox changed gate, but Blacksmith auth reports: "You don't have any GitHub organizations with Blacksmith installed. Install the Blacksmith GitHub App first." My GitHub account therefore cannot start an OpenClaw Testbox from here.

Since the remaining required gate is specifically pnpm check:changed in Testbox, a maintainer with OpenClaw Blacksmith/Testbox access will need to run that before merge, or grant/setup the required access for me.

One note: because this PR now touches CHANGELOG.md, there may be a small conflict risk if new changelog entries land before merge. I can rebase/fix that if needed.

@BunsDev

BunsDev commented May 14, 2026

Copy link
Copy Markdown
Contributor

Related maintainer note: #81629 has now landed a separate persisted WebChat auto-scroll mode selector on main.

This PR is still a distinct bug-fix lane for manual-scroll follow-lock / sticky-bottom behavior, not a duplicate of the setting work. Before any further review or merge attempt, please rebase against latest main and recheck the scroll state changes with the new chatAutoScroll setting now present in the same Control UI files.

Thanks for the work on the follow-lock edge cases; this remains useful, just separate from the preference that shipped today.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed size: L proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@enis-uys

Copy link
Copy Markdown
Author

Thanks, done.

I rebased this PR onto latest main after #81629 landed and rechecked the scroll-state changes with the persisted chatAutoScroll setting present.

This PR now keeps #81629's auto-scroll mode selector/settings path intact and only carries the separate manual-scroll follow-lock / sticky-bottom fix:

  • manual upward scroll releases auto-follow immediately
  • streaming updates no longer snap back while follow is locked
  • follow reacquires only once the user returns to the bottom
  • chatAutoScroll: "off" and manual scroll-to-bottom behavior remain respected

Verification:

  • pnpm test ui/src/ui/app-scroll.test.ts ui/src/ui/app-chat.test.ts
  • pnpm exec oxfmt --check --threads=1 CHANGELOG.md ui/src/ui/app-chat.test.ts ui/src/ui/app-chat.ts ui/src/ui/app-render.ts ui/src/ui/app-scroll.test.ts ui/src/ui/app-scroll.ts ui/src/ui/app-view-state.ts ui/src/ui/app.ts ui/src/ui/views/chat.ts
  • manual Control UI smoke with Near bottom: started a long streaming response, scrolled slightly upward during generation, confirmed the view stayed released instead of snapping back down, then returned to bottom and confirmed follow resumed

I also updated the PR body with a Real behavior proof section covering the rebased behavior.

@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 17, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. P2 Normal backlog priority with limited blast radius. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 17, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 31, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 31, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 2, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 2, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 2, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 2, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 3, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 3, 2026
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jun 14, 2026
@clawsweeper

clawsweeper Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper applied the proposed close for this PR.

  • Action: closed this PR.
  • Close reason: duplicate or superseded.
  • Evidence: durable ClawSweeper review.
  • Coverage proof: PR B clearly carries PR A's core WebChat manual-scroll/follow-lock intent and has already merged as the narrower canonical implementation with tests. PR A's extra wheel/smooth-scroll path and changelog entry are not independent remaining work on the current evidence. Covering PR: fix(ui): preserve WebChat backscroll during streaming #92622.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: M status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Control UI] Chat auto-scroll overrides user scroll during streaming

2 participants