@coffer-org/plugin-transit 4.0.0 → 6.0.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 +136 -129
  2. package/package.json +4 -4
package/dist/schema.js CHANGED
@@ -4371,8 +4371,28 @@ var UNITS_MAP = {
4371
4371
  label: "core.units.ppm"
4372
4372
  }]
4373
4373
  };
4374
+ /**
4375
+ * Resolve a unit spec — a named scale or an explicit option list — to its options.
4376
+ *
4377
+ * Throws rather than returning `undefined`, because both ways of reaching `undefined` here
4378
+ * produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
4379
+ * `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
4380
+ * refine, so a missing spec or a typo'd scale name turns a user's save into a
4381
+ * `Cannot read properties of undefined` instead of a validation message. The golden matrix
4382
+ * recorded exactly that for `f.measured({})`.
4383
+ *
4384
+ * A declaration error belongs at declaration time, where the plugin author sees it and the
4385
+ * message can name the scale they meant — the same reason the host-services seam fails at
4386
+ * registration rather than at first render.
4387
+ */
4374
4388
  function resolveUnits(u) {
4375
- return typeof u === "string" ? UNITS_MAP[u] : u;
4389
+ if (typeof u === "string") {
4390
+ const known = UNITS_MAP[u];
4391
+ if (!known) throw new Error(`units: unknown scale '${u}' (known: ${Object.keys(UNITS_MAP).sort().join(", ")})`);
4392
+ return known;
4393
+ }
4394
+ if (!Array.isArray(u)) throw new Error("units: a unit spec is required — a named scale, or an explicit list of options");
4395
+ return u;
4376
4396
  }
4377
4397
  //#endregion
4378
4398
  //#region ../sdk/src/fields/validation.ts
@@ -4395,6 +4415,23 @@ function typeErr(code = "invalid_type") {
4395
4415
  function reqTypeErr() {
4396
4416
  return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
4397
4417
  }
4418
+ /**
4419
+ * A string schema whose CONTENT checks only speak once the value is known to be a string.
4420
+ *
4421
+ * zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
4422
+ * one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
4423
+ * about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
4424
+ * turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
4425
+ * alone, having no `length` to read — so the redundancy was array-shaped and invisible until
4426
+ * the golden matrix started recording the whole issue list instead of `issues[0]`.)
4427
+ *
4428
+ * Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
4429
+ * content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
4430
+ * has no `.min`/`.max`/`.regex` to chain.
4431
+ */
4432
+ function stringContent(add) {
4433
+ return string$1(reqTypeErr()).pipe(add(string$1()));
4434
+ }
4398
4435
  /** Parse a JSON string, else return the native value (object/array) as-is.
4399
4436
  * Tolerant input for native form-state AND legacy JSON-string payloads.
4400
4437
  * A string that fails JSON.parse is returned unchanged — callers detect
@@ -4650,6 +4687,13 @@ function wrapKey(key, opts, meta) {
4650
4687
  editor: opts.editor
4651
4688
  }
4652
4689
  };
4690
+ if (opts.activate) m = {
4691
+ ...m,
4692
+ hints: {
4693
+ ...m.hints,
4694
+ activate: opts.activate
4695
+ }
4696
+ };
4653
4697
  if (opts.span !== void 0) m = {
4654
4698
  ...m,
4655
4699
  hints: {
@@ -4721,11 +4765,10 @@ function wrapKey(key, opts, meta) {
4721
4765
  //#region ../sdk/src/fields/normalize.ts
4722
4766
  function normalizeOpts(rawIn) {
4723
4767
  const raw = rawIn;
4768
+ 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).");
4769
+ if (raw.ui?.["agent"] !== void 0) throw new Error("[normalizeOpts] `agent` is not presentation — declare it at the top level of the field call, not in `ui: {}`.");
4724
4770
  const r = raw.rules ?? {};
4725
- const v = {
4726
- ...raw.view ?? {},
4727
- ...raw.ui ?? {}
4728
- };
4771
+ const v = raw.ui ?? {};
4729
4772
  const config = {};
4730
4773
  if (r.min != null && typeof r.min === "number") config.min = r.min;
4731
4774
  if (r.max != null && typeof r.max === "number") config.max = r.max;
@@ -4734,7 +4777,7 @@ function normalizeOpts(rawIn) {
4734
4777
  if (r.messageKey != null) config.messageKey = r.messageKey;
4735
4778
  if (r.granularity != null) config.granularity = r.granularity;
4736
4779
  if (r.language != null) config.language = r.language;
4737
- const { rules: _r, view: _v, ui: _ui, ...flat } = raw;
4780
+ const { rules: _r, ui: _ui, ...flat } = raw;
4738
4781
  const result = {
4739
4782
  ...flat,
4740
4783
  ...Object.keys(config).length ? { config } : {},
@@ -4749,7 +4792,6 @@ function normalizeOpts(rawIn) {
4749
4792
  step: r.step,
4750
4793
  hidden: v.hidden,
4751
4794
  noEditControl: v.noEditControl,
4752
- noSearch: v.noSearch,
4753
4795
  display: v.display,
4754
4796
  kind: v.kind,
4755
4797
  icon: v.icon,
@@ -4757,7 +4799,12 @@ function normalizeOpts(rawIn) {
4757
4799
  ...v.keyLabel !== void 0 ? { keyLabel: v.keyLabel } : {},
4758
4800
  ...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
4759
4801
  compareWith: v.compareWith,
4760
- span: v.span
4802
+ span: v.span,
4803
+ emphasis: v.emphasis,
4804
+ noLabel: v.noLabel,
4805
+ role: v.role,
4806
+ editor: v.editor,
4807
+ activate: v.activate
4761
4808
  };
4762
4809
  return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
4763
4810
  }
@@ -4908,16 +4955,17 @@ function registerPreset(factory, base, presetOpts, over = {}) {
4908
4955
  function buildComposite(def, opts, parts) {
4909
4956
  const required = opts.required ?? false;
4910
4957
  const roleZod = def.roleZod(opts, parts);
4911
- const rowSchema = object(partsRowShape(roleZod, parts, required !== true));
4912
- const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, true)) : rowSchema;
4958
+ const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
4959
+ const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
4913
4960
  const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
4914
4961
  const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
4915
4962
  const clientKeys = def.absenceRoles ? roleKeys() : void 0;
4916
4963
  const zod = unknown().transform((raw, ctx) => {
4917
4964
  const parsed = jsonValue(raw);
4918
4965
  const p = parsed;
4966
+ const isRow = p == null || typeof p === "object" && !Array.isArray(p);
4919
4967
  /** Every one of `keys` empty in this value — `null`/absent alike. */
4920
- const allEmpty = (keys) => p == null || typeof p === "object" && keys.length > 0 && keys.every((k) => p[k] == null);
4968
+ const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
4921
4969
  let absent = false;
4922
4970
  if (absenceKeys && clientKeys) {
4923
4971
  absent = allEmpty(absenceKeys);
@@ -4991,6 +5039,7 @@ function materialize(decl, name) {
4991
5039
  const base = {
4992
5040
  kind: type.kind,
4993
5041
  label: opts.label ?? "",
5042
+ ...opts.agent !== void 0 && { agent: opts.agent },
4994
5043
  required,
4995
5044
  prim: type.prim,
4996
5045
  column: built.column,
@@ -5034,6 +5083,7 @@ function collectionGroup(key, opts, parts, collectionView) {
5034
5083
  multiple: true,
5035
5084
  required: opts.required ?? false,
5036
5085
  label: opts.label ?? key,
5086
+ ...opts.agent !== void 0 && { agent: opts.agent },
5037
5087
  display: "wrap",
5038
5088
  kind: collectionView,
5039
5089
  fields: rowFields
@@ -5157,15 +5207,14 @@ function materializeDef(def) {
5157
5207
  * open: a magnitude may be real/int/rating, a unit may be a select or a relation.
5158
5208
  */
5159
5209
  /**
5160
- * `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `view` property — the
5161
- * author's `view`/`ui` are flattened into individual fields (`hidden`, `display`, `kind`, …)
5162
- * by `normalizeOpts`. A correction that wants to carry "view" forward has to collect those
5163
- * flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
5210
+ * `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
5211
+ * author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
5212
+ * by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
5213
+ * those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
5164
5214
  */
5165
- var VIEW_KEYS = [
5215
+ var UI_KEYS = [
5166
5216
  "hidden",
5167
5217
  "noEditControl",
5168
- "noSearch",
5169
5218
  "display",
5170
5219
  "kind",
5171
5220
  "icon",
@@ -5176,11 +5225,11 @@ var VIEW_KEYS = [
5176
5225
  "span"
5177
5226
  ];
5178
5227
  /** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
5179
- * none were set, so a correction never adds an empty `view: {}` no filling ever had. */
5180
- function viewOf(opts) {
5228
+ * none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
5229
+ function uiOf(opts) {
5181
5230
  if (!opts) return void 0;
5182
5231
  const v = {};
5183
- for (const k of VIEW_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5232
+ for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5184
5233
  return Object.keys(v).length ? v : void 0;
5185
5234
  }
5186
5235
  /** A filling built from a ported factory arrives as a FieldDecl — build it into the
@@ -5203,7 +5252,7 @@ function accepts(def, d) {
5203
5252
  }
5204
5253
  /**
5205
5254
  * The correction every role gets for free: keep what the author said ABOUT the field
5206
- * (`label`, `required`, `view`, `value` — a constant or a ComputeFn), replace the type, and
5255
+ * (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
5207
5256
  * drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
5208
5257
  * picture — and `multiple`, because a composite role owns exactly one column). A role whose
5209
5258
  * own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
@@ -5216,7 +5265,8 @@ function retypeTo(factory) {
5216
5265
  label,
5217
5266
  required,
5218
5267
  value,
5219
- view: viewOf(d?.opts)
5268
+ noSearch: d?.opts.noSearch,
5269
+ ui: uiOf(d?.opts)
5220
5270
  });
5221
5271
  };
5222
5272
  }
@@ -5399,56 +5449,13 @@ function partColumns(parts) {
5399
5449
  }
5400
5450
  return cols;
5401
5451
  }
5402
- /**
5403
- * A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
5404
- * the composite's DEFAULT schema per role — the literal the factory writes by hand
5405
- * (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
5406
- * schema guards its (role-named) key:
5407
- *
5408
- * - a plain STORED part is keyed by its role name, matching the value object and the
5409
- * column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
5410
- * to its default filling keeps the default schema — byte-identical to the hand-written
5411
- * literal, so every existing structure/unit/range code and message is preserved.
5412
- * - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
5413
- * default schema for that role. This is what makes a RETYPED filling real: an
5414
- * `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
5415
- * and an `f.relation({…})` currency accepts a record id instead of being measured
5416
- * against an ISO-4217 string rule it was never meant to satisfy. The filling also
5417
- * brings its own optionality and its own min/max — an author replacing a filling
5418
- * takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
5419
- * replaced magnitude keeps a bound.
5420
- * - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
5421
- * other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
5422
- * whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
5423
- * moment later and the write path recomputes the column. It is stripped rather than
5424
- * `z.never()`-rejected because a read hands the client that key (it is a real column,
5425
- * present in every response), so rejecting it would break a read → PATCH-the-whole-object
5426
- * round trip.
5427
- * - a CLIENT-OWNED stored part of a composite that is not `required: true` also accepts
5428
- * an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
5429
- * for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
5430
- * magnitude column is set and whose unit column is empty reads back as
5431
- * `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
5432
- * empty — `0` is a value), and feeding that exact object back used to fail with
5433
- * `measured_structure`: the write path refused the shape its own read produced. The null
5434
- * slot now parses through and is written back as NULL, so the round trip is closed and
5435
- * clearing ONE role of a filled composite works instead of erroring.
5436
- * NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
5437
- * how "this record has no value there" arrives, while an ABSENT key is a writer that
5438
- * never mentioned a role it owns — which stays the structure error it has always been
5439
- * (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
5440
- * all-or-nothing rule for a DECLARATION, and it is untouched.
5441
- * A `required: true` composite keeps every client-owned slot MANDATORY, null included —
5442
- * "tolerate null" must not turn a required field optional, so an empty or half-empty
5443
- * value still fails with the same structure code it fails with today. A filling the
5444
- * author declared `required` keeps its own mandate too.
5445
- */
5446
- function partsRowShape(shape, parts, tolerateNull = false) {
5452
+ function partsRowShape(shape, parts, tolerance = "strict") {
5447
5453
  const out = { ...shape };
5448
5454
  for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
5449
5455
  else {
5450
5456
  const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
5451
- out[p.key] = tolerateNull && !(p.overridden && p.meta.required === true) ? own.nullable() : own;
5457
+ const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
5458
+ out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
5452
5459
  }
5453
5460
  return out;
5454
5461
  }
@@ -5643,7 +5650,7 @@ function internalApiToken(o) {
5643
5650
  label: o.label,
5644
5651
  multiple: true,
5645
5652
  rules: { unique: ["name"] },
5646
- view: { kind: "internalApiToken" },
5653
+ ui: { kind: "internalApiToken" },
5647
5654
  fields: {
5648
5655
  name: string({}),
5649
5656
  token: password({}),
@@ -5669,7 +5676,7 @@ function internalOauthGrants(o) {
5669
5676
  scope: "nest",
5670
5677
  label: o.label,
5671
5678
  multiple: true,
5672
- view: { kind: "internalOauthGrants" },
5679
+ ui: { kind: "internalOauthGrants" },
5673
5680
  fields: {
5674
5681
  clientName: string({}),
5675
5682
  createdAt: string({}),
@@ -6296,10 +6303,8 @@ function country(raw) {
6296
6303
  ...raw,
6297
6304
  options: "countries",
6298
6305
  strict: true,
6299
- view: {
6300
- ...raw.view ?? {},
6301
- noSearch: true
6302
- }
6306
+ ui: { ...raw.ui ?? {} },
6307
+ noSearch: true
6303
6308
  });
6304
6309
  }
6305
6310
  /**
@@ -6323,10 +6328,8 @@ function currency(raw) {
6323
6328
  ...raw,
6324
6329
  options: "currencies",
6325
6330
  strict: true,
6326
- view: {
6327
- ...raw.view ?? {},
6328
- noSearch: true
6329
- }
6331
+ ui: { ...raw.ui ?? {} },
6332
+ noSearch: true
6330
6333
  });
6331
6334
  }
6332
6335
  /**
@@ -6486,7 +6489,7 @@ function bento(opts) {
6486
6489
  label: opts.label,
6487
6490
  icon: opts.icon,
6488
6491
  fields,
6489
- view: { kind: "bento" }
6492
+ ui: { kind: "bento" }
6490
6493
  }),
6491
6494
  view: {
6492
6495
  kind: "bento",
@@ -6515,7 +6518,7 @@ function split(opts) {
6515
6518
  return {
6516
6519
  ...group({
6517
6520
  fields: merge("split", opts.columns[0], opts.columns[1]),
6518
- view: { kind: "split" }
6521
+ ui: { kind: "split" }
6519
6522
  }),
6520
6523
  view: {
6521
6524
  kind: "split",
@@ -6539,7 +6542,7 @@ function grid(opts) {
6539
6542
  label: opts.label,
6540
6543
  icon: opts.icon,
6541
6544
  fields: opts.fields,
6542
- view: { kind: "grid" }
6545
+ ui: { kind: "grid" }
6543
6546
  }),
6544
6547
  view: {
6545
6548
  kind: "grid",
@@ -6564,7 +6567,7 @@ function spread(opts) {
6564
6567
  return {
6565
6568
  ...group({
6566
6569
  fields: merge("spread", visual, quote, opts.facts),
6567
- view: { kind: "spread" }
6570
+ ui: { kind: "spread" }
6568
6571
  }),
6569
6572
  view: {
6570
6573
  kind: "spread",
@@ -6589,7 +6592,7 @@ function aside(opts) {
6589
6592
  return {
6590
6593
  ...group({
6591
6594
  fields: merge("aside", opts.fields, opts.aside),
6592
- view: { kind: "aside" }
6595
+ ui: { kind: "aside" }
6593
6596
  }),
6594
6597
  view: {
6595
6598
  kind: "aside",
@@ -6621,7 +6624,7 @@ function tabs(opts) {
6621
6624
  return {
6622
6625
  ...group({
6623
6626
  fields,
6624
- view: { kind: "tabs" }
6627
+ ui: { kind: "tabs" }
6625
6628
  }),
6626
6629
  view: {
6627
6630
  kind: "tabs",
@@ -6653,7 +6656,7 @@ function accordion(opts) {
6653
6656
  return {
6654
6657
  ...group({
6655
6658
  fields,
6656
- view: { kind: "accordion" }
6659
+ ui: { kind: "accordion" }
6657
6660
  }),
6658
6661
  view: {
6659
6662
  kind: "accordion",
@@ -6683,7 +6686,7 @@ function prose(opts) {
6683
6686
  label: opts.label,
6684
6687
  icon: opts.icon,
6685
6688
  fields: opts.fields,
6686
- view: { kind: "prose" }
6689
+ ui: { kind: "prose" }
6687
6690
  }),
6688
6691
  view: {
6689
6692
  kind: "prose",
@@ -6709,7 +6712,7 @@ function plaque(opts) {
6709
6712
  label: opts.label,
6710
6713
  icon: opts.icon,
6711
6714
  fields: merge("plaque", title, opts.subtitle),
6712
- view: { kind: "plaque" }
6715
+ ui: { kind: "plaque" }
6713
6716
  }),
6714
6717
  view: {
6715
6718
  kind: "plaque",
@@ -6732,7 +6735,7 @@ function ledger(opts) {
6732
6735
  label: opts.label,
6733
6736
  icon: opts.icon,
6734
6737
  fields: opts.fields,
6735
- view: { kind: "ledger" }
6738
+ ui: { kind: "ledger" }
6736
6739
  }),
6737
6740
  view: { kind: "ledger" }
6738
6741
  };
@@ -6751,7 +6754,7 @@ function stats(opts) {
6751
6754
  label: opts.label,
6752
6755
  icon: opts.icon,
6753
6756
  fields: opts.fields,
6754
- view: { kind: "stats" }
6757
+ ui: { kind: "stats" }
6755
6758
  }),
6756
6759
  view: { kind: "stats" }
6757
6760
  };
@@ -6770,7 +6773,7 @@ function facets(opts) {
6770
6773
  label: opts.label,
6771
6774
  icon: opts.icon,
6772
6775
  fields: opts.fields,
6773
- view: { kind: "facets" }
6776
+ ui: { kind: "facets" }
6774
6777
  }),
6775
6778
  view: { kind: "facets" }
6776
6779
  };
@@ -6789,7 +6792,7 @@ function terminal(opts) {
6789
6792
  label: opts.label,
6790
6793
  icon: opts.icon,
6791
6794
  fields: opts.fields,
6792
- view: { kind: "terminal" }
6795
+ ui: { kind: "terminal" }
6793
6796
  }),
6794
6797
  view: {
6795
6798
  kind: "terminal",
@@ -6811,7 +6814,7 @@ function callout(opts) {
6811
6814
  label: opts.label,
6812
6815
  icon: opts.icon,
6813
6816
  fields: opts.fields,
6814
- view: { kind: "callout" }
6817
+ ui: { kind: "callout" }
6815
6818
  }),
6816
6819
  view: {
6817
6820
  kind: "callout",
@@ -6836,7 +6839,7 @@ function compare(opts) {
6836
6839
  label: opts.label,
6837
6840
  icon: opts.icon,
6838
6841
  fields: merge("compare", opts.left, opts.right),
6839
- view: { kind: "compare" }
6842
+ ui: { kind: "compare" }
6840
6843
  }),
6841
6844
  view: {
6842
6845
  kind: "compare",
@@ -6867,7 +6870,7 @@ function figure(opts) {
6867
6870
  return {
6868
6871
  ...group({
6869
6872
  fields: merge("figure", image, caption),
6870
- view: { kind: "figure" }
6873
+ ui: { kind: "figure" }
6871
6874
  }),
6872
6875
  view: {
6873
6876
  kind: "figure",
@@ -6892,7 +6895,7 @@ function epigraph(opts) {
6892
6895
  return {
6893
6896
  ...group({
6894
6897
  fields: merge("epigraph", source, attribution),
6895
- view: { kind: "epigraph" }
6898
+ ui: { kind: "epigraph" }
6896
6899
  }),
6897
6900
  view: {
6898
6901
  kind: "epigraph",
@@ -6918,7 +6921,7 @@ function masthead(opts) {
6918
6921
  return {
6919
6922
  ...group({
6920
6923
  fields: merge("masthead", overline, title, opts.meta),
6921
- view: { kind: "masthead" }
6924
+ ui: { kind: "masthead" }
6922
6925
  }),
6923
6926
  view: {
6924
6927
  kind: "masthead",
@@ -6942,7 +6945,7 @@ function timeline(opts) {
6942
6945
  label: opts.label,
6943
6946
  icon: opts.icon,
6944
6947
  fields: opts.fields,
6945
- view: { kind: "timeline" }
6948
+ ui: { kind: "timeline" }
6946
6949
  }),
6947
6950
  view: { kind: "timeline" }
6948
6951
  };
@@ -6961,7 +6964,7 @@ function countdown(opts) {
6961
6964
  ...group({
6962
6965
  label: opts.label,
6963
6966
  fields: source,
6964
- view: { kind: "countdown" }
6967
+ ui: { kind: "countdown" }
6965
6968
  }),
6966
6969
  view: {
6967
6970
  kind: "countdown",
@@ -6984,7 +6987,7 @@ function deck(opts) {
6984
6987
  ...group({
6985
6988
  label: opts.label,
6986
6989
  fields: source,
6987
- view: { kind: "deck" }
6990
+ ui: { kind: "deck" }
6988
6991
  }),
6989
6992
  view: {
6990
6993
  kind: "deck",
@@ -7006,7 +7009,7 @@ function people(opts) {
7006
7009
  ...group({
7007
7010
  label: opts.label,
7008
7011
  fields: source,
7009
- view: { kind: "people" }
7012
+ ui: { kind: "people" }
7010
7013
  }),
7011
7014
  view: {
7012
7015
  kind: "people",
@@ -7032,7 +7035,7 @@ function identity(opts) {
7032
7035
  return {
7033
7036
  ...group({
7034
7037
  fields: merge("identity", avatar, name, role, opts.channels),
7035
- view: { kind: "identity" }
7038
+ ui: { kind: "identity" }
7036
7039
  }),
7037
7040
  view: {
7038
7041
  kind: "identity",
@@ -7058,7 +7061,7 @@ function score(opts) {
7058
7061
  ...group({
7059
7062
  label: opts.label,
7060
7063
  fields: merge("score", source, verdict),
7061
- view: { kind: "score" }
7064
+ ui: { kind: "score" }
7062
7065
  }),
7063
7066
  view: {
7064
7067
  kind: "score",
@@ -7083,7 +7086,7 @@ function status(opts) {
7083
7086
  ...group({
7084
7087
  label: opts.label,
7085
7088
  fields: merge("status", source, since),
7086
- view: { kind: "status" }
7089
+ ui: { kind: "status" }
7087
7090
  }),
7088
7091
  view: {
7089
7092
  kind: "status",
@@ -7107,7 +7110,7 @@ function meter(opts) {
7107
7110
  ...group({
7108
7111
  label: opts.label,
7109
7112
  fields: merge("meter", source, of),
7110
- view: { kind: "meter" }
7113
+ ui: { kind: "meter" }
7111
7114
  }),
7112
7115
  view: {
7113
7116
  kind: "meter",
@@ -7134,7 +7137,7 @@ function receipt(opts) {
7134
7137
  ...group({
7135
7138
  label: opts.label,
7136
7139
  fields: merge("receipt", opts.fields, total),
7137
- view: { kind: "receipt" }
7140
+ ui: { kind: "receipt" }
7138
7141
  }),
7139
7142
  view: {
7140
7143
  kind: "receipt",
@@ -7157,7 +7160,7 @@ function balance(opts) {
7157
7160
  ...group({
7158
7161
  label: opts.label,
7159
7162
  fields: source,
7160
- view: { kind: "balance" }
7163
+ ui: { kind: "balance" }
7161
7164
  }),
7162
7165
  view: {
7163
7166
  kind: "balance",
@@ -7185,7 +7188,7 @@ function route(opts) {
7185
7188
  return {
7186
7189
  ...group({
7187
7190
  fields: merge("route", from, to, depart, arrive, duration),
7188
- view: { kind: "route" }
7191
+ ui: { kind: "route" }
7189
7192
  }),
7190
7193
  view: {
7191
7194
  kind: "route",
@@ -7334,7 +7337,7 @@ function nutrition(opts) {
7334
7337
  ...group({
7335
7338
  label: opts.label,
7336
7339
  fields: merge("nutrition", energy, opts.fields),
7337
- view: { kind: "nutrition" }
7340
+ ui: { kind: "nutrition" }
7338
7341
  }),
7339
7342
  view: {
7340
7343
  kind: "nutrition",
@@ -7360,7 +7363,7 @@ function specimen(opts) {
7360
7363
  return {
7361
7364
  ...group({
7362
7365
  fields: merge("specimen", title, subtitle, opts.conditions),
7363
- view: { kind: "specimen" }
7366
+ ui: { kind: "specimen" }
7364
7367
  }),
7365
7368
  view: {
7366
7369
  kind: "specimen",
@@ -7437,7 +7440,7 @@ function idcard(opts) {
7437
7440
  return {
7438
7441
  ...group({
7439
7442
  fields: merge("idcard", overline, number, opts.meta),
7440
- view: { kind: "idcard" }
7443
+ ui: { kind: "idcard" }
7441
7444
  }),
7442
7445
  view: {
7443
7446
  kind: "idcard",
@@ -7463,7 +7466,7 @@ function properties(opts) {
7463
7466
  label: opts.label,
7464
7467
  icon: opts.icon,
7465
7468
  fields: opts.fields,
7466
- view: { kind: "properties" }
7469
+ ui: { kind: "properties" }
7467
7470
  }),
7468
7471
  view: {
7469
7472
  kind: "properties",
@@ -7485,7 +7488,7 @@ function stack(opts) {
7485
7488
  label: opts.label,
7486
7489
  icon: opts.icon,
7487
7490
  fields: opts.fields,
7488
- view: { kind: "stack" }
7491
+ ui: { kind: "stack" }
7489
7492
  }),
7490
7493
  view: { kind: "stack" }
7491
7494
  };
@@ -8381,8 +8384,9 @@ var isNamedField = (x) => hasValue(x) && !hasChildren(x) && x.key !== void 0;
8381
8384
  * @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
8382
8385
  */
8383
8386
  function group(o) {
8387
+ 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).");
8384
8388
  const r = o.rules ?? {};
8385
- const v = o.view ?? {};
8389
+ const v = o.ui ?? {};
8386
8390
  const fields = materializeTree(o.fields);
8387
8391
  const scope = o.scope ?? "hoist";
8388
8392
  if (scope === "nest") {
@@ -8428,7 +8432,7 @@ function row(o) {
8428
8432
  multiple: o.multiple,
8429
8433
  required: o.required,
8430
8434
  rules: o.rules,
8431
- view: { display: "scroll" }
8435
+ ui: { display: "scroll" }
8432
8436
  });
8433
8437
  }
8434
8438
  /**
@@ -8456,7 +8460,7 @@ function sheet(o) {
8456
8460
  label: o.label,
8457
8461
  icon: o.icon,
8458
8462
  fields: rows,
8459
- view: { display: "sheet" }
8463
+ ui: { display: "sheet" }
8460
8464
  });
8461
8465
  }
8462
8466
  /**
@@ -8475,7 +8479,7 @@ function url(o) {
8475
8479
  scope: "nest",
8476
8480
  label: o.label,
8477
8481
  required: o.required,
8478
- view: { kind: "url" },
8482
+ ui: { kind: "url" },
8479
8483
  fields: {
8480
8484
  scheme: string({}),
8481
8485
  username: string({}),
@@ -8620,13 +8624,15 @@ registerType("string", {
8620
8624
  build(o) {
8621
8625
  const cfg = o.config ?? {};
8622
8626
  const min = cfg.min ?? (o.required === true ? 1 : 0);
8623
- let s = string$1(reqTypeErr());
8624
- if (min > 0) s = s.min(min, { message: vmsg("min_length", { min }) });
8625
- if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
8626
- if (cfg.pattern) s = s.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
8627
8627
  return {
8628
8628
  column: "text",
8629
- zod: s,
8629
+ zod: stringContent((str) => {
8630
+ let c = str;
8631
+ if (min > 0) c = c.min(min, { message: vmsg("min_length", { min }) });
8632
+ if (cfg.max != null) c = c.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
8633
+ if (cfg.pattern) c = c.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
8634
+ return c;
8635
+ }),
8630
8636
  hints: {
8631
8637
  minLength: cfg.min,
8632
8638
  maxLength: cfg.max
@@ -8647,9 +8653,7 @@ function string(o) {
8647
8653
  }
8648
8654
  /** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
8649
8655
  function pathZod(o) {
8650
- let s = string$1(reqTypeErr());
8651
- if (o.required === true) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
8652
- return s;
8656
+ return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
8653
8657
  }
8654
8658
  registerType("localDir", {
8655
8659
  kind: "localDir",
@@ -9311,7 +9315,8 @@ var MEASURED_ROLES = (opts) => {
9311
9315
  label,
9312
9316
  required,
9313
9317
  value,
9314
- view: viewOf(d?.opts),
9318
+ noSearch: d?.opts.noSearch,
9319
+ ui: uiOf(d?.opts),
9315
9320
  options: opts.units,
9316
9321
  strict: true
9317
9322
  });
@@ -9680,7 +9685,7 @@ function periodMkSubDecl(granularity) {
9680
9685
  * (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
9681
9686
  * filling still keeps the composite's own granularity (`canonical.opts`) — the same
9682
9687
  * `date`/`datetime` config the role's own default carries — with the author's
9683
- * label/required/view/value layered on top, exactly like `retypeTo`'s default.
9688
+ * label/required/ui/value layered on top, exactly like `retypeTo`'s default.
9684
9689
  */
9685
9690
  function periodRole(key, label, mkSubDecl) {
9686
9691
  const def = {
@@ -9696,7 +9701,8 @@ function periodRole(key, label, mkSubDecl) {
9696
9701
  label: l ?? canonical.opts.label,
9697
9702
  required,
9698
9703
  value,
9699
- view: viewOf(d.opts)
9704
+ noSearch: d.opts.noSearch,
9705
+ ui: uiOf(d.opts)
9700
9706
  });
9701
9707
  }
9702
9708
  };
@@ -10164,10 +10170,11 @@ var field = new Proxy({}, {
10164
10170
  has: (_t, k) => k in composedField()
10165
10171
  });
10166
10172
  function toClient(field) {
10167
- const { kind, label, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
10173
+ const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
10168
10174
  return {
10169
10175
  kind,
10170
10176
  label,
10177
+ ...agent !== void 0 && { agent },
10171
10178
  required,
10172
10179
  prim,
10173
10180
  hints,
@@ -10960,7 +10967,7 @@ function defineShelf(m) {
10960
10967
  const keys = fieldEntries(built.fields).map(([k]) => k);
10961
10968
  const dup = keys.find((k, i) => keys.indexOf(k) !== i);
10962
10969
  if (dup) throw new Error(`[shelf] ${built.library}/${built.shelf}: duplicate key '${dup}'`);
10963
- if (built.single && !built.claude) throw new Error(`[shelf] ${built.library}/${built.shelf}: single shelf requires \`claude\` — the agent cannot find it otherwise`);
10970
+ if (built.single && !built.agent) throw new Error(`[shelf] ${built.library}/${built.shelf}: single shelf requires \`agent\` — the agent cannot find it otherwise`);
10964
10971
  if (built.views?.list !== void 0 && !Array.isArray(built.views.list)) throw new Error(`[shelf] ${built.library}/${built.shelf}: views.list is a flat array of field keys — the { kind, fields } form is gone; use views.list: [...] plus views.listKind`);
10965
10972
  if (built.standalone !== false && !built.single && !built.views?.list?.length) console.warn(`[shelf] ${built.library}/${built.shelf}: standalone shelf without an explicit views.list`);
10966
10973
  if (built.views?.title && !ownFieldEntries(built.fields).some(([k]) => k === built.views.title)) throw new Error(`[shelf] ${built.library}/${built.shelf}: views.title '${built.views.title}' is not a field`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-transit",
3
- "version": "4.0.0",
3
+ "version": "6.0.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -26,9 +26,9 @@
26
26
  "test": "node --import tsx/esm --test 'src/runtime/*.test.ts'"
27
27
  },
28
28
  "dependencies": {
29
- "@coffer-org/sdk": "^4.0.0",
30
- "@coffer-org/server": "^4.0.0",
31
- "@coffer-org/helper-dav": "^4.0.0"
29
+ "@coffer-org/sdk": "^6.0.0",
30
+ "@coffer-org/server": "^6.0.0",
31
+ "@coffer-org/helper-dav": "^6.0.0"
32
32
  },
33
33
  "coffer": {
34
34
  "runtime": "node",