{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "query-builder",
  "title": "Media / Container Query Builder",
  "description": "Ridiculously typed editor for CSS media and container queries (mode prop). The strict tier validates the query skeleton, the no-mixing-and/or rule, and each feature's value dimension against a known feature table. Ships a live 'matches now?' indicator.",
  "dependencies": [],
  "registryDependencies": [
    "ridiculous-type-kit",
    "unit-input",
    "button",
    "popover",
    "input",
    "label",
    "select"
  ],
  "files": [
    {
      "path": "src/components/ui/query-builder/index.ts",
      "content": "export type {\n  ContainerNameInputProps,\n  FeatureTestRowProps,\n  JoinerSelectProps,\n  MediaTypeSelectProps,\n  NotToggleProps,\n  QueryBuilderPanelProps,\n  QueryBuilderProps,\n  QueryPreviewProps,\n} from \"./query-builder\"\nexport {\n  ContainerNameInput,\n  FeatureTestRow,\n  JoinerSelect,\n  MediaTypeSelect,\n  NotToggle,\n  QueryBuilder,\n  QueryBuilderPanel,\n  QueryPreview,\n} from \"./query-builder\"\nexport {\n  defaultFeatureTest,\n  defaultQuery,\n  enumOptionsFor,\n  featureKind,\n  featuresFor,\n  formatQuery,\n  matchesNow,\n  parseFeatureTest,\n  parseQuery,\n  parseQueryState,\n  queryToString,\n} from \"./query-builder.helpers\"\nexport type {\n  ColorGamut,\n  ContainerQueryLiteral,\n  ContainerQueryString,\n  Dimension,\n  DisplayMode,\n  DynamicRange,\n  FeatureCountOf,\n  FeatureOperator,\n  FeaturesOf,\n  FeatureTest,\n  ForcedColors,\n  Hover,\n  MediaModifier,\n  MediaQueryLiteral,\n  MediaQueryString,\n  MediaType,\n  Orientation,\n  OverflowBlock,\n  OverflowInline,\n  Pointer,\n  PrefersColorScheme,\n  PrefersContrast,\n  PrefersReducedMotion,\n  PrefersReducedTransparency,\n  QueryMode,\n  QueryNode,\n  QueryState,\n  QueryString,\n  QueryStringMap,\n  Scripting,\n  UpdateFrequency,\n} from \"./query-builder.types\"\nexport { cssContainerQuery, cssMediaQuery } from \"./query-builder.types\"\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/index.ts"
    },
    {
      "path": "src/components/ui/query-builder/query-builder.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, 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 { FeatureTestRow } from \"./feature-test-row\"\nimport {\n  defaultFeatureTest,\n  parseQueryState,\n  queryToString,\n} from \"./query-builder.helpers\"\nimport type {\n  FeatureTest,\n  QueryMode,\n  QueryState,\n  QueryString,\n} from \"./query-builder.types\"\nimport {\n  AddTestButton,\n  ContainerNameInput,\n  JoinerSelect,\n  LiveString,\n  MediaTypeSelect,\n  NotToggle,\n} from \"./query-builder-fields\"\nimport { QueryPreview } from \"./query-preview\"\n\nexport type { FeatureTestRowProps } from \"./feature-test-row\"\nexport { FeatureTestRow } from \"./feature-test-row\"\nexport type {\n  ContainerNameInputProps,\n  JoinerSelectProps,\n  MediaTypeSelectProps,\n  NotToggleProps,\n} from \"./query-builder-fields\"\n// Re-export the public sub-components + their prop types so consumers (and the\n// barrel) can keep importing them from `./query-builder` after the split.\nexport {\n  ContainerNameInput,\n  JoinerSelect,\n  MediaTypeSelect,\n  NotToggle,\n} from \"./query-builder-fields\"\nexport type { QueryPreviewProps } from \"./query-preview\"\nexport { QueryPreview } from \"./query-preview\"\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface QueryBuilderPanelProps {\n  value: QueryString | (string & {})\n  onChange: (value: QueryString) => void\n  /** `\"media\"` (default) edits a media query; `\"container\"` a container query. */\n  mode?: QueryMode\n  className?: string\n  \"aria-label\"?: string\n}\n\nexport interface QueryBuilderProps extends QueryBuilderPanelProps {}\n\n// ---------------------------------------------------------------------------\n// QueryBuilder — popover-wrapped\n// ---------------------------------------------------------------------------\n\nexport function QueryBuilder(props: QueryBuilderProps) {\n  const {\n    value,\n    mode = \"media\",\n    className,\n    \"aria-label\": ariaLabel = \"Edit a CSS media or container query\",\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 className=\"text-[10px] text-muted-foreground uppercase\">\n            {mode}\n          </span>\n          <span className=\"max-w-[220px] truncate text-xs\">{value}</span>\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-0\" align=\"start\">\n        <QueryBuilderPanel {...props} />\n      </PopoverContent>\n    </Popover>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// QueryBuilderPanel — inline\n// ---------------------------------------------------------------------------\n\nexport function QueryBuilderPanel({\n  value,\n  onChange,\n  mode = \"media\",\n  className,\n  \"aria-label\": ariaLabel = \"CSS media / container query editor\",\n}: QueryBuilderPanelProps) {\n  const [state, setState] = useState<QueryState>(\n    () =>\n      parseQueryState(String(value), mode) ?? {\n        mode,\n        joiner: \"and\",\n        not: false,\n        tests: [],\n      },\n  )\n  const lastEmittedRef = useRef<string | null>(null)\n\n  // Resync from external value or a mode change (skip our own emits).\n  // biome-ignore lint/correctness/useExhaustiveDependencies: re-parse on value OR mode change; state is the derived target\n  useEffect(() => {\n    if (value === lastEmittedRef.current && state.mode === mode) return\n    const parsed = parseQueryState(String(value), mode)\n    if (parsed !== null) setState(parsed)\n    else setState((s) => ({ ...s, mode }))\n  }, [value, mode])\n\n  const commit = (next: QueryState) => {\n    setState(next)\n    const str = queryToString(next)\n    lastEmittedRef.current = str\n    onChange(str as QueryString)\n  }\n\n  const updateAt = (index: number, test: FeatureTest) => {\n    commit({\n      ...state,\n      tests: state.tests.map((it, i) => (i === index ? test : it)),\n    })\n  }\n  const removeAt = (index: number) => {\n    commit({ ...state, tests: state.tests.filter((_, i) => i !== index) })\n  }\n  const add = () => {\n    commit({ ...state, tests: [...state.tests, defaultFeatureTest(mode)] })\n  }\n\n  const liveString = queryToString(state)\n\n  return (\n    <fieldset\n      className={cn(\n        \"m-0 w-[600px] space-y-3 border-0 bg-background p-3\",\n        className,\n      )}\n      aria-label={ariaLabel}\n    >\n      <div className=\"flex flex-wrap items-center gap-2\">\n        {mode === \"media\" ? (\n          <MediaTypeSelect\n            modifier={state.modifier}\n            mediaType={state.mediaType}\n            onChange={({ modifier, mediaType }) =>\n              commit({ ...state, modifier, mediaType })\n            }\n          />\n        ) : (\n          <ContainerNameInput\n            name={state.containerName ?? \"\"}\n            onChange={(containerName) => commit({ ...state, containerName })}\n          />\n        )}\n        <NotToggle\n          checked={state.not}\n          onChange={(not) => commit({ ...state, not })}\n        />\n        {state.tests.length > 1 && (\n          <JoinerSelect\n            value={state.joiner}\n            onChange={(joiner) => commit({ ...state, joiner })}\n          />\n        )}\n      </div>\n\n      <div className=\"space-y-2\">\n        {state.tests.map((test, i) => (\n          <FeatureTestRow\n            // biome-ignore lint/suspicious/noArrayIndexKey: rows are positional, reordered only by add/remove\n            key={`test-${i}`}\n            index={i}\n            mode={mode}\n            test={test}\n            onChange={(next) => updateAt(i, next)}\n            onRemove={() => removeAt(i)}\n          />\n        ))}\n      </div>\n\n      <AddTestButton onAdd={add} />\n      <LiveString value={liveString} />\n      <QueryPreview value={liveString} mode={mode} />\n    </fieldset>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/query-builder.tsx"
    },
    {
      "path": "src/components/ui/query-builder/query-builder.types.ts",
      "content": "// =====================================================================\n// query-builder.types.ts\n//\n// The \"ridiculous\" tier for CSS media queries AND container queries.\n// `MediaQueryLiteral<S>` / `ContainerQueryLiteral<S>` resolve to `S` for a\n// structurally + dimensionally valid query, `never` otherwise. Built on\n// `ridiculous-type-kit` (DimensionOf, IsLength/IsResolution/IsNumber, And/Or,\n// KeepIf, Trim). The boolean/parenthesized skeleton mirrors if-function (local\n// paren-aware splitters — the kit ships no joiner-word splitter); the value\n// dimension dispatch mirrors transform-builder.\n//\n// Grammar (see 2026-05-29-query-builder-design.md §1.1):\n//   media:     [ (only|not)? <type> [ and <cond-no-or> ]? ] | <condition> | not <test>\n//   container: [ <name> ]? <condition>\n//   <condition> = <test> [ (and|or) <test> ]*   (NO mixing and/or at one level)\n//   <test>      = ( <feature-test> ) | ( <condition> )   (nested, depth-capped)\n//   <feature-test> = <feature> | <feature>:<value>\n//                  | <feature> <op> <value> | <value> <op> <feature> <op> <value>\n//\n// Validated vs deferred boundary is design §3.4 / §7: strict validates the\n// skeleton + the no-mix rule + a KNOWN feature table with per-feature value\n// dimensions; it DEFERS unknown features, operator-direction consistency, the\n// min-/max--with-range combo, calc()/var() values, and depth past 4 — the\n// runtime parser does the fuller job.\n// =====================================================================\n\nimport type {\n  And,\n  IsLength,\n  IsNumber,\n  IsResolution,\n  Or,\n  Trim,\n} from \"@/lib/ridiculous-type-kit\"\n\n// =====================================================================\n// 1. LOCAL PAREN-AWARE SPLITTERS (kit ships no joiner-word splitter)\n// =====================================================================\n\ntype Push<Acc extends string[], Cur extends string> = [...Acc, Cur]\n\n// Walk char-by-char tracking ()/[] depth; split on the whole word ` ${W} `\n// (space-delimited) only at depth 0. We detect the joiner by peeking for\n// `${W} ` after a space at depth 0 — implemented by matching ` and `/` or `\n// against the remaining string.\ntype SplitWord<\n  S extends string,\n  W extends string,\n  Depth extends unknown[] = [],\n  Cur extends string = \"\",\n  Acc extends string[] = [],\n> = S extends `${infer C}${infer Rest}`\n  ? C extends \"(\" | \"[\"\n    ? SplitWord<Rest, W, [...Depth, unknown], `${Cur}${C}`, Acc>\n    : C extends \")\" | \"]\"\n      ? SplitWord<\n          Rest,\n          W,\n          Depth extends [unknown, ...infer D] ? D : [],\n          `${Cur}${C}`,\n          Acc\n        >\n      : Depth[\"length\"] extends 0\n        ? S extends ` ${W} ${infer After}`\n          ? SplitWord<After, W, Depth, \"\", Push<Acc, Cur>>\n          : SplitWord<Rest, W, Depth, `${Cur}${C}`, Acc>\n        : SplitWord<Rest, W, Depth, `${Cur}${C}`, Acc>\n  : Push<Acc, Cur>\n\ntype TrimAllDrop<T extends string[]> = T extends [\n  infer H extends string,\n  ...infer R extends string[],\n]\n  ? Trim<H> extends \"\"\n    ? TrimAllDrop<R>\n    : [Trim<H>, ...TrimAllDrop<R>]\n  : []\n\n/** Split `S` on the top-level whole word `W` (` and ` / ` or `). */\ntype SplitByWord<S extends string, W extends string> = TrimAllDrop<\n  SplitWord<S, W>\n>\n\n// Does the whole word `W` occur at top level (depth 0)? Used for the no-mix\n// check: when we split on one joiner we verify the other is absent.\ntype HasWord<\n  S extends string,\n  W extends string,\n  Depth extends unknown[] = [],\n> = S extends `${infer C}${infer Rest}`\n  ? C extends \"(\" | \"[\"\n    ? HasWord<Rest, W, [...Depth, unknown]>\n    : C extends \")\" | \"]\"\n      ? HasWord<Rest, W, Depth extends [unknown, ...infer D] ? D : []>\n      : Depth[\"length\"] extends 0\n        ? S extends ` ${W} ${string}`\n          ? true\n          : HasWord<Rest, W, Depth>\n        : HasWord<Rest, W, Depth>\n  : false\n\n// Parens balance + never go negative (if-function shape).\ntype IsBalancedAcc<\n  S extends string,\n  Depth extends unknown[] = [],\n> = S extends `${infer C}${infer Rest}`\n  ? C extends \"(\" | \"[\"\n    ? IsBalancedAcc<Rest, [...Depth, unknown]>\n    : C extends \")\" | \"]\"\n      ? Depth extends [unknown, ...infer D]\n        ? IsBalancedAcc<Rest, D>\n        : false\n      : IsBalancedAcc<Rest, Depth>\n  : Depth[\"length\"] extends 0\n    ? true\n    : false\n\ntype IsBalanced<S extends string> = IsBalancedAcc<S>\n\n// =====================================================================\n// 2. OPERATOR SPLITS (for range feature tests)\n//    Ops: < <= > >= = . Try 2-char (<=,>=) before 1-char.\n// =====================================================================\n\nexport type FeatureOperator = \"<\" | \"<=\" | \">\" | \">=\" | \"=\"\n\n// Split on the FIRST operator → [left, op, right]. Walks char-by-char so the\n// 2-char ops win over their 1-char prefixes. Operators only appear at depth 0\n// in a feature test (the test is already unwrapped of its outer parens).\ntype SplitFirstOp<\n  S extends string,\n  Cur extends string = \"\",\n> = S extends `${infer A}${infer B}${infer Rest}`\n  ? `${A}${B}` extends \"<=\" | \">=\"\n    ? [Cur, `${A}${B}`, Rest]\n    : A extends \"<\" | \">\" | \"=\"\n      ? [Cur, A, `${B}${Rest}`]\n      : SplitFirstOp<`${B}${Rest}`, `${Cur}${A}`>\n  : S extends `${infer A}${infer Rest}`\n    ? A extends \"<\" | \">\" | \"=\"\n      ? [Cur, A, Rest]\n      : never\n    : never\n\n// =====================================================================\n// 3. VALUE-DIMENSION PREDICATES (built on the kit)\n// =====================================================================\n\ntype IsLengthVal<S extends string> = IsLength<Trim<S>>\ntype IsResolutionVal<S extends string> = IsResolution<Trim<S>>\ntype IsIntegerVal<S extends string> = IsNumber<Trim<S>>\n\n// A <ratio>: `<number>` or `<number>/<number>` (lenient — design A11).\ntype IsRatio<S extends string> =\n  Trim<S> extends `${infer N}/${infer D}`\n    ? And<IsNumber<Trim<N>>, IsNumber<Trim<D>>>\n    : IsNumber<Trim<S>>\n\n// =====================================================================\n// 4. FEATURE TABLES (design §3.3) — KNOWN sets, per mode.\n//    `min-`/`max-` prefixes are stripped (StripMinMax) before lookup.\n// =====================================================================\n\ntype StripMinMax<F extends string> = F extends `min-${infer R}`\n  ? R\n  : F extends `max-${infer R}`\n    ? R\n    : F\n\nexport type QueryMode = \"media\" | \"container\"\n\n// --- enum keyword unions (exported for IntelliSense) ------------------\nexport type Orientation = \"portrait\" | \"landscape\"\nexport type PrefersColorScheme = \"light\" | \"dark\"\nexport type PrefersReducedMotion = \"no-preference\" | \"reduce\"\nexport type PrefersReducedTransparency = \"no-preference\" | \"reduce\"\nexport type PrefersContrast = \"no-preference\" | \"more\" | \"less\" | \"custom\"\nexport type ForcedColors = \"none\" | \"active\"\nexport type Hover = \"none\" | \"hover\"\nexport type Pointer = \"none\" | \"coarse\" | \"fine\"\nexport type ColorGamut = \"srgb\" | \"p3\" | \"rec2020\"\nexport type DynamicRange = \"standard\" | \"high\"\nexport type Scripting = \"none\" | \"initial-only\" | \"enabled\"\nexport type UpdateFrequency = \"none\" | \"slow\" | \"fast\"\nexport type OverflowBlock = \"none\" | \"scroll\" | \"paged\"\nexport type OverflowInline = \"none\" | \"scroll\"\nexport type DisplayMode =\n  | \"fullscreen\"\n  | \"standalone\"\n  | \"minimal-ui\"\n  | \"browser\"\n  | \"window-controls-overlay\"\n\n// --- media feature classes -------------------------------------------\ntype MediaLengthFeature = \"width\" | \"height\" | \"device-width\" | \"device-height\"\ntype MediaRatioFeature = \"aspect-ratio\" | \"device-aspect-ratio\"\ntype MediaResolutionFeature = \"resolution\"\ntype MediaIntegerFeature =\n  | \"color\"\n  | \"color-index\"\n  | \"monochrome\"\n  | \"device-pixel-ratio\"\n\n// Map an enum media feature to its keyword union (false if not an enum).\ntype MediaEnumValues<F extends string> = F extends \"orientation\"\n  ? Orientation\n  : F extends \"prefers-color-scheme\"\n    ? PrefersColorScheme\n    : F extends \"prefers-reduced-motion\"\n      ? PrefersReducedMotion\n      : F extends \"prefers-reduced-transparency\"\n        ? PrefersReducedTransparency\n        : F extends \"prefers-contrast\"\n          ? PrefersContrast\n          : F extends \"forced-colors\"\n            ? ForcedColors\n            : F extends \"hover\" | \"any-hover\"\n              ? Hover\n              : F extends \"pointer\" | \"any-pointer\"\n                ? Pointer\n                : F extends \"color-gamut\"\n                  ? ColorGamut\n                  : F extends \"dynamic-range\" | \"video-dynamic-range\"\n                    ? DynamicRange\n                    : F extends \"scripting\"\n                      ? Scripting\n                      : F extends \"update\"\n                        ? UpdateFrequency\n                        : F extends \"overflow-block\"\n                          ? OverflowBlock\n                          : F extends \"overflow-inline\"\n                            ? OverflowInline\n                            : F extends \"display-mode\"\n                              ? DisplayMode\n                              : never\n\n// Boolean-capable media features (valid as a bare `(feature)`).\ntype MediaBooleanFeature =\n  | \"color\"\n  | \"color-index\"\n  | \"monochrome\"\n  | \"grid\"\n  | \"hover\"\n  | \"any-hover\"\n  | \"pointer\"\n  | \"any-pointer\"\n  | \"orientation\"\n  | \"prefers-color-scheme\"\n  | \"prefers-reduced-motion\"\n  | \"prefers-reduced-transparency\"\n  | \"prefers-contrast\"\n  | \"forced-colors\"\n  | \"color-gamut\"\n  | \"dynamic-range\"\n  | \"video-dynamic-range\"\n  | \"scripting\"\n  | \"update\"\n  | \"overflow-block\"\n  | \"overflow-inline\"\n  | \"display-mode\"\n\n// --- container feature classes (size/style subset — design A5/A6) -----\ntype ContainerLengthFeature = \"width\" | \"height\" | \"inline-size\" | \"block-size\"\ntype ContainerRatioFeature = \"aspect-ratio\"\ntype ContainerEnumValues<F extends string> = F extends \"orientation\"\n  ? Orientation\n  : never\ntype ContainerBooleanFeature = \"orientation\"\n\n// =====================================================================\n// 5. VALUE VALIDATION PER FEATURE (the dispatch core)\n//    Strip min-/max-, look up the base feature's class for the mode, check\n//    the value's dimension / keyword. Unknown feature → false.\n// =====================================================================\n\ntype ValidateMediaValue<Feature extends string, Value extends string> =\n  StripMinMax<Feature> extends infer F extends string\n    ? F extends MediaLengthFeature\n      ? IsLengthVal<Value>\n      : F extends MediaRatioFeature\n        ? IsRatio<Value>\n        : F extends MediaResolutionFeature\n          ? IsResolutionVal<Value>\n          : F extends MediaIntegerFeature\n            ? IsIntegerVal<Value>\n            : MediaEnumValues<F> extends never\n              ? false\n              : Trim<Value> extends MediaEnumValues<F>\n                ? true\n                : false\n    : false\n\ntype ValidateContainerValue<Feature extends string, Value extends string> =\n  StripMinMax<Feature> extends infer F extends string\n    ? F extends ContainerLengthFeature\n      ? IsLengthVal<Value>\n      : F extends ContainerRatioFeature\n        ? IsRatio<Value>\n        : ContainerEnumValues<F> extends never\n          ? false\n          : Trim<Value> extends ContainerEnumValues<F>\n            ? true\n            : false\n    : false\n\ntype ValidateValue<\n  Feature extends string,\n  Value extends string,\n  Mode extends QueryMode,\n> = Mode extends \"media\"\n  ? ValidateMediaValue<Feature, Value>\n  : ValidateContainerValue<Feature, Value>\n\n// Boolean test: the bare feature must be boolean-capable for the mode.\ntype ValidateBoolean<\n  Feature extends string,\n  Mode extends QueryMode,\n> = Mode extends \"media\"\n  ? Trim<Feature> extends MediaBooleanFeature\n    ? true\n    : false\n  : Trim<Feature> extends ContainerBooleanFeature\n    ? true\n    : false\n\n// =====================================================================\n// 6. FEATURE TEST DISPATCH (the four shapes)\n// =====================================================================\n\ntype ValidateFeatureTest<Inner extends string, Mode extends QueryMode> =\n  // [X] extends [never] guards against SplitFirstOp returning `never` (no op):\n  // a bare `never` would vacuously satisfy the tuple pattern and widen L/Rest.\n  [SplitFirstOp<Trim<Inner>>] extends [never]\n    ? // no operator → plain `feature: value` or boolean `(feature)`\n      Trim<Inner> extends `${infer F}:${infer V}`\n      ? ValidateValue<Trim<F>, Trim<V>, Mode>\n      : ValidateBoolean<Trim<Inner>, Mode>\n    : SplitFirstOp<Trim<Inner>> extends [\n          infer L extends string,\n          FeatureOperator,\n          infer Rest extends string,\n        ]\n      ? [SplitFirstOp<Trim<Rest>>] extends [never]\n        ? // range2: L op Rest → L is the feature\n          ValidateValue<Trim<L>, Trim<Rest>, Mode>\n        : SplitFirstOp<Trim<Rest>> extends [\n              infer Mid extends string,\n              FeatureOperator,\n              infer R extends string,\n            ]\n          ? // range3: L op Mid op R → Mid is the feature\n            And<\n              ValidateValue<Trim<Mid>, Trim<L>, Mode>,\n              ValidateValue<Trim<Mid>, Trim<R>, Mode>\n            >\n          : false\n      : false\n\n// =====================================================================\n// 7. TEST + CONDITION (recursive, depth-capped — design A10)\n//    Depth is a tuple; we recurse on nested groups until it empties, then\n//    accept the tail leniently (keeps tsc bounded).\n// =====================================================================\n\n// Is `Inner` (the content of a `( … )`) a GROUP rather than a feature test?\n// A group starts with `(` or `not ` or has a top-level joiner.\ntype IsGroup<Inner extends string> =\n  Trim<Inner> extends `(${string}`\n    ? true\n    : Trim<Inner> extends `not ${string}`\n      ? true\n      : Or<HasWord<Trim<Inner>, \"and\">, HasWord<Trim<Inner>, \"or\">>\n\ntype ValidateTest<\n  S extends string,\n  Mode extends QueryMode,\n  Depth extends unknown[],\n> =\n  Trim<S> extends `(${infer Inner})`\n    ? IsBalanced<Inner> extends true\n      ? IsGroup<Inner> extends true\n        ? Depth extends [unknown, ...infer D]\n          ? ValidateCondition<Inner, Mode, D>\n          : true // depth cap reached → accept leniently\n        : ValidateFeatureTest<Inner, Mode>\n      : false\n    : false\n\n// Every element of a tuple is a valid test.\ntype AllTests<\n  Parts extends string[],\n  Mode extends QueryMode,\n  Depth extends unknown[],\n> = Parts extends [infer H extends string, ...infer T extends string[]]\n  ? ValidateTest<H, Mode, Depth> extends true\n    ? AllTests<T, Mode, Depth>\n    : false\n  : true\n\ntype ValidateCondition<\n  S extends string,\n  Mode extends QueryMode,\n  Depth extends unknown[],\n> =\n  Trim<S> extends `not ${infer Rest}`\n    ? ValidateTest<Trim<Rest>, Mode, Depth>\n    : SplitByWord<Trim<S>, \"and\"> extends infer AndParts extends string[]\n      ? AndParts[\"length\"] extends 0 | 1\n        ? // no top-level `and` → try `or`\n          SplitByWord<Trim<S>, \"or\"> extends infer OrParts extends string[]\n          ? OrParts[\"length\"] extends 0 | 1\n            ? // single test\n              ValidateTest<Trim<S>, Mode, Depth>\n            : // all-or, and `and` must NOT appear at top level (no-mix)\n              HasWord<Trim<S>, \"and\"> extends true\n              ? false\n              : AllTests<OrParts, Mode, Depth>\n          : false\n        : // all-and, and `or` must NOT appear at top level (no-mix)\n          HasWord<Trim<S>, \"or\"> extends true\n          ? false\n          : AllTests<AndParts, Mode, Depth>\n      : false\n\n// =====================================================================\n// 8. STRICT VALIDATORS + CALL-SITE HELPERS\n// =====================================================================\n\ntype Depth4 = [unknown, unknown, unknown, unknown]\nexport type MediaType = \"all\" | \"screen\" | \"print\"\nexport type MediaModifier = \"only\" | \"not\"\n\n// Strip an optional leading `only `/`not ` + media-type, then validate.\ntype ValidateMedia<S extends string> =\n  Trim<S> extends `${MediaType}` | `only ${MediaType}` | `not ${MediaType}`\n    ? true // a bare (optionally modified) media type with no condition\n    : Trim<S> extends `${MediaType} and ${infer Cond}`\n      ? // <type> and <condition> — after a type only `and` may join (we reuse\n        // ValidateCondition; an `or` there would be invalid CSS anyway and the\n        // no-mix scan handles a mixed tail)\n        ValidateCondition<Trim<Cond>, \"media\", Depth4>\n      : Trim<S> extends `only ${MediaType} and ${infer Cond}`\n        ? ValidateCondition<Trim<Cond>, \"media\", Depth4>\n        : Trim<S> extends `not ${MediaType} and ${infer Cond}`\n          ? ValidateCondition<Trim<Cond>, \"media\", Depth4>\n          : // no media type → a bare condition (which itself handles `not <test>`)\n            ValidateCondition<Trim<S>, \"media\", Depth4>\n\n/**\n * Strict media-query validator. Resolves to `S` for a structurally +\n * dimensionally valid `@media` condition, `never` otherwise.\n *\n * @example\n * type A = MediaQueryLiteral<\"screen and (min-width: 600px)\"> // the literal\n * type B = MediaQueryLiteral<\"(width: 16/9)\">                 // never\n */\nexport type MediaQueryLiteral<S extends string> =\n  And<IsBalanced<Trim<S>>, ValidateMedia<S>> extends true ? S : never\n\n// Strip an optional leading <container-name> ident (head not `(` / `not`).\ntype ValidateContainer<S extends string> =\n  Trim<S> extends `(${string}`\n    ? ValidateCondition<Trim<S>, \"container\", Depth4>\n    : Trim<S> extends `not ${string}`\n      ? ValidateCondition<Trim<S>, \"container\", Depth4>\n      : Trim<S> extends `${string} ${infer Rest}`\n        ? // a leading name token, then the condition\n          ValidateCondition<Trim<Rest>, \"container\", Depth4>\n        : false\n\n/**\n * Strict container-query validator. Resolves to `S` for a valid `@container`\n * condition (size/style subset), `never` otherwise.\n *\n * @example\n * type A = ContainerQueryLiteral<\"(inline-size > 30rem)\"> // the literal\n * type B = ContainerQueryLiteral<\"(min-resolution: 2dppx)\"> // never\n */\nexport type ContainerQueryLiteral<S extends string> =\n  And<IsBalanced<Trim<S>>, ValidateContainer<S>> extends true ? S : never\n\n/**\n * Call-site media-query validator. Mirrors `cssIf()` / `cssTransform()`. An\n * invalid query becomes a type error at the argument.\n */\nexport const cssMediaQuery = <S extends string>(\n  value: S & MediaQueryLiteral<S>,\n): S => value\n\n/** Call-site container-query validator. */\nexport const cssContainerQuery = <S extends string>(\n  value: S & ContainerQueryLiteral<S>,\n): S => value\n\n// =====================================================================\n// 9. SUGGESTION STRINGS — IntelliSense + onChange return types\n//    Permissive (the strict tier is the gate), mirrors if-function.\n// =====================================================================\n\n/** Suggestion union — \"this is a media-query string\". */\nexport type MediaQueryString =\n  | `(${string})`\n  | `${MediaType}${string}`\n  | (string & {})\n\n/** Suggestion union — \"this is a container-query string\". */\nexport type ContainerQueryString = `(${string})` | (string & {})\n\n/** Either dialect. */\nexport type QueryString = MediaQueryString | ContainerQueryString\n\n/** Mode → output-string map (mirrors TransformStringMap). */\nexport interface QueryStringMap {\n  media: MediaQueryString\n  container: ContainerQueryString\n}\n\n// =====================================================================\n// 10. UTILITY TYPES — operate on query literals at the type level\n// =====================================================================\n\n// The feature name of one parenthesized test (or never). Guarded against\n// SplitFirstOp returning `never` (no operator) with the [X] extends [never]\n// idiom — otherwise the tuple pattern is vacuously satisfied and L widens.\ntype FeatureOfTest<S extends string> =\n  Trim<S> extends `(${infer Inner})`\n    ? [SplitFirstOp<Trim<Inner>>] extends [never]\n      ? Trim<Inner> extends `${infer F}:${string}`\n        ? Trim<F> // plain\n        : Trim<Inner> // boolean\n      : SplitFirstOp<Trim<Inner>> extends [\n            infer L extends string,\n            FeatureOperator,\n            infer Rest extends string,\n          ]\n        ? [SplitFirstOp<Trim<Rest>>] extends [never]\n          ? Trim<L> // range2 → left token\n          : SplitFirstOp<Trim<Rest>> extends [\n                infer Mid extends string,\n                FeatureOperator,\n                string,\n              ]\n            ? Trim<Mid> // range3 → middle token\n            : never\n        : never\n    : never\n\ntype FeatureNames<Parts extends string[]> = Parts extends [\n  infer H extends string,\n  ...infer T extends string[],\n]\n  ? FeatureOfTest<H> extends never\n    ? FeatureNames<T>\n    : [FeatureOfTest<H>, ...FeatureNames<T>]\n  : []\n\n// Flatten a query to its top-level parenthesized tests (drops leading\n// type/modifier/name + the `not`), splitting on whichever joiner is present.\ntype TopLevelTests<S extends string> =\n  Trim<S> extends `not ${infer R}`\n    ? TopLevelTests<R>\n    : SplitByWord<Trim<S>, \"and\"> extends infer A extends string[]\n      ? A[\"length\"] extends 0 | 1\n        ? SplitByWord<Trim<S>, \"or\">\n        : A\n      : []\n\n// Strip a leading media-type / modifier so FeaturesOf sees only tests. (A\n// container name is a bare ident — it won't match `(`, so FeatureOfTest yields\n// never for it and FeatureNames drops it.)\ntype StripLead<S extends string> =\n  Trim<S> extends `only ${infer R}`\n    ? StripLead<R>\n    : Trim<S> extends `${MediaType} and ${infer R}`\n      ? R\n      : Trim<S> extends `${MediaType}`\n        ? \"\"\n        : Trim<S>\n\n/**\n * The feature names used in a query (top level).\n *\n * @example\n * type T = FeaturesOf<\"(min-width: 600px) and (orientation: landscape)\">\n * //   [\"min-width\", \"orientation\"]\n */\nexport type FeaturesOf<S extends string> = FeatureNames<\n  TopLevelTests<StripLead<S>>\n>\n\n/**\n * The number of feature tests at the top level.\n *\n * @example\n * type C = FeatureCountOf<\"(min-width: 600px) and (max-width: 900px)\"> // 2\n */\nexport type FeatureCountOf<S extends string> = FeaturesOf<S>[\"length\"]\n\n// =====================================================================\n// 11. INTERNAL STATE — discriminated union (exported for advanced use)\n//\n// The editor's flat state: an optional leading modifier/type (media) or name\n// (container), one boolean joiner, a top-level `not`, and a list of tests.\n// The full nested grammar is handled by the types + runtime parser; the row\n// UI edits this flat case (design A8). Values are strings (they carry tokens).\n// =====================================================================\n\n/** One feature test, discriminated by `kind`. */\nexport type FeatureTest =\n  | { kind: \"boolean\"; feature: string }\n  | { kind: \"plain\"; feature: string; value: string }\n  | { kind: \"range2\"; feature: string; op: FeatureOperator; value: string }\n  | {\n      kind: \"range3\"\n      feature: string\n      op: FeatureOperator\n      value: string\n      op2: FeatureOperator\n      value2: string\n    }\n\n/** A parsed query node: a flat group of tests, or (from the parser) a nested\n * group / single test. The editor primarily uses `group`. */\nexport type QueryNode =\n  | { type: \"group\"; joiner: \"and\" | \"or\"; not: boolean; tests: FeatureTest[] }\n  | { type: \"test\"; not: boolean; test: FeatureTest }\n  | { type: \"raw\"; not: boolean; text: string }\n\n/** The editor's flat internal state. */\nexport interface QueryState {\n  mode: QueryMode\n  /** media only — `only` / `not` modifier on the media type. */\n  modifier?: MediaModifier\n  /** media only — the media type. */\n  mediaType?: MediaType\n  /** container only — the optional container name. */\n  containerName?: string\n  /** the single boolean joiner for the flat test list. */\n  joiner: \"and\" | \"or\"\n  /** a top-level `not`. */\n  not: boolean\n  /** the feature tests. */\n  tests: FeatureTest[]\n}\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/query-builder/query-builder.types.ts"
    },
    {
      "path": "src/components/ui/query-builder/query-builder.helpers.ts",
      "content": "// =====================================================================\n// query-builder.helpers.ts\n//\n// Pure runtime parse / format for CSS media + container queries. This is the\n// SUPERSET of the strict type tier: it parses the structure (modifier/type or\n// name, the boolean condition tree, the four feature-test shapes) but does NOT\n// gate on the known-feature whitelist — it classifies and keeps the literal\n// (mirrors if-function.helpers.ts). The single source of truth the UI drives off.\n// =====================================================================\n\nimport type {\n  FeatureOperator,\n  FeatureTest,\n  MediaModifier,\n  MediaType,\n  QueryMode,\n  QueryNode,\n  QueryState,\n} from \"./query-builder.types\"\n\n// ---------------------------------------------------------------------------\n// Paren-aware low-level scanners (runtime mirrors of the type splitters)\n//\n// All five scanners below walk `src` left→right tracking ()/[] nesting depth\n// and act only at depth 0. `scanDepth0` is the single shared primitive: it\n// applies the bracket depth adjustment for each char, then calls `visit(char,\n// index, depth)` with the post-adjustment depth (so a closing `)` is reported\n// at depth 0, matching the original loops). `visit` returns a control signal —\n// `undefined` to advance one char, `{ skip }` to jump ahead (variable-width\n// token consumption, `skip` must be >= 1), or `{ stop }` to end the walk early.\n// ---------------------------------------------------------------------------\n\ninterface ScanStep {\n  stop?: true\n  skip?: number\n}\n\nfunction scanDepth0(\n  src: string,\n  visit: (char: string, index: number, depth: number) => ScanStep | undefined,\n): void {\n  let depth = 0\n  let i = 0\n  while (i < src.length) {\n    const ch = src[i]\n    if (ch === \"(\" || ch === \"[\") depth++\n    else if (ch === \")\" || ch === \"]\") depth--\n    const step = visit(ch, i, depth)\n    if (step?.stop) return\n    i += step?.skip ?? 1\n  }\n}\n\n/** Split `src` on the whole word ` ${word} ` only at bracket depth 0. */\nfunction splitTopLevelWord(src: string, word: string): string[] {\n  const out: string[] = []\n  const token = ` ${word} `\n  let cur = \"\"\n  scanDepth0(src, (ch, i, depth) => {\n    if (depth === 0 && src.startsWith(token, i)) {\n      out.push(cur)\n      cur = \"\"\n      return { skip: token.length }\n    }\n    cur += ch\n    return undefined\n  })\n  out.push(cur)\n  return out.map((s) => s.trim()).filter((s) => s.length > 0)\n}\n\n/** Does the whole word ` ${word} ` occur at bracket depth 0? */\nfunction hasTopLevelWord(src: string, word: string): boolean {\n  const token = ` ${word} `\n  let found = false\n  scanDepth0(src, (_ch, i, depth) => {\n    if (depth === 0 && src.startsWith(token, i)) {\n      found = true\n      return { stop: true }\n    }\n    return undefined\n  })\n  return found\n}\n\n/** True iff every `(`/`[` is matched and depth never goes negative. */\nfunction isBalanced(src: string): boolean {\n  let ok = true\n  let last = 0\n  scanDepth0(src, (_ch, _i, depth) => {\n    last = depth\n    if (depth < 0) {\n      ok = false\n      return { stop: true }\n    }\n    return undefined\n  })\n  return ok && last === 0\n}\n\nconst OPS: readonly FeatureOperator[] = [\"<=\", \">=\", \"<\", \">\", \"=\"]\n\n/** Find the first operator at depth 0 → `{ index, op }`, or null. */\nfunction firstOp(\n  src: string,\n): { index: number; op: FeatureOperator; len: number } | null {\n  let hit: { index: number; op: FeatureOperator; len: number } | null = null\n  scanDepth0(src, (_ch, i, depth) => {\n    if (depth !== 0) return undefined\n    for (const op of OPS) {\n      if (src.startsWith(op, i)) {\n        hit = { index: i, op, len: op.length }\n        return { stop: true }\n      }\n    }\n    return undefined\n  })\n  return hit\n}\n\n/** Index of the first `:` at depth 0, or -1. */\nfunction firstTopLevelColon(src: string): number {\n  let at = -1\n  scanDepth0(src, (ch, i, depth) => {\n    if (ch === \":\" && depth === 0) {\n      at = i\n      return { stop: true }\n    }\n    return undefined\n  })\n  return at\n}\n\n// ---------------------------------------------------------------------------\n// Feature table — one row per KNOWN base feature (min-/max- stripped at lookup)\n// ---------------------------------------------------------------------------\n\ntype FeatureKind = \"length\" | \"resolution\" | \"ratio\" | \"integer\" | \"enum\"\n\ninterface FeatureRow {\n  modes: readonly QueryMode[]\n  kind: FeatureKind\n  enums?: readonly string[]\n}\n\nconst FEATURE_TABLE: Record<string, FeatureRow> = {\n  // --- length ---\n  width: { modes: [\"media\", \"container\"], kind: \"length\" },\n  height: { modes: [\"media\", \"container\"], kind: \"length\" },\n  \"inline-size\": { modes: [\"container\"], kind: \"length\" },\n  \"block-size\": { modes: [\"container\"], kind: \"length\" },\n  \"device-width\": { modes: [\"media\"], kind: \"length\" },\n  \"device-height\": { modes: [\"media\"], kind: \"length\" },\n  // --- ratio ---\n  \"aspect-ratio\": { modes: [\"media\", \"container\"], kind: \"ratio\" },\n  \"device-aspect-ratio\": { modes: [\"media\"], kind: \"ratio\" },\n  // --- resolution ---\n  resolution: { modes: [\"media\"], kind: \"resolution\" },\n  // --- integer ---\n  color: { modes: [\"media\"], kind: \"integer\" },\n  \"color-index\": { modes: [\"media\"], kind: \"integer\" },\n  monochrome: { modes: [\"media\"], kind: \"integer\" },\n  \"device-pixel-ratio\": { modes: [\"media\"], kind: \"integer\" },\n  grid: { modes: [\"media\"], kind: \"integer\" },\n  // --- enum ---\n  orientation: {\n    modes: [\"media\", \"container\"],\n    kind: \"enum\",\n    enums: [\"portrait\", \"landscape\"],\n  },\n  \"prefers-color-scheme\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"light\", \"dark\"],\n  },\n  \"prefers-reduced-motion\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"no-preference\", \"reduce\"],\n  },\n  \"prefers-reduced-transparency\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"no-preference\", \"reduce\"],\n  },\n  \"prefers-contrast\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"no-preference\", \"more\", \"less\", \"custom\"],\n  },\n  \"forced-colors\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"active\"],\n  },\n  hover: { modes: [\"media\"], kind: \"enum\", enums: [\"none\", \"hover\"] },\n  \"any-hover\": { modes: [\"media\"], kind: \"enum\", enums: [\"none\", \"hover\"] },\n  pointer: {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"coarse\", \"fine\"],\n  },\n  \"any-pointer\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"coarse\", \"fine\"],\n  },\n  \"color-gamut\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"srgb\", \"p3\", \"rec2020\"],\n  },\n  \"dynamic-range\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"standard\", \"high\"],\n  },\n  \"video-dynamic-range\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"standard\", \"high\"],\n  },\n  scripting: {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"initial-only\", \"enabled\"],\n  },\n  update: {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"slow\", \"fast\"],\n  },\n  \"overflow-block\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"scroll\", \"paged\"],\n  },\n  \"overflow-inline\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\"none\", \"scroll\"],\n  },\n  \"display-mode\": {\n    modes: [\"media\"],\n    kind: \"enum\",\n    enums: [\n      \"fullscreen\",\n      \"standalone\",\n      \"minimal-ui\",\n      \"browser\",\n      \"window-controls-overlay\",\n    ],\n  },\n}\n\nfunction stripMinMax(feature: string): string {\n  if (feature.startsWith(\"min-\")) return feature.slice(4)\n  if (feature.startsWith(\"max-\")) return feature.slice(4)\n  return feature\n}\n\nfunction lookup(feature: string, mode: QueryMode): FeatureRow | null {\n  const row = FEATURE_TABLE[stripMinMax(feature.trim())]\n  if (row === undefined) return null\n  return row.modes.includes(mode) ? row : null\n}\n\n/** Classify a feature for a mode, or \"unknown\". */\nexport function featureKind(\n  feature: string,\n  mode: QueryMode,\n): FeatureKind | \"unknown\" {\n  const row = lookup(feature, mode)\n  return row === null ? \"unknown\" : row.kind\n}\n\n/**\n * The feature `<select>` options for a mode. Numerically-bounded features\n * (length / resolution / ratio / integer) also expose their legacy `min-` /\n * `max-` prefixed variants so the row select can hold e.g. `min-width`.\n */\nexport function featuresFor(mode: QueryMode): readonly string[] {\n  const out: string[] = []\n  for (const f of Object.keys(FEATURE_TABLE)) {\n    const row = FEATURE_TABLE[f]\n    if (!row.modes.includes(mode)) continue\n    out.push(f)\n    if (row.kind !== \"enum\") {\n      out.push(`min-${f}`, `max-${f}`)\n    }\n  }\n  return out.sort()\n}\n\n/** The enum keyword options for a feature, or null if it is not an enum. */\nexport function enumOptionsFor(feature: string): readonly string[] | null {\n  const row = FEATURE_TABLE[stripMinMax(feature.trim())]\n  if (row === undefined || row.kind !== \"enum\") return null\n  return row.enums ?? null\n}\n\n// ---------------------------------------------------------------------------\n// row-editor pure transforms (UI-facing, no React)\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the trailing CSS unit of a length value (defaults to `px`) so length\n * values flow through the unit-aware `UnitInput`.\n */\nexport function unitOf(value: string): string {\n  const m = value.trim().match(/[a-z%]+$/i)\n  return m ? m[0] : \"px\"\n}\n\n/**\n * Re-shape a feature test when its shape selector changes, keeping the feature.\n * `range3` is a numeric range (`v op f op v`) so it only carries the current\n * value when the feature is length-kind; for a discrete feature (enum/ratio/…)\n * duplicating the keyword would emit an invalid `kw <= f <= kw`, so reset both\n * bounds to a numeric default instead.\n */\nexport function reshape(\n  test: FeatureTest,\n  shape: FeatureTest[\"kind\"],\n  mode: QueryMode,\n): FeatureTest {\n  const feature = test.feature\n  const value = \"value\" in test ? test.value : \"0\"\n  switch (shape) {\n    case \"boolean\":\n      return { kind: \"boolean\", feature }\n    case \"plain\":\n      return { kind: \"plain\", feature, value }\n    case \"range2\":\n      return { kind: \"range2\", feature, op: \">=\", value }\n    case \"range3\": {\n      const bound = featureKind(feature, mode) === \"length\" ? value : \"0px\"\n      return {\n        kind: \"range3\",\n        feature,\n        op: \"<=\",\n        value: bound,\n        op2: \"<=\",\n        value2: bound,\n      }\n    }\n  }\n}\n\n// ---------------------------------------------------------------------------\n// parseFeatureTest — classify one (unwrapped) feature test\n// ---------------------------------------------------------------------------\n\n/**\n * Classify the content of a `( … )` feature test into a discriminated record,\n * or `null` if it is empty / malformed (an empty value, a lone operator).\n */\nexport function parseFeatureTest(inner: string): FeatureTest | null {\n  const s = inner.trim()\n  if (s === \"\") return null\n\n  // range: one or two top-level operators\n  const op1 = firstOp(s)\n  if (op1 !== null) {\n    const left = s.slice(0, op1.index).trim()\n    const afterOp1 = s.slice(op1.index + op1.len)\n    const op2 = firstOp(afterOp1)\n    if (op2 !== null) {\n      // range3: left op1 mid op2 right\n      const mid = afterOp1.slice(0, op2.index).trim()\n      const right = afterOp1.slice(op2.index + op2.len).trim()\n      if (left === \"\" || mid === \"\" || right === \"\") return null\n      return {\n        kind: \"range3\",\n        feature: mid,\n        op: op1.op,\n        value: left,\n        op2: op2.op,\n        value2: right,\n      }\n    }\n    // range2: left op1 right (left is the feature)\n    const right = afterOp1.trim()\n    if (left === \"\" || right === \"\") return null\n    return { kind: \"range2\", feature: left, op: op1.op, value: right }\n  }\n\n  // plain `feature: value`\n  const colon = firstTopLevelColon(s)\n  if (colon !== -1) {\n    const feature = s.slice(0, colon).trim()\n    const value = s.slice(colon + 1).trim()\n    if (feature === \"\" || value === \"\") return null\n    return { kind: \"plain\", feature, value }\n  }\n\n  // boolean `feature`\n  return { kind: \"boolean\", feature: s }\n}\n\n// ---------------------------------------------------------------------------\n// parseQuery — string → QueryNode | null\n// ---------------------------------------------------------------------------\n\nconst MEDIA_TYPES = new Set([\"all\", \"screen\", \"print\"])\nconst MODIFIERS = new Set([\"only\", \"not\"])\n\n/** Parse a balanced `( … )` test or nested group into a QueryNode. */\nfunction parseTestToken(token: string, mode: QueryMode): QueryNode | null {\n  let s = token.trim()\n  let not = false\n  if (s.startsWith(\"not \")) {\n    not = true\n    s = s.slice(4).trim()\n  }\n  if (!s.startsWith(\"(\") || !s.endsWith(\")\") || !isBalanced(s)) return null\n  const inner = s.slice(1, -1).trim()\n  // nested group? inner starts with `(` / `not ` or has a top-level joiner\n  const nested =\n    inner.startsWith(\"(\") ||\n    inner.startsWith(\"not \") ||\n    hasTopLevelWord(inner, \"and\") ||\n    hasTopLevelWord(inner, \"or\")\n  if (nested) {\n    const sub = parseCondition(inner, mode)\n    if (sub === null) return null\n    // mark the group's own negation\n    return { ...sub, not: sub.not !== not }\n  }\n  const test = parseFeatureTest(inner)\n  if (test === null) return null\n  return { type: \"test\", not, test }\n}\n\n/** Parse a boolean condition (handles `not`, and/or splits) into a QueryNode. */\nfunction parseCondition(src: string, mode: QueryMode): QueryNode | null {\n  let s = src.trim()\n  if (s === \"\") return null\n\n  let not = false\n  // a leading `not ` that negates the whole condition (a single test follows)\n  if (\n    s.startsWith(\"not \") &&\n    !hasTopLevelWord(s, \"and\") &&\n    !hasTopLevelWord(s, \"or\")\n  ) {\n    not = true\n    s = s.slice(4).trim()\n  }\n\n  // pick the joiner present at top level\n  const andParts = splitTopLevelWord(s, \"and\")\n  const orParts = splitTopLevelWord(s, \"or\")\n  let joiner: \"and\" | \"or\" = \"and\"\n  let parts: string[]\n  if (andParts.length > 1) {\n    joiner = \"and\"\n    parts = andParts\n  } else if (orParts.length > 1) {\n    joiner = \"or\"\n    parts = orParts\n  } else {\n    // single test (possibly a parenthesized group)\n    const node = parseTestToken(s, mode)\n    if (node === null) return null\n    if (node.type === \"group\") return { ...node, not: node.not !== not }\n    return { type: \"group\", joiner: \"and\", not, tests: nodeTests(node) }\n  }\n\n  // flatten each part into its FeatureTest(s); a nested group becomes raw-ish —\n  // for the flat editor we keep its tests if it is itself a simple group.\n  const tests: FeatureTest[] = []\n  for (const part of parts) {\n    const node = parseTestToken(part, mode)\n    if (node === null) return null\n    tests.push(...nodeTests(node))\n  }\n  return { type: \"group\", joiner, not, tests }\n}\n\n/** Extract the flat FeatureTest list from a node (for the flat editor model). */\nfunction nodeTests(node: QueryNode): FeatureTest[] {\n  if (node.type === \"test\") return [node.test]\n  if (node.type === \"group\") return node.tests\n  return []\n}\n\n// ---------------------------------------------------------------------------\n// stripLead — peel the optional leading media-type+modifier or container name\n// ---------------------------------------------------------------------------\n\n/**\n * The leading-token analysis shared by `parseQuery` and `parseQueryState`.\n *  - `invalid`: a media type was present but not followed by `and <cond>`.\n *  - `bare`:    a (modified) media type with no condition at all.\n *  - `rest`:    the remaining condition source, with any modifier / type /\n *               container name peeled off (and surfaced for the state path).\n */\ntype LeadResult =\n  | { status: \"invalid\" }\n  | { status: \"bare\"; modifier?: MediaModifier; mediaType?: MediaType }\n  | {\n      status: \"rest\"\n      modifier?: MediaModifier\n      mediaType?: MediaType\n      containerName?: string\n      rest: string\n    }\n\nfunction stripLead(trimmed: string, mode: QueryMode): LeadResult {\n  if (mode === \"media\") {\n    const tokens = trimmed.split(/\\s+/)\n    let modifier: MediaModifier | undefined\n    let mediaType: MediaType | undefined\n    let consumed = 0\n    if (MODIFIERS.has(tokens[0]) && MEDIA_TYPES.has(tokens[1] ?? \"\")) {\n      modifier = tokens[0] as MediaModifier\n      mediaType = tokens[1] as MediaType\n      consumed = 2\n    } else if (MEDIA_TYPES.has(tokens[0])) {\n      mediaType = tokens[0] as MediaType\n      consumed = 1\n    }\n    if (consumed === 0) return { status: \"rest\", rest: trimmed }\n    const after = tokens.slice(consumed)\n    if (after.length === 0) return { status: \"bare\", modifier, mediaType }\n    if (after[0] !== \"and\") return { status: \"invalid\" }\n    return {\n      status: \"rest\",\n      modifier,\n      mediaType,\n      rest: after.slice(1).join(\" \"),\n    }\n  }\n\n  // container: optional leading name ident (head not `(` and not `not`)\n  if (!trimmed.startsWith(\"(\") && !trimmed.startsWith(\"not \")) {\n    const space = trimmed.indexOf(\" \")\n    if (space !== -1) {\n      return {\n        status: \"rest\",\n        containerName: trimmed.slice(0, space).trim(),\n        rest: trimmed.slice(space + 1).trim(),\n      }\n    }\n  }\n  return { status: \"rest\", rest: trimmed }\n}\n\n/**\n * Parse a media / container query string into a QueryNode, or an error.\n * Strips the optional leading media-type + modifier (media) or container name\n * (container), then parses the condition.\n */\nexport function parseQuery(\n  src: string,\n  mode: QueryMode,\n): { node: QueryNode | null; error: string | null } {\n  const trimmed = src.trim()\n  if (trimmed === \"\") return { node: null, error: \"empty query\" }\n  if (!isBalanced(trimmed)) return { node: null, error: \"unbalanced parens\" }\n\n  const lead = stripLead(trimmed, mode)\n  if (lead.status === \"invalid\") {\n    return { node: null, error: \"expected `and` after media type\" }\n  }\n  if (lead.status === \"bare\") {\n    // a bare (modified) media type — no condition\n    return {\n      node: { type: \"group\", joiner: \"and\", not: false, tests: [] },\n      error: null,\n    }\n  }\n\n  const node = parseCondition(lead.rest, mode)\n  if (node === null) return { node: null, error: \"could not parse condition\" }\n  return { node, error: null }\n}\n\n// ---------------------------------------------------------------------------\n// parseQueryState — string → flat QueryState | null (drives the row editor)\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a query string into the flat editor state: the leading modifier/type\n * (media) or name (container), the single joiner, the top-level `not`, and the\n * flat feature-test list. Returns `null` if the string does not parse. Nested\n * groups collapse to their flat tests (design A8); the casual tier preserves\n * arbitrary strings, but the row editor edits this flat shape.\n */\nexport function parseQueryState(\n  src: string,\n  mode: QueryMode,\n): QueryState | null {\n  const trimmed = src.trim()\n  if (trimmed === \"\") return null\n  if (!isBalanced(trimmed)) return null\n\n  const lead = stripLead(trimmed, mode)\n  if (lead.status === \"invalid\") return null\n\n  const { modifier, mediaType } = lead\n  if (lead.status === \"bare\") {\n    return { mode, modifier, mediaType, joiner: \"and\", not: false, tests: [] }\n  }\n  const { containerName } = lead\n\n  const node = parseCondition(lead.rest, mode)\n  if (node === null || node.type === \"raw\") return null\n  if (node.type === \"test\") {\n    return {\n      mode,\n      modifier,\n      mediaType,\n      containerName,\n      joiner: \"and\",\n      not: node.not,\n      tests: [node.test],\n    }\n  }\n  return {\n    mode,\n    modifier,\n    mediaType,\n    containerName,\n    joiner: node.joiner,\n    not: node.not,\n    tests: node.tests,\n  }\n}\n\n// ---------------------------------------------------------------------------\n// format — canonical serialization\n// ---------------------------------------------------------------------------\n\n/** Serialize one feature test to its `feature[: value | op value | …]` body. */\nexport function formatFeatureTest(test: FeatureTest): string {\n  switch (test.kind) {\n    case \"boolean\":\n      return test.feature\n    case \"plain\":\n      return `${test.feature}: ${test.value}`\n    case \"range2\":\n      return `${test.feature} ${test.op} ${test.value}`\n    case \"range3\":\n      return `${test.value} ${test.op} ${test.feature} ${test.op2} ${test.value2}`\n  }\n}\n\n/** Wrap a feature test in its parens. */\nfunction testToCss(test: FeatureTest): string {\n  return `(${formatFeatureTest(test)})`\n}\n\n/** Canonical re-serialization of a parsed node. */\nexport function formatQuery(node: QueryNode, _mode: QueryMode): string {\n  if (node.type === \"raw\") {\n    return node.not ? `not ${node.text}` : node.text\n  }\n  if (node.type === \"test\") {\n    const body = testToCss(node.test)\n    return node.not ? `not ${body}` : body\n  }\n  const body = node.tests.map(testToCss).join(` ${node.joiner} `)\n  if (!node.not) return body\n  // not binds one operand → wrap multi-test conditions\n  return node.tests.length > 1 ? `not (${body})` : `not ${body}`\n}\n\n// ---------------------------------------------------------------------------\n// queryToString — from the flat editor state\n// ---------------------------------------------------------------------------\n\n/** Serialize the flat editor state into a query string. */\nexport function queryToString(state: QueryState): string {\n  const condition = state.tests.map(testToCss).join(` ${state.joiner} `)\n\n  if (state.mode === \"media\") {\n    const lead = [state.modifier, state.mediaType].filter(Boolean).join(\" \")\n    if (state.tests.length === 0) return lead\n    const negated = applyNot(condition, state.tests.length, state.not)\n    return lead === \"\" ? negated : `${lead} and ${negated}`\n  }\n\n  // container\n  const negated = applyNot(condition, state.tests.length, state.not)\n  return state.containerName ? `${state.containerName} ${negated}` : negated\n}\n\nfunction applyNot(condition: string, count: number, not: boolean): string {\n  if (!not) return condition\n  return count > 1 ? `not (${condition})` : `not ${condition}`\n}\n\n// ---------------------------------------------------------------------------\n// defaults\n// ---------------------------------------------------------------------------\n\n/** A sensible default feature test for a mode. */\nexport function defaultFeatureTest(mode: QueryMode): FeatureTest {\n  return mode === \"container\"\n    ? { kind: \"range2\", feature: \"inline-size\", op: \">\", value: \"400px\" }\n    : { kind: \"range2\", feature: \"width\", op: \">=\", value: \"600px\" }\n}\n\n/** A default flat query state (one test). */\nexport function defaultQuery(mode: QueryMode): QueryState {\n  return {\n    mode,\n    mediaType: mode === \"media\" ? \"screen\" : undefined,\n    joiner: \"and\",\n    not: false,\n    tests: [defaultFeatureTest(mode)],\n  }\n}\n\n// ---------------------------------------------------------------------------\n// matchesNow — media only, guarded\n// ---------------------------------------------------------------------------\n\n/**\n * Whether a media query matches right now via `window.matchMedia`. Returns\n * `null` for container mode (no standard live-match API) or when `matchMedia`\n * is unavailable (SSR / older jsdom).\n */\nexport function matchesNow(query: string, mode: QueryMode): boolean | null {\n  if (mode !== \"media\") return null\n  if (\n    typeof window === \"undefined\" ||\n    typeof window.matchMedia !== \"function\"\n  ) {\n    return null\n  }\n  try {\n    return window.matchMedia(query).matches\n  } catch {\n    return null\n  }\n}\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/query-builder.helpers.ts"
    },
    {
      "path": "src/components/ui/query-builder/feature-test-row.tsx",
      "content": "\"use client\"\n\nimport { Input } from \"@/components/ui/input\"\nimport { UnitInput } from \"@/components/ui/unit-input\"\nimport { cn } from \"@/lib/utils\"\nimport { MiniSelect } from \"./mini-select\"\nimport {\n  enumOptionsFor,\n  featureKind,\n  featuresFor,\n  reshape,\n  unitOf,\n} from \"./query-builder.helpers\"\nimport type {\n  FeatureOperator,\n  FeatureTest,\n  QueryMode,\n} from \"./query-builder.types\"\n\nconst OPERATORS: readonly FeatureOperator[] = [\"<\", \"<=\", \">\", \">=\", \"=\"]\n\n// ---------------------------------------------------------------------------\n// FeatureTestRow (public)\n// ---------------------------------------------------------------------------\n\nexport interface FeatureTestRowProps {\n  mode: QueryMode\n  test: FeatureTest\n  onChange: (test: FeatureTest) => void\n  onRemove: () => void\n  /** Positional index — used only for stable control labels. */\n  index?: number\n  className?: string\n}\n\nexport function FeatureTestRow({\n  mode,\n  test,\n  onChange,\n  onRemove,\n  index,\n  className,\n}: FeatureTestRowProps) {\n  const n = index === undefined ? \"\" : ` ${index + 1}`\n  const features = featuresFor(mode)\n  const kind = featureKind(test.feature, mode)\n  const enums = enumOptionsFor(test.feature)\n  const shape = test.kind\n\n  const setFeature = (feature: string) => {\n    // when the new feature is an enum, snap a plain value into a valid keyword\n    const opts = enumOptionsFor(feature)\n    if (opts && \"value\" in test && !opts.includes(test.value)) {\n      onChange({ kind: \"plain\", feature, value: opts[0] })\n      return\n    }\n    onChange({ ...test, feature })\n  }\n\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=\"font-mono text-muted-foreground text-xs\">(</span>\n      <MiniSelect\n        aria-label={`feature${n}`}\n        value={features.includes(test.feature) ? test.feature : \"\"}\n        onValueChange={setFeature}\n      >\n        {!features.includes(test.feature) && (\n          <option value=\"\">{test.feature || \"(feature)\"}</option>\n        )}\n        {features.map((f) => (\n          <option key={f} value={f}>\n            {f}\n          </option>\n        ))}\n      </MiniSelect>\n\n      <ShapeSelect\n        label={`shape${n}`}\n        value={shape}\n        onChange={(s) => onChange(reshape(test, s, mode))}\n      />\n\n      {test.kind === \"plain\" && (\n        <>\n          <span className=\"font-mono text-muted-foreground text-xs\">:</span>\n          <ValueField\n            label={`value${n}`}\n            kind={kind}\n            value={test.value}\n            onChange={(value) => onChange({ ...test, value })}\n            enums={enums}\n          />\n        </>\n      )}\n\n      {test.kind === \"range2\" && (\n        <>\n          <OperatorSelect\n            label={`operator${n}`}\n            value={test.op}\n            onChange={(op) => onChange({ ...test, op })}\n          />\n          <ValueField\n            label={`value${n}`}\n            kind={kind}\n            value={test.value}\n            onChange={(value) => onChange({ ...test, value })}\n            enums={enums}\n          />\n        </>\n      )}\n\n      {test.kind === \"range3\" && (\n        <>\n          <OperatorSelect\n            label={`operator${n}`}\n            value={test.op}\n            onChange={(op) => onChange({ ...test, op })}\n          />\n          <ValueField\n            label={`value${n}`}\n            kind=\"length\"\n            value={test.value}\n            onChange={(value) => onChange({ ...test, value })}\n            enums={null}\n          />\n          <OperatorSelect\n            label={`operator2${n}`}\n            value={test.op2}\n            onChange={(op2) => onChange({ ...test, op2 })}\n          />\n          <ValueField\n            label={`value2${n}`}\n            kind=\"length\"\n            value={test.value2}\n            onChange={(value2) => onChange({ ...test, value2 })}\n            enums={null}\n          />\n        </>\n      )}\n\n      <span className=\"font-mono text-muted-foreground text-xs\">)</span>\n      <button\n        type=\"button\"\n        onClick={onRemove}\n        aria-label={`Remove feature test${n}`}\n        className=\"ml-auto 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\n// ---------------------------------------------------------------------------\n// ShapeSelect (internal) — boolean / : / op / range\n// ---------------------------------------------------------------------------\n\nfunction ShapeSelect({\n  label,\n  value,\n  onChange,\n}: {\n  label: string\n  value: FeatureTest[\"kind\"]\n  onChange: (value: FeatureTest[\"kind\"]) => void\n}) {\n  return (\n    <MiniSelect\n      aria-label={label}\n      value={value}\n      onValueChange={(v) => onChange(v as FeatureTest[\"kind\"])}\n    >\n      <option value=\"boolean\">exists</option>\n      <option value=\"plain\">: value</option>\n      <option value=\"range2\">op value</option>\n      <option value=\"range3\">v op f op v</option>\n    </MiniSelect>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// OperatorSelect (internal)\n// ---------------------------------------------------------------------------\n\nfunction OperatorSelect({\n  label,\n  value,\n  onChange,\n}: {\n  label: string\n  value: FeatureOperator\n  onChange: (value: FeatureOperator) => void\n}) {\n  return (\n    <MiniSelect\n      aria-label={label}\n      value={value}\n      onValueChange={(v) => onChange(v as FeatureOperator)}\n    >\n      {OPERATORS.map((op) => (\n        <option key={op} value={op}>\n          {op}\n        </option>\n      ))}\n    </MiniSelect>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ValueField (internal) — length → unit-input; enum → select; else → input\n// ---------------------------------------------------------------------------\n\nfunction ValueField({\n  label,\n  kind,\n  value,\n  onChange,\n  enums,\n}: {\n  label: string\n  kind: ReturnType<typeof featureKind>\n  value: string\n  onChange: (value: string) => void\n  enums: readonly string[] | null\n}) {\n  if (enums) {\n    return (\n      <MiniSelect\n        aria-label={label}\n        value={enums.includes(value) ? value : \"\"}\n        onValueChange={onChange}\n      >\n        {!enums.includes(value) && <option value=\"\">{value || \"—\"}</option>}\n        {enums.map((k) => (\n          <option key={k} value={k}>\n            {k}\n          </option>\n        ))}\n      </MiniSelect>\n    )\n  }\n  if (kind === \"length\") {\n    return (\n      <div className=\"w-[120px]\">\n        <UnitInput\n          aria-label={label}\n          unit={unitOf(value)}\n          value={value}\n          onChange={onChange}\n        />\n      </div>\n    )\n  }\n  return (\n    <Input\n      aria-label={label}\n      value={value}\n      spellCheck={false}\n      autoComplete=\"off\"\n      placeholder={kind === \"ratio\" ? \"16/9\" : \"value\"}\n      onChange={(e) => onChange(e.target.value)}\n      className=\"h-8 w-[110px] font-mono text-xs\"\n    />\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/feature-test-row.tsx"
    },
    {
      "path": "src/components/ui/query-builder/mini-select.tsx",
      "content": "\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport { cn } from \"@/lib/utils\"\n\n// ---------------------------------------------------------------------------\n// MiniSelect — the compact `<select>` chrome shared by every query-builder\n// dropdown (media type/modifier, joiner, shape, operator, feature, enum value).\n// Owns the one class-string so the controls never drift (they previously hand-\n// rolled `h-8 … text-xs` ~6× plus one stray `text-[11px]`). `onValueChange`\n// hands back the raw `e.target.value`; callers that model an optional value map\n// `\"\" ⇄ undefined` themselves (the placeholder text differs per control).\n// ---------------------------------------------------------------------------\n\nexport const selectClass =\n  \"h-8 rounded-md border border-input bg-background px-1 font-mono text-xs\"\n\nexport interface MiniSelectProps {\n  \"aria-label\": string\n  value: string\n  onValueChange: (value: string) => void\n  children: ReactNode\n  className?: string\n}\n\nexport function MiniSelect({\n  \"aria-label\": ariaLabel,\n  value,\n  onValueChange,\n  children,\n  className,\n}: MiniSelectProps) {\n  return (\n    <select\n      aria-label={ariaLabel}\n      value={value}\n      onChange={(e) => onValueChange(e.target.value)}\n      className={cn(selectClass, className)}\n    >\n      {children}\n    </select>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/mini-select.tsx"
    },
    {
      "path": "src/components/ui/query-builder/query-builder-fields.tsx",
      "content": "\"use client\"\n\nimport { Input } from \"@/components/ui/input\"\nimport { cn } from \"@/lib/utils\"\nimport { MiniSelect } from \"./mini-select\"\nimport type { MediaModifier, MediaType } from \"./query-builder.types\"\n\n// ---------------------------------------------------------------------------\n// Shared query-level field controls: the media type/modifier or container-name\n// header, the and/or joiner, the negate toggle, plus the add-row + live-string\n// affordances. All are presentational; the container owns the state.\n// ---------------------------------------------------------------------------\n\nconst MEDIA_TYPES: readonly MediaType[] = [\"all\", \"screen\", \"print\"]\n\n// ---------------------------------------------------------------------------\n// MediaTypeSelect (public)\n// ---------------------------------------------------------------------------\n\nexport interface MediaTypeSelectProps {\n  modifier: MediaModifier | undefined\n  mediaType: MediaType | undefined\n  onChange: (next: {\n    modifier: MediaModifier | undefined\n    mediaType: MediaType | undefined\n  }) => void\n  className?: string\n}\n\nexport function MediaTypeSelect({\n  modifier,\n  mediaType,\n  onChange,\n  className,\n}: MediaTypeSelectProps) {\n  return (\n    <div className={cn(\"flex items-center gap-1.5\", className)}>\n      <MiniSelect\n        aria-label=\"media modifier\"\n        value={modifier ?? \"\"}\n        onValueChange={(v) =>\n          onChange({\n            modifier: (v || undefined) as MediaModifier | undefined,\n            mediaType,\n          })\n        }\n      >\n        <option value=\"\">(no modifier)</option>\n        <option value=\"only\">only</option>\n        <option value=\"not\">not</option>\n      </MiniSelect>\n      <MiniSelect\n        aria-label=\"media type\"\n        value={mediaType ?? \"\"}\n        onValueChange={(v) =>\n          onChange({\n            modifier,\n            mediaType: (v || undefined) as MediaType | undefined,\n          })\n        }\n      >\n        <option value=\"\">(any)</option>\n        {MEDIA_TYPES.map((t) => (\n          <option key={t} value={t}>\n            {t}\n          </option>\n        ))}\n      </MiniSelect>\n    </div>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ContainerNameInput (public)\n// ---------------------------------------------------------------------------\n\nexport interface ContainerNameInputProps {\n  name: string\n  onChange: (name: string) => void\n  className?: string\n}\n\nexport function ContainerNameInput({\n  name,\n  onChange,\n  className,\n}: ContainerNameInputProps) {\n  return (\n    <Input\n      aria-label=\"container name\"\n      value={name}\n      spellCheck={false}\n      autoComplete=\"off\"\n      placeholder=\"(optional name)\"\n      onChange={(e) => onChange(e.target.value)}\n      className={cn(\"h-8 w-[160px] font-mono text-xs\", className)}\n    />\n  )\n}\n\n// ---------------------------------------------------------------------------\n// JoinerSelect (public)\n// ---------------------------------------------------------------------------\n\nexport interface JoinerSelectProps {\n  value: \"and\" | \"or\"\n  onChange: (value: \"and\" | \"or\") => void\n  className?: string\n}\n\nexport function JoinerSelect({\n  value,\n  onChange,\n  className,\n}: JoinerSelectProps) {\n  return (\n    <MiniSelect\n      aria-label=\"combine tests with\"\n      value={value}\n      onValueChange={(v) => onChange(v as \"and\" | \"or\")}\n      className={className}\n    >\n      <option value=\"and\">and</option>\n      <option value=\"or\">or</option>\n    </MiniSelect>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// NotToggle (public)\n// ---------------------------------------------------------------------------\n\nexport interface NotToggleProps {\n  checked: boolean\n  onChange: (checked: boolean) => void\n  className?: string\n}\n\nexport function NotToggle({ checked, onChange, className }: NotToggleProps) {\n  return (\n    <label\n      className={cn(\n        \"flex h-8 items-center gap-1.5 rounded-md border border-input px-2 font-mono text-muted-foreground text-xs\",\n        className,\n      )}\n    >\n      <input\n        type=\"checkbox\"\n        aria-label=\"negate the whole query (not)\"\n        checked={checked}\n        onChange={(e) => onChange(e.target.checked)}\n        className=\"size-3.5\"\n      />\n      not\n    </label>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// AddTestButton (internal — used by the container)\n// ---------------------------------------------------------------------------\n\nexport function AddTestButton({ onAdd }: { onAdd: () => void }) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onAdd}\n      aria-label=\"Add a feature test\"\n      className=\"h-9 w-full rounded-md border border-dashed bg-background px-2 font-mono text-muted-foreground text-xs hover:text-foreground\"\n    >\n      + add feature test\n    </button>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// LiveString (internal — used by the container)\n// ---------------------------------------------------------------------------\n\nexport function 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/query-builder/query-builder-fields.tsx"
    },
    {
      "path": "src/components/ui/query-builder/query-preview.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { matchesNow } from \"./query-builder.helpers\"\nimport type { QueryMode } from \"./query-builder.types\"\n\n// ---------------------------------------------------------------------------\n// QueryPreview (public) — live \"matches now?\" indicator (media only)\n// ---------------------------------------------------------------------------\n\nexport interface QueryPreviewProps {\n  value: string\n  mode: QueryMode\n  className?: string\n}\n\nexport function QueryPreview({ value, mode, className }: QueryPreviewProps) {\n  const [matches, setMatches] = useState<boolean | null>(() =>\n    matchesNow(value, mode),\n  )\n\n  useEffect(() => {\n    if (mode !== \"media\") {\n      setMatches(null)\n      return\n    }\n    if (\n      typeof window === \"undefined\" ||\n      typeof window.matchMedia !== \"function\"\n    ) {\n      setMatches(null)\n      return\n    }\n    let mql: MediaQueryList\n    try {\n      mql = window.matchMedia(value)\n    } catch {\n      setMatches(null)\n      return\n    }\n    setMatches(mql.matches)\n    const onChange = (e: MediaQueryListEvent) => setMatches(e.matches)\n    mql.addEventListener?.(\"change\", onChange)\n    return () => mql.removeEventListener?.(\"change\", onChange)\n  }, [value, mode])\n\n  return (\n    <div className={cn(\"space-y-2 rounded-lg border p-3\", className)}>\n      <div className=\"flex items-center justify-between\">\n        <div className=\"text-muted-foreground text-xs\">preview</div>\n        {mode === \"media\" ? (\n          <span\n            role=\"status\"\n            className={cn(\n              \"rounded px-2 py-0.5 font-mono text-[10px]\",\n              matches\n                ? \"bg-emerald-500/15 text-emerald-400\"\n                : \"bg-muted text-muted-foreground\",\n            )}\n          >\n            {matches === null\n              ? \"matchMedia unavailable\"\n              : matches\n                ? \"matches now ✓\"\n                : \"no match now\"}\n          </span>\n        ) : (\n          <span className=\"font-mono text-[10px] text-muted-foreground/70\">\n            container — match depends on the element size\n          </span>\n        )}\n      </div>\n      <p className=\"text-[10px] text-muted-foreground/70 leading-relaxed\">\n        {mode === \"media\" ? (\n          <>\n            Live result from{\" \"}\n            <code className=\"font-mono\">window.matchMedia()</code>, updated as\n            the viewport changes.\n          </>\n        ) : (\n          <>\n            Container queries match against a sized container element at\n            runtime; there is no global live-match API, so no indicator is shown\n            here.\n          </>\n        )}\n      </p>\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/query-builder/query-preview.tsx"
    }
  ],
  "type": "registry:ui"
}