@quillmark/wasm 0.108.2 → 0.109.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,301 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.109.0 - 2026-08-24
4
+
5
+ - **breaking** content: **`Normalized` is the precondition the projections
6
+ require, and every codec returns one.** A projection over a container tree
7
+ owes totality, and `Content` alone does not say whether `normalize` has run —
8
+ so `to_markdown` and `emit_content` each trusted a canonical shape their
9
+ signature did not ask for. `Content::into_normalized` is the mint (infallible:
10
+ canonicalizing is total, and the codecs go on calling `validate` after it),
11
+ `Normalized::into_content` the way back out, and the token derefs to
12
+ `&Content`, so **a read-only consumer needs no change** — `.text`, `.lines`,
13
+ `.marks`, `.islands`, `validate()`, `is_inline()` all reach through. What
14
+ moves is the signatures a caller names or a value it mutates:
15
+
16
+ | Crate | 0.108 | 0.109 |
17
+ |---|---|---|
18
+ | `quillmark-content` | `from_markdown` / `from_plaintext` / `from_canonical_json` / `serial::from_canonical_value` / `serial::from_authored_value` → `Content` | → `Normalized` |
19
+ | | `to_markdown(&Content)` | `to_markdown(&Normalized)` |
20
+ | | `Content::to_canonical_json` | `Normalized::to_canonical_json` |
21
+ | | `serial::to_canonical_value(&Content)` | `(&Normalized)` |
22
+ | `quillmark-typst` | `emit::emit_content(&Content)` | `(&Normalized)` |
23
+ | `quillmark-core` | `Card::body() -> &Content` | `-> &Normalized` |
24
+ | | `Card::overwrite_body(Content)` / `overwrite_field(_, Content)` | `impl Into<Normalized>` — a `Content` still passes |
25
+ | | `TypedReader::get_content{,_at}` / `CardReader::get_content{,_at}` → `Option<Content>` | `Option<Normalized>` |
26
+
27
+ A consumer that *mutates* a decoded content takes the round trip, which is
28
+ what the codecs used to run for it silently:
29
+
30
+ ```rust
31
+ // 0.108
32
+ let mut rt = from_markdown(md)?;
33
+ rt.marks.push(mark);
34
+ rt.normalize();
35
+
36
+ // 0.109
37
+ let mut rt = from_markdown(md)?.into_content();
38
+ rt.marks.push(mark);
39
+ let rt = rt.into_normalized();
40
+ ```
41
+
42
+ The op channel needs none of that: `apply_text_delta`, `apply_mark_ops`,
43
+ `apply_line_ops`, `apply_island_ops` and `apply_field_change` are forwarded on
44
+ `Normalized` and re-establish the invariant on the error path as well as the
45
+ success one. `to_plaintext` still takes `&Content` and reads a token through
46
+ the deref, projecting `text` alone with no walk to make total.
47
+
48
+ - feat(content): **`quillmark_content::traverse` is where the container walks
49
+ live**, `runs` (adjacent lines sharing one container instance at a depth),
50
+ `items` (adjacent lines whose whole container is equal) and `segment` (a line
51
+ plus the continuations at its own nesting). Five call sites across three
52
+ crates had spelled these by hand, each with its own idea of when a run ends;
53
+ `Span` and the walks are public so a consumer reading `Content.lines` groups
54
+ them the way both projections and the quill census do.
55
+
56
+ - perf(content): **`serial::to_canonical_value` and `to_canonical_json` take a
57
+ `Normalized`.**
58
+ Both cloned the whole content and normalized the copy on every call, on a
59
+ lane whose callers — the codecs, `Card::body`, the storage DTO — were already
60
+ holding the canonical form. The token now carries that, so the serialize path
61
+ spends an encode instead of a deep clone plus a repair pass. `to_canonical_json`
62
+ moves from `Content` to `Normalized` with it; a caller holding a raw `Content`
63
+ mints first, which is what the old body did for them silently.
64
+
65
+ - fix(core): **a document body that `validate` refuses is refused on write, not
66
+ discovered on read.** `CanonicalContent`'s `Deserialize` parsed, normalized and
67
+ validated; its `Serialize` validated nothing, and `Card::overwrite_body` takes a
68
+ caller's content on the canonical-form token alone. A store could therefore
69
+ accept bytes it could not read back. The serializer now validates too and fails
70
+ with the invariant, at the boundary that cares and while the caller still holds
71
+ the value that produced it.
72
+
73
+ - refactor(content): **the leaf-segment walk is one loop, not two.** `traverse`
74
+ gains `segment` — the block-opening line plus every following one that
75
+ continues it at the same nesting — and `export::emit_block` and the Typst
76
+ emitter's `segment_end` both call it. The fifth duplicated traversal, the one
77
+ #1364 did not list.
78
+
79
+ - fix(content): **`to_markdown` no longer aborts the process on a deeply nested
80
+ content.** `Normalized` states that `normalize` has run, and `normalize`
81
+ repairs where `validate` rejects: nothing about canonicalization brings a
82
+ container path under `MAX_NESTING_DEPTH`, so a hand-built `Content` mints a
83
+ token that `validate` refuses. `export::emit_block` recursed one frame per
84
+ container level and overflowed the stack a few thousand levels down — a
85
+ SIGABRT no caller can catch, against a Typst emitter that checks the depth up
86
+ front and returns `EmitError::NestingTooDeep` for the same input. The walk is
87
+ now an explicit frame stack, as `json_depth_exceeds` and the quill census
88
+ already are, so the projection is total over every token its signature
89
+ accepts. `Normalized`'s docs settle the half the newtype does not close: the
90
+ token promises canonical, not valid — the mint stays infallible, the codecs go
91
+ on calling `validate` after it, and a projection that takes one owes totality
92
+ rather than trust. Only a Rust embedder hand-building a `Content` reaches the
93
+ shape; every decode lane (`from_markdown`, `from_canonical_value`, storage,
94
+ WASM, Python) rejects the depth already.
95
+
96
+ - fix(typst): **a container inside a list item no longer terminates the list.**
97
+ The item's continuation indent reached its leaf path only, so a quote inside
98
+ an item opened at column 0 — where Typst ends the enclosing list. The item's
99
+ later blocks came back as top-level paragraphs and the next item started a
100
+ fresh list, which renumbers an ordered one from the quote on. A fence and a
101
+ nested list escaped it by reaching that indented leaf path; a transparent
102
+ unknown container did not, and neither would any container added later.
103
+ Indentation is now the walk's rather than each construct's: one rule opens
104
+ every block, leaf and container alike, at the enclosing list depth, so what
105
+ the content nests, the markup nests.
106
+
107
+ - fix(content): **a `continues` line that crosses a container boundary no longer
108
+ survives.** A within-block break lives inside one container, and `LineOp::Join`
109
+ mints the crossing shape whenever it merges two lines of differing paths — the
110
+ line after the seam keeps continuing across it. Both projections already read
111
+ the flag as dead there (`export::emit_block` and `emit::segment_end` each
112
+ require the depth to match before absorbing a continuation), so `normalize`
113
+ now clears it, which states what was already true and changes nothing
114
+ observable. `Content::validate` gains `Invariant::ContinuesAcrossContainers`
115
+ to catch a hand-built content that skipped `normalize`, and
116
+ `LineOp::SetContinues` refuses the *deliberate* crossing up front with
117
+ `ApplyError::ContinuesAcrossContainers` — the same repair-or-refuse split the
118
+ line-kind rule already makes. This was the one relational line invariant
119
+ nothing checked: `validate` is otherwise strictly per-line, while every
120
+ container rule is a property of a line pair.
121
+
122
+ - fix(content): **two adjacent containers of one shape are no longer read as
123
+ one.** Container identity is the container path plus contiguity, and the path
124
+ carried nothing to tell one instance from the next, so two adjacent runs of
125
+ equal shape welded: `[Quote], [Quote]` read as a single two-paragraph quote,
126
+ and two one-item lists as a single item whose second line came back as an
127
+ unnumbered continuation paragraph — the marker gone. `Container` now carries
128
+ an `instance` discriminator on every arm, `Content::normalize` canonicalizes
129
+ it to `0` (flipping to `1` only where the adjacent preceding run would
130
+ otherwise weld), and the two projections read it. Four defects close with it:
131
+ - `from_markdown("- a\n\n<!-- -->\n\n- b")` — the CommonMark idiom for
132
+ spelling two lists apart — no longer destroys the second list's marker.
133
+ - Two adjacent ordered lists typeset with their own numbering. They reached
134
+ the Typst emitter as one run and `+` markers numbered the second list on
135
+ from the first, so `1. 2.` / `1. 2.` rendered **1 2 3 4**. The run's first
136
+ item now states its number, which resets Typst's running counter. Every
137
+ ordered run's first item therefore lowers as `N. ` where a run starting at
138
+ 1 lowered as `+ `; the page is identical, the generated markup is not, so
139
+ anything diffing or golden-comparing Typst source sees it.
140
+ - `1. a` beside a list starting at `3` keeps that `start` through the
141
+ Markdown projection. CommonMark reads only a list's first number, so
142
+ `1. a\n\n3. b` re-imported as one list of two items and the `start` was
143
+ lost — breaking the round-trip fixed point `export` documents. Adjacent
144
+ lists now alternate their marker (`-`/`+`, `.`/`)`), which is how
145
+ CommonMark itself spells two lists apart, so the boundary survives the
146
+ projection with no comment marker in the authored file.
147
+ - Adjacent `Unknown` containers of equal `(tag, attrs)` round-trip as two
148
+ **through storage**, the lane they have: an unknown container has no
149
+ Markdown syntax to alternate, so it projects transparently there as it
150
+ always did. The open-set promise that a container this build does not know
151
+ survives untouched is now total rather than holding up to an adjacency
152
+ quotient.
153
+
154
+ An item boundary is a parent boundary: two inner lists under two outer list
155
+ items are two lists, so an inner run restarts its `ordinal` and needs no
156
+ discriminator. `ordinal` is canonicalized alongside it, to a gapless 0-based index within
157
+ its run, so `[5, 9]` and `[0, 1]` stop being two spellings of the same two
158
+ items. `instance` is written to the wire only when non-zero, so a stored row
159
+ that needs no discriminator — nearly all of them — keeps its exact bytes and
160
+ its content hash. **Breaking for Rust consumers** that match `Container`
161
+ exhaustively: `Quote` is now a struct variant, and `ListItem`/`Unknown` carry
162
+ the extra field. On the TypeScript surface `instance` is optional; a consumer
163
+ that never writes adjacent same-shape siblings needs no change.
164
+
165
+ The block census counts what the projections see, so two adjacent runs of one
166
+ shape now count two where they counted one: a quill declining `list` or
167
+ `quote` reports the construct at a document that has two of them where it
168
+ reported one, and `plate::unsupported_construct` moves with it.
169
+
170
+ A blob written here carries `instance` only where a document holds adjacent
171
+ same-shape siblings, and a reader that predates the field ignores the key —
172
+ so such a blob loads on 0.108 with the two runs welded, and re-saving there
173
+ drops the boundary for good. The `@0.93.0` tag is unchanged because every
174
+ blob written before this release re-encodes byte for byte; the forward
175
+ direction is the one that costs, and only for the documents that spend the
176
+ key.
177
+
178
+ - fix(blueprint): **a variant's `object` or `array<object>` cell expands per
179
+ property.** The cell went through the scalar path, so it rendered as
180
+ `controlled_by: !must_fill # object` — a null where the schema wants a
181
+ mapping, with every property's description, `default:` and type annotation
182
+ dropped, and the marker on a path the obligation predicate never warns at.
183
+ A cell is a field of its container and now expands as one, like every other
184
+ surface already did.
185
+ - fix(core): **a `.quillignore` pattern holding more than one `*` ignores what
186
+ it names.** The matcher handled exactly one wildcard and returned no match
187
+ for the rest, so `**/*.tmp` and `*.sublime-*` were dead lines. Patterns now
188
+ compile once through `glob::Pattern`, matched against the whole path and the
189
+ basename. Two readings tighten to gitignore's: `*` stops at `/`, and a
190
+ pattern spelling out a `/` anchors at the bundle root rather than matching
191
+ any path that opens and closes with its halves. Both narrow what a line
192
+ ignores, so a bundle can gain a file it used to drop: `assets/*` covers
193
+ `assets/logo.png` and no longer `assets/icons/logo.png`, which `assets/**`
194
+ or the directory line `assets/` covers. No in-tree quill spells either shape.
195
+ A line always ignores the
196
+ name it spells out as well: `[` opens a character class and is an ordinary
197
+ character in a filename, so `Cinzel[wght].ttf` ignores both the variable font
198
+ of that name and the class it describes.
199
+ - refactor: **`RenderError::coded(code, message)` is the one constructor for a
200
+ single-error-diagnostic failure.** Nine sites across five crates spelled
201
+ `from_diag(Diagnostic::new(Severity::Error, msg).with_code(code))` by hand,
202
+ two of them as a per-crate `engine_err` helper the backends each carried
203
+ their own copy of. Additive to `quillmark-core`'s public API; no code, message
204
+ or shape changes.
205
+ - refactor(pdfform): **a session holds its flattened PDF parsed, not as bytes
206
+ each render path reparses.** Flatten and parse now happen together in `open`
207
+ and `update`, the two places `field_specs` are set, so the derived flat PDF
208
+ moves only with the specs it comes from and `render_svg`/`render_png`/
209
+ `render_rgba` paint parsed pages. A malformed flatten now surfaces from the
210
+ call that produced it under one code, `pdfform::flat_parse_failed`, replacing
211
+ the per-format `pdfform::svg_parse_failed` and `pdfform::png_parse_failed`
212
+ raised at render time (neither documented, and both reachable only through a
213
+ bug in this crate's own flatten). Opening a session fails on that bug now,
214
+ including for a caller that only ever renders the AcroForm PDF, which is
215
+ stamped from the base and reads nothing flattened.
216
+ - perf(pdf): **filling a PDF form no longer slows down with the size of its
217
+ background or its page count.** Reading one object from the base walks every
218
+ byte of it — the live copy is the last revision, so a scan cannot stop early
219
+ — and nothing memoized that, so a stamp or flatten pass paid O(pages) whole-
220
+ file scans and the live-edit path repaid them on every keystroke. The base's
221
+ object offsets are now collected in one pass and each read is a lookup: a
222
+ 20-page 300 KB form stamps in 0.7 ms rather than 37 ms, flat in page count.
223
+
224
+ **breaking** in `quillmark-pdf`: `PdfUpdate::begin` and
225
+ `PdfUpdate::resolve_pages` take the `&ObjectIndex` the caller builds over the
226
+ base rather than its bytes, and `reader::find_object_bytes` /
227
+ `reader::object_dict` become `ObjectIndex::object_bytes` / `ObjectIndex::dict`.
228
+ - **breaking** content: the op wire is a reading direction. `mark_op_to_value`,
229
+ `line_op_to_value` and `island_op_to_value` are removed from
230
+ `quillmark-content` — an op bundle is authored on the JS/Python side and
231
+ reaches Rust through `change_bundle_from_value`, so nothing in the workspace
232
+ ever emitted one and every wire change was made twice, once in code no product
233
+ path executes. The decoders are unchanged. Their round-trip tests become
234
+ decoder tests over literal JSON, which is what the wire actually is: an
235
+ encoder agreeing with its own reader never proved the shape a binding sends.
236
+ - perf(content): **canonical serialization stops rebuilding the tree it just
237
+ built.** `to_canonical_value` normalized a copy — which already recursively
238
+ key-sorts every opaque bag reachable from it (island `props`, an unknown's
239
+ `attrs`) — and then ran a whole-tree `sort_keys_owned` over the encoded
240
+ result, re-collecting and re-allocating every object and array in the document
241
+ to reorder the handful of fixed keys the encoders insert themselves. The
242
+ encoders now emit those keys in ascending order and the terminal pass is
243
+ `canonicalize_keys`, which scans and returns when the tree is already
244
+ canonical. Canonical bytes are unchanged, byte for byte; a tree that somehow
245
+ arrives unsorted is still repaired rather than shipped.
246
+
247
+ The public `container_to_value` and `mark_to_value`, and the crate-internal
248
+ `island_to_value`, now emit their own keys in a different order. An unknown's `attrs` bag is
249
+ untouched, as in 0.99, and nothing hashes the op wire.
250
+
251
+ <!-- seed: commits since v0.108.3, confirm the entries above cover them, then delete this comment
252
+ - docs: the 0.108 → 0.109 release record, and two misattached doc comments
253
+ - docs: dense-prose pass over the branch
254
+ - docs: dense-prose pass over the branch
255
+ - fix: close the rest of the normalized-is-not-validated gap
256
+ - fix(typst): open every block at the enclosing list indent
257
+ - fix(content): walk export's block tree on a frame stack
258
+ - docs: dense-prose pass over the branch
259
+ - fix: hold the token's guarantee on error paths, and walk census iteratively
260
+ - feat(content): make "normalized" a type the projections require
261
+ - refactor: consume the shared container traversal
262
+ - feat(content): one traversal for container runs and items
263
+ - fix(content): an item boundary is a parent boundary
264
+ - fix(content): clear a `continues` that crosses a container boundary
265
+ - fix(content): give a container the instance its identity was missing
266
+ - Bulk PR integration: #1347–#1356 (#1358)
267
+ -->
268
+
269
+
270
+ ## v0.108.3 - 2026-08-21
271
+
272
+ - fix(typst): **a paragraph holding one bare `/` renders instead of failing the
273
+ compile.** Typst's heading `=`, list `-`/`+`/`N.`, and term `/` markers fire
274
+ on a space after them *or* on the line ending there; the emitter's
275
+ line-anchor guard tested only for the space, so a run that was one bare
276
+ marker reached Typst unescaped — `/` as a term list whose colon is missing
277
+ (`expected colon`), the other four as an empty heading, bullet or enum item.
278
+ The guard now takes Typst's own test, and covers a list item's body head as
279
+ well as column 0, that being a line start the parser reads as one.
280
+
281
+ - fix(typst): **bold text, a table cell or an indented paragraph opening with
282
+ `-`, `=`, `+`, `/` or `N.` renders as that text.** Typst reads the head of
283
+ every content block `[…]` as a line start of its own, so the marker in
284
+ `**/ x**` or in a table cell reached it as a term list whose colon is missing
285
+ and failed the compile, while `**- x**` drew a bullet list inside the bold.
286
+ Indentation is trivia and holds that line start open behind it, so a
287
+ paragraph beginning ` / x` failed the same way. The line-anchor guard now
288
+ covers every position Typst reads as a line start.
289
+
290
+ - fix(typst): **text directly after inline code, bold or an image renders when
291
+ it opens with `(` or `.name`.** Typst reads a `(` directly after a `#…`
292
+ expression as that call's arguments and a `.` before an identifier as a field
293
+ access, so the emitter's own `#raw(…)`, `#strong[…]` and `#image(…)` handed
294
+ the text behind them to Typst as code — `` `x`(y) `` became a call on
295
+ content, which fails the compile. Such a run now takes the same `\` prefix
296
+ the line-anchor guard uses. Debug builds parse every emission with Typst's
297
+ parser, so markup that reaches it as syntax fails a test rather than a render.
298
+
3
299
  ## v0.108.2 - 2026-08-20
4
300
 
5
301
  - fix(core): **storage blobs tagged `@0.81.0` and `@0.82.0` load again.** Both
@@ -53,27 +349,6 @@
53
349
  sequence item (`- {}`) and a wholly empty `$ext: {}` already used, so an
54
350
  emptied container survives the round-trip as the value a consumer stored.
55
351
 
56
- <!-- seed: commits since v0.108.1, confirm the entries above cover them, then delete this comment
57
- - docs(canon): record that @0.81.0 is the oldest tag that exists
58
- - docs: leave the applied migration guide untouched
59
- - docs: dense-prose pass on the DTO backfill
60
- - test(core): drop the vacuous V0_81_0 body-import test
61
- - fix(core): restore the V0_82_0 read shim; 0.82.0 was never yanked (#1327)
62
- - chore(deps): bump the cargo group with 2 updates
63
- - chore(deps): bump taiki-e/install-action in the actions group
64
- - fix(core): restore the V0_81_0 storage read shim (#1327)
65
- - docs: changelog entry for the simplify sweep
66
- - refactor(core)!: apply simplify-review cleanups to the session seam
67
- - docs(pdf): trim test helper doc to its contract
68
- - refactor(core/document): apply simplify-review cleanups
69
- - refactor(pdf): apply simplify-review cleanups
70
- - refactor(typst): apply simplify-review cleanups
71
- - refactor(core/quill): apply simplify-review cleanups
72
- - refactor(content): apply simplify-review cleanups
73
- - fix: an empty mapping emits as `{}` rather than losing its key
74
- -->
75
-
76
-
77
352
  ## v0.108.1 - 2026-08-19
78
353
 
79
354
  - fix: **a content cell under `variants:` is readable at its codec.**
@@ -93,11 +93,18 @@ export type ContentLineKind =
93
93
 
94
94
  /** An ancestor block a line nests inside, outermost first. Open like
95
95
  * `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
96
- * and renders transparently (its lines sit at the enclosing level). */
96
+ * and renders transparently (its lines sit at the enclosing level).
97
+ *
98
+ * Two adjacent lines sit in the same container iff their whole path matches.
99
+ * `instance` is what tells one container from an adjacent sibling of identical
100
+ * shape — two consecutive quotes, or two consecutive lists — which contiguity
101
+ * alone reads as one. Omit it (or write `0`) unless a path immediately above or
102
+ * below is otherwise identical; a write is canonicalized to `0`/`1` on the way
103
+ * in, so any distinct pair of values works. */
97
104
  export type ContentContainer =
98
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number }
99
- | { container: "quote" }
100
- | { container: string; attrs: unknown };
105
+ | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
106
+ | { container: "quote"; instance?: number }
107
+ | { container: string; attrs: unknown; instance?: number };
101
108
 
102
109
  /** A mark over char range `[start, end)` into `Content.text`. The open `type`
103
110
  * arm blocks discriminant narrowing, so read a payload-carrying arm behind its
Binary file
@@ -93,11 +93,18 @@ export type ContentLineKind =
93
93
 
94
94
  /** An ancestor block a line nests inside, outermost first. Open like
95
95
  * `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
96
- * and renders transparently (its lines sit at the enclosing level). */
96
+ * and renders transparently (its lines sit at the enclosing level).
97
+ *
98
+ * Two adjacent lines sit in the same container iff their whole path matches.
99
+ * `instance` is what tells one container from an adjacent sibling of identical
100
+ * shape — two consecutive quotes, or two consecutive lists — which contiguity
101
+ * alone reads as one. Omit it (or write `0`) unless a path immediately above or
102
+ * below is otherwise identical; a write is canonicalized to `0`/`1` on the way
103
+ * in, so any distinct pair of values works. */
97
104
  export type ContentContainer =
98
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number }
99
- | { container: "quote" }
100
- | { container: string; attrs: unknown };
105
+ | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
106
+ | { container: "quote"; instance?: number }
107
+ | { container: string; attrs: unknown; instance?: number };
101
108
 
102
109
  /** A mark over char range `[start, end)` into `Content.text`. The open `type`
103
110
  * arm blocks discriminant narrowing, so read a payload-carrying arm behind its
Binary file
package/core/wasm.d.ts CHANGED
@@ -93,11 +93,18 @@ export type ContentLineKind =
93
93
 
94
94
  /** An ancestor block a line nests inside, outermost first. Open like
95
95
  * `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
96
- * and renders transparently (its lines sit at the enclosing level). */
96
+ * and renders transparently (its lines sit at the enclosing level).
97
+ *
98
+ * Two adjacent lines sit in the same container iff their whole path matches.
99
+ * `instance` is what tells one container from an adjacent sibling of identical
100
+ * shape — two consecutive quotes, or two consecutive lists — which contiguity
101
+ * alone reads as one. Omit it (or write `0`) unless a path immediately above or
102
+ * below is otherwise identical; a write is canonicalized to `0`/`1` on the way
103
+ * in, so any distinct pair of values works. */
97
104
  export type ContentContainer =
98
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number }
99
- | { container: "quote" }
100
- | { container: string; attrs: unknown };
105
+ | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
106
+ | { container: "quote"; instance?: number }
107
+ | { container: string; attrs: unknown; instance?: number };
101
108
 
102
109
  /** A mark over char range `[start, end)` into `Content.text`. The open `type`
103
110
  * arm blocks discriminant narrowing, so read a payload-carrying arm behind its
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.108.2",
3
+ "version": "0.109.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -245,6 +245,7 @@ export declare function isListItemContainer(
245
245
  ordered: boolean;
246
246
  start: number;
247
247
  ordinal: number;
248
+ instance?: number;
248
249
  };
249
250
 
250
251
  // The guards above answer "is this arm X". These four answer "is this a value
@@ -454,7 +454,7 @@ export function isCodeLine(line) {
454
454
 
455
455
  /**
456
456
  * @param {import('../core/wasm.js').ContentContainer} container
457
- * @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; ordered: boolean; start: number; ordinal: number }}
457
+ * @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; ordered: boolean; start: number; ordinal: number; instance?: number }}
458
458
  */
459
459
  export function isListItemContainer(container) {
460
460
  return container.container === 'list_item';