← snapshot
2260 bytes
import { layoutGraph } from "@/lib/graph";
import type { SnapshotDto } from "@/lib/types";
const ROW_H = 45.5; // matches .jac-row height (padding + line)
const LANE_W = 14;
const R = 5;
const PROV_COLOR: Record<string, string> = {
human: "var(--jac-human)",
agent: "var(--jac-agent)",
mixed: "url(#jac-mixed)",
};
/**
* The provenance rail: a hand-rolled SVG DAG whose rows align with the
* adjacent snapshot list. Nodes are filled with their provenance color, so
* the graph doubles as a timeline of whose hands wove each stretch.
*/
export function SnapshotGraph({ entries }: { entries: SnapshotDto[] }) {
const { nodes, byId, laneCount } = layoutGraph(entries);
const width = 16 + laneCount * LANE_W;
const height = entries.length * ROW_H;
const cx = (lane: number) => 10 + lane * LANE_W;
const cy = (row: number) => row * ROW_H + ROW_H / 2;
return (
<svg
className="jac-graph-svg"
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
aria-hidden="true"
>
<defs>
<linearGradient id="jac-mixed" x1="0" y1="0" x2="1" y2="1">
<stop offset="50%" stopColor="var(--jac-human)" />
<stop offset="50%" stopColor="var(--jac-agent)" />
</linearGradient>
</defs>
{nodes.map((node) =>
node.parents.map((parentId) => {
const parent = byId.get(parentId);
if (!parent) return null;
const x1 = cx(node.lane);
const y1 = cy(node.row);
const x2 = cx(parent.lane);
const y2 = cy(parent.row);
const d =
x1 === x2
? `M ${x1} ${y1} L ${x2} ${y2}`
: `M ${x1} ${y1} C ${x1} ${y1 + ROW_H * 0.8}, ${x2} ${y2 - ROW_H * 0.8}, ${x2} ${y2}`;
return (
<path
key={`${node.id}-${parentId}`}
d={d}
fill="none"
stroke="var(--jac-line)"
strokeWidth="1.5"
/>
);
}),
)}
{nodes.map((node) => (
<circle
key={node.id}
cx={cx(node.lane)}
cy={cy(node.row)}
r={R}
fill={PROV_COLOR[node.provenance] ?? "var(--jac-muted)"}
/>
))}
</svg>
);
}