schema.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { defineSchema } from "@json-render/core";
  2. /**
  3. * The schema for @json-render/react
  4. *
  5. * Defines:
  6. * - Spec: A flat tree of elements with keys, types, props, and children references
  7. * - Catalog: Components with props schemas, and optional actions
  8. */
  9. export const schema = defineSchema(
  10. (s) => ({
  11. // What the AI-generated SPEC looks like
  12. spec: s.object({
  13. /** Root element key */
  14. root: s.string(),
  15. /** Flat map of elements by key */
  16. elements: s.record(
  17. s.object({
  18. /** Component type from catalog */
  19. type: s.ref("catalog.components"),
  20. /** Component props */
  21. props: s.propsOf("catalog.components"),
  22. /** Child element keys (flat reference) */
  23. children: s.array(s.string()),
  24. /** Visibility condition */
  25. visible: s.any(),
  26. }),
  27. ),
  28. }),
  29. // What the CATALOG must provide
  30. catalog: s.object({
  31. /** Component definitions */
  32. components: s.map({
  33. /** Zod schema for component props */
  34. props: s.zod(),
  35. /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
  36. slots: s.array(s.string()),
  37. /** Description for AI generation hints */
  38. description: s.string(),
  39. /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
  40. example: s.any(),
  41. }),
  42. /** Action definitions (optional) */
  43. actions: s.map({
  44. /** Zod schema for action params */
  45. params: s.zod(),
  46. /** Description for AI generation hints */
  47. description: s.string(),
  48. }),
  49. }),
  50. }),
  51. {
  52. builtInActions: [
  53. {
  54. name: "setState",
  55. description:
  56. "Update a value in the state model at the given statePath. Params: { statePath: string, value: any }",
  57. },
  58. {
  59. name: "pushState",
  60. description:
  61. 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
  62. },
  63. {
  64. name: "removeState",
  65. description:
  66. "Remove an item from an array in state by index. Params: { statePath: string, index: number }",
  67. },
  68. {
  69. name: "validateForm",
  70. description:
  71. "Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.",
  72. },
  73. ],
  74. defaultRules: [
  75. // Element integrity
  76. "CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
  77. "SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.",
  78. // Field placement
  79. 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
  80. 'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
  81. // State and data
  82. "When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).",
  83. 'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. Replace <ContainerComponent> with an appropriate component from the AVAILABLE COMPONENTS list. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
  84. // Design quality
  85. "Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.",
  86. "For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.",
  87. "Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.",
  88. ],
  89. },
  90. );
  91. /**
  92. * Type for the React schema
  93. */
  94. export type ReactSchema = typeof schema;
  95. /**
  96. * Infer the spec type from a catalog
  97. */
  98. export type ReactSpec<TCatalog> = typeof schema extends {
  99. createCatalog: (catalog: TCatalog) => { _specType: infer S };
  100. }
  101. ? S
  102. : never;
  103. // Backward compatibility aliases
  104. /** @deprecated Use `schema` instead */
  105. export const elementTreeSchema = schema;
  106. /** @deprecated Use `ReactSchema` instead */
  107. export type ElementTreeSchema = ReactSchema;
  108. /** @deprecated Use `ReactSpec` instead */
  109. export type ElementTreeSpec<T> = ReactSpec<T>;