@quillmark/wasm 0.103.0 → 0.104.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,125 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.104.0 - 2026-08-13
4
+
5
+ - feat(core,wasm): a quill declares, per body, the block constructs its plate
6
+ does not typeset (`main.body.unsupported`, `card_kinds.<k>.body.unsupported`;
7
+ names from `heading`, `rule`, `code`, `list`, `quote`, `table`, `image`).
8
+ A body holding one anyway draws the non-fatal `plate::unsupported_construct`,
9
+ a fifth warning family, on the pre-render walk `Quill::parse` runs beside
10
+ `conform`: one diagnostic per (body, construct) carrying the count in `args`
11
+ and the body's path, so occurrences collapse rather than scatter. The
12
+ declaration also rides `QuillConfig::schema()` to the editor, which is the
13
+ half a render-time warning could not serve: it answers before the gesture.
14
+ Nothing verifies a declaration — a plate that drops an undeclared construct
15
+ stays as silent as before. `usaf_memo` declares `rule`; empty everywhere
16
+ else, so no existing quill's schema or warnings change.
17
+ - fix(fixtures): `usaf_memo`'s `render-body` drained its heading buffer in the
18
+ three shapes that used to discard it. A heading with nothing after it (the
19
+ buffer died with the loop, taking a list item's bullet with it), a heading
20
+ whose next element opened a *different* list item (its text was delivered
21
+ into that item), and a heading following a heading (the assignment overwrote
22
+ the earlier one) each lost their text with nothing in the render to say so.
23
+ The run-in style is unchanged where it was right: a heading joins the next
24
+ block of its own item, or the next paragraph at top level.
25
+ - fix(content)!: `to_markdown` writes `***` for a thematic break, not `---`.
26
+ `- ` + `---` is four dashes separated by spaces, which re-imports as a
27
+ top-level break, so a rule as a bullet item's first block lost its item on
28
+ every markdown round-trip. The canonical spelling is now the one with the
29
+ fewest other readings (`---` is also a setext underline and the root-block
30
+ front-matter opener). Exported markdown changes for documents holding a
31
+ rule; the content model, wire data and rendered output do not.
32
+ - refactor(core,pdfform,cli,wasm)!: the `enum:` modifier on `type: string`
33
+ retires. `type: enum` with a `values:` list is the one spelling of a finite
34
+ string domain; `enum:` on any type is now `quill::field_parse_error`, whose
35
+ message names the replacement — it is the only diagnostic a quill written
36
+ against the modifier ever received, since the deprecation shipped in 0.94
37
+ with no warning code behind it. `QuillConfig::schema()` re-emits every
38
+ domain as `values:`, so a consumer reading `enum:` off the schema echo (the
39
+ wasm `QuillFieldSchema.enum`, dropped here) reads `values:` instead. The
40
+ `usaf_memo` and `sample_form` fixtures migrate; wire data and rendered
41
+ output are unchanged, the projections being domain-keyed already.
42
+ - fix(core): `build_transform_schema` keys a field's finite domain on the
43
+ domain itself rather than the `Enum` token, joining the render floor, the
44
+ pdfform widget kind and the blueprint annotation. Under the retired
45
+ spelling every `usaf_memo` enum — `classification`, `format`, `action` —
46
+ projected as a bare `{"type":"string"}`, so a consumer building a
47
+ JSON-Schema validator from the transform schema accepted
48
+ `classification: "banana"` while pdfform drew the six-option dropdown for
49
+ the same field and `QuillConfig` rejected the value at coercion (#1237)
50
+ - fix(core)!: geometry addresses parse segment-wise, so `locate` and
51
+ `fieldBoxes` answer for an address deeper than one segment. The translation
52
+ boundary folded a plate address's whole tail into one `Field`, so
53
+ `references.0` minted `main.references.0` — a string that reparses as a field
54
+ literally named `0`, and that the reverse direction refused outright. Both
55
+ spellings returned `None`, leaving caret placement and whole-field highlight
56
+ dead for **every** `array<richtext>` element (the flagship memo's
57
+ `references` among them) and for every nested key a pdfform widget binds
58
+ (`address.city`). `region.rs` now reads and renders a plate tail one segment
59
+ at a time: an all-digit segment is an array index, `$body` the body terminal,
60
+ anything else a field or map key.
61
+ - change(wasm, python)!: `RenderedRegion.field`, `FieldRegion.field` and
62
+ `ContentHit.field` spell an array element bracketed — `main.references.0`
63
+ becomes `main.references[0]` — on `regions()`, `fieldAt`, `positionAt` and
64
+ `RenderResult.regions`. This is the spelling schema validation already emits,
65
+ so a `Diagnostic.path` and the geometry address for one place are now the same
66
+ string. A consumer finding an address's children by prefix (`startsWith(`${field}.`)`)
67
+ needs the `[` opener too, and any heuristic reading a trailing all-digit field
68
+ name as a lost index is dead.
69
+ - feat(wasm): `doc.pathFor(addr)` mints an `Addr` as the canonical `DocPath`
70
+ string `Diagnostic.path` carries and `session.locate` / `session.fieldBoxes`
71
+ take; `doc.cardPath(i)` is the card's own root. `Document` computed the
72
+ kind-qualified root for every addressed write and did not hand it out, so a
73
+ consumer building a path restated the kind lookup, the `Addr` defaults and the
74
+ range guard — and a wrong-kind path is compared as a string, matching nothing
75
+ and drawing no highlight without throwing. Both are quill-free (the stored
76
+ `$kind` verbatim) and total on the index axis: a path is an anchor, not a
77
+ read, so a per-keystroke call needs no `try` (#1225)
78
+ - change(wasm)!: `@quillmark/wasm` declares `engines: { node: ">=24" }`, the
79
+ tier CI builds and tests the bindings on and the one both devcontainers hand a
80
+ contributor. Nothing in the package requires it at runtime, so a Node 22
81
+ install fails `engines` checking without failing at import.
82
+ - docs: `docs/migrations/0.103-to-0.104.md` carries the four breaks — the
83
+ retired `enum:` modifier, the bracketed index spelling, the `***` thematic
84
+ break and the Node floor — with the prefix-match, trailing-digit and stored
85
+ -markdown shapes a consumer has to fix, and the two additive surfaces
86
+ (`pathFor` / `cardPath`, and the `plate::unsupported_construct` family a
87
+ code-routing consumer gains an arm for).
88
+ - test(core): three characterization tests pin the render floor's two
89
+ type-domain edges (a defaultless enum, top-level and nested in a typed
90
+ dictionary) and an authored empty `date` beside an empty `string`, so the
91
+ coercion difference between the two is one test's diff. Every shipped quill
92
+ declares a `default:` on every enum and none authors an empty `date`, so the
93
+ fixture suite reached neither path. A fourth carries a `!must_fill` tag on two
94
+ example-seeded cells through seed → store → load → conform. Refs #1234
95
+
96
+ <!-- seed: commits since v0.103.0, confirm the entries above cover them, then delete this comment
97
+ - Carry the thematic-break and unsupported-construct work into the guide
98
+ - Dense-prose pass over the new comments and docs
99
+ - Let a quill declare the constructs its plate does not typeset
100
+ - Drain the memo's heading buffer instead of dropping it
101
+ - Canonicalize a thematic break to `***`
102
+ - Close the 0.104 release gaps: the migration guide, and four changelog entries
103
+ - Retire the enum: modifier; project a field's domain by capability
104
+ - Pin the render floor's two type-domain edges and the seeded fill tag
105
+ - Raise the Node floor to 24
106
+ - dense-prose pass over the added prose
107
+ - docs: dense-prose pass on the text-type sections
108
+ - docs: give the text-type choice a decision procedure
109
+ - docs: state the backend seam's scope and reserve its namespaces
110
+ - docs: drop the private intra-doc link from pathFor
111
+ - fix(core): state the unspellable-tail conditions instead of linking a private fn
112
+ - chore(wasm): density pass, and gate a core-build dead function
113
+ - docs: dense-prose pass over the geometry-address translation
114
+ - docs: dense-prose pass over the pathFor / cardPath prose
115
+ - test(wasm): execute the static-export exemption instead of listing it
116
+ - feat(wasm): mint a DocPath from an Addr (#1225)
117
+ - fix(core)!: parse geometry addresses segment-wise (#1223)
118
+ - docs: carry the 0.103 CLI break, and the init gate into the quickstart (#1222)
119
+ - SCHEMAS.md: a declared type change rewrites stored values (#1221)
120
+ -->
121
+
122
+
3
123
  ## v0.103.0 - 2026-08-09
4
124
 
5
125
  - docs: `docs/integration/operations.md`, carrying what the other integration
@@ -50,32 +170,6 @@
50
170
  documents and stored blobs are unaffected. See
51
171
  `docs/migrations/0.102-to-0.103.md`
52
172
 
53
- <!-- seed: commits since v0.102.0, confirm the entries above cover them, then delete this comment
54
- - ci: tag the merge commit, and serialize a release against itself
55
- - ci: drop the tagger GitHub App from the release workflows
56
- - Cut SECURITY.md, and operations.md down to what only it can say
57
- - Drop the audit gate; keep the reachability finding in ci.yml's note
58
- - Fix two wrong claims, a dead property, and the stdout bug the review found
59
- - dense-prose pass over the added prose, and read the fixture once
60
- - Close four production-readiness gaps: audit gate, ops docs, CLI tests, PDF fuzzing
61
- - Drop the README's claim about the deleted sentinel patch
62
- - dense-prose pass: cut the restatement the change introduced
63
- - docs: the 0.102 → 0.103 step, and correct the surface it describes
64
- - The gate is the only door to the core surface
65
- - docs(migration): a span guide for 0.92 → 0.102
66
- - Cut over-specified style rules; author preference wins
67
- - dense-prose: remove the em-dash ban
68
- - dense-prose: drop the line-budget clause
69
- - dense-prose: wrong is worse than missing; relax em-dash ban
70
- - docs: dense-prose pass over the conform-scope and date-grammar prose
71
- - docs: migration guides are era-stamped, not immutable
72
- - docs(migrations): give the datetime split its corpus audit step
73
- - docs: scope conform to content fields, state the date corpus precondition
74
- - Tighten the failure-delivery prose
75
- - Deliver init's conflict as a rejection, not a synchronous throw
76
- -->
77
-
78
-
79
173
  ## v0.102.0 - 2026-08-04
80
174
 
81
175
  The pre-1.0 vocabulary reset. Verbs, diagnostic codes, and two words that meant
package/README.md CHANGED
@@ -604,11 +604,11 @@ call returns: `try { return engine.render(quill, doc); } finally
604
604
  { doc.free(); }`: is safe even on the first render, while the backend
605
605
  binary is still loading.
606
606
 
607
- The package floor is Node 22+ (`engines: { node: ">=22" }`) and current
607
+ The package floor is Node 24+ (`engines: { node: ">=24" }`) and current
608
608
  evergreen browsers; `--weak-refs` itself only needs Node 14.6+. The `using`
609
- sugar ([explicit resource management][erm]) needs Node 24 and is optional.
610
- Where it hasn't landed, an explicit `try` / `finally` runs on the Node 22
611
- floor:
609
+ sugar ([explicit resource management][erm]) is on that floor and optional;
610
+ an explicit `try` / `finally` is the equivalent, and the form that also runs
611
+ in a browser that hasn't shipped it:
612
612
 
613
613
  ```ts
614
614
  const session = await engine.open(quill, doc);
@@ -181,6 +181,11 @@ export type ContentIsland = {
181
181
  * (`doc.storeField("qty", 3)`, `doc.revise("intro", md)`) the one coercion
182
182
  * rule. A bare number is *not* an addr (`{ card: 2 }` is the self-documenting
183
183
  * spelling), so no third navigation idiom re-fragments the surface.
184
+ *
185
+ * `doc.pathFor(addr)` mints the address as its canonical `DocPath` string, the
186
+ * anchor `Diagnostic.path` carries and `session.locate` / `session.fieldBoxes`
187
+ * take: a card path is kind-qualified, so building one by hand needs the card's
188
+ * `$kind`, and a wrong-kind path matches nothing silently.
184
189
  */
185
190
  export interface Addr {
186
191
  card?: number;
@@ -479,12 +484,32 @@ export interface QuillCardUi {
479
484
  groups?: Record<string, QuillGroupUi>;
480
485
  }
481
486
 
487
+ /** A block construct a body can hold. `paragraph` is absent on purpose: it is
488
+ * the floor and cannot be declined. */
489
+ export type QuillBlockConstruct =
490
+ | "heading"
491
+ | "rule"
492
+ | "code"
493
+ | "list"
494
+ | "quote"
495
+ | "table"
496
+ | "image";
497
+
482
498
  /** Body namespace for a card (main or named card kind). */
483
499
  export interface QuillCardBody {
484
500
  /** When false, consumers must not accept or store body content for this card kind. Defaults to true. */
485
501
  enabled?: boolean;
486
502
  /** Example body content embedded verbatim in the blueprint body region. Fallback is "Write <card> body here." */
487
503
  example?: string;
504
+ /** Block constructs this quill's plate does not typeset in this body.
505
+ *
506
+ * Absent or empty means it declines nothing, which is the default. An
507
+ * editor reads this to decline a gesture before the author makes it; a body
508
+ * that holds one anyway draws a non-fatal `plate::unsupported_construct`
509
+ * warning carrying the construct and a count. It is the quill's claim about
510
+ * its own plate, and nothing verifies it: a construct absent from this list
511
+ * is not a promise that the plate typesets it. */
512
+ unsupported?: QuillBlockConstruct[];
488
513
  }
489
514
 
490
515
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
@@ -501,11 +526,8 @@ export interface QuillFieldSchema {
501
526
  description?: string;
502
527
  default?: unknown;
503
528
  example?: unknown;
504
- /** Closed value domain. On `type: "enum"` declared as `values`; the
505
- * deprecated `enum` modifier on `type: "string"` is accepted for one
506
- * release. Both round-trip through this field. */
507
- enum?: string[];
508
- /** Required on `type: "enum"`: the closed set of allowed string values. */
529
+ /** Required on `type: "enum"`, and valid nowhere else: the closed set of
530
+ * allowed string values. */
509
531
  values?: string[];
510
532
  ui?: QuillFieldUi;
511
533
  properties?: Record<string, QuillFieldSchema>;
@@ -752,7 +774,7 @@ export interface Location {
752
774
  }
753
775
 
754
776
  /**
755
- * What a committed `LiveSession.apply` changed. `dirtyPages` lists the pages
777
+ * What a committed `LiveSession.update` changed. `dirtyPages` lists the pages
756
778
  * whose rendered content differs from the previous compile, including pages
757
779
  * the edit added; removed pages are implied by `pageCount`. A preview
758
780
  * repaints `dirty ∩ visible` and nothing else.
@@ -812,6 +834,13 @@ export class Document {
812
834
  * verbs.
813
835
  */
814
836
  card(index: number): Card;
837
+ /**
838
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
839
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
840
+ * card rather than one of its fields. Total on the index axis for the same
841
+ * reason, out of range renders `cards[index]`.
842
+ */
843
+ cardPath(index: number): string;
815
844
  clone(): Document;
816
845
  /**
817
846
  * Storage version this build writes via [`toJson`](Document::to_json).
@@ -959,6 +988,29 @@ export class Document {
959
988
  * not a canonical content object.
960
989
  */
961
990
  overwrite(addr: Addr | string, rt: Content): void;
991
+ /**
992
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
993
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
994
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
995
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
996
+ * or the range guard.
997
+ *
998
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
999
+ * addressed mutators anchor with and the geometry translation uses, not
1000
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
1001
+ * reference and no schema. That is the one edge where this path and a
1002
+ * `validate` diagnostic path differ for the same card.
1003
+ *
1004
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
1005
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
1006
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
1007
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
1008
+ * `cards[7].from`, which parses back and resolves to nothing rather than
1009
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
1010
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
1011
+ * address throws.
1012
+ */
1013
+ pathFor(addr: Addr | string): string;
962
1014
  /**
963
1015
  * The canonical `$quill` reference grammar as author-facing text. Core is
964
1016
  * the single source of truth: drive schema `describe` and validation
@@ -1513,6 +1565,7 @@ export interface InitOutput {
1513
1565
  readonly document_bodyMarkdown: (a: number, b: number, c: number) => void;
1514
1566
  readonly document_card: (a: number, b: number, c: number) => void;
1515
1567
  readonly document_cardCount: (a: number) => number;
1568
+ readonly document_cardPath: (a: number, b: number, c: number) => void;
1516
1569
  readonly document_cards: (a: number, b: number) => void;
1517
1570
  readonly document_clone: (a: number) => number;
1518
1571
  readonly document_currentStorageVersion: (a: number) => void;
@@ -1532,6 +1585,7 @@ export interface InitOutput {
1532
1585
  readonly document_moveCard: (a: number, b: number, c: number, d: number) => void;
1533
1586
  readonly document_new: (a: number, b: number, c: number) => void;
1534
1587
  readonly document_overwrite: (a: number, b: number, c: number, d: number) => void;
1588
+ readonly document_pathFor: (a: number, b: number, c: number) => void;
1535
1589
  readonly document_quillRef: (a: number, b: number) => void;
1536
1590
  readonly document_quillRefHint: (a: number) => void;
1537
1591
  readonly document_removeCard: (a: number, b: number, c: number) => void;
@@ -368,6 +368,30 @@ export class Document {
368
368
  const ret = wasm.document_cardCount(this.__wbg_ptr);
369
369
  return ret >>> 0;
370
370
  }
371
+ /**
372
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
373
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
374
+ * card rather than one of its fields. Total on the index axis for the same
375
+ * reason, out of range renders `cards[index]`.
376
+ * @param {number} index
377
+ * @returns {string}
378
+ */
379
+ cardPath(index) {
380
+ let deferred1_0;
381
+ let deferred1_1;
382
+ try {
383
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
384
+ wasm.document_cardPath(retptr, this.__wbg_ptr, index);
385
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
386
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
387
+ deferred1_0 = r0;
388
+ deferred1_1 = r1;
389
+ return getStringFromWasm0(r0, r1);
390
+ } finally {
391
+ wasm.__wbindgen_add_to_stack_pointer(16);
392
+ wasm.__wbindgen_export4(deferred1_0, deferred1_1, 1);
393
+ }
394
+ }
371
395
  /**
372
396
  * @returns {Card[]}
373
397
  */
@@ -815,6 +839,54 @@ export class Document {
815
839
  wasm.__wbindgen_add_to_stack_pointer(16);
816
840
  }
817
841
  }
842
+ /**
843
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
844
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
845
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
846
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
847
+ * or the range guard.
848
+ *
849
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
850
+ * addressed mutators anchor with and the geometry translation uses, not
851
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
852
+ * reference and no schema. That is the one edge where this path and a
853
+ * `validate` diagnostic path differ for the same card.
854
+ *
855
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
856
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
857
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
858
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
859
+ * `cards[7].from`, which parses back and resolves to nothing rather than
860
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
861
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
862
+ * address throws.
863
+ * @param {Addr | string} addr
864
+ * @returns {string}
865
+ */
866
+ pathFor(addr) {
867
+ let deferred2_0;
868
+ let deferred2_1;
869
+ try {
870
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
871
+ wasm.document_pathFor(retptr, this.__wbg_ptr, addHeapObject(addr));
872
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
873
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
874
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
875
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
876
+ var ptr1 = r0;
877
+ var len1 = r1;
878
+ if (r3) {
879
+ ptr1 = 0; len1 = 0;
880
+ throw takeObject(r2);
881
+ }
882
+ deferred2_0 = ptr1;
883
+ deferred2_1 = len1;
884
+ return getStringFromWasm0(ptr1, len1);
885
+ } finally {
886
+ wasm.__wbindgen_add_to_stack_pointer(16);
887
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
888
+ }
889
+ }
818
890
  /**
819
891
  * @returns {string}
820
892
  */
Binary file
@@ -16,6 +16,7 @@ export const document_blueprintInstruction: (a: number, b: number, c: number) =>
16
16
  export const document_bodyMarkdown: (a: number, b: number, c: number) => void;
17
17
  export const document_card: (a: number, b: number, c: number) => void;
18
18
  export const document_cardCount: (a: number) => number;
19
+ export const document_cardPath: (a: number, b: number, c: number) => void;
19
20
  export const document_cards: (a: number, b: number) => void;
20
21
  export const document_clone: (a: number) => number;
21
22
  export const document_currentStorageVersion: (a: number) => void;
@@ -35,6 +36,7 @@ export const document_makeCard: (a: number, b: number, c: number, d: number, e:
35
36
  export const document_moveCard: (a: number, b: number, c: number, d: number) => void;
36
37
  export const document_new: (a: number, b: number, c: number) => void;
37
38
  export const document_overwrite: (a: number, b: number, c: number, d: number) => void;
39
+ export const document_pathFor: (a: number, b: number, c: number) => void;
38
40
  export const document_quillRef: (a: number, b: number) => void;
39
41
  export const document_quillRefHint: (a: number) => void;
40
42
  export const document_removeCard: (a: number, b: number, c: number) => void;
@@ -181,6 +181,11 @@ export type ContentIsland = {
181
181
  * (`doc.storeField("qty", 3)`, `doc.revise("intro", md)`) the one coercion
182
182
  * rule. A bare number is *not* an addr (`{ card: 2 }` is the self-documenting
183
183
  * spelling), so no third navigation idiom re-fragments the surface.
184
+ *
185
+ * `doc.pathFor(addr)` mints the address as its canonical `DocPath` string, the
186
+ * anchor `Diagnostic.path` carries and `session.locate` / `session.fieldBoxes`
187
+ * take: a card path is kind-qualified, so building one by hand needs the card's
188
+ * `$kind`, and a wrong-kind path matches nothing silently.
184
189
  */
185
190
  export interface Addr {
186
191
  card?: number;
@@ -479,12 +484,32 @@ export interface QuillCardUi {
479
484
  groups?: Record<string, QuillGroupUi>;
480
485
  }
481
486
 
487
+ /** A block construct a body can hold. `paragraph` is absent on purpose: it is
488
+ * the floor and cannot be declined. */
489
+ export type QuillBlockConstruct =
490
+ | "heading"
491
+ | "rule"
492
+ | "code"
493
+ | "list"
494
+ | "quote"
495
+ | "table"
496
+ | "image";
497
+
482
498
  /** Body namespace for a card (main or named card kind). */
483
499
  export interface QuillCardBody {
484
500
  /** When false, consumers must not accept or store body content for this card kind. Defaults to true. */
485
501
  enabled?: boolean;
486
502
  /** Example body content embedded verbatim in the blueprint body region. Fallback is "Write <card> body here." */
487
503
  example?: string;
504
+ /** Block constructs this quill's plate does not typeset in this body.
505
+ *
506
+ * Absent or empty means it declines nothing, which is the default. An
507
+ * editor reads this to decline a gesture before the author makes it; a body
508
+ * that holds one anyway draws a non-fatal `plate::unsupported_construct`
509
+ * warning carrying the construct and a count. It is the quill's claim about
510
+ * its own plate, and nothing verifies it: a construct absent from this list
511
+ * is not a promise that the plate typesets it. */
512
+ unsupported?: QuillBlockConstruct[];
488
513
  }
489
514
 
490
515
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
@@ -501,11 +526,8 @@ export interface QuillFieldSchema {
501
526
  description?: string;
502
527
  default?: unknown;
503
528
  example?: unknown;
504
- /** Closed value domain. On `type: "enum"` declared as `values`; the
505
- * deprecated `enum` modifier on `type: "string"` is accepted for one
506
- * release. Both round-trip through this field. */
507
- enum?: string[];
508
- /** Required on `type: "enum"`: the closed set of allowed string values. */
529
+ /** Required on `type: "enum"`, and valid nowhere else: the closed set of
530
+ * allowed string values. */
509
531
  values?: string[];
510
532
  ui?: QuillFieldUi;
511
533
  properties?: Record<string, QuillFieldSchema>;
@@ -752,7 +774,7 @@ export interface Location {
752
774
  }
753
775
 
754
776
  /**
755
- * What a committed `LiveSession.apply` changed. `dirtyPages` lists the pages
777
+ * What a committed `LiveSession.update` changed. `dirtyPages` lists the pages
756
778
  * whose rendered content differs from the previous compile, including pages
757
779
  * the edit added; removed pages are implied by `pageCount`. A preview
758
780
  * repaints `dirty ∩ visible` and nothing else.
@@ -812,6 +834,13 @@ export class Document {
812
834
  * verbs.
813
835
  */
814
836
  card(index: number): Card;
837
+ /**
838
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
839
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
840
+ * card rather than one of its fields. Total on the index axis for the same
841
+ * reason, out of range renders `cards[index]`.
842
+ */
843
+ cardPath(index: number): string;
815
844
  clone(): Document;
816
845
  /**
817
846
  * Storage version this build writes via [`toJson`](Document::to_json).
@@ -959,6 +988,29 @@ export class Document {
959
988
  * not a canonical content object.
960
989
  */
961
990
  overwrite(addr: Addr | string, rt: Content): void;
991
+ /**
992
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
993
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
994
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
995
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
996
+ * or the range guard.
997
+ *
998
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
999
+ * addressed mutators anchor with and the geometry translation uses, not
1000
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
1001
+ * reference and no schema. That is the one edge where this path and a
1002
+ * `validate` diagnostic path differ for the same card.
1003
+ *
1004
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
1005
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
1006
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
1007
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
1008
+ * `cards[7].from`, which parses back and resolves to nothing rather than
1009
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
1010
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
1011
+ * address throws.
1012
+ */
1013
+ pathFor(addr: Addr | string): string;
962
1014
  /**
963
1015
  * The canonical `$quill` reference grammar as author-facing text. Core is
964
1016
  * the single source of truth: drive schema `describe` and validation
@@ -1513,6 +1565,7 @@ export interface InitOutput {
1513
1565
  readonly document_bodyMarkdown: (a: number, b: number, c: number) => void;
1514
1566
  readonly document_card: (a: number, b: number, c: number) => void;
1515
1567
  readonly document_cardCount: (a: number) => number;
1568
+ readonly document_cardPath: (a: number, b: number, c: number) => void;
1516
1569
  readonly document_cards: (a: number, b: number) => void;
1517
1570
  readonly document_clone: (a: number) => number;
1518
1571
  readonly document_currentStorageVersion: (a: number) => void;
@@ -1532,6 +1585,7 @@ export interface InitOutput {
1532
1585
  readonly document_moveCard: (a: number, b: number, c: number, d: number) => void;
1533
1586
  readonly document_new: (a: number, b: number, c: number) => void;
1534
1587
  readonly document_overwrite: (a: number, b: number, c: number, d: number) => void;
1588
+ readonly document_pathFor: (a: number, b: number, c: number) => void;
1535
1589
  readonly document_quillRef: (a: number, b: number) => void;
1536
1590
  readonly document_quillRefHint: (a: number) => void;
1537
1591
  readonly document_removeCard: (a: number, b: number, c: number) => void;
@@ -368,6 +368,30 @@ export class Document {
368
368
  const ret = wasm.document_cardCount(this.__wbg_ptr);
369
369
  return ret >>> 0;
370
370
  }
371
+ /**
372
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
373
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
374
+ * card rather than one of its fields. Total on the index axis for the same
375
+ * reason, out of range renders `cards[index]`.
376
+ * @param {number} index
377
+ * @returns {string}
378
+ */
379
+ cardPath(index) {
380
+ let deferred1_0;
381
+ let deferred1_1;
382
+ try {
383
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
384
+ wasm.document_cardPath(retptr, this.__wbg_ptr, index);
385
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
386
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
387
+ deferred1_0 = r0;
388
+ deferred1_1 = r1;
389
+ return getStringFromWasm0(r0, r1);
390
+ } finally {
391
+ wasm.__wbindgen_add_to_stack_pointer(16);
392
+ wasm.__wbindgen_export4(deferred1_0, deferred1_1, 1);
393
+ }
394
+ }
371
395
  /**
372
396
  * @returns {Card[]}
373
397
  */
@@ -815,6 +839,54 @@ export class Document {
815
839
  wasm.__wbindgen_add_to_stack_pointer(16);
816
840
  }
817
841
  }
842
+ /**
843
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
844
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
845
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
846
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
847
+ * or the range guard.
848
+ *
849
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
850
+ * addressed mutators anchor with and the geometry translation uses, not
851
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
852
+ * reference and no schema. That is the one edge where this path and a
853
+ * `validate` diagnostic path differ for the same card.
854
+ *
855
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
856
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
857
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
858
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
859
+ * `cards[7].from`, which parses back and resolves to nothing rather than
860
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
861
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
862
+ * address throws.
863
+ * @param {Addr | string} addr
864
+ * @returns {string}
865
+ */
866
+ pathFor(addr) {
867
+ let deferred2_0;
868
+ let deferred2_1;
869
+ try {
870
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
871
+ wasm.document_pathFor(retptr, this.__wbg_ptr, addHeapObject(addr));
872
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
873
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
874
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
875
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
876
+ var ptr1 = r0;
877
+ var len1 = r1;
878
+ if (r3) {
879
+ ptr1 = 0; len1 = 0;
880
+ throw takeObject(r2);
881
+ }
882
+ deferred2_0 = ptr1;
883
+ deferred2_1 = len1;
884
+ return getStringFromWasm0(ptr1, len1);
885
+ } finally {
886
+ wasm.__wbindgen_add_to_stack_pointer(16);
887
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
888
+ }
889
+ }
818
890
  /**
819
891
  * @returns {string}
820
892
  */
Binary file
@@ -16,6 +16,7 @@ export const document_blueprintInstruction: (a: number, b: number, c: number) =>
16
16
  export const document_bodyMarkdown: (a: number, b: number, c: number) => void;
17
17
  export const document_card: (a: number, b: number, c: number) => void;
18
18
  export const document_cardCount: (a: number) => number;
19
+ export const document_cardPath: (a: number, b: number, c: number) => void;
19
20
  export const document_cards: (a: number, b: number) => void;
20
21
  export const document_clone: (a: number) => number;
21
22
  export const document_currentStorageVersion: (a: number) => void;
@@ -35,6 +36,7 @@ export const document_makeCard: (a: number, b: number, c: number, d: number, e:
35
36
  export const document_moveCard: (a: number, b: number, c: number, d: number) => void;
36
37
  export const document_new: (a: number, b: number, c: number) => void;
37
38
  export const document_overwrite: (a: number, b: number, c: number, d: number) => void;
39
+ export const document_pathFor: (a: number, b: number, c: number) => void;
38
40
  export const document_quillRef: (a: number, b: number) => void;
39
41
  export const document_quillRefHint: (a: number) => void;
40
42
  export const document_removeCard: (a: number, b: number, c: number) => void;
package/core/wasm.d.ts CHANGED
@@ -181,6 +181,11 @@ export type ContentIsland = {
181
181
  * (`doc.storeField("qty", 3)`, `doc.revise("intro", md)`) the one coercion
182
182
  * rule. A bare number is *not* an addr (`{ card: 2 }` is the self-documenting
183
183
  * spelling), so no third navigation idiom re-fragments the surface.
184
+ *
185
+ * `doc.pathFor(addr)` mints the address as its canonical `DocPath` string, the
186
+ * anchor `Diagnostic.path` carries and `session.locate` / `session.fieldBoxes`
187
+ * take: a card path is kind-qualified, so building one by hand needs the card's
188
+ * `$kind`, and a wrong-kind path matches nothing silently.
184
189
  */
185
190
  export interface Addr {
186
191
  card?: number;
@@ -394,12 +399,32 @@ export interface QuillCardUi {
394
399
  groups?: Record<string, QuillGroupUi>;
395
400
  }
396
401
 
402
+ /** A block construct a body can hold. `paragraph` is absent on purpose: it is
403
+ * the floor and cannot be declined. */
404
+ export type QuillBlockConstruct =
405
+ | "heading"
406
+ | "rule"
407
+ | "code"
408
+ | "list"
409
+ | "quote"
410
+ | "table"
411
+ | "image";
412
+
397
413
  /** Body namespace for a card (main or named card kind). */
398
414
  export interface QuillCardBody {
399
415
  /** When false, consumers must not accept or store body content for this card kind. Defaults to true. */
400
416
  enabled?: boolean;
401
417
  /** Example body content embedded verbatim in the blueprint body region. Fallback is "Write <card> body here." */
402
418
  example?: string;
419
+ /** Block constructs this quill's plate does not typeset in this body.
420
+ *
421
+ * Absent or empty means it declines nothing, which is the default. An
422
+ * editor reads this to decline a gesture before the author makes it; a body
423
+ * that holds one anyway draws a non-fatal `plate::unsupported_construct`
424
+ * warning carrying the construct and a count. It is the quill's claim about
425
+ * its own plate, and nothing verifies it: a construct absent from this list
426
+ * is not a promise that the plate typesets it. */
427
+ unsupported?: QuillBlockConstruct[];
403
428
  }
404
429
 
405
430
  /** Schema entry for a single field declared in a quill's `Quill.yaml`.
@@ -416,11 +441,8 @@ export interface QuillFieldSchema {
416
441
  description?: string;
417
442
  default?: unknown;
418
443
  example?: unknown;
419
- /** Closed value domain. On `type: "enum"` declared as `values`; the
420
- * deprecated `enum` modifier on `type: "string"` is accepted for one
421
- * release. Both round-trip through this field. */
422
- enum?: string[];
423
- /** Required on `type: "enum"`: the closed set of allowed string values. */
444
+ /** Required on `type: "enum"`, and valid nowhere else: the closed set of
445
+ * allowed string values. */
424
446
  values?: string[];
425
447
  ui?: QuillFieldUi;
426
448
  properties?: Record<string, QuillFieldSchema>;
@@ -560,6 +582,13 @@ export class Document {
560
582
  * verbs.
561
583
  */
562
584
  card(index: number): Card;
585
+ /**
586
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
587
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
588
+ * card rather than one of its fields. Total on the index axis for the same
589
+ * reason, out of range renders `cards[index]`.
590
+ */
591
+ cardPath(index: number): string;
563
592
  clone(): Document;
564
593
  /**
565
594
  * Storage version this build writes via [`toJson`](Document::to_json).
@@ -707,6 +736,29 @@ export class Document {
707
736
  * not a canonical content object.
708
737
  */
709
738
  overwrite(addr: Addr | string, rt: Content): void;
739
+ /**
740
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
741
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
742
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
743
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
744
+ * or the range guard.
745
+ *
746
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
747
+ * addressed mutators anchor with and the geometry translation uses, not
748
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
749
+ * reference and no schema. That is the one edge where this path and a
750
+ * `validate` diagnostic path differ for the same card.
751
+ *
752
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
753
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
754
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
755
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
756
+ * `cards[7].from`, which parses back and resolves to nothing rather than
757
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
758
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
759
+ * address throws.
760
+ */
761
+ pathFor(addr: Addr | string): string;
710
762
  /**
711
763
  * The canonical `$quill` reference grammar as author-facing text. Core is
712
764
  * the single source of truth: drive schema `describe` and validation
@@ -1097,6 +1149,7 @@ export interface InitOutput {
1097
1149
  readonly document_bodyMarkdown: (a: number, b: number, c: number) => void;
1098
1150
  readonly document_card: (a: number, b: number, c: number) => void;
1099
1151
  readonly document_cardCount: (a: number) => number;
1152
+ readonly document_cardPath: (a: number, b: number, c: number) => void;
1100
1153
  readonly document_cards: (a: number, b: number) => void;
1101
1154
  readonly document_clone: (a: number) => number;
1102
1155
  readonly document_currentStorageVersion: (a: number) => void;
@@ -1116,6 +1169,7 @@ export interface InitOutput {
1116
1169
  readonly document_moveCard: (a: number, b: number, c: number, d: number) => void;
1117
1170
  readonly document_new: (a: number, b: number, c: number) => void;
1118
1171
  readonly document_overwrite: (a: number, b: number, c: number, d: number) => void;
1172
+ readonly document_pathFor: (a: number, b: number, c: number) => void;
1119
1173
  readonly document_quillRef: (a: number, b: number) => void;
1120
1174
  readonly document_quillRefHint: (a: number) => void;
1121
1175
  readonly document_removeCard: (a: number, b: number, c: number) => void;
package/core/wasm.js CHANGED
@@ -368,6 +368,30 @@ export class Document {
368
368
  const ret = wasm.document_cardCount(this.__wbg_ptr);
369
369
  return ret >>> 0;
370
370
  }
371
+ /**
372
+ * The composable card's own path, `cards.<kind>[index]`: the whole-card
373
+ * root [`pathFor`](Self::path_for) extends, for a consumer anchoring the
374
+ * card rather than one of its fields. Total on the index axis for the same
375
+ * reason, out of range renders `cards[index]`.
376
+ * @param {number} index
377
+ * @returns {string}
378
+ */
379
+ cardPath(index) {
380
+ let deferred1_0;
381
+ let deferred1_1;
382
+ try {
383
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
384
+ wasm.document_cardPath(retptr, this.__wbg_ptr, index);
385
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
386
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
387
+ deferred1_0 = r0;
388
+ deferred1_1 = r1;
389
+ return getStringFromWasm0(r0, r1);
390
+ } finally {
391
+ wasm.__wbindgen_add_to_stack_pointer(16);
392
+ wasm.__wbindgen_export4(deferred1_0, deferred1_1, 1);
393
+ }
394
+ }
371
395
  /**
372
396
  * @returns {Card[]}
373
397
  */
@@ -815,6 +839,54 @@ export class Document {
815
839
  wasm.__wbindgen_add_to_stack_pointer(16);
816
840
  }
817
841
  }
842
+ /**
843
+ * `addr`'s canonical `DocPath` string, the anchor `Diagnostic.path`
844
+ * carries: `pathFor()` is `main.body`, `pathFor("intro")` `main.intro`,
845
+ * `pathFor({card: 2})` `cards.<kind>[2].body`. A consumer holding an
846
+ * `Addr` mints one without restating the kind lookup, the `Addr` defaults
847
+ * or the range guard.
848
+ *
849
+ * The kind is the card's stored `$kind` verbatim, the quill-free rule the
850
+ * addressed mutators anchor with and the geometry translation uses, not
851
+ * `validate`'s declared-kind filter: a `Document` holds a `$quill`
852
+ * reference and no schema. That is the one edge where this path and a
853
+ * `validate` diagnostic path differ for the same card.
854
+ *
855
+ * **Total on the index axis**, unlike the `Addr` reads (`getStored`,
856
+ * `isFill`, `bodyMarkdown`), which throw there: a path is an anchor, not
857
+ * a read. An out-of-range `{card: 7, field: "from"}` extends the
858
+ * unknown-kind root `edit::index_out_of_range` anchors at, rendering
859
+ * `cards[7].from`, which parses back and resolves to nothing rather than
860
+ * mis-targeting. So a per-keystroke call needs no `try`; a caller wanting
861
+ * a drop-it guard has [`cardCount`](Self::card_count). Only a malformed
862
+ * address throws.
863
+ * @param {Addr | string} addr
864
+ * @returns {string}
865
+ */
866
+ pathFor(addr) {
867
+ let deferred2_0;
868
+ let deferred2_1;
869
+ try {
870
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
871
+ wasm.document_pathFor(retptr, this.__wbg_ptr, addHeapObject(addr));
872
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
873
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
874
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
875
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
876
+ var ptr1 = r0;
877
+ var len1 = r1;
878
+ if (r3) {
879
+ ptr1 = 0; len1 = 0;
880
+ throw takeObject(r2);
881
+ }
882
+ deferred2_0 = ptr1;
883
+ deferred2_1 = len1;
884
+ return getStringFromWasm0(ptr1, len1);
885
+ } finally {
886
+ wasm.__wbindgen_add_to_stack_pointer(16);
887
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
888
+ }
889
+ }
818
890
  /**
819
891
  * @returns {string}
820
892
  */
package/core/wasm_bg.wasm CHANGED
Binary file
@@ -14,6 +14,7 @@ export const document_blueprintInstruction: (a: number, b: number, c: number) =>
14
14
  export const document_bodyMarkdown: (a: number, b: number, c: number) => void;
15
15
  export const document_card: (a: number, b: number, c: number) => void;
16
16
  export const document_cardCount: (a: number) => number;
17
+ export const document_cardPath: (a: number, b: number, c: number) => void;
17
18
  export const document_cards: (a: number, b: number) => void;
18
19
  export const document_clone: (a: number) => number;
19
20
  export const document_currentStorageVersion: (a: number) => void;
@@ -33,6 +34,7 @@ export const document_makeCard: (a: number, b: number, c: number, d: number, e:
33
34
  export const document_moveCard: (a: number, b: number, c: number, d: number) => void;
34
35
  export const document_new: (a: number, b: number, c: number) => void;
35
36
  export const document_overwrite: (a: number, b: number, c: number, d: number) => void;
37
+ export const document_pathFor: (a: number, b: number, c: number) => void;
36
38
  export const document_quillRef: (a: number, b: number) => void;
37
39
  export const document_quillRefHint: (a: number) => void;
38
40
  export const document_removeCard: (a: number, b: number, c: number) => void;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.103.0",
3
+ "version": "0.104.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "engines": {
8
- "node": ">=22"
8
+ "node": ">=24"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
@@ -398,7 +398,10 @@ export interface FieldRegion {
398
398
  * The field's canonical `DocPath` address (`parseDocPath`-routable), not a
399
399
  * backend widget name: `main.signature_block` for a main field,
400
400
  * `cards.<kind>[<i>].signature_block` for a card field (`cards[<i>].…` when the
401
- * card's kind is unknown).
401
+ * card's kind is unknown). Nested addresses spell out in full, an array
402
+ * element bracketed and a key dotted — `main.references[0]`,
403
+ * `cards.<kind>[<i>].addr.city` — the same spelling `Diagnostic.path` uses,
404
+ * so the two join on string equality.
402
405
  */
403
406
  field: string;
404
407
  /** 0-based page index. */
@@ -90,19 +90,25 @@ import { parseDocPath, formatDocPath } from '../core/wasm.js';
90
90
  // so there is no subpath around the gate either.
91
91
  //
92
92
  // The gate is the shape the lazy-backend idiom (§ DEFAULT_BACKENDS) takes when
93
- // the surface it guards cannot be async: `Quill.fromTree` and `seedDocument`
94
- // are sync and static, so there is nowhere to hide an await except in front.
93
+ // the surface it guards cannot be async: `Quill.fromTree` and
94
+ // `quill.seedDocument` return synchronously, so there is nowhere to hide an
95
+ // await except in front.
95
96
  //
96
- // WHAT STAYS STATIC is what needs no instance. `MAIN_CARD_ADDR`, the open-set
97
- // guards and `isQuillmarkError` are pure JS over plain objects; gating them
98
- // would cost a consumer of one an await it has no use for.
97
+ // WHAT STAYS A STATIC EXPORT is what needs no instance. `MAIN_CARD_ADDR`, the
98
+ // open-set guards and `isQuillmarkError` are pure JS over plain objects; gating
99
+ // them would cost a consumer of one an await it has no use for.
99
100
  //
100
- // The classes stay static too, and are gated by their ARGUMENTS. Every `Engine`
101
- // verb takes a `Quill` first (`#backendOf` is the single reader), and the
102
- // writer/reader constructors take both handles, so a caller who has not awaited
103
- // cannot produce an argument to call them with. `new Engine()` alone touches no
104
- // wasm: it validates a descriptor map. Holding them out of the gate keeps them
105
- // tree-shakable, so the editor path drops the dispatcher it never calls.
101
+ // `Engine`, `LiveSession` and the four writer/reader classes stay static too,
102
+ // gated by their ARGUMENTS rather than by the door. Every `Engine` verb takes a
103
+ // `Quill` first (`#backendOf` is the single reader) and the writer/reader
104
+ // constructors take both handles, so a caller who has not awaited cannot
105
+ // produce an argument to call them with. The two constructors taking no handle
106
+ // reach no wasm: `new Engine()` validates a descriptor map, and a `LiveSession`
107
+ // forwards to the backend session `engine.open` is the sole source of. None of
108
+ // the six carries a static method, the one member shape an argument cannot
109
+ // gate. `gate.test.js` is the executable guard, driving the whole static
110
+ // surface before `init`. Holding them out of the gate keeps them tree-shakable,
111
+ // so the editor path drops the dispatcher it never calls.
106
112
  //
107
113
  // FAILURE DELIVERY follows the FUNCTION kind, not the failure kind: a sync verb
108
114
  // throws, a promise-returning verb rejects, and nothing does both. A
@@ -510,9 +516,10 @@ export function isListItemContainer(container) {
510
516
  // A predicate rather than an exported name list, because the known tables below
511
517
  // are upstream's business. They are pinned against the Rust source
512
518
  // (`Content::RESERVED_*` and `KnownIslandType`) by the
513
- // `known_open_set_names_are_pinned` drift-guard test in
514
- // `crates/content/src/model.rs`: adding a built-in means editing there, here, and
515
- // the TS unions in `crates/bindings/wasm/src/engine.rs` in one commit.
519
+ // `js_known_name_tables_match_the_rust_open_sets` drift-guard test in
520
+ // `crates/bindings/wasm/tests/known_names_drift.rs`: adding a built-in means
521
+ // editing there, here, and the TS unions in `crates/bindings/wasm/src/engine.rs`
522
+ // in one commit.
516
523
  //
517
524
  // These classify unknown *tags*, not unknown *payloads on known tags*. A future
518
525
  // `kind: "footnote"` with a sibling `ref` loses `ref` at a consumer that predates