ns-ui
Prompt Version Grid
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.
Usage
registry/core/prompt-version-grain/demo.tsx"use client";
import { PromptVersionGrain } from "./component";
import type { PromptGrainBlock, PromptGrainVersion } from "./component";
// Twelve releases of one production support-triage system prompt, newest first.
// Static data, so the server render and the client hydration agree.
const BLOCKS: PromptGrainBlock[] = [
{ id: "system", label: "system" },
{ id: "persona", label: "persona" },
{ id: "tools", label: "tools" },
{ id: "rules", label: "rules" },
{ id: "examples", label: "examples" },
{ id: "format", label: "format" },
];
/** Row index (0 = newest, v37) -> blocks changed in that release. */
const CHANGED: string[][] = [
["tools"],
["tools", "rules", "examples"],
["persona", "tools"],
["tools", "rules"],
["tools", "rules"],
["tools"],
["tools", "rules"],
["persona", "tools"],
["rules"],
[],
["rules"],
[],
];
const TOKENS = [
1840, 1788, 1745, 1702, 1664, 1611, 1580, 1524, 1498, 1440, 1402, 1361,
];
const VERSIONS: PromptGrainVersion[] = CHANGED.map((ids, r) => ({
id: `v${37 - r}`,
label: `v${37 - r}`,
tokens: TOKENS[r]!,
changed: Object.fromEntries(BLOCKS.map((b) => [b.id, ids.includes(b.id)])),
}));
export default function PromptVersionGrainDemo() {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-8 px-6 py-16">
<p className="font-mono text-xs uppercase tracking-[0.2em] text-ns-muted">
ns-ui / prompt-version-grain
</p>
<div
data-grain-card=""
className="w-full max-w-lg rounded-[12px] border border-border bg-background px-7 pb-7 pt-6"
>
<div className="mb-5 flex items-baseline justify-between gap-8">
<h2 className="font-mono text-[11px] uppercase tracking-[0.18em] text-foreground">
support-triage / system prompt
</h2>
<span className="font-mono text-[10px] tabular-nums text-ns-muted">
12 releases · v26 → v37
</span>
</div>
<PromptVersionGrain
blocks={BLOCKS}
versions={VERSIONS}
ariaLabel="Prompt blocks changed in each of the last twelve releases"
/>
</div>
<p className="max-w-md text-center text-xs text-ns-muted">
Which prompt blocks changed in each of the last twelve releases.
</p>
</div>
);
}Component
registry/core/prompt-version-grain/component.tsx"use client";
// ---------------------------------------------------------------------------
// PromptVersionGrain — which prompt blocks changed in which release.
//
// A table: columns are prompt blocks, rows are versions (newest first). A cell
// is filled when that block changed in that release and blank when it did not.
// No hover, no animation, no decoding.
// ---------------------------------------------------------------------------
export interface PromptGrainBlock {
/** Stable key used to look the block up in each version's `changed` map. */
id: string;
/** Column header, e.g. "rules". */
label: string;
}
export interface PromptGrainVersion {
id: string;
/** Short version label, e.g. "v37". */
label: string;
/** Total prompt tokens for this version. */
tokens: number;
/** blockId -> did this block change in this release. */
changed: Record<string, boolean>;
}
export interface PromptVersionGrainProps {
/** Prompt blocks, left to right. */
blocks?: PromptGrainBlock[];
/** Versions, NEWEST FIRST. */
versions?: PromptGrainVersion[];
/** accessible name for the version list */
ariaLabel?: string;
/** extra classes merged onto the rendered root element */
className?: string;
}
const DEFAULT_BLOCKS: PromptGrainBlock[] = [
{ id: "system", label: "system" },
{ id: "persona", label: "persona" },
{ id: "tools", label: "tools" },
{ id: "rules", label: "rules" },
{ id: "examples", label: "examples" },
{ id: "format", label: "format" },
];
function Swatch() {
return (
<span aria-hidden="true" className="inline-block h-2.5 w-2.5 bg-foreground" />
);
}
export function PromptVersionGrain({
blocks = DEFAULT_BLOCKS,
versions = [],
ariaLabel = "Prompt blocks changed per release",
className = "",
}: PromptVersionGrainProps) {
const head =
"font-mono text-[10px] font-normal leading-none tracking-tight text-ns-muted";
return (
<div className={className}>
<table className="w-full border-collapse">
<caption className="sr-only">{ariaLabel}</caption>
<thead>
<tr>
<th scope="col" className={`${head} pb-3 pr-4 text-left`}>
version
</th>
{blocks.map((b) => (
<th key={b.id} scope="col" className={`${head} px-3 pb-3 text-center`}>
{b.label}
</th>
))}
<th scope="col" className={`${head} pb-3 pl-4 text-right`}>
tokens
</th>
</tr>
</thead>
<tbody>
{versions.map((v) => (
<tr key={v.id}>
<th
scope="row"
className="border-t border-border py-2 pr-4 text-left font-mono text-[11px] font-normal tabular-nums text-foreground"
>
{v.label}
</th>
{blocks.map((b) => (
<td key={b.id} className="border-t border-border px-3 py-2 text-center">
{v.changed[b.id] ? <Swatch /> : null}
<span className="sr-only">
{v.changed[b.id] ? "changed" : "unchanged"}
</span>
</td>
))}
<td className="border-t border-border py-2 pl-4 text-right font-mono text-[11px] tabular-nums text-ns-muted">
{v.tokens}
</td>
</tr>
))}
</tbody>
</table>
<p className="mt-4 flex items-center gap-2 font-mono text-[10px] text-ns-muted">
<Swatch />
changed in this release
</p>
</div>
);
}
export default PromptVersionGrain;The usage example imports ./component because it sits next to the component file in this repo. Installed, that file lands at components/ui/prompt-version-grain.tsx, so point the import there.
Use when the panel that answers 'which parts of this prompt changed in which release' across a whole version history at once. Pick diff-unified-viewer instead when the user needs the actual line-level text change between two specific versions, approval-inline-diff when a proposed edit needs accepting or rejecting, and context-prompt-shims when the user is composing what goes into the prompt rather than auditing what already happened to it.
Install
npx shadcn add https://design.helpmarq.com /r/prompt-version-grain.jsonBuild spec
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.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| blocks? | PromptGrainBlock[] | DEFAULT_BLOCKS | Prompt blocks, left to right. |
| versions? | PromptGrainVersion[] | [] | Versions, NEWEST FIRST. |
| ariaLabel? | string | "Prompt blocks changed per release" | accessible name for the version list |
| className? | string | — | extra classes merged onto the rendered root element |