@@ -102,6 +102,9 @@ class ChatScrollCoordinator {
102102 int _programmaticBottomScrollDepth = 0 ;
103103 int _jumpButtonFrozenUpdates = 0 ;
104104 bool _followTailAfterProgrammaticScroll = false ;
105+ double _lastTracedScrollPixels = double .nan;
106+ double _lastTracedScrollMax = double .nan;
107+ double _lastTracedScrollDistanceToBottom = double .nan;
105108
106109 void captureViewportState (
107110 List <MessageItem > messages,
@@ -113,6 +116,12 @@ class ChatScrollCoordinator {
113116 _tailFollowAnchorSnapshot = _tailFollowEligible
114117 ? _tailFollowAnchorSnapshotFromViewport ()
115118 : null ;
119+ traceChatScroll (
120+ 'capture tailEligible=$_tailFollowEligible '
121+ 'anchor=${shortMessageId (_tailFollowAnchorSnapshot ?.messageId )} '
122+ 'anchorBottom=${formatDouble (_tailFollowAnchorSnapshot ?.bottom )} '
123+ '${_formatScrollState ()}' ,
124+ );
116125 }
117126
118127 void updateMessages (
@@ -237,6 +246,16 @@ class ChatScrollCoordinator {
237246 _scheduleViewportStateUpdate (
238247 updateVisibleDate: notification is ScrollEndNotification ,
239248 );
249+ if (notification is ScrollStartNotification ||
250+ notification is ScrollEndNotification ||
251+ notification is UserScrollNotification ) {
252+ traceChatScroll (
253+ 'notification ${notification .runtimeType } '
254+ 'direction=${notification is UserScrollNotification ? notification .direction : '-' } '
255+ 'drag=${notification is ScrollStartNotification ? notification .dragDetails != null : '-' } '
256+ '${formatScrollMetrics (notification .metrics )}' ,
257+ );
258+ }
240259 _unlockPreloadOnUserScrollIntent (notification);
241260 if (notification is ! ScrollUpdateNotification ) return false ;
242261 if (_programmaticScrollDepth > 0 ) {
@@ -505,6 +524,10 @@ class ChatScrollCoordinator {
505524 }) async {
506525 _clearJumpButtonFreeze ();
507526 _programmaticBottomScrollDepth++ ;
527+ traceChatScroll (
528+ 'bottom-jump start animated=$animated '
529+ 'direction=$animationDirection ${_formatScrollState ()}' ,
530+ );
508531 try {
509532 await _jumpToClamped (
510533 scrollController.position.maxScrollExtent,
@@ -514,6 +537,7 @@ class ChatScrollCoordinator {
514537 );
515538 } finally {
516539 _programmaticBottomScrollDepth-- ;
540+ traceChatScroll ('bottom-jump done ${_formatScrollState ()}' );
517541 }
518542 }
519543
@@ -744,10 +768,16 @@ class ChatScrollCoordinator {
744768 'delta=${formatDouble (delta )} start=${formatDouble (start )} '
745769 '${formatScrollMetrics (position )}' ,
746770 );
747- scrollController.jumpTo (start);
771+ traceChatScroll (
772+ 'tail-follow start anchor=${shortMessageId (snapshot .messageId )} '
773+ 'delta=${formatDouble (delta )} start=${formatDouble (start )} '
774+ '${_formatScrollState ()}' ,
775+ );
776+ scrollController.position.correctPixels (start);
748777 }
749778
750779 void _scheduleScrollPositionUpdate () {
780+ _traceScrollPosition ();
751781 _scheduleViewportStateUpdate (updateVisibleDate: false );
752782 }
753783
@@ -791,6 +821,13 @@ class ChatScrollCoordinator {
791821 List <MessageItem > messages,
792822 Map <String , GlobalKey > keysByMessageId,
793823 ) {
824+ final previousCount = _messages.length;
825+ final previousBottomMessageId = _messages.isEmpty
826+ ? null
827+ : _messages.last.messageId;
828+ final nextBottomMessageId = messages.isEmpty
829+ ? null
830+ : messages.last.messageId;
794831 final messageWindowChanged = ! _hasSameMessageIds (messages);
795832 if (! identical (_messages, messages)) {
796833 _messages = messages;
@@ -801,6 +838,12 @@ class ChatScrollCoordinator {
801838 }
802839 if (messageWindowChanged) {
803840 _freezeJumpButton ();
841+ traceChatScroll (
842+ 'messages changed count=$previousCount ->${messages .length } '
843+ 'bottom=${shortMessageId (previousBottomMessageId )}'
844+ '->${shortMessageId (nextBottomMessageId )} '
845+ '${_formatScrollState ()}' ,
846+ );
804847 }
805848 _keysByMessageId = keysByMessageId;
806849 }
@@ -845,6 +888,40 @@ class ChatScrollCoordinator {
845888 return object is RenderBox ? object : null ;
846889 }
847890
891+ void _traceScrollPosition () {
892+ if (! chatScrollTraceEnabled || ! scrollController.hasClients) return ;
893+ final position = scrollController.position;
894+ if (! position.hasContentDimensions) return ;
895+ final distanceToBottom = _distanceToBottom ();
896+ final pixels = position.pixels;
897+ final max = position.maxScrollExtent;
898+ final shouldTrace =
899+ _lastTracedScrollPixels.isNaN ||
900+ (pixels - _lastTracedScrollPixels).abs () >= 1 ||
901+ (max - _lastTracedScrollMax).abs () >= 1 ||
902+ (distanceToBottom - _lastTracedScrollDistanceToBottom).abs () >= 1 ;
903+ if (! shouldTrace) return ;
904+ _lastTracedScrollPixels = pixels;
905+ _lastTracedScrollMax = max;
906+ _lastTracedScrollDistanceToBottom = distanceToBottom;
907+ traceChatScroll (
908+ 'position distanceBottom=${formatDouble (distanceToBottom )} '
909+ 'programmatic=$_programmaticScrollDepth '
910+ 'bottomProgrammatic=$_programmaticBottomScrollDepth '
911+ 'followPending=$_followTailAfterProgrammaticScroll '
912+ '${formatScrollMetrics (position )}' ,
913+ );
914+ }
915+
916+ String _formatScrollState () {
917+ if (! scrollController.hasClients) return 'no-scroll-client' ;
918+ return 'distanceBottom=${formatDouble (_distanceToBottom ())} '
919+ 'programmatic=$_programmaticScrollDepth '
920+ 'bottomProgrammatic=$_programmaticBottomScrollDepth '
921+ 'messages=${_messages .length } '
922+ '${formatScrollMetrics (scrollController .position )}' ;
923+ }
924+
848925 void _traceTargetAfterLayout (
849926 String phase,
850927 String messageId,
0 commit comments