Skip to content

Commit 67f5162

Browse files
authored
Merge pull request #12 from built-by-as/unread-indicator-appearance
Unread indicator appearance
2 parents 74bcedf + 12d9191 commit 67f5162

File tree

2 files changed

+82
-10
lines changed

2 files changed

+82
-10
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: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,25 @@
113113
@apply bg-gray-800 border-b-2 border-blue-500;
114114
}
115115

116-
.tab.unread .tab-name::after {
117-
content: '';
116+
.unread-indicator {
117+
display: none;
118+
width: 10px;
119+
height: 10px;
120+
border-radius: 50%;
121+
margin-right: 6px;
122+
}
123+
124+
.tab.unread .unread-indicator {
118125
display: inline-block;
119-
width: 6px;
120-
height: 6px;
121126
background-color: #3b82f6;
122-
border-radius: 50%;
123-
margin-left: 6px;
124-
vertical-align: middle;
127+
}
128+
129+
.tab.activity .unread-indicator {
130+
display: inline-block;
131+
border: 2px solid #eab308;
132+
border-top-color: transparent;
133+
background-color: transparent;
134+
animation: spin 1s linear infinite;
125135
}
126136

127137
.tab-name {

0 commit comments

Comments
 (0)