import { AlertTriangleIcon, ChevronDownIcon, ChevronRightIcon, CopyIcon, FolderOpenIcon, InfoIcon, RefreshCwIcon,} from "lucide-react";import { useCallback, useMemo, useState, type ReactNode } from "react";import type { ServerProcessDiagnosticsEntry, ServerProcessSignal } from "@t3tools/contracts";import * as DateTime from "effect/DateTime";import * as Option from "effect/Option"; import { ensureLocalApi } from "../../localApi";import { cn } from "../../lib/utils";import { resolveAndPersistPreferredEditor } from "../../editorPreferences";import { formatRelativeTime } from "../../timestampFormat";import { useServerAvailableEditors, useServerObservability } from "../../rpc/serverState";import { useProcessDiagnostics } from "../../lib/processDiagnosticsState";import { useTraceDiagnostics } from "../../lib/traceDiagnosticsState";import { Button } from "../ui/button";import { ScrollArea } from "../ui/scroll-area";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";import { toastManager } from "../ui/toast";import { SettingsPageContainer, SettingsSection, useRelativeTimeTick } from "./settingsLayout"; const NUMBER_FORMAT = new Intl.NumberFormat(); function formatCount(value: number): string { return NUMBER_FORMAT.format(value);} function formatDuration(value: number): string { if (value < 1_000) return `${Math.round(value)} ms`; return `${(value / 1_000).toFixed(value >= 10_000 ? 1 : 2)} s`;} function formatBytes(value: number): string { if (value < 1024) return `${value} B`; const units = ["KB", "MB", "GB"] as const; let unitIndex = -1; let next = value; do { next /= 1024; unitIndex += 1; } while (next >= 1024 && unitIndex < units.length - 1); return `${next.toFixed(next >= 10 ? 1 : 2)} ${units[unitIndex]}`;} function formatRelative(value: DateTime.Utc | null): string { if (!value) return "No trace records"; const relative = formatRelativeTime(DateTime.formatIso(value)); return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value;} function formatRelativeNoWrap(value: DateTime.Utc | null): string { return formatRelative(value).replaceAll(" ", "\u00a0");} function shortenTraceId(traceId: string): string { if (traceId.length <= 32) return traceId; return `${traceId.slice(0, 18)}...${traceId.slice(-10)}`;} function isStaleProcessSignalMessage(message: string | undefined): boolean { return message?.includes("not a live descendant") ?? false;} function StatBlock({ label, value, tooltip, tone = "default",}: { label: string; value: string; tooltip?: ReactNode; tone?: "default" | "warning" | "danger";}) { return ( <div className="min-w-0 border-border/60 px-4 py-3 sm:px-5"> <div className="flex min-w-0 items-center gap-1.5 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground/70"> <span className="min-w-0 truncate">{label}</span> {tooltip ? ( <Tooltip> <TooltipTrigger render={ <button type="button" className="inline-flex size-3.5 shrink-0 items-center justify-center rounded-sm text-muted-foreground/60 hover:text-foreground" aria-label={`${label} details`} > <InfoIcon className="size-3" /> </button> } /> <TooltipPopup side="top" className="max-w-[min(300px,calc(100vw-2rem))] whitespace-normal text-left text-[11px] leading-relaxed text-wrap" > {tooltip} </TooltipPopup> </Tooltip> ) : null} </div> <div className={cn( "mt-1 truncate font-mono text-lg font-semibold tabular-nums text-foreground", tone === "warning" && "text-amber-600 dark:text-amber-400", tone === "danger" && "text-destructive", )} > {value} </div> </div> );} function StatsGrid({ children }: { children: ReactNode }) { return ( <div className="relative grid grid-cols-2 sm:grid-cols-4"> <span className="pointer-events-none absolute inset-y-0 left-1/2 w-px bg-border/60" aria-hidden /> <span className="pointer-events-none absolute inset-x-0 top-1/2 h-px bg-border/60 sm:hidden" aria-hidden /> <span className="pointer-events-none absolute inset-y-0 left-1/4 hidden w-px bg-border/60 sm:block" aria-hidden /> <span className="pointer-events-none absolute inset-y-0 left-3/4 hidden w-px bg-border/60 sm:block" aria-hidden /> {children} </div> );} function EmptyRows({ label }: { label: string }) { return <div className="px-4 py-4 text-xs text-muted-foreground sm:px-5">{label}</div>;} function ExpandableText({ text, className, collapsedClassName = "line-clamp-3", expandLabel = "Show full error",}: { text: string; className?: string; collapsedClassName?: string; expandLabel?: string;}) { const [expanded, setExpanded] = useState(false); const canExpand = text.length > 180 || text.includes("\n"); return ( <div className={cn("min-w-0", className)}> <div className={cn( "whitespace-pre-wrap break-words", !expanded && canExpand ? collapsedClassName : null, )} > {text} </div> {canExpand ? ( <button type="button" className="mt-1 text-[11px] font-medium text-foreground/70 underline-offset-2 hover:text-foreground hover:underline" onClick={() => setExpanded((value) => !value)} > {expanded ? "Show less" : expandLabel} </button> ) : null} </div> );} function DiagnosticsTable({ headers, children, minTableWidth = "min-w-[640px]", columnWidths,}: { headers: ReadonlyArray<string>; children: ReactNode; minTableWidth?: string; columnWidths?: ReadonlyArray<string>;}) { return ( <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none" > <table className={cn("w-full text-left text-xs", minTableWidth, columnWidths && "table-fixed")} > {columnWidths ? ( <colgroup> {headers.map((header, index) => ( <col key={header} className={columnWidths[index]} /> ))} </colgroup> ) : null} <thead className="border-b border-border/60 text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> <tr> {headers.map((header, index) => ( <th key={header} className={cn( "whitespace-nowrap px-4 py-2.5 font-semibold first:sm:pl-5 last:sm:pr-5", !columnWidths && index === headers.length - 1 && "w-px", )} > {header.replaceAll(" ", "\u00a0")} </th> ))} </tr> </thead> <tbody className="divide-y divide-border/60">{children}</tbody> </table> </ScrollArea> );} function TraceIdCell({ traceId }: { traceId: string }) { const [copied, setCopied] = useState(false); const copyTraceId = useCallback(() => { void navigator.clipboard ?.writeText(traceId) .then(() => { setCopied(true); window.setTimeout(() => setCopied(false), 1_200); }) .catch(() => undefined); }, [traceId]); return ( <div className="flex w-full min-w-0 max-w-full items-center gap-2"> <Tooltip> <TooltipTrigger render={ <span className="min-w-0 flex-1 truncate font-mono text-[11px]"> {shortenTraceId(traceId)} </span> } /> <TooltipPopup side="top" className="max-w-[min(520px,calc(100vw-2rem))] break-all font-mono text-[11px]" > {traceId} </TooltipPopup> </Tooltip> <Tooltip> <TooltipTrigger render={ <button type="button" className="inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" aria-label={copied ? "Copied trace ID" : "Copy trace ID"} onClick={copyTraceId} > <CopyIcon className="size-3" /> </button> } /> <TooltipPopup side="top">{copied ? "Copied" : "Copy full trace ID"}</TooltipPopup> </Tooltip> </div> );} function formatProcessName(command: string): string { const firstToken = command.trim().split(/\s+/)[0]; if (!firstToken) return command; const normalized = firstToken.replace(/^['"]|['"]$/g, ""); const segments = normalized.split(/[\\/]/).filter(Boolean); return segments.at(-1) ?? normalized;} function formatProcessType(process: ServerProcessDiagnosticsEntry): string { if (process.depth > 0) return "Subprocess"; if (/\b(codex|claude|opencode|cursor)\b/i.test(process.command)) return "Agent"; return "Process";} function ProcessNameCell({ process, isExpanded, onToggle,}: { process: ServerProcessDiagnosticsEntry; isExpanded: boolean; onToggle: (pid: number) => void;}) { const name = formatProcessName(process.command); const hasChildren = process.childPids.length > 0; const ChevronIcon = isExpanded ? ChevronDownIcon : ChevronRightIcon; return ( <div className="grid min-w-0 grid-cols-[1.25rem_0.375rem_minmax(0,1fr)] items-center gap-2" style={{ paddingLeft: `${Math.min(process.depth, 8) * 16}px` }} > {hasChildren ? ( <button type="button" className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" aria-label={isExpanded ? `Collapse ${name}` : `Expand ${name}`} onClick={() => onToggle(process.pid)} > <ChevronIcon className="size-3.5" /> </button> ) : ( <span className="size-5 shrink-0" aria-hidden="true" /> )} <span className="size-1.5 shrink-0 rounded-full bg-emerald-500/80" /> <span className="min-w-0 truncate font-medium text-foreground">{name}</span> </div> );} function ProcessSignalActions({ process, isSignaling, onSignal,}: { process: ServerProcessDiagnosticsEntry; isSignaling: boolean; onSignal: (pid: number, signal: ServerProcessSignal) => void;}) { return ( <div className="flex items-center justify-end gap-1.5"> <Tooltip> <TooltipTrigger render={ <button type="button" disabled={isSignaling} className="text-[11px] font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline disabled:pointer-events-none disabled:opacity-50" onClick={() => onSignal(process.pid, "SIGINT")} > INT </button> } /> <TooltipPopup side="top">Send SIGINT</TooltipPopup> </Tooltip> <Tooltip> <TooltipTrigger render={ <button type="button" disabled={isSignaling} className="text-[11px] font-medium text-destructive underline-offset-2 hover:underline disabled:pointer-events-none disabled:opacity-50" onClick={() => onSignal(process.pid, "SIGKILL")} > KILL </button> } /> <TooltipPopup side="top">Send SIGKILL</TooltipPopup> </Tooltip> </div> );} function ProcessDiagnosticsTable({ processes, signalingPid, onSignal, emptyLabel,}: { processes: ReadonlyArray<ServerProcessDiagnosticsEntry>; signalingPid: number | null; onSignal: (pid: number, signal: ServerProcessSignal) => void; emptyLabel?: string;}) { const [collapsedPids, setCollapsedPids] = useState<ReadonlySet<number>>(() => new Set()); const visibleProcesses = useMemo(() => { const visible: ServerProcessDiagnosticsEntry[] = []; let hiddenChildDepth: number | null = null; for (const process of processes) { if (hiddenChildDepth !== null) { if (process.depth > hiddenChildDepth) continue; hiddenChildDepth = null; } visible.push(process); if (collapsedPids.has(process.pid)) { hiddenChildDepth = process.depth; } } return visible; }, [collapsedPids, processes]); const toggleProcess = useCallback((pid: number) => { setCollapsedPids((previous) => { const next = new Set(previous); if (next.has(pid)) { next.delete(pid); } else { next.add(pid); } return next; }); }, []); return ( <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none border-t border-border/60" > <table className="w-full min-w-[1040px] table-fixed text-left text-xs"> <colgroup> <col className="w-[24%]" /> <col className="w-[8%]" /> <col className="w-[10%]" /> <col className="w-[33%]" /> <col className="w-[8%]" /> <col className="w-[11%]" /> <col className="w-[6%]" /> </colgroup> <thead className="border-b border-border/60 bg-muted/15 text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> <tr> <th className="px-4 py-2 font-semibold sm:pl-5">Name</th> <th className="px-3 py-2 text-right font-semibold">CPU</th> <th className="px-3 py-2 text-right font-semibold">Memory</th> <th className="px-3 py-2 font-semibold">Command</th> <th className="px-3 py-2 text-right font-semibold">PID</th> <th className="px-3 py-2 font-semibold">Type</th> <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th> </tr> </thead> <tbody className="divide-y divide-border/50"> {visibleProcesses.length === 0 ? ( <tr> <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> {emptyLabel ?? "No live descendant processes found."} </td> </tr> ) : null} {visibleProcesses.map((process) => ( <tr key={process.pid} className="hover:bg-muted/20"> <td className="px-4 py-2 align-middle sm:pl-5"> <ProcessNameCell process={process} isExpanded={!collapsedPids.has(process.pid)} onToggle={toggleProcess} /> </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> {process.cpuPercent.toFixed(1)}% </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> {formatBytes(process.rssBytes)} </td> <td className="px-3 py-2 align-middle text-muted-foreground"> <Tooltip> <TooltipTrigger render={<span className="block truncate">{process.command}</span>} /> <TooltipPopup side="top" className="max-w-[min(440px,calc(100vw-2rem))] whitespace-normal break-words text-left font-mono text-[11px] leading-relaxed text-wrap" > {process.command} </TooltipPopup> </Tooltip> </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground"> {process.pid} </td> <td className="truncate px-3 py-2 align-middle text-muted-foreground"> {formatProcessType(process)} </td> <td className="px-2 py-2 align-middle sm:pr-4"> <ProcessSignalActions process={process} isSignaling={signalingPid === process.pid} onSignal={onSignal} /> </td> </tr> ))} </tbody> </table>Lines 1–500 of 983. Load more
import { AlertTriangleIcon, ChevronDownIcon, ChevronRightIcon, CopyIcon, FolderOpenIcon, InfoIcon, RefreshCwIcon, } from "lucide-react"; import { useCallback, useMemo, useState, type ReactNode } from "react"; import type { ServerProcessDiagnosticsEntry, ServerProcessSignal } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import * as Option from "effect/Option"; import { ensureLocalApi } from "../../localApi"; import { cn } from "../../lib/utils"; import { resolveAndPersistPreferredEditor } from "../../editorPreferences"; import { formatRelativeTime } from "../../timestampFormat"; import { useServerAvailableEditors, useServerObservability } from "../../rpc/serverState"; import { useProcessDiagnostics } from "../../lib/processDiagnosticsState"; import { useTraceDiagnostics } from "../../lib/traceDiagnosticsState"; import { Button } from "../ui/button"; import { ScrollArea } from "../ui/scroll-area"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { toastManager } from "../ui/toast"; import { SettingsPageContainer, SettingsSection, useRelativeTimeTick } from "./settingsLayout"; const NUMBER_FORMAT = new Intl.NumberFormat(); function formatCount(value: number): string { return NUMBER_FORMAT.format(value); } function formatDuration(value: number): string { if (value < 1_000) return `${Math.round(value)} ms`; return `${(value / 1_000).toFixed(value >= 10_000 ? 1 : 2)} s`; } function formatBytes(value: number): string { if (value < 1024) return `${value} B`; const units = ["KB", "MB", "GB"] as const; let unitIndex = -1; let next = value; do { next /= 1024; unitIndex += 1; } while (next >= 1024 && unitIndex < units.length - 1); return `${next.toFixed(next >= 10 ? 1 : 2)} ${units[unitIndex]}`; } function formatRelative(value: DateTime.Utc | null): string { if (!value) return "No trace records"; const relative = formatRelativeTime(DateTime.formatIso(value)); return relative.suffix ? `${relative.value} ${relative.suffix}` : relative.value; } function formatRelativeNoWrap(value: DateTime.Utc | null): string { return formatRelative(value).replaceAll(" ", "\u00a0"); } function shortenTraceId(traceId: string): string { if (traceId.length <= 32) return traceId; return `${traceId.slice(0, 18)}...${traceId.slice(-10)}`; } function isStaleProcessSignalMessage(message: string | undefined): boolean { return message?.includes("not a live descendant") ?? false; } function StatBlock({ label, value, tooltip, tone = "default", }: { label: string; value: string; tooltip?: ReactNode; tone?: "default" | "warning" | "danger"; }) { return ( <div className="min-w-0 border-border/60 px-4 py-3 sm:px-5"> <div className="flex min-w-0 items-center gap-1.5 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground/70"> <span className="min-w-0 truncate">{label}</span> {tooltip ? ( <Tooltip> <TooltipTrigger render={ <button type="button" className="inline-flex size-3.5 shrink-0 items-center justify-center rounded-sm text-muted-foreground/60 hover:text-foreground" aria-label={`${label} details`} > <InfoIcon className="size-3" /> </button> } /> <TooltipPopup side="top" className="max-w-[min(300px,calc(100vw-2rem))] whitespace-normal text-left text-[11px] leading-relaxed text-wrap" > {tooltip} </TooltipPopup> </Tooltip> ) : null} </div> <div className={cn( "mt-1 truncate font-mono text-lg font-semibold tabular-nums text-foreground", tone === "warning" && "text-amber-600 dark:text-amber-400", tone === "danger" && "text-destructive", )} > {value} </div> </div> ); } function StatsGrid({ children }: { children: ReactNode }) { return ( <div className="relative grid grid-cols-2 sm:grid-cols-4"> <span className="pointer-events-none absolute inset-y-0 left-1/2 w-px bg-border/60" aria-hidden /> <span className="pointer-events-none absolute inset-x-0 top-1/2 h-px bg-border/60 sm:hidden" aria-hidden /> <span className="pointer-events-none absolute inset-y-0 left-1/4 hidden w-px bg-border/60 sm:block" aria-hidden /> <span className="pointer-events-none absolute inset-y-0 left-3/4 hidden w-px bg-border/60 sm:block" aria-hidden /> {children} </div> ); } function EmptyRows({ label }: { label: string }) { return <div className="px-4 py-4 text-xs text-muted-foreground sm:px-5">{label}</div>; } function ExpandableText({ text, className, collapsedClassName = "line-clamp-3", expandLabel = "Show full error", }: { text: string; className?: string; collapsedClassName?: string; expandLabel?: string; }) { const [expanded, setExpanded] = useState(false); const canExpand = text.length > 180 || text.includes("\n"); return ( <div className={cn("min-w-0", className)}> <div className={cn( "whitespace-pre-wrap break-words", !expanded && canExpand ? collapsedClassName : null, )} > {text} </div> {canExpand ? ( <button type="button" className="mt-1 text-[11px] font-medium text-foreground/70 underline-offset-2 hover:text-foreground hover:underline" onClick={() => setExpanded((value) => !value)} > {expanded ? "Show less" : expandLabel} </button> ) : null} </div> ); } function DiagnosticsTable({ headers, children, minTableWidth = "min-w-[640px]", columnWidths, }: { headers: ReadonlyArray<string>; children: ReactNode; minTableWidth?: string; columnWidths?: ReadonlyArray<string>; }) { return ( <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none" > <table className={cn("w-full text-left text-xs", minTableWidth, columnWidths && "table-fixed")} > {columnWidths ? ( <colgroup> {headers.map((header, index) => ( <col key={header} className={columnWidths[index]} /> ))} </colgroup> ) : null} <thead className="border-b border-border/60 text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> <tr> {headers.map((header, index) => ( <th key={header} className={cn( "whitespace-nowrap px-4 py-2.5 font-semibold first:sm:pl-5 last:sm:pr-5", !columnWidths && index === headers.length - 1 && "w-px", )} > {header.replaceAll(" ", "\u00a0")} </th> ))} </tr> </thead> <tbody className="divide-y divide-border/60">{children}</tbody> </table> </ScrollArea> ); } function TraceIdCell({ traceId }: { traceId: string }) { const [copied, setCopied] = useState(false); const copyTraceId = useCallback(() => { void navigator.clipboard ?.writeText(traceId) .then(() => { setCopied(true); window.setTimeout(() => setCopied(false), 1_200); }) .catch(() => undefined); }, [traceId]); return ( <div className="flex w-full min-w-0 max-w-full items-center gap-2"> <Tooltip> <TooltipTrigger render={ <span className="min-w-0 flex-1 truncate font-mono text-[11px]"> {shortenTraceId(traceId)} </span> } /> <TooltipPopup side="top" className="max-w-[min(520px,calc(100vw-2rem))] break-all font-mono text-[11px]" > {traceId} </TooltipPopup> </Tooltip> <Tooltip> <TooltipTrigger render={ <button type="button" className="inline-flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" aria-label={copied ? "Copied trace ID" : "Copy trace ID"} onClick={copyTraceId} > <CopyIcon className="size-3" /> </button> } /> <TooltipPopup side="top">{copied ? "Copied" : "Copy full trace ID"}</TooltipPopup> </Tooltip> </div> ); } function formatProcessName(command: string): string { const firstToken = command.trim().split(/\s+/)[0]; if (!firstToken) return command; const normalized = firstToken.replace(/^['"]|['"]$/g, ""); const segments = normalized.split(/[\\/]/).filter(Boolean); return segments.at(-1) ?? normalized; } function formatProcessType(process: ServerProcessDiagnosticsEntry): string { if (process.depth > 0) return "Subprocess"; if (/\b(codex|claude|opencode|cursor)\b/i.test(process.command)) return "Agent"; return "Process"; } function ProcessNameCell({ process, isExpanded, onToggle, }: { process: ServerProcessDiagnosticsEntry; isExpanded: boolean; onToggle: (pid: number) => void; }) { const name = formatProcessName(process.command); const hasChildren = process.childPids.length > 0; const ChevronIcon = isExpanded ? ChevronDownIcon : ChevronRightIcon; return ( <div className="grid min-w-0 grid-cols-[1.25rem_0.375rem_minmax(0,1fr)] items-center gap-2" style={{ paddingLeft: `${Math.min(process.depth, 8) * 16}px` }} > {hasChildren ? ( <button type="button" className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" aria-label={isExpanded ? `Collapse ${name}` : `Expand ${name}`} onClick={() => onToggle(process.pid)} > <ChevronIcon className="size-3.5" /> </button> ) : ( <span className="size-5 shrink-0" aria-hidden="true" /> )} <span className="size-1.5 shrink-0 rounded-full bg-emerald-500/80" /> <span className="min-w-0 truncate font-medium text-foreground">{name}</span> </div> ); } function ProcessSignalActions({ process, isSignaling, onSignal, }: { process: ServerProcessDiagnosticsEntry; isSignaling: boolean; onSignal: (pid: number, signal: ServerProcessSignal) => void; }) { return ( <div className="flex items-center justify-end gap-1.5"> <Tooltip> <TooltipTrigger render={ <button type="button" disabled={isSignaling} className="text-[11px] font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline disabled:pointer-events-none disabled:opacity-50" onClick={() => onSignal(process.pid, "SIGINT")} > INT </button> } /> <TooltipPopup side="top">Send SIGINT</TooltipPopup> </Tooltip> <Tooltip> <TooltipTrigger render={ <button type="button" disabled={isSignaling} className="text-[11px] font-medium text-destructive underline-offset-2 hover:underline disabled:pointer-events-none disabled:opacity-50" onClick={() => onSignal(process.pid, "SIGKILL")} > KILL </button> } /> <TooltipPopup side="top">Send SIGKILL</TooltipPopup> </Tooltip> </div> ); } function ProcessDiagnosticsTable({ processes, signalingPid, onSignal, emptyLabel, }: { processes: ReadonlyArray<ServerProcessDiagnosticsEntry>; signalingPid: number | null; onSignal: (pid: number, signal: ServerProcessSignal) => void; emptyLabel?: string; }) { const [collapsedPids, setCollapsedPids] = useState<ReadonlySet<number>>(() => new Set()); const visibleProcesses = useMemo(() => { const visible: ServerProcessDiagnosticsEntry[] = []; let hiddenChildDepth: number | null = null; for (const process of processes) { if (hiddenChildDepth !== null) { if (process.depth > hiddenChildDepth) continue; hiddenChildDepth = null; } visible.push(process); if (collapsedPids.has(process.pid)) { hiddenChildDepth = process.depth; } } return visible; }, [collapsedPids, processes]); const toggleProcess = useCallback((pid: number) => { setCollapsedPids((previous) => { const next = new Set(previous); if (next.has(pid)) { next.delete(pid); } else { next.add(pid); } return next; }); }, []); return ( <ScrollArea chainVerticalScroll scrollFade hideScrollbars className="w-full max-w-full rounded-none border-t border-border/60" > <table className="w-full min-w-[1040px] table-fixed text-left text-xs"> <colgroup> <col className="w-[24%]" /> <col className="w-[8%]" /> <col className="w-[10%]" /> <col className="w-[33%]" /> <col className="w-[8%]" /> <col className="w-[11%]" /> <col className="w-[6%]" /> </colgroup> <thead className="border-b border-border/60 bg-muted/15 text-[11px] uppercase tracking-[0.08em] text-muted-foreground/70"> <tr> <th className="px-4 py-2 font-semibold sm:pl-5">Name</th> <th className="px-3 py-2 text-right font-semibold">CPU</th> <th className="px-3 py-2 text-right font-semibold">Memory</th> <th className="px-3 py-2 font-semibold">Command</th> <th className="px-3 py-2 text-right font-semibold">PID</th> <th className="px-3 py-2 font-semibold">Type</th> <th className="px-2 py-2 text-right font-semibold sm:pr-4">Kill</th> </tr> </thead> <tbody className="divide-y divide-border/50"> {visibleProcesses.length === 0 ? ( <tr> <td colSpan={7} className="px-4 py-4 text-xs text-muted-foreground sm:px-5"> {emptyLabel ?? "No live descendant processes found."} </td> </tr> ) : null} {visibleProcesses.map((process) => ( <tr key={process.pid} className="hover:bg-muted/20"> <td className="px-4 py-2 align-middle sm:pl-5"> <ProcessNameCell process={process} isExpanded={!collapsedPids.has(process.pid)} onToggle={toggleProcess} /> </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> {process.cpuPercent.toFixed(1)}% </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums"> {formatBytes(process.rssBytes)} </td> <td className="px-3 py-2 align-middle text-muted-foreground"> <Tooltip> <TooltipTrigger render={<span className="block truncate">{process.command}</span>} /> <TooltipPopup side="top" className="max-w-[min(440px,calc(100vw-2rem))] whitespace-normal break-words text-left font-mono text-[11px] leading-relaxed text-wrap" > {process.command} </TooltipPopup> </Tooltip> </td> <td className="px-3 py-2 text-right align-middle font-mono tabular-nums text-muted-foreground"> {process.pid} </td> <td className="truncate px-3 py-2 align-middle text-muted-foreground"> {formatProcessType(process)} </td> <td className="px-2 py-2 align-middle sm:pr-4"> <ProcessSignalActions process={process} isSignaling={signalingPid === process.pid} onSignal={onSignal} /> </td> </tr> ))} </tbody> </table>