Skip to content

Fix chat scrollbar not reaching bottom during streaming via scroll anchoring#1014

Open
karkarl wants to merge 3 commits into
openclaw:mainfrom
karkarl:karkarl-fix-chat-scrollbar-reconciliation
Open

Fix chat scrollbar not reaching bottom during streaming via scroll anchoring#1014
karkarl wants to merge 3 commits into
openclaw:mainfrom
karkarl:karkarl-fix-chat-scrollbar-reconciliation

Conversation

@karkarl

@karkarl karkarl commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the chat-timeline bug where the scrollbar doesn't reach the bottom while the agent is thinking/streaming.

While the agent streams, the last assistant row grows in place — no new Props.Entries, no ScrollToBottomToken bump, and no reliable SizeChanged. Under ItemsRepeater virtualization the extent estimate climbs across realization frames, so the offset lands a few px short of the bottom with nothing to re-pin it.

An earlier attempt used a reactive ViewChanged re-pin. Live testing showed it (a) painted an intermediate short frame → jitter, and (b) its programmatic ChangeView fought the user's own drag. This PR replaces that with WinUI ScrollViewer scroll anchoring.

Note: despite the branch name, the root cause is not ComboBox-style FunctionalUI control recreation (PR #991). It's follow-to-bottom convergence timing under ItemsRepeater virtualization. The original hypothesis was empirically disproven.

Changes

src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:

  • Set sv.VerticalAnchorRatio = 1.0 in the one-time ScrollViewer init. WinUI keeps the bottom-most realized row glued to the viewport bottom before each frame is painted as the extent grows — smooth, no self-feeding loop, and it does not fight programmatic ChangeView or the user scrolling up to read.
  • Removed the reactive re-pin from the ViewChanged handler; it now only refreshes scroll metrics and drives the load-earlier trigger.
  • QueuePreservePrependOffset toggles anchoring to NaN during the manual load-earlier delta correction (so it doesn't double-correct content inserted above) and restores it to 1.0 afterward, with a fail-safe restore if a dispatcher enqueue is rejected or the correction throws. Expanded the inline comment to document that double.NaN is the WinUI-documented sentinel that disables anchoring (the default value; 0.0 would instead anchor to the top edge), so a future reader doesn't "simplify" it to 0.0.
  • Discrete events (append, session switch, initial load, ScrollToBottom token) still use QueueScrollToBottom; anchoring only covers the in-place growth case.

tests/OpenClaw.Tray.UITests/ChatTimelineVirtualizationProofTests.cs:

  • ThinkingAndStreamingGrowth_KeepsViewPinnedToBottom — regression proof that the bottom stays pinned (gap ≤ 4px) across the thinking indicator + 4 streaming revisions. Now also logs the gap/offset/scrollable each step (CHAT_TIMELINE_PIN_PROOF).
  • New UserScrolledUpDuringStreaming_KeepsReadingPositionStable — proves the "don't fight the scrollbar" half: with the user deliberately scrolled up, streaming growth of the off-screen newest row does not yank them to the bottom (gap stays > FollowThreshold, reading-position drift ≤ 24px across 4 revisions).
  • New PrependHistory_PreservesOffset_AndRestoresBottomAnchoring — proves the highest-risk path that a fresh live recording can't reach (a new session has no earlier history to load): load-earlier prepend preserves the visible offset (shifts by the inserted content height) and restores VerticalAnchorRatio to 1.0 — not left at the NaN disable sentinel — after which a ScrollToBottomToken bump follows to the newest row again end-to-end. A regression that left anchoring disabled would silently break bottom-follow for the rest of the session; this test guards it.
  • LargeNativeChatTimeline_VirtualizesRowsAndFollowsNewMessages / RealizedComponentRow_ReplacesRootAndPrunesRemovedEffects — unchanged virtualization proofs.

tests/OpenClaw.Tray.UITests/UIThreadFixture.cs:

  • Add YieldToRenderAsync() — awaits one DispatcherQueuePriority.Low callback as a bounded, deterministic render-queue drain primitive (all higher-priority layout/render/realization work has run by the time a Low continuation resumes); completes immediately if the dispatcher is shutting down so a caller can never block.
  • DrainRenderQueueAsync replaces its single Task.Delay(50) with 6 bounded passes of UpdateLayout + YieldToRenderAsync + a 5ms floor — fewer magic sleeps, and it can't spin or hang even if layout never fully quiesces.

Known residual (accepted, temporary)

Slight jitter as the viewport expands during virtualization/streaming remains: anchoring keeps the bottom pinned, but per-frame extent re-estimation from ItemsRepeater still produces small visible jumps during rapid in-place growth (see the clip-3 resize drift below). Accepted as a temporary fix; the clean solution is the Reactor port. Documented on #996.

Validation

Host is arm64; targeted -r win-arm64 where a RID is required.

  • ./build.ps1 — ✅ all projects
  • dotnet test ./tests/OpenClaw.Shared.Tests/... — ✅ 2863 passed, 31 skipped (MXC host-gated)
  • dotnet test ./tests/OpenClaw.Tray.Tests/... — ✅ 1717 passed
  • dotnet build tests/OpenClaw.Tray.UITests -c Debug -r win-arm64 — ✅ 0 warnings / 0 errors (warnings-as-errors on) — confirms the 3 new/changed test files compile clean
  • Rubber-duck review: no blocking issues; the non-blocking fail-safe anchor-restore finding is addressed.
  • UITests (OpenClaw.Tray.UITests) execution: Not run locally — this suite times out in the dev harness. All UITests (including the two new ones) run in CI via the test job in .github/workflows/ci.yml. Latest run for this head (8acd9248): https://github.com/openclaw/openclaw-windows-node/actions/runs/29621572276

Real behavior proof

Verified on an isolated running instance (OPENCLAW_TRAY_DATA_DIR redirect, real %APPDATA%\OpenClawTray untouched), current PR head, real gateway/node path (agent: Claude Opus 4.7).

Proof video (multi-scenario)

Three back-to-back scenarios on the isolated instance:

# Scenario Diagnostic (live ScrollPattern %)
1 Streaming, following — long reply grows in place pinned 100% held for 12s while streaming=True
2 Streaming, scrolled up — user scrolls up to read, then holds scrolled to 28.3%, held 28.3% for 14s while streaming=True (not yanked to bottom)
3 Resize during streaming — viewport height changes mid-stream recovers to bottom band then drifts 100% → ~91% — the accepted residual jitter

Video (58s, uploaded to a fork release since it exceeds the inline-attachment limit):
https://github.com/karkarl/openclaw-windows-node/releases/download/scroll-proof-1014/scroll-proof-3scenarios.mp4

scroll-proof-3scenarios.mp4

Original single-scenario embedded clip (thinking → streaming, pinned throughout) retained for inline playback:

scroll-proof.mp4

Developer-confirmed: "It does pin and scroll better now." Residual jitter as the viewport expands is the accepted temporary limitation noted above.

Refs #996

…choring

While the agent is thinking/streaming, the last assistant row grows in place
(no new Props.Entries, no ScrollToBottomToken bump, no reliable SizeChanged).
The ItemsRepeater extent estimate climbs across realization frames, leaving the
offset a few px short of the bottom with nothing to re-pin it -- the "scrollbar
doesn't reach the bottom while the agent is thinking" bug.

Replace the earlier reactive ViewChanged re-pin (which painted an intermediate
short frame -> jitter, and whose programmatic ChangeView fought the user's own
drag) with WinUI ScrollViewer scroll anchoring:

- Set sv.VerticalAnchorRatio = 1.0 in the one-time ScrollViewer init so the
  bottom-most realized row stays glued to the viewport bottom BEFORE paint as the
  extent grows. The ViewChanged handler no longer re-pins; it only refreshes
  metrics and drives the load-earlier trigger.
- QueuePreservePrependOffset toggles anchoring to NaN during the manual
  load-earlier delta correction and restores it to 1.0 afterward, with a
  fail-safe restore if a dispatcher enqueue is rejected or the correction throws.

Discrete events (append, session switch, initial load, ScrollToBottom token)
still use QueueScrollToBottom; anchoring only covers the in-place growth case.

Known residual (acceptable temporary fix): slight jitter as the viewport expands
during virtualization/streaming. Tracked on issue openclaw#996 (Reactor port).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels Jul 17, 2026
@clawsweeper

clawsweeper Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed July 17, 2026, 8:38 PM ET / July 18, 2026, 00:38 UTC.

Summary
The PR uses WinUI scroll anchoring to keep the chat timeline at the bottom during in-place streaming growth, adds prepend/user-scroll regression coverage, and replaces a fixed UI-test delay with bounded render-queue draining.

Reproducibility: yes. from source: loading earlier history calls the changed prepend helper, which sets suppression and then assigns an unsupported anchor ratio before it can queue restoration. The supplied native recording also directly demonstrates the streaming follow and user-scroll scenarios on the PR head.

Review metrics: 2 noteworthy metrics.

  • Patch surface: 3 files affected; 458 added, 17 removed. Most of the patch is native UI regression proof, while the merge blocker is concentrated in the production prepend path.
  • Streaming scenarios: 3 recorded scenarios. The recording directly covers bottom-follow and user scroll intent, but it also exposes the scoped resize limitation.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦪 silver shellfish
Result: blocked by patch quality or review findings.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Replace the NaN assignment with a documented WinUI anchoring control path.
  • Run the targeted native UI regression tests on the repaired head and publish their actual result.
  • Capture or retain redacted native proof that history prepend preserves position and streaming follow resumes afterward.

Risk before merge

  • [P1] Merging the unsupported NaN path can break load-earlier recovery and leave later auto-follow behavior suppressed.
  • [P1] The supplied recording shows the scope limitation during resize while streaming; maintainers should retain that limitation as an explicit follow-up rather than treating this patch as a complete virtualization-jitter solution.

Maintainer options:

  1. Use a supported prepend anchor strategy (recommended)
    Replace the NaN assignment with documented scroll-anchor control and rerun the prepend plus streaming regression proof before merge.
  2. Pause the anchoring change
    Do not merge the current implementation if a supported way to suspend or override anchoring cannot preserve the existing prepend invariant.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Replace the unsupported NaN-based anchor suspension with a documented WinUI anchor-control strategy, preserve prepend offset semantics, and provide native UI proof for history prepend followed by streaming.

Next step before merge

  • [P2] A narrow production-path repair is available, but it must retain native prepend and streaming proof after replacing the unsupported API use.

Security
Cleared: The patch changes local WinUI scrolling and UI tests only; no dependency, secret, workflow, privilege, or supply-chain concern was found.

Review findings

  • [P1] Use a supported way to suspend scroll anchoring — src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:692
Review details

Best possible solution:

Keep VerticalAnchorRatio = 1.0 for the documented bottom-follow behavior, but replace the NaN-based prepend workaround with a supported anchor-selection/candidate mechanism and prove both prepend preservation and post-prepend bottom follow on native WinUI.

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

Yes, from source: loading earlier history calls the changed prepend helper, which sets suppression and then assigns an unsupported anchor ratio before it can queue restoration. The supplied native recording also directly demonstrates the streaming follow and user-scroll scenarios on the PR head.

Is this the best way to solve the issue?

No. Bottom anchoring at 1.0 is supported for end-of-content chat behavior, but using NaN to disable it is outside the documented ScrollViewer contract; the prepend exception path needs a supported anchoring mechanism.

Full review comments:

  • [P1] Use a supported way to suspend scroll anchoring — src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:692
    VerticalAnchorRatio is publicly documented as a validated normalized value from 0.0 through 1.0, with 0.0 as its default—not a NaN disable sentinel. This assignment runs after suppressAutoFollowRef.Current = true but before the dispatcher callback that restores state, so a validation failure during history prepend can escape the fail-safe and leave later auto-follow suppressed. Replace it with a supported anchor-selection/candidate mechanism and retain the prepend regression proof. (learn.microsoft.com)
    Confidence: 0.96

Overall correctness: patch is incorrect
Overall confidence: 0.96

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4518dd87ed2b.

Label changes

Label changes:

  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦞 diamond lobster and patch quality is 🦪 silver shellfish.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (recording): A current-head native recording with live scroll diagnostics directly shows bottom-follow during streaming and a user remaining scrolled up; redact any private gateway or endpoint details in future replacement media.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🦪 silver shellfish, so this older rating label is no longer current.

Label justifications:

  • P2: This is a bounded chat-timeline correctness fix with a concrete merge-blocking defect, but it is not an emergency availability or security incident.
  • merge-risk: 🚨 other: The new anchor-suspension path can leave chat auto-follow in an invalid state after the existing load-earlier workflow.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦞 diamond lobster and patch quality is 🦪 silver shellfish.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (recording): A current-head native recording with live scroll diagnostics directly shows bottom-follow during streaming and a user remaining scrolled up; redact any private gateway or endpoint details in future replacement media.
  • proof: sufficient: Contributor real behavior proof is sufficient. A current-head native recording with live scroll diagnostics directly shows bottom-follow during streaming and a user remaining scrolled up; redact any private gateway or endpoint details in future replacement media.
  • proof: 🎥 video: Contributor real behavior proof includes video or recording evidence. A current-head native recording with live scroll diagnostics directly shows bottom-follow during streaming and a user remaining scrolled up; redact any private gateway or endpoint details in future replacement media.
Evidence reviewed

Acceptance criteria:

  • [P1] ./build.ps1.
  • [P1] dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore.
  • [P1] dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore.
  • [P1] dotnet test ./tests/OpenClaw.Tray.UITests/OpenClaw.Tray.UITests.csproj --no-restore.

What I checked:

  • Unsupported anchor suspension: The changed prepend path assigns double.NaN to VerticalAnchorRatio after setting suppressAutoFollowRef.Current = true; Microsoft documents this property as a normalized value from 0.0 through 1.0 with a default of 0.0, and its API metadata declares validation. The PR's assertion that NaN is the documented disable sentinel conflicts with that public contract. (learn.microsoft.com) (src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:692, 8acd9248712d)
  • Failure escapes the advertised restore path: Because the unsupported assignment occurs before DispatcherQueue.TryEnqueue, an exception prevents the queued finally restoration from running and can leave the already-set auto-follow suppression active for the session's later scroll handling. (src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:692, 8acd9248712d)
  • Positive real-behavior evidence: The PR body supplies a current-head 58-second native recording with live ScrollPattern diagnostics for bottom-follow and user-scroll-up behavior; it directly supports the central streaming-use-case claim, while also disclosing resize drift as a remaining limitation. (8acd9248712d)
  • Related history: The related merged chat work in commit a0042af establishes karkarl as a recent contributor to adjacent FunctionalUI chat behavior, although it does not establish that the ComboBox work caused this scroll issue. (src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs:77, a0042affb8c0)

Likely related people:

  • karkarl: Authored the current PR and previously landed the related FunctionalUI chat stabilization work merged as PR 991; the current scroll-timeline change is in the same chat UI area. (role: recent adjacent chat contributor; confidence: medium; commits: a0042affb8c0, 8acd9248712d; files: src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (5 earlier review cycles)
  • reviewed 2026-07-17T20:53:40.270Z sha 214a47c :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-17T22:32:51.287Z sha 214a47c :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-17T23:15:14.289Z sha 214a47c :: needs real behavior proof before merge. :: [P2] Replace fixed compositor delays with eventual assertions
  • reviewed 2026-07-17T23:50:26.834Z sha 8acd924 :: needs real behavior proof before merge. :: [P1] Use a supported way to suspend scroll anchoring
  • reviewed 2026-07-17T23:56:56.875Z sha 8acd924 :: needs maintainer review before merge. :: none

@shanselman

Copy link
Copy Markdown
Contributor

GitHub Copilot maintainer-assistant review — automated feedback, not written personally by Scott.

Reviewed head: 214a47ce270cc967fa80d49c7d1ea9fc8b05a5ba

Using native VerticalAnchorRatio = 1.0 for in-place streaming growth is the right architectural direction; it is preferable to continuously fighting layout with post-render ChangeView calls. The implementation also carefully disables/restores anchoring around prepend-offset correction.

The current proof/test coverage is not sufficient to merge yet:

  1. The new ThinkingAndStreamingGrowth_KeepsViewPinnedToBottom UI test was reportedly not run. Please run it on current-head Windows CI/local UI and include actual gap values, not only a pass claim.
  2. Add a real UI test for user intent: scroll above FollowThreshold, stream multiple revisions into the active assistant row, and prove VerticalOffset remains near the user-selected position rather than snapping to bottom.
  3. Add a prepend-history test: with HasMoreHistory=true, prepend entries, verify offset preservation, then append/stream again and prove anchoring was restored rather than left disabled or double-correcting.
  4. Replace fixed Task.Delay(50) compositor drains with bounded polling/eventual assertions. The test should wait for stable extent/offset conditions with a timeout; a tight fixed delay can be flaky or pass before the intended anchor adjustment occurs.
  5. Provide a short current-head recording showing: bottom-follow during streaming, user scroll-up remaining stable, and resize/DPI change during streaming.

Please also document or verify the double.NaN anchoring-disable sentinel. VerticalAnchorRatio documents normalized values; if NaN is retained as a WinUI implementation pattern, pin it with the prepend test and a safe restoration path. Otherwise prefer a documented value/strategy.

The code approach looks promising, but the tests currently prove only the happy bottom-pinned path and have not themselves been demonstrated to run. This is a UI behavior change, so current-head native proof is the remaining gate.

@shanselman

Copy link
Copy Markdown
Contributor

GitHub Copilot maintainer-assistant note — automated, not written personally by Scott.

@karkarl, thank you for working on this—the native anchoring approach looks promising. Could you please add a short current-head screen recording? A static screenshot cannot demonstrate the timing/scroll behavior we need to verify.

Ideally the recording would show:

  1. an assistant response actively streaming while the view remains pinned to the bottom;
  2. scrolling up during that same stream and remaining at the chosen position without being pulled back down;
  3. loading earlier history without a viewport jump, followed by streaming/appending to show anchoring was restored;
  4. resizing the window during streaming without losing the expected position.

Please also include the exact PR SHA and the command/result for the new Tray UI tests. If the UI test suite is difficult to run locally, a link to the current-head CI job/artifact is fine once it demonstrably executes the new test.

That proof plus deterministic eventual/polling assertions in place of fixed compositor delays would give maintainers a much stronger basis to land the change. Thanks!

@clawsweeper clawsweeper Bot added proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. labels Jul 17, 2026
…ic drain

Address PR review feedback (shanselman + clawsweeper) requesting deeper test
coverage of the VerticalAnchorRatio=1.0 scroll-anchoring fix.

Tests (tests/OpenClaw.Tray.UITests/ChatTimelineVirtualizationProofTests.cs):
- UserScrolledUpDuringStreaming_KeepsReadingPositionStable: proves streaming
  growth of the off-screen newest row does NOT yank a scrolled-up user to the
  bottom (gap stays > FollowThreshold, reading position drift <= 24px across 4
  revisions) - the "don't fight the scrollbar" half of the fix.
- PrependHistory_PreservesOffset_AndRestoresBottomAnchoring: proves load-earlier
  prepend preserves the visible offset (shifts by inserted height) AND restores
  VerticalAnchorRatio to 1.0 (not left at the NaN disable sentinel), then a
  scroll-to-bottom token follows again end-to-end. Guards the highest-risk path
  that a fresh live recording cannot exercise.
- Gap-value diagnostics logging on the streaming pin test (CHAT_TIMELINE_*).

Determinism (UIThreadFixture.cs + test drain):
- Add YieldToRenderAsync(): awaits one Low-priority dispatcher callback as a
  bounded render-queue drain primitive; completes immediately if the dispatcher
  rejects the enqueue so callers can never block.
- Replace DrainRenderQueueAsync's single Task.Delay(50) with 6 bounded passes of
  UpdateLayout + YieldToRenderAsync + a 5ms floor.

Docs (OpenClawChatTimeline.cs):
- Expand the QueuePreservePrependOffset comment to document that double.NaN is
  the documented WinUI sentinel that disables anchoring (default value; 0.0 would
  anchor to the TOP edge, not disable), per the VerticalAnchorRatio contract.

Validation: build.ps1 green; UITests project compiles clean (0 warn/err);
Shared 2863 passed; Tray 1717 passed. UITests run on CI (they time out in the
local harness); the 2 new tests are exercised by the ci.yml UITests job.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 17, 2026
… prepend preservation

The ItemsRepeater SizeChanged storm during row realization fired a flood of
reactive QueueScrollToBottom calls that each superseded the user's own
ChangeView, so the proof tests' scroll-up never "took" and the view stayed
pinned. Replace the multi-pass QueuePass stabilization with:

- A coalescing guard (scrollPinPendingRef) so host.SizeChanged enqueues at most
  one reactive pin at a time. Held across the synchronous pin window (which can
  re-enter SizeChanged) and released only in terminal paths.
- An authoritative ViewChanged handler: a genuine user scroll past the abandon
  gap cancels the in-flight settle timer/pending pin and disables anchoring
  immediately; symmetric re-arm to 1.0 when back in the bottom band.
- A single settle timer (converges on atBottom, hands off to VerticalAnchorRatio
  = 1.0) instead of parallel timers fighting over ChangeView.
- A per-render anchoring gate: VerticalAnchorRatio = following ? 1.0 : NaN,
  deferring to any pin/prepend that owns the ratio for its async window.

Prepend: our FunctionalUI reconciler full-remounts every Entry (openclaw#996), which
destroys the element the platform chose as the bottom anchor, so leaving
VerticalAnchorRatio = 1.0 re-pinned a scrolled-up reader to the NEW bottom
(offset 2528 -> 8531, gap 0). QueuePreservePrependOffset now disables anchoring
and manually re-seats the offset by the measured inserted height instead, keeping
the reader in the middle band (not reset to top, not dragged to bottom).

Also update the ChatTimelineVirtualizationContractTests source-text assertions to
the current mechanism and relax the prepend proof's anchoring assertion to the
dynamic model (NaN while scrolled up, or 1.0 at bottom). Removed dead DIAG
diagnostics and the unused FollowToBottomExtentEpsilon constant.

Validation: build.ps1 green; Shared 2863 passed/31 skipped; Tray 1717 passed;
UITests ChatTimelineVirtualizationProofTests 5/5; contract tests 3/3.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@clawsweeper

clawsweeper Bot commented Jul 20, 2026

Copy link
Copy Markdown

ClawSweeper status: review started.

I am starting a fresh review of this pull request: Fix chat scrollbar not reaching bottom during streaming via scroll anchoring This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

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

Labels

merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. proof: 🎥 video Contributor real behavior proof includes video or recording evidence. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants