@@ -35,6 +35,10 @@ export interface TerminalProps extends ComponentProps<"div"> {
3535 runtimeId ?: string
3636 onSubmit ?: ( ) => void
3737 onStatusChange ?: ( status : TerminalStatus , error ?: TerminalFailure ) => void
38+ /** When true (restored PTY), show the terminal immediately after xterm initialises
39+ * instead of waiting for the WebSocket handshake. The WebSocket still connects in
40+ * the background; input typed before it's ready is buffered. */
41+ optimisticReady ?: boolean
3842}
3943
4044let shared : Promise < { mod : typeof import ( "ghostty-web" ) ; ghostty : Ghostty } > | undefined
@@ -159,6 +163,7 @@ export const Terminal = (props: TerminalProps) => {
159163 "runtimeId" ,
160164 "onSubmit" ,
161165 "onStatusChange" ,
166+ "optimisticReady" ,
162167 ] )
163168 const id = local . pty . ptyId
164169 let ws : WebSocket | undefined
@@ -435,6 +440,8 @@ export const Terminal = (props: TerminalProps) => {
435440 } )
436441 cleanups . push ( ( ) => disposeIfDisposable ( onResize ) )
437442 const onData = t . onData ( ( data ) => {
443+ // When optimisticReady is active the buffering handler below takes over.
444+ if ( local . optimisticReady ) return
438445 if ( ws ?. readyState === WebSocket . OPEN ) ws . send ( data )
439446 } )
440447 cleanups . push ( ( ) => disposeIfDisposable ( onData ) )
@@ -456,6 +463,28 @@ export const Terminal = (props: TerminalProps) => {
456463 scheduleSize ( t . cols , t . rows )
457464 startResize ( )
458465
466+ // For restored PTYs: show the terminal surface immediately after xterm is
467+ // initialised instead of waiting for the full WebSocket handshake (which can
468+ // take 2-4 s). Input typed before the socket opens is buffered and flushed
469+ // once the connection is established.
470+ let inputBuffer = local . optimisticReady ? "" : undefined
471+ if ( local . optimisticReady ) {
472+ markReady ( )
473+ // Intercept onData to buffer keystrokes until the WebSocket is open.
474+ const onDataOpt = t . onData ( ( data ) => {
475+ if ( ws ?. readyState === WebSocket . OPEN ) {
476+ if ( inputBuffer ) {
477+ ws . send ( inputBuffer )
478+ inputBuffer = undefined
479+ }
480+ ws . send ( data )
481+ } else {
482+ inputBuffer = ( inputBuffer ?? "" ) + data
483+ }
484+ } )
485+ cleanups . push ( ( ) => disposeIfDisposable ( onDataOpt ) )
486+ }
487+
459488 const once = { value : false }
460489 const decoder = new TextDecoder ( )
461490
0 commit comments