@valbuild/core 0.129.0 → 0.134.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/declarations/src/index.d.ts +11 -1
  3. package/dist/declarations/src/initSchema.d.ts +20 -0
  4. package/dist/declarations/src/module.d.ts +21 -4
  5. package/dist/declarations/src/preview.d.ts +65 -8
  6. package/dist/declarations/src/render.d.ts +26 -0
  7. package/dist/declarations/src/schema/array.d.ts +8 -5
  8. package/dist/declarations/src/schema/boolean.d.ts +8 -6
  9. package/dist/declarations/src/schema/code.d.ts +8 -6
  10. package/dist/declarations/src/schema/color.d.ts +8 -6
  11. package/dist/declarations/src/schema/date.d.ts +8 -6
  12. package/dist/declarations/src/schema/datetime.d.ts +8 -6
  13. package/dist/declarations/src/schema/discriminatedUnion.d.ts +8 -5
  14. package/dist/declarations/src/schema/enum.d.ts +8 -6
  15. package/dist/declarations/src/schema/file.d.ts +8 -6
  16. package/dist/declarations/src/schema/image.d.ts +8 -6
  17. package/dist/declarations/src/schema/index.d.ts +31 -2
  18. package/dist/declarations/src/schema/keyOf.d.ts +8 -6
  19. package/dist/declarations/src/schema/literal.d.ts +8 -6
  20. package/dist/declarations/src/schema/locale.d.ts +1 -2
  21. package/dist/declarations/src/schema/number.d.ts +8 -6
  22. package/dist/declarations/src/schema/object.d.ts +18 -5
  23. package/dist/declarations/src/schema/record.d.ts +8 -5
  24. package/dist/declarations/src/schema/richtext.d.ts +8 -6
  25. package/dist/declarations/src/schema/route.d.ts +8 -6
  26. package/dist/declarations/src/schema/settings.d.ts +1 -1
  27. package/dist/declarations/src/schema/string.d.ts +8 -11
  28. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  29. package/dist/declarations/src/schema/view.d.ts +197 -0
  30. package/dist/declarations/src/selector/index.d.ts +4 -2
  31. package/dist/declarations/src/selector/view.d.ts +22 -0
  32. package/dist/declarations/src/source/index.d.ts +2 -1
  33. package/dist/declarations/src/source/settings.d.ts +27 -0
  34. package/dist/declarations/src/source/view.d.ts +41 -0
  35. package/dist/declarations/src/source/viewHandle.d.ts +69 -0
  36. package/dist/{index-f5842720.cjs.prod.js → index-48a8f8a5.cjs.prod.js} +1281 -353
  37. package/dist/{index-21fd16b1.cjs.dev.js → index-85a2ffba.cjs.dev.js} +1281 -353
  38. package/dist/{index-06bb2832.esm.js → index-e3458565.esm.js} +1280 -354
  39. package/dist/valbuild-core.cjs.dev.js +3 -1
  40. package/dist/valbuild-core.cjs.prod.js +3 -1
  41. package/dist/valbuild-core.esm.js +1 -1
  42. package/package.json +1 -1
  43. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  44. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  45. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -411,8 +411,93 @@ var Schema = /*#__PURE__*/function () {
411
411
  * For example, if you have an object schema, you must assert each key / value pair manually.
412
412
  */
413
413
  }, {
414
- key: "executePreviewItem",
414
+ key: "executePreview",
415
415
  value:
416
+ /**
417
+ * This value's preview, and every preview below it.
418
+ *
419
+ * The default is the whole of it for a LEAF: a leaf has nothing below it, so
420
+ * all it can contribute is {@link PreviewNode.self}. Containers override this
421
+ * and recurse; object and discriminated union add nothing of their own beyond
422
+ * the self they inherit here, and array and record add `rows`.
423
+ *
424
+ * @param scope Which paths the caller needs a preview for. Absent means the
425
+ * whole module, which is what every caller passed before scoping existed.
426
+ * See {@link PreviewScope}: a container prunes recursion where nothing is
427
+ * wanted, and previews a WINDOW when its own path is not wanted but some of
428
+ * its items are — which is the single-visible-row case.
429
+ * @param selfIsReifiedByParent Set by an array or record on its DIRECT items,
430
+ * and by nothing else. Such an item's preview is the same closure the
431
+ * container is already running into {@link PreviewNode.rows}, so emitting a
432
+ * `self` for it as well would call the user's closure twice per row — which
433
+ * it did, and which the scoped-preview tests caught by counting. The flag
434
+ * does not travel further down: a grandchild is nobody's row.
435
+ */
436
+ function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
437
+ if (selfIsReifiedByParent) {
438
+ return {};
439
+ }
440
+ return this.executeSelfPreview(sourcePath, src, scope);
441
+ }
442
+
443
+ /**
444
+ * THIS value as a preview, at its own path — `{ self }` and nothing else.
445
+ *
446
+ * Every schema can produce one, which is the point: before this existed a
447
+ * value's preview was reified only by its CONTAINER, so a value with no
448
+ * container had none. A module root is exactly that, and `.preview(...)` on
449
+ * a module's own schema was therefore dead code — the studio showed the file
450
+ * name and nothing a developer wrote could change it. So is any field of an
451
+ * object, which reifies no rows.
452
+ *
453
+ * Gated on {@link PreviewScope.wants} rather than `wantsUnder`: a caller
454
+ * asking about a CONTAINER wants its rows, and computing a self for every
455
+ * descendant on the way past would run each item's closure twice.
456
+ */
457
+ }, {
458
+ key: "executeSelfPreview",
459
+ value: function executeSelfPreview(sourcePath, src, scope) {
460
+ if (src === null || src === undefined) {
461
+ return {};
462
+ }
463
+ if (!this.declaresItemPreview()) {
464
+ return {};
465
+ }
466
+ if (scope !== undefined && !scope.wants(sourcePath)) {
467
+ return {};
468
+ }
469
+ try {
470
+ // NB NB: the closure is user code.
471
+ var item = this.executePreviewItem(src);
472
+ if (item === null) {
473
+ return {};
474
+ }
475
+ var title = item.title,
476
+ subtitle = item.subtitle,
477
+ image = item.image;
478
+ // Assigned into an annotated local rather than returned as a literal: a
479
+ // computed key widens `status` to `string`.
480
+ var res = {};
481
+ res[sourcePath] = {
482
+ status: "success",
483
+ data: {
484
+ self: {
485
+ title: title,
486
+ subtitle: subtitle,
487
+ image: image
488
+ }
489
+ }
490
+ };
491
+ return res;
492
+ } catch (e) {
493
+ var _res = {};
494
+ _res[sourcePath] = {
495
+ status: "error",
496
+ message: e instanceof Error ? e.message : "Unknown error"
497
+ };
498
+ return _res;
499
+ }
500
+ }
416
501
  /**
417
502
  * This value AS A PREVIEW — what a container's row, a reference dropdown or
418
503
  * a search hit shows for it. Runs the schema's own `preview` closure;
@@ -423,7 +508,9 @@ var Schema = /*#__PURE__*/function () {
423
508
  * `executePreview` reifies an {@link ArrayPreview} / {@link RecordPreview}
424
509
  * from item-level declarations.
425
510
  */
426
- function executePreviewItem(src) {
511
+ }, {
512
+ key: "executePreviewItem",
513
+ value: function executePreviewItem(src) {
427
514
  return null;
428
515
  }
429
516
  /**
@@ -504,6 +591,241 @@ var Schema = /*#__PURE__*/function () {
504
591
 
505
592
  // TODO: SourceError<"Could not determine type of Schema">
506
593
 
594
+ function ownKeys(e, r) {
595
+ var t = Object.keys(e);
596
+ if (Object.getOwnPropertySymbols) {
597
+ var o = Object.getOwnPropertySymbols(e);
598
+ r && (o = o.filter(function (r) {
599
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
600
+ })), t.push.apply(t, o);
601
+ }
602
+ return t;
603
+ }
604
+ function _objectSpread2(e) {
605
+ for (var r = 1; r < arguments.length; r++) {
606
+ var t = null != arguments[r] ? arguments[r] : {};
607
+ r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
608
+ _defineProperty(e, r, t[r]);
609
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
610
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
611
+ });
612
+ }
613
+ return e;
614
+ }
615
+
616
+ /**
617
+ * THE RULE, and the only sentence that needs to be remembered:
618
+ *
619
+ * **`.describe()` is INPUT HELP and is shown wherever that field — or a
620
+ * record's key — is being ENTERED; `.preview()` is a NAME and is shown
621
+ * wherever the value is REFERRED TO rather than edited; `.render()` is
622
+ * LAYOUT and applies only while the field is open in front of you.**
623
+ *
624
+ * The test that settles every case: **can the reader change something here?**
625
+ * If yes it is a place for a description — the input beside a label, the key
626
+ * box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
627
+ * reference dropdown. If no, it is a place for a preview — a list row, a
628
+ * reference once chosen, a search hit, a sitemap row, the heading of what you
629
+ * navigated to.
630
+ *
631
+ * That is why a description is plain data on the serialized schema and a
632
+ * preview is a closure: a description is true before any value exists and says
633
+ * the same thing to everyone filling the field in, and a preview cannot exist
634
+ * without the one value it names. So a description must never be used as a
635
+ * subtitle — it would repeat one sentence under every row of a list — and a
636
+ * preview must never be used as help text, because there is nothing to preview
637
+ * until after the value has been entered.
638
+ *
639
+ * None of the three substitutes for another: a field with a perfect
640
+ * description still previews as `#3` until someone writes the preview.
641
+ */
642
+ /**
643
+ * A PREVIEW is how a VALUE is shown wherever a preview of it is needed — a row
644
+ * in a sortable list, a key in a reference dropdown, a search hit, a
645
+ * reference — which is everywhere the value is NAVIGABLE to rather than open.
646
+ * It is never how the field itself is edited: that is a RENDER (`render.ts`),
647
+ * which applies only when you are looking at the field. The two do not
648
+ * intersect — a schema can carry both, and each is read in its own places.
649
+ *
650
+ * A preview is declared on the schema of the VALUE being previewed:
651
+ *
652
+ * ```ts
653
+ * const author = s.object({ name: s.string() })
654
+ * .preview(({ val }) => ({ title: val.name }));
655
+ * const authors = s.array(author);
656
+ * ```
657
+ *
658
+ * The container reifies its rows by running each ITEM's preview closure — see
659
+ * {@link ArrayPreview} / {@link RecordPreview}. (Previews used to be declared
660
+ * on the container instead; a `.preview` on an array/record now previews the
661
+ * array/record itself as a value, for when IT is the item of something.)
662
+ */
663
+ /**
664
+ * What a preview shows for one value: the user's `preview` callback returns
665
+ * this, and the Studio draws a row from it.
666
+ *
667
+ * NB: {@link ArrayPreview} / {@link RecordPreview} name the DATA a container
668
+ * previews with. The Studio also has React components called `ArrayPreview` /
669
+ * `RecordPreview` (the fallback rendering of an array / record field, part of
670
+ * the `<Type>Preview` convention in `components/Preview.tsx`). They do not
671
+ * collide today because no file needs both - if one ever does, alias this type
672
+ * at the import rather than renaming that convention.
673
+ */
674
+
675
+ /**
676
+ * What `.preview(...)` takes, on every schema: the value's own source in, a
677
+ * {@link PreviewItem} out. `NonNullable` because a container skips null items
678
+ * rather than previewing them.
679
+ *
680
+ * Declared through method syntax deliberately (the same bivariance shape
681
+ * React's event handler types use): `nullable()` copies a schema's closure
682
+ * into the `Src | null` variant of the same class, and with a plain function
683
+ * type the checker cannot see that `NonNullable<Src | null>` IS
684
+ * `NonNullable<Src>` while `Src` is still a type parameter — every schema's
685
+ * `nullable()` would need a cast instead.
686
+ */
687
+
688
+ /**
689
+ * How a CONTAINER's children preview. `parent` is the discriminant, and every
690
+ * consumer narrows on it rather than on the union as a whole. There is no
691
+ * `layout` here on purpose - how a preview is laid out is the editor's
692
+ * business, not the schema's.
693
+ */
694
+
695
+ /**
696
+ * Everything known about ONE path, which is two independent things.
697
+ *
698
+ * They are separate fields rather than a union because a path can have both,
699
+ * and that is not a corner case: `s.array(section).preview(...)` where
700
+ * `section` also previews is a section list that shows its rows AND names
701
+ * itself when it is nested in something. One of them would have had to win.
702
+ *
703
+ * - {@link self} - what THIS value is called, from its own schema's
704
+ * `.preview(...)`. This is the only answer for a value with no container to
705
+ * ask: a module root (whose `.preview` was never run before this existed) and
706
+ * any field of an object.
707
+ * - {@link rows} - what its CHILDREN are called, from the ITEM schema's
708
+ * `.preview(...)`, reified by the container. Kept alongside `self` rather
709
+ * than derived from the children's `self` entries because a list needs every
710
+ * row in one answer, and because an EMPTY list still has to preview as an
711
+ * empty list - which is a fact about the schema, not about any value.
712
+ */
713
+
714
+ /**
715
+ * Fold `incoming` into `target`, path by path. MUTATES `target`.
716
+ *
717
+ * Every `executePreview` used to copy child results across with a plain
718
+ * assignment, which was fine while one path held one thing. It no longer does:
719
+ * a container writes `rows` at its own path and its `self` arrives from the
720
+ * same walk, so an overwrite silently drops whichever landed first. Merging is
721
+ * per FIELD, and an error at a path beats any data there - a preview that threw
722
+ * must not be reported as a preview that is merely absent.
723
+ */
724
+ function mergePreviewInto(target, incoming) {
725
+ for (var keyS in incoming) {
726
+ var key = keyS;
727
+ var next = incoming[key];
728
+ var current = target[key];
729
+ if (current === undefined || next.status === "error") {
730
+ target[key] = next;
731
+ continue;
732
+ }
733
+ if (current.status === "error" || next.status === "loading") {
734
+ continue;
735
+ }
736
+ if (current.status === "loading") {
737
+ target[key] = next;
738
+ continue;
739
+ }
740
+ target[key] = {
741
+ status: "success",
742
+ data: _objectSpread2(_objectSpread2({}, current.data), next.data)
743
+ };
744
+ }
745
+ }
746
+
747
+ /**
748
+ * Which paths a preview is being computed FOR.
749
+ *
750
+ * `executePreview` takes a whole module, so one request has always walked every
751
+ * node in it and run every `preview` closure - for `handboka`, with `preview` at
752
+ * two nested array levels, that is every chapter and every section to serve one
753
+ * visible row. A scope is what makes the walk proportional to what is being
754
+ * looked at instead of to the project.
755
+ *
756
+ * Two questions, because a container and its items need different answers:
757
+ *
758
+ * - {@link PreviewScope.wants} - is a preview AT this exact path wanted? A
759
+ * container answers `true` here when the whole of it is being shown, and its
760
+ * preview is then computed in full.
761
+ * - {@link PreviewScope.wantsUnder} - could anything at or below this path be
762
+ * wanted? Recursion is pruned where this is `false`, and a container whose own
763
+ * path is not wanted but which has wanted descendants previews a WINDOW: only
764
+ * the items that were asked for. That is the case a single visible row is, and
765
+ * it is why {@link ArrayPreview} carries indices.
766
+ *
767
+ * Absent scope means the whole module, which is what every existing caller
768
+ * passes and what every existing caller got.
769
+ */
770
+
771
+ /**
772
+ * A scope covering exactly `paths` and their subtrees.
773
+ *
774
+ * Compares SEGMENTS rather than string prefixes. A source path's module path is
775
+ * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
776
+ * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
777
+ * dot or a quote - `"title"` against `"titles"` being the cheapest example.
778
+ * Segments are parsed once here, not per comparison.
779
+ */
780
+ function previewScope(paths) {
781
+ var wanted = paths.map(function (path) {
782
+ var _splitModuleFilePathA = splitModuleFilePathAndModulePath(path),
783
+ _splitModuleFilePathA2 = _slicedToArray(_splitModuleFilePathA, 2),
784
+ moduleFilePath = _splitModuleFilePathA2[0],
785
+ modulePath = _splitModuleFilePathA2[1];
786
+ return {
787
+ moduleFilePath: moduleFilePath,
788
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
789
+ };
790
+ });
791
+ var parse = function parse(path) {
792
+ var _splitModuleFilePathA3 = splitModuleFilePathAndModulePath(path),
793
+ _splitModuleFilePathA4 = _slicedToArray(_splitModuleFilePathA3, 2),
794
+ moduleFilePath = _splitModuleFilePathA4[0],
795
+ modulePath = _splitModuleFilePathA4[1];
796
+ return {
797
+ moduleFilePath: moduleFilePath,
798
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
799
+ };
800
+ };
801
+ var isPrefix = function isPrefix(prefix, of) {
802
+ if (prefix.length > of.length) return false;
803
+ for (var i = 0; i < prefix.length; i++) {
804
+ if (prefix[i] !== of[i]) return false;
805
+ }
806
+ return true;
807
+ };
808
+ return {
809
+ wants: function wants(path) {
810
+ var at = parse(path);
811
+ return wanted.some(function (entry) {
812
+ return entry.moduleFilePath === at.moduleFilePath && entry.segments.length === at.segments.length && isPrefix(entry.segments, at.segments);
813
+ });
814
+ },
815
+ wantsUnder: function wantsUnder(path) {
816
+ var at = parse(path);
817
+ return wanted.some(function (entry) {
818
+ return entry.moduleFilePath === at.moduleFilePath && (
819
+ // Either direction: a wanted path below `path` means recurse into it,
820
+ // and a wanted path ABOVE it means the whole subtree was asked for.
821
+ isPrefix(at.segments, entry.segments) || isPrefix(entry.segments, at.segments));
822
+ });
823
+ }
824
+ };
825
+ }
826
+
827
+ // TODO: improve this so that we do not get RawString and string, only string. Are there other things?
828
+
507
829
  // Identity symbols are registered in the global Symbol registry so that
508
830
  // multiple bundled copies of @valbuild/core (e.g. the editor SPA bundle vs.
509
831
  // the host Next.js bundle) resolve to the same Symbol instance. Without
@@ -616,28 +938,6 @@ function unsafeCreateSourcePath(path, itemKey) {
616
938
  return "".concat(path).concat(Internal.ModuleFilePathSep).concat(JSON.stringify(itemKey));
617
939
  }
618
940
 
619
- function ownKeys(e, r) {
620
- var t = Object.keys(e);
621
- if (Object.getOwnPropertySymbols) {
622
- var o = Object.getOwnPropertySymbols(e);
623
- r && (o = o.filter(function (r) {
624
- return Object.getOwnPropertyDescriptor(e, r).enumerable;
625
- })), t.push.apply(t, o);
626
- }
627
- return t;
628
- }
629
- function _objectSpread2(e) {
630
- for (var r = 1; r < arguments.length; r++) {
631
- var t = null != arguments[r] ? arguments[r] : {};
632
- r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
633
- _defineProperty(e, r, t[r]);
634
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
635
- Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
636
- });
637
- }
638
- return e;
639
- }
640
-
641
941
  var StringSchema = /*#__PURE__*/function (_Schema) {
642
942
  function StringSchema(options) {
643
943
  var _this;
@@ -668,10 +968,13 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
668
968
  /**
669
969
  * Describe this field.
670
970
  *
671
- * The description is shown next to the field's label in the Val editor, so
672
- * it is where you say what an editor needs to know but the field name cannot
673
- * carry. It also travels in the serialized schema, which is what the AI
674
- * assistant and the MCP tools read.
971
+ * The description is INPUT HELP: it is shown where this field's value is
972
+ * entered — beside its input in the Val editor, and for a record's key
973
+ * schema in every form that asks for a key — so it is where you say what an
974
+ * editor needs to know to fill it in RIGHT, which the field name cannot
975
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
976
+ * read somewhere else. The description also travels in the serialized
977
+ * schema, which is what the AI assistant and the MCP tools read.
675
978
  *
676
979
  * Pass `null` to clear a description set earlier.
677
980
  *
@@ -1008,11 +1311,6 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
1008
1311
  * is a `render`, which travels in the serialized schema instead of through
1009
1312
  * this pipeline.
1010
1313
  */
1011
- }, {
1012
- key: "executePreview",
1013
- value: function executePreview() {
1014
- return {};
1015
- }
1016
1314
  }]);
1017
1315
  }(Schema);
1018
1316
  var string = function string(options) {
@@ -1045,10 +1343,13 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1045
1343
  /**
1046
1344
  * Describe this field.
1047
1345
  *
1048
- * The description is shown next to the field's label in the Val editor, so
1049
- * it is where you say what an editor needs to know but the field name cannot
1050
- * carry. It also travels in the serialized schema, which is what the AI
1051
- * assistant and the MCP tools read.
1346
+ * The description is INPUT HELP: it is shown where this field's value is
1347
+ * entered — beside its input in the Val editor, and for a record's key
1348
+ * schema in every form that asks for a key — so it is where you say what an
1349
+ * editor needs to know to fill it in RIGHT, which the field name cannot
1350
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
1351
+ * read somewhere else. The description also travels in the serialized
1352
+ * schema, which is what the AI assistant and the MCP tools read.
1052
1353
  *
1053
1354
  * Pass `null` to clear a description set earlier.
1054
1355
  *
@@ -1360,7 +1661,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1360
1661
  }
1361
1662
  }, {
1362
1663
  key: "executePreview",
1363
- value: function executePreview(sourcePath, src, scope) {
1664
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
1364
1665
  var res = {};
1365
1666
  if (src === null) {
1366
1667
  return res;
@@ -1377,11 +1678,14 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1377
1678
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
1378
1679
  continue;
1379
1680
  }
1380
- var itemResult = this.items[_key3]["executePreview"](subPath, itemSrc, scope);
1381
- for (var keyS in itemResult) {
1382
- var _key4 = keyS;
1383
- res[_key4] = itemResult[_key4];
1384
- }
1681
+ mergePreviewInto(res, this.items[_key3]["executePreview"](subPath, itemSrc, scope));
1682
+ }
1683
+ // An object reifies no rows of its own, so the only thing it adds is what
1684
+ // IT is called — which nothing else can supply for a field of an object.
1685
+ // Its own items are NOT rows, so the flag stops here rather than travelling
1686
+ // down with the recursion above.
1687
+ if (!selfIsReifiedByParent) {
1688
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
1385
1689
  }
1386
1690
  return res;
1387
1691
  }
@@ -1422,10 +1726,13 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1422
1726
  /**
1423
1727
  * Describe this field.
1424
1728
  *
1425
- * The description is shown next to the field's label in the Val editor, so
1426
- * it is where you say what an editor needs to know but the field name cannot
1427
- * carry. It also travels in the serialized schema, which is what the AI
1428
- * assistant and the MCP tools read.
1729
+ * The description is INPUT HELP: it is shown where this field's value is
1730
+ * entered — beside its input in the Val editor, and for a record's key
1731
+ * schema in every form that asks for a key — so it is where you say what an
1732
+ * editor needs to know to fill it in RIGHT, which the field name cannot
1733
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
1734
+ * read somewhere else. The description also travels in the serialized
1735
+ * schema, which is what the AI assistant and the MCP tools read.
1429
1736
  *
1430
1737
  * Pass `null` to clear a description set earlier.
1431
1738
  *
@@ -1584,7 +1891,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1584
1891
  }
1585
1892
  }, {
1586
1893
  key: "executePreview",
1587
- value: function executePreview(sourcePath, src, scope) {
1894
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
1588
1895
  var res = {};
1589
1896
  if (src === null) {
1590
1897
  return res;
@@ -1599,11 +1906,10 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1599
1906
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
1600
1907
  continue;
1601
1908
  }
1602
- var itemResult = this.item["executePreview"](subPath, itemSrc, scope);
1603
- for (var keyS in itemResult) {
1604
- var _key = keyS;
1605
- res[_key] = itemResult[_key];
1606
- }
1909
+ mergePreviewInto(res,
1910
+ // `true`: this item is a ROW, and its preview is the closure reified
1911
+ // into `rows` below. See `executePreview` on `Schema`.
1912
+ this.item["executePreview"](subPath, itemSrc, scope, true));
1607
1913
  }
1608
1914
  // The rows preview comes from the ITEM schema's own `preview` — the
1609
1915
  // container just runs it per row. Asked as a fact rather than by running
@@ -1650,13 +1956,29 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1650
1956
  };
1651
1957
  }
1652
1958
  }
1653
- res[sourcePath] = {
1959
+ var rows = {};
1960
+ rows[sourcePath] = {
1654
1961
  status: "success",
1655
1962
  data: {
1656
- parent: "array",
1657
- items: items
1963
+ rows: {
1964
+ parent: "array",
1965
+ items: items
1966
+ }
1658
1967
  }
1659
1968
  };
1969
+ mergePreviewInto(res, rows);
1970
+ }
1971
+ // ...and what the LIST ITSELF is called, which is a different closure on a
1972
+ // different schema: `s.array(section.preview(...)).preview(...)` previews
1973
+ // its rows AND names itself for when it is nested in something. Merged
1974
+ // rather than assigned, or one of the two would win. See `PreviewNode`.
1975
+ //
1976
+ // Unless this list is itself a ROW of an outer array or record, which has
1977
+ // already run this very closure to reify it — a container both PASSES the
1978
+ // flag to its items and RECEIVES it as one, and honouring only the first
1979
+ // half ran `s.array(s.array(x).preview(...))`'s closure twice per row.
1980
+ if (!selfIsReifiedByParent) {
1981
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
1660
1982
  }
1661
1983
  return res;
1662
1984
  }
@@ -1759,10 +2081,13 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1759
2081
  /**
1760
2082
  * Describe this field.
1761
2083
  *
1762
- * The description is shown next to the field's label in the Val editor, so
1763
- * it is where you say what an editor needs to know but the field name cannot
1764
- * carry. It also travels in the serialized schema, which is what the AI
1765
- * assistant and the MCP tools read.
2084
+ * The description is INPUT HELP: it is shown where this field's value is
2085
+ * entered — beside its input in the Val editor, and for a record's key
2086
+ * schema in every form that asks for a key — so it is where you say what an
2087
+ * editor needs to know to fill it in RIGHT, which the field name cannot
2088
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
2089
+ * read somewhere else. The description also travels in the serialized
2090
+ * schema, which is what the AI assistant and the MCP tools read.
1766
2091
  *
1767
2092
  * Pass `null` to clear a description set earlier.
1768
2093
  *
@@ -1951,11 +2276,6 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1951
2276
  description: this.description
1952
2277
  };
1953
2278
  }
1954
- }, {
1955
- key: "executePreview",
1956
- value: function executePreview() {
1957
- return {};
1958
- }
1959
2279
  }]);
1960
2280
  }(Schema);
1961
2281
  var literal = function literal(value) {
@@ -2011,10 +2331,13 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2011
2331
  /**
2012
2332
  * Describe this field.
2013
2333
  *
2014
- * The description is shown next to the field's label in the Val editor, so it is
2015
- * where you say what an editor needs to know but the field name cannot carry. It
2016
- * also travels in the serialized schema, which is what the AI assistant and the
2017
- * MCP tools read.
2334
+ * The description is INPUT HELP: it is shown where this field's value is
2335
+ * entered — beside its input in the Val editor, and for a record's key
2336
+ * schema in every form that asks for a key — so it is where you say what an
2337
+ * editor needs to know to fill it in RIGHT, which the field name cannot
2338
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
2339
+ * read somewhere else. The description also travels in the serialized
2340
+ * schema, which is what the AI assistant and the MCP tools read.
2018
2341
  *
2019
2342
  * Pass `null` to clear a description set earlier.
2020
2343
  *
@@ -2467,7 +2790,7 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2467
2790
  }
2468
2791
  }, {
2469
2792
  key: "executePreview",
2470
- value: function executePreview(sourcePath, src, scope) {
2793
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
2471
2794
  var res = {};
2472
2795
  if (src === null) {
2473
2796
  return res;
@@ -2492,10 +2815,16 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2492
2815
  return false;
2493
2816
  });
2494
2817
  if (thisSchema) {
2495
- var itemResult = thisSchema["executePreview"](sourcePath, src, scope);
2496
- for (var keyS in itemResult) {
2497
- var _key2 = keyS;
2498
- res[_key2] = itemResult[_key2];
2818
+ // The variant owns the path — see the note on `executeCustomValidateAt`.
2819
+ // It contributes a `self` of its own here; the union's comes second and
2820
+ // wins, because `executePreviewItem` already encodes the precedence (the
2821
+ // union's own closure if it has one, else the matched variant's) and
2822
+ // there must not be a second copy of that rule.
2823
+ // The variant SHARES this path, so the flag passes straight through: a
2824
+ // union that is a row is a variant that is a row.
2825
+ mergePreviewInto(res, thisSchema["executePreview"](sourcePath, src, scope, selfIsReifiedByParent));
2826
+ if (!selfIsReifiedByParent) {
2827
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
2499
2828
  }
2500
2829
  return res;
2501
2830
  }
@@ -2508,8 +2837,8 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2508
2837
  }]);
2509
2838
  }(Schema);
2510
2839
  var discriminatedUnion = function discriminatedUnion(key) {
2511
- for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key3 = 1; _key3 < _len; _key3++) {
2512
- objects[_key3 - 1] = arguments[_key3];
2840
+ for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key2 = 1; _key2 < _len; _key2++) {
2841
+ objects[_key2 - 1] = arguments[_key2];
2513
2842
  }
2514
2843
  return new DiscriminatedUnionSchema(key, objects);
2515
2844
  };
@@ -3486,10 +3815,13 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3486
3815
  /**
3487
3816
  * Describe this field.
3488
3817
  *
3489
- * The description is shown next to the field's label in the Val editor, so
3490
- * it is where you say what an editor needs to know but the field name cannot
3491
- * carry. It also travels in the serialized schema, which is what the AI
3492
- * assistant and the MCP tools read.
3818
+ * The description is INPUT HELP: it is shown where this field's value is
3819
+ * entered — beside its input in the Val editor, and for a record's key
3820
+ * schema in every form that asks for a key — so it is where you say what an
3821
+ * editor needs to know to fill it in RIGHT, which the field name cannot
3822
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
3823
+ * read somewhere else. The description also travels in the serialized
3824
+ * schema, which is what the AI assistant and the MCP tools read.
3493
3825
  *
3494
3826
  * Pass `null` to clear a description set earlier.
3495
3827
  *
@@ -3870,11 +4202,6 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3870
4202
  description: this.description
3871
4203
  };
3872
4204
  }
3873
- }, {
3874
- key: "executePreview",
3875
- value: function executePreview() {
3876
- return {};
3877
- }
3878
4205
  }]);
3879
4206
  }(Schema);
3880
4207
 
@@ -3930,10 +4257,13 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3930
4257
  /**
3931
4258
  * Describe this field.
3932
4259
  *
3933
- * The description is shown next to the field's label in the Val editor, so
3934
- * it is where you say what an editor needs to know but the field name cannot
3935
- * carry. It also travels in the serialized schema, which is what the AI
3936
- * assistant and the MCP tools read.
4260
+ * The description is INPUT HELP: it is shown where this field's value is
4261
+ * entered — beside its input in the Val editor, and for a record's key
4262
+ * schema in every form that asks for a key — so it is where you say what an
4263
+ * editor needs to know to fill it in RIGHT, which the field name cannot
4264
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
4265
+ * read somewhere else. The description also travels in the serialized
4266
+ * schema, which is what the AI assistant and the MCP tools read.
3937
4267
  *
3938
4268
  * Pass `null` to clear a description set earlier.
3939
4269
  *
@@ -4162,11 +4492,6 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
4162
4492
  description: this.description
4163
4493
  };
4164
4494
  }
4165
- }, {
4166
- key: "executePreview",
4167
- value: function executePreview() {
4168
- return {};
4169
- }
4170
4495
  }]);
4171
4496
  }(Schema);
4172
4497
  var route = function route(options) {
@@ -4199,10 +4524,13 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4199
4524
  /**
4200
4525
  * Describe this field.
4201
4526
  *
4202
- * The description is shown next to the field's label in the Val editor, so
4203
- * it is where you say what an editor needs to know but the field name cannot
4204
- * carry. It also travels in the serialized schema, which is what the AI
4205
- * assistant and the MCP tools read.
4527
+ * The description is INPUT HELP: it is shown where this field's value is
4528
+ * entered — beside its input in the Val editor, and for a record's key
4529
+ * schema in every form that asks for a key — so it is where you say what an
4530
+ * editor needs to know to fill it in RIGHT, which the field name cannot
4531
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
4532
+ * read somewhere else. The description also travels in the serialized
4533
+ * schema, which is what the AI assistant and the MCP tools read.
4206
4534
  *
4207
4535
  * Pass `null` to clear a description set earlier.
4208
4536
  *
@@ -4848,11 +5176,6 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4848
5176
  description: this.description
4849
5177
  };
4850
5178
  }
4851
- }, {
4852
- key: "executePreview",
4853
- value: function executePreview() {
4854
- return {};
4855
- }
4856
5179
  }]);
4857
5180
  }(Schema);
4858
5181
  var richtext = function richtext(options) {
@@ -5367,10 +5690,13 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5367
5690
  /**
5368
5691
  * Describe this field.
5369
5692
  *
5370
- * The description is shown next to the field's label in the Val editor, so
5371
- * it is where you say what an editor needs to know but the field name cannot
5372
- * carry. It also travels in the serialized schema, which is what the AI
5373
- * assistant and the MCP tools read.
5693
+ * The description is INPUT HELP: it is shown where this field's value is
5694
+ * entered — beside its input in the Val editor, and for a record's key
5695
+ * schema in every form that asks for a key — so it is where you say what an
5696
+ * editor needs to know to fill it in RIGHT, which the field name cannot
5697
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
5698
+ * read somewhere else. The description also travels in the serialized
5699
+ * schema, which is what the AI assistant and the MCP tools read.
5374
5700
  *
5375
5701
  * Pass `null` to clear a description set earlier.
5376
5702
  *
@@ -6163,7 +6489,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6163
6489
  }
6164
6490
  }, {
6165
6491
  key: "executePreview",
6166
- value: function executePreview(sourcePath, src, scope) {
6492
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
6167
6493
  var res = {};
6168
6494
  if (src === null) {
6169
6495
  return res;
@@ -6188,11 +6514,10 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6188
6514
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
6189
6515
  continue;
6190
6516
  }
6191
- var itemResult = this.item["executePreview"](subPath, itemSrc, scope);
6192
- for (var keyS in itemResult) {
6193
- var _key3 = keyS;
6194
- res[_key3] = itemResult[_key3];
6195
- }
6517
+ mergePreviewInto(res,
6518
+ // `true`: this item is a ROW, and its preview is the closure reified
6519
+ // into `rows` below. See `executePreview` on `Schema`.
6520
+ this.item["executePreview"](subPath, itemSrc, scope, true));
6196
6521
  }
6197
6522
  // The entries preview comes from the ITEM schema's own `preview` — the
6198
6523
  // container just runs it per entry. Asked as a fact rather than by running
@@ -6204,7 +6529,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6204
6529
  var items = [];
6205
6530
  for (var _i2 = 0, _Object$entries = Object.entries(src); _i2 < _Object$entries.length; _i2++) {
6206
6531
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
6207
- _key4 = _Object$entries$_i[0],
6532
+ _key3 = _Object$entries$_i[0],
6208
6533
  val = _Object$entries$_i[1];
6209
6534
  if (isJson(val)) {
6210
6535
  continue; // as above: nothing to select from an un-loaded entry
@@ -6212,7 +6537,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6212
6537
  if (val === null || val === undefined) {
6213
6538
  continue;
6214
6539
  }
6215
- if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key4))) {
6540
+ if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key3))) {
6216
6541
  continue;
6217
6542
  }
6218
6543
  // Per KEY, not per record: the closure is user code, and one entry whose
@@ -6224,26 +6549,37 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6224
6549
  var title = _item.title,
6225
6550
  subtitle = _item.subtitle,
6226
6551
  image = _item.image;
6227
- items.push([_key4, {
6552
+ items.push([_key3, {
6228
6553
  title: title,
6229
6554
  subtitle: subtitle,
6230
6555
  image: image
6231
6556
  }]);
6232
6557
  }
6233
6558
  } catch (e) {
6234
- res[unsafeCreateSourcePath(sourcePath, _key4)] = {
6559
+ res[unsafeCreateSourcePath(sourcePath, _key3)] = {
6235
6560
  status: "error",
6236
6561
  message: e instanceof Error ? e.message : "Unknown error"
6237
6562
  };
6238
6563
  }
6239
6564
  }
6240
- res[sourcePath] = {
6565
+ var rows = {};
6566
+ rows[sourcePath] = {
6241
6567
  status: "success",
6242
6568
  data: {
6243
- parent: "record",
6244
- items: items
6569
+ rows: {
6570
+ parent: "record",
6571
+ items: items
6572
+ }
6245
6573
  }
6246
6574
  };
6575
+ mergePreviewInto(res, rows);
6576
+ }
6577
+ // ...and what the RECORD ITSELF is called — a different closure on a
6578
+ // different schema. Skipped when this record is itself a ROW, whose
6579
+ // closure the outer container has already run. See the same block in
6580
+ // `array`, and `PreviewNode`.
6581
+ if (!selfIsReifiedByParent) {
6582
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
6247
6583
  }
6248
6584
  return res;
6249
6585
  }
@@ -6368,6 +6704,16 @@ function record(keyOrSchema, schema) {
6368
6704
  * written without removing the key.
6369
6705
  */
6370
6706
 
6707
+ /**
6708
+ * How the Studio behaves for the people editing this project.
6709
+ *
6710
+ * Not how it looks — that is {@link ThemeSettingsSource} — and not one person's
6711
+ * own preferences, which stay in their browser. This is the project's word on
6712
+ * what its editors are offered, which is why it is content: a team that has
6713
+ * decided something about the way they work should not need a developer and a
6714
+ * deploy to say so.
6715
+ */
6716
+
6371
6717
  /**
6372
6718
  * How the Studio looks in this project.
6373
6719
  *
@@ -6735,11 +7081,6 @@ var LocaleSchema = /*#__PURE__*/function (_Schema) {
6735
7081
  description: this.description
6736
7082
  };
6737
7083
  }
6738
- }, {
6739
- key: "executePreview",
6740
- value: function executePreview() {
6741
- return {};
6742
- }
6743
7084
  }]);
6744
7085
  }(Schema);
6745
7086
 
@@ -6784,10 +7125,13 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6784
7125
  /**
6785
7126
  * Describe this field.
6786
7127
  *
6787
- * The description is shown next to the field's label in the Val editor, so
6788
- * it is where you say what an editor needs to know but the field name cannot
6789
- * carry. It also travels in the serialized schema, which is what the AI
6790
- * assistant and the MCP tools read.
7128
+ * The description is INPUT HELP: it is shown where this field's value is
7129
+ * entered — beside its input in the Val editor, and for a record's key
7130
+ * schema in every form that asks for a key — so it is where you say what an
7131
+ * editor needs to know to fill it in RIGHT, which the field name cannot
7132
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
7133
+ * read somewhere else. The description also travels in the serialized
7134
+ * schema, which is what the AI assistant and the MCP tools read.
6791
7135
  *
6792
7136
  * Pass `null` to clear a description set earlier.
6793
7137
  *
@@ -6961,11 +7305,6 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6961
7305
  description: this.description
6962
7306
  };
6963
7307
  }
6964
- }, {
6965
- key: "executePreview",
6966
- value: function executePreview() {
6967
- return {};
6968
- }
6969
7308
  }]);
6970
7309
  }(Schema);
6971
7310
  var _boolean = function _boolean() {
@@ -7478,10 +7817,13 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
7478
7817
  /**
7479
7818
  * Describe this field.
7480
7819
  *
7481
- * The description is shown next to the field's label in the Val editor, so
7482
- * it is where you say what an editor needs to know but the field name cannot
7483
- * carry. It also travels in the serialized schema, which is what the AI
7484
- * assistant and the MCP tools read.
7820
+ * The description is INPUT HELP: it is shown where this field's value is
7821
+ * entered — beside its input in the Val editor, and for a record's key
7822
+ * schema in every form that asks for a key — so it is where you say what an
7823
+ * editor needs to know to fill it in RIGHT, which the field name cannot
7824
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
7825
+ * read somewhere else. The description also travels in the serialized
7826
+ * schema, which is what the AI assistant and the MCP tools read.
7485
7827
  *
7486
7828
  * Pass `null` to clear a description set earlier.
7487
7829
  *
@@ -7705,11 +8047,6 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
7705
8047
  description: this.description
7706
8048
  };
7707
8049
  }
7708
- }, {
7709
- key: "executePreview",
7710
- value: function executePreview() {
7711
- return {};
7712
- }
7713
8050
  }]);
7714
8051
  }(Schema);
7715
8052
  var color = function color(options) {
@@ -7760,10 +8097,13 @@ var EnumSchema = /*#__PURE__*/function (_Schema) {
7760
8097
  /**
7761
8098
  * Describe this field.
7762
8099
  *
7763
- * The description is shown next to the field's label in the Val editor, so it is
7764
- * where you say what an editor needs to know but the field name cannot carry. It
7765
- * also travels in the serialized schema, which is what the AI assistant and the
7766
- * MCP tools read.
8100
+ * The description is INPUT HELP: it is shown where this field's value is
8101
+ * entered — beside its input in the Val editor, and for a record's key
8102
+ * schema in every form that asks for a key — so it is where you say what an
8103
+ * editor needs to know to fill it in RIGHT, which the field name cannot
8104
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
8105
+ * read somewhere else. The description also travels in the serialized
8106
+ * schema, which is what the AI assistant and the MCP tools read.
7767
8107
  *
7768
8108
  * Pass `null` to clear a description set earlier.
7769
8109
  *
@@ -7976,12 +8316,6 @@ var EnumSchema = /*#__PURE__*/function (_Schema) {
7976
8316
  description: this.description
7977
8317
  };
7978
8318
  }
7979
- }, {
7980
- key: "executePreview",
7981
- value: function executePreview() {
7982
- // A leaf: nothing below it to preview.
7983
- return {};
7984
- }
7985
8319
  }]);
7986
8320
  }(Schema);
7987
8321
 
@@ -8321,7 +8655,7 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
8321
8655
  }
8322
8656
  }, {
8323
8657
  key: "executePreview",
8324
- value: function executePreview(sourcePath, src, scope) {
8658
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
8325
8659
  var res = {};
8326
8660
  if (src === null) {
8327
8661
  return res;
@@ -8335,11 +8669,14 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
8335
8669
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
8336
8670
  continue;
8337
8671
  }
8338
- var itemResult = this.items[_key4]["executePreview"](subPath, itemSrc, scope);
8339
- for (var keyS in itemResult) {
8340
- var _key5 = keyS;
8341
- res[_key5] = itemResult[_key5];
8342
- }
8672
+ mergePreviewInto(res, this.items[_key4]["executePreview"](subPath, itemSrc, scope));
8673
+ }
8674
+ // An object reifies no rows of its own, so the only thing it adds is what
8675
+ // IT is called — which nothing else can supply for a field of an object.
8676
+ // Its own items are NOT rows, so the flag stops here rather than travelling
8677
+ // down with the recursion above.
8678
+ if (!selfIsReifiedByParent) {
8679
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
8343
8680
  }
8344
8681
  return res;
8345
8682
  }
@@ -8402,6 +8739,9 @@ function settings() {
8402
8739
  dir: THEME_LOGO_DIRECTORY
8403
8740
  }).nullable().describe("The project's own mark, shown where Val's is in the Studio. A square-ish mark rather than a wordmark: the slot is 32px wide.")
8404
8741
  }),
8742
+ studio: new SettingsSchema({
8743
+ tour: _boolean().nullable().describe("Whether editors are offered the guided tour of the Studio. Unset means yes. Off hides the offer for everyone on this project — the tour stays in Quick actions for anyone who wants it.")
8744
+ }),
8405
8745
  locales: new SettingsSchema({
8406
8746
  // Nullable because the source type is, and because the Studio writes
8407
8747
  // `null` for every sibling the first time a section is created.
@@ -8451,6 +8791,317 @@ function localesSectionErrors(src) {
8451
8791
  return errors;
8452
8792
  }
8453
8793
 
8794
+ /**
8795
+ * Type-level-only slot carrying the SOURCE TYPE of the module a view names.
8796
+ *
8797
+ * Optional, and that is what makes the marker writable: `{ view: "/foo.val.ts" }`
8798
+ * is a plain literal, and an optional symbol-keyed property costs the author
8799
+ * nothing to satisfy. It exists so the read side can say WHAT is behind the
8800
+ * pointer — `ValView<FooSrc>` rather than `ValView<unknown>` — without the
8801
+ * author having to write a type they do not have.
8802
+ */
8803
+
8804
+ /**
8805
+ * The source of a view field: a pointer to another module, and nothing else.
8806
+ *
8807
+ * Written as a plain object, like media and for the same reason — the same
8808
+ * value has to work in a `.val.ts` and in a `*.val.json` entry, and a literal
8809
+ * survives the static extraction that a constructor call does not. See
8810
+ * `architecture/media.md`.
8811
+ *
8812
+ * `Id` is the target's module file path as a LITERAL type, inferred from the
8813
+ * module handed to `s.view(...)`. So the path autocompletes at the call site,
8814
+ * and a source naming a different module than its schema does is a type error.
8815
+ *
8816
+ * Nothing decides "this is a view" by looking at the value; the SCHEMA does
8817
+ * (`type === "view"`). {@link isValViewSource} exists for the few places that have
8818
+ * a value and no schema, and is not the normal way to ask.
8819
+ */
8820
+
8821
+ /**
8822
+ * Is this value shaped like a view marker?
8823
+ *
8824
+ * A shape test, not an identity: `view` is a reserved object key
8825
+ * (`ObjectSchemaProps`), so no ordinary object source can take this shape — but
8826
+ * hand-written JSON can, which is why validation checks the value against the
8827
+ * schema rather than trusting it.
8828
+ */
8829
+ function isValViewSource(value) {
8830
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value) && "view" in value && typeof value.view === "string";
8831
+ }
8832
+
8833
+ /** The source type of the module a selector points at. */
8834
+
8835
+ /**
8836
+ * A field that points at ANOTHER module, for the Val editor's benefit.
8837
+ *
8838
+ * The source is `{ view: "/other.val.ts" }` — a pointer, and nothing else. The
8839
+ * module it names keeps its own source, its own patches, its own validation and
8840
+ * its own address; this field puts a row on THIS module's screen that an editor
8841
+ * clicks through to it.
8842
+ *
8843
+ * Two type parameters, each earning its place:
8844
+ *
8845
+ * - `Id` — the target's module file path as a literal, so the author writes
8846
+ * `{ view: "/other.val.ts" }` with autocomplete, and naming a different module
8847
+ * than the schema does is a type error rather than a validation error.
8848
+ * - `T` — the target's source type, carried on a phantom slot so the READ side
8849
+ * can say `ValView<T>` rather than `ValView<unknown>`. Nothing reads it yet;
8850
+ * it is here so that resolving a view through `useVal` can be added without
8851
+ * changing what is stored.
8852
+ */
8853
+ var ValViewSchema = /*#__PURE__*/function (_Schema) {
8854
+ function ValViewSchema(moduleFilePath) {
8855
+ var _this;
8856
+ var valModule = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
8857
+ var isReadonly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
8858
+ var isHidden = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
8859
+ var description = arguments.length > 4 ? arguments[4] : undefined;
8860
+ var renderInput = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
8861
+ _classCallCheck(this, ValViewSchema);
8862
+ _this = _callSuper(this, ValViewSchema);
8863
+ _this.moduleFilePath = moduleFilePath;
8864
+ _this.valModule = valModule;
8865
+ _this.isReadonly = isReadonly;
8866
+ _this.isHidden = isHidden;
8867
+ _this.description = description;
8868
+ _this.renderInput = renderInput;
8869
+ return _this;
8870
+ }
8871
+
8872
+ /**
8873
+ * Describe this field.
8874
+ *
8875
+ * Shown next to the field's label in the Val editor — here, the place to say
8876
+ * WHY the other module is on this screen, since an editor who follows the row
8877
+ * is about to change content that other pages use too.
8878
+ *
8879
+ * @example
8880
+ * import otherVal from "./other.val"; // another module
8881
+ * const schema = s.object({
8882
+ * shared: s.view(otherVal).describe("Shared by every page"),
8883
+ * title: s.string(),
8884
+ * });
8885
+ * export default c.define("/example.val.ts", schema, {
8886
+ * shared: { view: "/other.val.ts" },
8887
+ * title: "Hello",
8888
+ * });
8889
+ */
8890
+ _inherits(ValViewSchema, _Schema);
8891
+ return _createClass(ValViewSchema, [{
8892
+ key: "describe",
8893
+ value: function describe(description) {
8894
+ return new ValViewSchema(this.moduleFilePath, this.valModule, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput);
8895
+ }
8896
+
8897
+ /**
8898
+ * The stored pointer must name the module the SCHEMA names.
8899
+ *
8900
+ * It cannot disagree in a `.val.ts` — the source type is the literal path, so
8901
+ * a mismatch does not compile. It can disagree in hand-written JSON (a
8902
+ * `.jsonValues()` entry, an external record), which is what this is for.
8903
+ */
8904
+ }, {
8905
+ key: "executeValidate",
8906
+ value: function executeValidate(path, src) {
8907
+ if (!isValViewSource(src)) {
8908
+ return _defineProperty({}, path, [{
8909
+ message: "Expected a view pointer ({ view: \"".concat(this.moduleFilePath, "\" }), got '").concat(src === null ? "null" : _typeof(src), "'"),
8910
+ value: src,
8911
+ fixes: ["view:check-module"]
8912
+ }]);
8913
+ }
8914
+ if (src.view !== this.moduleFilePath) {
8915
+ return _defineProperty({}, path, [{
8916
+ message: "This view points at '".concat(src.view, "', but its schema says '").concat(this.moduleFilePath, "'"),
8917
+ value: src,
8918
+ fixes: ["view:check-module"]
8919
+ }]);
8920
+ }
8921
+ return false;
8922
+ }
8923
+ }, {
8924
+ key: "executeCustomValidateAt",
8925
+ value: function executeCustomValidateAt(_path, _src) {
8926
+ return [];
8927
+ }
8928
+ }, {
8929
+ key: "executeAssert",
8930
+ value: function executeAssert(path, src) {
8931
+ if (isValViewSource(src)) {
8932
+ return {
8933
+ success: true,
8934
+ data: src
8935
+ };
8936
+ }
8937
+ return {
8938
+ success: false,
8939
+ errors: _defineProperty({}, path, [{
8940
+ message: "Expected a view pointer ({ view: string }), got '".concat(src === null ? "null" : _typeof(src), "'"),
8941
+ typeError: true
8942
+ }])
8943
+ };
8944
+ }
8945
+
8946
+ /**
8947
+ * Not available: a view points at a module, and `null` is not a module.
8948
+ * Throws if called.
8949
+ *
8950
+ * @example
8951
+ * import otherVal from "./other.val"; // another module
8952
+ * // `s.view(otherVal).nullable()` throws — a view is never nullable.
8953
+ * const schema = s.object({ shared: s.view(otherVal), title: s.string() });
8954
+ * export default c.define("/example.val.ts", schema, {
8955
+ * shared: { view: "/other.val.ts" },
8956
+ * title: "Hello",
8957
+ * });
8958
+ */
8959
+ }, {
8960
+ key: "nullable",
8961
+ value: function nullable() {
8962
+ throw new Error("s.view() cannot be nullable: it points at a module");
8963
+ }
8964
+
8965
+ /**
8966
+ * Marks the row read-only.
8967
+ *
8968
+ * The view's OWN flag, and it says nothing about the module it names: that
8969
+ * module has its own `readonly`, which is what governs editing once an
8970
+ * editor is there. A view is a link rather than an editor, so this changes
8971
+ * nothing about the row today — it exists because every schema has it, and
8972
+ * because a view that renders its target inline would need it.
8973
+ *
8974
+ * @example
8975
+ * import otherVal from "./other.val"; // another module
8976
+ * const schema = s.object({
8977
+ * shared: s.view(otherVal).readonly(),
8978
+ * title: s.string(),
8979
+ * });
8980
+ * export default c.define("/example.val.ts", schema, {
8981
+ * shared: { view: "/other.val.ts" },
8982
+ * title: "Hello",
8983
+ * });
8984
+ */
8985
+ }, {
8986
+ key: "readonly",
8987
+ value: function readonly() {
8988
+ var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
8989
+ return new ValViewSchema(this.moduleFilePath, this.valModule, isReadonly, this.isHidden, this.description, this.renderInput);
8990
+ }
8991
+
8992
+ /**
8993
+ * Hide the ROW in the editor.
8994
+ *
8995
+ * The view's OWN flag, and it is the only thing that decides whether the row
8996
+ * is drawn — a view whose TARGET is hidden is still shown, and still leads
8997
+ * there. That is the point of it: a module that many modules `keyOf` into is
8998
+ * noise in the nav and belongs on exactly one page, so it is hidden from the
8999
+ * listing and reached through the view that puts it where it belongs.
9000
+ *
9001
+ * @example
9002
+ * import otherVal from "./other.val"; // another module
9003
+ * const schema = s.object({
9004
+ * shared: s.view(otherVal).hidden(),
9005
+ * title: s.string(),
9006
+ * });
9007
+ * export default c.define("/example.val.ts", schema, {
9008
+ * shared: { view: "/other.val.ts" },
9009
+ * title: "Hello",
9010
+ * });
9011
+ */
9012
+ }, {
9013
+ key: "hidden",
9014
+ value: function hidden() {
9015
+ var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
9016
+ return new ValViewSchema(this.moduleFilePath, this.valModule, this.isReadonly, isHidden, this.description, this.renderInput);
9017
+ }
9018
+
9019
+ /**
9020
+ * How this field is laid out in the editor. Static configuration, not a
9021
+ * callback — see `render.ts`.
9022
+ *
9023
+ * @example
9024
+ * import otherVal from "./other.val"; // another module
9025
+ * const schema = s.object({
9026
+ * shared: s.view(otherVal).render({ as: "inline" }),
9027
+ * title: s.string(),
9028
+ * });
9029
+ * export default c.define("/example.val.ts", schema, {
9030
+ * shared: { view: "/other.val.ts" },
9031
+ * title: "Hello",
9032
+ * });
9033
+ */
9034
+ }, {
9035
+ key: "render",
9036
+ value: function render(input) {
9037
+ return new ValViewSchema(this.moduleFilePath, this.valModule, this.isReadonly, this.isHidden, this.description, input);
9038
+ }
9039
+ }, {
9040
+ key: "executeSerialize",
9041
+ value: function executeSerialize() {
9042
+ var _this$renderInput;
9043
+ return {
9044
+ type: "view",
9045
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
9046
+ opt: false,
9047
+ moduleFilePath: this.moduleFilePath,
9048
+ readonly: this.isReadonly,
9049
+ hidden: this.isHidden,
9050
+ description: this.description
9051
+ };
9052
+ }
9053
+ }, {
9054
+ key: "executePreview",
9055
+ value: function executePreview() {
9056
+ return {};
9057
+ }
9058
+ }]);
9059
+ }(Schema);
9060
+
9061
+ /**
9062
+ * Point at another module, so the Val editor shows a way into it from here.
9063
+ *
9064
+ * A view stores a pointer and nothing else: the module it names keeps its own
9065
+ * source, patches, validation and address, and an editor reaches it by clicking
9066
+ * through rather than by hunting for it. It is there so content that has to live
9067
+ * in its own module — a `keyOf` target, shared settings, a route-keyed record —
9068
+ * can still be found from the page it belongs to.
9069
+ *
9070
+ * Reading it in consuming code gives you a pointer with no fields on it. Read
9071
+ * the module it names directly instead.
9072
+ *
9073
+ * @example
9074
+ * import otherVal from "./other.val"; // another module
9075
+ * const schema = s.object({ shared: s.view(otherVal), title: s.string() });
9076
+ * export default c.define("/example.val.ts", schema, {
9077
+ * shared: { view: "/other.val.ts" },
9078
+ * title: "Hello",
9079
+ * });
9080
+ */
9081
+ var view = function view(valModule) {
9082
+ var path = getValPath(valModule);
9083
+ if (!path) {
9084
+ throw new Error("s.view() must be given a Val module");
9085
+ }
9086
+ return new ValViewSchema(path, valModule);
9087
+ };
9088
+
9089
+ /**
9090
+ * The brand that says "this is a module", carrying the module's own id.
9091
+ *
9092
+ * `Id` is inferred from {@link define}'s first argument, so a module's PATH is
9093
+ * in its type. That is what lets `s.view(fooVal)` produce a schema whose source
9094
+ * type is the literal `{ view: "/foo.val.ts" }` — the path autocompletes at the
9095
+ * call site, and a source that names a different module than the schema does is
9096
+ * a type error rather than something validation has to catch later.
9097
+ *
9098
+ * Defaulted to `string` so every `ValModule<T>` written without it keeps
9099
+ * meaning what it did. The property is covariant, so a module with a known id
9100
+ * is still assignable everywhere `ValModule<T>` is expected.
9101
+ */
9102
+
9103
+ /** A module's own path, read back off its type. */
9104
+
8454
9105
  /**
8455
9106
  * What may be WRITTEN as an external record's source, on top of the marker.
8456
9107
  *
@@ -8470,6 +9121,17 @@ function localesSectionErrors(src) {
8470
9121
  function define(id,
8471
9122
  // TODO: `/${string}`
8472
9123
  schema, source) {
9124
+ // A module cannot BE a view of another module. `s.view()` stores nothing, so
9125
+ // the module's whole source would be `undefined` — which type-checks, because
9126
+ // `undefined` is a `SelectorSource`, and then fails everywhere downstream that
9127
+ // expects a module to have source. A view is a field of a module, never the
9128
+ // module. Thrown rather than typed: making the `schema` parameter conditional
9129
+ // on T would put it in a non-inferable position and break inference for every
9130
+ // other schema. `extractValModules` reports what this throws as a module
9131
+ // error, which is where a developer will look.
9132
+ if (schema instanceof ValViewSchema) {
9133
+ throw Error("Cannot define '".concat(id, "' as a view: s.view() shows another module and stores nothing, so it cannot be a module's own schema. Put it in an s.object({ ... }) field instead."));
9134
+ }
8473
9135
  return _defineProperty(_defineProperty(_defineProperty({}, GetSource, source), GetSchema, schema), Path, id);
8474
9136
  }
8475
9137
  function getSource(valModule) {
@@ -8579,10 +9241,25 @@ function resolvePath(path, valModule, schema) {
8579
9241
  if (_typeof(resolvedSource) !== "object" && !Array.isArray(resolvedSource)) {
8580
9242
  throw Error("Schema type error: expected source to be type of record, but got ".concat(_typeof(resolvedSource)));
8581
9243
  }
8582
- if (!resolvedSource[part]) {
9244
+ // PRESENCE, not truthiness. An entry that exists and is falsy is an
9245
+ // entry: `null` for a declared key nobody has written yet, but also `""`,
9246
+ // `0` and `false` in any record at all. Testing the value made every one
9247
+ // of those report as a key the record does not have, so nothing could
9248
+ // resolve them — not the Studio, not the language server, not a fix.
9249
+ //
9250
+ // A record source that is itself `null` keeps resolving as `null`, one
9251
+ // part per level, the way the object branch below does: the path still
9252
+ // names a real place in the schema, and the caller wants the schema
9253
+ // there in order to say that nothing is written yet.
9254
+ //
9255
+ // `hasOwnProperty`, never `in`: `in` walks the prototype chain, so
9256
+ // `"toString"` would resolve on every record and hand back
9257
+ // `Object.prototype.toString` as if it were Source. Same reason
9258
+ // `patch/json.ts` guards every key it reads.
9259
+ if (resolvedSource !== null && !Object.prototype.hasOwnProperty.call(resolvedSource, part)) {
8583
9260
  throw Error("Invalid path: record source did not have key ".concat(part, " from path: ").concat(path));
8584
9261
  }
8585
- resolvedSource = resolvedSource[part];
9262
+ resolvedSource = resolvedSource === null ? resolvedSource : resolvedSource[part];
8586
9263
  resolvedSchema = resolvedSchema instanceof RecordSchema ? (_resolvedSchema2 = resolvedSchema) === null || _resolvedSchema2 === void 0 ? void 0 : _resolvedSchema2["item"] : resolvedSchema.item;
8587
9264
  // A `.jsonValues()` entry value is a lazily-loaded JsonSource marker. We
8588
9265
  // can resolve the entry itself (schema is now `item`), but we cannot
@@ -8784,7 +9461,15 @@ function safeResolvePath(path, valModule, schema) {
8784
9461
  }
8785
9462
  };
8786
9463
  }
8787
- if (resolvedSource[part] === undefined) {
9464
+ // A `null` record keeps resolving as `null`, as the object branch below
9465
+ // does — see the same note in `resolvePath`. Without it, indexing `null`
9466
+ // threw a TypeError out of the function whose whole point is not to
9467
+ // throw.
9468
+ //
9469
+ // Own properties only. Testing `resolvedSource[part] === undefined` let
9470
+ // an inherited name through — `"toString"` is not undefined on any
9471
+ // object — so the walk continued into `Object.prototype`.
9472
+ if (resolvedSource !== null && !Object.prototype.hasOwnProperty.call(resolvedSource, part)) {
8788
9473
  return {
8789
9474
  v: {
8790
9475
  status: "source-undefined",
@@ -8794,7 +9479,7 @@ function safeResolvePath(path, valModule, schema) {
8794
9479
  }
8795
9480
  };
8796
9481
  }
8797
- resolvedSource = resolvedSource[part];
9482
+ resolvedSource = resolvedSource === null ? resolvedSource : resolvedSource[part];
8798
9483
  resolvedSchema = resolvedSchema instanceof RecordSchema ? (_resolvedSchema4 = resolvedSchema) === null || _resolvedSchema4 === void 0 ? void 0 : _resolvedSchema4["item"] : resolvedSchema.item;
8799
9484
  // jsonValues entry: cannot descend deeper until the backing `*.val.json`
8800
9485
  // content is loaded into the source tree.
@@ -9053,10 +9738,13 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
9053
9738
  /**
9054
9739
  * Describe this field.
9055
9740
  *
9056
- * The description is shown next to the field's label in the Val editor, so
9057
- * it is where you say what an editor needs to know but the field name cannot
9058
- * carry. It also travels in the serialized schema, which is what the AI
9059
- * assistant and the MCP tools read.
9741
+ * The description is INPUT HELP: it is shown where this field's value is
9742
+ * entered — beside its input in the Val editor, and for a record's key
9743
+ * schema in every form that asks for a key — so it is where you say what an
9744
+ * editor needs to know to fill it in RIGHT, which the field name cannot
9745
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
9746
+ * read somewhere else. The description also travels in the serialized
9747
+ * schema, which is what the AI assistant and the MCP tools read.
9060
9748
  *
9061
9749
  * Pass `null` to clear a description set earlier.
9062
9750
  *
@@ -9280,11 +9968,6 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
9280
9968
  description: this.description
9281
9969
  };
9282
9970
  }
9283
- }, {
9284
- key: "executePreview",
9285
- value: function executePreview() {
9286
- return {};
9287
- }
9288
9971
  }]);
9289
9972
  }(Schema);
9290
9973
  var number = function number(options) {
@@ -9397,10 +10080,13 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
9397
10080
  /**
9398
10081
  * Describe this field.
9399
10082
  *
9400
- * The description is shown next to the field's label in the Val editor, so
9401
- * it is where you say what an editor needs to know but the field name cannot
9402
- * carry. It also travels in the serialized schema, which is what the AI
9403
- * assistant and the MCP tools read.
10083
+ * The description is INPUT HELP: it is shown where this field's value is
10084
+ * entered — beside its input in the Val editor, and for a record's key
10085
+ * schema in every form that asks for a key — so it is where you say what an
10086
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10087
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10088
+ * read somewhere else. The description also travels in the serialized
10089
+ * schema, which is what the AI assistant and the MCP tools read.
9404
10090
  *
9405
10091
  * Pass `null` to clear a description set earlier.
9406
10092
  *
@@ -9682,11 +10368,6 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
9682
10368
  description: this.description
9683
10369
  };
9684
10370
  }
9685
- }, {
9686
- key: "executePreview",
9687
- value: function executePreview() {
9688
- return {};
9689
- }
9690
10371
  }]);
9691
10372
  }(Schema);
9692
10373
  var keyOf = function keyOf(valModule) {
@@ -9743,10 +10424,13 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
9743
10424
  /**
9744
10425
  * Describe this field.
9745
10426
  *
9746
- * The description is shown next to the field's label in the Val editor, so
9747
- * it is where you say what an editor needs to know but the field name cannot
9748
- * carry. It also travels in the serialized schema, which is what the AI
9749
- * assistant and the MCP tools read.
10427
+ * The description is INPUT HELP: it is shown where this field's value is
10428
+ * entered — beside its input in the Val editor, and for a record's key
10429
+ * schema in every form that asks for a key — so it is where you say what an
10430
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10431
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10432
+ * read somewhere else. The description also travels in the serialized
10433
+ * schema, which is what the AI assistant and the MCP tools read.
9750
10434
  *
9751
10435
  * Pass `null` to clear a description set earlier.
9752
10436
  *
@@ -10087,11 +10771,6 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10087
10771
  description: this.description
10088
10772
  };
10089
10773
  }
10090
- }, {
10091
- key: "executePreview",
10092
- value: function executePreview() {
10093
- return {};
10094
- }
10095
10774
  }]);
10096
10775
  }(Schema);
10097
10776
 
@@ -10184,10 +10863,13 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
10184
10863
  /**
10185
10864
  * Describe this field.
10186
10865
  *
10187
- * The description is shown next to the field's label in the Val editor, so
10188
- * it is where you say what an editor needs to know but the field name cannot
10189
- * carry. It also travels in the serialized schema, which is what the AI
10190
- * assistant and the MCP tools read.
10866
+ * The description is INPUT HELP: it is shown where this field's value is
10867
+ * entered — beside its input in the Val editor, and for a record's key
10868
+ * schema in every form that asks for a key — so it is where you say what an
10869
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10870
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10871
+ * read somewhere else. The description also travels in the serialized
10872
+ * schema, which is what the AI assistant and the MCP tools read.
10191
10873
  *
10192
10874
  * Pass `null` to clear a description set earlier.
10193
10875
  *
@@ -10432,11 +11114,6 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
10432
11114
  description: this.description
10433
11115
  };
10434
11116
  }
10435
- }, {
10436
- key: "executePreview",
10437
- value: function executePreview() {
10438
- return {};
10439
- }
10440
11117
  }]);
10441
11118
  }(Schema);
10442
11119
  var date = function date(options) {
@@ -10469,10 +11146,13 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
10469
11146
  /**
10470
11147
  * Describe this field.
10471
11148
  *
10472
- * The description is shown next to the field's label in the Val editor, so
10473
- * it is where you say what an editor needs to know but the field name cannot
10474
- * carry. It also travels in the serialized schema, which is what the AI
10475
- * assistant and the MCP tools read.
11149
+ * The description is INPUT HELP: it is shown where this field's value is
11150
+ * entered — beside its input in the Val editor, and for a record's key
11151
+ * schema in every form that asks for a key — so it is where you say what an
11152
+ * editor needs to know to fill it in RIGHT, which the field name cannot
11153
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
11154
+ * read somewhere else. The description also travels in the serialized
11155
+ * schema, which is what the AI assistant and the MCP tools read.
10476
11156
  *
10477
11157
  * Pass `null` to clear a description set earlier.
10478
11158
  *
@@ -10772,11 +11452,6 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
10772
11452
  description: this.description
10773
11453
  };
10774
11454
  }
10775
- }, {
10776
- key: "executePreview",
10777
- value: function executePreview() {
10778
- return {};
10779
- }
10780
11455
  }]);
10781
11456
  }(Schema);
10782
11457
  var datetime = function datetime(options) {
@@ -10823,10 +11498,13 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
10823
11498
  /**
10824
11499
  * Describe this field.
10825
11500
  *
10826
- * The description is shown next to the field's label in the Val editor, so
10827
- * it is where you say what an editor needs to know but the field name cannot
10828
- * carry. It also travels in the serialized schema, which is what the AI
10829
- * assistant and the MCP tools read.
11501
+ * The description is INPUT HELP: it is shown where this field's value is
11502
+ * entered — beside its input in the Val editor, and for a record's key
11503
+ * schema in every form that asks for a key — so it is where you say what an
11504
+ * editor needs to know to fill it in RIGHT, which the field name cannot
11505
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
11506
+ * read somewhere else. The description also travels in the serialized
11507
+ * schema, which is what the AI assistant and the MCP tools read.
10830
11508
  *
10831
11509
  * Pass `null` to clear a description set earlier.
10832
11510
  *
@@ -11017,11 +11695,6 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
11017
11695
  description: this.description
11018
11696
  };
11019
11697
  }
11020
- }, {
11021
- key: "executePreview",
11022
- value: function executePreview() {
11023
- return {};
11024
- }
11025
11698
  }]);
11026
11699
  }(Schema);
11027
11700
  var code = function code(options) {
@@ -11148,6 +11821,7 @@ function initSchema() {
11148
11821
  image: image,
11149
11822
  literal: literal,
11150
11823
  keyOf: keyOf,
11824
+ view: view,
11151
11825
  record: record,
11152
11826
  file: file,
11153
11827
  fileset: fileset,
@@ -11655,6 +12329,185 @@ function resolveSettingsModule(schemas) {
11655
12329
  };
11656
12330
  }
11657
12331
 
12332
+ /**
12333
+ * Every module a schema points at with `s.view(...)`, at any depth.
12334
+ *
12335
+ * A GENERIC walk over the serialized schema rather than a switch over the
12336
+ * container types, and that is deliberate: a schema can hold another schema in
12337
+ * places an enumeration keeps forgetting — a richtext's `a` and `img` options
12338
+ * each carry one, and a discriminated union's variants are an array of them.
12339
+ * Missing a nesting site here means missing a cycle, and a missed cycle is the
12340
+ * failure this check exists to prevent.
12341
+ */
12342
+ function viewTargetsOf(schema) {
12343
+ var targets = [];
12344
+ var seen = new Set();
12345
+ function walk(node) {
12346
+ if (_typeof(node) !== "object" || node === null) {
12347
+ return;
12348
+ }
12349
+ // A serialized schema is a tree, but guard anyway: this runs over data the
12350
+ // walk does not own, and a cycle here would hang module extraction.
12351
+ if (seen.has(node)) {
12352
+ return;
12353
+ }
12354
+ seen.add(node);
12355
+ if (Array.isArray(node)) {
12356
+ var _iterator = _createForOfIteratorHelper(node),
12357
+ _step;
12358
+ try {
12359
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
12360
+ var item = _step.value;
12361
+ walk(item);
12362
+ }
12363
+ } catch (err) {
12364
+ _iterator.e(err);
12365
+ } finally {
12366
+ _iterator.f();
12367
+ }
12368
+ return;
12369
+ }
12370
+ var record = node;
12371
+ if (record["type"] === "view") {
12372
+ var target = record["moduleFilePath"];
12373
+ if (typeof target === "string") {
12374
+ targets.push(target);
12375
+ }
12376
+ // A view schema holds nothing else worth walking.
12377
+ return;
12378
+ }
12379
+ for (var _i = 0, _Object$values = Object.values(record); _i < _Object$values.length; _i++) {
12380
+ var value = _Object$values[_i];
12381
+ walk(value);
12382
+ }
12383
+ }
12384
+ walk(schema);
12385
+ return targets;
12386
+ }
12387
+
12388
+ /**
12389
+ * The cycles in a project's views: `A → B → A`, and `A → A`.
12390
+ *
12391
+ * Each cycle is returned once, as the modules in it in order, rotated so it
12392
+ * starts at the lexicographically smallest — otherwise the same cycle would be
12393
+ * reported once per module it passes through, worded differently each time.
12394
+ */
12395
+ function findViewCycles(schemas) {
12396
+ var edges = new Map();
12397
+ for (var _i2 = 0, _Object$entries = Object.entries(schemas); _i2 < _Object$entries.length; _i2++) {
12398
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
12399
+ moduleFilePath = _Object$entries$_i[0],
12400
+ schema = _Object$entries$_i[1];
12401
+ edges.set(moduleFilePath, viewTargetsOf(schema));
12402
+ }
12403
+ var cycles = [];
12404
+ var found = new Set();
12405
+ var done = new Set();
12406
+ var stack = [];
12407
+ var onStack = new Set();
12408
+ function visit(moduleFilePath) {
12409
+ var _edges$get;
12410
+ if (done.has(moduleFilePath)) {
12411
+ return;
12412
+ }
12413
+ if (onStack.has(moduleFilePath)) {
12414
+ var cycle = stack.slice(stack.indexOf(moduleFilePath));
12415
+ var key = canonical(cycle);
12416
+ if (!found.has(key)) {
12417
+ found.add(key);
12418
+ cycles.push(rotate(cycle));
12419
+ }
12420
+ return;
12421
+ }
12422
+ stack.push(moduleFilePath);
12423
+ onStack.add(moduleFilePath);
12424
+ var _iterator2 = _createForOfIteratorHelper((_edges$get = edges.get(moduleFilePath)) !== null && _edges$get !== void 0 ? _edges$get : []),
12425
+ _step2;
12426
+ try {
12427
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
12428
+ var target = _step2.value;
12429
+ // A target that is not a module of this project is a different error —
12430
+ // the view names something that does not exist — and is reported where
12431
+ // the field is, not here.
12432
+ if (edges.has(target)) {
12433
+ visit(target);
12434
+ }
12435
+ }
12436
+ } catch (err) {
12437
+ _iterator2.e(err);
12438
+ } finally {
12439
+ _iterator2.f();
12440
+ }
12441
+ stack.pop();
12442
+ onStack["delete"](moduleFilePath);
12443
+ done.add(moduleFilePath);
12444
+ }
12445
+ var _iterator3 = _createForOfIteratorHelper(edges.keys()),
12446
+ _step3;
12447
+ try {
12448
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
12449
+ var _moduleFilePath = _step3.value;
12450
+ visit(_moduleFilePath);
12451
+ }
12452
+ } catch (err) {
12453
+ _iterator3.e(err);
12454
+ } finally {
12455
+ _iterator3.f();
12456
+ }
12457
+ return cycles;
12458
+ }
12459
+ function rotate(cycle) {
12460
+ var at = 0;
12461
+ for (var i = 1; i < cycle.length; i++) {
12462
+ if (cycle[i] < cycle[at]) {
12463
+ at = i;
12464
+ }
12465
+ }
12466
+ return [].concat(_toConsumableArray(cycle.slice(at)), _toConsumableArray(cycle.slice(0, at)));
12467
+ }
12468
+ function canonical(cycle) {
12469
+ return rotate(cycle).join(" -> ");
12470
+ }
12471
+
12472
+ /**
12473
+ * A cycle of views, as one error per module in it.
12474
+ *
12475
+ * Per module rather than once, because a developer opens ONE of them and the
12476
+ * error has to be there when they do. Every copy names the whole cycle, so it
12477
+ * reads the same wherever it is found.
12478
+ */
12479
+ function viewCycleErrors(schemas) {
12480
+ var errors = [];
12481
+ var _iterator4 = _createForOfIteratorHelper(findViewCycles(schemas)),
12482
+ _step4;
12483
+ try {
12484
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
12485
+ var cycle = _step4.value;
12486
+ var described = [].concat(_toConsumableArray(cycle), [cycle[0]]).join(" -> ");
12487
+ var _iterator5 = _createForOfIteratorHelper(cycle),
12488
+ _step5;
12489
+ try {
12490
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
12491
+ var moduleFilePath = _step5.value;
12492
+ errors.push({
12493
+ path: moduleFilePath,
12494
+ message: cycle.length === 1 ? "s.view() cycle: '".concat(moduleFilePath, "' views itself. A view must point at another module.") : "s.view() cycle: ".concat(described, ". Views may not form a cycle.")
12495
+ });
12496
+ }
12497
+ } catch (err) {
12498
+ _iterator5.e(err);
12499
+ } finally {
12500
+ _iterator5.f();
12501
+ }
12502
+ }
12503
+ } catch (err) {
12504
+ _iterator4.e(err);
12505
+ } finally {
12506
+ _iterator4.f();
12507
+ }
12508
+ return errors;
12509
+ }
12510
+
11658
12511
  // Lazily constructed: this module is also evaluated inside the `vm` sandbox
11659
12512
  // used by loadValModules (when user val modules import @valbuild/core), and
11660
12513
  // that sandbox has no `TextEncoder` global. `hash()` is never called from
@@ -11822,7 +12675,7 @@ function extractValModules(_x) {
11822
12675
  }
11823
12676
  function _extractValModules() {
11824
12677
  _extractValModules = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(valModules) {
11825
- var moduleErrors, addModuleError, sources, schemas, serializedSchemas, shaEntries, moduleIdx, module, at, value, path, schema, source, serializedSchema, pathM, _iterator2, _step2, error, _t, _t2;
12678
+ var moduleErrors, addModuleError, sources, schemas, serializedSchemas, shaEntries, moduleIdx, module, at, value, path, schema, source, serializedSchema, pathM, _iterator2, _step2, error, _iterator3, _step3, _error, _t, _t2;
11826
12679
  return _regenerator().w(function (_context) {
11827
12680
  while (1) switch (_context.p = _context.n) {
11828
12681
  case 0:
@@ -11975,11 +12828,26 @@ function _extractValModules() {
11975
12828
  error = _step2.value;
11976
12829
  moduleErrors.push(error);
11977
12830
  }
12831
+ // Same kind of rule, same reason it is here: a view cycle is a property of the
12832
+ // whole set of schemas, so no single module can see it. Nothing further down
12833
+ // catches it either — a view stores a pointer, not content, so there is no
12834
+ // data cycle for a source walk to trip over.
11978
12835
  } catch (err) {
11979
12836
  _iterator2.e(err);
11980
12837
  } finally {
11981
12838
  _iterator2.f();
11982
12839
  }
12840
+ _iterator3 = _createForOfIteratorHelper(viewCycleErrors(serializedSchemas));
12841
+ try {
12842
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
12843
+ _error = _step3.value;
12844
+ moduleErrors.push(_error);
12845
+ }
12846
+ } catch (err) {
12847
+ _iterator3.e(err);
12848
+ } finally {
12849
+ _iterator3.f();
12850
+ }
11983
12851
  return _context.a(2, _objectSpread2(_objectSpread2({
11984
12852
  sources: sources,
11985
12853
  schemas: schemas,
@@ -12082,7 +12950,7 @@ function hasRemoteFileSchema(schema) {
12082
12950
  return false;
12083
12951
  } else if (schema.type === "boolean" ||
12084
12952
  // An enum's values are strings, so there is nothing to look inside.
12085
- schema.type === "enum" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" || schema.type === "locale") {
12953
+ schema.type === "enum" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" || schema.type === "locale" || schema.type === "view") {
12086
12954
  return false;
12087
12955
  } else {
12088
12956
  var exhaustiveCheck = schema;
@@ -12157,7 +13025,9 @@ function hasMediaSchema(schema) {
12157
13025
  schema.type === "enum" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" || schema.type === "locale" ||
12158
13026
  // A settings module holds the assistant's configuration, not content, and
12159
13027
  // nothing in it is media.
12160
- schema.type === "settings") {
13028
+ schema.type === "settings" ||
13029
+ // A view shows another module; that module answers for its own media.
13030
+ schema.type === "view") {
12161
13031
  return false;
12162
13032
  } else {
12163
13033
  // Same contract as above: adding a schema type without teaching this
@@ -12473,6 +13343,160 @@ function discriminatedUnionBranchOf(schema, tag) {
12473
13343
  return undefined;
12474
13344
  }
12475
13345
 
13346
+ /**
13347
+ * Where a resolved view handle keeps the module it points at.
13348
+ *
13349
+ * A symbol, so it is invisible to `JSON.stringify`, `Object.keys`, the source
13350
+ * SHAs and every walk that treats source as data — the handle still IS
13351
+ * `{ view: "/foo.val.ts" }` everywhere that matters.
13352
+ */
13353
+ var ViewTargetModule = Symbol["for"]("val/ViewTargetModule");
13354
+
13355
+ /**
13356
+ * A view's source with the module it names attached.
13357
+ *
13358
+ * `useVal(page.header)` needs the target's SOURCE and its SCHEMA, and the
13359
+ * stored pointer has neither — it is a path. The app has no way to turn a path
13360
+ * back into a module either: `val.modules` holds lazy `import()` thunks, and
13361
+ * `<ValModulesClient>` is optional. So the module travels with the handle.
13362
+ *
13363
+ * Attached by the READ path (`stegaEncode`) rather than written into source,
13364
+ * and that is the whole reason it works in draft mode: with an edit pending,
13365
+ * the source comes from the overlay store as plain JSON that never went near a
13366
+ * module. The schema is the module's own either way, so the encoder always has
13367
+ * it to hand.
13368
+ */
13369
+
13370
+ /** Attach the module a view names to its pointer. */
13371
+ function createViewHandle(source, valModule) {
13372
+ return _objectSpread2(_objectSpread2({}, source), {}, _defineProperty({}, ViewTargetModule, valModule));
13373
+ }
13374
+
13375
+ /** Is this a view pointer that carries the module it names? */
13376
+ function isViewHandle(value) {
13377
+ return _typeof(value) === "object" && value !== null && ViewTargetModule in value && value[ViewTargetModule] !== undefined;
13378
+ }
13379
+
13380
+ /** The module a handle carries, or undefined if it carries none. */
13381
+ function viewHandleModule(value) {
13382
+ if (!isViewHandle(value)) {
13383
+ return undefined;
13384
+ }
13385
+ return value[ViewTargetModule];
13386
+ }
13387
+
13388
+ /**
13389
+ * What a reader that needs a MODULE accepts, rather than a value.
13390
+ *
13391
+ * `useValKey`, `useValRoute`, `useValRouteUrl` and their `fetch*` counterparts
13392
+ * all read a path, a schema and a source off what they are handed, so — unlike
13393
+ * `useVal` — they cannot take an arbitrary selector. They CAN take a view
13394
+ * pointing at one, which is the point of a page declaring which module it
13395
+ * shows. {@link resolveViewedModule} is the runtime half, and takes exactly
13396
+ * this, so the two cannot drift.
13397
+ */
13398
+
13399
+ /**
13400
+ * What a reader was actually handed: the module itself, or — when it is a view
13401
+ * handle — the module that view points at.
13402
+ *
13403
+ * Every reader that needs a module rather than a value goes through this:
13404
+ * `useValKey`, `useValRoute`, `useValRouteUrl` and their `fetch*` counterparts
13405
+ * all pull the path, the schema and the source off the thing they are given,
13406
+ * and a view handle has none of those. It is `{ view: "/foo.val.ts" }` with the
13407
+ * module on a symbol, so reading `Internal.getValPath` off it gives
13408
+ * `undefined`, and every one of those readers treats `undefined` as its own
13409
+ * "no route matched" answer. Without this the call is not an error — it is a
13410
+ * silently empty one.
13411
+ *
13412
+ * A POINTER with no module on it throws rather than being passed through, for
13413
+ * the same reason `stegaEncode` throws on one: symbols do not survive
13414
+ * serialization, so a handle that crossed the server/client boundary as a prop
13415
+ * arrives looking like content and is not.
13416
+ */
13417
+
13418
+ /*
13419
+ * A widened implementation signature rather than assertions in the body.
13420
+ *
13421
+ * The module rides on a symbol as `unknown` — the encoder that attaches it has
13422
+ * no type for it — and no narrowing tells the checker that what is left after
13423
+ * both guards is the module arm. Declaring the callable signature above and
13424
+ * implementing against `unknown` says that once, where the runtime fact lives,
13425
+ * instead of casting at each of the twelve readers.
13426
+ */
13427
+ function resolveViewedModule(selector) {
13428
+ var resolved = viewHandleModule(selector);
13429
+ if (resolved !== undefined) {
13430
+ return resolved;
13431
+ }
13432
+ if (isValViewSource(selector)) {
13433
+ throw Error("Cannot resolve the view of '".concat(selector.view, "': it carries no module. ") + "Either it crossed a server/client boundary, which drops the module because it rides on a symbol, " + "or it points at a different module than its schema declares \u2014 which `val validate --fix` repairs. " + "Resolve it in the same component that read the module containing it, or read '".concat(selector.view, "' directly."));
13434
+ }
13435
+ return selector;
13436
+ }
13437
+
13438
+ /**
13439
+ * Every module a schema's views point at, keyed by path.
13440
+ *
13441
+ * Walked GENERICALLY over the schema instance's own properties rather than by
13442
+ * switching on schema type, for the same reason `viewTargetsOf` is: a schema
13443
+ * holds another schema in places an enumeration keeps forgetting. Missing a
13444
+ * site here is a view that cannot be resolved at runtime, which is a worse
13445
+ * failure than the walk being slightly broad.
13446
+ *
13447
+ * Memoised per schema INSTANCE. A module's schema is built once at import, and
13448
+ * `useVal` runs on every render — so without this the walk would be a render
13449
+ * cost rather than a load cost.
13450
+ */
13451
+ var cache = new WeakMap();
13452
+ function viewModulesOf(schema) {
13453
+ if (schema === undefined) {
13454
+ return new Map();
13455
+ }
13456
+ var cached = cache.get(schema);
13457
+ if (cached) {
13458
+ return cached;
13459
+ }
13460
+ var modules = new Map();
13461
+ var seen = new Set();
13462
+ function walk(node) {
13463
+ if (_typeof(node) !== "object" || node === null || seen.has(node)) {
13464
+ return;
13465
+ }
13466
+ seen.add(node);
13467
+ if (node instanceof ValViewSchema) {
13468
+ var target = node["moduleFilePath"];
13469
+ var valModule = node["valModule"];
13470
+ if (typeof target === "string" && valModule !== undefined) {
13471
+ modules.set(target, valModule);
13472
+ }
13473
+ return;
13474
+ }
13475
+ if (Array.isArray(node)) {
13476
+ var _iterator = _createForOfIteratorHelper(node),
13477
+ _step;
13478
+ try {
13479
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
13480
+ var item = _step.value;
13481
+ walk(item);
13482
+ }
13483
+ } catch (err) {
13484
+ _iterator.e(err);
13485
+ } finally {
13486
+ _iterator.f();
13487
+ }
13488
+ return;
13489
+ }
13490
+ for (var _i = 0, _Object$values = Object.values(node); _i < _Object$values.length; _i++) {
13491
+ var value = _Object$values[_i];
13492
+ walk(value);
13493
+ }
13494
+ }
13495
+ walk(schema);
13496
+ cache.set(schema, modules);
13497
+ return modules;
13498
+ }
13499
+
12476
13500
  function deserializeSchema(serialized) {
12477
13501
  var schema = deserializeSchemaImpl(serialized);
12478
13502
  if (serialized.readonly) {
@@ -12484,7 +13508,7 @@ function deserializeSchema(serialized) {
12484
13508
  return schema;
12485
13509
  }
12486
13510
  function deserializeSchemaImpl(serialized) {
12487
- var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$render8, _serialized$accept, _serialized$dir, _serialized$remote, _serialized$jsonValue, _serialized$render0, _serialized$external, _serialized$render1, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15, _serialized$render16, _serialized$render17;
13511
+ var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$render8, _serialized$accept, _serialized$dir, _serialized$remote, _serialized$jsonValue, _serialized$render0, _serialized$external, _serialized$render1, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15, _serialized$render16, _serialized$render17, _serialized$readonly, _serialized$hidden, _serialized$render18;
12488
13512
  switch (serialized.type) {
12489
13513
  case "string":
12490
13514
  return new StringSchema(_objectSpread2(_objectSpread2({}, serialized.options), {}, {
@@ -12581,6 +13605,11 @@ function deserializeSchemaImpl(serialized) {
12581
13605
  return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render16 = serialized.render) !== null && _serialized$render16 !== void 0 ? _serialized$render16 : null);
12582
13606
  case "code":
12583
13607
  return new CodeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render17 = serialized.render) !== null && _serialized$render17 !== void 0 ? _serialized$render17 : null);
13608
+ case "view":
13609
+ return new ValViewSchema(serialized.moduleFilePath,
13610
+ // No module: this schema came off the wire. The Studio navigates to a
13611
+ // view rather than resolving one, so nothing here needs it.
13612
+ undefined, (_serialized$readonly = serialized.readonly) !== null && _serialized$readonly !== void 0 ? _serialized$readonly : false, (_serialized$hidden = serialized.hidden) !== null && _serialized$hidden !== void 0 ? _serialized$hidden : false, serialized.description, (_serialized$render18 = serialized.render) !== null && _serialized$render18 !== void 0 ? _serialized$render18 : null);
12584
13613
  default:
12585
13614
  {
12586
13615
  var exhaustiveCheck = serialized;
@@ -12595,142 +13624,31 @@ function deserializeSchemaImpl(serialized) {
12595
13624
  }
12596
13625
 
12597
13626
  /**
12598
- * A PREVIEW is how a VALUE is shown wherever a preview of it is needed — a row
12599
- * in a sortable list, a key in a reference dropdown, a search hit, a
12600
- * reference — which is everywhere the value is NAVIGABLE to rather than open.
12601
- * It is never how the field itself is edited: that is a RENDER (`render.ts`),
12602
- * which applies only when you are looking at the field. The two do not
12603
- * intersect — a schema can carry both, and each is read in its own places.
12604
- *
12605
- * A preview is declared on the schema of the VALUE being previewed:
13627
+ * THE RULE, and the only sentence that needs to be remembered:
12606
13628
  *
12607
- * ```ts
12608
- * const author = s.object({ name: s.string() })
12609
- * .preview(({ val }) => ({ title: val.name }));
12610
- * const authors = s.array(author);
12611
- * ```
12612
- *
12613
- * The container reifies its rows by running each ITEM's preview closure — see
12614
- * {@link ArrayPreview} / {@link RecordPreview}. (Previews used to be declared
12615
- * on the container instead; a `.preview` on an array/record now previews the
12616
- * array/record itself as a value, for when IT is the item of something.)
12617
- */
12618
- /**
12619
- * What a preview shows for one value: the user's `preview` callback returns
12620
- * this, and the Studio draws a row from it.
13629
+ * **`.describe()` is INPUT HELP and is shown wherever that field — or a
13630
+ * record's key — is being ENTERED; `.preview()` is a NAME and is shown
13631
+ * wherever the value is REFERRED TO rather than edited; `.render()` is
13632
+ * LAYOUT and applies only while the field is open in front of you.**
12621
13633
  *
12622
- * NB: {@link ArrayPreview} / {@link RecordPreview} name the DATA a container
12623
- * previews with. The Studio also has React components called `ArrayPreview` /
12624
- * `RecordPreview` (the fallback rendering of an array / record field, part of
12625
- * the `<Type>Preview` convention in `components/Preview.tsx`). They do not
12626
- * collide today because no file needs both - if one ever does, alias this type
12627
- * at the import rather than renaming that convention.
12628
- */
12629
-
12630
- /**
12631
- * What `.preview(...)` takes, on every schema: the value's own source in, a
12632
- * {@link PreviewItem} out. `NonNullable` because a container skips null items
12633
- * rather than previewing them.
12634
- *
12635
- * Declared through method syntax deliberately (the same bivariance shape
12636
- * React's event handler types use): `nullable()` copies a schema's closure
12637
- * into the `Src | null` variant of the same class, and with a plain function
12638
- * type the checker cannot see that `NonNullable<Src | null>` IS
12639
- * `NonNullable<Src>` while `Src` is still a type parameter — every schema's
12640
- * `nullable()` would need a cast instead.
12641
- */
12642
-
12643
- /**
12644
- * Main preview type.
12645
- *
12646
- * Deliberately not exported: `parent` is the discriminant, and every consumer
12647
- * narrows on it rather than on the union as a whole. There is no `layout` here
12648
- * on purpose - how a preview is laid out is the editor's business, not the
12649
- * schema's.
12650
- */
12651
-
12652
- /**
12653
- * Which paths a preview is being computed FOR.
12654
- *
12655
- * `executePreview` takes a whole module, so one request has always walked every
12656
- * node in it and run every `preview` closure - for `handboka`, with `preview` at
12657
- * two nested array levels, that is every chapter and every section to serve one
12658
- * visible row. A scope is what makes the walk proportional to what is being
12659
- * looked at instead of to the project.
12660
- *
12661
- * Two questions, because a container and its items need different answers:
12662
- *
12663
- * - {@link PreviewScope.wants} - is a preview AT this exact path wanted? A
12664
- * container answers `true` here when the whole of it is being shown, and its
12665
- * preview is then computed in full.
12666
- * - {@link PreviewScope.wantsUnder} - could anything at or below this path be
12667
- * wanted? Recursion is pruned where this is `false`, and a container whose own
12668
- * path is not wanted but which has wanted descendants previews a WINDOW: only
12669
- * the items that were asked for. That is the case a single visible row is, and
12670
- * it is why {@link ArrayPreview} carries indices.
13634
+ * The test that settles every case: **can the reader change something here?**
13635
+ * If yes it is a place for a description — the input beside a label, the key
13636
+ * box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
13637
+ * reference dropdown. If no, it is a place for a preview — a list row, a
13638
+ * reference once chosen, a search hit, a sitemap row, the heading of what you
13639
+ * navigated to.
12671
13640
  *
12672
- * Absent scope means the whole module, which is what every existing caller
12673
- * passes and what every existing caller got.
12674
- */
12675
-
12676
- /**
12677
- * A scope covering exactly `paths` and their subtrees.
13641
+ * That is why a description is plain data on the serialized schema and a
13642
+ * preview is a closure: a description is true before any value exists and says
13643
+ * the same thing to everyone filling the field in, and a preview cannot exist
13644
+ * without the one value it names. So a description must never be used as a
13645
+ * subtitle — it would repeat one sentence under every row of a list — and a
13646
+ * preview must never be used as help text, because there is nothing to preview
13647
+ * until after the value has been entered.
12678
13648
  *
12679
- * Compares SEGMENTS rather than string prefixes. A source path's module path is
12680
- * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
12681
- * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
12682
- * dot or a quote - `"title"` against `"titles"` being the cheapest example.
12683
- * Segments are parsed once here, not per comparison.
13649
+ * None of the three substitutes for another: a field with a perfect
13650
+ * description still previews as `#3` until someone writes the preview.
12684
13651
  */
12685
- function previewScope(paths) {
12686
- var wanted = paths.map(function (path) {
12687
- var _splitModuleFilePathA = splitModuleFilePathAndModulePath(path),
12688
- _splitModuleFilePathA2 = _slicedToArray(_splitModuleFilePathA, 2),
12689
- moduleFilePath = _splitModuleFilePathA2[0],
12690
- modulePath = _splitModuleFilePathA2[1];
12691
- return {
12692
- moduleFilePath: moduleFilePath,
12693
- segments: modulePath === "" ? [] : splitModulePath(modulePath)
12694
- };
12695
- });
12696
- var parse = function parse(path) {
12697
- var _splitModuleFilePathA3 = splitModuleFilePathAndModulePath(path),
12698
- _splitModuleFilePathA4 = _slicedToArray(_splitModuleFilePathA3, 2),
12699
- moduleFilePath = _splitModuleFilePathA4[0],
12700
- modulePath = _splitModuleFilePathA4[1];
12701
- return {
12702
- moduleFilePath: moduleFilePath,
12703
- segments: modulePath === "" ? [] : splitModulePath(modulePath)
12704
- };
12705
- };
12706
- var isPrefix = function isPrefix(prefix, of) {
12707
- if (prefix.length > of.length) return false;
12708
- for (var i = 0; i < prefix.length; i++) {
12709
- if (prefix[i] !== of[i]) return false;
12710
- }
12711
- return true;
12712
- };
12713
- return {
12714
- wants: function wants(path) {
12715
- var at = parse(path);
12716
- return wanted.some(function (entry) {
12717
- return entry.moduleFilePath === at.moduleFilePath && entry.segments.length === at.segments.length && isPrefix(entry.segments, at.segments);
12718
- });
12719
- },
12720
- wantsUnder: function wantsUnder(path) {
12721
- var at = parse(path);
12722
- return wanted.some(function (entry) {
12723
- return entry.moduleFilePath === at.moduleFilePath && (
12724
- // Either direction: a wanted path below `path` means recurse into it,
12725
- // and a wanted path ABOVE it means the whole subtree was asked for.
12726
- isPrefix(at.segments, entry.segments) || isPrefix(entry.segments, at.segments));
12727
- });
12728
- }
12729
- };
12730
- }
12731
-
12732
- // TODO: improve this so that we do not get RawString and string, only string. Are there other things?
12733
-
12734
13652
  /**
12735
13653
  * A RENDER is how the FIELD ITSELF is laid out in the editor, and it applies
12736
13654
  * only when you are looking at the field: `.render({ as: "inline" })` on an
@@ -13247,7 +14165,7 @@ function isPageRouter(routerId) {
13247
14165
 
13248
14166
  var corePackageJson = {
13249
14167
  name: "@valbuild/core",
13250
- version: "0.129.0",
14168
+ version: "0.134.0",
13251
14169
  "private": false,
13252
14170
  description: "Val - supercharged hard-coded content",
13253
14171
  repository: {
@@ -13309,6 +14227,14 @@ var Internal = {
13309
14227
  VERSION: {
13310
14228
  core: corePackageJson.version
13311
14229
  },
14230
+ // A view's read-path plumbing: `Internal` rather than the public surface,
14231
+ // because an app never builds or unwraps a handle — it passes one to
14232
+ // `useVal`.
14233
+ createViewHandle: createViewHandle,
14234
+ isViewHandle: isViewHandle,
14235
+ resolveViewedModule: resolveViewedModule,
14236
+ viewHandleModule: viewHandleModule,
14237
+ viewModulesOf: viewModulesOf,
13312
14238
  mediaUrl: mediaUrl,
13313
14239
  resolveMedia: resolveMedia,
13314
14240
  isRemoteMediaPath: isRemoteMediaPath,
@@ -13418,4 +14344,4 @@ function tryJsonParse(str) {
13418
14344
  }
13419
14345
  }
13420
14346
 
13421
- export { KeyOfSchema as $, ArraySchema as A, BooleanSchema as B, localeTagError as C, DEFAULT_CONTENT_HOST as D, localeOfValue as E, FATAL_ERROR_TYPES as F, GenericSelector as G, declaredKeySetOf as H, Internal as I, missingDeclaredKeys as J, discriminatedUnionBranchOf as K, LocaleSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, ColorSchema as Q, RecordSchema as R, Schema as S, THEME_LOGO_DIRECTORY as T, CodeSchema as U, VAL_EXTENSION as V, CODE_LANGUAGES as W, COLOR_FORMATS as X, DEFAULT_COLOR_FORMAT as Y, DateTimeSchema as Z, _typeof as _, _slicedToArray as a, RouteSchema as a0, RichTextSchema as a1, DiscriminatedUnionSchema as a2, EnumSchema as a3, LiteralSchema as a4, deserializeSchema as a5, previewScope as a6, isInlineRender as a7, parseNextJsRoutePattern as a8, parseTanStackRoutePattern as a9, tanStackSegmentsOfRoutePath as aa, validateUrlAgainstPattern as ab, getSourcePathFromRoute as ac, isPageRouter as ad, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, hasMediaSchema as j, DEFAULT_VAL_REMOTE_HOST as k, derefPatch as l, modules as m, StringSchema as n, ImageSchema as o, FileSchema as p, DateSchema as q, SettingsSchema as r, splitModuleFilePathAndModulePath as s, ASSISTANT_SETTINGS_MAX_LENGTH as t, THEME_RADIUS_STEPS as u, THEME_RADIUS_LENGTHS as v, assistantAvailability as w, SETTINGS_MODULE_CONVENTION as x, isRootModuleFilePath as y, resolveSettingsModule as z };
14347
+ export { KeyOfSchema as $, ArraySchema as A, BooleanSchema as B, localeTagError as C, DEFAULT_CONTENT_HOST as D, localeOfValue as E, FATAL_ERROR_TYPES as F, GenericSelector as G, declaredKeySetOf as H, Internal as I, missingDeclaredKeys as J, discriminatedUnionBranchOf as K, LocaleSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, ColorSchema as Q, RecordSchema as R, Schema as S, THEME_LOGO_DIRECTORY as T, CodeSchema as U, VAL_EXTENSION as V, CODE_LANGUAGES as W, COLOR_FORMATS as X, DEFAULT_COLOR_FORMAT as Y, DateTimeSchema as Z, _typeof as _, _slicedToArray as a, ValViewSchema as a0, isValViewSource as a1, RouteSchema as a2, RichTextSchema as a3, DiscriminatedUnionSchema as a4, EnumSchema as a5, LiteralSchema as a6, deserializeSchema as a7, previewScope as a8, isInlineRender as a9, parseNextJsRoutePattern as aa, parseTanStackRoutePattern as ab, tanStackSegmentsOfRoutePath as ac, validateUrlAgainstPattern as ad, getSourcePathFromRoute as ae, isPageRouter as af, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, hasMediaSchema as j, DEFAULT_VAL_REMOTE_HOST as k, derefPatch as l, modules as m, StringSchema as n, ImageSchema as o, FileSchema as p, DateSchema as q, SettingsSchema as r, splitModuleFilePathAndModulePath as s, ASSISTANT_SETTINGS_MAX_LENGTH as t, THEME_RADIUS_STEPS as u, THEME_RADIUS_LENGTHS as v, assistantAvailability as w, SETTINGS_MODULE_CONVENTION as x, isRootModuleFilePath as y, resolveSettingsModule as z };