@@ -22,8 +22,8 @@ import { MAX_TERMINALS } from '@sim/terminal-protocol'
2222import { TERMINAL_SESSION_RESOURCE_ID } from '@/lib/copilot/resources/types'
2323import {
2424 closeTerminal ,
25+ getTerminalScrollback ,
2526 onTerminalData ,
26- onTerminalReplay ,
2727 openTerminal ,
2828 resizeTerminal ,
2929 startTerminalSession ,
@@ -144,6 +144,7 @@ function TerminalView({ terminalId, active }: { terminalId: string; active: bool
144144 const host = hostRef . current
145145 if ( ! host ) return
146146
147+ let disposed = false
147148 const terminal = new Terminal ( {
148149 allowProposedApi : true ,
149150 cursorBlink : true ,
@@ -171,18 +172,32 @@ function TerminalView({ terminalId, active }: { terminalId: string; active: bool
171172 const disposeResize = terminal . onResize ( ( { cols, rows } ) =>
172173 resizeTerminal ( terminalId , cols , rows )
173174 )
175+ // The desktop app owns the scrollback, so a view opening onto a shell that
176+ // has been running without it paints from what is already on that screen.
177+ //
178+ // Live bytes are held until that paint lands rather than written straight
179+ // through: the snapshot is a moment in time, and anything arriving while
180+ // it is in flight would either be wiped by the reset or, written first,
181+ // appear above the history it followed. Buffering keeps the order true.
182+ let painted = false
183+ let buffered = ''
174184 const unsubscribeData = onTerminalData ( ( id , data ) => {
175- if ( id === terminalId ) terminal . write ( data )
176- } )
177- // The desktop app owns the scrollback, so a panel mounting over a shell
178- // that was already running repaints from it. Resetting first makes the
179- // repaint idempotent, so bytes that arrived before it cannot show up twice.
180- const unsubscribeReplay = onTerminalReplay ( ( id , data ) => {
181185 if ( id !== terminalId ) return
182- terminal . reset ( )
183- if ( data ) terminal . write ( data )
186+ if ( painted ) terminal . write ( data )
187+ else buffered += data
184188 } )
185189
190+ void getTerminalScrollback ( terminalId )
191+ . catch ( ( ) => '' )
192+ . then ( ( scrollback ) => {
193+ if ( disposed ) return
194+ terminal . reset ( )
195+ if ( scrollback ) terminal . write ( scrollback )
196+ if ( buffered ) terminal . write ( buffered )
197+ buffered = ''
198+ painted = true
199+ } )
200+
186201 // Resizing is debounced, and deliberately not applied to hidden tabs.
187202 //
188203 // Every distinct column count reaches the shell as a SIGWINCH and makes it
@@ -212,10 +227,10 @@ function TerminalView({ terminalId, active }: { terminalId: string; active: bool
212227 observer . observe ( host )
213228
214229 return ( ) => {
230+ disposed = true
215231 if ( resizeTimer ) clearTimeout ( resizeTimer )
216232 observer . disconnect ( )
217233 unsubscribeData ( )
218- unsubscribeReplay ( )
219234 disposeData . dispose ( )
220235 disposeResize . dispose ( )
221236 terminal . dispose ( )
0 commit comments