@valbuild/core 0.129.0 → 0.130.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 (36) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/declarations/src/index.d.ts +1 -1
  3. package/dist/declarations/src/preview.d.ts +65 -8
  4. package/dist/declarations/src/render.d.ts +26 -0
  5. package/dist/declarations/src/schema/array.d.ts +8 -5
  6. package/dist/declarations/src/schema/boolean.d.ts +8 -6
  7. package/dist/declarations/src/schema/code.d.ts +8 -6
  8. package/dist/declarations/src/schema/color.d.ts +8 -6
  9. package/dist/declarations/src/schema/date.d.ts +8 -6
  10. package/dist/declarations/src/schema/datetime.d.ts +8 -6
  11. package/dist/declarations/src/schema/discriminatedUnion.d.ts +8 -5
  12. package/dist/declarations/src/schema/enum.d.ts +8 -6
  13. package/dist/declarations/src/schema/file.d.ts +8 -6
  14. package/dist/declarations/src/schema/image.d.ts +8 -6
  15. package/dist/declarations/src/schema/index.d.ts +29 -1
  16. package/dist/declarations/src/schema/keyOf.d.ts +8 -6
  17. package/dist/declarations/src/schema/literal.d.ts +8 -6
  18. package/dist/declarations/src/schema/locale.d.ts +1 -2
  19. package/dist/declarations/src/schema/number.d.ts +8 -6
  20. package/dist/declarations/src/schema/object.d.ts +8 -5
  21. package/dist/declarations/src/schema/record.d.ts +8 -5
  22. package/dist/declarations/src/schema/richtext.d.ts +8 -6
  23. package/dist/declarations/src/schema/route.d.ts +8 -6
  24. package/dist/declarations/src/schema/settings.d.ts +1 -1
  25. package/dist/declarations/src/schema/string.d.ts +8 -11
  26. package/dist/declarations/src/source/settings.d.ts +27 -0
  27. package/dist/{index-21fd16b1.cjs.dev.js → index-27d57011.cjs.dev.js} +589 -349
  28. package/dist/{index-06bb2832.esm.js → index-71a59a88.esm.js} +589 -349
  29. package/dist/{index-f5842720.cjs.prod.js → index-9e372bd7.cjs.prod.js} +589 -349
  30. package/dist/valbuild-core.cjs.dev.js +1 -1
  31. package/dist/valbuild-core.cjs.prod.js +1 -1
  32. package/dist/valbuild-core.esm.js +1 -1
  33. package/package.json +1 -1
  34. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  35. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  36. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -413,8 +413,93 @@ var Schema = /*#__PURE__*/function () {
413
413
  * For example, if you have an object schema, you must assert each key / value pair manually.
414
414
  */
415
415
  }, {
416
- key: "executePreviewItem",
416
+ key: "executePreview",
417
417
  value:
418
+ /**
419
+ * This value's preview, and every preview below it.
420
+ *
421
+ * The default is the whole of it for a LEAF: a leaf has nothing below it, so
422
+ * all it can contribute is {@link PreviewNode.self}. Containers override this
423
+ * and recurse; object and discriminated union add nothing of their own beyond
424
+ * the self they inherit here, and array and record add `rows`.
425
+ *
426
+ * @param scope Which paths the caller needs a preview for. Absent means the
427
+ * whole module, which is what every caller passed before scoping existed.
428
+ * See {@link PreviewScope}: a container prunes recursion where nothing is
429
+ * wanted, and previews a WINDOW when its own path is not wanted but some of
430
+ * its items are — which is the single-visible-row case.
431
+ * @param selfIsReifiedByParent Set by an array or record on its DIRECT items,
432
+ * and by nothing else. Such an item's preview is the same closure the
433
+ * container is already running into {@link PreviewNode.rows}, so emitting a
434
+ * `self` for it as well would call the user's closure twice per row — which
435
+ * it did, and which the scoped-preview tests caught by counting. The flag
436
+ * does not travel further down: a grandchild is nobody's row.
437
+ */
438
+ function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
439
+ if (selfIsReifiedByParent) {
440
+ return {};
441
+ }
442
+ return this.executeSelfPreview(sourcePath, src, scope);
443
+ }
444
+
445
+ /**
446
+ * THIS value as a preview, at its own path — `{ self }` and nothing else.
447
+ *
448
+ * Every schema can produce one, which is the point: before this existed a
449
+ * value's preview was reified only by its CONTAINER, so a value with no
450
+ * container had none. A module root is exactly that, and `.preview(...)` on
451
+ * a module's own schema was therefore dead code — the studio showed the file
452
+ * name and nothing a developer wrote could change it. So is any field of an
453
+ * object, which reifies no rows.
454
+ *
455
+ * Gated on {@link PreviewScope.wants} rather than `wantsUnder`: a caller
456
+ * asking about a CONTAINER wants its rows, and computing a self for every
457
+ * descendant on the way past would run each item's closure twice.
458
+ */
459
+ }, {
460
+ key: "executeSelfPreview",
461
+ value: function executeSelfPreview(sourcePath, src, scope) {
462
+ if (src === null || src === undefined) {
463
+ return {};
464
+ }
465
+ if (!this.declaresItemPreview()) {
466
+ return {};
467
+ }
468
+ if (scope !== undefined && !scope.wants(sourcePath)) {
469
+ return {};
470
+ }
471
+ try {
472
+ // NB NB: the closure is user code.
473
+ var item = this.executePreviewItem(src);
474
+ if (item === null) {
475
+ return {};
476
+ }
477
+ var title = item.title,
478
+ subtitle = item.subtitle,
479
+ image = item.image;
480
+ // Assigned into an annotated local rather than returned as a literal: a
481
+ // computed key widens `status` to `string`.
482
+ var res = {};
483
+ res[sourcePath] = {
484
+ status: "success",
485
+ data: {
486
+ self: {
487
+ title: title,
488
+ subtitle: subtitle,
489
+ image: image
490
+ }
491
+ }
492
+ };
493
+ return res;
494
+ } catch (e) {
495
+ var _res = {};
496
+ _res[sourcePath] = {
497
+ status: "error",
498
+ message: e instanceof Error ? e.message : "Unknown error"
499
+ };
500
+ return _res;
501
+ }
502
+ }
418
503
  /**
419
504
  * This value AS A PREVIEW — what a container's row, a reference dropdown or
420
505
  * a search hit shows for it. Runs the schema's own `preview` closure;
@@ -425,7 +510,9 @@ var Schema = /*#__PURE__*/function () {
425
510
  * `executePreview` reifies an {@link ArrayPreview} / {@link RecordPreview}
426
511
  * from item-level declarations.
427
512
  */
428
- function executePreviewItem(src) {
513
+ }, {
514
+ key: "executePreviewItem",
515
+ value: function executePreviewItem(src) {
429
516
  return null;
430
517
  }
431
518
  /**
@@ -506,6 +593,241 @@ var Schema = /*#__PURE__*/function () {
506
593
 
507
594
  // TODO: SourceError<"Could not determine type of Schema">
508
595
 
596
+ function ownKeys(e, r) {
597
+ var t = Object.keys(e);
598
+ if (Object.getOwnPropertySymbols) {
599
+ var o = Object.getOwnPropertySymbols(e);
600
+ r && (o = o.filter(function (r) {
601
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
602
+ })), t.push.apply(t, o);
603
+ }
604
+ return t;
605
+ }
606
+ function _objectSpread2(e) {
607
+ for (var r = 1; r < arguments.length; r++) {
608
+ var t = null != arguments[r] ? arguments[r] : {};
609
+ r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
610
+ _defineProperty(e, r, t[r]);
611
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
612
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
613
+ });
614
+ }
615
+ return e;
616
+ }
617
+
618
+ /**
619
+ * THE RULE, and the only sentence that needs to be remembered:
620
+ *
621
+ * **`.describe()` is INPUT HELP and is shown wherever that field — or a
622
+ * record's key — is being ENTERED; `.preview()` is a NAME and is shown
623
+ * wherever the value is REFERRED TO rather than edited; `.render()` is
624
+ * LAYOUT and applies only while the field is open in front of you.**
625
+ *
626
+ * The test that settles every case: **can the reader change something here?**
627
+ * If yes it is a place for a description — the input beside a label, the key
628
+ * box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
629
+ * reference dropdown. If no, it is a place for a preview — a list row, a
630
+ * reference once chosen, a search hit, a sitemap row, the heading of what you
631
+ * navigated to.
632
+ *
633
+ * That is why a description is plain data on the serialized schema and a
634
+ * preview is a closure: a description is true before any value exists and says
635
+ * the same thing to everyone filling the field in, and a preview cannot exist
636
+ * without the one value it names. So a description must never be used as a
637
+ * subtitle — it would repeat one sentence under every row of a list — and a
638
+ * preview must never be used as help text, because there is nothing to preview
639
+ * until after the value has been entered.
640
+ *
641
+ * None of the three substitutes for another: a field with a perfect
642
+ * description still previews as `#3` until someone writes the preview.
643
+ */
644
+ /**
645
+ * A PREVIEW is how a VALUE is shown wherever a preview of it is needed — a row
646
+ * in a sortable list, a key in a reference dropdown, a search hit, a
647
+ * reference — which is everywhere the value is NAVIGABLE to rather than open.
648
+ * It is never how the field itself is edited: that is a RENDER (`render.ts`),
649
+ * which applies only when you are looking at the field. The two do not
650
+ * intersect — a schema can carry both, and each is read in its own places.
651
+ *
652
+ * A preview is declared on the schema of the VALUE being previewed:
653
+ *
654
+ * ```ts
655
+ * const author = s.object({ name: s.string() })
656
+ * .preview(({ val }) => ({ title: val.name }));
657
+ * const authors = s.array(author);
658
+ * ```
659
+ *
660
+ * The container reifies its rows by running each ITEM's preview closure — see
661
+ * {@link ArrayPreview} / {@link RecordPreview}. (Previews used to be declared
662
+ * on the container instead; a `.preview` on an array/record now previews the
663
+ * array/record itself as a value, for when IT is the item of something.)
664
+ */
665
+ /**
666
+ * What a preview shows for one value: the user's `preview` callback returns
667
+ * this, and the Studio draws a row from it.
668
+ *
669
+ * NB: {@link ArrayPreview} / {@link RecordPreview} name the DATA a container
670
+ * previews with. The Studio also has React components called `ArrayPreview` /
671
+ * `RecordPreview` (the fallback rendering of an array / record field, part of
672
+ * the `<Type>Preview` convention in `components/Preview.tsx`). They do not
673
+ * collide today because no file needs both - if one ever does, alias this type
674
+ * at the import rather than renaming that convention.
675
+ */
676
+
677
+ /**
678
+ * What `.preview(...)` takes, on every schema: the value's own source in, a
679
+ * {@link PreviewItem} out. `NonNullable` because a container skips null items
680
+ * rather than previewing them.
681
+ *
682
+ * Declared through method syntax deliberately (the same bivariance shape
683
+ * React's event handler types use): `nullable()` copies a schema's closure
684
+ * into the `Src | null` variant of the same class, and with a plain function
685
+ * type the checker cannot see that `NonNullable<Src | null>` IS
686
+ * `NonNullable<Src>` while `Src` is still a type parameter — every schema's
687
+ * `nullable()` would need a cast instead.
688
+ */
689
+
690
+ /**
691
+ * How a CONTAINER's children preview. `parent` is the discriminant, and every
692
+ * consumer narrows on it rather than on the union as a whole. There is no
693
+ * `layout` here on purpose - how a preview is laid out is the editor's
694
+ * business, not the schema's.
695
+ */
696
+
697
+ /**
698
+ * Everything known about ONE path, which is two independent things.
699
+ *
700
+ * They are separate fields rather than a union because a path can have both,
701
+ * and that is not a corner case: `s.array(section).preview(...)` where
702
+ * `section` also previews is a section list that shows its rows AND names
703
+ * itself when it is nested in something. One of them would have had to win.
704
+ *
705
+ * - {@link self} - what THIS value is called, from its own schema's
706
+ * `.preview(...)`. This is the only answer for a value with no container to
707
+ * ask: a module root (whose `.preview` was never run before this existed) and
708
+ * any field of an object.
709
+ * - {@link rows} - what its CHILDREN are called, from the ITEM schema's
710
+ * `.preview(...)`, reified by the container. Kept alongside `self` rather
711
+ * than derived from the children's `self` entries because a list needs every
712
+ * row in one answer, and because an EMPTY list still has to preview as an
713
+ * empty list - which is a fact about the schema, not about any value.
714
+ */
715
+
716
+ /**
717
+ * Fold `incoming` into `target`, path by path. MUTATES `target`.
718
+ *
719
+ * Every `executePreview` used to copy child results across with a plain
720
+ * assignment, which was fine while one path held one thing. It no longer does:
721
+ * a container writes `rows` at its own path and its `self` arrives from the
722
+ * same walk, so an overwrite silently drops whichever landed first. Merging is
723
+ * per FIELD, and an error at a path beats any data there - a preview that threw
724
+ * must not be reported as a preview that is merely absent.
725
+ */
726
+ function mergePreviewInto(target, incoming) {
727
+ for (var keyS in incoming) {
728
+ var key = keyS;
729
+ var next = incoming[key];
730
+ var current = target[key];
731
+ if (current === undefined || next.status === "error") {
732
+ target[key] = next;
733
+ continue;
734
+ }
735
+ if (current.status === "error" || next.status === "loading") {
736
+ continue;
737
+ }
738
+ if (current.status === "loading") {
739
+ target[key] = next;
740
+ continue;
741
+ }
742
+ target[key] = {
743
+ status: "success",
744
+ data: _objectSpread2(_objectSpread2({}, current.data), next.data)
745
+ };
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Which paths a preview is being computed FOR.
751
+ *
752
+ * `executePreview` takes a whole module, so one request has always walked every
753
+ * node in it and run every `preview` closure - for `handboka`, with `preview` at
754
+ * two nested array levels, that is every chapter and every section to serve one
755
+ * visible row. A scope is what makes the walk proportional to what is being
756
+ * looked at instead of to the project.
757
+ *
758
+ * Two questions, because a container and its items need different answers:
759
+ *
760
+ * - {@link PreviewScope.wants} - is a preview AT this exact path wanted? A
761
+ * container answers `true` here when the whole of it is being shown, and its
762
+ * preview is then computed in full.
763
+ * - {@link PreviewScope.wantsUnder} - could anything at or below this path be
764
+ * wanted? Recursion is pruned where this is `false`, and a container whose own
765
+ * path is not wanted but which has wanted descendants previews a WINDOW: only
766
+ * the items that were asked for. That is the case a single visible row is, and
767
+ * it is why {@link ArrayPreview} carries indices.
768
+ *
769
+ * Absent scope means the whole module, which is what every existing caller
770
+ * passes and what every existing caller got.
771
+ */
772
+
773
+ /**
774
+ * A scope covering exactly `paths` and their subtrees.
775
+ *
776
+ * Compares SEGMENTS rather than string prefixes. A source path's module path is
777
+ * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
778
+ * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
779
+ * dot or a quote - `"title"` against `"titles"` being the cheapest example.
780
+ * Segments are parsed once here, not per comparison.
781
+ */
782
+ function previewScope(paths) {
783
+ var wanted = paths.map(function (path) {
784
+ var _splitModuleFilePathA = splitModuleFilePathAndModulePath(path),
785
+ _splitModuleFilePathA2 = _slicedToArray(_splitModuleFilePathA, 2),
786
+ moduleFilePath = _splitModuleFilePathA2[0],
787
+ modulePath = _splitModuleFilePathA2[1];
788
+ return {
789
+ moduleFilePath: moduleFilePath,
790
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
791
+ };
792
+ });
793
+ var parse = function parse(path) {
794
+ var _splitModuleFilePathA3 = splitModuleFilePathAndModulePath(path),
795
+ _splitModuleFilePathA4 = _slicedToArray(_splitModuleFilePathA3, 2),
796
+ moduleFilePath = _splitModuleFilePathA4[0],
797
+ modulePath = _splitModuleFilePathA4[1];
798
+ return {
799
+ moduleFilePath: moduleFilePath,
800
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
801
+ };
802
+ };
803
+ var isPrefix = function isPrefix(prefix, of) {
804
+ if (prefix.length > of.length) return false;
805
+ for (var i = 0; i < prefix.length; i++) {
806
+ if (prefix[i] !== of[i]) return false;
807
+ }
808
+ return true;
809
+ };
810
+ return {
811
+ wants: function wants(path) {
812
+ var at = parse(path);
813
+ return wanted.some(function (entry) {
814
+ return entry.moduleFilePath === at.moduleFilePath && entry.segments.length === at.segments.length && isPrefix(entry.segments, at.segments);
815
+ });
816
+ },
817
+ wantsUnder: function wantsUnder(path) {
818
+ var at = parse(path);
819
+ return wanted.some(function (entry) {
820
+ return entry.moduleFilePath === at.moduleFilePath && (
821
+ // Either direction: a wanted path below `path` means recurse into it,
822
+ // and a wanted path ABOVE it means the whole subtree was asked for.
823
+ isPrefix(at.segments, entry.segments) || isPrefix(entry.segments, at.segments));
824
+ });
825
+ }
826
+ };
827
+ }
828
+
829
+ // TODO: improve this so that we do not get RawString and string, only string. Are there other things?
830
+
509
831
  // Identity symbols are registered in the global Symbol registry so that
510
832
  // multiple bundled copies of @valbuild/core (e.g. the editor SPA bundle vs.
511
833
  // the host Next.js bundle) resolve to the same Symbol instance. Without
@@ -618,28 +940,6 @@ function unsafeCreateSourcePath(path, itemKey) {
618
940
  return "".concat(path).concat(Internal.ModuleFilePathSep).concat(JSON.stringify(itemKey));
619
941
  }
620
942
 
621
- function ownKeys(e, r) {
622
- var t = Object.keys(e);
623
- if (Object.getOwnPropertySymbols) {
624
- var o = Object.getOwnPropertySymbols(e);
625
- r && (o = o.filter(function (r) {
626
- return Object.getOwnPropertyDescriptor(e, r).enumerable;
627
- })), t.push.apply(t, o);
628
- }
629
- return t;
630
- }
631
- function _objectSpread2(e) {
632
- for (var r = 1; r < arguments.length; r++) {
633
- var t = null != arguments[r] ? arguments[r] : {};
634
- r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
635
- _defineProperty(e, r, t[r]);
636
- }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
637
- Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
638
- });
639
- }
640
- return e;
641
- }
642
-
643
943
  var StringSchema = /*#__PURE__*/function (_Schema) {
644
944
  function StringSchema(options) {
645
945
  var _this;
@@ -670,10 +970,13 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
670
970
  /**
671
971
  * Describe this field.
672
972
  *
673
- * The description is shown next to the field's label in the Val editor, so
674
- * it is where you say what an editor needs to know but the field name cannot
675
- * carry. It also travels in the serialized schema, which is what the AI
676
- * assistant and the MCP tools read.
973
+ * The description is INPUT HELP: it is shown where this field's value is
974
+ * entered — beside its input in the Val editor, and for a record's key
975
+ * schema in every form that asks for a key — so it is where you say what an
976
+ * editor needs to know to fill it in RIGHT, which the field name cannot
977
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
978
+ * read somewhere else. The description also travels in the serialized
979
+ * schema, which is what the AI assistant and the MCP tools read.
677
980
  *
678
981
  * Pass `null` to clear a description set earlier.
679
982
  *
@@ -1010,11 +1313,6 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
1010
1313
  * is a `render`, which travels in the serialized schema instead of through
1011
1314
  * this pipeline.
1012
1315
  */
1013
- }, {
1014
- key: "executePreview",
1015
- value: function executePreview() {
1016
- return {};
1017
- }
1018
1316
  }]);
1019
1317
  }(Schema);
1020
1318
  var string = function string(options) {
@@ -1047,10 +1345,13 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1047
1345
  /**
1048
1346
  * Describe this field.
1049
1347
  *
1050
- * The description is shown next to the field's label in the Val editor, so
1051
- * it is where you say what an editor needs to know but the field name cannot
1052
- * carry. It also travels in the serialized schema, which is what the AI
1053
- * assistant and the MCP tools read.
1348
+ * The description is INPUT HELP: it is shown where this field's value is
1349
+ * entered — beside its input in the Val editor, and for a record's key
1350
+ * schema in every form that asks for a key — so it is where you say what an
1351
+ * editor needs to know to fill it in RIGHT, which the field name cannot
1352
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
1353
+ * read somewhere else. The description also travels in the serialized
1354
+ * schema, which is what the AI assistant and the MCP tools read.
1054
1355
  *
1055
1356
  * Pass `null` to clear a description set earlier.
1056
1357
  *
@@ -1362,7 +1663,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1362
1663
  }
1363
1664
  }, {
1364
1665
  key: "executePreview",
1365
- value: function executePreview(sourcePath, src, scope) {
1666
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
1366
1667
  var res = {};
1367
1668
  if (src === null) {
1368
1669
  return res;
@@ -1379,11 +1680,14 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1379
1680
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
1380
1681
  continue;
1381
1682
  }
1382
- var itemResult = this.items[_key3]["executePreview"](subPath, itemSrc, scope);
1383
- for (var keyS in itemResult) {
1384
- var _key4 = keyS;
1385
- res[_key4] = itemResult[_key4];
1386
- }
1683
+ mergePreviewInto(res, this.items[_key3]["executePreview"](subPath, itemSrc, scope));
1684
+ }
1685
+ // An object reifies no rows of its own, so the only thing it adds is what
1686
+ // IT is called — which nothing else can supply for a field of an object.
1687
+ // Its own items are NOT rows, so the flag stops here rather than travelling
1688
+ // down with the recursion above.
1689
+ if (!selfIsReifiedByParent) {
1690
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
1387
1691
  }
1388
1692
  return res;
1389
1693
  }
@@ -1424,10 +1728,13 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1424
1728
  /**
1425
1729
  * Describe this field.
1426
1730
  *
1427
- * The description is shown next to the field's label in the Val editor, so
1428
- * it is where you say what an editor needs to know but the field name cannot
1429
- * carry. It also travels in the serialized schema, which is what the AI
1430
- * assistant and the MCP tools read.
1731
+ * The description is INPUT HELP: it is shown where this field's value is
1732
+ * entered — beside its input in the Val editor, and for a record's key
1733
+ * schema in every form that asks for a key — so it is where you say what an
1734
+ * editor needs to know to fill it in RIGHT, which the field name cannot
1735
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
1736
+ * read somewhere else. The description also travels in the serialized
1737
+ * schema, which is what the AI assistant and the MCP tools read.
1431
1738
  *
1432
1739
  * Pass `null` to clear a description set earlier.
1433
1740
  *
@@ -1586,7 +1893,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1586
1893
  }
1587
1894
  }, {
1588
1895
  key: "executePreview",
1589
- value: function executePreview(sourcePath, src, scope) {
1896
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
1590
1897
  var res = {};
1591
1898
  if (src === null) {
1592
1899
  return res;
@@ -1601,11 +1908,10 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1601
1908
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
1602
1909
  continue;
1603
1910
  }
1604
- var itemResult = this.item["executePreview"](subPath, itemSrc, scope);
1605
- for (var keyS in itemResult) {
1606
- var _key = keyS;
1607
- res[_key] = itemResult[_key];
1608
- }
1911
+ mergePreviewInto(res,
1912
+ // `true`: this item is a ROW, and its preview is the closure reified
1913
+ // into `rows` below. See `executePreview` on `Schema`.
1914
+ this.item["executePreview"](subPath, itemSrc, scope, true));
1609
1915
  }
1610
1916
  // The rows preview comes from the ITEM schema's own `preview` — the
1611
1917
  // container just runs it per row. Asked as a fact rather than by running
@@ -1652,13 +1958,29 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1652
1958
  };
1653
1959
  }
1654
1960
  }
1655
- res[sourcePath] = {
1961
+ var rows = {};
1962
+ rows[sourcePath] = {
1656
1963
  status: "success",
1657
1964
  data: {
1658
- parent: "array",
1659
- items: items
1965
+ rows: {
1966
+ parent: "array",
1967
+ items: items
1968
+ }
1660
1969
  }
1661
1970
  };
1971
+ mergePreviewInto(res, rows);
1972
+ }
1973
+ // ...and what the LIST ITSELF is called, which is a different closure on a
1974
+ // different schema: `s.array(section.preview(...)).preview(...)` previews
1975
+ // its rows AND names itself for when it is nested in something. Merged
1976
+ // rather than assigned, or one of the two would win. See `PreviewNode`.
1977
+ //
1978
+ // Unless this list is itself a ROW of an outer array or record, which has
1979
+ // already run this very closure to reify it — a container both PASSES the
1980
+ // flag to its items and RECEIVES it as one, and honouring only the first
1981
+ // half ran `s.array(s.array(x).preview(...))`'s closure twice per row.
1982
+ if (!selfIsReifiedByParent) {
1983
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
1662
1984
  }
1663
1985
  return res;
1664
1986
  }
@@ -1761,10 +2083,13 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1761
2083
  /**
1762
2084
  * Describe this field.
1763
2085
  *
1764
- * The description is shown next to the field's label in the Val editor, so
1765
- * it is where you say what an editor needs to know but the field name cannot
1766
- * carry. It also travels in the serialized schema, which is what the AI
1767
- * assistant and the MCP tools read.
2086
+ * The description is INPUT HELP: it is shown where this field's value is
2087
+ * entered — beside its input in the Val editor, and for a record's key
2088
+ * schema in every form that asks for a key — so it is where you say what an
2089
+ * editor needs to know to fill it in RIGHT, which the field name cannot
2090
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
2091
+ * read somewhere else. The description also travels in the serialized
2092
+ * schema, which is what the AI assistant and the MCP tools read.
1768
2093
  *
1769
2094
  * Pass `null` to clear a description set earlier.
1770
2095
  *
@@ -1953,11 +2278,6 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1953
2278
  description: this.description
1954
2279
  };
1955
2280
  }
1956
- }, {
1957
- key: "executePreview",
1958
- value: function executePreview() {
1959
- return {};
1960
- }
1961
2281
  }]);
1962
2282
  }(Schema);
1963
2283
  var literal = function literal(value) {
@@ -2013,10 +2333,13 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2013
2333
  /**
2014
2334
  * Describe this field.
2015
2335
  *
2016
- * The description is shown next to the field's label in the Val editor, so it is
2017
- * where you say what an editor needs to know but the field name cannot carry. It
2018
- * also travels in the serialized schema, which is what the AI assistant and the
2019
- * MCP tools read.
2336
+ * The description is INPUT HELP: it is shown where this field's value is
2337
+ * entered — beside its input in the Val editor, and for a record's key
2338
+ * schema in every form that asks for a key — so it is where you say what an
2339
+ * editor needs to know to fill it in RIGHT, which the field name cannot
2340
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
2341
+ * read somewhere else. The description also travels in the serialized
2342
+ * schema, which is what the AI assistant and the MCP tools read.
2020
2343
  *
2021
2344
  * Pass `null` to clear a description set earlier.
2022
2345
  *
@@ -2469,7 +2792,7 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2469
2792
  }
2470
2793
  }, {
2471
2794
  key: "executePreview",
2472
- value: function executePreview(sourcePath, src, scope) {
2795
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
2473
2796
  var res = {};
2474
2797
  if (src === null) {
2475
2798
  return res;
@@ -2494,10 +2817,16 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2494
2817
  return false;
2495
2818
  });
2496
2819
  if (thisSchema) {
2497
- var itemResult = thisSchema["executePreview"](sourcePath, src, scope);
2498
- for (var keyS in itemResult) {
2499
- var _key2 = keyS;
2500
- res[_key2] = itemResult[_key2];
2820
+ // The variant owns the path — see the note on `executeCustomValidateAt`.
2821
+ // It contributes a `self` of its own here; the union's comes second and
2822
+ // wins, because `executePreviewItem` already encodes the precedence (the
2823
+ // union's own closure if it has one, else the matched variant's) and
2824
+ // there must not be a second copy of that rule.
2825
+ // The variant SHARES this path, so the flag passes straight through: a
2826
+ // union that is a row is a variant that is a row.
2827
+ mergePreviewInto(res, thisSchema["executePreview"](sourcePath, src, scope, selfIsReifiedByParent));
2828
+ if (!selfIsReifiedByParent) {
2829
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
2501
2830
  }
2502
2831
  return res;
2503
2832
  }
@@ -2510,8 +2839,8 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2510
2839
  }]);
2511
2840
  }(Schema);
2512
2841
  var discriminatedUnion = function discriminatedUnion(key) {
2513
- for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key3 = 1; _key3 < _len; _key3++) {
2514
- objects[_key3 - 1] = arguments[_key3];
2842
+ for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key2 = 1; _key2 < _len; _key2++) {
2843
+ objects[_key2 - 1] = arguments[_key2];
2515
2844
  }
2516
2845
  return new DiscriminatedUnionSchema(key, objects);
2517
2846
  };
@@ -3488,10 +3817,13 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3488
3817
  /**
3489
3818
  * Describe this field.
3490
3819
  *
3491
- * The description is shown next to the field's label in the Val editor, so
3492
- * it is where you say what an editor needs to know but the field name cannot
3493
- * carry. It also travels in the serialized schema, which is what the AI
3494
- * assistant and the MCP tools read.
3820
+ * The description is INPUT HELP: it is shown where this field's value is
3821
+ * entered — beside its input in the Val editor, and for a record's key
3822
+ * schema in every form that asks for a key — so it is where you say what an
3823
+ * editor needs to know to fill it in RIGHT, which the field name cannot
3824
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
3825
+ * read somewhere else. The description also travels in the serialized
3826
+ * schema, which is what the AI assistant and the MCP tools read.
3495
3827
  *
3496
3828
  * Pass `null` to clear a description set earlier.
3497
3829
  *
@@ -3872,11 +4204,6 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3872
4204
  description: this.description
3873
4205
  };
3874
4206
  }
3875
- }, {
3876
- key: "executePreview",
3877
- value: function executePreview() {
3878
- return {};
3879
- }
3880
4207
  }]);
3881
4208
  }(Schema);
3882
4209
 
@@ -3932,10 +4259,13 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3932
4259
  /**
3933
4260
  * Describe this field.
3934
4261
  *
3935
- * The description is shown next to the field's label in the Val editor, so
3936
- * it is where you say what an editor needs to know but the field name cannot
3937
- * carry. It also travels in the serialized schema, which is what the AI
3938
- * assistant and the MCP tools read.
4262
+ * The description is INPUT HELP: it is shown where this field's value is
4263
+ * entered — beside its input in the Val editor, and for a record's key
4264
+ * schema in every form that asks for a key — so it is where you say what an
4265
+ * editor needs to know to fill it in RIGHT, which the field name cannot
4266
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
4267
+ * read somewhere else. The description also travels in the serialized
4268
+ * schema, which is what the AI assistant and the MCP tools read.
3939
4269
  *
3940
4270
  * Pass `null` to clear a description set earlier.
3941
4271
  *
@@ -4164,11 +4494,6 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
4164
4494
  description: this.description
4165
4495
  };
4166
4496
  }
4167
- }, {
4168
- key: "executePreview",
4169
- value: function executePreview() {
4170
- return {};
4171
- }
4172
4497
  }]);
4173
4498
  }(Schema);
4174
4499
  var route = function route(options) {
@@ -4201,10 +4526,13 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4201
4526
  /**
4202
4527
  * Describe this field.
4203
4528
  *
4204
- * The description is shown next to the field's label in the Val editor, so
4205
- * it is where you say what an editor needs to know but the field name cannot
4206
- * carry. It also travels in the serialized schema, which is what the AI
4207
- * assistant and the MCP tools read.
4529
+ * The description is INPUT HELP: it is shown where this field's value is
4530
+ * entered — beside its input in the Val editor, and for a record's key
4531
+ * schema in every form that asks for a key — so it is where you say what an
4532
+ * editor needs to know to fill it in RIGHT, which the field name cannot
4533
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
4534
+ * read somewhere else. The description also travels in the serialized
4535
+ * schema, which is what the AI assistant and the MCP tools read.
4208
4536
  *
4209
4537
  * Pass `null` to clear a description set earlier.
4210
4538
  *
@@ -4850,11 +5178,6 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4850
5178
  description: this.description
4851
5179
  };
4852
5180
  }
4853
- }, {
4854
- key: "executePreview",
4855
- value: function executePreview() {
4856
- return {};
4857
- }
4858
5181
  }]);
4859
5182
  }(Schema);
4860
5183
  var richtext = function richtext(options) {
@@ -5369,10 +5692,13 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5369
5692
  /**
5370
5693
  * Describe this field.
5371
5694
  *
5372
- * The description is shown next to the field's label in the Val editor, so
5373
- * it is where you say what an editor needs to know but the field name cannot
5374
- * carry. It also travels in the serialized schema, which is what the AI
5375
- * assistant and the MCP tools read.
5695
+ * The description is INPUT HELP: it is shown where this field's value is
5696
+ * entered — beside its input in the Val editor, and for a record's key
5697
+ * schema in every form that asks for a key — so it is where you say what an
5698
+ * editor needs to know to fill it in RIGHT, which the field name cannot
5699
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
5700
+ * read somewhere else. The description also travels in the serialized
5701
+ * schema, which is what the AI assistant and the MCP tools read.
5376
5702
  *
5377
5703
  * Pass `null` to clear a description set earlier.
5378
5704
  *
@@ -6165,7 +6491,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6165
6491
  }
6166
6492
  }, {
6167
6493
  key: "executePreview",
6168
- value: function executePreview(sourcePath, src, scope) {
6494
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
6169
6495
  var res = {};
6170
6496
  if (src === null) {
6171
6497
  return res;
@@ -6190,11 +6516,10 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6190
6516
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
6191
6517
  continue;
6192
6518
  }
6193
- var itemResult = this.item["executePreview"](subPath, itemSrc, scope);
6194
- for (var keyS in itemResult) {
6195
- var _key3 = keyS;
6196
- res[_key3] = itemResult[_key3];
6197
- }
6519
+ mergePreviewInto(res,
6520
+ // `true`: this item is a ROW, and its preview is the closure reified
6521
+ // into `rows` below. See `executePreview` on `Schema`.
6522
+ this.item["executePreview"](subPath, itemSrc, scope, true));
6198
6523
  }
6199
6524
  // The entries preview comes from the ITEM schema's own `preview` — the
6200
6525
  // container just runs it per entry. Asked as a fact rather than by running
@@ -6206,7 +6531,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6206
6531
  var items = [];
6207
6532
  for (var _i2 = 0, _Object$entries = Object.entries(src); _i2 < _Object$entries.length; _i2++) {
6208
6533
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
6209
- _key4 = _Object$entries$_i[0],
6534
+ _key3 = _Object$entries$_i[0],
6210
6535
  val = _Object$entries$_i[1];
6211
6536
  if (isJson(val)) {
6212
6537
  continue; // as above: nothing to select from an un-loaded entry
@@ -6214,7 +6539,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6214
6539
  if (val === null || val === undefined) {
6215
6540
  continue;
6216
6541
  }
6217
- if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key4))) {
6542
+ if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key3))) {
6218
6543
  continue;
6219
6544
  }
6220
6545
  // Per KEY, not per record: the closure is user code, and one entry whose
@@ -6226,26 +6551,37 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6226
6551
  var title = _item.title,
6227
6552
  subtitle = _item.subtitle,
6228
6553
  image = _item.image;
6229
- items.push([_key4, {
6554
+ items.push([_key3, {
6230
6555
  title: title,
6231
6556
  subtitle: subtitle,
6232
6557
  image: image
6233
6558
  }]);
6234
6559
  }
6235
6560
  } catch (e) {
6236
- res[unsafeCreateSourcePath(sourcePath, _key4)] = {
6561
+ res[unsafeCreateSourcePath(sourcePath, _key3)] = {
6237
6562
  status: "error",
6238
6563
  message: e instanceof Error ? e.message : "Unknown error"
6239
6564
  };
6240
6565
  }
6241
6566
  }
6242
- res[sourcePath] = {
6567
+ var rows = {};
6568
+ rows[sourcePath] = {
6243
6569
  status: "success",
6244
6570
  data: {
6245
- parent: "record",
6246
- items: items
6571
+ rows: {
6572
+ parent: "record",
6573
+ items: items
6574
+ }
6247
6575
  }
6248
6576
  };
6577
+ mergePreviewInto(res, rows);
6578
+ }
6579
+ // ...and what the RECORD ITSELF is called — a different closure on a
6580
+ // different schema. Skipped when this record is itself a ROW, whose
6581
+ // closure the outer container has already run. See the same block in
6582
+ // `array`, and `PreviewNode`.
6583
+ if (!selfIsReifiedByParent) {
6584
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
6249
6585
  }
6250
6586
  return res;
6251
6587
  }
@@ -6370,6 +6706,16 @@ function record(keyOrSchema, schema) {
6370
6706
  * written without removing the key.
6371
6707
  */
6372
6708
 
6709
+ /**
6710
+ * How the Studio behaves for the people editing this project.
6711
+ *
6712
+ * Not how it looks — that is {@link ThemeSettingsSource} — and not one person's
6713
+ * own preferences, which stay in their browser. This is the project's word on
6714
+ * what its editors are offered, which is why it is content: a team that has
6715
+ * decided something about the way they work should not need a developer and a
6716
+ * deploy to say so.
6717
+ */
6718
+
6373
6719
  /**
6374
6720
  * How the Studio looks in this project.
6375
6721
  *
@@ -6737,11 +7083,6 @@ var LocaleSchema = /*#__PURE__*/function (_Schema) {
6737
7083
  description: this.description
6738
7084
  };
6739
7085
  }
6740
- }, {
6741
- key: "executePreview",
6742
- value: function executePreview() {
6743
- return {};
6744
- }
6745
7086
  }]);
6746
7087
  }(Schema);
6747
7088
 
@@ -6786,10 +7127,13 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6786
7127
  /**
6787
7128
  * Describe this field.
6788
7129
  *
6789
- * The description is shown next to the field's label in the Val editor, so
6790
- * it is where you say what an editor needs to know but the field name cannot
6791
- * carry. It also travels in the serialized schema, which is what the AI
6792
- * assistant and the MCP tools read.
7130
+ * The description is INPUT HELP: it is shown where this field's value is
7131
+ * entered — beside its input in the Val editor, and for a record's key
7132
+ * schema in every form that asks for a key — so it is where you say what an
7133
+ * editor needs to know to fill it in RIGHT, which the field name cannot
7134
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
7135
+ * read somewhere else. The description also travels in the serialized
7136
+ * schema, which is what the AI assistant and the MCP tools read.
6793
7137
  *
6794
7138
  * Pass `null` to clear a description set earlier.
6795
7139
  *
@@ -6963,11 +7307,6 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6963
7307
  description: this.description
6964
7308
  };
6965
7309
  }
6966
- }, {
6967
- key: "executePreview",
6968
- value: function executePreview() {
6969
- return {};
6970
- }
6971
7310
  }]);
6972
7311
  }(Schema);
6973
7312
  var _boolean = function _boolean() {
@@ -7480,10 +7819,13 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
7480
7819
  /**
7481
7820
  * Describe this field.
7482
7821
  *
7483
- * The description is shown next to the field's label in the Val editor, so
7484
- * it is where you say what an editor needs to know but the field name cannot
7485
- * carry. It also travels in the serialized schema, which is what the AI
7486
- * assistant and the MCP tools read.
7822
+ * The description is INPUT HELP: it is shown where this field's value is
7823
+ * entered — beside its input in the Val editor, and for a record's key
7824
+ * schema in every form that asks for a key — so it is where you say what an
7825
+ * editor needs to know to fill it in RIGHT, which the field name cannot
7826
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
7827
+ * read somewhere else. The description also travels in the serialized
7828
+ * schema, which is what the AI assistant and the MCP tools read.
7487
7829
  *
7488
7830
  * Pass `null` to clear a description set earlier.
7489
7831
  *
@@ -7707,11 +8049,6 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
7707
8049
  description: this.description
7708
8050
  };
7709
8051
  }
7710
- }, {
7711
- key: "executePreview",
7712
- value: function executePreview() {
7713
- return {};
7714
- }
7715
8052
  }]);
7716
8053
  }(Schema);
7717
8054
  var color = function color(options) {
@@ -7762,10 +8099,13 @@ var EnumSchema = /*#__PURE__*/function (_Schema) {
7762
8099
  /**
7763
8100
  * Describe this field.
7764
8101
  *
7765
- * The description is shown next to the field's label in the Val editor, so it is
7766
- * where you say what an editor needs to know but the field name cannot carry. It
7767
- * also travels in the serialized schema, which is what the AI assistant and the
7768
- * MCP tools read.
8102
+ * The description is INPUT HELP: it is shown where this field's value is
8103
+ * entered — beside its input in the Val editor, and for a record's key
8104
+ * schema in every form that asks for a key — so it is where you say what an
8105
+ * editor needs to know to fill it in RIGHT, which the field name cannot
8106
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
8107
+ * read somewhere else. The description also travels in the serialized
8108
+ * schema, which is what the AI assistant and the MCP tools read.
7769
8109
  *
7770
8110
  * Pass `null` to clear a description set earlier.
7771
8111
  *
@@ -7978,12 +8318,6 @@ var EnumSchema = /*#__PURE__*/function (_Schema) {
7978
8318
  description: this.description
7979
8319
  };
7980
8320
  }
7981
- }, {
7982
- key: "executePreview",
7983
- value: function executePreview() {
7984
- // A leaf: nothing below it to preview.
7985
- return {};
7986
- }
7987
8321
  }]);
7988
8322
  }(Schema);
7989
8323
 
@@ -8323,7 +8657,7 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
8323
8657
  }
8324
8658
  }, {
8325
8659
  key: "executePreview",
8326
- value: function executePreview(sourcePath, src, scope) {
8660
+ value: function executePreview(sourcePath, src, scope, selfIsReifiedByParent) {
8327
8661
  var res = {};
8328
8662
  if (src === null) {
8329
8663
  return res;
@@ -8337,11 +8671,14 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
8337
8671
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
8338
8672
  continue;
8339
8673
  }
8340
- var itemResult = this.items[_key4]["executePreview"](subPath, itemSrc, scope);
8341
- for (var keyS in itemResult) {
8342
- var _key5 = keyS;
8343
- res[_key5] = itemResult[_key5];
8344
- }
8674
+ mergePreviewInto(res, this.items[_key4]["executePreview"](subPath, itemSrc, scope));
8675
+ }
8676
+ // An object reifies no rows of its own, so the only thing it adds is what
8677
+ // IT is called — which nothing else can supply for a field of an object.
8678
+ // Its own items are NOT rows, so the flag stops here rather than travelling
8679
+ // down with the recursion above.
8680
+ if (!selfIsReifiedByParent) {
8681
+ mergePreviewInto(res, this.executeSelfPreview(sourcePath, src, scope));
8345
8682
  }
8346
8683
  return res;
8347
8684
  }
@@ -8404,6 +8741,9 @@ function settings() {
8404
8741
  dir: THEME_LOGO_DIRECTORY
8405
8742
  }).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.")
8406
8743
  }),
8744
+ studio: new SettingsSchema({
8745
+ 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.")
8746
+ }),
8407
8747
  locales: new SettingsSchema({
8408
8748
  // Nullable because the source type is, and because the Studio writes
8409
8749
  // `null` for every sibling the first time a section is created.
@@ -8581,10 +8921,25 @@ function resolvePath(path, valModule, schema) {
8581
8921
  if (_typeof(resolvedSource) !== "object" && !Array.isArray(resolvedSource)) {
8582
8922
  throw Error("Schema type error: expected source to be type of record, but got ".concat(_typeof(resolvedSource)));
8583
8923
  }
8584
- if (!resolvedSource[part]) {
8924
+ // PRESENCE, not truthiness. An entry that exists and is falsy is an
8925
+ // entry: `null` for a declared key nobody has written yet, but also `""`,
8926
+ // `0` and `false` in any record at all. Testing the value made every one
8927
+ // of those report as a key the record does not have, so nothing could
8928
+ // resolve them — not the Studio, not the language server, not a fix.
8929
+ //
8930
+ // A record source that is itself `null` keeps resolving as `null`, one
8931
+ // part per level, the way the object branch below does: the path still
8932
+ // names a real place in the schema, and the caller wants the schema
8933
+ // there in order to say that nothing is written yet.
8934
+ //
8935
+ // `hasOwnProperty`, never `in`: `in` walks the prototype chain, so
8936
+ // `"toString"` would resolve on every record and hand back
8937
+ // `Object.prototype.toString` as if it were Source. Same reason
8938
+ // `patch/json.ts` guards every key it reads.
8939
+ if (resolvedSource !== null && !Object.prototype.hasOwnProperty.call(resolvedSource, part)) {
8585
8940
  throw Error("Invalid path: record source did not have key ".concat(part, " from path: ").concat(path));
8586
8941
  }
8587
- resolvedSource = resolvedSource[part];
8942
+ resolvedSource = resolvedSource === null ? resolvedSource : resolvedSource[part];
8588
8943
  resolvedSchema = resolvedSchema instanceof RecordSchema ? (_resolvedSchema2 = resolvedSchema) === null || _resolvedSchema2 === void 0 ? void 0 : _resolvedSchema2["item"] : resolvedSchema.item;
8589
8944
  // A `.jsonValues()` entry value is a lazily-loaded JsonSource marker. We
8590
8945
  // can resolve the entry itself (schema is now `item`), but we cannot
@@ -8786,7 +9141,15 @@ function safeResolvePath(path, valModule, schema) {
8786
9141
  }
8787
9142
  };
8788
9143
  }
8789
- if (resolvedSource[part] === undefined) {
9144
+ // A `null` record keeps resolving as `null`, as the object branch below
9145
+ // does — see the same note in `resolvePath`. Without it, indexing `null`
9146
+ // threw a TypeError out of the function whose whole point is not to
9147
+ // throw.
9148
+ //
9149
+ // Own properties only. Testing `resolvedSource[part] === undefined` let
9150
+ // an inherited name through — `"toString"` is not undefined on any
9151
+ // object — so the walk continued into `Object.prototype`.
9152
+ if (resolvedSource !== null && !Object.prototype.hasOwnProperty.call(resolvedSource, part)) {
8790
9153
  return {
8791
9154
  v: {
8792
9155
  status: "source-undefined",
@@ -8796,7 +9159,7 @@ function safeResolvePath(path, valModule, schema) {
8796
9159
  }
8797
9160
  };
8798
9161
  }
8799
- resolvedSource = resolvedSource[part];
9162
+ resolvedSource = resolvedSource === null ? resolvedSource : resolvedSource[part];
8800
9163
  resolvedSchema = resolvedSchema instanceof RecordSchema ? (_resolvedSchema4 = resolvedSchema) === null || _resolvedSchema4 === void 0 ? void 0 : _resolvedSchema4["item"] : resolvedSchema.item;
8801
9164
  // jsonValues entry: cannot descend deeper until the backing `*.val.json`
8802
9165
  // content is loaded into the source tree.
@@ -9055,10 +9418,13 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
9055
9418
  /**
9056
9419
  * Describe this field.
9057
9420
  *
9058
- * The description is shown next to the field's label in the Val editor, so
9059
- * it is where you say what an editor needs to know but the field name cannot
9060
- * carry. It also travels in the serialized schema, which is what the AI
9061
- * assistant and the MCP tools read.
9421
+ * The description is INPUT HELP: it is shown where this field's value is
9422
+ * entered — beside its input in the Val editor, and for a record's key
9423
+ * schema in every form that asks for a key — so it is where you say what an
9424
+ * editor needs to know to fill it in RIGHT, which the field name cannot
9425
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
9426
+ * read somewhere else. The description also travels in the serialized
9427
+ * schema, which is what the AI assistant and the MCP tools read.
9062
9428
  *
9063
9429
  * Pass `null` to clear a description set earlier.
9064
9430
  *
@@ -9282,11 +9648,6 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
9282
9648
  description: this.description
9283
9649
  };
9284
9650
  }
9285
- }, {
9286
- key: "executePreview",
9287
- value: function executePreview() {
9288
- return {};
9289
- }
9290
9651
  }]);
9291
9652
  }(Schema);
9292
9653
  var number = function number(options) {
@@ -9399,10 +9760,13 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
9399
9760
  /**
9400
9761
  * Describe this field.
9401
9762
  *
9402
- * The description is shown next to the field's label in the Val editor, so
9403
- * it is where you say what an editor needs to know but the field name cannot
9404
- * carry. It also travels in the serialized schema, which is what the AI
9405
- * assistant and the MCP tools read.
9763
+ * The description is INPUT HELP: it is shown where this field's value is
9764
+ * entered — beside its input in the Val editor, and for a record's key
9765
+ * schema in every form that asks for a key — so it is where you say what an
9766
+ * editor needs to know to fill it in RIGHT, which the field name cannot
9767
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
9768
+ * read somewhere else. The description also travels in the serialized
9769
+ * schema, which is what the AI assistant and the MCP tools read.
9406
9770
  *
9407
9771
  * Pass `null` to clear a description set earlier.
9408
9772
  *
@@ -9684,11 +10048,6 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
9684
10048
  description: this.description
9685
10049
  };
9686
10050
  }
9687
- }, {
9688
- key: "executePreview",
9689
- value: function executePreview() {
9690
- return {};
9691
- }
9692
10051
  }]);
9693
10052
  }(Schema);
9694
10053
  var keyOf = function keyOf(valModule) {
@@ -9745,10 +10104,13 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
9745
10104
  /**
9746
10105
  * Describe this field.
9747
10106
  *
9748
- * The description is shown next to the field's label in the Val editor, so
9749
- * it is where you say what an editor needs to know but the field name cannot
9750
- * carry. It also travels in the serialized schema, which is what the AI
9751
- * assistant and the MCP tools read.
10107
+ * The description is INPUT HELP: it is shown where this field's value is
10108
+ * entered — beside its input in the Val editor, and for a record's key
10109
+ * schema in every form that asks for a key — so it is where you say what an
10110
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10111
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10112
+ * read somewhere else. The description also travels in the serialized
10113
+ * schema, which is what the AI assistant and the MCP tools read.
9752
10114
  *
9753
10115
  * Pass `null` to clear a description set earlier.
9754
10116
  *
@@ -10089,11 +10451,6 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10089
10451
  description: this.description
10090
10452
  };
10091
10453
  }
10092
- }, {
10093
- key: "executePreview",
10094
- value: function executePreview() {
10095
- return {};
10096
- }
10097
10454
  }]);
10098
10455
  }(Schema);
10099
10456
 
@@ -10186,10 +10543,13 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
10186
10543
  /**
10187
10544
  * Describe this field.
10188
10545
  *
10189
- * The description is shown next to the field's label in the Val editor, so
10190
- * it is where you say what an editor needs to know but the field name cannot
10191
- * carry. It also travels in the serialized schema, which is what the AI
10192
- * assistant and the MCP tools read.
10546
+ * The description is INPUT HELP: it is shown where this field's value is
10547
+ * entered — beside its input in the Val editor, and for a record's key
10548
+ * schema in every form that asks for a key — so it is where you say what an
10549
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10550
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10551
+ * read somewhere else. The description also travels in the serialized
10552
+ * schema, which is what the AI assistant and the MCP tools read.
10193
10553
  *
10194
10554
  * Pass `null` to clear a description set earlier.
10195
10555
  *
@@ -10434,11 +10794,6 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
10434
10794
  description: this.description
10435
10795
  };
10436
10796
  }
10437
- }, {
10438
- key: "executePreview",
10439
- value: function executePreview() {
10440
- return {};
10441
- }
10442
10797
  }]);
10443
10798
  }(Schema);
10444
10799
  var date = function date(options) {
@@ -10471,10 +10826,13 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
10471
10826
  /**
10472
10827
  * Describe this field.
10473
10828
  *
10474
- * The description is shown next to the field's label in the Val editor, so
10475
- * it is where you say what an editor needs to know but the field name cannot
10476
- * carry. It also travels in the serialized schema, which is what the AI
10477
- * assistant and the MCP tools read.
10829
+ * The description is INPUT HELP: it is shown where this field's value is
10830
+ * entered — beside its input in the Val editor, and for a record's key
10831
+ * schema in every form that asks for a key — so it is where you say what an
10832
+ * editor needs to know to fill it in RIGHT, which the field name cannot
10833
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
10834
+ * read somewhere else. The description also travels in the serialized
10835
+ * schema, which is what the AI assistant and the MCP tools read.
10478
10836
  *
10479
10837
  * Pass `null` to clear a description set earlier.
10480
10838
  *
@@ -10774,11 +11132,6 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
10774
11132
  description: this.description
10775
11133
  };
10776
11134
  }
10777
- }, {
10778
- key: "executePreview",
10779
- value: function executePreview() {
10780
- return {};
10781
- }
10782
11135
  }]);
10783
11136
  }(Schema);
10784
11137
  var datetime = function datetime(options) {
@@ -10825,10 +11178,13 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
10825
11178
  /**
10826
11179
  * Describe this field.
10827
11180
  *
10828
- * The description is shown next to the field's label in the Val editor, so
10829
- * it is where you say what an editor needs to know but the field name cannot
10830
- * carry. It also travels in the serialized schema, which is what the AI
10831
- * assistant and the MCP tools read.
11181
+ * The description is INPUT HELP: it is shown where this field's value is
11182
+ * entered — beside its input in the Val editor, and for a record's key
11183
+ * schema in every form that asks for a key — so it is where you say what an
11184
+ * editor needs to know to fill it in RIGHT, which the field name cannot
11185
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
11186
+ * read somewhere else. The description also travels in the serialized
11187
+ * schema, which is what the AI assistant and the MCP tools read.
10832
11188
  *
10833
11189
  * Pass `null` to clear a description set earlier.
10834
11190
  *
@@ -11019,11 +11375,6 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
11019
11375
  description: this.description
11020
11376
  };
11021
11377
  }
11022
- }, {
11023
- key: "executePreview",
11024
- value: function executePreview() {
11025
- return {};
11026
- }
11027
11378
  }]);
11028
11379
  }(Schema);
11029
11380
  var code = function code(options) {
@@ -12597,142 +12948,31 @@ function deserializeSchemaImpl(serialized) {
12597
12948
  }
12598
12949
 
12599
12950
  /**
12600
- * A PREVIEW is how a VALUE is shown wherever a preview of it is needed — a row
12601
- * in a sortable list, a key in a reference dropdown, a search hit, a
12602
- * reference — which is everywhere the value is NAVIGABLE to rather than open.
12603
- * It is never how the field itself is edited: that is a RENDER (`render.ts`),
12604
- * which applies only when you are looking at the field. The two do not
12605
- * intersect — a schema can carry both, and each is read in its own places.
12606
- *
12607
- * A preview is declared on the schema of the VALUE being previewed:
12608
- *
12609
- * ```ts
12610
- * const author = s.object({ name: s.string() })
12611
- * .preview(({ val }) => ({ title: val.name }));
12612
- * const authors = s.array(author);
12613
- * ```
12614
- *
12615
- * The container reifies its rows by running each ITEM's preview closure — see
12616
- * {@link ArrayPreview} / {@link RecordPreview}. (Previews used to be declared
12617
- * on the container instead; a `.preview` on an array/record now previews the
12618
- * array/record itself as a value, for when IT is the item of something.)
12619
- */
12620
- /**
12621
- * What a preview shows for one value: the user's `preview` callback returns
12622
- * this, and the Studio draws a row from it.
12623
- *
12624
- * NB: {@link ArrayPreview} / {@link RecordPreview} name the DATA a container
12625
- * previews with. The Studio also has React components called `ArrayPreview` /
12626
- * `RecordPreview` (the fallback rendering of an array / record field, part of
12627
- * the `<Type>Preview` convention in `components/Preview.tsx`). They do not
12628
- * collide today because no file needs both - if one ever does, alias this type
12629
- * at the import rather than renaming that convention.
12630
- */
12631
-
12632
- /**
12633
- * What `.preview(...)` takes, on every schema: the value's own source in, a
12634
- * {@link PreviewItem} out. `NonNullable` because a container skips null items
12635
- * rather than previewing them.
12951
+ * THE RULE, and the only sentence that needs to be remembered:
12636
12952
  *
12637
- * Declared through method syntax deliberately (the same bivariance shape
12638
- * React's event handler types use): `nullable()` copies a schema's closure
12639
- * into the `Src | null` variant of the same class, and with a plain function
12640
- * type the checker cannot see that `NonNullable<Src | null>` IS
12641
- * `NonNullable<Src>` while `Src` is still a type parameter — every schema's
12642
- * `nullable()` would need a cast instead.
12643
- */
12644
-
12645
- /**
12646
- * Main preview type.
12953
+ * **`.describe()` is INPUT HELP and is shown wherever that field — or a
12954
+ * record's key — is being ENTERED; `.preview()` is a NAME and is shown
12955
+ * wherever the value is REFERRED TO rather than edited; `.render()` is
12956
+ * LAYOUT and applies only while the field is open in front of you.**
12647
12957
  *
12648
- * Deliberately not exported: `parent` is the discriminant, and every consumer
12649
- * narrows on it rather than on the union as a whole. There is no `layout` here
12650
- * on purpose - how a preview is laid out is the editor's business, not the
12651
- * schema's.
12652
- */
12653
-
12654
- /**
12655
- * Which paths a preview is being computed FOR.
12656
- *
12657
- * `executePreview` takes a whole module, so one request has always walked every
12658
- * node in it and run every `preview` closure - for `handboka`, with `preview` at
12659
- * two nested array levels, that is every chapter and every section to serve one
12660
- * visible row. A scope is what makes the walk proportional to what is being
12661
- * looked at instead of to the project.
12958
+ * The test that settles every case: **can the reader change something here?**
12959
+ * If yes it is a place for a description — the input beside a label, the key
12960
+ * box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
12961
+ * reference dropdown. If no, it is a place for a preview — a list row, a
12962
+ * reference once chosen, a search hit, a sitemap row, the heading of what you
12963
+ * navigated to.
12662
12964
  *
12663
- * Two questions, because a container and its items need different answers:
12664
- *
12665
- * - {@link PreviewScope.wants} - is a preview AT this exact path wanted? A
12666
- * container answers `true` here when the whole of it is being shown, and its
12667
- * preview is then computed in full.
12668
- * - {@link PreviewScope.wantsUnder} - could anything at or below this path be
12669
- * wanted? Recursion is pruned where this is `false`, and a container whose own
12670
- * path is not wanted but which has wanted descendants previews a WINDOW: only
12671
- * the items that were asked for. That is the case a single visible row is, and
12672
- * it is why {@link ArrayPreview} carries indices.
12965
+ * That is why a description is plain data on the serialized schema and a
12966
+ * preview is a closure: a description is true before any value exists and says
12967
+ * the same thing to everyone filling the field in, and a preview cannot exist
12968
+ * without the one value it names. So a description must never be used as a
12969
+ * subtitle — it would repeat one sentence under every row of a list — and a
12970
+ * preview must never be used as help text, because there is nothing to preview
12971
+ * until after the value has been entered.
12673
12972
  *
12674
- * Absent scope means the whole module, which is what every existing caller
12675
- * passes and what every existing caller got.
12973
+ * None of the three substitutes for another: a field with a perfect
12974
+ * description still previews as `#3` until someone writes the preview.
12676
12975
  */
12677
-
12678
- /**
12679
- * A scope covering exactly `paths` and their subtrees.
12680
- *
12681
- * Compares SEGMENTS rather than string prefixes. A source path's module path is
12682
- * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
12683
- * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
12684
- * dot or a quote - `"title"` against `"titles"` being the cheapest example.
12685
- * Segments are parsed once here, not per comparison.
12686
- */
12687
- function previewScope(paths) {
12688
- var wanted = paths.map(function (path) {
12689
- var _splitModuleFilePathA = splitModuleFilePathAndModulePath(path),
12690
- _splitModuleFilePathA2 = _slicedToArray(_splitModuleFilePathA, 2),
12691
- moduleFilePath = _splitModuleFilePathA2[0],
12692
- modulePath = _splitModuleFilePathA2[1];
12693
- return {
12694
- moduleFilePath: moduleFilePath,
12695
- segments: modulePath === "" ? [] : splitModulePath(modulePath)
12696
- };
12697
- });
12698
- var parse = function parse(path) {
12699
- var _splitModuleFilePathA3 = splitModuleFilePathAndModulePath(path),
12700
- _splitModuleFilePathA4 = _slicedToArray(_splitModuleFilePathA3, 2),
12701
- moduleFilePath = _splitModuleFilePathA4[0],
12702
- modulePath = _splitModuleFilePathA4[1];
12703
- return {
12704
- moduleFilePath: moduleFilePath,
12705
- segments: modulePath === "" ? [] : splitModulePath(modulePath)
12706
- };
12707
- };
12708
- var isPrefix = function isPrefix(prefix, of) {
12709
- if (prefix.length > of.length) return false;
12710
- for (var i = 0; i < prefix.length; i++) {
12711
- if (prefix[i] !== of[i]) return false;
12712
- }
12713
- return true;
12714
- };
12715
- return {
12716
- wants: function wants(path) {
12717
- var at = parse(path);
12718
- return wanted.some(function (entry) {
12719
- return entry.moduleFilePath === at.moduleFilePath && entry.segments.length === at.segments.length && isPrefix(entry.segments, at.segments);
12720
- });
12721
- },
12722
- wantsUnder: function wantsUnder(path) {
12723
- var at = parse(path);
12724
- return wanted.some(function (entry) {
12725
- return entry.moduleFilePath === at.moduleFilePath && (
12726
- // Either direction: a wanted path below `path` means recurse into it,
12727
- // and a wanted path ABOVE it means the whole subtree was asked for.
12728
- isPrefix(at.segments, entry.segments) || isPrefix(entry.segments, at.segments));
12729
- });
12730
- }
12731
- };
12732
- }
12733
-
12734
- // TODO: improve this so that we do not get RawString and string, only string. Are there other things?
12735
-
12736
12976
  /**
12737
12977
  * A RENDER is how the FIELD ITSELF is laid out in the editor, and it applies
12738
12978
  * only when you are looking at the field: `.render({ as: "inline" })` on an
@@ -13249,7 +13489,7 @@ function isPageRouter(routerId) {
13249
13489
 
13250
13490
  var corePackageJson = {
13251
13491
  name: "@valbuild/core",
13252
- version: "0.129.0",
13492
+ version: "0.130.0",
13253
13493
  "private": false,
13254
13494
  description: "Val - supercharged hard-coded content",
13255
13495
  repository: {