@json-render/core 0.5.1 → 0.6.0

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.js CHANGED
@@ -29,35 +29,39 @@ __export(index_exports, {
29
29
  DynamicNumberSchema: () => DynamicNumberSchema,
30
30
  DynamicStringSchema: () => DynamicStringSchema,
31
31
  DynamicValueSchema: () => DynamicValueSchema,
32
- LogicExpressionSchema: () => LogicExpressionSchema,
32
+ SPEC_DATA_PART: () => SPEC_DATA_PART,
33
+ SPEC_DATA_PART_TYPE: () => SPEC_DATA_PART_TYPE,
33
34
  ValidationCheckSchema: () => ValidationCheckSchema,
34
35
  ValidationConfigSchema: () => ValidationConfigSchema,
35
36
  VisibilityConditionSchema: () => VisibilityConditionSchema,
36
37
  action: () => action,
37
38
  actionBinding: () => actionBinding,
38
39
  addByPath: () => addByPath,
40
+ applySpecPatch: () => applySpecPatch,
39
41
  applySpecStreamPatch: () => applySpecStreamPatch,
40
42
  autoFixSpec: () => autoFixSpec,
41
43
  buildUserPrompt: () => buildUserPrompt,
42
44
  builtInValidationFunctions: () => builtInValidationFunctions,
43
45
  check: () => check,
44
46
  compileSpecStream: () => compileSpecStream,
45
- createCatalog: () => createCatalog,
47
+ createJsonRenderTransform: () => createJsonRenderTransform,
48
+ createMixedStreamParser: () => createMixedStreamParser,
46
49
  createSpecStreamCompiler: () => createSpecStreamCompiler,
47
50
  defineCatalog: () => defineCatalog,
48
51
  defineSchema: () => defineSchema,
49
- evaluateLogicExpression: () => evaluateLogicExpression,
50
52
  evaluateVisibility: () => evaluateVisibility,
51
53
  executeAction: () => executeAction,
52
54
  findFormValue: () => findFormValue,
53
55
  formatSpecIssues: () => formatSpecIssues,
54
- generateCatalogPrompt: () => generateCatalogPrompt,
55
- generateSystemPrompt: () => generateSystemPrompt,
56
56
  getByPath: () => getByPath,
57
57
  interpolateString: () => interpolateString,
58
+ nestedToFlat: () => nestedToFlat,
58
59
  parseSpecStreamLine: () => parseSpecStreamLine,
60
+ pipeJsonRender: () => pipeJsonRender,
59
61
  removeByPath: () => removeByPath,
60
62
  resolveAction: () => resolveAction,
63
+ resolveActionParam: () => resolveActionParam,
64
+ resolveBindings: () => resolveBindings,
61
65
  resolveDynamicValue: () => resolveDynamicValue,
62
66
  resolveElementProps: () => resolveElementProps,
63
67
  resolvePropValue: () => resolvePropValue,
@@ -76,26 +80,26 @@ var DynamicValueSchema = import_zod.z.union([
76
80
  import_zod.z.number(),
77
81
  import_zod.z.boolean(),
78
82
  import_zod.z.null(),
79
- import_zod.z.object({ path: import_zod.z.string() })
83
+ import_zod.z.object({ $state: import_zod.z.string() })
80
84
  ]);
81
85
  var DynamicStringSchema = import_zod.z.union([
82
86
  import_zod.z.string(),
83
- import_zod.z.object({ path: import_zod.z.string() })
87
+ import_zod.z.object({ $state: import_zod.z.string() })
84
88
  ]);
85
89
  var DynamicNumberSchema = import_zod.z.union([
86
90
  import_zod.z.number(),
87
- import_zod.z.object({ path: import_zod.z.string() })
91
+ import_zod.z.object({ $state: import_zod.z.string() })
88
92
  ]);
89
93
  var DynamicBooleanSchema = import_zod.z.union([
90
94
  import_zod.z.boolean(),
91
- import_zod.z.object({ path: import_zod.z.string() })
95
+ import_zod.z.object({ $state: import_zod.z.string() })
92
96
  ]);
93
97
  function resolveDynamicValue(value, stateModel) {
94
98
  if (value === null || value === void 0) {
95
99
  return void 0;
96
100
  }
97
- if (typeof value === "object" && "path" in value) {
98
- return getByPath(stateModel, value.path);
101
+ if (typeof value === "object" && "$state" in value) {
102
+ return getByPath(stateModel, value.$state);
99
103
  }
100
104
  return value;
101
105
  }
@@ -242,7 +246,7 @@ function deepEqual(a, b) {
242
246
  if (aKeys.length !== bKeys.length) return false;
243
247
  return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
244
248
  }
245
- function findFormValue(fieldName, params, data) {
249
+ function findFormValue(fieldName, params, state) {
246
250
  if (params?.[fieldName] !== void 0) {
247
251
  const val = params[fieldName];
248
252
  if (typeof val !== "string" || !val.includes(".")) {
@@ -259,19 +263,15 @@ function findFormValue(fieldName, params, data) {
259
263
  }
260
264
  }
261
265
  }
262
- if (data) {
263
- for (const key of Object.keys(data)) {
266
+ if (state) {
267
+ for (const key of Object.keys(state)) {
264
268
  if (key === fieldName || key.endsWith(`.${fieldName}`)) {
265
- return data[key];
269
+ return state[key];
266
270
  }
267
271
  }
268
- const prefixes = ["form", "newCustomer", "customer", ""];
269
- for (const prefix of prefixes) {
270
- const path = prefix ? `${prefix}/${fieldName}` : fieldName;
271
- const val = getByPath(data, path);
272
- if (val !== void 0) {
273
- return val;
274
- }
272
+ const val = getByPath(state, fieldName);
273
+ if (val !== void 0) {
274
+ return val;
275
275
  }
276
276
  }
277
277
  return void 0;
@@ -325,6 +325,44 @@ function applySpecStreamPatch(obj, patch) {
325
325
  }
326
326
  return obj;
327
327
  }
328
+ function applySpecPatch(spec, patch) {
329
+ applySpecStreamPatch(spec, patch);
330
+ return spec;
331
+ }
332
+ function nestedToFlat(nested) {
333
+ const elements = {};
334
+ let counter = 0;
335
+ function walk(node) {
336
+ const key = `el-${counter++}`;
337
+ const { type, props, children: rawChildren, ...rest } = node;
338
+ const childKeys = [];
339
+ if (Array.isArray(rawChildren)) {
340
+ for (const child of rawChildren) {
341
+ if (child && typeof child === "object" && "type" in child) {
342
+ childKeys.push(walk(child));
343
+ }
344
+ }
345
+ }
346
+ const element = {
347
+ type: type ?? "unknown",
348
+ props: props ?? {},
349
+ children: childKeys
350
+ };
351
+ for (const [k, v] of Object.entries(rest)) {
352
+ if (k !== "state" && v !== void 0) {
353
+ element[k] = v;
354
+ }
355
+ }
356
+ elements[key] = element;
357
+ return key;
358
+ }
359
+ const root = walk(nested);
360
+ const spec = { root, elements };
361
+ if (nested.state && typeof nested.state === "object" && !Array.isArray(nested.state)) {
362
+ spec.state = nested.state;
363
+ }
364
+ return spec;
365
+ }
328
366
  function compileSpecStream(stream, initial = {}) {
329
367
  const lines = stream.split("\n");
330
368
  const result = { ...initial };
@@ -387,129 +425,304 @@ function createSpecStreamCompiler(initial = {}) {
387
425
  }
388
426
  };
389
427
  }
428
+ function createMixedStreamParser(callbacks) {
429
+ let buffer = "";
430
+ let inSpecFence = false;
431
+ function processLine(line) {
432
+ const trimmed = line.trim();
433
+ if (!inSpecFence && trimmed.startsWith("```spec")) {
434
+ inSpecFence = true;
435
+ return;
436
+ }
437
+ if (inSpecFence && trimmed === "```") {
438
+ inSpecFence = false;
439
+ return;
440
+ }
441
+ if (!trimmed) return;
442
+ if (inSpecFence) {
443
+ const patch2 = parseSpecStreamLine(trimmed);
444
+ if (patch2) {
445
+ callbacks.onPatch(patch2);
446
+ }
447
+ return;
448
+ }
449
+ const patch = parseSpecStreamLine(trimmed);
450
+ if (patch) {
451
+ callbacks.onPatch(patch);
452
+ } else {
453
+ callbacks.onText(line);
454
+ }
455
+ }
456
+ return {
457
+ push(chunk) {
458
+ buffer += chunk;
459
+ const lines = buffer.split("\n");
460
+ buffer = lines.pop() || "";
461
+ for (const line of lines) {
462
+ processLine(line);
463
+ }
464
+ },
465
+ flush() {
466
+ if (buffer.trim()) {
467
+ processLine(buffer);
468
+ }
469
+ buffer = "";
470
+ }
471
+ };
472
+ }
473
+ var SPEC_FENCE_OPEN = "```spec";
474
+ var SPEC_FENCE_CLOSE = "```";
475
+ function createJsonRenderTransform() {
476
+ let lineBuffer = "";
477
+ let currentTextId = "";
478
+ let buffering = false;
479
+ let inSpecFence = false;
480
+ function emitPatch(patch, controller) {
481
+ controller.enqueue({
482
+ type: SPEC_DATA_PART_TYPE,
483
+ data: { type: "patch", patch }
484
+ });
485
+ }
486
+ function flushBuffer(controller) {
487
+ if (!lineBuffer) return;
488
+ const trimmed = lineBuffer.trim();
489
+ if (inSpecFence) {
490
+ if (trimmed) {
491
+ const patch = parseSpecStreamLine(trimmed);
492
+ if (patch) emitPatch(patch, controller);
493
+ }
494
+ lineBuffer = "";
495
+ buffering = false;
496
+ return;
497
+ }
498
+ if (trimmed) {
499
+ const patch = parseSpecStreamLine(trimmed);
500
+ if (patch) {
501
+ emitPatch(patch, controller);
502
+ } else {
503
+ controller.enqueue({
504
+ type: "text-delta",
505
+ id: currentTextId,
506
+ delta: lineBuffer
507
+ });
508
+ }
509
+ } else {
510
+ controller.enqueue({
511
+ type: "text-delta",
512
+ id: currentTextId,
513
+ delta: lineBuffer
514
+ });
515
+ }
516
+ lineBuffer = "";
517
+ buffering = false;
518
+ }
519
+ function processCompleteLine(line, controller) {
520
+ const trimmed = line.trim();
521
+ if (!inSpecFence && trimmed.startsWith(SPEC_FENCE_OPEN)) {
522
+ inSpecFence = true;
523
+ return;
524
+ }
525
+ if (inSpecFence && trimmed === SPEC_FENCE_CLOSE) {
526
+ inSpecFence = false;
527
+ return;
528
+ }
529
+ if (inSpecFence) {
530
+ if (trimmed) {
531
+ const patch2 = parseSpecStreamLine(trimmed);
532
+ if (patch2) emitPatch(patch2, controller);
533
+ }
534
+ return;
535
+ }
536
+ if (!trimmed) {
537
+ controller.enqueue({
538
+ type: "text-delta",
539
+ id: currentTextId,
540
+ delta: "\n"
541
+ });
542
+ return;
543
+ }
544
+ const patch = parseSpecStreamLine(trimmed);
545
+ if (patch) {
546
+ emitPatch(patch, controller);
547
+ } else {
548
+ controller.enqueue({
549
+ type: "text-delta",
550
+ id: currentTextId,
551
+ delta: line + "\n"
552
+ });
553
+ }
554
+ }
555
+ return new TransformStream({
556
+ transform(chunk, controller) {
557
+ switch (chunk.type) {
558
+ case "text-start": {
559
+ currentTextId = chunk.id;
560
+ controller.enqueue(chunk);
561
+ break;
562
+ }
563
+ case "text-delta": {
564
+ const delta = chunk;
565
+ currentTextId = delta.id;
566
+ const text = delta.delta;
567
+ for (let i = 0; i < text.length; i++) {
568
+ const ch = text.charAt(i);
569
+ if (ch === "\n") {
570
+ if (buffering) {
571
+ processCompleteLine(lineBuffer, controller);
572
+ lineBuffer = "";
573
+ buffering = false;
574
+ } else {
575
+ if (!inSpecFence) {
576
+ controller.enqueue({
577
+ type: "text-delta",
578
+ id: currentTextId,
579
+ delta: "\n"
580
+ });
581
+ }
582
+ }
583
+ } else if (lineBuffer.length === 0 && !buffering) {
584
+ if (inSpecFence || ch === "{" || ch === "`") {
585
+ buffering = true;
586
+ lineBuffer += ch;
587
+ } else {
588
+ controller.enqueue({
589
+ type: "text-delta",
590
+ id: currentTextId,
591
+ delta: ch
592
+ });
593
+ }
594
+ } else if (buffering) {
595
+ lineBuffer += ch;
596
+ } else {
597
+ controller.enqueue({
598
+ type: "text-delta",
599
+ id: currentTextId,
600
+ delta: ch
601
+ });
602
+ }
603
+ }
604
+ break;
605
+ }
606
+ case "text-end": {
607
+ flushBuffer(controller);
608
+ controller.enqueue(chunk);
609
+ break;
610
+ }
611
+ default: {
612
+ controller.enqueue(chunk);
613
+ break;
614
+ }
615
+ }
616
+ },
617
+ flush(controller) {
618
+ flushBuffer(controller);
619
+ }
620
+ });
621
+ }
622
+ var SPEC_DATA_PART = "spec";
623
+ var SPEC_DATA_PART_TYPE = `data-${SPEC_DATA_PART}`;
624
+ function pipeJsonRender(stream) {
625
+ return stream.pipeThrough(
626
+ createJsonRenderTransform()
627
+ );
628
+ }
390
629
 
391
630
  // src/visibility.ts
392
631
  var import_zod2 = require("zod");
393
- var DynamicNumberValueSchema = import_zod2.z.union([
632
+ var numericOrStateRef = import_zod2.z.union([
394
633
  import_zod2.z.number(),
395
- import_zod2.z.object({ path: import_zod2.z.string() })
634
+ import_zod2.z.object({ $state: import_zod2.z.string() })
635
+ ]);
636
+ var comparisonOps = {
637
+ eq: import_zod2.z.unknown().optional(),
638
+ neq: import_zod2.z.unknown().optional(),
639
+ gt: numericOrStateRef.optional(),
640
+ gte: numericOrStateRef.optional(),
641
+ lt: numericOrStateRef.optional(),
642
+ lte: numericOrStateRef.optional(),
643
+ not: import_zod2.z.literal(true).optional()
644
+ };
645
+ var StateConditionSchema = import_zod2.z.object({
646
+ $state: import_zod2.z.string(),
647
+ ...comparisonOps
648
+ });
649
+ var ItemConditionSchema = import_zod2.z.object({
650
+ $item: import_zod2.z.string(),
651
+ ...comparisonOps
652
+ });
653
+ var IndexConditionSchema = import_zod2.z.object({
654
+ $index: import_zod2.z.literal(true),
655
+ ...comparisonOps
656
+ });
657
+ var SingleConditionSchema = import_zod2.z.union([
658
+ StateConditionSchema,
659
+ ItemConditionSchema,
660
+ IndexConditionSchema
396
661
  ]);
397
- var LogicExpressionSchema = import_zod2.z.lazy(
662
+ var VisibilityConditionSchema = import_zod2.z.lazy(
398
663
  () => import_zod2.z.union([
399
- import_zod2.z.object({ and: import_zod2.z.array(LogicExpressionSchema) }),
400
- import_zod2.z.object({ or: import_zod2.z.array(LogicExpressionSchema) }),
401
- import_zod2.z.object({ not: LogicExpressionSchema }),
402
- import_zod2.z.object({ path: import_zod2.z.string() }),
403
- import_zod2.z.object({ eq: import_zod2.z.tuple([DynamicValueSchema, DynamicValueSchema]) }),
404
- import_zod2.z.object({ neq: import_zod2.z.tuple([DynamicValueSchema, DynamicValueSchema]) }),
405
- import_zod2.z.object({
406
- gt: import_zod2.z.tuple([DynamicNumberValueSchema, DynamicNumberValueSchema])
407
- }),
408
- import_zod2.z.object({
409
- gte: import_zod2.z.tuple([DynamicNumberValueSchema, DynamicNumberValueSchema])
410
- }),
411
- import_zod2.z.object({
412
- lt: import_zod2.z.tuple([DynamicNumberValueSchema, DynamicNumberValueSchema])
413
- }),
414
- import_zod2.z.object({
415
- lte: import_zod2.z.tuple([DynamicNumberValueSchema, DynamicNumberValueSchema])
416
- })
664
+ import_zod2.z.boolean(),
665
+ SingleConditionSchema,
666
+ import_zod2.z.array(SingleConditionSchema),
667
+ import_zod2.z.object({ $and: import_zod2.z.array(VisibilityConditionSchema) }),
668
+ import_zod2.z.object({ $or: import_zod2.z.array(VisibilityConditionSchema) })
417
669
  ])
418
670
  );
419
- var VisibilityConditionSchema = import_zod2.z.union([
420
- import_zod2.z.boolean(),
421
- import_zod2.z.object({ path: import_zod2.z.string() }),
422
- import_zod2.z.object({ auth: import_zod2.z.enum(["signedIn", "signedOut"]) }),
423
- LogicExpressionSchema
424
- ]);
425
- function evaluateLogicExpression(expr, ctx) {
426
- const { stateModel } = ctx;
427
- if ("and" in expr) {
428
- return expr.and.every((subExpr) => evaluateLogicExpression(subExpr, ctx));
429
- }
430
- if ("or" in expr) {
431
- return expr.or.some((subExpr) => evaluateLogicExpression(subExpr, ctx));
432
- }
433
- if ("not" in expr) {
434
- return !evaluateLogicExpression(expr.not, ctx);
435
- }
436
- if ("path" in expr) {
437
- const value = resolveDynamicValue({ path: expr.path }, stateModel);
438
- return Boolean(value);
439
- }
440
- if ("eq" in expr) {
441
- const [left, right] = expr.eq;
442
- const leftValue = resolveDynamicValue(left, stateModel);
443
- const rightValue = resolveDynamicValue(right, stateModel);
444
- return leftValue === rightValue;
445
- }
446
- if ("neq" in expr) {
447
- const [left, right] = expr.neq;
448
- const leftValue = resolveDynamicValue(left, stateModel);
449
- const rightValue = resolveDynamicValue(right, stateModel);
450
- return leftValue !== rightValue;
451
- }
452
- if ("gt" in expr) {
453
- const [left, right] = expr.gt;
454
- const leftValue = resolveDynamicValue(
455
- left,
456
- stateModel
457
- );
458
- const rightValue = resolveDynamicValue(
459
- right,
460
- stateModel
461
- );
462
- if (typeof leftValue === "number" && typeof rightValue === "number") {
463
- return leftValue > rightValue;
671
+ function resolveComparisonValue(value, ctx) {
672
+ if (typeof value === "object" && value !== null) {
673
+ if ("$state" in value && typeof value.$state === "string") {
674
+ return getByPath(ctx.stateModel, value.$state);
464
675
  }
465
- return false;
466
676
  }
467
- if ("gte" in expr) {
468
- const [left, right] = expr.gte;
469
- const leftValue = resolveDynamicValue(
470
- left,
471
- stateModel
472
- );
473
- const rightValue = resolveDynamicValue(
474
- right,
475
- stateModel
476
- );
477
- if (typeof leftValue === "number" && typeof rightValue === "number") {
478
- return leftValue >= rightValue;
479
- }
480
- return false;
677
+ return value;
678
+ }
679
+ function isItemCondition(cond) {
680
+ return "$item" in cond;
681
+ }
682
+ function isIndexCondition(cond) {
683
+ return "$index" in cond;
684
+ }
685
+ function resolveConditionValue(cond, ctx) {
686
+ if (isIndexCondition(cond)) {
687
+ return ctx.repeatIndex;
481
688
  }
482
- if ("lt" in expr) {
483
- const [left, right] = expr.lt;
484
- const leftValue = resolveDynamicValue(
485
- left,
486
- stateModel
487
- );
488
- const rightValue = resolveDynamicValue(
489
- right,
490
- stateModel
491
- );
492
- if (typeof leftValue === "number" && typeof rightValue === "number") {
493
- return leftValue < rightValue;
494
- }
495
- return false;
689
+ if (isItemCondition(cond)) {
690
+ if (ctx.repeatItem === void 0) return void 0;
691
+ return cond.$item === "" ? ctx.repeatItem : getByPath(ctx.repeatItem, cond.$item);
496
692
  }
497
- if ("lte" in expr) {
498
- const [left, right] = expr.lte;
499
- const leftValue = resolveDynamicValue(
500
- left,
501
- stateModel
502
- );
503
- const rightValue = resolveDynamicValue(
504
- right,
505
- stateModel
506
- );
507
- if (typeof leftValue === "number" && typeof rightValue === "number") {
508
- return leftValue <= rightValue;
509
- }
510
- return false;
693
+ return getByPath(ctx.stateModel, cond.$state);
694
+ }
695
+ function evaluateCondition(cond, ctx) {
696
+ const value = resolveConditionValue(cond, ctx);
697
+ let result;
698
+ if (cond.eq !== void 0) {
699
+ const rhs = resolveComparisonValue(cond.eq, ctx);
700
+ result = value === rhs;
701
+ } else if (cond.neq !== void 0) {
702
+ const rhs = resolveComparisonValue(cond.neq, ctx);
703
+ result = value !== rhs;
704
+ } else if (cond.gt !== void 0) {
705
+ const rhs = resolveComparisonValue(cond.gt, ctx);
706
+ result = typeof value === "number" && typeof rhs === "number" ? value > rhs : false;
707
+ } else if (cond.gte !== void 0) {
708
+ const rhs = resolveComparisonValue(cond.gte, ctx);
709
+ result = typeof value === "number" && typeof rhs === "number" ? value >= rhs : false;
710
+ } else if (cond.lt !== void 0) {
711
+ const rhs = resolveComparisonValue(cond.lt, ctx);
712
+ result = typeof value === "number" && typeof rhs === "number" ? value < rhs : false;
713
+ } else if (cond.lte !== void 0) {
714
+ const rhs = resolveComparisonValue(cond.lte, ctx);
715
+ result = typeof value === "number" && typeof rhs === "number" ? value <= rhs : false;
716
+ } else {
717
+ result = Boolean(value);
511
718
  }
512
- return false;
719
+ return cond.not === true ? !result : result;
720
+ }
721
+ function isAndCondition(condition) {
722
+ return typeof condition === "object" && condition !== null && !Array.isArray(condition) && "$and" in condition;
723
+ }
724
+ function isOrCondition(condition) {
725
+ return typeof condition === "object" && condition !== null && !Array.isArray(condition) && "$or" in condition;
513
726
  }
514
727
  function evaluateVisibility(condition, ctx) {
515
728
  if (condition === void 0) {
@@ -518,74 +731,114 @@ function evaluateVisibility(condition, ctx) {
518
731
  if (typeof condition === "boolean") {
519
732
  return condition;
520
733
  }
521
- if ("path" in condition && !("and" in condition) && !("or" in condition)) {
522
- const value = resolveDynamicValue({ path: condition.path }, ctx.stateModel);
523
- return Boolean(value);
734
+ if (Array.isArray(condition)) {
735
+ return condition.every((c) => evaluateCondition(c, ctx));
524
736
  }
525
- if ("auth" in condition) {
526
- const isSignedIn = ctx.authState?.isSignedIn ?? false;
527
- if (condition.auth === "signedIn") {
528
- return isSignedIn;
529
- }
530
- if (condition.auth === "signedOut") {
531
- return !isSignedIn;
532
- }
533
- return false;
737
+ if (isAndCondition(condition)) {
738
+ return condition.$and.every((child) => evaluateVisibility(child, ctx));
739
+ }
740
+ if (isOrCondition(condition)) {
741
+ return condition.$or.some((child) => evaluateVisibility(child, ctx));
534
742
  }
535
- return evaluateLogicExpression(condition, ctx);
743
+ return evaluateCondition(condition, ctx);
536
744
  }
537
745
  var visibility = {
538
746
  /** Always visible */
539
747
  always: true,
540
748
  /** Never visible */
541
749
  never: false,
542
- /** Visible when path is truthy */
543
- when: (path) => ({ path }),
544
- /** Visible when signed in */
545
- signedIn: { auth: "signedIn" },
546
- /** Visible when signed out */
547
- signedOut: { auth: "signedOut" },
548
- /** AND multiple conditions */
549
- and: (...conditions) => ({
550
- and: conditions
551
- }),
552
- /** OR multiple conditions */
553
- or: (...conditions) => ({
554
- or: conditions
555
- }),
556
- /** NOT a condition */
557
- not: (condition) => ({ not: condition }),
750
+ /** Visible when state path is truthy */
751
+ when: (path) => ({ $state: path }),
752
+ /** Visible when state path is falsy */
753
+ unless: (path) => ({ $state: path, not: true }),
558
754
  /** Equality check */
559
- eq: (left, right) => ({
560
- eq: [left, right]
755
+ eq: (path, value) => ({
756
+ $state: path,
757
+ eq: value
561
758
  }),
562
759
  /** Not equal check */
563
- neq: (left, right) => ({
564
- neq: [left, right]
760
+ neq: (path, value) => ({
761
+ $state: path,
762
+ neq: value
565
763
  }),
566
764
  /** Greater than */
567
- gt: (left, right) => ({ gt: [left, right] }),
765
+ gt: (path, value) => ({
766
+ $state: path,
767
+ gt: value
768
+ }),
568
769
  /** Greater than or equal */
569
- gte: (left, right) => ({ gte: [left, right] }),
770
+ gte: (path, value) => ({
771
+ $state: path,
772
+ gte: value
773
+ }),
570
774
  /** Less than */
571
- lt: (left, right) => ({ lt: [left, right] }),
775
+ lt: (path, value) => ({
776
+ $state: path,
777
+ lt: value
778
+ }),
572
779
  /** Less than or equal */
573
- lte: (left, right) => ({ lte: [left, right] })
780
+ lte: (path, value) => ({
781
+ $state: path,
782
+ lte: value
783
+ }),
784
+ /** AND multiple conditions */
785
+ and: (...conditions) => ({
786
+ $and: conditions
787
+ }),
788
+ /** OR multiple conditions */
789
+ or: (...conditions) => ({
790
+ $or: conditions
791
+ })
574
792
  };
575
793
 
576
794
  // src/props.ts
577
- function isPathExpression(value) {
578
- return typeof value === "object" && value !== null && "$path" in value && typeof value.$path === "string";
795
+ function isStateExpression(value) {
796
+ return typeof value === "object" && value !== null && "$state" in value && typeof value.$state === "string";
797
+ }
798
+ function isItemExpression(value) {
799
+ return typeof value === "object" && value !== null && "$item" in value && typeof value.$item === "string";
800
+ }
801
+ function isIndexExpression(value) {
802
+ return typeof value === "object" && value !== null && "$index" in value && value.$index === true;
803
+ }
804
+ function isBindStateExpression(value) {
805
+ return typeof value === "object" && value !== null && "$bindState" in value && typeof value.$bindState === "string";
806
+ }
807
+ function isBindItemExpression(value) {
808
+ return typeof value === "object" && value !== null && "$bindItem" in value && typeof value.$bindItem === "string";
579
809
  }
580
810
  function isCondExpression(value) {
581
811
  return typeof value === "object" && value !== null && "$cond" in value && "$then" in value && "$else" in value;
582
812
  }
813
+ function resolveBindItemPath(itemPath, ctx) {
814
+ if (ctx.repeatBasePath == null) {
815
+ console.warn(`$bindItem used outside repeat scope: "${itemPath}"`);
816
+ return void 0;
817
+ }
818
+ if (itemPath === "") return ctx.repeatBasePath;
819
+ return ctx.repeatBasePath + "/" + itemPath;
820
+ }
583
821
  function resolvePropValue(value, ctx) {
584
822
  if (value === null || value === void 0) {
585
823
  return value;
586
824
  }
587
- if (isPathExpression(value)) {
588
- return getByPath(ctx.stateModel, value.$path);
825
+ if (isStateExpression(value)) {
826
+ return getByPath(ctx.stateModel, value.$state);
827
+ }
828
+ if (isItemExpression(value)) {
829
+ if (ctx.repeatItem === void 0) return void 0;
830
+ return value.$item === "" ? ctx.repeatItem : getByPath(ctx.repeatItem, value.$item);
831
+ }
832
+ if (isIndexExpression(value)) {
833
+ return ctx.repeatIndex;
834
+ }
835
+ if (isBindStateExpression(value)) {
836
+ return getByPath(ctx.stateModel, value.$bindState);
837
+ }
838
+ if (isBindItemExpression(value)) {
839
+ const resolvedPath = resolveBindItemPath(value.$bindItem, ctx);
840
+ if (resolvedPath === void 0) return void 0;
841
+ return getByPath(ctx.stateModel, resolvedPath);
589
842
  }
590
843
  if (isCondExpression(value)) {
591
844
  const result = evaluateVisibility(value.$cond, ctx);
@@ -610,6 +863,31 @@ function resolveElementProps(props, ctx) {
610
863
  }
611
864
  return resolved;
612
865
  }
866
+ function resolveBindings(props, ctx) {
867
+ let bindings;
868
+ for (const [key, value] of Object.entries(props)) {
869
+ if (isBindStateExpression(value)) {
870
+ if (!bindings) bindings = {};
871
+ bindings[key] = value.$bindState;
872
+ } else if (isBindItemExpression(value)) {
873
+ const resolved = resolveBindItemPath(value.$bindItem, ctx);
874
+ if (resolved !== void 0) {
875
+ if (!bindings) bindings = {};
876
+ bindings[key] = resolved;
877
+ }
878
+ }
879
+ }
880
+ return bindings;
881
+ }
882
+ function resolveActionParam(value, ctx) {
883
+ if (isItemExpression(value)) {
884
+ return resolveBindItemPath(value.$item, ctx);
885
+ }
886
+ if (isIndexExpression(value)) {
887
+ return ctx.repeatIndex;
888
+ }
889
+ return resolvePropValue(value, ctx);
890
+ }
613
891
 
614
892
  // src/actions.ts
615
893
  var import_zod3 = require("zod");
@@ -662,7 +940,7 @@ function resolveAction(binding, stateModel) {
662
940
  }
663
941
  function interpolateString(template, stateModel) {
664
942
  return template.replace(/\$\{([^}]+)\}/g, (_, path) => {
665
- const value = resolveDynamicValue({ path }, stateModel);
943
+ const value = resolveDynamicValue({ $state: path }, stateModel);
666
944
  return String(value ?? "");
667
945
  });
668
946
  }
@@ -720,14 +998,14 @@ var action = actionBinding;
720
998
  // src/validation.ts
721
999
  var import_zod4 = require("zod");
722
1000
  var ValidationCheckSchema = import_zod4.z.object({
723
- fn: import_zod4.z.string(),
1001
+ type: import_zod4.z.string(),
724
1002
  args: import_zod4.z.record(import_zod4.z.string(), DynamicValueSchema).optional(),
725
1003
  message: import_zod4.z.string()
726
1004
  });
727
1005
  var ValidationConfigSchema = import_zod4.z.object({
728
1006
  checks: import_zod4.z.array(ValidationCheckSchema).optional(),
729
1007
  validateOn: import_zod4.z.enum(["change", "blur", "submit"]).optional(),
730
- enabled: LogicExpressionSchema.optional()
1008
+ enabled: VisibilityConditionSchema.optional()
731
1009
  });
732
1010
  var builtInValidationFunctions = {
733
1011
  /**
@@ -831,19 +1109,19 @@ function runValidationCheck(check2, ctx) {
831
1109
  resolvedArgs[key] = resolveDynamicValue(argValue, stateModel);
832
1110
  }
833
1111
  }
834
- const fn = builtInValidationFunctions[check2.fn] ?? customFunctions?.[check2.fn];
835
- if (!fn) {
836
- console.warn(`Unknown validation function: ${check2.fn}`);
1112
+ const validationFn = builtInValidationFunctions[check2.type] ?? customFunctions?.[check2.type];
1113
+ if (!validationFn) {
1114
+ console.warn(`Unknown validation function: ${check2.type}`);
837
1115
  return {
838
- fn: check2.fn,
1116
+ type: check2.type,
839
1117
  valid: true,
840
1118
  // Don't fail on unknown functions
841
1119
  message: check2.message
842
1120
  };
843
1121
  }
844
- const valid = fn(value, resolvedArgs);
1122
+ const valid = validationFn(value, resolvedArgs);
845
1123
  return {
846
- fn: check2.fn,
1124
+ type: check2.type,
847
1125
  valid,
848
1126
  message: check2.message
849
1127
  };
@@ -852,9 +1130,8 @@ function runValidation(config, ctx) {
852
1130
  const checks = [];
853
1131
  const errors = [];
854
1132
  if (config.enabled) {
855
- const enabled = evaluateLogicExpression(config.enabled, {
856
- stateModel: ctx.stateModel,
857
- authState: ctx.authState
1133
+ const enabled = evaluateVisibility(config.enabled, {
1134
+ stateModel: ctx.stateModel
858
1135
  });
859
1136
  if (!enabled) {
860
1137
  return { valid: true, errors: [], checks: [] };
@@ -877,45 +1154,45 @@ function runValidation(config, ctx) {
877
1154
  }
878
1155
  var check = {
879
1156
  required: (message = "This field is required") => ({
880
- fn: "required",
1157
+ type: "required",
881
1158
  message
882
1159
  }),
883
1160
  email: (message = "Invalid email address") => ({
884
- fn: "email",
1161
+ type: "email",
885
1162
  message
886
1163
  }),
887
1164
  minLength: (min, message) => ({
888
- fn: "minLength",
1165
+ type: "minLength",
889
1166
  args: { min },
890
1167
  message: message ?? `Must be at least ${min} characters`
891
1168
  }),
892
1169
  maxLength: (max, message) => ({
893
- fn: "maxLength",
1170
+ type: "maxLength",
894
1171
  args: { max },
895
1172
  message: message ?? `Must be at most ${max} characters`
896
1173
  }),
897
1174
  pattern: (pattern, message = "Invalid format") => ({
898
- fn: "pattern",
1175
+ type: "pattern",
899
1176
  args: { pattern },
900
1177
  message
901
1178
  }),
902
1179
  min: (min, message) => ({
903
- fn: "min",
1180
+ type: "min",
904
1181
  args: { min },
905
1182
  message: message ?? `Must be at least ${min}`
906
1183
  }),
907
1184
  max: (max, message) => ({
908
- fn: "max",
1185
+ type: "max",
909
1186
  args: { max },
910
1187
  message: message ?? `Must be at most ${max}`
911
1188
  }),
912
1189
  url: (message = "Invalid URL") => ({
913
- fn: "url",
1190
+ type: "url",
914
1191
  message
915
1192
  }),
916
1193
  matches: (otherPath, message = "Fields must match") => ({
917
- fn: "matches",
918
- args: { other: { path: otherPath } },
1194
+ type: "matches",
1195
+ args: { other: { $state: otherPath } },
919
1196
  message
920
1197
  })
921
1198
  };
@@ -1055,7 +1332,7 @@ function autoFixSpec(spec) {
1055
1332
  fixedElements[key] = fixed;
1056
1333
  }
1057
1334
  return {
1058
- spec: { root: spec.root, elements: fixedElements },
1335
+ spec: { root: spec.root, elements: fixedElements, state: spec.state },
1059
1336
  fixes
1060
1337
  };
1061
1338
  }
@@ -1239,39 +1516,104 @@ function generatePrompt(catalog, options) {
1239
1516
  }
1240
1517
  const {
1241
1518
  system = "You are a UI generator that outputs JSON.",
1242
- customRules = []
1519
+ customRules = [],
1520
+ mode = "generate"
1243
1521
  } = options;
1244
1522
  const lines = [];
1245
1523
  lines.push(system);
1246
1524
  lines.push("");
1247
- lines.push("OUTPUT FORMAT:");
1248
- lines.push(
1249
- "Output JSONL (one JSON object per line) with patches to build a UI tree."
1250
- );
1525
+ if (mode === "chat") {
1526
+ lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
1527
+ lines.push(
1528
+ "You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence."
1529
+ );
1530
+ lines.push(
1531
+ "The JSONL lines use RFC 6902 JSON Patch operations to build a UI tree. Always wrap them in a ```spec fence block:"
1532
+ );
1533
+ lines.push(" ```spec");
1534
+ lines.push(' {"op":"add","path":"/root","value":"main"}');
1535
+ lines.push(
1536
+ ' {"op":"add","path":"/elements/main","value":{"type":"Card","props":{"title":"Hello"},"children":[]}}'
1537
+ );
1538
+ lines.push(" ```");
1539
+ lines.push(
1540
+ "If the user's message does not require a UI (e.g. a greeting or clarifying question), respond with text only \u2014 no JSONL."
1541
+ );
1542
+ } else {
1543
+ lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
1544
+ lines.push(
1545
+ "Output JSONL (one JSON object per line) using RFC 6902 JSON Patch operations to build a UI tree."
1546
+ );
1547
+ }
1251
1548
  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."
1549
+ "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
1550
  );
1254
1551
  lines.push("");
1255
1552
  lines.push("Example output (each line is a separate JSON object):");
1256
1553
  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"}}
1554
+ const allComponents = catalog.data.components;
1555
+ const cn = catalog.componentNames;
1556
+ const comp1 = cn[0] || "Component";
1557
+ const comp2 = cn.length > 1 ? cn[1] : comp1;
1558
+ const comp1Def = allComponents?.[comp1];
1559
+ const comp2Def = allComponents?.[comp2];
1560
+ const comp1Props = comp1Def ? getExampleProps(comp1Def) : {};
1561
+ const comp2Props = comp2Def ? getExampleProps(comp2Def) : {};
1562
+ const dynamicPropName = comp2Def?.props ? findFirstStringProp(comp2Def.props) : null;
1563
+ const dynamicProps = dynamicPropName ? { ...comp2Props, [dynamicPropName]: { $item: "title" } } : comp2Props;
1564
+ const exampleOutput = [
1565
+ JSON.stringify({ op: "add", path: "/root", value: "main" }),
1566
+ JSON.stringify({
1567
+ op: "add",
1568
+ path: "/elements/main",
1569
+ value: {
1570
+ type: comp1,
1571
+ props: comp1Props,
1572
+ children: ["child-1", "list"]
1573
+ }
1574
+ }),
1575
+ JSON.stringify({
1576
+ op: "add",
1577
+ path: "/elements/child-1",
1578
+ value: { type: comp2, props: comp2Props, children: [] }
1579
+ }),
1580
+ JSON.stringify({
1581
+ op: "add",
1582
+ path: "/elements/list",
1583
+ value: {
1584
+ type: comp1,
1585
+ props: comp1Props,
1586
+ repeat: { statePath: "/items", key: "id" },
1587
+ children: ["item"]
1588
+ }
1589
+ }),
1590
+ JSON.stringify({
1591
+ op: "add",
1592
+ path: "/elements/item",
1593
+ value: { type: comp2, props: dynamicProps, children: [] }
1594
+ }),
1595
+ JSON.stringify({ op: "add", path: "/state/items", value: [] }),
1596
+ JSON.stringify({
1597
+ op: "add",
1598
+ path: "/state/items/0",
1599
+ value: { id: "1", title: "First Item" }
1600
+ }),
1601
+ JSON.stringify({
1602
+ op: "add",
1603
+ path: "/state/items/1",
1604
+ value: { id: "2", title: "Second Item" }
1605
+ })
1606
+ ].join("\n");
1607
+ lines.push(`${exampleOutput}
1266
1608
 
1267
- Note: state patches appear right after the elements that use them, so the UI fills in as it streams.`);
1609
+ 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
1610
  lines.push("");
1269
1611
  lines.push("INITIAL STATE:");
1270
1612
  lines.push(
1271
- "Specs include a /state field to seed the state model. Components with statePath read from and write to this state, and $path expressions read from it."
1613
+ "Specs include a /state field to seed the state model. Components with { $bindState } or { $bindItem } read from and write to this state, and $state expressions read from it."
1272
1614
  );
1273
1615
  lines.push(
1274
- "CRITICAL: You MUST include state patches whenever your UI displays data via $path expressions, uses repeat to iterate over arrays, or uses statePath bindings. Without state, $path references resolve to nothing and repeat lists render zero items."
1616
+ "CRITICAL: You MUST include state patches whenever your UI displays data via $state, $bindState, $bindItem, $item, or $index expressions, or uses repeat to iterate over arrays. Without state, these references resolve to nothing and repeat lists render zero items."
1275
1617
  );
1276
1618
  lines.push(
1277
1619
  "Output state patches right after the elements that reference them, so the UI fills in progressively as it streams."
@@ -1289,7 +1631,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1289
1631
  ' Initialize the array first if needed: {"op":"add","path":"/state/posts","value":[]}'
1290
1632
  );
1291
1633
  lines.push(
1292
- 'When content comes from the state model, use { "$path": "/some/path" } dynamic props to display it instead of hardcoding the same value in both state and props. The state model is the single source of truth.'
1634
+ 'When content comes from the state model, use { "$state": "/some/path" } dynamic props to display it instead of hardcoding the same value in both state and props. The state model is the single source of truth.'
1293
1635
  );
1294
1636
  lines.push(
1295
1637
  "Include realistic sample data in state. For blogs: 3-4 posts with titles, excerpts, authors, dates. For product lists: 3-5 items with names, prices, descriptions. Never leave arrays empty."
@@ -1297,16 +1639,16 @@ Note: state patches appear right after the elements that use them, so the UI fil
1297
1639
  lines.push("");
1298
1640
  lines.push("DYNAMIC LISTS (repeat field):");
1299
1641
  lines.push(
1300
- 'Any element can have a top-level "repeat" field to render its children once per item in a state array: { "repeat": { "path": "/arrayPath", "key": "id" } }.'
1642
+ 'Any element can have a top-level "repeat" field to render its children once per item in a state array: { "repeat": { "statePath": "/arrayPath", "key": "id" } }.'
1301
1643
  );
1302
1644
  lines.push(
1303
- '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.'
1645
+ 'The element itself renders once (as the container), and its children are expanded once per array item. "statePath" is the state array path. "key" is an optional field name on each item for stable React keys.'
1304
1646
  );
1305
1647
  lines.push(
1306
- 'Example: { "type": "Column", "props": { "gap": 8 }, "repeat": { "path": "/todos", "key": "id" }, "children": ["todo-item"] }'
1648
+ `Example: ${JSON.stringify({ type: comp1, props: comp1Props, repeat: { statePath: "/todos", key: "id" }, children: ["todo-item"] })}`
1307
1649
  );
1308
1650
  lines.push(
1309
- '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.'
1651
+ 'Inside children of a repeated element, use { "$item": "field" } to read a field from the current item, and { "$index": true } to get the current array index. For two-way binding to an item field use { "$bindItem": "completed" } on the appropriate prop.'
1310
1652
  );
1311
1653
  lines.push(
1312
1654
  "ALWAYS use the repeat field for lists backed by state arrays. NEVER hardcode individual elements for each array item."
@@ -1317,19 +1659,19 @@ Note: state patches appear right after the elements that use them, so the UI fil
1317
1659
  lines.push("");
1318
1660
  lines.push("ARRAY STATE ACTIONS:");
1319
1661
  lines.push(
1320
- 'Use action "pushState" to append items to arrays. Params: { path: "/arrayPath", value: { ...item }, clearPath: "/inputPath" }.'
1662
+ 'Use action "pushState" to append items to arrays. Params: { statePath: "/arrayPath", value: { ...item }, clearStatePath: "/inputPath" }.'
1321
1663
  );
1322
1664
  lines.push(
1323
- 'Values inside pushState can contain { "path": "/statePath" } references to read current state (e.g. the text from an input field).'
1665
+ 'Values inside pushState can contain { "$state": "/statePath" } references to read current state (e.g. the text from an input field).'
1324
1666
  );
1325
1667
  lines.push(
1326
1668
  'Use "$id" inside a pushState value to auto-generate a unique ID.'
1327
1669
  );
1328
1670
  lines.push(
1329
- 'Example: on: { "press": { "action": "pushState", "params": { "path": "/todos", "value": { "id": "$id", "title": { "path": "/newTodoText" }, "completed": false }, "clearPath": "/newTodoText" } } }'
1671
+ 'Example: on: { "press": { "action": "pushState", "params": { "statePath": "/todos", "value": { "id": "$id", "title": { "$state": "/newTodoText" }, "completed": false }, "clearStatePath": "/newTodoText" } } }'
1330
1672
  );
1331
1673
  lines.push(
1332
- `Use action "removeState" to remove items from arrays by index. Params: { path: "/arrayPath", index: N }. Inside a repeated element's children, use "$index" for the current item index.`
1674
+ `Use action "removeState" to remove items from arrays by index. Params: { statePath: "/arrayPath", index: N }. Inside a repeated element's children, use { "$index": true } for the current item index. Action params support the same expressions as props: { "$item": "field" } resolves to the absolute state path, { "$index": true } resolves to the index number, and { "$state": "/path" } reads a value from state.`
1333
1675
  );
1334
1676
  lines.push(
1335
1677
  "For lists where users can add/remove items (todos, carts, etc.), use pushState and removeState instead of hardcoding with setState."
@@ -1339,7 +1681,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
1339
1681
  '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
1682
  );
1341
1683
  lines.push("");
1342
- const components = catalog.data.components;
1684
+ const components = allComponents;
1343
1685
  if (components) {
1344
1686
  lines.push(`AVAILABLE COMPONENTS (${catalog.componentNames.length}):`);
1345
1687
  lines.push("");
@@ -1372,11 +1714,11 @@ Note: state patches appear right after the elements that use them, so the UI fil
1372
1714
  lines.push("");
1373
1715
  lines.push("Example:");
1374
1716
  lines.push(
1375
- ' {"type":"Button","props":{"label":"Save"},"on":{"press":{"action":"setState","params":{"path":"/saved","value":true}}},"children":[]}'
1717
+ ` ${JSON.stringify({ type: comp1, props: comp1Props, on: { press: { action: "setState", params: { statePath: "/saved", value: true } } }, children: [] })}`
1376
1718
  );
1377
1719
  lines.push("");
1378
1720
  lines.push(
1379
- 'Action params can use dynamic path references to read from state: { "path": "/statePath" }.'
1721
+ 'Action params can use dynamic references to read from state: { "$state": "/statePath" }.'
1380
1722
  );
1381
1723
  lines.push(
1382
1724
  "IMPORTANT: Do NOT put action/actionParams inside props. Always use the `on` field for event bindings."
@@ -1384,62 +1726,141 @@ Note: state patches appear right after the elements that use them, so the UI fil
1384
1726
  lines.push("");
1385
1727
  lines.push("VISIBILITY CONDITIONS:");
1386
1728
  lines.push(
1387
- "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."
1729
+ "Elements can have an optional `visible` field to conditionally show/hide based on state. IMPORTANT: `visible` is a top-level field on the element object (sibling of type/props/children), NOT inside props."
1730
+ );
1731
+ lines.push(
1732
+ `Correct: ${JSON.stringify({ type: comp1, props: comp1Props, visible: { $state: "/activeTab", eq: "home" }, children: ["..."] })}`
1733
+ );
1734
+ lines.push(
1735
+ '- `{ "$state": "/path" }` - visible when state at path is truthy'
1736
+ );
1737
+ lines.push(
1738
+ '- `{ "$state": "/path", "not": true }` - visible when state at path is falsy'
1739
+ );
1740
+ lines.push(
1741
+ '- `{ "$state": "/path", "eq": "value" }` - visible when state equals value'
1388
1742
  );
1389
1743
  lines.push(
1390
- 'Correct: {"type":"Column","props":{"gap":8},"visible":{"eq":[{"path":"/tab"},"home"]},"children":[...]}'
1744
+ '- `{ "$state": "/path", "neq": "value" }` - visible when state does not equal value'
1391
1745
  );
1392
1746
  lines.push(
1393
- '- `{ "eq": [{ "path": "/statePath" }, "value"] }` - visible when state at path equals value'
1747
+ '- `{ "$state": "/path", "gt": N }` / `gte` / `lt` / `lte` - numeric comparisons'
1394
1748
  );
1395
1749
  lines.push(
1396
- '- `{ "neq": [{ "path": "/statePath" }, "value"] }` - visible when state at path does not equal value'
1750
+ "- Use ONE operator per condition (eq, neq, gt, gte, lt, lte). Do not combine multiple operators."
1397
1751
  );
1398
- lines.push('- `{ "path": "/statePath" }` - visible when path is truthy');
1752
+ lines.push('- Any condition can add `"not": true` to invert its result');
1399
1753
  lines.push(
1400
- '- `{ "and": [...] }`, `{ "or": [...] }`, `{ "not": {...} }` - combine conditions'
1754
+ "- `[condition, condition]` - all conditions must be true (implicit AND)"
1755
+ );
1756
+ lines.push(
1757
+ '- `{ "$and": [condition, condition] }` - explicit AND (use when nesting inside $or)'
1758
+ );
1759
+ lines.push(
1760
+ '- `{ "$or": [condition, condition] }` - at least one must be true (OR)'
1401
1761
  );
1402
1762
  lines.push("- `true` / `false` - always visible/hidden");
1403
1763
  lines.push("");
1404
1764
  lines.push(
1405
- "Use the Pressable component with on.press bound to setState to update state and drive visibility."
1765
+ "Use a component with on.press bound to setState to update state and drive visibility."
1406
1766
  );
1407
1767
  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.'
1768
+ `Example: A ${comp1} with on: { "press": { "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } } } sets state, then a container with visible: { "$state": "/activeTab", "eq": "home" } shows only when that tab is active.`
1769
+ );
1770
+ lines.push("");
1771
+ lines.push(
1772
+ 'For tab patterns where the first/default tab should be visible when no tab is selected yet, use $or to handle both cases: visible: { "$or": [{ "$state": "/activeTab", "eq": "home" }, { "$state": "/activeTab", "not": true }] }. This ensures the first tab is visible both when explicitly selected AND when /activeTab is not yet set.'
1409
1773
  );
1410
1774
  lines.push("");
1411
1775
  lines.push("DYNAMIC PROPS:");
1412
1776
  lines.push(
1413
- "Any prop value can be a dynamic expression that resolves based on state. Two forms are supported:"
1777
+ "Any prop value can be a dynamic expression that resolves based on state. Three forms are supported:"
1414
1778
  );
1415
1779
  lines.push("");
1416
1780
  lines.push(
1417
- '1. State binding: `{ "$path": "/statePath" }` - resolves to the value at that state path.'
1781
+ '1. Read-only state: `{ "$state": "/statePath" }` - resolves to the value at that state path (one-way read).'
1418
1782
  );
1419
1783
  lines.push(
1420
- ' Example: `"color": { "$path": "/theme/primary" }` reads the color from state.'
1784
+ ' Example: `"color": { "$state": "/theme/primary" }` reads the color from state.'
1421
1785
  );
1422
1786
  lines.push("");
1423
1787
  lines.push(
1424
- '2. Conditional: `{ "$cond": <condition>, "$then": <value>, "$else": <value> }` - evaluates the condition (same syntax as visibility conditions) and picks the matching value.'
1788
+ '2. Two-way binding: `{ "$bindState": "/statePath" }` - resolves to the value at the state path AND enables write-back. Use on form input props (value, checked, pressed, etc.).'
1789
+ );
1790
+ lines.push(
1791
+ ' Example: `"value": { "$bindState": "/form/email" }` binds the input value to /form/email.'
1792
+ );
1793
+ lines.push(
1794
+ ' Inside repeat scopes: `"checked": { "$bindItem": "completed" }` binds to the current item\'s completed field.'
1425
1795
  );
1796
+ lines.push("");
1426
1797
  lines.push(
1427
- ' Example: `"color": { "$cond": { "eq": [{ "path": "/activeTab" }, "home"] }, "$then": "#007AFF", "$else": "#8E8E93" }`'
1798
+ '3. Conditional: `{ "$cond": <condition>, "$then": <value>, "$else": <value> }` - evaluates the condition (same syntax as visibility conditions) and picks the matching value.'
1428
1799
  );
1429
1800
  lines.push(
1430
- ' Example: `"name": { "$cond": { "eq": [{ "path": "/activeTab" }, "home"] }, "$then": "home", "$else": "home-outline" }`'
1801
+ ' Example: `"color": { "$cond": { "$state": "/activeTab", "eq": "home" }, "$then": "#007AFF", "$else": "#8E8E93" }`'
1431
1802
  );
1432
1803
  lines.push("");
1433
1804
  lines.push(
1434
- "Use dynamic props instead of duplicating elements with opposing visible conditions when only prop values differ."
1805
+ "Use $bindState for form inputs (text fields, checkboxes, selects, sliders, etc.) and $state for read-only data display. Inside repeat scopes, use $bindItem for form inputs bound to the current item. Use dynamic props instead of duplicating elements with opposing visible conditions when only prop values differ."
1435
1806
  );
1436
1807
  lines.push("");
1808
+ const hasChecksComponents = allComponents ? Object.entries(allComponents).some(([, def]) => {
1809
+ if (!def.props) return false;
1810
+ const formatted = formatZodType(def.props);
1811
+ return formatted.includes("checks");
1812
+ }) : false;
1813
+ if (hasChecksComponents) {
1814
+ lines.push("VALIDATION:");
1815
+ lines.push(
1816
+ "Form components that accept a `checks` prop support client-side validation."
1817
+ );
1818
+ lines.push(
1819
+ 'Each check is an object: { "type": "<name>", "message": "...", "args": { ... } }'
1820
+ );
1821
+ lines.push("");
1822
+ lines.push("Built-in validation types:");
1823
+ lines.push(" - required \u2014 value must be non-empty");
1824
+ lines.push(" - email \u2014 valid email format");
1825
+ lines.push(' - minLength \u2014 minimum string length (args: { "min": N })');
1826
+ lines.push(' - maxLength \u2014 maximum string length (args: { "max": N })');
1827
+ lines.push(' - pattern \u2014 match a regex (args: { "pattern": "regex" })');
1828
+ lines.push(' - min \u2014 minimum numeric value (args: { "min": N })');
1829
+ lines.push(' - max \u2014 maximum numeric value (args: { "max": N })');
1830
+ lines.push(" - numeric \u2014 value must be a number");
1831
+ lines.push(" - url \u2014 valid URL format");
1832
+ lines.push(
1833
+ ' - matches \u2014 must equal another field (args: { "other": "value" })'
1834
+ );
1835
+ lines.push("");
1836
+ lines.push("Example:");
1837
+ lines.push(
1838
+ ' "checks": [{ "type": "required", "message": "Email is required" }, { "type": "email", "message": "Invalid email" }]'
1839
+ );
1840
+ lines.push("");
1841
+ lines.push(
1842
+ "IMPORTANT: When using checks, the component must also have a { $bindState } or { $bindItem } on its value/checked prop for two-way binding."
1843
+ );
1844
+ lines.push(
1845
+ "Always include validation checks on form inputs for a good user experience (e.g. required, email, minLength)."
1846
+ );
1847
+ lines.push("");
1848
+ }
1437
1849
  lines.push("RULES:");
1438
- const baseRules = [
1850
+ const baseRules = mode === "chat" ? [
1851
+ "When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
1852
+ "Write a brief conversational response before any JSONL output",
1853
+ 'First set root: {"op":"add","path":"/root","value":"<root-key>"}',
1854
+ 'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
1855
+ "Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $state, $bindState, $bindItem, $item, $index, or repeat.",
1856
+ "ONLY use components listed above",
1857
+ "Each element value needs: type, props, children (array of child keys)",
1858
+ "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
1859
+ ] : [
1439
1860
  "Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
1440
1861
  'First set root: {"op":"add","path":"/root","value":"<root-key>"}',
1441
1862
  'Then add each element: {"op":"add","path":"/elements/<key>","value":{...}}',
1442
- "Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $path, repeat, or statePath.",
1863
+ "Output /state patches right after the elements that use them, one per array item for progressive loading. REQUIRED whenever using $state, $bindState, $bindItem, $item, $index, or repeat.",
1443
1864
  "ONLY use components listed above",
1444
1865
  "Each element value needs: type, props, children (array of child keys)",
1445
1866
  "Use unique keys for the element map entries (e.g., 'header', 'metric-1', 'chart-revenue')"
@@ -1451,6 +1872,101 @@ Note: state patches appear right after the elements that use them, so the UI fil
1451
1872
  });
1452
1873
  return lines.join("\n");
1453
1874
  }
1875
+ function getExampleProps(def) {
1876
+ if (def.example && Object.keys(def.example).length > 0) {
1877
+ return def.example;
1878
+ }
1879
+ if (def.props) {
1880
+ return generateExamplePropsFromZod(def.props);
1881
+ }
1882
+ return {};
1883
+ }
1884
+ function generateExamplePropsFromZod(schema) {
1885
+ if (!schema || !schema._def) return {};
1886
+ const def = schema._def;
1887
+ const typeName = getZodTypeName(schema);
1888
+ if (typeName !== "ZodObject" && typeName !== "object") return {};
1889
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1890
+ if (!shape) return {};
1891
+ const result = {};
1892
+ for (const [key, value] of Object.entries(shape)) {
1893
+ const innerTypeName = getZodTypeName(value);
1894
+ if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") {
1895
+ continue;
1896
+ }
1897
+ result[key] = generateExampleValue(value);
1898
+ }
1899
+ return result;
1900
+ }
1901
+ function generateExampleValue(schema) {
1902
+ if (!schema || !schema._def) return "...";
1903
+ const def = schema._def;
1904
+ const typeName = getZodTypeName(schema);
1905
+ switch (typeName) {
1906
+ case "ZodString":
1907
+ case "string":
1908
+ return "example";
1909
+ case "ZodNumber":
1910
+ case "number":
1911
+ return 0;
1912
+ case "ZodBoolean":
1913
+ case "boolean":
1914
+ return true;
1915
+ case "ZodLiteral":
1916
+ case "literal":
1917
+ return def.value;
1918
+ case "ZodEnum":
1919
+ case "enum": {
1920
+ if (Array.isArray(def.values) && def.values.length > 0)
1921
+ return def.values[0];
1922
+ if (def.entries && typeof def.entries === "object") {
1923
+ const values = Object.values(def.entries);
1924
+ return values.length > 0 ? values[0] : "example";
1925
+ }
1926
+ return "example";
1927
+ }
1928
+ case "ZodOptional":
1929
+ case "optional":
1930
+ case "ZodNullable":
1931
+ case "nullable":
1932
+ case "ZodDefault":
1933
+ case "default": {
1934
+ const inner = def.innerType ?? def.wrapped;
1935
+ return inner ? generateExampleValue(inner) : null;
1936
+ }
1937
+ case "ZodArray":
1938
+ case "array":
1939
+ return [];
1940
+ case "ZodObject":
1941
+ case "object":
1942
+ return generateExamplePropsFromZod(schema);
1943
+ case "ZodUnion":
1944
+ case "union": {
1945
+ const options = def.options;
1946
+ return options && options.length > 0 ? generateExampleValue(options[0]) : "...";
1947
+ }
1948
+ default:
1949
+ return "...";
1950
+ }
1951
+ }
1952
+ function findFirstStringProp(schema) {
1953
+ if (!schema || !schema._def) return null;
1954
+ const def = schema._def;
1955
+ const typeName = getZodTypeName(schema);
1956
+ if (typeName !== "ZodObject" && typeName !== "object") return null;
1957
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
1958
+ if (!shape) return null;
1959
+ for (const [key, value] of Object.entries(shape)) {
1960
+ const innerTypeName = getZodTypeName(value);
1961
+ if (innerTypeName === "ZodOptional" || innerTypeName === "optional" || innerTypeName === "ZodNullable" || innerTypeName === "nullable") {
1962
+ continue;
1963
+ }
1964
+ if (innerTypeName === "ZodString" || innerTypeName === "string") {
1965
+ return key;
1966
+ }
1967
+ }
1968
+ return null;
1969
+ }
1454
1970
  function getZodTypeName(schema) {
1455
1971
  if (!schema || !schema._def) return "";
1456
1972
  const def = schema._def;
@@ -1632,285 +2148,6 @@ Remember: Output /root first, then interleave /elements and /state patches so th
1632
2148
  );
1633
2149
  return parts.join("\n");
1634
2150
  }
1635
-
1636
- // src/catalog.ts
1637
- var import_zod6 = require("zod");
1638
- function createCatalog(config) {
1639
- const {
1640
- name = "unnamed",
1641
- components,
1642
- actions = {},
1643
- functions = {},
1644
- validation = "strict"
1645
- } = config;
1646
- const componentNames = Object.keys(components);
1647
- const actionNames = Object.keys(actions);
1648
- const functionNames = Object.keys(functions);
1649
- const componentSchemas = componentNames.map((componentName) => {
1650
- const def = components[componentName];
1651
- return import_zod6.z.object({
1652
- type: import_zod6.z.literal(componentName),
1653
- props: def.props,
1654
- children: import_zod6.z.array(import_zod6.z.string()).optional(),
1655
- visible: VisibilityConditionSchema.optional()
1656
- });
1657
- });
1658
- let elementSchema;
1659
- if (componentSchemas.length === 0) {
1660
- elementSchema = import_zod6.z.object({
1661
- type: import_zod6.z.string(),
1662
- props: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.unknown()),
1663
- children: import_zod6.z.array(import_zod6.z.string()).optional(),
1664
- visible: VisibilityConditionSchema.optional()
1665
- });
1666
- } else if (componentSchemas.length === 1) {
1667
- elementSchema = componentSchemas[0];
1668
- } else {
1669
- elementSchema = import_zod6.z.discriminatedUnion("type", [
1670
- componentSchemas[0],
1671
- componentSchemas[1],
1672
- ...componentSchemas.slice(2)
1673
- ]);
1674
- }
1675
- const specSchema = import_zod6.z.object({
1676
- root: import_zod6.z.string(),
1677
- elements: import_zod6.z.record(import_zod6.z.string(), elementSchema)
1678
- });
1679
- return {
1680
- name,
1681
- componentNames,
1682
- actionNames,
1683
- functionNames,
1684
- validation,
1685
- components,
1686
- actions,
1687
- functions,
1688
- elementSchema,
1689
- specSchema,
1690
- hasComponent(type) {
1691
- return type in components;
1692
- },
1693
- hasAction(name2) {
1694
- return name2 in actions;
1695
- },
1696
- hasFunction(name2) {
1697
- return name2 in functions;
1698
- },
1699
- validateElement(element) {
1700
- const result = elementSchema.safeParse(element);
1701
- if (result.success) {
1702
- return { success: true, data: result.data };
1703
- }
1704
- return { success: false, error: result.error };
1705
- },
1706
- validateSpec(spec) {
1707
- const result = specSchema.safeParse(spec);
1708
- if (result.success) {
1709
- return { success: true, data: result.data };
1710
- }
1711
- return { success: false, error: result.error };
1712
- }
1713
- };
1714
- }
1715
- function generateCatalogPrompt(catalog) {
1716
- const lines = [
1717
- `# ${catalog.name} Component Catalog`,
1718
- "",
1719
- "## Available Components",
1720
- ""
1721
- ];
1722
- for (const name of catalog.componentNames) {
1723
- const def = catalog.components[name];
1724
- lines.push(`### ${String(name)}`);
1725
- if (def.description) {
1726
- lines.push(def.description);
1727
- }
1728
- lines.push("");
1729
- }
1730
- if (catalog.actionNames.length > 0) {
1731
- lines.push("## Available Actions");
1732
- lines.push("");
1733
- for (const name of catalog.actionNames) {
1734
- const def = catalog.actions[name];
1735
- lines.push(
1736
- `- \`${String(name)}\`${def.description ? `: ${def.description}` : ""}`
1737
- );
1738
- }
1739
- lines.push("");
1740
- }
1741
- lines.push("## Visibility Conditions");
1742
- lines.push("");
1743
- lines.push("Components can have a `visible` property:");
1744
- lines.push("- `true` / `false` - Always visible/hidden");
1745
- lines.push('- `{ "path": "/state/path" }` - Visible when path is truthy');
1746
- lines.push('- `{ "auth": "signedIn" }` - Visible when user is signed in');
1747
- lines.push('- `{ "and": [...] }` - All conditions must be true');
1748
- lines.push('- `{ "or": [...] }` - Any condition must be true');
1749
- lines.push('- `{ "not": {...} }` - Negates a condition');
1750
- lines.push('- `{ "eq": [a, b] }` - Equality check');
1751
- lines.push("");
1752
- lines.push("## Validation Functions");
1753
- lines.push("");
1754
- lines.push(
1755
- "Built-in: `required`, `email`, `minLength`, `maxLength`, `pattern`, `min`, `max`, `url`"
1756
- );
1757
- if (catalog.functionNames.length > 0) {
1758
- lines.push(`Custom: ${catalog.functionNames.map(String).join(", ")}`);
1759
- }
1760
- lines.push("");
1761
- return lines.join("\n");
1762
- }
1763
- function formatZodType2(schema, isOptional = false) {
1764
- const def = schema._def;
1765
- const typeName = def.typeName ?? "";
1766
- let result;
1767
- switch (typeName) {
1768
- case "ZodString":
1769
- result = "string";
1770
- break;
1771
- case "ZodNumber":
1772
- result = "number";
1773
- break;
1774
- case "ZodBoolean":
1775
- result = "boolean";
1776
- break;
1777
- case "ZodLiteral":
1778
- result = JSON.stringify(def.value);
1779
- break;
1780
- case "ZodEnum":
1781
- result = def.values.map((v) => `"${v}"`).join("|");
1782
- break;
1783
- case "ZodNativeEnum":
1784
- result = Object.values(def.values).map((v) => `"${v}"`).join("|");
1785
- break;
1786
- case "ZodArray":
1787
- result = def.type ? `Array<${formatZodType2(def.type)}>` : "Array<unknown>";
1788
- break;
1789
- case "ZodObject": {
1790
- if (!def.shape) {
1791
- result = "object";
1792
- break;
1793
- }
1794
- const shape = def.shape();
1795
- const props = Object.entries(shape).map(([key, value]) => {
1796
- const innerDef = value._def;
1797
- const innerOptional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
1798
- return `${key}${innerOptional ? "?" : ""}: ${formatZodType2(value)}`;
1799
- }).join(", ");
1800
- result = `{ ${props} }`;
1801
- break;
1802
- }
1803
- case "ZodOptional":
1804
- return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
1805
- case "ZodNullable":
1806
- return def.innerType ? formatZodType2(def.innerType, true) : "unknown?";
1807
- case "ZodDefault":
1808
- return def.innerType ? formatZodType2(def.innerType, isOptional) : "unknown";
1809
- case "ZodUnion":
1810
- result = def.options ? def.options.map((opt) => formatZodType2(opt)).join("|") : "unknown";
1811
- break;
1812
- case "ZodNull":
1813
- result = "null";
1814
- break;
1815
- case "ZodUndefined":
1816
- result = "undefined";
1817
- break;
1818
- case "ZodAny":
1819
- result = "any";
1820
- break;
1821
- case "ZodUnknown":
1822
- result = "unknown";
1823
- break;
1824
- default:
1825
- result = "unknown";
1826
- }
1827
- return isOptional ? `${result}?` : result;
1828
- }
1829
- function extractPropsFromSchema(schema) {
1830
- const def = schema._def;
1831
- const typeName = def.typeName ?? "";
1832
- if (typeName !== "ZodObject" || !def.shape) {
1833
- return [];
1834
- }
1835
- const shape = def.shape();
1836
- return Object.entries(shape).map(([name, value]) => {
1837
- const innerDef = value._def;
1838
- const optional = innerDef.typeName === "ZodOptional" || innerDef.typeName === "ZodNullable";
1839
- return {
1840
- name,
1841
- type: formatZodType2(value),
1842
- optional
1843
- };
1844
- });
1845
- }
1846
- function formatPropsCompact(props) {
1847
- if (props.length === 0) return "{}";
1848
- const entries = props.map(
1849
- (p) => `${p.name}${p.optional ? "?" : ""}: ${p.type}`
1850
- );
1851
- return `{ ${entries.join(", ")} }`;
1852
- }
1853
- function generateSystemPrompt(catalog, options = {}) {
1854
- const {
1855
- system = "You are a UI generator that outputs JSONL (JSON Lines) patches.",
1856
- customRules = []
1857
- } = options;
1858
- const lines = [];
1859
- lines.push(system);
1860
- lines.push("");
1861
- const componentCount = catalog.componentNames.length;
1862
- lines.push(`AVAILABLE COMPONENTS (${componentCount}):`);
1863
- lines.push("");
1864
- for (const name of catalog.componentNames) {
1865
- const def = catalog.components[name];
1866
- const props = extractPropsFromSchema(def.props);
1867
- const propsStr = formatPropsCompact(props);
1868
- const hasChildrenStr = def.hasChildren ? " Has children." : "";
1869
- const descStr = def.description ? ` ${def.description}` : "";
1870
- lines.push(`- ${String(name)}: ${propsStr}${descStr}${hasChildrenStr}`);
1871
- }
1872
- lines.push("");
1873
- if (catalog.actionNames.length > 0) {
1874
- lines.push("AVAILABLE ACTIONS:");
1875
- lines.push("");
1876
- for (const name of catalog.actionNames) {
1877
- const def = catalog.actions[name];
1878
- lines.push(
1879
- `- ${String(name)}${def.description ? `: ${def.description}` : ""}`
1880
- );
1881
- }
1882
- lines.push("");
1883
- }
1884
- lines.push("OUTPUT FORMAT (JSONL, RFC 6902 JSON Patch):");
1885
- lines.push('{"op":"add","path":"/root","value":"element-key"}');
1886
- lines.push(
1887
- '{"op":"add","path":"/elements/key","value":{"type":"...","props":{...},"children":[...]}}'
1888
- );
1889
- lines.push('{"op":"remove","path":"/elements/key"}');
1890
- lines.push("");
1891
- lines.push("RULES:");
1892
- const baseRules = [
1893
- 'First line sets /root to root element key: {"op":"add","path":"/root","value":"<key>"}',
1894
- 'Add elements with /elements/{key}: {"op":"add","path":"/elements/<key>","value":{...}}',
1895
- "Remove elements with op:remove - also update the parent's children array to exclude the removed key",
1896
- "Children array contains string keys, not objects",
1897
- "Parent first, then children",
1898
- "Each element needs: type, props",
1899
- "ONLY use props listed above - never invent new props"
1900
- ];
1901
- const allRules = [...baseRules, ...customRules];
1902
- allRules.forEach((rule, i) => {
1903
- lines.push(`${i + 1}. ${rule}`);
1904
- });
1905
- lines.push("");
1906
- if (catalog.functionNames.length > 0) {
1907
- lines.push("CUSTOM VALIDATION FUNCTIONS:");
1908
- lines.push(catalog.functionNames.map(String).join(", "));
1909
- lines.push("");
1910
- }
1911
- lines.push("Generate JSONL:");
1912
- return lines.join("\n");
1913
- }
1914
2151
  // Annotate the CommonJS export names for ESM import in node:
1915
2152
  0 && (module.exports = {
1916
2153
  ActionBindingSchema,
@@ -1922,35 +2159,39 @@ function generateSystemPrompt(catalog, options = {}) {
1922
2159
  DynamicNumberSchema,
1923
2160
  DynamicStringSchema,
1924
2161
  DynamicValueSchema,
1925
- LogicExpressionSchema,
2162
+ SPEC_DATA_PART,
2163
+ SPEC_DATA_PART_TYPE,
1926
2164
  ValidationCheckSchema,
1927
2165
  ValidationConfigSchema,
1928
2166
  VisibilityConditionSchema,
1929
2167
  action,
1930
2168
  actionBinding,
1931
2169
  addByPath,
2170
+ applySpecPatch,
1932
2171
  applySpecStreamPatch,
1933
2172
  autoFixSpec,
1934
2173
  buildUserPrompt,
1935
2174
  builtInValidationFunctions,
1936
2175
  check,
1937
2176
  compileSpecStream,
1938
- createCatalog,
2177
+ createJsonRenderTransform,
2178
+ createMixedStreamParser,
1939
2179
  createSpecStreamCompiler,
1940
2180
  defineCatalog,
1941
2181
  defineSchema,
1942
- evaluateLogicExpression,
1943
2182
  evaluateVisibility,
1944
2183
  executeAction,
1945
2184
  findFormValue,
1946
2185
  formatSpecIssues,
1947
- generateCatalogPrompt,
1948
- generateSystemPrompt,
1949
2186
  getByPath,
1950
2187
  interpolateString,
2188
+ nestedToFlat,
1951
2189
  parseSpecStreamLine,
2190
+ pipeJsonRender,
1952
2191
  removeByPath,
1953
2192
  resolveAction,
2193
+ resolveActionParam,
2194
+ resolveBindings,
1954
2195
  resolveDynamicValue,
1955
2196
  resolveElementProps,
1956
2197
  resolvePropValue,