@@ -51,6 +51,7 @@ function createScrollHost(
5151 chatLastScrollTop : 0 ,
5252 chatHasAutoScrolled : false ,
5353 chatUserNearBottom : true ,
54+ chatFollowLocked : false ,
5455 chatHeaderControlsHidden : false ,
5556 chatNewMessagesBelow : false ,
5657 chatIsProgrammaticScroll : false ,
@@ -107,10 +108,9 @@ describe("handleChatScroll", () => {
107108 expect ( host . chatUserNearBottom ) . toBe ( false ) ;
108109 } ) ;
109110
110- it ( "sets chatUserNearBottom=false when user scrolled up past one long message (>200px <450px) " , ( ) => {
111+ it ( "sets chatUserNearBottom=false when scrolled past the near-bottom threshold " , ( ) => {
111112 const { host } = createScrollHost ( { } ) ;
112- // distanceFromBottom = 2000 - 1250 - 400 = 350 → old threshold would say "near", new says "near"
113- // distanceFromBottom = 2000 - 1100 - 400 = 500 → old threshold would say "not near", new also "not near"
113+ // distanceFromBottom = 2000 - 1100 - 400 = 500 → beyond threshold
114114 const event = createScrollEvent ( 2000 , 1100 , 400 ) ;
115115 handleChatScroll ( host , event ) ;
116116 expect ( host . chatUserNearBottom ) . toBe ( false ) ;
@@ -232,6 +232,24 @@ describe("scheduleChatScroll", () => {
232232 expect ( container . scrollTop ) . toBe ( container . scrollHeight ) ;
233233 } ) ;
234234
235+ it ( "uses force=true on initial load even after a previous follow lock" , async ( ) => {
236+ const { host, container } = createScrollHost ( {
237+ scrollHeight : 2000 ,
238+ scrollTop : 500 ,
239+ clientHeight : 400 ,
240+ } ) ;
241+ host . chatUserNearBottom = false ;
242+ host . chatFollowLocked = true ;
243+ host . chatHasAutoScrolled = false ;
244+
245+ scheduleChatScroll ( host , true ) ;
246+ await host . updateComplete ;
247+
248+ expect ( container . scrollTop ) . toBe ( container . scrollHeight ) ;
249+ expect ( host . chatFollowLocked ) . toBe ( false ) ;
250+ expect ( host . chatNewMessagesBelow ) . toBe ( false ) ;
251+ } ) ;
252+
235253 it ( "sets chatNewMessagesBelow when not scrolling due to user position" , async ( ) => {
236254 const { host } = createScrollHost ( {
237255 scrollHeight : 2000 ,
@@ -248,6 +266,62 @@ describe("scheduleChatScroll", () => {
248266 expect ( host . chatNewMessagesBelow ) . toBe ( true ) ;
249267 } ) ;
250268
269+ it ( "does not re-stick streaming after a user scrolls slightly up near the bottom" , async ( ) => {
270+ const { host, container } = createScrollHost ( {
271+ scrollHeight : 2000 ,
272+ scrollTop : 1540 ,
273+ clientHeight : 400 ,
274+ } ) ;
275+ host . chatHasAutoScrolled = true ;
276+ host . chatUserNearBottom = true ;
277+ host . chatIsProgrammaticScroll = true ;
278+ host . chatProgrammaticScrollTarget = 1800 ;
279+ host . chatLastScrollTop = 1600 ;
280+
281+ handleChatScroll ( host , createScrollEvent ( 2000 , 1540 , 400 ) ) ;
282+
283+ expect ( host . chatFollowLocked ) . toBe ( true ) ;
284+ expect ( host . chatUserNearBottom ) . toBe ( false ) ;
285+
286+ scheduleChatScroll ( host ) ;
287+ await host . updateComplete ;
288+
289+ expect ( container . scrollTop ) . toBe ( 1540 ) ;
290+ expect ( host . chatNewMessagesBelow ) . toBe ( true ) ;
291+
292+ host . chatIsProgrammaticScroll = false ;
293+ container . scrollTop = 1600 ;
294+ handleChatScroll ( host , createScrollEvent ( 2000 , 1600 , 400 ) ) ;
295+
296+ expect ( host . chatFollowLocked ) . toBe ( false ) ;
297+ expect ( host . chatUserNearBottom ) . toBe ( true ) ;
298+ expect ( host . chatNewMessagesBelow ) . toBe ( false ) ;
299+ } ) ;
300+
301+ it ( "does not re-stick streaming after a small user scroll-up near the bottom" , async ( ) => {
302+ const { host, container } = createScrollHost ( {
303+ scrollHeight : 2000 ,
304+ scrollTop : 1589 ,
305+ clientHeight : 400 ,
306+ } ) ;
307+ host . chatHasAutoScrolled = true ;
308+ host . chatUserNearBottom = true ;
309+ host . chatIsProgrammaticScroll = true ;
310+ host . chatProgrammaticScrollTarget = 1800 ;
311+ host . chatLastScrollTop = 1600 ;
312+
313+ handleChatScroll ( host , createScrollEvent ( 2000 , 1589 , 400 ) ) ;
314+
315+ expect ( host . chatFollowLocked ) . toBe ( true ) ;
316+ expect ( host . chatUserNearBottom ) . toBe ( false ) ;
317+
318+ scheduleChatScroll ( host ) ;
319+ await host . updateComplete ;
320+
321+ expect ( container . scrollTop ) . toBe ( 1589 ) ;
322+ expect ( host . chatNewMessagesBelow ) . toBe ( true ) ;
323+ } ) ;
324+
251325 it ( "does NOT scroll automatically when chat auto-scroll is off" , async ( ) => {
252326 const { host, container } = createScrollHost ( {
253327 scrollHeight : 2000 ,
@@ -360,13 +434,15 @@ describe("resetChatScroll", () => {
360434 const { host } = createScrollHost ( { } ) ;
361435 host . chatHasAutoScrolled = true ;
362436 host . chatUserNearBottom = false ;
437+ host . chatFollowLocked = true ;
363438 host . chatLastScrollTop = 300 ;
364439 host . chatHeaderControlsHidden = true ;
365440
366441 resetChatScroll ( host ) ;
367442
368443 expect ( host . chatHasAutoScrolled ) . toBe ( false ) ;
369444 expect ( host . chatUserNearBottom ) . toBe ( true ) ;
445+ expect ( host . chatFollowLocked ) . toBe ( false ) ;
370446 expect ( host . chatLastScrollTop ) . toBe ( 0 ) ;
371447 expect ( host . chatHeaderControlsHidden ) . toBe ( false ) ;
372448 expect ( host . chatIsProgrammaticScroll ) . toBe ( false ) ;
0 commit comments