@@ -76,18 +76,58 @@ function createTerminalUI(sessionId: string) {
7676
7777 // Listen for bell character to mark unread activity
7878 term . onBell ( ( ) => {
79- console . log ( `Bell received for session ${ sessionId } , activeSessionId: ${ activeSessionId } ` ) ;
8079 if ( activeSessionId !== sessionId ) {
81- console . log ( `Marking session ${ sessionId } as unread` ) ;
8280 markSessionAsUnread ( sessionId ) ;
8381 }
8482 } ) ;
8583
86- // Handle resize
84+ // Handle resize - only refit if dimensions actually changed
85+ let lastCols = term . cols ;
86+ let lastRows = term . rows ;
87+ let resizeTimeout : NodeJS . Timeout | null = null ;
88+
8789 const resizeHandler = ( ) => {
8890 if ( activeSessionId === sessionId ) {
89- fitAddon . fit ( ) ;
90- ipcRenderer . send ( "session-resize" , sessionId , term . cols , term . rows ) ;
91+ // Clear any pending resize
92+ if ( resizeTimeout ) {
93+ clearTimeout ( resizeTimeout ) ;
94+ }
95+
96+ // Debounce the fit call
97+ resizeTimeout = setTimeout ( ( ) => {
98+ // Calculate what the new dimensions would be
99+ const container = sessionElement ;
100+ if ( ! container ) return ;
101+
102+ const rect = container . getBoundingClientRect ( ) ;
103+ const core = ( term as any ) . _core ;
104+ if ( ! core ) return ;
105+
106+ // Estimate new dimensions based on container size
107+ const newCols = Math . floor ( rect . width / core . _renderService . dimensions . actualCellWidth ) ;
108+ const newRows = Math . floor ( rect . height / core . _renderService . dimensions . actualCellHeight ) ;
109+
110+ // Only fit if dimensions actually changed significantly (more than 1 char difference)
111+ if ( Math . abs ( newCols - lastCols ) > 1 || Math . abs ( newRows - lastRows ) > 1 ) {
112+ // Save scroll position before fitting
113+ const wasAtBottom = term . buffer . active . viewportY === term . buffer . active . baseY ;
114+ const savedScrollPosition = term . buffer . active . viewportY ;
115+
116+ fitAddon . fit ( ) ;
117+
118+ lastCols = term . cols ;
119+ lastRows = term . rows ;
120+
121+ // Restore scroll position unless we were at the bottom (in which case stay at bottom)
122+ if ( ! wasAtBottom && savedScrollPosition !== term . buffer . active . viewportY ) {
123+ term . scrollToLine ( savedScrollPosition ) ;
124+ }
125+
126+ ipcRenderer . send ( "session-resize" , sessionId , term . cols , term . rows ) ;
127+ }
128+
129+ resizeTimeout = null ;
130+ } , 100 ) ; // 100ms debounce
91131 }
92132 } ;
93133 window . addEventListener ( "resize" , resizeHandler ) ;
@@ -375,7 +415,17 @@ function switchToSession(sessionId: string) {
375415 session . terminal . focus ( ) ;
376416 setTimeout ( ( ) => {
377417 if ( session . fitAddon && session . terminal ) {
418+ // Save scroll position before fitting
419+ const wasAtBottom = session . terminal . buffer . active . viewportY === session . terminal . buffer . active . baseY ;
420+ const savedScrollPosition = session . terminal . buffer . active . viewportY ;
421+
378422 session . fitAddon . fit ( ) ;
423+
424+ // Restore scroll position unless we were at the bottom
425+ if ( ! wasAtBottom && savedScrollPosition !== session . terminal . buffer . active . viewportY ) {
426+ session . terminal . scrollToLine ( savedScrollPosition ) ;
427+ }
428+
379429 ipcRenderer . send ( "session-resize" , sessionId , session . terminal . cols , session . terminal . rows ) ;
380430 }
381431 } , 0 ) ;
@@ -464,28 +514,27 @@ const IDLE_DELAY_MS = 500; // 0.5 seconds of no output = Claude is done
464514ipcRenderer . on ( "session-output" , ( _event , sessionId : string , data : string ) => {
465515 const session = sessions . get ( sessionId ) ;
466516 if ( session && session . terminal ) {
467- session . terminal . write ( data ) ;
517+ // Filter out [3J (clear scrollback) to prevent viewport resets during interactive menus
518+ // Keep [2J (clear screen) which is needed for the menu redraw
519+ const filteredData = data . replace ( / \x1b \[ 3 J / g, '' ) ;
520+
521+ session . terminal . write ( filteredData ) ;
468522
469523 // Only mark as unread if this is not the active session
470524 if ( activeSessionId !== sessionId && session . hasActivePty ) {
471- console . log ( `[Unread] Session ${ sessionId } received output while inactive` ) ;
472-
473525 // Clear any existing idle timer
474526 const existingTimer = sessionIdleTimers . get ( sessionId ) ;
475527 if ( existingTimer ) {
476- console . log ( `[Unread] Clearing existing idle timer for session ${ sessionId } ` ) ;
477528 clearTimeout ( existingTimer ) ;
478529 }
479530
480531 // Set a new timer - if no output for IDLE_DELAY_MS, mark as unread
481532 const timer = setTimeout ( ( ) => {
482- console . log ( `[Unread] ✓ No output for ${ IDLE_DELAY_MS } ms - Claude is done! Marking session ${ sessionId } as unread` ) ;
483533 markSessionAsUnread ( sessionId ) ;
484534 sessionIdleTimers . delete ( sessionId ) ;
485535 } , IDLE_DELAY_MS ) ;
486536
487537 sessionIdleTimers . set ( sessionId , timer ) ;
488- console . log ( `[Unread] Set idle timer for session ${ sessionId } - will trigger in ${ IDLE_DELAY_MS } ms if no more output` ) ;
489538 }
490539 }
491540} ) ;
0 commit comments