import type { CSSProperties } from "react"; import { AceAvatar } from "../ace-avatar/ace-avatar"; import { cn } from "../../lib/utils"; import { Badge } from "../../ui/badge"; import type { SidebarRow } from "./session-item.types"; import { IconArchive as Archive, IconHand as HandWave, IconHash, IconLock, IconMerge, IconMessage as Msg, IconPullRequest, IconPullRequestClosed, } from "../../icons"; type BadgeState = ReturnType; const ACTIVE_ICON = "group-data-[status=active]/row:text-current/80"; /** * Resolves the status-dot treatment for a row. Connection issues take * precedence over unread activity. */ function resolveBadge(data: SidebarRow): { show: boolean; pulse: boolean; color?: string } { if (data.connection === "error") { return { show: true, pulse: false, color: "var(--color-destructive)" }; } // The channel's host is asleep or away: expected, not a fault. if (data.connection === "offline") { return { show: true, pulse: false, color: "var(--color-muted-foreground)" }; } if (data.connection === "reconnecting") { return { show: true, pulse: true, color: "var(--color-warning)" }; } // Transport is up but the session hasn't answered yet (resuming/rehydrating). Calm, // neutral pulse — "starting up", not a problem and not yet healthy/green. if (data.connection === "warming") { return { show: true, pulse: true, color: "var(--color-muted-foreground)" }; } if (data.unreadCount > 0 && data.pr?.state === "draft") { return { show: true, pulse: false, color: "var(--color-muted-foreground)" }; } if (data.unreadCount > 0 && data.pr?.state === "closed") { return { show: true, pulse: false, color: "var(--color-destructive)" }; } if (data.unreadCount > 0 && data.pr?.state === "merged") { return { show: true, pulse: false, color: "var(--color-merged)" }; } if (data.unreadCount > 0 && data.pr?.state === "open") { return { show: true, pulse: false, color: "var(--color-accent)" }; } if (data.unreadCount > 0) return { show: true, pulse: false }; return { show: false, pulse: false }; } function resolveIcon(data: SidebarRow) { if (data.kind === "lobby") return ; if (data.lifecycle === "archived") { return ( ); } if (data.pr) { if (data.pr.state === "draft") { return ( ); } if (data.pr.state === "closed") { return ( ); } if (data.pr.state === "merged") { return ( ); } return ( ); } if (data.private) return ; return ; } function working(data: SidebarRow) { return data.connection === "connected" && data.agent !== "idle"; } function MentionBadge({ count, label }: { count: number; label: string }) { return ( {count} ); } function AttentionWave({ label, mentionCount }: { label: string; mentionCount?: number }) { return ( span:first-child]:grid [&>span:first-child]:size-4.5 [&>span:first-child]:place-items-center", )} > ); } function mentionText(count: number) { return `${count} direct ${count === 1 ? "mention" : "mentions"}`; } function attentionText(data: SidebarRow, showAttention: boolean) { if (!showAttention) return mentionText(data.mentionCount); if (data.mentionCount <= 0) return "Agent needs your attention"; return `${mentionText(data.mentionCount)} and agent needs your attention`; } function canShowMention({ badge, isWorking, selected, showAttention, count, }: { badge: BadgeState; isWorking: boolean; selected?: boolean; showAttention: boolean; count: number; }) { return count > 0 && !showAttention && !selected && !badge.color && !badge.pulse && !isWorking; } export function RowGlyph({ data, selected }: { data: SidebarRow; selected?: boolean }) { let badge = resolveBadge(data); let isWorking = working(data); let showAttention = Boolean(data.needsAttention) && !selected && !isWorking; let label = attentionText(data, showAttention); let showMention = canShowMention({ badge, isWorking, selected, showAttention, count: data.mentionCount, }); if (isWorking) { return ( ); } if (showAttention) { return ; } if (showMention) { return ; } return ( {resolveIcon(data)} ); }