@@ -75,6 +75,8 @@ public record OpenClawChatTimelineProps(
7575public class OpenClawChatTimeline : Component < OpenClawChatTimelineProps >
7676{
7777 const double FollowThreshold = 60 ;
78+ const int FollowToBottomMaxStabilizationPasses = 4 ;
79+ const double FollowToBottomExtentEpsilon = 0.5 ;
7880
7981 /// <summary>
8082 /// Static scroll-offset store shared across all timeline instances so that
@@ -466,7 +468,7 @@ public override Element Render()
466468
467469 var scrollViewRef = UseRef < Microsoft . UI . Xaml . Controls . ScrollViewer ? > ( null ) ;
468470 var isFollowingRef = UseRef ( true ) ;
469- var contentRef = UseRef < Microsoft . UI . Xaml . Controls . StackPanel ? > ( null ) ;
471+ var contentRef = UseRef < FrameworkElement ? > ( null ) ;
470472 var prevEntryCountRef = UseRef ( 0 ) ;
471473 var prevSessionIdRef = UseRef < string ? > ( null ) ;
472474 var prevFirstEntryIdRef = UseRef < string ? > ( null ) ;
@@ -509,7 +511,10 @@ public override Element Render()
509511 if ( prevShowToolCallsRef . Current != showToolCalls )
510512 {
511513 prevShowToolCallsRef . Current = showToolCalls ;
512- contentRef . Current ? . Children . Clear ( ) ;
514+ if ( contentRef . Current is ItemsRepeater repeater )
515+ repeater . ItemsSource = Array . Empty < object > ( ) ;
516+ else if ( contentRef . Current is StackPanel stackPanel )
517+ stackPanel . Children . Clear ( ) ;
513518 }
514519
515520 // Hover state — set of entry ids currently under the pointer. Used to
@@ -636,15 +641,27 @@ void UpdateScrollMetrics(Microsoft.UI.Xaml.Controls.ScrollViewer sv)
636641 void QueueScrollToBottom ( Microsoft . UI . Xaml . Controls . ScrollViewer sv , string ? sessionId , bool disableAnimation )
637642 {
638643 isFollowingRef . Current = true ;
639- sv . DispatcherQueue . TryEnqueue ( ( ) =>
644+
645+ void QueuePass ( int pass , double previousScrollableHeight , bool passDisableAnimation )
640646 {
641- var bottom = sv . ScrollableHeight ;
642- sv . ChangeView ( null , bottom , null , disableAnimation ) ;
643- lastVerticalOffsetRef . Current = bottom ;
644- lastScrollableHeightRef . Current = sv . ScrollableHeight ;
645- isFollowingRef . Current = true ;
646- StoreSessionOffset ( sessionId , bottom ) ;
647- } ) ;
647+ sv . DispatcherQueue . TryEnqueue ( ( ) =>
648+ {
649+ sv . UpdateLayout ( ) ;
650+ var bottom = sv . ScrollableHeight ;
651+ sv . ChangeView ( null , bottom , null , passDisableAnimation ) ;
652+ lastVerticalOffsetRef . Current = bottom ;
653+ lastScrollableHeightRef . Current = sv . ScrollableHeight ;
654+ isFollowingRef . Current = true ;
655+ StoreSessionOffset ( sessionId , bottom ) ;
656+
657+ var extentStable = Math . Abs ( bottom - previousScrollableHeight ) <= FollowToBottomExtentEpsilon ;
658+ var atBottom = sv . ScrollableHeight - sv . VerticalOffset <= FollowThreshold ;
659+ if ( pass < FollowToBottomMaxStabilizationPasses && ( ! extentStable || ! atBottom ) )
660+ QueuePass ( pass + 1 , sv . ScrollableHeight , passDisableAnimation : true ) ;
661+ } ) ;
662+ }
663+
664+ QueuePass ( 0 , double . NaN , disableAnimation ) ;
648665 }
649666
650667 void QueuePreservePrependOffset ( Microsoft . UI . Xaml . Controls . ScrollViewer sv , string ? sessionId , double oldOffset , double oldScrollableHeight )
@@ -2490,12 +2507,12 @@ bool BurstIsNestable(System.Collections.Generic.List<ChatTimelineItem> b)
24902507 Grid ( [ GridSize . Star ( ) ] , [ GridSize . Auto , GridSize . Auto , GridSize . Auto , GridSize . Auto ] ,
24912508 loadMoreButton . Grid ( row : 0 , column : 0 ) ,
24922509 Border ( Empty ( ) ) . Height ( 20 ) . Grid ( row : 1 , column : 0 ) ,
2493- VStack ( 2 , timelineRows ) . Set ( sp =>
2510+ VirtualVStack ( 2 , timelineRows ) . Set ( host =>
24942511 {
2495- if ( contentRef . Current != sp )
2512+ if ( contentRef . Current != host )
24962513 {
2497- contentRef . Current = ( Microsoft . UI . Xaml . Controls . StackPanel ) sp ;
2498- sp . SizeChanged += ( _ , _ ) =>
2514+ contentRef . Current = host ;
2515+ host . SizeChanged += ( _ , _ ) =>
24992516 {
25002517 if ( scrollViewRef . Current is not { } sv ) return ;
25012518
0 commit comments