@coffer-org/plugin-transit 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schema.js CHANGED
@@ -21,14 +21,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- //#region ../sdk/src/library.ts
24
+ //#region ../sdk/dist/library.js
25
25
  function defineLibrary(v) {
26
26
  return v;
27
27
  }
28
28
  //#endregion
29
- //#region ../sdk/src/plugin.ts
29
+ //#region ../sdk/dist/plugin.js
30
30
  function definePlugin(p) {
31
31
  if (!p.id) throw new Error("[plugin] missing id");
32
+ if (!p.label) throw new Error(`[plugin] ${p.id}: missing label`);
33
+ if (!p.description) throw new Error(`[plugin] ${p.id}: missing description`);
32
34
  if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
33
35
  return p;
34
36
  }
@@ -169,7 +171,7 @@ function slugify(input) {
169
171
  return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
170
172
  }
171
173
  var captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {};
172
- function isObject$1(data) {
174
+ function isObject(data) {
173
175
  return typeof data === "object" && data !== null && !Array.isArray(data);
174
176
  }
175
177
  var allowsEval = /* @__PURE__*/ cached(() => {
@@ -183,12 +185,12 @@ var allowsEval = /* @__PURE__*/ cached(() => {
183
185
  }
184
186
  });
185
187
  function isPlainObject(o) {
186
- if (isObject$1(o) === false) return false;
188
+ if (isObject(o) === false) return false;
187
189
  const ctor = o.constructor;
188
190
  if (ctor === void 0) return true;
189
191
  if (typeof ctor !== "function") return true;
190
192
  const prot = ctor.prototype;
191
- if (isObject$1(prot) === false) return false;
193
+ if (isObject(prot) === false) return false;
192
194
  if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) return false;
193
195
  return true;
194
196
  }
@@ -1590,13 +1592,13 @@ var $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
1590
1592
  }
1591
1593
  return propValues;
1592
1594
  });
1593
- const isObject = isObject$1;
1595
+ const isObject$2 = isObject;
1594
1596
  const catchall = def.catchall;
1595
1597
  let value;
1596
1598
  inst._zod.parse = (payload, ctx) => {
1597
1599
  value ?? (value = _normalized.value);
1598
1600
  const input = payload.value;
1599
- if (!isObject(input)) {
1601
+ if (!isObject$2(input)) {
1600
1602
  payload.issues.push({
1601
1603
  expected: "object",
1602
1604
  code: "invalid_type",
@@ -1719,7 +1721,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1719
1721
  return (payload, ctx) => fn(shape, payload, ctx);
1720
1722
  };
1721
1723
  let fastpass;
1722
- const isObject = isObject$1;
1724
+ const isObject$1 = isObject;
1723
1725
  const jit = !globalConfig.jitless;
1724
1726
  const fastEnabled = jit && allowsEval.value;
1725
1727
  const catchall = def.catchall;
@@ -1727,7 +1729,7 @@ var $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) => {
1727
1729
  inst._zod.parse = (payload, ctx) => {
1728
1730
  value ?? (value = _normalized.value);
1729
1731
  const input = payload.value;
1730
- if (!isObject(input)) {
1732
+ if (!isObject$1(input)) {
1731
1733
  payload.issues.push({
1732
1734
  expected: "object",
1733
1735
  code: "invalid_type",
@@ -2729,7 +2731,7 @@ function initializeContext(params) {
2729
2731
  external: params?.external ?? void 0
2730
2732
  };
2731
2733
  }
2732
- function process$1(schema, ctx, _params = {
2734
+ function process(schema, ctx, _params = {
2733
2735
  path: [],
2734
2736
  schemaPath: []
2735
2737
  }) {
@@ -2766,7 +2768,7 @@ function process$1(schema, ctx, _params = {
2766
2768
  const parent = schema._zod.parent;
2767
2769
  if (parent) {
2768
2770
  if (!result.ref) result.ref = parent;
2769
- process$1(parent, ctx, params);
2771
+ process(parent, ctx, params);
2770
2772
  ctx.seen.get(parent).isParent = true;
2771
2773
  }
2772
2774
  }
@@ -2986,7 +2988,7 @@ var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
2986
2988
  ...params,
2987
2989
  processors
2988
2990
  });
2989
- process$1(schema, ctx);
2991
+ process(schema, ctx);
2990
2992
  extractDefs(ctx, schema);
2991
2993
  return finalize(ctx, schema);
2992
2994
  };
@@ -2998,7 +3000,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
2998
3000
  io,
2999
3001
  processors
3000
3002
  });
3001
- process$1(schema, ctx);
3003
+ process(schema, ctx);
3002
3004
  extractDefs(ctx, schema);
3003
3005
  return finalize(ctx, schema);
3004
3006
  };
@@ -3078,7 +3080,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
3078
3080
  if (typeof minimum === "number") json.minItems = minimum;
3079
3081
  if (typeof maximum === "number") json.maxItems = maximum;
3080
3082
  json.type = "array";
3081
- json.items = process$1(def.element, ctx, {
3083
+ json.items = process(def.element, ctx, {
3082
3084
  ...params,
3083
3085
  path: [...params.path, "items"]
3084
3086
  });
@@ -3089,7 +3091,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
3089
3091
  json.type = "object";
3090
3092
  json.properties = {};
3091
3093
  const shape = def.shape;
3092
- for (const key in shape) json.properties[key] = process$1(shape[key], ctx, {
3094
+ for (const key in shape) json.properties[key] = process(shape[key], ctx, {
3093
3095
  ...params,
3094
3096
  path: [
3095
3097
  ...params.path,
@@ -3107,7 +3109,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
3107
3109
  if (def.catchall?._zod.def.type === "never") json.additionalProperties = false;
3108
3110
  else if (!def.catchall) {
3109
3111
  if (ctx.io === "output") json.additionalProperties = false;
3110
- } else if (def.catchall) json.additionalProperties = process$1(def.catchall, ctx, {
3112
+ } else if (def.catchall) json.additionalProperties = process(def.catchall, ctx, {
3111
3113
  ...params,
3112
3114
  path: [...params.path, "additionalProperties"]
3113
3115
  });
@@ -3115,7 +3117,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
3115
3117
  var unionProcessor = (schema, ctx, json, params) => {
3116
3118
  const def = schema._zod.def;
3117
3119
  const isExclusive = def.inclusive === false;
3118
- const options = def.options.map((x, i) => process$1(x, ctx, {
3120
+ const options = def.options.map((x, i) => process(x, ctx, {
3119
3121
  ...params,
3120
3122
  path: [
3121
3123
  ...params.path,
@@ -3128,7 +3130,7 @@ var unionProcessor = (schema, ctx, json, params) => {
3128
3130
  };
3129
3131
  var intersectionProcessor = (schema, ctx, json, params) => {
3130
3132
  const def = schema._zod.def;
3131
- const a = process$1(def.left, ctx, {
3133
+ const a = process(def.left, ctx, {
3132
3134
  ...params,
3133
3135
  path: [
3134
3136
  ...params.path,
@@ -3136,7 +3138,7 @@ var intersectionProcessor = (schema, ctx, json, params) => {
3136
3138
  0
3137
3139
  ]
3138
3140
  });
3139
- const b = process$1(def.right, ctx, {
3141
+ const b = process(def.right, ctx, {
3140
3142
  ...params,
3141
3143
  path: [
3142
3144
  ...params.path,
@@ -3149,7 +3151,7 @@ var intersectionProcessor = (schema, ctx, json, params) => {
3149
3151
  };
3150
3152
  var nullableProcessor = (schema, ctx, json, params) => {
3151
3153
  const def = schema._zod.def;
3152
- const inner = process$1(def.innerType, ctx, params);
3154
+ const inner = process(def.innerType, ctx, params);
3153
3155
  const seen = ctx.seen.get(schema);
3154
3156
  if (ctx.target === "openapi-3.0") {
3155
3157
  seen.ref = def.innerType;
@@ -3158,27 +3160,27 @@ var nullableProcessor = (schema, ctx, json, params) => {
3158
3160
  };
3159
3161
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
3160
3162
  const def = schema._zod.def;
3161
- process$1(def.innerType, ctx, params);
3163
+ process(def.innerType, ctx, params);
3162
3164
  const seen = ctx.seen.get(schema);
3163
3165
  seen.ref = def.innerType;
3164
3166
  };
3165
3167
  var defaultProcessor = (schema, ctx, json, params) => {
3166
3168
  const def = schema._zod.def;
3167
- process$1(def.innerType, ctx, params);
3169
+ process(def.innerType, ctx, params);
3168
3170
  const seen = ctx.seen.get(schema);
3169
3171
  seen.ref = def.innerType;
3170
3172
  json.default = JSON.parse(JSON.stringify(def.defaultValue));
3171
3173
  };
3172
3174
  var prefaultProcessor = (schema, ctx, json, params) => {
3173
3175
  const def = schema._zod.def;
3174
- process$1(def.innerType, ctx, params);
3176
+ process(def.innerType, ctx, params);
3175
3177
  const seen = ctx.seen.get(schema);
3176
3178
  seen.ref = def.innerType;
3177
3179
  if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
3178
3180
  };
3179
3181
  var catchProcessor = (schema, ctx, json, params) => {
3180
3182
  const def = schema._zod.def;
3181
- process$1(def.innerType, ctx, params);
3183
+ process(def.innerType, ctx, params);
3182
3184
  const seen = ctx.seen.get(schema);
3183
3185
  seen.ref = def.innerType;
3184
3186
  let catchValue;
@@ -3193,20 +3195,20 @@ var pipeProcessor = (schema, ctx, _json, params) => {
3193
3195
  const def = schema._zod.def;
3194
3196
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
3195
3197
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
3196
- process$1(innerType, ctx, params);
3198
+ process(innerType, ctx, params);
3197
3199
  const seen = ctx.seen.get(schema);
3198
3200
  seen.ref = innerType;
3199
3201
  };
3200
3202
  var readonlyProcessor = (schema, ctx, json, params) => {
3201
3203
  const def = schema._zod.def;
3202
- process$1(def.innerType, ctx, params);
3204
+ process(def.innerType, ctx, params);
3203
3205
  const seen = ctx.seen.get(schema);
3204
3206
  seen.ref = def.innerType;
3205
3207
  json.readOnly = true;
3206
3208
  };
3207
3209
  var optionalProcessor = (schema, ctx, _json, params) => {
3208
3210
  const def = schema._zod.def;
3209
- process$1(def.innerType, ctx, params);
3211
+ process(def.innerType, ctx, params);
3210
3212
  const seen = ctx.seen.get(schema);
3211
3213
  seen.ref = def.innerType;
3212
3214
  };
@@ -4067,7 +4069,7 @@ function number(params) {
4067
4069
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
4068
4070
  }
4069
4071
  //#endregion
4070
- //#region ../sdk/src/units.ts
4072
+ //#region ../sdk/dist/units.js
4071
4073
  var UNITS_CURRENCY = [
4072
4074
  {
4073
4075
  value: "UAH",
@@ -4371,20 +4373,6 @@ var UNITS_MAP = {
4371
4373
  label: "core.units.ppm"
4372
4374
  }]
4373
4375
  };
4374
- /**
4375
- * Resolve a unit spec — a named scale or an explicit option list — to its options.
4376
- *
4377
- * Throws rather than returning `undefined`, because both ways of reaching `undefined` here
4378
- * produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
4379
- * `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
4380
- * refine, so a missing spec or a typo'd scale name turns a user's save into a
4381
- * `Cannot read properties of undefined` instead of a validation message. The golden matrix
4382
- * recorded exactly that for `f.measured({})`.
4383
- *
4384
- * A declaration error belongs at declaration time, where the plugin author sees it and the
4385
- * message can name the scale they meant — the same reason the host-services seam fails at
4386
- * registration rather than at first render.
4387
- */
4388
4376
  function resolveUnits(u) {
4389
4377
  if (typeof u === "string") {
4390
4378
  const known = UNITS_MAP[u];
@@ -4395,48 +4383,25 @@ function resolveUnits(u) {
4395
4383
  return u;
4396
4384
  }
4397
4385
  //#endregion
4398
- //#region ../sdk/src/fields/validation.ts
4399
- /** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
4386
+ //#region ../sdk/dist/fields/validation.js
4400
4387
  function vmsg(code, params) {
4401
4388
  return JSON.stringify(params ? {
4402
4389
  code,
4403
4390
  params
4404
4391
  } : { code });
4405
4392
  }
4406
- /** v4 error-map: message for a missing value (formerly required_error). */
4407
4393
  function reqErr(code = "required") {
4408
4394
  return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
4409
4395
  }
4410
- /** v4 error-map: message for an invalid type (formerly invalid_type_error). */
4411
4396
  function typeErr(code = "invalid_type") {
4412
4397
  return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
4413
4398
  }
4414
- /** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
4415
4399
  function reqTypeErr() {
4416
4400
  return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
4417
4401
  }
4418
- /**
4419
- * A string schema whose CONTENT checks only speak once the value is known to be a string.
4420
- *
4421
- * zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
4422
- * one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
4423
- * about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
4424
- * turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
4425
- * alone, having no `length` to read — so the redundancy was array-shaped and invisible until
4426
- * the golden matrix started recording the whole issue list instead of `issues[0]`.)
4427
- *
4428
- * Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
4429
- * content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
4430
- * has no `.min`/`.max`/`.regex` to chain.
4431
- */
4432
4402
  function stringContent(add) {
4433
4403
  return string$1(reqTypeErr()).pipe(add(string$1()));
4434
4404
  }
4435
- /** Parse a JSON string, else return the native value (object/array) as-is.
4436
- * Tolerant input for native form-state AND legacy JSON-string payloads.
4437
- * A string that fails JSON.parse is returned unchanged — callers detect
4438
- * parse failure via `typeof result === 'string'` (inners here are object/array,
4439
- * never a bare string). */
4440
4405
  function jsonValue(raw) {
4441
4406
  if (typeof raw === "string") try {
4442
4407
  return JSON.parse(raw);
@@ -4445,12 +4410,6 @@ function jsonValue(raw) {
4445
4410
  }
4446
4411
  return raw;
4447
4412
  }
4448
- /**
4449
- * Factory for fields that store JSON and validate it with a nested zod schema.
4450
- * Accepts a native object/array (native form state) OR a JSON string (legacy).
4451
- * Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
4452
- * string remains a string → code 'json'.
4453
- */
4454
4413
  function jsonRefined(inner, code) {
4455
4414
  return unknown().superRefine((raw, ctx) => {
4456
4415
  const parsed = jsonValue(raw);
@@ -4467,35 +4426,11 @@ function jsonRefined(inner, code) {
4467
4426
  });
4468
4427
  });
4469
4428
  }
4470
- function optionalize(schema, required) {
4471
- const req = required === true;
4472
- const pre = (v) => v === "" || v === null ? void 0 : v;
4473
- if (!req) return preprocess(pre, schema.optional());
4474
- return preprocess(pre, unknown().superRefine((v, ctx) => {
4475
- if (v === void 0) ctx.addIssue({
4476
- code: ZodIssueCode.custom,
4477
- message: vmsg("required")
4478
- });
4479
- }).pipe(schema));
4429
+ function optionalize(schema) {
4430
+ return preprocess((v) => v === "" || v === null ? void 0 : v, schema.optional());
4480
4431
  }
4481
4432
  //#endregion
4482
- //#region ../sdk/src/fields/meta.ts
4483
- /**
4484
- * Field META modifiers — the shared machinery a built field (FieldMeta) passes through
4485
- * regardless of its type: value options (`applyOptions`/`bindOptionsZod`), the `multiple`
4486
- * array wrap (`applyMultiple`), and the final node-or-bare-FieldMeta shape
4487
- * (`wrapKey`, EVERY factory's last step).
4488
- *
4489
- * A LEAF module, the same shape as `fields/validation.ts`/`fields/normalize.ts`: it
4490
- * borrows the FieldMeta/LayoutNode/FieldClient/ColumnType/OptionItem SHAPES from
4491
- * `../fields.ts` as TYPE-ONLY imports (erased at compile time — no runtime edge), but has
4492
- * NO runtime dependency on `../fields.ts` itself. This is what lets
4493
- * `materialize/pipeline.ts` import `applyMultiple`/`wrapKey` from here instead of from
4494
- * `fields.ts`, which in turn is what lets `fields.ts` import `materialize` FROM
4495
- * `materialize/pipeline.ts` without closing a cycle (`fields.ts` → `pipeline.ts` →
4496
- * `fields/meta.ts`, nothing pointing back). `fields.ts` re-exports every symbol here, so
4497
- * no external import path (`@coffer-org/sdk/fields`) changes.
4498
- */
4433
+ //#region ../sdk/dist/fields/meta.js
4499
4434
  function safeJsonParse(s) {
4500
4435
  try {
4501
4436
  return JSON.parse(s);
@@ -4503,14 +4438,6 @@ function safeJsonParse(s) {
4503
4438
  return;
4504
4439
  }
4505
4440
  }
4506
- /**
4507
- * Wraps a field into a JSON array of values (storage: TEXT). The inner zod is `base.zod`.
4508
- * Modifier order: base → applyMultiple.
4509
- *
4510
- * Exception: kind 'image'/'media' handle multiple themselves in the renderer (gallery) —
4511
- * the MultipleField* wrappers on the web skip them.
4512
- */
4513
- /** Zod "JSON array of inner values" — used by applyMultiple. */
4514
4441
  function multipleZod(inner) {
4515
4442
  return unknown().superRefine((raw, ctx) => {
4516
4443
  let arr;
@@ -4558,18 +4485,15 @@ function applyMultiple(base, multiple) {
4558
4485
  multiple: true
4559
4486
  },
4560
4487
  json: true,
4561
- zod: optionalize(s, base.required)
4488
+ zod: optionalize(s)
4562
4489
  };
4563
4490
  }
4564
- /** Inline option entry → OptionItem (plain string = value and label at once). */
4565
4491
  function toOptionItem(o) {
4566
4492
  return typeof o === "string" ? {
4567
4493
  value: o,
4568
4494
  title: o
4569
4495
  } : o;
4570
4496
  }
4571
- /** Canonical comparison form. Both the input and the option value pass through it, so a
4572
- * numeric field compares `100` against the option spelled `'100'`. */
4573
4497
  function optionKey(column, v) {
4574
4498
  if (column === "integer" || column === "real") {
4575
4499
  const n = Number(v);
@@ -4577,12 +4501,6 @@ function optionKey(column, v) {
4577
4501
  }
4578
4502
  return String(v ?? "");
4579
4503
  }
4580
- /**
4581
- * Membership check LAYERED OVER the field's own schema, never replacing it. Replacing it
4582
- * with `z.enum` — which is what select used to do — works only over a bare string: over an
4583
- * int it would destroy the int-ness, the coercion and the min/max, and over an ip it would
4584
- * destroy the structure. Layering keeps every type's own error code and adds one.
4585
- */
4586
4504
  function gateZod(inner, gate, multiple, column) {
4587
4505
  return inner.superRefine((v, ctx) => {
4588
4506
  if (gate.values === null || v === void 0 || v === null || v === "") return;
@@ -4594,11 +4512,6 @@ function gateZod(inner, gate, multiple, column) {
4594
4512
  });
4595
4513
  });
4596
4514
  }
4597
- /**
4598
- * Attaches `options`/`strict` to any field. Called from `wrapKey`, which EVERY factory
4599
- * funnels through — core and plugin-defined alike — so a new field type gets value options
4600
- * without writing a line for them.
4601
- */
4602
4515
  function applyOptions(base, o) {
4603
4516
  if (base.optionGate) return base;
4604
4517
  if (base.relation || base.parts || base.options) return base;
@@ -4627,22 +4540,10 @@ function applyOptions(base, o) {
4627
4540
  }
4628
4541
  return m;
4629
4542
  }
4630
- /**
4631
- * Fills (or refills) a strict field's closed set. Called by composeRegistry once the full
4632
- * option set is known — inline options, plugin contributions and the named source merged.
4633
- * A non-strict field has no gate and is left alone.
4634
- */
4635
4543
  function bindOptionsZod(field, values) {
4636
4544
  if (!field.optionGate || !values.length) return;
4637
4545
  field.optionGate.values = new Set(values.map((v) => optionKey(field.column, v)));
4638
4546
  }
4639
- /**
4640
- * Wraps a FieldMeta into a node. `key` is the name the PARENT gave this child
4641
- * (materialize/pipeline.ts) — an argument, not an option a factory reads out of its own
4642
- * opts, which is why it comes first. Absent means the element was never declared under a
4643
- * name: it becomes a keyless value node when it carries a `value`, and a bare FieldMeta
4644
- * otherwise.
4645
- */
4646
4547
  function wrapKey(key, opts, meta) {
4647
4548
  let m = applyOptions(meta, opts);
4648
4549
  if (opts.noEditControl) m = {
@@ -4673,13 +4574,6 @@ function wrapKey(key, opts, meta) {
4673
4574
  noLabel: true
4674
4575
  }
4675
4576
  };
4676
- if (opts.role) m = {
4677
- ...m,
4678
- hints: {
4679
- ...m.hints,
4680
- role: opts.role
4681
- }
4682
- };
4683
4577
  if (opts.editor) m = {
4684
4578
  ...m,
4685
4579
  hints: {
@@ -4769,7 +4663,7 @@ function wrapKey(key, opts, meta) {
4769
4663
  return m;
4770
4664
  }
4771
4665
  //#endregion
4772
- //#region ../sdk/src/fields/normalize.ts
4666
+ //#region ../sdk/dist/fields/normalize.js
4773
4667
  function normalizeOpts(rawIn) {
4774
4668
  const raw = rawIn;
4775
4669
  if (raw["view"] !== void 0) throw new Error("[normalizeOpts] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
@@ -4810,37 +4704,14 @@ function normalizeOpts(rawIn) {
4810
4704
  faces: v.faces,
4811
4705
  emphasis: v.emphasis,
4812
4706
  noLabel: v.noLabel,
4813
- role: v.role,
4814
4707
  editor: v.editor,
4815
4708
  activate: v.activate
4816
4709
  };
4817
4710
  return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
4818
4711
  }
4819
4712
  //#endregion
4820
- //#region ../sdk/src/materialize/decl.ts
4821
- /**
4822
- * PHASE 1 — declaration. A factory returns this and nothing else: plain data, no zod,
4823
- * no column, no widget. A parent may rewrite ANY property of a child's declaration
4824
- * before phase 2 builds it (see materialize/pipeline.ts).
4825
- */
4713
+ //#region ../sdk/dist/materialize/decl.js
4826
4714
  var BRAND = "__fieldDecl";
4827
- /**
4828
- * `opts.fields` vs `decl.fields` — the same author input reachable two ways, and only one of
4829
- * them is ever safe to read as BUILT children:
4830
- *
4831
- * - `opts.fields` is the RAW author input, unpacked by `normalizeOpts`'s spread (it does
4832
- * not know or care that `fields` is special) — never rewritten, never materialized. A
4833
- * composite that computes its OWN roles from what the author passed (`check`'s content
4834
- * fields) reads it from here, at declare() time, exactly as the author wrote it — decls
4835
- * and all.
4836
- * - `decl.fields` is what `declare()` copies from that same raw input for the tree walk
4837
- * (`materialize/pipeline.ts`) and for `resolveParts` to find. Once a parent has been
4838
- * materialized, this is where its BUILT children live.
4839
- *
4840
- * A consumer that wants built children — not raw author input — reads the materialized tree
4841
- * (`materialize()`/`materializeTree()`'s output), never `decl.opts.fields`: that stays
4842
- * exactly what the author passed, decls included, for as long as the decl is unbuilt.
4843
- */
4844
4715
  function declare(factory, raw) {
4845
4716
  const opts = normalizeOpts(raw);
4846
4717
  const spec = raw;
@@ -4855,9 +4726,7 @@ function isDecl(v) {
4855
4726
  return typeof v === "object" && v !== null && BRAND in v;
4856
4727
  }
4857
4728
  //#endregion
4858
- //#region ../sdk/src/materialize/registry.ts
4859
- /** True for a type registered through `roles`/`roleZod`/`structureCode` (materialize/composite.ts)
4860
- * rather than a plain `build()` — read generically, never by factory name. */
4729
+ //#region ../sdk/dist/materialize/registry.js
4861
4730
  function isComposite(type) {
4862
4731
  return "roles" in type;
4863
4732
  }
@@ -4871,12 +4740,6 @@ function typeOf(factory) {
4871
4740
  if (!def) throw new Error(`[types] unknown factory '${factory}'`);
4872
4741
  return def;
4873
4742
  }
4874
- /**
4875
- * `presetOpts` merged as DEFAULTS under the caller's own opts (`{ ...presetOpts, ...o }`) —
4876
- * an author's own value always wins. `config` is merged the same way one level deep, so a
4877
- * preset's own default (`weight`'s `config.min: 1`) survives an author setting a DIFFERENT
4878
- * config key (`rules: { max: 5000 }`) without the preset needing to repeat it.
4879
- */
4880
4743
  function mergePresetOpts(presetOpts, o) {
4881
4744
  const config = presetOpts.config || o.config ? {
4882
4745
  ...presetOpts.config,
@@ -4888,37 +4751,6 @@ function mergePresetOpts(presetOpts, o) {
4888
4751
  ...config ? { config } : {}
4889
4752
  };
4890
4753
  }
4891
- /**
4892
- * Registers a PRESET: a type that is its base type's `TypeDef` with fixed/default opts
4893
- * merged in, and — where it differs — its own `kind`/`widget`/`build`. This is what lets
4894
- * `field-presets.ts` express "a preset is its base type plus these opts" as DATA instead of
4895
- * copy-pasting normalizeOpts/optionalize/applyMultiple/wrapKey into a hand-written body:
4896
- * `kind`/`prim`/`widget`/`selfManages` default to the base type's own — a
4897
- * preset differs from its base in BEHAVIOUR, not in what column/prim family it belongs to; a
4898
- * change that DOES need a different prim/column is not a preset, it is a new type — and
4899
- * `over` replaces exactly the properties a given preset differs in.
4900
- *
4901
- * `presetOpts` are DEFAULTS the caller's own opts can still override (see `mergePresetOpts`
4902
- * above). A preset that must instead FORCE a value regardless of the caller (`country`'s
4903
- * `options: 'countries'`, `tags`'s `multiple: true`) is not this mechanism — those forward
4904
- * directly to the base factory with the forced opts spread AFTER the caller's own
4905
- * (`string({ ...raw, options: 'countries' })`), the pattern `country`/`currency`/`percent`/
4906
- * `year`/`tags` already use, none of which needs its own registry entry: their `factory` IS
4907
- * the base's, unchanged.
4908
- *
4909
- * `over.build`, when given, replaces the base type's `build()` entirely for THIS preset —
4910
- * still called with the merged opts, so it may itself delegate to the base's own `build()`
4911
- * and layer extra hints on top (`snippet` adds `hints.language` over `text`'s own build).
4912
- *
4913
- * Resolves `base` LAZILY (on first actual read, memoized), never at the moment
4914
- * `registerPreset` itself is called: `field-presets.ts` calls it at MODULE TOP LEVEL, inside
4915
- * the same circular import (`fields.ts` ⇄ `field-presets.ts`) its own header note already
4916
- * documents — depending on which module the loader entered first, the base's own
4917
- * `registerType` call (in `fields.ts`) may not have run yet. `text`/`i18n`/`smallText` in
4918
- * fields.ts sidestep the same trap by calling `typeOf('string')` only from inside their
4919
- * `build()`, never at their own `registerType()` call site; a preset's `kind`/`prim`/
4920
- * `widget`/`selfManages` need the identical deferral since they too default from the base.
4921
- */
4922
4754
  function registerPreset(factory, base, presetOpts, over = {}) {
4923
4755
  let resolved;
4924
4756
  const baseType = () => {
@@ -4945,44 +4777,17 @@ function registerPreset(factory, base, presetOpts, over = {}) {
4945
4777
  });
4946
4778
  }
4947
4779
  //#endregion
4948
- //#region ../sdk/src/materialize/composite.ts
4949
- /**
4950
- * ONE composite transform. It was written six times (geo, illustrated, measured, range,
4951
- * dimensions, check(single)) and differed only in the validation code and the per-role
4952
- * default zod.
4953
- *
4954
- * raw → jsonValue → absent by `absenceRoles`? (required → 'required')
4955
- * → carries nothing at all? (pass through, there is nothing to judge)
4956
- * → rowSchema (partsRowShape) → stripServerOwnedParts → optional per-type refine
4957
- *
4958
- * A `CompositeTypeDef` names ONLY what its own type contributes: its roles, the default
4959
- * zod per role, and how its structure failure reports. Everything else — the empty
4960
- * check, the JSON tolerance, the row parse, stripping server-owned parts — lives here
4961
- * exactly once.
4962
- */
4780
+ //#region ../sdk/dist/materialize/composite.js
4963
4781
  function buildComposite(def, opts, parts) {
4964
- const required = opts.required ?? false;
4965
- const roleZod = def.roleZod(opts, parts);
4966
- const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
4967
- const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
4782
+ const rowSchema = object(partsRowShape(def.roleZod(opts, parts), parts));
4968
4783
  const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
4969
- const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
4970
- const clientKeys = def.absenceRoles ? roleKeys() : void 0;
4784
+ const clientKeys = def.acceptsEmptyRow ? roleKeys() : void 0;
4971
4785
  const zod = unknown().transform((raw, ctx) => {
4972
4786
  const parsed = jsonValue(raw);
4973
4787
  const p = parsed;
4974
4788
  const isRow = p == null || typeof p === "object" && !Array.isArray(p);
4975
- /** Every one of `keys` empty in this value — `null`/absent alike. */
4976
4789
  const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
4977
- let absent = false;
4978
- if (absenceKeys && clientKeys) {
4979
- absent = allEmpty(absenceKeys);
4980
- if (absent && required) ctx.addIssue({
4981
- code: ZodIssueCode.custom,
4982
- message: vmsg("required")
4983
- });
4984
- if (allEmpty(clientKeys)) return raw;
4985
- }
4790
+ if (clientKeys && allEmpty(clientKeys)) return raw;
4986
4791
  if (typeof parsed === "string") {
4987
4792
  ctx.addIssue({
4988
4793
  code: ZodIssueCode.custom,
@@ -4990,7 +4795,7 @@ function buildComposite(def, opts, parts) {
4990
4795
  });
4991
4796
  return NEVER;
4992
4797
  }
4993
- const r = (absent ? absentRowSchema : rowSchema).safeParse(parsed);
4798
+ const r = rowSchema.safeParse(parsed);
4994
4799
  if (!r.success) {
4995
4800
  const message = typeof def.structureCode === "function" ? def.structureCode(r.error) : vmsg(def.structureCode);
4996
4801
  ctx.addIssue({
@@ -5015,27 +4820,7 @@ function buildComposite(def, opts, parts) {
5015
4820
  };
5016
4821
  }
5017
4822
  //#endregion
5018
- //#region ../sdk/src/materialize/pipeline.ts
5019
- /**
5020
- * PHASE 2 — materialization. The six steps every field goes through, in one place:
5021
- * they used to be copied into 40-odd factory bodies (43 normalizeOpts heads,
5022
- * 38 wrapKey tails, 37 optionalize calls).
5023
- *
5024
- * Composites (roles, the shared structure transform) arrive in materialize/composite.ts;
5025
- * this file resolves a composite's parts and picks the right build path, but the
5026
- * transform itself lives there, exactly once.
5027
- */
5028
- /**
5029
- * `name` is the property this declaration was declared under in its parent's map — the ONLY
5030
- * source of a key a declared field ever has (a factory has no `key` option to read one from)
5031
- * and it is threaded straight through to `wrapKey`, which keys the result whenever `name` is
5032
- * defined. Absent (a top-level `materialize()` call on a standalone declaration) the result
5033
- * is keyless: a value node if it carries a `value`, a bare FieldMeta otherwise.
5034
- *
5035
- * This function does not itself stamp the result's `name` (the ADDRESS, `LayoutNode.name` —
5036
- * see that type's own doc comment) — its caller, `materializeEl`, does that for every branch,
5037
- * including this one.
5038
- */
4823
+ //#region ../sdk/dist/materialize/pipeline.js
5039
4824
  function materialize(decl, name) {
5040
4825
  const opts = decl.opts;
5041
4826
  const type = typeOf(decl.factory);
@@ -5043,12 +4828,10 @@ function materialize(decl, name) {
5043
4828
  if (isComposite(type) && (opts.multiple ?? false) && parts.length > 0 && !type.selfManages?.has("multiple")) return collectionGroup(name, opts, parts, type.collectionView);
5044
4829
  const built = isComposite(type) ? buildComposite(type, opts, parts) : type.build(opts, parts, name);
5045
4830
  const selfOptional = type.selfManages?.has("optional") ?? false;
5046
- const required = selfOptional ? false : opts.required ?? false;
5047
4831
  const base = {
5048
4832
  kind: type.kind,
5049
4833
  label: opts.label ?? "",
5050
4834
  ...opts.agent !== void 0 && { agent: opts.agent },
5051
- required,
5052
4835
  prim: type.prim,
5053
4836
  column: built.column,
5054
4837
  hints: built.hints ?? {},
@@ -5059,26 +4842,10 @@ function materialize(decl, name) {
5059
4842
  columns: built.columns,
5060
4843
  parts
5061
4844
  },
5062
- zod: selfOptional ? built.zod : optionalize(built.zod, required)
4845
+ zod: selfOptional ? built.zod : optionalize(built.zod)
5063
4846
  };
5064
4847
  return wrapKey(name, opts, type.selfManages?.has("multiple") ? base : applyMultiple(base, opts.multiple ?? false));
5065
4848
  }
5066
- /**
5067
- * The shared storage transform behind a composite's `multiple` form: a collection group
5068
- * (child table) whose row fields ARE the given parts, under their role names — the exact
5069
- * `ContainerNode` shape `fields.ts`'s own `group({key,label,multiple:true,required,fields,view})`
5070
- * would build for this call pattern (no icon, no `rules`, no `value` — none of the three
5071
- * former call sites ever passed them), constructed directly rather than through `group()`
5072
- * itself to avoid a runtime edge back into fields.ts (see this module's own header note).
5073
- *
5074
- * Reached automatically, by the storage rule in `materialize()` above, for every composite
5075
- * registered through `registerType`/`CompositeTypeDef` — `illustrated`, `check` and
5076
- * `period` among them. `check`'s `roles(opts)` already returns checkbox-first order for
5077
- * `multiple` and content-first for `single`, and `period`'s `roles(opts)` returns the same
5078
- * from/until order regardless of `multiple`, so the generic path needs no special case for
5079
- * either — the row-field ORDER is each type's own declared data (its `roles`), not
5080
- * something this helper infers.
5081
- */
5082
4849
  function collectionGroup(key, opts, parts, collectionView) {
5083
4850
  const rowFields = parts.map((p) => ({
5084
4851
  ...wrapKey(p.role, p.mode === "computedStored" ? { value: p.value } : {}, p.meta),
@@ -5089,7 +4856,6 @@ function collectionGroup(key, opts, parts, collectionView) {
5089
4856
  key,
5090
4857
  scope: "nest",
5091
4858
  multiple: true,
5092
- required: opts.required ?? false,
5093
4859
  label: opts.label ?? key,
5094
4860
  ...opts.agent !== void 0 && { agent: opts.agent },
5095
4861
  display: "wrap",
@@ -5097,22 +4863,6 @@ function collectionGroup(key, opts, parts, collectionView) {
5097
4863
  fields: rowFields
5098
4864
  };
5099
4865
  }
5100
- /**
5101
- * One tree node, named by the property it was declared under. Every returned node — every
5102
- * branch below — carries that name (`name`, the ADDRESS a parent reaches it by) once `name`
5103
- * is defined; only the STORAGE name (`key`) is conditional.
5104
- *
5105
- * A DECLARATION takes that name as both its `name` and its `key` (`materialize` → `wrapKey`
5106
- * always keys a named declaration). An already-built element takes `key` too, but only if it
5107
- * OWNS A SCOPE and has not been named yet: a `ContainerNode` that declares `scope: 'nest'`
5108
- * owns a column prefix or a child table, and that scope has to be named — `group()`/`row()`/
5109
- * `keyed()` no longer take a key of their own, so the parent's property name is where it
5110
- * comes from. Everything else — a `'hoist'` group (an editorial block, a sheet row: it
5111
- * arranges children inside the parent's scope and owns none), a node already carrying its
5112
- * key, a keyless value node, a pseudo-element — owns no scope, so it keeps whatever `key` it
5113
- * already had (usually none), but still gets `name` stamped: that is the whole point of this
5114
- * task — a `'hoist'` child's declared property name used to be discarded here entirely.
5115
- */
5116
4866
  function materializeEl(el, name) {
5117
4867
  if (isDecl(el)) {
5118
4868
  const built = materialize(el, name);
@@ -5138,61 +4888,25 @@ function materializeEl(el, name) {
5138
4888
  name
5139
4889
  } : el;
5140
4890
  }
5141
- /**
5142
- * A built group that owns a scope but has not been given its storage key yet — i.e. it HAS
5143
- * children (`fields`, the same question `hasChildren` in `../fields.ts` asks) and a `'nest'`
5144
- * scope, but no key. Checked structurally rather than by importing `hasChildren`: this file
5145
- * has no RUNTIME edge back into `fields.ts` (see the header note) — `Array.isArray` is the
5146
- * same HAS-question, asked locally to keep that boundary.
5147
- */
5148
4891
  function isNamelessScope(el) {
5149
4892
  const g = el;
5150
4893
  return typeof g === "object" && g !== null && Array.isArray(g.fields) && g.scope === "nest" && g.key == null;
5151
4894
  }
5152
- /**
5153
- * A bare `FieldMeta`: it HAS none of a node's own marks yet — no `el` (so not a static value or
5154
- * a pseudo-element — those are keyless ON PURPOSE and keep only an address) and no `key` (so
5155
- * not a named field) — but it DOES carry a built field's own marks (`kind`, `zod`). This is a
5156
- * declaration-stage shape, before it becomes a node at all, so it asks about its own
5157
- * properties directly rather than through `hasValue`/`hasChildren` (which read `.type`/
5158
- * `.fields`, not `.kind`/`.zod`).
5159
- */
5160
4895
  function isNamelessField(el) {
5161
4896
  const m = el;
5162
4897
  return typeof m === "object" && m !== null && !("el" in m) && !("key" in m) && typeof m.kind === "string" && m.zod !== void 0;
5163
4898
  }
5164
- /** `group()` falls back to the key for a missing label; with the key arriving here instead,
5165
- * that fallback has to be applied here too, or a nameless scope would keep an empty one. */
5166
4899
  function labelOf(el, name) {
5167
4900
  const g = el;
5168
4901
  return g.label === void 0 || g.label === "" ? name : g.label;
5169
4902
  }
5170
4903
  var NUMERIC_LIKE$1 = /^\d+$/;
5171
- /**
5172
- * A LayoutEl tree: declarations become built elements, everything else passes through.
5173
- * ONE input shape — a NAMED MAP, whose property name becomes the child's key. A
5174
- * numeric-like name throws: object iteration would reorder such keys, and declaration
5175
- * order IS the field order.
5176
- */
5177
4904
  function materializeTree(els) {
5178
4905
  return Object.entries(els).map(([name, el]) => {
5179
4906
  if (NUMERIC_LIKE$1.test(name)) throw new Error(`fields: numeric-like child name '${name}' (object iteration would reorder the children)`);
5180
4907
  return materializeEl(el, name);
5181
4908
  });
5182
4909
  }
5183
- /**
5184
- * A ShelfDef/ExtendDef/SettingsDef-shaped object: materializes its `fields` tree, every
5185
- * other property carried through unchanged. `defineShelf`/`defineExtend`/`defineSettings`
5186
- * (and `composeRegistry`'s defensive pass over a hand-rolled manifest) all call this instead
5187
- * of repeating `{ ...def, fields: materializeTree(def.fields) as LayoutEl[] }` — the
5188
- * copy-paste this refactor exists to delete, one level up from the factory bodies.
5189
- *
5190
- * `T`'s own `fields` may be the author-facing `LayoutInput` (a `ShelfDefInput`/
5191
- * `ExtendDefInput`/`SettingsDefInput`) or an already-built `LayoutEl[]` (materializeDef is
5192
- * a no-op on an already-built tree — see fields.ts's own note on that) — either way, the
5193
- * RETURNED object's `fields` is always the built `LayoutEl[]`: the input type widens, the
5194
- * materialized type never does.
5195
- */
5196
4910
  function materializeDef(def) {
5197
4911
  const fields = Array.isArray(def.fields) ? def.fields : materializeTree(def.fields);
5198
4912
  return {
@@ -5201,25 +4915,7 @@ function materializeDef(def) {
5201
4915
  };
5202
4916
  }
5203
4917
  //#endregion
5204
- //#region ../sdk/src/parts.ts
5205
- /**
5206
- * Composite value parts.
5207
- *
5208
- * A composite field type declares its ROLES (what the pieces of its value mean); the
5209
- * author of a shelf supplies FILLINGS for those roles through `parts`. The storage
5210
- * columns of the composite are derived from the result instead of being written by
5211
- * hand, and each piece is an ordinary field — typed, labelled and renderable.
5212
- *
5213
- * The role set is closed: consumers (unit conversion, formatting, describe_shelf) rely
5214
- * on knowing which piece is the magnitude and which is the unit. What FILLS a role is
5215
- * open: a magnitude may be real/int/rating, a unit may be a select or a relation.
5216
- */
5217
- /**
5218
- * `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
5219
- * author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
5220
- * by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
5221
- * those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
5222
- */
4918
+ //#region ../sdk/dist/parts.js
5223
4919
  var UI_KEYS = [
5224
4920
  "hidden",
5225
4921
  "noEditControl",
@@ -5232,58 +4928,29 @@ var UI_KEYS = [
5232
4928
  "compareWith",
5233
4929
  "span"
5234
4930
  ];
5235
- /** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
5236
- * none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
5237
4931
  function uiOf(opts) {
5238
4932
  if (!opts) return void 0;
5239
4933
  const v = {};
5240
4934
  for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5241
4935
  return Object.keys(v).length ? v : void 0;
5242
4936
  }
5243
- /** A filling built from a ported factory arrives as a FieldDecl — build it into the
5244
- * node or bare FieldMeta it represents; anything else (an unported factory's
5245
- * result, or an already-built filling) passes through unchanged. `name` (the role) is
5246
- * the part's own storage key (`ResolvedPart.key` is always the role, see below) — passing
5247
- * it to `materialize` is what lets `wrapKey` recognize a computed filling as computed
5248
- * (its `computed` test requires a key) and stamp `compute`/`derived`/`hints.noEditControl`
5249
- * on the built meta, exactly as it would for any other keyed field. */
5250
4937
  function built(input, name) {
5251
4938
  return isDecl(input) ? materialize(input, name) : input;
5252
4939
  }
5253
- /**
5254
- * Whether `def` admits `d` as-is — the diagnostic question `RoleDef.accepts` answers.
5255
- * `resolveParts` uses this to decide whether a correction happened (and is worth logging);
5256
- * a role's own `normalize` uses it to decide whether to return the filling unchanged.
5257
- */
5258
4940
  function accepts(def, d) {
5259
4941
  return Array.isArray(def.accepts) ? def.accepts.includes(typeOf(d.factory).prim) : def.accepts.test(d);
5260
4942
  }
5261
- /**
5262
- * The correction every role gets for free: keep what the author said ABOUT the field
5263
- * (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
5264
- * drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
5265
- * picture — and `multiple`, because a composite role owns exactly one column). A role whose
5266
- * own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
5267
- * writes its own `normalize` instead of using this.
5268
- */
5269
4943
  function retypeTo(factory) {
5270
4944
  return (d) => {
5271
- const { label, required, value } = d?.opts ?? {};
4945
+ const { label, value } = d?.opts ?? {};
5272
4946
  return declare(factory, {
5273
4947
  label,
5274
- required,
5275
4948
  value,
5276
4949
  noSearch: d?.opts.noSearch,
5277
4950
  ui: uiOf(d?.opts)
5278
4951
  });
5279
4952
  };
5280
4953
  }
5281
- /**
5282
- * Build a RoleDef whose correction is the shared default: accept a filling `accepts`
5283
- * admits UNCHANGED, otherwise `retypeTo(defaultFactory)`. The common case — every role that
5284
- * needs no per-instance default (a currency scaled to the field's own `units`, an endpoint
5285
- * at the composite's own granularity) reads as one line.
5286
- */
5287
4954
  function role(name, acceptsSpec, defaultFactory) {
5288
4955
  const retype = retypeTo(defaultFactory);
5289
4956
  const def = {
@@ -5293,28 +4960,15 @@ function role(name, acceptsSpec, defaultFactory) {
5293
4960
  };
5294
4961
  return def;
5295
4962
  }
5296
- /**
5297
- * THE NAMING RULE for a composite: every part lives under its ROLE name, always — in the
5298
- * value object (`{ value: 5, unit: 'kg' }`) and in storage (column `<field key>__<role>`,
5299
- * `partColumns`). Consumers that reason about MEANING (a widget looking for the unit, unit
5300
- * conversion, formatting) may hard-code the role name as a property name directly; this
5301
- * helper exists only so a caller that already has a `ResolvedPart`/`PartSlot` need not know
5302
- * that.
5303
- */
5304
4963
  function partValueKey(p) {
5305
4964
  return p.role;
5306
4965
  }
5307
- /** role → the key that role occupies in the value object. See `partValueKey`. */
5308
4966
  function partValueKeys(parts) {
5309
4967
  const out = {};
5310
4968
  for (const p of parts) out[p.role] = p.role;
5311
4969
  return out;
5312
4970
  }
5313
4971
  var NUMERIC_LIKE = /^\d+$/;
5314
- /** The three shapes a factory returns, reduced to (key, meta, value). `name` is the
5315
- * role this filling occupies — threaded to `built()` so a computed filling is built AS a
5316
- * keyed field (see `built`'s own note); `ResolvedPart.key` below still always uses the
5317
- * role directly, never this function's `key`. */
5318
4972
  function readInput(input, name) {
5319
4973
  input = built(input, name);
5320
4974
  if (hasValue(input) && input.key !== void 0) return {
@@ -5333,18 +4987,6 @@ function readInput(input, name) {
5333
4987
  value: void 0
5334
4988
  };
5335
4989
  }
5336
- /**
5337
- * Roles in declaration order, each with its filling. `owner` names the composite this
5338
- * call resolves parts for (its own field key, supplied by the pipeline) — used only to
5339
- * tag the one diagnostic line below.
5340
- *
5341
- * Throws with a message naming the composite's roles for an UNKNOWN role name — a typo
5342
- * there fails loudly at `defineShelf` time rather than yielding a column that silently
5343
- * never appears. It has nothing to say about a RENAMED storage key any more: a filling
5344
- * carries no name of its own, so the role is the only source of one. A filling whose TYPE
5345
- * the role does not accept is never rejected either: the role is a GUARD, not a filter, and
5346
- * CORRECTS it instead (`RoleDef.normalize`) — see this module's header note.
5347
- */
5348
4990
  function resolveParts(roles, spec, owner) {
5349
4991
  const known = new Set(roles.map((r) => r.role));
5350
4992
  for (const r of roles) if (r.role.includes("__")) throw new Error(`parts: role name '${r.role}' contains '__', the storage path separator`);
@@ -5393,25 +5035,6 @@ function resolveParts(roles, spec, owner) {
5393
5035
  for (const p of out) if (p.meta.parts) assertInertNesting(p.meta, `${owner ?? "(unkeyed)"}.${p.key}`);
5394
5036
  return out;
5395
5037
  }
5396
- /**
5397
- * A composite filling a composite's role STORES fine at any depth: `partColumns` expands
5398
- * it, and the value↔columns walks (`flattenComposite`/`nestComposite`) follow it. The
5399
- * COMPUTE side does not follow it yet. Every engine that addresses a composite part reaches
5400
- * exactly ONE level below a field:
5401
- *
5402
- * - `completeValue` / `resolveField` (server/compute-unit.ts) — runs a part's `value`
5403
- * - `applyStoredParts` (server/part-injection.ts) — writes the computed column
5404
- * - `cacheClockColumns` / `levelClockColumnsByOwner` (server/cache-clock.ts) — the
5405
- * `__cachedAt` column a `cache` needs to ever refresh
5406
- * - the file normalizer (server/file-fields.ts) — resolves a part's `{name}` pointer
5407
- * - `collectMutateSources` / `collectComputeUnits` (sdk/mutate-graph.ts) — the trigger graph
5408
- *
5409
- * A part BELOW that level carrying any of those would own a real column that nothing ever
5410
- * writes, refreshes, validates or triggers — the exact silent failure a storage change must
5411
- * not ship. So it is refused where it is DECLARED, naming the path, instead of failing
5412
- * quietly at runtime. Lifting this is a compute-engine change, not a storage one: nothing
5413
- * about the columns needs to move for it.
5414
- */
5415
5038
  function assertInertNesting(meta, path) {
5416
5039
  for (const p of meta.parts ?? []) {
5417
5040
  const where = `${path}.${p.key}`;
@@ -5425,29 +5048,6 @@ function assertInertNesting(meta, path) {
5425
5048
  if (p.meta.parts) assertInertNesting(p.meta, where);
5426
5049
  }
5427
5050
  }
5428
- /**
5429
- * Storage columns of a composite: sub-PATH → column type. Every part contributes its own
5430
- * columns, whatever they are.
5431
- *
5432
- * A part is a NODE, not necessarily a leaf: a filling that is itself a composite carries
5433
- * its own `columns`, and those are what it contributes — under its role name as a prefix,
5434
- * joined by the same `__` the physical column name uses (`at` filled by `f.geo` →
5435
- * `at__lat`/`at__lng`/`at__label`). A part with no `columns` is a scalar and contributes
5436
- * exactly one entry, its own `column`, under its role name. That is where the walk STOPS,
5437
- * and every walk stops there: a scalar is the only thing a composite can bottom out in.
5438
- *
5439
- * The loop is one level deep because the result is built INDUCTIVELY, not by re-walking a
5440
- * tree. A filling is materialized before the composite that holds it reads it, so
5441
- * `p.meta.columns` was already produced by this same function and already carries the FULL
5442
- * sub-path of everything below it. Depth therefore costs nothing here and is bounded by
5443
- * nothing but the column NAME the joined path produces — see `storageColumns` (shelf.ts)
5444
- * for the join, and `MAX_COLUMN_NAME` (compose.ts) for the budget that name must fit.
5445
- *
5446
- * The result is the FLAT storage projection of the composite. The STRUCTURE lives in
5447
- * `FieldMeta.parts`, which is what the value↔columns walks (`nestComposite`/
5448
- * `flattenComposite` in shelf.ts) read: they need to know where one nested object ends and
5449
- * the next begins, and a joined key cannot say that on its own.
5450
- */
5451
5051
  function partColumns(parts) {
5452
5052
  const cols = {};
5453
5053
  for (const p of parts) {
@@ -5457,81 +5057,15 @@ function partColumns(parts) {
5457
5057
  }
5458
5058
  return cols;
5459
5059
  }
5460
- function partsRowShape(shape, parts, tolerance = "strict") {
5060
+ function partsRowShape(shape, parts) {
5461
5061
  const out = { ...shape };
5462
5062
  for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
5463
5063
  else {
5464
5064
  const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
5465
- const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
5466
- out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
5065
+ out[p.key] = own.nullable();
5467
5066
  }
5468
5067
  return out;
5469
5068
  }
5470
- /**
5471
- * MIXED OWNERSHIP of the STORED parts: the composite has a computed-and-stored part (the
5472
- * server owns that column) AND a plain stored part (the client owns that one). Returns the
5473
- * two role lists, or null when the composite is uniformly owned.
5474
- *
5475
- * Such a composite IS materialized by the write path (`applyStoredParts`): the server's part
5476
- * is computed on every write, whether or not the write carries the field, and the client's
5477
- * columns keep whatever the record already holds. A computed sum exists whether or not a
5478
- * user has picked a unit, and a NULL column is invisible to SQL. See THE ABSENT-COMPOSITE
5479
- * RULE in `@coffer-org/server/part-injection` and `docs/decisions.md` (2026-08-12).
5480
- *
5481
- * The one shape this is still wrong for is a composite that MANDATES one of those client-owned
5482
- * roles — see `mandatedClientParts` below.
5483
- */
5484
- function mixedOwnedStoredParts(parts) {
5485
- const server = parts.filter((p) => p.mode === "computedStored").map((p) => p.role);
5486
- const client = parts.filter((p) => p.mode === "stored").map((p) => p.role);
5487
- return server.length > 0 && client.length > 0 ? {
5488
- server,
5489
- client
5490
- } : null;
5491
- }
5492
- /**
5493
- * The CLIENT-OWNED stored roles this composite demands a value for — the roles a
5494
- * server-materialized half-value would leave empty in breach of the field's own contract.
5495
- * Empty list → nothing is mandated and the composite is safe to materialize.
5496
- *
5497
- * THE MANDATE HAS TWO SOURCES, and reading only the first is a bug this had:
5498
- *
5499
- * - the FIELD's own `required`, which `partsRowShape` turns into `tolerateNull = false` for
5500
- * every client-owned slot;
5501
- * - the FILLING's `required`, per part: `partsRowShape` keeps an OVERRIDDEN part's own
5502
- * mandate ("a filling the author declared `required` keeps its mandate"), so
5503
- * `fields: { unit: f.string({ required: true, … }) }` makes THAT slot non-nullable even
5504
- * when the field around it is optional.
5505
- *
5506
- * Either way the materialized value — the server's column filled, the client's empty — reads
5507
- * back as `{value: 5, unit: null}` and is rejected by the composite's own zod with
5508
- * `{"code":"required"}` the moment a record form or an MCP agent feeds it straight back. So
5509
- * both sources must count, and the write path refuses to materialize when either speaks.
5510
- *
5511
- * A CONDITION counts too, though its harm is different and milder. `partsRowShape` compares
5512
- * `=== true`, so a conditional mandate does not make the slot non-nullable and the round trip
5513
- * still parses; what it does instead is let a materialized object satisfy
5514
- * `updateRecord`'s conditional-required check (which only asks whether the value is non-null),
5515
- * excusing the user from a field the schema says is mandatory. Nothing static can evaluate a
5516
- * Condition here, so any mandate that is not literally `false`/absent is treated as speaking —
5517
- * conservative in the safe direction, since refusing to materialize is exactly the older
5518
- * behaviour and loses nothing.
5519
- */
5520
- function mandatedClientParts(required, parts) {
5521
- const speaks = (flag) => flag !== void 0 && flag !== false;
5522
- const field = speaks(required);
5523
- return parts.filter((p) => p.mode === "stored" && (field || p.overridden && speaks(p.meta.required))).map((p) => p.role);
5524
- }
5525
- /**
5526
- * Drops SERVER-OWNED part slots from a successfully parsed composite row: a computed-and-stored
5527
- * role, keyed by its role name. Its value belongs to the schema, not to the writer — it is
5528
- * recomputed and written by the server on the same write (`applyStoredParts`) — so a client
5529
- * sending one back is stripped rather than rejected: the schema wins either way.
5530
- *
5531
- * Stripping (not `z.never()`-rejecting, as a computed-and-stored top-level FIELD does) is
5532
- * what keeps a read → PATCH-the-whole-object round trip working: a read hands the client that
5533
- * slot straight out of its column, so rejecting the echo would reject every full-object PATCH.
5534
- */
5535
5069
  function stripServerOwnedParts(value, parts) {
5536
5070
  let out = value;
5537
5071
  for (const p of parts) if (p.mode === "computedStored" && p.key in out) {
@@ -5541,41 +5075,17 @@ function stripServerOwnedParts(value, parts) {
5541
5075
  return out;
5542
5076
  }
5543
5077
  //#endregion
5544
- //#region ../sdk/src/field-presets.ts
5545
- /**
5546
- * Field presets are thin wrappers around the primitives in fields.ts.
5547
- *
5548
- * Each preset = one kind (one widget). Presets do not accept `format`;
5549
- * they are semantic types themselves. min/max/step go through `config`.
5550
- *
5551
- * Presets are added to `f` through `composeF`, which guarantees no preset
5552
- * overrides a primitive.
5553
- *
5554
- * The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
5555
- * and presets call them only inside their function bodies.
5556
- */
5557
- /**
5558
- * Email — kind 'email', email validation, inputType 'email'.
5559
- *
5560
- * @layer preset
5561
- * @base string
5562
- * @prim text
5563
- * @widget email
5564
- * @example f.email({ label: 'mod.fields.contact' })
5565
- */
5078
+ //#region ../sdk/dist/field-presets.js
5566
5079
  function email(o) {
5567
5080
  return declare("email", o);
5568
5081
  }
5569
5082
  registerPreset("email", "string", {}, {
5570
5083
  kind: "email",
5571
5084
  widget: "email",
5572
- build(o) {
5573
- const required = o.required ?? false;
5574
- let s = string$1().email({ message: vmsg("email") });
5575
- if (required) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
5085
+ build() {
5576
5086
  return {
5577
5087
  column: "text",
5578
- zod: s,
5088
+ zod: string$1().email({ message: vmsg("email") }),
5579
5089
  hints: {
5580
5090
  format: "email",
5581
5091
  inputType: "email"
@@ -5583,27 +5093,16 @@ registerPreset("email", "string", {}, {
5583
5093
  };
5584
5094
  }
5585
5095
  });
5586
- /**
5587
- * Phone — kind 'tel', loose format /^\+?[\d\s()-]{4,}$/, inputType 'tel'.
5588
- *
5589
- * @layer preset
5590
- * @base string
5591
- * @prim text
5592
- * @widget tel
5593
- * @example f.tel({ label: 'mod.fields.phone' })
5594
- */
5595
5096
  function tel(o) {
5596
5097
  return declare("tel", o);
5597
5098
  }
5598
5099
  registerPreset("tel", "string", {}, {
5599
5100
  kind: "tel",
5600
5101
  widget: "tel",
5601
- build(o) {
5602
- const required = o.required ?? false;
5603
- const base_z = string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) });
5102
+ build() {
5604
5103
  return {
5605
5104
  column: "text",
5606
- zod: required ? base_z.min(1, { message: vmsg("min_length", { min: 1 }) }) : base_z,
5105
+ zod: string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) }),
5607
5106
  hints: {
5608
5107
  format: "tel",
5609
5108
  inputType: "tel"
@@ -5611,15 +5110,6 @@ registerPreset("tel", "string", {}, {
5611
5110
  };
5612
5111
  }
5613
5112
  });
5614
- /**
5615
- * Password — kind 'password', inputType 'password'. No strict schema. No multiple.
5616
- *
5617
- * @layer preset
5618
- * @base string
5619
- * @prim text
5620
- * @widget password
5621
- * @example f.password({ label: 'mod.fields.password' })
5622
- */
5623
5113
  function password(o) {
5624
5114
  return declare("password", o);
5625
5115
  }
@@ -5627,10 +5117,10 @@ registerPreset("password", "string", {}, {
5627
5117
  kind: "password",
5628
5118
  widget: "password",
5629
5119
  selfManages: /* @__PURE__ */ new Set(["multiple"]),
5630
- build(o) {
5120
+ build() {
5631
5121
  return {
5632
5122
  column: "text",
5633
- zod: o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1(),
5123
+ zod: string$1(),
5634
5124
  hints: {
5635
5125
  format: "password",
5636
5126
  inputType: "password"
@@ -5638,20 +5128,6 @@ registerPreset("password", "string", {}, {
5638
5128
  };
5639
5129
  }
5640
5130
  });
5641
- /**
5642
- * Internal API-token list — keyValue-shaped collection (name + write-only
5643
- * token + timestamps), custom renderer (`kind:'internalApiToken'`, own
5644
- * create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
5645
- * general-purpose field type for plugin shelves, only for the core account
5646
- * settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
5647
- * already mask/preserve it for free.
5648
- *
5649
- * @layer preset
5650
- * @base group
5651
- * @prim —
5652
- * @widget internalApiToken
5653
- * @example f.internalApiToken({ label: 'mod.fields.apiTokens' })
5654
- */
5655
5131
  function internalApiToken(o) {
5656
5132
  return group({
5657
5133
  scope: "nest",
@@ -5667,18 +5143,6 @@ function internalApiToken(o) {
5667
5143
  }
5668
5144
  });
5669
5145
  }
5670
- /**
5671
- * Internal "connected apps" list — the OAuth clients (claude.ai & co) the user
5672
- * has authorized for MCP access. Same "internal" caveat and custom-renderer
5673
- * trick as internalApiToken(): read-only rows + a revoke action, never part of
5674
- * the parent form's save.
5675
- *
5676
- * @layer preset
5677
- * @base group
5678
- * @prim —
5679
- * @widget internalOauthGrants
5680
- * @example f.internalOauthGrants({ label: 'mod.fields.oauthGrants' })
5681
- */
5682
5146
  function internalOauthGrants(o) {
5683
5147
  return group({
5684
5148
  scope: "nest",
@@ -5692,16 +5156,20 @@ function internalOauthGrants(o) {
5692
5156
  }
5693
5157
  });
5694
5158
  }
5159
+ function internalIdentityLinks(o) {
5160
+ return group({
5161
+ scope: "nest",
5162
+ label: o.label,
5163
+ multiple: true,
5164
+ ui: { kind: "internalIdentityLinks" },
5165
+ fields: {
5166
+ provider: string({}),
5167
+ externalId: string({}),
5168
+ linkedAt: string({})
5169
+ }
5170
+ });
5171
+ }
5695
5172
  var SLUG_RE = /^[a-z0-9-]+$/;
5696
- /**
5697
- * Slug — kind 'slug', `^[a-z0-9-]+$`.
5698
- *
5699
- * @layer preset
5700
- * @base string
5701
- * @prim text
5702
- * @widget slug
5703
- * @example f.slug({ label: 'mod.fields.slug' })
5704
- */
5705
5173
  function slug(o) {
5706
5174
  return declare("slug", o);
5707
5175
  }
@@ -5716,21 +5184,6 @@ registerPreset("slug", "string", {}, {
5716
5184
  };
5717
5185
  }
5718
5186
  });
5719
- /**
5720
- * Identifier — a code printed for a machine and read back by one: a serial number, an order
5721
- * number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
5722
- * why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
5723
- * is one word on any string now, but the click that copies belongs to the kind.
5724
- *
5725
- * Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
5726
- * it, and a pattern here would reject the next manufacturer's.
5727
- *
5728
- * @layer preset
5729
- * @base string
5730
- * @prim text
5731
- * @widget identifier
5732
- * @example f.identifier({ label: 'mod.fields.serialNumber' })
5733
- */
5734
5187
  function identifier(o) {
5735
5188
  return declare("identifier", o);
5736
5189
  }
@@ -5745,20 +5198,6 @@ registerPreset("identifier", "string", {}, {
5745
5198
  };
5746
5199
  }
5747
5200
  });
5748
- /**
5749
- * The machine-readable zone of a travel document — the identifier taken to its limit: printed
5750
- * for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
5751
- * line breaks preserved, because an MRZ's line structure is part of what it encodes.
5752
- *
5753
- * Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
5754
- * a strip transcribed from a real document is worth storing even when it does not check out.
5755
- *
5756
- * @layer preset
5757
- * @base string
5758
- * @prim text
5759
- * @widget mrz
5760
- * @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
5761
- */
5762
5201
  function mrz(o) {
5763
5202
  return declare("mrz", o);
5764
5203
  }
@@ -5774,15 +5213,6 @@ registerPreset("mrz", "string", {}, {
5774
5213
  }
5775
5214
  });
5776
5215
  var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
5777
- /**
5778
- * Hex color — kind 'color', `#rrggbb` + swatch widget.
5779
- *
5780
- * @layer preset
5781
- * @base string
5782
- * @prim text
5783
- * @widget color
5784
- * @example f.color({ label: 'mod.fields.color' })
5785
- */
5786
5216
  function color(o) {
5787
5217
  return declare("color", o);
5788
5218
  }
@@ -5797,74 +5227,44 @@ registerPreset("color", "string", {}, {
5797
5227
  };
5798
5228
  }
5799
5229
  });
5800
- /**
5801
- * CSS color name — kind 'colorname'.
5802
- *
5803
- * @layer preset
5804
- * @base string
5805
- * @prim text
5806
- * @widget colorname
5807
- * @example f.colorname({ label: 'mod.fields.colorName' })
5808
- */
5809
5230
  function colorname(o) {
5810
5231
  return declare("colorname", o);
5811
5232
  }
5812
5233
  registerPreset("colorname", "string", {}, {
5813
5234
  kind: "colorname",
5814
5235
  widget: "colorname",
5815
- build(o) {
5236
+ build() {
5816
5237
  return {
5817
5238
  column: "text",
5818
- zod: (o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1()).refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
5239
+ zod: string$1().refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
5819
5240
  hints: {}
5820
5241
  };
5821
5242
  }
5822
5243
  });
5823
- /**
5824
- * Shelf heading — kind 'title'. Heading renderer.
5825
- *
5826
- * @layer preset
5827
- * @base string
5828
- * @prim text
5829
- * @widget title
5830
- * @example f.title({ label: 'mod.fields.name' })
5831
- */
5832
5244
  function title(o) {
5833
5245
  return declare("title", o);
5834
5246
  }
5835
5247
  registerPreset("title", "string", {}, {
5836
5248
  kind: "title",
5837
5249
  widget: "title",
5838
- build(o) {
5250
+ build() {
5839
5251
  return {
5840
5252
  column: "text",
5841
- zod: o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1(),
5253
+ zod: string$1(),
5842
5254
  hints: {}
5843
5255
  };
5844
5256
  }
5845
5257
  });
5846
- /**
5847
- * Link — kind 'link', edited as string, view = clickable <a target=_blank>.
5848
- * Stores the raw URL as text (unlike composite f.url). Loose validation.
5849
- *
5850
- * @layer preset
5851
- * @base string
5852
- * @prim text
5853
- * @widget link
5854
- * @example f.link({ label: 'mod.fields.link' })
5855
- */
5856
5258
  function link(o) {
5857
5259
  return declare("link", o);
5858
5260
  }
5859
5261
  registerPreset("link", "string", {}, {
5860
5262
  kind: "link",
5861
5263
  widget: "link",
5862
- build(o) {
5863
- const required = o.required ?? false;
5864
- const base_z = string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) });
5264
+ build() {
5865
5265
  return {
5866
5266
  column: "text",
5867
- zod: required ? base_z.min(1, { message: vmsg("min_length", { min: 1 }) }) : base_z,
5267
+ zod: string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) }),
5868
5268
  hints: {
5869
5269
  format: "url",
5870
5270
  inputType: "url"
@@ -5873,9 +5273,7 @@ registerPreset("link", "string", {}, {
5873
5273
  }
5874
5274
  });
5875
5275
  var TEL_RE = /^\+?[\d\s()-]{4,}$/;
5876
- /** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
5877
5276
  var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
5878
- /** CSS named colors (CSS Color Module L4) for f.colorname. */
5879
5277
  var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
5880
5278
  "aliceblue",
5881
5279
  "antiquewhite",
@@ -6027,15 +5425,6 @@ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
6027
5425
  "yellowgreen",
6028
5426
  "transparent"
6029
5427
  ]);
6030
- /**
6031
- * Tag — non-empty string + chip widget. `multiple` → array of tags (`tags`).
6032
- *
6033
- * @layer preset
6034
- * @base string
6035
- * @prim text
6036
- * @widget tag
6037
- * @example f.tag({ label: 'mod.fields.tag' })
6038
- */
6039
5428
  function tag(o) {
6040
5429
  return declare("tag", o);
6041
5430
  }
@@ -6056,21 +5445,6 @@ registerPreset("tag", "string", {}, {
6056
5445
  };
6057
5446
  }
6058
5447
  });
6059
- /**
6060
- * A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
6061
- * `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
6062
- * sequence — the plurality is the type, not a modifier someone remembered to add.
6063
- *
6064
- * Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
6065
- * an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
6066
- * containment — the only thing the value actually carries — is lost.
6067
- *
6068
- * @layer preset
6069
- * @base string
6070
- * @prim text
6071
- * @widget path
6072
- * @example f.path({ label: 'things.storage_location.fields.path' })
6073
- */
6074
5448
  function path(o) {
6075
5449
  return declare("path", {
6076
5450
  ...o,
@@ -6088,30 +5462,12 @@ registerPreset("path", "string", {}, {
6088
5462
  };
6089
5463
  }
6090
5464
  });
6091
- /**
6092
- * Tags — `tag({ multiple: true })`.
6093
- *
6094
- * @layer preset
6095
- * @base string
6096
- * @prim text
6097
- * @widget tag
6098
- * @example f.tags({ label: 'mod.fields.tags' })
6099
- */
6100
5465
  function tags(o) {
6101
5466
  return tag({
6102
5467
  ...o,
6103
5468
  multiple: true
6104
5469
  });
6105
5470
  }
6106
- /**
6107
- * Markdown — text() + Markdown editor.
6108
- *
6109
- * @layer preset
6110
- * @base text
6111
- * @prim text
6112
- * @widget markdown
6113
- * @example f.markdown({ label: 'mod.fields.body' })
6114
- */
6115
5471
  function markdown(o) {
6116
5472
  return declare("markdown", o);
6117
5473
  }
@@ -6119,16 +5475,6 @@ registerPreset("markdown", "text", {}, {
6119
5475
  kind: "markdown",
6120
5476
  widget: "markdown"
6121
5477
  });
6122
- /**
6123
- * Code snippet — text() + monospace code editor.
6124
- * `rules.language` is a highlighting hint (json/yaml/bash/ini…), stored in hints.
6125
- *
6126
- * @layer preset
6127
- * @base text
6128
- * @prim text
6129
- * @widget snippet
6130
- * @example f.snippet({ label: 'mod.fields.config' })
6131
- */
6132
5478
  function snippet(o) {
6133
5479
  return declare("snippet", o);
6134
5480
  }
@@ -6147,22 +5493,9 @@ registerPreset("snippet", "text", {}, {
6147
5493
  };
6148
5494
  }
6149
5495
  });
6150
- /** A NUL byte, or U+FFFD left behind by decoding non-UTF-8 bytes as text. */
6151
5496
  function isBinaryText(v) {
6152
5497
  return v.includes("\0") || v.includes("�");
6153
5498
  }
6154
- /**
6155
- * Source — plain text in one column, shaped like a file in the UI.
6156
- * Written through the API or by uploading a file; never hand-edited in the web form
6157
- * (hints.noEditControl). The content is never rendered — only name + Upload/Download.
6158
- * `rules.ext` is the displayed/downloaded extension, `rules.maxBytes` the size cap.
6159
- *
6160
- * @layer preset
6161
- * @base text
6162
- * @prim text
6163
- * @widget source
6164
- * @example f.source({ label: 'mod.fields.file' })
6165
- */
6166
5499
  function source(o) {
6167
5500
  return declare("source", o);
6168
5501
  }
@@ -6197,15 +5530,6 @@ registerPreset("source", "text", {}, {
6197
5530
  };
6198
5531
  }
6199
5532
  });
6200
- /**
6201
- * Rating 1..N — integer + star widget. rules.max is the upper bound (default 5).
6202
- *
6203
- * @layer preset
6204
- * @base int
6205
- * @prim number
6206
- * @widget rating
6207
- * @example f.rating({ label: 'mod.fields.rating' })
6208
- */
6209
5533
  function rating(o) {
6210
5534
  return declare("rating", o);
6211
5535
  }
@@ -6221,16 +5545,6 @@ registerPreset("rating", "int", { config: { max: 5 } }, {
6221
5545
  };
6222
5546
  }
6223
5547
  });
6224
- /** Duration in seconds — integer + duration widget ("2d 3h 30m 15s").
6225
- * rules.max is the maximum in seconds (default 604800 = 1 week).
6226
- * rules.step is the step in seconds (default 60 = 1 minute).
6227
- *
6228
- * @layer preset
6229
- * @base int
6230
- * @prim number
6231
- * @widget duration
6232
- * @example f.duration({ label: 'mod.fields.runtime' })
6233
- */
6234
5548
  function duration(o) {
6235
5549
  return declare("duration", o);
6236
5550
  }
@@ -6254,15 +5568,6 @@ registerPreset("duration", "int", { config: {
6254
5568
  };
6255
5569
  }
6256
5570
  });
6257
- /** Reminder — date with visual urgency. kind 'reminder', prim 'date'.
6258
- * rules.lead = day threshold (default 30). rules.min/rules.max = ISO dates.
6259
- *
6260
- * @layer preset
6261
- * @base date
6262
- * @prim date
6263
- * @widget reminder
6264
- * @example f.reminder({ label: 'mod.fields.renewal' })
6265
- */
6266
5571
  function reminder(o) {
6267
5572
  return declare("reminder", o);
6268
5573
  }
@@ -6281,24 +5586,6 @@ registerPreset("reminder", "date", { lead: 30 }, {
6281
5586
  };
6282
5587
  }
6283
5588
  });
6284
- /** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
6285
- * kind 'timetable', prim 'time', always `multiple`.
6286
- *
6287
- * Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
6288
- * the values MEAN. Everything it shows beyond the times themselves — when the service starts
6289
- * and ends, how many runs there are, which one is next — is derived from the values at render
6290
- * time, never stored: a "next departure" is wrong the moment it is written down, exactly the
6291
- * argument `f.age` already makes for an age.
6292
- *
6293
- * Storage is a plain multiple `time`, so a field can be switched to this from
6294
- * `f.time({ multiple: true })` and back with no migration.
6295
- *
6296
- * @layer preset
6297
- * @base time
6298
- * @prim time
6299
- * @widget timetable
6300
- * @example f.timetable({ label: 'mod.fields.weekdays' })
6301
- */
6302
5589
  function timetable(o) {
6303
5590
  return declare("timetable", {
6304
5591
  ...o,
@@ -6309,18 +5596,6 @@ registerPreset("timetable", "time", {}, {
6309
5596
  kind: "timetable",
6310
5597
  widget: "timetable"
6311
5598
  });
6312
- /** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
6313
- * kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
6314
- * `mutate`-driven computed field, it must be right on every day that passes, not only the day
6315
- * the record was last written, so this is display metadata over `date`, the same shape as
6316
- * `f.reminder`'s own relative-to-today note.
6317
- *
6318
- * @layer preset
6319
- * @base date
6320
- * @prim date
6321
- * @widget age
6322
- * @example f.age({ label: 'mod.fields.birthDate' })
6323
- */
6324
5599
  function age(o) {
6325
5600
  return declare("age", o);
6326
5601
  }
@@ -6331,15 +5606,6 @@ registerPreset("age", "date", {}, {
6331
5606
  return typeOf("date").build(o, parts);
6332
5607
  }
6333
5608
  });
6334
- /**
6335
- * Percentage 0..100 — real with rules:{min:0,max:100}.
6336
- *
6337
- * @layer preset
6338
- * @base real
6339
- * @prim number
6340
- * @widget number
6341
- * @example f.percent({ label: 'mod.fields.progress' })
6342
- */
6343
5609
  function percent(o) {
6344
5610
  return real({
6345
5611
  ...o,
@@ -6350,15 +5616,6 @@ function percent(o) {
6350
5616
  }
6351
5617
  });
6352
5618
  }
6353
- /**
6354
- * Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max.
6355
- *
6356
- * @layer preset
6357
- * @base int
6358
- * @prim number
6359
- * @widget number
6360
- * @example f.year({ label: 'mod.fields.releaseYear' })
6361
- */
6362
5619
  function year(o) {
6363
5620
  return int({
6364
5621
  ...o,
@@ -6369,15 +5626,6 @@ function year(o) {
6369
5626
  }
6370
5627
  });
6371
5628
  }
6372
- /**
6373
- * Weight — always grams (int). kind 'weight', prim 'number'.
6374
- *
6375
- * @layer preset
6376
- * @base int
6377
- * @prim number
6378
- * @widget weight
6379
- * @example f.weight({ label: 'mod.fields.weight' })
6380
- */
6381
5629
  function weight(o) {
6382
5630
  return declare("weight", o);
6383
5631
  }
@@ -6395,12 +5643,6 @@ registerPreset("weight", "int", { config: { min: 1 } }, {
6395
5643
  };
6396
5644
  }
6397
5645
  });
6398
- /**
6399
- * The three extents of a box, in declaration order — which is also the column order, so
6400
- * `w` is the magnitude `magnitudeSub()` hands to list sorting and to a filter by field key.
6401
- * The unit is NOT a role: dimensions are always millimetres, and nothing in the app converts
6402
- * units (see `docs/decisions.md`, 2026-08-05), so it stays a hint rather than a fourth column.
6403
- */
6404
5646
  var DIMENSION_ROLES = () => [
6405
5647
  role("w", ["number"], "int"),
6406
5648
  role("h", ["number"], "int"),
@@ -6424,27 +5666,9 @@ registerType("dimensions", {
6424
5666
  structureCode: "dimensions_structure",
6425
5667
  hints: () => ({ unit: "mm" })
6426
5668
  });
6427
- /**
6428
- * Dimensions — box dimensions {w,h,d}, int mm across the real sub-columns `key__w|h|d`.
6429
- *
6430
- * @layer preset
6431
- * @base int
6432
- * @prim text
6433
- * @widget dimensions
6434
- * @example f.dimensions({ label: 'mod.fields.size' })
6435
- */
6436
5669
  function dimensions(o) {
6437
5670
  return declare("dimensions", o);
6438
5671
  }
6439
- /**
6440
- * Country — a string whose values are the country list, rendered with flags.
6441
- *
6442
- * @layer preset
6443
- * @base string
6444
- * @prim text
6445
- * @widget text
6446
- * @example f.country({ label: 'mod.fields.country' })
6447
- */
6448
5672
  function country(raw) {
6449
5673
  return string({
6450
5674
  ...raw,
@@ -6454,22 +5678,6 @@ function country(raw) {
6454
5678
  noSearch: true
6455
5679
  });
6456
5680
  }
6457
- /**
6458
- * Currency — the ISO 4217 code the record's amounts are in, declared ONCE per record.
6459
- * The money twin of `f.unit`: an ordinary string over the global `currencies` list, so it
6460
- * is an ordinary column (groupable, filterable) and its picker is an expanded list rather
6461
- * than a combobox wedged into some amount's editor.
6462
- *
6463
- * A record carries ONE currency. Two amounts on one record that need different currencies
6464
- * are two records — `multiple: true` here means "the currencies this record deals in"
6465
- * (what an account holds), and binds no amount.
6466
- *
6467
- * @layer preset
6468
- * @base string
6469
- * @prim text
6470
- * @widget text
6471
- * @example f.currency({ label: 'mod.fields.currency' })
6472
- */
6473
5681
  function currency(raw) {
6474
5682
  return string({
6475
5683
  ...raw,
@@ -6479,22 +5687,6 @@ function currency(raw) {
6479
5687
  noSearch: true
6480
5688
  });
6481
5689
  }
6482
- /**
6483
- * A monetary amount — a plain number in a `real` column, whose currency is a field of the
6484
- * record (`currencyFrom`). Summable, sortable and filterable by its own name, like any
6485
- * other number.
6486
- *
6487
- * It used to be a composite `{value, currency}`: the amount then lived in `price__value`,
6488
- * reachable only through `magnitudeSub`, a sum over a collection had to re-assemble
6489
- * objects in JavaScript, and the currency needed a second control inside the amount's own
6490
- * editor — the one place a collapsed control must never be (skill field-render).
6491
- *
6492
- * @layer preset
6493
- * @base real
6494
- * @prim number
6495
- * @widget money
6496
- * @example f.money({ label: 'mod.fields.price' })
6497
- */
6498
5690
  function money(o) {
6499
5691
  return declare("money", o);
6500
5692
  }
@@ -6549,60 +5741,17 @@ var presets = {
6549
5741
  currency,
6550
5742
  money,
6551
5743
  internalApiToken,
5744
+ internalIdentityLinks,
6552
5745
  internalOauthGrants
6553
5746
  };
6554
5747
  //#endregion
6555
- //#region ../sdk/src/blocks.ts
6556
- /**
6557
- * Editorial "blocks" — record-page layout sugar over `group()` (bento grids, split
6558
- * columns, tabs, …), exactly like `row`/`sheet` in fields.ts. Each factory sets
6559
- * the group's `kind` (renderer resolution — see `elementKind()`) and stashes its own
6560
- * layout metadata on `ContainerNode.view`, read by the block's own renderer, never by the
6561
- * generic layout core.
6562
- *
6563
- * `calendar`/`journal`/`manifest`/`table` are the one exception to "sugar over a fresh
6564
- * `group()`": their `source` is already a KEYED, `multiple: true` collection built
6565
- * elsewhere (`row`/`sheet` in fields.ts — there is no separate factory to DECLARE a
6566
- * tabular collection), and they stamp `kind`/`view` directly onto THAT group
6567
- * (`stampCollection`) rather than wrapping it in a new keyless one — see
6568
- * `stampCollection`'s own comment for why the wrapping shape does not reach the layout
6569
- * engine's storage-group branch. `table` specifically is a VIEW over a collection built
6570
- * elsewhere, never a second way to declare one. `calendar`/`journal`/`manifest`/`table`'s
6571
- * `source` stays typed `ContainerNode` (already built, already keyed) — the exception is about
6572
- * this wrapping, not about naming, so it is untouched by `SlotInput` below.
6573
- *
6574
- * Every OTHER el-valued slot a factory here declares — a single element (`figure.image`,
6575
- * `masthead.title`, `route.from`, …) or a list (`masthead.meta`, `compare.left`, …) — takes
6576
- * a NAMED MAP: see `SlotInput`'s own doc comment for the single case, `LayoutInput`
6577
- * (materialize/decl.ts) for the list case. The slot names the ROLE; the map's own property
6578
- * name(s) become the STORAGE key(s).
6579
- */
5748
+ //#region ../sdk/dist/blocks.js
6580
5749
  var isSpanned = (i) => typeof i === "object" && i !== null && "span" in i && "el" in i;
6581
- /**
6582
- * Validates a block's single-element slot and hands back the one-entry map it is. A slot
6583
- * is not built here: every slot a block lays out is merged into ONE map (`merge` below)
6584
- * and walked by `group()` exactly once, like any other container's `fields`.
6585
- *
6586
- * A missing/nullish slot is the same "got 0" failure as an empty map, not a native
6587
- * TypeError from `Object.keys(undefined)` — every empty-slot failure mode speaks the same
6588
- * message, whether the author wrote `{}`, left the option out, or passed `undefined`.
6589
- */
6590
5750
  function slot(block, name, input) {
6591
5751
  const count = input == null ? 0 : Object.keys(input).length;
6592
5752
  if (count !== 1) throw new Error(`[field.${block}] slot '${name}' expects exactly one entry, got ${count}`);
6593
5753
  return input;
6594
5754
  }
6595
- /**
6596
- * A block's children, in the order the block lays them out. Every slot is a NAMED MAP, so
6597
- * flattening several slots into one child list is a MERGE: the names the author gave are
6598
- * already there and nothing has to be generated or derived from a position. Insertion
6599
- * order is declaration order, which is also the order every ORDERED NAME LIST the layout
6600
- * metadata below carries (`first`/`second`, `meta`, `conditions`, …) reports its names in —
6601
- * a NAME, never a count against a flattened position (Task 5).
6602
- *
6603
- * A name claimed by two slots is a storage-key collision, not a layout choice — the flat
6604
- * form used to let both through and store one of them nowhere.
6605
- */
6606
5755
  function merge(block, ...slots) {
6607
5756
  const out = {};
6608
5757
  for (const s of slots) {
@@ -6614,21 +5763,6 @@ function merge(block, ...slots) {
6614
5763
  }
6615
5764
  return out;
6616
5765
  }
6617
- /**
6618
- * Asymmetric card grid. '1x1' is the implicit default — only wider spans are recorded, BY
6619
- * NAME (Task 5): each tile IS one declared field, so `spans` is keyed by that field's own
6620
- * storage name — the same name the renderer already addresses it by — never a position in
6621
- * the built list.
6622
- *
6623
- * `fields` takes a NAMED MAP of `BentoItem` values, handed to `group()` which walks it
6624
- * exactly like every other container's `fields`.
6625
- *
6626
- * @layer block
6627
- * @base group
6628
- * @prim —
6629
- * @widget bento
6630
- * @example f.bento({ fields: { name: f.string({ label: '…' }), model: { span: '2x1', el: f.string({ label: '…' }) } } })
6631
- */
6632
5766
  function bento(opts) {
6633
5767
  const spans = {};
6634
5768
  const fields = Object.fromEntries(Object.entries(opts.fields).map(([name, item]) => {
@@ -6649,23 +5783,6 @@ function bento(opts) {
6649
5783
  }
6650
5784
  };
6651
5785
  }
6652
- /**
6653
- * Two-column split (default ratio 1:1). Columns are flattened; the left/right boundary is
6654
- * remembered.
6655
- *
6656
- * Each column is a LIST slot (a named map — see `LayoutInput`'s own doc comment). The
6657
- * tuple POSITION (index 0/1) stays the left/right layout signal, unrelated to storage
6658
- * naming; each column's own fields are named the same way `fields` is everywhere else, and
6659
- * the two columns are MERGED into one map for `group()` (`merge`, above). `view.first`/
6660
- * `view.second` (Task 5) are the ORDERED LISTS of each column's own field names — the
6661
- * renderer reaches every child by name, never a count against a flattened position.
6662
- *
6663
- * @layer block
6664
- * @base group
6665
- * @prim —
6666
- * @widget split
6667
- * @example f.split({ columns: [{ left: f.string({ label: '…' }) }, { right: f.string({ label: '…' }) }] })
6668
- */
6669
5786
  function split(opts) {
6670
5787
  return {
6671
5788
  ...group({
@@ -6680,14 +5797,6 @@ function split(opts) {
6680
5797
  }
6681
5798
  };
6682
5799
  }
6683
- /** Even N-column grid (default 3).
6684
- *
6685
- * @layer block
6686
- * @base group
6687
- * @prim —
6688
- * @widget grid
6689
- * @example f.grid({ columns: 3, fields: { a: f.string({ label: '…' }), b: f.string({ label: '…' }) } })
6690
- */
6691
5800
  function grid(opts) {
6692
5801
  return {
6693
5802
  ...group({
@@ -6702,17 +5811,6 @@ function grid(opts) {
6702
5811
  }
6703
5812
  };
6704
5813
  }
6705
- /**
6706
- * A visual, an optional pull quote, and supporting facts. `view` names each role (Task 5):
6707
- * `visual`/`quote` are single field names (`quote` `undefined` when not declared),
6708
- * `facts` is the ORDERED LIST of the fact fields' own storage names.
6709
- *
6710
- * @layer block
6711
- * @base group
6712
- * @prim —
6713
- * @widget spread
6714
- * @example f.spread({ visual: { photo: f.image({ label: '…' }) }, quote: { quote: f.string({ label: '…' }) }, facts: { fact: f.string({ label: '…' }) } })
6715
- */
6716
5814
  function spread(opts) {
6717
5815
  const visual = slot("spread", "visual", opts.visual);
6718
5816
  const quote = opts.quote !== void 0 ? slot("spread", "quote", opts.quote) : void 0;
@@ -6729,17 +5827,6 @@ function spread(opts) {
6729
5827
  }
6730
5828
  };
6731
5829
  }
6732
- /**
6733
- * Main content plus a side rail. Flattened main-then-aside; the boundary is remembered.
6734
- * `view.main`/`view.aside` (Task 5) are the ORDERED LISTS of each region's own field
6735
- * names — see `split`'s own doc comment for what that means and why.
6736
- *
6737
- * @layer block
6738
- * @base group
6739
- * @prim —
6740
- * @widget aside
6741
- * @example f.aside({ fields: { main: f.string({ label: '…' }) }, aside: { side: f.string({ label: '…' }) } })
6742
- */
6743
5830
  function aside(opts) {
6744
5831
  return {
6745
5832
  ...group({
@@ -6753,20 +5840,6 @@ function aside(opts) {
6753
5840
  }
6754
5841
  };
6755
5842
  }
6756
- /**
6757
- * Tabbed sections. Each tab is a NAMED GROUP of its own fields (Task 5) — `t.name` becomes
6758
- * the storage key of a NESTED, keyless (`scope: 'hoist'`, storage-transparent — see
6759
- * `group`'s own `scope` doc comment) group wrapping `t.fields`, so the renderer reaches a
6760
- * tab's own children by descending into that name (`SlotScope`, `render/slot.tsx`) instead
6761
- * of slicing a flattened list by a cumulative per-tab count. A duplicate `name` across
6762
- * tabs is a storage-key collision, exactly like `merge`'s own duplicate-name guard.
6763
- *
6764
- * @layer block
6765
- * @base group
6766
- * @prim —
6767
- * @widget tabs
6768
- * @example f.tabs({ tabs: [{ name: 'overview', label: '…', fields: { a: f.string({ label: '…' }) } }] })
6769
- */
6770
5843
  function tabs(opts) {
6771
5844
  const fields = {};
6772
5845
  for (const t of opts.tabs) {
@@ -6788,17 +5861,6 @@ function tabs(opts) {
6788
5861
  }
6789
5862
  };
6790
5863
  }
6791
- /**
6792
- * Collapsible sections. Each section is a NAMED GROUP of its own fields (Task 5) — see
6793
- * `tabs`'s own doc comment for what that means and why; the renderer descends into a
6794
- * section's own children by name (`SlotScope`) instead of a cumulative count.
6795
- *
6796
- * @layer block
6797
- * @base group
6798
- * @prim —
6799
- * @widget accordion
6800
- * @example f.accordion({ sections: [{ name: 'details', label: '…', fields: { a: f.string({ label: '…' }) } }] })
6801
- */
6802
5864
  function accordion(opts) {
6803
5865
  const fields = {};
6804
5866
  for (const s of opts.sections) {
@@ -6821,16 +5883,6 @@ function accordion(opts) {
6821
5883
  }
6822
5884
  };
6823
5885
  }
6824
- /** Flowing prose paragraph, optionally with a dropped first capital letter on ONE named
6825
- * field — `dropCap` names WHICH one (Task 5), never "whichever field was declared
6826
- * first." Must be one of `fields`'s own storage keys.
6827
- *
6828
- * @layer block
6829
- * @base group
6830
- * @prim —
6831
- * @widget prose
6832
- * @example f.prose({ fields: { body: f.text({ label: '…' }) }, dropCap: 'body' })
6833
- */
6834
5886
  function prose(opts) {
6835
5887
  if (opts.dropCap !== void 0 && !(opts.dropCap in opts.fields)) throw new Error(`[field.prose] dropCap '${opts.dropCap}' is not one of this block's own fields`);
6836
5888
  return {
@@ -6846,17 +5898,6 @@ function prose(opts) {
6846
5898
  }
6847
5899
  };
6848
5900
  }
6849
- /** A small labeled plate of fields: one TITLE (large, semibold), then zero or more
6850
- * SUBTITLE fields (small, tracked-out, muted) beneath it. Which field is the title is
6851
- * NAMED (Task 5), the same way `figure.image`/`masthead.title` is — never "whichever
6852
- * field was declared first."
6853
- *
6854
- * @layer block
6855
- * @base group
6856
- * @prim —
6857
- * @widget plaque
6858
- * @example f.plaque({ title: { a: f.string({ label: '…' }) }, subtitle: { b: f.string({ label: '…' }) } })
6859
- */
6860
5901
  function plaque(opts) {
6861
5902
  const title = slot("plaque", "title", opts.title);
6862
5903
  return {
@@ -6873,14 +5914,6 @@ function plaque(opts) {
6873
5914
  }
6874
5915
  };
6875
5916
  }
6876
- /** A ledger-style stack of label/value rows.
6877
- *
6878
- * @layer block
6879
- * @base group
6880
- * @prim —
6881
- * @widget ledger
6882
- * @example f.ledger({ fields: { a: f.string({ label: '…' }) } })
6883
- */
6884
5917
  function ledger(opts) {
6885
5918
  return {
6886
5919
  ...group({
@@ -6892,14 +5925,6 @@ function ledger(opts) {
6892
5925
  view: { kind: "ledger" }
6893
5926
  };
6894
5927
  }
6895
- /** A row of headline statistics.
6896
- *
6897
- * @layer block
6898
- * @base group
6899
- * @prim —
6900
- * @widget stats
6901
- * @example f.stats({ fields: { count: f.real({ label: '…' }) } })
6902
- */
6903
5928
  function stats(opts) {
6904
5929
  return {
6905
5930
  ...group({
@@ -6911,14 +5936,6 @@ function stats(opts) {
6911
5936
  view: { kind: "stats" }
6912
5937
  };
6913
5938
  }
6914
- /** A cluster of small tag-like facets.
6915
- *
6916
- * @layer block
6917
- * @base group
6918
- * @prim —
6919
- * @widget facets
6920
- * @example f.facets({ fields: { tag: f.string({ label: '…' }) } })
6921
- */
6922
5939
  function facets(opts) {
6923
5940
  return {
6924
5941
  ...group({
@@ -6930,14 +5947,6 @@ function facets(opts) {
6930
5947
  view: { kind: "facets" }
6931
5948
  };
6932
5949
  }
6933
- /** A monospace terminal/code block, with an optional i18n caption.
6934
- *
6935
- * @layer block
6936
- * @base group
6937
- * @prim —
6938
- * @widget terminal
6939
- * @example f.terminal({ fields: { log: f.text({ label: '…' }) }, caption: 'mod.blocks.terminalCaption' })
6940
- */
6941
5950
  function terminal(opts) {
6942
5951
  return {
6943
5952
  ...group({
@@ -6952,14 +5961,6 @@ function terminal(opts) {
6952
5961
  }
6953
5962
  };
6954
5963
  }
6955
- /** A toned callout box (default tone: note).
6956
- *
6957
- * @layer block
6958
- * @base group
6959
- * @prim —
6960
- * @widget callout
6961
- * @example f.callout({ tone: 'warn', fields: { note: f.string({ label: '…' }) } })
6962
- */
6963
5964
  function callout(opts) {
6964
5965
  return {
6965
5966
  ...group({
@@ -6974,17 +5975,6 @@ function callout(opts) {
6974
5975
  }
6975
5976
  };
6976
5977
  }
6977
- /**
6978
- * Two side-by-side sets of fields. Flattened left-then-right; the boundary is remembered.
6979
- * `view.left`/`view.right` (Task 5) are the ORDERED LISTS of each column's own field
6980
- * names — see `split`'s own doc comment for what that means and why.
6981
- *
6982
- * @layer block
6983
- * @base group
6984
- * @prim —
6985
- * @widget compare
6986
- * @example f.compare({ left: { a: f.string({ label: '…' }) }, right: { b: f.string({ label: '…' }) } })
6987
- */
6988
5978
  function compare(opts) {
6989
5979
  return {
6990
5980
  ...group({
@@ -7002,20 +5992,6 @@ function compare(opts) {
7002
5992
  }
7003
5993
  };
7004
5994
  }
7005
- /**
7006
- * A picture and its caption as one typographic unit.
7007
- *
7008
- * `view` carries the STORAGE NAME of each role (not a count/boolean, Task 5): `image` is
7009
- * always declared; `caption` is `undefined` when the slot was never given. The renderer
7010
- * reaches each child through `useSlot`/`<Slot>` by this name — never by position in
7011
- * `el.fields`, so reordering how `merge()` (above) assembles the fields never matters.
7012
- *
7013
- * @layer block
7014
- * @base group
7015
- * @prim —
7016
- * @widget figure
7017
- * @example f.figure({ image: { photo: f.image({ label: '…' }) }, caption: { caption: f.string({ label: '…' }) } })
7018
- */
7019
5995
  function figure(opts) {
7020
5996
  const image = slot("figure", "image", opts.image);
7021
5997
  const caption = opts.caption !== void 0 ? slot("figure", "caption", opts.caption) : void 0;
@@ -7031,16 +6007,6 @@ function figure(opts) {
7031
6007
  }
7032
6008
  };
7033
6009
  }
7034
- /**
7035
- * A quoted source with an optional attribution. `view` names each role (Task 5) — see
7036
- * `figure`'s own doc comment for what that means and why.
7037
- *
7038
- * @layer block
7039
- * @base group
7040
- * @prim —
7041
- * @widget epigraph
7042
- * @example f.epigraph({ source: { quote: f.text({ label: '…' }) }, attribution: { author: f.string({ label: '…' }) } })
7043
- */
7044
6010
  function epigraph(opts) {
7045
6011
  const source = slot("epigraph", "source", opts.source);
7046
6012
  const attribution = opts.attribution !== void 0 ? slot("epigraph", "attribution", opts.attribution) : void 0;
@@ -7056,17 +6022,6 @@ function epigraph(opts) {
7056
6022
  }
7057
6023
  };
7058
6024
  }
7059
- /**
7060
- * A masthead header: optional overline, title, and trailing meta fields. `view` names each
7061
- * role (Task 5) — `meta` is the ORDERED LIST of the meta fields' own storage names (declared
7062
- * insertion order of `opts.meta`), not a count against a flattened position.
7063
- *
7064
- * @layer block
7065
- * @base group
7066
- * @prim —
7067
- * @widget masthead
7068
- * @example f.masthead({ title: { title: f.string({ label: '…' }) }, meta: { subtitle: f.string({ label: '…' }) } })
7069
- */
7070
6025
  function masthead(opts) {
7071
6026
  const overline = opts.overline !== void 0 ? slot("masthead", "overline", opts.overline) : void 0;
7072
6027
  const title = slot("masthead", "title", opts.title);
@@ -7083,14 +6038,6 @@ function masthead(opts) {
7083
6038
  }
7084
6039
  };
7085
6040
  }
7086
- /** A vertical timeline of dated entries.
7087
- *
7088
- * @layer block
7089
- * @base group
7090
- * @prim —
7091
- * @widget timeline
7092
- * @example f.timeline({ fields: { date: f.date({ label: '…' }) } })
7093
- */
7094
6041
  function timeline(opts) {
7095
6042
  return {
7096
6043
  ...group({
@@ -7102,14 +6049,6 @@ function timeline(opts) {
7102
6049
  view: { kind: "timeline" }
7103
6050
  };
7104
6051
  }
7105
- /** A single value counting down (or up) to/from a target, with an optional i18n caption.
7106
- *
7107
- * @layer block
7108
- * @base group
7109
- * @prim —
7110
- * @widget countdown
7111
- * @example f.countdown({ source: { deadline: f.date({ label: '…' }) }, caption: 'mod.blocks.countdownCaption' })
7112
- */
7113
6052
  function countdown(opts) {
7114
6053
  const source = slot("countdown", "source", opts.source);
7115
6054
  return {
@@ -7125,14 +6064,6 @@ function countdown(opts) {
7125
6064
  }
7126
6065
  };
7127
6066
  }
7128
- /** A card deck rendered from a single collection source.
7129
- *
7130
- * @layer block
7131
- * @base group
7132
- * @prim —
7133
- * @widget deck
7134
- * @example f.deck({ source: { cards: f.group({ scope: 'nest', multiple: true, fields: { name: f.string({ label: '…' }) } }) } })
7135
- */
7136
6067
  function deck(opts) {
7137
6068
  const source = slot("deck", "source", opts.source);
7138
6069
  return {
@@ -7147,14 +6078,6 @@ function deck(opts) {
7147
6078
  }
7148
6079
  };
7149
6080
  }
7150
- /** A roster rendered from a single collection source.
7151
- *
7152
- * @layer block
7153
- * @base group
7154
- * @prim —
7155
- * @widget people
7156
- * @example f.people({ source: { members: f.group({ scope: 'nest', multiple: true, fields: { name: f.string({ label: '…' }) } }) } })
7157
- */
7158
6081
  function people(opts) {
7159
6082
  const source = slot("people", "source", opts.source);
7160
6083
  return {
@@ -7169,17 +6092,6 @@ function people(opts) {
7169
6092
  }
7170
6093
  };
7171
6094
  }
7172
- /**
7173
- * A person/entity identity block: optional avatar, name, optional role, and channel links.
7174
- * `view` names each role (Task 5): `avatar`/`role` are the declared field's own storage
7175
- * name or `undefined`; `channels` is the ORDERED LIST of the channel fields' own names.
7176
- *
7177
- * @layer block
7178
- * @base group
7179
- * @prim —
7180
- * @widget identity
7181
- * @example f.identity({ name: { name: f.string({ label: '…' }) }, role: { role: f.string({ label: '…' }) } })
7182
- */
7183
6095
  function identity(opts) {
7184
6096
  const avatar = opts.avatar !== void 0 ? slot("identity", "avatar", opts.avatar) : void 0;
7185
6097
  const name = slot("identity", "name", opts.name);
@@ -7198,25 +6110,6 @@ function identity(opts) {
7198
6110
  }
7199
6111
  };
7200
6112
  }
7201
- /** One measure out of an optional max, from ONE OR MORE named sources, with an optional verdict.
7202
- *
7203
- * `source` takes several entries for the same reason `f.identity`'s `channels` does — it is a
7204
- * role that holds a LIST, so `view.source` is an ordered list of names rather than one name.
7205
- * Two ratings of the same film out of ten are one measure read twice, and drawing them apart is
7206
- * what makes them incomparable: the eye has to carry the scale between two figures instead of
7207
- * reading them against a shared one. `media/title` had exactly that, two `f.score` blocks of
7208
- * `max: 10` held apart inside an `f.compare`, until this took the restriction off.
7209
- *
7210
- * NOT `multiple`. A multiple field is an anonymous array; these are NAMED sources, and the name
7211
- * is what says which reading came from where. The two are different shapes and the block wants
7212
- * this one.
7213
- *
7214
- * @layer block
7215
- * @base group
7216
- * @prim —
7217
- * @widget score
7218
- * @example f.score({ source: { imdb: f.real({ label: '…' }), tmdb: f.real({ label: '…' }) }, max: 10 })
7219
- */
7220
6113
  function score(opts) {
7221
6114
  const names = Object.keys(opts.source ?? {});
7222
6115
  if (names.length === 0) throw new Error(`[field.score] slot 'source' expects at least one entry, got 0`);
@@ -7235,14 +6128,6 @@ function score(opts) {
7235
6128
  }
7236
6129
  };
7237
6130
  }
7238
- /** A status value with an optional "since" timestamp.
7239
- *
7240
- * @layer block
7241
- * @base group
7242
- * @prim —
7243
- * @widget status
7244
- * @example f.status({ source: { state: f.string({ label: '…' }) }, since: { since: f.date({ label: '…' }) } })
7245
- */
7246
6131
  function status(opts) {
7247
6132
  const source = slot("status", "source", opts.source);
7248
6133
  const since = opts.since !== void 0 ? slot("status", "since", opts.since) : void 0;
@@ -7259,23 +6144,6 @@ function status(opts) {
7259
6144
  }
7260
6145
  };
7261
6146
  }
7262
- /** A gauge value between an optional min and max, with an optional "of" total.
7263
- *
7264
- * `direction` is opt-in: when given, the gauge decides in plain JavaScript whether its own
7265
- * value crossed the bound that matters (`meterTone`, `blocks/meter.tsx`) and reaches for the
7266
- * tone palette — the fill, the value text and the `of` companion all move together, never
7267
- * colour alone (a glyph rides along, see the renderer's own comment). Omit it and nothing
7268
- * about the gauge changes from today. A `'ceiling'` gauge with no declared `max` (config or
7269
- * the source field's own hints) resolves it from `of`'s own value instead — a budget's cap is
7270
- * a per-record field, never a compile-time constant.
7271
- *
7272
- * @layer block
7273
- * @base group
7274
- * @prim —
7275
- * @widget meter
7276
- * @example f.meter({ source: { used: f.real({ label: '…' }) }, min: 0, max: 100 })
7277
- * @example f.meter({ source: { spent: f.real({ label: '…' }) }, of: { budget: f.real({ label: '…' }) }, direction: 'ceiling' })
7278
- */
7279
6147
  function meter(opts) {
7280
6148
  const source = slot("meter", "source", opts.source);
7281
6149
  const of = opts.of !== void 0 ? slot("meter", "of", opts.of) : void 0;
@@ -7295,16 +6163,6 @@ function meter(opts) {
7295
6163
  }
7296
6164
  };
7297
6165
  }
7298
- /**
7299
- * A line-item receipt with a trailing total. `view.lines` (Task 5) is the ORDERED LIST of
7300
- * the line fields' own storage names; `view.total` is the total field's own name.
7301
- *
7302
- * @layer block
7303
- * @base group
7304
- * @prim —
7305
- * @widget receipt
7306
- * @example f.receipt({ fields: { line: f.real({ label: '…' }) }, total: { total: f.real({ label: '…' }) } })
7307
- */
7308
6166
  function receipt(opts) {
7309
6167
  const total = slot("receipt", "total", opts.total);
7310
6168
  return {
@@ -7320,14 +6178,6 @@ function receipt(opts) {
7320
6178
  }
7321
6179
  };
7322
6180
  }
7323
- /** A financial balance value, with an optional i18n caption.
7324
- *
7325
- * @layer block
7326
- * @base group
7327
- * @prim —
7328
- * @widget balance
7329
- * @example f.balance({ source: { balance: f.real({ label: '…' }) }, caption: 'mod.blocks.balanceCaption' })
7330
- */
7331
6181
  function balance(opts) {
7332
6182
  const source = slot("balance", "source", opts.source);
7333
6183
  return {
@@ -7343,21 +6193,6 @@ function balance(opts) {
7343
6193
  }
7344
6194
  };
7345
6195
  }
7346
- /**
7347
- * A from/to route with optional depart/arrive times and duration. `view` names each role
7348
- * (Task 5) — see `figure`'s own doc comment for what that means and why.
7349
- *
7350
- * @layer block
7351
- * @base group
7352
- * @prim —
7353
- * @widget route
7354
- * A `stub` is the part of the ticket that is TORN OFF and kept — a seat, a gate, a booking
7355
- * reference. Declaring one makes the block a ticket rather than a line: the two halves are
7356
- * separated by a perforation, and the fields in the stub sit below it. Without one the block
7357
- * is exactly what it was, a route from here to there, so no existing call changes.
7358
- *
7359
- * @example f.route({ from: { from: f.string({ label: '…' }) }, to: { to: f.string({ label: '…' }) } })
7360
- */
7361
6196
  function route(opts) {
7362
6197
  const from = slot("route", "from", opts.from);
7363
6198
  const to = slot("route", "to", opts.to);
@@ -7381,25 +6216,6 @@ function route(opts) {
7381
6216
  }
7382
6217
  };
7383
6218
  }
7384
- /**
7385
- * Stamps a block's `kind` (and, for journal/manifest, its part-key config) directly onto
7386
- * the collection `source` itself, rather than wrapping it in a keyless layout group.
7387
- *
7388
- * The earlier shape (`group({ fields: [source], view: {kind} })`) put `kind` on an outer,
7389
- * KEYLESS wrapper and left `source` — a KEYED, `multiple: true` collection — as an ordinary
7390
- * child. `elementKind()` (`web-ui/registry.ts`) resolves a group's `kind` ahead of its
7391
- * storage kind, so that wrapper WOULD have resolved through the registry — but only the
7392
- * layout engine's KEYLESS-group dispatch (a declared `kind`, resolved through the field
7393
- * renderer registry — `layout.tsx`) ever looks at it there; the actual collection one level
7394
- * down still elementKind()s to the generic 'collection' and renders through the ordinary
7395
- * CollectionPage widget, opaque to the block.
7396
- *
7397
- * Stamping `source` itself instead routes it through the layout engine's STORAGE-group
7398
- * branch (`isStorageGroup`, `layout.tsx`) exactly like `field.check({multiple:true})`'s
7399
- * `checklist` kind: an ordinary `registerRenderer({kinds:[...]})` Page receives the raw row
7400
- * array as `value` and the collection's own write path as `onChange` directly — see
7401
- * `render/blocks/{calendar,journal,manifest,table}.tsx`.
7402
- */
7403
6219
  function stampCollection(block, slotted, label, view) {
7404
6220
  const [name, source] = Object.entries(slot(block, "source", slotted))[0];
7405
6221
  if (!hasChildren(source) || source.scope !== "nest" || source.multiple !== true) throw new Error(`[field.${block}] source must be a collection (a 'nest', multiple:true group)`);
@@ -7411,45 +6227,12 @@ function stampCollection(block, slotted, label, view) {
7411
6227
  view
7412
6228
  };
7413
6229
  }
7414
- /**
7415
- * A calendar rendered from a single collection source — see `stampCollection`. `date` is a
7416
- * part KEY inside each collection row (a `date` or `datetime` field), same as `journal`'s
7417
- * `date`/`manifest`'s `quantity`/`item` — an explicit key, not a guess: a collection with
7418
- * more than one date-shaped part (e.g. `start`/`end`) would otherwise mark the grid by
7419
- * whichever one is found first, silently.
7420
- *
7421
- * @layer block
7422
- * @base group
7423
- * @prim —
7424
- * @widget calendar
7425
- * @example f.calendar({ source: { entries: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }) } }) }, date: 'day' })
7426
- */
7427
6230
  function calendar(opts) {
7428
6231
  return stampCollection("calendar", opts.source, opts.label, {
7429
6232
  kind: "calendar",
7430
6233
  date: opts.date
7431
6234
  });
7432
6235
  }
7433
- /**
7434
- * A sparkline over a series held in a collection source — see `stampCollection`. `value`
7435
- * and `date` are part KEYS inside each collection row, the same explicit addressing
7436
- * `calendar`'s `date` and `journal`'s `date`/`text` use: a collection with more than one
7437
- * numeric or date-shaped part would otherwise be charted by whichever one is found first.
7438
- * `date` orders the series; it is never an axis (the sparkline spaces its points evenly —
7439
- * `render/blocks/series.ts` says why). `delta: true` adds the change against the previous
7440
- * reading beside the last value.
7441
- *
7442
- * The design sketched this as `f.chart({ source: collectionKey, … })`. There is no
7443
- * `collectionKey` type and no resolver for one anywhere in the tree — the design's own
7444
- * "As shipped" note had already retired that sketch for journal/manifest/calendar without
7445
- * updating this row.
7446
- *
7447
- * @layer block
7448
- * @base group
7449
- * @prim —
7450
- * @widget chart
7451
- * @example f.chart({ source: { readings: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }), kg: f.real({ label: '…' }) } }) }, value: 'kg', date: 'day', delta: true })
7452
- */
7453
6236
  function chart(opts) {
7454
6237
  return stampCollection("chart", opts.source, opts.label, {
7455
6238
  kind: "chart",
@@ -7458,45 +6241,12 @@ function chart(opts) {
7458
6241
  delta: opts.delta
7459
6242
  });
7460
6243
  }
7461
- /**
7462
- * A calendar heat map of event DENSITY rendered from a collection source — see
7463
- * `stampCollection`. `date` is a part KEY inside each collection row, the same explicit
7464
- * addressing `calendar`'s `date` and `chart`'s `value`/`date` use.
7465
- *
7466
- * `heatmap` counts ROWS PER DAY; it reads no value column at all, which is the whole
7467
- * difference from `chart`: a chart plots what a row measured, a heat map plots how often
7468
- * rows happened. That is why there is no `value` option here and why one must not be added
7469
- * as a shortcut for "colour by amount" — a magnitude per day is a different question from a
7470
- * count per day, and answering it through the same cells would make the picture ambiguous.
7471
- *
7472
- * The design sketched this as `f.heatmap({ source: collectionKey, … })`. There is no
7473
- * `collectionKey` type and no resolver for one anywhere in the tree — the same correction
7474
- * `f.chart` records above.
7475
- *
7476
- * @layer block
7477
- * @base group
7478
- * @prim —
7479
- * @widget heatmap
7480
- * @example f.heatmap({ source: { sessions: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }) } }) }, date: 'day' })
7481
- */
7482
6244
  function heatmap(opts) {
7483
6245
  return stampCollection("heatmap", opts.source, opts.label, {
7484
6246
  kind: "heatmap",
7485
6247
  date: opts.date
7486
6248
  });
7487
6249
  }
7488
- /**
7489
- * A journal-style log rendered from a single collection source — see `stampCollection`.
7490
- * `date` and `text` are part KEYS inside each collection row, NOT `LayoutEl` positions:
7491
- * they address parts INSIDE the collection's own rows, which is not a slot a block lays
7492
- * out itself.
7493
- *
7494
- * @layer block
7495
- * @base group
7496
- * @prim —
7497
- * @widget journal
7498
- * @example f.journal({ source: { entries: f.group({ scope: 'nest', multiple: true, fields: { day: f.date({ label: '…' }), note: f.text({ label: '…' }) } }) }, date: 'day', text: 'note' })
7499
- */
7500
6250
  function journal(opts) {
7501
6251
  return stampCollection("journal", opts.source, opts.label, {
7502
6252
  kind: "journal",
@@ -7504,14 +6254,6 @@ function journal(opts) {
7504
6254
  text: opts.text
7505
6255
  });
7506
6256
  }
7507
- /** A nutrition facts panel, with an optional leading energy value.
7508
- *
7509
- * @layer block
7510
- * @base group
7511
- * @prim —
7512
- * @widget nutrition
7513
- * @example f.nutrition({ fields: { fat: f.real({ label: '…' }) }, energy: { calories: f.real({ label: '…' }) } })
7514
- */
7515
6257
  function nutrition(opts) {
7516
6258
  const energy = opts.energy !== void 0 ? slot("nutrition", "energy", opts.energy) : void 0;
7517
6259
  return {
@@ -7527,17 +6269,6 @@ function nutrition(opts) {
7527
6269
  }
7528
6270
  };
7529
6271
  }
7530
- /**
7531
- * A specimen card: a title, an optional subtitle, and a set of condition facets. `view`
7532
- * names each role (Task 5) — `conditions` is the ORDERED LIST of the condition fields' own
7533
- * storage names, not a count against a flattened position.
7534
- *
7535
- * @layer block
7536
- * @base group
7537
- * @prim —
7538
- * @widget specimen
7539
- * @example f.specimen({ title: { name: f.string({ label: '…' }) }, conditions: { condition: f.string({ label: '…' }) } })
7540
- */
7541
6272
  function specimen(opts) {
7542
6273
  const title = slot("specimen", "title", opts.title);
7543
6274
  const subtitle = opts.subtitle !== void 0 ? slot("specimen", "subtitle", opts.subtitle) : void 0;
@@ -7554,26 +6285,6 @@ function specimen(opts) {
7554
6285
  }
7555
6286
  };
7556
6287
  }
7557
- /**
7558
- * Measurements against the range they were supposed to fall in, rendered from a single
7559
- * collection source — see `stampCollection`. `analyte`, `value`, `unit`, `low` and `high` are
7560
- * part KEYS inside each collection row, NOT `LayoutEl` positions — the same reasoning as
7561
- * `manifest`'s `quantity`/`item`.
7562
- *
7563
- * The point is the COMPARISON. A `f.table` over the same rows prints the bounds as two more
7564
- * columns and leaves the reader to do it; here each value sits on its own band with the
7565
- * reference span marked on it, so "outside the range" is seen rather than worked out. That is
7566
- * also the only thing on the row worth a colour — it is what the reader has to act on.
7567
- *
7568
- * `unit`, `low` and `high` are optional: a measurement with no published range (a culture, a
7569
- * description) still belongs in the same list and simply gets no band.
7570
- *
7571
- * @layer block
7572
- * @base group
7573
- * @prim —
7574
- * @widget assay
7575
- * @example f.assay({ source: { results: f.group({ scope: 'nest', multiple: true, fields: { analyte: f.string({ label: '…' }), value: f.real({ label: '…' }) } }) }, analyte: 'analyte', value: 'value' })
7576
- */
7577
6288
  function assay(opts) {
7578
6289
  return stampCollection("assay", opts.source, opts.label, {
7579
6290
  kind: "assay",
@@ -7584,32 +6295,6 @@ function assay(opts) {
7584
6295
  ...opts.high ? { high: opts.high } : {}
7585
6296
  });
7586
6297
  }
7587
- /**
7588
- * Value moving from one place to another, where the MOVEMENT is the subject — not one more
7589
- * labelled row among the record's fields. The sum is set large in the machine voice, and the two
7590
- * ends read as a path beneath it.
7591
- *
7592
- * amount — what moved. The one required role, and the reason the block exists
7593
- * from — where it left. Absent on money that only arrived
7594
- * to — where it arrived. Absent on money that only left
7595
- * meta — the record's own remaining fields, under a rule: date, category, reference
7596
- *
7597
- * An absent end is not drawn, and that is the whole reading: money that left an account and
7598
- * arrived nowhere IS an expense. It is read off which ends the record actually has, never
7599
- * guessed from the shape of a value. What the movement MEANS beyond that — whether this
7600
- * particular kind of transfer is good news — is carried by the classifier's own option `tone`,
7601
- * the same declaration every other coloured value in the product uses.
7602
- *
7603
- * Distinct from `f.route`, which also has two ends: there the journey is the subject and the
7604
- * ends are places, so it carries times and a duration; here the ends are accounts and the
7605
- * subject is the quantity.
7606
- *
7607
- * @layer block
7608
- * @base group
7609
- * @prim —
7610
- * @widget flow
7611
- * @example f.flow({ amount: { amount: f.money({ label: '…' }) }, from: { source: f.relation({ label: '…' }) } })
7612
- */
7613
6298
  function flow(opts) {
7614
6299
  const amount = slot("flow", "amount", opts.amount);
7615
6300
  const from = opts.from !== void 0 ? slot("flow", "from", opts.from) : void 0;
@@ -7629,17 +6314,6 @@ function flow(opts) {
7629
6314
  }
7630
6315
  };
7631
6316
  }
7632
- /**
7633
- * A packing/cargo manifest rendered from a single collection source — see `stampCollection`.
7634
- * `quantity` and `item` are part KEYS inside each collection row, NOT `LayoutEl` positions —
7635
- * the same reasoning as `journal`'s `date`/`text`.
7636
- *
7637
- * @layer block
7638
- * @base group
7639
- * @prim —
7640
- * @widget manifest
7641
- * @example f.manifest({ source: { cargo: f.group({ scope: 'nest', multiple: true, fields: { item: f.string({ label: '…' }), qty: f.real({ label: '…' }) } }) }, quantity: 'qty', item: 'item' })
7642
- */
7643
6317
  function manifest(opts) {
7644
6318
  return stampCollection("manifest", opts.source, opts.label, {
7645
6319
  kind: "manifest",
@@ -7647,26 +6321,6 @@ function manifest(opts) {
7647
6321
  item: opts.item
7648
6322
  });
7649
6323
  }
7650
- /**
7651
- * A real table over a single collection source — see `stampCollection`.
7652
- *
7653
- * A plain collection (`f.row({ key, multiple })`) is a LAYOUT: repeated rows whose columns
7654
- * line up. A table is an INSTRUMENT: its header sorts, `groupBy` buckets the rows, `totals`
7655
- * closes it with an aggregate, `numbered` counts them. The server sees the same rows either
7656
- * way — this factory declares no storage and changes none.
7657
- *
7658
- * `sort`, `groupBy` and every key of `totals` name a field of the ROW, exactly as `journal`'s
7659
- * `date`/`text` and `manifest`'s `quantity`/`item` do: an explicit key, never a guess at which
7660
- * column looks sortable or summable. `groupBy` needs no categorical filter — the list panel
7661
- * groups a text field only when `views.groupBy` names it explicitly (`sdk/shelf.ts`), and this
7662
- * IS that explicit naming.
7663
- *
7664
- * @layer block
7665
- * @base group
7666
- * @prim —
7667
- * @widget table
7668
- * @example f.table({ source: { rows: f.row({ scope: 'nest', multiple: true, fields: { a: f.string({ label: 'mod.fields.a' }) } }) }, sort: 'a' })
7669
- */
7670
6324
  function table(opts) {
7671
6325
  return stampCollection("table", opts.source, opts.label, {
7672
6326
  kind: "table",
@@ -7680,28 +6334,6 @@ function table(opts) {
7680
6334
  ...opts.numbered ? { numbered: true } : {}
7681
6335
  });
7682
6336
  }
7683
- /**
7684
- * An identity document, drawn as the card it is: a caption and its number across the top, a
7685
- * portrait beside the holder's own fields, the issuing details under a rule, and the
7686
- * machine-readable strip at the foot. `view` names each role (Task 5) — see `masthead`'s own
7687
- * doc comment for what an ordered name list means.
7688
- *
7689
- * overline — the caption at the top left ("PASSPORT", "DRIVING LICENCE")
7690
- * number — the document's number, set apart at the top right
7691
- * photo — the portrait. One field, rendered in a portrait frame beside the body
7692
- * meta — the card's OWN fields, labelled, at each field's declared span
7693
- * footer — the fields below the rule: issued, authority, record number
7694
- * mrz — the machine-readable strip, set in monospace at the foot
7695
- *
7696
- * Every role is optional but `number`: a bank card has no `mrz`, a library card no `photo`,
7697
- * and the card simply omits the part it was given nothing for.
7698
- *
7699
- * @layer block
7700
- * @base group
7701
- * @prim —
7702
- * @widget idcard
7703
- * @example f.idcard({ number: { number: f.string({ label: '…' }) }, meta: { issued: f.string({ label: '…' }) } })
7704
- */
7705
6337
  function idcard(opts) {
7706
6338
  const overline = opts.overline !== void 0 ? slot("idcard", "overline", opts.overline) : void 0;
7707
6339
  const number = slot("idcard", "number", opts.number);
@@ -7723,16 +6355,6 @@ function idcard(opts) {
7723
6355
  }
7724
6356
  };
7725
6357
  }
7726
- /**
7727
- * Properties inspector — a compact key-value metadata grid (columns default 2).
7728
- * Formatted pairs with clear labels and concise values (like Notion / Linear property bars).
7729
- *
7730
- * @layer block
7731
- * @base group
7732
- * @prim —
7733
- * @widget properties
7734
- * @example f.properties({ columns: 2, fields: { a: f.string({ label: '…' }) } })
7735
- */
7736
6358
  function properties(opts) {
7737
6359
  return {
7738
6360
  ...group({
@@ -7747,14 +6369,6 @@ function properties(opts) {
7747
6369
  }
7748
6370
  };
7749
6371
  }
7750
- /** Vertical stack — 1-column full-width flow of fields.
7751
- *
7752
- * @layer block
7753
- * @base group
7754
- * @prim —
7755
- * @widget stack
7756
- * @example f.stack({ fields: { a: f.string({ label: '…' }) } })
7757
- */
7758
6372
  function stack(opts) {
7759
6373
  return {
7760
6374
  ...group({
@@ -7766,7 +6380,6 @@ function stack(opts) {
7766
6380
  view: { kind: "stack" }
7767
6381
  };
7768
6382
  }
7769
- /** The block factories, merged into the `f` namespace beside row/table/sheet — see fields.ts. */
7770
6383
  var blocks = {
7771
6384
  bento,
7772
6385
  split,
@@ -8550,7 +7163,7 @@ var require_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8550
7163
  };
8551
7164
  }));
8552
7165
  //#endregion
8553
- //#region ../sdk/src/fields/constants.ts
7166
+ //#region ../sdk/dist/fields/constants.js
8554
7167
  var import_src = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
8555
7168
  var LANGUAGES_LIST = require_data();
8556
7169
  var LANGUAGES = {};
@@ -8606,58 +7219,10 @@ import_src.default.getAllCodes().map((code) => ({
8606
7219
  name: import_src.default.getNativeName(code)
8607
7220
  })).sort((a, b) => a.name.localeCompare(b.name));
8608
7221
  //#endregion
8609
- //#region ../sdk/src/fields.ts
8610
- /**
8611
- * The field type system — the heart of the platform. A pure, isomorphic module.
8612
- * One field description → three consumers:
8613
- * 1. `zod` — validation (identical on server and client)
8614
- * 2. `column` — the DB column type
8615
- * 3. `prim`/`kind` — keys for resolving the renderer (see web/render/registry)
8616
- */
8617
- /**
8618
- * Does this node carry a value of its own? True for a leaf field, for a static display
8619
- * value, and for a composite — a composite's parts do not replace its own meta.
8620
- */
7222
+ //#region ../sdk/dist/fields.js
8621
7223
  var hasValue = (x) => x.type !== void 0 && typeof x.type === "object";
8622
- /**
8623
- * Does this node carry children? True for every container and for a composite. An EMPTY
8624
- * `fields` still counts: a row with no cells is a container that happens to hold nothing,
8625
- * not a leaf.
8626
- */
8627
7224
  var hasChildren = (x) => Array.isArray(x.fields);
8628
- /**
8629
- * Is this node an ORDINARY NAMED FIELD — the case almost every call site actually means by
8630
- * "a field", as opposed to a keyless display value (a computed value with no storage) or a
8631
- * container? `hasValue(x) && !hasChildren(x) && x.key !== undefined` is this exact test,
8632
- * spelled out by hand at some twenty call sites before this predicate existed.
8633
- *
8634
- * That duplication is why this predicate exists: today `hasValue(x) && !hasChildren(x)`
8635
- * alone answers the same question, because no composite in today's composed schema is a
8636
- * both-node (see `LayoutNode`'s own doc comment above). The day one exists, a hand-spelled
8637
- * `hasValue && !hasChildren` silently reclassifies it as a container, while this predicate
8638
- * — which also checks `key` — still has to be taught what to do with it. One place to fix
8639
- * instead of twenty. See docs/decisions.md, 2026-08-26, "HAZARD for whoever produces the
8640
- * first both-node".
8641
- */
8642
7225
  var isNamedField = (x) => hasValue(x) && !hasChildren(x) && x.key !== void 0;
8643
- /**
8644
- * A visual or storage group of fields, nestable to any depth — a group inside a group
8645
- * inside a group, columns expanding recursively (`key__child__grandchild__…`, see
8646
- * `checkStorage` in `compose.ts`). There is no level count: the only ceiling is the
8647
- * identifier budget PostgreSQL imposes on a generated name, checked at composition time
8648
- * (`MAX_COLUMN_NAME`/`MAX_TABLE_NAME`, `packages/sdk/src/compose.ts`), not a depth guard.
8649
- * `scope` decides which: `'hoist'` (the default) arranges children inside the PARENT's
8650
- * scope and stores nothing of its own; `'nest'` owns a scope and is stored —
8651
- * - embedded (no multiple): a nested object over `key__sub` columns;
8652
- * - collection (multiple): a child table (array of rows).
8653
- * A 'nest' group is NAMED BY ITS PARENT — the property it is declared under — and takes
8654
- * only NAMED children (each with a storage key of its own).
8655
- *
8656
- * @layer primitive
8657
- * @prim —
8658
- * @widget group
8659
- * @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
8660
- */
8661
7226
  function group(o) {
8662
7227
  if (o["view"] !== void 0) throw new Error("[group] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
8663
7228
  const r = o.rules ?? {};
@@ -8678,7 +7243,6 @@ function group(o) {
8678
7243
  scope,
8679
7244
  multiple: o.multiple,
8680
7245
  compute: o.value,
8681
- required: o.required,
8682
7246
  unique: r.unique,
8683
7247
  label: o.label,
8684
7248
  icon: o.icon,
@@ -8689,21 +7253,6 @@ function group(o) {
8689
7253
  fields
8690
7254
  };
8691
7255
  }
8692
- /**
8693
- * Layout group, flow nowrap + horizontal scroll (single line). Sugar over group().
8694
- *
8695
- * `multiple` is an ordinary modifier here as everywhere else: `f.row({ scope: 'nest',
8696
- * multiple: true })` is a COLLECTION of rows, and the renderer aligns its columns (a repeated row is what a
8697
- * table is). There is no separate factory and no separate `display` value to DECLARE a
8698
- * tabular collection — `f.table` (`blocks.ts`) is a VIEW stamped onto a collection built
8699
- * here, never a second way to declare one.
8700
- *
8701
- * @layer primitive
8702
- * @prim —
8703
- * @widget group
8704
- * @example f.row({ label: 'mod.fields.row', fields: {} })
8705
- * @example f.row({ scope: 'nest', label: 'mod.fields.items', multiple: true, fields: {} })
8706
- */
8707
7256
  function row(o) {
8708
7257
  return group({
8709
7258
  scope: o.scope,
@@ -8711,27 +7260,10 @@ function row(o) {
8711
7260
  icon: o.icon,
8712
7261
  fields: o.fields,
8713
7262
  multiple: o.multiple,
8714
- required: o.required,
8715
7263
  rules: o.rules,
8716
7264
  ui: { display: "scroll" }
8717
7265
  });
8718
7266
  }
8719
- /**
8720
- * Layout group with a 2D layout (rows×columns → aligned CSS grid). Sugar over group().
8721
- *
8722
- * A ROW IS A GROUP. `fields` is a map of rows, each row a map of its cells: the outer
8723
- * property names the row, the inner ones name the cells (their storage keys, like any
8724
- * other named child). Each row materializes into a nested `scope: 'hoist'` group, so a
8725
- * row boundary is the nesting itself and the renderer reads `group.fields` as its rows
8726
- * instead of scanning a flat list for separator markers. The cells still belong to the
8727
- * PARENT's scope — a hoist group owns none — so the columns are exactly the ones the flat
8728
- * form produced, an empty row included (a row group with no cells).
8729
- *
8730
- * @layer primitive
8731
- * @prim —
8732
- * @widget group
8733
- * @example f.sheet({ label: 'mod.fields.sheet', fields: { net: { ip: f.string({}) }, hw: { mac: f.string({}) } } })
8734
- */
8735
7267
  function sheet(o) {
8736
7268
  const rows = Object.fromEntries(Object.entries(o.fields).map(([name, cells]) => [name, group({
8737
7269
  scope: "hoist",
@@ -8744,22 +7276,10 @@ function sheet(o) {
8744
7276
  ui: { display: "sheet" }
8745
7277
  });
8746
7278
  }
8747
- /**
8748
- * Composite URL — embedded-group sugar. Storage: separate nested columns
8749
- * (`key__scheme`, `key__host`, `key__port`, …) in the same table. The renderer
8750
- * is custom (a single link/input, `kind:'url'`) — string↔parts via
8751
- * parseUrl/buildUrl. Partial/invalid URLs are stored by parts.
8752
- *
8753
- * @layer primitive
8754
- * @prim —
8755
- * @widget url
8756
- * @example f.url({ label: 'mod.fields.website' })
8757
- */
8758
7279
  function url(o) {
8759
7280
  return group({
8760
7281
  scope: "nest",
8761
7282
  label: o.label,
8762
- required: o.required,
8763
7283
  ui: { kind: "url" },
8764
7284
  fields: {
8765
7285
  scheme: string({}),
@@ -8773,16 +7293,6 @@ function url(o) {
8773
7293
  }
8774
7294
  });
8775
7295
  }
8776
- /**
8777
- * Keyed collection: one row per value of a single named field of that row — `by` names
8778
- * which one (default: the first), and its own name is the row's storage key. See the block
8779
- * comment above for the full contract (container, fixed, unique).
8780
- *
8781
- * @layer primitive
8782
- * @prim —
8783
- * @widget group
8784
- * @example f.keyed({ label: 'mod.fields.translations', fields: { lang: f.string({ options: ['en', 'uk'] }) } })
8785
- */
8786
7296
  function keyed(o) {
8787
7297
  const entries = Object.entries(o.fields);
8788
7298
  if (entries.length === 0) throw new Error(`[field.keyed] fields must declare at least the key field`);
@@ -8811,67 +7321,25 @@ function keyed(o) {
8811
7321
  }),
8812
7322
  scope: "nest",
8813
7323
  multiple: true,
8814
- required: o.required,
8815
7324
  unique: o.unique ?? [role],
8816
7325
  fixed: o.fixed,
8817
7326
  view: { by: role }
8818
7327
  };
8819
7328
  }
8820
- var isStorageGroup = (g) => g.scope === "nest";
8821
7329
  var isCollectionGroup = (g) => g.scope === "nest" && g.multiple === true;
8822
7330
  var isEmbeddedGroup = (g) => g.scope === "nest" && g.multiple !== true;
8823
- /**
8824
- * Horizontal divider, optionally labeled (a section heading).
8825
- *
8826
- * @layer primitive
8827
- * @prim —
8828
- * @widget divider
8829
- * @example f.divider({ label: 'mod.fields.section' })
8830
- */
8831
7331
  function divider(o) {
8832
7332
  return {
8833
7333
  el: "divider",
8834
7334
  label: o?.label
8835
7335
  };
8836
7336
  }
8837
- /**
8838
- * Reference markdown block by i18n key.
8839
- *
8840
- * The key is resolved WITH the variables of the running instance, so the locale string
8841
- * may name facts that are not constants: `{{origin}}` is this instance's own address
8842
- * (`https://coffer.example`), which is how a block documents an endpoint without
8843
- * freezing a developer's `localhost` into every translation. The set is fixed and
8844
- * ambient — the block declares only its key. See `web-ui/src/render/info-text.ts`.
8845
- *
8846
- * @layer primitive
8847
- * @prim —
8848
- * @widget info
8849
- * @example f.info('mod.fields.infoText')
8850
- */
8851
7337
  function info(textKey) {
8852
7338
  return {
8853
7339
  el: "info",
8854
7340
  text: textKey
8855
7341
  };
8856
7342
  }
8857
- /**
8858
- * What else points AT this record — the inverse of a relation, which is often the more useful
8859
- * direction on a record page: a box is more usefully "what is in it" than "what it is in".
8860
- *
8861
- * Stores NOTHING and has no column: it declares where to look, and the server answers by asking
8862
- * the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
8863
- * a field — there is no value here to validate or save.
8864
- *
8865
- * `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
8866
- * "everything that happens to point here": a shelf may point at the same target through two
8867
- * fields — a transaction has a source account and a destination account — and a panel that
8868
- * merged them would answer a question nobody asked.
8869
- *
8870
- * @layer primitive
8871
- * @prim —
8872
- * @widget backrefs
8873
- * @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
8874
- */
8875
7343
  function backrefs(o) {
8876
7344
  return {
8877
7345
  el: "backrefs",
@@ -8879,14 +7347,6 @@ function backrefs(o) {
8879
7347
  from: o.from
8880
7348
  };
8881
7349
  }
8882
- /**
8883
- * Action button: invokes the handler registered in actionRegistry under the key `value`.
8884
- *
8885
- * @layer primitive
8886
- * @prim —
8887
- * @widget button
8888
- * @example f.button({ label: 'mod.fields.run', value: 'runAction' })
8889
- */
8890
7350
  function button(o) {
8891
7351
  return {
8892
7352
  el: "button",
@@ -8896,38 +7356,14 @@ function button(o) {
8896
7356
  variant: o.variant
8897
7357
  };
8898
7358
  }
8899
- /**
8900
- * Brand for `fromShelf()` — a private key, never producible by a plain object literal, so
8901
- * `isShelfSource` tells a dynamic options source apart from a literal array or a named-list
8902
- * string CHEAPLY (one property read) and WITHOUT guessing from shape (`{library,shelf}`
8903
- * alone is not enough: a plugin's own option-list item could coincidentally carry those
8904
- * keys). Not exported — callers only ever get a `ShelfOptionsSource` back from `fromShelf`.
8905
- */
8906
7359
  var SHELF_SOURCE = Symbol("shelfSource");
8907
- /** Runtime discriminator for `ShelfOptionsSource` — see `SHELF_SOURCE`'s own doc comment. */
8908
7360
  function isShelfSource(v) {
8909
7361
  return typeof v === "object" && v !== null && v[SHELF_SOURCE] === true;
8910
7362
  }
8911
7363
  function resolveDate(v) {
8912
7364
  return v === "today" ? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) : v;
8913
7365
  }
8914
- /**
8915
- * Does this field offer preset values? Not `options.length`: a field whose options come
8916
- * from a NAMED source carries an empty array until composeRegistry resolves it, and every
8917
- * consumer that asks the question earlier (a `keyed({fixed})` check at declaration time, a
8918
- * role's `accepts`) would get the wrong answer.
8919
- */
8920
7366
  var hasOptions = (f) => (f.options?.length ?? 0) > 0 || f.hints["source"] != null;
8921
- /**
8922
- * `hasOptions`'s own companion for phase 1 (a DECLARATION, not a built meta) — the same
8923
- * question ENUMERATED's `test` asks further down. Now that `options` means the same thing
8924
- * for every type — a literal list, a named list, or `fromShelf(...)` all describe where
8925
- * this field's choices come from — the question is exactly "is `options` present?", nothing
8926
- * more: a `relation` field's choices come from records, which is as much an answer as a
8927
- * literal array is. No separate `opts.source` to check either: composeRegistry only
8928
- * resolves the two STATIC forms into values, but `opts.options` itself is set at
8929
- * declaration time regardless of which form it is.
8930
- */
8931
7367
  var declHasOptions = (d) => d.opts.options != null;
8932
7368
  registerType("string", {
8933
7369
  kind: "text",
@@ -8935,7 +7371,7 @@ registerType("string", {
8935
7371
  widget: "text",
8936
7372
  build(o) {
8937
7373
  const cfg = o.config ?? {};
8938
- const min = cfg.min ?? (o.required === true ? 1 : 0);
7374
+ const min = cfg.min ?? 0;
8939
7375
  return {
8940
7376
  column: "text",
8941
7377
  zod: stringContent((str) => {
@@ -8952,20 +7388,11 @@ registerType("string", {
8952
7388
  };
8953
7389
  }
8954
7390
  });
8955
- /**
8956
- * Single-line text. Arbitrary pattern/messageKey — via `rules`. Multi-line — `text()`.
8957
- *
8958
- * @layer primitive
8959
- * @prim text
8960
- * @widget text
8961
- * @example f.string({ label: 'mod.fields.name' })
8962
- */
8963
7391
  function string(o) {
8964
7392
  return declare("string", o);
8965
7393
  }
8966
- /** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
8967
- function pathZod(o) {
8968
- return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
7394
+ function pathZod(_o) {
7395
+ return stringContent((s) => s);
8969
7396
  }
8970
7397
  registerType("localDir", {
8971
7398
  kind: "localDir",
@@ -8980,14 +7407,6 @@ registerType("localDir", {
8980
7407
  };
8981
7408
  }
8982
7409
  });
8983
- /**
8984
- * Path to a DIRECTORY on the server (autocomplete, within home only).
8985
- *
8986
- * @layer primitive
8987
- * @prim text
8988
- * @widget localDir
8989
- * @example f.localDir({ label: 'mod.fields.path' })
8990
- */
8991
7410
  function localDir(o) {
8992
7411
  return declare("localDir", o);
8993
7412
  }
@@ -9005,14 +7424,6 @@ registerType("localFile", {
9005
7424
  };
9006
7425
  }
9007
7426
  });
9008
- /**
9009
- * Path to a FILE on the server (autocomplete; config.exts filters extensions).
9010
- *
9011
- * @layer primitive
9012
- * @prim text
9013
- * @widget localFile
9014
- * @example f.localFile({ label: 'mod.fields.path' })
9015
- */
9016
7427
  function localFile(o) {
9017
7428
  return declare("localFile", o);
9018
7429
  }
@@ -9024,14 +7435,6 @@ registerType("text", {
9024
7435
  return typeOf("string").build(o, []);
9025
7436
  }
9026
7437
  });
9027
- /**
9028
- * Multi-line text (`kind: 'textarea'`). No `multiple`.
9029
- *
9030
- * @layer primitive
9031
- * @prim text
9032
- * @widget textarea
9033
- * @example f.text({ label: 'mod.fields.notes' })
9034
- */
9035
7438
  function text(o) {
9036
7439
  return declare("text", o);
9037
7440
  }
@@ -9043,17 +7446,6 @@ registerType("i18n", {
9043
7446
  return typeOf("text").build(o, []);
9044
7447
  }
9045
7448
  });
9046
- /**
9047
- * Translatable text (kind 'i18n'). Stores a string — an i18n key OR a literal.
9048
- * In view it shows `t(value, { defaultValue: value })`: a known key → translation,
9049
- * any other text → shown as-is. Edit — raw input (key/literal without t()).
9050
- * Built-in reference catalogs write a key; user records — a plain literal.
9051
- *
9052
- * @layer primitive
9053
- * @prim text
9054
- * @widget i18n
9055
- * @example f.i18n({ label: 'mod.fields.title' })
9056
- */
9057
7449
  function i18n(o) {
9058
7450
  return declare("i18n", o);
9059
7451
  }
@@ -9065,14 +7457,6 @@ registerType("smallText", {
9065
7457
  return typeOf("text").build(o, []);
9066
7458
  }
9067
7459
  });
9068
- /**
9069
- * Multi-line text, scalar size (narrow column ~2 rows). Full-width — `text()`.
9070
- *
9071
- * @layer primitive
9072
- * @prim text
9073
- * @widget smalltext
9074
- * @example f.smallText({ label: 'mod.fields.summary' })
9075
- */
9076
7460
  function smallText(o) {
9077
7461
  return declare("smallText", o);
9078
7462
  }
@@ -9096,14 +7480,6 @@ registerType("real", {
9096
7480
  };
9097
7481
  }
9098
7482
  });
9099
- /**
9100
- * Real number (column 'real'). min/max/step — via `rules`.
9101
- *
9102
- * @layer primitive
9103
- * @prim number
9104
- * @widget number
9105
- * @example f.real({ label: 'mod.fields.weight' })
9106
- */
9107
7483
  function real(o) {
9108
7484
  return declare("real", o);
9109
7485
  }
@@ -9127,14 +7503,6 @@ registerType("int", {
9127
7503
  };
9128
7504
  }
9129
7505
  });
9130
- /**
9131
- * Integer (column 'integer'). min/max/step — via `rules`.
9132
- *
9133
- * @layer primitive
9134
- * @prim number
9135
- * @widget number
9136
- * @example f.int({ label: 'mod.fields.count' })
9137
- */
9138
7506
  function int(o) {
9139
7507
  return declare("int", o);
9140
7508
  }
@@ -9188,14 +7556,6 @@ registerType("date", {
9188
7556
  };
9189
7557
  }
9190
7558
  });
9191
- /**
9192
- * Date value with configurable granularity ('day' | 'month' | 'year', default 'day').
9193
- *
9194
- * @layer primitive
9195
- * @prim date
9196
- * @widget date
9197
- * @example f.date({ label: 'mod.fields.purchaseDate' })
9198
- */
9199
7559
  function date(o) {
9200
7560
  return declare("date", o);
9201
7561
  }
@@ -9217,15 +7577,6 @@ registerType("time", {
9217
7577
  };
9218
7578
  }
9219
7579
  });
9220
- /**
9221
- * Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
9222
- * 'minute' — the same default `f.datetime` has always had).
9223
- *
9224
- * @layer primitive
9225
- * @prim time
9226
- * @widget time
9227
- * @example f.time({ label: 'mod.fields.startTime' })
9228
- */
9229
7580
  function time(o) {
9230
7581
  return declare("time", o);
9231
7582
  }
@@ -9246,14 +7597,6 @@ registerType("datetime", {
9246
7597
  };
9247
7598
  }
9248
7599
  });
9249
- /**
9250
- * Combined date-and-time value with configurable granularity ('hour' | 'minute' | 'second', default 'minute').
9251
- *
9252
- * @layer primitive
9253
- * @prim datetime
9254
- * @widget datetime
9255
- * @example f.datetime({ label: 'mod.fields.startedAt' })
9256
- */
9257
7600
  function datetime(o) {
9258
7601
  return declare("datetime", o);
9259
7602
  }
@@ -9270,15 +7613,6 @@ registerType("boolean", {
9270
7613
  };
9271
7614
  }
9272
7615
  });
9273
- /**
9274
- * Boolean value rendered as a checkbox; defaults to false, nullable in storage (an
9275
- * unchanged collection row sends `null`, not `undefined`).
9276
- *
9277
- * @layer primitive
9278
- * @prim checkbox
9279
- * @widget boolean
9280
- * @example f.boolean({ label: 'mod.fields.active' })
9281
- */
9282
7616
  function boolean(o) {
9283
7617
  return declare("boolean", o);
9284
7618
  }
@@ -9299,14 +7633,6 @@ registerType("triState", {
9299
7633
  };
9300
7634
  }
9301
7635
  });
9302
- /**
9303
- * Three-way choice: 'yes' / 'no' / 'unknown'.
9304
- *
9305
- * @layer primitive
9306
- * @prim triState
9307
- * @widget triState
9308
- * @example f.triState({ label: 'mod.fields.reviewed' })
9309
- */
9310
7636
  function triState(o) {
9311
7637
  return declare("triState", o);
9312
7638
  }
@@ -9324,60 +7650,12 @@ registerType("stepper", {
9324
7650
  };
9325
7651
  }
9326
7652
  });
9327
- /**
9328
- * The REACHED stage of an ORDERED set of options. ONE value, so a gap is UNREPRESENTABLE
9329
- * rather than merely rejected: reaching stage 3 IS stages 1 and 2 having happened, and
9330
- * "3 but not 2" cannot be written down at all — there is no sequential-gate rule anywhere,
9331
- * in zod or in the UI, because there is nothing to gate. That is the whole difference from
9332
- * `f.string({ options, strict })`, whose options are PEERS (a weekday, a category), and
9333
- * from `f.check`, whose boxes tick independently. An empty value is the legitimate
9334
- * "not started" state, and going back a stage is legitimate too: the field stores where
9335
- * the record IS, never a history of how it got there.
9336
- *
9337
- * STORAGE — the stage's own VALUE, not its index, and which one it is matters because the
9338
- * two fail differently. An index survives renaming a stage and breaks on a reorder or an
9339
- * insertion; a value survives a reorder and breaks on a rename. Inserting a stage into the
9340
- * middle of the set ('customs' between 'shipped' and 'delivered') is the ORDINARY edit of a
9341
- * domain that grows, and under index storage it silently reinterprets every row already
9342
- * stored — corruption with no symptom anywhere. A rename is the rarer edit, it leaves a
9343
- * value the option set no longer contains, and THAT state is loud at both ends: `strict`
9344
- * (forced, below) rejects the next write of it, and the renderer draws it as an unknown
9345
- * stage rather than guessing a position for it or clamping it to a neighbour. A rename is
9346
- * also the case the tree already has a migration for (`TableOps.fill`/`convert`, skill
9347
- * `data-migrations`); nothing in it renumbers stored rows after an insertion. So: the
9348
- * stored value is the option's `value`, and the ORDER is `options`' declaration order.
9349
- *
9350
- * What "loud" costs, stated plainly because this field advertises it: `strict` rejects on
9351
- * WRITE, so a record carrying a stale stage cannot be saved AT ALL until the stage is
9352
- * repaired — not even for an unrelated edit to another field. That is ordinary strict-field
9353
- * behaviour rather than anything new here, but on a field whose whole promise is failing
9354
- * loudly it is the half of the promise that costs something.
9355
- *
9356
- * `strict` is forced on and is not the author's to choose — a value outside the ordered set
9357
- * has no position in it, so it can never be a legitimate entry. Never `multiple`: a record
9358
- * is at exactly one stage.
9359
- *
9360
- * @layer primitive
9361
- * @prim text
9362
- * @widget stepper
9363
- * @example f.stepper({ label: 'mod.fields.stage', options: ['ordered', 'shipped', 'delivered'] })
9364
- */
9365
7653
  function stepper(o) {
9366
7654
  return declare("stepper", {
9367
7655
  ...o,
9368
7656
  strict: true
9369
7657
  });
9370
7658
  }
9371
- /**
9372
- * Reject anything beyond a flat equality map: an operator key (`$in`, `$and`, …), an
9373
- * object-valued entry (which is how a nested operator like `{ type: { $in: [...] } }`
9374
- * would smuggle itself in), or an explicit `undefined` value all throw, naming the
9375
- * offending key. `undefined` is rejected alongside objects because it would otherwise
9376
- * slip through every later layer unnoticed: `Condition` types it as a valid value,
9377
- * `conditionKeys` skips it so compose validates nothing against the target shelf, and
9378
- * the picker's query-string builder turns it into the literal string `'undefined'`
9379
- * (an empty picker with no error anywhere).
9380
- */
9381
7659
  function assertFlatEqualityFilter(filter) {
9382
7660
  for (const [key, value] of Object.entries(filter)) {
9383
7661
  if (key.startsWith("$")) throw new Error(`[relation] filter key '${key}' is an operator — only flat equality is allowed here`);
@@ -9420,17 +7698,6 @@ registerType("relation", {
9420
7698
  };
9421
7699
  }
9422
7700
  });
9423
- /**
9424
- * Link to records of another shelf (`{library, shelf}`). Stores an integer id (single)
9425
- * or a JSON array of integer ids (multiple). Rendered as RecordInline + a record picker.
9426
- * This used to be the record branch of `select()`; split out into a separate primitive
9427
- * so select stays a pure enum/source list.
9428
- *
9429
- * @layer primitive
9430
- * @prim relation
9431
- * @widget relation
9432
- * @example f.relation({ label: 'mod.fields.owner', options: fromShelf({ library: 'people', shelf: 'person' }) })
9433
- */
9434
7701
  function relation(o) {
9435
7702
  return declare("relation", o);
9436
7703
  }
@@ -9457,49 +7724,13 @@ registerType("json", {
9457
7724
  };
9458
7725
  }
9459
7726
  });
9460
- /**
9461
- * Arbitrary JSON value: a native object/array, or a string parsed as JSON.
9462
- *
9463
- * @layer primitive
9464
- * @prim json
9465
- * @widget json
9466
- * @example f.json({ label: 'mod.fields.raw' })
9467
- */
9468
7727
  function json(o) {
9469
7728
  return declare("json", o);
9470
7729
  }
9471
- /**
9472
- * A content role accepts whatever the author declared, because for `check` the author's
9473
- * field IS the filling: there is no second override channel to correct one AGAINST (see
9474
- * `contentRole` below — both branches of its `normalize` hand back the same declaration).
9475
- *
9476
- * It used to demand a field owning ONE column, on the grounds that "a composite inside a
9477
- * composite would need `key__sub__sub`, which nothing on the flatten/nest path builds".
9478
- * `partColumns` builds it now, and the flatten/nest walks follow it — so the ground is
9479
- * gone and with it the only thing this role ever refused.
9480
- */
9481
7730
  var ANY_CONTENT = {
9482
7731
  test: () => true,
9483
7732
  describe: "the author's own content field"
9484
7733
  };
9485
- /**
9486
- * A single check's roles are not a fixed table: they come from the author's own `fields`
9487
- * and `slots`, the way `measured`'s unit role comes from its `units`. There is no second
9488
- * override channel: `fields` IS how an author fills the content roles, so every role
9489
- * resolves through its own `fallback` and `check` takes no `parts` spec. Reusing the
9490
- * author's `FieldMeta` as that fallback is safe for the same reason — the node was
9491
- * built inside this one `f.check(…)` call and is shared with nothing else.
9492
- *
9493
- * The two forms use DIFFERENT role orders, both load-bearing — `roles(opts)` reads
9494
- * `opts.multiple` (declared data, already resolved to a concrete boolean for THIS
9495
- * declaration by the time the pipeline calls it once) and returns the matching order, the
9496
- * same way `numberRange`/`realRange`'s own `roleZod` reads `opts.config`:
9497
- * - single: content first, then one boolean per slot — declaration order is column
9498
- * order, so the leading content field is the magnitude `magnitudeSub()` sorts and
9499
- * filters by;
9500
- * - multiple: checkbox columns first, then content — its own established child-table
9501
- * shape (the renderer's checklist reads the toggles as the row's leading columns).
9502
- */
9503
7734
  var contentRole = (f) => {
9504
7735
  const def = {
9505
7736
  role: f.key,
@@ -9516,55 +7747,25 @@ var checkboxRole = (label, i) => {
9516
7747
  };
9517
7748
  return def;
9518
7749
  };
9519
- /** `opts.slots`, defaulted to one unnamed checkbox — read the same way by `roles`/`roleZod`/`hints`. */
9520
7750
  function checkSlots(opts) {
9521
7751
  const slots = opts.slots;
9522
7752
  return slots && slots.length > 0 ? slots : [""];
9523
7753
  }
9524
- /**
9525
- * The content fields of one `f.check(…)` call: materialized, and validated at `roles()`
9526
- * time — declaration-time-equivalent, since every real entry point (`defineShelf`, `group()`,
9527
- * …) materializes synchronously at shelf-definition time — no duplicate key, no
9528
- * `check\d+`-shaped key (reserved for the checkbox slots).
9529
- *
9530
- * A composite content field is ordinary now: `partColumns` expands it into
9531
- * `<content>__<sub>` and the single form stores it across `<field>__<content>__<sub>`,
9532
- * exactly as the multiple form has always stored it in its child table. The guard that
9533
- * refused one in the single form is gone with the flat collector that made it necessary.
9534
- */
9535
7754
  function checkContentFields(opts) {
9536
7755
  const fields = opts.fields;
9537
7756
  const contentFields = materializeTree(fields !== void 0 && Object.keys(fields).length > 0 ? fields : { text: string({ label: "core.fields.text" }) });
9538
7757
  for (const f of contentFields) if (/^check\d+$/.test(f.key)) throw new Error(`[field.check] content field '${f.key}' is reserved for checkbox slots`);
9539
7758
  return contentFields;
9540
7759
  }
9541
- /** `check`'s roles, in the order its CURRENT form (single vs multiple) stores them — see
9542
- * this section's own header note for why the order differs and why reading `opts.multiple`
9543
- * here is enough: one `materialize()` call means one already-resolved `multiple`. This is
9544
- * the ONE call `checkContentFields` gets per build — the pipeline calls `roles(opts)`
9545
- * once and resolves the result into `parts`, which `roleZod`/`hints` below read back
9546
- * instead of recomputing. */
9547
7760
  function checkRoles(opts) {
9548
7761
  const contentRoles = checkContentFields(opts).map(contentRole);
9549
7762
  const checkboxRoles = checkSlots(opts).map(checkboxRole);
9550
7763
  return opts.multiple ? [...checkboxRoles, ...contentRoles] : [...contentRoles, ...checkboxRoles];
9551
7764
  }
9552
- /**
9553
- * The CONTENT slice of `check`'s own already-resolved `parts` — everything but the
9554
- * checkbox roles `roleZod`/`hints` handle separately (the checkbox roles' default zod is
9555
- * a bare `z.boolean()` with no content behind it; `hints.fields` is documented content-
9556
- * only). Sliced by the checkbox COUNT (`checkSlots`, declared data, no materialize), off
9557
- * whichever end `checkRoles` put them on for this form — not a role-name pattern match,
9558
- * which would re-hardcode the very `check\d+` shape `checkContentFields` already guards as
9559
- * a RESERVED key, not a way to recognize one.
9560
- */
9561
7765
  function checkContentParts(opts, parts) {
9562
7766
  const slotCount = checkSlots(opts).length;
9563
7767
  return opts.multiple ? parts.slice(slotCount) : parts.slice(0, parts.length - slotCount);
9564
7768
  }
9565
- /** `check`'s per-role default zod, from the parts the pipeline already resolved — reading
9566
- * `p.meta.zod`/`p.role` off each content part instead of re-materializing the author's
9567
- * `fields` a second time (see `checkContentParts`). */
9568
7769
  function checkRoleZod(opts, parts) {
9569
7770
  const shape = {};
9570
7771
  for (const p of checkContentParts(opts, parts)) shape[p.role] = p.meta.zod;
@@ -9587,47 +7788,22 @@ registerType("check", {
9587
7788
  }))
9588
7789
  })
9589
7790
  });
9590
- /**
9591
- * Row(s) of N labeled checkboxes plus content fields. Single (non-multiple) stores real
9592
- * columns (`check0`, `check1`, …) alongside the content fields; multiple stores a child
9593
- * table with the checkbox columns first, then the content fields.
9594
- *
9595
- * @layer primitive
9596
- * @prim check
9597
- * @widget check
9598
- * @example f.check({ label: 'mod.fields.tasks' })
9599
- */
9600
7791
  function check(o) {
9601
7792
  return declare("check", o);
9602
7793
  }
9603
- /**
9604
- * A role whose filling must be ENUMERATED: a unit, a currency. Now that `options` means the
9605
- * same thing for every type — a literal list, a named list, or `fromShelf(...)` all describe
9606
- * where this field's choices come from — the question is exactly "is `options` present?": a
9607
- * `relation` filling qualifies exactly like a select does, its choices being its target
9608
- * shelf's records rather than a literal list.
9609
- *
9610
- * Tests the DECLARED opts (`d.opts.options`), not `hasOptions` on a BUILT meta: a named
9611
- * option source is bound later, at `composeRegistry`, so asking `hasOptions` here — in
9612
- * phase 1, before that binding — would read an empty array and reject a perfectly good
9613
- * named-source declaration.
9614
- */
9615
7794
  var ENUMERATED = {
9616
7795
  test: (d) => d.opts.options != null,
9617
7796
  describe: "a field with options, or a relation"
9618
7797
  };
9619
- /** Roles of `measured`: the magnitude and its unit. The unit's default depends on THIS
9620
- * field's own `units`, so it writes its own `normalize` rather than using `role()`. */
9621
7798
  var MEASURED_ROLES = (opts) => {
9622
7799
  const unitRole = {
9623
7800
  role: "unit",
9624
7801
  accepts: ENUMERATED,
9625
7802
  normalize: (d) => {
9626
7803
  if (d && accepts(unitRole, d)) return d;
9627
- const { label, required, value } = d?.opts ?? {};
7804
+ const { label, value } = d?.opts ?? {};
9628
7805
  return declare("string", {
9629
7806
  label,
9630
- required,
9631
7807
  value,
9632
7808
  noSearch: d?.opts.noSearch,
9633
7809
  ui: uiOf(d?.opts),
@@ -9638,8 +7814,6 @@ var MEASURED_ROLES = (opts) => {
9638
7814
  };
9639
7815
  return [role("value", ["number"], "real"), unitRole];
9640
7816
  };
9641
- /** `measured`'s per-role default zod: the magnitude (int via step:1, kept for compat) and
9642
- * the unit, validated against `opts.units`. */
9643
7817
  function measuredRoleZod(opts) {
9644
7818
  const cfg = opts.config ?? {};
9645
7819
  const units = resolveUnits(opts.units);
@@ -9652,14 +7826,6 @@ function measuredRoleZod(opts) {
9652
7826
  unit: string$1().refine((v) => units.some((u) => u.value === v), { message: vmsg("measured_unit") })
9653
7827
  };
9654
7828
  }
9655
- /**
9656
- * `measured`'s own structure-failure report: not a flat code, but a SEARCH of the failed
9657
- * `rowSchema`'s own issues for one that already carries a vmsg-coded message (the unit
9658
- * role's own `measured_unit` refine) — that message is forwarded verbatim instead of the
9659
- * flat `measured_structure` code, so the caller learns WHICH part failed. Falls back to
9660
- * `measured_structure` when no such issue is found (a genuinely malformed row, not a bad
9661
- * unit).
9662
- */
9663
7829
  function measuredStructureCode(error) {
9664
7830
  const vmsgIssue = error.issues.find((iss) => {
9665
7831
  try {
@@ -9688,15 +7854,6 @@ registerType("measured", {
9688
7854
  };
9689
7855
  }
9690
7856
  });
9691
- /**
9692
- * Magnitude + per-row unit stored together as a composite {value, unit}; the unit is
9693
- * validated against `units`.
9694
- *
9695
- * @layer primitive
9696
- * @prim measured
9697
- * @widget measured
9698
- * @example f.measured({ label: 'mod.fields.weight', units: 'weight' })
9699
- */
9700
7857
  function measured(o) {
9701
7858
  return declare("measured", o);
9702
7859
  }
@@ -9719,16 +7876,6 @@ registerType("unit", {
9719
7876
  };
9720
7877
  }
9721
7878
  });
9722
- /**
9723
- * Declares the unit ONCE per record. Every `amount` below it is a plain number on
9724
- * this scale, so sums and comparisons are ordinary SQL. Deliberately not a
9725
- * conversion mechanism: units.ts holds labels, not factors.
9726
- *
9727
- * @layer primitive
9728
- * @prim text
9729
- * @widget unit
9730
- * @example f.unit({ label: 'mod.fields.unit', options: 'weight' })
9731
- */
9732
7879
  function unit(o) {
9733
7880
  return declare("unit", o);
9734
7881
  }
@@ -9755,20 +7902,9 @@ registerType("amount", {
9755
7902
  };
9756
7903
  }
9757
7904
  });
9758
- /**
9759
- * A plain number whose unit is declared elsewhere (`unitFrom`). Stored in a `real`
9760
- * column: summable, comparable and orderable in SQL, unlike `measured`, which pairs
9761
- * a number with a per-row unit across two sub-columns.
9762
- *
9763
- * @layer primitive
9764
- * @prim number
9765
- * @widget amount
9766
- * @example f.amount({ label: 'mod.fields.quantity', unitFrom: 'unit' })
9767
- */
9768
7905
  function amount(o) {
9769
7906
  return declare("amount", o);
9770
7907
  }
9771
- /** Roles of `geo`: the coordinate pair and an optional human label. */
9772
7908
  var GEO_ROLES = () => [
9773
7909
  role("lat", ["number"], "real"),
9774
7910
  role("lng", ["number"], "real"),
@@ -9786,32 +7922,11 @@ registerType("geo", {
9786
7922
  label: string$1().nullish()
9787
7923
  }),
9788
7924
  structureCode: "geo_structure",
9789
- absenceRoles: ["lat", "lng"]
7925
+ acceptsEmptyRow: true
9790
7926
  });
9791
- /**
9792
- * Geographic point composite {lat, lng, label?}; lat/lng validated to valid coordinate
9793
- * ranges.
9794
- *
9795
- * @layer primitive
9796
- * @prim geo
9797
- * @widget geo
9798
- * @example f.geo({ label: 'mod.fields.location' })
9799
- */
9800
7927
  function geo(o) {
9801
7928
  return declare("geo", o);
9802
7929
  }
9803
- /**
9804
- * Roles of `illustrated`: the prose and the picture that illustrates it.
9805
- *
9806
- * ORDER IS LOAD-BEARING. `partColumns` emits columns in role order and `magnitudeSub`
9807
- * (sdk/shelf.ts) takes the first one, so it decides what `?about=…` filters and what a list
9808
- * column sorts by. `text` first points both at the prose; `image` first would point them at a
9809
- * file name.
9810
- *
9811
- * Both roles use a PREDICATE rather than a prim list: "renders as a picture" and "is prose" are
9812
- * properties of the field's `kind`, not of its storage family — every file field stores JSON and
9813
- * every text field stores `text`, so a prim list could not tell an image from a PDF.
9814
- */
9815
7930
  var ILLUSTRATED_IMAGE_KINDS = /* @__PURE__ */ new Set([
9816
7931
  "image",
9817
7932
  "media",
@@ -9832,23 +7947,6 @@ var ILLUSTRATED_ROLES = () => [role("text", {
9832
7947
  test: (d) => ILLUSTRATED_IMAGE_KINDS.has(typeOf(d.factory).kind),
9833
7948
  describe: "an image field (image, media, avatar, cover, poster)"
9834
7949
  }, "image")];
9835
- /**
9836
- * A composite's per-role default zod, read from each role's OWN fallback filling instead of
9837
- * being hand-written per type — `partsRowShape` substitutes a part's zod only when the
9838
- * author OVERRODE the filling (`p.overridden`), so whatever this returns is the entire
9839
- * validation a DEFAULT filling gets. Hand-writing it is how `illustrated`'s picture role
9840
- * ended up with `z.unknown()`: a bare `f.illustrated({})`, the factory's own @example,
9841
- * accepted any garbage in `image` and lost whatever rules the prose filling declares.
9842
- * Reading the fallbacks makes the two agree by construction, the way `f.period` builds its
9843
- * own default shape from its resolved sub-fields. Each fallback's zod is already
9844
- * `optionalize(…, false)`, which maps `''`/`null` to `undefined` — the nullish tolerance the
9845
- * previous literal spelled out by hand.
9846
- *
9847
- * Shared by every composite whose default schema needs nothing from the call's own opts
9848
- * (`illustrated`, `attachment`) — a type whose per-role shape IS derived from its opts
9849
- * (`period`'s granularity) or from the resolved fillings (`check`'s content roles) builds
9850
- * its own instead.
9851
- */
9852
7950
  function defaultRoleZod(roles) {
9853
7951
  const shape = {};
9854
7952
  for (const r of roles()) {
@@ -9864,42 +7962,13 @@ registerType("illustrated", {
9864
7962
  roles: ILLUSTRATED_ROLES,
9865
7963
  roleZod: () => defaultRoleZod(ILLUSTRATED_ROLES),
9866
7964
  structureCode: "illustrated_structure",
9867
- absenceRoles: ["text", "image"],
7965
+ acceptsEmptyRow: true,
9868
7966
  collectionView: "illustratedList",
9869
7967
  hints: (o) => o.config ? { config: o.config } : {}
9870
7968
  });
9871
- /**
9872
- * A picture and its description as ONE value: `{ text, image }`, stored across
9873
- * `<key>__text` / `<key>__image`. The renderer wraps the prose around the picture, so a
9874
- * record page states "what this is, shown and told" as one editorial unit instead of two
9875
- * fields with an empty rectangle between them.
9876
- *
9877
- * `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
9878
- * `f.period` and `f.check` do — a child table whose columns are the resolved parts.
9879
- *
9880
- * @layer primitive
9881
- * @prim illustrated
9882
- * @widget illustrated
9883
- * @example f.illustrated({ label: 'mod.fields.about' })
9884
- */
9885
7969
  function illustrated(o) {
9886
7970
  return declare("illustrated", o);
9887
7971
  }
9888
- /**
9889
- * Roles of `attachment`: the name the owner gives the document, and the document itself.
9890
- *
9891
- * ORDER IS LOAD-BEARING, for the same reason it is in `ILLUSTRATED_ROLES` above:
9892
- * `partColumns` emits columns in role order and `magnitudeSub` (sdk/shelf.ts) takes the
9893
- * first one, so it decides what `?<key>=…` filters and what a list column sorts by. `label`
9894
- * first points both at the words a person wrote; `file` first would point them at a JSON
9895
- * blob whose lexical order is an accident of how the entry's keys happen to be serialized.
9896
- *
9897
- * Both roles are PRIM LISTS, not predicates — unlike `illustrated`, which needs a predicate
9898
- * because it must tell a picture from a PDF and every file kind stores the same prim. This
9899
- * type accepts the whole file family, which is exactly what `prim: 'file'` names
9900
- * (`registerFileType`, below in this file — the roles resolve lazily, long after it runs),
9901
- * so a list says it precisely and a predicate would only restate it at more length.
9902
- */
9903
7972
  var ATTACHMENT_ROLES = () => [role("label", ["text"], "string"), role("file", ["file"], "file")];
9904
7973
  registerType("attachment", {
9905
7974
  kind: "attachment",
@@ -9908,38 +7977,12 @@ registerType("attachment", {
9908
7977
  roles: ATTACHMENT_ROLES,
9909
7978
  roleZod: () => defaultRoleZod(ATTACHMENT_ROLES),
9910
7979
  structureCode: "attachment_structure",
9911
- absenceRoles: ["file"],
7980
+ acceptsEmptyRow: true,
9912
7981
  collectionView: "attachmentList"
9913
7982
  });
9914
- /**
9915
- * An uploaded file and the label that names it as ONE value: `{ label, file }`, stored
9916
- * across `<key>__label` / `<key>__file`. A file field alone can only ever show what the
9917
- * uploader happened to call the file — `scan_20240817_final(2).pdf` — so the record page
9918
- * states what a document IS beside it instead of leaving the reader to open every one.
9919
- *
9920
- * `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
9921
- * `f.illustrated` and `f.period` do — a child table whose columns are the resolved parts.
9922
- * The `file` role accepts any of the file family and defaults to `f.file` (accept `*`); a
9923
- * shelf that wants the narrower document uploader fills it: `fields: { file: f.document({}) }`.
9924
- *
9925
- * @layer primitive
9926
- * @prim attachment
9927
- * @widget attachment
9928
- * @example f.attachment({ label: 'mod.fields.attachment' })
9929
- */
9930
7983
  function attachment(o) {
9931
7984
  return declare("attachment", o);
9932
7985
  }
9933
- /**
9934
- * 7 values, Mon–Sun. A thin wrapper over keyed({fixed}): a fixed collection,
9935
- * keyed by the weekday list, one real value per day. Its own kind 'per-weekday'
9936
- * dispatches a compact renderer. Storage: a grandchild table of {day, value} rows.
9937
- *
9938
- * @layer primitive
9939
- * @prim —
9940
- * @widget per-weekday
9941
- * @example f.perWeekday({ label: 'mod.fields.hours' })
9942
- */
9943
7986
  function perWeekday(o) {
9944
7987
  return {
9945
7988
  ...keyed({
@@ -9962,45 +8005,18 @@ function perWeekday(o) {
9962
8005
  kind: "per-weekday"
9963
8006
  };
9964
8007
  }
9965
- /**
9966
- * How a cross-field ordering check (a range's `from > to`, a period's) reads one endpoint
9967
- * once `stripServerOwnedParts` has removed the slots the schema owns. The check must judge
9968
- * the value the record will actually CARRY, never the client's echo of a server-owned slot:
9969
- * that echo is discarded on the same write, so letting it decide would reject legitimate
9970
- * writes over a value that never reaches storage, and would never see the value that does.
9971
- *
9972
- * - a CLIENT-owned (plain `stored`) endpoint → the parsed value, under its role name;
9973
- * - a COMPUTED-AND-STORED endpoint → undefined, so the pair is not compared at all: the
9974
- * server produces that value later in the write (a constant included — it is written by
9975
- * the same pass as a function) and nothing here can know it ahead of time.
9976
- *
9977
- * With every role left to its default filling every endpoint is client-owned, and this is
9978
- * exactly `row[role]` — the comparison, its code and its message are unchanged.
9979
- */
9980
8008
  function endpointReader(parts, keys) {
9981
8009
  return (role, stored) => stored?.[keys[role]];
9982
8010
  }
9983
- /** period's own granularity, defaulted like every other config-driven composite —
9984
- * read the same way by `roles`/`roleZod`/`hints`. */
9985
8011
  function periodGranularity(opts) {
9986
8012
  return opts.config?.granularity ?? "day";
9987
8013
  }
9988
- /** The endpoint factory at a given granularity — a ported date/datetime FieldDecl. It
9989
- * takes no key: an endpoint stores under its ROLE name (`from`/`until`), which
9990
- * `resolveParts` supplies, so there was never a second name to pass here. */
9991
8014
  function periodMkSubDecl(granularity) {
9992
8015
  return (label) => granularity === "datetime" ? datetime({ label }) : date({
9993
8016
  label,
9994
8017
  rules: { granularity: granularity === "month" ? "month" : "day" }
9995
8018
  });
9996
8019
  }
9997
- /**
9998
- * One endpoint role of a `period`: its default depends on THIS field's own granularity
9999
- * (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
10000
- * filling still keeps the composite's own granularity (`canonical.opts`) — the same
10001
- * `date`/`datetime` config the role's own default carries — with the author's
10002
- * label/required/ui/value layered on top, exactly like `retypeTo`'s default.
10003
- */
10004
8020
  function periodRole(key, label, mkSubDecl) {
10005
8021
  const def = {
10006
8022
  role: key,
@@ -10009,11 +8025,10 @@ function periodRole(key, label, mkSubDecl) {
10009
8025
  if (d && accepts(def, d)) return d;
10010
8026
  const canonical = mkSubDecl(label);
10011
8027
  if (!d) return canonical;
10012
- const { label: l, required, value } = d.opts;
8028
+ const { label: l, value } = d.opts;
10013
8029
  return declare(canonical.factory, {
10014
8030
  ...canonical.opts,
10015
8031
  label: l ?? canonical.opts.label,
10016
- required,
10017
8032
  value,
10018
8033
  noSearch: d.opts.noSearch,
10019
8034
  ui: uiOf(d.opts)
@@ -10022,15 +8037,10 @@ function periodRole(key, label, mkSubDecl) {
10022
8037
  };
10023
8038
  return def;
10024
8039
  }
10025
- /** Roles of a single `period`: the two date/datetime endpoints, at the type's granularity. */
10026
8040
  function periodRoles(opts) {
10027
8041
  const mkSubDecl = periodMkSubDecl(periodGranularity(opts));
10028
8042
  return [periodRole("from", "core.period.from", mkSubDecl), periodRole("until", "core.period.until", mkSubDecl)];
10029
8043
  }
10030
- /** period's per-role default zod: each role unconditionally `.optional()`, at the type's
10031
- * own granularity (`periodMkSubDecl`) — the schema a role falls back to when the author
10032
- * did not override it (`partsRowShape`'s own rule: an overridden part is validated by its
10033
- * OWN zod instead, never this default). */
10034
8044
  function periodRoleZod(opts) {
10035
8045
  const zod = materialize(periodMkSubDecl(periodGranularity(opts))("")).zod.optional();
10036
8046
  return {
@@ -10038,9 +8048,6 @@ function periodRoleZod(opts) {
10038
8048
  until: zod
10039
8049
  };
10040
8050
  }
10041
- /** period's own ordering check: `from > until` is rejected — the same `endpointReader`
10042
- * `rangeRefine` uses, comparing the ISO date/datetime strings directly (they sort
10043
- * lexicographically) instead of `rangeRefine`'s numeric endpoints. */
10044
8051
  function periodRefine(stored, parts) {
10045
8052
  const endpoint = endpointReader(parts, partValueKeys(parts));
10046
8053
  const f = endpoint("from", stored);
@@ -10065,39 +8072,20 @@ registerType("period", {
10065
8072
  granularity: periodGranularity(opts)
10066
8073
  })
10067
8074
  });
10068
- /**
10069
- * Date/datetime period composite {from, until}; `multiple` → an array of periods stored
10070
- * as a child table.
10071
- *
10072
- * @layer primitive
10073
- * @prim period
10074
- * @widget period
10075
- * @example f.period({ label: 'mod.fields.employment' })
10076
- */
10077
8075
  function period(raw) {
10078
8076
  return declare("period", raw);
10079
8077
  }
10080
- /** Keys a file entry may carry. Everything else is rejected — see fileEntryIssue. */
10081
8078
  var FILE_KEYS = /* @__PURE__ */ new Set([
10082
8079
  "name",
10083
8080
  "mime",
10084
8081
  "size"
10085
8082
  ]);
10086
- /** Keys that mean "the author pasted a remote address" — the one mistake worth naming. */
10087
8083
  var FILE_URL_KEYS = [
10088
8084
  "url",
10089
8085
  "src",
10090
8086
  "href",
10091
8087
  "link"
10092
8088
  ];
10093
- /**
10094
- * A file entry points at a file already uploaded to the server: `{ name }`, where
10095
- * name is the bare filename returned by POST /api/upload. Anything else — a remote
10096
- * URL, an extra key, a path — is refused here, so a record can never hold a
10097
- * reference the server cannot serve. `mime`/`size` are accepted (legacy payloads
10098
- * and the web uploader send them) but the server overwrites them from disk.
10099
- * Returns a vmsg code, or null when the entry is well-formed.
10100
- */
10101
8089
  function fileEntryIssue(it) {
10102
8090
  if (typeof it !== "object" || it === null || Array.isArray(it)) return "file_structure";
10103
8091
  const rec = it;
@@ -10111,11 +8099,6 @@ function fileEntryIssue(it) {
10111
8099
  if (rec["size"] !== void 0 && typeof rec["size"] !== "number") return "file_structure";
10112
8100
  return null;
10113
8101
  }
10114
- /**
10115
- * Shared by the whole file family's build(): a scalar {name,...} OR an array
10116
- * [{name,...},...], native or JSON-string, stored as JSON regardless of `multiple` (the
10117
- * renderer's gallery mode draws the single/many difference, not zod).
10118
- */
10119
8102
  function fileZod() {
10120
8103
  return unknown().superRefine((raw, ctx) => {
10121
8104
  if (typeof raw === "string") try {
@@ -10141,10 +8124,6 @@ function fileZod() {
10141
8124
  }
10142
8125
  });
10143
8126
  }
10144
- /**
10145
- * Registers one file-family TypeDef: same zod/column/json for all of them, `kind` (and
10146
- * `widget`, always identical to `kind` in this family) is the only thing that varies.
10147
- */
10148
8127
  function registerFileType(kind) {
10149
8128
  registerType(kind, {
10150
8129
  kind,
@@ -10162,123 +8141,41 @@ function registerFileType(kind) {
10162
8141
  });
10163
8142
  }
10164
8143
  registerFileType("file");
10165
- /**
10166
- * Generic uploaded file reference `{ name }`, pointing at a file already on the server.
10167
- *
10168
- * @layer primitive
10169
- * @prim file
10170
- * @widget file
10171
- * @example f.file({ label: 'mod.fields.attachment' })
10172
- */
10173
8144
  function file(o) {
10174
8145
  return declare("file", o);
10175
8146
  }
10176
8147
  registerFileType("document");
10177
- /**
10178
- * Uploaded document reference `{ name }`, rendered as a document widget.
10179
- *
10180
- * @layer primitive
10181
- * @prim file
10182
- * @widget document
10183
- * @example f.document({ label: 'mod.fields.doc' })
10184
- */
10185
8148
  function document(o) {
10186
8149
  return declare("document", o);
10187
8150
  }
10188
8151
  registerFileType("audio");
10189
- /**
10190
- * Uploaded audio file reference `{ name }`, rendered as an audio player.
10191
- *
10192
- * @layer primitive
10193
- * @prim file
10194
- * @widget audio
10195
- * @example f.audio({ label: 'mod.fields.recording' })
10196
- */
10197
8152
  function audio(o) {
10198
8153
  return declare("audio", o);
10199
8154
  }
10200
8155
  registerFileType("video");
10201
- /**
10202
- * Uploaded video file reference `{ name }`, rendered as a video player.
10203
- *
10204
- * @layer primitive
10205
- * @prim file
10206
- * @widget video
10207
- * @example f.video({ label: 'mod.fields.clip' })
10208
- */
10209
8156
  function video(o) {
10210
8157
  return declare("video", o);
10211
8158
  }
10212
8159
  registerFileType("image");
10213
- /**
10214
- * Uploaded image reference `{ name }`, rendered as an inline image.
10215
- *
10216
- * @layer primitive
10217
- * @prim file
10218
- * @widget image
10219
- * @example f.image({ label: 'mod.fields.photo' })
10220
- */
10221
8160
  function image(o) {
10222
8161
  return declare("image", o);
10223
8162
  }
10224
8163
  registerFileType("media");
10225
- /**
10226
- * Uploaded media reference `{ name }`, rendered as a generic media widget (mixed
10227
- * image/video).
10228
- *
10229
- * @layer primitive
10230
- * @prim file
10231
- * @widget media
10232
- * @example f.media({ label: 'mod.fields.attachment' })
10233
- */
10234
8164
  function media(o) {
10235
8165
  return declare("media", o);
10236
8166
  }
10237
8167
  registerFileType("avatar");
10238
- /**
10239
- * Uploaded image reference `{ name }`, rendered as a circular avatar.
10240
- *
10241
- * @layer primitive
10242
- * @prim file
10243
- * @widget avatar
10244
- * @example f.avatar({ label: 'mod.fields.avatar' })
10245
- */
10246
8168
  function avatar(o) {
10247
8169
  return declare("avatar", o);
10248
8170
  }
10249
8171
  registerFileType("cover");
10250
- /**
10251
- * Uploaded image reference `{ name }`, rendered as a wide cover image.
10252
- *
10253
- * @layer primitive
10254
- * @prim file
10255
- * @widget cover
10256
- * @example f.cover({ label: 'mod.fields.cover' })
10257
- */
10258
8172
  function cover(o) {
10259
8173
  return declare("cover", o);
10260
8174
  }
10261
8175
  registerFileType("poster");
10262
- /**
10263
- * Like `image` on the backend, but rendered large (portrait 2:3 poster).
10264
- *
10265
- * @layer primitive
10266
- * @prim file
10267
- * @widget poster
10268
- * @example f.poster({ label: 'mod.fields.poster' })
10269
- */
10270
8176
  function poster(o) {
10271
8177
  return declare("poster", o);
10272
8178
  }
10273
- /**
10274
- * Sugar over `keyed`: a collection table of {key, value} rows, `key` unique within a
10275
- * record; the value cell's type is set via `fields.value` (default: plain string).
10276
- *
10277
- * @layer primitive
10278
- * @prim —
10279
- * @widget group
10280
- * @example f.keyValue({ label: 'mod.fields.secrets' })
10281
- */
10282
8179
  function keyValue(raw) {
10283
8180
  const { fields, ...rest } = raw;
10284
8181
  const o = normalizeOpts(rest);
@@ -10297,13 +8194,10 @@ function keyValue(raw) {
10297
8194
  type: valueType
10298
8195
  }
10299
8196
  },
10300
- by: "key",
10301
- required: o.required
8197
+ by: "key"
10302
8198
  });
10303
8199
  }
10304
- /** Roles of a range: the two endpoints, integer or real depending on `isInt`. */
10305
8200
  var RANGE_ROLES = (isInt) => [role("from", ["number"], isInt ? "int" : "real"), role("to", ["number"], isInt ? "int" : "real")];
10306
- /** Shared by numberRange/realRange's `roleZod`: the endpoint schema, int or real per `isInt`. */
10307
8201
  function rangeEndpointZod(isInt, opts) {
10308
8202
  const cfg = opts.config ?? {};
10309
8203
  let n = number$1().finite();
@@ -10312,11 +8206,6 @@ function rangeEndpointZod(isInt, opts) {
10312
8206
  if (cfg.max != null) n = n.max(cfg.max);
10313
8207
  return n;
10314
8208
  }
10315
- /** Shared by numberRange/realRange's `refine`: `from > to` is rejected. Strip FIRST, then
10316
- * compare — the check must judge the value the record will actually carry, never the
10317
- * client's echo of an endpoint the SCHEMA owns (buildComposite strips it before calling
10318
- * this). Each endpoint is read under the key it occupies in the value object — its role
10319
- * name, always, now. */
10320
8209
  function rangeRefine(stored, parts) {
10321
8210
  const endpoint = endpointReader(parts, partValueKeys(parts));
10322
8211
  const fromV = endpoint("from", stored);
@@ -10348,25 +8237,9 @@ function registerRangeType(kind, isInt) {
10348
8237
  }
10349
8238
  registerRangeType("numberRange", true);
10350
8239
  registerRangeType("realRange", false);
10351
- /**
10352
- * Integer range composite {from, to}; `from > to` is rejected.
10353
- *
10354
- * @layer primitive
10355
- * @prim numberRange
10356
- * @widget numberRange
10357
- * @example f.numberRange({ label: 'mod.fields.ageRange' })
10358
- */
10359
8240
  function numberRange(o) {
10360
8241
  return declare("numberRange", o);
10361
8242
  }
10362
- /**
10363
- * Real-number range composite {from, to}; `from > to` is rejected.
10364
- *
10365
- * @layer primitive
10366
- * @prim realRange
10367
- * @widget realRange
10368
- * @example f.realRange({ label: 'mod.fields.priceRange' })
10369
- */
10370
8243
  function realRange(o) {
10371
8244
  return declare("realRange", o);
10372
8245
  }
@@ -10401,23 +8274,9 @@ registerType("embed", {
10401
8274
  };
10402
8275
  }
10403
8276
  });
10404
- /**
10405
- * External content snapshot by URL — oEmbed/OpenGraph metadata (title, thumbnail,
10406
- * provider, …) captured at save time via /api/embed.
10407
- *
10408
- * @layer primitive
10409
- * @prim embed
10410
- * @widget embed
10411
- * @example f.embed({ label: 'mod.fields.link' })
10412
- */
10413
8277
  function embed(o) {
10414
8278
  return declare("embed", o);
10415
8279
  }
10416
- /**
10417
- * Base primitives (storage-aligned) + structural types. Presets (thin wrappers
10418
- * over these primitives) are added below from field-presets.ts and CANNOT override
10419
- * any key from here (guard in composeF).
10420
- */
10421
8280
  var PRIMITIVES = {
10422
8281
  string,
10423
8282
  text,
@@ -10467,7 +8326,6 @@ var PRIMITIVES = {
10467
8326
  button,
10468
8327
  backrefs
10469
8328
  };
10470
- /** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
10471
8329
  function composeF(presets, blocks) {
10472
8330
  for (const k of Object.keys(presets)) if (k in PRIMITIVES) throw new Error(`[f] preset '${k}' overrides a primitive`);
10473
8331
  for (const k of Object.keys(blocks)) if (k in PRIMITIVES) throw new Error(`[f] block '${k}' overrides a primitive`);
@@ -10485,12 +8343,11 @@ var field = new Proxy({}, {
10485
8343
  has: (_t, k) => k in composedField()
10486
8344
  });
10487
8345
  function toClient(field) {
10488
- const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
8346
+ const { kind, label, agent, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
10489
8347
  return {
10490
8348
  kind,
10491
8349
  label,
10492
8350
  ...agent !== void 0 && { agent },
10493
- required,
10494
8351
  prim,
10495
8352
  hints,
10496
8353
  options,
@@ -10509,688 +8366,12 @@ function toClient(field) {
10509
8366
  };
10510
8367
  }
10511
8368
  //#endregion
10512
- //#region ../sdk/src/settings.ts
8369
+ //#region ../sdk/dist/settings.js
10513
8370
  function defineSettings(s) {
10514
8371
  return materializeDef(s);
10515
8372
  }
10516
8373
  //#endregion
10517
- //#region ../../node_modules/sift/es5m/index.js
10518
- /******************************************************************************
10519
- Copyright (c) Microsoft Corporation.
10520
-
10521
- Permission to use, copy, modify, and/or distribute this software for any
10522
- purpose with or without fee is hereby granted.
10523
-
10524
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10525
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10526
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10527
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10528
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
10529
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
10530
- PERFORMANCE OF THIS SOFTWARE.
10531
- ***************************************************************************** */
10532
- var extendStatics = function(d, b) {
10533
- extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d, b) {
10534
- d.__proto__ = b;
10535
- } || function(d, b) {
10536
- for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p];
10537
- };
10538
- return extendStatics(d, b);
10539
- };
10540
- function __extends(d, b) {
10541
- if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
10542
- extendStatics(d, b);
10543
- function __() {
10544
- this.constructor = d;
10545
- }
10546
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
10547
- }
10548
- var typeChecker = function(type) {
10549
- var typeString = "[object " + type + "]";
10550
- return function(value) {
10551
- return getClassName(value) === typeString;
10552
- };
10553
- };
10554
- var getClassName = function(value) {
10555
- return Object.prototype.toString.call(value);
10556
- };
10557
- var comparable = function(value) {
10558
- if (value instanceof Date) return value.getTime();
10559
- else if (isArray(value)) return value.map(comparable);
10560
- else if (value && typeof value.toJSON === "function") return value.toJSON();
10561
- return value;
10562
- };
10563
- var coercePotentiallyNull = function(value) {
10564
- return value == null ? null : value;
10565
- };
10566
- var isArray = typeChecker("Array");
10567
- var isObject = typeChecker("Object");
10568
- var isFunction = typeChecker("Function");
10569
- var isProperty = function(item, key) {
10570
- return item.hasOwnProperty(key) && !isFunction(item[key]);
10571
- };
10572
- var isVanillaObject = function(value) {
10573
- return value && (value.constructor === Object || value.constructor === Array || value.constructor.toString() === "function Object() { [native code] }" || value.constructor.toString() === "function Array() { [native code] }") && !value.toJSON;
10574
- };
10575
- var equals = function(a, b) {
10576
- if (a == null && a == b) return true;
10577
- if (a === b) return true;
10578
- if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) return false;
10579
- if (isArray(a)) {
10580
- if (a.length !== b.length) return false;
10581
- for (var i = 0, length_1 = a.length; i < length_1; i++) if (!equals(a[i], b[i])) return false;
10582
- return true;
10583
- } else if (isObject(a)) {
10584
- if (Object.keys(a).length !== Object.keys(b).length) return false;
10585
- for (var key in a) if (!equals(a[key], b[key])) return false;
10586
- return true;
10587
- }
10588
- return false;
10589
- };
10590
- /**
10591
- * Walks through each value given the context - used for nested operations. E.g:
10592
- * { "person.address": { $eq: "blarg" }}
10593
- */
10594
- var walkKeyPathValues = function(item, keyPath, next, depth, key, owner) {
10595
- var currentKey = keyPath[depth];
10596
- if (isArray(item) && isNaN(Number(currentKey)) && !isProperty(item, currentKey)) {
10597
- for (var i = 0, length_1 = item.length; i < length_1; i++) if (!walkKeyPathValues(item[i], keyPath, next, depth, i, item)) return false;
10598
- }
10599
- if (depth === keyPath.length || item == null) return next(item, key, owner, depth === 0, depth === keyPath.length);
10600
- return walkKeyPathValues(item[currentKey], keyPath, next, depth + 1, currentKey, item);
10601
- };
10602
- var BaseOperation = function() {
10603
- function BaseOperation(params, owneryQuery, options, name) {
10604
- this.params = params;
10605
- this.owneryQuery = owneryQuery;
10606
- this.options = options;
10607
- this.name = name;
10608
- this.init();
10609
- }
10610
- BaseOperation.prototype.init = function() {};
10611
- BaseOperation.prototype.reset = function() {
10612
- this.done = false;
10613
- this.keep = false;
10614
- };
10615
- return BaseOperation;
10616
- }();
10617
- var GroupOperation = function(_super) {
10618
- __extends(GroupOperation, _super);
10619
- function GroupOperation(params, owneryQuery, options, children) {
10620
- var _this = _super.call(this, params, owneryQuery, options) || this;
10621
- _this.children = children;
10622
- return _this;
10623
- }
10624
- /**
10625
- */
10626
- GroupOperation.prototype.reset = function() {
10627
- this.keep = false;
10628
- this.done = false;
10629
- for (var i = 0, length_2 = this.children.length; i < length_2; i++) this.children[i].reset();
10630
- };
10631
- /**
10632
- */
10633
- GroupOperation.prototype.childrenNext = function(item, key, owner, root, leaf) {
10634
- var done = true;
10635
- var keep = true;
10636
- for (var i = 0, length_3 = this.children.length; i < length_3; i++) {
10637
- var childOperation = this.children[i];
10638
- if (!childOperation.done) childOperation.next(item, key, owner, root, leaf);
10639
- if (!childOperation.keep) keep = false;
10640
- if (childOperation.done) {
10641
- if (!childOperation.keep) break;
10642
- } else done = false;
10643
- }
10644
- this.done = done;
10645
- this.keep = keep;
10646
- };
10647
- return GroupOperation;
10648
- }(BaseOperation);
10649
- var NamedGroupOperation = function(_super) {
10650
- __extends(NamedGroupOperation, _super);
10651
- function NamedGroupOperation(params, owneryQuery, options, children, name) {
10652
- var _this = _super.call(this, params, owneryQuery, options, children) || this;
10653
- _this.name = name;
10654
- return _this;
10655
- }
10656
- return NamedGroupOperation;
10657
- }(GroupOperation);
10658
- var QueryOperation = function(_super) {
10659
- __extends(QueryOperation, _super);
10660
- function QueryOperation() {
10661
- var _this = _super !== null && _super.apply(this, arguments) || this;
10662
- _this.propop = true;
10663
- return _this;
10664
- }
10665
- /**
10666
- */
10667
- QueryOperation.prototype.next = function(item, key, parent, root) {
10668
- this.childrenNext(item, key, parent, root);
10669
- };
10670
- return QueryOperation;
10671
- }(GroupOperation);
10672
- var NestedOperation = function(_super) {
10673
- __extends(NestedOperation, _super);
10674
- function NestedOperation(keyPath, params, owneryQuery, options, children) {
10675
- var _this = _super.call(this, params, owneryQuery, options, children) || this;
10676
- _this.keyPath = keyPath;
10677
- _this.propop = true;
10678
- /**
10679
- */
10680
- _this._nextNestedValue = function(value, key, owner, root, leaf) {
10681
- _this.childrenNext(value, key, owner, root, leaf);
10682
- return !_this.done;
10683
- };
10684
- return _this;
10685
- }
10686
- /**
10687
- */
10688
- NestedOperation.prototype.next = function(item, key, parent) {
10689
- walkKeyPathValues(item, this.keyPath, this._nextNestedValue, 0, key, parent);
10690
- };
10691
- return NestedOperation;
10692
- }(GroupOperation);
10693
- var createTester = function(a, compare) {
10694
- if (a instanceof Function) return a;
10695
- if (a instanceof RegExp) return function(b) {
10696
- var result = typeof b === "string" && a.test(b);
10697
- a.lastIndex = 0;
10698
- return result;
10699
- };
10700
- var comparableA = comparable(a);
10701
- return function(b) {
10702
- return compare(comparableA, comparable(b));
10703
- };
10704
- };
10705
- var EqualsOperation = function(_super) {
10706
- __extends(EqualsOperation, _super);
10707
- function EqualsOperation() {
10708
- var _this = _super !== null && _super.apply(this, arguments) || this;
10709
- _this.propop = true;
10710
- return _this;
10711
- }
10712
- EqualsOperation.prototype.init = function() {
10713
- this._test = createTester(this.params, this.options.compare);
10714
- };
10715
- EqualsOperation.prototype.next = function(item, key, parent) {
10716
- if (!Array.isArray(parent) || parent.hasOwnProperty(key)) {
10717
- if (this._test(item, key, parent)) {
10718
- this.done = true;
10719
- this.keep = true;
10720
- }
10721
- }
10722
- };
10723
- return EqualsOperation;
10724
- }(BaseOperation);
10725
- var numericalOperationCreator = function(createNumericalOperation) {
10726
- return function(params, owneryQuery, options, name) {
10727
- return createNumericalOperation(params, owneryQuery, options, name);
10728
- };
10729
- };
10730
- var numericalOperation = function(createTester) {
10731
- return numericalOperationCreator(function(params, owneryQuery, options, name) {
10732
- var typeofParams = typeof comparable(params);
10733
- var test = createTester(params);
10734
- return new EqualsOperation(function(b) {
10735
- var actualValue = coercePotentiallyNull(b);
10736
- return typeof comparable(actualValue) === typeofParams && test(actualValue);
10737
- }, owneryQuery, options, name);
10738
- });
10739
- };
10740
- var createNamedOperation = function(name, params, parentQuery, options) {
10741
- var operationCreator = options.operations[name];
10742
- if (!operationCreator) throwUnsupportedOperation(name);
10743
- return operationCreator(params, parentQuery, options, name);
10744
- };
10745
- var throwUnsupportedOperation = function(name) {
10746
- throw new Error("Unsupported operation: ".concat(name));
10747
- };
10748
- var containsOperation = function(query, options) {
10749
- for (var key in query) if (options.operations.hasOwnProperty(key) || key.charAt(0) === "$") return true;
10750
- return false;
10751
- };
10752
- var createNestedOperation = function(keyPath, nestedQuery, parentKey, owneryQuery, options) {
10753
- if (containsOperation(nestedQuery, options)) {
10754
- var _a = createQueryOperations(nestedQuery, parentKey, options), selfOperations = _a[0];
10755
- if (_a[1].length) throw new Error("Property queries must contain only operations, or exact objects.");
10756
- return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, selfOperations);
10757
- }
10758
- return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, [new EqualsOperation(nestedQuery, owneryQuery, options)]);
10759
- };
10760
- var createQueryOperation = function(query, owneryQuery, _a) {
10761
- if (owneryQuery === void 0) owneryQuery = null;
10762
- var _b = _a === void 0 ? {} : _a, compare = _b.compare, operations = _b.operations;
10763
- var options = {
10764
- compare: compare || equals,
10765
- operations: Object.assign({}, operations || {})
10766
- };
10767
- var _c = createQueryOperations(query, null, options), selfOperations = _c[0], nestedOperations = _c[1];
10768
- var ops = [];
10769
- if (selfOperations.length) ops.push(new NestedOperation([], query, owneryQuery, options, selfOperations));
10770
- ops.push.apply(ops, nestedOperations);
10771
- if (ops.length === 1) return ops[0];
10772
- return new QueryOperation(query, owneryQuery, options, ops);
10773
- };
10774
- var createQueryOperations = function(query, parentKey, options) {
10775
- var selfOperations = [];
10776
- var nestedOperations = [];
10777
- if (!isVanillaObject(query)) {
10778
- selfOperations.push(new EqualsOperation(query, query, options));
10779
- return [selfOperations, nestedOperations];
10780
- }
10781
- for (var key in query) if (options.operations.hasOwnProperty(key)) {
10782
- var op = createNamedOperation(key, query[key], query, options);
10783
- if (op) {
10784
- if (!op.propop && parentKey && !options.operations[parentKey]) throw new Error("Malformed query. ".concat(key, " cannot be matched against property."));
10785
- }
10786
- if (op != null) selfOperations.push(op);
10787
- } else if (key.charAt(0) === "$") throwUnsupportedOperation(key);
10788
- else nestedOperations.push(createNestedOperation(key.split("."), query[key], key, query, options));
10789
- return [selfOperations, nestedOperations];
10790
- };
10791
- var createOperationTester = function(operation) {
10792
- return function(item, key, owner) {
10793
- operation.reset();
10794
- operation.next(item, key, owner);
10795
- return operation.keep;
10796
- };
10797
- };
10798
- var $Ne = function(_super) {
10799
- __extends($Ne, _super);
10800
- function $Ne() {
10801
- var _this = _super !== null && _super.apply(this, arguments) || this;
10802
- _this.propop = true;
10803
- return _this;
10804
- }
10805
- $Ne.prototype.init = function() {
10806
- this._test = createTester(this.params, this.options.compare);
10807
- };
10808
- $Ne.prototype.reset = function() {
10809
- _super.prototype.reset.call(this);
10810
- this.keep = true;
10811
- };
10812
- $Ne.prototype.next = function(item) {
10813
- if (this._test(item)) {
10814
- this.done = true;
10815
- this.keep = false;
10816
- }
10817
- };
10818
- return $Ne;
10819
- }(BaseOperation);
10820
- var $ElemMatch = function(_super) {
10821
- __extends($ElemMatch, _super);
10822
- function $ElemMatch() {
10823
- var _this = _super !== null && _super.apply(this, arguments) || this;
10824
- _this.propop = true;
10825
- return _this;
10826
- }
10827
- $ElemMatch.prototype.init = function() {
10828
- if (!this.params || typeof this.params !== "object") throw new Error("Malformed query. $elemMatch must by an object.");
10829
- this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
10830
- };
10831
- $ElemMatch.prototype.reset = function() {
10832
- _super.prototype.reset.call(this);
10833
- this._queryOperation.reset();
10834
- };
10835
- $ElemMatch.prototype.next = function(item) {
10836
- if (isArray(item)) {
10837
- for (var i = 0, length_1 = item.length; i < length_1; i++) {
10838
- this._queryOperation.reset();
10839
- var child = item[i];
10840
- this._queryOperation.next(child, i, item, false);
10841
- this.keep = this.keep || this._queryOperation.keep;
10842
- }
10843
- this.done = true;
10844
- } else {
10845
- this.done = false;
10846
- this.keep = false;
10847
- }
10848
- };
10849
- return $ElemMatch;
10850
- }(BaseOperation);
10851
- var $Not = function(_super) {
10852
- __extends($Not, _super);
10853
- function $Not() {
10854
- var _this = _super !== null && _super.apply(this, arguments) || this;
10855
- _this.propop = true;
10856
- return _this;
10857
- }
10858
- $Not.prototype.init = function() {
10859
- this._queryOperation = createQueryOperation(this.params, this.owneryQuery, this.options);
10860
- };
10861
- $Not.prototype.reset = function() {
10862
- _super.prototype.reset.call(this);
10863
- this._queryOperation.reset();
10864
- };
10865
- $Not.prototype.next = function(item, key, owner, root) {
10866
- this._queryOperation.next(item, key, owner, root);
10867
- this.done = this._queryOperation.done;
10868
- this.keep = !this._queryOperation.keep;
10869
- };
10870
- return $Not;
10871
- }(BaseOperation);
10872
- var $Size = function(_super) {
10873
- __extends($Size, _super);
10874
- function $Size() {
10875
- var _this = _super !== null && _super.apply(this, arguments) || this;
10876
- _this.propop = true;
10877
- return _this;
10878
- }
10879
- $Size.prototype.init = function() {};
10880
- $Size.prototype.next = function(item) {
10881
- if (isArray(item) && item.length === this.params) {
10882
- this.done = true;
10883
- this.keep = true;
10884
- }
10885
- };
10886
- return $Size;
10887
- }(BaseOperation);
10888
- var assertGroupNotEmpty = function(values) {
10889
- if (values.length === 0) throw new Error("$and/$or/$nor must be a nonempty array");
10890
- };
10891
- var $Or = function(_super) {
10892
- __extends($Or, _super);
10893
- function $Or() {
10894
- var _this = _super !== null && _super.apply(this, arguments) || this;
10895
- _this.propop = false;
10896
- return _this;
10897
- }
10898
- $Or.prototype.init = function() {
10899
- var _this = this;
10900
- assertGroupNotEmpty(this.params);
10901
- this._ops = this.params.map(function(op) {
10902
- return createQueryOperation(op, null, _this.options);
10903
- });
10904
- };
10905
- $Or.prototype.reset = function() {
10906
- this.done = false;
10907
- this.keep = false;
10908
- for (var i = 0, length_2 = this._ops.length; i < length_2; i++) this._ops[i].reset();
10909
- };
10910
- $Or.prototype.next = function(item, key, owner) {
10911
- var done = false;
10912
- var success = false;
10913
- for (var i = 0, length_3 = this._ops.length; i < length_3; i++) {
10914
- var op = this._ops[i];
10915
- op.next(item, key, owner);
10916
- if (op.keep) {
10917
- done = true;
10918
- success = op.keep;
10919
- break;
10920
- }
10921
- }
10922
- this.keep = success;
10923
- this.done = done;
10924
- };
10925
- return $Or;
10926
- }(BaseOperation);
10927
- var $Nor = function(_super) {
10928
- __extends($Nor, _super);
10929
- function $Nor() {
10930
- var _this = _super !== null && _super.apply(this, arguments) || this;
10931
- _this.propop = false;
10932
- return _this;
10933
- }
10934
- $Nor.prototype.next = function(item, key, owner) {
10935
- _super.prototype.next.call(this, item, key, owner);
10936
- this.keep = !this.keep;
10937
- };
10938
- return $Nor;
10939
- }($Or);
10940
- var $In = function(_super) {
10941
- __extends($In, _super);
10942
- function $In() {
10943
- var _this = _super !== null && _super.apply(this, arguments) || this;
10944
- _this.propop = true;
10945
- return _this;
10946
- }
10947
- $In.prototype.init = function() {
10948
- var _this = this;
10949
- var params = Array.isArray(this.params) ? this.params : [this.params];
10950
- this._testers = params.map(function(value) {
10951
- if (containsOperation(value, _this.options)) throw new Error("cannot nest $ under ".concat(_this.name.toLowerCase()));
10952
- return createTester(value, _this.options.compare);
10953
- });
10954
- };
10955
- $In.prototype.next = function(item, key, owner) {
10956
- var done = false;
10957
- var success = false;
10958
- for (var i = 0, length_4 = this._testers.length; i < length_4; i++) {
10959
- var test = this._testers[i];
10960
- if (test(item)) {
10961
- done = true;
10962
- success = true;
10963
- break;
10964
- }
10965
- }
10966
- this.keep = success;
10967
- this.done = done;
10968
- };
10969
- return $In;
10970
- }(BaseOperation);
10971
- var $Nin = function(_super) {
10972
- __extends($Nin, _super);
10973
- function $Nin(params, ownerQuery, options, name) {
10974
- var _this = _super.call(this, params, ownerQuery, options, name) || this;
10975
- _this.propop = true;
10976
- _this._in = new $In(params, ownerQuery, options, name);
10977
- return _this;
10978
- }
10979
- $Nin.prototype.next = function(item, key, owner, root) {
10980
- this._in.next(item, key, owner);
10981
- if (isArray(owner) && !root) {
10982
- if (this._in.keep) {
10983
- this.keep = false;
10984
- this.done = true;
10985
- } else if (key == owner.length - 1) {
10986
- this.keep = true;
10987
- this.done = true;
10988
- }
10989
- } else {
10990
- this.keep = !this._in.keep;
10991
- this.done = true;
10992
- }
10993
- };
10994
- $Nin.prototype.reset = function() {
10995
- _super.prototype.reset.call(this);
10996
- this._in.reset();
10997
- };
10998
- return $Nin;
10999
- }(BaseOperation);
11000
- var $Exists = function(_super) {
11001
- __extends($Exists, _super);
11002
- function $Exists() {
11003
- var _this = _super !== null && _super.apply(this, arguments) || this;
11004
- _this.propop = true;
11005
- return _this;
11006
- }
11007
- $Exists.prototype.next = function(item, key, owner, root, leaf) {
11008
- if (!leaf) {
11009
- this.done = true;
11010
- this.keep = !this.params;
11011
- } else if (owner.hasOwnProperty(key) === this.params) {
11012
- this.done = true;
11013
- this.keep = true;
11014
- }
11015
- };
11016
- return $Exists;
11017
- }(BaseOperation);
11018
- var $And = function(_super) {
11019
- __extends($And, _super);
11020
- function $And(params, owneryQuery, options, name) {
11021
- var _this = _super.call(this, params, owneryQuery, options, params.map(function(query) {
11022
- return createQueryOperation(query, owneryQuery, options);
11023
- }), name) || this;
11024
- _this.propop = false;
11025
- assertGroupNotEmpty(params);
11026
- return _this;
11027
- }
11028
- $And.prototype.next = function(item, key, owner, root) {
11029
- this.childrenNext(item, key, owner, root);
11030
- };
11031
- return $And;
11032
- }(NamedGroupOperation);
11033
- var $All = function(_super) {
11034
- __extends($All, _super);
11035
- function $All(params, owneryQuery, options, name) {
11036
- var _this = _super.call(this, params, owneryQuery, options, params.map(function(query) {
11037
- return createQueryOperation(query, owneryQuery, options);
11038
- }), name) || this;
11039
- _this.propop = true;
11040
- return _this;
11041
- }
11042
- $All.prototype.next = function(item, key, owner, root) {
11043
- this.childrenNext(item, key, owner, root);
11044
- };
11045
- return $All;
11046
- }(NamedGroupOperation);
11047
- var $eq = function(params, owneryQuery, options) {
11048
- return new EqualsOperation(params, owneryQuery, options);
11049
- };
11050
- var $ne = function(params, owneryQuery, options, name) {
11051
- return new $Ne(params, owneryQuery, options, name);
11052
- };
11053
- var $or = function(params, owneryQuery, options, name) {
11054
- return new $Or(params, owneryQuery, options, name);
11055
- };
11056
- var $nor = function(params, owneryQuery, options, name) {
11057
- return new $Nor(params, owneryQuery, options, name);
11058
- };
11059
- var $elemMatch = function(params, owneryQuery, options, name) {
11060
- return new $ElemMatch(params, owneryQuery, options, name);
11061
- };
11062
- var $nin = function(params, owneryQuery, options, name) {
11063
- return new $Nin(params, owneryQuery, options, name);
11064
- };
11065
- var $in = function(params, owneryQuery, options, name) {
11066
- return new $In(params, owneryQuery, options, name);
11067
- };
11068
- var $lt = numericalOperation(function(params) {
11069
- return function(b) {
11070
- return b != null && b < params;
11071
- };
11072
- });
11073
- var $lte = numericalOperation(function(params) {
11074
- return function(b) {
11075
- return b === params || b <= params;
11076
- };
11077
- });
11078
- var $gt = numericalOperation(function(params) {
11079
- return function(b) {
11080
- return b != null && b > params;
11081
- };
11082
- });
11083
- var $gte = numericalOperation(function(params) {
11084
- return function(b) {
11085
- return b === params || b >= params;
11086
- };
11087
- });
11088
- var $mod = function(_a, owneryQuery, options) {
11089
- var mod = _a[0], equalsValue = _a[1];
11090
- return new EqualsOperation(function(b) {
11091
- return comparable(b) % mod === equalsValue;
11092
- }, owneryQuery, options);
11093
- };
11094
- var $exists = function(params, owneryQuery, options, name) {
11095
- return new $Exists(params, owneryQuery, options, name);
11096
- };
11097
- var $regex = function(pattern, owneryQuery, options) {
11098
- return new EqualsOperation(new RegExp(pattern, owneryQuery.$options), owneryQuery, options);
11099
- };
11100
- var $not = function(params, owneryQuery, options, name) {
11101
- return new $Not(params, owneryQuery, options, name);
11102
- };
11103
- var typeAliases = {
11104
- number: function(v) {
11105
- return typeof v === "number";
11106
- },
11107
- string: function(v) {
11108
- return typeof v === "string";
11109
- },
11110
- bool: function(v) {
11111
- return typeof v === "boolean";
11112
- },
11113
- array: function(v) {
11114
- return Array.isArray(v);
11115
- },
11116
- null: function(v) {
11117
- return v === null;
11118
- },
11119
- timestamp: function(v) {
11120
- return v instanceof Date;
11121
- }
11122
- };
11123
- var $type = function(clazz, owneryQuery, options) {
11124
- return new EqualsOperation(function(b) {
11125
- if (typeof clazz === "string") {
11126
- if (!typeAliases[clazz]) throw new Error("Type alias does not exist");
11127
- return typeAliases[clazz](b);
11128
- }
11129
- return b != null ? b instanceof clazz || b.constructor === clazz : false;
11130
- }, owneryQuery, options);
11131
- };
11132
- var $and = function(params, ownerQuery, options, name) {
11133
- return new $And(params, ownerQuery, options, name);
11134
- };
11135
- var $all = function(params, ownerQuery, options, name) {
11136
- return new $All(params, ownerQuery, options, name);
11137
- };
11138
- var $size = function(params, ownerQuery, options) {
11139
- return new $Size(params, ownerQuery, options, "$size");
11140
- };
11141
- var $options = function() {
11142
- return null;
11143
- };
11144
- var $where = function(params, ownerQuery, options) {
11145
- var test;
11146
- if (isFunction(params)) test = params;
11147
- else if (!process.env.CSP_ENABLED) test = new Function("obj", "return " + params);
11148
- else throw new Error("In CSP mode, sift does not support strings in \"$where\" condition");
11149
- return new EqualsOperation(function(b) {
11150
- return test.bind(b)(b);
11151
- }, ownerQuery, options);
11152
- };
11153
- var defaultOperations = /*#__PURE__*/ Object.freeze({
11154
- __proto__: null,
11155
- $Size,
11156
- $all,
11157
- $and,
11158
- $elemMatch,
11159
- $eq,
11160
- $exists,
11161
- $gt,
11162
- $gte,
11163
- $in,
11164
- $lt,
11165
- $lte,
11166
- $mod,
11167
- $ne,
11168
- $nin,
11169
- $nor,
11170
- $not,
11171
- $options,
11172
- $or,
11173
- $regex,
11174
- $size,
11175
- $type,
11176
- $where
11177
- });
11178
- var createDefaultQueryOperation = function(query, ownerQuery, _a) {
11179
- var _b = _a === void 0 ? {} : _a, compare = _b.compare, operations = _b.operations;
11180
- return createQueryOperation(query, ownerQuery, {
11181
- compare,
11182
- operations: Object.assign({}, defaultOperations, operations || {})
11183
- });
11184
- };
11185
- var createDefaultQueryTester = function(query, options) {
11186
- if (options === void 0) options = {};
11187
- return createOperationTester(createDefaultQueryOperation(query, null, options));
11188
- };
11189
- createDefaultQueryTester.createEqualsOperation;
11190
- //#endregion
11191
- //#region ../sdk/src/shelf.ts
11192
- /** Flat [storageKey, FieldMeta] pairs of the parent table's columns.
11193
- * layout group → flattened; embedded group → prefix `key__`; collection → skipped. */
8374
+ //#region ../sdk/dist/shelf.js
11194
8375
  function fieldEntries(items) {
11195
8376
  const out = [];
11196
8377
  for (const raw of items) {
@@ -11204,78 +8385,14 @@ function fieldEntries(items) {
11204
8385
  }
11205
8386
  return out;
11206
8387
  }
11207
- /**
11208
- * Rejects a keyless computed element (`field.x({ value: fn })`, no `key`) anywhere in
11209
- * the tree: at the shelf's own top level, inside a keyless layout group (`f.group`/
11210
- * `f.row`/`f.sheet` without a storage key), AND inside a keyed embedded group or
11211
- * collection row. A keyless computed node never carries a key of its own regardless of what it is
11212
- * nested in, so the enclosing group having a storage key does not give it one — there
11213
- * is still no mechanism that computes and returns such an element: `applyStoredParts`
11214
- * (`@coffer-org/server/part-injection`) only ever writes into a KEYED unit — a
11215
- * top-level field with `fm.compute`, or a composite part, and every composite part now
11216
- * always has a key too (its role name) — so a bare keyless value node has no column to
11217
- * land in either way. Left unrejected, a keyless computed element nested inside a
11218
- * keyed group would silently render blank forever instead of failing loudly at
11219
- * `defineShelf` time — recurse into every group, not only keyless ones.
11220
- */
11221
8388
  function assertNoKeylessCompute(items, owner) {
11222
8389
  for (const it of items) {
11223
8390
  if ("value" in it && typeof it.value === "function") throw new Error(`${owner}: computed element needs a key — the server computes it and needs a name to return it under`);
11224
8391
  if (hasChildren(it)) assertNoKeylessCompute(it.fields, owner);
11225
8392
  }
11226
8393
  }
11227
- /**
11228
- * Warns (does not throw) about the ONE composite shape mixed ownership is still wrong for: a
11229
- * MANDATED client-owned stored part beside a server-owned one — one computed-and-stored (the
11230
- * server writes that column) next to a plain stored one (the client writes that one) that the
11231
- * declaration demands a value for.
11232
- *
11233
- * Mixed ownership itself is supported: the write path MATERIALIZES such a composite and
11234
- * computes the server's part even when the write does not carry the field, leaving the
11235
- * client's columns as they were (THE ABSENT-COMPOSITE RULE in
11236
- * `@coffer-org/server/part-injection`). What a MANDATE adds is a demand the materialized value
11237
- * cannot honour: the half-value the server produces on its own — the computed column filled,
11238
- * the client's empty — reads back as `{value: 5, unit: null}` and is rejected by the
11239
- * composite's own zod, yet it is non-null enough to satisfy the required check on the next
11240
- * PATCH. The write path therefore refuses to materialize that one combination, and this names
11241
- * it rather than leaving the author to wonder why the computed part never fills.
11242
- *
11243
- * The mandate is read through `mandatedClientParts`, which counts the FIELD's `required` AND
11244
- * each OVERRIDDEN FILLING's own — `partsRowShape` honours both, so a check that read only the
11245
- * field let `fields: { unit: f.string({ required: true }) }` through silently.
11246
- *
11247
- * Recurses into every group — an embedded group's composite and a collection row's carry
11248
- * exactly the same mandate as a top-level one.
11249
- */
11250
- function warnMixedOwnedParts(items, owner, path = "") {
11251
- for (const it of items) {
11252
- if (hasChildren(it)) {
11253
- warnMixedOwnedParts(it.fields, owner, isStorageGroup(it) ? `${path}${it.key}.` : path);
11254
- continue;
11255
- }
11256
- if (!(hasValue(it) && it.key !== void 0) || !it.type.parts) continue;
11257
- const mixed = mixedOwnedStoredParts(it.type.parts);
11258
- if (!mixed) continue;
11259
- const mandated = mandatedClientParts(it.type.required, it.type.parts);
11260
- if (!mandated.length) continue;
11261
- console.warn(`${owner}: composite '${path}${it.key}' mandates a client-owned stored part (${mandated.join(", ")}) and mixes it with a server-owned stored part (${mixed.server.join(", ")}) — a mandated part is the client's to declare, so the write path does NOT materialize this composite and the computed part stays absent until a client sends the field. Either drop \`required\` (from the field or from the ${mandated.join("/")} filling), or give every stored part a \`value\` (the composite becomes fully server-owned).`);
11262
- }
11263
- }
11264
- /**
11265
- * The declaration-time checks that are about a FIELD LIST, not about a shelf: they read
11266
- * `fields` and nothing else, so they are equally true of an extend's fields — an extend
11267
- * stores composites in its own table through the very same write path (`upsertExtendRecord`
11268
- * → `applyStoredParts`) and renders them through the same layout walk.
11269
- *
11270
- * They live in ONE function called by both `defineShelf` and `defineExtend` rather than
11271
- * being copied: `defineExtend` had NEITHER, so a keyless computed element on an extend
11272
- * rendered blank forever instead of throwing, and a mixed-ownership composite on an extend
11273
- * warned about nothing at all. `owner` is the prefix each diagnostic is tagged with
11274
- * (`[shelf] things/item`, `[extend] device_info`), the one thing the two callers differ in.
11275
- */
11276
8394
  function checkFieldDeclaration(fields, owner) {
11277
8395
  assertNoKeylessCompute(fields, owner);
11278
- warnMixedOwnedParts(fields, owner);
11279
8396
  }
11280
8397
  function defineShelf(m) {
11281
8398
  const built = materializeDef(m);
@@ -11293,32 +8410,12 @@ function defineShelf(m) {
11293
8410
  checkFieldDeclaration(built.fields, `[shelf] ${built.library}/${built.shelf}`);
11294
8411
  return built;
11295
8412
  }
11296
- /**
11297
- * Contract of a real entity:
11298
- * id — uuid (the ONLY required system field, auto).
11299
- * The rest (including name) are regular fields in ShelfDef.fields.
11300
- * created_at/updated_at — system infrastructure (outside fields, auto-managed).
11301
- */
11302
- /**
11303
- * Keyed fields of the record itself, in declaration order: top level and plain
11304
- * layout groups. An embedded group or a collection describes a sub-record, so its
11305
- * fields are not candidates for the record's own heading.
11306
- */
11307
8413
  function ownFieldEntries(items) {
11308
8414
  const out = [];
11309
8415
  for (const it of items) if (isNamedField(it)) out.push([it.key, it.type]);
11310
8416
  else if (hasChildren(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it)) out.push(...ownFieldEntries(it.fields));
11311
8417
  return out;
11312
8418
  }
11313
- /**
11314
- * Key of the record's title field (lists, relation pickers, inlines, record heading).
11315
- * Priority: views.title → the first keyed kind:'title' field → 'id'.
11316
- *
11317
- * The title is declared, never guessed: a shelf that wants a heading marks the field
11318
- * with field.title(), or names another field through views.title when the heading is
11319
- * not a text field (a date, a computed value). A shelf that declares nothing resolves
11320
- * to 'id' and renders no heading — every field stays in the body with its own label.
11321
- */
11322
8419
  function titleKey(m) {
11323
8420
  if (m.views?.title) return m.views.title;
11324
8421
  const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === "title");
@@ -11344,29 +8441,32 @@ var bus_route_default = defineShelf({
11344
8441
  ] },
11345
8442
  fields: {
11346
8443
  name: field.title({ label: "core.fields.name" }),
8444
+ /**
8445
+ * What this route IS, read before anything else: its number, its two ends and the
8446
+ * stop these timetables are measured at. They already drew as one row — the spans
8447
+ * said so — but below the description and with their labels above. A stats cell
8448
+ * inverts that and makes them the record's headline, which is what they are.
8449
+ */
8450
+ route: field.stats({ fields: {
8451
+ route_number: field.int({ label: "transit.bus_route.fields.route_number" }),
8452
+ from: field.string({
8453
+ label: "transit.bus_route.fields.from",
8454
+ rules: { max: 100 }
8455
+ }),
8456
+ to: field.string({
8457
+ label: "transit.bus_route.fields.to",
8458
+ rules: { max: 100 }
8459
+ }),
8460
+ stop: field.string({
8461
+ label: "transit.bus_route.fields.stop",
8462
+ rules: { max: 200 }
8463
+ })
8464
+ } }),
8465
+ hairline: field.divider({}),
11347
8466
  description: field.text({
11348
8467
  label: "transit.bus_route.fields.description",
11349
8468
  rules: { max: 1e3 }
11350
8469
  }),
11351
- route_number: field.int({
11352
- label: "transit.bus_route.fields.route_number",
11353
- ui: { span: 3 }
11354
- }),
11355
- from: field.string({
11356
- label: "transit.bus_route.fields.from",
11357
- rules: { max: 100 },
11358
- ui: { span: 3 }
11359
- }),
11360
- to: field.string({
11361
- label: "transit.bus_route.fields.to",
11362
- rules: { max: 100 },
11363
- ui: { span: 3 }
11364
- }),
11365
- stop: field.string({
11366
- label: "transit.bus_route.fields.stop",
11367
- rules: { max: 200 },
11368
- ui: { span: 3 }
11369
- }),
11370
8470
  weekday_times: field.timetable({
11371
8471
  label: "transit.bus_route.fields.weekday_times",
11372
8472
  ui: { span: 12 }
@@ -11392,6 +8492,8 @@ var src_default = definePlugin({
11392
8492
  id: "transit",
11393
8493
  version: "1.0.0",
11394
8494
  dependsOn: [],
8495
+ label: "transit.plugin.label",
8496
+ description: "transit.plugin.description",
11395
8497
  libraries: [{
11396
8498
  meta: defineLibrary({
11397
8499
  id: "transit",