{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "eval-regression-shear",
  "title": "Eval Delta Table",
  "description": "Two eval runs compared case by case: a table of baseline, candidate and signed delta, sorted by delta, with a diverging bar per case drawn off a labelled zero axis.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/eval-regression-shear/component.tsx",
      "content": "\"use client\";\n\n// ---------------------------------------------------------------------------\n// EvalDeltaTable — two eval runs compared case by case.\n//\n// One table row per case, sorted by delta descending, with a diverging bar\n// drawn off a labelled zero line: bars to the right of zero are gains, bars to\n// the left are regressions. Both scores and the signed delta are printed next\n// to every bar, so the table reads without the chart and the chart reads\n// without the table.\n// ---------------------------------------------------------------------------\n\nimport { useMemo } from \"react\";\n\nexport interface EvalCase {\n  /** Short, stable case identifier. */\n  id: string;\n  /** Score on the baseline run, 0..1. */\n  baseline: number;\n  /** Score on the candidate run, 0..1. */\n  candidate: number;\n}\n\nexport interface EvalDeltaTableProps {\n  /** The two runs, one entry per eval case. */\n  cases: EvalCase[];\n  /** |delta| below this counts as unchanged. Default 0.005. */\n  tieEps?: number;\n  /** Half-width of the delta axis. Defaults to the largest |delta|, rounded up to 0.05. */\n  scale?: number;\n  /** Accessible caption for the table. */\n  label?: string;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\nfunction signed(n: number, digits = 3): string {\n  const s = Math.abs(n).toFixed(digits);\n  return n > 0 ? `+${s}` : n < 0 ? `−${s}` : s;\n}\n\nexport function EvalDeltaTable({\n  cases,\n  tieEps = 0.005,\n  scale,\n  label = \"Eval cases, sorted by score delta\",\n  className,\n}: EvalDeltaTableProps) {\n  const { rows, up, down, tie, max } = useMemo(() => {\n    const rows = cases\n      .map((c) => ({ ...c, delta: c.candidate - c.baseline }))\n      .sort((a, b) => b.delta - a.delta);\n    const peak = rows.reduce((m, r) => Math.max(m, Math.abs(r.delta)), 0);\n    return {\n      rows,\n      up: rows.filter((r) => r.delta >= tieEps).length,\n      down: rows.filter((r) => r.delta <= -tieEps).length,\n      tie: rows.filter((r) => Math.abs(r.delta) < tieEps).length,\n      max: scale ?? Math.max(0.05, Math.ceil(peak / 0.05) * 0.05),\n    };\n  }, [cases, tieEps, scale]);\n\n  return (\n    <div className={[\"w-full font-sans text-foreground\", className].filter(Boolean).join(\" \")}>\n      <p className=\"mb-3 font-mono text-[11px] tabular-nums text-ns-muted\">\n        {rows.length} cases · {up} improved · {down} regressed · {tie} unchanged\n      </p>\n\n      <table className=\"w-full table-fixed border-collapse\">\n        <caption className=\"sr-only\">{label}</caption>\n        <colgroup>\n          <col />\n          <col style={{ width: 52 }} />\n          <col style={{ width: 52 }} />\n          <col style={{ width: \"42%\" }} />\n          <col style={{ width: 64 }} />\n        </colgroup>\n        <thead>\n          <tr className=\"border-b border-border\">\n            <th scope=\"col\" className=\"pb-2 text-left font-mono text-[10px] font-normal uppercase tracking-[0.16em] text-ns-muted\">\n              Case\n            </th>\n            <th scope=\"col\" className=\"pb-2 text-right font-mono text-[10px] font-normal uppercase tracking-[0.16em] text-ns-muted\">\n              Base\n            </th>\n            <th scope=\"col\" className=\"pb-2 pr-4 text-right font-mono text-[10px] font-normal uppercase tracking-[0.16em] text-ns-muted\">\n              Cand\n            </th>\n            <th scope=\"col\" className=\"pb-2 text-left font-mono text-[10px] font-normal uppercase tracking-[0.16em] text-ns-muted\">\n              Δ score\n            </th>\n            <th scope=\"col\" className=\"pb-2 text-right font-mono text-[10px] font-normal uppercase tracking-[0.16em] text-ns-muted\">\n              Δ\n            </th>\n          </tr>\n        </thead>\n        <tbody>\n          {rows.map((r) => {\n            const pct = Math.min(1, Math.abs(r.delta) / max) * 50;\n            return (\n              <tr key={r.id}>\n                <td className=\"py-[3px] pr-3\">\n                  <span className=\"block truncate font-mono text-[11px] text-foreground\">{r.id}</span>\n                </td>\n                <td className=\"py-[3px] text-right font-mono text-[11px] tabular-nums text-ns-muted\">\n                  {r.baseline.toFixed(2)}\n                </td>\n                <td className=\"py-[3px] pr-4 text-right font-mono text-[11px] tabular-nums text-ns-muted\">\n                  {r.candidate.toFixed(2)}\n                </td>\n                <td className=\"relative py-[3px]\">\n                  <span aria-hidden className=\"absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border\" />\n                  {Math.abs(r.delta) < tieEps ? null : (\n                    <span\n                      aria-hidden\n                      className=\"absolute top-1/2 block h-[7px] -translate-y-1/2 bg-foreground/70\"\n                      style={\n                        r.delta > 0\n                          ? { left: \"50%\", width: `${pct}%`, minWidth: 1 }\n                          : { left: `${50 - pct}%`, width: `${pct}%`, minWidth: 1 }\n                      }\n                    />\n                  )}\n                </td>\n                <td className=\"py-[3px] text-right font-mono text-[11px] tabular-nums text-foreground\">\n                  {signed(r.delta)}\n                </td>\n              </tr>\n            );\n          })}\n        </tbody>\n        <tfoot>\n          <tr>\n            <td colSpan={3} className=\"pr-4 pt-2 align-top font-mono text-[10px] uppercase tracking-[0.16em] text-ns-muted\">\n              Δ score · candidate − baseline\n            </td>\n            <td className=\"relative pt-2\">\n              <span className=\"block h-px w-full bg-border\" />\n              <span className=\"mt-1 flex justify-between font-mono text-[10px] tabular-nums text-ns-muted\">\n                <span>{\"−\"}{max.toFixed(2)}</span>\n                <span>0</span>\n                <span>+{max.toFixed(2)}</span>\n              </span>\n            </td>\n            <td />\n          </tr>\n        </tfoot>\n      </table>\n    </div>\n  );\n}\n\nexport default EvalDeltaTable;\n",
      "type": "registry:ui",
      "target": "components/ui/eval-regression-shear.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-ns-muted": "var(--ns-muted)"
    },
    "light": {
      "ns-muted": "#4d4d4d"
    },
    "dark": {
      "ns-muted": "#8f8f8f"
    }
  },
  "meta": {
    "collection": "core",
    "tags": [
      "eval",
      "regression",
      "benchmark",
      "data-viz",
      "table",
      "diverging-bar",
      "mono",
      "dashboard",
      "comparison",
      "accessibility"
    ],
    "instruction": "Build <EvalDeltaTable cases tieEps? scale? label? className?> as a per-case comparison of two eval runs. DATA: `cases` is {id, baseline, candidate}[] with scores in 0..1; delta = candidate − baseline; rows are sorted by delta descending so gains sit at the top and regressions at the bottom. MARKUP: a real <table> with a visually hidden <caption> (the `label` prop) and five columns — Case (mono id), Base, Cand, 'Δ score' (the bar track), Δ (the signed delta, three decimals, using a real minus sign). Widths come from a <colgroup> with table-fixed so the bar track and the axis under it line up exactly; the id cell truncates inside a block span. CHART: the track cell is position:relative with a 1px --border zero rule at its centre spanning the full row height, so consecutive rows form one continuous zero line. Each case draws a single 7px-tall bar in --foreground at 70% alpha: positive deltas start at the centre and run right, negative deltas end at the centre and run left, width = |delta| / scale * 50% with a 1px minimum. Sign is encoded by side of the zero line only, never by colour. `scale` is the axis half-width and defaults to the largest |delta| in the data rounded up to 0.05; |delta| below `tieEps` (default 0.005) draws no bar at all and just reads as the printed 0.000. AXIS: a <tfoot> row prints the caption 'Δ score · candidate − baseline' beside a 1px --border rule under the track with −scale / 0 / +scale mono tick labels. HEADER: a mono line above the table reading 'n cases · n improved · n regressed · n unchanged'. No interaction, no animation, no canvas, no dependencies — the whole component is static markup that themes from tokens alone, and every number a bar encodes is also printed in the row so the table is readable by a screen reader without any chart-specific description."
  },
  "type": "registry:ui"
}