@coffer-org/plugin-documents 4.0.0 → 5.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.
@@ -12,7 +12,7 @@ nationality — ISO alpha-2. variants — given/family name per language (field.
12
12
  label: 'documents.person_identity.fields.nationality',
13
13
  options: 'countries',
14
14
  strict: true,
15
- view: { noSearch: true },
15
+ noSearch: true,
16
16
  }),
17
17
  variants: field.keyed({
18
18
  label: 'documents.person_identity.fields.variants',
@@ -22,7 +22,7 @@ nationality — ISO alpha-2. variants — given/family name per language (field.
22
22
  label: 'documents.person_identity.variants.lang',
23
23
  options: 'languages',
24
24
  strict: true,
25
- view: { noSearch: true },
25
+ noSearch: true,
26
26
  }),
27
27
  name: field.string({ label: 'documents.person_identity.variants.name' }),
28
28
  surname: field.string({ label: 'documents.person_identity.variants.surname' }),
@@ -12,7 +12,7 @@ export default defineShelf({
12
12
  name: field.title({ label: 'core.fields.name' }),
13
13
  about: field.illustrated({
14
14
  label: 'documents.personal_document.fields.about',
15
- fields: { image: field.avatar({ role: 'avatar' }), text: field.text({ rules: { max: 1000 } }) },
15
+ fields: { image: field.avatar({ ui: { role: 'avatar' } }), text: field.text({ rules: { max: 1000 } }) },
16
16
  }),
17
17
  idcard: field.idcard({
18
18
  number: { number: field.string({ label: 'documents.personal_document.fields.number', rules: { max: 50 } }) },
@@ -38,7 +38,7 @@ export default defineShelf({
38
38
  sex: field.string({
39
39
  label: 'documents.personal_document.fields.sex',
40
40
  strict: true,
41
- view: { noSearch: true },
41
+ noSearch: true,
42
42
  options: [
43
43
  { value: 'M', title: 'documents.personal_document.options.sex.M' },
44
44
  { value: 'F', title: 'documents.personal_document.options.sex.F' },
@@ -49,7 +49,7 @@ export default defineShelf({
49
49
  label: 'documents.personal_document.fields.nationality',
50
50
  options: 'countries',
51
51
  strict: true,
52
- view: { noSearch: true },
52
+ noSearch: true,
53
53
  ui: { span: 4 },
54
54
  }),
55
55
  owner: field.relation({
@@ -73,7 +73,7 @@ export default defineShelf({
73
73
  label: 'core.fields.type',
74
74
  required: true,
75
75
  strict: true,
76
- view: { noSearch: true },
76
+ noSearch: true,
77
77
  options: [
78
78
  { value: 'P', title: 'documents.personal_document.options.type.P' },
79
79
  { value: 'PA', title: 'documents.personal_document.options.type.PA' },
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
@@ -4721,11 +4758,9 @@ function wrapKey(key, opts, meta) {
4721
4758
  //#region ../sdk/src/fields/normalize.ts
4722
4759
  function normalizeOpts(rawIn) {
4723
4760
  const raw = rawIn;
4761
+ 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).");
4724
4762
  const r = raw.rules ?? {};
4725
- const v = {
4726
- ...raw.view ?? {},
4727
- ...raw.ui ?? {}
4728
- };
4763
+ const v = raw.ui ?? {};
4729
4764
  const config = {};
4730
4765
  if (r.min != null && typeof r.min === "number") config.min = r.min;
4731
4766
  if (r.max != null && typeof r.max === "number") config.max = r.max;
@@ -4734,7 +4769,7 @@ function normalizeOpts(rawIn) {
4734
4769
  if (r.messageKey != null) config.messageKey = r.messageKey;
4735
4770
  if (r.granularity != null) config.granularity = r.granularity;
4736
4771
  if (r.language != null) config.language = r.language;
4737
- const { rules: _r, view: _v, ui: _ui, ...flat } = raw;
4772
+ const { rules: _r, ui: _ui, ...flat } = raw;
4738
4773
  const result = {
4739
4774
  ...flat,
4740
4775
  ...Object.keys(config).length ? { config } : {},
@@ -4749,7 +4784,6 @@ function normalizeOpts(rawIn) {
4749
4784
  step: r.step,
4750
4785
  hidden: v.hidden,
4751
4786
  noEditControl: v.noEditControl,
4752
- noSearch: v.noSearch,
4753
4787
  display: v.display,
4754
4788
  kind: v.kind,
4755
4789
  icon: v.icon,
@@ -4757,7 +4791,11 @@ function normalizeOpts(rawIn) {
4757
4791
  ...v.keyLabel !== void 0 ? { keyLabel: v.keyLabel } : {},
4758
4792
  ...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
4759
4793
  compareWith: v.compareWith,
4760
- span: v.span
4794
+ span: v.span,
4795
+ emphasis: v.emphasis,
4796
+ noLabel: v.noLabel,
4797
+ role: v.role,
4798
+ editor: v.editor
4761
4799
  };
4762
4800
  return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
4763
4801
  }
@@ -4908,16 +4946,17 @@ function registerPreset(factory, base, presetOpts, over = {}) {
4908
4946
  function buildComposite(def, opts, parts) {
4909
4947
  const required = opts.required ?? false;
4910
4948
  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;
4949
+ const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
4950
+ const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
4913
4951
  const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
4914
4952
  const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
4915
4953
  const clientKeys = def.absenceRoles ? roleKeys() : void 0;
4916
4954
  const zod = unknown().transform((raw, ctx) => {
4917
4955
  const parsed = jsonValue(raw);
4918
4956
  const p = parsed;
4957
+ const isRow = p == null || typeof p === "object" && !Array.isArray(p);
4919
4958
  /** 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);
4959
+ const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
4921
4960
  let absent = false;
4922
4961
  if (absenceKeys && clientKeys) {
4923
4962
  absent = allEmpty(absenceKeys);
@@ -5157,15 +5196,14 @@ function materializeDef(def) {
5157
5196
  * open: a magnitude may be real/int/rating, a unit may be a select or a relation.
5158
5197
  */
5159
5198
  /**
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.
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.
5164
5203
  */
5165
- var VIEW_KEYS = [
5204
+ var UI_KEYS = [
5166
5205
  "hidden",
5167
5206
  "noEditControl",
5168
- "noSearch",
5169
5207
  "display",
5170
5208
  "kind",
5171
5209
  "icon",
@@ -5176,11 +5214,11 @@ var VIEW_KEYS = [
5176
5214
  "span"
5177
5215
  ];
5178
5216
  /** 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) {
5217
+ * none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
5218
+ function uiOf(opts) {
5181
5219
  if (!opts) return void 0;
5182
5220
  const v = {};
5183
- for (const k of VIEW_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5221
+ for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
5184
5222
  return Object.keys(v).length ? v : void 0;
5185
5223
  }
5186
5224
  /** A filling built from a ported factory arrives as a FieldDecl — build it into the
@@ -5203,7 +5241,7 @@ function accepts(def, d) {
5203
5241
  }
5204
5242
  /**
5205
5243
  * 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
5244
+ * (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
5207
5245
  * drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
5208
5246
  * picture — and `multiple`, because a composite role owns exactly one column). A role whose
5209
5247
  * own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
@@ -5216,7 +5254,8 @@ function retypeTo(factory) {
5216
5254
  label,
5217
5255
  required,
5218
5256
  value,
5219
- view: viewOf(d?.opts)
5257
+ noSearch: d?.opts.noSearch,
5258
+ ui: uiOf(d?.opts)
5220
5259
  });
5221
5260
  };
5222
5261
  }
@@ -5399,56 +5438,13 @@ function partColumns(parts) {
5399
5438
  }
5400
5439
  return cols;
5401
5440
  }
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) {
5441
+ function partsRowShape(shape, parts, tolerance = "strict") {
5447
5442
  const out = { ...shape };
5448
5443
  for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
5449
5444
  else {
5450
5445
  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;
5446
+ const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
5447
+ out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
5452
5448
  }
5453
5449
  return out;
5454
5450
  }
@@ -5643,7 +5639,7 @@ function internalApiToken(o) {
5643
5639
  label: o.label,
5644
5640
  multiple: true,
5645
5641
  rules: { unique: ["name"] },
5646
- view: { kind: "internalApiToken" },
5642
+ ui: { kind: "internalApiToken" },
5647
5643
  fields: {
5648
5644
  name: string({}),
5649
5645
  token: password({}),
@@ -5669,7 +5665,7 @@ function internalOauthGrants(o) {
5669
5665
  scope: "nest",
5670
5666
  label: o.label,
5671
5667
  multiple: true,
5672
- view: { kind: "internalOauthGrants" },
5668
+ ui: { kind: "internalOauthGrants" },
5673
5669
  fields: {
5674
5670
  clientName: string({}),
5675
5671
  createdAt: string({}),
@@ -6296,10 +6292,8 @@ function country(raw) {
6296
6292
  ...raw,
6297
6293
  options: "countries",
6298
6294
  strict: true,
6299
- view: {
6300
- ...raw.view ?? {},
6301
- noSearch: true
6302
- }
6295
+ ui: { ...raw.ui ?? {} },
6296
+ noSearch: true
6303
6297
  });
6304
6298
  }
6305
6299
  /**
@@ -6323,10 +6317,8 @@ function currency(raw) {
6323
6317
  ...raw,
6324
6318
  options: "currencies",
6325
6319
  strict: true,
6326
- view: {
6327
- ...raw.view ?? {},
6328
- noSearch: true
6329
- }
6320
+ ui: { ...raw.ui ?? {} },
6321
+ noSearch: true
6330
6322
  });
6331
6323
  }
6332
6324
  /**
@@ -6486,7 +6478,7 @@ function bento(opts) {
6486
6478
  label: opts.label,
6487
6479
  icon: opts.icon,
6488
6480
  fields,
6489
- view: { kind: "bento" }
6481
+ ui: { kind: "bento" }
6490
6482
  }),
6491
6483
  view: {
6492
6484
  kind: "bento",
@@ -6515,7 +6507,7 @@ function split(opts) {
6515
6507
  return {
6516
6508
  ...group({
6517
6509
  fields: merge("split", opts.columns[0], opts.columns[1]),
6518
- view: { kind: "split" }
6510
+ ui: { kind: "split" }
6519
6511
  }),
6520
6512
  view: {
6521
6513
  kind: "split",
@@ -6539,7 +6531,7 @@ function grid(opts) {
6539
6531
  label: opts.label,
6540
6532
  icon: opts.icon,
6541
6533
  fields: opts.fields,
6542
- view: { kind: "grid" }
6534
+ ui: { kind: "grid" }
6543
6535
  }),
6544
6536
  view: {
6545
6537
  kind: "grid",
@@ -6564,7 +6556,7 @@ function spread(opts) {
6564
6556
  return {
6565
6557
  ...group({
6566
6558
  fields: merge("spread", visual, quote, opts.facts),
6567
- view: { kind: "spread" }
6559
+ ui: { kind: "spread" }
6568
6560
  }),
6569
6561
  view: {
6570
6562
  kind: "spread",
@@ -6589,7 +6581,7 @@ function aside(opts) {
6589
6581
  return {
6590
6582
  ...group({
6591
6583
  fields: merge("aside", opts.fields, opts.aside),
6592
- view: { kind: "aside" }
6584
+ ui: { kind: "aside" }
6593
6585
  }),
6594
6586
  view: {
6595
6587
  kind: "aside",
@@ -6621,7 +6613,7 @@ function tabs(opts) {
6621
6613
  return {
6622
6614
  ...group({
6623
6615
  fields,
6624
- view: { kind: "tabs" }
6616
+ ui: { kind: "tabs" }
6625
6617
  }),
6626
6618
  view: {
6627
6619
  kind: "tabs",
@@ -6653,7 +6645,7 @@ function accordion(opts) {
6653
6645
  return {
6654
6646
  ...group({
6655
6647
  fields,
6656
- view: { kind: "accordion" }
6648
+ ui: { kind: "accordion" }
6657
6649
  }),
6658
6650
  view: {
6659
6651
  kind: "accordion",
@@ -6683,7 +6675,7 @@ function prose(opts) {
6683
6675
  label: opts.label,
6684
6676
  icon: opts.icon,
6685
6677
  fields: opts.fields,
6686
- view: { kind: "prose" }
6678
+ ui: { kind: "prose" }
6687
6679
  }),
6688
6680
  view: {
6689
6681
  kind: "prose",
@@ -6709,7 +6701,7 @@ function plaque(opts) {
6709
6701
  label: opts.label,
6710
6702
  icon: opts.icon,
6711
6703
  fields: merge("plaque", title, opts.subtitle),
6712
- view: { kind: "plaque" }
6704
+ ui: { kind: "plaque" }
6713
6705
  }),
6714
6706
  view: {
6715
6707
  kind: "plaque",
@@ -6732,7 +6724,7 @@ function ledger(opts) {
6732
6724
  label: opts.label,
6733
6725
  icon: opts.icon,
6734
6726
  fields: opts.fields,
6735
- view: { kind: "ledger" }
6727
+ ui: { kind: "ledger" }
6736
6728
  }),
6737
6729
  view: { kind: "ledger" }
6738
6730
  };
@@ -6751,7 +6743,7 @@ function stats(opts) {
6751
6743
  label: opts.label,
6752
6744
  icon: opts.icon,
6753
6745
  fields: opts.fields,
6754
- view: { kind: "stats" }
6746
+ ui: { kind: "stats" }
6755
6747
  }),
6756
6748
  view: { kind: "stats" }
6757
6749
  };
@@ -6770,7 +6762,7 @@ function facets(opts) {
6770
6762
  label: opts.label,
6771
6763
  icon: opts.icon,
6772
6764
  fields: opts.fields,
6773
- view: { kind: "facets" }
6765
+ ui: { kind: "facets" }
6774
6766
  }),
6775
6767
  view: { kind: "facets" }
6776
6768
  };
@@ -6789,7 +6781,7 @@ function terminal(opts) {
6789
6781
  label: opts.label,
6790
6782
  icon: opts.icon,
6791
6783
  fields: opts.fields,
6792
- view: { kind: "terminal" }
6784
+ ui: { kind: "terminal" }
6793
6785
  }),
6794
6786
  view: {
6795
6787
  kind: "terminal",
@@ -6811,7 +6803,7 @@ function callout(opts) {
6811
6803
  label: opts.label,
6812
6804
  icon: opts.icon,
6813
6805
  fields: opts.fields,
6814
- view: { kind: "callout" }
6806
+ ui: { kind: "callout" }
6815
6807
  }),
6816
6808
  view: {
6817
6809
  kind: "callout",
@@ -6836,7 +6828,7 @@ function compare(opts) {
6836
6828
  label: opts.label,
6837
6829
  icon: opts.icon,
6838
6830
  fields: merge("compare", opts.left, opts.right),
6839
- view: { kind: "compare" }
6831
+ ui: { kind: "compare" }
6840
6832
  }),
6841
6833
  view: {
6842
6834
  kind: "compare",
@@ -6867,7 +6859,7 @@ function figure(opts) {
6867
6859
  return {
6868
6860
  ...group({
6869
6861
  fields: merge("figure", image, caption),
6870
- view: { kind: "figure" }
6862
+ ui: { kind: "figure" }
6871
6863
  }),
6872
6864
  view: {
6873
6865
  kind: "figure",
@@ -6892,7 +6884,7 @@ function epigraph(opts) {
6892
6884
  return {
6893
6885
  ...group({
6894
6886
  fields: merge("epigraph", source, attribution),
6895
- view: { kind: "epigraph" }
6887
+ ui: { kind: "epigraph" }
6896
6888
  }),
6897
6889
  view: {
6898
6890
  kind: "epigraph",
@@ -6918,7 +6910,7 @@ function masthead(opts) {
6918
6910
  return {
6919
6911
  ...group({
6920
6912
  fields: merge("masthead", overline, title, opts.meta),
6921
- view: { kind: "masthead" }
6913
+ ui: { kind: "masthead" }
6922
6914
  }),
6923
6915
  view: {
6924
6916
  kind: "masthead",
@@ -6942,7 +6934,7 @@ function timeline(opts) {
6942
6934
  label: opts.label,
6943
6935
  icon: opts.icon,
6944
6936
  fields: opts.fields,
6945
- view: { kind: "timeline" }
6937
+ ui: { kind: "timeline" }
6946
6938
  }),
6947
6939
  view: { kind: "timeline" }
6948
6940
  };
@@ -6961,7 +6953,7 @@ function countdown(opts) {
6961
6953
  ...group({
6962
6954
  label: opts.label,
6963
6955
  fields: source,
6964
- view: { kind: "countdown" }
6956
+ ui: { kind: "countdown" }
6965
6957
  }),
6966
6958
  view: {
6967
6959
  kind: "countdown",
@@ -6984,7 +6976,7 @@ function deck(opts) {
6984
6976
  ...group({
6985
6977
  label: opts.label,
6986
6978
  fields: source,
6987
- view: { kind: "deck" }
6979
+ ui: { kind: "deck" }
6988
6980
  }),
6989
6981
  view: {
6990
6982
  kind: "deck",
@@ -7006,7 +6998,7 @@ function people(opts) {
7006
6998
  ...group({
7007
6999
  label: opts.label,
7008
7000
  fields: source,
7009
- view: { kind: "people" }
7001
+ ui: { kind: "people" }
7010
7002
  }),
7011
7003
  view: {
7012
7004
  kind: "people",
@@ -7032,7 +7024,7 @@ function identity(opts) {
7032
7024
  return {
7033
7025
  ...group({
7034
7026
  fields: merge("identity", avatar, name, role, opts.channels),
7035
- view: { kind: "identity" }
7027
+ ui: { kind: "identity" }
7036
7028
  }),
7037
7029
  view: {
7038
7030
  kind: "identity",
@@ -7058,7 +7050,7 @@ function score(opts) {
7058
7050
  ...group({
7059
7051
  label: opts.label,
7060
7052
  fields: merge("score", source, verdict),
7061
- view: { kind: "score" }
7053
+ ui: { kind: "score" }
7062
7054
  }),
7063
7055
  view: {
7064
7056
  kind: "score",
@@ -7083,7 +7075,7 @@ function status(opts) {
7083
7075
  ...group({
7084
7076
  label: opts.label,
7085
7077
  fields: merge("status", source, since),
7086
- view: { kind: "status" }
7078
+ ui: { kind: "status" }
7087
7079
  }),
7088
7080
  view: {
7089
7081
  kind: "status",
@@ -7107,7 +7099,7 @@ function meter(opts) {
7107
7099
  ...group({
7108
7100
  label: opts.label,
7109
7101
  fields: merge("meter", source, of),
7110
- view: { kind: "meter" }
7102
+ ui: { kind: "meter" }
7111
7103
  }),
7112
7104
  view: {
7113
7105
  kind: "meter",
@@ -7134,7 +7126,7 @@ function receipt(opts) {
7134
7126
  ...group({
7135
7127
  label: opts.label,
7136
7128
  fields: merge("receipt", opts.fields, total),
7137
- view: { kind: "receipt" }
7129
+ ui: { kind: "receipt" }
7138
7130
  }),
7139
7131
  view: {
7140
7132
  kind: "receipt",
@@ -7157,7 +7149,7 @@ function balance(opts) {
7157
7149
  ...group({
7158
7150
  label: opts.label,
7159
7151
  fields: source,
7160
- view: { kind: "balance" }
7152
+ ui: { kind: "balance" }
7161
7153
  }),
7162
7154
  view: {
7163
7155
  kind: "balance",
@@ -7185,7 +7177,7 @@ function route(opts) {
7185
7177
  return {
7186
7178
  ...group({
7187
7179
  fields: merge("route", from, to, depart, arrive, duration),
7188
- view: { kind: "route" }
7180
+ ui: { kind: "route" }
7189
7181
  }),
7190
7182
  view: {
7191
7183
  kind: "route",
@@ -7334,7 +7326,7 @@ function nutrition(opts) {
7334
7326
  ...group({
7335
7327
  label: opts.label,
7336
7328
  fields: merge("nutrition", energy, opts.fields),
7337
- view: { kind: "nutrition" }
7329
+ ui: { kind: "nutrition" }
7338
7330
  }),
7339
7331
  view: {
7340
7332
  kind: "nutrition",
@@ -7360,7 +7352,7 @@ function specimen(opts) {
7360
7352
  return {
7361
7353
  ...group({
7362
7354
  fields: merge("specimen", title, subtitle, opts.conditions),
7363
- view: { kind: "specimen" }
7355
+ ui: { kind: "specimen" }
7364
7356
  }),
7365
7357
  view: {
7366
7358
  kind: "specimen",
@@ -7437,7 +7429,7 @@ function idcard(opts) {
7437
7429
  return {
7438
7430
  ...group({
7439
7431
  fields: merge("idcard", overline, number, opts.meta),
7440
- view: { kind: "idcard" }
7432
+ ui: { kind: "idcard" }
7441
7433
  }),
7442
7434
  view: {
7443
7435
  kind: "idcard",
@@ -7463,7 +7455,7 @@ function properties(opts) {
7463
7455
  label: opts.label,
7464
7456
  icon: opts.icon,
7465
7457
  fields: opts.fields,
7466
- view: { kind: "properties" }
7458
+ ui: { kind: "properties" }
7467
7459
  }),
7468
7460
  view: {
7469
7461
  kind: "properties",
@@ -7485,7 +7477,7 @@ function stack(opts) {
7485
7477
  label: opts.label,
7486
7478
  icon: opts.icon,
7487
7479
  fields: opts.fields,
7488
- view: { kind: "stack" }
7480
+ ui: { kind: "stack" }
7489
7481
  }),
7490
7482
  view: { kind: "stack" }
7491
7483
  };
@@ -8381,8 +8373,9 @@ var isNamedField = (x) => hasValue(x) && !hasChildren(x) && x.key !== void 0;
8381
8373
  * @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
8382
8374
  */
8383
8375
  function group(o) {
8376
+ 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
8377
  const r = o.rules ?? {};
8385
- const v = o.view ?? {};
8378
+ const v = o.ui ?? {};
8386
8379
  const fields = materializeTree(o.fields);
8387
8380
  const scope = o.scope ?? "hoist";
8388
8381
  if (scope === "nest") {
@@ -8428,7 +8421,7 @@ function row(o) {
8428
8421
  multiple: o.multiple,
8429
8422
  required: o.required,
8430
8423
  rules: o.rules,
8431
- view: { display: "scroll" }
8424
+ ui: { display: "scroll" }
8432
8425
  });
8433
8426
  }
8434
8427
  /**
@@ -8456,7 +8449,7 @@ function sheet(o) {
8456
8449
  label: o.label,
8457
8450
  icon: o.icon,
8458
8451
  fields: rows,
8459
- view: { display: "sheet" }
8452
+ ui: { display: "sheet" }
8460
8453
  });
8461
8454
  }
8462
8455
  /**
@@ -8475,7 +8468,7 @@ function url(o) {
8475
8468
  scope: "nest",
8476
8469
  label: o.label,
8477
8470
  required: o.required,
8478
- view: { kind: "url" },
8471
+ ui: { kind: "url" },
8479
8472
  fields: {
8480
8473
  scheme: string({}),
8481
8474
  username: string({}),
@@ -8637,13 +8630,15 @@ registerType("string", {
8637
8630
  build(o) {
8638
8631
  const cfg = o.config ?? {};
8639
8632
  const min = cfg.min ?? (o.required === true ? 1 : 0);
8640
- let s = string$1(reqTypeErr());
8641
- if (min > 0) s = s.min(min, { message: vmsg("min_length", { min }) });
8642
- if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
8643
- if (cfg.pattern) s = s.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
8644
8633
  return {
8645
8634
  column: "text",
8646
- zod: s,
8635
+ zod: stringContent((str) => {
8636
+ let c = str;
8637
+ if (min > 0) c = c.min(min, { message: vmsg("min_length", { min }) });
8638
+ if (cfg.max != null) c = c.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
8639
+ if (cfg.pattern) c = c.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
8640
+ return c;
8641
+ }),
8647
8642
  hints: {
8648
8643
  minLength: cfg.min,
8649
8644
  maxLength: cfg.max
@@ -8664,9 +8659,7 @@ function string(o) {
8664
8659
  }
8665
8660
  /** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
8666
8661
  function pathZod(o) {
8667
- let s = string$1(reqTypeErr());
8668
- if (o.required === true) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
8669
- return s;
8662
+ return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
8670
8663
  }
8671
8664
  registerType("localDir", {
8672
8665
  kind: "localDir",
@@ -9328,7 +9321,8 @@ var MEASURED_ROLES = (opts) => {
9328
9321
  label,
9329
9322
  required,
9330
9323
  value,
9331
- view: viewOf(d?.opts),
9324
+ noSearch: d?.opts.noSearch,
9325
+ ui: uiOf(d?.opts),
9332
9326
  options: opts.units,
9333
9327
  strict: true
9334
9328
  });
@@ -9697,7 +9691,7 @@ function periodMkSubDecl(granularity) {
9697
9691
  * (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
9698
9692
  * filling still keeps the composite's own granularity (`canonical.opts`) — the same
9699
9693
  * `date`/`datetime` config the role's own default carries — with the author's
9700
- * label/required/view/value layered on top, exactly like `retypeTo`'s default.
9694
+ * label/required/ui/value layered on top, exactly like `retypeTo`'s default.
9701
9695
  */
9702
9696
  function periodRole(key, label, mkSubDecl) {
9703
9697
  const def = {
@@ -9713,7 +9707,8 @@ function periodRole(key, label, mkSubDecl) {
9713
9707
  label: l ?? canonical.opts.label,
9714
9708
  required,
9715
9709
  value,
9716
- view: viewOf(d.opts)
9710
+ noSearch: d.opts.noSearch,
9711
+ ui: uiOf(d.opts)
9717
9712
  });
9718
9713
  }
9719
9714
  };
@@ -11032,7 +11027,7 @@ var personal_document_default = defineShelf({
11032
11027
  about: field.illustrated({
11033
11028
  label: "documents.personal_document.fields.about",
11034
11029
  fields: {
11035
- image: field.avatar({ role: "avatar" }),
11030
+ image: field.avatar({ ui: { role: "avatar" } }),
11036
11031
  text: field.text({ rules: { max: 1e3 } })
11037
11032
  }
11038
11033
  }),
@@ -11070,7 +11065,7 @@ var personal_document_default = defineShelf({
11070
11065
  sex: field.string({
11071
11066
  label: "documents.personal_document.fields.sex",
11072
11067
  strict: true,
11073
- view: { noSearch: true },
11068
+ noSearch: true,
11074
11069
  options: [{
11075
11070
  value: "M",
11076
11071
  title: "documents.personal_document.options.sex.M"
@@ -11084,7 +11079,7 @@ var personal_document_default = defineShelf({
11084
11079
  label: "documents.personal_document.fields.nationality",
11085
11080
  options: "countries",
11086
11081
  strict: true,
11087
- view: { noSearch: true },
11082
+ noSearch: true,
11088
11083
  ui: { span: 4 }
11089
11084
  }),
11090
11085
  owner: field.relation({
@@ -11121,7 +11116,7 @@ var personal_document_default = defineShelf({
11121
11116
  label: "core.fields.type",
11122
11117
  required: true,
11123
11118
  strict: true,
11124
- view: { noSearch: true },
11119
+ noSearch: true,
11125
11120
  options: [
11126
11121
  {
11127
11122
  value: "P",
@@ -11196,7 +11191,7 @@ nationality — ISO alpha-2. variants — given/family name per language (field.
11196
11191
  label: "documents.person_identity.fields.nationality",
11197
11192
  options: "countries",
11198
11193
  strict: true,
11199
- view: { noSearch: true }
11194
+ noSearch: true
11200
11195
  }),
11201
11196
  variants: field.keyed({
11202
11197
  label: "documents.person_identity.fields.variants",
@@ -11206,7 +11201,7 @@ nationality — ISO alpha-2. variants — given/family name per language (field.
11206
11201
  label: "documents.person_identity.variants.lang",
11207
11202
  options: "languages",
11208
11203
  strict: true,
11209
- view: { noSearch: true }
11204
+ noSearch: true
11210
11205
  }),
11211
11206
  name: field.string({ label: "documents.person_identity.variants.name" }),
11212
11207
  surname: field.string({ label: "documents.person_identity.variants.surname" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-documents",
3
- "version": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -26,9 +26,9 @@
26
26
  "postpack": "node ../../scripts/swap-exports.mjs src"
27
27
  },
28
28
  "dependencies": {
29
- "@coffer-org/plugin-people": "^4.0.0",
30
- "@coffer-org/sdk": "^4.0.0",
31
- "@coffer-org/server": "^4.0.0"
29
+ "@coffer-org/plugin-people": "^5.0.0",
30
+ "@coffer-org/sdk": "^5.0.0",
31
+ "@coffer-org/server": "^5.0.0"
32
32
  },
33
33
  "coffer": {
34
34
  "runtime": "node",