authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-12 22:21:36-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-12 22:24:03-08:00
log5da67c40ebab884911f3fb1d51121d42b0fd1fba
tree935beb0b848f1d06047a27dc5e2cc76575e93fed
parent804572c8ff6c19a4e0355ff673b4e76cfe2069a2

Show pulsing terminal icon for active thread processes

- Replace terminal text badge with a right-aligned `TerminalIcon` - Add accessible label/title: "Terminal process running" - Keep pulse animation and terminal-active color styling

1 files changed, 28 insertions(+), 21 deletions(-)

apps/web/src/components/Sidebar.tsx+28-21
......@@ -1,4 +1,4 @@
1import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
1import { MonitorIcon, MoonIcon, SunIcon, TerminalIcon } from "lucide-react";
22import { useCallback, useEffect, useMemo, useState } from "react";
33import { isElectron } from "../env";
44import { useTheme } from "../hooks/useTheme";
......@@ -32,12 +32,18 @@ function inferProjectName(cwd: string): string {
3232}
3333
3434interface ThreadStatusPill {
35 label: "Working" | "Connecting" | "Completed" | "Awaiting response" | "Terminal";
35 label: "Working" | "Connecting" | "Completed" | "Awaiting response";
3636 colorClass: string;
3737 dotClass: string;
3838 pulse: boolean;
3939}
4040
41interface TerminalStatusIndicator {
42 label: "Terminal process running";
43 colorClass: string;
44 pulse: boolean;
45}
46
4147function hasUnseenCompletion(thread: Thread): boolean {
4248 if (!thread.latestTurnCompletedAt) return false;
4349 const completedAt = Date.parse(thread.latestTurnCompletedAt);
......@@ -89,14 +95,13 @@ function threadStatusPill(thread: Thread, hasPendingApprovals: boolean): ThreadS
8995 return null;
9096}
9197
92function terminalStatusPill(thread: Thread): ThreadStatusPill | null {
98function terminalStatusIndicator(thread: Thread): TerminalStatusIndicator | null {
9399 if (thread.runningTerminalIds.length === 0) {
94100 return null;
95101 }
96102 return {
97 label: "Terminal",
103 label: "Terminal process running",
98104 colorClass: "text-teal-600 dark:text-teal-300/90",
99 dotClass: "bg-teal-500 dark:bg-teal-300/90",
100105 pulse: true,
101106 };
102107}
......@@ -401,7 +406,7 @@ export default function Sidebar() {
401406 thread,
402407 pendingApprovalByThreadId.get(thread.id) === true,
403408 );
404 const terminalStatus = terminalStatusPill(thread);
409 const terminalStatus = terminalStatusIndicator(thread);
405410 return (
406411 <button
407412 key={thread.id}
......@@ -426,18 +431,6 @@ export default function Sidebar() {
426431 }}
427432 >
428433 <div className="flex min-w-0 flex-1 items-center gap-1.5">
429 {terminalStatus && (
430 <span
431 className={`inline-flex items-center gap-1 text-[10px] ${terminalStatus.colorClass}`}
432 >
433 <span
434 className={`h-1.5 w-1.5 rounded-full ${terminalStatus.dotClass} ${
435 terminalStatus.pulse ? "animate-pulse" : ""
436 }`}
437 />
438 <span className="hidden md:inline">{terminalStatus.label}</span>
439 </span>
440 )}
441434 {threadStatus && (
442435 <span
443436 className={`inline-flex items-center gap-1 text-[10px] ${threadStatus.colorClass}`}
......@@ -452,9 +445,23 @@ export default function Sidebar() {
452445 )}
453446 <span className="min-w-0 flex-1 truncate text-xs">{thread.title}</span>
454447 </div>
455 <span className="shrink-0 text-[10px] text-muted-foreground/40">
456 {formatRelativeTime(thread.createdAt)}
457 </span>
448 <div className="ml-2 flex shrink-0 items-center gap-1.5">
449 {terminalStatus && (
450 <span
451 role="img"
452 aria-label={terminalStatus.label}
453 title={terminalStatus.label}
454 className={`inline-flex items-center justify-center ${terminalStatus.colorClass}`}
455 >
456 <TerminalIcon
457 className={`size-3 ${terminalStatus.pulse ? "animate-pulse" : ""}`}
458 />
459 </span>
460 )}
461 <span className="text-[10px] text-muted-foreground/40">
462 {formatRelativeTime(thread.createdAt)}
463 </span>
464 </div>
458465 </button>
459466 );
460467 })}