Skip to content

Commit 44d2928

Browse files
bkudiessCopilot
andcommitted
Refine virtualized chat follow behavior
Avoid synchronous row realization during manual scrolling and make the scroll-to-latest affordance force the latest row fully into view while resetting follow tracking. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 065f1be commit 44d2928

1 file changed

Lines changed: 31 additions & 2 deletions

File tree

src/OpenClaw.Tray.WinUI/Chat/Controls/VirtualizedChatView.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public void Update(ChatTimelineView view)
149149
var appendedEntries = !sessionChanged
150150
&& view.EntryCount > previousEntryCount
151151
&& !prependedHistory;
152+
var shouldFollowAppendedEntries = _isFollowing || IsAtBottom;
152153

153154
_view = view;
154155
_rowFactory.Update(view.Rows);
@@ -184,7 +185,7 @@ public void Update(ChatTimelineView view)
184185
QueueScrollToBottom(view.SessionId, disableAnimation: true);
185186
}
186187
}
187-
else if (appendedEntries && _isFollowing)
188+
else if (appendedEntries && shouldFollowAppendedEntries)
188189
{
189190
QueueScrollToBottom(view.SessionId, disableAnimation: false);
190191
}
@@ -214,7 +215,9 @@ public void ScrollToEnd()
214215
ResetScrollToEndState();
215216
_suppressAutoFollow = false;
216217
ClearSessionOffset(_view.SessionId);
217-
QueueScrollToBottom(_view.SessionId, disableAnimation: false);
218+
_isFollowing = true;
219+
_scrollToLatestButton.Visibility = Visibility.Collapsed;
220+
EnqueueOnView(() => ForceScrollToLatest(_view.SessionId, remainingPasses: 4, disableAnimation: false));
218221
}
219222

220223
private void SyncRows(IReadOnlyList<ChatTimelineRow> desiredRows)
@@ -417,6 +420,32 @@ private void ResetScrollToEndState()
417420
_scrollToEndState = ScrollToEndState.Idle;
418421
}
419422

423+
private void ForceScrollToLatest(string? sessionId, int remainingPasses, bool disableAnimation)
424+
{
425+
if (_rows.Count == 0)
426+
return;
427+
428+
var latest = _itemsRepeater.GetOrCreateElement(_rows.Count - 1);
429+
latest.StartBringIntoView(new BringIntoViewOptions
430+
{
431+
AnimationDesired = !disableAnimation,
432+
VerticalAlignmentRatio = 1.0,
433+
});
434+
435+
EnqueueOnView(() =>
436+
{
437+
var bottom = _scrollViewer.ScrollableHeight;
438+
_scrollViewer.ChangeView(null, bottom, null, disableAnimation);
439+
_lastVerticalOffset = bottom;
440+
_lastScrollableHeight = _scrollViewer.ScrollableHeight;
441+
_isFollowing = true;
442+
ClearSessionOffset(sessionId);
443+
444+
if (remainingPasses > 0)
445+
ForceScrollToLatest(sessionId, remainingPasses - 1, disableAnimation: true);
446+
});
447+
}
448+
420449
private void QueuePreservePrependOffset(string? sessionId, double oldOffset, double oldScrollableHeight)
421450
{
422451
_pendingPrependSessionId = sessionId;

0 commit comments

Comments
 (0)