@coffer-org/plugin-documents 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.
@@ -5,14 +5,14 @@ export default defineExtend({
5
5
  label: 'documents.person_identity.label',
6
6
  icon: 'lucide:globe',
7
7
  attachTo: [{ library: 'people', shelf: 'person' }],
8
- claude: `Canonical person identity (not tied to a physical document).
8
+ agent: `Canonical person identity (not tied to a physical document).
9
9
  nationality — ISO alpha-2. variants — given/family name per language (field.keyed by lang: name+surname).`,
10
10
  fields: {
11
11
  nationality: field.string({
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
@@ -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({}),
@@ -8637,13 +8641,15 @@ registerType("string", {
8637
8641
  build(o) {
8638
8642
  const cfg = o.config ?? {};
8639
8643
  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
8644
  return {
8645
8645
  column: "text",
8646
- zod: s,
8646
+ zod: stringContent((str) => {
8647
+ let c = str;
8648
+ if (min > 0) c = c.min(min, { message: vmsg("min_length", { min }) });
8649
+ if (cfg.max != null) c = c.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
8650
+ if (cfg.pattern) c = c.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
8651
+ return c;
8652
+ }),
8647
8653
  hints: {
8648
8654
  minLength: cfg.min,
8649
8655
  maxLength: cfg.max
@@ -8664,9 +8670,7 @@ function string(o) {
8664
8670
  }
8665
8671
  /** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
8666
8672
  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;
8673
+ return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
8670
8674
  }
8671
8675
  registerType("localDir", {
8672
8676
  kind: "localDir",
@@ -9328,7 +9332,8 @@ var MEASURED_ROLES = (opts) => {
9328
9332
  label,
9329
9333
  required,
9330
9334
  value,
9331
- view: viewOf(d?.opts),
9335
+ noSearch: d?.opts.noSearch,
9336
+ ui: uiOf(d?.opts),
9332
9337
  options: opts.units,
9333
9338
  strict: true
9334
9339
  });
@@ -9697,7 +9702,7 @@ function periodMkSubDecl(granularity) {
9697
9702
  * (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
9698
9703
  * filling still keeps the composite's own granularity (`canonical.opts`) — the same
9699
9704
  * `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.
9705
+ * label/required/ui/value layered on top, exactly like `retypeTo`'s default.
9701
9706
  */
9702
9707
  function periodRole(key, label, mkSubDecl) {
9703
9708
  const def = {
@@ -9713,7 +9718,8 @@ function periodRole(key, label, mkSubDecl) {
9713
9718
  label: l ?? canonical.opts.label,
9714
9719
  required,
9715
9720
  value,
9716
- view: viewOf(d.opts)
9721
+ noSearch: d.opts.noSearch,
9722
+ ui: uiOf(d.opts)
9717
9723
  });
9718
9724
  }
9719
9725
  };
@@ -10181,10 +10187,11 @@ var field = new Proxy({}, {
10181
10187
  has: (_t, k) => k in composedField()
10182
10188
  });
10183
10189
  function toClient(field) {
10184
- const { kind, label, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
10190
+ const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
10185
10191
  return {
10186
10192
  kind,
10187
10193
  label,
10194
+ ...agent !== void 0 && { agent },
10188
10195
  required,
10189
10196
  prim,
10190
10197
  hints,
@@ -10972,7 +10979,7 @@ function defineShelf(m) {
10972
10979
  const keys = fieldEntries(built.fields).map(([k]) => k);
10973
10980
  const dup = keys.find((k, i) => keys.indexOf(k) !== i);
10974
10981
  if (dup) throw new Error(`[shelf] ${built.library}/${built.shelf}: duplicate key '${dup}'`);
10975
- if (built.single && !built.claude) throw new Error(`[shelf] ${built.library}/${built.shelf}: single shelf requires \`claude\` — the agent cannot find it otherwise`);
10982
+ if (built.single && !built.agent) throw new Error(`[shelf] ${built.library}/${built.shelf}: single shelf requires \`agent\` — the agent cannot find it otherwise`);
10976
10983
  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`);
10977
10984
  if (built.standalone !== false && !built.single && !built.views?.list?.length) console.warn(`[shelf] ${built.library}/${built.shelf}: standalone shelf without an explicit views.list`);
10978
10985
  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`);
@@ -11032,7 +11039,7 @@ var personal_document_default = defineShelf({
11032
11039
  about: field.illustrated({
11033
11040
  label: "documents.personal_document.fields.about",
11034
11041
  fields: {
11035
- image: field.avatar({ role: "avatar" }),
11042
+ image: field.avatar({ ui: { role: "avatar" } }),
11036
11043
  text: field.text({ rules: { max: 1e3 } })
11037
11044
  }
11038
11045
  }),
@@ -11070,7 +11077,7 @@ var personal_document_default = defineShelf({
11070
11077
  sex: field.string({
11071
11078
  label: "documents.personal_document.fields.sex",
11072
11079
  strict: true,
11073
- view: { noSearch: true },
11080
+ noSearch: true,
11074
11081
  options: [{
11075
11082
  value: "M",
11076
11083
  title: "documents.personal_document.options.sex.M"
@@ -11084,7 +11091,7 @@ var personal_document_default = defineShelf({
11084
11091
  label: "documents.personal_document.fields.nationality",
11085
11092
  options: "countries",
11086
11093
  strict: true,
11087
- view: { noSearch: true },
11094
+ noSearch: true,
11088
11095
  ui: { span: 4 }
11089
11096
  }),
11090
11097
  owner: field.relation({
@@ -11121,7 +11128,7 @@ var personal_document_default = defineShelf({
11121
11128
  label: "core.fields.type",
11122
11129
  required: true,
11123
11130
  strict: true,
11124
- view: { noSearch: true },
11131
+ noSearch: true,
11125
11132
  options: [
11126
11133
  {
11127
11134
  value: "P",
@@ -11189,14 +11196,14 @@ var person_identity_default = defineExtend({
11189
11196
  library: "people",
11190
11197
  shelf: "person"
11191
11198
  }],
11192
- claude: `Canonical person identity (not tied to a physical document).
11199
+ agent: `Canonical person identity (not tied to a physical document).
11193
11200
  nationality — ISO alpha-2. variants — given/family name per language (field.keyed by lang: name+surname).`,
11194
11201
  fields: {
11195
11202
  nationality: field.string({
11196
11203
  label: "documents.person_identity.fields.nationality",
11197
11204
  options: "countries",
11198
11205
  strict: true,
11199
- view: { noSearch: true }
11206
+ noSearch: true
11200
11207
  }),
11201
11208
  variants: field.keyed({
11202
11209
  label: "documents.person_identity.fields.variants",
@@ -11206,7 +11213,7 @@ nationality — ISO alpha-2. variants — given/family name per language (field.
11206
11213
  label: "documents.person_identity.variants.lang",
11207
11214
  options: "languages",
11208
11215
  strict: true,
11209
- view: { noSearch: true }
11216
+ noSearch: true
11210
11217
  }),
11211
11218
  name: field.string({ label: "documents.person_identity.variants.name" }),
11212
11219
  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": "6.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": "^6.0.0",
30
+ "@coffer-org/sdk": "^6.0.0",
31
+ "@coffer-org/server": "^6.0.0"
32
32
  },
33
33
  "coffer": {
34
34
  "runtime": "node",