File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -125,6 +125,39 @@ describe("createAppShell", () => {
125125 ) ;
126126 } ) ;
127127
128+ test ( "one wheel detent up from a followed tail stays off the bottom" , async ( ) => {
129+ await withTestRenderer (
130+ async ( h ) => {
131+ const shell = createAppShell ( h . renderer , {
132+ terminal : { columns : 80 , rows : 24 } ,
133+ wireKeys : false ,
134+ } ) ;
135+ try {
136+ for ( let i = 0 ; i < 50 ; i ++ ) {
137+ appendTranscript ( shell , `seed-${ i } ` ) ;
138+ }
139+ await h . renderOnce ( ) ;
140+ const max = Math . max (
141+ 0 ,
142+ shell . transcript . scrollHeight - shell . transcript . height ,
143+ ) ;
144+ expect ( max ) . toBeGreaterThan ( 1 ) ;
145+ expect ( isTranscriptFollowing ( shell ) ) . toBe ( true ) ;
146+
147+ // One detent, not a multi-tick fling. The next frame must not
148+ // treat max - 1 as the tail and scroll it back.
149+ shell . transcript . scrollTop = max - 1 ;
150+ await h . renderOnce ( ) ;
151+ expect ( shell . transcript . scrollTop ) . toBe ( max - 1 ) ;
152+ expect ( isTranscriptFollowing ( shell ) ) . toBe ( false ) ;
153+ } finally {
154+ shell . dispose ( ) ;
155+ }
156+ } ,
157+ { width : 80 , height : 24 } ,
158+ ) ;
159+ } ) ;
160+
128161 test ( "the notice says pinned only while the tail is off screen" , async ( ) => {
129162 await withTestRenderer (
130163 async ( h ) => {
Original file line number Diff line number Diff line change @@ -498,11 +498,14 @@ export type PrimaryOverlayKind =
498498 | "mcp"
499499 | "plugin_credentials" ;
500500
501- /** Whether the transcript viewport is stuck to the bottom (FOLLOW vs PINNED). */
501+ /** Whether the transcript viewport is stuck to the bottom (FOLLOW vs PINNED).
502+ * The bottom is `scrollTop >= max` only. `max - 1` is one wheel detent up;
503+ * treating it as followed makes the next frame's `scrollChildIntoView` yank
504+ * the tail back. */
502505export function isTranscriptFollowing ( shell : AppShell ) : boolean {
503506 const { transcript } = shell ;
504507 const max = Math . max ( 0 , transcript . scrollHeight - transcript . height ) ;
505- return transcript . scrollTop >= max - 1 ;
508+ return transcript . scrollTop >= max ;
506509}
507510
508511/** Sticky-scroll mode label (surfaced on the notice row only when PINNED). */
You can’t perform that action at this time.
0 commit comments