import { memo, useCallback } from 'react'; import type { WireEntry } from '../../types'; import { formatDuration, formatWallClock } from '../../util/time'; import { TypeBadge } from './TypeBadge'; import { renderHeadline } from './WireHeadline'; import { WireRowDetail } from './WireRowDetail'; /** Pairing hint for a `tool.call` ↔ `tool.result` row. Computed by the * parent (WireTab) from the full record list and threaded down here so * the row can render an inline cross-reference and participate in the * hover-highlight protocol. */ export interface PairHint { toolCallId: string; kind: 'call' | 'result'; callLineNo: number | null; resultLineNo: number | null; /** result.time − call.time, when both records carry a timestamp. */ durationMs: number | null; } interface WireRowProps { entry: WireEntry; expanded: boolean; onToggle: () => void; /** Scroll to a line and expand it — wired by the Wire tab via the virtualizer. */ onJumpTo?: (lineNo: number) => void; /** Set when this entry is a tool.call/tool.result; carries the matching counterpart's line. */ pair?: PairHint; /** True when another row from this pair is currently hovered. */ highlighted: boolean; /** Notify the parent that this row's pair group is being hovered. */ onHoverPair?: (toolCallId: string | null) => void; } export const WireRow = memo(function WireRow({ entry, expanded, onToggle, onJumpTo, pair, highlighted, onHoverPair, }: WireRowProps) { const record = entry.data; const h = renderHeadline(record); const timeTitle = formatTimeTitle(record.time); const handleEnter = useCallback(() => { if (pair !== undefined && onHoverPair !== undefined) { onHoverPair(pair.toolCallId); } }, [pair, onHoverPair]); const handleLeave = useCallback(() => { if (pair !== undefined && onHoverPair !== undefined) { onHoverPair(null); } }, [pair, onHoverPair]); return (
{expanded ? (
) : null}
); }); function PairIndicator({ pair, onJumpTo, }: { pair: PairHint; onJumpTo?: (lineNo: number) => void; }) { const isCall = pair.kind === 'call'; const target = isCall ? pair.resultLineNo : pair.callLineNo; const arrow = isCall ? '→' : '←'; const orphan = target === null; const label = orphan ? `${arrow} ?` : `${arrow} #${target}`; const title = orphan ? isCall ? 'no matching tool.result yet' : 'no preceding tool.call seen' : isCall ? `jump to tool.result on line ${target}` : `jump to tool.call on line ${target}`; const className = `font-mono text-[10px] tabular ${ orphan ? 'text-[var(--color-sev-error)]' : 'text-[var(--color-cat-tools)] hover:text-fg-0' }`; // Show the call→result elapsed time on whichever row has its partner. const duration = pair.durationMs !== null ? ( {formatDuration(pair.durationMs)} ) : null; if (orphan || target === null || onJumpTo === undefined) { return ( {duration} {label} ); } return ( {duration} { e.stopPropagation(); onJumpTo(target); }} onKeyDown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); onJumpTo(target); } }} > {label} ); } function formatTimeTitle(epochMs: number | undefined): string { if (epochMs === undefined || !Number.isFinite(epochMs)) return 'missing time'; const date = new Date(epochMs); if (!Number.isFinite(date.getTime())) return 'invalid time'; return date.toISOString(); } function Chevron({ open }: { open: boolean }) { return ( ); }