@mulmoclaude/core 0.13.1 → 0.14.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 (51) hide show
  1. package/assets/helps/billing-clients-worklog.md +15 -12
  2. package/assets/helps/collection-skills.md +42 -19
  3. package/assets/helps/todo-collection.md +13 -7
  4. package/dist/{deriveAll-CMFXDQ_G.js → calendarGrid-C1rfCXJ3.cjs} +468 -3
  5. package/dist/calendarGrid-C1rfCXJ3.cjs.map +1 -0
  6. package/dist/{deriveAll-BQ_p5HSB.cjs → calendarGrid-kO6rGfm0.js} +289 -74
  7. package/dist/calendarGrid-kO6rGfm0.js.map +1 -0
  8. package/dist/collection/core/ids.d.ts +12 -0
  9. package/dist/collection/core/recordZ.d.ts +25 -0
  10. package/dist/collection/core/schema.d.ts +65 -418
  11. package/dist/collection/core/schemaZ.d.ts +846 -0
  12. package/dist/collection/core/templatePath.d.ts +34 -0
  13. package/dist/collection/core/where.d.ts +9 -15
  14. package/dist/collection/index.cjs +34 -295
  15. package/dist/collection/index.cjs.map +1 -1
  16. package/dist/collection/index.d.ts +1 -0
  17. package/dist/collection/index.js +5 -270
  18. package/dist/collection/index.js.map +1 -1
  19. package/dist/collection/paths.cjs +1 -1
  20. package/dist/collection/paths.cjs.map +1 -1
  21. package/dist/collection/paths.js +1 -1
  22. package/dist/collection/paths.js.map +1 -1
  23. package/dist/collection/registry/server/index.cjs +1 -1
  24. package/dist/collection/registry/server/index.js +1 -1
  25. package/dist/collection/server/discovery.d.ts +2 -244
  26. package/dist/collection/server/index.cjs +3 -1
  27. package/dist/collection/server/index.js +2 -2
  28. package/dist/collection/server/templatePath.d.ts +1 -34
  29. package/dist/collection/server/validate.d.ts +11 -7
  30. package/dist/collection-watchers/index.cjs +1 -1
  31. package/dist/collection-watchers/index.js +1 -1
  32. package/dist/feeds/index.cjs +4 -4
  33. package/dist/feeds/index.js +2 -2
  34. package/dist/feeds/ingestTypes.d.ts +15 -40
  35. package/dist/feeds/server/index.cjs +5 -5
  36. package/dist/feeds/server/index.js +3 -3
  37. package/dist/{ingestTypes-C7EheYZX.cjs → ingestTypes-BIFXlw7M.cjs} +3 -3
  38. package/dist/ingestTypes-BIFXlw7M.cjs.map +1 -0
  39. package/dist/{ingestTypes-CmJeOUJc.js → ingestTypes-KuYPX9Ea.js} +2 -2
  40. package/dist/ingestTypes-KuYPX9Ea.js.map +1 -0
  41. package/dist/{server-DKXXFTbw.js → server-Cc6XcyOA.js} +417 -187
  42. package/dist/server-Cc6XcyOA.js.map +1 -0
  43. package/dist/{server-G6GtOdaW.cjs → server-Cd5WdM8a.cjs} +440 -198
  44. package/dist/server-Cd5WdM8a.cjs.map +1 -0
  45. package/package.json +1 -1
  46. package/dist/deriveAll-BQ_p5HSB.cjs.map +0 -1
  47. package/dist/deriveAll-CMFXDQ_G.js.map +0 -1
  48. package/dist/ingestTypes-C7EheYZX.cjs.map +0 -1
  49. package/dist/ingestTypes-CmJeOUJc.js.map +0 -1
  50. package/dist/server-DKXXFTbw.js.map +0 -1
  51. package/dist/server-G6GtOdaW.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
2
- const require_deriveAll = require("./deriveAll-BQ_p5HSB.cjs");
2
+ const require_calendarGrid = require("./calendarGrid-C1rfCXJ3.cjs");
3
3
  const require_collection_paths = require("./collection/paths.cjs");
4
4
  let node_path = require("node:path");
5
5
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
@@ -80,7 +80,6 @@ var log = {
80
80
  //#endregion
81
81
  //#region src/collection/server/paths.ts
82
82
  var SCHEMA_FILE = "schema.json";
83
- var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
84
83
  /** Sanitise a user-supplied slug into a safe directory-name leaf.
85
84
  * Returns null for anything that fails the slug whitelist OR isn't a
86
85
  * basename (i.e. survives `path.basename` round-trip unchanged).
@@ -88,12 +87,11 @@ var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
88
87
  * `js/path-injection` sanitiser. */
89
88
  function safeSlugName(slug) {
90
89
  if (typeof slug !== "string") return null;
91
- if (!SAFE_SLUG_PATTERN.test(slug)) return null;
90
+ if (!require_calendarGrid.SAFE_SLUG_PATTERN.test(slug)) return null;
92
91
  const basename = node_path.default.basename(slug);
93
92
  if (basename !== slug) return null;
94
93
  return basename;
95
94
  }
96
- var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
97
95
  /** Sanitise a user-supplied record id into a safe filename stem. Like
98
96
  * `safeSlugName` but tolerates interior dots (so natural keys work),
99
97
  * while still rejecting any `..` substring, path separators, and
@@ -101,7 +99,7 @@ var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
101
99
  * `js/path-injection` sanitiser CodeQL recognises on `safeSlugName`. */
102
100
  function safeRecordId(recordId) {
103
101
  if (typeof recordId !== "string") return null;
104
- if (!SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
102
+ if (!require_calendarGrid.SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
105
103
  if (recordId.includes("..")) return null;
106
104
  const basename = node_path.default.basename(recordId);
107
105
  if (basename !== recordId) return null;
@@ -636,13 +634,119 @@ ${dataJson}
636
634
  ${templateText}`;
637
635
  }
638
636
  //#endregion
639
- //#region src/collection/server/validate.ts
640
- var MAX_ISSUES = 25;
637
+ //#region src/collection/core/recordZ.ts
638
+ /** derived/embed/toggle are host-computed or projected — never written to
639
+ * the record JSON, so required / value checks must not apply to them. */
641
640
  var COMPUTED_TYPES = /* @__PURE__ */ new Set([
642
641
  "derived",
643
642
  "embed",
644
643
  "toggle"
645
644
  ]);
645
+ /** The emptiness rule shared by `required` and the "only check present
646
+ * values" gate. NOT a truthiness check — `0` and `false` are filled. */
647
+ var isEmptyValue = (value) => value === void 0 || value === null || value === "";
648
+ /** The historical write-gate checks, verbatim: required non-empty, enum
649
+ * membership (compared as strings, so a numeric `5` satisfies `"5"`). */
650
+ function enforcedProblem(key, spec, value) {
651
+ const empty = isEmptyValue(value);
652
+ if (spec.required && empty) return `missing required field '${key}'`;
653
+ if (!empty && spec.type === "enum" && !spec.values.includes(String(value))) return `'${key}' = '${String(value)}' is not one of [${spec.values.join(", ")}]`;
654
+ return null;
655
+ }
656
+ /** Numeric coercion for the strict `number`/`money` check: a plain number,
657
+ * or a non-blank numeric string (renderers coerce those via `Number(...)`,
658
+ * so they display fine). Anything else — arrays (`[]` stringifies to `""`
659
+ * = 0, `[42]` to `"42"`), booleans, objects — is NaN and gets flagged. */
660
+ function coerceNumeric(value) {
661
+ if (typeof value === "number") return value;
662
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
663
+ return NaN;
664
+ }
665
+ /** Report-only per-type checks on a PRESENT value. Date / datetime reuse the
666
+ * calendar's STRICT civil parsers (`parseIsoDate` / `parseIsoDateTime`), so
667
+ * the lint flags exactly the values the calendar / trigger / spawn code
668
+ * would silently drop — impossible days like `2026-02-30`, and datetimes
669
+ * outside the canonical `YYYY-MM-DDTHH:MM[:SS]` shape (e.g. a `Z` suffix,
670
+ * which the day view can't place). `string`-backed types accept anything
671
+ * stringifiable; `ref` existence is out of scope. */
672
+ function strictTypeProblem(key, spec, value) {
673
+ switch (spec.type) {
674
+ case "number":
675
+ case "money": return Number.isFinite(coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;
676
+ case "boolean": return value === true || value === false ? null : `'${key}' = '${String(value)}' is not a boolean (store true or false, unquoted)`;
677
+ case "date": return require_calendarGrid.parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;
678
+ case "datetime": return require_calendarGrid.parseIsoDateTime(value) !== null ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;
679
+ default: return null;
680
+ }
681
+ }
682
+ /** Strict check for a PRESENT `table` value: an array of row objects, each
683
+ * row conforming to the sub-schema (required / enum / typed sub-values).
684
+ * First row problem wins, prefixed with the row number so the fix is
685
+ * locatable. */
686
+ function strictTableProblem(key, spec, value) {
687
+ if (!Array.isArray(value)) return `'${key}' = '${String(value)}' is not an array of rows (a 'table' field stores an array of row objects)`;
688
+ for (let index = 0; index < value.length; index++) {
689
+ const row = value[index];
690
+ if (!row || typeof row !== "object" || Array.isArray(row)) return `'${key}' row ${index + 1} is not an object`;
691
+ for (const [subKey, subSpec] of Object.entries(spec.of)) {
692
+ const subValue = row[subKey];
693
+ const problem = enforcedProblem(subKey, subSpec, subValue) ?? (isEmptyValue(subValue) ? null : strictTypeProblem(subKey, subSpec, subValue));
694
+ if (problem) return `'${key}' row ${index + 1}: ${problem}`;
695
+ }
696
+ }
697
+ return null;
698
+ }
699
+ /** First problem for one field's stored value under `tier`, or null.
700
+ * Enforced checks always run (and their messages never vary by tier — the
701
+ * scan and the write gate must agree on them); strict adds the per-type
702
+ * layer on present values only. */
703
+ function recordFieldProblem(key, spec, value, tier) {
704
+ const enforced = enforcedProblem(key, spec, value);
705
+ if (enforced || tier === "enforced") return enforced;
706
+ if (isEmptyValue(value)) return null;
707
+ if (spec.type === "table") return strictTableProblem(key, spec, value);
708
+ return strictTypeProblem(key, spec, value);
709
+ }
710
+ var compiled = /* @__PURE__ */ new WeakMap();
711
+ /** Compile `schema.fields` into a zod validator for a stored record.
712
+ * Loose object: unknown keys are allowed and any declared key may be
713
+ * absent (records are user files, not parse-and-rewrite targets —
714
+ * callers validate, they never persist the parse output). The checks run
715
+ * as ONE object-level refine iterating fields in declaration order —
716
+ * per-key shape schemas can't express "key may be absent BUT its absence
717
+ * must still reach the required check", and the single loop keeps the
718
+ * first reported issue identical to the historical first-problem-wins
719
+ * contract. */
720
+ function compileRecordZ(schema, tier) {
721
+ const cached = compiled.get(schema)?.[tier];
722
+ if (cached) return cached;
723
+ const stored = Object.entries(schema.fields).filter(([, spec]) => !COMPUTED_TYPES.has(spec.type));
724
+ const validator = zod.z.looseObject({}).superRefine((record, ctx) => {
725
+ for (const [key, spec] of stored) {
726
+ const problem = recordFieldProblem(key, spec, record[key], tier);
727
+ if (problem) ctx.addIssue({
728
+ code: "custom",
729
+ message: problem,
730
+ path: [key]
731
+ });
732
+ }
733
+ });
734
+ const entry = compiled.get(schema) ?? {};
735
+ entry[tier] = validator;
736
+ compiled.set(schema, entry);
737
+ return validator;
738
+ }
739
+ /** First schema problem on an in-memory record under `tier`, or null. One
740
+ * issue per record keeps the report short and the fix obvious (the
741
+ * historical contract of `validateRecordObject`). */
742
+ function firstRecordProblem(record, schema, tier) {
743
+ const result = compileRecordZ(schema, tier).safeParse(record);
744
+ if (result.success) return null;
745
+ return result.error.issues[0]?.message ?? "record failed schema validation";
746
+ }
747
+ //#endregion
748
+ //#region src/collection/server/validate.ts
749
+ var MAX_ISSUES = 25;
646
750
  /** Read every `<id>.json` under the collection's dataDir and report the
647
751
  * ones that won't load or violate the schema. An empty list means every
648
752
  * record is fine. */
@@ -705,72 +809,70 @@ async function inspectRecord(fullPath, name, schema) {
705
809
  file: name,
706
810
  problem: "not a JSON object — skipped, won't appear"
707
811
  };
708
- const problem = validateRecordObject(parsed, name.replace(/\.json$/, ""), schema);
812
+ const problem = validateRecordObject(parsed, name.replace(/\.json$/, ""), schema, "strict");
709
813
  return problem ? {
710
814
  file: name,
711
815
  problem
712
816
  } : null;
713
817
  }
714
818
  /** First schema problem on an in-memory record (primaryKey↔id mismatch,
715
- * missing required, bad enum value), or null when it's fine. One issue
716
- * per record keeps the report short and the fix obvious. Pure +
717
- * exported so write paths (manageCollection putItems) can gate on the
718
- * SAME rules the post-hoc file scan reports `itemId` is the id the
719
- * record is (or would be) stored under. */
720
- function validateRecordObject(record, itemId, schema) {
819
+ * then the compiled per-field checks see `../core/recordZ` for the two
820
+ * tiers), or null when it's fine. One issue per record keeps the report
821
+ * short and the fix obvious. Pure + exported so write paths
822
+ * (manageCollection putItems) can gate on the SAME enforced rules the
823
+ * post-hoc file scan reports — `itemId` is the id the record is (or
824
+ * would be) stored under. The default `"enforced"` tier keeps every
825
+ * write gate on the historical three checks; only pass `"strict"` from
826
+ * report-only surfaces. */
827
+ function validateRecordObject(record, itemId, schema, tier = "enforced") {
721
828
  const idValue = record[schema.primaryKey];
722
829
  if (typeof idValue !== "string" || idValue !== itemId) return `'${schema.primaryKey}' is '${String(idValue ?? "")}' but must equal the filename ('${itemId}'), or the record can't be opened`;
723
- for (const [field, spec] of Object.entries(schema.fields)) {
724
- if (COMPUTED_TYPES.has(spec.type)) continue;
725
- const value = record[field];
726
- const empty = value === void 0 || value === null || value === "";
727
- if (spec.required && empty) return `missing required field '${field}'`;
728
- if (!empty && spec.type === "enum" && spec.values && !spec.values.includes(String(value))) return `'${field}' = '${String(value)}' is not one of [${spec.values.join(", ")}]`;
729
- }
730
- return null;
830
+ return firstRecordProblem(record, schema, tier);
731
831
  }
732
832
  //#endregion
733
- //#region src/collection/server/discovery.ts
734
- var refRefine = (spec) => {
735
- if (spec.type !== "ref") return true;
736
- if (typeof spec.to !== "string") return false;
737
- return safeSlugName(spec.to) !== null;
738
- };
739
- var refMessage = {
740
- message: "fields with type 'ref' must declare a `to` that is a valid collection slug (alphanumeric / hyphen / underscore, no path separators)",
741
- path: ["to"]
742
- };
743
- var isDateLike = (type) => type === "date" || type === "datetime";
744
- var isTimeStringField = (type) => type === "string" || type === "text";
745
- var embedRefine = (spec) => {
746
- if (spec.type !== "embed") return true;
747
- if (typeof spec.to !== "string" || safeSlugName(spec.to) === null) return false;
748
- return (typeof spec.id === "string" && spec.id.trim().length > 0) !== (typeof spec.idField === "string" && spec.idField.trim().length > 0);
749
- };
750
- var embedMessage = {
751
- message: "fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)",
752
- path: ["id"]
753
- };
754
- var enumRefine = (spec) => spec.type !== "enum" || Array.isArray(spec.values) && spec.values.length > 0 && spec.values.every((value) => typeof value === "string" && value.length > 0);
755
- var enumMessage = {
756
- message: "fields with type 'enum' must declare a non-empty `values` array of non-empty strings",
757
- path: ["values"]
833
+ //#region src/collection/core/schemaZ.ts
834
+ /** Optional visibility predicate shared by actions and fields: the target
835
+ * shows only when the open record's `field` (stringified) is one of `in`.
836
+ * Domain-free `field` is any non-empty key, `in` a non-empty array of
837
+ * non-empty values; the host never interprets the meaning.
838
+ *
839
+ * `trim().min(1)` rather than bare `min(1)` so a whitespace-only string
840
+ * (" ") fails validation otherwise the cell formatter / dropdown would
841
+ * render visual blanks that look like missing data. Applied consistently to
842
+ * every "non-empty string" slot in this file (CodeRabbit PR #1497). */
843
+ var WhenZ = zod.z.object({
844
+ field: zod.z.string().trim().min(1),
845
+ in: zod.z.array(zod.z.string().trim().min(1)).min(1)
846
+ });
847
+ var fieldBase = {
848
+ label: zod.z.string().min(1),
849
+ primary: zod.z.boolean().optional(),
850
+ required: zod.z.boolean().optional(),
851
+ when: WhenZ.optional()
758
852
  };
759
- var currencyRefine = (spec) => {
760
- if (!(spec.type === "money" || spec.type === "derived" && spec.display === "money")) return true;
761
- const hasLiteral = typeof spec.currency === "string" && spec.currency.trim().length > 0;
762
- const hasPointer = typeof spec.currencyField === "string" && spec.currencyField.trim().length > 0;
763
- return hasLiteral || hasPointer;
853
+ var currencyKeys = {
854
+ currency: zod.z.string().trim().min(1).optional(),
855
+ currencyField: zod.z.string().trim().min(1).optional()
764
856
  };
857
+ var hasCurrencySource = (spec) => spec.currency !== void 0 || spec.currencyField !== void 0;
765
858
  var currencyMessage = {
766
859
  message: "fields that render as money (type 'money', or 'derived' with display 'money') must declare either a literal `currency` (ISO 4217 code, e.g. 'USD', 'JPY') or a `currencyField` naming the record field that holds the code",
767
860
  path: ["currency"]
768
861
  };
769
- var WhenSchema = zod.z.object({
770
- field: zod.z.string().trim().min(1),
771
- in: zod.z.array(zod.z.string().trim().min(1)).min(1)
772
- });
773
- var SubFieldSpecSchema = zod.z.object({
862
+ var slugMessage = (key) => ({
863
+ message: `\`${key}\` must be a valid collection slug (alphanumeric / hyphen / underscore, no path separators)`,
864
+ path: [key]
865
+ });
866
+ /** The plain scalar field types. Stored and edited as primitive values; no
867
+ * variant-specific keys.
868
+ * - `image`: a workspace-relative image path (e.g. a `data/attachments/...`
869
+ * upload); rendered as an <img> in the detail view (not the list table —
870
+ * a per-row fetch is too expensive at scale). Stored as a plain string.
871
+ * - `file`: a workspace-relative file path as a plain string (e.g. an
872
+ * `artifacts/html/<name>.html` app). Rendered as a clickable link in both
873
+ * the list table and the detail view: HTML / SVG artifacts open their
874
+ * rendered form in a new tab; any other path opens in the File Explorer. */
875
+ var ScalarFieldZ = zod.z.object({
774
876
  type: zod.z.enum([
775
877
  "string",
776
878
  "text",
@@ -780,18 +882,39 @@ var SubFieldSpecSchema = zod.z.object({
780
882
  "datetime",
781
883
  "boolean",
782
884
  "markdown",
783
- "ref",
784
- "money",
785
- "enum"
885
+ "image",
886
+ "file"
786
887
  ]),
888
+ ...fieldBase
889
+ });
890
+ /** A link to another collection: the record stores the target item's
891
+ * primary-key slug and the host renders a clickable link + dropdown picker.
892
+ * `to` must be a real slug (not `../foo`, not `mc-clients/extra` — see
893
+ * Codex P2 on PR #1495); whether the target collection exists resolves
894
+ * fail-soft at render time, never here. */
895
+ var RefFieldZ = zod.z.object({
896
+ type: zod.z.literal("ref"),
897
+ ...fieldBase,
898
+ to: zod.z.string().min(1)
899
+ }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
900
+ /** A money amount. See `currencyKeys` for the currency-source contract. */
901
+ var MoneyFieldZ = zod.z.object({
902
+ type: zod.z.literal("money"),
903
+ ...fieldBase,
904
+ ...currencyKeys
905
+ }).refine(hasCurrencySource, currencyMessage);
906
+ /** A closed set of allowed string values. The form renders a `<select>`
907
+ * populated from `values`; storage is a plain string. */
908
+ var EnumFieldZ = zod.z.object({
909
+ type: zod.z.literal("enum"),
910
+ ...fieldBase,
911
+ values: zod.z.array(zod.z.string().trim().min(1)).min(1)
912
+ });
913
+ var subFieldBase = {
787
914
  label: zod.z.string().min(1),
788
- required: zod.z.boolean().optional(),
789
- to: zod.z.string().min(1).optional(),
790
- currency: zod.z.string().trim().min(1).optional(),
791
- currencyField: zod.z.string().trim().min(1).optional(),
792
- values: zod.z.array(zod.z.string().trim().min(1)).min(1).optional()
793
- }).refine(refRefine, refMessage).refine(enumRefine, enumMessage).refine(currencyRefine, currencyMessage);
794
- var FieldSpecSchema = zod.z.object({
915
+ required: zod.z.boolean().optional()
916
+ };
917
+ var SubScalarFieldZ = zod.z.object({
795
918
  type: zod.z.enum([
796
919
  "string",
797
920
  "text",
@@ -800,62 +923,126 @@ var FieldSpecSchema = zod.z.object({
800
923
  "date",
801
924
  "datetime",
802
925
  "boolean",
803
- "markdown",
804
- "ref",
805
- "money",
806
- "enum",
807
- "table",
808
- "derived",
809
- "embed",
810
- "image",
811
- "file",
812
- "toggle"
926
+ "markdown"
813
927
  ]),
814
- label: zod.z.string().min(1),
815
- primary: zod.z.boolean().optional(),
816
- required: zod.z.boolean().optional(),
817
- to: zod.z.string().min(1).optional(),
818
- id: zod.z.string().trim().min(1).optional(),
819
- idField: zod.z.string().trim().min(1).optional(),
820
- currency: zod.z.string().trim().min(1).optional(),
821
- currencyField: zod.z.string().trim().min(1).optional(),
822
- values: zod.z.array(zod.z.string().trim().min(1)).min(1).optional(),
823
- field: zod.z.string().trim().min(1).optional(),
824
- onValue: zod.z.string().trim().min(1).optional(),
825
- offValue: zod.z.string().trim().min(1).optional(),
826
- of: zod.z.record(zod.z.string(), SubFieldSpecSchema).optional(),
827
- formula: zod.z.string().trim().min(1).optional(),
828
- /** Inner type to render a derived value as (e.g. `"money"`).
829
- * Restricted to the non-composite display targets — derived
830
- * values are scalars, so rendering them via `table` or another
831
- * `derived` would be meaningless. */
928
+ ...subFieldBase
929
+ });
930
+ var SubRefFieldZ = zod.z.object({
931
+ type: zod.z.literal("ref"),
932
+ ...subFieldBase,
933
+ to: zod.z.string().min(1)
934
+ }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
935
+ var SubMoneyFieldZ = zod.z.object({
936
+ type: zod.z.literal("money"),
937
+ ...subFieldBase,
938
+ ...currencyKeys
939
+ }).refine(hasCurrencySource, currencyMessage);
940
+ var SubEnumFieldZ = zod.z.object({
941
+ type: zod.z.literal("enum"),
942
+ ...subFieldBase,
943
+ values: zod.z.array(zod.z.string().trim().min(1)).min(1)
944
+ });
945
+ var SubFieldSpecZ = zod.z.discriminatedUnion("type", [
946
+ SubScalarFieldZ,
947
+ SubRefFieldZ,
948
+ SubMoneyFieldZ,
949
+ SubEnumFieldZ
950
+ ]);
951
+ /** A flat sub-table: each row is a record of `of`'s sub-schema (insertion
952
+ * order = column order). v0 disallows nested tables and derived columns to
953
+ * keep the editor + evaluator simple. */
954
+ var TableFieldZ = zod.z.object({
955
+ type: zod.z.literal("table"),
956
+ ...fieldBase,
957
+ of: zod.z.record(zod.z.string(), SubFieldSpecZ)
958
+ }).refine((spec) => Object.keys(spec.of).length > 0, {
959
+ message: "fields with type 'table' must declare a non-empty `of` (sub-schema for each row)",
960
+ path: ["of"]
961
+ });
962
+ /** A computed scalar: `formula` is a tiny expression evaluated against the
963
+ * record — `+ - * /`, parens, identifier refs to top-level fields,
964
+ * `sum(tableField[].col)`, and `sum(tableField[].col * tableField[].col)`
965
+ * (see `./derivedFormula`). `display` picks the inner type the value renders
966
+ * as (default `"number"`) — restricted to the non-composite display targets,
967
+ * since a derived value is a scalar. Never stored; computed by `deriveAll`
968
+ * on both server and client. */
969
+ var DerivedFieldZ = zod.z.object({
970
+ type: zod.z.literal("derived"),
971
+ ...fieldBase,
972
+ formula: zod.z.string().trim().min(1),
832
973
  display: zod.z.enum([
833
974
  "string",
834
975
  "number",
835
976
  "money",
836
977
  "date"
837
978
  ]).optional(),
838
- when: WhenSchema.optional()
839
- }).refine(refRefine, refMessage).refine(enumRefine, enumMessage).refine(embedRefine, embedMessage).refine(currencyRefine, currencyMessage).refine((spec) => spec.type !== "table" || spec.of !== void 0 && Object.keys(spec.of).length > 0, {
840
- message: "fields with type 'table' must declare a non-empty `of` (sub-schema for each row)",
841
- path: ["of"]
842
- }).refine((spec) => spec.type !== "derived" || typeof spec.formula === "string" && spec.formula.length > 0, {
843
- message: "fields with type 'derived' must declare a non-empty `formula` (see src/utils/collections/derivedFormula.ts)",
844
- path: ["formula"]
845
- }).refine((spec) => spec.type !== "toggle" || typeof spec.field === "string" && spec.field.length > 0 && typeof spec.onValue === "string" && typeof spec.offValue === "string", {
846
- message: "fields with type 'toggle' must declare `field` (the enum field to project), `onValue`, and `offValue`",
847
- path: ["field"]
848
- });
849
- var ActionSpecSchema = zod.z.object({
979
+ ...currencyKeys
980
+ }).refine((spec) => spec.display !== "money" || hasCurrencySource(spec), currencyMessage);
981
+ /** Pulls a record from another collection into the read-only detail view.
982
+ * Display-only — nothing is stored on this record, so it never appears in
983
+ * the list table or the edit form. Must declare a valid `to` slug (same
984
+ * path-traversal guard as `ref`) AND exactly one of `id` (a fixed target
985
+ * record, e.g. `me` for the singleton profile — same for every record) or
986
+ * `idField` (a sibling top-level field naming the per-record target, e.g.
987
+ * an invoice's `issuerId` selecting which profile to embed as the bill-from
988
+ * block; an absent/empty value resolves fail-soft to "no record"). The
989
+ * `idField` target is validated to be a real `ref`/`string` field by a
990
+ * schema-level refine below. */
991
+ var EmbedFieldZ = zod.z.object({
992
+ type: zod.z.literal("embed"),
993
+ ...fieldBase,
994
+ to: zod.z.string().min(1),
995
+ id: zod.z.string().trim().min(1).optional(),
996
+ idField: zod.z.string().trim().min(1).optional()
997
+ }).refine((spec) => require_calendarGrid.isSafeSlug(spec.to) && spec.id !== void 0 !== (spec.idField !== void 0), {
998
+ message: "fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)",
999
+ path: ["id"]
1000
+ });
1001
+ /** A checkbox that is a pure PROJECTION of an `enum` field — it stores
1002
+ * nothing of its own. Checked when the enum named by `field` equals
1003
+ * `onValue`; toggling writes `onValue` / `offValue` back to that enum
1004
+ * field. Lets a "done" checkbox front a kanban `status` field with the enum
1005
+ * as the single source of truth (no separate stored boolean to keep in
1006
+ * sync). `field` / `onValue` / `offValue` are validated against the target
1007
+ * enum's `values` by a schema-level refine below. */
1008
+ var ToggleFieldZ = zod.z.object({
1009
+ type: zod.z.literal("toggle"),
1010
+ ...fieldBase,
1011
+ field: zod.z.string().trim().min(1),
1012
+ onValue: zod.z.string().trim().min(1),
1013
+ offValue: zod.z.string().trim().min(1)
1014
+ });
1015
+ var FieldSpecZ = zod.z.discriminatedUnion("type", [
1016
+ ScalarFieldZ,
1017
+ RefFieldZ,
1018
+ MoneyFieldZ,
1019
+ EnumFieldZ,
1020
+ TableFieldZ,
1021
+ DerivedFieldZ,
1022
+ EmbedFieldZ,
1023
+ ToggleFieldZ
1024
+ ]);
1025
+ /** A schema-declared record action, rendered as a button in the read-only
1026
+ * detail view. Domain-free: the host validates the shape; the meaning
1027
+ * (which role, which template) is data. v1 ships only `kind: "chat"` —
1028
+ * start a new chat in `role` with a templated seed prompt; the enum
1029
+ * reserves room for a future `"mutate"` (status transitions) without
1030
+ * another schema-shape change. */
1031
+ var ActionSpecZ = zod.z.object({
850
1032
  id: zod.z.string().trim().min(1),
851
1033
  label: zod.z.string().trim().min(1),
852
1034
  icon: zod.z.string().trim().min(1).optional(),
853
1035
  kind: zod.z.enum(["chat"]),
854
1036
  role: zod.z.string().trim().min(1),
855
1037
  template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/invoice.md`; no `..`, no leading `/`, no backslash)"),
856
- when: WhenSchema.optional()
857
- });
858
- var CustomViewSchema = zod.z.object({
1038
+ when: WhenZ.optional()
1039
+ });
1040
+ /** A custom (LLM-authored) HTML view registration. Domain-free: the host
1041
+ * validates the shape; the view's behaviour lives in the HTML file. `file`
1042
+ * is constrained to `views/*.html` (path-safe) so the view-file reader can
1043
+ * never reach the data folder or the schema/template files. `id` is
1044
+ * validated to be a real slug + unique by schema-level refines below. */
1045
+ var CustomViewZ = zod.z.object({
859
1046
  id: zod.z.string().trim().min(1),
860
1047
  label: zod.z.string().trim().min(1),
861
1048
  icon: zod.z.string().trim().min(1).optional(),
@@ -868,7 +1055,14 @@ var CustomViewSchema = zod.z.object({
868
1055
  imageFields: zod.z.array(zod.z.string().trim().min(1)).optional(),
869
1056
  imageMaxEdge: zod.z.number().int().min(1).optional()
870
1057
  });
871
- var EveryLiteralSchema = zod.z.object({
1058
+ /** Recurrence advance for `spawn.every`. `interval` is a positive integer
1059
+ * count of `unit`s (`interval: 3` + `unit: "month"` = quarterly);
1060
+ * `dayOfMonth` (month/year only) is the CANONICAL day-of-month anchor
1061
+ * (1-31, read from the rule and clamped per-month at compute time so "31st
1062
+ * of every month" never drifts) or the `"last"` sentinel for end-of-month.
1063
+ * `.strict()` so the union below cleanly rejects an object carrying BOTH
1064
+ * `unit` and `fromField` (it fails this arm on the unknown `fromField`). */
1065
+ var EveryLiteralZ = zod.z.object({
872
1066
  unit: zod.z.enum([
873
1067
  "day",
874
1068
  "week",
@@ -878,17 +1072,114 @@ var EveryLiteralSchema = zod.z.object({
878
1072
  interval: zod.z.number().int().min(1),
879
1073
  dayOfMonth: zod.z.union([zod.z.number().int().min(1).max(31), zod.z.literal("last")]).optional()
880
1074
  }).strict();
881
- var EveryFieldDrivenSchema = zod.z.object({
1075
+ /** Field-driven recurrence: pick the interval per-record by an `enum`
1076
+ * field's value — one collection can mix daily / weekly / monthly
1077
+ * obligations in a single list. `map` keys are validated to exactly cover
1078
+ * that field's `values` by a `CollectionSchemaZ` refine (which can see the
1079
+ * sibling `fields`); here each map value just has to be a well-formed
1080
+ * literal `every`. `.strict()` mirrors the literal arm so a both-keys
1081
+ * object fails this arm too. */
1082
+ var EveryFieldDrivenZ = zod.z.object({
882
1083
  fromField: zod.z.string().trim().min(1),
883
- map: zod.z.record(zod.z.string(), EveryLiteralSchema)
1084
+ map: zod.z.record(zod.z.string(), EveryLiteralZ)
884
1085
  }).strict();
885
- var EverySchema = zod.z.union([EveryLiteralSchema, EveryFieldDrivenSchema]);
886
- var SpawnSchema = zod.z.object({
887
- when: WhenSchema.optional(),
888
- every: EverySchema,
1086
+ /** Either a single literal interval (applied to every record) or the
1087
+ * field-driven map. Two `.strict()` arms mean "both keys" and "neither
1088
+ * key" both fail validation, with no extra refine. */
1089
+ var EveryZ = zod.z.union([EveryLiteralZ, EveryFieldDrivenZ]);
1090
+ /** Host-driven recurrence: when a record satisfies `when` (default:
1091
+ * "`completionField` value ∈ `completionDoneValues`"), the host creates the
1092
+ * next record with a forward-advanced `triggerField` date. `carry` copies
1093
+ * record fields verbatim onto the successor; `set` forces fixed values
1094
+ * (typically resetting the status field to its pending value). The
1095
+ * successor's id and contents are a pure function of (source record, this
1096
+ * rule); creation is create-if-absent, so the mechanism stays convergent. */
1097
+ var SpawnZ = zod.z.object({
1098
+ when: WhenZ.optional(),
1099
+ every: EveryZ,
889
1100
  carry: zod.z.array(zod.z.string().trim().min(1)).optional(),
890
1101
  set: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
891
1102
  });
1103
+ /** Declarative retrieval config for a Feed (a collection that refills itself
1104
+ * from the internet). `http-json` needs `itemsAt` (a path to the items
1105
+ * array) only when the response body isn't itself the array; rss/atom yield
1106
+ * items natively and ignore it — so no kind-specific requirement here. */
1107
+ var DeclarativeIngestZ = zod.z.object({
1108
+ kind: zod.z.enum(require_calendarGrid.INGEST_KINDS),
1109
+ url: zod.z.string().url(),
1110
+ schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
1111
+ atHour: zod.z.number().int().min(0).max(23).optional(),
1112
+ itemsAt: zod.z.string().trim().min(1).optional(),
1113
+ map: zod.z.record(zod.z.string().trim().min(1), zod.z.string().trim().min(1)),
1114
+ idFrom: zod.z.string().trim().min(1).optional(),
1115
+ maxItems: zod.z.number().int().min(0).optional()
1116
+ });
1117
+ /** Agent-performed retrieval. Valid on any collection (the primary consumer
1118
+ * is skill-backed collections — feeds keep their declarative kinds). No
1119
+ * `url`/`map`: the worker owns retrieval and record shape, seeded by
1120
+ * `template` + a summary of every record, run in `role`. `template` is
1121
+ * validated the SAME way an action's template is (safe path under
1122
+ * `templates/`), so the skill-bridge mirrors it identically. */
1123
+ var AgentIngestZ = zod.z.object({
1124
+ kind: zod.z.literal(require_calendarGrid.AGENT_INGEST_KIND),
1125
+ schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
1126
+ atHour: zod.z.number().int().min(0).max(23).optional(),
1127
+ role: zod.z.string().trim().min(1),
1128
+ template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)")
1129
+ });
1130
+ /** `ingest` is a discriminated union on `kind`: the three declarative
1131
+ * retrievers fetch-and-map; `agent` dispatches a hidden worker. Optional on
1132
+ * every schema — skill-backed collections usually omit it; only feeds
1133
+ * discovered from `<workspace>/feeds/` are REQUIRED to carry it (gated by
1134
+ * `acceptParsedSchema`). */
1135
+ var IngestZ = zod.z.discriminatedUnion("kind", [DeclarativeIngestZ, AgentIngestZ]);
1136
+ var ValueRefZ = zod.z.object({
1137
+ record: zod.z.string().trim().min(1).optional(),
1138
+ field: zod.z.string().trim().min(1)
1139
+ });
1140
+ var WhereCondZ = zod.z.object({
1141
+ field: zod.z.string().trim().min(1),
1142
+ op: zod.z.enum([
1143
+ "eq",
1144
+ "ne",
1145
+ "in",
1146
+ "gt",
1147
+ "gte",
1148
+ "lt",
1149
+ "lte",
1150
+ "contains"
1151
+ ]),
1152
+ value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
1153
+ valueFrom: ValueRefZ.optional()
1154
+ }).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
1155
+ message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
1156
+ path: ["value"]
1157
+ }).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
1158
+ message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
1159
+ path: ["value"]
1160
+ });
1161
+ var WhereZ = zod.z.array(WhereCondZ);
1162
+ var DynamicIconSourceZ = zod.z.object({
1163
+ collection: zod.z.string().trim().min(1),
1164
+ from: zod.z.enum([
1165
+ "latest",
1166
+ "first",
1167
+ "when"
1168
+ ]).optional(),
1169
+ orderBy: zod.z.string().trim().min(1).optional(),
1170
+ where: WhereZ.optional()
1171
+ });
1172
+ var DynamicIconRuleZ = zod.z.object({
1173
+ where: WhereZ,
1174
+ icon: zod.z.string().trim().min(1)
1175
+ });
1176
+ var DynamicIconSpecZ = zod.z.object({
1177
+ source: DynamicIconSourceZ,
1178
+ rules: zod.z.array(DynamicIconRuleZ),
1179
+ fallback: zod.z.string().trim().min(1).optional()
1180
+ });
1181
+ var isDateLike = (type) => type === "date" || type === "datetime";
1182
+ var isTimeStringField = (type) => type === "string" || type === "text";
892
1183
  var CODE_FIELD_TYPES = /* @__PURE__ */ new Set([
893
1184
  "string",
894
1185
  "text",
@@ -926,7 +1217,7 @@ function spawnSuccessorStartsInert(schema) {
926
1217
  }
927
1218
  function fieldDrivenSpawnEvery(schema) {
928
1219
  const every = schema.spawn?.every;
929
- if (!every || !require_deriveAll.isFieldDrivenEvery(every)) return null;
1220
+ if (!every || !("fromField" in every)) return null;
930
1221
  return every;
931
1222
  }
932
1223
  function fieldDrivenFromFieldIsEnum(schema) {
@@ -954,93 +1245,30 @@ function fieldDrivenFromFieldCarried(schema) {
954
1245
  }
955
1246
  return (carry ?? []).includes(driven.fromField);
956
1247
  }
957
- var DeclarativeIngestZ = zod.z.object({
958
- kind: zod.z.enum(require_deriveAll.INGEST_KINDS),
959
- url: zod.z.string().url(),
960
- schedule: zod.z.enum(require_deriveAll.FEED_SCHEDULES),
961
- atHour: zod.z.number().int().min(0).max(23).optional(),
962
- itemsAt: zod.z.string().trim().min(1).optional(),
963
- map: zod.z.record(zod.z.string().trim().min(1), zod.z.string().trim().min(1)),
964
- idFrom: zod.z.string().trim().min(1).optional(),
965
- maxItems: zod.z.number().int().min(0).optional()
966
- });
967
- var AgentIngestZ = zod.z.object({
968
- kind: zod.z.literal(require_deriveAll.AGENT_INGEST_KIND),
969
- schedule: zod.z.enum(require_deriveAll.FEED_SCHEDULES),
970
- atHour: zod.z.number().int().min(0).max(23).optional(),
971
- role: zod.z.string().trim().min(1),
972
- template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)")
973
- });
974
- var IngestSchemaZ = zod.z.discriminatedUnion("kind", [DeclarativeIngestZ, AgentIngestZ]);
975
- var ValueRefZ = zod.z.object({
976
- record: zod.z.string().trim().min(1).optional(),
977
- field: zod.z.string().trim().min(1)
978
- });
979
- var WhereCondZ = zod.z.object({
980
- field: zod.z.string().trim().min(1),
981
- op: zod.z.enum([
982
- "eq",
983
- "ne",
984
- "in",
985
- "gt",
986
- "gte",
987
- "lt",
988
- "lte",
989
- "contains"
990
- ]),
991
- value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
992
- valueFrom: ValueRefZ.optional()
993
- }).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
994
- message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
995
- path: ["value"]
996
- }).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
997
- message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
998
- path: ["value"]
999
- });
1000
- var WhereZ = zod.z.array(WhereCondZ);
1001
- var DynamicIconSourceZ = zod.z.object({
1002
- collection: zod.z.string().trim().min(1),
1003
- from: zod.z.enum([
1004
- "latest",
1005
- "first",
1006
- "when"
1007
- ]).optional(),
1008
- orderBy: zod.z.string().trim().min(1).optional(),
1009
- where: WhereZ.optional()
1010
- });
1011
- var DynamicIconRuleZ = zod.z.object({
1012
- where: WhereZ,
1013
- icon: zod.z.string().trim().min(1)
1014
- });
1015
- var DynamicIconSpecZ = zod.z.object({
1016
- source: DynamicIconSourceZ,
1017
- rules: zod.z.array(DynamicIconRuleZ),
1018
- fallback: zod.z.string().trim().min(1).optional()
1019
- });
1020
1248
  var CollectionSchemaZ = zod.z.object({
1021
1249
  title: zod.z.string().min(1),
1022
1250
  icon: zod.z.string().min(1),
1023
1251
  dataPath: zod.z.string().min(1),
1024
1252
  primaryKey: zod.z.string().min(1),
1025
1253
  singleton: zod.z.string().trim().min(1).optional(),
1026
- fields: zod.z.record(zod.z.string(), FieldSpecSchema),
1027
- actions: zod.z.array(ActionSpecSchema).optional(),
1028
- collectionActions: zod.z.array(ActionSpecSchema).optional(),
1254
+ fields: zod.z.record(zod.z.string(), FieldSpecZ),
1255
+ actions: zod.z.array(ActionSpecZ).optional(),
1256
+ collectionActions: zod.z.array(ActionSpecZ).optional(),
1029
1257
  completionField: zod.z.string().trim().min(1).optional(),
1030
1258
  completionDoneValues: zod.z.array(zod.z.string().trim().min(1)).min(1).optional(),
1031
1259
  displayField: zod.z.string().trim().min(1).optional(),
1032
1260
  triggerField: zod.z.string().trim().min(1).optional(),
1033
1261
  triggerLeadDays: zod.z.number().int().min(0).optional(),
1034
- spawn: SpawnSchema.optional(),
1262
+ spawn: SpawnZ.optional(),
1035
1263
  calendarField: zod.z.string().trim().min(1).optional(),
1036
1264
  calendarEndField: zod.z.string().trim().min(1).optional(),
1037
1265
  calendarTimeField: zod.z.string().trim().min(1).optional(),
1038
1266
  kanbanField: zod.z.string().trim().min(1).optional(),
1039
- views: zod.z.array(CustomViewSchema).optional(),
1040
- notifyWhen: WhenSchema.optional(),
1041
- ingest: IngestSchemaZ.optional(),
1267
+ views: zod.z.array(CustomViewZ).optional(),
1268
+ notifyWhen: WhenZ.optional(),
1269
+ ingest: IngestZ.optional(),
1042
1270
  dynamicIcon: DynamicIconSpecZ.optional()
1043
- }).refine((schema) => schema.singleton === void 0 || safeRecordId(schema.singleton) !== null, {
1271
+ }).refine((schema) => schema.singleton === void 0 || require_calendarGrid.isSafeRecordId(schema.singleton), {
1044
1272
  message: "schema `singleton` must be a valid item id (alphanumeric / hyphen / underscore / interior dot, no `..` or path separators)",
1045
1273
  path: ["singleton"]
1046
1274
  }).refine((schema) => schema.actions === void 0 || new Set(schema.actions.map((action) => action.id)).size === schema.actions.length, {
@@ -1131,13 +1359,15 @@ var CollectionSchemaZ = zod.z.object({
1131
1359
  }).refine((schema) => schema.notifyWhen === void 0 || schema.fields[schema.notifyWhen.field] !== void 0, {
1132
1360
  message: "schema `notifyWhen.field` must name a top-level field declared in `fields`",
1133
1361
  path: ["notifyWhen"]
1134
- }).refine((schema) => schema.views === void 0 || schema.views.every((view) => safeSlugName(view.id) !== null), {
1362
+ }).refine((schema) => schema.views === void 0 || schema.views.every((view) => require_calendarGrid.isSafeSlug(view.id)), {
1135
1363
  message: "every `views[].id` must be a valid slug (alphanumeric / hyphen / underscore, no path separators)",
1136
1364
  path: ["views"]
1137
1365
  }).refine((schema) => schema.views === void 0 || new Set(schema.views.map((view) => view.id)).size === schema.views.length, {
1138
1366
  message: "schema `views` must have unique `id`s",
1139
1367
  path: ["views"]
1140
1368
  });
1369
+ //#endregion
1370
+ //#region src/collection/server/discovery.ts
1141
1371
  function applyFeedSchemaDefaults(parsed, slug) {
1142
1372
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
1143
1373
  const obj = parsed;
@@ -1349,7 +1579,7 @@ async function loadTarget(slug, opts) {
1349
1579
  const byId = {};
1350
1580
  for (const item of items) {
1351
1581
  const itemId = item[target.schema.primaryKey];
1352
- if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_deriveAll.deriveAll(target.schema, item, {});
1582
+ if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_calendarGrid.deriveAll(target.schema, item, {});
1353
1583
  }
1354
1584
  return {
1355
1585
  schema: target.schema,
@@ -1378,7 +1608,7 @@ function projectComputed(schema, enriched, linked) {
1378
1608
  for (const [key, field] of Object.entries(schema.fields)) {
1379
1609
  if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
1380
1610
  if (field.type === "embed" && field.to) {
1381
- const targetId = require_deriveAll.embedTargetId(field, enriched);
1611
+ const targetId = require_calendarGrid.embedTargetId(field, enriched);
1382
1612
  enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
1383
1613
  }
1384
1614
  }
@@ -1392,7 +1622,7 @@ async function enrichItems(collection, items, opts = {}) {
1392
1622
  const { schema } = collection;
1393
1623
  const linked = await loadLinkedTargets(schema, opts);
1394
1624
  const refRecords = toRefRecords(linked);
1395
- return items.map((item) => projectComputed(schema, require_deriveAll.deriveAll(schema, item, refRecords), linked));
1625
+ return items.map((item) => projectComputed(schema, require_calendarGrid.deriveAll(schema, item, refRecords), linked));
1396
1626
  }
1397
1627
  //#endregion
1398
1628
  //#region src/collection/server/dynamicIcon.ts
@@ -1428,9 +1658,9 @@ async function computeCollectionIcon(collection, opts = {}) {
1428
1658
  const source = await loadCollection(spec.source.collection, opts);
1429
1659
  if (!source) return spec.fallback ?? schema.icon;
1430
1660
  const ordered = sortByPrimaryKey(await listItems(source.dataDir, { workspaceRoot: opts.workspaceRoot }), source.schema.primaryKey);
1431
- const orderBy = spec.source.orderBy ?? require_deriveAll.firstDateField(source.schema);
1661
+ const orderBy = spec.source.orderBy ?? require_calendarGrid.firstDateField(source.schema);
1432
1662
  const recordsById = buildRecordsById(ordered, source.schema.primaryKey);
1433
- return require_deriveAll.resolveIcon(require_deriveAll.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
1663
+ return require_calendarGrid.resolveIcon(require_calendarGrid.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
1434
1664
  } catch (err) {
1435
1665
  log.warn("collections", "dynamic icon compute failed, falling back", {
1436
1666
  slug: collection.slug,
@@ -1564,7 +1794,7 @@ function matchesWhen(when, schema, item) {
1564
1794
  * Discovery rejects a map that doesn't cover the enum's values, so null
1565
1795
  * here means a record that predates a map/enum edit. */
1566
1796
  function resolveEvery(every, sourceItem) {
1567
- if (!require_deriveAll.isFieldDrivenEvery(every)) return every;
1797
+ if (!require_calendarGrid.isFieldDrivenEvery(every)) return every;
1568
1798
  const raw = sourceItem[every.fromField];
1569
1799
  if (raw === void 0 || raw === null || raw === "") return null;
1570
1800
  return every.map[String(raw)] ?? null;
@@ -1604,7 +1834,7 @@ function logSpawnSkip(slug, triggerField, every, sourceItem, sourceId) {
1604
1834
  });
1605
1835
  return;
1606
1836
  }
1607
- const fromField = require_deriveAll.isFieldDrivenEvery(every) ? every.fromField : void 0;
1837
+ const fromField = require_calendarGrid.isFieldDrivenEvery(every) ? every.fromField : void 0;
1608
1838
  log.warn("collections", "spawn skipped: no `every` mapping for frequency value", {
1609
1839
  slug,
1610
1840
  sourceId,
@@ -1971,6 +2201,12 @@ Object.defineProperty(exports, "collectionsRegistriesConfigPath", {
1971
2201
  return collectionsRegistriesConfigPath;
1972
2202
  }
1973
2203
  });
2204
+ Object.defineProperty(exports, "compileRecordZ", {
2205
+ enumerable: true,
2206
+ get: function() {
2207
+ return compileRecordZ;
2208
+ }
2209
+ });
1974
2210
  Object.defineProperty(exports, "computeCollectionIcon", {
1975
2211
  enumerable: true,
1976
2212
  get: function() {
@@ -2145,6 +2381,12 @@ Object.defineProperty(exports, "readSkillTemplate", {
2145
2381
  return readSkillTemplate;
2146
2382
  }
2147
2383
  });
2384
+ Object.defineProperty(exports, "recordFieldProblem", {
2385
+ enumerable: true,
2386
+ get: function() {
2387
+ return recordFieldProblem;
2388
+ }
2389
+ });
2148
2390
  Object.defineProperty(exports, "resolveCreateItemId", {
2149
2391
  enumerable: true,
2150
2392
  get: function() {
@@ -2230,4 +2472,4 @@ Object.defineProperty(exports, "writeItem", {
2230
2472
  }
2231
2473
  });
2232
2474
 
2233
- //# sourceMappingURL=server-G6GtOdaW.cjs.map
2475
+ //# sourceMappingURL=server-Cd5WdM8a.cjs.map