@@ -50,6 +50,7 @@ interface TerminalRuntime extends TerminalRendererRuntime {
5050 scrollDisposable ?: { dispose : ( ) => void } ;
5151 viewportEl ?: HTMLDivElement | null ;
5252 viewportHandler ?: ( ( ) => void ) | null ;
53+ viewportRestoreRaf ?: number ;
5354 activationRaf ?: number ;
5455 rendererAttachRaf ?: number ;
5556 webglPostInitTimer ?: number ;
@@ -305,7 +306,7 @@ export function useAppState(
305306 const [ config , setConfig ] = useState < AppConfig > ( defaultConfig ) ;
306307 const [ hasSavedConfig , setHasSavedConfig ] = useState < boolean | null > ( null ) ;
307308 const [ sessions , setSessions ] = useState < Session [ ] > ( [ ] ) ;
308- const [ activeSessionId , setActiveSessionId ] = useState < string | null > ( null ) ;
309+ const [ activeSessionId , setActiveSessionIdState ] = useState < string | null > ( null ) ;
309310 const [ filter , setFilter ] = useState ( "" ) ;
310311 const [ unreadOutput , setUnreadOutput ] = useState < Record < string , boolean > > ( { } ) ;
311312 const [ agentOutputting , setAgentOutputting ] = useState < Record < string , boolean > > ( { } ) ;
@@ -492,6 +493,9 @@ export function useAppState(
492493 const setFollowingState = useCallback (
493494 ( runtime : TerminalRuntime , sessionId : string , kind : PaneKind , isFollowing : boolean ) => {
494495 runtime . isFollowing = isFollowing ;
496+ if ( isFollowing ) {
497+ runtime . savedViewportY = undefined ;
498+ }
495499 setUnreadFor ( sessionId , kind , ! isFollowing ) ;
496500 } ,
497501 [ setUnreadFor ]
@@ -509,6 +513,65 @@ export function useAppState(
509513 [ setFollowingState ]
510514 ) ;
511515
516+ const snapshotRuntimeViewport = useCallback ( ( runtime : TerminalRuntime ) => {
517+ const viewportY = runtime . term ?. buffer . active . viewportY ;
518+ if ( runtime . isFollowing === false && viewportY !== undefined ) {
519+ runtime . savedViewportY = viewportY ;
520+ return ;
521+ }
522+ runtime . savedViewportY = undefined ;
523+ } , [ ] ) ;
524+
525+ const snapshotActiveRuntimeViewport = useCallback ( ( ) => {
526+ const sessionId = activeSessionRef . current ;
527+ const kind = activePaneKindRef . current ;
528+ if ( ! sessionId ) {
529+ return ;
530+ }
531+ const runtime = runtimeRef . current . get ( sessionId ) ?. [ kind ] ;
532+ if ( ! runtime ?. term ) {
533+ return ;
534+ }
535+ updateFollowState ( runtime , sessionId , kind , runtime . viewportEl ?? undefined ) ;
536+ snapshotRuntimeViewport ( runtime ) ;
537+ } , [ snapshotRuntimeViewport , updateFollowState ] ) ;
538+
539+ const restoreRuntimeViewport = useCallback (
540+ ( runtime : TerminalRuntime , sessionId : string , kind : PaneKind ) => {
541+ if ( ! runtime . term || runtime . isFollowing !== false || runtime . savedViewportY === undefined ) {
542+ return ;
543+ }
544+ if ( runtime . viewportRestoreRaf !== undefined ) {
545+ window . cancelAnimationFrame ( runtime . viewportRestoreRaf ) ;
546+ }
547+ const retryRestore = ( ) => {
548+ runtime . viewportRestoreRaf = undefined ;
549+ const term = runtime . term ;
550+ const savedViewportY = runtime . savedViewportY ;
551+ if ( ! term || savedViewportY === undefined || runtime . isFollowing !== false ) {
552+ return ;
553+ }
554+ if ( ! isRuntimeVisible ( runtime ) ) {
555+ runtime . viewportRestoreRaf = window . requestAnimationFrame ( retryRestore ) ;
556+ return ;
557+ }
558+ term . write ( "" , ( ) => {
559+ if ( ! runtime . term || runtime . savedViewportY !== savedViewportY || runtime . isFollowing !== false ) {
560+ return ;
561+ }
562+ if ( ! isRuntimeVisible ( runtime ) ) {
563+ return ;
564+ }
565+ runtime . term . scrollToLine ( savedViewportY ) ;
566+ runtime . savedViewportY = undefined ;
567+ updateFollowState ( runtime , sessionId , kind , runtime . viewportEl ?? undefined ) ;
568+ } ) ;
569+ } ;
570+ runtime . viewportRestoreRaf = window . requestAnimationFrame ( retryRestore ) ;
571+ } ,
572+ [ updateFollowState ]
573+ ) ;
574+
512575 const focusSession = useCallback ( ( sessionId : string , kind : PaneKind ) => {
513576 const runtime = runtimeRef . current . get ( sessionId ) ?. [ kind ] ;
514577 if ( runtime ?. term ) {
@@ -548,6 +611,16 @@ export function useAppState(
548611 activeSessionRef . current = activeSessionId ;
549612 } , [ activeSessionId ] ) ;
550613
614+ const setActiveSessionId = useCallback (
615+ ( nextSessionId : string | null ) => {
616+ if ( activeSessionRef . current !== nextSessionId ) {
617+ snapshotActiveRuntimeViewport ( ) ;
618+ }
619+ setActiveSessionIdState ( nextSessionId ) ;
620+ } ,
621+ [ snapshotActiveRuntimeViewport ]
622+ ) ;
623+
551624 const markConfigReady = useCallback ( ( ) => {
552625 configReadyResolveRef . current ?.( ) ;
553626 configReadyResolveRef . current = null ;
@@ -722,6 +795,20 @@ export function useAppState(
722795 [ applyTerminalAppearanceToRuntime , toTerminalAppearance ]
723796 ) ;
724797
798+ const activateVisibleRuntime = useCallback (
799+ (
800+ runtime : TerminalRuntime ,
801+ sessionId : string ,
802+ kind : PaneKind ,
803+ options ?: { focus ?: boolean ; clearSelection ?: boolean }
804+ ) => {
805+ activateRuntime ( runtime , options ) ;
806+ scheduleTerminalFit ( runtime , true ) ;
807+ restoreRuntimeViewport ( runtime , sessionId , kind ) ;
808+ } ,
809+ [ activateRuntime , restoreRuntimeViewport , scheduleTerminalFit ]
810+ ) ;
811+
725812 const refreshTerminalRenderersAfterFontLoad = useCallback ( ( ) => {
726813 applyTerminalAppearanceToAll ( toTerminalAppearance ( configRef . current . settings ) ) ;
727814 } , [ applyTerminalAppearanceToAll , toTerminalAppearance ] ) ;
@@ -1200,6 +1287,10 @@ export function useAppState(
12001287 window . cancelAnimationFrame ( runtime . activationRaf ) ;
12011288 runtime . activationRaf = undefined ;
12021289 }
1290+ if ( runtime . viewportRestoreRaf !== undefined ) {
1291+ window . cancelAnimationFrame ( runtime . viewportRestoreRaf ) ;
1292+ runtime . viewportRestoreRaf = undefined ;
1293+ }
12031294 runtime . container = null ;
12041295 runtime . lastFit = undefined ;
12051296 if ( runtime . resizeObserver ) {
@@ -1223,12 +1314,12 @@ export function useAppState(
12231314
12241315 const detachTerminalRuntime = useCallback (
12251316 ( runtime : TerminalRuntime , preserveViewport = false ) => {
1226- if ( preserveViewport && runtime . term ) {
1227- runtime . savedViewportY = runtime . term . buffer . active . viewportY ;
1317+ if ( preserveViewport ) {
1318+ snapshotRuntimeViewport ( runtime ) ;
12281319 }
12291320 cleanupTerminalRuntimeAttachment ( runtime ) ;
12301321 } ,
1231- [ cleanupTerminalRuntimeAttachment ]
1322+ [ cleanupTerminalRuntimeAttachment , snapshotRuntimeViewport ]
12321323 ) ;
12331324
12341325 const disposeTerminalRuntime = useCallback (
@@ -1248,6 +1339,7 @@ export function useAppState(
12481339 runtime . starting = false ;
12491340 runtime . isFollowing = undefined ;
12501341 runtime . savedViewportY = undefined ;
1342+ runtime . viewportRestoreRaf = undefined ;
12511343 runtime . activationRaf = undefined ;
12521344 } ,
12531345 [ detachTerminalRuntime ]
@@ -1264,22 +1356,24 @@ export function useAppState(
12641356
12651357 const setActivePaneKind = useCallback (
12661358 ( kind : PaneKind ) => {
1359+ if ( activePaneKindRef . current !== kind ) {
1360+ snapshotActiveRuntimeViewport ( ) ;
1361+ }
12671362 activePaneKindRef . current = kind ;
12681363 const sessionId = activeSessionRef . current ;
12691364 if ( ! sessionId ) {
12701365 return ;
12711366 }
12721367 const runtime = runtimeRef . current . get ( sessionId ) ?. [ kind ] ;
12731368 if ( runtime ?. term ) {
1274- activateRuntime ( runtime , { focus : true , clearSelection : true } ) ;
1275- scheduleTerminalFit ( runtime ) ;
1369+ activateVisibleRuntime ( runtime , sessionId , kind , { focus : true , clearSelection : true } ) ;
12761370 return ;
12771371 }
12781372 if ( ! focusSession ( sessionId , kind ) ) {
12791373 pendingFocusRef . current = { sessionId, kind } ;
12801374 }
12811375 } ,
1282- [ activateRuntime , focusSession , scheduleTerminalFit ]
1376+ [ activateVisibleRuntime , focusSession , snapshotActiveRuntimeViewport ]
12831377 ) ;
12841378
12851379 const jumpToBottom = useCallback (
@@ -1290,7 +1384,7 @@ export function useAppState(
12901384 }
12911385 setFollowingState ( runtime , sessionId , kind , true ) ;
12921386 runtime . term . scrollToBottom ( ) ;
1293- scheduleTerminalFit ( runtime ) ;
1387+ scheduleTerminalFit ( runtime , true ) ;
12941388 } ,
12951389 [ scheduleTerminalFit , setFollowingState ]
12961390 ) ;
@@ -1342,10 +1436,6 @@ export function useAppState(
13421436 viewport . addEventListener ( "scroll" , handler , { passive : true } ) ;
13431437 handler ( ) ;
13441438 }
1345- if ( runtime . savedViewportY !== undefined && runtime . isFollowing === false ) {
1346- term . scrollToLine ( runtime . savedViewportY ) ;
1347- }
1348- runtime . savedViewportY = undefined ;
13491439 updateFollowState ( runtime , sessionId , kind , runtime . viewportEl ?? undefined ) ;
13501440
13511441 const isActiveRuntime = activeSessionRef . current === sessionId && activePaneKindRef . current === kind ;
@@ -1358,7 +1448,7 @@ export function useAppState(
13581448 pendingFocusRef . current = null ;
13591449 }
13601450 if ( isActiveRuntime ) {
1361- activateRuntime ( runtime , { focus : true , clearSelection : true } ) ;
1451+ activateVisibleRuntime ( runtime , sessionId , kind , { focus : true , clearSelection : true } ) ;
13621452 } else {
13631453 activateRuntime ( runtime , { focus : false , clearSelection : false } ) ;
13641454 }
@@ -1407,6 +1497,7 @@ export function useAppState(
14071497 updateFollowState ,
14081498 notify ,
14091499 activateRuntime ,
1500+ activateVisibleRuntime ,
14101501 ]
14111502 ) ;
14121503
@@ -1925,14 +2016,13 @@ export function useAppState(
19252016 const kind = activePaneKindRef . current ;
19262017 const runtime = runtimeRef . current . get ( sessionId ) ?. [ kind ] ;
19272018 if ( runtime ?. term ) {
1928- activateRuntime ( runtime , { focus : true , clearSelection : true } ) ;
1929- scheduleTerminalFit ( runtime ) ;
2019+ activateVisibleRuntime ( runtime , sessionId , kind , { focus : true , clearSelection : true } ) ;
19302020 return ;
19312021 }
19322022 if ( ! focusSession ( sessionId , kind ) ) {
19332023 pendingFocusRef . current = { sessionId, kind } ;
19342024 }
1935- } , [ activeSessionId , activateRuntime , focusSession , scheduleTerminalFit ] ) ;
2025+ } , [ activeSessionId , activateVisibleRuntime , focusSession ] ) ;
19362026
19372027 const handleCloseRequest = useCallback ( async ( ) => {
19382028 if ( closeInProgressRef . current || closePromptInProgressRef . current ) {
0 commit comments