{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "type": "registry:block",
  "title": "Chart",
  "description": "Visualize data with interactive charts.",
  "dependencies": [
    "recharts@2.15.4",
    "zod"
  ],
  "registryDependencies": [
    "card",
    "chart"
  ],
  "files": [
    {
      "path": "components/tool-ui/chart/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/chart/_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 *   Chart → shadcn/ui Chart (recharts wrapper)\n *   Card  → shadcn/ui Card\n */\n\nexport { cn } from \"@/lib/utils\";\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  type ChartConfig,\n} from \"@/components/ui/chart\";\nexport {\n  Card,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n  CardContent,\n} from \"@/components/ui/card\";\n"
    },
    {
      "path": "components/tool-ui/chart/chart.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/chart/chart.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useCallback, memo } from \"react\";\nimport {\n  BarChart,\n  LineChart,\n  Bar,\n  Line,\n  XAxis,\n  YAxis,\n  CartesianGrid,\n} from \"recharts\";\n\nimport {\n  cn,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  Card,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n  CardContent,\n  type ChartConfig,\n} from \"./_adapter\";\nimport type { ChartProps } from \"./schema\";\n\nconst DEFAULT_COLORS = [\n  \"var(--chart-1)\",\n  \"var(--chart-2)\",\n  \"var(--chart-3)\",\n  \"var(--chart-4)\",\n  \"var(--chart-5)\",\n];\n\nexport const Chart = memo(function Chart({\n  id,\n  type,\n  title,\n  description,\n  data,\n  xKey,\n  series,\n  colors,\n  showLegend = false,\n  showGrid = true,\n  className,\n  onDataPointClick,\n}: ChartProps) {\n  const palette = colors?.length ? colors : DEFAULT_COLORS;\n\n  const seriesColors = useMemo(\n    () =>\n      series.map(\n        (seriesItem, index) =>\n          seriesItem.color ?? palette[index % palette.length],\n      ),\n    [series, palette],\n  );\n\n  const chartConfig: ChartConfig = useMemo(\n    () =>\n      Object.fromEntries(\n        series.map((seriesItem, index) => [\n          seriesItem.key,\n          {\n            label: seriesItem.label,\n            color: seriesColors[index],\n          },\n        ]),\n      ),\n    [series, seriesColors],\n  );\n\n  const handleDataPointClick = useCallback(\n    (\n      seriesKey: string,\n      seriesLabel: string,\n      payload: Record<string, unknown>,\n      index: number,\n    ) => {\n      onDataPointClick?.({\n        seriesKey,\n        seriesLabel,\n        xValue: payload[xKey],\n        yValue: payload[seriesKey],\n        index,\n        payload,\n      });\n    },\n    [onDataPointClick, xKey],\n  );\n\n  const ChartComponent = type === \"bar\" ? BarChart : LineChart;\n\n  const chartContent = (\n    <ChartContainer\n      config={chartConfig}\n      className=\"min-h-[200px] w-full\"\n      data-tool-ui-id={id}\n    >\n      <ChartComponent data={data} accessibilityLayer>\n        {showGrid && <CartesianGrid vertical={false} />}\n        <XAxis\n          dataKey={xKey}\n          tickLine={false}\n          tickMargin={10}\n          axisLine={false}\n        />\n        <YAxis tickLine={false} axisLine={false} tickMargin={10} />\n        <ChartTooltip content={<ChartTooltipContent />} />\n        {showLegend && <ChartLegend content={<ChartLegendContent />} />}\n\n        {type === \"bar\" &&\n          series.map((s, i) => (\n            <Bar\n              key={s.key}\n              dataKey={s.key}\n              fill={seriesColors[i]}\n              radius={4}\n              onClick={(data) =>\n                handleDataPointClick(s.key, s.label, data.payload, data.index)\n              }\n              cursor={onDataPointClick ? \"pointer\" : undefined}\n            />\n          ))}\n\n        {type === \"line\" &&\n          series.map((s, i) => (\n            <Line\n              key={s.key}\n              dataKey={s.key}\n              type=\"monotone\"\n              stroke={seriesColors[i]}\n              strokeWidth={2}\n              dot={{ r: 4, cursor: onDataPointClick ? \"pointer\" : undefined }}\n              activeDot={{\n                r: 6,\n                cursor: onDataPointClick ? \"pointer\" : undefined,\n                // Recharts types are incorrect - onClick receives (event, dotData) at runtime\n                onClick: ((\n                  _: unknown,\n                  dotData: { payload: Record<string, unknown>; index: number },\n                ) => {\n                  handleDataPointClick(\n                    s.key,\n                    s.label,\n                    dotData.payload,\n                    dotData.index,\n                  );\n                }) as unknown as React.MouseEventHandler,\n              }}\n            />\n          ))}\n      </ChartComponent>\n    </ChartContainer>\n  );\n\n  return (\n    <Card\n      className={cn(\"w-full min-w-80\", className)}\n      data-tool-ui-id={id}\n      data-slot=\"chart\"\n    >\n      {(title || description) && (\n        <CardHeader>\n          {title && <CardTitle className=\"text-pretty\">{title}</CardTitle>}\n          {description && (\n            <CardDescription className=\"text-pretty\">\n              {description}\n            </CardDescription>\n          )}\n        </CardHeader>\n      )}\n      <CardContent>{chartContent}</CardContent>\n    </Card>\n  );\n});\n"
    },
    {
      "path": "components/tool-ui/chart/index.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/chart/index.tsx",
      "content": "export { Chart } from \"./chart\";\nexport {\n  type ChartProps,\n  type ChartSeries,\n  type ChartDataPoint,\n  type ChartClientProps,\n  type SerializableChart,\n} from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/chart/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/chart/README.md",
      "content": "# Chart\n\nImplementation for the \"chart\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/chart/index.tsx\n- serializable schema + parse helpers: components/tool-ui/chart/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/chart/content.mdx\n- Preset payload: lib/presets/chart.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/chart/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/chart/schema.ts",
      "content": "import { z } from \"zod\";\nimport { defineToolUiContract } from \"../shared/contract\";\nimport {\n  ToolUIIdSchema,\n  ToolUIReceiptSchema,\n  ToolUIRoleSchema,\n} from \"../shared/schema\";\n\nexport const ChartSeriesSchema = z.object({\n  key: z.string().min(1),\n  label: z.string().min(1),\n  color: z.string().optional(),\n});\n\nexport type ChartSeries = z.infer<typeof ChartSeriesSchema>;\n\nexport const ChartPropsSchema = z\n  .object({\n    id: ToolUIIdSchema,\n    role: ToolUIRoleSchema.optional(),\n    receipt: ToolUIReceiptSchema.optional(),\n    type: z.enum([\"bar\", \"line\"]),\n    title: z.string().optional(),\n    description: z.string().optional(),\n    data: z.array(z.record(z.string(), z.unknown())).min(1),\n    xKey: z.string().min(1),\n    series: z.array(ChartSeriesSchema).min(1),\n    /** Color palette applied to series in order. Individual series.color takes precedence. */\n    colors: z.array(z.string().min(1)).min(1).optional(),\n    showLegend: z.boolean().optional(),\n    showGrid: z.boolean().optional(),\n  })\n  .superRefine((value, ctx) => {\n    const seenSeriesKeys = new Set<string>();\n    value.series.forEach((series, index) => {\n      if (seenSeriesKeys.has(series.key)) {\n        ctx.addIssue({\n          code: \"custom\",\n          path: [\"series\", index, \"key\"],\n          message: `Duplicate series key \"${series.key}\".`,\n        });\n        return;\n      }\n      seenSeriesKeys.add(series.key);\n    });\n\n    value.data.forEach((row, rowIndex) => {\n      if (!(value.xKey in row)) {\n        ctx.addIssue({\n          code: \"custom\",\n          path: [\"data\", rowIndex, value.xKey],\n          message: `Missing xKey \"${value.xKey}\" in data row.`,\n        });\n      } else {\n        const xVal = row[value.xKey];\n        const isValidX = typeof xVal === \"string\" || typeof xVal === \"number\";\n        if (!isValidX) {\n          ctx.addIssue({\n            code: \"custom\",\n            path: [\"data\", rowIndex, value.xKey],\n            message: `Expected \"${value.xKey}\" to be a string or number.`,\n          });\n        }\n      }\n\n      value.series.forEach((series) => {\n        if (!(series.key in row)) {\n          ctx.addIssue({\n            code: \"custom\",\n            path: [\"data\", rowIndex, series.key],\n            message: `Missing series key \"${series.key}\" in data row.`,\n          });\n          return;\n        }\n\n        const yVal = row[series.key];\n        if (yVal === null) {\n          return;\n        }\n        if (typeof yVal !== \"number\" || !Number.isFinite(yVal)) {\n          ctx.addIssue({\n            code: \"custom\",\n            path: [\"data\", rowIndex, series.key],\n            message: `Expected \"${series.key}\" to be a finite number (or null).`,\n          });\n        }\n      });\n    });\n  });\n\nexport type ChartDataPoint = {\n  seriesKey: string;\n  seriesLabel: string;\n  xValue: unknown;\n  yValue: unknown;\n  index: number;\n  payload: Record<string, unknown>;\n};\n\nexport type ChartClientProps = {\n  className?: string;\n  onDataPointClick?: (point: ChartDataPoint) => void;\n};\n\nexport type ChartProps = z.infer<typeof ChartPropsSchema> & ChartClientProps;\n\nexport const SerializableChartSchema = ChartPropsSchema;\n\nexport type SerializableChart = z.infer<typeof SerializableChartSchema>;\n\nconst SerializableChartSchemaContract = defineToolUiContract(\n  \"Chart\",\n  SerializableChartSchema,\n);\n\nexport const parseSerializableChart: (input: unknown) => SerializableChart =\n  SerializableChartSchemaContract.parse;\n\nexport const safeParseSerializableChart: (\n  input: unknown,\n) => SerializableChart | null = SerializableChartSchemaContract.safeParse;\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"
    }
  ]
}
