@json-render/core 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1012,6 +1012,8 @@ interface ComponentDefinition<TProps extends ComponentSchema = ComponentSchema>
1012
1012
  hasChildren?: boolean;
1013
1013
  /** Description for AI generation */
1014
1014
  description?: string;
1015
+ /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
1016
+ example?: Record<string, unknown>;
1015
1017
  }
1016
1018
  /**
1017
1019
  * Catalog configuration
package/dist/index.d.ts CHANGED
@@ -1012,6 +1012,8 @@ interface ComponentDefinition<TProps extends ComponentSchema = ComponentSchema>
1012
1012
  hasChildren?: boolean;
1013
1013
  /** Description for AI generation */
1014
1014
  description?: string;
1015
+ /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
1016
+ example?: Record<string, unknown>;
1015
1017
  }
1016
1018
  /**
1017
1019
  * Catalog configuration
package/dist/index.js CHANGED
@@ -1244,27 +1244,72 @@ function generatePrompt(catalog, options) {
1244
1244
  const lines = [];
1245
1245
  lines.push(system);
1246
1246
  lines.push("");
1247
- lines.push("OUTPUT FORMAT:");
1247
+ lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
1248
1248
  lines.push(
1249
- "Output JSONL (one JSON object per line) with patches to build a UI tree."
1249
+ "Output JSONL (one JSON object per line) using RFC 6902 JSON Patch operations to build a UI tree."
1250
1250
  );
1251
1251
  lines.push(
1252
- "Each line is a JSON patch operation. Start with /root, then stream /elements and /state patches interleaved so the UI fills in progressively as it streams."
1252
+ "Each line is a JSON patch operation (add, remove, replace). Start with /root, then stream /elements and /state patches interleaved so the UI fills in progressively as it streams."
1253
1253
  );
1254
1254
  lines.push("");
1255
1255
  lines.push("Example output (each line is a separate JSON object):");
1256
1256
  lines.push("");
1257
- lines.push(`{"op":"add","path":"/root","value":"blog"}
1258
- {"op":"add","path":"/elements/blog","value":{"type":"Stack","props":{"direction":"vertical","gap":"md"},"children":["heading","posts-grid"]}}
1259
- {"op":"add","path":"/elements/heading","value":{"type":"Heading","props":{"text":"Blog","level":"h1"},"children":[]}}
1260
- {"op":"add","path":"/elements/posts-grid","value":{"type":"Grid","props":{"columns":2,"gap":"md"},"repeat":{"path":"/posts","key":"id"},"children":["post-card"]}}
1261
- {"op":"add","path":"/elements/post-card","value":{"type":"Card","props":{"title":{"$path":"$item/title"}},"children":["post-meta"]}}
1262
- {"op":"add","path":"/elements/post-meta","value":{"type":"Text","props":{"text":{"$path":"$item/author"},"variant":"muted"},"children":[]}}
1263
- {"op":"add","path":"/state/posts","value":[]}
1264
- {"op":"add","path":"/state/posts/0","value":{"id":"1","title":"Getting Started","author":"Jane","date":"Jan 15"}}
1265
- {"op":"add","path":"/state/posts/1","value":{"id":"2","title":"Advanced Tips","author":"Bob","date":"Feb 3"}}
1257
+ const allComponents = catalog.data.components;
1258
+ const cn = catalog.componentNames;
1259
+ const comp1 = cn[0] || "Component";
1260
+ const comp2 = cn.length > 1 ? cn[1] : comp1;
1261
+ const comp1Def = allComponents?.[comp1];
1262
+ const comp2Def = allComponents?.[comp2];
1263
+ const comp1Props = comp1Def ? getExampleProps(comp1Def) : {};
1264
+ const comp2Props = comp2Def ? getExampleProps(comp2Def) : {};
1265
+ const dynamicPropName = comp2Def?.props ? findFirstStringProp(comp2Def.props) : null;
1266
+ const dynamicProps = dynamicPropName ? { ...comp2Props, [dynamicPropName]: { $path: "$item/title" } } : comp2Props;
1267
+ const exampleOutput = [
1268
+ JSON.stringify({ op: "add", path: "/root", value: "main" }),
1269
+ JSON.stringify({
1270
+ op: "add",
1271
+ path: "/elements/main",
1272
+ value: {
1273
+ type: comp1,
1274
+ props: comp1Props,
1275
+ children: ["child-1", "list"]
1276
+ }
1277
+ }),
1278
+ JSON.stringify({
1279
+ op: "add",
1280
+ path: "/elements/child-1",
1281
+ value: { type: comp2, props: comp2Props, children: [] }
1282
+ }),
1283
+ JSON.stringify({
1284
+ op: "add",
1285
+ path: "/elements/list",
1286
+ value: {
1287
+ type: comp1,
1288
+ props: comp1Props,
1289
+ repeat: { path: "/items", key: "id" },
1290
+ children: ["item"]
1291
+ }
1292
+ }),
1293
+ JSON.stringify({
1294
+ op: "add",
1295
+ path: "/elements/item",
1296
+ value: { type: comp2, props: dynamicProps, children: [] }
1297
+ }),
1298
+ JSON.stringify({ op: "add", path: "/state/items", value: [] }),
1299
+ JSON.stringify({
1300
+ op: "add",
1301
+ path: "/state/items/0",
1302
+ value: { id: "1", title: "First Item" }
1303
+ }),
1304
+ JSON.stringify({
1305
+ op: "add",
1306
+ path: "/state/items/1",
1307
+ value: { id: "2", title: "Second Item" }
1308
+ })
1309
+ ].join("\n");
1310
+ lines.push(`${exampleOutput}
1266
1311
 
1267
- Note: state patches appear right after the elements that use them, so the UI fills in as it streams.`);
1312
+ Note: state patches appear right after the elements that use them, so the UI fills in as it streams. ONLY use component types from the AVAILABLE COMPONENTS list below.`);
1268
1313
  lines.push("");
1269
1314
  lines.push("INITIAL STATE:");
1270
1315
  lines.push(
@@ -1303,7 +1348,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1303
1348
  'The element itself renders once (as the container), and its children are expanded once per array item. "path" is the state array path. "key" is an optional field name on each item for stable React keys.'
1304
1349
  );
1305
1350
  lines.push(
1306
- 'Example: { "type": "Column", "props": { "gap": 8 }, "repeat": { "path": "/todos", "key": "id" }, "children": ["todo-item"] }'
1351
+ `Example: ${JSON.stringify({ type: comp1, props: comp1Props, repeat: { path: "/todos", key: "id" }, children: ["todo-item"] })}`
1307
1352
  );
1308
1353
  lines.push(
1309
1354
  'Inside children of a repeated element, use "$item/field" for per-item paths: statePath:"$item/completed", { "$path": "$item/title" }. Use "$index" for the current array index.'
@@ -1339,7 +1384,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1339
1384
  'IMPORTANT: State paths use RFC 6901 JSON Pointer syntax (e.g. "/todos/0/title"). Do NOT use JavaScript-style dot notation (e.g. "/todos.length" is WRONG). To generate unique IDs for new items, use "$id" instead of trying to read array length.'
1340
1385
  );
1341
1386
  lines.push("");
1342
- const components = catalog.data.components;
1387
+ const components = allComponents;
1343
1388
  if (components) {
1344
1389
  lines.push(`AVAILABLE COMPONENTS (${catalog.componentNames.length}):`);
1345
1390
  lines.push("");
@@ -1372,7 +1417,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1372
1417
  lines.push("");
1373
1418
  lines.push("Example:");
1374
1419
  lines.push(
1375
- ' {"type":"Button","props":{"label":"Save"},"on":{"press":{"action":"setState","params":{"path":"/saved","value":true}}},"children":[]}'
1420
+ ` ${JSON.stringify({ type: comp1, props: comp1Props, on: { press: { action: "setState", params: { path: "/saved", value: true } } }, children: [] })}`
1376
1421
  );
1377
1422
  lines.push("");
1378
1423
  lines.push(
@@ -1387,7 +1432,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1387
1432
  "Elements can have an optional `visible` field to conditionally show/hide based on data state. IMPORTANT: `visible` is a top-level field on the element object (sibling of type/props/children), NOT inside props."
1388
1433
  );
1389
1434
  lines.push(
1390
- 'Correct: {"type":"Column","props":{"gap":8},"visible":{"eq":[{"path":"/tab"},"home"]},"children":[...]}'
1435
+ `Correct: ${JSON.stringify({ type: comp1, props: comp1Props, visible: { eq: [{ path: "/tab" }, "home"] }, children: ["..."] })}`
1391
1436
  );
1392
1437
  lines.push(
1393
1438
  '- `{ "eq": [{ "path": "/statePath" }, "value"] }` - visible when state at path equals value'
@@ -1402,10 +1447,10 @@ Note: state patches appear right after the elements that use them, so the UI fil
1402
1447
  lines.push("- `true` / `false` - always visible/hidden");
1403
1448
  lines.push("");
1404
1449
  lines.push(
1405
- "Use the Pressable component with on.press bound to setState to update state and drive visibility."
1450
+ "Use a component with on.press bound to setState to update state and drive visibility."
1406
1451
  );
1407
1452
  lines.push(
1408
- 'Example: A Pressable with on: { "press": { "action": "setState", "params": { "path": "/activeTab", "value": "home" } } } sets state, then a container with visible: { "eq": [{ "path": "/activeTab" }, "home"] } shows only when that tab is active.'
1453
+ `Example: A ${comp1} with on: { "press": { "action": "setState", "params": { "path": "/activeTab", "value": "home" } } } sets state, then a container with visible: { "eq": [{ "path": "/activeTab" }, "home"] } shows only when that tab is active.`
1409
1454
  );
1410
1455
  lines.push("");
1411
1456
  lines.push("DYNAMIC PROPS:");
@@ -1451,6 +1496,101 @@ Note: state patches appear right after the elements that use them, so the UI fil
1451
1496
  });
1452
1497
  return lines.join("\n");
1453
1498
  }
1499
+ function getExampleProps(def) {
1500
+ if (def.example && Object.keys(def.example).length > 0) {
1501
+ return def.example;
1502
+ }
1503
+ if (def.props) {
1504
+ return generateExamplePropsFromZod(def.props);
1505
+ }
1506
+ return {};
1507
+ }
1508
+ function generateExamplePropsFromZod(schema) {
1509
+ if (!schema || !schema._def) return {};
1510
+ const def = schema._def;
1511
+ const typeName = getZodTypeName(schema);
1512
+ if (typeName !== "ZodObject" && typeName !== "object") return {};
1513
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1514
+ if (!shape) return {};
1515
+ const result = {};
1516
+ for (const [key, value] of Object.entries(shape)) {
1517
+ const innerTypeName = getZodTypeName(value);
1518
+ if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") {
1519
+ continue;
1520
+ }
1521
+ result[key] = generateExampleValue(value);
1522
+ }
1523
+ return result;
1524
+ }
1525
+ function generateExampleValue(schema) {
1526
+ if (!schema || !schema._def) return "...";
1527
+ const def = schema._def;
1528
+ const typeName = getZodTypeName(schema);
1529
+ switch (typeName) {
1530
+ case "ZodString":
1531
+ case "string":
1532
+ return "example";
1533
+ case "ZodNumber":
1534
+ case "number":
1535
+ return 0;
1536
+ case "ZodBoolean":
1537
+ case "boolean":
1538
+ return true;
1539
+ case "ZodLiteral":
1540
+ case "literal":
1541
+ return def.value;
1542
+ case "ZodEnum":
1543
+ case "enum": {
1544
+ if (Array.isArray(def.values) && def.values.length > 0)
1545
+ return def.values[0];
1546
+ if (def.entries && typeof def.entries === "object") {
1547
+ const values = Object.values(def.entries);
1548
+ return values.length > 0 ? values[0] : "example";
1549
+ }
1550
+ return "example";
1551
+ }
1552
+ case "ZodOptional":
1553
+ case "optional":
1554
+ case "ZodNullable":
1555
+ case "nullable":
1556
+ case "ZodDefault":
1557
+ case "default": {
1558
+ const inner = def.innerType ?? def.wrapped;
1559
+ return inner ? generateExampleValue(inner) : null;
1560
+ }
1561
+ case "ZodArray":
1562
+ case "array":
1563
+ return [];
1564
+ case "ZodObject":
1565
+ case "object":
1566
+ return generateExamplePropsFromZod(schema);
1567
+ case "ZodUnion":
1568
+ case "union": {
1569
+ const options = def.options;
1570
+ return options && options.length > 0 ? generateExampleValue(options[0]) : "...";
1571
+ }
1572
+ default:
1573
+ return "...";
1574
+ }
1575
+ }
1576
+ function findFirstStringProp(schema) {
1577
+ if (!schema || !schema._def) return null;
1578
+ const def = schema._def;
1579
+ const typeName = getZodTypeName(schema);
1580
+ if (typeName !== "ZodObject" && typeName !== "object") return null;
1581
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1582
+ if (!shape) return null;
1583
+ for (const [key, value] of Object.entries(shape)) {
1584
+ const innerTypeName = getZodTypeName(value);
1585
+ if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") {
1586
+ continue;
1587
+ }
1588
+ if (innerTypeName === "ZodString" || innerTypeName === "string") {
1589
+ return key;
1590
+ }
1591
+ }
1592
+ return null;
1593
+ }
1454
1594
  function getZodTypeName(schema) {
1455
1595
  if (!schema || !schema._def) return "";
1456
1596
  const def = schema._def;