@quillmark/wasm 0.106.0 → 0.108.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/CHANGELOG.md CHANGED
@@ -1,5 +1,325 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.108.0 - 2026-08-18
4
+
5
+ - fix: **the value ladder is cut per cell, so the plate is total at every
6
+ depth.** An absent container returned a value instead of descending, and
7
+ everything below it was decided by that one branch: an absent `contact` never
8
+ reached `contact.email`'s own `default:`, and a container `default: {name: A}`
9
+ crossed whole, so a declared property it omitted was **missing from the
10
+ plate** — a direct Typst read of it a compile error, on an address
11
+ `form-field` still binds. Two spellings of the same state disagreed:
12
+ authoring `contact: {}` rendered the leaf defaults that leaving `contact` out
13
+ did not, and `default: {}` — documented as expanding to the blank-filled
14
+ shape — emitted `{}` with no declared key at all. Resolution is now a descent:
15
+ a rung supplies a *seed*, and the same composition runs over it whichever rung
16
+ it came from, so absence is inherited rather than terminal and each cell cuts
17
+ its own ladder. A partial element inside an `array` `default:` is completed
18
+ against `items` as an authored element is. The variant container already
19
+ worked this way and stops being the special case.
20
+ - **breaking** a `default:`/`example:` on an `object` with `properties` is a
21
+ load error (`quill::default_on_namespace`, `quill::example_on_namespace`),
22
+ naming the properties that hold it. A quill declaring one loaded before, so
23
+ the upgrade reads as a quill that stopped loading rather than as a fix; no
24
+ in-tree quill declares one. A typed dictionary is a namespace, not a cell:
25
+ the container literal was a second declaration of a value the property
26
+ already holds, and the two axes read different ones — `default: {name: A}`
27
+ rendered `A` while `must_fill` derives per property and still reported `name`
28
+ unauthored. It was also unchecked, so `default: {nope: 1}` loaded and crossed
29
+ an undeclared key to the plate. This is the variant container's rule
30
+ (`quill::default_type_mismatch`) generalized; an `array` keeps its literal,
31
+ since `items:` fixes the element type but never the arity.
32
+ - **breaking** `must_fill:` is retired: obligation is a reading of `default:`,
33
+ never a declaration of its own. Declaring the key is a load error
34
+ (`quill::field_parse_error`) naming the migration that field's shape takes,
35
+ `FieldSchema::must_fill()` is `default.is_none()`, and the raw
36
+ `FieldSchema.must_fill` field is gone. Four of the five legacy declarations
37
+ restate the derivation and migrate by **deletion**: `must_fill:` on a typed
38
+ dictionary (a namespace carries no obligation — its leaves do), `must_fill:
39
+ true` with no `default:`, and `must_fill: false` beside one. `must_fill:
40
+ false` with no `default:` becomes `default: <the type's blank>` (`""`, `[]`,
41
+ `0`, `false`) — already the corpus's most common `default:`. The fifth,
42
+ `must_fill: true` beside a `default:`, is the one behavior deleted and the
43
+ one judgment call: keep the `default:` to render the value unasked, or move
44
+ it to `example:` to keep the ask. An example fills the blueprint cell the
45
+ default vacated, seeds *carrying* the `!must_fill` marker where a
46
+ `default:`-only field seeds nothing, and never renders — so an untouched
47
+ document renders the blank rather than asserting a value nobody chose. For a
48
+ `string` or `enum` the blueprint bytes are identical either way; three shapes
49
+ are not. A `richtext` example never inlines, so its cell becomes a bare
50
+ marker and the value survives only as the `# e.g.` hint. An
51
+ `integer`/`number`/`boolean` blank is indistinguishable at the plate from an
52
+ authored zero. On a variant container the two targets select different
53
+ worlds: `default: CUI` renders the CUI world and obliges its cells, while
54
+ `example: CUI` leaves the discriminant blank. Also removed:
55
+ `quillmark:must_fill` from the transform schema, which is the wire *validity*
56
+ contract, and an unauthored must-fill cell is wire-valid by design; the
57
+ declaration view carries `default:` for a consumer that wants to derive.
58
+ No in-tree quill declared the key and the declaration view emits only what an
59
+ author wrote, so no emitted JSON changes for any real quill — the WASM
60
+ `QuillFieldSchema` TS interface loses `must_fill?: boolean`, a compile-time
61
+ break for editors typed against it.
62
+ - fix: **seeding descends into a container's `example:`.** A dictionary with no
63
+ `example:` of its own seeded nothing, so a property's `example:` was
64
+ unreachable at every projection — the render floor never emits an example, and
65
+ the blueprint is a different document. A seed is now composed from whatever
66
+ its cells commit, sparse at every depth, and stays absent when none of them
67
+ commit anything. Markers ride the cell they belong to.
68
+ - fix: `resolve()`'s rung is honest for a container. It has no rung of its own,
69
+ so it reports the strongest that contributed: `authored` when the document
70
+ wrote any of it, else `default` when a cell below resolved to one, else the
71
+ floor. An absent container over defaulted cells read `blank` while rendering
72
+ those defaults, which is the fact an editor ghosts from. Nothing inside a
73
+ container the document did not author reads `authored`. A variant container
74
+ counts its live world's cells the same way, so writing one of them lifts a
75
+ container whose discriminant fell to the schema's `default:`.
76
+
77
+ - chore(deps): the Typst floor moves to 0.15.1. The workspace already resolved
78
+ there under the 0.15.0 caret; the pin now names the version the tree is built
79
+ and tested against. `pdf-writer` stays at 0.15.0, still the version
80
+ `typst-pdf` → `krilla` forces and the newest published.
81
+ - docs: `0.107-to-0.108.md`, the guide for this step. It leads with the two load
82
+ errors, since both reject the quill rather than the document, and gives
83
+ `must_fill: true` beside a `default:` — the one behavior deleted — the space
84
+ its judgment call needs. `BLUEPRINT.md` § "Typed dictionaries" loses the `{}`
85
+ expansion and the container-literal renderings with the cascade that produced
86
+ them, and states the nesting the 0.107 collapse admits.
87
+
88
+ ## v0.107.0 - 2026-08-17
89
+
90
+ - fix(typst): `display(field, ..)` validates its address against the schema, the
91
+ assert `form-field` and `field-region` already carry. It is the one helper keyed
92
+ by address rather than by value, and it was the one accepting an address the
93
+ schema does not have: `display("issed", "[year]")` compiled, drew nothing, and
94
+ reported nothing — the failure a plate author is least placed to see, a card
95
+ address being a string the plate builds by concatenating `$path`. `_qm-display`
96
+ cannot catch it, carrying an entry per *present* date, so a blank date and a
97
+ typo are absent from it alike; `_qm-known-path` answers about the schema and
98
+ tells the two apart. A known address carrying no date still returns `none`, so
99
+ a `== none` fallback is unchanged: the assert is about the address, the `none`
100
+ about the value.
101
+ - fix: **a container's own `default:` reaches the plate as content.** The render
102
+ floor read `default_content` only for a `richtext`/`plaintext` leaf. An
103
+ `object` or `array` carrying its `default:` on the container fell through to
104
+ the raw literal instead, crossing as unimported markdown where every other
105
+ content position delivers a canonical content object. The companion was
106
+ already cached and never read. The floor now keys off the cache — present is
107
+ the form to commit, absent over a content-bearing tree blank-fills — which
108
+ covers leaf and container alike and drops the type test. `usaf_memo`'s
109
+ `references` (`array<richtext(inline)>`, `default: []`) carried the same
110
+ defect, invisible only because the list was empty.
111
+ - **breaking** a container-shaped `default:`/`example:` on a variant-bearing
112
+ enum is a load error (`quill::default_type_mismatch`,
113
+ `quill::example_type_mismatch`). A quill declaring one loaded before, so the
114
+ upgrade reads as a quill that stopped loading rather than as a fix.
115
+ The container is the shape a *document* writes. As a schema literal it cached
116
+ no content form and yielded no discriminant, so the field blank-filled in
117
+ silence as if nothing were declared. The diagnostic names the discriminant
118
+ spelling instead, which is where a world's cells carry their own literals.
119
+ Scalar literals are unaffected.
120
+
121
+ - feat: **every type nests at every depth**. A property or an element is an
122
+ ordinary field, so it carries whatever a card-level field carries, itself
123
+ included: `object<array<string>>`, `array<array<integer>>`, a typed table whose
124
+ row holds a typed dictionary, and a variant cell holding either.
125
+ `quill::nested_object_not_supported` and `quill::nested_array_not_supported`
126
+ are gone, and `ShapePosition`'s three positions collapse to the one question
127
+ the walk still asks — is this card level, where `variants:` and `ui.group` are
128
+ the two keys that live. A widening: every quill that loaded before loads
129
+ unchanged.
130
+ The depth budget was what the flat address tables existed for.
131
+ `SchemaMeta` carried six name-keyed tables (`array_fields`, `object_fields`
132
+ and their card twins) so the helper's `_qm-known-path` could enumerate two
133
+ suffix steps rather than derive a grammar; they are replaced by one address
134
+ tree — the schema pruned to the steps it offers — that the helper and the span
135
+ scan both walk, converging on the unbounded descent `pdfform::bind` always
136
+ had. Three components deriving an address become one walk each side of the
137
+ seam, and `quillmark/tests/address_grammar.rs` pins the two against the deep
138
+ shapes as well as the shallow ones.
139
+ `variants:` stays card-level, now on its own reasoning rather than by
140
+ inheriting the depth ban: a variant's shape is a function of the schema *and*
141
+ the discriminant, and the union projection, the once-bound form, the plate's
142
+ single branch and `validation::out_of_variant` each hold because that gap is
143
+ one level deep ([SCHEMAS.md](prose/canon/SCHEMAS.md) §"Enum variants"). An
144
+ array-valued variant cell used to report `nested_array_not_supported`, whose
145
+ message named array elements and object properties — neither the situation;
146
+ the shape is now legal and `quill::variant_placement` is left saying only what
147
+ it means.
148
+ - fix: `blueprint()` expands a container at every depth. `build_property_mapping`
149
+ spent each property through the scalar builder, so a nested `object` or typed
150
+ table rendered as `key: null # object` — its own properties, their markers and
151
+ their annotations absent — where the same shape one level up expanded. It now
152
+ recurses, and the card-level and nested paths are one implementation, so a
153
+ `default:` covers its subtree identically wherever it is declared. No
154
+ document changes shape: the shapes this fixes could not be declared before.
155
+ - fix: a content leaf's `default:` reaches the plate from **every** position it
156
+ can be declared in, not only card level. The load pass that imports each
157
+ richtext/plaintext literal into its companion cache walked the card's field
158
+ map, so an `object` property, a typed-table row property and a variant cell
159
+ each kept their authored `default:` and cached nothing; the render floor read
160
+ the leaf's empty companion and blank-filled. A document authoring only the
161
+ container (`dict: {}`, `rows: [{}]`, `c: {value: CUI}`) rendered correctly
162
+ with the author's default missing, and nothing upstream had anything to
163
+ report. The walk now recurses `properties` / `items` / `variants`, the shapes
164
+ `field_contains_content` already descended, so such a document now renders
165
+ **with** the author's default — a render-output change for any quill that
166
+ declared one. Importing a literal is also what checks it, so a nested
167
+ `richtext(inline)` violation — in a `default:` or an `example:` — now fails
168
+ load as a card-level one always has, naming the leaf's declaration path.
169
+ **Breaking on that second count**: the literal loaded before, so a quill
170
+ carrying one stops loading. Nested `example:` *surfacing* was never broken:
171
+ the blueprint prints the raw literal at every depth.
172
+ - **breaking** typst: a plate's direct read of a typed-table row cell regions on
173
+ the cell (`refs.0.org`), where it regioned on the whole array before — a
174
+ *wrong* address, not a missing one, routing a click on the org cell to the
175
+ entire table. The span scan was the third component deriving a schema address
176
+ and the one left at the one-level ceiling: 0.106 lifted the lowering walk and
177
+ `_qm-known-path` to the row property, so the three no longer agreed, and the
178
+ scan is the one that decides what a *read* is attributed to. It now takes the
179
+ index step (`.at(n)`, the only spelling Typst has for an array index) and then
180
+ the row property, gated on the `array_fields` table. Each step is its own
181
+ address, so a whole-row read names the row (`refs.0`) and a primitive
182
+ element's read names the element (`tags.0`); a negative index and an
183
+ undeclared row key mint nothing and fall back as before. Consumers keying on
184
+ the array's address for element ink see the narrower address instead. Explicit
185
+ `field-region` / `form-field` claims are unchanged, and the alias lane keeps
186
+ parity: `#let row = data.refs.at(0)` … `#row.org` regions on `refs.0.org`.
187
+ - test: one table pins the schema address grammar on both backends
188
+ (`quillmark/tests/address_grammar.rs`), covering every position the nesting
189
+ contract admits, each position's card twin, and the rejects that bound each
190
+ step. `PLATE_DATA.md` promises a plate author that one address binds on
191
+ either backend, and the grammar is written twice to keep it — an unbounded
192
+ schema walk in `pdfform::bind`, an enumeration of the suffix forms in the
193
+ Typst helper's `_qm-known-path` — reading two different projections of the
194
+ same `QuillConfig`, so either side can move alone. `pdfform` exports
195
+ `resolves_schema_address` (`#[doc(hidden)]`) so the pin can ask both the same
196
+ question. A body address is the plate grammar's alone and is pinned as such.
197
+
198
+ - **breaking** typst: lowering dispatches on the schema node beside each value
199
+ rather than on tables of top-level field names, so a declared type means the
200
+ same thing wherever it is declared. A `date`, `richtext` or `plaintext`
201
+ declared inside an `object` or an `array` row reached the plate as its raw
202
+ wire value before — a bare string for a date, and for a rich field the
203
+ *internal canonical-content JSON*, rendered as a Typst dict — while the same
204
+ type one level up lowered correctly. Ten of the twelve nested positions the
205
+ schema admits degraded that way, silently: core coerced and validated the
206
+ value correctly, so nothing upstream had anything to report. `contact.note`
207
+ is now a markup block, `contact.reply_by` and `rows.0.on` are `datetime`s,
208
+ and `_qm-plaintext` gains the nested entries that closed the
209
+ `plaintext(field)` escape hatch. The walk is the inverse of the one
210
+ `build_transform_schema` builds the node with, so it cannot be shallower than
211
+ the schema is.
212
+ - **breaking** typst: a `date` / `datetime` field lowers to a **native**
213
+ `datetime`, not the `(value:, display:)` wrapper. `data.issued.year()`,
214
+ `data.issued < data.due` and handing the field to a datetime-consuming
215
+ package are ordinary Typst; `.value` and the paren form `(data.issued.display)(..)`
216
+ are hard Typst compile errors, never a silent degrade, and all consumers are
217
+ first party. A date has no canonical rendering the way authored text does —
218
+ every rendering of `2026-01-02` is a typographic decision the plate owns — so
219
+ it lowers to its value and reaches ink by address instead.
220
+ - feat(typst): `display(field, ..args)`, a date field's content projection,
221
+ keyed by schema address rather than carried on the value. It places rendered
222
+ ink whose glyphs are born in generated source, so a date formatted through a
223
+ `#let` binding, a per-card loop variable, or a vendored package keeps a
224
+ region on its schema field — the affordance the value-object existed to buy,
225
+ now available to any date at any depth without shaping the value. `none` for
226
+ a blank date, so a `== none` fallback still fires. The rule plates follow:
227
+ want a value → `data.<field>`; want clickable ink → `display("<field>", ..)`.
228
+ - feat: a variant cell may carry **any type a card field may**, prose and dates
229
+ included — `quill::variant_field_type` is gone. The load error existed because
230
+ lowering read flat top-level name tables that could not descend into a
231
+ container, so a `date` or `richtext` cell inside a variant would have loaded
232
+ clean and reached the plate as its raw wire value; the schema-node walk reads
233
+ the cell's own declaration, leaving the ceiling nothing to protect. Every value
234
+ surface already descended per live-world cell — coercion through
235
+ `conform_value`, validation through `validate_value`, the render floor through
236
+ `resolve_value` — so the widening needed one real fix:
237
+ `field_contains_content` returned `false` for a variant container on the
238
+ strength of this very guard, which would have silently skipped the content
239
+ companion caches, the resting-form conversion and the seed path for a variant
240
+ content cell. It now answers on the union of the worlds' cells. Containers
241
+ are included: "every type nests at every depth" lands in this same release, so
242
+ a variant cell holds a typed table or a typed dictionary like any other
243
+ position. `variants:` itself stays card-level (`quill::variant_placement`) on
244
+ the reasoning stated there.
245
+
246
+ - **breaking** typst: the `plaintext(field)` helper and its `_qm-plaintext`
247
+ table are removed. Shipped in 0.94 as the sanctioned content→`str` coercion,
248
+ it never acquired a caller: no plate, no vendored package, and no binding
249
+ surface referenced it, and the `create-auto-grid` consumer its own docstring
250
+ cited passes an `array<string>` rather than a content field, so the only
251
+ things exercising it were its three tests. It also carried a three-way name
252
+ collision with the `plaintext` field type and that type's document-layer
253
+ resting shape, which took a standing caveat in canon and the template to hold
254
+ down. A plate that needs a `str` from a content field now has no route, which
255
+ is the honest state of the requirement: reinstating it is additive and cheap
256
+ when a plate actually asks.
257
+ - feat(typst): a typed table's row property (`refs.0.org`) is a writable
258
+ schema address. `form-field(field:)` and `field-region` capped at one suffix
259
+ step while pdfform's resolver descended unboundedly, so a shape
260
+ `ShapePosition` explicitly admits bound on one backend only, against
261
+ `PLATE_DATA.md`'s claim that one address binds on either. `array_fields` now
262
+ carries each array's row property names, the same shape `object_fields`
263
+ already had.
264
+ - fix(typst): a non-blank date the shared parsers reject raises
265
+ `backend::invalid_date` from codegen rather than a pre-pass over top-level
266
+ name tables, so the check covers every depth. Only a direct `apply` can
267
+ deliver one; coercion parses the same way.
268
+ - feat(typst): a scalar read through a `let` alias regions on the address the
269
+ chain it names would carry, so `#let c = data.classification` … `#c.poc`
270
+ surfaces `classification.poc` where it surfaced nothing at all — not the
271
+ container's address, absent. Binding a container once and stepping into it
272
+ three times is the refactor 0.106's property addressing invites, and it cost
273
+ the address silently, the document still rendering correctly. An alias holds
274
+ only where the plate binds the name exactly once to one whole `data` chain: a
275
+ name a second `let`, a closure parameter, a loop pattern, an import, or an
276
+ assignment could rebind is dropped rather than risk attributing another
277
+ value's ink to the field, and a wildcard import disqualifies every alias.
278
+ Which name is followed is half the rule; which *occurrence* is the other half,
279
+ since a schema field name collides freely with the parameter names of a callee
280
+ the plate never defines (`date`, `title`, `caption`, `align`, `subject`). Only
281
+ an occurrence that reads the binding anchors: an identifier spelling the alias
282
+ as a named argument (`#text(size: 12pt)`), a dict key, another value's field
283
+ (`#styles.subject`) or an imported item's path draws no ink off the field, and
284
+ a window minted over one would carry a *wrong* address rather than a missing
285
+ one.
286
+ Laundering past that — a function parameter, a destructured binding, a
287
+ per-card loop variable — is unchanged and still needs a `field-region` claim,
288
+ now stated for plate authors under "Which Reads Get Regions" in the Typst
289
+ backend guide. Content and date fields are unaffected: their ink is born in
290
+ generated code.
291
+
292
+ - feat(wasm): `VARIANT_DISCRIMINANT_KEY` joins the runtime's static exports,
293
+ beside `MAIN_CARD_ADDR`. v0.106.0 announced the constant as new API but shipped
294
+ it to Rust only, leaving a JS consumer reading or writing a variant container
295
+ to spell `"value"` itself — a hardcoded copy of the one value whose purpose is
296
+ to not be hardcoded, at the seam where the two can drift unobserved, since the
297
+ key crosses the boundary inside untyped container data. The `.d.ts` types it as
298
+ the string *literal*, which `string` would stop narrowing an index into the
299
+ container. `known_names_drift.rs` pins both spellings against the Rust
300
+ constant, the guard the hand-spelled name tables beside it already carry.
301
+ `VariantFields` stays Rust-only: it is a type alias the TypeScript surface
302
+ already inlines as `QuillFieldSchema.variants`, naming no shape a consumer
303
+ builds.
304
+ - docs: `0.105-to-0.106.md`, the migration guide v0.106.0 shipped without. It
305
+ leads with the region-address shift rather than the three `!` entries: the
306
+ changelog is organized by feature, and the address step is one clause inside a
307
+ long entry while being the item most likely to break a working consumer, since
308
+ `FieldRegion.field` is a bare `string` that no type checker reports a grammar
309
+ change under. The guide states both gates on the step, and points a consumer at
310
+ `doc.pathFor` for the prefix grammar it would otherwise match as a literal —
311
+ with that helper's limit stated, a field name passing through it verbatim.
312
+ `CONTRIBUTING.md` gains the two rules that would have caught the gap: `!` marks
313
+ an observable-contract shift even where no type changes, and a release carrying
314
+ one ships its guide.
315
+ - docs: `0.106-to-0.107.md`, this release's guide, under the rule the entry above
316
+ adds. It leads with the region-address index step — `main.refs[0].org` where
317
+ `main.refs` stood, a wrong address rather than a missing one — and then with
318
+ the two schema literals that stop a quill loading, since those read as a build
319
+ that broke rather than as a fix. `ERROR.md` names `display(..)` as the
320
+ address-keyed template-author contract, `plaintext(..)` having been removed
321
+ here.
322
+
3
323
  ## v0.106.0 - 2026-08-16
4
324
 
5
325
  - feat(typst,pdfform): a schema address may step one property into a declared
@@ -122,22 +442,6 @@
122
442
  names, which PyPI rejected the sdist for lacking. v0.104.0 and v0.105.0 are
123
443
  wheels only.
124
444
 
125
- <!-- seed: commits since v0.105.0, confirm the entries above cover them, then delete this comment
126
- - chore(repo): union-merge CHANGELOG.md
127
- - feat: migrate usaf_memo to enum variants, document the axis
128
- - feat(core): enum variants — fields that exist only for one enum value
129
- - fix(fixtures): drop the rule under the indorsement date
130
- - fix(fixtures): size the indorsement date widget in ems, not inches
131
- - fix(fixtures): set the memo's indorsement date widget like a date
132
- - feat(typst,pdf): font, size, and align on injected form fields
133
- - Make the usaf_memo indorsement's blank date a bound fill-in widget
134
- - Compress the field-region prose
135
- - Add `field-region` so plates can tie composed content to a field
136
- - fix(python): declare license-files so the sdist ships its LICENSE
137
- - Anchor an undeclared property at the property, not at a bare name
138
- -->
139
-
140
-
141
445
  ## v0.105.0 - 2026-08-14
142
446
 
143
447
  - feat(core,wasm,python)!: a `Content` nested inside a composite field is
package/README.md CHANGED
@@ -396,8 +396,8 @@ that value under a type-only `# <type>` annotation and the render path uses it
396
396
  when the document omits the field. Without one, an `example` takes the cell as
397
397
  a suggested value, and an absent field blank-fills.
398
398
 
399
- **Obligation** — whether a human must author the field, declared by
400
- `must_fill:` and deriving from `default:`'s absence when left unset. An obliged
399
+ **Obligation** — whether a human must author the field, read off `default:`'s
400
+ absence: a defaulted field asks nobody, a defaultless one asks. An obliged
401
401
  field carries the `!must_fill` marker in `quill.blueprint`, and
402
402
  `quill.validate(doc)` emits the non-fatal `validation::must_fill` warning while
403
403
  the document leaves it unauthored — from either of two triggers, named by the
@@ -347,7 +347,14 @@ export interface PaintResult {
347
347
 
348
348
 
349
349
 
350
- /** The commitment-ladder rung that produced a `ResolvedField.value`. */
350
+ /**
351
+ * The commitment-ladder rung that produced a `ResolvedField.value`.
352
+ *
353
+ * A container has no rung of its own — it is a namespace, and its value is the
354
+ * composition of its cells' — so it reports the strongest rung that contributed:
355
+ * `authored` if the document wrote any of it, else `default` if any cell below
356
+ * resolved to one, else `blank`.
357
+ */
351
358
  export type FieldSource = "authored" | "default" | "blank";
352
359
 
353
360
  /**
@@ -447,12 +454,11 @@ export interface QuillCardBody {
447
454
 
448
455
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
449
456
  *
450
- * Two independent axes, and no separate `required` one. `default` and
451
- * `example` say what the cell holds; `must_fill` says whether a human must
452
- * author it, deriving from `default`'s absence when left unset. An obliged
453
- * field carries a `!must_fill` marker in the blueprint and warns
457
+ * One declaration, and no `required` key. `default` and `example` say what the
458
+ * cell holds, and `default`'s absence is the obligation: a field nobody
459
+ * declared a value for carries a `!must_fill` marker in the blueprint and warns
454
460
  * `validation::must_fill` while the document leaves it unauthored. Neither
455
- * axis gates render: an absent field blank-fills.
461
+ * gates render: an absent field blank-fills.
456
462
  */
457
463
  export interface QuillFieldSchema {
458
464
  type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
@@ -467,9 +473,6 @@ export interface QuillFieldSchema {
467
473
  * member. Declaring it makes the field rest as a container,
468
474
  * `{value: <member>, …that member's fields}`, rather than a bare string. */
469
475
  variants?: Record<string, Record<string, QuillFieldSchema>>;
470
- /** Whether a human must author the field. Absent, it derives from
471
- * `default`: a defaulted field is unobliged, a defaultless one obliged. */
472
- must_fill?: boolean;
473
476
  ui?: QuillFieldUi;
474
477
  properties?: Record<string, QuillFieldSchema>;
475
478
  items?: QuillFieldSchema;
Binary file
@@ -347,7 +347,14 @@ export interface PaintResult {
347
347
 
348
348
 
349
349
 
350
- /** The commitment-ladder rung that produced a `ResolvedField.value`. */
350
+ /**
351
+ * The commitment-ladder rung that produced a `ResolvedField.value`.
352
+ *
353
+ * A container has no rung of its own — it is a namespace, and its value is the
354
+ * composition of its cells' — so it reports the strongest rung that contributed:
355
+ * `authored` if the document wrote any of it, else `default` if any cell below
356
+ * resolved to one, else `blank`.
357
+ */
351
358
  export type FieldSource = "authored" | "default" | "blank";
352
359
 
353
360
  /**
@@ -447,12 +454,11 @@ export interface QuillCardBody {
447
454
 
448
455
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
449
456
  *
450
- * Two independent axes, and no separate `required` one. `default` and
451
- * `example` say what the cell holds; `must_fill` says whether a human must
452
- * author it, deriving from `default`'s absence when left unset. An obliged
453
- * field carries a `!must_fill` marker in the blueprint and warns
457
+ * One declaration, and no `required` key. `default` and `example` say what the
458
+ * cell holds, and `default`'s absence is the obligation: a field nobody
459
+ * declared a value for carries a `!must_fill` marker in the blueprint and warns
454
460
  * `validation::must_fill` while the document leaves it unauthored. Neither
455
- * axis gates render: an absent field blank-fills.
461
+ * gates render: an absent field blank-fills.
456
462
  */
457
463
  export interface QuillFieldSchema {
458
464
  type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
@@ -467,9 +473,6 @@ export interface QuillFieldSchema {
467
473
  * member. Declaring it makes the field rest as a container,
468
474
  * `{value: <member>, …that member's fields}`, rather than a bare string. */
469
475
  variants?: Record<string, Record<string, QuillFieldSchema>>;
470
- /** Whether a human must author the field. Absent, it derives from
471
- * `default`: a defaulted field is unobliged, a defaultless one obliged. */
472
- must_fill?: boolean;
473
476
  ui?: QuillFieldUi;
474
477
  properties?: Record<string, QuillFieldSchema>;
475
478
  items?: QuillFieldSchema;
Binary file
package/core/wasm.d.ts CHANGED
@@ -292,7 +292,14 @@ export type DocPathSeg =
292
292
 
293
293
 
294
294
 
295
- /** The commitment-ladder rung that produced a `ResolvedField.value`. */
295
+ /**
296
+ * The commitment-ladder rung that produced a `ResolvedField.value`.
297
+ *
298
+ * A container has no rung of its own — it is a namespace, and its value is the
299
+ * composition of its cells' — so it reports the strongest rung that contributed:
300
+ * `authored` if the document wrote any of it, else `default` if any cell below
301
+ * resolved to one, else `blank`.
302
+ */
296
303
  export type FieldSource = "authored" | "default" | "blank";
297
304
 
298
305
  /**
@@ -392,12 +399,11 @@ export interface QuillCardBody {
392
399
 
393
400
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
394
401
  *
395
- * Two independent axes, and no separate `required` one. `default` and
396
- * `example` say what the cell holds; `must_fill` says whether a human must
397
- * author it, deriving from `default`'s absence when left unset. An obliged
398
- * field carries a `!must_fill` marker in the blueprint and warns
402
+ * One declaration, and no `required` key. `default` and `example` say what the
403
+ * cell holds, and `default`'s absence is the obligation: a field nobody
404
+ * declared a value for carries a `!must_fill` marker in the blueprint and warns
399
405
  * `validation::must_fill` while the document leaves it unauthored. Neither
400
- * axis gates render: an absent field blank-fills.
406
+ * gates render: an absent field blank-fills.
401
407
  */
402
408
  export interface QuillFieldSchema {
403
409
  type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
@@ -412,9 +418,6 @@ export interface QuillFieldSchema {
412
418
  * member. Declaring it makes the field rest as a container,
413
419
  * `{value: <member>, …that member's fields}`, rather than a bare string. */
414
420
  variants?: Record<string, Record<string, QuillFieldSchema>>;
415
- /** Whether a human must author the field. Absent, it derives from
416
- * `default`: a defaulted field is unobliged, a defaultless one obliged. */
417
- must_fill?: boolean;
418
421
  ui?: QuillFieldUi;
419
422
  properties?: Record<string, QuillFieldSchema>;
420
423
  items?: QuillFieldSchema;
package/core/wasm_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.106.0",
3
+ "version": "0.108.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -84,6 +84,16 @@ import type { CardAddr } from '../core/wasm.js';
84
84
  */
85
85
  export declare const MAIN_CARD_ADDR: CardAddr;
86
86
 
87
+ /**
88
+ * The key carrying the discriminant inside a variant-bearing enum's value. A
89
+ * field declaring `variants:` rests as `{value: <member>, …that member's
90
+ * fields}`, so reading or writing one means naming this key; it crosses the
91
+ * boundary inside untyped container data, with no type to read it off.
92
+ * Reserved: no variant may declare a field under it, and
93
+ * {@link QuillFieldSchema.variants}, keyed by member, never contains it.
94
+ */
95
+ export declare const VARIANT_DISCRIMINANT_KEY: 'value';
96
+
87
97
  // Core-build types consumers read off `Quill`/`Document`.
88
98
  export type {
89
99
  Card,
@@ -206,6 +206,23 @@ async function instantiateCore(source) {
206
206
  */
207
207
  export const MAIN_CARD_ADDR = Object.freeze({});
208
208
 
209
+ // ── The variant discriminant key ────────────────────────────────────────────
210
+ /**
211
+ * The key carrying the discriminant inside a variant-bearing enum's value.
212
+ *
213
+ * A field declaring `variants:` rests as a container, `{value: <member>, …that
214
+ * member's fields}`, so reading or writing one means naming this key:
215
+ * `doc.storeFields(MAIN_CARD_ADDR, { classification: { [VARIANT_DISCRIMINANT_KEY]: 'CUI' } })`.
216
+ * It crosses the boundary inside untyped container data, with no type to read
217
+ * it off.
218
+ *
219
+ * Reserved: no variant may declare a field under it
220
+ * (`quill::variant_reserved_field_name`), and `QuillFieldSchema.variants`,
221
+ * keyed by member, never contains it.
222
+ * @type {'value'}
223
+ */
224
+ export const VARIANT_DISCRIMINANT_KEY = 'value';
225
+
209
226
  /**
210
227
  * Narrow an unknown caught value to a `QuillmarkError`, the error every
211
228
  * fallible method in this package throws: a real `Error` with a non-empty