{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pokerstudy-json-render",
  "title": "Generative UI (json-render)",
  "description": "Every Poker Study component as a json-render catalog entry: a props schema, a description that tells a model when to use it, an example, and the renderer that draws it. Spread them into your catalog and registry next to @json-render/shadcn.",
  "dependencies": [
    "@json-render/core@^0.19.0",
    "@json-render/react@^0.19.0",
    "zod@^4"
  ],
  "registryDependencies": [
    "https://www.pokerstudy.ai/r/poker-core.json",
    "https://www.pokerstudy.ai/r/pokerstudy-api.json",
    "https://www.pokerstudy.ai/r/playing-card.json",
    "https://www.pokerstudy.ai/r/strategy-bar.json",
    "https://www.pokerstudy.ai/r/grade-badge.json",
    "https://www.pokerstudy.ai/r/player-stats.json",
    "https://www.pokerstudy.ai/r/hand-matrix.json",
    "https://www.pokerstudy.ai/r/chips.json",
    "https://www.pokerstudy.ai/r/trainer-cards.json",
    "https://www.pokerstudy.ai/r/gto-strategy-bar.json",
    "https://www.pokerstudy.ai/r/poker-table.json",
    "https://www.pokerstudy.ai/r/range-explorer.json",
    "https://www.pokerstudy.ai/r/preflop-range-explorer.json",
    "https://www.pokerstudy.ai/r/decision-grader.json",
    "https://www.pokerstudy.ai/r/agent-advisor.json",
    "https://www.pokerstudy.ai/r/training-report.json",
    "https://www.pokerstudy.ai/r/opponent-profile.json",
    "https://www.pokerstudy.ai/r/limp-range.json",
    "https://www.pokerstudy.ai/r/session-stats.json",
    "https://www.pokerstudy.ai/r/trainer-action-bars.json",
    "https://www.pokerstudy.ai/r/trainer-feedback.json"
  ],
  "files": [
    {
      "path": "src/lib/pokerstudy/catalog.ts",
      "content": "import { z } from \"zod\";\n\n/**\n * Poker Study component definitions for json-render catalogs, the poker\n * counterpart of `shadcnComponentDefinitions`. Each entry is the props schema\n * a model may fill, a description that tells it when to pick the component,\n * and an example. Pair them with `pokerstudyComponents` from\n * `components/pokerstudy/json-render`:\n *\n *   const catalog = defineCatalog(schema, {\n *     components: { Stack: shadcnComponentDefinitions.Stack, ...pokerstudyComponentDefinitions },\n *     actions: {},\n *   });\n *   const { registry } = defineRegistry(catalog, { components: { Stack: shadcnComponents.Stack, ...pokerstudyComponents } });\n *\n * Every prop is plain JSON: nullable rather than optional (models omit\n * fields less reliably than they write null), no callbacks, no React nodes.\n * Blocks that read data fetch it themselves through the `pokerstudy-api`\n * proxy, so the model passes ids and spots, never the payloads.\n *\n * Zod only, so a server route can build the prompt from it without pulling in\n * client components.\n */\n\nconst card = z.string().describe('One card: rank then suit, e.g. \"Ah\", \"Td\", \"2c\".');\nconst cardSize = z.enum([\"xs\", \"sm\", \"md\", \"lg\"]).nullable();\nconst grade = z.enum([\"perfect\", \"correct\", \"inaccuracy\", \"mistake\", \"blunder\"]);\nconst statKey = z.enum([\n  \"vpip\",\n  \"pfr\",\n  \"rfi\",\n  \"threeBet\",\n  \"foldToThreeBet\",\n  \"cbetFlop\",\n  \"cbetFlopIp\",\n  \"cbetFlopOop\",\n  \"foldToCbetFlop\",\n  \"checkRaiseFlop\",\n  \"cbetTurn\",\n  \"cbetRiver\",\n  \"wtsd\",\n  \"wonAtShowdown\",\n]);\nconst palette = z\n  .enum([\"classic\", \"midnight\", \"frost\", \"mint\"])\n  .nullable()\n  .describe('Action colors. \"classic\": fold blue, check and call green, raises red. \"midnight\": steel-blue fold, mint call, rose raises. \"frost\": slate-blue fold, teal call, violet raises. \"mint\": blue fold, soft green call, warm red raises. Null is mint.');\nconst tableTheme = z.enum([\"matrix\", \"coinpoker\", \"ggpoker\", \"acr\", \"ignition\", \"halloween\", \"pokerdata\", \"felt\"]);\n\nconst snapshot = z.object({\n  hole: z.array(card),\n  board: z.array(card).nullable(),\n  pot: z.number().nullable(),\n  big_blind: z.number().nullable(),\n  my_seat: z.number(),\n  dealer_seat: z.number(),\n  seats: z.array(z.number()).describe(\"The occupied seat numbers in table order, e.g. [1, 2, 3, 4, 5, 6].\"),\n  valid_actions: z.array(\n    z.union([\n      z.object({ action: z.enum([\"fold\", \"check\"]) }),\n      z.object({ action: z.literal(\"call\"), amount: z.number() }),\n      z.object({ action: z.literal(\"raise\"), min: z.number(), max: z.number() }),\n    ]),\n  ),\n});\n\nconst snapshotExample = {\n  hole: [\"Ah\", \"Ad\"],\n  board: null,\n  pot: 30,\n  big_blind: 20,\n  my_seat: 4,\n  dealer_seat: 1,\n  seats: [1, 2, 3, 4, 5, 6],\n  valid_actions: [{ action: \"fold\" }, { action: \"call\", amount: 20 }, { action: \"raise\", min: 40, max: 2000 }],\n};\n\nexport const pokerstudyComponentDefinitions = {\n  PlayingCard: {\n    props: z.object({ card: card.nullable(), size: cardSize }),\n    description: \"One four-color playing card. A null card is an empty slot. For a hand or a board use HoleCards or Board.\",\n    example: { card: \"As\", size: \"md\" },\n  },\n\n  HoleCards: {\n    props: z.object({\n      cards: z.array(card).describe(\"2 for hold'em, 4 to 6 for Omaha.\"),\n      size: cardSize,\n    }),\n    description: \"A player's hole cards, fanned. Use whenever the answer names a specific hand.\",\n    example: { cards: [\"Ah\", \"Kd\"], size: \"md\" },\n  },\n\n  Board: {\n    props: z.object({ cards: z.array(card).describe(\"0 to 5 community cards.\"), size: cardSize }),\n    description: \"The community cards. Always five slots; streets not dealt yet show as empty.\",\n    example: { cards: [\"Qs\", \"Jh\", \"2c\"], size: \"sm\" },\n  },\n\n  StrategyBar: {\n    props: z.object({\n      items: z.array(\n        z.object({\n          label: z.string().describe('Action, e.g. \"Fold\", \"Call\", \"Raise 2.5bb\".'),\n          freq: z.number().describe(\"Frequency from 0 to 1. The bar normalizes.\"),\n          color: z.string().nullable().describe(\"CSS color. Null picks the action color from the label.\"),\n          detail: z.string().nullable().describe('Text after the label, e.g. an EV like \"+1.2bb\".'),\n        }),\n      ),\n      highlight: z.string().nullable().describe(\"Label of the action the player chose; it is outlined.\"),\n      palette,\n    }),\n    description: \"One hand's solver mix as a stacked bar with a legend. Use to show how often each action is taken.\",\n    example: {\n      items: [\n        { label: \"Fold\", freq: 0, color: null, detail: null },\n        { label: \"Call\", freq: 0.17, color: null, detail: \"+0.06bb\" },\n        { label: \"Raise 77%\", freq: 0.83, color: null, detail: \"+0.06bb\" },\n      ],\n      highlight: \"Call\",\n      palette: null,\n    },\n  },\n\n  GradeBadge: {\n    props: z.object({ grade, evLossBb: z.number().nullable().describe(\"EV given up in big blinds.\") }),\n    description: \"How good a decision was: perfect, correct, inaccuracy, mistake or blunder, with the bb lost.\",\n    example: { grade: \"mistake\", evLossBb: 0.62 },\n  },\n\n  PlayerStats: {\n    props: z.object({\n      stats: z\n        .record(z.string(), z.unknown())\n        .describe(\n          \"A PlayerStats object copied verbatim from the API: hands, netChips, and { num, denom, pct } per stat key.\",\n        ),\n      keys: z.array(statKey).nullable().describe(\"Which stats to show, in order. Null shows the standard HUD set.\"),\n    }),\n    description: \"HUD tiles for one player with the counts behind each percentage. Bind `stats` from state.\",\n    example: { stats: { $state: \"/stats\" }, keys: [\"vpip\", \"pfr\", \"threeBet\", \"foldToThreeBet\"] },\n  },\n\n  HandMatrix: {\n    props: z.object({\n      cells: z\n        .array(z.object({ label: z.string(), freqs: z.array(z.number()) }).nullable())\n        .describe('169 cells in grid order (AA, AKs, … 22). `freqs` is parallel to `colors`; null is \"not in range\".'),\n      colors: z.array(z.string()),\n      legend: z.array(z.object({ label: z.string(), color: z.string() })),\n      selected: z.string().nullable().describe('Highlighted hand, e.g. \"AKs\". Bind with $bindState to make cells clickable.'),\n    }),\n    description:\n      \"The 13×13 grid of hand classes, each a stacked bar of action frequencies. For a solver spot prefer RangeExplorer, which fetches the range itself.\",\n  },\n\n  ChipStack: {\n    props: z.object({\n      amount: z.number().describe(\"Chips in big blinds.\"),\n      label: z.string().nullable().describe('Text beside the chip, e.g. \"12bb\". Null shows the chip only.'),\n      variant: z.enum([\"pill\", \"table\"]).nullable(),\n    }),\n    description: \"A chip colored by its size in big blinds, with the amount beside it. Use for bets and stacks.\",\n    example: { amount: 12, label: \"12bb\", variant: \"pill\" },\n  },\n\n  GtoStrategyBar: {\n    props: z.object({\n      actions: z.array(\n        z.object({\n          action: z.string().describe('The node\\'s action token, e.g. \"F\", \"C\", or a size like \"60%\".'),\n          kind: z.enum([\"fold\", \"call\", \"raise\", \"allin\", \"other\"]),\n        }),\n      ),\n      strategy: z.record(z.string(), z.number()).describe(\"Action token → frequency for this hand.\"),\n      highlightAction: z.string().nullable(),\n    }),\n    description: \"A solver node's mix for one hand in the trainer's action colors. Copy `actions` from the ranges API node.\",\n    example: {\n      actions: [\n        { action: \"F\", kind: \"fold\" },\n        { action: \"C\", kind: \"call\" },\n        { action: \"60%\", kind: \"raise\" },\n      ],\n      strategy: { F: 0.1, C: 0.55, \"60%\": 0.35 },\n      highlightAction: \"C\",\n    },\n  },\n\n  PokerTable: {\n    props: z.object({\n      seats: z\n        .array(\n          z.object({\n            pos: z.string().describe('Position, e.g. \"BTN\".'),\n            name: z.string().nullable(),\n            stack: z.number().describe(\"Chips behind, in bb.\"),\n            bet: z.number().describe(\"Chips in front of the seat this street, in bb.\"),\n            folded: z.boolean(),\n            dealer: z.boolean(),\n            action: z.string().nullable().describe('Last action, e.g. \"Raise 2.5\".'),\n            cards: z.array(card).nullable().describe(\"Face-up cards for a villain at showdown. Null shows backs.\"),\n            acting: z.boolean().nullable(),\n          }),\n        )\n        .describe(\"Hero first, then the rest clockwise. 2 to 9 seats.\"),\n      heroCards: z.array(card).nullable(),\n      board: z.array(card).nullable(),\n      pot: z.number().describe(\"Pot in bb.\"),\n      theme: tableTheme.nullable().describe(\"Table theme. Null keeps the page's theme.\"),\n    }),\n    description:\n      \"A full poker table: seats round an oval, stacks, bets, the board and the pot. Use to show a spot or a moment in a hand.\",\n    example: {\n      seats: [\n        { pos: \"BTN\", name: \"Hero\", stack: 97.5, bet: 2.5, folded: false, dealer: true, action: \"Raise 2.5\", cards: null, acting: null },\n        { pos: \"SB\", name: null, stack: 99.5, bet: 0.5, folded: true, dealer: false, action: \"Fold\", cards: null, acting: null },\n        { pos: \"BB\", name: null, stack: 99, bet: 1, folded: false, dealer: false, action: null, cards: null, acting: true },\n      ],\n      heroCards: [\"Ah\", \"7s\"],\n      board: null,\n      pot: 4,\n      theme: \"coinpoker\",\n    },\n  },\n\n  RangeExplorer: {\n    props: z.object({\n      stack: z.number().describe(\"Solved stack depth in bb.\"),\n      history: z\n        .string()\n        .describe('Decision node with no trailing action: \"UTG\" for the open, \"UTG_60%_HJ_Fold_CO_Fold_BTN\" for BTN facing an open.'),\n      game: z.enum([\"nl\", \"nl9\", \"nlmtt8\"]).nullable(),\n      initialHand: z.string().nullable().describe('Hand class to select first, e.g. \"A5s\".'),\n      palette,\n    }),\n    description:\n      \"The Range Explorer at a solver preflop decision: the table, the acting seat's range as a matrix, every action's share of the range and any hand's EV, walkable from there. Fetches each node itself.\",\n    example: { stack: 100, history: \"UTG_60%_HJ_Fold_CO_Fold_BTN\", game: \"nl\", initialHand: \"A5s\", palette: null },\n  },\n\n  DecisionGrader: {\n    props: z.object({\n      snapshot,\n      raiseTo: z.array(z.number()).nullable().describe(\"Raise sizes to offer as buttons, in chips.\"),\n    }),\n    description:\n      \"Lets the player pick an action at a table snapshot; the agent grades it and says what it would do. Needs the API key.\",\n    example: { snapshot: snapshotExample, raiseTo: [50, 70] },\n  },\n\n  AgentAdvisor: {\n    props: z.object({ snapshot }),\n    description: \"What the Poker Study AI agent would do at a table snapshot, and which model answered. Needs the API key.\",\n    example: { snapshot: { ...snapshotExample, hole: [\"Ks\", \"Qs\"] } },\n  },\n\n  TrainingReport: {\n    props: z.object({}),\n    description: \"The player's preflop drill accuracy by stack depth and spot, with the biggest leak called out.\",\n    example: {},\n  },\n\n  OpponentProfile: {\n    props: z.object({\n      opponentId: z.string().nullable().describe(\"Id from the opponents API. Null shows a picker of opponents.\"),\n    }),\n    description: \"Scouting report on one opponent from the player's uploaded hands: HUD stats and noted leaks.\",\n    example: { opponentId: null },\n  },\n\n  LimpRange: {\n    props: z.object({\n      opponentId: z.string().nullable(),\n      position: z.array(z.string()).nullable().describe('Limper positions to include, e.g. [\"SB\"]. Null is all.'),\n      palette,\n    }),\n    description: \"The hands opponents showed down after limping first in, on the hand matrix.\",\n    example: { opponentId: null, position: null, palette: null },\n  },\n\n  ActionBar: {\n    props: z.object({\n      actions: z\n        .array(\n          z.object({\n            action: z.string().describe('The node\\'s action token: \"Fold\", \"Call\", a size like \"75%\", or \"AI\".'),\n            kind: z.enum([\"fold\", \"call\", \"raise\", \"allin\", \"other\"]),\n          }),\n        )\n        .describe(\"Copy from the ranges API node.\"),\n      history: z.string().nullable().describe(\"The spot's history. With stackBb, raises show as raise-to bb.\"),\n      stackBb: z.number().nullable(),\n      value: z.string().nullable().describe(\"The picked action. Bind with $bindState to read the player's choice.\"),\n    }),\n    description:\n      'The trainer\\'s preflop decision buttons. Use when the player should choose an action at a solver node; bind `value` to state and emit \"select\" to react.',\n    events: [\"select\"],\n    example: {\n      actions: [\n        { action: \"Fold\", kind: \"fold\" },\n        { action: \"Call\", kind: \"call\" },\n        { action: \"75%\", kind: \"raise\" },\n      ],\n      history: \"UTG_60%_HJ_Fold_CO_Fold_BTN\",\n      stackBb: 100,\n      value: null,\n    },\n  },\n\n  PostflopActionBar: {\n    props: z.object({\n      actions: z.array(\n        z.object({\n          label: z.string().describe('\"Check\", \"Bet 5.8\", \"Call\", …'),\n          add: z.number().describe(\"Chips the action adds, in bb.\"),\n          kind: z.enum([\"fold\", \"check\", \"call\", \"raise\", \"allin\"]),\n          freq: z.number(),\n          evLoss: z.number().nullable(),\n        }),\n      ),\n      value: z.string().nullable().describe(\"The picked label. Bind with $bindState.\"),\n    }),\n    description: 'The trainer\\'s postflop decision buttons, from a postflop drill\\'s options. Emits \"select\".',\n    events: [\"select\"],\n    example: {\n      actions: [\n        { label: \"Fold\", add: 0, kind: \"fold\", freq: 0.2, evLoss: 0 },\n        { label: \"Call\", add: 3.3, kind: \"call\", freq: 0.65, evLoss: 0 },\n        { label: \"Raise 11\", add: 11, kind: \"raise\", freq: 0.15, evLoss: 0.4 },\n      ],\n      value: null,\n    },\n  },\n\n  HandHistory: {\n    props: z.object({\n      items: z.array(\n        z.object({\n          id: z.string(),\n          cards: z.array(card).nullable(),\n          title: z.string().nullable().describe(\"Shown when there are no cards, e.g. a PLO hand class.\"),\n          subtitle: z.string().describe('The spot, e.g. \"BTN vs UTG open\".'),\n          decisions: z.array(\n            z.object({\n              phase: z.string().nullable().describe('\"Flop\", \"Turn\", … Null for a one-decision hand.'),\n              label: z.string().describe(\"The action taken.\"),\n              tier: grade,\n              freq: z.number().describe(\"The solver's frequency for that action, 0 to 1.\"),\n              evBb: z.number().nullable(),\n              evLossBb: z.number().nullable(),\n            }),\n          ),\n        }),\n      ),\n    }),\n    description:\n      \"Graded hands, newest first: the cards, the spot, each decision's grade, EV and what was lost. Use to review a run of decisions.\",\n    example: {\n      items: [\n        {\n          id: \"1\",\n          cards: [\"As\", \"5s\"],\n          title: null,\n          subtitle: \"BTN vs UTG open\",\n          decisions: [{ phase: null, label: \"Call\", tier: \"inaccuracy\", freq: 0.21, evBb: 0.41, evLossBb: 0.17 }],\n        },\n      ],\n    },\n  },\n\n  SessionStats: {\n    props: z.object({ sessionId: z.string().nullable().describe(\"Null shows the most recent session.\") }),\n    description: \"HUD stats from one uploaded session.\",\n    example: { sessionId: null },\n  },\n};\n\nexport type PokerstudyComponentName = keyof typeof pokerstudyComponentDefinitions;\n\n/** Props a model may set on `name`, as the schema parses them. */\nexport type PokerstudyProps<K extends PokerstudyComponentName> = z.output<\n  (typeof pokerstudyComponentDefinitions)[K][\"props\"]\n>;\n",
      "type": "registry:lib",
      "target": "lib/pokerstudy/catalog.ts"
    },
    {
      "path": "src/components/pokerstudy/json-render.tsx",
      "content": "\"use client\";\n\nimport type { BaseComponentProps } from \"@json-render/react\";\nimport { useBoundProp } from \"@json-render/react\";\nimport { AgentAdvisor } from \"@/components/pokerstudy/agent-advisor\";\nimport { DecisionGrader } from \"@/components/pokerstudy/decision-grader\";\nimport { GradeBadge } from \"@/components/pokerstudy/grade-badge\";\nimport { HandMatrix } from \"@/components/pokerstudy/hand-matrix\";\nimport { LimpRange } from \"@/components/pokerstudy/limp-range\";\nimport { OpponentProfile } from \"@/components/pokerstudy/opponent-profile\";\nimport { Board, HoleCards, PlayingCard } from \"@/components/pokerstudy/playing-card\";\nimport { PlayerStatsGrid } from \"@/components/pokerstudy/player-stats\";\nimport { PreflopRangeExplorer } from \"@/components/pokerstudy/preflop-range-explorer\";\nimport { SessionStats } from \"@/components/pokerstudy/session-stats\";\nimport { StrategyBar } from \"@/components/pokerstudy/strategy-bar\";\nimport { TrainingReport } from \"@/components/pokerstudy/training-report\";\nimport { ChipStack } from \"@/components/trainer/chips\";\nimport { PlayingCard as TableCard } from \"@/components/trainer/cards\";\nimport { ActionBar } from \"@/components/trainer/action-bar\";\nimport { GtoStrategyBar } from \"@/components/trainer/gto-strategy-bar\";\nimport { HistoryPanel } from \"@/components/trainer/history-panel\";\nimport { PostflopActionBar } from \"@/components/trainer/postflop-action-bar\";\nimport { TableTheme } from \"@/components/trainer/skin\";\nimport { PokerTable } from \"@/components/trainer/table\";\nimport type { PokerstudyComponentName, PokerstudyProps } from \"@/lib/pokerstudy/catalog\";\nimport { actionColor, type ActionPaletteId, type PlayerStats, type TableSnapshot } from \"@/lib/pokerstudy/poker\";\n\n/**\n * Renderers for `pokerstudyComponentDefinitions`: each takes the JSON props a\n * model wrote and draws the real component. Pass them to `defineRegistry`\n * next to the definitions, the way `@json-render/shadcn` pairs\n * `shadcnComponents` with `shadcnComponentDefinitions`.\n */\n\ntype P<K extends PokerstudyComponentName> = BaseComponentProps<PokerstudyProps<K>>;\n\n/** Where the data blocks fetch from. Defaults are the routes the `pokerstudy-api` item installs. */\nexport interface PokerstudyEndpoints {\n  /** RangeExplorer. Default `/api/pokerstudy/node`. */\n  node?: string;\n  /** DecisionGrader. Default `/api/pokerstudy/evaluate`. */\n  evaluate?: string;\n  /** AgentAdvisor. Default `/api/pokerstudy/decide`. */\n  decide?: string;\n  /** TrainingReport, OpponentProfile, LimpRange and SessionStats. Default `/api/pokerstudy`. */\n  base?: string;\n}\n\n/** The action color for a label such as \"Raise 2.5bb\" or \"All-in\". */\nfunction labelColor(label: string, palette: ActionPaletteId | null): string {\n  const l = label.toLowerCase();\n  if (l.startsWith(\"fold\")) return actionColor(\"fold\", undefined, palette);\n  if (l.startsWith(\"check\")) return actionColor(\"check\", undefined, palette);\n  if (l.startsWith(\"call\") || l.startsWith(\"limp\")) return actionColor(\"call\", undefined, palette);\n  if (l.startsWith(\"all\") || l === \"ai\" || l.startsWith(\"jam\") || l.startsWith(\"shove\")) return actionColor(\"allin\", undefined, palette);\n  if (l.startsWith(\"bet\") || l.startsWith(\"raise\") || l.startsWith(\"open\") || /^\\d/.test(l)) return actionColor(\"raise\", label, palette);\n  return actionColor(\"other\");\n}\n\n/** The API's snapshot leaves unknown fields out; a spec writes them as null. */\nfunction toSnapshot(snapshot: PokerstudyProps<\"AgentAdvisor\">[\"snapshot\"]): TableSnapshot {\n  return Object.fromEntries(Object.entries(snapshot).filter(([, v]) => v != null)) as TableSnapshot;\n}\n\nfunction HandMatrixRenderer({ props, bindings }: P<\"HandMatrix\">) {\n  const [selected, setSelected] = useBoundProp<string | null>(props.selected, bindings?.selected);\n  return (\n    <HandMatrix\n      cells={props.cells}\n      colors={props.colors}\n      legend={props.legend}\n      selected={selected}\n      onSelect={bindings?.selected ? setSelected : undefined}\n    />\n  );\n}\n\nfunction ActionBarRenderer({ props, bindings, emit }: P<\"ActionBar\">) {\n  const [, setValue] = useBoundProp<string | null>(props.value, bindings?.value);\n  return (\n    <ActionBar\n      actions={props.actions.map((a) => ({ ...a, weights: {} }))}\n      history={props.history ?? undefined}\n      stackBb={props.stackBb ?? undefined}\n      disabled={false}\n      onSelect={(action) => {\n        setValue(action);\n        emit(\"select\");\n      }}\n    />\n  );\n}\n\nfunction PostflopActionBarRenderer({ props, bindings, emit }: P<\"PostflopActionBar\">) {\n  const [, setValue] = useBoundProp<string | null>(props.value, bindings?.value);\n  return (\n    <PostflopActionBar\n      actions={props.actions}\n      disabled={false}\n      onSelect={(label) => {\n        setValue(label);\n        emit(\"select\");\n      }}\n    />\n  );\n}\n\nfunction PokerTableRenderer({ props }: P<\"PokerTable\">) {\n  const table = (\n    <div data-skin={props.theme ?? undefined} className=\"@container h-[560px] w-full overflow-hidden rounded-lg sm:h-[440px]\">\n      <PokerTable\n        seats={props.seats.map((s, i) => ({\n          pos: s.pos,\n          name: s.name,\n          remaining: s.stack,\n          committed: s.bet,\n          folded: s.folded,\n          isHero: i === 0,\n          isDealer: s.dealer,\n          action: s.action,\n          hole: s.cards,\n          acting: s.acting ?? false,\n        }))}\n        pot={props.pot}\n        heroCards={props.heroCards}\n        avatars={false}\n        center={\n          props.board?.length ? (\n            <div className=\"flex items-center justify-center gap-1.5\">\n              {props.board.map((card) => (\n                <TableCard key={card} card={card} size=\"board\" />\n              ))}\n            </div>\n          ) : undefined\n        }\n      />\n    </div>\n  );\n  return props.theme ? <TableTheme skin={props.theme}>{table}</TableTheme> : table;\n}\n\n/**\n * The renderers, with the data blocks pointed at `endpoints`. Use this when\n * your proxy lives somewhere other than `/api/pokerstudy`.\n */\nexport function createPokerstudyComponents(endpoints: PokerstudyEndpoints = {}) {\n  const base = endpoints.base ?? \"/api/pokerstudy\";\n  return {\n    PlayingCard: ({ props }: P<\"PlayingCard\">) => <PlayingCard card={props.card} size={props.size ?? undefined} />,\n    HoleCards: ({ props }: P<\"HoleCards\">) => <HoleCards cards={props.cards} size={props.size ?? undefined} />,\n    Board: ({ props }: P<\"Board\">) => <Board cards={props.cards} size={props.size ?? undefined} />,\n    StrategyBar: ({ props }: P<\"StrategyBar\">) => (\n      <StrategyBar\n        highlight={props.highlight}\n        items={props.items.map((i) => ({\n          label: i.label,\n          freq: i.freq,\n          color: i.color ?? labelColor(i.label, props.palette),\n          detail: i.detail ?? undefined,\n        }))}\n      />\n    ),\n    GradeBadge: ({ props }: P<\"GradeBadge\">) => <GradeBadge grade={props.grade} evLossBb={props.evLossBb} />,\n    PlayerStats: ({ props }: P<\"PlayerStats\">) => (\n      <PlayerStatsGrid stats={props.stats as PlayerStats} keys={props.keys ?? undefined} />\n    ),\n    HandMatrix: HandMatrixRenderer,\n    ChipStack: ({ props }: P<\"ChipStack\">) => (\n      <ChipStack amount={props.amount} label={props.label ?? undefined} variant={props.variant ?? undefined} />\n    ),\n    GtoStrategyBar: ({ props }: P<\"GtoStrategyBar\">) => (\n      <GtoStrategyBar\n        actions={props.actions.map((a) => ({ ...a, weights: {} }))}\n        strategy={props.strategy}\n        highlightAction={props.highlightAction}\n      />\n    ),\n    PokerTable: PokerTableRenderer,\n    ActionBar: ActionBarRenderer,\n    PostflopActionBar: PostflopActionBarRenderer,\n    HandHistory: ({ props }: P<\"HandHistory\">) => (\n      <div className=\"h-[360px] overflow-hidden rounded-md\">\n        <HistoryPanel\n          onClose={() => {}}\n          items={props.items.map((item) => ({\n            id: item.id,\n            cards: item.cards ?? undefined,\n            title: item.title ?? undefined,\n            subtitle: item.subtitle,\n            decisions: item.decisions.map((d) => ({\n              phase: d.phase ?? undefined,\n              label: d.label,\n              tier: d.tier,\n              freq: d.freq,\n              evBb: d.evBb,\n              evLossBb: d.evLossBb ?? undefined,\n            })),\n          }))}\n        />\n      </div>\n    ),\n    RangeExplorer: ({ props }: P<\"RangeExplorer\">) => (\n      <PreflopRangeExplorer\n        stack={props.stack}\n        history={props.history}\n        game={props.game ?? undefined}\n        initialHand={props.initialHand}\n        endpoint={endpoints.node ?? \"/api/pokerstudy/node\"}\n        palette={props.palette ?? undefined}\n      />\n    ),\n    DecisionGrader: ({ props }: P<\"DecisionGrader\">) => (\n      <DecisionGrader\n        snapshot={toSnapshot(props.snapshot)}\n        raiseTo={props.raiseTo ?? undefined}\n        endpoint={endpoints.evaluate ?? \"/api/pokerstudy/evaluate\"}\n      />\n    ),\n    AgentAdvisor: ({ props }: P<\"AgentAdvisor\">) => (\n      <AgentAdvisor snapshot={toSnapshot(props.snapshot)} endpoint={endpoints.decide ?? \"/api/pokerstudy/decide\"} />\n    ),\n    TrainingReport: () => <TrainingReport endpoint={base} />,\n    OpponentProfile: ({ props }: P<\"OpponentProfile\">) => (\n      <OpponentProfile opponentId={props.opponentId ?? undefined} endpoint={base} />\n    ),\n    LimpRange: ({ props }: P<\"LimpRange\">) => (\n      <LimpRange\n        opponentId={props.opponentId ?? undefined}\n        position={props.position ?? undefined}\n        endpoint={base}\n        palette={props.palette ?? undefined}\n      />\n    ),\n    SessionStats: ({ props }: P<\"SessionStats\">) => <SessionStats sessionId={props.sessionId ?? undefined} endpoint={base} />,\n  };\n}\n\n/** Every renderer, fetching through the `/api/pokerstudy` proxy. */\nexport const pokerstudyComponents = createPokerstudyComponents();\n",
      "type": "registry:component",
      "target": "components/pokerstudy/json-render.tsx"
    }
  ],
  "docs": "Spread pokerstudyComponentDefinitions into defineCatalog and pokerstudyComponents into defineRegistry. The data blocks fetch through /api/pokerstudy; use createPokerstudyComponents({ ... }) to point them elsewhere.",
  "type": "registry:component"
}