{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flag-targeting",
  "type": "registry:component",
  "title": "Flag Targeting",
  "description": "Targeting rules for a feature flag, per environment: if beta and in Europe serve the new checkout, roll out to 10% of everyone else, fall back when it's off, and see who gets what across 2,000 simulated users while you edit.",
  "categories": [
    "component",
    "Filters"
  ],
  "dependencies": [
    "@qnot/core",
    "@qnot/react",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://coss.com/ui/r/button.json",
    "https://coss.com/ui/r/input.json",
    "https://coss.com/ui/r/menu.json",
    "https://coss.com/ui/r/select.json",
    "https://coss.com/ui/r/slider.json",
    "https://coss.com/ui/r/switch.json",
    "https://coss.com/ui/r/tabs.json"
  ],
  "files": [
    {
      "path": "registry/devl/flag-targeting.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport {\n  type AnyFieldDef,\n  compileCases,\n  type EditorSpec,\n  type Fields,\n  formatInput,\n  formatList,\n  type Issue,\n  makeRule,\n  parseInput,\n  parseList,\n  type Query,\n  type QueryOf,\n  validateQuery,\n} from \"@qnot/core\";\nimport { QueryProvider, useGroup, useQueryStore, useRule } from \"@qnot/react\";\nimport { ArrowDownIcon, ArrowUpIcon, EllipsisIcon, PlusIcon, Trash2Icon, XIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { memo, type ReactNode, useMemo, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Menu, MenuItem, MenuPopup, MenuSeparator, MenuTrigger } from \"@/components/ui/menu\";\nimport { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsList, TabsTab } from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface FlagVariant {\n  id: string;\n  name: string;\n  tone: string;\n}\n\nexport interface TargetingRule {\n  id: string;\n  when: Query;\n  serve: string;\n}\n\nexport interface EnvironmentTargeting {\n  on: boolean;\n  rules: TargetingRule[];\n  rollout: Record<string, number>;\n  offVariant: string;\n}\n\nexport interface FlagEnvironment {\n  id: string;\n  name: string;\n}\n\nexport interface FlagTargetingProps {\n  flagKey: string;\n  flagName: string;\n  fields: Fields;\n  variants: FlagVariant[];\n  environments: FlagEnvironment[];\n  targeting: Record<string, EnvironmentTargeting>;\n  population: readonly Record<string, unknown>[];\n  onChange?: (targeting: Record<string, EnvironmentTargeting>) => void;\n  className?: string;\n}\n\nconst easeOut = [0.23, 1, 0.32, 1] as const;\nconst glide = { type: \"spring\", duration: 0.3, bounce: 0 } as const;\n\nfunction bucketOf(id: string, salt: string) {\n  let h = 2166136261;\n  for (const ch of `${salt}:${id}`) h = Math.imul(h ^ ch.charCodeAt(0), 16777619);\n  return ((h >>> 0) % 10000) / 100;\n}\n\nfunction pickRollout(rollout: Record<string, number>, bucket: number, variants: FlagVariant[]) {\n  let edge = 0;\n  for (const v of variants) {\n    edge += rollout[v.id] ?? 0;\n    if (bucket < edge) return v.id;\n  }\n  return variants[variants.length - 1].id;\n}\n\nlet nextId = 0;\nconst ruleId = () => `rule-${++nextId}-${Date.now().toString(36)}`;\n\nexport function FlagTargeting({\n  flagKey,\n  flagName,\n  fields,\n  variants,\n  environments,\n  targeting: initial,\n  population,\n  onChange,\n  className,\n}: FlagTargetingProps) {\n  const reduce = useReducedMotion();\n  const [targeting, setTargeting] = useState(initial);\n  const [env, setEnv] = useState(environments[0].id);\n  const config = targeting[env];\n\n  const update = (patch: Partial<EnvironmentTargeting>) => {\n    const next = { ...targeting, [env]: { ...config, ...patch } };\n    setTargeting(next);\n    onChange?.(next);\n  };\n\n  const setRule = (id: string, patch: Partial<TargetingRule>) =>\n    update({ rules: config.rules.map((r) => (r.id === id ? { ...r, ...patch } : r)) });\n\n  const moveRule = (index: number, delta: -1 | 1) => {\n    const rules = [...config.rules];\n    const [rule] = rules.splice(index, 1);\n    rules.splice(index + delta, 0, rule);\n    update({ rules });\n  };\n\n  const addRule = () => {\n    const first = Object.keys(fields)[0];\n    update({\n      rules: [\n        ...config.rules,\n        {\n          id: ruleId(),\n          when: { kind: \"group\", combinator: \"and\", children: [makeRule(fields, { field: first })] },\n          serve: variants[variants.length - 1].id,\n        },\n      ],\n    });\n  };\n\n  const outcome = useMemo(() => {\n    const route = compileCases(\n      fields,\n      { cases: config.rules.map((r) => ({ when: r.when as QueryOf<Fields>, then: r.id })), otherwise: null },\n      { incompleteRules: false },\n    );\n    const byVariant: Record<string, number> = Object.fromEntries(variants.map((v) => [v.id, 0]));\n    const byRule: Record<string, number> = {};\n    for (const user of population) {\n      if (!config.on) {\n        byVariant[config.offVariant]++;\n        continue;\n      }\n      const hit = route(user as never);\n      if (hit.index >= 0) {\n        const rule = config.rules[hit.index];\n        byRule[rule.id] = (byRule[rule.id] ?? 0) + 1;\n        byVariant[rule.serve]++;\n      } else {\n        byVariant[pickRollout(config.rollout, bucketOf(String(user.id), flagKey), variants)]++;\n      }\n    }\n    const targeted = Object.values(byRule).reduce((a, b) => a + b, 0);\n    return { byVariant, byRule, everyoneElse: config.on ? population.length - targeted : 0 };\n  }, [config, fields, flagKey, population, variants]);\n\n  const variantOf = (id: string) => variants.find((v) => v.id === id) ?? variants[0];\n  const [a, b] = variants;\n  const bShare = config.rollout[b.id] ?? 0;\n\n  return (\n    <div\n      className={cn(\n        \"flex w-full min-w-0 max-w-[580px] flex-col overflow-hidden rounded-2xl border bg-card text-sm shadow-xs/5\",\n        className,\n      )}\n    >\n      <div className=\"flex items-start gap-3 px-4 pt-3.5 pb-3\">\n        <div className=\"min-w-0 flex-1\">\n          <h3 className=\"truncate font-medium\">{flagName}</h3>\n          <p className=\"truncate font-mono text-muted-foreground text-xs\">{flagKey}</p>\n        </div>\n        <label className=\"flex shrink-0 cursor-pointer items-center gap-2 text-xs\">\n          <span className={cn(\"font-medium tabular-nums\", config.on ? \"text-foreground\" : \"text-muted-foreground\")}>\n            {config.on ? \"On\" : \"Off\"}\n          </span>\n          <Switch aria-label={`Serve ${flagName} in ${env}`} checked={config.on} onCheckedChange={(on) => update({ on })} />\n        </label>\n      </div>\n\n      <div className=\"px-4 pb-3\">\n        <Tabs onValueChange={(v) => setEnv(String(v))} value={env}>\n          <TabsList className=\"w-full text-muted-foreground\">\n            {environments.map((e) => (\n              <TabsTab className=\"gap-1.5 text-xs sm:text-xs\" key={e.id} value={e.id}>\n                <span\n                  className={cn(\"size-1.5 rounded-full\", targeting[e.id].on ? \"bg-success\" : \"bg-muted-foreground/40\")}\n                />\n                {e.name}\n              </TabsTab>\n            ))}\n          </TabsList>\n        </Tabs>\n      </div>\n\n      <div className={cn(\"flex flex-col gap-4 px-4 pb-4 transition-opacity duration-200\", !config.on && \"opacity-56\")}>\n        <section className=\"flex flex-col gap-2\">\n          <SectionTitle hint=\"First match wins\">Targeting rules</SectionTitle>\n          <div className=\"relative flex flex-col gap-2\">\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              {config.rules.map((rule, index) => (\n                <motion.div\n                  animate={{ opacity: 1, scale: 1 }}\n                  exit={{ opacity: 0, scale: 0.98, transition: { duration: reduce ? 0 : 0.15 } }}\n                  initial={{ opacity: 0, scale: reduce ? 1 : 0.98 }}\n                  key={`${env}-${rule.id}`}\n                  layout={reduce ? false : \"position\"}\n                  layoutDependency={index}\n                  transition={reduce ? { duration: 0 } : { ...glide, opacity: { duration: 0.18, ease: easeOut } }}\n                >\n                  <RuleCard\n                    count={config.on ? (outcome.byRule[rule.id] ?? 0) : null}\n                    fields={fields}\n                    index={index}\n                    isFirst={index === 0}\n                    isLast={index === config.rules.length - 1}\n                    onChange={(when) => setRule(rule.id, { when })}\n                    onMove={(delta) => moveRule(index, delta)}\n                    onRemove={() => update({ rules: config.rules.filter((r) => r.id !== rule.id) })}\n                    onServe={(serve) => setRule(rule.id, { serve })}\n                    rule={rule}\n                    variants={variants}\n                  />\n                </motion.div>\n              ))}\n            </AnimatePresence>\n            {config.rules.length === 0 && (\n              <p className=\"rounded-xl border border-dashed px-3 py-4 text-center text-muted-foreground text-xs\">\n                No rules in {environments.find((e) => e.id === env)?.name}. Everyone falls through to the rollout.\n              </p>\n            )}\n          </div>\n          <Button className=\"self-start\" onClick={addRule} size=\"sm\" variant=\"outline\">\n            <PlusIcon />\n            Add rule\n          </Button>\n        </section>\n\n        <section className=\"flex flex-col gap-2.5\">\n          <SectionTitle hint={`${outcome.everyoneElse.toLocaleString()} users`}>Everyone else</SectionTitle>\n          <div className=\"flex flex-col gap-3 rounded-xl border bg-muted/32 p-3 dark:bg-muted/16\">\n            <div className=\"flex items-center justify-between gap-3 text-xs\">\n              <VariantLabel share={100 - bShare} variant={a} />\n              <VariantLabel align=\"end\" share={bShare} variant={b} />\n            </div>\n            <Slider\n              getAriaLabel={() => `Share of everyone else who get ${b.name}`}\n              max={100}\n              min={0}\n              onValueChange={(v) => {\n                const share = Array.isArray(v) ? v[0] : v;\n                update({ rollout: { [a.id]: 100 - share, [b.id]: share } });\n              }}\n              step={5}\n              value={bShare}\n            />\n          </div>\n        </section>\n\n        <section className=\"flex items-center justify-between gap-3\">\n          <div className=\"min-w-0\">\n            <p className=\"font-medium text-xs\">When the flag is off</p>\n            <p className=\"text-muted-foreground text-xs\">Everyone gets this variant.</p>\n          </div>\n          <VariantSelect\n            label=\"Variant served when off\"\n            onChange={(offVariant) => update({ offVariant })}\n            value={config.offVariant}\n            variants={variants}\n          />\n        </section>\n      </div>\n\n      <Distribution population={population.length} split={outcome.byVariant} variantOf={variantOf} variants={variants} />\n    </div>\n  );\n}\n\nfunction SectionTitle({ children, hint }: { children: string; hint?: string }) {\n  return (\n    <div className=\"flex items-baseline justify-between gap-3\">\n      <h4 className=\"font-medium text-xs\">{children}</h4>\n      {hint && <span className=\"text-muted-foreground text-xs tabular-nums\">{hint}</span>}\n    </div>\n  );\n}\n\nfunction VariantDot({ variant }: { variant: FlagVariant }) {\n  return <span className={cn(\"size-2 shrink-0 rounded-full\", variant.tone)} />;\n}\n\nfunction VariantLabel({ variant, share, align }: { variant: FlagVariant; share: number; align?: \"end\" }) {\n  return (\n    <span className={cn(\"flex min-w-0 items-center gap-1.5\", align === \"end\" && \"flex-row-reverse\")}>\n      <VariantDot variant={variant} />\n      <span className=\"truncate\">{variant.name}</span>\n      <span className=\"font-medium tabular-nums\">{share}%</span>\n    </span>\n  );\n}\n\nfunction VariantSelect({\n  value,\n  variants,\n  label,\n  onChange,\n}: {\n  value: string;\n  variants: FlagVariant[];\n  label: string;\n  onChange: (id: string) => void;\n}) {\n  return (\n    <Select items={variants.map((v) => ({ value: v.id, label: v.name }))} onValueChange={(v) => v && onChange(v)} value={value}>\n      <SelectTrigger aria-label={label} className=\"w-auto min-w-0 shrink\" size=\"sm\">\n        <span className=\"flex items-center gap-1.5\">\n          <VariantDot variant={variants.find((v) => v.id === value) ?? variants[0]} />\n          <SelectValue />\n        </span>\n      </SelectTrigger>\n      <SelectPopup>\n        {variants.map((v) => (\n          <SelectItem key={v.id} value={v.id}>\n            <span className=\"flex items-center gap-2\">\n              <VariantDot variant={v} />\n              {v.name}\n            </span>\n          </SelectItem>\n        ))}\n      </SelectPopup>\n    </Select>\n  );\n}\n\nfunction RuleCard({\n  rule,\n  index,\n  fields,\n  variants,\n  count,\n  isFirst,\n  isLast,\n  onChange,\n  onServe,\n  onMove,\n  onRemove,\n}: {\n  rule: TargetingRule;\n  index: number;\n  fields: Fields;\n  variants: FlagVariant[];\n  count: number | null;\n  isFirst: boolean;\n  isLast: boolean;\n  onChange: (when: Query) => void;\n  onServe: (variant: string) => void;\n  onMove: (delta: -1 | 1) => void;\n  onRemove: () => void;\n}) {\n  const store = useQueryStore({\n    fields,\n    initial: rule.when as QueryOf<Fields>,\n    onChange: (q) => onChange(q as Query),\n  });\n  const issues = useMemo(() => validateQuery(fields, rule.when), [fields, rule.when]);\n  const incomplete = issues.some((i) => i.code === \"required\") || (rule.when.kind === \"group\" && rule.when.children.length === 0);\n\n  return (\n    <QueryProvider store={store}>\n      <div className=\"flex flex-col gap-2 rounded-xl border bg-background p-2.5 dark:bg-input/16\">\n        <div className=\"flex items-center gap-2\">\n          <span className=\"flex size-5 shrink-0 items-center justify-center rounded-md bg-muted font-medium text-[11px] tabular-nums\">\n            {index + 1}\n          </span>\n          <span className=\"text-muted-foreground text-xs\">Serve</span>\n          <VariantSelect label={`Variant for rule ${index + 1}`} onChange={onServe} value={rule.serve} variants={variants} />\n          <span className=\"flex-1\" />\n          <Menu>\n            <MenuTrigger render={<Button aria-label={`Rule ${index + 1} actions`} size=\"icon-sm\" variant=\"ghost\" />}>\n              <EllipsisIcon />\n            </MenuTrigger>\n            <MenuPopup align=\"end\" className=\"w-40\">\n              <MenuItem disabled={isFirst} onClick={() => onMove(-1)}>\n                <ArrowUpIcon />\n                Move up\n              </MenuItem>\n              <MenuItem disabled={isLast} onClick={() => onMove(1)}>\n                <ArrowDownIcon />\n                Move down\n              </MenuItem>\n              <MenuSeparator />\n              <MenuItem onClick={onRemove} variant=\"destructive\">\n                <Trash2Icon />\n                Delete rule\n              </MenuItem>\n            </MenuPopup>\n          </Menu>\n        </div>\n        <Conditions\n          id={store.rootId}\n          status={\n            incomplete ? (\n              <span className=\"text-warning-foreground\">Incomplete, not serving yet</span>\n            ) : count === null ? (\n              \"Flag is off\"\n            ) : (\n              `Serves ${count.toLocaleString()} ${count === 1 ? \"user\" : \"users\"}`\n            )\n          }\n        />\n      </div>\n    </QueryProvider>\n  );\n}\n\nfunction Conditions({ id, status }: { id: string; status: ReactNode }) {\n  const { group, addRule, setCombinator } = useGroup(id);\n  return (\n    <div className=\"@container flex flex-col gap-2.5 @sm:gap-1.5\">\n      {group.children.map((child, i) => (\n        <div className=\"flex items-start gap-1.5\" key={child}>\n          {i === 0 ? (\n            <span className=\"flex h-8 w-9 shrink-0 sm:h-7 items-center text-muted-foreground text-xs\">If</span>\n          ) : i === 1 ? (\n            <button\n              aria-label={`Match ${group.combinator === \"and\" ? \"any\" : \"all\"} conditions instead`}\n              className=\"flex h-8 w-9 shrink-0 sm:h-7 items-center justify-center rounded-lg border border-input bg-background font-medium text-xs shadow-xs/5 outline-none transition-[background-color,scale] duration-150 hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.97] dark:bg-input/32\"\n              onClick={() => setCombinator(group.combinator === \"and\" ? \"or\" : \"and\")}\n              type=\"button\"\n            >\n              {group.combinator}\n            </button>\n          ) : (\n            <span className=\"flex h-8 w-9 shrink-0 sm:h-7 items-center ps-1 text-muted-foreground text-xs\">{group.combinator}</span>\n          )}\n          <div className=\"min-w-0 flex-1\">\n            <Condition id={child} />\n          </div>\n        </div>\n      ))}\n      <div className=\"flex items-center justify-between gap-2 ps-10.5\">\n        <button\n          className=\"-ms-1.5 flex h-7 items-center gap-1 rounded-md px-1.5 text-muted-foreground text-xs outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring\"\n          onClick={() => addRule()}\n          type=\"button\"\n        >\n          <PlusIcon className=\"size-3.5\" />\n          Condition\n        </button>\n        <span className=\"min-w-0 truncate text-muted-foreground text-xs tabular-nums\">{status}</span>\n      </div>\n    </div>\n  );\n}\n\nconst Condition = memo(function Condition({ id }: { id: string }) {\n  const { rule, def, editor, operators, fieldOptions, issues, setField, setOperator, setValue, remove } = useRule(id);\n  const field = typeof rule.field === \"string\" ? rule.field : \"\";\n  const label = fieldOptions.find((o) => o.value === field)?.label ?? field;\n  const errors = issues.filter((i) => i.severity === \"error\" && i.code !== \"required\");\n  return (\n    <div>\n      <div className=\"grid grid-cols-[minmax(0,1fr)_2rem] items-start gap-1.5 @sm:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_2rem] @lg:grid-cols-[8.5rem_8rem_minmax(0,1fr)_2rem]\">\n        <Select items={fieldOptions} onValueChange={(v) => v && setField(v)} value={field}>\n          <SelectTrigger aria-label=\"Attribute\" className=\"col-start-1 row-start-1 min-w-0\" size=\"sm\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectPopup>\n            {fieldOptions.map((o) => (\n              <SelectItem key={o.value} value={o.value}>\n                {o.label}\n              </SelectItem>\n            ))}\n          </SelectPopup>\n        </Select>\n        <Select items={operators} onValueChange={(v) => v && setOperator(v)} value={rule.operator}>\n          <SelectTrigger\n            aria-label=\"Operator\"\n            className=\"col-start-1 row-start-2 min-w-0 @sm:col-start-2 @sm:row-start-1\"\n            size=\"sm\"\n          >\n            <SelectValue />\n          </SelectTrigger>\n          <SelectPopup>\n            {operators.map((o) => (\n              <SelectItem key={o.value} value={o.value}>\n                {o.label}\n              </SelectItem>\n            ))}\n          </SelectPopup>\n        </Select>\n        <div\n          className={cn(\n            \"col-start-1 row-start-3 min-w-0 @sm:col-span-2 @sm:row-start-2 @lg:col-span-1 @lg:col-start-3 @lg:row-start-1\",\n            editor.arity === \"none\" && \"hidden\",\n          )}\n        >\n          <ValueEditor def={def} editor={editor} issues={issues} label={label} onChange={setValue} value={rule.value} />\n        </div>\n        <Button\n          aria-label={`Remove condition on ${label}`}\n          className=\"col-start-2 row-start-1 @sm:col-start-3 @lg:col-start-4\"\n          onClick={remove}\n          size=\"icon-sm\"\n          variant=\"ghost\"\n        >\n          <XIcon />\n        </Button>\n      </div>\n      {errors.length > 0 && <p className=\"pt-1 text-destructive-foreground text-xs\">{errors.map((e) => e.message).join(\" · \")}</p>}\n    </div>\n  );\n});\n\nfunction ValueEditor({\n  editor,\n  def,\n  value,\n  issues,\n  label,\n  onChange,\n}: {\n  editor: EditorSpec;\n  def: AnyFieldDef | undefined;\n  value: unknown;\n  issues: readonly Issue[];\n  label: string;\n  onChange: (value: unknown) => void;\n}) {\n  const invalidAt = (index?: number) =>\n    issues.some((i) => i.severity === \"error\" && i.code !== \"required\" && (index === undefined || i.index === undefined || i.index === index));\n  if (editor.arity === \"none\") return null;\n  if (editor.arity === \"two\") {\n    const pair = Array.isArray(value) ? value : [null, null];\n    return (\n      <div className=\"flex items-center gap-1.5\">\n        <Scalar def={def} editor={editor} invalid={invalidAt(0)} label={`${label} from`} onChange={(v) => onChange([v, pair[1] ?? null])} value={pair[0]} />\n        <span className=\"shrink-0 text-muted-foreground text-xs\">and</span>\n        <Scalar def={def} editor={editor} invalid={invalidAt(1)} label={`${label} to`} onChange={(v) => onChange([pair[0] ?? null, v])} value={pair[1]} />\n      </div>\n    );\n  }\n  if (editor.type === \"multiselect\") {\n    const options = editor.options ?? [];\n    const selected = Array.isArray(value) ? value.map(String) : [];\n    return (\n      <Select\n        items={options.map((o) => ({ value: String(o.value), label: o.label }))}\n        multiple\n        onValueChange={(next) => onChange((next as string[]).map((raw) => parseInput(def, raw)))}\n        value={selected}\n      >\n        <SelectTrigger aria-invalid={invalidAt() || undefined} aria-label={`${label} values`} className=\"min-w-0\" size=\"sm\">\n          <SelectValue>\n            {(vals: string[]) =>\n              vals.length === 0 ? (\n                <span className=\"text-muted-foreground\">Choose…</span>\n              ) : (\n                vals.map((v) => options.find((o) => String(o.value) === v)?.label ?? v).join(\", \")\n              )\n            }\n          </SelectValue>\n        </SelectTrigger>\n        <SelectPopup>\n          {options.map((o) => (\n            <SelectItem key={String(o.value)} value={String(o.value)}>\n              {o.label}\n            </SelectItem>\n          ))}\n        </SelectPopup>\n      </Select>\n    );\n  }\n  if (editor.type === \"list\") {\n    return (\n      <DraftInput\n        format={formatList}\n        invalid={invalidAt()}\n        label={`${label} values`}\n        onChange={onChange}\n        parse={(raw) => parseList(def, raw)}\n        placeholder=\"Comma, separated\"\n        value={value}\n      />\n    );\n  }\n  return <Scalar def={def} editor={editor} invalid={invalidAt()} label={label} onChange={onChange} value={value} />;\n}\n\nfunction Scalar({\n  editor,\n  def,\n  value,\n  invalid,\n  label,\n  onChange,\n}: {\n  editor: EditorSpec;\n  def: AnyFieldDef | undefined;\n  value: unknown;\n  invalid: boolean;\n  label: string;\n  onChange: (value: unknown) => void;\n}) {\n  if (editor.type === \"select\" || editor.type === \"boolean\") {\n    const items =\n      editor.type === \"boolean\"\n        ? [\n            { value: \"true\", label: \"Yes\" },\n            { value: \"false\", label: \"No\" },\n          ]\n        : (editor.options ?? []).map((o) => ({ value: String(o.value), label: o.label }));\n    return (\n      <Select\n        items={items}\n        onValueChange={(raw) => raw !== null && onChange(parseInput(def, String(raw)))}\n        value={value === null || value === undefined ? null : String(value)}\n      >\n        <SelectTrigger aria-invalid={invalid || undefined} aria-label={label} className=\"min-w-0\" size=\"sm\">\n          <SelectValue placeholder=\"Choose…\" />\n        </SelectTrigger>\n        <SelectPopup>\n          {items.map((o) => (\n            <SelectItem key={o.value} value={o.value}>\n              {o.label}\n            </SelectItem>\n          ))}\n        </SelectPopup>\n      </Select>\n    );\n  }\n  return (\n    <DraftInput\n      format={formatInput}\n      inputMode={editor.type === \"number\" ? \"decimal\" : undefined}\n      invalid={invalid}\n      label={label}\n      onChange={onChange}\n      parse={(raw) => parseInput(def, raw)}\n      placeholder={editor.type === \"number\" ? \"0\" : \"Value\"}\n      type={editor.type === \"date\" ? \"date\" : \"text\"}\n      value={value}\n    />\n  );\n}\n\nfunction DraftInput({\n  value,\n  parse,\n  format,\n  invalid,\n  label,\n  placeholder,\n  inputMode,\n  type = \"text\",\n  onChange,\n}: {\n  value: unknown;\n  parse: (raw: string) => unknown;\n  format: (value: unknown) => string;\n  invalid: boolean;\n  label: string;\n  placeholder?: string;\n  inputMode?: \"decimal\";\n  type?: \"text\" | \"date\";\n  onChange: (value: unknown) => void;\n}) {\n  const [draft, setDraft] = useState<string | null>(null);\n  const unreadable = draft !== null && draft.trim() !== \"\" && parse(draft) === null;\n  return (\n    <div className=\"flex min-w-0 flex-col gap-1\">\n      <Input\n        aria-invalid={invalid || unreadable || undefined}\n        aria-label={label}\n        className={type === \"date\" ? \"[color-scheme:light] dark:[color-scheme:dark]\" : undefined}\n        inputMode={inputMode}\n        onBlur={() => setDraft(null)}\n        onChange={(e) => {\n          setDraft(e.target.value);\n          onChange(parse(e.target.value));\n        }}\n        onFocus={() => setDraft(format(value))}\n        placeholder={placeholder}\n        size=\"sm\"\n        type={type}\n        value={draft ?? format(value)}\n      />\n      {unreadable && <p className=\"text-destructive-foreground text-xs\">Enter a number</p>}\n    </div>\n  );\n}\n\nfunction Distribution({\n  split,\n  population,\n  variants,\n  variantOf,\n}: {\n  split: Record<string, number>;\n  population: number;\n  variants: FlagVariant[];\n  variantOf: (id: string) => FlagVariant;\n}) {\n  const reduce = useReducedMotion();\n  const percents = tenths(variants.map((v) => split[v.id] ?? 0), population);\n  let offset = 0;\n  return (\n    <div className=\"border-t bg-muted/32 px-4 py-3 dark:bg-muted/16\">\n      <div className=\"mb-2 flex items-baseline justify-between gap-3\">\n        <h4 className=\"font-medium text-xs\">Who gets what</h4>\n        <span className=\"text-muted-foreground text-xs tabular-nums\">Simulated on {population.toLocaleString()} users</span>\n      </div>\n      <div aria-hidden className=\"relative h-2 overflow-hidden rounded-full bg-muted\">\n        {variants.map((v) => {\n          const share = population ? split[v.id] / population : 0;\n          const left = offset;\n          offset += share;\n          return (\n            <motion.span\n              animate={{ x: `${left * 100}%`, scaleX: share }}\n              className={cn(\"absolute inset-y-0 left-0 w-full origin-left\", v.tone)}\n              initial={false}\n              key={v.id}\n              style={{ x: `${left * 100}%`, scaleX: share }}\n              transition={reduce ? { duration: 0 } : glide}\n            />\n          );\n        })}\n      </div>\n      <ul className=\"mt-2 flex flex-wrap gap-x-4 gap-y-1\">\n        {variants.map((v, i) => (\n          <li className=\"flex items-center gap-1.5 text-xs\" key={v.id}>\n            <VariantDot variant={variantOf(v.id)} />\n            <span className=\"text-muted-foreground\">{v.name}</span>\n            <span className=\"font-medium tabular-nums\">{percents[i]}%</span>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n\nfunction tenths(counts: number[], total: number) {\n  if (!total) return counts.map(() => 0);\n  const raw = counts.map((c) => (c / total) * 1000);\n  const floored = raw.map(Math.floor);\n  let left = 1000 - floored.reduce((a, b) => a + b, 0);\n  const order = raw.map((r, i) => [r - floored[i], i] as const).sort((a, b) => b[0] - a[0]);\n  for (const [, i] of order) {\n    if (left <= 0) break;\n    floored[i]++;\n    left--;\n  }\n  return floored.map((t) => t / 10);\n}\n"
    }
  ]
}