Skip to content

Commit c431f8f

Browse files
committed
Move unread indicator to left and add activity spinner
- Move unread indicator from right to left of session name in tabs - Add yellow spinning indicator when output is actively coming in - Spinner automatically transitions to blue dot after 1s of idle - Activity indicator properly preserves unread status
1 parent 74bcedf commit c431f8f

File tree

2 files changed

+80
-9
lines changed

2 files changed

+80
-9
lines changed

renderer.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ let mcpServers: McpServer[] = [];
213213
let mcpPollerActive = false;
214214
let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS };
215215

216+
// Track activity timers for each session
217+
const activityTimers = new Map<string, NodeJS.Timeout>();
218+
216219
function 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+
501548
function 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
}

styles.css

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,24 @@
113113
@apply bg-gray-800 border-b-2 border-blue-500;
114114
}
115115

116-
.tab.unread .tab-name::after {
117-
content: '';
118-
display: inline-block;
116+
.unread-indicator {
117+
display: none;
119118
width: 6px;
120119
height: 6px;
121-
background-color: #3b82f6;
122120
border-radius: 50%;
123-
margin-left: 6px;
124-
vertical-align: middle;
121+
margin-right: 6px;
122+
}
123+
124+
.tab.unread .unread-indicator {
125+
display: inline-block;
126+
background-color: #3b82f6;
127+
}
128+
129+
.tab.activity .unread-indicator {
130+
display: inline-block;
131+
border: 1.5px solid #eab308;
132+
background-color: transparent;
133+
animation: spin 1s linear infinite;
125134
}
126135

127136
.tab-name {

0 commit comments

Comments
 (0)