import { useEffect, useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { api, runReportExportUrl, type HarnessGateNode, type HarnessGateStatus, type HarnessTraceGraph } from '../../lib/api'; import { Card, StatusBadge } from '../ui/Card'; const NODE_TONE: Record = { queued: 'border-slate-200 bg-slate-50 text-slate-500', running: 'border-blue-300 bg-blue-50 text-blue-800 shadow-[0_0_0_4px_rgba(59,130,246,0.1)]', pass: 'border-emerald-300 bg-emerald-50 text-emerald-800', warning: 'border-amber-300 bg-amber-50 text-amber-800', blocked: 'border-rose-400 bg-rose-50 text-rose-800 shadow-[0_0_0_4px_rgba(244,63,94,0.09)]', error: 'border-rose-400 bg-rose-50 text-rose-800 shadow-[0_0_0_4px_rgba(244,63,94,0.09)]', skipped: 'border-slate-200 bg-slate-100 text-slate-400', }; const STATUS_MARK: Record = { queued: '○', running: '●', pass: '✓', warning: '!', blocked: '×', error: '×', skipped: '–', }; function Evidence({ node }: { node: HarnessGateNode }) { const entries = Object.entries(node.evidence); return (

{node.title}

{node.description}

Latest decision

{node.reason}

{node.updated_at &&

{node.updated_at.replace('T', ' ').replace('Z', '')}

}
Safe evidence
{entries.length > 0 ? (
{entries.map(([key, value]) => (
{key}
{typeof value === 'string' ? value : JSON.stringify(value)}
))}
) :

No evidence has been emitted for this gate yet.

}
); } export function TraceExplorer({ traceId, onClose }: { traceId: string; onClose?: () => void }) { const query = useQuery({ queryKey: ['trace-graph', traceId], queryFn: () => api.traceGraph(traceId), enabled: Boolean(traceId), }); const [graph, setGraph] = useState(null); const [selectedId, setSelectedId] = useState('H1-context'); useEffect(() => { if (query.data) setGraph(query.data); }, [query.data]); useEffect(() => { if (!traceId) return undefined; return api.watchTrace(traceId, setGraph); }, [traceId]); const selected = useMemo( () => graph?.nodes.find((node) => node.id === selectedId) ?? graph?.nodes[0] ?? null, [graph, selectedId], ); return ( Open H6 Export evidence {onClose && } } className="overflow-hidden" >

Trace ID

{traceId}

{graph?.terminal ? 'Completed' : 'Live · waiting for events'} · {graph?.progress ?? 0}/7 gates
{query.isLoading && !graph ?
Loading trace…
: ( <>
{graph?.nodes.map((node, index) => { const next = graph.nodes[index + 1]; const edgeActive = node.status === 'pass' || node.status === 'running' || node.status === 'warning' || next?.status === 'running'; return (
{next && ); })}
{selected && } {!graph?.found &&
This is a legacy or not-yet-started trace. New chat turns emit detailed H1–H7 events automatically.
} )} ); }