@@ -820,13 +820,13 @@ export const MessagesTimeline = memo(function MessagesTimeline({
820820 if ( viewport ) void list . scrollToOffset ( { offset : viewport . scrollTop , animated : false } ) ;
821821 setPositionedThreadKey ( listIdentityKey ) ;
822822 } ;
823- const position = rememberedPosition ;
823+ const savedRow = rememberedPosition ?. atEnd === false ? rememberedPosition : null ;
824824 // A restore to the end only stops correcting on a gesture. ChatView's own
825825 // listeners decide whether that gesture leaves the end, so a wheel down
826826 // or a click at the bottom keeps following.
827827 const cancelForNavigation = ( ) => {
828828 cancelRestoration ( ) ;
829- if ( position ?. atEnd === false ) onManualNavigation ( ) ;
829+ if ( savedRow ) onManualNavigation ( ) ;
830830 } ;
831831 const onScrollKey = ( event : globalThis . KeyboardEvent ) => {
832832 if (
@@ -842,26 +842,24 @@ export const MessagesTimeline = memo(function MessagesTimeline({
842842 viewport ?. addEventListener ( "touchmove" , cancelForNavigation , { passive : true } ) ;
843843 viewport ?. addEventListener ( "pointerdown" , cancelForNavigation , { passive : true } ) ;
844844 viewport ?. ownerDocument . addEventListener ( "keydown" , onScrollKey ) ;
845- if ( position ?. atEnd === false ) onManualNavigation ( ) ;
845+ if ( savedRow ) onManualNavigation ( ) ;
846846 if ( cancelPositionRestoreRef ) cancelPositionRestoreRef . current = cancelRestoration ;
847- const scrolling =
848- position ?. atEnd === false
849- ? restoreRowIndex >= 0
850- ? list . scrollToIndex ( {
851- index : restoreRowIndex ,
852- animated : false ,
853- viewPosition : 0 ,
854- viewOffset : - position . offsetWithinRow ,
855- } )
856- : list . scrollToOffset ( { offset : position . scrollOffset , animated : false } )
857- : list . scrollToEnd ( { animated : false } ) ;
847+ const scrolling = savedRow
848+ ? restoreRowIndex >= 0
849+ ? list . scrollToIndex ( {
850+ index : restoreRowIndex ,
851+ animated : false ,
852+ viewPosition : 0 ,
853+ viewOffset : - savedRow . offsetWithinRow ,
854+ } )
855+ : list . scrollToOffset ( { offset : savedRow . scrollOffset , animated : false } )
856+ : list . scrollToEnd ( { animated : false } ) ;
858857 void Promise . resolve ( scrolling ) . then ( ( ) => {
859858 if ( cancelled ) return ;
860- if ( position ?. atEnd === false && restoreRowIndex < 0 ) {
859+ if ( savedRow && restoreRowIndex < 0 ) {
861860 setPositionedThreadKey ( listIdentityKey ) ;
862861 return ;
863862 }
864- const savedRow = position ?. atEnd === false ? position : null ;
865863 // Scrolling starts from estimated row sizes, so the first jump lands
866864 // short once real rows measure. Keep correcting toward the target (the
867865 // saved row, or the end) until it and the DOM agree for two frames.
@@ -870,40 +868,30 @@ export const MessagesTimeline = memo(function MessagesTimeline({
870868 const reconcile = ( ) => {
871869 if ( cancelled ) return ;
872870 const element = list . getScrollableNode ( ) ;
871+ const endOffset = element ? Math . max ( 0 , element . scrollHeight - element . clientHeight ) : 0 ;
873872 if ( ++ frames > THREAD_RESTORE_MAX_FRAMES ) {
874873 // End maintenance only re-pins within a viewport of the end, so
875874 // hand over from the current end rather than wherever this stopped.
876- if ( ! savedRow && element ) {
877- void list . scrollToOffset ( {
878- offset : Math . max ( 0 , element . scrollHeight - element . clientHeight ) ,
879- animated : false ,
880- } ) ;
881- }
875+ if ( ! savedRow && element )
876+ void list . scrollToOffset ( { offset : endOffset , animated : false } ) ;
882877 setPositionedThreadKey ( listIdentityKey ) ;
883878 return ;
884879 }
885- const state = list . getState ( ) ;
886- const rowIndex = savedRow ? state . indexByKey ( savedRow . rowId ) : undefined ;
887- const row = rowIndex === undefined ? undefined : state . elementAtIndex ( rowIndex ) ;
880+ // The effect restarts when the saved row's index changes.
881+ const row = savedRow ? list . getState ( ) . elementAtIndex ( restoreRowIndex ) : null ;
888882 // The saved row can mount a frame after the estimated scroll.
889883 if ( ! element || ( savedRow && ! row ) ) {
890884 settleFrame = requestAnimationFrame ( reconcile ) ;
891885 return ;
892886 }
893- const endOffset = Math . max ( 0 , element . scrollHeight - element . clientHeight ) ;
894- const offset =
887+ const target =
895888 savedRow && row
896- ? Math . max (
897- 0 ,
898- Math . min (
899- element . scrollTop +
900- row . getBoundingClientRect ( ) . top -
901- element . getBoundingClientRect ( ) . top +
902- savedRow . offsetWithinRow ,
903- endOffset ,
904- ) ,
905- )
889+ ? element . scrollTop +
890+ row . getBoundingClientRect ( ) . top -
891+ element . getBoundingClientRect ( ) . top +
892+ savedRow . offsetWithinRow
906893 : endOffset ;
894+ const offset = Math . max ( 0 , Math . min ( target , endOffset ) ) ;
907895 if ( Math . abs ( element . scrollTop - offset ) > 1 ) {
908896 stableFrames = 0 ;
909897 void list . scrollToOffset ( { offset, animated : false } ) . then ( ( ) => {
0 commit comments