{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "preferences-panel",
  "type": "registry:block",
  "title": "Preferences Panel",
  "description": "Compact settings panel for user preferences.",
  "dependencies": [
    "lucide-react",
    "zod"
  ],
  "registryDependencies": [
    "button",
    "label",
    "select",
    "separator",
    "switch",
    "toggle-group"
  ],
  "files": [
    {
      "path": "components/tool-ui/preferences-panel/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/preferences-panel/_adapter.tsx",
      "content": "/**\n * Adapter: UI and utility re-exports for copy-standalone portability.\n *\n * When copying this component to another project, update these imports\n * to match your project's paths:\n *\n *   cn           → Your Tailwind merge utility (e.g., \"@/lib/utils\", \"~/lib/cn\")\n *   Button       → shadcn/ui Button\n *   Switch       → shadcn/ui Switch\n *   ToggleGroup  → shadcn/ui ToggleGroup\n *   Select       → shadcn/ui Select\n *   Separator    → shadcn/ui Separator\n *   Label        → shadcn/ui Label\n */\n\nexport { cn } from \"@/lib/utils\";\nexport { Button } from \"@/components/ui/button\";\nexport { Switch } from \"@/components/ui/switch\";\nexport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\nexport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nexport { Separator } from \"@/components/ui/separator\";\nexport { Label } from \"@/components/ui/label\";\n"
    },
    {
      "path": "components/tool-ui/preferences-panel/index.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/preferences-panel/index.tsx",
      "content": "export { PreferencesPanel, PreferencesPanelReceipt } from \"./preferences-panel\";\nexport {\n  type SerializablePreferencesPanel,\n  type SerializablePreferencesPanelReceipt,\n  type PreferencesPanelProps,\n  type PreferencesPanelReceiptProps,\n  type PreferencesValue,\n  type PreferenceItem,\n  type PreferenceSection,\n} from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/preferences-panel/preferences-panel.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/preferences-panel/preferences-panel.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useMemo } from \"react\";\nimport type {\n  PreferencesPanelProps,\n  PreferencesPanelReceiptProps,\n  PreferencesValue,\n  PreferenceItem,\n  PreferenceSection,\n} from \"./schema\";\nimport { ActionButtons } from \"../shared/action-buttons\";\nimport { normalizeActionsConfig } from \"../shared/actions-config\";\nimport { type Action } from \"../shared/schema\";\nimport { useControllableState } from \"../shared/use-controllable-state\";\nimport { useSignatureReset } from \"../shared/use-signature-reset\";\n\nimport {\n  cn,\n  Switch,\n  ToggleGroup,\n  ToggleGroupItem,\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n  Separator,\n  Label,\n} from \"./_adapter\";\nimport { Check, AlertCircle } from \"lucide-react\";\nimport { createPreferencesSectionSignature } from \"./signature\";\n\nfunction getInitialValue(item: PreferenceItem): string | boolean {\n  switch (item.type) {\n    case \"switch\":\n      return item.defaultChecked ?? false;\n    case \"toggle\":\n      return item.defaultValue ?? item.options?.[0]?.value ?? \"\";\n    case \"select\":\n      return item.defaultSelected ?? item.selectOptions?.[0]?.value ?? \"\";\n  }\n}\n\nfunction formatDisplayValue(\n  item: PreferenceItem,\n  value: string | boolean,\n): string {\n  if (item.type === \"switch\") {\n    return typeof value === \"boolean\" && value ? \"On\" : \"Off\";\n  }\n\n  const stringValue = typeof value === \"string\" ? value : \"\";\n  const options = item.type === \"toggle\" ? item.options : item.selectOptions;\n  const option = options?.find((opt) => opt.value === stringValue);\n\n  return option?.label ?? stringValue;\n}\n\nfunction computeInitialValues(sections: PreferenceSection[]): PreferencesValue {\n  return sections.reduce<PreferencesValue>((acc, section) => {\n    section.items.forEach((item) => {\n      acc[item.id] = getInitialValue(item);\n    });\n    return acc;\n  }, {});\n}\n\ninterface PreferenceControlProps {\n  item: PreferenceItem;\n  value: string | boolean;\n  onChange: (value: string | boolean) => void;\n  disabled?: boolean;\n}\n\nfunction SwitchControl({\n  id,\n  checked,\n  onChange,\n  disabled,\n  label,\n}: {\n  id: string;\n  checked: boolean;\n  onChange: (value: boolean) => void;\n  disabled?: boolean;\n  label: string;\n}) {\n  return (\n    <Switch\n      id={id}\n      checked={checked}\n      onCheckedChange={onChange}\n      disabled={disabled}\n      aria-label={label}\n    />\n  );\n}\n\nfunction ToggleControl({\n  value,\n  options,\n  onChange,\n  disabled,\n  label,\n}: {\n  value: string;\n  options: Array<{ value: string; label: string }>;\n  onChange: (value: string) => void;\n  disabled?: boolean;\n  label: string;\n}) {\n  return (\n    <ToggleGroup\n      type=\"single\"\n      value={value}\n      onValueChange={(v) => v && onChange(v)}\n      disabled={disabled}\n      aria-label={label}\n      className=\"gap-1\"\n    >\n      {options.map((opt) => (\n        <ToggleGroupItem\n          key={opt.value}\n          value={opt.value}\n          aria-label={opt.label}\n          className=\"!rounded-full px-3 py-1.5 text-sm\"\n        >\n          {opt.label}\n        </ToggleGroupItem>\n      ))}\n    </ToggleGroup>\n  );\n}\n\nfunction SelectControl({\n  id,\n  value,\n  options,\n  onChange,\n  disabled,\n  label,\n}: {\n  id: string;\n  value: string;\n  options: Array<{ value: string; label: string }>;\n  onChange: (value: string) => void;\n  disabled?: boolean;\n  label: string;\n}) {\n  return (\n    <Select value={value} onValueChange={onChange} disabled={disabled}>\n      <SelectTrigger id={id} className=\"w-[180px]\" aria-label={label}>\n        <SelectValue placeholder=\"Select...\" />\n      </SelectTrigger>\n      <SelectContent>\n        {options.map((opt) => (\n          <SelectItem key={opt.value} value={opt.value}>\n            {opt.label}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n}\n\nfunction PreferenceControl({\n  item,\n  value,\n  onChange,\n  disabled,\n}: PreferenceControlProps) {\n  const id = `preference-${item.id}`;\n\n  if (item.type === \"switch\") {\n    return (\n      <SwitchControl\n        id={id}\n        checked={typeof value === \"boolean\" ? value : false}\n        onChange={onChange}\n        disabled={disabled}\n        label={item.label}\n      />\n    );\n  }\n\n  const stringValue = typeof value === \"string\" ? value : \"\";\n\n  if (item.type === \"toggle\" && item.options) {\n    return (\n      <ToggleControl\n        value={stringValue}\n        options={item.options}\n        onChange={onChange}\n        disabled={disabled}\n        label={item.label}\n      />\n    );\n  }\n\n  if (item.type === \"select\" && item.selectOptions) {\n    return (\n      <SelectControl\n        id={id}\n        value={stringValue}\n        options={item.selectOptions}\n        onChange={onChange}\n        disabled={disabled}\n        label={item.label}\n      />\n    );\n  }\n\n  return null;\n}\n\ninterface PreferenceItemRowProps {\n  item: PreferenceItem;\n  value: string | boolean;\n  onChange?: (value: string | boolean) => void;\n  disabled?: boolean;\n  isReceipt?: boolean;\n  error?: string;\n  showSuccessIndicators?: boolean;\n  isFirstInSectionWithoutHeading?: boolean;\n}\n\nfunction ItemLabel({\n  item,\n  error,\n  isReceipt,\n}: {\n  item: PreferenceItem;\n  error?: string;\n  isReceipt: boolean;\n}) {\n  const htmlFor = `preference-${item.id}`;\n\n  if (isReceipt) {\n    return (\n      <>\n        <span className=\"text-sm leading-6 font-medium text-pretty\">\n          {item.label}\n        </span>\n        {error ? (\n          <span className=\"text-destructive text-sm font-normal text-pretty\">\n            {error}\n          </span>\n        ) : item.description ? (\n          <span className=\"text-muted-foreground text-sm font-normal text-pretty\">\n            {item.description}\n          </span>\n        ) : null}\n      </>\n    );\n  }\n\n  return (\n    <>\n      <Label htmlFor={htmlFor} className=\"leading-6 font-medium text-pretty\">\n        {item.label}\n      </Label>\n      {item.description && (\n        <p className=\"text-muted-foreground text-sm font-normal text-pretty\">\n          {item.description}\n        </p>\n      )}\n    </>\n  );\n}\n\nfunction ItemValue({\n  item,\n  value,\n  error,\n  showSuccessIndicators,\n}: {\n  item: PreferenceItem;\n  value: string | boolean;\n  error?: string;\n  showSuccessIndicators: boolean;\n}) {\n  const displayValue = formatDisplayValue(item, value);\n\n  return (\n    <div className=\"flex shrink-0 items-center gap-2\">\n      <span className=\"text-muted-foreground text-sm font-medium\">\n        {displayValue}\n      </span>\n      {error ? (\n        <AlertCircle className=\"text-destructive size-3.5\" />\n      ) : showSuccessIndicators ? (\n        <Check className=\"size-3.5 text-emerald-600 dark:text-emerald-500\" />\n      ) : null}\n    </div>\n  );\n}\n\nfunction PreferenceItemRow({\n  item,\n  value,\n  onChange,\n  disabled,\n  isReceipt = false,\n  error,\n  showSuccessIndicators = false,\n  isFirstInSectionWithoutHeading = false,\n}: PreferenceItemRowProps) {\n  const shouldStack = item.type !== \"switch\" && !isReceipt;\n\n  return (\n    <div\n      className={cn(\n        \"flex items-start justify-between gap-4\",\n        isFirstInSectionWithoutHeading ? \"pt-0 pb-3\" : \"py-3\",\n        shouldStack &&\n          \"flex-col gap-3 @sm/preferences-panel:flex-row @sm/preferences-panel:gap-4\",\n      )}\n    >\n      <div className=\"flex flex-col gap-1\">\n        <ItemLabel item={item} error={error} isReceipt={isReceipt} />\n      </div>\n\n      {isReceipt ? (\n        <ItemValue\n          item={item}\n          value={value}\n          error={error}\n          showSuccessIndicators={showSuccessIndicators}\n        />\n      ) : (\n        <div className=\"flex shrink-0\">\n          <PreferenceControl\n            item={item}\n            value={value}\n            onChange={onChange!}\n            disabled={disabled}\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\ninterface ItemListProps {\n  items: PreferenceItem[];\n  values: PreferencesValue;\n  onChangeValue?: (itemId: string, value: string | boolean) => void;\n  disabled?: boolean;\n  isReceipt?: boolean;\n  errors?: Record<string, string>;\n  showSuccessIndicators?: boolean;\n  hasHeading?: boolean;\n  hasTitle?: boolean;\n}\n\nfunction ItemList({\n  items,\n  values,\n  onChangeValue,\n  disabled,\n  isReceipt,\n  errors,\n  showSuccessIndicators,\n  hasHeading = false,\n  hasTitle = false,\n}: ItemListProps) {\n  const shouldRemoveFirstPadding = !hasHeading && hasTitle;\n\n  return (\n    <div className=\"flex flex-col\">\n      {items.map((item, index) => {\n        const isFirst = index === 0;\n        const itemValue = values[item.id] ?? getInitialValue(item);\n        const handleChange = onChangeValue\n          ? (v: string | boolean) => onChangeValue(item.id, v)\n          : undefined;\n\n        return (\n          <div key={item.id}>\n            {!isFirst && <Separator className=\"my-1\" />}\n            <PreferenceItemRow\n              item={item}\n              value={itemValue}\n              onChange={handleChange}\n              disabled={disabled}\n              isReceipt={isReceipt}\n              error={errors?.[item.id]}\n              showSuccessIndicators={showSuccessIndicators}\n              isFirstInSectionWithoutHeading={\n                isFirst && shouldRemoveFirstPadding\n              }\n            />\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\ninterface PreferencesSectionProps {\n  section: PreferenceSection;\n  values: PreferencesValue;\n  onChangeValue?: (itemId: string, value: string | boolean) => void;\n  disabled?: boolean;\n  isReceipt?: boolean;\n  errors?: Record<string, string>;\n  hasTitle?: boolean;\n}\n\nfunction PreferencesSection({\n  section,\n  values,\n  onChangeValue,\n  disabled,\n  isReceipt = false,\n  errors,\n  hasTitle = false,\n}: PreferencesSectionProps) {\n  const hasErrors = !!(errors && Object.keys(errors).length > 0);\n\n  const content = (\n    <ItemList\n      items={section.items}\n      values={values}\n      onChangeValue={onChangeValue}\n      disabled={disabled}\n      isReceipt={isReceipt}\n      errors={errors}\n      showSuccessIndicators={hasErrors}\n      hasHeading={!!section.heading}\n      hasTitle={hasTitle}\n    />\n  );\n\n  if (section.heading) {\n    return (\n      <fieldset className=\"flex flex-col\">\n        <legend className=\"text-muted-foreground pb-1 text-xs tracking-widest uppercase\">\n          {section.heading}\n        </legend>\n        {content}\n      </fieldset>\n    );\n  }\n\n  return content;\n}\n\ninterface ReceiptHeaderProps {\n  title: string;\n  hasErrors: boolean;\n}\n\nfunction ReceiptHeader({ title, hasErrors }: ReceiptHeaderProps) {\n  return (\n    <>\n      <div className=\"flex items-center justify-between gap-3 px-5 py-4\">\n        <h2 className=\"text-base leading-none font-semibold\">{title}</h2>\n        {hasErrors === true ? (\n          <span className=\"text-destructive flex items-center gap-1.5 text-xs font-medium\">\n            <AlertCircle className=\"size-3.5\" />\n            Error\n          </span>\n        ) : (\n          <span className=\"flex items-center gap-1.5 text-xs font-medium text-emerald-600 dark:text-emerald-500\">\n            <Check className=\"size-3.5\" />\n            Saved\n          </span>\n        )}\n      </div>\n      <Separator />\n    </>\n  );\n}\n\nexport function PreferencesPanelReceipt({\n  id,\n  title,\n  sections,\n  choice,\n  error,\n  className,\n}: PreferencesPanelReceiptProps) {\n  const hasErrors = error && Object.keys(error).length > 0;\n\n  return (\n    <article\n      data-slot=\"preferences-panel\"\n      data-tool-ui-id={id}\n      data-receipt=\"true\"\n      role=\"status\"\n      aria-label={\n        hasErrors ? \"Preferences with errors\" : \"Confirmed preferences\"\n      }\n      className={cn(\n        \"@container/preferences-panel flex w-full max-w-md min-w-80 flex-col\",\n        className,\n      )}\n    >\n      <div className=\"bg-card/60 flex w-full flex-col overflow-hidden rounded-2xl border opacity-95 shadow-xs\">\n        {title && <ReceiptHeader title={title} hasErrors={!!hasErrors} />}\n        <div\n          className={cn(\"flex flex-col gap-4 px-5\", title ? \"py-6\" : \"py-2\")}\n        >\n          {sections.map((section, index) => (\n            <div key={index}>\n              <PreferencesSection\n                section={section}\n                values={choice}\n                errors={error}\n                isReceipt={true}\n                hasTitle={!!title}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n    </article>\n  );\n}\n\nfunction PreferencesPanelRoot({\n  id,\n  title,\n  sections,\n  value: controlledValue,\n  onChange,\n  actions,\n  onAction,\n  onBeforeAction,\n  className,\n}: PreferencesPanelProps) {\n  const initialValues = useMemo(\n    () => computeInitialValues(sections),\n    [sections],\n  );\n  const sectionsSignature = useMemo(\n    () => createPreferencesSectionSignature(sections),\n    [sections],\n  );\n  const {\n    value: currentValue,\n    isControlled,\n    setValue,\n    setUncontrolledValue,\n  } = useControllableState<PreferencesValue>({\n    value: controlledValue,\n    defaultValue: initialValues,\n    onChange,\n  });\n\n  useSignatureReset(sectionsSignature, () => {\n    if (!isControlled) {\n      setUncontrolledValue(initialValues);\n    }\n  });\n\n  const updateValue = useCallback(\n    (itemId: string, newValue: string | boolean) => {\n      setValue((prev) => ({ ...prev, [itemId]: newValue }));\n    },\n    [setValue],\n  );\n\n  const isDirty = useMemo(() => {\n    return Object.keys(currentValue).some(\n      (key) => currentValue[key] !== initialValues[key],\n    );\n  }, [currentValue, initialValues]);\n\n  const handleCancel = useCallback((): PreferencesValue => {\n    setValue(initialValues);\n    return initialValues;\n  }, [initialValues, setValue]);\n\n  const handleAction = useCallback(\n    async (actionId: string) => {\n      let nextValue = currentValue;\n\n      if (actionId === \"cancel\") {\n        nextValue = handleCancel();\n      }\n\n      await onAction?.(actionId, nextValue);\n    },\n    [currentValue, handleCancel, onAction],\n  );\n\n  const normalizedActions = useMemo(() => {\n    const normalized = normalizeActionsConfig(actions);\n    if (normalized) {\n      return {\n        ...normalized,\n        align: normalized.align ?? (\"right\" as const),\n      };\n    }\n\n    const defaultActions: Action[] = [\n      { id: \"cancel\", label: \"Cancel\", variant: \"ghost\" },\n      { id: \"save\", label: \"Save Changes\", variant: \"default\" },\n    ];\n\n    return {\n      items: defaultActions,\n      align: \"right\" as const,\n    };\n  }, [actions]);\n\n  const actionsWithState = useMemo((): Action[] => {\n    return normalizedActions.items.map((action) => {\n      const isSaveAction = action.id === \"save\";\n      const baseDisabled = \"disabled\" in action ? action.disabled : false;\n      const shouldDisable = baseDisabled || (isSaveAction && !isDirty);\n\n      return {\n        ...action,\n        disabled: shouldDisable,\n      };\n    });\n  }, [normalizedActions.items, isDirty]);\n\n  return (\n    <article\n      data-slot=\"preferences-panel\"\n      data-tool-ui-id={id}\n      role=\"form\"\n      className={cn(\n        \"text-foreground @container/preferences-panel flex w-full max-w-md min-w-80 flex-col gap-3\",\n        className,\n      )}\n    >\n      <div className=\"bg-card flex w-full flex-col overflow-hidden rounded-2xl border shadow-xs\">\n        {title && (\n          <>\n            <div className=\"px-5 py-4\">\n              <h2 className=\"text-base leading-none font-semibold\">{title}</h2>\n            </div>\n            <Separator />\n          </>\n        )}\n        <div\n          className={cn(\"flex flex-col gap-4 px-5\", title ? \"py-6\" : \"py-2\")}\n        >\n          {sections.map((section, sectionIndex) => (\n            <div key={sectionIndex}>\n              <PreferencesSection\n                section={section}\n                values={currentValue}\n                onChangeValue={updateValue}\n                isReceipt={false}\n                hasTitle={!!title}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n\n      <div className=\"@container/actions\">\n        <ActionButtons\n          actions={actionsWithState}\n          align={normalizedActions.align}\n          confirmTimeout={normalizedActions.confirmTimeout}\n          onAction={handleAction}\n          onBeforeAction={\n            onBeforeAction\n              ? (actionId) => onBeforeAction(actionId, currentValue)\n              : undefined\n          }\n        />\n      </div>\n    </article>\n  );\n}\n\ntype PreferencesPanelComponent = typeof PreferencesPanelRoot & {\n  Receipt: typeof PreferencesPanelReceipt;\n};\n\nexport const PreferencesPanel = Object.assign(PreferencesPanelRoot, {\n  Receipt: PreferencesPanelReceipt,\n}) as PreferencesPanelComponent;\n"
    },
    {
      "path": "components/tool-ui/preferences-panel/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/preferences-panel/README.md",
      "content": "# Preferences Panel\n\nImplementation for the \"preferences-panel\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/preferences-panel/index.tsx\n- serializable schema + parse helpers: components/tool-ui/preferences-panel/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/preferences-panel/content.mdx\n- Preset payload: lib/presets/preferences-panel.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/preferences-panel/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/preferences-panel/schema.ts",
      "content": "import { z } from \"zod\";\nimport { type ActionsProp } from \"../shared/actions-config\";\nimport type { EmbeddedActionsProps } from \"../shared/embedded-actions\";\nimport { defineToolUiContract } from \"../shared/contract\";\nimport {\n  SerializableActionSchema,\n  SerializableActionsConfigSchema,\n  ToolUIIdSchema,\n  ToolUIReceiptSchema,\n  ToolUIRoleSchema,\n} from \"../shared/schema\";\n\nconst PreferenceItemBaseSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  description: z.string().optional(),\n});\n\nconst PreferenceSwitchSchema = PreferenceItemBaseSchema.extend({\n  type: z.literal(\"switch\"),\n  defaultChecked: z.boolean().optional(),\n});\n\nconst PreferenceToggleSchema = PreferenceItemBaseSchema.extend({\n  type: z.literal(\"toggle\"),\n  options: z\n    .array(\n      z.object({\n        value: z.string().min(1),\n        label: z.string().min(1),\n      }),\n    )\n    .min(2),\n  defaultValue: z.string().optional(),\n});\n\nconst PreferenceSelectSchema = PreferenceItemBaseSchema.extend({\n  type: z.literal(\"select\"),\n  selectOptions: z\n    .array(\n      z.object({\n        value: z.string().min(1),\n        label: z.string().min(1),\n      }),\n    )\n    .min(5),\n  defaultSelected: z.string().optional(),\n});\n\nconst PreferenceItemSchema = z.discriminatedUnion(\"type\", [\n  PreferenceSwitchSchema,\n  PreferenceToggleSchema,\n  PreferenceSelectSchema,\n]);\n\nconst PreferenceSectionSchema = z.object({\n  heading: z.string().min(1).optional(),\n  items: z.array(PreferenceItemSchema).min(1),\n});\n\nconst PreferencesPanelBaseSchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  receipt: ToolUIReceiptSchema.optional(),\n  title: z.string().min(1).optional(),\n  sections: z.array(PreferenceSectionSchema).min(1),\n});\n\nexport const SerializablePreferencesPanelSchema =\n  PreferencesPanelBaseSchema.extend({\n    actions: z\n      .union([\n        z.array(SerializableActionSchema),\n        SerializableActionsConfigSchema,\n      ])\n      .optional(),\n  }).strict();\n\nexport const SerializablePreferencesPanelReceiptSchema =\n  PreferencesPanelBaseSchema.extend({\n    choice: z.record(z.string(), z.union([z.string(), z.boolean()])),\n    error: z.record(z.string(), z.string()).optional(),\n  }).strict();\n\nexport type SerializablePreferencesPanel = z.infer<\n  typeof SerializablePreferencesPanelSchema\n>;\n\nexport type SerializablePreferencesPanelReceipt = z.infer<\n  typeof SerializablePreferencesPanelReceiptSchema\n>;\n\nconst SerializablePreferencesPanelSchemaContract = defineToolUiContract(\n  \"PreferencesPanel\",\n  SerializablePreferencesPanelSchema,\n);\n\nconst SerializablePreferencesPanelReceiptSchemaContract = defineToolUiContract(\n  \"PreferencesPanelReceipt\",\n  SerializablePreferencesPanelReceiptSchema,\n);\n\nexport const parseSerializablePreferencesPanel: (\n  input: unknown,\n) => SerializablePreferencesPanel =\n  SerializablePreferencesPanelSchemaContract.parse;\n\nexport const safeParseSerializablePreferencesPanel: (\n  input: unknown,\n) => SerializablePreferencesPanel | null =\n  SerializablePreferencesPanelSchemaContract.safeParse;\n\nexport const parseSerializablePreferencesPanelReceipt: (\n  input: unknown,\n) => SerializablePreferencesPanelReceipt =\n  SerializablePreferencesPanelReceiptSchemaContract.parse;\n\nexport const safeParseSerializablePreferencesPanelReceipt: (\n  input: unknown,\n) => SerializablePreferencesPanelReceipt | null =\n  SerializablePreferencesPanelReceiptSchemaContract.safeParse;\n\nexport interface PreferencesValue {\n  [itemId: string]: string | boolean;\n}\n\nexport interface PreferencesPanelProps extends Omit<\n  SerializablePreferencesPanel,\n  \"actions\"\n> {\n  className?: string;\n  value?: PreferencesValue;\n  onChange?: (value: PreferencesValue) => void;\n  actions?: ActionsProp;\n  onAction?: EmbeddedActionsProps<PreferencesValue>[\"onAction\"];\n  onBeforeAction?: EmbeddedActionsProps<PreferencesValue>[\"onBeforeAction\"];\n}\n\nexport interface PreferencesPanelReceiptProps extends SerializablePreferencesPanelReceipt {\n  className?: string;\n}\n\nexport type PreferenceItem = z.infer<typeof PreferenceItemSchema>;\nexport type PreferenceSection = z.infer<typeof PreferenceSectionSchema>;\n"
    },
    {
      "path": "components/tool-ui/preferences-panel/signature.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/preferences-panel/signature.ts",
      "content": "import type { PreferenceSection } from \"./schema\";\n\nexport function createPreferencesSectionSignature(\n  sections: PreferenceSection[],\n): string {\n  return JSON.stringify(\n    sections.map((section) => ({\n      heading: section.heading ?? \"\",\n      items: section.items.map((item) => {\n        if (item.type === \"switch\") {\n          return {\n            id: item.id,\n            type: item.type,\n            defaultChecked: item.defaultChecked ?? false,\n          };\n        }\n\n        if (item.type === \"toggle\") {\n          return {\n            id: item.id,\n            type: item.type,\n            defaultValue: item.defaultValue ?? item.options[0]?.value ?? \"\",\n            options: item.options.map((option) => option.value),\n          };\n        }\n\n        return {\n          id: item.id,\n          type: item.type,\n          defaultSelected:\n            item.defaultSelected ?? item.selectOptions[0]?.value ?? \"\",\n          options: item.selectOptions.map((option) => option.value),\n        };\n      }),\n    })),\n  );\n}\n"
    },
    {
      "path": "components/tool-ui/shared/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/shared/_adapter.tsx",
      "content": "/**\n * Adapter: UI and utility re-exports for copy-standalone portability.\n *\n * When copying this component to another project, update these imports\n * to match your project's paths:\n *\n *   cn     → Your Tailwind merge utility (e.g., \"@/lib/utils\", \"~/lib/cn\")\n *   Button → shadcn/ui Button\n */\n\nexport { cn } from \"@/lib/utils\";\nexport { Button } from \"@/components/ui/button\";\n"
    },
    {
      "path": "components/tool-ui/shared/action-buttons.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/shared/action-buttons.tsx",
      "content": "\"use client\";\n\nimport type { Action } from \"./schema\";\nimport { cn, Button } from \"./_adapter\";\nimport { useActionButtons } from \"./use-action-buttons\";\n\nexport interface ActionButtonsProps {\n  actions: Action[];\n  onAction: (actionId: string) => void | Promise<void>;\n  onBeforeAction?: (actionId: string) => boolean | Promise<boolean>;\n  confirmTimeout?: number;\n  align?: \"left\" | \"center\" | \"right\";\n  className?: string;\n}\n\nexport function ActionButtons({\n  actions,\n  onAction,\n  onBeforeAction,\n  confirmTimeout = 3000,\n  align = \"right\",\n  className,\n}: ActionButtonsProps) {\n  const { actions: resolvedActions, runAction } = useActionButtons({\n    actions,\n    onAction,\n    onBeforeAction,\n    confirmTimeout,\n  });\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col gap-3\",\n        \"@sm/actions:flex-row @sm/actions:flex-wrap @sm/actions:items-center @sm/actions:gap-2\",\n        align === \"left\" && \"@sm/actions:justify-start\",\n        align === \"center\" && \"@sm/actions:justify-center\",\n        align === \"right\" && \"@sm/actions:justify-end\",\n        className,\n      )}\n    >\n      {resolvedActions.map((action) => {\n        const label = action.currentLabel;\n        const variant = action.variant || \"default\";\n\n        return (\n          <Button\n            key={action.id}\n            variant={variant}\n            onClick={() => runAction(action.id)}\n            disabled={action.isDisabled}\n            className={cn(\n              \"rounded-full px-4!\",\n              \"justify-center\",\n              \"min-h-11 w-full text-base\",\n              \"@sm/actions:min-h-0 @sm/actions:w-auto @sm/actions:px-3 @sm/actions:py-2 @sm/actions:text-sm\",\n              action.isConfirming &&\n                \"ring-destructive ring-2 ring-offset-2 motion-safe:animate-pulse\",\n            )}\n            aria-label={\n              action.shortcut ? `${label} (${action.shortcut})` : label\n            }\n          >\n            {action.isLoading && (\n              <svg\n                className=\"mr-2 h-4 w-4 motion-safe:animate-spin\"\n                xmlns=\"http://www.w3.org/2000/svg\"\n                fill=\"none\"\n                viewBox=\"0 0 24 24\"\n              >\n                <circle\n                  className=\"opacity-25\"\n                  cx=\"12\"\n                  cy=\"12\"\n                  r=\"10\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"4\"\n                />\n                <path\n                  className=\"opacity-75\"\n                  fill=\"currentColor\"\n                  d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n                />\n              </svg>\n            )}\n            {action.icon && !action.isLoading && (\n              <span className=\"mr-2\">{action.icon}</span>\n            )}\n            {label}\n            {action.shortcut && !action.isLoading && (\n              <kbd className=\"border-border bg-muted ml-2.5 hidden rounded-lg border px-2 py-0.5 font-mono text-xs font-medium sm:inline-block\">\n                {action.shortcut}\n              </kbd>\n            )}\n          </Button>\n        );\n      })}\n    </div>\n  );\n}\n"
    },
    {
      "path": "components/tool-ui/shared/actions-config.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/actions-config.ts",
      "content": "import type { Action, ActionsConfig } from \"./schema\";\n\nexport type ActionsProp = ActionsConfig | Action[];\n\nconst NEGATORY_ACTION_IDS = new Set([\n  \"cancel\",\n  \"dismiss\",\n  \"skip\",\n  \"no\",\n  \"reset\",\n  \"close\",\n  \"decline\",\n  \"reject\",\n  \"back\",\n  \"later\",\n  \"not-now\",\n  \"maybe-later\",\n]);\n\nfunction inferVariant(action: Action): Action {\n  if (action.variant) return action;\n  if (NEGATORY_ACTION_IDS.has(action.id)) {\n    return { ...action, variant: \"ghost\" };\n  }\n  return action;\n}\n\nexport function normalizeActionsConfig(\n  actions?: ActionsProp,\n): ActionsConfig | null {\n  if (!actions) return null;\n\n  const rawItems = Array.isArray(actions) ? actions : (actions.items ?? []);\n\n  if (rawItems.length === 0) {\n    return null;\n  }\n\n  const items = rawItems.map(inferVariant);\n\n  return Array.isArray(actions)\n    ? { items }\n    : {\n        items,\n        align: actions.align,\n        confirmTimeout: actions.confirmTimeout,\n      };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/contract.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/contract.ts",
      "content": "import { z } from \"zod\";\nimport { parseWithSchema, safeParseWithSchema } from \"./parse\";\n\nexport interface ToolUiContract<T> {\n  schema: z.ZodType<T>;\n  parse: (input: unknown) => T;\n  safeParse: (input: unknown) => T | null;\n}\n\nexport function defineToolUiContract<T>(\n  componentName: string,\n  schema: z.ZodType<T>,\n): ToolUiContract<T> {\n  return {\n    schema,\n    parse: (input: unknown) => parseWithSchema(schema, input, componentName),\n    safeParse: (input: unknown) => safeParseWithSchema(schema, input),\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/embedded-actions.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/embedded-actions.ts",
      "content": "import type { ActionsProp } from \"./actions-config\";\n\nexport type EmbeddedActionHandler<TState> = (\n  actionId: string,\n  state: TState,\n) => void | Promise<void>;\n\nexport type EmbeddedBeforeActionHandler<TState> = (\n  actionId: string,\n  state: TState,\n) => boolean | Promise<boolean>;\n\nexport interface EmbeddedActionsProps<TState> {\n  actions?: ActionsProp;\n  onAction?: EmbeddedActionHandler<TState>;\n  onBeforeAction?: EmbeddedBeforeActionHandler<TState>;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/parse.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/parse.ts",
      "content": "import { z } from \"zod\";\n\nfunction formatZodPath(path: Array<string | number | symbol>): string {\n  if (path.length === 0) return \"root\";\n  return path\n    .map((segment) =>\n      typeof segment === \"number\" ? `[${segment}]` : String(segment),\n    )\n    .join(\".\");\n}\n\n/**\n * Format Zod errors into a compact `path: message` string.\n */\nexport function formatZodError(error: z.ZodError): string {\n  const parts = error.issues.map((issue) => {\n    const path = formatZodPath(issue.path);\n    return `${path}: ${issue.message}`;\n  });\n\n  return Array.from(new Set(parts)).join(\"; \");\n}\n\n/**\n * Parse unknown input and throw a readable error.\n */\nexport function parseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n  name: string,\n): T {\n  const res = schema.safeParse(input);\n  if (!res.success) {\n    throw new Error(`Invalid ${name} payload: ${formatZodError(res.error)}`);\n  }\n  return res.data;\n}\n\n/**\n * Parse unknown input, returning `null` instead of throwing on failure.\n *\n * Use this in assistant-ui `render` functions where `args` stream in\n * incrementally and may be incomplete until the tool call finishes.\n */\nexport function safeParseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n): T | null {\n  const res = schema.safeParse(input);\n  return res.success ? res.data : null;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/schema.ts",
      "content": "import { z } from \"zod\";\nimport type { ReactNode } from \"react\";\n\n/**\n * Tool UI conventions:\n * - Serializable schemas are JSON-safe (no callbacks/ReactNode/`className`).\n * - Schema: `SerializableXSchema`\n * - Parser: `parseSerializableX(input: unknown)` (throws on invalid)\n * - Safe parser: `safeParseSerializableX(input: unknown)` (returns `null` on invalid)\n * - Actions: `LocalActions` for non-receipt actions and `DecisionActions` for consequential actions\n * - Root attrs: `data-tool-ui-id` + `data-slot`\n */\n\n/**\n * Schema for tool UI identity.\n *\n * Every tool UI should have a unique identifier that:\n * - Is stable across re-renders\n * - Is meaningful (not auto-generated)\n * - Is unique within the conversation\n *\n * Format recommendation: `{component-type}-{semantic-identifier}`\n * Examples: \"data-table-expenses-q3\", \"option-list-deploy-target\"\n */\nexport const ToolUIIdSchema = z.string().min(1);\n\nexport type ToolUIId = z.infer<typeof ToolUIIdSchema>;\n\n/**\n * Primary role of a Tool UI surface in a chat context.\n */\nexport const ToolUIRoleSchema = z.enum([\n  \"information\",\n  \"decision\",\n  \"control\",\n  \"state\",\n  \"composite\",\n]);\n\nexport type ToolUIRole = z.infer<typeof ToolUIRoleSchema>;\n\nexport const ToolUIReceiptOutcomeSchema = z.enum([\n  \"success\",\n  \"partial\",\n  \"failed\",\n  \"cancelled\",\n]);\n\nexport type ToolUIReceiptOutcome = z.infer<typeof ToolUIReceiptOutcomeSchema>;\n\n/**\n * Optional receipt metadata: a durable summary of an outcome.\n */\nexport const ToolUIReceiptSchema = z.object({\n  outcome: ToolUIReceiptOutcomeSchema,\n  summary: z.string().min(1),\n  identifiers: z.record(z.string(), z.string()).optional(),\n  at: z.string().datetime(),\n});\n\nexport type ToolUIReceipt = z.infer<typeof ToolUIReceiptSchema>;\n\n/**\n * Base schema for Tool UI payloads (id + optional role/receipt).\n */\nexport const ToolUISurfaceSchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  receipt: ToolUIReceiptSchema.optional(),\n});\n\nexport type ToolUISurface = z.infer<typeof ToolUISurfaceSchema>;\n\nexport const ActionSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  /**\n   * Canonical narration the assistant can use after this action is taken.\n   *\n   * Example: \"I exported the table as CSV.\" / \"I opened the link in a new tab.\"\n   */\n  sentence: z.string().optional(),\n  confirmLabel: z.string().optional(),\n  variant: z\n    .enum([\"default\", \"destructive\", \"secondary\", \"ghost\", \"outline\"])\n    .optional(),\n  icon: z.custom<ReactNode>().optional(),\n  loading: z.boolean().optional(),\n  disabled: z.boolean().optional(),\n  shortcut: z.string().optional(),\n});\n\nexport type Action = z.infer<typeof ActionSchema>;\nexport type LocalAction = Action;\nexport type DecisionAction = Action;\n\nexport const DecisionResultSchema = z.object({\n  kind: z.literal(\"decision\"),\n  version: z.literal(1),\n  decisionId: z.string().min(1),\n  actionId: z.string().min(1),\n  actionLabel: z.string().min(1),\n  at: z.string().datetime(),\n  payload: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport type DecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n> = Omit<z.infer<typeof DecisionResultSchema>, \"payload\"> & {\n  payload?: TPayload;\n};\n\nexport function createDecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n>(args: {\n  decisionId: string;\n  action: { id: string; label: string };\n  payload?: TPayload;\n}): DecisionResult<TPayload> {\n  return {\n    kind: \"decision\",\n    version: 1,\n    decisionId: args.decisionId,\n    actionId: args.action.id,\n    actionLabel: args.action.label,\n    at: new Date().toISOString(),\n    payload: args.payload,\n  };\n}\n\nexport const ActionButtonsPropsSchema = z.object({\n  actions: z.array(ActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n  className: z.string().optional(),\n});\n\nexport const SerializableActionSchema = ActionSchema.omit({ icon: true });\nexport const SerializableActionsSchema = ActionButtonsPropsSchema.extend({\n  actions: z.array(SerializableActionSchema),\n}).omit({ className: true });\n\nexport interface ActionsConfig {\n  items: Action[];\n  align?: \"left\" | \"center\" | \"right\";\n  confirmTimeout?: number;\n}\n\nexport const SerializableActionsConfigSchema = z.object({\n  items: z.array(SerializableActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n});\n\nexport type SerializableActionsConfig = z.infer<\n  typeof SerializableActionsConfigSchema\n>;\n\nexport type SerializableAction = z.infer<typeof SerializableActionSchema>;\n"
    },
    {
      "path": "components/tool-ui/shared/use-action-buttons.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/shared/use-action-buttons.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { Action } from \"./schema\";\n\nexport type UseActionButtonsOptions = {\n  actions: Action[];\n  onAction: (actionId: string) => void | Promise<void>;\n  onBeforeAction?: (actionId: string) => boolean | Promise<boolean>;\n  confirmTimeout?: number;\n};\n\nexport type UseActionButtonsResult = {\n  actions: Array<\n    Action & {\n      currentLabel: string;\n      isConfirming: boolean;\n      isExecuting: boolean;\n      isDisabled: boolean;\n      isLoading: boolean;\n    }\n  >;\n  runAction: (actionId: string) => Promise<void>;\n  confirmingActionId: string | null;\n  executingActionId: string | null;\n};\n\ntype ActionExecutionLock = {\n  tryAcquire: () => boolean;\n  release: () => void;\n};\n\nexport function createActionExecutionLock(): ActionExecutionLock {\n  let locked = false;\n\n  return {\n    tryAcquire: () => {\n      if (locked) return false;\n      locked = true;\n      return true;\n    },\n    release: () => {\n      locked = false;\n    },\n  };\n}\n\nexport function useActionButtons(\n  options: UseActionButtonsOptions,\n): UseActionButtonsResult {\n  const { actions, onAction, onBeforeAction, confirmTimeout = 3000 } = options;\n\n  const [confirmingActionId, setConfirmingActionId] = useState<string | null>(\n    null,\n  );\n  const [executingActionId, setExecutingActionId] = useState<string | null>(\n    null,\n  );\n  const executionLockRef = useRef<ActionExecutionLock>(\n    createActionExecutionLock(),\n  );\n\n  useEffect(() => {\n    if (!confirmingActionId) return;\n    const id = setTimeout(() => setConfirmingActionId(null), confirmTimeout);\n    return () => clearTimeout(id);\n  }, [confirmingActionId, confirmTimeout]);\n\n  useEffect(() => {\n    if (!confirmingActionId) return;\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        setConfirmingActionId(null);\n      }\n    };\n\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [confirmingActionId]);\n\n  const runAction = useCallback(\n    async (actionId: string) => {\n      const action = actions.find((a) => a.id === actionId);\n      if (!action) return;\n\n      const isAnyActionExecuting = executingActionId !== null;\n      if (action.disabled || action.loading || isAnyActionExecuting) {\n        return;\n      }\n\n      if (action.confirmLabel && confirmingActionId !== action.id) {\n        setConfirmingActionId(action.id);\n        return;\n      }\n\n      if (!executionLockRef.current.tryAcquire()) {\n        return;\n      }\n\n      if (onBeforeAction) {\n        const shouldProceed = await onBeforeAction(action.id);\n        if (!shouldProceed) {\n          setConfirmingActionId(null);\n          executionLockRef.current.release();\n          return;\n        }\n      }\n\n      try {\n        setExecutingActionId(action.id);\n        await onAction(action.id);\n      } finally {\n        executionLockRef.current.release();\n        setExecutingActionId(null);\n        setConfirmingActionId(null);\n      }\n    },\n    [actions, confirmingActionId, executingActionId, onAction, onBeforeAction],\n  );\n\n  const resolvedActions = useMemo(\n    () =>\n      actions.map((action) => {\n        const isConfirming = confirmingActionId === action.id;\n        const isThisActionExecuting = executingActionId === action.id;\n        const isLoading = action.loading || isThisActionExecuting;\n        const isDisabled =\n          action.disabled ||\n          (executingActionId !== null && !isThisActionExecuting);\n        const currentLabel =\n          isConfirming && action.confirmLabel\n            ? action.confirmLabel\n            : action.label;\n\n        return {\n          ...action,\n          currentLabel,\n          isConfirming,\n          isExecuting: isThisActionExecuting,\n          isDisabled,\n          isLoading,\n        };\n      }),\n    [actions, confirmingActionId, executingActionId],\n  );\n\n  return {\n    actions: resolvedActions,\n    runAction,\n    confirmingActionId,\n    executingActionId,\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/use-controllable-state.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/use-controllable-state.ts",
      "content": "\"use client\";\n\nimport { useCallback, useMemo, useRef, useState } from \"react\";\n\nexport type UseControllableStateOptions<T> = {\n  value?: T;\n  defaultValue: T;\n  onChange?: (next: T) => void;\n};\n\nexport function useControllableState<T>({\n  value,\n  defaultValue,\n  onChange,\n}: UseControllableStateOptions<T>) {\n  const [uncontrolled, setUncontrolled] = useState<T>(defaultValue);\n  const isControlled = value !== undefined;\n\n  const currentValue = useMemo(\n    () => (isControlled ? (value as T) : uncontrolled),\n    [isControlled, value, uncontrolled],\n  );\n  const currentValueRef = useRef(currentValue);\n  currentValueRef.current = currentValue;\n\n  const setValue = useCallback(\n    (next: T | ((prev: T) => T)) => {\n      const resolved =\n        typeof next === \"function\"\n          ? (next as (prev: T) => T)(currentValueRef.current)\n          : next;\n\n      currentValueRef.current = resolved;\n      if (!isControlled) {\n        setUncontrolled(resolved);\n      }\n\n      onChange?.(resolved);\n      return resolved;\n    },\n    [isControlled, onChange],\n  );\n\n  const setUncontrolledValue = useCallback((next: T) => {\n    setUncontrolled(next);\n  }, []);\n\n  return {\n    value: currentValue,\n    isControlled,\n    setValue,\n    setUncontrolledValue,\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/use-signature-reset.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/use-signature-reset.ts",
      "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport function useSignatureReset(\n  signature: string,\n  onSignatureChange: () => void,\n) {\n  const previousSignature = useRef(signature);\n\n  useEffect(() => {\n    if (previousSignature.current === signature) return;\n    previousSignature.current = signature;\n    onSignatureChange();\n  }, [signature, onSignatureChange]);\n}\n"
    }
  ]
}
