import { scopeProjectRef, scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; import type { VcsStatusResult } from "@t3tools/contracts"; import { CloudIcon, GitPullRequestIcon, TerminalIcon } from "lucide-react"; import { useMemo } from "react"; import { usePrimaryEnvironmentId } from "../environments/primary"; import { useSavedEnvironmentRegistryStore, useSavedEnvironmentRuntimeStore, } from "../environments/runtime"; import { useVcsStatus } from "../lib/vcsStatusState"; import { type AppState, selectProjectByRef, useStore } from "../store"; import { useThreadRunningTerminalIds } from "../terminalSessionState"; import { useUiStateStore } from "../uiStateStore"; import { resolveChangeRequestPresentation } from "../sourceControlPresentation"; import { resolveThreadStatusPill, type ThreadStatusPill } from "./Sidebar.logic"; import type { SidebarThreadSummary } from "../types"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; export interface PrStatusIndicator { label: string; colorClass: string; tooltip: string; url: string; } export interface TerminalStatusIndicator { label: "Terminal process running"; colorClass: string; pulse: boolean; } export type ThreadPr = VcsStatusResult["pr"]; export function prStatusIndicator( pr: ThreadPr, provider: VcsStatusResult["sourceControlProvider"] | null | undefined, ): PrStatusIndicator | null { if (!pr) return null; const presentation = resolveChangeRequestPresentation(provider); if (pr.state === "open") { return { label: `${presentation.shortName} open`, colorClass: "text-emerald-600 dark:text-emerald-300/90", tooltip: `#${pr.number} ${presentation.shortName} open: ${pr.title}`, url: pr.url, }; } if (pr.state === "closed") { return { label: `${presentation.shortName} closed`, colorClass: "text-zinc-500 dark:text-zinc-400/80", tooltip: `#${pr.number} ${presentation.shortName} closed: ${pr.title}`, url: pr.url, }; } if (pr.state === "merged") { return { label: `${presentation.shortName} merged`, colorClass: "text-violet-600 dark:text-violet-300/90", tooltip: `#${pr.number} ${presentation.shortName} merged: ${pr.title}`, url: pr.url, }; } return null; } export function ChangeRequestStatusIcon({ className }: { className?: string }) { return ; } export function resolveThreadPr( threadBranch: string | null, gitStatus: VcsStatusResult | null, ): ThreadPr | null { if (threadBranch === null || gitStatus === null || gitStatus.refName !== threadBranch) { return null; } return gitStatus.pr ?? null; } export function terminalStatusFromRunningIds( runningTerminalIds: ReadonlyArray, ): TerminalStatusIndicator | null { if (runningTerminalIds.length === 0) { return null; } return { label: "Terminal process running", colorClass: "text-teal-600 dark:text-teal-300/90", pulse: true, }; } export function ThreadStatusLabel({ status, compact = false, }: { status: ThreadStatusPill; compact?: boolean; }) { if (compact) { return ( {status.label} ); } return ( {status.label} ); } /** * Non-interactive leading status icons for a thread row in compact contexts * like the command palette. Shows the change request state icon (if present) and the * thread status dot, matching the sidebar's leading indicators. */ export function ThreadRowLeadingStatus({ thread }: { thread: SidebarThreadSummary }) { const threadRef = scopeThreadRef(thread.environmentId, thread.id); const lastVisitedAt = useUiStateStore( (state) => state.threadLastVisitedAtById[scopedThreadKey(threadRef)], ); const threadProjectCwd = useStore( useMemo( () => (state: AppState) => selectProjectByRef(state, scopeProjectRef(thread.environmentId, thread.projectId))?.cwd ?? null, [thread.environmentId, thread.projectId], ), ); const gitCwd = thread.worktreePath ?? threadProjectCwd; const gitStatus = useVcsStatus({ environmentId: thread.environmentId, cwd: thread.branch != null ? gitCwd : null, }); const pr = resolveThreadPr(thread.branch, gitStatus.data); const prStatus = prStatusIndicator(pr, gitStatus.data?.sourceControlProvider); const threadStatus = resolveThreadStatusPill({ thread: { ...thread, lastVisitedAt, }, }); if (!prStatus && !threadStatus) { return null; } return ( {prStatus ? ( } > {prStatus.tooltip} ) : null} {threadStatus ? : null} ); } /** * Non-interactive trailing status icons for a thread row in compact contexts * like the command palette. Shows a terminal-running indicator and a remote * environment indicator, matching the sidebar's trailing indicators. */ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSummary }) { const runningTerminalIds = useThreadRunningTerminalIds({ environmentId: thread.environmentId, threadId: thread.id, }); const primaryEnvironmentId = usePrimaryEnvironmentId(); const isRemoteThread = primaryEnvironmentId !== null && thread.environmentId !== primaryEnvironmentId; const remoteEnvLabel = useSavedEnvironmentRuntimeStore( (state) => state.byId[thread.environmentId]?.descriptor?.label ?? null, ); const remoteEnvSavedLabel = useSavedEnvironmentRegistryStore( (state) => state.byId[thread.environmentId]?.label ?? null, ); const threadEnvironmentLabel = isRemoteThread ? (remoteEnvLabel ?? remoteEnvSavedLabel ?? "Remote") : null; const terminalStatus = terminalStatusFromRunningIds(runningTerminalIds); if (!terminalStatus && !isRemoteThread) { return null; } return ( {terminalStatus ? ( ) : null} {isRemoteThread ? ( } > {threadEnvironmentLabel} ) : null} ); }