From c431f8ff9dce0a5b9a83f0959e5679de62bafb12 Mon Sep 17 00:00:00 2001 From: Amrit Subramanian Date: Fri, 10 Oct 2025 19:55:23 -0400 Subject: [PATCH 1/2] 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 --- renderer.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++--- styles.css | 21 ++++++++++++----- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/renderer.ts b/renderer.ts index 0ff7359..d2664e1 100644 --- a/renderer.ts +++ b/renderer.ts @@ -213,6 +213,9 @@ let mcpServers: McpServer[] = []; let mcpPollerActive = false; let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS }; +// Track activity timers for each session +const activityTimers = new Map(); + function createTerminalUI(sessionId: string) { const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"]; @@ -457,6 +460,7 @@ function addTab(sessionId: string, name: string) { tab.id = `tab-${sessionId}`; tab.className = "tab"; tab.innerHTML = ` + ${name} `; @@ -498,6 +502,49 @@ function clearUnreadStatus(sessionId: string) { } } +function markSessionActivity(sessionId: string) { + const session = sessions.get(sessionId); + if (!session) return; + + // Add activity indicator to tab + const tab = document.getElementById(`tab-${sessionId}`); + if (tab) { + tab.classList.add("activity"); + tab.classList.remove("unread"); + } + + // Clear any existing timer + const existingTimer = activityTimers.get(sessionId); + if (existingTimer) { + clearTimeout(existingTimer); + } + + // Set a new timer to remove activity after 1 second of no output + const timer = setTimeout(() => { + clearActivityStatus(sessionId); + }, 1000); + + activityTimers.set(sessionId, timer); +} + +function clearActivityStatus(sessionId: string) { + const session = sessions.get(sessionId); + if (!session) return; + + // Remove activity indicator from tab, but keep unread if it's set + const tab = document.getElementById(`tab-${sessionId}`); + if (tab) { + tab.classList.remove("activity"); + // If there's no unread status, transition to unread after activity ends + if (!tab.classList.contains("unread") && activeSessionId !== sessionId) { + tab.classList.add("unread"); + } + } + + // Clear the timer + activityTimers.delete(sessionId); +} + function switchToSession(sessionId: string) { // Hide all sessions sessions.forEach((session, id) => { @@ -529,8 +576,9 @@ function switchToSession(sessionId: string) { // Load MCP servers for this session loadMcpServers(); - // Clear unread status when switching to this session + // Clear unread and activity status when switching to this session clearUnreadStatus(sessionId); + clearActivityStatus(sessionId); // Focus and resize session.terminal.focus(); @@ -633,11 +681,25 @@ ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => { session.terminal.write(filteredData); - // Only mark as unread if this is not the active session + // Only mark as unread/activity if this is not the active session if (activeSessionId !== sessionId && session.hasActivePty) { + // Show activity spinner while output is coming in + markSessionActivity(sessionId); + // Check if Claude session is ready for input if (isClaudeSessionReady(filteredData)) { - markSessionAsUnread(sessionId); + // Clear activity timer and set unread + const existingTimer = activityTimers.get(sessionId); + if (existingTimer) { + clearTimeout(existingTimer); + activityTimers.delete(sessionId); + } + + const tab = document.getElementById(`tab-${sessionId}`); + if (tab) { + tab.classList.remove("activity"); + tab.classList.add("unread"); + } } } } diff --git a/styles.css b/styles.css index 486afef..3ac61a1 100644 --- a/styles.css +++ b/styles.css @@ -113,15 +113,24 @@ @apply bg-gray-800 border-b-2 border-blue-500; } - .tab.unread .tab-name::after { - content: ''; - display: inline-block; + .unread-indicator { + display: none; width: 6px; height: 6px; - background-color: #3b82f6; border-radius: 50%; - margin-left: 6px; - vertical-align: middle; + margin-right: 6px; + } + + .tab.unread .unread-indicator { + display: inline-block; + background-color: #3b82f6; + } + + .tab.activity .unread-indicator { + display: inline-block; + border: 1.5px solid #eab308; + background-color: transparent; + animation: spin 1s linear infinite; } .tab-name { From 12d91912cd2d7f1cee5c228eac601232cd4f179c Mon Sep 17 00:00:00 2001 From: Amrit Subramanian Date: Fri, 10 Oct 2025 19:59:07 -0400 Subject: [PATCH 2/2] Increase size of unread and activity indicators - Increase indicator size from 6px to 10px for better visibility - Increase spinner border from 1.5px to 2px - Add transparent top border for cleaner spinner animation --- styles.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/styles.css b/styles.css index 3ac61a1..b394707 100644 --- a/styles.css +++ b/styles.css @@ -115,8 +115,8 @@ .unread-indicator { display: none; - width: 6px; - height: 6px; + width: 10px; + height: 10px; border-radius: 50%; margin-right: 6px; } @@ -128,7 +128,8 @@ .tab.activity .unread-indicator { display: inline-block; - border: 1.5px solid #eab308; + border: 2px solid #eab308; + border-top-color: transparent; background-color: transparent; animation: spin 1s linear infinite; }