@coffer-org/plugin-transit 7.1.1 → 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.
Files changed (2) hide show
  1. package/dist/schema.js +18 -2200
  2. package/package.json +3 -3
package/dist/schema.js CHANGED
@@ -21,12 +21,12 @@ 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
32
  if (!p.label) throw new Error(`[plugin] ${p.id}: missing label`);
@@ -4069,7 +4069,7 @@ function number(params) {
4069
4069
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
4070
4070
  }
4071
4071
  //#endregion
4072
- //#region ../sdk/src/units.ts
4072
+ //#region ../sdk/dist/units.js
4073
4073
  var UNITS_CURRENCY = [
4074
4074
  {
4075
4075
  value: "UAH",
@@ -4373,20 +4373,6 @@ var UNITS_MAP = {
4373
4373
  label: "core.units.ppm"
4374
4374
  }]
4375
4375
  };
4376
- /**
4377
- * Resolve a unit spec — a named scale or an explicit option list — to its options.
4378
- *
4379
- * Throws rather than returning `undefined`, because both ways of reaching `undefined` here
4380
- * produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
4381
- * `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
4382
- * refine, so a missing spec or a typo'd scale name turns a user's save into a
4383
- * `Cannot read properties of undefined` instead of a validation message. The golden matrix
4384
- * recorded exactly that for `f.measured({})`.
4385
- *
4386
- * A declaration error belongs at declaration time, where the plugin author sees it and the
4387
- * message can name the scale they meant — the same reason the host-services seam fails at
4388
- * registration rather than at first render.
4389
- */
4390
4376
  function resolveUnits(u) {
4391
4377
  if (typeof u === "string") {
4392
4378
  const known = UNITS_MAP[u];
@@ -4397,48 +4383,25 @@ function resolveUnits(u) {
4397
4383
  return u;
4398
4384
  }
4399
4385
  //#endregion
4400
- //#region ../sdk/src/fields/validation.ts
4401
- /** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
4386
+ //#region ../sdk/dist/fields/validation.js
4402
4387
  function vmsg(code, params) {
4403
4388
  return JSON.stringify(params ? {
4404
4389
  code,
4405
4390
  params
4406
4391
  } : { code });
4407
4392
  }
4408
- /** v4 error-map: message for a missing value (formerly required_error). */
4409
4393
  function reqErr(code = "required") {
4410
4394
  return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
4411
4395
  }
4412
- /** v4 error-map: message for an invalid type (formerly invalid_type_error). */
4413
4396
  function typeErr(code = "invalid_type") {
4414
4397
  return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
4415
4398
  }
4416
- /** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
4417
4399
  function reqTypeErr() {
4418
4400
  return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
4419
4401
  }
4420
- /**
4421
- * A string schema whose CONTENT checks only speak once the value is known to be a string.
4422
- *
4423
- * zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
4424
- * one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
4425
- * about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
4426
- * turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
4427
- * alone, having no `length` to read — so the redundancy was array-shaped and invisible until
4428
- * the golden matrix started recording the whole issue list instead of `issues[0]`.)
4429
- *
4430
- * Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
4431
- * content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
4432
- * has no `.min`/`.max`/`.regex` to chain.
4433
- */
4434
4402
  function stringContent(add) {
4435
4403
  return string$1(reqTypeErr()).pipe(add(string$1()));
4436
4404
  }
4437
- /** Parse a JSON string, else return the native value (object/array) as-is.
4438
- * Tolerant input for native form-state AND legacy JSON-string payloads.
4439
- * A string that fails JSON.parse is returned unchanged — callers detect
4440
- * parse failure via `typeof result === 'string'` (inners here are object/array,
4441
- * never a bare string). */
4442
4405
  function jsonValue(raw) {
4443
4406
  if (typeof raw === "string") try {
4444
4407
  return JSON.parse(raw);
@@ -4447,12 +4410,6 @@ function jsonValue(raw) {
4447
4410
  }
4448
4411
  return raw;
4449
4412
  }
4450
- /**
4451
- * Factory for fields that store JSON and validate it with a nested zod schema.
4452
- * Accepts a native object/array (native form state) OR a JSON string (legacy).
4453
- * Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
4454
- * string remains a string → code 'json'.
4455
- */
4456
4413
  function jsonRefined(inner, code) {
4457
4414
  return unknown().superRefine((raw, ctx) => {
4458
4415
  const parsed = jsonValue(raw);
@@ -4469,37 +4426,11 @@ function jsonRefined(inner, code) {
4469
4426
  });
4470
4427
  });
4471
4428
  }
4472
- /**
4473
- * An empty value is no value: `''` and `null` become `undefined`, and the field's own schema
4474
- * never sees them. Every field is optional, so this is the ONLY shape — a field cannot demand
4475
- * a value, and the rules it carries judge what was entered rather than whether anything was.
4476
- *
4477
- * This used to have a second branch, for `required` fields, which turned the same emptiness
4478
- * into a `required` issue through a `z.unknown()` guard piped into the schema. That branch is
4479
- * why a `.min(1)` on a required string was unreachable: the empty string had already been
4480
- * converted to the issue before the string schema ran.
4481
- */
4482
4429
  function optionalize(schema) {
4483
4430
  return preprocess((v) => v === "" || v === null ? void 0 : v, schema.optional());
4484
4431
  }
4485
4432
  //#endregion
4486
- //#region ../sdk/src/fields/meta.ts
4487
- /**
4488
- * Field META modifiers — the shared machinery a built field (FieldMeta) passes through
4489
- * regardless of its type: value options (`applyOptions`/`bindOptionsZod`), the `multiple`
4490
- * array wrap (`applyMultiple`), and the final node-or-bare-FieldMeta shape
4491
- * (`wrapKey`, EVERY factory's last step).
4492
- *
4493
- * A LEAF module, the same shape as `fields/validation.ts`/`fields/normalize.ts`: it
4494
- * borrows the FieldMeta/LayoutNode/FieldClient/ColumnType/OptionItem SHAPES from
4495
- * `../fields.ts` as TYPE-ONLY imports (erased at compile time — no runtime edge), but has
4496
- * NO runtime dependency on `../fields.ts` itself. This is what lets
4497
- * `materialize/pipeline.ts` import `applyMultiple`/`wrapKey` from here instead of from
4498
- * `fields.ts`, which in turn is what lets `fields.ts` import `materialize` FROM
4499
- * `materialize/pipeline.ts` without closing a cycle (`fields.ts` → `pipeline.ts` →
4500
- * `fields/meta.ts`, nothing pointing back). `fields.ts` re-exports every symbol here, so
4501
- * no external import path (`@coffer-org/sdk/fields`) changes.
4502
- */
4433
+ //#region ../sdk/dist/fields/meta.js
4503
4434
  function safeJsonParse(s) {
4504
4435
  try {
4505
4436
  return JSON.parse(s);
@@ -4507,14 +4438,6 @@ function safeJsonParse(s) {
4507
4438
  return;
4508
4439
  }
4509
4440
  }
4510
- /**
4511
- * Wraps a field into a JSON array of values (storage: TEXT). The inner zod is `base.zod`.
4512
- * Modifier order: base → applyMultiple.
4513
- *
4514
- * Exception: kind 'image'/'media' handle multiple themselves in the renderer (gallery) —
4515
- * the MultipleField* wrappers on the web skip them.
4516
- */
4517
- /** Zod "JSON array of inner values" — used by applyMultiple. */
4518
4441
  function multipleZod(inner) {
4519
4442
  return unknown().superRefine((raw, ctx) => {
4520
4443
  let arr;
@@ -4565,15 +4488,12 @@ function applyMultiple(base, multiple) {
4565
4488
  zod: optionalize(s)
4566
4489
  };
4567
4490
  }
4568
- /** Inline option entry → OptionItem (plain string = value and label at once). */
4569
4491
  function toOptionItem(o) {
4570
4492
  return typeof o === "string" ? {
4571
4493
  value: o,
4572
4494
  title: o
4573
4495
  } : o;
4574
4496
  }
4575
- /** Canonical comparison form. Both the input and the option value pass through it, so a
4576
- * numeric field compares `100` against the option spelled `'100'`. */
4577
4497
  function optionKey(column, v) {
4578
4498
  if (column === "integer" || column === "real") {
4579
4499
  const n = Number(v);
@@ -4581,12 +4501,6 @@ function optionKey(column, v) {
4581
4501
  }
4582
4502
  return String(v ?? "");
4583
4503
  }
4584
- /**
4585
- * Membership check LAYERED OVER the field's own schema, never replacing it. Replacing it
4586
- * with `z.enum` — which is what select used to do — works only over a bare string: over an
4587
- * int it would destroy the int-ness, the coercion and the min/max, and over an ip it would
4588
- * destroy the structure. Layering keeps every type's own error code and adds one.
4589
- */
4590
4504
  function gateZod(inner, gate, multiple, column) {
4591
4505
  return inner.superRefine((v, ctx) => {
4592
4506
  if (gate.values === null || v === void 0 || v === null || v === "") return;
@@ -4598,11 +4512,6 @@ function gateZod(inner, gate, multiple, column) {
4598
4512
  });
4599
4513
  });
4600
4514
  }
4601
- /**
4602
- * Attaches `options`/`strict` to any field. Called from `wrapKey`, which EVERY factory
4603
- * funnels through — core and plugin-defined alike — so a new field type gets value options
4604
- * without writing a line for them.
4605
- */
4606
4515
  function applyOptions(base, o) {
4607
4516
  if (base.optionGate) return base;
4608
4517
  if (base.relation || base.parts || base.options) return base;
@@ -4631,22 +4540,10 @@ function applyOptions(base, o) {
4631
4540
  }
4632
4541
  return m;
4633
4542
  }
4634
- /**
4635
- * Fills (or refills) a strict field's closed set. Called by composeRegistry once the full
4636
- * option set is known — inline options, plugin contributions and the named source merged.
4637
- * A non-strict field has no gate and is left alone.
4638
- */
4639
4543
  function bindOptionsZod(field, values) {
4640
4544
  if (!field.optionGate || !values.length) return;
4641
4545
  field.optionGate.values = new Set(values.map((v) => optionKey(field.column, v)));
4642
4546
  }
4643
- /**
4644
- * Wraps a FieldMeta into a node. `key` is the name the PARENT gave this child
4645
- * (materialize/pipeline.ts) — an argument, not an option a factory reads out of its own
4646
- * opts, which is why it comes first. Absent means the element was never declared under a
4647
- * name: it becomes a keyless value node when it carries a `value`, and a bare FieldMeta
4648
- * otherwise.
4649
- */
4650
4547
  function wrapKey(key, opts, meta) {
4651
4548
  let m = applyOptions(meta, opts);
4652
4549
  if (opts.noEditControl) m = {
@@ -4766,7 +4663,7 @@ function wrapKey(key, opts, meta) {
4766
4663
  return m;
4767
4664
  }
4768
4665
  //#endregion
4769
- //#region ../sdk/src/fields/normalize.ts
4666
+ //#region ../sdk/dist/fields/normalize.js
4770
4667
  function normalizeOpts(rawIn) {
4771
4668
  const raw = rawIn;
4772
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).");
@@ -4813,30 +4710,8 @@ function normalizeOpts(rawIn) {
4813
4710
  return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
4814
4711
  }
4815
4712
  //#endregion
4816
- //#region ../sdk/src/materialize/decl.ts
4817
- /**
4818
- * PHASE 1 — declaration. A factory returns this and nothing else: plain data, no zod,
4819
- * no column, no widget. A parent may rewrite ANY property of a child's declaration
4820
- * before phase 2 builds it (see materialize/pipeline.ts).
4821
- */
4713
+ //#region ../sdk/dist/materialize/decl.js
4822
4714
  var BRAND = "__fieldDecl";
4823
- /**
4824
- * `opts.fields` vs `decl.fields` — the same author input reachable two ways, and only one of
4825
- * them is ever safe to read as BUILT children:
4826
- *
4827
- * - `opts.fields` is the RAW author input, unpacked by `normalizeOpts`'s spread (it does
4828
- * not know or care that `fields` is special) — never rewritten, never materialized. A
4829
- * composite that computes its OWN roles from what the author passed (`check`'s content
4830
- * fields) reads it from here, at declare() time, exactly as the author wrote it — decls
4831
- * and all.
4832
- * - `decl.fields` is what `declare()` copies from that same raw input for the tree walk
4833
- * (`materialize/pipeline.ts`) and for `resolveParts` to find. Once a parent has been
4834
- * materialized, this is where its BUILT children live.
4835
- *
4836
- * A consumer that wants built children — not raw author input — reads the materialized tree
4837
- * (`materialize()`/`materializeTree()`'s output), never `decl.opts.fields`: that stays
4838
- * exactly what the author passed, decls included, for as long as the decl is unbuilt.
4839
- */
4840
4715
  function declare(factory, raw) {
4841
4716
  const opts = normalizeOpts(raw);
4842
4717
  const spec = raw;
@@ -4851,9 +4726,7 @@ function isDecl(v) {
4851
4726
  return typeof v === "object" && v !== null && BRAND in v;
4852
4727
  }
4853
4728
  //#endregion
4854
- //#region ../sdk/src/materialize/registry.ts
4855
- /** True for a type registered through `roles`/`roleZod`/`structureCode` (materialize/composite.ts)
4856
- * rather than a plain `build()` — read generically, never by factory name. */
4729
+ //#region ../sdk/dist/materialize/registry.js
4857
4730
  function isComposite(type) {
4858
4731
  return "roles" in type;
4859
4732
  }
@@ -4867,12 +4740,6 @@ function typeOf(factory) {
4867
4740
  if (!def) throw new Error(`[types] unknown factory '${factory}'`);
4868
4741
  return def;
4869
4742
  }
4870
- /**
4871
- * `presetOpts` merged as DEFAULTS under the caller's own opts (`{ ...presetOpts, ...o }`) —
4872
- * an author's own value always wins. `config` is merged the same way one level deep, so a
4873
- * preset's own default (`weight`'s `config.min: 1`) survives an author setting a DIFFERENT
4874
- * config key (`rules: { max: 5000 }`) without the preset needing to repeat it.
4875
- */
4876
4743
  function mergePresetOpts(presetOpts, o) {
4877
4744
  const config = presetOpts.config || o.config ? {
4878
4745
  ...presetOpts.config,
@@ -4884,37 +4751,6 @@ function mergePresetOpts(presetOpts, o) {
4884
4751
  ...config ? { config } : {}
4885
4752
  };
4886
4753
  }
4887
- /**
4888
- * Registers a PRESET: a type that is its base type's `TypeDef` with fixed/default opts
4889
- * merged in, and — where it differs — its own `kind`/`widget`/`build`. This is what lets
4890
- * `field-presets.ts` express "a preset is its base type plus these opts" as DATA instead of
4891
- * copy-pasting normalizeOpts/optionalize/applyMultiple/wrapKey into a hand-written body:
4892
- * `kind`/`prim`/`widget`/`selfManages` default to the base type's own — a
4893
- * preset differs from its base in BEHAVIOUR, not in what column/prim family it belongs to; a
4894
- * change that DOES need a different prim/column is not a preset, it is a new type — and
4895
- * `over` replaces exactly the properties a given preset differs in.
4896
- *
4897
- * `presetOpts` are DEFAULTS the caller's own opts can still override (see `mergePresetOpts`
4898
- * above). A preset that must instead FORCE a value regardless of the caller (`country`'s
4899
- * `options: 'countries'`, `tags`'s `multiple: true`) is not this mechanism — those forward
4900
- * directly to the base factory with the forced opts spread AFTER the caller's own
4901
- * (`string({ ...raw, options: 'countries' })`), the pattern `country`/`currency`/`percent`/
4902
- * `year`/`tags` already use, none of which needs its own registry entry: their `factory` IS
4903
- * the base's, unchanged.
4904
- *
4905
- * `over.build`, when given, replaces the base type's `build()` entirely for THIS preset —
4906
- * still called with the merged opts, so it may itself delegate to the base's own `build()`
4907
- * and layer extra hints on top (`snippet` adds `hints.language` over `text`'s own build).
4908
- *
4909
- * Resolves `base` LAZILY (on first actual read, memoized), never at the moment
4910
- * `registerPreset` itself is called: `field-presets.ts` calls it at MODULE TOP LEVEL, inside
4911
- * the same circular import (`fields.ts` ⇄ `field-presets.ts`) its own header note already
4912
- * documents — depending on which module the loader entered first, the base's own
4913
- * `registerType` call (in `fields.ts`) may not have run yet. `text`/`i18n`/`smallText` in
4914
- * fields.ts sidestep the same trap by calling `typeOf('string')` only from inside their
4915
- * `build()`, never at their own `registerType()` call site; a preset's `kind`/`prim`/
4916
- * `widget`/`selfManages` need the identical deferral since they too default from the base.
4917
- */
4918
4754
  function registerPreset(factory, base, presetOpts, over = {}) {
4919
4755
  let resolved;
4920
4756
  const baseType = () => {
@@ -4941,21 +4777,7 @@ function registerPreset(factory, base, presetOpts, over = {}) {
4941
4777
  });
4942
4778
  }
4943
4779
  //#endregion
4944
- //#region ../sdk/src/materialize/composite.ts
4945
- /**
4946
- * ONE composite transform. It was written six times (geo, illustrated, measured, range,
4947
- * dimensions, check(single)) and differed only in the validation code and the per-role
4948
- * default zod.
4949
- *
4950
- * raw → jsonValue → carries nothing? (passthrough, when `acceptsEmptyRow`)
4951
- * → carries nothing at all? (pass through, there is nothing to judge)
4952
- * → rowSchema (partsRowShape) → stripServerOwnedParts → optional per-type refine
4953
- *
4954
- * A `CompositeTypeDef` names ONLY what its own type contributes: its roles, the default
4955
- * zod per role, and how its structure failure reports. Everything else — the empty
4956
- * check, the JSON tolerance, the row parse, stripping server-owned parts — lives here
4957
- * exactly once.
4958
- */
4780
+ //#region ../sdk/dist/materialize/composite.js
4959
4781
  function buildComposite(def, opts, parts) {
4960
4782
  const rowSchema = object(partsRowShape(def.roleZod(opts, parts), parts));
4961
4783
  const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
@@ -4964,7 +4786,6 @@ function buildComposite(def, opts, parts) {
4964
4786
  const parsed = jsonValue(raw);
4965
4787
  const p = parsed;
4966
4788
  const isRow = p == null || typeof p === "object" && !Array.isArray(p);
4967
- /** Every one of `keys` empty in this value — `null`/absent alike. */
4968
4789
  const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
4969
4790
  if (clientKeys && allEmpty(clientKeys)) return raw;
4970
4791
  if (typeof parsed === "string") {
@@ -4999,27 +4820,7 @@ function buildComposite(def, opts, parts) {
4999
4820
  };
5000
4821
  }
5001
4822
  //#endregion
5002
- //#region ../sdk/src/materialize/pipeline.ts
5003
- /**
5004
- * PHASE 2 — materialization. The six steps every field goes through, in one place:
5005
- * they used to be copied into 40-odd factory bodies (43 normalizeOpts heads,
5006
- * 38 wrapKey tails, 37 optionalize calls).
5007
- *
5008
- * Composites (roles, the shared structure transform) arrive in materialize/composite.ts;
5009
- * this file resolves a composite's parts and picks the right build path, but the
5010
- * transform itself lives there, exactly once.
5011
- */
5012
- /**
5013
- * `name` is the property this declaration was declared under in its parent's map — the ONLY
5014
- * source of a key a declared field ever has (a factory has no `key` option to read one from)
5015
- * and it is threaded straight through to `wrapKey`, which keys the result whenever `name` is
5016
- * defined. Absent (a top-level `materialize()` call on a standalone declaration) the result
5017
- * is keyless: a value node if it carries a `value`, a bare FieldMeta otherwise.
5018
- *
5019
- * This function does not itself stamp the result's `name` (the ADDRESS, `LayoutNode.name` —
5020
- * see that type's own doc comment) — its caller, `materializeEl`, does that for every branch,
5021
- * including this one.
5022
- */
4823
+ //#region ../sdk/dist/materialize/pipeline.js
5023
4824
  function materialize(decl, name) {
5024
4825
  const opts = decl.opts;
5025
4826
  const type = typeOf(decl.factory);
@@ -5045,22 +4846,6 @@ function materialize(decl, name) {
5045
4846
  };
5046
4847
  return wrapKey(name, opts, type.selfManages?.has("multiple") ? base : applyMultiple(base, opts.multiple ?? false));
5047
4848
  }
5048
- /**
5049
- * The shared storage transform behind a composite's `multiple` form: a collection group
5050
- * (child table) whose row fields ARE the given parts, under their role names — the exact
5051
- * `ContainerNode` shape `fields.ts`'s own `group({key,label,multiple:true,required,fields,view})`
5052
- * would build for this call pattern (no icon, no `rules`, no `value` — none of the three
5053
- * former call sites ever passed them), constructed directly rather than through `group()`
5054
- * itself to avoid a runtime edge back into fields.ts (see this module's own header note).
5055
- *
5056
- * Reached automatically, by the storage rule in `materialize()` above, for every composite
5057
- * registered through `registerType`/`CompositeTypeDef` — `illustrated`, `check` and
5058
- * `period` among them. `check`'s `roles(opts)` already returns checkbox-first order for
5059
- * `multiple` and content-first for `single`, and `period`'s `roles(opts)` returns the same
5060
- * from/until order regardless of `multiple`, so the generic path needs no special case for
5061
- * either — the row-field ORDER is each type's own declared data (its `roles`), not
5062
- * something this helper infers.
5063
- */
5064
4849
  function collectionGroup(key, opts, parts, collectionView) {
5065
4850
  const rowFields = parts.map((p) => ({
5066
4851
  ...wrapKey(p.role, p.mode === "computedStored" ? { value: p.value } : {}, p.meta),
@@ -5078,22 +4863,6 @@ function collectionGroup(key, opts, parts, collectionView) {
5078
4863
  fields: rowFields
5079
4864
  };
5080
4865
  }
5081
- /**
5082
- * One tree node, named by the property it was declared under. Every returned node — every
5083
- * branch below — carries that name (`name`, the ADDRESS a parent reaches it by) once `name`
5084
- * is defined; only the STORAGE name (`key`) is conditional.
5085
- *
5086
- * A DECLARATION takes that name as both its `name` and its `key` (`materialize` → `wrapKey`
5087
- * always keys a named declaration). An already-built element takes `key` too, but only if it
5088
- * OWNS A SCOPE and has not been named yet: a `ContainerNode` that declares `scope: 'nest'`
5089
- * owns a column prefix or a child table, and that scope has to be named — `group()`/`row()`/
5090
- * `keyed()` no longer take a key of their own, so the parent's property name is where it
5091
- * comes from. Everything else — a `'hoist'` group (an editorial block, a sheet row: it
5092
- * arranges children inside the parent's scope and owns none), a node already carrying its
5093
- * key, a keyless value node, a pseudo-element — owns no scope, so it keeps whatever `key` it
5094
- * already had (usually none), but still gets `name` stamped: that is the whole point of this
5095
- * task — a `'hoist'` child's declared property name used to be discarded here entirely.
5096
- */
5097
4866
  function materializeEl(el, name) {
5098
4867
  if (isDecl(el)) {
5099
4868
  const built = materialize(el, name);
@@ -5119,61 +4888,25 @@ function materializeEl(el, name) {
5119
4888
  name
5120
4889
  } : el;
5121
4890
  }
5122
- /**
5123
- * A built group that owns a scope but has not been given its storage key yet — i.e. it HAS
5124
- * children (`fields`, the same question `hasChildren` in `../fields.ts` asks) and a `'nest'`
5125
- * scope, but no key. Checked structurally rather than by importing `hasChildren`: this file
5126
- * has no RUNTIME edge back into `fields.ts` (see the header note) — `Array.isArray` is the
5127
- * same HAS-question, asked locally to keep that boundary.
5128
- */
5129
4891
  function isNamelessScope(el) {
5130
4892
  const g = el;
5131
4893
  return typeof g === "object" && g !== null && Array.isArray(g.fields) && g.scope === "nest" && g.key == null;
5132
4894
  }
5133
- /**
5134
- * A bare `FieldMeta`: it HAS none of a node's own marks yet — no `el` (so not a static value or
5135
- * a pseudo-element — those are keyless ON PURPOSE and keep only an address) and no `key` (so
5136
- * not a named field) — but it DOES carry a built field's own marks (`kind`, `zod`). This is a
5137
- * declaration-stage shape, before it becomes a node at all, so it asks about its own
5138
- * properties directly rather than through `hasValue`/`hasChildren` (which read `.type`/
5139
- * `.fields`, not `.kind`/`.zod`).
5140
- */
5141
4895
  function isNamelessField(el) {
5142
4896
  const m = el;
5143
4897
  return typeof m === "object" && m !== null && !("el" in m) && !("key" in m) && typeof m.kind === "string" && m.zod !== void 0;
5144
4898
  }
5145
- /** `group()` falls back to the key for a missing label; with the key arriving here instead,
5146
- * that fallback has to be applied here too, or a nameless scope would keep an empty one. */
5147
4899
  function labelOf(el, name) {
5148
4900
  const g = el;
5149
4901
  return g.label === void 0 || g.label === "" ? name : g.label;
5150
4902
  }
5151
4903
  var NUMERIC_LIKE$1 = /^\d+$/;
5152
- /**
5153
- * A LayoutEl tree: declarations become built elements, everything else passes through.
5154
- * ONE input shape — a NAMED MAP, whose property name becomes the child's key. A
5155
- * numeric-like name throws: object iteration would reorder such keys, and declaration
5156
- * order IS the field order.
5157
- */
5158
4904
  function materializeTree(els) {
5159
4905
  return Object.entries(els).map(([name, el]) => {
5160
4906
  if (NUMERIC_LIKE$1.test(name)) throw new Error(`fields: numeric-like child name '${name}' (object iteration would reorder the children)`);
5161
4907
  return materializeEl(el, name);
5162
4908
  });
5163
4909
  }
5164
- /**
5165
- * A ShelfDef/ExtendDef/SettingsDef-shaped object: materializes its `fields` tree, every
5166
- * other property carried through unchanged. `defineShelf`/`defineExtend`/`defineSettings`
5167
- * (and `composeRegistry`'s defensive pass over a hand-rolled manifest) all call this instead
5168
- * of repeating `{ ...def, fields: materializeTree(def.fields) as LayoutEl[] }` — the
5169
- * copy-paste this refactor exists to delete, one level up from the factory bodies.
5170
- *
5171
- * `T`'s own `fields` may be the author-facing `LayoutInput` (a `ShelfDefInput`/
5172
- * `ExtendDefInput`/`SettingsDefInput`) or an already-built `LayoutEl[]` (materializeDef is
5173
- * a no-op on an already-built tree — see fields.ts's own note on that) — either way, the
5174
- * RETURNED object's `fields` is always the built `LayoutEl[]`: the input type widens, the
5175
- * materialized type never does.
5176
- */
5177
4910
  function materializeDef(def) {
5178
4911
  const fields = Array.isArray(def.fields) ? def.fields : materializeTree(def.fields);
5179
4912
  return {
@@ -5182,25 +4915,7 @@ function materializeDef(def) {
5182
4915
  };
5183
4916
  }
5184
4917
  //#endregion
5185
- //#region ../sdk/src/parts.ts
5186
- /**
5187
- * Composite value parts.
5188
- *
5189
- * A composite field type declares its ROLES (what the pieces of its value mean); the
5190
- * author of a shelf supplies FILLINGS for those roles through `parts`. The storage
5191
- * columns of the composite are derived from the result instead of being written by
5192
- * hand, and each piece is an ordinary field — typed, labelled and renderable.
5193
- *
5194
- * The role set is closed: consumers (unit conversion, formatting, describe_shelf) rely
5195
- * on knowing which piece is the magnitude and which is the unit. What FILLS a role is
5196
- * open: a magnitude may be real/int/rating, a unit may be a select or a relation.
5197
- */
5198
- /**
5199
- * `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
5200
- * author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
5201
- * by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
5202
- * those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
5203
- */
4918
+ //#region ../sdk/dist/parts.js
5204
4919
  var UI_KEYS = [
5205
4920
  "hidden",
5206
4921
  "noEditControl",
@@ -5213,40 +4928,18 @@ var UI_KEYS = [
5213
4928
  "compareWith",
5214
4929
  "span"
5215
4930
  ];
5216
- /** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
5217
- * none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
5218
4931
  function uiOf(opts) {
5219
4932
  if (!opts) return void 0;
5220
4933
  const v = {};
5221
4934
  for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5222
4935
  return Object.keys(v).length ? v : void 0;
5223
4936
  }
5224
- /** A filling built from a ported factory arrives as a FieldDecl — build it into the
5225
- * node or bare FieldMeta it represents; anything else (an unported factory's
5226
- * result, or an already-built filling) passes through unchanged. `name` (the role) is
5227
- * the part's own storage key (`ResolvedPart.key` is always the role, see below) — passing
5228
- * it to `materialize` is what lets `wrapKey` recognize a computed filling as computed
5229
- * (its `computed` test requires a key) and stamp `compute`/`derived`/`hints.noEditControl`
5230
- * on the built meta, exactly as it would for any other keyed field. */
5231
4937
  function built(input, name) {
5232
4938
  return isDecl(input) ? materialize(input, name) : input;
5233
4939
  }
5234
- /**
5235
- * Whether `def` admits `d` as-is — the diagnostic question `RoleDef.accepts` answers.
5236
- * `resolveParts` uses this to decide whether a correction happened (and is worth logging);
5237
- * a role's own `normalize` uses it to decide whether to return the filling unchanged.
5238
- */
5239
4940
  function accepts(def, d) {
5240
4941
  return Array.isArray(def.accepts) ? def.accepts.includes(typeOf(d.factory).prim) : def.accepts.test(d);
5241
4942
  }
5242
- /**
5243
- * The correction every role gets for free: keep what the author said ABOUT the field
5244
- * (`label`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
5245
- * drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
5246
- * picture — and `multiple`, because a composite role owns exactly one column). A role whose
5247
- * own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
5248
- * writes its own `normalize` instead of using this.
5249
- */
5250
4943
  function retypeTo(factory) {
5251
4944
  return (d) => {
5252
4945
  const { label, value } = d?.opts ?? {};
@@ -5258,12 +4951,6 @@ function retypeTo(factory) {
5258
4951
  });
5259
4952
  };
5260
4953
  }
5261
- /**
5262
- * Build a RoleDef whose correction is the shared default: accept a filling `accepts`
5263
- * admits UNCHANGED, otherwise `retypeTo(defaultFactory)`. The common case — every role that
5264
- * needs no per-instance default (a currency scaled to the field's own `units`, an endpoint
5265
- * at the composite's own granularity) reads as one line.
5266
- */
5267
4954
  function role(name, acceptsSpec, defaultFactory) {
5268
4955
  const retype = retypeTo(defaultFactory);
5269
4956
  const def = {
@@ -5273,28 +4960,15 @@ function role(name, acceptsSpec, defaultFactory) {
5273
4960
  };
5274
4961
  return def;
5275
4962
  }
5276
- /**
5277
- * THE NAMING RULE for a composite: every part lives under its ROLE name, always — in the
5278
- * value object (`{ value: 5, unit: 'kg' }`) and in storage (column `<field key>__<role>`,
5279
- * `partColumns`). Consumers that reason about MEANING (a widget looking for the unit, unit
5280
- * conversion, formatting) may hard-code the role name as a property name directly; this
5281
- * helper exists only so a caller that already has a `ResolvedPart`/`PartSlot` need not know
5282
- * that.
5283
- */
5284
4963
  function partValueKey(p) {
5285
4964
  return p.role;
5286
4965
  }
5287
- /** role → the key that role occupies in the value object. See `partValueKey`. */
5288
4966
  function partValueKeys(parts) {
5289
4967
  const out = {};
5290
4968
  for (const p of parts) out[p.role] = p.role;
5291
4969
  return out;
5292
4970
  }
5293
4971
  var NUMERIC_LIKE = /^\d+$/;
5294
- /** The three shapes a factory returns, reduced to (key, meta, value). `name` is the
5295
- * role this filling occupies — threaded to `built()` so a computed filling is built AS a
5296
- * keyed field (see `built`'s own note); `ResolvedPart.key` below still always uses the
5297
- * role directly, never this function's `key`. */
5298
4972
  function readInput(input, name) {
5299
4973
  input = built(input, name);
5300
4974
  if (hasValue(input) && input.key !== void 0) return {
@@ -5313,18 +4987,6 @@ function readInput(input, name) {
5313
4987
  value: void 0
5314
4988
  };
5315
4989
  }
5316
- /**
5317
- * Roles in declaration order, each with its filling. `owner` names the composite this
5318
- * call resolves parts for (its own field key, supplied by the pipeline) — used only to
5319
- * tag the one diagnostic line below.
5320
- *
5321
- * Throws with a message naming the composite's roles for an UNKNOWN role name — a typo
5322
- * there fails loudly at `defineShelf` time rather than yielding a column that silently
5323
- * never appears. It has nothing to say about a RENAMED storage key any more: a filling
5324
- * carries no name of its own, so the role is the only source of one. A filling whose TYPE
5325
- * the role does not accept is never rejected either: the role is a GUARD, not a filter, and
5326
- * CORRECTS it instead (`RoleDef.normalize`) — see this module's header note.
5327
- */
5328
4990
  function resolveParts(roles, spec, owner) {
5329
4991
  const known = new Set(roles.map((r) => r.role));
5330
4992
  for (const r of roles) if (r.role.includes("__")) throw new Error(`parts: role name '${r.role}' contains '__', the storage path separator`);
@@ -5373,25 +5035,6 @@ function resolveParts(roles, spec, owner) {
5373
5035
  for (const p of out) if (p.meta.parts) assertInertNesting(p.meta, `${owner ?? "(unkeyed)"}.${p.key}`);
5374
5036
  return out;
5375
5037
  }
5376
- /**
5377
- * A composite filling a composite's role STORES fine at any depth: `partColumns` expands
5378
- * it, and the value↔columns walks (`flattenComposite`/`nestComposite`) follow it. The
5379
- * COMPUTE side does not follow it yet. Every engine that addresses a composite part reaches
5380
- * exactly ONE level below a field:
5381
- *
5382
- * - `completeValue` / `resolveField` (server/compute-unit.ts) — runs a part's `value`
5383
- * - `applyStoredParts` (server/part-injection.ts) — writes the computed column
5384
- * - `cacheClockColumns` / `levelClockColumnsByOwner` (server/cache-clock.ts) — the
5385
- * `__cachedAt` column a `cache` needs to ever refresh
5386
- * - the file normalizer (server/file-fields.ts) — resolves a part's `{name}` pointer
5387
- * - `collectMutateSources` / `collectComputeUnits` (sdk/mutate-graph.ts) — the trigger graph
5388
- *
5389
- * A part BELOW that level carrying any of those would own a real column that nothing ever
5390
- * writes, refreshes, validates or triggers — the exact silent failure a storage change must
5391
- * not ship. So it is refused where it is DECLARED, naming the path, instead of failing
5392
- * quietly at runtime. Lifting this is a compute-engine change, not a storage one: nothing
5393
- * about the columns needs to move for it.
5394
- */
5395
5038
  function assertInertNesting(meta, path) {
5396
5039
  for (const p of meta.parts ?? []) {
5397
5040
  const where = `${path}.${p.key}`;
@@ -5405,29 +5048,6 @@ function assertInertNesting(meta, path) {
5405
5048
  if (p.meta.parts) assertInertNesting(p.meta, where);
5406
5049
  }
5407
5050
  }
5408
- /**
5409
- * Storage columns of a composite: sub-PATH → column type. Every part contributes its own
5410
- * columns, whatever they are.
5411
- *
5412
- * A part is a NODE, not necessarily a leaf: a filling that is itself a composite carries
5413
- * its own `columns`, and those are what it contributes — under its role name as a prefix,
5414
- * joined by the same `__` the physical column name uses (`at` filled by `f.geo` →
5415
- * `at__lat`/`at__lng`/`at__label`). A part with no `columns` is a scalar and contributes
5416
- * exactly one entry, its own `column`, under its role name. That is where the walk STOPS,
5417
- * and every walk stops there: a scalar is the only thing a composite can bottom out in.
5418
- *
5419
- * The loop is one level deep because the result is built INDUCTIVELY, not by re-walking a
5420
- * tree. A filling is materialized before the composite that holds it reads it, so
5421
- * `p.meta.columns` was already produced by this same function and already carries the FULL
5422
- * sub-path of everything below it. Depth therefore costs nothing here and is bounded by
5423
- * nothing but the column NAME the joined path produces — see `storageColumns` (shelf.ts)
5424
- * for the join, and `MAX_COLUMN_NAME` (compose.ts) for the budget that name must fit.
5425
- *
5426
- * The result is the FLAT storage projection of the composite. The STRUCTURE lives in
5427
- * `FieldMeta.parts`, which is what the value↔columns walks (`nestComposite`/
5428
- * `flattenComposite` in shelf.ts) read: they need to know where one nested object ends and
5429
- * the next begins, and a joined key cannot say that on its own.
5430
- */
5431
5051
  function partColumns(parts) {
5432
5052
  const cols = {};
5433
5053
  for (const p of parts) {
@@ -5437,63 +5057,6 @@ function partColumns(parts) {
5437
5057
  }
5438
5058
  return cols;
5439
5059
  }
5440
- /**
5441
- * A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
5442
- * the composite's DEFAULT schema per role — the literal the factory writes by hand
5443
- * (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
5444
- * schema guards its (role-named) key:
5445
- *
5446
- * - a plain STORED part is keyed by its role name, matching the value object and the
5447
- * column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
5448
- * to its default filling keeps the default schema — byte-identical to the hand-written
5449
- * literal, so every existing structure/unit/range code and message is preserved.
5450
- * - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
5451
- * default schema for that role. This is what makes a RETYPED filling real: an
5452
- * `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
5453
- * and an `f.relation({…})` currency accepts a record id instead of being measured
5454
- * against an ISO-4217 string rule it was never meant to satisfy. The filling also
5455
- * brings its own optionality and its own min/max — an author replacing a filling
5456
- * takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
5457
- * replaced magnitude keeps a bound.
5458
- * - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
5459
- * other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
5460
- * whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
5461
- * moment later and the write path recomputes the column. It is stripped rather than
5462
- * `z.never()`-rejected because a read hands the client that key (it is a real column,
5463
- * present in every response), so rejecting it would break a read → PATCH-the-whole-object
5464
- * round trip.
5465
- * - a CLIENT-OWNED stored part of a composite also accepts
5466
- * an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
5467
- * for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
5468
- * magnitude column is set and whose unit column is empty reads back as
5469
- * `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
5470
- * empty — `0` is a value), and feeding that exact object back used to fail with
5471
- * `measured_structure`: the write path refused the shape its own read produced. The null
5472
- * slot now parses through and is written back as NULL, so the round trip is closed and
5473
- * clearing ONE role of a filled composite works instead of erroring.
5474
- * NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
5475
- * how "this record has no value there" arrives, while an ABSENT key is a writer that
5476
- * never mentioned a role it owns — which stays the structure error it has always been
5477
- * (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
5478
- * all-or-nothing rule for a DECLARATION, and it is untouched.
5479
- * A composite used to be able to override this: `required: true` kept every client-owned
5480
- * slot MANDATORY, null included, so that "tolerate null" could not turn a demanded field
5481
- * optional. Nothing demands a value now, so there is no override and the rule above is the
5482
- * only one.
5483
- */
5484
- /**
5485
- * How much a CLIENT-OWNED slot forgives in the row shape: exactly one thing, an explicit
5486
- * `null`. `.nullable()`, never `.nullish()` — a part that is MISSING entirely is still a
5487
- * malformed row (`{lat: 5.5}` is half a coordinate), which is the one rule about composites
5488
- * that judges CORRECTNESS rather than presence.
5489
- *
5490
- * There used to be a `RowTolerance` of three levels, and the field's `required` is what chose
5491
- * between them: 'strict' when the field demanded a value, 'null-or-missing' when the value was
5492
- * ABSENT under that demand — the third existing only so an absent required composite did not
5493
- * report the same absence twice, once as `required` and once as a structure code. With nothing
5494
- * able to demand a value there is no first issue to restate, so both levels and the type
5495
- * itself are gone.
5496
- */
5497
5060
  function partsRowShape(shape, parts) {
5498
5061
  const out = { ...shape };
5499
5062
  for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
@@ -5503,16 +5066,6 @@ function partsRowShape(shape, parts) {
5503
5066
  }
5504
5067
  return out;
5505
5068
  }
5506
- /**
5507
- * Drops SERVER-OWNED part slots from a successfully parsed composite row: a computed-and-stored
5508
- * role, keyed by its role name. Its value belongs to the schema, not to the writer — it is
5509
- * recomputed and written by the server on the same write (`applyStoredParts`) — so a client
5510
- * sending one back is stripped rather than rejected: the schema wins either way.
5511
- *
5512
- * Stripping (not `z.never()`-rejecting, as a computed-and-stored top-level FIELD does) is
5513
- * what keeps a read → PATCH-the-whole-object round trip working: a read hands the client that
5514
- * slot straight out of its column, so rejecting the echo would reject every full-object PATCH.
5515
- */
5516
5069
  function stripServerOwnedParts(value, parts) {
5517
5070
  let out = value;
5518
5071
  for (const p of parts) if (p.mode === "computedStored" && p.key in out) {
@@ -5522,28 +5075,7 @@ function stripServerOwnedParts(value, parts) {
5522
5075
  return out;
5523
5076
  }
5524
5077
  //#endregion
5525
- //#region ../sdk/src/field-presets.ts
5526
- /**
5527
- * Field presets are thin wrappers around the primitives in fields.ts.
5528
- *
5529
- * Each preset = one kind (one widget). Presets do not accept `format`;
5530
- * they are semantic types themselves. min/max/step go through `config`.
5531
- *
5532
- * Presets are added to `f` through `composeF`, which guarantees no preset
5533
- * overrides a primitive.
5534
- *
5535
- * The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
5536
- * and presets call them only inside their function bodies.
5537
- */
5538
- /**
5539
- * Email — kind 'email', email validation, inputType 'email'.
5540
- *
5541
- * @layer preset
5542
- * @base string
5543
- * @prim text
5544
- * @widget email
5545
- * @example f.email({ label: 'mod.fields.contact' })
5546
- */
5078
+ //#region ../sdk/dist/field-presets.js
5547
5079
  function email(o) {
5548
5080
  return declare("email", o);
5549
5081
  }
@@ -5561,15 +5093,6 @@ registerPreset("email", "string", {}, {
5561
5093
  };
5562
5094
  }
5563
5095
  });
5564
- /**
5565
- * Phone — kind 'tel', loose format /^\+?[\d\s()-]{4,}$/, inputType 'tel'.
5566
- *
5567
- * @layer preset
5568
- * @base string
5569
- * @prim text
5570
- * @widget tel
5571
- * @example f.tel({ label: 'mod.fields.phone' })
5572
- */
5573
5096
  function tel(o) {
5574
5097
  return declare("tel", o);
5575
5098
  }
@@ -5587,15 +5110,6 @@ registerPreset("tel", "string", {}, {
5587
5110
  };
5588
5111
  }
5589
5112
  });
5590
- /**
5591
- * Password — kind 'password', inputType 'password'. No strict schema. No multiple.
5592
- *
5593
- * @layer preset
5594
- * @base string
5595
- * @prim text
5596
- * @widget password
5597
- * @example f.password({ label: 'mod.fields.password' })
5598
- */
5599
5113
  function password(o) {
5600
5114
  return declare("password", o);
5601
5115
  }
@@ -5614,20 +5128,6 @@ registerPreset("password", "string", {}, {
5614
5128
  };
5615
5129
  }
5616
5130
  });
5617
- /**
5618
- * Internal API-token list — keyValue-shaped collection (name + write-only
5619
- * token + timestamps), custom renderer (`kind:'internalApiToken'`, own
5620
- * create/revoke UI — same trick as url()/perWeekday()). "Internal" = not a
5621
- * general-purpose field type for plugin shelves, only for the core account
5622
- * settings page. `token` is `kind:'password'` so maskSecrets/preserveTree
5623
- * already mask/preserve it for free.
5624
- *
5625
- * @layer preset
5626
- * @base group
5627
- * @prim —
5628
- * @widget internalApiToken
5629
- * @example f.internalApiToken({ label: 'mod.fields.apiTokens' })
5630
- */
5631
5131
  function internalApiToken(o) {
5632
5132
  return group({
5633
5133
  scope: "nest",
@@ -5643,18 +5143,6 @@ function internalApiToken(o) {
5643
5143
  }
5644
5144
  });
5645
5145
  }
5646
- /**
5647
- * Internal "connected apps" list — the OAuth clients (claude.ai & co) the user
5648
- * has authorized for MCP access. Same "internal" caveat and custom-renderer
5649
- * trick as internalApiToken(): read-only rows + a revoke action, never part of
5650
- * the parent form's save.
5651
- *
5652
- * @layer preset
5653
- * @base group
5654
- * @prim —
5655
- * @widget internalOauthGrants
5656
- * @example f.internalOauthGrants({ label: 'mod.fields.oauthGrants' })
5657
- */
5658
5146
  function internalOauthGrants(o) {
5659
5147
  return group({
5660
5148
  scope: "nest",
@@ -5668,18 +5156,6 @@ function internalOauthGrants(o) {
5668
5156
  }
5669
5157
  });
5670
5158
  }
5671
- /**
5672
- * The account page's linked-accounts section. Same trick as `internalApiToken`: a container
5673
- * with a custom `ui.kind`, drawn by its own renderer, which reads `/api/auth/links` itself.
5674
- * The declared children are what one ROW holds — the renderer reads them through the slot
5675
- * contract, so this is a declaration, not a layout.
5676
- *
5677
- * @layer preset
5678
- * @base group
5679
- * @prim —
5680
- * @widget internalIdentityLinks
5681
- * @example f.internalIdentityLinks({ label: 'auth.linksTitle' })
5682
- */
5683
5159
  function internalIdentityLinks(o) {
5684
5160
  return group({
5685
5161
  scope: "nest",
@@ -5694,15 +5170,6 @@ function internalIdentityLinks(o) {
5694
5170
  });
5695
5171
  }
5696
5172
  var SLUG_RE = /^[a-z0-9-]+$/;
5697
- /**
5698
- * Slug — kind 'slug', `^[a-z0-9-]+$`.
5699
- *
5700
- * @layer preset
5701
- * @base string
5702
- * @prim text
5703
- * @widget slug
5704
- * @example f.slug({ label: 'mod.fields.slug' })
5705
- */
5706
5173
  function slug(o) {
5707
5174
  return declare("slug", o);
5708
5175
  }
@@ -5717,21 +5184,6 @@ registerPreset("slug", "string", {}, {
5717
5184
  };
5718
5185
  }
5719
5186
  });
5720
- /**
5721
- * Identifier — a code printed for a machine and read back by one: a serial number, an order
5722
- * number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
5723
- * why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
5724
- * is one word on any string now, but the click that copies belongs to the kind.
5725
- *
5726
- * Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
5727
- * it, and a pattern here would reject the next manufacturer's.
5728
- *
5729
- * @layer preset
5730
- * @base string
5731
- * @prim text
5732
- * @widget identifier
5733
- * @example f.identifier({ label: 'mod.fields.serialNumber' })
5734
- */
5735
5187
  function identifier(o) {
5736
5188
  return declare("identifier", o);
5737
5189
  }
@@ -5746,20 +5198,6 @@ registerPreset("identifier", "string", {}, {
5746
5198
  };
5747
5199
  }
5748
5200
  });
5749
- /**
5750
- * The machine-readable zone of a travel document — the identifier taken to its limit: printed
5751
- * for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
5752
- * line breaks preserved, because an MRZ's line structure is part of what it encodes.
5753
- *
5754
- * Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
5755
- * a strip transcribed from a real document is worth storing even when it does not check out.
5756
- *
5757
- * @layer preset
5758
- * @base string
5759
- * @prim text
5760
- * @widget mrz
5761
- * @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
5762
- */
5763
5201
  function mrz(o) {
5764
5202
  return declare("mrz", o);
5765
5203
  }
@@ -5775,15 +5213,6 @@ registerPreset("mrz", "string", {}, {
5775
5213
  }
5776
5214
  });
5777
5215
  var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
5778
- /**
5779
- * Hex color — kind 'color', `#rrggbb` + swatch widget.
5780
- *
5781
- * @layer preset
5782
- * @base string
5783
- * @prim text
5784
- * @widget color
5785
- * @example f.color({ label: 'mod.fields.color' })
5786
- */
5787
5216
  function color(o) {
5788
5217
  return declare("color", o);
5789
5218
  }
@@ -5798,15 +5227,6 @@ registerPreset("color", "string", {}, {
5798
5227
  };
5799
5228
  }
5800
5229
  });
5801
- /**
5802
- * CSS color name — kind 'colorname'.
5803
- *
5804
- * @layer preset
5805
- * @base string
5806
- * @prim text
5807
- * @widget colorname
5808
- * @example f.colorname({ label: 'mod.fields.colorName' })
5809
- */
5810
5230
  function colorname(o) {
5811
5231
  return declare("colorname", o);
5812
5232
  }
@@ -5821,15 +5241,6 @@ registerPreset("colorname", "string", {}, {
5821
5241
  };
5822
5242
  }
5823
5243
  });
5824
- /**
5825
- * Shelf heading — kind 'title'. Heading renderer.
5826
- *
5827
- * @layer preset
5828
- * @base string
5829
- * @prim text
5830
- * @widget title
5831
- * @example f.title({ label: 'mod.fields.name' })
5832
- */
5833
5244
  function title(o) {
5834
5245
  return declare("title", o);
5835
5246
  }
@@ -5844,16 +5255,6 @@ registerPreset("title", "string", {}, {
5844
5255
  };
5845
5256
  }
5846
5257
  });
5847
- /**
5848
- * Link — kind 'link', edited as string, view = clickable <a target=_blank>.
5849
- * Stores the raw URL as text (unlike composite f.url). Loose validation.
5850
- *
5851
- * @layer preset
5852
- * @base string
5853
- * @prim text
5854
- * @widget link
5855
- * @example f.link({ label: 'mod.fields.link' })
5856
- */
5857
5258
  function link(o) {
5858
5259
  return declare("link", o);
5859
5260
  }
@@ -5872,9 +5273,7 @@ registerPreset("link", "string", {}, {
5872
5273
  }
5873
5274
  });
5874
5275
  var TEL_RE = /^\+?[\d\s()-]{4,}$/;
5875
- /** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
5876
5276
  var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
5877
- /** CSS named colors (CSS Color Module L4) for f.colorname. */
5878
5277
  var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
5879
5278
  "aliceblue",
5880
5279
  "antiquewhite",
@@ -6026,15 +5425,6 @@ var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
6026
5425
  "yellowgreen",
6027
5426
  "transparent"
6028
5427
  ]);
6029
- /**
6030
- * Tag — non-empty string + chip widget. `multiple` → array of tags (`tags`).
6031
- *
6032
- * @layer preset
6033
- * @base string
6034
- * @prim text
6035
- * @widget tag
6036
- * @example f.tag({ label: 'mod.fields.tag' })
6037
- */
6038
5428
  function tag(o) {
6039
5429
  return declare("tag", o);
6040
5430
  }
@@ -6055,21 +5445,6 @@ registerPreset("tag", "string", {}, {
6055
5445
  };
6056
5446
  }
6057
5447
  });
6058
- /**
6059
- * A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
6060
- * `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
6061
- * sequence — the plurality is the type, not a modifier someone remembered to add.
6062
- *
6063
- * Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
6064
- * an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
6065
- * containment — the only thing the value actually carries — is lost.
6066
- *
6067
- * @layer preset
6068
- * @base string
6069
- * @prim text
6070
- * @widget path
6071
- * @example f.path({ label: 'things.storage_location.fields.path' })
6072
- */
6073
5448
  function path(o) {
6074
5449
  return declare("path", {
6075
5450
  ...o,
@@ -6087,30 +5462,12 @@ registerPreset("path", "string", {}, {
6087
5462
  };
6088
5463
  }
6089
5464
  });
6090
- /**
6091
- * Tags — `tag({ multiple: true })`.
6092
- *
6093
- * @layer preset
6094
- * @base string
6095
- * @prim text
6096
- * @widget tag
6097
- * @example f.tags({ label: 'mod.fields.tags' })
6098
- */
6099
5465
  function tags(o) {
6100
5466
  return tag({
6101
5467
  ...o,
6102
5468
  multiple: true
6103
5469
  });
6104
5470
  }
6105
- /**
6106
- * Markdown — text() + Markdown editor.
6107
- *
6108
- * @layer preset
6109
- * @base text
6110
- * @prim text
6111
- * @widget markdown
6112
- * @example f.markdown({ label: 'mod.fields.body' })
6113
- */
6114
5471
  function markdown(o) {
6115
5472
  return declare("markdown", o);
6116
5473
  }
@@ -6118,16 +5475,6 @@ registerPreset("markdown", "text", {}, {
6118
5475
  kind: "markdown",
6119
5476
  widget: "markdown"
6120
5477
  });
6121
- /**
6122
- * Code snippet — text() + monospace code editor.
6123
- * `rules.language` is a highlighting hint (json/yaml/bash/ini…), stored in hints.
6124
- *
6125
- * @layer preset
6126
- * @base text
6127
- * @prim text
6128
- * @widget snippet
6129
- * @example f.snippet({ label: 'mod.fields.config' })
6130
- */
6131
5478
  function snippet(o) {
6132
5479
  return declare("snippet", o);
6133
5480
  }
@@ -6146,22 +5493,9 @@ registerPreset("snippet", "text", {}, {
6146
5493
  };
6147
5494
  }
6148
5495
  });
6149
- /** A NUL byte, or U+FFFD left behind by decoding non-UTF-8 bytes as text. */
6150
5496
  function isBinaryText(v) {
6151
5497
  return v.includes("\0") || v.includes("�");
6152
5498
  }
6153
- /**
6154
- * Source — plain text in one column, shaped like a file in the UI.
6155
- * Written through the API or by uploading a file; never hand-edited in the web form
6156
- * (hints.noEditControl). The content is never rendered — only name + Upload/Download.
6157
- * `rules.ext` is the displayed/downloaded extension, `rules.maxBytes` the size cap.
6158
- *
6159
- * @layer preset
6160
- * @base text
6161
- * @prim text
6162
- * @widget source
6163
- * @example f.source({ label: 'mod.fields.file' })
6164
- */
6165
5499
  function source(o) {
6166
5500
  return declare("source", o);
6167
5501
  }
@@ -6196,15 +5530,6 @@ registerPreset("source", "text", {}, {
6196
5530
  };
6197
5531
  }
6198
5532
  });
6199
- /**
6200
- * Rating 1..N — integer + star widget. rules.max is the upper bound (default 5).
6201
- *
6202
- * @layer preset
6203
- * @base int
6204
- * @prim number
6205
- * @widget rating
6206
- * @example f.rating({ label: 'mod.fields.rating' })
6207
- */
6208
5533
  function rating(o) {
6209
5534
  return declare("rating", o);
6210
5535
  }
@@ -6220,16 +5545,6 @@ registerPreset("rating", "int", { config: { max: 5 } }, {
6220
5545
  };
6221
5546
  }
6222
5547
  });
6223
- /** Duration in seconds — integer + duration widget ("2d 3h 30m 15s").
6224
- * rules.max is the maximum in seconds (default 604800 = 1 week).
6225
- * rules.step is the step in seconds (default 60 = 1 minute).
6226
- *
6227
- * @layer preset
6228
- * @base int
6229
- * @prim number
6230
- * @widget duration
6231
- * @example f.duration({ label: 'mod.fields.runtime' })
6232
- */
6233
5548
  function duration(o) {
6234
5549
  return declare("duration", o);
6235
5550
  }
@@ -6253,15 +5568,6 @@ registerPreset("duration", "int", { config: {
6253
5568
  };
6254
5569
  }
6255
5570
  });
6256
- /** Reminder — date with visual urgency. kind 'reminder', prim 'date'.
6257
- * rules.lead = day threshold (default 30). rules.min/rules.max = ISO dates.
6258
- *
6259
- * @layer preset
6260
- * @base date
6261
- * @prim date
6262
- * @widget reminder
6263
- * @example f.reminder({ label: 'mod.fields.renewal' })
6264
- */
6265
5571
  function reminder(o) {
6266
5572
  return declare("reminder", o);
6267
5573
  }
@@ -6280,24 +5586,6 @@ registerPreset("reminder", "date", { lead: 30 }, {
6280
5586
  };
6281
5587
  }
6282
5588
  });
6283
- /** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
6284
- * kind 'timetable', prim 'time', always `multiple`.
6285
- *
6286
- * Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
6287
- * the values MEAN. Everything it shows beyond the times themselves — when the service starts
6288
- * and ends, how many runs there are, which one is next — is derived from the values at render
6289
- * time, never stored: a "next departure" is wrong the moment it is written down, exactly the
6290
- * argument `f.age` already makes for an age.
6291
- *
6292
- * Storage is a plain multiple `time`, so a field can be switched to this from
6293
- * `f.time({ multiple: true })` and back with no migration.
6294
- *
6295
- * @layer preset
6296
- * @base time
6297
- * @prim time
6298
- * @widget timetable
6299
- * @example f.timetable({ label: 'mod.fields.weekdays' })
6300
- */
6301
5589
  function timetable(o) {
6302
5590
  return declare("timetable", {
6303
5591
  ...o,
@@ -6308,18 +5596,6 @@ registerPreset("timetable", "time", {}, {
6308
5596
  kind: "timetable",
6309
5597
  widget: "timetable"
6310
5598
  });
6311
- /** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
6312
- * kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
6313
- * `mutate`-driven computed field, it must be right on every day that passes, not only the day
6314
- * the record was last written, so this is display metadata over `date`, the same shape as
6315
- * `f.reminder`'s own relative-to-today note.
6316
- *
6317
- * @layer preset
6318
- * @base date
6319
- * @prim date
6320
- * @widget age
6321
- * @example f.age({ label: 'mod.fields.birthDate' })
6322
- */
6323
5599
  function age(o) {
6324
5600
  return declare("age", o);
6325
5601
  }
@@ -6330,15 +5606,6 @@ registerPreset("age", "date", {}, {
6330
5606
  return typeOf("date").build(o, parts);
6331
5607
  }
6332
5608
  });
6333
- /**
6334
- * Percentage 0..100 — real with rules:{min:0,max:100}.
6335
- *
6336
- * @layer preset
6337
- * @base real
6338
- * @prim number
6339
- * @widget number
6340
- * @example f.percent({ label: 'mod.fields.progress' })
6341
- */
6342
5609
  function percent(o) {
6343
5610
  return real({
6344
5611
  ...o,
@@ -6349,15 +5616,6 @@ function percent(o) {
6349
5616
  }
6350
5617
  });
6351
5618
  }
6352
- /**
6353
- * Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max.
6354
- *
6355
- * @layer preset
6356
- * @base int
6357
- * @prim number
6358
- * @widget number
6359
- * @example f.year({ label: 'mod.fields.releaseYear' })
6360
- */
6361
5619
  function year(o) {
6362
5620
  return int({
6363
5621
  ...o,
@@ -6368,15 +5626,6 @@ function year(o) {
6368
5626
  }
6369
5627
  });
6370
5628
  }
6371
- /**
6372
- * Weight — always grams (int). kind 'weight', prim 'number'.
6373
- *
6374
- * @layer preset
6375
- * @base int
6376
- * @prim number
6377
- * @widget weight
6378
- * @example f.weight({ label: 'mod.fields.weight' })
6379
- */
6380
5629
  function weight(o) {
6381
5630
  return declare("weight", o);
6382
5631
  }
@@ -6394,12 +5643,6 @@ registerPreset("weight", "int", { config: { min: 1 } }, {
6394
5643
  };
6395
5644
  }
6396
5645
  });
6397
- /**
6398
- * The three extents of a box, in declaration order — which is also the column order, so
6399
- * `w` is the magnitude `magnitudeSub()` hands to list sorting and to a filter by field key.
6400
- * The unit is NOT a role: dimensions are always millimetres, and nothing in the app converts
6401
- * units (see `docs/decisions.md`, 2026-08-05), so it stays a hint rather than a fourth column.
6402
- */
6403
5646
  var DIMENSION_ROLES = () => [
6404
5647
  role("w", ["number"], "int"),
6405
5648
  role("h", ["number"], "int"),
@@ -6423,27 +5666,9 @@ registerType("dimensions", {
6423
5666
  structureCode: "dimensions_structure",
6424
5667
  hints: () => ({ unit: "mm" })
6425
5668
  });
6426
- /**
6427
- * Dimensions — box dimensions {w,h,d}, int mm across the real sub-columns `key__w|h|d`.
6428
- *
6429
- * @layer preset
6430
- * @base int
6431
- * @prim text
6432
- * @widget dimensions
6433
- * @example f.dimensions({ label: 'mod.fields.size' })
6434
- */
6435
5669
  function dimensions(o) {
6436
5670
  return declare("dimensions", o);
6437
5671
  }
6438
- /**
6439
- * Country — a string whose values are the country list, rendered with flags.
6440
- *
6441
- * @layer preset
6442
- * @base string
6443
- * @prim text
6444
- * @widget text
6445
- * @example f.country({ label: 'mod.fields.country' })
6446
- */
6447
5672
  function country(raw) {
6448
5673
  return string({
6449
5674
  ...raw,
@@ -6453,22 +5678,6 @@ function country(raw) {
6453
5678
  noSearch: true
6454
5679
  });
6455
5680
  }
6456
- /**
6457
- * Currency — the ISO 4217 code the record's amounts are in, declared ONCE per record.
6458
- * The money twin of `f.unit`: an ordinary string over the global `currencies` list, so it
6459
- * is an ordinary column (groupable, filterable) and its picker is an expanded list rather
6460
- * than a combobox wedged into some amount's editor.
6461
- *
6462
- * A record carries ONE currency. Two amounts on one record that need different currencies
6463
- * are two records — `multiple: true` here means "the currencies this record deals in"
6464
- * (what an account holds), and binds no amount.
6465
- *
6466
- * @layer preset
6467
- * @base string
6468
- * @prim text
6469
- * @widget text
6470
- * @example f.currency({ label: 'mod.fields.currency' })
6471
- */
6472
5681
  function currency(raw) {
6473
5682
  return string({
6474
5683
  ...raw,
@@ -6478,22 +5687,6 @@ function currency(raw) {
6478
5687
  noSearch: true
6479
5688
  });
6480
5689
  }
6481
- /**
6482
- * A monetary amount — a plain number in a `real` column, whose currency is a field of the
6483
- * record (`currencyFrom`). Summable, sortable and filterable by its own name, like any
6484
- * other number.
6485
- *
6486
- * It used to be a composite `{value, currency}`: the amount then lived in `price__value`,
6487
- * reachable only through `magnitudeSub`, a sum over a collection had to re-assemble
6488
- * objects in JavaScript, and the currency needed a second control inside the amount's own
6489
- * editor — the one place a collapsed control must never be (skill field-render).
6490
- *
6491
- * @layer preset
6492
- * @base real
6493
- * @prim number
6494
- * @widget money
6495
- * @example f.money({ label: 'mod.fields.price' })
6496
- */
6497
5690
  function money(o) {
6498
5691
  return declare("money", o);
6499
5692
  }
@@ -6552,57 +5745,13 @@ var presets = {
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
- }
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
- */
6249
+ }
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 ?? {};
@@ -8688,21 +7253,6 @@ function group(o) {
8688
7253
  fields
8689
7254
  };
8690
7255
  }
8691
- /**
8692
- * Layout group, flow nowrap + horizontal scroll (single line). Sugar over group().
8693
- *
8694
- * `multiple` is an ordinary modifier here as everywhere else: `f.row({ scope: 'nest',
8695
- * multiple: true })` is a COLLECTION of rows, and the renderer aligns its columns (a repeated row is what a
8696
- * table is). There is no separate factory and no separate `display` value to DECLARE a
8697
- * tabular collection — `f.table` (`blocks.ts`) is a VIEW stamped onto a collection built
8698
- * here, never a second way to declare one.
8699
- *
8700
- * @layer primitive
8701
- * @prim —
8702
- * @widget group
8703
- * @example f.row({ label: 'mod.fields.row', fields: {} })
8704
- * @example f.row({ scope: 'nest', label: 'mod.fields.items', multiple: true, fields: {} })
8705
- */
8706
7256
  function row(o) {
8707
7257
  return group({
8708
7258
  scope: o.scope,
@@ -8714,22 +7264,6 @@ function row(o) {
8714
7264
  ui: { display: "scroll" }
8715
7265
  });
8716
7266
  }
8717
- /**
8718
- * Layout group with a 2D layout (rows×columns → aligned CSS grid). Sugar over group().
8719
- *
8720
- * A ROW IS A GROUP. `fields` is a map of rows, each row a map of its cells: the outer
8721
- * property names the row, the inner ones name the cells (their storage keys, like any
8722
- * other named child). Each row materializes into a nested `scope: 'hoist'` group, so a
8723
- * row boundary is the nesting itself and the renderer reads `group.fields` as its rows
8724
- * instead of scanning a flat list for separator markers. The cells still belong to the
8725
- * PARENT's scope — a hoist group owns none — so the columns are exactly the ones the flat
8726
- * form produced, an empty row included (a row group with no cells).
8727
- *
8728
- * @layer primitive
8729
- * @prim —
8730
- * @widget group
8731
- * @example f.sheet({ label: 'mod.fields.sheet', fields: { net: { ip: f.string({}) }, hw: { mac: f.string({}) } } })
8732
- */
8733
7267
  function sheet(o) {
8734
7268
  const rows = Object.fromEntries(Object.entries(o.fields).map(([name, cells]) => [name, group({
8735
7269
  scope: "hoist",
@@ -8742,17 +7276,6 @@ function sheet(o) {
8742
7276
  ui: { display: "sheet" }
8743
7277
  });
8744
7278
  }
8745
- /**
8746
- * Composite URL — embedded-group sugar. Storage: separate nested columns
8747
- * (`key__scheme`, `key__host`, `key__port`, …) in the same table. The renderer
8748
- * is custom (a single link/input, `kind:'url'`) — string↔parts via
8749
- * parseUrl/buildUrl. Partial/invalid URLs are stored by parts.
8750
- *
8751
- * @layer primitive
8752
- * @prim —
8753
- * @widget url
8754
- * @example f.url({ label: 'mod.fields.website' })
8755
- */
8756
7279
  function url(o) {
8757
7280
  return group({
8758
7281
  scope: "nest",
@@ -8770,16 +7293,6 @@ function url(o) {
8770
7293
  }
8771
7294
  });
8772
7295
  }
8773
- /**
8774
- * Keyed collection: one row per value of a single named field of that row — `by` names
8775
- * which one (default: the first), and its own name is the row's storage key. See the block
8776
- * comment above for the full contract (container, fixed, unique).
8777
- *
8778
- * @layer primitive
8779
- * @prim —
8780
- * @widget group
8781
- * @example f.keyed({ label: 'mod.fields.translations', fields: { lang: f.string({ options: ['en', 'uk'] }) } })
8782
- */
8783
7296
  function keyed(o) {
8784
7297
  const entries = Object.entries(o.fields);
8785
7298
  if (entries.length === 0) throw new Error(`[field.keyed] fields must declare at least the key field`);
@@ -8815,58 +7328,18 @@ function keyed(o) {
8815
7328
  }
8816
7329
  var isCollectionGroup = (g) => g.scope === "nest" && g.multiple === true;
8817
7330
  var isEmbeddedGroup = (g) => g.scope === "nest" && g.multiple !== true;
8818
- /**
8819
- * Horizontal divider, optionally labeled (a section heading).
8820
- *
8821
- * @layer primitive
8822
- * @prim —
8823
- * @widget divider
8824
- * @example f.divider({ label: 'mod.fields.section' })
8825
- */
8826
7331
  function divider(o) {
8827
7332
  return {
8828
7333
  el: "divider",
8829
7334
  label: o?.label
8830
7335
  };
8831
7336
  }
8832
- /**
8833
- * Reference markdown block by i18n key.
8834
- *
8835
- * The key is resolved WITH the variables of the running instance, so the locale string
8836
- * may name facts that are not constants: `{{origin}}` is this instance's own address
8837
- * (`https://coffer.example`), which is how a block documents an endpoint without
8838
- * freezing a developer's `localhost` into every translation. The set is fixed and
8839
- * ambient — the block declares only its key. See `web-ui/src/render/info-text.ts`.
8840
- *
8841
- * @layer primitive
8842
- * @prim —
8843
- * @widget info
8844
- * @example f.info('mod.fields.infoText')
8845
- */
8846
7337
  function info(textKey) {
8847
7338
  return {
8848
7339
  el: "info",
8849
7340
  text: textKey
8850
7341
  };
8851
7342
  }
8852
- /**
8853
- * What else points AT this record — the inverse of a relation, which is often the more useful
8854
- * direction on a record page: a box is more usefully "what is in it" than "what it is in".
8855
- *
8856
- * Stores NOTHING and has no column: it declares where to look, and the server answers by asking
8857
- * the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
8858
- * a field — there is no value here to validate or save.
8859
- *
8860
- * `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
8861
- * "everything that happens to point here": a shelf may point at the same target through two
8862
- * fields — a transaction has a source account and a destination account — and a panel that
8863
- * merged them would answer a question nobody asked.
8864
- *
8865
- * @layer primitive
8866
- * @prim —
8867
- * @widget backrefs
8868
- * @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
8869
- */
8870
7343
  function backrefs(o) {
8871
7344
  return {
8872
7345
  el: "backrefs",
@@ -8874,14 +7347,6 @@ function backrefs(o) {
8874
7347
  from: o.from
8875
7348
  };
8876
7349
  }
8877
- /**
8878
- * Action button: invokes the handler registered in actionRegistry under the key `value`.
8879
- *
8880
- * @layer primitive
8881
- * @prim —
8882
- * @widget button
8883
- * @example f.button({ label: 'mod.fields.run', value: 'runAction' })
8884
- */
8885
7350
  function button(o) {
8886
7351
  return {
8887
7352
  el: "button",
@@ -8891,38 +7356,14 @@ function button(o) {
8891
7356
  variant: o.variant
8892
7357
  };
8893
7358
  }
8894
- /**
8895
- * Brand for `fromShelf()` — a private key, never producible by a plain object literal, so
8896
- * `isShelfSource` tells a dynamic options source apart from a literal array or a named-list
8897
- * string CHEAPLY (one property read) and WITHOUT guessing from shape (`{library,shelf}`
8898
- * alone is not enough: a plugin's own option-list item could coincidentally carry those
8899
- * keys). Not exported — callers only ever get a `ShelfOptionsSource` back from `fromShelf`.
8900
- */
8901
7359
  var SHELF_SOURCE = Symbol("shelfSource");
8902
- /** Runtime discriminator for `ShelfOptionsSource` — see `SHELF_SOURCE`'s own doc comment. */
8903
7360
  function isShelfSource(v) {
8904
7361
  return typeof v === "object" && v !== null && v[SHELF_SOURCE] === true;
8905
7362
  }
8906
7363
  function resolveDate(v) {
8907
7364
  return v === "today" ? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) : v;
8908
7365
  }
8909
- /**
8910
- * Does this field offer preset values? Not `options.length`: a field whose options come
8911
- * from a NAMED source carries an empty array until composeRegistry resolves it, and every
8912
- * consumer that asks the question earlier (a `keyed({fixed})` check at declaration time, a
8913
- * role's `accepts`) would get the wrong answer.
8914
- */
8915
7366
  var hasOptions = (f) => (f.options?.length ?? 0) > 0 || f.hints["source"] != null;
8916
- /**
8917
- * `hasOptions`'s own companion for phase 1 (a DECLARATION, not a built meta) — the same
8918
- * question ENUMERATED's `test` asks further down. Now that `options` means the same thing
8919
- * for every type — a literal list, a named list, or `fromShelf(...)` all describe where
8920
- * this field's choices come from — the question is exactly "is `options` present?", nothing
8921
- * more: a `relation` field's choices come from records, which is as much an answer as a
8922
- * literal array is. No separate `opts.source` to check either: composeRegistry only
8923
- * resolves the two STATIC forms into values, but `opts.options` itself is set at
8924
- * declaration time regardless of which form it is.
8925
- */
8926
7367
  var declHasOptions = (d) => d.opts.options != null;
8927
7368
  registerType("string", {
8928
7369
  kind: "text",
@@ -8947,18 +7388,9 @@ registerType("string", {
8947
7388
  };
8948
7389
  }
8949
7390
  });
8950
- /**
8951
- * Single-line text. Arbitrary pattern/messageKey — via `rules`. Multi-line — `text()`.
8952
- *
8953
- * @layer primitive
8954
- * @prim text
8955
- * @widget text
8956
- * @example f.string({ label: 'mod.fields.name' })
8957
- */
8958
7391
  function string(o) {
8959
7392
  return declare("string", o);
8960
7393
  }
8961
- /** Shared by localDir/localFile's build(): a server path, judged only when one was entered. */
8962
7394
  function pathZod(_o) {
8963
7395
  return stringContent((s) => s);
8964
7396
  }
@@ -8975,14 +7407,6 @@ registerType("localDir", {
8975
7407
  };
8976
7408
  }
8977
7409
  });
8978
- /**
8979
- * Path to a DIRECTORY on the server (autocomplete, within home only).
8980
- *
8981
- * @layer primitive
8982
- * @prim text
8983
- * @widget localDir
8984
- * @example f.localDir({ label: 'mod.fields.path' })
8985
- */
8986
7410
  function localDir(o) {
8987
7411
  return declare("localDir", o);
8988
7412
  }
@@ -9000,14 +7424,6 @@ registerType("localFile", {
9000
7424
  };
9001
7425
  }
9002
7426
  });
9003
- /**
9004
- * Path to a FILE on the server (autocomplete; config.exts filters extensions).
9005
- *
9006
- * @layer primitive
9007
- * @prim text
9008
- * @widget localFile
9009
- * @example f.localFile({ label: 'mod.fields.path' })
9010
- */
9011
7427
  function localFile(o) {
9012
7428
  return declare("localFile", o);
9013
7429
  }
@@ -9019,14 +7435,6 @@ registerType("text", {
9019
7435
  return typeOf("string").build(o, []);
9020
7436
  }
9021
7437
  });
9022
- /**
9023
- * Multi-line text (`kind: 'textarea'`). No `multiple`.
9024
- *
9025
- * @layer primitive
9026
- * @prim text
9027
- * @widget textarea
9028
- * @example f.text({ label: 'mod.fields.notes' })
9029
- */
9030
7438
  function text(o) {
9031
7439
  return declare("text", o);
9032
7440
  }
@@ -9038,17 +7446,6 @@ registerType("i18n", {
9038
7446
  return typeOf("text").build(o, []);
9039
7447
  }
9040
7448
  });
9041
- /**
9042
- * Translatable text (kind 'i18n'). Stores a string — an i18n key OR a literal.
9043
- * In view it shows `t(value, { defaultValue: value })`: a known key → translation,
9044
- * any other text → shown as-is. Edit — raw input (key/literal without t()).
9045
- * Built-in reference catalogs write a key; user records — a plain literal.
9046
- *
9047
- * @layer primitive
9048
- * @prim text
9049
- * @widget i18n
9050
- * @example f.i18n({ label: 'mod.fields.title' })
9051
- */
9052
7449
  function i18n(o) {
9053
7450
  return declare("i18n", o);
9054
7451
  }
@@ -9060,14 +7457,6 @@ registerType("smallText", {
9060
7457
  return typeOf("text").build(o, []);
9061
7458
  }
9062
7459
  });
9063
- /**
9064
- * Multi-line text, scalar size (narrow column ~2 rows). Full-width — `text()`.
9065
- *
9066
- * @layer primitive
9067
- * @prim text
9068
- * @widget smalltext
9069
- * @example f.smallText({ label: 'mod.fields.summary' })
9070
- */
9071
7460
  function smallText(o) {
9072
7461
  return declare("smallText", o);
9073
7462
  }
@@ -9091,14 +7480,6 @@ registerType("real", {
9091
7480
  };
9092
7481
  }
9093
7482
  });
9094
- /**
9095
- * Real number (column 'real'). min/max/step — via `rules`.
9096
- *
9097
- * @layer primitive
9098
- * @prim number
9099
- * @widget number
9100
- * @example f.real({ label: 'mod.fields.weight' })
9101
- */
9102
7483
  function real(o) {
9103
7484
  return declare("real", o);
9104
7485
  }
@@ -9122,14 +7503,6 @@ registerType("int", {
9122
7503
  };
9123
7504
  }
9124
7505
  });
9125
- /**
9126
- * Integer (column 'integer'). min/max/step — via `rules`.
9127
- *
9128
- * @layer primitive
9129
- * @prim number
9130
- * @widget number
9131
- * @example f.int({ label: 'mod.fields.count' })
9132
- */
9133
7506
  function int(o) {
9134
7507
  return declare("int", o);
9135
7508
  }
@@ -9183,14 +7556,6 @@ registerType("date", {
9183
7556
  };
9184
7557
  }
9185
7558
  });
9186
- /**
9187
- * Date value with configurable granularity ('day' | 'month' | 'year', default 'day').
9188
- *
9189
- * @layer primitive
9190
- * @prim date
9191
- * @widget date
9192
- * @example f.date({ label: 'mod.fields.purchaseDate' })
9193
- */
9194
7559
  function date(o) {
9195
7560
  return declare("date", o);
9196
7561
  }
@@ -9212,15 +7577,6 @@ registerType("time", {
9212
7577
  };
9213
7578
  }
9214
7579
  });
9215
- /**
9216
- * Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
9217
- * 'minute' — the same default `f.datetime` has always had).
9218
- *
9219
- * @layer primitive
9220
- * @prim time
9221
- * @widget time
9222
- * @example f.time({ label: 'mod.fields.startTime' })
9223
- */
9224
7580
  function time(o) {
9225
7581
  return declare("time", o);
9226
7582
  }
@@ -9241,14 +7597,6 @@ registerType("datetime", {
9241
7597
  };
9242
7598
  }
9243
7599
  });
9244
- /**
9245
- * Combined date-and-time value with configurable granularity ('hour' | 'minute' | 'second', default 'minute').
9246
- *
9247
- * @layer primitive
9248
- * @prim datetime
9249
- * @widget datetime
9250
- * @example f.datetime({ label: 'mod.fields.startedAt' })
9251
- */
9252
7600
  function datetime(o) {
9253
7601
  return declare("datetime", o);
9254
7602
  }
@@ -9265,15 +7613,6 @@ registerType("boolean", {
9265
7613
  };
9266
7614
  }
9267
7615
  });
9268
- /**
9269
- * Boolean value rendered as a checkbox; defaults to false, nullable in storage (an
9270
- * unchanged collection row sends `null`, not `undefined`).
9271
- *
9272
- * @layer primitive
9273
- * @prim checkbox
9274
- * @widget boolean
9275
- * @example f.boolean({ label: 'mod.fields.active' })
9276
- */
9277
7616
  function boolean(o) {
9278
7617
  return declare("boolean", o);
9279
7618
  }
@@ -9294,14 +7633,6 @@ registerType("triState", {
9294
7633
  };
9295
7634
  }
9296
7635
  });
9297
- /**
9298
- * Three-way choice: 'yes' / 'no' / 'unknown'.
9299
- *
9300
- * @layer primitive
9301
- * @prim triState
9302
- * @widget triState
9303
- * @example f.triState({ label: 'mod.fields.reviewed' })
9304
- */
9305
7636
  function triState(o) {
9306
7637
  return declare("triState", o);
9307
7638
  }
@@ -9319,60 +7650,12 @@ registerType("stepper", {
9319
7650
  };
9320
7651
  }
9321
7652
  });
9322
- /**
9323
- * The REACHED stage of an ORDERED set of options. ONE value, so a gap is UNREPRESENTABLE
9324
- * rather than merely rejected: reaching stage 3 IS stages 1 and 2 having happened, and
9325
- * "3 but not 2" cannot be written down at all — there is no sequential-gate rule anywhere,
9326
- * in zod or in the UI, because there is nothing to gate. That is the whole difference from
9327
- * `f.string({ options, strict })`, whose options are PEERS (a weekday, a category), and
9328
- * from `f.check`, whose boxes tick independently. An empty value is the legitimate
9329
- * "not started" state, and going back a stage is legitimate too: the field stores where
9330
- * the record IS, never a history of how it got there.
9331
- *
9332
- * STORAGE — the stage's own VALUE, not its index, and which one it is matters because the
9333
- * two fail differently. An index survives renaming a stage and breaks on a reorder or an
9334
- * insertion; a value survives a reorder and breaks on a rename. Inserting a stage into the
9335
- * middle of the set ('customs' between 'shipped' and 'delivered') is the ORDINARY edit of a
9336
- * domain that grows, and under index storage it silently reinterprets every row already
9337
- * stored — corruption with no symptom anywhere. A rename is the rarer edit, it leaves a
9338
- * value the option set no longer contains, and THAT state is loud at both ends: `strict`
9339
- * (forced, below) rejects the next write of it, and the renderer draws it as an unknown
9340
- * stage rather than guessing a position for it or clamping it to a neighbour. A rename is
9341
- * also the case the tree already has a migration for (`TableOps.fill`/`convert`, skill
9342
- * `data-migrations`); nothing in it renumbers stored rows after an insertion. So: the
9343
- * stored value is the option's `value`, and the ORDER is `options`' declaration order.
9344
- *
9345
- * What "loud" costs, stated plainly because this field advertises it: `strict` rejects on
9346
- * WRITE, so a record carrying a stale stage cannot be saved AT ALL until the stage is
9347
- * repaired — not even for an unrelated edit to another field. That is ordinary strict-field
9348
- * behaviour rather than anything new here, but on a field whose whole promise is failing
9349
- * loudly it is the half of the promise that costs something.
9350
- *
9351
- * `strict` is forced on and is not the author's to choose — a value outside the ordered set
9352
- * has no position in it, so it can never be a legitimate entry. Never `multiple`: a record
9353
- * is at exactly one stage.
9354
- *
9355
- * @layer primitive
9356
- * @prim text
9357
- * @widget stepper
9358
- * @example f.stepper({ label: 'mod.fields.stage', options: ['ordered', 'shipped', 'delivered'] })
9359
- */
9360
7653
  function stepper(o) {
9361
7654
  return declare("stepper", {
9362
7655
  ...o,
9363
7656
  strict: true
9364
7657
  });
9365
7658
  }
9366
- /**
9367
- * Reject anything beyond a flat equality map: an operator key (`$in`, `$and`, …), an
9368
- * object-valued entry (which is how a nested operator like `{ type: { $in: [...] } }`
9369
- * would smuggle itself in), or an explicit `undefined` value all throw, naming the
9370
- * offending key. `undefined` is rejected alongside objects because it would otherwise
9371
- * slip through every later layer unnoticed: `Condition` types it as a valid value,
9372
- * `conditionKeys` skips it so compose validates nothing against the target shelf, and
9373
- * the picker's query-string builder turns it into the literal string `'undefined'`
9374
- * (an empty picker with no error anywhere).
9375
- */
9376
7659
  function assertFlatEqualityFilter(filter) {
9377
7660
  for (const [key, value] of Object.entries(filter)) {
9378
7661
  if (key.startsWith("$")) throw new Error(`[relation] filter key '${key}' is an operator — only flat equality is allowed here`);
@@ -9415,17 +7698,6 @@ registerType("relation", {
9415
7698
  };
9416
7699
  }
9417
7700
  });
9418
- /**
9419
- * Link to records of another shelf (`{library, shelf}`). Stores an integer id (single)
9420
- * or a JSON array of integer ids (multiple). Rendered as RecordInline + a record picker.
9421
- * This used to be the record branch of `select()`; split out into a separate primitive
9422
- * so select stays a pure enum/source list.
9423
- *
9424
- * @layer primitive
9425
- * @prim relation
9426
- * @widget relation
9427
- * @example f.relation({ label: 'mod.fields.owner', options: fromShelf({ library: 'people', shelf: 'person' }) })
9428
- */
9429
7701
  function relation(o) {
9430
7702
  return declare("relation", o);
9431
7703
  }
@@ -9452,49 +7724,13 @@ registerType("json", {
9452
7724
  };
9453
7725
  }
9454
7726
  });
9455
- /**
9456
- * Arbitrary JSON value: a native object/array, or a string parsed as JSON.
9457
- *
9458
- * @layer primitive
9459
- * @prim json
9460
- * @widget json
9461
- * @example f.json({ label: 'mod.fields.raw' })
9462
- */
9463
7727
  function json(o) {
9464
7728
  return declare("json", o);
9465
7729
  }
9466
- /**
9467
- * A content role accepts whatever the author declared, because for `check` the author's
9468
- * field IS the filling: there is no second override channel to correct one AGAINST (see
9469
- * `contentRole` below — both branches of its `normalize` hand back the same declaration).
9470
- *
9471
- * It used to demand a field owning ONE column, on the grounds that "a composite inside a
9472
- * composite would need `key__sub__sub`, which nothing on the flatten/nest path builds".
9473
- * `partColumns` builds it now, and the flatten/nest walks follow it — so the ground is
9474
- * gone and with it the only thing this role ever refused.
9475
- */
9476
7730
  var ANY_CONTENT = {
9477
7731
  test: () => true,
9478
7732
  describe: "the author's own content field"
9479
7733
  };
9480
- /**
9481
- * A single check's roles are not a fixed table: they come from the author's own `fields`
9482
- * and `slots`, the way `measured`'s unit role comes from its `units`. There is no second
9483
- * override channel: `fields` IS how an author fills the content roles, so every role
9484
- * resolves through its own `fallback` and `check` takes no `parts` spec. Reusing the
9485
- * author's `FieldMeta` as that fallback is safe for the same reason — the node was
9486
- * built inside this one `f.check(…)` call and is shared with nothing else.
9487
- *
9488
- * The two forms use DIFFERENT role orders, both load-bearing — `roles(opts)` reads
9489
- * `opts.multiple` (declared data, already resolved to a concrete boolean for THIS
9490
- * declaration by the time the pipeline calls it once) and returns the matching order, the
9491
- * same way `numberRange`/`realRange`'s own `roleZod` reads `opts.config`:
9492
- * - single: content first, then one boolean per slot — declaration order is column
9493
- * order, so the leading content field is the magnitude `magnitudeSub()` sorts and
9494
- * filters by;
9495
- * - multiple: checkbox columns first, then content — its own established child-table
9496
- * shape (the renderer's checklist reads the toggles as the row's leading columns).
9497
- */
9498
7734
  var contentRole = (f) => {
9499
7735
  const def = {
9500
7736
  role: f.key,
@@ -9511,55 +7747,25 @@ var checkboxRole = (label, i) => {
9511
7747
  };
9512
7748
  return def;
9513
7749
  };
9514
- /** `opts.slots`, defaulted to one unnamed checkbox — read the same way by `roles`/`roleZod`/`hints`. */
9515
7750
  function checkSlots(opts) {
9516
7751
  const slots = opts.slots;
9517
7752
  return slots && slots.length > 0 ? slots : [""];
9518
7753
  }
9519
- /**
9520
- * The content fields of one `f.check(…)` call: materialized, and validated at `roles()`
9521
- * time — declaration-time-equivalent, since every real entry point (`defineShelf`, `group()`,
9522
- * …) materializes synchronously at shelf-definition time — no duplicate key, no
9523
- * `check\d+`-shaped key (reserved for the checkbox slots).
9524
- *
9525
- * A composite content field is ordinary now: `partColumns` expands it into
9526
- * `<content>__<sub>` and the single form stores it across `<field>__<content>__<sub>`,
9527
- * exactly as the multiple form has always stored it in its child table. The guard that
9528
- * refused one in the single form is gone with the flat collector that made it necessary.
9529
- */
9530
7754
  function checkContentFields(opts) {
9531
7755
  const fields = opts.fields;
9532
7756
  const contentFields = materializeTree(fields !== void 0 && Object.keys(fields).length > 0 ? fields : { text: string({ label: "core.fields.text" }) });
9533
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`);
9534
7758
  return contentFields;
9535
7759
  }
9536
- /** `check`'s roles, in the order its CURRENT form (single vs multiple) stores them — see
9537
- * this section's own header note for why the order differs and why reading `opts.multiple`
9538
- * here is enough: one `materialize()` call means one already-resolved `multiple`. This is
9539
- * the ONE call `checkContentFields` gets per build — the pipeline calls `roles(opts)`
9540
- * once and resolves the result into `parts`, which `roleZod`/`hints` below read back
9541
- * instead of recomputing. */
9542
7760
  function checkRoles(opts) {
9543
7761
  const contentRoles = checkContentFields(opts).map(contentRole);
9544
7762
  const checkboxRoles = checkSlots(opts).map(checkboxRole);
9545
7763
  return opts.multiple ? [...checkboxRoles, ...contentRoles] : [...contentRoles, ...checkboxRoles];
9546
7764
  }
9547
- /**
9548
- * The CONTENT slice of `check`'s own already-resolved `parts` — everything but the
9549
- * checkbox roles `roleZod`/`hints` handle separately (the checkbox roles' default zod is
9550
- * a bare `z.boolean()` with no content behind it; `hints.fields` is documented content-
9551
- * only). Sliced by the checkbox COUNT (`checkSlots`, declared data, no materialize), off
9552
- * whichever end `checkRoles` put them on for this form — not a role-name pattern match,
9553
- * which would re-hardcode the very `check\d+` shape `checkContentFields` already guards as
9554
- * a RESERVED key, not a way to recognize one.
9555
- */
9556
7765
  function checkContentParts(opts, parts) {
9557
7766
  const slotCount = checkSlots(opts).length;
9558
7767
  return opts.multiple ? parts.slice(slotCount) : parts.slice(0, parts.length - slotCount);
9559
7768
  }
9560
- /** `check`'s per-role default zod, from the parts the pipeline already resolved — reading
9561
- * `p.meta.zod`/`p.role` off each content part instead of re-materializing the author's
9562
- * `fields` a second time (see `checkContentParts`). */
9563
7769
  function checkRoleZod(opts, parts) {
9564
7770
  const shape = {};
9565
7771
  for (const p of checkContentParts(opts, parts)) shape[p.role] = p.meta.zod;
@@ -9582,37 +7788,13 @@ registerType("check", {
9582
7788
  }))
9583
7789
  })
9584
7790
  });
9585
- /**
9586
- * Row(s) of N labeled checkboxes plus content fields. Single (non-multiple) stores real
9587
- * columns (`check0`, `check1`, …) alongside the content fields; multiple stores a child
9588
- * table with the checkbox columns first, then the content fields.
9589
- *
9590
- * @layer primitive
9591
- * @prim check
9592
- * @widget check
9593
- * @example f.check({ label: 'mod.fields.tasks' })
9594
- */
9595
7791
  function check(o) {
9596
7792
  return declare("check", o);
9597
7793
  }
9598
- /**
9599
- * A role whose filling must be ENUMERATED: a unit, a currency. Now that `options` means the
9600
- * same thing for every type — a literal list, a named list, or `fromShelf(...)` all describe
9601
- * where this field's choices come from — the question is exactly "is `options` present?": a
9602
- * `relation` filling qualifies exactly like a select does, its choices being its target
9603
- * shelf's records rather than a literal list.
9604
- *
9605
- * Tests the DECLARED opts (`d.opts.options`), not `hasOptions` on a BUILT meta: a named
9606
- * option source is bound later, at `composeRegistry`, so asking `hasOptions` here — in
9607
- * phase 1, before that binding — would read an empty array and reject a perfectly good
9608
- * named-source declaration.
9609
- */
9610
7794
  var ENUMERATED = {
9611
7795
  test: (d) => d.opts.options != null,
9612
7796
  describe: "a field with options, or a relation"
9613
7797
  };
9614
- /** Roles of `measured`: the magnitude and its unit. The unit's default depends on THIS
9615
- * field's own `units`, so it writes its own `normalize` rather than using `role()`. */
9616
7798
  var MEASURED_ROLES = (opts) => {
9617
7799
  const unitRole = {
9618
7800
  role: "unit",
@@ -9632,8 +7814,6 @@ var MEASURED_ROLES = (opts) => {
9632
7814
  };
9633
7815
  return [role("value", ["number"], "real"), unitRole];
9634
7816
  };
9635
- /** `measured`'s per-role default zod: the magnitude (int via step:1, kept for compat) and
9636
- * the unit, validated against `opts.units`. */
9637
7817
  function measuredRoleZod(opts) {
9638
7818
  const cfg = opts.config ?? {};
9639
7819
  const units = resolveUnits(opts.units);
@@ -9646,14 +7826,6 @@ function measuredRoleZod(opts) {
9646
7826
  unit: string$1().refine((v) => units.some((u) => u.value === v), { message: vmsg("measured_unit") })
9647
7827
  };
9648
7828
  }
9649
- /**
9650
- * `measured`'s own structure-failure report: not a flat code, but a SEARCH of the failed
9651
- * `rowSchema`'s own issues for one that already carries a vmsg-coded message (the unit
9652
- * role's own `measured_unit` refine) — that message is forwarded verbatim instead of the
9653
- * flat `measured_structure` code, so the caller learns WHICH part failed. Falls back to
9654
- * `measured_structure` when no such issue is found (a genuinely malformed row, not a bad
9655
- * unit).
9656
- */
9657
7829
  function measuredStructureCode(error) {
9658
7830
  const vmsgIssue = error.issues.find((iss) => {
9659
7831
  try {
@@ -9682,15 +7854,6 @@ registerType("measured", {
9682
7854
  };
9683
7855
  }
9684
7856
  });
9685
- /**
9686
- * Magnitude + per-row unit stored together as a composite {value, unit}; the unit is
9687
- * validated against `units`.
9688
- *
9689
- * @layer primitive
9690
- * @prim measured
9691
- * @widget measured
9692
- * @example f.measured({ label: 'mod.fields.weight', units: 'weight' })
9693
- */
9694
7857
  function measured(o) {
9695
7858
  return declare("measured", o);
9696
7859
  }
@@ -9713,16 +7876,6 @@ registerType("unit", {
9713
7876
  };
9714
7877
  }
9715
7878
  });
9716
- /**
9717
- * Declares the unit ONCE per record. Every `amount` below it is a plain number on
9718
- * this scale, so sums and comparisons are ordinary SQL. Deliberately not a
9719
- * conversion mechanism: units.ts holds labels, not factors.
9720
- *
9721
- * @layer primitive
9722
- * @prim text
9723
- * @widget unit
9724
- * @example f.unit({ label: 'mod.fields.unit', options: 'weight' })
9725
- */
9726
7879
  function unit(o) {
9727
7880
  return declare("unit", o);
9728
7881
  }
@@ -9749,20 +7902,9 @@ registerType("amount", {
9749
7902
  };
9750
7903
  }
9751
7904
  });
9752
- /**
9753
- * A plain number whose unit is declared elsewhere (`unitFrom`). Stored in a `real`
9754
- * column: summable, comparable and orderable in SQL, unlike `measured`, which pairs
9755
- * a number with a per-row unit across two sub-columns.
9756
- *
9757
- * @layer primitive
9758
- * @prim number
9759
- * @widget amount
9760
- * @example f.amount({ label: 'mod.fields.quantity', unitFrom: 'unit' })
9761
- */
9762
7905
  function amount(o) {
9763
7906
  return declare("amount", o);
9764
7907
  }
9765
- /** Roles of `geo`: the coordinate pair and an optional human label. */
9766
7908
  var GEO_ROLES = () => [
9767
7909
  role("lat", ["number"], "real"),
9768
7910
  role("lng", ["number"], "real"),
@@ -9782,30 +7924,9 @@ registerType("geo", {
9782
7924
  structureCode: "geo_structure",
9783
7925
  acceptsEmptyRow: true
9784
7926
  });
9785
- /**
9786
- * Geographic point composite {lat, lng, label?}; lat/lng validated to valid coordinate
9787
- * ranges.
9788
- *
9789
- * @layer primitive
9790
- * @prim geo
9791
- * @widget geo
9792
- * @example f.geo({ label: 'mod.fields.location' })
9793
- */
9794
7927
  function geo(o) {
9795
7928
  return declare("geo", o);
9796
7929
  }
9797
- /**
9798
- * Roles of `illustrated`: the prose and the picture that illustrates it.
9799
- *
9800
- * ORDER IS LOAD-BEARING. `partColumns` emits columns in role order and `magnitudeSub`
9801
- * (sdk/shelf.ts) takes the first one, so it decides what `?about=…` filters and what a list
9802
- * column sorts by. `text` first points both at the prose; `image` first would point them at a
9803
- * file name.
9804
- *
9805
- * Both roles use a PREDICATE rather than a prim list: "renders as a picture" and "is prose" are
9806
- * properties of the field's `kind`, not of its storage family — every file field stores JSON and
9807
- * every text field stores `text`, so a prim list could not tell an image from a PDF.
9808
- */
9809
7930
  var ILLUSTRATED_IMAGE_KINDS = /* @__PURE__ */ new Set([
9810
7931
  "image",
9811
7932
  "media",
@@ -9826,23 +7947,6 @@ var ILLUSTRATED_ROLES = () => [role("text", {
9826
7947
  test: (d) => ILLUSTRATED_IMAGE_KINDS.has(typeOf(d.factory).kind),
9827
7948
  describe: "an image field (image, media, avatar, cover, poster)"
9828
7949
  }, "image")];
9829
- /**
9830
- * A composite's per-role default zod, read from each role's OWN fallback filling instead of
9831
- * being hand-written per type — `partsRowShape` substitutes a part's zod only when the
9832
- * author OVERRODE the filling (`p.overridden`), so whatever this returns is the entire
9833
- * validation a DEFAULT filling gets. Hand-writing it is how `illustrated`'s picture role
9834
- * ended up with `z.unknown()`: a bare `f.illustrated({})`, the factory's own @example,
9835
- * accepted any garbage in `image` and lost whatever rules the prose filling declares.
9836
- * Reading the fallbacks makes the two agree by construction, the way `f.period` builds its
9837
- * own default shape from its resolved sub-fields. Each fallback's zod is already
9838
- * `optionalize(…, false)`, which maps `''`/`null` to `undefined` — the nullish tolerance the
9839
- * previous literal spelled out by hand.
9840
- *
9841
- * Shared by every composite whose default schema needs nothing from the call's own opts
9842
- * (`illustrated`, `attachment`) — a type whose per-role shape IS derived from its opts
9843
- * (`period`'s granularity) or from the resolved fillings (`check`'s content roles) builds
9844
- * its own instead.
9845
- */
9846
7950
  function defaultRoleZod(roles) {
9847
7951
  const shape = {};
9848
7952
  for (const r of roles()) {
@@ -9862,38 +7966,9 @@ registerType("illustrated", {
9862
7966
  collectionView: "illustratedList",
9863
7967
  hints: (o) => o.config ? { config: o.config } : {}
9864
7968
  });
9865
- /**
9866
- * A picture and its description as ONE value: `{ text, image }`, stored across
9867
- * `<key>__text` / `<key>__image`. The renderer wraps the prose around the picture, so a
9868
- * record page states "what this is, shown and told" as one editorial unit instead of two
9869
- * fields with an empty rectangle between them.
9870
- *
9871
- * `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
9872
- * `f.period` and `f.check` do — a child table whose columns are the resolved parts.
9873
- *
9874
- * @layer primitive
9875
- * @prim illustrated
9876
- * @widget illustrated
9877
- * @example f.illustrated({ label: 'mod.fields.about' })
9878
- */
9879
7969
  function illustrated(o) {
9880
7970
  return declare("illustrated", o);
9881
7971
  }
9882
- /**
9883
- * Roles of `attachment`: the name the owner gives the document, and the document itself.
9884
- *
9885
- * ORDER IS LOAD-BEARING, for the same reason it is in `ILLUSTRATED_ROLES` above:
9886
- * `partColumns` emits columns in role order and `magnitudeSub` (sdk/shelf.ts) takes the
9887
- * first one, so it decides what `?<key>=…` filters and what a list column sorts by. `label`
9888
- * first points both at the words a person wrote; `file` first would point them at a JSON
9889
- * blob whose lexical order is an accident of how the entry's keys happen to be serialized.
9890
- *
9891
- * Both roles are PRIM LISTS, not predicates — unlike `illustrated`, which needs a predicate
9892
- * because it must tell a picture from a PDF and every file kind stores the same prim. This
9893
- * type accepts the whole file family, which is exactly what `prim: 'file'` names
9894
- * (`registerFileType`, below in this file — the roles resolve lazily, long after it runs),
9895
- * so a list says it precisely and a predicate would only restate it at more length.
9896
- */
9897
7972
  var ATTACHMENT_ROLES = () => [role("label", ["text"], "string"), role("file", ["file"], "file")];
9898
7973
  registerType("attachment", {
9899
7974
  kind: "attachment",
@@ -9905,35 +7980,9 @@ registerType("attachment", {
9905
7980
  acceptsEmptyRow: true,
9906
7981
  collectionView: "attachmentList"
9907
7982
  });
9908
- /**
9909
- * An uploaded file and the label that names it as ONE value: `{ label, file }`, stored
9910
- * across `<key>__label` / `<key>__file`. A file field alone can only ever show what the
9911
- * uploader happened to call the file — `scan_20240817_final(2).pdf` — so the record page
9912
- * states what a document IS beside it instead of leaving the reader to open every one.
9913
- *
9914
- * `multiple: true` is the ordinary modifier and takes the collection-group path, exactly as
9915
- * `f.illustrated` and `f.period` do — a child table whose columns are the resolved parts.
9916
- * The `file` role accepts any of the file family and defaults to `f.file` (accept `*`); a
9917
- * shelf that wants the narrower document uploader fills it: `fields: { file: f.document({}) }`.
9918
- *
9919
- * @layer primitive
9920
- * @prim attachment
9921
- * @widget attachment
9922
- * @example f.attachment({ label: 'mod.fields.attachment' })
9923
- */
9924
7983
  function attachment(o) {
9925
7984
  return declare("attachment", o);
9926
7985
  }
9927
- /**
9928
- * 7 values, Mon–Sun. A thin wrapper over keyed({fixed}): a fixed collection,
9929
- * keyed by the weekday list, one real value per day. Its own kind 'per-weekday'
9930
- * dispatches a compact renderer. Storage: a grandchild table of {day, value} rows.
9931
- *
9932
- * @layer primitive
9933
- * @prim —
9934
- * @widget per-weekday
9935
- * @example f.perWeekday({ label: 'mod.fields.hours' })
9936
- */
9937
7986
  function perWeekday(o) {
9938
7987
  return {
9939
7988
  ...keyed({
@@ -9956,45 +8005,18 @@ function perWeekday(o) {
9956
8005
  kind: "per-weekday"
9957
8006
  };
9958
8007
  }
9959
- /**
9960
- * How a cross-field ordering check (a range's `from > to`, a period's) reads one endpoint
9961
- * once `stripServerOwnedParts` has removed the slots the schema owns. The check must judge
9962
- * the value the record will actually CARRY, never the client's echo of a server-owned slot:
9963
- * that echo is discarded on the same write, so letting it decide would reject legitimate
9964
- * writes over a value that never reaches storage, and would never see the value that does.
9965
- *
9966
- * - a CLIENT-owned (plain `stored`) endpoint → the parsed value, under its role name;
9967
- * - a COMPUTED-AND-STORED endpoint → undefined, so the pair is not compared at all: the
9968
- * server produces that value later in the write (a constant included — it is written by
9969
- * the same pass as a function) and nothing here can know it ahead of time.
9970
- *
9971
- * With every role left to its default filling every endpoint is client-owned, and this is
9972
- * exactly `row[role]` — the comparison, its code and its message are unchanged.
9973
- */
9974
8008
  function endpointReader(parts, keys) {
9975
8009
  return (role, stored) => stored?.[keys[role]];
9976
8010
  }
9977
- /** period's own granularity, defaulted like every other config-driven composite —
9978
- * read the same way by `roles`/`roleZod`/`hints`. */
9979
8011
  function periodGranularity(opts) {
9980
8012
  return opts.config?.granularity ?? "day";
9981
8013
  }
9982
- /** The endpoint factory at a given granularity — a ported date/datetime FieldDecl. It
9983
- * takes no key: an endpoint stores under its ROLE name (`from`/`until`), which
9984
- * `resolveParts` supplies, so there was never a second name to pass here. */
9985
8014
  function periodMkSubDecl(granularity) {
9986
8015
  return (label) => granularity === "datetime" ? datetime({ label }) : date({
9987
8016
  label,
9988
8017
  rules: { granularity: granularity === "month" ? "month" : "day" }
9989
8018
  });
9990
8019
  }
9991
- /**
9992
- * One endpoint role of a `period`: its default depends on THIS field's own granularity
9993
- * (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
9994
- * filling still keeps the composite's own granularity (`canonical.opts`) — the same
9995
- * `date`/`datetime` config the role's own default carries — with the author's
9996
- * label/required/ui/value layered on top, exactly like `retypeTo`'s default.
9997
- */
9998
8020
  function periodRole(key, label, mkSubDecl) {
9999
8021
  const def = {
10000
8022
  role: key,
@@ -10015,15 +8037,10 @@ function periodRole(key, label, mkSubDecl) {
10015
8037
  };
10016
8038
  return def;
10017
8039
  }
10018
- /** Roles of a single `period`: the two date/datetime endpoints, at the type's granularity. */
10019
8040
  function periodRoles(opts) {
10020
8041
  const mkSubDecl = periodMkSubDecl(periodGranularity(opts));
10021
8042
  return [periodRole("from", "core.period.from", mkSubDecl), periodRole("until", "core.period.until", mkSubDecl)];
10022
8043
  }
10023
- /** period's per-role default zod: each role unconditionally `.optional()`, at the type's
10024
- * own granularity (`periodMkSubDecl`) — the schema a role falls back to when the author
10025
- * did not override it (`partsRowShape`'s own rule: an overridden part is validated by its
10026
- * OWN zod instead, never this default). */
10027
8044
  function periodRoleZod(opts) {
10028
8045
  const zod = materialize(periodMkSubDecl(periodGranularity(opts))("")).zod.optional();
10029
8046
  return {
@@ -10031,9 +8048,6 @@ function periodRoleZod(opts) {
10031
8048
  until: zod
10032
8049
  };
10033
8050
  }
10034
- /** period's own ordering check: `from > until` is rejected — the same `endpointReader`
10035
- * `rangeRefine` uses, comparing the ISO date/datetime strings directly (they sort
10036
- * lexicographically) instead of `rangeRefine`'s numeric endpoints. */
10037
8051
  function periodRefine(stored, parts) {
10038
8052
  const endpoint = endpointReader(parts, partValueKeys(parts));
10039
8053
  const f = endpoint("from", stored);
@@ -10058,39 +8072,20 @@ registerType("period", {
10058
8072
  granularity: periodGranularity(opts)
10059
8073
  })
10060
8074
  });
10061
- /**
10062
- * Date/datetime period composite {from, until}; `multiple` → an array of periods stored
10063
- * as a child table.
10064
- *
10065
- * @layer primitive
10066
- * @prim period
10067
- * @widget period
10068
- * @example f.period({ label: 'mod.fields.employment' })
10069
- */
10070
8075
  function period(raw) {
10071
8076
  return declare("period", raw);
10072
8077
  }
10073
- /** Keys a file entry may carry. Everything else is rejected — see fileEntryIssue. */
10074
8078
  var FILE_KEYS = /* @__PURE__ */ new Set([
10075
8079
  "name",
10076
8080
  "mime",
10077
8081
  "size"
10078
8082
  ]);
10079
- /** Keys that mean "the author pasted a remote address" — the one mistake worth naming. */
10080
8083
  var FILE_URL_KEYS = [
10081
8084
  "url",
10082
8085
  "src",
10083
8086
  "href",
10084
8087
  "link"
10085
8088
  ];
10086
- /**
10087
- * A file entry points at a file already uploaded to the server: `{ name }`, where
10088
- * name is the bare filename returned by POST /api/upload. Anything else — a remote
10089
- * URL, an extra key, a path — is refused here, so a record can never hold a
10090
- * reference the server cannot serve. `mime`/`size` are accepted (legacy payloads
10091
- * and the web uploader send them) but the server overwrites them from disk.
10092
- * Returns a vmsg code, or null when the entry is well-formed.
10093
- */
10094
8089
  function fileEntryIssue(it) {
10095
8090
  if (typeof it !== "object" || it === null || Array.isArray(it)) return "file_structure";
10096
8091
  const rec = it;
@@ -10104,11 +8099,6 @@ function fileEntryIssue(it) {
10104
8099
  if (rec["size"] !== void 0 && typeof rec["size"] !== "number") return "file_structure";
10105
8100
  return null;
10106
8101
  }
10107
- /**
10108
- * Shared by the whole file family's build(): a scalar {name,...} OR an array
10109
- * [{name,...},...], native or JSON-string, stored as JSON regardless of `multiple` (the
10110
- * renderer's gallery mode draws the single/many difference, not zod).
10111
- */
10112
8102
  function fileZod() {
10113
8103
  return unknown().superRefine((raw, ctx) => {
10114
8104
  if (typeof raw === "string") try {
@@ -10134,10 +8124,6 @@ function fileZod() {
10134
8124
  }
10135
8125
  });
10136
8126
  }
10137
- /**
10138
- * Registers one file-family TypeDef: same zod/column/json for all of them, `kind` (and
10139
- * `widget`, always identical to `kind` in this family) is the only thing that varies.
10140
- */
10141
8127
  function registerFileType(kind) {
10142
8128
  registerType(kind, {
10143
8129
  kind,
@@ -10155,123 +8141,41 @@ function registerFileType(kind) {
10155
8141
  });
10156
8142
  }
10157
8143
  registerFileType("file");
10158
- /**
10159
- * Generic uploaded file reference `{ name }`, pointing at a file already on the server.
10160
- *
10161
- * @layer primitive
10162
- * @prim file
10163
- * @widget file
10164
- * @example f.file({ label: 'mod.fields.attachment' })
10165
- */
10166
8144
  function file(o) {
10167
8145
  return declare("file", o);
10168
8146
  }
10169
8147
  registerFileType("document");
10170
- /**
10171
- * Uploaded document reference `{ name }`, rendered as a document widget.
10172
- *
10173
- * @layer primitive
10174
- * @prim file
10175
- * @widget document
10176
- * @example f.document({ label: 'mod.fields.doc' })
10177
- */
10178
8148
  function document(o) {
10179
8149
  return declare("document", o);
10180
8150
  }
10181
8151
  registerFileType("audio");
10182
- /**
10183
- * Uploaded audio file reference `{ name }`, rendered as an audio player.
10184
- *
10185
- * @layer primitive
10186
- * @prim file
10187
- * @widget audio
10188
- * @example f.audio({ label: 'mod.fields.recording' })
10189
- */
10190
8152
  function audio(o) {
10191
8153
  return declare("audio", o);
10192
8154
  }
10193
8155
  registerFileType("video");
10194
- /**
10195
- * Uploaded video file reference `{ name }`, rendered as a video player.
10196
- *
10197
- * @layer primitive
10198
- * @prim file
10199
- * @widget video
10200
- * @example f.video({ label: 'mod.fields.clip' })
10201
- */
10202
8156
  function video(o) {
10203
8157
  return declare("video", o);
10204
8158
  }
10205
8159
  registerFileType("image");
10206
- /**
10207
- * Uploaded image reference `{ name }`, rendered as an inline image.
10208
- *
10209
- * @layer primitive
10210
- * @prim file
10211
- * @widget image
10212
- * @example f.image({ label: 'mod.fields.photo' })
10213
- */
10214
8160
  function image(o) {
10215
8161
  return declare("image", o);
10216
8162
  }
10217
8163
  registerFileType("media");
10218
- /**
10219
- * Uploaded media reference `{ name }`, rendered as a generic media widget (mixed
10220
- * image/video).
10221
- *
10222
- * @layer primitive
10223
- * @prim file
10224
- * @widget media
10225
- * @example f.media({ label: 'mod.fields.attachment' })
10226
- */
10227
8164
  function media(o) {
10228
8165
  return declare("media", o);
10229
8166
  }
10230
8167
  registerFileType("avatar");
10231
- /**
10232
- * Uploaded image reference `{ name }`, rendered as a circular avatar.
10233
- *
10234
- * @layer primitive
10235
- * @prim file
10236
- * @widget avatar
10237
- * @example f.avatar({ label: 'mod.fields.avatar' })
10238
- */
10239
8168
  function avatar(o) {
10240
8169
  return declare("avatar", o);
10241
8170
  }
10242
8171
  registerFileType("cover");
10243
- /**
10244
- * Uploaded image reference `{ name }`, rendered as a wide cover image.
10245
- *
10246
- * @layer primitive
10247
- * @prim file
10248
- * @widget cover
10249
- * @example f.cover({ label: 'mod.fields.cover' })
10250
- */
10251
8172
  function cover(o) {
10252
8173
  return declare("cover", o);
10253
8174
  }
10254
8175
  registerFileType("poster");
10255
- /**
10256
- * Like `image` on the backend, but rendered large (portrait 2:3 poster).
10257
- *
10258
- * @layer primitive
10259
- * @prim file
10260
- * @widget poster
10261
- * @example f.poster({ label: 'mod.fields.poster' })
10262
- */
10263
8176
  function poster(o) {
10264
8177
  return declare("poster", o);
10265
8178
  }
10266
- /**
10267
- * Sugar over `keyed`: a collection table of {key, value} rows, `key` unique within a
10268
- * record; the value cell's type is set via `fields.value` (default: plain string).
10269
- *
10270
- * @layer primitive
10271
- * @prim —
10272
- * @widget group
10273
- * @example f.keyValue({ label: 'mod.fields.secrets' })
10274
- */
10275
8179
  function keyValue(raw) {
10276
8180
  const { fields, ...rest } = raw;
10277
8181
  const o = normalizeOpts(rest);
@@ -10293,9 +8197,7 @@ function keyValue(raw) {
10293
8197
  by: "key"
10294
8198
  });
10295
8199
  }
10296
- /** Roles of a range: the two endpoints, integer or real depending on `isInt`. */
10297
8200
  var RANGE_ROLES = (isInt) => [role("from", ["number"], isInt ? "int" : "real"), role("to", ["number"], isInt ? "int" : "real")];
10298
- /** Shared by numberRange/realRange's `roleZod`: the endpoint schema, int or real per `isInt`. */
10299
8201
  function rangeEndpointZod(isInt, opts) {
10300
8202
  const cfg = opts.config ?? {};
10301
8203
  let n = number$1().finite();
@@ -10304,11 +8206,6 @@ function rangeEndpointZod(isInt, opts) {
10304
8206
  if (cfg.max != null) n = n.max(cfg.max);
10305
8207
  return n;
10306
8208
  }
10307
- /** Shared by numberRange/realRange's `refine`: `from > to` is rejected. Strip FIRST, then
10308
- * compare — the check must judge the value the record will actually carry, never the
10309
- * client's echo of an endpoint the SCHEMA owns (buildComposite strips it before calling
10310
- * this). Each endpoint is read under the key it occupies in the value object — its role
10311
- * name, always, now. */
10312
8209
  function rangeRefine(stored, parts) {
10313
8210
  const endpoint = endpointReader(parts, partValueKeys(parts));
10314
8211
  const fromV = endpoint("from", stored);
@@ -10340,25 +8237,9 @@ function registerRangeType(kind, isInt) {
10340
8237
  }
10341
8238
  registerRangeType("numberRange", true);
10342
8239
  registerRangeType("realRange", false);
10343
- /**
10344
- * Integer range composite {from, to}; `from > to` is rejected.
10345
- *
10346
- * @layer primitive
10347
- * @prim numberRange
10348
- * @widget numberRange
10349
- * @example f.numberRange({ label: 'mod.fields.ageRange' })
10350
- */
10351
8240
  function numberRange(o) {
10352
8241
  return declare("numberRange", o);
10353
8242
  }
10354
- /**
10355
- * Real-number range composite {from, to}; `from > to` is rejected.
10356
- *
10357
- * @layer primitive
10358
- * @prim realRange
10359
- * @widget realRange
10360
- * @example f.realRange({ label: 'mod.fields.priceRange' })
10361
- */
10362
8243
  function realRange(o) {
10363
8244
  return declare("realRange", o);
10364
8245
  }
@@ -10393,23 +8274,9 @@ registerType("embed", {
10393
8274
  };
10394
8275
  }
10395
8276
  });
10396
- /**
10397
- * External content snapshot by URL — oEmbed/OpenGraph metadata (title, thumbnail,
10398
- * provider, …) captured at save time via /api/embed.
10399
- *
10400
- * @layer primitive
10401
- * @prim embed
10402
- * @widget embed
10403
- * @example f.embed({ label: 'mod.fields.link' })
10404
- */
10405
8277
  function embed(o) {
10406
8278
  return declare("embed", o);
10407
8279
  }
10408
- /**
10409
- * Base primitives (storage-aligned) + structural types. Presets (thin wrappers
10410
- * over these primitives) are added below from field-presets.ts and CANNOT override
10411
- * any key from here (guard in composeF).
10412
- */
10413
8280
  var PRIMITIVES = {
10414
8281
  string,
10415
8282
  text,
@@ -10459,7 +8326,6 @@ var PRIMITIVES = {
10459
8326
  button,
10460
8327
  backrefs
10461
8328
  };
10462
- /** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
10463
8329
  function composeF(presets, blocks) {
10464
8330
  for (const k of Object.keys(presets)) if (k in PRIMITIVES) throw new Error(`[f] preset '${k}' overrides a primitive`);
10465
8331
  for (const k of Object.keys(blocks)) if (k in PRIMITIVES) throw new Error(`[f] block '${k}' overrides a primitive`);
@@ -10500,14 +8366,12 @@ function toClient(field) {
10500
8366
  };
10501
8367
  }
10502
8368
  //#endregion
10503
- //#region ../sdk/src/settings.ts
8369
+ //#region ../sdk/dist/settings.js
10504
8370
  function defineSettings(s) {
10505
8371
  return materializeDef(s);
10506
8372
  }
10507
8373
  //#endregion
10508
- //#region ../sdk/src/shelf.ts
10509
- /** Flat [storageKey, FieldMeta] pairs of the parent table's columns.
10510
- * layout group → flattened; embedded group → prefix `key__`; collection → skipped. */
8374
+ //#region ../sdk/dist/shelf.js
10511
8375
  function fieldEntries(items) {
10512
8376
  const out = [];
10513
8377
  for (const raw of items) {
@@ -10521,38 +8385,12 @@ function fieldEntries(items) {
10521
8385
  }
10522
8386
  return out;
10523
8387
  }
10524
- /**
10525
- * Rejects a keyless computed element (`field.x({ value: fn })`, no `key`) anywhere in
10526
- * the tree: at the shelf's own top level, inside a keyless layout group (`f.group`/
10527
- * `f.row`/`f.sheet` without a storage key), AND inside a keyed embedded group or
10528
- * collection row. A keyless computed node never carries a key of its own regardless of what it is
10529
- * nested in, so the enclosing group having a storage key does not give it one — there
10530
- * is still no mechanism that computes and returns such an element: `applyStoredParts`
10531
- * (`@coffer-org/server/part-injection`) only ever writes into a KEYED unit — a
10532
- * top-level field with `fm.compute`, or a composite part, and every composite part now
10533
- * always has a key too (its role name) — so a bare keyless value node has no column to
10534
- * land in either way. Left unrejected, a keyless computed element nested inside a
10535
- * keyed group would silently render blank forever instead of failing loudly at
10536
- * `defineShelf` time — recurse into every group, not only keyless ones.
10537
- */
10538
8388
  function assertNoKeylessCompute(items, owner) {
10539
8389
  for (const it of items) {
10540
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`);
10541
8391
  if (hasChildren(it)) assertNoKeylessCompute(it.fields, owner);
10542
8392
  }
10543
8393
  }
10544
- /**
10545
- * The declaration-time checks that are about a FIELD LIST, not about a shelf: they read
10546
- * `fields` and nothing else, so they are equally true of an extend's fields — an extend
10547
- * stores composites in its own table through the very same write path (`upsertExtendRecord`
10548
- * → `applyStoredParts`) and renders them through the same layout walk.
10549
- *
10550
- * They live in ONE function called by both `defineShelf` and `defineExtend` rather than
10551
- * being copied: `defineExtend` had NEITHER, so a keyless computed element on an extend
10552
- * rendered blank forever instead of throwing, and a mixed-ownership composite on an extend
10553
- * warned about nothing at all. `owner` is the prefix each diagnostic is tagged with
10554
- * (`[shelf] things/item`, `[extend] device_info`), the one thing the two callers differ in.
10555
- */
10556
8394
  function checkFieldDeclaration(fields, owner) {
10557
8395
  assertNoKeylessCompute(fields, owner);
10558
8396
  }
@@ -10572,32 +8410,12 @@ function defineShelf(m) {
10572
8410
  checkFieldDeclaration(built.fields, `[shelf] ${built.library}/${built.shelf}`);
10573
8411
  return built;
10574
8412
  }
10575
- /**
10576
- * Contract of a real entity:
10577
- * id — uuid (the ONLY required system field, auto).
10578
- * The rest (including name) are regular fields in ShelfDef.fields.
10579
- * created_at/updated_at — system infrastructure (outside fields, auto-managed).
10580
- */
10581
- /**
10582
- * Keyed fields of the record itself, in declaration order: top level and plain
10583
- * layout groups. An embedded group or a collection describes a sub-record, so its
10584
- * fields are not candidates for the record's own heading.
10585
- */
10586
8413
  function ownFieldEntries(items) {
10587
8414
  const out = [];
10588
8415
  for (const it of items) if (isNamedField(it)) out.push([it.key, it.type]);
10589
8416
  else if (hasChildren(it) && !isCollectionGroup(it) && !isEmbeddedGroup(it)) out.push(...ownFieldEntries(it.fields));
10590
8417
  return out;
10591
8418
  }
10592
- /**
10593
- * Key of the record's title field (lists, relation pickers, inlines, record heading).
10594
- * Priority: views.title → the first keyed kind:'title' field → 'id'.
10595
- *
10596
- * The title is declared, never guessed: a shelf that wants a heading marks the field
10597
- * with field.title(), or names another field through views.title when the heading is
10598
- * not a text field (a date, a computed value). A shelf that declares nothing resolves
10599
- * to 'id' and renders no heading — every field stays in the body with its own label.
10600
- */
10601
8419
  function titleKey(m) {
10602
8420
  if (m.views?.title) return m.views.title;
10603
8421
  const declared = ownFieldEntries(m.fields).find(([, f]) => f.kind === "title");