@quillmark/wasm 0.102.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,175 @@
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
+
123
+ ## v0.103.0 - 2026-08-09
124
+
125
+ - docs: `docs/integration/operations.md`, carrying what the other integration
126
+ pages leave unsaid: that **render is not bounded** — no deadline, no
127
+ cancellation, and the parse limits do not carry through — with the
128
+ worker-termination recipe that is the only abort a browser has; that
129
+ `Quillmark`, `Quill` and `Document` are `Send + Sync`, pinned by a test rather
130
+ than asserted; `comemo` eviction as what a long-lived process's memory tracks;
131
+ the no-network, no-ambient-filesystem isolation properties; and that a panic
132
+ is terminal on every surface.
133
+ - docs: `parse::input_too_large` carries four of the five §8 caps, separable
134
+ only by its `max` arg, which `error-handling.md` now says where it names the
135
+ code.
136
+ - test(cli): `quillmark-cli` gets its first tests. The bin carries
137
+ `test = false`, so twelve cases drive the built executable instead — every
138
+ subcommand, `-o` and `--stdout`, PDF and SVG output, and the error paths,
139
+ which must exit 1 rather than panic. The crate had no `[dev-dependencies]`
140
+ and no workflow invoked it (#1068).
141
+ - fix(cli)!: `render --verbose` writes its progress lines to stderr, as the
142
+ warning printer already did. Under `--stdout` they went to stdout ahead of and
143
+ after the artifact, so `quillmark render q --stdout --verbose > out.pdf`
144
+ produced a PDF with `Loading quill from: …` before its header and
145
+ `Rendering completed successfully` past its trailer. A script that parses
146
+ `--verbose` output from stdout reads it from stderr now.
147
+ - test(fuzz): `pdf_fuzz` covers the AcroForm stamp spine's byte-level reads,
148
+ the one hand-rolled parser with no fuzz target. Arbitrary bytes, and a real
149
+ form truncated, single-byte-corrupted, or spliced, all through
150
+ `page_media_boxes` / `PdfUpdate::begin` / `stamp`. The oracle is no panic:
151
+ nothing in the workspace catches unwind, so a panic there kills the CLI and
152
+ the Python extension and poisons the WASM module. No failures found.
153
+ - fix(core): `MAX_FIELD_COUNT`'s rustdoc said "per document"; the check is per
154
+ card-yaml block, counted after `$`-key extraction.
155
+ - refactor(wasm)!: `init()` resolves to the core surface, and it is the only way
156
+ to reach one. `Quill`, `Document`, `importMarkdown`, `exportMarkdown`,
157
+ `rebase`, `mapPos`, `parseDocPath` and `formatDocPath` leave the static
158
+ exports of `@quillmark/wasm`: `const { Quill, Document } = await init()`
159
+ replaces the value import. The precondition was carried entirely by
160
+ `init`'s signature, and a floating promise is an ESLint rule rather than a
161
+ `tsc` diagnostic, so a call site that skipped the await type-checked and then
162
+ passed or failed by load order. It now has no name to call. `Engine`,
163
+ `MAIN_CARD_ADDR`, `isQuillmarkError`, the open-set guards and the
164
+ writer/reader classes are unchanged, needing no instance or gated by their
165
+ arguments; the `Quill` / `Document` **type** exports are unchanged, so
166
+ annotations and `import type` compile as before. Class identity is untouched:
167
+ the gate hands out the core build's classes verbatim, and `instanceof` stays
168
+ the whole membership test. `runtime::not_initialized` and the build-time
169
+ sentinel that raised it retire with the door they guarded. Rust, Python,
170
+ documents and stored blobs are unaffected. See
171
+ `docs/migrations/0.102-to-0.103.md`
172
+
3
173
  ## v0.102.0 - 2026-08-04
4
174
 
5
175
  The pre-1.0 vocabulary reset. Verbs, diagnostic codes, and two words that meant
package/README.md CHANGED
@@ -10,11 +10,11 @@ Use Quillmark in browsers/Node.js with explicit in-memory trees (`Map<string, Ui
10
10
 
11
11
  The package exposes **one import surface**:
12
12
 
13
- - `@quillmark/wasm` (the root), the **canonical API**: `Quill`, `Document`, and
14
- an `Engine` that renders them.
13
+ - `@quillmark/wasm` (the root), the **canonical API**: `init`, resolving to
14
+ `Quill` and `Document`, and an `Engine` that renders them.
15
15
 
16
- `Quill` and `Document` are re-exported verbatim from the internal Typst-less
17
- core build, so editor/validation code (`Quill.fromTree`,
16
+ `Quill` and `Document` are the internal Typst-less core build's own classes,
17
+ handed out verbatim by `init`, so editor/validation code (`Quill.fromTree`,
18
18
  `Document.fromMarkdown`) loads only that small core binary: no backend is
19
19
  loaded until you render. The `Engine` hides everything else: each backend
20
20
  (`typst`, `pdfform`) is a separate, private WASM binary with its own linear
@@ -31,9 +31,8 @@ bash scripts/build-wasm.sh
31
31
  The script builds three variants: the core (no backend), the Typst backend
32
32
  (default features), and the Typst-free pdfform backend (`pdfform` feature):
33
33
  each with `--target web` and `--weak-refs` enabled (see
34
- [Initialization](#initialization) and [Lifecycle](#lifecycle)). It then patches
35
- each generated build with the pre-init sentinel (`runtime/uninit.js`) and
36
- asserts none of them carries a `.wasm` ESM import or a top-level await.
34
+ [Initialization](#initialization) and [Lifecycle](#lifecycle)). It then asserts
35
+ none of them carries a `.wasm` ESM import or a top-level await.
37
36
 
38
37
  ## Test
39
38
 
@@ -47,9 +46,9 @@ npm test
47
46
  ## Usage
48
47
 
49
48
  ```ts
50
- import { init, Document, Quill, Engine } from "@quillmark/wasm";
49
+ import { init, Engine } from "@quillmark/wasm";
51
50
 
52
- await init(); // once at startup; see Initialization
51
+ const { Quill, Document } = await init(); // see Initialization
53
52
 
54
53
  const quill = Quill.fromTree(tree); // no engine needed: build + validate
55
54
  const engine = new Engine(); // loads a backend lazily on first render
@@ -68,12 +67,13 @@ const result = await engine.render(quill, parsed, { format: "pdf" });
68
67
 
69
68
  ## Initialization
70
69
 
71
- `await init()` once, at startup, before any other export is used. Everything
72
- after it is the synchronous surface the rest of this README describes.
70
+ `init` resolves to the core surface: `Quill`, `Document`, and the free
71
+ functions. Everything after the await is the synchronous surface the rest of
72
+ this README describes.
73
73
 
74
74
  ```js
75
- import { init, Quill, Engine } from "@quillmark/wasm";
76
- await init();
75
+ import { init, Engine } from "@quillmark/wasm";
76
+ const { Quill, Document } = await init();
77
77
  ```
78
78
 
79
79
  The same line works everywhere: the binary streams from a URL in a browser and
@@ -83,10 +83,11 @@ the builds are `--target web`, so nothing in the package graph imports a `.wasm`
83
83
  module or carries a top-level await, and a static `import` of this package is
84
84
  safe anywhere, SSR included.
85
85
 
86
- `init` is idempotent and concurrency-safe: every call returns the same promise,
87
- so several entry points may each `await init()` for one instantiation. A failed
88
- init clears the memo, so a retry works. Each realm initializes its own copy; a
89
- Worker calls `init()` too.
86
+ `init` is idempotent and concurrency-safe: every non-conflicting call returns
87
+ the same promise, so several entry points may each `await init()` for one
88
+ instantiation. Destructure at **every** entry point (route loader, hydration
89
+ path, worker) rather than threading one result around. A failed init clears the
90
+ memo, so a retry works. Each realm initializes its own copy, a Worker included.
90
91
 
91
92
  **Backends need nothing.** `Engine` instantiates a backend inside its lazy load,
92
93
  on the first render against it.
@@ -94,13 +95,17 @@ on the first render against it.
94
95
  **Overriding the source.** `init(source)` accepts bytes, a `Response`, a
95
96
  `WebAssembly.Module`, or a URL, for hosts that route assets themselves or embed
96
97
  the binary. Pass it on the first call; a later call passing a *different* source
97
- throws `runtime::init_conflict` rather than silently ignoring it. Passing the
98
- same value again is fine, so several entry points may each `await init(BYTES)`
99
- against one constant.
98
+ rejects with `runtime::init_conflict` rather than silently ignoring it. Passing
99
+ the same value again is fine, so several entry points may each
100
+ `await init(BYTES)` against one constant.
100
101
 
101
- **If you forget.** Reaching the surface early throws a `QuillmarkError` coded
102
- `runtime::not_initialized` that names the fix, rather than a `TypeError` from
103
- inside generated code.
102
+ **Both failures reject.** `runtime::init_conflict` and `runtime::init_failed`
103
+ alike ride the returned promise, so one `catch` around `await init(...)` covers
104
+ the gate. See [Errors](#errors) for the rule this follows.
105
+
106
+ **You cannot forget.** The core surface has no static export, so a call site
107
+ that skips the await has no name to call. The precondition is structural rather
108
+ than a convention: no load order can make one entry point pass and another fail.
104
109
 
105
110
  **Vite's dev server** pre-bundles dependencies, which moves the package away
106
111
  from its binary. Exclude it:
@@ -547,6 +552,14 @@ try {
547
552
  }
548
553
  ```
549
554
 
555
+ **Delivery follows the function, not the failure.** A synchronous method throws;
556
+ a promise-returning one rejects. The promise-returning surface is `init` and the
557
+ four `Engine` verbs (`render`, `open`, `supportedFormats`, `supportsCanvas`), so
558
+ a programming error reached through one of them (a foreign handle, an
559
+ unregistered backend) rejects like any other failure. Nothing here both returns
560
+ a promise and throws, so a `.catch` on a promise-returning call is a whole
561
+ guard.
562
+
550
563
  `QuillmarkError` is a **structural interface, not a class**: the WASM layer
551
564
  throws a real `Error` and attaches the property, so there is no constructor to
552
565
  `instanceof` against; narrow with `isQuillmarkError` (which also works on
@@ -591,11 +604,11 @@ call returns: `try { return engine.render(quill, doc); } finally
591
604
  { doc.free(); }`: is safe even on the first render, while the backend
592
605
  binary is still loading.
593
606
 
594
- The package floor is Node 22+ (`engines: { node: ">=22" }`) and current
607
+ The package floor is Node 24+ (`engines: { node: ">=24" }`) and current
595
608
  evergreen browsers; `--weak-refs` itself only needs Node 14.6+. The `using`
596
- sugar ([explicit resource management][erm]) needs Node 24 and is optional.
597
- Where it hasn't landed, an explicit `try` / `finally` runs on the Node 22
598
- 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:
599
612
 
600
613
  ```ts
601
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
  */
@@ -2885,8 +2957,7 @@ if (!('encodeInto' in cachedTextEncoder)) {
2885
2957
 
2886
2958
  let WASM_VECTOR_LEN = 0;
2887
2959
 
2888
- import { uninitSentinel, UNINIT } from "../../runtime/uninit.js";
2889
- let wasmModule, wasm = uninitSentinel("@quillmark/wasm internal error: the 'pdfform' backend was used before instantiation.", "This is a bug in @quillmark/wasm, not in your code. Please report it.");
2960
+ let wasmModule, wasm;
2890
2961
  function __wbg_finalize_init(instance, module) {
2891
2962
  wasm = instance.exports;
2892
2963
  wasmModule = module;
@@ -2933,7 +3004,7 @@ async function __wbg_load(module, imports) {
2933
3004
  }
2934
3005
 
2935
3006
  function initSync(module) {
2936
- if (wasm !== undefined && !wasm[UNINIT]) return wasm;
3007
+ if (wasm !== undefined) return wasm;
2937
3008
 
2938
3009
 
2939
3010
  if (module !== undefined) {
@@ -2953,7 +3024,7 @@ function initSync(module) {
2953
3024
  }
2954
3025
 
2955
3026
  async function __wbg_init(module_or_path) {
2956
- if (wasm !== undefined && !wasm[UNINIT]) return wasm;
3027
+ if (wasm !== undefined) return wasm;
2957
3028
 
2958
3029
 
2959
3030
  if (module_or_path !== undefined) {
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;