| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | | import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"; |
| 1 | import { MonitorIcon, MoonIcon, SunIcon, TerminalIcon } from "lucide-react"; |
| 2 | 2 | import { useCallback, useEffect, useMemo, useState } from "react"; |
| 3 | 3 | import { isElectron } from "../env"; |
| 4 | 4 | import { useTheme } from "../hooks/useTheme"; |
| ... | ... | @@ -32,12 +32,18 @@ function inferProjectName(cwd: string): string { |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | interface ThreadStatusPill { |
| 35 | | label: "Working" | "Connecting" | "Completed" | "Awaiting response" | "Terminal"; |
| 35 | label: "Working" | "Connecting" | "Completed" | "Awaiting response"; |
| 36 | 36 | colorClass: string; |
| 37 | 37 | dotClass: string; |
| 38 | 38 | pulse: boolean; |
| 39 | 39 | } |
| 40 | 40 | |
| 41 | interface TerminalStatusIndicator { |
| 42 | label: "Terminal process running"; |
| 43 | colorClass: string; |
| 44 | pulse: boolean; |
| 45 | } |
| 46 | |
| 41 | 47 | function hasUnseenCompletion(thread: Thread): boolean { |
| 42 | 48 | if (!thread.latestTurnCompletedAt) return false; |
| 43 | 49 | const completedAt = Date.parse(thread.latestTurnCompletedAt); |
| ... | ... | @@ -89,14 +95,13 @@ function threadStatusPill(thread: Thread, hasPendingApprovals: boolean): ThreadS |
| 89 | 95 | return null; |
| 90 | 96 | } |
| 91 | 97 | |
| 92 | | function terminalStatusPill(thread: Thread): ThreadStatusPill | null { |
| 98 | function terminalStatusIndicator(thread: Thread): TerminalStatusIndicator | null { |
| 93 | 99 | if (thread.runningTerminalIds.length === 0) { |
| 94 | 100 | return null; |
| 95 | 101 | } |
| 96 | 102 | return { |
| 97 | | label: "Terminal", |
| 103 | label: "Terminal process running", |
| 98 | 104 | colorClass: "text-teal-600 dark:text-teal-300/90", |
| 99 | | dotClass: "bg-teal-500 dark:bg-teal-300/90", |
| 100 | 105 | pulse: true, |
| 101 | 106 | }; |
| 102 | 107 | } |
| ... | ... | @@ -401,7 +406,7 @@ export default function Sidebar() { |
| 401 | 406 | thread, |
| 402 | 407 | pendingApprovalByThreadId.get(thread.id) === true, |
| 403 | 408 | ); |
| 404 | | const terminalStatus = terminalStatusPill(thread); |
| 409 | const terminalStatus = terminalStatusIndicator(thread); |
| 405 | 410 | return ( |
| 406 | 411 | <button |
| 407 | 412 | key={thread.id} |
| ... | ... | @@ -426,18 +431,6 @@ export default function Sidebar() { |
| 426 | 431 | }} |
| 427 | 432 | > |
| 428 | 433 | <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 | | )} |
| 441 | 434 | {threadStatus && ( |
| 442 | 435 | <span |
| 443 | 436 | className={`inline-flex items-center gap-1 text-[10px] ${threadStatus.colorClass}`} |
| ... | ... | @@ -452,9 +445,23 @@ export default function Sidebar() { |
| 452 | 445 | )} |
| 453 | 446 | <span className="min-w-0 flex-1 truncate text-xs">{thread.title}</span> |
| 454 | 447 | </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> |
| 458 | 465 | </button> |
| 459 | 466 | ); |
| 460 | 467 | })} |