{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-tracker",
  "type": "registry:block",
  "title": "Progress Tracker",
  "description": "Show real-time status feedback for multi-step operations in AI interfaces.",
  "dependencies": [
    "lucide-react",
    "zod"
  ],
  "files": [
    {
      "path": "components/tool-ui/progress-tracker/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/progress-tracker/_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 */\n\nexport { cn } from \"@/lib/utils\";\n"
    },
    {
      "path": "components/tool-ui/progress-tracker/index.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/progress-tracker/index.tsx",
      "content": "export { ProgressTracker } from \"./progress-tracker\";\nexport {\n  type SerializableProgressTracker,\n  type ProgressTrackerProps,\n  type ProgressTrackerChoice,\n  type ProgressStep,\n} from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/progress-tracker/progress-tracker.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/progress-tracker/progress-tracker.tsx",
      "content": "import { cn } from \"./_adapter\";\nimport type {\n  ProgressStep,\n  ProgressTrackerChoice,\n  ProgressTrackerProps,\n} from \"./schema\";\nimport { Check, X, Loader2, Timer, AlertCircle } from \"lucide-react\";\nimport type { LucideIcon } from \"lucide-react\";\n\nfunction formatElapsedTime(milliseconds: number): string {\n  const roundedSeconds = Math.round(Math.max(0, milliseconds) / 100) / 10;\n\n  if (roundedSeconds < 60) {\n    return `${roundedSeconds.toFixed(1)}s`;\n  }\n\n  const wholeSeconds = Math.floor(roundedSeconds);\n  const minutes = Math.floor(wholeSeconds / 60);\n  const remainingSeconds = wholeSeconds % 60;\n  return `${minutes}m ${remainingSeconds}s`;\n}\n\nfunction formatElapsedTimeDateTime(milliseconds: number): string {\n  const roundedSeconds = Math.round(Math.max(0, milliseconds) / 100) / 10;\n\n  if (roundedSeconds < 60) {\n    return `PT${Number(roundedSeconds.toFixed(1))}S`;\n  }\n\n  const wholeSeconds = Math.floor(roundedSeconds);\n  const hours = Math.floor(wholeSeconds / 3600);\n  const minutes = Math.floor((wholeSeconds % 3600) / 60);\n  const seconds = wholeSeconds % 60;\n\n  const hourPart = hours > 0 ? `${hours}H` : \"\";\n  const minutePart = minutes > 0 ? `${minutes}M` : \"\";\n  const secondPart = seconds > 0 ? `${seconds}S` : \"\";\n\n  if (!hourPart && !minutePart && !secondPart) {\n    return \"PT0S\";\n  }\n\n  return `PT${hourPart}${minutePart}${secondPart}`;\n}\n\nfunction getCurrentStepId(steps: ProgressStep[]): string | null {\n  const inProgressStep = steps.find((s) => s.status === \"in-progress\");\n  if (inProgressStep) return inProgressStep.id;\n\n  const failedStep = steps.find((s) => s.status === \"failed\");\n  if (failedStep) return failedStep.id;\n\n  const firstPendingStep = steps.find((s) => s.status === \"pending\");\n  if (firstPendingStep) return firstPendingStep.id;\n\n  return null;\n}\n\nfunction getReceiptState(outcome: ProgressTrackerChoice[\"outcome\"]): {\n  toneClassName: string;\n  icon: LucideIcon;\n} {\n  switch (outcome) {\n    case \"success\":\n      return {\n        toneClassName: \"text-emerald-600 dark:text-emerald-500\",\n        icon: Check,\n      };\n    case \"partial\":\n      return {\n        toneClassName: \"text-amber-600 dark:text-amber-500\",\n        icon: AlertCircle,\n      };\n    case \"failed\":\n      return {\n        toneClassName: \"text-destructive\",\n        icon: AlertCircle,\n      };\n    case \"cancelled\":\n      return {\n        toneClassName: \"text-muted-foreground\",\n        icon: X,\n      };\n  }\n}\n\ninterface StepIndicatorProps {\n  status: \"pending\" | \"in-progress\" | \"completed\" | \"failed\";\n}\n\nfunction StepIndicator({ status }: StepIndicatorProps) {\n  if (status === \"pending\") {\n    return (\n      <span\n        className=\"bg-card border-border flex size-6 shrink-0 items-center justify-center rounded-full border motion-safe:transition-all motion-safe:duration-200\"\n        aria-hidden=\"true\"\n      />\n    );\n  }\n\n  if (status === \"in-progress\") {\n    return (\n      <span\n        className=\"bg-card border-border flex size-6 shrink-0 items-center justify-center rounded-full border shadow-[0_0_0_4px_hsl(var(--primary)/0.1)] motion-safe:transition-all motion-safe:duration-300\"\n        aria-hidden=\"true\"\n      >\n        <Loader2 className=\"text-primary size-5 motion-safe:animate-spin\" />\n      </span>\n    );\n  }\n\n  if (status === \"completed\") {\n    return (\n      <span\n        className=\"bg-primary text-primary-foreground border-primary flex size-6 shrink-0 items-center justify-center rounded-full border shadow-sm motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-75 motion-safe:duration-300 motion-safe:ease-out\"\n        aria-hidden=\"true\"\n      >\n        <Check\n          className=\"size-4 motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-75 motion-safe:delay-75 motion-safe:duration-200 motion-safe:fill-mode-both\"\n          strokeWidth={3}\n        />\n      </span>\n    );\n  }\n\n  if (status === \"failed\") {\n    return (\n      <span\n        className=\"bg-destructive border-destructive flex size-6 shrink-0 items-center justify-center rounded-full border text-white shadow-sm motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-75 motion-safe:duration-300 motion-safe:ease-out dark:border-red-600 dark:bg-red-600\"\n        aria-hidden=\"true\"\n      >\n        <X\n          className=\"size-4 motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-75 motion-safe:delay-75 motion-safe:duration-200 motion-safe:fill-mode-both\"\n          strokeWidth={3}\n        />\n      </span>\n    );\n  }\n\n  return null;\n}\n\nfunction ElapsedTimeBadge({ elapsedTime }: { elapsedTime?: number }) {\n  if (elapsedTime === undefined || elapsedTime <= 0) {\n    return null;\n  }\n\n  return (\n    <div className=\"text-muted-foreground flex items-center gap-1.5 font-mono text-xs\">\n      <Timer className=\"-mt-px size-3.5\" />\n      <time dateTime={formatElapsedTimeDateTime(elapsedTime)}>\n        {formatElapsedTime(elapsedTime)}\n      </time>\n    </div>\n  );\n}\n\ninterface ProgressTrackerBaseProps {\n  id: ProgressTrackerProps[\"id\"];\n  steps: ProgressTrackerProps[\"steps\"];\n  elapsedTime?: ProgressTrackerProps[\"elapsedTime\"];\n  className?: ProgressTrackerProps[\"className\"];\n}\n\nfunction ProgressTrackerReceipt({\n  id,\n  steps,\n  elapsedTime,\n  className,\n  choice,\n}: ProgressTrackerBaseProps & { choice: ProgressTrackerChoice }) {\n  const receiptState = getReceiptState(choice.outcome);\n  const ReceiptIcon = receiptState.icon;\n\n  return (\n    <div\n      className={cn(\n        \"isolate flex w-full max-w-md min-w-80 flex-col\",\n        \"text-foreground select-none\",\n        \"motion-safe:animate-in motion-safe:fade-in motion-safe:blur-in-sm motion-safe:zoom-in-95 motion-safe:duration-300 motion-safe:ease-[cubic-bezier(0.16,1,0.3,1)] motion-safe:fill-mode-both\",\n        className,\n      )}\n      data-slot=\"progress-tracker\"\n      data-tool-ui-id={id}\n      data-receipt=\"true\"\n      role=\"status\"\n      aria-label={choice.summary}\n    >\n      <div className=\"bg-card/60 flex w-full flex-col gap-4 rounded-2xl border p-5 shadow-xs\">\n        <div className=\"flex items-center justify-between\">\n          <ElapsedTimeBadge elapsedTime={elapsedTime} />\n          <span\n            className={cn(\n              \"flex items-center gap-1.5 text-xs font-medium\",\n              receiptState.toneClassName,\n            )}\n          >\n            <ReceiptIcon className=\"size-3.5\" />\n            {choice.summary}\n          </span>\n        </div>\n\n        <ol className=\"m-0 flex list-none flex-col gap-2 p-0\">\n          {steps.map((step, index) => (\n            <li\n              key={step.id}\n              className=\"relative -mx-2 flex items-start gap-3 rounded-lg px-2 py-1.5\"\n            >\n              {index < steps.length - 1 && (\n                <div\n                  className=\"bg-border absolute top-8 left-5 w-px\"\n                  style={{\n                    height: \"calc(100% + 0.5rem)\",\n                  }}\n                  aria-hidden=\"true\"\n                />\n              )}\n              <div className=\"relative z-10\">\n                <StepIndicator status={step.status} />\n              </div>\n              <div className=\"flex flex-1 flex-col gap-0.5\">\n                <span className=\"text-sm leading-6 font-medium\">\n                  {step.label}\n                </span>\n                {step.description && (\n                  <span className=\"text-muted-foreground text-sm\">\n                    {step.description}\n                  </span>\n                )}\n              </div>\n            </li>\n          ))}\n        </ol>\n      </div>\n    </div>\n  );\n}\n\nfunction ProgressTrackerLive({\n  id,\n  steps,\n  elapsedTime,\n  className,\n}: ProgressTrackerBaseProps) {\n  const hasInProgress = steps.some((step) => step.status === \"in-progress\");\n  const currentStepId = getCurrentStepId(steps);\n\n  return (\n    <article\n      className={cn(\n        \"isolate flex w-full max-w-md min-w-80 flex-col gap-3\",\n        \"text-foreground select-none\",\n        className,\n      )}\n      data-slot=\"progress-tracker\"\n      data-tool-ui-id={id}\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-busy={hasInProgress}\n    >\n      <div className=\"bg-card flex w-full flex-col gap-4 rounded-2xl border p-5 shadow-xs\">\n        <ElapsedTimeBadge elapsedTime={elapsedTime} />\n\n        <ol className=\"m-0 flex list-none flex-col gap-3 p-0\">\n          {steps.map((step, index) => {\n            const isCurrent = step.id === currentStepId;\n            const isActive = step.status === \"in-progress\";\n            const isFailed = step.status === \"failed\";\n            const hasDescription = !!step.description;\n            const shouldShowDescription = isActive || isFailed;\n\n            return (\n              <li\n                key={step.id}\n                className=\"relative -mx-2\"\n                aria-current={isCurrent ? \"step\" : undefined}\n              >\n                {index < steps.length - 1 && (\n                  <div\n                    className={cn(\n                      \"bg-border absolute top-6 left-5 w-px\",\n                      \"motion-safe:transition-all motion-safe:duration-300\",\n                    )}\n                    style={{\n                      height: \"calc(100% + 0.25rem)\",\n                    }}\n                    aria-hidden=\"true\"\n                  />\n                )}\n                <div\n                  className={cn(\n                    \"relative z-10 flex items-start gap-3 rounded-lg px-2 py-1.5\",\n                    \"motion-safe:transition-all motion-safe:duration-300\",\n                    isCurrent && \"bg-primary/5\",\n                  )}\n                  style={{\n                    backdropFilter: isCurrent ? \"blur(2px)\" : undefined,\n                  }}\n                >\n                  <div className=\"relative z-10\">\n                    <StepIndicator status={step.status} />\n                  </div>\n                  <div className=\"flex flex-1 flex-col\">\n                    <span\n                      className={cn(\n                        \"text-sm leading-6 font-medium\",\n                        step.status === \"pending\" && \"text-muted-foreground\",\n                        step.status === \"in-progress\" &&\n                          \"motion-safe:shimmer shimmer-invert text-foreground\",\n                      )}\n                    >\n                      {step.label}\n                    </span>\n                    {hasDescription && (\n                      <div\n                        className={cn(\n                          \"grid motion-safe:transition-[grid-template-rows,opacity] motion-safe:duration-300 motion-safe:ease-out\",\n                          shouldShowDescription\n                            ? \"grid-rows-[1fr] opacity-100\"\n                            : \"grid-rows-[0fr] opacity-0\",\n                        )}\n                        aria-hidden={!shouldShowDescription}\n                      >\n                        <div className=\"overflow-hidden\">\n                          <span className=\"text-muted-foreground block pt-0.5 text-sm\">\n                            {step.description}\n                          </span>\n                        </div>\n                      </div>\n                    )}\n                  </div>\n                </div>\n              </li>\n            );\n          })}\n        </ol>\n      </div>\n    </article>\n  );\n}\n\nfunction ProgressTrackerRoot({\n  id,\n  steps,\n  elapsedTime,\n  className,\n  choice,\n}: ProgressTrackerProps) {\n  const viewKey = choice ? `receipt-${choice.outcome}` : \"interactive\";\n\n  return (\n    <div key={viewKey} className=\"contents\">\n      {choice ? (\n        <ProgressTrackerReceipt\n          id={id}\n          steps={steps}\n          elapsedTime={elapsedTime}\n          className={className}\n          choice={choice}\n        />\n      ) : (\n        <ProgressTrackerLive\n          id={id}\n          steps={steps}\n          elapsedTime={elapsedTime}\n          className={className}\n        />\n      )}\n    </div>\n  );\n}\n\ntype ProgressTrackerComponent = typeof ProgressTrackerRoot & {\n  Live: typeof ProgressTrackerLive;\n  Receipt: typeof ProgressTrackerReceipt;\n};\n\nexport const ProgressTracker = Object.assign(ProgressTrackerRoot, {\n  Live: ProgressTrackerLive,\n  Receipt: ProgressTrackerReceipt,\n}) as ProgressTrackerComponent;\n"
    },
    {
      "path": "components/tool-ui/progress-tracker/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/progress-tracker/README.md",
      "content": "# Progress Tracker\n\nImplementation for the \"progress-tracker\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/progress-tracker/index.tsx\n- serializable schema + parse helpers: components/tool-ui/progress-tracker/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/progress-tracker/content.mdx\n- Preset payload: lib/presets/progress-tracker.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/progress-tracker/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/progress-tracker/schema.ts",
      "content": "import { z } from \"zod\";\nimport {\n  ToolUISurfaceSchema,\n  ToolUIReceiptSchema,\n  type ToolUIReceipt,\n} from \"../shared/schema\";\nimport { defineToolUiContract } from \"../shared/contract\";\n\n/**\n * Receipt state for ProgressTracker showing the outcome of a workflow.\n */\nexport type ProgressTrackerChoice = ToolUIReceipt;\n\nexport const ProgressStepSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  description: z.string().optional(),\n  status: z.enum([\"pending\", \"in-progress\", \"completed\", \"failed\"]),\n});\n\nexport type ProgressStep = z.infer<typeof ProgressStepSchema>;\n\nconst ProgressStepsSchema = z\n  .array(ProgressStepSchema)\n  .min(1)\n  .superRefine((steps, ctx) => {\n    const seenIds = new Set<string>();\n\n    for (const [index, step] of steps.entries()) {\n      if (seenIds.has(step.id)) {\n        ctx.addIssue({\n          code: z.ZodIssueCode.custom,\n          message: `Duplicate step id: \"${step.id}\"`,\n          path: [index, \"id\"],\n        });\n      }\n\n      seenIds.add(step.id);\n    }\n  });\n\nexport const SerializableProgressTrackerSchema = ToolUISurfaceSchema.omit({\n  receipt: true,\n})\n  .extend({\n    steps: ProgressStepsSchema,\n    elapsedTime: z.number().finite().nonnegative().optional(),\n    /**\n     * When set, renders the component in receipt state showing the workflow outcome.\n     */\n    choice: ToolUIReceiptSchema.optional(),\n  })\n  .strict();\n\nexport type SerializableProgressTracker = z.infer<\n  typeof SerializableProgressTrackerSchema\n>;\n\nconst SerializableProgressTrackerSchemaContract = defineToolUiContract(\n  \"ProgressTracker\",\n  SerializableProgressTrackerSchema,\n);\n\nexport const parseSerializableProgressTracker: (\n  input: unknown,\n) => SerializableProgressTracker =\n  SerializableProgressTrackerSchemaContract.parse;\n\nexport const safeParseSerializableProgressTracker: (\n  input: unknown,\n) => SerializableProgressTracker | null =\n  SerializableProgressTrackerSchemaContract.safeParse;\n\nexport interface ProgressTrackerProps extends SerializableProgressTracker {\n  className?: string;\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/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"
    }
  ]
}
