{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "prompt-version-grain",
  "title": "Prompt Version Grid",
  "description": "A table of prompt blocks against releases: one filled cell wherever that block changed in that release, blank where it did not, plus each version's total token count.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/prompt-version-grain/component.tsx",
      "content": "\"use client\";\n\n// ---------------------------------------------------------------------------\n// PromptVersionGrain — which prompt blocks changed in which release.\n//\n// A table: columns are prompt blocks, rows are versions (newest first). A cell\n// is filled when that block changed in that release and blank when it did not.\n// No hover, no animation, no decoding.\n// ---------------------------------------------------------------------------\n\nexport interface PromptGrainBlock {\n  /** Stable key used to look the block up in each version's `changed` map. */\n  id: string;\n  /** Column header, e.g. \"rules\". */\n  label: string;\n}\n\nexport interface PromptGrainVersion {\n  id: string;\n  /** Short version label, e.g. \"v37\". */\n  label: string;\n  /** Total prompt tokens for this version. */\n  tokens: number;\n  /** blockId -> did this block change in this release. */\n  changed: Record<string, boolean>;\n}\n\nexport interface PromptVersionGrainProps {\n  /** Prompt blocks, left to right. */\n  blocks?: PromptGrainBlock[];\n  /** Versions, NEWEST FIRST. */\n  versions?: PromptGrainVersion[];\n  /** accessible name for the version list */\n  ariaLabel?: string;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}\n\nconst DEFAULT_BLOCKS: PromptGrainBlock[] = [\n  { id: \"system\", label: \"system\" },\n  { id: \"persona\", label: \"persona\" },\n  { id: \"tools\", label: \"tools\" },\n  { id: \"rules\", label: \"rules\" },\n  { id: \"examples\", label: \"examples\" },\n  { id: \"format\", label: \"format\" },\n];\n\nfunction Swatch() {\n  return (\n    <span aria-hidden=\"true\" className=\"inline-block h-2.5 w-2.5 bg-foreground\" />\n  );\n}\n\nexport function PromptVersionGrain({\n  blocks = DEFAULT_BLOCKS,\n  versions = [],\n  ariaLabel = \"Prompt blocks changed per release\",\n  className = \"\",\n}: PromptVersionGrainProps) {\n  const head =\n    \"font-mono text-[10px] font-normal leading-none tracking-tight text-ns-muted\";\n\n  return (\n    <div className={className}>\n      <table className=\"w-full border-collapse\">\n        <caption className=\"sr-only\">{ariaLabel}</caption>\n        <thead>\n          <tr>\n            <th scope=\"col\" className={`${head} pb-3 pr-4 text-left`}>\n              version\n            </th>\n            {blocks.map((b) => (\n              <th key={b.id} scope=\"col\" className={`${head} px-3 pb-3 text-center`}>\n                {b.label}\n              </th>\n            ))}\n            <th scope=\"col\" className={`${head} pb-3 pl-4 text-right`}>\n              tokens\n            </th>\n          </tr>\n        </thead>\n        <tbody>\n          {versions.map((v) => (\n            <tr key={v.id}>\n              <th\n                scope=\"row\"\n                className=\"border-t border-border py-2 pr-4 text-left font-mono text-[11px] font-normal tabular-nums text-foreground\"\n              >\n                {v.label}\n              </th>\n              {blocks.map((b) => (\n                <td key={b.id} className=\"border-t border-border px-3 py-2 text-center\">\n                  {v.changed[b.id] ? <Swatch /> : null}\n                  <span className=\"sr-only\">\n                    {v.changed[b.id] ? \"changed\" : \"unchanged\"}\n                  </span>\n                </td>\n              ))}\n              <td className=\"border-t border-border py-2 pl-4 text-right font-mono text-[11px] tabular-nums text-ns-muted\">\n                {v.tokens}\n              </td>\n            </tr>\n          ))}\n        </tbody>\n      </table>\n\n      <p className=\"mt-4 flex items-center gap-2 font-mono text-[10px] text-ns-muted\">\n        <Swatch />\n        changed in this release\n      </p>\n    </div>\n  );\n}\n\nexport default PromptVersionGrain;\n",
      "type": "registry:ui",
      "target": "components/ui/prompt-version-grain.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-ns-muted": "var(--ns-muted)"
    },
    "light": {
      "ns-muted": "#4d4d4d"
    },
    "dark": {
      "ns-muted": "#8f8f8f"
    }
  },
  "meta": {
    "collection": "core",
    "tags": [
      "agent",
      "llm",
      "prompt",
      "versioning",
      "history",
      "table",
      "data-viz",
      "mono",
      "dashboard"
    ],
    "instruction": "PromptVersionGrain renders a plain HTML table. Columns are prompt BLOCKS (`blocks: {id,label}[]` — system, persona, tools, rules, examples, format) as `<th scope=\"col\">`, rows are VERSIONS newest at the top (`versions: {id,label,tokens,changed}[]`) with the version label as `<th scope=\"row\">`. `changed` maps blockId to a boolean: true draws a 10px `--foreground` square in that cell, false leaves the cell blank. Every cell also carries a visually-hidden 'changed' / 'unchanged' word, so a screen reader gets the same facts as the swatch and nothing is conveyed by geometry alone. A trailing right-aligned column gives each version's total token count in Geist Mono tabular-nums. Rows are separated by a 1px `--border` hairline. A one-line legend under the table shows the same square next to the words 'changed in this release'. There is no hover state, no focusable element, no animation, no canvas, no SVG, no dependency and no hex literal — colours are only `--foreground`, `--ns-muted` and `--border`, so both themes come for free and `prefers-reduced-motion` is moot. Props: `blocks`, `versions`, `ariaLabel` (becomes the table's visually-hidden `<caption>`), `className`. The component is fully controlled and holds no state. Demo: twelve releases of a support-triage system prompt where `tools` changed in eight releases, `rules` in six, `persona` in two, `examples` in one, and `system` and `format` were never touched."
  },
  "type": "registry:ui"
}