{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-builder",
  "title": "Grid Builder",
  "description": "Ridiculously typed CSS grid-template editor for track lists and grid-template-areas across three modes, with TrackListLiteral validating the full track grammar and GridAreasLiteral checking quoting, equal column counts, and cell idents at the type level. Ships a live display:grid preview and a clickable areas painter.",
  "dependencies": [],
  "registryDependencies": [
    "ridiculous-type-kit",
    "unit-input",
    "button",
    "popover",
    "input",
    "label"
  ],
  "files": [
    {
      "path": "src/components/ui/grid-builder/index.ts",
      "content": "export type {\n  AreasEditorProps,\n  AreasPainterProps,\n  GridBuilderPanelProps,\n  GridBuilderProps,\n  GridPreviewProps,\n  TrackListEditorProps,\n  TrackTokenRowProps,\n} from \"./grid-builder\"\nexport {\n  AreasEditor,\n  AreasPainter,\n  GridBuilder,\n  GridBuilderPanel,\n  GridPreview,\n  TrackListEditor,\n  TrackTokenRow,\n} from \"./grid-builder\"\nexport type {\n  ParseAreasOptions,\n  TrackToken,\n} from \"./grid-builder.helpers\"\nexport {\n  areaNames,\n  defaultTrack,\n  formatAreas,\n  formatTracks,\n  gridAreaFor,\n  parseAreas,\n  parseTracks,\n  validateAreasRectangles,\n} from \"./grid-builder.helpers\"\nexport type {\n  AreaColumnCountOf,\n  AreaRowCountOf,\n  Dimension,\n  GridAreasLiteral,\n  GridAreasString,\n  GridMode,\n  GridTemplateState,\n  GridTemplateString,\n  GridTemplateStringMap,\n  GridTrackSize,\n  TrackCountOf,\n  TrackListLiteral,\n  TrackListString,\n  TracksOf,\n} from \"./grid-builder.types\"\nexport { cssGridAreas, cssTracks } from \"./grid-builder.types\"\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/index.ts"
    },
    {
      "path": "src/components/ui/grid-builder/grid-builder.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useMemo, useState } from \"react\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { cn } from \"@/lib/utils\"\nimport { AreasEditor } from \"./areas-editor\"\nimport { MODES } from \"./grid-builder.constants\"\nimport {\n  formatAreas,\n  formatTracks,\n  parseAreas,\n  parseTracks,\n  type TrackToken,\n} from \"./grid-builder.helpers\"\nimport type { GridMode, GridTemplateString } from \"./grid-builder.types\"\nimport { GridPreview } from \"./grid-preview\"\nimport { TrackListEditor } from \"./track-list-editor\"\n\nexport type { AreasEditorProps, AreasPainterProps } from \"./areas-editor\"\n// Re-export the sub-component public API so the entry module remains the\n// single import surface (`./grid-builder`) for consumers + tests.\nexport { AreasEditor, AreasPainter } from \"./areas-editor\"\nexport type { GridPreviewProps } from \"./grid-preview\"\nexport { GridPreview } from \"./grid-preview\"\nexport type {\n  TrackListEditorProps,\n  TrackTokenRowProps,\n} from \"./track-list-editor\"\nexport { TrackListEditor, TrackTokenRow } from \"./track-list-editor\"\n\n// ---------------------------------------------------------------------------\n// Shared props\n// ---------------------------------------------------------------------------\n\nexport interface GridBuilderPanelProps {\n  value: GridTemplateString | (string & {})\n  onChange: (value: GridTemplateString) => void\n  /**\n   * Which template property the editor targets and the live preview renders.\n   * `columns`/`rows` share the track-list grammar; `areas` is the painter.\n   * This selects the active tab + preview target — it does not change\n   * validation. Defaults to `\"columns\"`.\n   */\n  mode?: GridMode\n  className?: string\n  \"aria-label\"?: string\n}\n\nexport type GridBuilderProps = GridBuilderPanelProps\n\n// ---------------------------------------------------------------------------\n// GridBuilder — popover-wrapped\n// ---------------------------------------------------------------------------\n\nexport function GridBuilder(props: GridBuilderProps) {\n  const {\n    value,\n    className,\n    mode = \"columns\",\n    \"aria-label\": ariaLabel = \"Edit a CSS grid template\",\n  } = props\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          className={cn(\"h-9 gap-2 px-3 font-mono\", className)}\n          aria-label={ariaLabel}\n        >\n          <span aria-hidden=\"true\" className=\"text-foreground/60\">\n            ▦\n          </span>\n          <span className=\"text-[10px] text-muted-foreground\">{mode}</span>\n          <span className=\"max-w-[200px] truncate text-xs\">{value}</span>\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-0\" align=\"start\">\n        <GridBuilderPanel {...props} />\n      </PopoverContent>\n    </Popover>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// GridBuilderPanel — inline\n// ---------------------------------------------------------------------------\n\nexport function GridBuilderPanel({\n  value,\n  onChange,\n  mode: modeProp = \"columns\",\n  className,\n  \"aria-label\": ariaLabel = \"CSS grid template builder\",\n}: GridBuilderPanelProps) {\n  const [mode, setMode] = useState<GridMode>(modeProp)\n  useEffect(() => setMode(modeProp), [modeProp])\n\n  const str = String(value)\n\n  // Derive the editable model from the incoming value, by mode.\n  const tokens = useMemo<TrackToken[]>(\n    () => (mode === \"areas\" ? [] : (parseTracks(str) ?? [])),\n    [str, mode],\n  )\n  const matrix = useMemo<string[][]>(\n    () => (mode === \"areas\" ? (parseAreas(str) ?? []) : []),\n    [str, mode],\n  )\n\n  const commitTracks = (next: TrackToken[]) => {\n    const out = formatTracks(next)\n    onChange(out as GridTemplateString)\n  }\n  const commitAreas = (next: string[][]) => {\n    const out = formatAreas(next)\n    onChange(out as GridTemplateString)\n  }\n\n  return (\n    <fieldset\n      className={cn(\n        \"m-0 w-[520px] space-y-3 border-0 bg-background p-3\",\n        className,\n      )}\n      aria-label={ariaLabel}\n    >\n      <div\n        role=\"tablist\"\n        aria-label=\"Grid template mode\"\n        className=\"flex gap-1\"\n      >\n        {MODES.map((m) => (\n          <button\n            key={m.id}\n            type=\"button\"\n            role=\"tab\"\n            aria-selected={mode === m.id}\n            onClick={() => setMode(m.id)}\n            className={cn(\n              \"flex-1 rounded-md border px-2 py-1.5 font-mono text-xs\",\n              mode === m.id\n                ? \"border-primary bg-primary text-primary-foreground\"\n                : \"border-input bg-background text-muted-foreground hover:bg-muted/50\",\n            )}\n          >\n            {m.label}\n          </button>\n        ))}\n      </div>\n\n      {mode === \"areas\" ? (\n        <AreasEditor matrix={matrix} onChange={commitAreas} />\n      ) : (\n        <TrackListEditor tokens={tokens} onChange={commitTracks} />\n      )}\n\n      <LiveString\n        value={mode === \"areas\" ? formatAreas(matrix) : formatTracks(tokens)}\n      />\n      <GridPreview\n        mode={mode}\n        columns={mode === \"rows\" ? \"none\" : formatTracks(tokens)}\n        rows={mode === \"rows\" ? formatTracks(tokens) : \"none\"}\n        areas={formatAreas(matrix)}\n      />\n    </fieldset>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// LiveString (internal)\n// ---------------------------------------------------------------------------\n\nfunction LiveString({ value }: { value: string }) {\n  return (\n    <code className=\"block overflow-x-auto rounded bg-muted/50 px-2 py-1.5 font-mono text-foreground text-xs\">\n      {value}\n    </code>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/grid-builder.tsx"
    },
    {
      "path": "src/components/ui/grid-builder/grid-builder.types.ts",
      "content": "// =====================================================================\n// grid-builder.types.ts\n//\n// The \"ridiculous\" tier: two compile-time validators for CSS grid\n// templates, built on `ridiculous-type-kit`.\n//\n//   1. TrackListLiteral<S> — `grid-template-columns` / `grid-template-rows`.\n//      A space-separated track list: <length>/<percentage>/<flex>,\n//      keywords (auto/min-content/max-content), minmax(), fit-content(),\n//      repeat(), and [named-line] brackets. FULL validation.\n//\n//   2. GridAreasLiteral<S> — `grid-template-areas`. A sequence of quoted\n//      row strings. Type-level: every row has an EQUAL cell count and each\n//      cell is a valid <ident> or a null cell (a run of dots). The\n//      contiguous-RECTANGLE invariant is PUNTED to runtime (see JSDoc +\n//      spec §7) — it is borderline-undecidable as a template-literal type.\n//\n//   \"minmax(100px, 1fr)\"        →  the literal\n//   \"minmax(1fr, 2fr)\"          →  never (an fr is not an inflexible min)\n//   \"repeat(auto-fill, 1fr)\"    →  the literal\n//   '\"a a\" \"b b\"'               →  the literal\n//   '\"a a\" \"b\"'                 →  never (unequal columns)\n//\n// REUSES the function-dispatch pattern (ParseFunction + SplitByComma +\n// SplitBySpace) from transform-builder / filter-builder, and the\n// depth-capped recursion + IsNever guard idiom from calc-editor.\n// =====================================================================\n\nimport type {\n  AllChars,\n  And,\n  Digit,\n  IsFlex,\n  IsLength,\n  IsPercentage,\n  IsPositiveInt,\n  Letter,\n  NonEmptyAllChars,\n  Or,\n  ParseFunction,\n  SplitByComma,\n  SplitBySpace,\n  Trim,\n} from \"@/lib/ridiculous-type-kit\"\n\n// =====================================================================\n// 0. SHARED — IsNever guard. `never extends X` is vacuously true, so a\n//    naive `extends true` on a possibly-never boolean wrongly accepts.\n// =====================================================================\n\ntype IsNever<T> = [T] extends [never] ? true : false\n\n// =====================================================================\n// 1. IDENT VALIDATION (line names + area names)\n//\n// ASCII letters / digits / - / _, must not start with a digit (CSS's\n// fuller <custom-ident> grammar — escapes, non-ASCII — is out of scope;\n// see spec §9.5).\n// =====================================================================\n\ntype IdentChar = Letter | Digit | \"-\" | \"_\"\n\n/** A valid CSS-ish ident: non-empty, not digit-led, ident chars only. */\ntype IsIdent<S extends string> = S extends `${infer First}${string}`\n  ? First extends Digit\n    ? false\n    : AllChars<S, IdentChar>\n  : false\n\n// =====================================================================\n// 2. TRACK-SIZE PRIMITIVES\n// =====================================================================\n\n/** auto | min-content | max-content. */\ntype IsTrackKeyword<S extends string> =\n  Trim<S> extends \"auto\" | \"min-content\" | \"max-content\" ? true : false\n\n/** <inflexible> — a minmax() min: length | percentage | auto | min/max-content.\n *  Notably NOT an <flex> (`fr`). */\ntype IsInflexible<S extends string> = Or<\n  Or<IsLength<Trim<S>>, IsPercentage<Trim<S>>>,\n  IsTrackKeyword<Trim<S>>\n>\n\n// =====================================================================\n// 3. FUNCTION DISPATCH — minmax / fit-content / repeat\n//\n// Nesting budget for repeat() recursion (CSS forbids nested repeat()).\n// Capped small per spec §9.2 (compile budget); pathological depth\n// weak-accepts at the type level and is caught by the runtime parser.\n// =====================================================================\n\ntype Depth = [unknown, unknown, unknown]\n\n/** minmax(min, max): exactly 2 args; min inflexible; max any track-size. */\ntype ValidateMinmax<Args extends string, D extends unknown[]> =\n  SplitByComma<Args> extends [\n    infer Min extends string,\n    infer Max extends string,\n  ]\n    ? And<IsInflexible<Min>, IsTrackSize<Max, D>>\n    : false\n\n/** fit-content(<length-percentage>): exactly 1 arg, length or percentage. */\ntype ValidateFitContent<Args extends string> =\n  SplitByComma<Args> extends [infer L extends string]\n    ? Or<IsLength<Trim<L>>, IsPercentage<Trim<L>>>\n    : false\n\n/** A repeat() count: positive integer | auto-fill | auto-fit. */\ntype IsRepeatCount<S extends string> =\n  Trim<S> extends \"auto-fill\" | \"auto-fit\" ? true : IsPositiveInt<Trim<S>>\n\n/**\n * repeat(count, tracks): the first comma-arg is the count; everything after\n * the first comma is the (space-separated) track list, validated recursively.\n * SplitByComma is paren/bracket-aware, so a nested minmax() comma does NOT\n * leak a top-level part — `repeat(2, minmax(0, 1fr))` is [count, tracks].\n */\ntype ValidateRepeat<Args extends string, D extends unknown[]> =\n  SplitByComma<Args> extends [\n    infer Count extends string,\n    ...infer Rest extends string[],\n  ]\n    ? Rest extends []\n      ? false // repeat needs a track list after the count\n      : And<\n          IsRepeatCount<Count>,\n          // Rest may itself contain commas only inside nested functions; the\n          // top-level repeat track list is space-separated, so there is at\n          // most one Rest element. Join defensively, validate as a track list.\n          ValidateTrackList<SplitBySpace<JoinComma<Rest>>, D>\n        >\n    : false\n\n/** Re-join comma-split parts (defensive; Rest is normally length 1). */\ntype JoinComma<T extends string[]> = T extends [infer H extends string]\n  ? H\n  : T extends [infer H extends string, ...infer R extends string[]]\n    ? `${H}, ${JoinComma<R>}`\n    : \"\"\n\n// =====================================================================\n// 4. TRACK-SIZE + TRACK-TOKEN\n// =====================================================================\n\n/**\n * <track-size> — length | percentage | flex | keyword | minmax() |\n * fit-content(). Used for top-level tracks and minmax()'s max. Functions\n * dispatch via ParseFunction; D carries the repeat() recursion budget.\n */\ntype IsTrackSize<S extends string, D extends unknown[]> =\n  Or<\n    Or<IsLength<Trim<S>>, IsPercentage<Trim<S>>>,\n    Or<IsFlex<Trim<S>>, IsTrackKeyword<Trim<S>>>\n  > extends true\n    ? true\n    : ParseFunction<Trim<S>> extends {\n          name: infer Name extends string\n          args: infer Args extends string\n        }\n      ? Name extends \"minmax\"\n        ? ValidateMinmax<Args, D>\n        : Name extends \"fit-content\"\n          ? ValidateFitContent<Args>\n          : Name extends \"repeat\"\n            ? D extends [unknown, ...infer Rest]\n              ? ValidateRepeat<Args, Rest>\n              : true // depth budget exhausted → weak-accept (runtime catches)\n            : false\n      : false\n\n/** A bracketed line-name group: `[a]`, `[a b]`. One+ space-separated idents. */\ntype ValidateLineNames<S extends string> =\n  Trim<S> extends `[${infer Body}]`\n    ? ValidateIdentList<SplitBySpace<Trim<Body>>>\n    : false\n\ntype ValidateIdentList<Names extends string[]> = Names extends []\n  ? false // empty bracket is not a valid line-name list\n  : ValidateIdentListInner<Names>\n\ntype ValidateIdentListInner<Names extends string[]> = Names extends [\n  infer H extends string,\n  ...infer T extends string[],\n]\n  ? IsIdent<Trim<H>> extends true\n    ? ValidateIdentListInner<T>\n    : false\n  : true\n\n/** A single track-list token: a track size OR a [named-line] group. */\ntype ValidateTrackToken<S extends string, D extends unknown[]> =\n  IsTrackSize<S, D> extends true ? true : ValidateLineNames<S>\n\n/** Fold a token list; every token must validate. */\ntype ValidateTrackList<\n  Tokens extends string[],\n  D extends unknown[],\n> = Tokens extends [infer H extends string, ...infer T extends string[]]\n  ? ValidateTrackToken<H, D> extends true\n    ? ValidateTrackList<T, D>\n    : false\n  : true // reached the end without a failure\n\n// =====================================================================\n// 5. STRICT VALIDATOR — TrackListLiteral + cssTracks\n// =====================================================================\n\n/**\n * Strict validator for `grid-template-columns` / `grid-template-rows`.\n * Resolves to `S` when `S` is a fully-valid track list (or `none`),\n * `never` otherwise. `calc()` / `var()` inside a track resolve to `never`\n * (undecidable at compile time) — use the casual / IntelliSense tier or\n * the runtime parser for those.\n *\n * @example\n * type A = TrackListLiteral<\"repeat(3, 1fr)\">    // the literal\n * type B = TrackListLiteral<\"minmax(1fr, 2fr)\">  // never (fr min)\n * type C = TrackListLiteral<\"none\">              // \"none\"\n */\nexport type TrackListLiteral<S extends string> =\n  Trim<S> extends \"none\"\n    ? S\n    : Trim<S> extends \"\"\n      ? never\n      : SplitBySpace<Trim<S>> extends infer Tokens extends string[]\n        ? Tokens extends []\n          ? never\n          : ValidateTrackList<Tokens, Depth> extends true\n            ? S\n            : never\n        : never\n\n/** Call-site validator. Mirrors `cssCalc()` / `cssFilter()`. */\nexport const cssTracks = <S extends string>(\n  value: S & TrackListLiteral<S>,\n): S => value\n\n// =====================================================================\n// 6. GRID AREAS VALIDATOR\n//\n// Type-level: quoting + equal column count + valid cells. The\n// contiguous-RECTANGLE invariant is enforced at RUNTIME (spec §7).\n// =====================================================================\n\n/** A null cell: a run of one or more dots (`.`, `..`, `...`). */\ntype IsDots<S extends string> = NonEmptyAllChars<S, \".\">\n\n/** A single areas cell: a valid ident OR a null-cell dot run. */\ntype IsAreaCell<S extends string> = Or<IsIdent<Trim<S>>, IsDots<Trim<S>>>\n\n/** Validate one row's cells; return the cell count or `never` on a bad cell. */\ntype RowCellCount<Cells extends string[]> = Cells extends []\n  ? never // empty row\n  : ValidateCells<Cells> extends true\n    ? Cells[\"length\"]\n    : never\n\ntype ValidateCells<Cells extends string[]> = Cells extends [\n  infer H extends string,\n  ...infer T extends string[],\n]\n  ? IsAreaCell<H> extends true\n    ? ValidateCells<T>\n    : false\n  : true\n\n/**\n * Split the areas string into quoted row bodies (without quotes), or `never`\n * if anything between/around the rows is not part of a `\"...\"` quoted string.\n * Walks `S` directly — the kit's space-splitter is quote-UNAWARE, so we can't\n * lean on it here; we match a leading `\"...\"` and recurse on the remainder.\n */\ntype SplitAreaRows<S extends string> =\n  Trim<S> extends \"\"\n    ? []\n    : Trim<S> extends `\"${infer Body}\"${infer Rest}`\n      ? SplitAreaRows<Rest> extends infer Tail extends string[]\n        ? [Body, ...Tail]\n        : never\n      : never // leftover non-quoted text → not an areas string\n\n/** Fold the rows, requiring every row's cell count to equal the first's. */\ntype EqualColumns<\n  Rows extends string[],\n  Expected extends number | \"init\" = \"init\",\n> = Rows extends [infer H extends string, ...infer T extends string[]]\n  ? RowCellCount<SplitBySpace<Trim<H>>> extends infer N\n    ? IsNever<N> extends true\n      ? false\n      : N extends number\n        ? Expected extends \"init\"\n          ? EqualColumns<T, N>\n          : Expected extends N\n            ? EqualColumns<T, Expected>\n            : false\n        : false\n    : false\n  : Expected extends \"init\"\n    ? false // no rows\n    : true\n\n/**\n * Strict validator for `grid-template-areas`. Resolves to `S` when `S` is a\n * sequence of quoted row strings with an EQUAL cell count per row and every\n * cell a valid <ident> or null cell (`.`), `never` otherwise.\n *\n * PUNT (spec §7): the contiguous-RECTANGLE invariant — each area name must\n * span a single filled rectangle — is NOT checked here (borderline-\n * undecidable as a template-literal type, and it would make `tsc` crawl). It\n * is enforced at RUNTIME by `validateAreasRectangles` / `parseAreas`. The\n * strict type tier validates SHAPE; the runtime does FULL validation.\n *\n * @example\n * type A = GridAreasLiteral<'\"a a\" \"b b\"'>  // the literal\n * type B = GridAreasLiteral<'\"a a\" \"b\"'>    // never (unequal columns)\n * type C = GridAreasLiteral<\"none\">         // \"none\"\n */\nexport type GridAreasLiteral<S extends string> =\n  Trim<S> extends \"none\"\n    ? S\n    : Trim<S> extends \"\"\n      ? never\n      : SplitAreaRows<Trim<S>> extends infer Rows\n        ? IsNever<Rows> extends true\n          ? never\n          : Rows extends string[]\n            ? Rows extends []\n              ? never\n              : EqualColumns<Rows> extends true\n                ? S\n                : never\n            : never\n        : never\n\n/** Call-site validator. Mirrors `cssTracks()`. */\nexport const cssGridAreas = <S extends string>(\n  value: S & GridAreasLiteral<S>,\n): S => value\n\n// =====================================================================\n// 7. SUGGESTION STRINGS — IntelliSense + onChange return types\n// =====================================================================\n\n/** Named track-size keywords/suggestions. */\nexport type GridTrackSize =\n  | \"auto\"\n  | \"min-content\"\n  | \"max-content\"\n  | `${number}fr`\n  | `${number}px`\n  | `${number}%`\n\n/**\n * Suggestion union — \"this is a track list\". Head-anchored function shapes\n * also match multi-track lists; plus the bare keywords/sizes and `none`.\n */\nexport type TrackListString =\n  | GridTrackSize\n  | `repeat(${string})`\n  | `minmax(${string})`\n  | `fit-content(${string})`\n  | `[${string}]${string}`\n  | `${string} ${string}`\n  | \"none\"\n\n/** Suggestion union — \"this is a grid-template-areas string\". */\nexport type GridAreasString = `\"${string}\"${string}` | \"none\"\n\n/** The union of both property shapes — the component's onChange return. */\nexport type GridTemplateString = TrackListString | GridAreasString\n\n/** The editor's three modes. */\nexport type GridMode = \"columns\" | \"rows\" | \"areas\"\n\n/** Mode → output-string shape. columns/rows are track lists; areas its own. */\nexport interface GridTemplateStringMap {\n  columns: TrackListString\n  rows: TrackListString\n  areas: GridAreasString\n}\n\n// =====================================================================\n// 8. UTILITY TYPES — operate on grid literals at the type level\n// =====================================================================\n\n/** Drop bracketed [line-name] tokens; keep only track tokens. */\ntype TrackTokensOnly<Tokens extends string[]> = Tokens extends [\n  infer H extends string,\n  ...infer T extends string[],\n]\n  ? Trim<H> extends `[${string}]`\n    ? TrackTokensOnly<T>\n    : [H, ...TrackTokensOnly<T>]\n  : []\n\n/**\n * The top-level track tokens (named lines excluded).\n *\n * @example\n * type T = TracksOf<\"[a] 1fr [b] 2fr [c]\">  // [\"1fr\", \"2fr\"]\n */\nexport type TracksOf<S extends string> =\n  Trim<S> extends \"none\" | \"\" ? [] : TrackTokensOnly<SplitBySpace<Trim<S>>>\n\n/**\n * The number of top-level tracks (named lines excluded).\n *\n * @example\n * type C = TrackCountOf<\"repeat(3, 1fr) 100px\">  // 2\n */\nexport type TrackCountOf<S extends string> = TracksOf<S>[\"length\"]\n\n/**\n * The number of rows in a grid-template-areas string.\n *\n * @example\n * type R = AreaRowCountOf<'\"a a\" \"b b\" \"c c\"'>  // 3\n */\nexport type AreaRowCountOf<S extends string> =\n  Trim<S> extends \"none\" | \"\"\n    ? 0\n    : SplitAreaRows<Trim<S>> extends infer Rows extends string[]\n      ? Rows[\"length\"]\n      : 0\n\n/**\n * The column count of an areas string (cells in the first row; the validator\n * guarantees uniformity).\n *\n * @example\n * type C = AreaColumnCountOf<'\"a a a\" \"b b b\"'>  // 3\n */\nexport type AreaColumnCountOf<S extends string> =\n  Trim<S> extends \"none\" | \"\"\n    ? 0\n    : SplitAreaRows<Trim<S>> extends [infer First extends string, ...string[]]\n      ? SplitBySpace<Trim<First>>[\"length\"]\n      : 0\n\n// =====================================================================\n// 9. INTERNAL STATE — discriminated union (exported)\n//\n// The editor keeps raw text per mode. Exported for advanced use (custom\n// serialization, programmatic build).\n// =====================================================================\n\nexport type GridTemplateState =\n  | { mode: \"columns\"; tracks: string }\n  | { mode: \"rows\"; tracks: string }\n  | { mode: \"areas\"; rows: string[] }\n\n// Re-export the kit's Dimension for convenience.\nexport type { Dimension } from \"@/lib/ridiculous-type-kit\"\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/grid-builder.types.ts"
    },
    {
      "path": "src/components/ui/grid-builder/grid-builder.helpers.ts",
      "content": "// =====================================================================\n// grid-builder.helpers.ts\n//\n// Pure runtime parse / format for CSS grid templates. This is the SUPERSET\n// of the strict type tier:\n//   - parseTracks tolerates calc()/var() (kept verbatim, opaque) and\n//     classifies each top-level token as a size / function / [named-line].\n//   - parseAreas validates quoting + equal column count + cell idents, and\n//     OPTIONALLY enforces the contiguous-RECTANGLE invariant that the type\n//     tier punts (spec §7) via validateAreasRectangles.\n// =====================================================================\n\n// ---------------------------------------------------------------------------\n// Track tokens\n// ---------------------------------------------------------------------------\n\nexport type TrackToken =\n  | { kind: \"size\"; value: string }\n  | { kind: \"fn\"; name: string; value: string }\n  | { kind: \"line\"; names: string[]; value: string }\n\nconst TRACK_FN_RE = /^([a-zA-Z][a-zA-Z-]*)\\((.*)\\)$/s\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/\n\nfunction isIdent(s: string): boolean {\n  return IDENT_RE.test(s)\n}\n\n// ---------------------------------------------------------------------------\n// Top-level splitter (paren/bracket-aware; runtime mirror of the kit)\n// ---------------------------------------------------------------------------\n\n/** Split on `sep` only at bracket depth 0. Returns `null` if depth never\n * returns to 0 (unbalanced) at the end. */\nfunction splitTopLevel(src: string, sep: string): string[] | null {\n  const out: string[] = []\n  let depth = 0\n  let cur = \"\"\n  for (const ch of src) {\n    if (ch === \"(\" || ch === \"[\") depth++\n    else if (ch === \")\" || ch === \"]\") depth--\n    if (depth < 0) return null\n    if (ch === sep && depth === 0) {\n      out.push(cur)\n      cur = \"\"\n    } else {\n      cur += ch\n    }\n  }\n  if (depth !== 0) return null\n  out.push(cur)\n  return out\n}\n\n/** Space-split at top level, dropping empty runs. `null` on unbalanced. */\nfunction splitSpaceTokens(src: string): string[] | null {\n  const parts = splitTopLevel(src, \" \")\n  if (parts === null) return null\n  return parts.map((s) => s.trim()).filter((s) => s.length > 0)\n}\n\n// ---------------------------------------------------------------------------\n// parseTracks — string → TrackToken[] | null\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a `grid-template-columns` / `grid-template-rows` value into tokens, or\n * `null` on a malformed token (unbalanced bracket, empty `[]`, bad line ident).\n * `none` / empty → `[]`. calc()/var() are kept verbatim as opaque size tokens.\n */\nexport function parseTracks(src: string): TrackToken[] | null {\n  const trimmed = src.trim()\n  if (trimmed === \"\" || trimmed === \"none\") return []\n\n  const tokens = splitSpaceTokens(trimmed)\n  if (tokens === null) return null\n\n  const out: TrackToken[] = []\n  for (const token of tokens) {\n    if (token.startsWith(\"[\")) {\n      if (!token.endsWith(\"]\")) return null\n      const body = token.slice(1, -1).trim()\n      const names = body.length === 0 ? [] : body.split(/\\s+/)\n      if (names.length === 0) return null\n      if (!names.every(isIdent)) return null\n      out.push({ kind: \"line\", names, value: token })\n      continue\n    }\n    const m = token.match(TRACK_FN_RE)\n    if (m) {\n      out.push({ kind: \"fn\", name: m[1], value: token })\n      continue\n    }\n    // a bare size (length / percentage / flex / keyword / calc()/var() opaque)\n    out.push({ kind: \"size\", value: token })\n  }\n  return out\n}\n\n/** Re-serialize a track-token list. Empty → `none`. */\nexport function formatTracks(tokens: TrackToken[]): string {\n  if (tokens.length === 0) return \"none\"\n  return tokens.map((t) => t.value).join(\" \")\n}\n\n/** A sensible default token for a freshly-added track. */\nexport function defaultTrack(kind: TrackToken[\"kind\"] = \"size\"): TrackToken {\n  switch (kind) {\n    case \"fn\":\n      return { kind: \"fn\", name: \"minmax\", value: \"minmax(100px, 1fr)\" }\n    case \"line\":\n      return { kind: \"line\", names: [\"line\"], value: \"[line]\" }\n    default:\n      return { kind: \"size\", value: \"1fr\" }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// parseAreas — string → string[][] | null\n// ---------------------------------------------------------------------------\n\nexport interface ParseAreasOptions {\n  /** Also enforce the contiguous-rectangle invariant (the type-tier punt). */\n  rectangles?: boolean\n}\n\n/** Match each `\"...\"` quoted row, in order. `null` if any non-quoted text. */\nfunction splitQuotedRows(src: string): string[] | null {\n  const rows: string[] = []\n  let rest = src.trim()\n  while (rest.length > 0) {\n    if (rest[0] !== '\"') return null\n    const end = rest.indexOf('\"', 1)\n    if (end === -1) return null\n    rows.push(rest.slice(1, end))\n    rest = rest.slice(end + 1).trim()\n  }\n  return rows\n}\n\n/** A cell is a valid ident OR a run of one or more dots (a null cell). */\nfunction isAreaCell(cell: string): boolean {\n  if (/^\\.+$/.test(cell)) return true\n  return isIdent(cell)\n}\n\n/**\n * Parse a `grid-template-areas` value into a row-major matrix of cell strings,\n * or `null` on any violation: a non-quoted segment, an empty row, unequal\n * column counts, or a bad cell ident. `none` / empty → `[]`.\n *\n * With `{ rectangles: true }` it additionally enforces that every named area\n * forms a single contiguous rectangle — the invariant the strict TYPE tier\n * punts (spec §7).\n */\nexport function parseAreas(\n  src: string,\n  options: ParseAreasOptions = {},\n): string[][] | null {\n  const trimmed = src.trim()\n  if (trimmed === \"\" || trimmed === \"none\") return []\n\n  const rowStrings = splitQuotedRows(trimmed)\n  if (rowStrings === null) return null\n\n  const matrix: string[][] = []\n  let cols = -1\n  for (const rowStr of rowStrings) {\n    const cells = rowStr\n      .trim()\n      .split(/\\s+/)\n      .filter((c) => c.length > 0)\n    if (cells.length === 0) return null\n    if (cols === -1) cols = cells.length\n    else if (cells.length !== cols) return null\n    if (!cells.every(isAreaCell)) return null\n    matrix.push(cells)\n  }\n  if (matrix.length === 0) return null\n\n  if (options.rectangles && !validateAreasRectangles(matrix)) return null\n  return matrix\n}\n\n/** Join a matrix back into a quoted-row areas string. Empty → `none`. */\nexport function formatAreas(matrix: string[][]): string {\n  if (matrix.length === 0) return \"none\"\n  return matrix.map((row) => `\"${row.join(\" \")}\"`).join(\" \")\n}\n\n// ---------------------------------------------------------------------------\n// validateAreasRectangles — the type-tier punt, done at runtime\n// ---------------------------------------------------------------------------\n\n/**\n * Whether every named area in the matrix forms a single contiguous rectangle.\n * For each name, compute the bounding box (min/max row + col) and assert every\n * cell inside it carries that name (which also rules out a split / L-shape).\n * Dot null cells are ignored.\n */\nexport function validateAreasRectangles(matrix: string[][]): boolean {\n  const seen = new Set<string>()\n  for (let r = 0; r < matrix.length; r++) {\n    for (let c = 0; c < matrix[r].length; c++) {\n      const name = matrix[r][c]\n      if (name === \"\" || /^\\.+$/.test(name)) continue\n      if (seen.has(name)) continue\n      seen.add(name)\n\n      let minR = r\n      let maxR = r\n      let minC = c\n      let maxC = c\n      let count = 0\n      for (let rr = 0; rr < matrix.length; rr++) {\n        for (let cc = 0; cc < matrix[rr].length; cc++) {\n          if (matrix[rr][cc] === name) {\n            minR = Math.min(minR, rr)\n            maxR = Math.max(maxR, rr)\n            minC = Math.min(minC, cc)\n            maxC = Math.max(maxC, cc)\n            count++\n          }\n        }\n      }\n      const boxArea = (maxR - minR + 1) * (maxC - minC + 1)\n      if (count !== boxArea) return false\n    }\n  }\n  return true\n}\n\n// ---------------------------------------------------------------------------\n// areaNames / gridAreaFor — drive the preview + painter\n// ---------------------------------------------------------------------------\n\n/** Distinct area names in first-seen (row-major) order, excluding dot cells. */\nexport function areaNames(matrix: string[][]): string[] {\n  const out: string[] = []\n  const seen = new Set<string>()\n  for (const row of matrix) {\n    for (const cell of row) {\n      if (cell === \"\" || /^\\.+$/.test(cell)) continue\n      if (seen.has(cell)) continue\n      seen.add(cell)\n      out.push(cell)\n    }\n  }\n  return out\n}\n\n/**\n * The CSS `grid-area` value (`row-start / col-start / row-end / col-end`, all\n * 1-based, end-exclusive) for a name's bounding box, or `null` if absent.\n */\nexport function gridAreaFor(matrix: string[][], name: string): string | null {\n  let minR = Number.POSITIVE_INFINITY\n  let maxR = -1\n  let minC = Number.POSITIVE_INFINITY\n  let maxC = -1\n  for (let r = 0; r < matrix.length; r++) {\n    for (let c = 0; c < matrix[r].length; c++) {\n      if (matrix[r][c] === name) {\n        minR = Math.min(minR, r)\n        maxR = Math.max(maxR, r)\n        minC = Math.min(minC, c)\n        maxC = Math.max(maxC, c)\n      }\n    }\n  }\n  if (maxR === -1) return null\n  return `${minR + 1} / ${minC + 1} / ${maxR + 2} / ${maxC + 2}`\n}\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/grid-builder.helpers.ts"
    },
    {
      "path": "src/components/ui/grid-builder/areas-editor.tsx",
      "content": "\"use client\"\n\nimport { useMemo } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { areaNames } from \"./grid-builder.helpers\"\n\n// ---------------------------------------------------------------------------\n// AreasEditor + AreasPainter (public)\n// ---------------------------------------------------------------------------\n\nexport interface AreasEditorProps {\n  matrix: string[][]\n  onChange: (matrix: string[][]) => void\n  className?: string\n}\n\nexport function AreasEditor({ matrix, onChange, className }: AreasEditorProps) {\n  const rows = matrix.length\n  const cols = rows > 0 ? matrix[0].length : 0\n\n  const resize = (nextRows: number, nextCols: number) => {\n    const r = Math.max(1, nextRows)\n    const c = Math.max(1, nextCols)\n    const next: string[][] = []\n    for (let i = 0; i < r; i++) {\n      const row: string[] = []\n      for (let j = 0; j < c; j++) {\n        row.push(matrix[i]?.[j] ?? \".\")\n      }\n      next.push(row)\n    }\n    onChange(next)\n  }\n\n  if (rows === 0) {\n    return (\n      <div className={cn(\"space-y-2\", className)}>\n        <p className=\"px-1 py-2 text-muted-foreground text-xs\">No areas yet.</p>\n        <button\n          type=\"button\"\n          onClick={() =>\n            onChange([\n              [\"a\", \"a\"],\n              [\"b\", \"b\"],\n            ])\n          }\n          className=\"rounded-md border border-dashed bg-background px-2.5 py-1.5 font-mono text-muted-foreground text-xs hover:bg-muted/50\"\n        >\n          + start a 2×2 grid\n        </button>\n      </div>\n    )\n  }\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      <AreasPainter matrix={matrix} onChange={onChange} />\n      <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n        <span className=\"font-mono text-muted-foreground\">\n          {rows}×{cols}\n        </span>\n        <Stepper\n          label=\"rows\"\n          unit=\"row\"\n          onDec={() => resize(rows - 1, cols)}\n          onInc={() => resize(rows + 1, cols)}\n        />\n        <Stepper\n          label=\"cols\"\n          unit=\"column\"\n          onDec={() => resize(rows, cols - 1)}\n          onInc={() => resize(rows, cols + 1)}\n        />\n      </div>\n    </div>\n  )\n}\n\nfunction Stepper({\n  label,\n  unit,\n  onDec,\n  onInc,\n}: {\n  label: string\n  unit: string\n  onDec: () => void\n  onInc: () => void\n}) {\n  return (\n    <span className=\"inline-flex items-center gap-1\">\n      <span className=\"font-mono text-muted-foreground\">{label}</span>\n      <button\n        type=\"button\"\n        aria-label={`Remove ${unit}`}\n        onClick={onDec}\n        className=\"h-6 w-6 rounded border font-mono hover:bg-muted/50\"\n      >\n        −\n      </button>\n      <button\n        type=\"button\"\n        aria-label={`Add ${unit}`}\n        onClick={onInc}\n        className=\"h-6 w-6 rounded border font-mono hover:bg-muted/50\"\n      >\n        +\n      </button>\n    </span>\n  )\n}\n\nexport interface AreasPainterProps {\n  matrix: string[][]\n  onChange: (matrix: string[][]) => void\n  className?: string\n}\n\n/**\n * A grid of clickable cells. Clicking a cell cycles it through the current\n * palette of area names plus `.` (null cell). Writes the new matrix back —\n * a pure inline-style React grid (no browser automation).\n */\nexport function AreasPainter({\n  matrix,\n  onChange,\n  className,\n}: AreasPainterProps) {\n  const cols = matrix[0]?.length ?? 0\n  const palette = useMemo(() => {\n    const names = areaNames(matrix)\n    const base = names.length > 0 ? names : [\"a\"]\n    return [...base, \".\"]\n  }, [matrix])\n\n  const cycle = (r: number, c: number) => {\n    const current = matrix[r][c]\n    const idx = palette.indexOf(current)\n    const nextName = palette[(idx + 1) % palette.length]\n    onChange(\n      matrix.map((row, ri) =>\n        row.map((cell, ci) => (ri === r && ci === c ? nextName : cell)),\n      ),\n    )\n  }\n\n  return (\n    <div\n      data-grid-painter\n      className={cn(\"grid gap-1\", className)}\n      style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}\n    >\n      {matrix.map((row, r) =>\n        row.map((cell, c) => {\n          const isNull = /^\\.+$/.test(cell)\n          return (\n            <button\n              // biome-ignore lint/suspicious/noArrayIndexKey: cells are positional in a fixed grid\n              key={`${r}-${c}`}\n              type=\"button\"\n              aria-label={`Cell row ${r + 1} column ${c + 1}: ${isNull ? \"empty\" : cell}`}\n              onClick={() => cycle(r, c)}\n              className={cn(\n                \"flex h-12 items-center justify-center rounded border font-mono text-xs\",\n                isNull\n                  ? \"border-dashed bg-muted/30 text-muted-foreground\"\n                  : \"border-primary/40 bg-primary/10 text-foreground\",\n              )}\n            >\n              {isNull ? \".\" : cell}\n            </button>\n          )\n        }),\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/areas-editor.tsx"
    },
    {
      "path": "src/components/ui/grid-builder/grid-builder.constants.ts",
      "content": "import type { GridMode } from \"./grid-builder.types\"\n\n// ---------------------------------------------------------------------------\n// Shared constants for the grid-builder editor + preview.\n// ---------------------------------------------------------------------------\n\nexport const MODES: readonly { id: GridMode; label: string }[] = [\n  { id: \"columns\", label: \"columns\" },\n  { id: \"rows\", label: \"rows\" },\n  { id: \"areas\", label: \"areas\" },\n]\n\nexport const LENGTH_UNITS = [\"fr\", \"px\", \"rem\", \"em\", \"%\", \"vw\", \"vh\"] as const\nexport const TRACK_KEYWORDS = [\"auto\", \"min-content\", \"max-content\"] as const\n\nexport const PREVIEW_BG = [\n  \"bg-indigo-500/30\",\n  \"bg-pink-500/30\",\n  \"bg-amber-500/30\",\n  \"bg-emerald-500/30\",\n  \"bg-sky-500/30\",\n  \"bg-rose-500/30\",\n]\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/grid-builder.constants.ts"
    },
    {
      "path": "src/components/ui/grid-builder/grid-preview.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { PREVIEW_BG } from \"./grid-builder.constants\"\nimport {\n  areaNames,\n  gridAreaFor,\n  parseAreas,\n  parseTracks,\n} from \"./grid-builder.helpers\"\nimport type { GridMode } from \"./grid-builder.types\"\n\n// ---------------------------------------------------------------------------\n// GridPreview (public) — the showcase. Pure inline-style display:grid.\n// ---------------------------------------------------------------------------\n\nexport interface GridPreviewProps {\n  mode: GridMode\n  columns: string\n  rows: string\n  areas: string\n  className?: string\n}\n\nexport function GridPreview({\n  mode,\n  columns,\n  rows,\n  areas,\n  className,\n}: GridPreviewProps) {\n  if (mode === \"areas\") {\n    const matrix = parseAreas(areas) ?? []\n    const names = areaNames(matrix)\n    const cols = matrix[0]?.length ?? 1\n    const rowCount = matrix.length || 1\n    return (\n      <div className={cn(\"space-y-2 rounded-lg border p-3\", className)}>\n        <div className=\"text-muted-foreground text-xs\">preview · areas</div>\n        <div\n          data-grid-preview\n          className=\"gap-1.5\"\n          style={{\n            display: \"grid\",\n            gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,\n            gridTemplateRows: `repeat(${rowCount}, 40px)`,\n          }}\n        >\n          {names.map((name, i) => {\n            const area = gridAreaFor(matrix, name)\n            return (\n              <div\n                key={name}\n                className={cn(\n                  \"flex items-center justify-center rounded font-mono text-[11px]\",\n                  PREVIEW_BG[i % PREVIEW_BG.length],\n                )}\n                style={{ gridArea: area ?? undefined }}\n              >\n                {name}\n              </div>\n            )\n          })}\n          {names.length === 0 ? (\n            <div className=\"flex h-10 items-center justify-center text-muted-foreground text-xs\">\n              empty\n            </div>\n          ) : null}\n        </div>\n      </div>\n    )\n  }\n\n  // columns / rows: render N numbered cells in the track layout.\n  const tokens = parseTracks(mode === \"rows\" ? rows : columns) ?? []\n  const trackCount = Math.max(1, tokens.filter((t) => t.kind !== \"line\").length)\n\n  return (\n    <div className={cn(\"space-y-2 rounded-lg border p-3\", className)}>\n      <div className=\"text-muted-foreground text-xs\">preview · {mode}</div>\n      <div\n        data-grid-preview\n        className=\"gap-1.5\"\n        style={{\n          display: \"grid\",\n          gridTemplateColumns: mode === \"rows\" ? undefined : columns,\n          gridTemplateRows: mode === \"rows\" ? rows : undefined,\n          gridAutoRows: mode === \"rows\" ? undefined : \"40px\",\n          gridAutoColumns: mode === \"rows\" ? \"60px\" : undefined,\n          gridAutoFlow: \"row\",\n          minHeight: 40,\n        }}\n      >\n        {Array.from({ length: trackCount }).map((_, i) => (\n          <div\n            // biome-ignore lint/suspicious/noArrayIndexKey: positional preview cells\n            key={i}\n            className={cn(\n              \"flex h-10 items-center justify-center rounded font-mono text-[11px]\",\n              PREVIEW_BG[i % PREVIEW_BG.length],\n            )}\n          >\n            {i + 1}\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/grid-preview.tsx"
    },
    {
      "path": "src/components/ui/grid-builder/track-list-editor.tsx",
      "content": "\"use client\"\n\nimport { Input } from \"@/components/ui/input\"\nimport { cn } from \"@/lib/utils\"\nimport { LENGTH_UNITS, TRACK_KEYWORDS } from \"./grid-builder.constants\"\nimport { defaultTrack, type TrackToken } from \"./grid-builder.helpers\"\n\n// ---------------------------------------------------------------------------\n// TrackListEditor (public)\n// ---------------------------------------------------------------------------\n\nexport interface TrackListEditorProps {\n  tokens: TrackToken[]\n  onChange: (tokens: TrackToken[]) => void\n  className?: string\n}\n\nexport function TrackListEditor({\n  tokens,\n  onChange,\n  className,\n}: TrackListEditorProps) {\n  const updateAt = (i: number, token: TrackToken) => {\n    onChange(tokens.map((t, idx) => (idx === i ? token : t)))\n  }\n  const removeAt = (i: number) => {\n    onChange(tokens.filter((_, idx) => idx !== i))\n  }\n  const add = (kind: TrackToken[\"kind\"]) => {\n    onChange([...tokens, defaultTrack(kind)])\n  }\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      <div className=\"space-y-1.5\">\n        {tokens.map((token, i) => (\n          <TrackTokenRow\n            // biome-ignore lint/suspicious/noArrayIndexKey: rows are positional, reordered only by add/remove\n            key={`${token.kind}-${i}`}\n            token={token}\n            onChange={(next) => updateAt(i, next)}\n            onRemove={() => removeAt(i)}\n          />\n        ))}\n        {tokens.length === 0 ? (\n          <p className=\"px-1 py-2 text-muted-foreground text-xs\">\n            No tracks yet — add a size, function, or named line.\n          </p>\n        ) : null}\n      </div>\n      <div className=\"flex flex-wrap gap-1.5\">\n        <AddButton label=\"+ size\" onClick={() => add(\"size\")} />\n        <AddButton label=\"+ function\" onClick={() => add(\"fn\")} />\n        <AddButton label=\"+ [line]\" onClick={() => add(\"line\")} />\n      </div>\n    </div>\n  )\n}\n\nfunction AddButton({ label, onClick }: { label: string; onClick: () => void }) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      className=\"rounded-md border border-dashed bg-background px-2.5 py-1.5 font-mono text-muted-foreground text-xs hover:bg-muted/50\"\n    >\n      {label}\n    </button>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// TrackTokenRow (public)\n// ---------------------------------------------------------------------------\n\nexport interface TrackTokenRowProps {\n  token: TrackToken\n  onChange: (token: TrackToken) => void\n  onRemove: () => void\n  className?: string\n}\n\n/** Split \"10px\"/\"1fr\"/\"50%\" into number + unit; opaque (calc/keyword) → null. */\nfunction splitSize(value: string): { num: string; unit: string } | null {\n  const m = value.match(/^(-?\\d*\\.?\\d+)([a-z%]*)$/i)\n  if (!m) return null\n  return { num: m[1], unit: m[2] }\n}\n\nexport function TrackTokenRow({\n  token,\n  onChange,\n  onRemove,\n  className,\n}: TrackTokenRowProps) {\n  return (\n    <div\n      className={cn(\n        \"flex flex-wrap items-center gap-1.5 rounded-md border p-1.5\",\n        className,\n      )}\n    >\n      <span className=\"rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground\">\n        {token.kind}\n      </span>\n      <div className=\"flex flex-1 flex-wrap items-center gap-1\">\n        {token.kind === \"size\" ? (\n          <SizeEditor\n            value={token.value}\n            onChange={(value) => onChange({ kind: \"size\", value })}\n          />\n        ) : token.kind === \"line\" ? (\n          <Input\n            aria-label=\"line names\"\n            value={token.names.join(\" \")}\n            spellCheck={false}\n            autoComplete=\"off\"\n            onChange={(e) => {\n              const names = e.target.value.split(/\\s+/).filter(Boolean)\n              onChange({\n                kind: \"line\",\n                names,\n                value: `[${names.join(\" \")}]`,\n              })\n            }}\n            className=\"h-8 w-full font-mono text-xs\"\n          />\n        ) : (\n          <Input\n            aria-label=\"function track\"\n            value={token.value}\n            spellCheck={false}\n            autoComplete=\"off\"\n            onChange={(e) => {\n              const value = e.target.value\n              const name = value.match(/^([a-zA-Z-]+)\\(/)?.[1] ?? token.name\n              onChange({ kind: \"fn\", name, value })\n            }}\n            className=\"h-8 w-full font-mono text-xs\"\n          />\n        )}\n      </div>\n      <button\n        type=\"button\"\n        onClick={onRemove}\n        aria-label={`Remove ${token.kind} track`}\n        className=\"rounded p-1 text-muted-foreground hover:bg-destructive/10 hover:text-destructive\"\n      >\n        <span aria-hidden=\"true\">×</span>\n      </button>\n    </div>\n  )\n}\n\nfunction SizeEditor({\n  value,\n  onChange,\n}: {\n  value: string\n  onChange: (value: string) => void\n}) {\n  const parts = splitSize(value)\n  const isKeyword = (TRACK_KEYWORDS as readonly string[]).includes(value)\n\n  if (parts === null || isKeyword) {\n    // keyword or opaque (calc/var) — raw input + keyword shortcut select\n    return (\n      <span className=\"inline-flex w-full items-center gap-1\">\n        <Input\n          aria-label=\"track size\"\n          value={value}\n          spellCheck={false}\n          autoComplete=\"off\"\n          onChange={(e) => onChange(e.target.value)}\n          className=\"h-8 flex-1 font-mono text-xs\"\n        />\n        <select\n          aria-label=\"track keyword\"\n          value={isKeyword ? value : \"\"}\n          onChange={(e) => e.target.value && onChange(e.target.value)}\n          className=\"h-8 rounded border bg-background px-1 font-mono text-xs\"\n        >\n          <option value=\"\">kw…</option>\n          {TRACK_KEYWORDS.map((k) => (\n            <option key={k} value={k}>\n              {k}\n            </option>\n          ))}\n        </select>\n      </span>\n    )\n  }\n\n  return (\n    <span className=\"inline-flex items-center\">\n      <Input\n        aria-label=\"track size\"\n        value={value}\n        spellCheck={false}\n        autoComplete=\"off\"\n        onChange={(e) => onChange(e.target.value)}\n        className=\"h-8 w-[80px] rounded-r-none border-r-0 font-mono text-xs\"\n      />\n      <select\n        aria-label=\"track size unit\"\n        value={parts.unit || LENGTH_UNITS[0]}\n        onChange={(e) => onChange(`${parts.num || \"1\"}${e.target.value}`)}\n        className=\"h-8 rounded-r-md rounded-l-none border border-input bg-background px-1 font-mono text-xs\"\n      >\n        {LENGTH_UNITS.map((u) => (\n          <option key={u} value={u}>\n            {u}\n          </option>\n        ))}\n      </select>\n    </span>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/grid-builder/track-list-editor.tsx"
    }
  ],
  "type": "registry:ui"
}