@coffer-org/plugin-webchat 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schema.js CHANGED
@@ -24,6 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  //#region ../sdk/src/plugin.ts
25
25
  function definePlugin(p) {
26
26
  if (!p.id) throw new Error("[plugin] missing id");
27
+ if (!p.label) throw new Error(`[plugin] ${p.id}: missing label`);
28
+ if (!p.description) throw new Error(`[plugin] ${p.id}: missing description`);
27
29
  if (!p.version) console.warn(`[plugin] ${p.id}: missing version`);
28
30
  return p;
29
31
  }
@@ -4134,16 +4136,18 @@ function jsonRefined(inner, code) {
4134
4136
  });
4135
4137
  });
4136
4138
  }
4137
- function optionalize(schema, required) {
4138
- const req = required === true;
4139
- const pre = (v) => v === "" || v === null ? void 0 : v;
4140
- if (!req) return preprocess(pre, schema.optional());
4141
- return preprocess(pre, unknown().superRefine((v, ctx) => {
4142
- if (v === void 0) ctx.addIssue({
4143
- code: ZodIssueCode.custom,
4144
- message: vmsg("required")
4145
- });
4146
- }).pipe(schema));
4139
+ /**
4140
+ * An empty value is no value: `''` and `null` become `undefined`, and the field's own schema
4141
+ * never sees them. Every field is optional, so this is the ONLY shape a field cannot demand
4142
+ * a value, and the rules it carries judge what was entered rather than whether anything was.
4143
+ *
4144
+ * This used to have a second branch, for `required` fields, which turned the same emptiness
4145
+ * into a `required` issue through a `z.unknown()` guard piped into the schema. That branch is
4146
+ * why a `.min(1)` on a required string was unreachable: the empty string had already been
4147
+ * converted to the issue before the string schema ran.
4148
+ */
4149
+ function optionalize(schema) {
4150
+ return preprocess((v) => v === "" || v === null ? void 0 : v, schema.optional());
4147
4151
  }
4148
4152
  //#endregion
4149
4153
  //#region ../sdk/src/fields/meta.ts
@@ -4225,7 +4229,7 @@ function applyMultiple(base, multiple) {
4225
4229
  multiple: true
4226
4230
  },
4227
4231
  json: true,
4228
- zod: optionalize(s, base.required)
4232
+ zod: optionalize(s)
4229
4233
  };
4230
4234
  }
4231
4235
  /** Inline option entry → OptionItem (plain string = value and label at once). */
@@ -4340,13 +4344,6 @@ function wrapKey(key, opts, meta) {
4340
4344
  noLabel: true
4341
4345
  }
4342
4346
  };
4343
- if (opts.role) m = {
4344
- ...m,
4345
- hints: {
4346
- ...m.hints,
4347
- role: opts.role
4348
- }
4349
- };
4350
4347
  if (opts.editor) m = {
4351
4348
  ...m,
4352
4349
  hints: {
@@ -4368,6 +4365,13 @@ function wrapKey(key, opts, meta) {
4368
4365
  span: opts.span
4369
4366
  }
4370
4367
  };
4368
+ if (opts.faces !== void 0) m = {
4369
+ ...m,
4370
+ hints: {
4371
+ ...m.hints,
4372
+ faces: opts.faces
4373
+ }
4374
+ };
4371
4375
  if (opts.default !== void 0) m = {
4372
4376
  ...m,
4373
4377
  default: opts.default
@@ -4795,9 +4799,9 @@ function normalizeOpts(rawIn) {
4795
4799
  ...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
4796
4800
  compareWith: v.compareWith,
4797
4801
  span: v.span,
4802
+ faces: v.faces,
4798
4803
  emphasis: v.emphasis,
4799
4804
  noLabel: v.noLabel,
4800
- role: v.role,
4801
4805
  editor: v.editor,
4802
4806
  activate: v.activate
4803
4807
  };
@@ -4960,13 +4964,10 @@ function email(o) {
4960
4964
  registerPreset("email", "string", {}, {
4961
4965
  kind: "email",
4962
4966
  widget: "email",
4963
- build(o) {
4964
- const required = o.required ?? false;
4965
- let s = string$1().email({ message: vmsg("email") });
4966
- if (required) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
4967
+ build() {
4967
4968
  return {
4968
4969
  column: "text",
4969
- zod: s,
4970
+ zod: string$1().email({ message: vmsg("email") }),
4970
4971
  hints: {
4971
4972
  format: "email",
4972
4973
  inputType: "email"
@@ -4989,12 +4990,10 @@ function tel(o) {
4989
4990
  registerPreset("tel", "string", {}, {
4990
4991
  kind: "tel",
4991
4992
  widget: "tel",
4992
- build(o) {
4993
- const required = o.required ?? false;
4994
- const base_z = string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) });
4993
+ build() {
4995
4994
  return {
4996
4995
  column: "text",
4997
- zod: required ? base_z.min(1, { message: vmsg("min_length", { min: 1 }) }) : base_z,
4996
+ zod: string$1().regex(TEL_RE, { message: vmsg("pattern", { messageKey: "core.presets.tel" }) }),
4998
4997
  hints: {
4999
4998
  format: "tel",
5000
4999
  inputType: "tel"
@@ -5018,10 +5017,10 @@ registerPreset("password", "string", {}, {
5018
5017
  kind: "password",
5019
5018
  widget: "password",
5020
5019
  selfManages: /* @__PURE__ */ new Set(["multiple"]),
5021
- build(o) {
5020
+ build() {
5022
5021
  return {
5023
5022
  column: "text",
5024
- zod: o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1(),
5023
+ zod: string$1(),
5025
5024
  hints: {
5026
5025
  format: "password",
5027
5026
  inputType: "password"
@@ -5083,6 +5082,31 @@ function internalOauthGrants(o) {
5083
5082
  }
5084
5083
  });
5085
5084
  }
5085
+ /**
5086
+ * The account page's linked-accounts section. Same trick as `internalApiToken`: a container
5087
+ * with a custom `ui.kind`, drawn by its own renderer, which reads `/api/auth/links` itself.
5088
+ * The declared children are what one ROW holds — the renderer reads them through the slot
5089
+ * contract, so this is a declaration, not a layout.
5090
+ *
5091
+ * @layer preset
5092
+ * @base group
5093
+ * @prim —
5094
+ * @widget internalIdentityLinks
5095
+ * @example f.internalIdentityLinks({ label: 'auth.linksTitle' })
5096
+ */
5097
+ function internalIdentityLinks(o) {
5098
+ return group({
5099
+ scope: "nest",
5100
+ label: o.label,
5101
+ multiple: true,
5102
+ ui: { kind: "internalIdentityLinks" },
5103
+ fields: {
5104
+ provider: string({}),
5105
+ externalId: string({}),
5106
+ linkedAt: string({})
5107
+ }
5108
+ });
5109
+ }
5086
5110
  var SLUG_RE = /^[a-z0-9-]+$/;
5087
5111
  /**
5088
5112
  * Slug — kind 'slug', `^[a-z0-9-]+$`.
@@ -5107,6 +5131,63 @@ registerPreset("slug", "string", {}, {
5107
5131
  };
5108
5132
  }
5109
5133
  });
5134
+ /**
5135
+ * Identifier — a code printed for a machine and read back by one: a serial number, an order
5136
+ * number, a policy number, an IMEI. What a person actually DOES with one is copy it, which is
5137
+ * why this is a type of its own and not `f.string({ ui: { voice: 'data' } })`: the machine face
5138
+ * is one word on any string now, but the click that copies belongs to the kind.
5139
+ *
5140
+ * Deliberately unvalidated beyond a length: an identifier's format belongs to whoever issued
5141
+ * it, and a pattern here would reject the next manufacturer's.
5142
+ *
5143
+ * @layer preset
5144
+ * @base string
5145
+ * @prim text
5146
+ * @widget identifier
5147
+ * @example f.identifier({ label: 'mod.fields.serialNumber' })
5148
+ */
5149
+ function identifier(o) {
5150
+ return declare("identifier", o);
5151
+ }
5152
+ registerPreset("identifier", "string", {}, {
5153
+ kind: "identifier",
5154
+ widget: "identifier",
5155
+ build() {
5156
+ return {
5157
+ column: "text",
5158
+ zod: string$1(),
5159
+ hints: {}
5160
+ };
5161
+ }
5162
+ });
5163
+ /**
5164
+ * The machine-readable zone of a travel document — the identifier taken to its limit: printed
5165
+ * for a scanner, not for a person. Fixed pitch, chevrons kept as the filler they are, and the
5166
+ * line breaks preserved, because an MRZ's line structure is part of what it encodes.
5167
+ *
5168
+ * Unvalidated on purpose, like `identifier`: the ICAO line formats differ by document type, and
5169
+ * a strip transcribed from a real document is worth storing even when it does not check out.
5170
+ *
5171
+ * @layer preset
5172
+ * @base string
5173
+ * @prim text
5174
+ * @widget mrz
5175
+ * @example f.mrz({ label: 'documents.personal_document.fields.mrz' })
5176
+ */
5177
+ function mrz(o) {
5178
+ return declare("mrz", o);
5179
+ }
5180
+ registerPreset("mrz", "string", {}, {
5181
+ kind: "mrz",
5182
+ widget: "mrz",
5183
+ build() {
5184
+ return {
5185
+ column: "text",
5186
+ zod: string$1(),
5187
+ hints: {}
5188
+ };
5189
+ }
5190
+ });
5110
5191
  var COLOR_RE = /^#[0-9a-fA-F]{6}$/;
5111
5192
  /**
5112
5193
  * Hex color — kind 'color', `#rrggbb` + swatch widget.
@@ -5146,10 +5227,10 @@ function colorname(o) {
5146
5227
  registerPreset("colorname", "string", {}, {
5147
5228
  kind: "colorname",
5148
5229
  widget: "colorname",
5149
- build(o) {
5230
+ build() {
5150
5231
  return {
5151
5232
  column: "text",
5152
- zod: (o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1()).refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
5233
+ zod: string$1().refine((v) => CSS_COLOR_NAMES.has(v.toLowerCase()), { message: vmsg("colorname") }),
5153
5234
  hints: {}
5154
5235
  };
5155
5236
  }
@@ -5169,10 +5250,10 @@ function title(o) {
5169
5250
  registerPreset("title", "string", {}, {
5170
5251
  kind: "title",
5171
5252
  widget: "title",
5172
- build(o) {
5253
+ build() {
5173
5254
  return {
5174
5255
  column: "text",
5175
- zod: o.required ?? false ? string$1().min(1, { message: vmsg("min_length", { min: 1 }) }) : string$1(),
5256
+ zod: string$1(),
5176
5257
  hints: {}
5177
5258
  };
5178
5259
  }
@@ -5193,12 +5274,10 @@ function link(o) {
5193
5274
  registerPreset("link", "string", {}, {
5194
5275
  kind: "link",
5195
5276
  widget: "link",
5196
- build(o) {
5197
- const required = o.required ?? false;
5198
- const base_z = string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) });
5277
+ build() {
5199
5278
  return {
5200
5279
  column: "text",
5201
- zod: required ? base_z.min(1, { message: vmsg("min_length", { min: 1 }) }) : base_z,
5280
+ zod: string$1().regex(LINK_RE, { message: vmsg("pattern", { messageKey: "core.presets.link" }) }),
5202
5281
  hints: {
5203
5282
  format: "url",
5204
5283
  inputType: "url"
@@ -5391,6 +5470,38 @@ registerPreset("tag", "string", {}, {
5391
5470
  }
5392
5471
  });
5393
5472
  /**
5473
+ * A location written the way it is said aloud: workshop › shelf 2 › box. A `string` fixed to
5474
+ * `multiple`, exactly as `f.tags` is, because a place inside a place inside a place IS a
5475
+ * sequence — the plurality is the type, not a modifier someone remembered to add.
5476
+ *
5477
+ * Distinct from `f.tags`, which stores the same shape: tags are an unordered SET, and a path is
5478
+ * an ordered CHAIN where each step is inside the one before it. Drawn as chips, that
5479
+ * containment — the only thing the value actually carries — is lost.
5480
+ *
5481
+ * @layer preset
5482
+ * @base string
5483
+ * @prim text
5484
+ * @widget path
5485
+ * @example f.path({ label: 'things.storage_location.fields.path' })
5486
+ */
5487
+ function path(o) {
5488
+ return declare("path", {
5489
+ ...o,
5490
+ multiple: true
5491
+ });
5492
+ }
5493
+ registerPreset("path", "string", {}, {
5494
+ kind: "path",
5495
+ widget: "path",
5496
+ build() {
5497
+ return {
5498
+ column: "text",
5499
+ zod: string$1(),
5500
+ hints: {}
5501
+ };
5502
+ }
5503
+ });
5504
+ /**
5394
5505
  * Tags — `tag({ multiple: true })`.
5395
5506
  *
5396
5507
  * @layer preset
@@ -5583,6 +5694,56 @@ registerPreset("reminder", "date", { lead: 30 }, {
5583
5694
  };
5584
5695
  }
5585
5696
  });
5697
+ /** Timetable — a set of times of day, read as a schedule rather than as a bag of values.
5698
+ * kind 'timetable', prim 'time', always `multiple`.
5699
+ *
5700
+ * Same shape as `f.reminder`/`f.age` over `date`: a semantic type whose renderer knows what
5701
+ * the values MEAN. Everything it shows beyond the times themselves — when the service starts
5702
+ * and ends, how many runs there are, which one is next — is derived from the values at render
5703
+ * time, never stored: a "next departure" is wrong the moment it is written down, exactly the
5704
+ * argument `f.age` already makes for an age.
5705
+ *
5706
+ * Storage is a plain multiple `time`, so a field can be switched to this from
5707
+ * `f.time({ multiple: true })` and back with no migration.
5708
+ *
5709
+ * @layer preset
5710
+ * @base time
5711
+ * @prim time
5712
+ * @widget timetable
5713
+ * @example f.timetable({ label: 'mod.fields.weekdays' })
5714
+ */
5715
+ function timetable(o) {
5716
+ return declare("timetable", {
5717
+ ...o,
5718
+ multiple: true
5719
+ });
5720
+ }
5721
+ registerPreset("timetable", "time", {}, {
5722
+ kind: "timetable",
5723
+ widget: "timetable"
5724
+ });
5725
+ /** Age — date whose whole-year age (as of today) renders alongside it, e.g. a birth date.
5726
+ * kind 'age', prim 'date'. The age itself is computed at RENDER time, never stored: unlike a
5727
+ * `mutate`-driven computed field, it must be right on every day that passes, not only the day
5728
+ * the record was last written, so this is display metadata over `date`, the same shape as
5729
+ * `f.reminder`'s own relative-to-today note.
5730
+ *
5731
+ * @layer preset
5732
+ * @base date
5733
+ * @prim date
5734
+ * @widget age
5735
+ * @example f.age({ label: 'mod.fields.birthDate' })
5736
+ */
5737
+ function age(o) {
5738
+ return declare("age", o);
5739
+ }
5740
+ registerPreset("age", "date", {}, {
5741
+ kind: "age",
5742
+ widget: "age",
5743
+ build(o, parts) {
5744
+ return typeOf("date").build(o, parts);
5745
+ }
5746
+ });
5586
5747
  /**
5587
5748
  * Percentage 0..100 — real with rules:{min:0,max:100}.
5588
5749
  *
@@ -5775,6 +5936,9 @@ var presets = {
5775
5936
  email,
5776
5937
  tel,
5777
5938
  slug,
5939
+ path,
5940
+ identifier,
5941
+ mrz,
5778
5942
  color,
5779
5943
  colorname,
5780
5944
  title,
@@ -5788,6 +5952,8 @@ var presets = {
5788
5952
  rating,
5789
5953
  duration,
5790
5954
  reminder,
5955
+ age,
5956
+ timetable,
5791
5957
  percent,
5792
5958
  year,
5793
5959
  weight,
@@ -5796,6 +5962,7 @@ var presets = {
5796
5962
  currency,
5797
5963
  money,
5798
5964
  internalApiToken,
5965
+ internalIdentityLinks,
5799
5966
  internalOauthGrants
5800
5967
  };
5801
5968
  //#endregion
@@ -6445,26 +6612,38 @@ function identity(opts) {
6445
6612
  }
6446
6613
  };
6447
6614
  }
6448
- /** A scored value out of an optional max, with an optional verdict.
6615
+ /** One measure out of an optional max, from ONE OR MORE named sources, with an optional verdict.
6616
+ *
6617
+ * `source` takes several entries for the same reason `f.identity`'s `channels` does — it is a
6618
+ * role that holds a LIST, so `view.source` is an ordered list of names rather than one name.
6619
+ * Two ratings of the same film out of ten are one measure read twice, and drawing them apart is
6620
+ * what makes them incomparable: the eye has to carry the scale between two figures instead of
6621
+ * reading them against a shared one. `media/title` had exactly that, two `f.score` blocks of
6622
+ * `max: 10` held apart inside an `f.compare`, until this took the restriction off.
6623
+ *
6624
+ * NOT `multiple`. A multiple field is an anonymous array; these are NAMED sources, and the name
6625
+ * is what says which reading came from where. The two are different shapes and the block wants
6626
+ * this one.
6449
6627
  *
6450
6628
  * @layer block
6451
6629
  * @base group
6452
6630
  * @prim —
6453
6631
  * @widget score
6454
- * @example f.score({ source: { value: f.real({ label: '…' }) }, max: 100, verdict: { verdict: f.string({ label: '…' }) } })
6632
+ * @example f.score({ source: { imdb: f.real({ label: '…' }), tmdb: f.real({ label: '…' }) }, max: 10 })
6455
6633
  */
6456
6634
  function score(opts) {
6457
- const source = slot("score", "source", opts.source);
6635
+ const names = Object.keys(opts.source ?? {});
6636
+ if (names.length === 0) throw new Error(`[field.score] slot 'source' expects at least one entry, got 0`);
6458
6637
  const verdict = opts.verdict !== void 0 ? slot("score", "verdict", opts.verdict) : void 0;
6459
6638
  return {
6460
6639
  ...group({
6461
6640
  label: opts.label,
6462
- fields: merge("score", source, verdict),
6641
+ fields: merge("score", opts.source, verdict),
6463
6642
  ui: { kind: "score" }
6464
6643
  }),
6465
6644
  view: {
6466
6645
  kind: "score",
6467
- source: Object.keys(source)[0],
6646
+ source: names,
6468
6647
  max: opts.max,
6469
6648
  verdict: verdict && Object.keys(verdict)[0]
6470
6649
  }
@@ -6496,11 +6675,20 @@ function status(opts) {
6496
6675
  }
6497
6676
  /** A gauge value between an optional min and max, with an optional "of" total.
6498
6677
  *
6678
+ * `direction` is opt-in: when given, the gauge decides in plain JavaScript whether its own
6679
+ * value crossed the bound that matters (`meterTone`, `blocks/meter.tsx`) and reaches for the
6680
+ * tone palette — the fill, the value text and the `of` companion all move together, never
6681
+ * colour alone (a glyph rides along, see the renderer's own comment). Omit it and nothing
6682
+ * about the gauge changes from today. A `'ceiling'` gauge with no declared `max` (config or
6683
+ * the source field's own hints) resolves it from `of`'s own value instead — a budget's cap is
6684
+ * a per-record field, never a compile-time constant.
6685
+ *
6499
6686
  * @layer block
6500
6687
  * @base group
6501
6688
  * @prim —
6502
6689
  * @widget meter
6503
6690
  * @example f.meter({ source: { used: f.real({ label: '…' }) }, min: 0, max: 100 })
6691
+ * @example f.meter({ source: { spent: f.real({ label: '…' }) }, of: { budget: f.real({ label: '…' }) }, direction: 'ceiling' })
6504
6692
  */
6505
6693
  function meter(opts) {
6506
6694
  const source = slot("meter", "source", opts.source);
@@ -6516,7 +6704,8 @@ function meter(opts) {
6516
6704
  source: Object.keys(source)[0],
6517
6705
  min: opts.min,
6518
6706
  max: opts.max,
6519
- of: of && Object.keys(of)[0]
6707
+ of: of && Object.keys(of)[0],
6708
+ direction: opts.direction
6520
6709
  }
6521
6710
  };
6522
6711
  }
@@ -6576,6 +6765,11 @@ function balance(opts) {
6576
6765
  * @base group
6577
6766
  * @prim —
6578
6767
  * @widget route
6768
+ * A `stub` is the part of the ticket that is TORN OFF and kept — a seat, a gate, a booking
6769
+ * reference. Declaring one makes the block a ticket rather than a line: the two halves are
6770
+ * separated by a perforation, and the fields in the stub sit below it. Without one the block
6771
+ * is exactly what it was, a route from here to there, so no existing call changes.
6772
+ *
6579
6773
  * @example f.route({ from: { from: f.string({ label: '…' }) }, to: { to: f.string({ label: '…' }) } })
6580
6774
  */
6581
6775
  function route(opts) {
@@ -6584,9 +6778,10 @@ function route(opts) {
6584
6778
  const depart = opts.depart !== void 0 ? slot("route", "depart", opts.depart) : void 0;
6585
6779
  const arrive = opts.arrive !== void 0 ? slot("route", "arrive", opts.arrive) : void 0;
6586
6780
  const duration = opts.duration !== void 0 ? slot("route", "duration", opts.duration) : void 0;
6781
+ const stub = opts.stub ?? {};
6587
6782
  return {
6588
6783
  ...group({
6589
- fields: merge("route", from, to, depart, arrive, duration),
6784
+ fields: merge("route", from, to, depart, arrive, duration, stub),
6590
6785
  ui: { kind: "route" }
6591
6786
  }),
6592
6787
  view: {
@@ -6595,7 +6790,8 @@ function route(opts) {
6595
6790
  to: Object.keys(to)[0],
6596
6791
  depart: depart && Object.keys(depart)[0],
6597
6792
  arrive: arrive && Object.keys(arrive)[0],
6598
- duration: duration && Object.keys(duration)[0]
6793
+ duration: duration && Object.keys(duration)[0],
6794
+ stub: Object.keys(stub)
6599
6795
  }
6600
6796
  };
6601
6797
  }
@@ -6773,6 +6969,81 @@ function specimen(opts) {
6773
6969
  };
6774
6970
  }
6775
6971
  /**
6972
+ * Measurements against the range they were supposed to fall in, rendered from a single
6973
+ * collection source — see `stampCollection`. `analyte`, `value`, `unit`, `low` and `high` are
6974
+ * part KEYS inside each collection row, NOT `LayoutEl` positions — the same reasoning as
6975
+ * `manifest`'s `quantity`/`item`.
6976
+ *
6977
+ * The point is the COMPARISON. A `f.table` over the same rows prints the bounds as two more
6978
+ * columns and leaves the reader to do it; here each value sits on its own band with the
6979
+ * reference span marked on it, so "outside the range" is seen rather than worked out. That is
6980
+ * also the only thing on the row worth a colour — it is what the reader has to act on.
6981
+ *
6982
+ * `unit`, `low` and `high` are optional: a measurement with no published range (a culture, a
6983
+ * description) still belongs in the same list and simply gets no band.
6984
+ *
6985
+ * @layer block
6986
+ * @base group
6987
+ * @prim —
6988
+ * @widget assay
6989
+ * @example f.assay({ source: { results: f.group({ scope: 'nest', multiple: true, fields: { analyte: f.string({ label: '…' }), value: f.real({ label: '…' }) } }) }, analyte: 'analyte', value: 'value' })
6990
+ */
6991
+ function assay(opts) {
6992
+ return stampCollection("assay", opts.source, opts.label, {
6993
+ kind: "assay",
6994
+ analyte: opts.analyte,
6995
+ value: opts.value,
6996
+ ...opts.unit ? { unit: opts.unit } : {},
6997
+ ...opts.low ? { low: opts.low } : {},
6998
+ ...opts.high ? { high: opts.high } : {}
6999
+ });
7000
+ }
7001
+ /**
7002
+ * Value moving from one place to another, where the MOVEMENT is the subject — not one more
7003
+ * labelled row among the record's fields. The sum is set large in the machine voice, and the two
7004
+ * ends read as a path beneath it.
7005
+ *
7006
+ * amount — what moved. The one required role, and the reason the block exists
7007
+ * from — where it left. Absent on money that only arrived
7008
+ * to — where it arrived. Absent on money that only left
7009
+ * meta — the record's own remaining fields, under a rule: date, category, reference
7010
+ *
7011
+ * An absent end is not drawn, and that is the whole reading: money that left an account and
7012
+ * arrived nowhere IS an expense. It is read off which ends the record actually has, never
7013
+ * guessed from the shape of a value. What the movement MEANS beyond that — whether this
7014
+ * particular kind of transfer is good news — is carried by the classifier's own option `tone`,
7015
+ * the same declaration every other coloured value in the product uses.
7016
+ *
7017
+ * Distinct from `f.route`, which also has two ends: there the journey is the subject and the
7018
+ * ends are places, so it carries times and a duration; here the ends are accounts and the
7019
+ * subject is the quantity.
7020
+ *
7021
+ * @layer block
7022
+ * @base group
7023
+ * @prim —
7024
+ * @widget flow
7025
+ * @example f.flow({ amount: { amount: f.money({ label: '…' }) }, from: { source: f.relation({ label: '…' }) } })
7026
+ */
7027
+ function flow(opts) {
7028
+ const amount = slot("flow", "amount", opts.amount);
7029
+ const from = opts.from !== void 0 ? slot("flow", "from", opts.from) : void 0;
7030
+ const to = opts.to !== void 0 ? slot("flow", "to", opts.to) : void 0;
7031
+ return {
7032
+ ...group({
7033
+ label: opts.label,
7034
+ fields: merge("flow", amount, from, to, opts.meta),
7035
+ ui: { kind: "flow" }
7036
+ }),
7037
+ view: {
7038
+ kind: "flow",
7039
+ amount: Object.keys(amount)[0],
7040
+ from: from && Object.keys(from)[0],
7041
+ to: to && Object.keys(to)[0],
7042
+ meta: Object.keys(opts.meta ?? {})
7043
+ }
7044
+ };
7045
+ }
7046
+ /**
6776
7047
  * A packing/cargo manifest rendered from a single collection source — see `stampCollection`.
6777
7048
  * `quantity` and `item` are part KEYS inside each collection row, NOT `LayoutEl` positions —
6778
7049
  * the same reasoning as `journal`'s `date`/`text`.
@@ -6819,13 +7090,25 @@ function table(opts) {
6819
7090
  } : {},
6820
7091
  ...opts.groupBy ? { groupBy: opts.groupBy } : {},
6821
7092
  ...opts.totals ? { totals: opts.totals } : {},
7093
+ ...opts.summary ? { summary: opts.summary } : {},
6822
7094
  ...opts.numbered ? { numbered: true } : {}
6823
7095
  });
6824
7096
  }
6825
7097
  /**
6826
- * An ID-card header: an optional overline, a prominent number, and trailing meta fields.
6827
- * `view` names each role (Task 5) see `masthead`'s own doc comment for what `meta` means
6828
- * as an ordered name list.
7098
+ * An identity document, drawn as the card it is: a caption and its number across the top, a
7099
+ * portrait beside the holder's own fields, the issuing details under a rule, and the
7100
+ * machine-readable strip at the foot. `view` names each role (Task 5) — see `masthead`'s own
7101
+ * doc comment for what an ordered name list means.
7102
+ *
7103
+ * overline — the caption at the top left ("PASSPORT", "DRIVING LICENCE")
7104
+ * number — the document's number, set apart at the top right
7105
+ * photo — the portrait. One field, rendered in a portrait frame beside the body
7106
+ * meta — the card's OWN fields, labelled, at each field's declared span
7107
+ * footer — the fields below the rule: issued, authority, record number
7108
+ * mrz — the machine-readable strip, set in monospace at the foot
7109
+ *
7110
+ * Every role is optional but `number`: a bank card has no `mrz`, a library card no `photo`,
7111
+ * and the card simply omits the part it was given nothing for.
6829
7112
  *
6830
7113
  * @layer block
6831
7114
  * @base group
@@ -6836,16 +7119,21 @@ function table(opts) {
6836
7119
  function idcard(opts) {
6837
7120
  const overline = opts.overline !== void 0 ? slot("idcard", "overline", opts.overline) : void 0;
6838
7121
  const number = slot("idcard", "number", opts.number);
7122
+ const photo = opts.photo !== void 0 ? slot("idcard", "photo", opts.photo) : void 0;
7123
+ const mrz = opts.mrz !== void 0 ? slot("idcard", "mrz", opts.mrz) : void 0;
6839
7124
  return {
6840
7125
  ...group({
6841
- fields: merge("idcard", overline, number, opts.meta),
7126
+ fields: merge("idcard", overline, number, photo, opts.meta, opts.footer, mrz),
6842
7127
  ui: { kind: "idcard" }
6843
7128
  }),
6844
7129
  view: {
6845
7130
  kind: "idcard",
6846
7131
  overline: overline && Object.keys(overline)[0],
6847
7132
  number: Object.keys(number)[0],
6848
- meta: Object.keys(opts.meta ?? {})
7133
+ photo: photo && Object.keys(photo)[0],
7134
+ meta: Object.keys(opts.meta ?? {}),
7135
+ footer: Object.keys(opts.footer ?? {}),
7136
+ mrz: mrz && Object.keys(mrz)[0]
6849
7137
  }
6850
7138
  };
6851
7139
  }
@@ -6930,6 +7218,8 @@ var blocks = {
6930
7218
  nutrition,
6931
7219
  specimen,
6932
7220
  manifest,
7221
+ assay,
7222
+ flow,
6933
7223
  table,
6934
7224
  idcard,
6935
7225
  properties,
@@ -7777,18 +8067,23 @@ function group(o) {
7777
8067
  for (const f of fields) if (!("key" in f && f.key !== void 0) && !hasChildren(f)) throw new Error(`[field.group] a 'nest' group's children must be fields or groups, not layout elements`);
7778
8068
  for (const k of r.unique ?? []) if (!fields.some((f) => "key" in f && f.key === k)) throw new Error(`[field.group] unique key '${k}' is not a subfield`);
7779
8069
  } else if (o.value !== void 0) throw new Error(`[field.group] value requires scope 'nest' — a hoist group owns no children to write`);
8070
+ const title = v.title ?? [];
8071
+ if (title.length > 0) {
8072
+ if (!o.multiple) throw new Error(`[field.group] ui.title heads a collection's ROWS — it needs multiple: true`);
8073
+ for (const name of title) if (!fields.some((f) => "key" in f && f.key === name)) throw new Error(`[field.group] ui.title '${name}' is not one of its fields`);
8074
+ }
7780
8075
  return {
7781
8076
  el: "group",
7782
8077
  scope,
7783
8078
  multiple: o.multiple,
7784
8079
  compute: o.value,
7785
- required: o.required,
7786
8080
  unique: r.unique,
7787
8081
  label: o.label,
7788
8082
  icon: o.icon,
7789
8083
  display: v.display ?? "wrap",
7790
8084
  kind: v.kind,
7791
8085
  fixed: r.fixed,
8086
+ view: title.length > 0 ? { title } : void 0,
7792
8087
  fields
7793
8088
  };
7794
8089
  }
@@ -7814,7 +8109,6 @@ function row(o) {
7814
8109
  icon: o.icon,
7815
8110
  fields: o.fields,
7816
8111
  multiple: o.multiple,
7817
- required: o.required,
7818
8112
  rules: o.rules,
7819
8113
  ui: { display: "scroll" }
7820
8114
  });
@@ -7862,7 +8156,6 @@ function url(o) {
7862
8156
  return group({
7863
8157
  scope: "nest",
7864
8158
  label: o.label,
7865
- required: o.required,
7866
8159
  ui: { kind: "url" },
7867
8160
  fields: {
7868
8161
  scheme: string({}),
@@ -7914,7 +8207,6 @@ function keyed(o) {
7914
8207
  }),
7915
8208
  scope: "nest",
7916
8209
  multiple: true,
7917
- required: o.required,
7918
8210
  unique: o.unique ?? [role],
7919
8211
  fixed: o.fixed,
7920
8212
  view: { by: role }
@@ -7955,6 +8247,31 @@ function info(textKey) {
7955
8247
  };
7956
8248
  }
7957
8249
  /**
8250
+ * What else points AT this record — the inverse of a relation, which is often the more useful
8251
+ * direction on a record page: a box is more usefully "what is in it" than "what it is in".
8252
+ *
8253
+ * Stores NOTHING and has no column: it declares where to look, and the server answers by asking
8254
+ * the pointing shelf. That is why it is a pseudo-element beside `divider` and `info` rather than
8255
+ * a field — there is no value here to validate or save.
8256
+ *
8257
+ * `from` names the pointing side explicitly (which shelf, and WHICH of its fields), never
8258
+ * "everything that happens to point here": a shelf may point at the same target through two
8259
+ * fields — a transaction has a source account and a destination account — and a panel that
8260
+ * merged them would answer a question nobody asked.
8261
+ *
8262
+ * @layer primitive
8263
+ * @prim —
8264
+ * @widget backrefs
8265
+ * @example f.backrefs({ label: 'mod.fields.storedHere', from: { library: 'things', shelf: 'item', field: 'location' } })
8266
+ */
8267
+ function backrefs(o) {
8268
+ return {
8269
+ el: "backrefs",
8270
+ label: o.label,
8271
+ from: o.from
8272
+ };
8273
+ }
8274
+ /**
7958
8275
  * Action button: invokes the handler registered in actionRegistry under the key `value`.
7959
8276
  *
7960
8277
  * @layer primitive
@@ -8010,7 +8327,7 @@ registerType("string", {
8010
8327
  widget: "text",
8011
8328
  build(o) {
8012
8329
  const cfg = o.config ?? {};
8013
- const min = cfg.min ?? (o.required === true ? 1 : 0);
8330
+ const min = cfg.min ?? 0;
8014
8331
  return {
8015
8332
  column: "text",
8016
8333
  zod: stringContent((str) => {
@@ -8038,9 +8355,9 @@ registerType("string", {
8038
8355
  function string(o) {
8039
8356
  return declare("string", o);
8040
8357
  }
8041
- /** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
8042
- function pathZod(o) {
8043
- return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
8358
+ /** Shared by localDir/localFile's build(): a server path, judged only when one was entered. */
8359
+ function pathZod(_o) {
8360
+ return stringContent((s) => s);
8044
8361
  }
8045
8362
  registerType("localDir", {
8046
8363
  kind: "localDir",
@@ -8279,20 +8596,22 @@ registerType("time", {
8279
8596
  prim: "time",
8280
8597
  widget: "time",
8281
8598
  build(o) {
8282
- const granularity = o.config?.granularity ?? "second";
8599
+ const granularity = o.config?.granularity ?? "minute";
8600
+ const s = string$1(reqErr()).regex({
8601
+ hour: /^([01]\d|2[0-3])$/,
8602
+ minute: /^([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/,
8603
+ second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
8604
+ }[granularity], { message: vmsg("time_format") });
8283
8605
  return {
8284
8606
  column: "time",
8285
- zod: string$1(reqErr()).regex({
8286
- hour: /^([01]\d|2[0-3])$/,
8287
- minute: /^([01]\d|2[0-3]):[0-5]\d$/,
8288
- second: /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
8289
- }[granularity], { message: vmsg("time_format") }),
8607
+ zod: granularity === "minute" ? s.transform((v) => v.slice(0, 5)) : s,
8290
8608
  hints: { granularity }
8291
8609
  };
8292
8610
  }
8293
8611
  });
8294
8612
  /**
8295
- * Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default 'second').
8613
+ * Time-of-day value with configurable granularity ('hour' | 'minute' | 'second', default
8614
+ * 'minute' — the same default `f.datetime` has always had).
8296
8615
  *
8297
8616
  * @layer primitive
8298
8617
  * @prim time
@@ -8697,10 +9016,9 @@ var MEASURED_ROLES = (opts) => {
8697
9016
  accepts: ENUMERATED,
8698
9017
  normalize: (d) => {
8699
9018
  if (d && accepts(unitRole, d)) return d;
8700
- const { label, required, value } = d?.opts ?? {};
9019
+ const { label, value } = d?.opts ?? {};
8701
9020
  return declare("string", {
8702
9021
  label,
8703
- required,
8704
9022
  value,
8705
9023
  noSearch: d?.opts.noSearch,
8706
9024
  ui: uiOf(d?.opts),
@@ -8859,7 +9177,7 @@ registerType("geo", {
8859
9177
  label: string$1().nullish()
8860
9178
  }),
8861
9179
  structureCode: "geo_structure",
8862
- absenceRoles: ["lat", "lng"]
9180
+ acceptsEmptyRow: true
8863
9181
  });
8864
9182
  /**
8865
9183
  * Geographic point composite {lat, lng, label?}; lat/lng validated to valid coordinate
@@ -8937,7 +9255,7 @@ registerType("illustrated", {
8937
9255
  roles: ILLUSTRATED_ROLES,
8938
9256
  roleZod: () => defaultRoleZod(ILLUSTRATED_ROLES),
8939
9257
  structureCode: "illustrated_structure",
8940
- absenceRoles: ["text", "image"],
9258
+ acceptsEmptyRow: true,
8941
9259
  collectionView: "illustratedList",
8942
9260
  hints: (o) => o.config ? { config: o.config } : {}
8943
9261
  });
@@ -8981,7 +9299,7 @@ registerType("attachment", {
8981
9299
  roles: ATTACHMENT_ROLES,
8982
9300
  roleZod: () => defaultRoleZod(ATTACHMENT_ROLES),
8983
9301
  structureCode: "attachment_structure",
8984
- absenceRoles: ["file"],
9302
+ acceptsEmptyRow: true,
8985
9303
  collectionView: "attachmentList"
8986
9304
  });
8987
9305
  /**
@@ -9082,11 +9400,10 @@ function periodRole(key, label, mkSubDecl) {
9082
9400
  if (d && accepts(def, d)) return d;
9083
9401
  const canonical = mkSubDecl(label);
9084
9402
  if (!d) return canonical;
9085
- const { label: l, required, value } = d.opts;
9403
+ const { label: l, value } = d.opts;
9086
9404
  return declare(canonical.factory, {
9087
9405
  ...canonical.opts,
9088
9406
  label: l ?? canonical.opts.label,
9089
- required,
9090
9407
  value,
9091
9408
  noSearch: d.opts.noSearch,
9092
9409
  ui: uiOf(d.opts)
@@ -9370,8 +9687,7 @@ function keyValue(raw) {
9370
9687
  type: valueType
9371
9688
  }
9372
9689
  },
9373
- by: "key",
9374
- required: o.required
9690
+ by: "key"
9375
9691
  });
9376
9692
  }
9377
9693
  /** Roles of a range: the two endpoints, integer or real depending on `isInt`. */
@@ -9537,7 +9853,8 @@ var PRIMITIVES = {
9537
9853
  url,
9538
9854
  divider,
9539
9855
  info,
9540
- button
9856
+ button,
9857
+ backrefs
9541
9858
  };
9542
9859
  /** Assembles `f`, guaranteeing no preset/block shadows a primitive. */
9543
9860
  function composeF(presets, blocks) {
@@ -9557,12 +9874,11 @@ var field = new Proxy({}, {
9557
9874
  has: (_t, k) => k in composedField()
9558
9875
  });
9559
9876
  function toClient(field) {
9560
- const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
9877
+ const { kind, label, agent, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
9561
9878
  return {
9562
9879
  kind,
9563
9880
  label,
9564
9881
  ...agent !== void 0 && { agent },
9565
- required,
9566
9882
  prim,
9567
9883
  hints,
9568
9884
  options,
@@ -9640,7 +9956,7 @@ function accepts(def, d) {
9640
9956
  }
9641
9957
  /**
9642
9958
  * The correction every role gets for free: keep what the author said ABOUT the field
9643
- * (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
9959
+ * (`label`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
9644
9960
  * drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
9645
9961
  * picture — and `multiple`, because a composite role owns exactly one column). A role whose
9646
9962
  * own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
@@ -9648,10 +9964,9 @@ function accepts(def, d) {
9648
9964
  */
9649
9965
  function retypeTo(factory) {
9650
9966
  return (d) => {
9651
- const { label, required, value } = d?.opts ?? {};
9967
+ const { label, value } = d?.opts ?? {};
9652
9968
  return declare(factory, {
9653
9969
  label,
9654
- required,
9655
9970
  value,
9656
9971
  noSearch: d?.opts.noSearch,
9657
9972
  ui: uiOf(d?.opts)
@@ -9837,13 +10152,69 @@ function partColumns(parts) {
9837
10152
  }
9838
10153
  return cols;
9839
10154
  }
9840
- function partsRowShape(shape, parts, tolerance = "strict") {
10155
+ /**
10156
+ * A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
10157
+ * the composite's DEFAULT schema per role — the literal the factory writes by hand
10158
+ * (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
10159
+ * schema guards its (role-named) key:
10160
+ *
10161
+ * - a plain STORED part is keyed by its role name, matching the value object and the
10162
+ * column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
10163
+ * to its default filling keeps the default schema — byte-identical to the hand-written
10164
+ * literal, so every existing structure/unit/range code and message is preserved.
10165
+ * - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
10166
+ * default schema for that role. This is what makes a RETYPED filling real: an
10167
+ * `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
10168
+ * and an `f.relation({…})` currency accepts a record id instead of being measured
10169
+ * against an ISO-4217 string rule it was never meant to satisfy. The filling also
10170
+ * brings its own optionality and its own min/max — an author replacing a filling
10171
+ * takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
10172
+ * replaced magnitude keeps a bound.
10173
+ * - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
10174
+ * other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
10175
+ * whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
10176
+ * moment later and the write path recomputes the column. It is stripped rather than
10177
+ * `z.never()`-rejected because a read hands the client that key (it is a real column,
10178
+ * present in every response), so rejecting it would break a read → PATCH-the-whole-object
10179
+ * round trip.
10180
+ * - a CLIENT-OWNED stored part of a composite also accepts
10181
+ * an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
10182
+ * for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
10183
+ * magnitude column is set and whose unit column is empty reads back as
10184
+ * `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
10185
+ * empty — `0` is a value), and feeding that exact object back used to fail with
10186
+ * `measured_structure`: the write path refused the shape its own read produced. The null
10187
+ * slot now parses through and is written back as NULL, so the round trip is closed and
10188
+ * clearing ONE role of a filled composite works instead of erroring.
10189
+ * NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
10190
+ * how "this record has no value there" arrives, while an ABSENT key is a writer that
10191
+ * never mentioned a role it owns — which stays the structure error it has always been
10192
+ * (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
10193
+ * all-or-nothing rule for a DECLARATION, and it is untouched.
10194
+ * A composite used to be able to override this: `required: true` kept every client-owned
10195
+ * slot MANDATORY, null included, so that "tolerate null" could not turn a demanded field
10196
+ * optional. Nothing demands a value now, so there is no override and the rule above is the
10197
+ * only one.
10198
+ */
10199
+ /**
10200
+ * How much a CLIENT-OWNED slot forgives in the row shape: exactly one thing, an explicit
10201
+ * `null`. `.nullable()`, never `.nullish()` — a part that is MISSING entirely is still a
10202
+ * malformed row (`{lat: 5.5}` is half a coordinate), which is the one rule about composites
10203
+ * that judges CORRECTNESS rather than presence.
10204
+ *
10205
+ * There used to be a `RowTolerance` of three levels, and the field's `required` is what chose
10206
+ * between them: 'strict' when the field demanded a value, 'null-or-missing' when the value was
10207
+ * ABSENT under that demand — the third existing only so an absent required composite did not
10208
+ * report the same absence twice, once as `required` and once as a structure code. With nothing
10209
+ * able to demand a value there is no first issue to restate, so both levels and the type
10210
+ * itself are gone.
10211
+ */
10212
+ function partsRowShape(shape, parts) {
9841
10213
  const out = { ...shape };
9842
10214
  for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
9843
10215
  else {
9844
10216
  const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
9845
- const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
9846
- out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
10217
+ out[p.key] = own.nullable();
9847
10218
  }
9848
10219
  return out;
9849
10220
  }
@@ -9872,7 +10243,7 @@ function stripServerOwnedParts(value, parts) {
9872
10243
  * dimensions, check(single)) and differed only in the validation code and the per-role
9873
10244
  * default zod.
9874
10245
  *
9875
- * raw → jsonValue → absent by `absenceRoles`? (required 'required')
10246
+ * raw → jsonValue → carries nothing? (passthrough, when `acceptsEmptyRow`)
9876
10247
  * → carries nothing at all? (pass through, there is nothing to judge)
9877
10248
  * → rowSchema (partsRowShape) → stripServerOwnedParts → optional per-type refine
9878
10249
  *
@@ -9882,28 +10253,16 @@ function stripServerOwnedParts(value, parts) {
9882
10253
  * exactly once.
9883
10254
  */
9884
10255
  function buildComposite(def, opts, parts) {
9885
- const required = opts.required ?? false;
9886
- const roleZod = def.roleZod(opts, parts);
9887
- const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
9888
- const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
10256
+ const rowSchema = object(partsRowShape(def.roleZod(opts, parts), parts));
9889
10257
  const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
9890
- const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
9891
- const clientKeys = def.absenceRoles ? roleKeys() : void 0;
10258
+ const clientKeys = def.acceptsEmptyRow ? roleKeys() : void 0;
9892
10259
  const zod = unknown().transform((raw, ctx) => {
9893
10260
  const parsed = jsonValue(raw);
9894
10261
  const p = parsed;
9895
10262
  const isRow = p == null || typeof p === "object" && !Array.isArray(p);
9896
10263
  /** Every one of `keys` empty in this value — `null`/absent alike. */
9897
10264
  const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
9898
- let absent = false;
9899
- if (absenceKeys && clientKeys) {
9900
- absent = allEmpty(absenceKeys);
9901
- if (absent && required) ctx.addIssue({
9902
- code: ZodIssueCode.custom,
9903
- message: vmsg("required")
9904
- });
9905
- if (allEmpty(clientKeys)) return raw;
9906
- }
10265
+ if (clientKeys && allEmpty(clientKeys)) return raw;
9907
10266
  if (typeof parsed === "string") {
9908
10267
  ctx.addIssue({
9909
10268
  code: ZodIssueCode.custom,
@@ -9911,7 +10270,7 @@ function buildComposite(def, opts, parts) {
9911
10270
  });
9912
10271
  return NEVER;
9913
10272
  }
9914
- const r = (absent ? absentRowSchema : rowSchema).safeParse(parsed);
10273
+ const r = rowSchema.safeParse(parsed);
9915
10274
  if (!r.success) {
9916
10275
  const message = typeof def.structureCode === "function" ? def.structureCode(r.error) : vmsg(def.structureCode);
9917
10276
  ctx.addIssue({
@@ -9964,12 +10323,10 @@ function materialize(decl, name) {
9964
10323
  if (isComposite(type) && (opts.multiple ?? false) && parts.length > 0 && !type.selfManages?.has("multiple")) return collectionGroup(name, opts, parts, type.collectionView);
9965
10324
  const built = isComposite(type) ? buildComposite(type, opts, parts) : type.build(opts, parts, name);
9966
10325
  const selfOptional = type.selfManages?.has("optional") ?? false;
9967
- const required = selfOptional ? false : opts.required ?? false;
9968
10326
  const base = {
9969
10327
  kind: type.kind,
9970
10328
  label: opts.label ?? "",
9971
10329
  ...opts.agent !== void 0 && { agent: opts.agent },
9972
- required,
9973
10330
  prim: type.prim,
9974
10331
  column: built.column,
9975
10332
  hints: built.hints ?? {},
@@ -9980,7 +10337,7 @@ function materialize(decl, name) {
9980
10337
  columns: built.columns,
9981
10338
  parts
9982
10339
  },
9983
- zod: selfOptional ? built.zod : optionalize(built.zod, required)
10340
+ zod: selfOptional ? built.zod : optionalize(built.zod)
9984
10341
  };
9985
10342
  return wrapKey(name, opts, type.selfManages?.has("multiple") ? base : applyMultiple(base, opts.multiple ?? false));
9986
10343
  }
@@ -10010,7 +10367,6 @@ function collectionGroup(key, opts, parts, collectionView) {
10010
10367
  key,
10011
10368
  scope: "nest",
10012
10369
  multiple: true,
10013
- required: opts.required ?? false,
10014
10370
  label: opts.label ?? key,
10015
10371
  ...opts.agent !== void 0 && { agent: opts.agent },
10016
10372
  display: "wrap",
@@ -10139,6 +10495,8 @@ var src_default = definePlugin({
10139
10495
  id: "webchat",
10140
10496
  version: "1.0.0",
10141
10497
  dependsOn: [],
10498
+ label: "webchat.plugin.label",
10499
+ description: "webchat.plugin.description",
10142
10500
  settings: defineSettings({
10143
10501
  label: "webchat.settings.label",
10144
10502
  fields: { reasoning_display: field.string({