@@ -213,6 +213,9 @@ let mcpServers: McpServer[] = [];
213213let mcpPollerActive = false ;
214214let terminalSettings : TerminalSettings = { ...DEFAULT_SETTINGS } ;
215215
216+ // Track activity timers for each session
217+ const activityTimers = new Map < string , NodeJS . Timeout > ( ) ;
218+
216219function createTerminalUI ( sessionId : string ) {
217220 const themeColors = THEME_PRESETS [ terminalSettings . theme ] || THEME_PRESETS [ "macos-dark" ] ;
218221
@@ -457,6 +460,7 @@ function addTab(sessionId: string, name: string) {
457460 tab . id = `tab-${ sessionId } ` ;
458461 tab . className = "tab" ;
459462 tab . innerHTML = `
463+ <span class="unread-indicator"></span>
460464 <span class="tab-name">${ name } </span>
461465 <button class="tab-close-btn" data-id="${ sessionId } ">×</button>
462466 ` ;
@@ -498,6 +502,49 @@ function clearUnreadStatus(sessionId: string) {
498502 }
499503}
500504
505+ function markSessionActivity ( sessionId : string ) {
506+ const session = sessions . get ( sessionId ) ;
507+ if ( ! session ) return ;
508+
509+ // Add activity indicator to tab
510+ const tab = document . getElementById ( `tab-${ sessionId } ` ) ;
511+ if ( tab ) {
512+ tab . classList . add ( "activity" ) ;
513+ tab . classList . remove ( "unread" ) ;
514+ }
515+
516+ // Clear any existing timer
517+ const existingTimer = activityTimers . get ( sessionId ) ;
518+ if ( existingTimer ) {
519+ clearTimeout ( existingTimer ) ;
520+ }
521+
522+ // Set a new timer to remove activity after 1 second of no output
523+ const timer = setTimeout ( ( ) => {
524+ clearActivityStatus ( sessionId ) ;
525+ } , 1000 ) ;
526+
527+ activityTimers . set ( sessionId , timer ) ;
528+ }
529+
530+ function clearActivityStatus ( sessionId : string ) {
531+ const session = sessions . get ( sessionId ) ;
532+ if ( ! session ) return ;
533+
534+ // Remove activity indicator from tab, but keep unread if it's set
535+ const tab = document . getElementById ( `tab-${ sessionId } ` ) ;
536+ if ( tab ) {
537+ tab . classList . remove ( "activity" ) ;
538+ // If there's no unread status, transition to unread after activity ends
539+ if ( ! tab . classList . contains ( "unread" ) && activeSessionId !== sessionId ) {
540+ tab . classList . add ( "unread" ) ;
541+ }
542+ }
543+
544+ // Clear the timer
545+ activityTimers . delete ( sessionId ) ;
546+ }
547+
501548function switchToSession ( sessionId : string ) {
502549 // Hide all sessions
503550 sessions . forEach ( ( session , id ) => {
@@ -529,8 +576,9 @@ function switchToSession(sessionId: string) {
529576 // Load MCP servers for this session
530577 loadMcpServers ( ) ;
531578
532- // Clear unread status when switching to this session
579+ // Clear unread and activity status when switching to this session
533580 clearUnreadStatus ( sessionId ) ;
581+ clearActivityStatus ( sessionId ) ;
534582
535583 // Focus and resize
536584 session . terminal . focus ( ) ;
@@ -633,11 +681,25 @@ ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => {
633681
634682 session . terminal . write ( filteredData ) ;
635683
636- // Only mark as unread if this is not the active session
684+ // Only mark as unread/activity if this is not the active session
637685 if ( activeSessionId !== sessionId && session . hasActivePty ) {
686+ // Show activity spinner while output is coming in
687+ markSessionActivity ( sessionId ) ;
688+
638689 // Check if Claude session is ready for input
639690 if ( isClaudeSessionReady ( filteredData ) ) {
640- markSessionAsUnread ( sessionId ) ;
691+ // Clear activity timer and set unread
692+ const existingTimer = activityTimers . get ( sessionId ) ;
693+ if ( existingTimer ) {
694+ clearTimeout ( existingTimer ) ;
695+ activityTimers . delete ( sessionId ) ;
696+ }
697+
698+ const tab = document . getElementById ( `tab-${ sessionId } ` ) ;
699+ if ( tab ) {
700+ tab . classList . remove ( "activity" ) ;
701+ tab . classList . add ( "unread" ) ;
702+ }
641703 }
642704 }
643705 }
0 commit comments