@@ -52,6 +52,8 @@ export type LocalPTY = {
5252 titleNumber : number
5353 status : TerminalStatus
5454 error ?: TerminalFailure
55+ /** True when restored from cross-project navigation cache; cleared on first ready. */
56+ restored ?: boolean
5557}
5658
5759export type TerminalStore = {
@@ -313,6 +315,14 @@ function removeTerminalPersistence(
313315type TerminalSession = ReturnType < typeof createWorkspaceTerminalSession >
314316const sessions = new Set < { dir : string ; scope : ServerScopeValue ; value : TerminalSession } > ( )
315317
318+ /** Per-directory PTY snapshot preserved across project switches so PTYs survive navigation. */
319+ interface TerminalPtySnapshot {
320+ ptys : Array < Pick < LocalPTY , "id" | "ptyId" | "title" | "titleNumber" > >
321+ root : PaneNode
322+ focusedPaneId : string
323+ }
324+ const directoryTerminalCache = new Map < string , { bottom : TerminalPtySnapshot | null ; side : TerminalPtySnapshot | null } > ( )
325+
316326export function clearWorkspaceTerminals (
317327 dir : string ,
318328 sessionIDs ?: string [ ] ,
@@ -535,6 +545,37 @@ function createWorkspaceTerminalSession(
535545 resetRuntime ( ) {
536546 reset ( )
537547 } ,
548+ snapshot ( ) : TerminalPtySnapshot | null {
549+ if ( store . all . length === 0 ) return null
550+ return {
551+ ptys : store . all . map ( ( pty ) => ( {
552+ id : pty . id ,
553+ ptyId : pty . ptyId ,
554+ title : pty . title ,
555+ titleNumber : pty . titleNumber ,
556+ } ) ) ,
557+ root : root ( ) ,
558+ focusedPaneId : focusedPaneId ( ) ,
559+ }
560+ } ,
561+ restore ( snapshot : TerminalPtySnapshot ) {
562+ batch ( ( ) => {
563+ setStore (
564+ "all" ,
565+ snapshot . ptys . map ( ( p ) => ( {
566+ id : p . id ,
567+ ptyId : p . ptyId ,
568+ title : p . title ,
569+ titleNumber : p . titleNumber ,
570+ status : "connecting" as TerminalStatus ,
571+ error : undefined ,
572+ restored : true ,
573+ } ) ) ,
574+ )
575+ setRootSignal ( clonePaneTree ( snapshot . root ) )
576+ setFocusedPaneId ( snapshot . focusedPaneId )
577+ } )
578+ } ,
538579 clear ( ) {
539580 const ptyIds = store . all . map ( ( pty ) => pty . ptyId )
540581 reset ( )
@@ -633,7 +674,7 @@ function createWorkspaceTerminalSession(
633674 setStatus ( id : string , ptyId : string , status : TerminalStatus , error ?: TerminalFailure ) {
634675 const index = store . all . findIndex ( ( pty ) => pty . id === id && pty . ptyId === ptyId )
635676 if ( index === - 1 ) return
636- setStore ( "all" , index , { status, error : status === "ready" ? undefined : error } )
677+ setStore ( "all" , index , { status, error : status === "ready" ? undefined : error , ... ( status === "ready" ? { restored : false } : { } ) } )
637678 } ,
638679 update ( input : Partial < LocalPTY > & { id : string } ) {
639680 if ( input . title === undefined ) return
@@ -772,6 +813,16 @@ const { use: useTerminalDual, provider: TerminalProvider } = createSimpleContext
772813 bottomSession = createWorkspaceTerminalSession ( sdk , runtime )
773814 sideSession = createWorkspaceTerminalSession ( sdk , runtime )
774815
816+ // Restore previously-saved PTYs when returning to a directory.
817+ // The cached snapshot was written by onCleanup — PTYs stayed alive on the
818+ // server and just need to be re-registered in local reactive state.
819+ const cached = directoryTerminalCache . get ( sdk . directory )
820+ if ( cached ) {
821+ directoryTerminalCache . delete ( sdk . directory )
822+ if ( cached . bottom ) bottomSession . restore ( cached . bottom )
823+ if ( cached . side ) sideSession . restore ( cached . side )
824+ }
825+
775826 const bottomReg = { dir : sdk . directory , scope, value : bottomSession }
776827 const sideReg = { dir : sdk . directory , scope, value : sideSession }
777828 sessions . add ( bottomReg )
@@ -786,10 +837,14 @@ const { use: useTerminalDual, provider: TerminalProvider } = createSimpleContext
786837 onCleanup ( ( ) => clearInterval ( timer ) )
787838 } )
788839 onCleanup ( ( ) => {
840+ // Save PTY state so terminals survive project navigation. Do NOT call
841+ // clear() here — that would DELETE PTYs on the server. The PTYs keep
842+ // running and are reconnected when the user returns to this directory.
843+ const bottomSnap = bottomSession ?. snapshot ( ) ?? null
844+ const sideSnap = sideSession ?. snapshot ( ) ?? null
845+ directoryTerminalCache . set ( sdk . directory , { bottom : bottomSnap , side : sideSnap } )
789846 sessions . delete ( bottomReg )
790847 sessions . delete ( sideReg )
791- bottomSession ?. clear ( )
792- sideSession ?. clear ( )
793848 } )
794849
795850 return { bottom : bottomSession , side : sideSession }
0 commit comments