jacquardSnapshot

← 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>
  );
}