@quillmark/wasm 0.113.0 → 0.115.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,302 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.115.0 - 2026-09-22
4
+
5
+ Upgrade path: [0.114 → 0.115](docs/migrations/0.114-to-0.115.md).
6
+
7
+ ### The quill authoring contract
8
+
9
+ - feat(core)!: **a `matrix` member mapping takes the ordinary ladder.** Two
10
+ rules decided what a stored member spelling meant — a bare scalar is the tick,
11
+ and a mapping naming no `held` is held — and the second retires. A member is
12
+ an `object` of a synthesized `held: {type: boolean, default: false}` beside
13
+ its columns, so a mapping is the member object already and the tick it names
14
+ no key for resolves as every other absent cell does, to its `default:`. The
15
+ old rule overrode that ladder for one key in one type, and bought a spelling
16
+ convenience for it: two mappings one key apart meant opposite ticks, the
17
+ shorter one ticked, and any producer building the member object without
18
+ conforming — an editor writing a column before the tick, a serde-built
19
+ payload, `{}` — set a tick it never wrote. `{ detail: X }` is now unheld with
20
+ its detail retained, which is what `{ held: false, detail: X }` already meant;
21
+ the bare scalar and every mapping naming `held` are unchanged. What remains is
22
+ exactly `conform_variant`'s bare-scalar rule, so `SCHEMAS.md` §Matrix lists
23
+ three things as the type's own rather than four. The flip is silent — the
24
+ value is well-typed under both readings, and a defaultless column's
25
+ `validation::must_fill` leaves with the tick it was gated on — so the upgrade
26
+ guide leads with it. (#1860)
27
+ - feat(core): **a typed dictionary's property opens a variant world.**
28
+ `variants:` was card-level, so a conditional block had to sit at the top of a
29
+ card even where the fields it governs belong to a dictionary. The rule was
30
+ never about depth: what stays one level deep is the gap between a field's
31
+ *declared* shape and its *live* one, and a dictionary's own shape is the
32
+ schema's — so a world opened there deepens the address and leaves the gap
33
+ alone. `header.classification` reads, binds, branches and strands exactly as
34
+ `classification` does, one step down. Every other position would deepen the
35
+ gap and still refuses one: an array element, whose liveness would vary per
36
+ index against a form bound once; a matrix column, inside a grid the page
37
+ prints in full; and another variant's cell, which would make the plate's one
38
+ branch a tree. `quill::variant_placement` now names the position carrying the
39
+ ban rather than restating the old rule, and the ban is **sticky** — an object
40
+ inside any of them inherits it instead of laundering a world into a position
41
+ that cannot hold one. The surfaces that dispatch on the container before
42
+ walking the type needed nothing (coercion, validation, the render floor,
43
+ conditional `must_fill`, the transform schema, `acroform` binding); the three
44
+ that scanned card fields now descend, so `validation::out_of_variant` finds a
45
+ strand at `main.header.classification.controlled_by`, the blueprint seats a
46
+ world's cells — live or commented — at the dictionary's indent, and the seed
47
+ resolves a nested discriminant before walking its field set.
48
+ - feat(core)!: **`ui.layout: table` contracts its columns.** The key was checked
49
+ for its outer shape alone — `type: array` whose `items` is an `object` — so a
50
+ row holding a container loaded clean and no editor could draw it: the author
51
+ wrote a key, the loader took it, and a surface ignored it with no signal at
52
+ either end. A column that is not a leaf is now
53
+ `quill::table_column_not_flat`, naming the column (`appendices[].entries`)
54
+ rather than the field. This splits the key along a seam it always had: a
55
+ **contract** that every column is a leaf, settled at load, and a **request**
56
+ to draw a grid, which an editor still declines for a width or a cell renderer
57
+ it does not have. Shape leaves the decline list, since shape was never a
58
+ consumer's judgement to make. The boundary is containment, not height — prose
59
+ is a column whatever its `inline`, because how tall a cell renders is the
60
+ editor's call and what it contains is not. `FieldType::is_leaf` is the one
61
+ definition, an exhaustive match a new type cannot skip, and `acroform`'s
62
+ `is_scalar_or_prose` is gone in favor of it: the same question had two
63
+ answers in two crates. Part of #1859.
64
+ - feat(core,wasm,python): **the empty document has a constructor.**
65
+ `Quill::empty_document()` is `Document::new` under the quill's own reference,
66
+ so the three canonical documents are three constructors beside
67
+ `QuillConfig::blueprint()` and `Quill::seed_document()`, and no caller spells
68
+ `name@version` to reach the one a plate is bound to render. `quiver_test.rs`
69
+ reads it rather than formatting the two-line markdown itself. The bindings
70
+ carry it as `Quill.emptyDocument()` / `Quill.empty_document()`.
71
+ - feat(cli)!: **`quillmark validate` compiles the plate.** It renders the empty
72
+ document, the blueprint and the seed through the quill's backend at the
73
+ backend's first declared format, and reports each failure as a
74
+ `cli::canonical_document_failed` error naming which of the three it is,
75
+ followed by the backend's own diagnostics. The contract that a plate renders
76
+ the empty document was checked for the fixtures quiver alone, and `render`
77
+ with no input file renders the seed, the fullest of the three: a plate that
78
+ reads a blank badly passed both. A quill whose plate does not compile now
79
+ exits `1` where it exited `0`, and a backend that does not resolve is
80
+ `cli::backend_unresolved` followed by the engine's own diagnostic. A render's
81
+ warnings join the `-v` list, and `validate --no-render` is the configuration
82
+ read on its own. Closes #1844.
83
+
84
+ ### The content model
85
+
86
+ - refactor(content)!: **an island stores its value, not its provenance.** `Loss`
87
+ had one minting rule — a table cell dropping an inline image's url — and no
88
+ reader: export dispatches on an island's `type`, the Typst emitter and the
89
+ acroform binder never looked, and an `islandOps` `set` stored whatever class
90
+ the op named. Two cells written `| a cat |` and `| ![a cat](cat.png) |` mint
91
+ equal `props` and differ only in the markdown behind them, which the
92
+ projection has no syntax to carry back out, so the class survived neither a
93
+ `to_markdown` → `from_markdown` hop nor re-derivation from the row holding it
94
+ — which is why the content fixed point had to be stated modulo it. An
95
+ island's canonical form is `{id, type, props}`, the fixed point is exact, and
96
+ the closed vocabularies cover the four discriminators a projection dispatches
97
+ on. `Loss`, `Island::loss`, `Island::with_loss`, `IslandType::default_loss`,
98
+ `ContentIsland.loss` and `ContentLossClass` are deleted; a stored row spelling
99
+ `loss` opens with the key ignored and absent from what comes back, and a
100
+ `loss` on an authored island or an `islandOps` entry is ignored rather than
101
+ refused. A host that surfaced the class surfaces the drop at the import
102
+ instead, where the markdown it is about is still in hand. Closes #1757.
103
+ - refactor(content,core,wasm,python)!: **a block island's line is a `para`, and
104
+ `LineKind::Island` is gone.** The kind was a second copy of a fact the island
105
+ beside it already carried: markdown writes a `table` as a block and an `image`
106
+ inline, so `IslandType::block_only` settled which line a lone slot sat on and
107
+ `normalize` wrote that answer back over whatever was stored. Every read site
108
+ now asks the island — export already dispatched on the type, the Typst emitter
109
+ already lowered `Island` as `Para`, and the one fact the kind alone still
110
+ answered, "this block renders one line", moves to the new public
111
+ `Content::block_island_at`, which `normalize` reads beside
112
+ `takes_continuations` when it clears a stranded `continues`. Landing a block
113
+ island is two channels rather than three: the delta opens the line, the island
114
+ op fills it, and the `setKind` goes. The TS `ContentLineKind` loses its
115
+ `{ kind: "island" }` arm, so a host branching on it typechecks red; a `setKind`
116
+ still naming `island` lands as the `para` it would have settled to.
117
+ - refactor(core)!: **the storage tag is `quillmark/document@0.115.0`.** The two
118
+ lines above move canonical bytes — the `loss` key for every document holding
119
+ an island, the line kind for every document holding a table — so content
120
+ hashes recompute once on those rows. `DocumentV0_112_0` freezes with a raw
121
+ `body` and the new `DocumentV0_115_0` carries the `CanonicalContent`; the hop
122
+ between them is that body's decode, and `V0_93_0 → V0_112_0` becomes a retag
123
+ now that both trees spell `body` raw. Stored rows need no migration: the
124
+ content decoder reads `island` as `para` wherever it meets it, permanently,
125
+ since the same spelling rests untagged inside every `richtext` field and
126
+ `$seed` overlay, where no hop reaches it. Closes #1647.
127
+
128
+ ### Schema, validation and the resolved view
129
+
130
+ - refactor(core)!: **a schema is loaded, not deserialized.** `QuillConfig`,
131
+ `CardSchema` and `FieldSchema` serialize and no longer implement
132
+ `Deserialize`, and `FieldSchema::from_quill_value` is crate-internal. A field
133
+ arrives in two steps — the parse, which folds the sibling keys (`inline:`,
134
+ `values:`, `members:`, `max:`) into the type payload, and the shape walk,
135
+ which rejects an `object` without `properties`, an `array` without `items`,
136
+ an empty `properties` map, and the placements — and only `parse_fields` ran
137
+ both, so a config assembled through serde carried states no `Quill.yaml` can
138
+ reach, absorbed downstream without a diagnostic. Gating the serde path
139
+ instead would have duplicated the walk and collapsed its diagnostic codes
140
+ into `quill::field_parse_error` at that boundary. The way to a config is
141
+ `QuillConfig::from_yaml_with_warnings`, or a `Quill` read off a file tree;
142
+ serialization is untouched, so `schema()`, `schema_yaml()` and every emission
143
+ read the same loaded form they did. Closes #1836.
144
+ - refactor(core,wasm)!: **a matrix roster is one flat mapping; group headings
145
+ are gone.** A block's `group:` was declared in the schema and absent from the
146
+ value, the address and the vocabulary — the blueprint emits
147
+ `matrix<a | b | c>` off one flat key space, so the partition reached the
148
+ visual editor and nowhere else, buying one shared `properties` map for a
149
+ schema concept, its load rules and a reserved key. `members:` is now
150
+ `{id: Title}` directly, `MatrixGroup`, `MATRIX_GROUP_KEY` and the TypeScript
151
+ `QuillMatrixGroup` are deleted, and `FieldType::Matrix` carries an
152
+ `IndexMap<String, String>` read through the new `FieldType::matrix_roster`,
153
+ which replaces `matrix_members`. The projection stops writing `group` onto
154
+ every member, which returns `group` to the column names a quill may declare,
155
+ and `quill::duplicate_matrix_member` retires: the roster is a mapping, so it
156
+ has one slot per id exactly as the stored value does and a duplicate is
157
+ unspellable on both sides. Closes #1851.
158
+
159
+ ### The blueprint
160
+
161
+ - feat(core)!: **a dormant variant world is its own cells, commented out.** A
162
+ variant-bearing enum's blueprint named every world it could not show, one
163
+ `# when CUI: controlled_by, poc, category` line each — the one place on the
164
+ surface where a cell was named rather than shown, dropping the position,
165
+ obligation, type, description and example a cell carries. Every world now sits
166
+ under a `# when <MEMBER>:` header in declaration order: the selected world's
167
+ cells live, every other world's the same cells with a `# ` in front, at the
168
+ slot they would occupy live. A reader activates a world by setting the
169
+ discriminant and deleting `# `. One builder and one emitter serve both, so a
170
+ dormant line is byte-for-byte the live line, and the block round-trips through
171
+ `Document::parse` as the comments it is, reaching neither the validator nor
172
+ the render floor. Closes #1846.
173
+
174
+
175
+ ## v0.114.0 - 2026-09-18
176
+
177
+ ### Schema, validation and the resolved view
178
+
179
+ - feat(core): **`type: matrix`: a closed vocabulary someone ticks.** A card kind
180
+ gave add and remove, the wrong verbs for a roster the page prints in full, and
181
+ an `array<{member: enum, detail}>` hid every unpicked option, let the same
182
+ member in twice, and left the plate owning a second copy of the vocabulary to
183
+ print the unheld boxes. A `matrix` declares `members:` — ordered blocks of
184
+ `id: Title`, groups optional — and `properties:` as the columns, and the
185
+ loader expands them into an `object` whose properties are the member ids, each
186
+ carrying a synthesized `held: {type: boolean, default: false}` beside the
187
+ columns. Coercion, validation, blank-fill and addressing are the typed
188
+ dictionary's, reached through the new `FieldSchema::namespace_props`, so
189
+ `qualifications.flight_cc.held` regions on Typst and binds a checkbox on
190
+ acroform without a new address grammar. Four behaviors are the type's own.
191
+ **Key presence is the tick**: `cyber_200: true` normalizes to the member
192
+ object as the bare `classification: CUI` normalizes to `{value: CUI}`, and a
193
+ mapping naming no `held` is held, so a document stays sparse. **The roster
194
+ reaches the wire**: every member present in declaration order carrying its own
195
+ `title` and `group`, so a plate prints the vocabulary without holding a second
196
+ copy of it. **The wire carries the live world only**: an unheld member's
197
+ columns render at their blanks whatever the document retains, the closed shape
198
+ variants already hold, so tick, type, untick, retick keeps the answer in the
199
+ file and stops rendering it. **Obligation is gated on the tick**: a column with
200
+ no `default:` reads "required when held", the variant rule one level down,
201
+ while the matrix itself obliges nothing, takes no `default:` or `example:`
202
+ (`quill::{default,example}_on_namespace`, the existing namespace rule) and
203
+ seeds empty. `held`, `title` and `group` are reserved as column names
204
+ (`quill::matrix_reserved_column`); a member id is a snake_case identifier
205
+ (`quill::invalid_matrix_member`), unique across the roster
206
+ (`quill::duplicate_matrix_member`), and one outside it is refused as an
207
+ out-of-domain enum member is. The blueprint spells the roster in the existing
208
+ format slot, `# matrix<flight_cc | dodin_ops | …>`, over a `{}` cell, so no
209
+ third annotation form is minted and a model sees the whole vocabulary without
210
+ twenty-seven lines to delete. Closes #1831.
211
+ - feat(core): **the transform schema carries a declared key order, and the Typst
212
+ codegen honours it.** `Codegen::emit_value` sorts dict keys so a reorder-only
213
+ `update` rebuilds byte-identical `lib.typ` and comemo reuses the whole compile.
214
+ A matrix is the one node that cannot pay it: printing the vocabulary as the
215
+ schema groups it is the type's purpose, and sorted keys hand the plate
216
+ `cyber_200` before `sq_cc_candidate`. A container node whose *schema* fixes an
217
+ order now says so with `quillmark:order`, and the emitter reads it. The order
218
+ is a property of the schema rather than of the data, so equal data still
219
+ produces byte-equal source and nothing about the reuse changes; a node
220
+ carrying no such key sorts as before.
221
+ - feat(core): **`max:` on an array, and `validation::cardinality`.** A form with
222
+ thirty-seven ruled lines silently swallowed a thirty-eighth row, and the limit
223
+ lived in `description:` prose that no consumer reads. `max:` is a non-negative
224
+ integer on an `array`, refused elsewhere, and a `default:`/`example:` longer
225
+ than it fails the load (`quill::{default,example}_over_max`) rather than
226
+ seeding a document that warns on arrival. `Quill::validate` reports
227
+ `validation::cardinality` at the field's own path with `{max, actual}`, at
228
+ every depth — an array in a typed dictionary, a matrix member, a live variant
229
+ world or another array's elements is capped by its own declaration — and never
230
+ gates render: fatal ≡ won't-render is an invariant of the diagnostic model, and
231
+ the plate keeps its own rule for the surplus. The count is the render floor's,
232
+ so a bare scalar coerced to a one-element array is counted as one. `min:` is
233
+ not taken: obligation is `default:`'s absence, so `min: 1` is `required:` under
234
+ another name. Closes #1826.
235
+ - feat(core): **the blueprint carries a cap as `# up to <N>`.** The author most
236
+ likely to exceed a limit is the MCP flow that reads the spec once and then
237
+ writes, and it reads the blueprint: `schema()` is consulted before that author
238
+ writes, and the `validate` warning arrives after the overflowing document
239
+ exists. The line rides the own-line leading slot directly under the field's
240
+ description, the form `# composable (0..N)` already takes for a card kind's
241
+ cardinality, so the `<type>[<format>]` inline grammar is untouched. It reaches
242
+ a field and an `object` property, the two positions that slot exists at.
243
+ - feat(core): **`ui.layout: table` asks an editor for the grid a typed table
244
+ wants.** A row of four short cells drew as a stack of collapsed rows that open
245
+ one at a time — the right control for a row of long prose, the wrong one here —
246
+ and `UiFieldSchema` is `deny_unknown_fields`, so the shape had no way to ask.
247
+ `layout: table` is valid on an `array` whose `items` is an `object` and refused
248
+ with `quill::invalid_ui` anywhere else, and `schema()` echoes it verbatim. A
249
+ request, not a contract: the plate, `validate` and the blueprint are all
250
+ deliberately inert on it, and a consumer that cannot draw the control falls
251
+ back to its own choice for the type. `items.ui.title` is documented beside it
252
+ as the collapsed row's summary template, which needed no loader change.
253
+ Closes #1825.
254
+ - feat(core): **a card kind declaring `body.enabled: false` warns.** A card is a
255
+ part someone writes; a repeated record someone fills in is a row, an
256
+ `array<object>` on the card that owns it. Four card kinds across three airmark
257
+ quills were rows in card costume, each bodiless with a plate reassembling them
258
+ from the stream by position. `quill::bodiless_card_kind` says so at
259
+ `Severity::Warning`, hinting at the row shape. A warning and not a load error,
260
+ because the loader sees the proxy and not the fact: whether a kind interleaves
261
+ among kinds of other sorts is a property of documents and the plate, nothing in
262
+ `Quill.yaml` states it, and a bodiless positional kind — a page break, a rule,
263
+ an inserted signature block — is a card. `main` keeps the key unwarned: a form
264
+ has no root prose. Closes #1824.
265
+
266
+ ### The binding surface
267
+
268
+ - feat(wasm): **`formatDiagnostic` renders a diagnostic as the CLI does.**
269
+ `Document.formatDiagnostic` left the binding in 0.113 among the five owner
270
+ calls a host can make itself, and it was the one of the five that left a
271
+ surface behind: Python keeps the rendering on `PyDiagnostic.__str__`, which is
272
+ `fmt_pretty`, and the CLI prints through the same call, so WASM alone lost the
273
+ engine's English. A consumer wording its own still selects by `code` + `args`.
274
+
275
+ ### Docs and tests
276
+
277
+ - docs(core): **`ERROR.md` § "Error Presentation" names the two lanes apart.** A
278
+ consumer surfacing the engine's wording calls the one printer; a consumer
279
+ wording its own selects by `code` + `args` and owns the layout with the
280
+ sentence.
281
+ - docs(acroform): **the worked example gains the richtext field the fixture
282
+ does.** `sample_form` is what the backend guide points at for a hand-authored
283
+ `form.pdf` + `form.json`, so its excerpts state what that fixture declares.
284
+ - test(core): **one fixture walk, one scalar-fidelity home, one card-order
285
+ test.** Closes #1676.
286
+ - test(core): **one loader, one coercion table, one quill header.** Closes #1677.
287
+ - test(pdf,typst): **one base-PDF builder, one home per invariant.** Closes
288
+ #1681, closes #1680.
289
+ - ci: **the release dispatches the docs deploy it no longer triggers.**
290
+ `docs.yml` deploys Pages on `release: published`, and moving `gh release
291
+ create` onto `GITHUB_TOKEN` stopped that event starting a run, so the site sat
292
+ eleven releases behind at v0.102.0. `workflow_dispatch` is the one event exempt
293
+ from that rule, which `release-prepare.yml` already leans on for `ci.yml`, so
294
+ the release dispatches `docs.yml` at the tag it just pushed — the site is built
295
+ from the released bytes, and a prerelease is skipped as the event path skipped
296
+ it.
297
+ - build(deps): **lopdf 0.44.0 → 0.45.0**, and **taiki-e/install-action 2.87.8 →
298
+ 2.87.12** in the actions group.
299
+
3
300
  ## v0.113.0 - 2026-09-16
4
301
 
5
302
  Upgrade path: [0.112 → 0.113](docs/migrations/0.112-to-0.113.md).
package/README.md CHANGED
@@ -175,9 +175,9 @@ const doc = Document.storageVersionOf(content)
175
175
  : Document.fromMarkdown(content);
176
176
  ```
177
177
 
178
- The `schema` value (`quillmark/document@0.112.0`) is the **model version**,
178
+ The `schema` value (`quillmark/document@0.115.0`) is the **model version**,
179
179
  not the running crate version. It is a hand-set constant, bumped only when
180
- the `Document` model itself changes, so every `0.112.x` patch release reads
180
+ the `Document` model itself changes, so every `0.115.x` patch release reads
181
181
  and writes that same value.
182
182
 
183
183
  - **Upgrading is safe.** A newer build reads documents an older build's
@@ -187,8 +187,10 @@ and writes that same value.
187
187
  canonical on-disk format keeps loading across crate upgrades: there is no
188
188
  need to pin old wasm to read old data. The exception is a row a host
189
189
  authored a content construct of its own into — a line `kind`, container,
190
- mark `type`, island `type` or `loss` outside the vocabulary. Those are
191
- refused from 0.113 on; see that release's migration guide.
190
+ mark `type` or island `type` outside the vocabulary. Those are refused from
191
+ 0.113 on; see that release's migration guide. An island `loss` is not one of
192
+ them: 0.115 drops the key, so a row spelling one opens whatever the class and
193
+ comes back without it.
192
194
  - **Downgrading is not.** `fromStored` rejects an *unknown* (i.e. newer)
193
195
  `schema` version rather than guessing at a format it predates. Don't feed
194
196
  documents written by a newer build back into an older one.
package/core/wasm.d.ts CHANGED
@@ -73,9 +73,9 @@ export interface Content {
73
73
  }
74
74
 
75
75
  /** One `\n`-separated segment of `Content.text`, in order. `kind` is a closed
76
- * set: a role outside it is refused wherever content is decoded. Every role
77
- * spells its payload in `attrs`, so `kind === "heading"` narrows `attrs` to
78
- * `{ level: number }` with no guard. */
76
+ * set: `island` reads as `para`, and any other role outside it is refused
77
+ * wherever content is decoded. Every role spells its payload in `attrs`, so
78
+ * `kind === "heading"` narrows `attrs` to `{ level: number }` with no guard. */
79
79
  export type ContentLine = {
80
80
  containers: ContentContainer[];
81
81
  /** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
@@ -87,7 +87,6 @@ export type ContentLineKind =
87
87
  | { kind: "para" }
88
88
  | { kind: "heading"; attrs: { level: number } }
89
89
  | { kind: "code"; attrs?: { lang?: string } }
90
- | { kind: "island" }
91
90
  | { kind: "rule" };
92
91
 
93
92
  /** An ancestor block a line nests inside, outermost first. Closed like
@@ -158,14 +157,10 @@ export interface ImageProps {
158
157
  alt: string;
159
158
  }
160
159
 
161
- /** How faithfully the markdown projection can carry an island. */
162
- export type ContentLossClass = "lossless" | "degraded" | "unrepresentable";
163
-
164
160
  /** A structured object occupying one island slot in `Content.text`. `type` is a
165
161
  * closed set, so `type === "table"` narrows `props` to `TableProps`. */
166
162
  export type ContentIsland = {
167
163
  id: string;
168
- loss: ContentLossClass;
169
164
  } & (
170
165
  | { type: "table"; props: TableProps }
171
166
  | { type: "image"; props: ImageProps }
@@ -237,10 +232,12 @@ export type MarkOp =
237
232
  * metadata. `setContinues` sets or clears a line's within-block hard-break flag
238
233
  * (`ContentLine.continues`); `continues: true` lands as `false` on line 0, which
239
234
  * nothing precedes, on a line whose containers differ from the line above, and
240
- * on one following a heading, island or rule, each a block of one line.
241
- * `setKind` lands a kind the line's text contradicts — `island` or `rule` over
235
+ * on one following a heading, a rule or a block island's line, each a block of
236
+ * one line. `setKind` lands a kind the line's text contradicts — `rule` over
242
237
  * prose, `code` over a slot — as `para`, which is what re-importing the line's
243
- * own markdown yields. Read the content back to see where an op settled.
238
+ * own markdown yields, and reads an `island` the union does not offer as the
239
+ * `para` that spelling projects. Read the content back to see where an op
240
+ * settled.
244
241
  */
245
242
  export type LineOp =
246
243
  | { op: "split"; at: number }
@@ -264,22 +261,16 @@ export type LineOp =
264
261
  * one `insert` per slot.
265
262
  *
266
263
  * Deleting an island needs no op: a `delta` that removes its slot drops the
267
- * island whole, and a block island's line demotes to `para`. Re-landing it is an
268
- * `insert` of the full island under its original id; a pasted copy of a live
269
- * island mints a fresh one.
270
- *
271
- * A `set` stores the `loss` it is given; nothing re-derives the class from the
272
- * new `props`.
264
+ * island whole. Re-landing it is an `insert` of the full island under its
265
+ * original id; a pasted copy of a live island mints a fresh one.
273
266
  *
274
267
  * An island is *inline* (a slot inside a paragraph) or a **block** (that slot
275
- * alone on a line under `kind: "island"`), and for a slot alone on a line the
276
- * type settles which: markdown writes a `table` as a block and an image inline,
277
- * so the line's `kind` is read off the type and a `setKind` spelling it
278
- * otherwise does not survive. Landing a block island is one bundle of all three
279
- * channels, in the order they apply: `delta` inserts the `\n` that opens the
280
- * line, `islandOps` inserts the slot, `lineOps` tags the line
281
- * `{ op: "setKind", kind: "island" }`. `{ op: "split" }` cannot open that line,
282
- * since line ops run after island ops.
268
+ * alone on its own line), and the type settles which: markdown writes a `table`
269
+ * as a block and an image inline. Both lines read `kind: "para"` — the block is
270
+ * the slot's markup, not a role the line carries — so landing a block island is
271
+ * two channels: `delta` inserts the `\n` that opens the line, `islandOps`
272
+ * inserts the slot. `{ op: "split" }` cannot open that line, since line ops run
273
+ * after island ops.
283
274
  *
284
275
  * A `table` has no inline placement: markdown writes it as a block, so an
285
276
  * `insert` whose `at` is not an empty line throws, as does a `set` retyping an
@@ -397,6 +388,12 @@ export interface QuillFieldUi {
397
388
  /** Label for an `enum`'s blank option. Absent, the consumer supplies a
398
389
  * conventional label of its own. */
399
390
  blank_title?: string;
391
+ /** The control the field asks for, where the shape admits more than one.
392
+ * `"table"` is valid only on an `array` whose `items` is an `object`.
393
+ * Every column is a leaf, a contract refused at load as
394
+ * `quill::table_column_not_flat`. Drawing the grid is a request: a
395
+ * consumer that cannot falls back to its own choice for the type. */
396
+ layout?: "table";
400
397
  }
401
398
 
402
399
  /** One entry in a card's `ui.groups` registry: a display-label override for the
@@ -448,21 +445,31 @@ export interface QuillCardBody {
448
445
  * gates render: an absent field blank-fills.
449
446
  */
450
447
  export interface QuillFieldSchema {
451
- type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
448
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum" | "matrix";
452
449
  description?: string;
453
450
  default?: unknown;
454
451
  example?: unknown;
455
452
  /** The closed set of allowed values. Required on `type: "enum"`, and valid
456
453
  * nowhere else. */
457
454
  values?: string[];
458
- /** Per-member field sets on a card-level `type: "enum"` field, keyed by
459
- * member: the fields that exist only where the discriminant holds that
460
- * member. Declaring it makes the field rest as a container,
461
- * `{value: <member>, …that member's fields}`, rather than a bare string. */
455
+ /** Per-member field sets on a `type: "enum"` field that is a card's own
456
+ * field or a typed dictionary's property, keyed by member: the fields that
457
+ * exist only where the discriminant holds that member. Declaring it makes
458
+ * the field rest as a container, `{value: <member>, …that member's
459
+ * fields}`, rather than a bare string. */
462
460
  variants?: Record<string, Record<string, QuillFieldSchema>>;
461
+ /** The roster of a `type: "matrix"` field, required there and valid
462
+ * nowhere else: member id to display title, key order the display order.
463
+ * Each member is an object of `held` plus the field's `properties`
464
+ * (the columns), addressed as `<field>.<member id>.held`. */
465
+ members?: Record<string, string>;
463
466
  ui?: QuillFieldUi;
464
467
  properties?: Record<string, QuillFieldSchema>;
465
468
  items?: QuillFieldSchema;
469
+ /** The element count past which an `array` overflows the page it is laid
470
+ * out on. Valid only on an `array`. Never gates render: a document over
471
+ * the cap warns `validation::cardinality` and renders. */
472
+ max?: number;
466
473
  /** `true` on a `richtext` or `plaintext` field declared `inline`: the
467
474
  * single-paragraph, container-free, island-free constraint. */
468
475
  inline?: boolean;
@@ -832,6 +839,14 @@ export class Quill {
832
839
  * mutation.
833
840
  */
834
841
  conform(doc: Document): Diagnostic[];
842
+ /**
843
+ * The empty document: a main card carrying this quill's `$quill` and
844
+ * `$kind: main`, no field committed, no composable card, no body. The
845
+ * type-minimal valid input, and the document a plate is bound to render
846
+ * (the quill authoring contract). `new Document(ref)` is the same document
847
+ * reached by spelling the reference.
848
+ */
849
+ emptyDocument(): Document;
835
850
  /**
836
851
  * Build a quill from a file tree. Pure: the declared backend is resolved
837
852
  * later, at render time. Accepts a `Map<string, Uint8Array>` or a plain
@@ -923,6 +938,19 @@ export class Quill {
923
938
  */
924
939
  export function exportMarkdown(rt: Content): string;
925
940
 
941
+ /**
942
+ * Render a diagnostic as the CLI and Python's `str(diagnostic)` render it:
943
+ * the severity tag and the message, the code parenthesised after them, then
944
+ * location, path and hint each on an indented line of its own. The engine
945
+ * owns the one printer, so a consumer surfacing diagnostics reads it here
946
+ * rather than keeping a copy of the layout that drifts from the CLI's.
947
+ *
948
+ * Takes the `Diagnostic` shape every read hands back. Throws on a value that
949
+ * is not one — a missing `severity` or `message`, or a `severity` outside the
950
+ * two-value ladder.
951
+ */
952
+ export function formatDiagnostic(diagnostic: Diagnostic): string;
953
+
926
954
  /**
927
955
  * Serialize structured [`DocPathSeg`] segments back to the canonical path
928
956
  * string: the inverse of `parseDocPath`. Throws on a segment array the
@@ -1043,6 +1071,7 @@ export interface InitOutput {
1043
1071
  readonly document_toStored: (a: number, b: number) => void;
1044
1072
  readonly document_warnings: (a: number, b: number) => void;
1045
1073
  readonly exportMarkdown: (a: number, b: number) => void;
1074
+ readonly formatDiagnostic: (a: number, b: number) => void;
1046
1075
  readonly formatDocPath: (a: number, b: number) => void;
1047
1076
  readonly importMarkdown: (a: number, b: number, c: number) => void;
1048
1077
  readonly mapMarks: (a: number, b: number, c: number) => void;
@@ -1052,6 +1081,7 @@ export interface InitOutput {
1052
1081
  readonly quill_backendId: (a: number, b: number) => void;
1053
1082
  readonly quill_blueprint: (a: number, b: number) => void;
1054
1083
  readonly quill_conform: (a: number, b: number, c: number) => void;
1084
+ readonly quill_emptyDocument: (a: number) => number;
1055
1085
  readonly quill_fromTree: (a: number, b: number) => void;
1056
1086
  readonly quill_metadata: (a: number, b: number) => void;
1057
1087
  readonly quill_parse: (a: number, b: number, c: number, d: number) => void;
package/core/wasm.js CHANGED
@@ -1297,6 +1297,18 @@ export class Quill {
1297
1297
  wasm.__wbindgen_add_to_stack_pointer(16);
1298
1298
  }
1299
1299
  }
1300
+ /**
1301
+ * The empty document: a main card carrying this quill's `$quill` and
1302
+ * `$kind: main`, no field committed, no composable card, no body. The
1303
+ * type-minimal valid input, and the document a plate is bound to render
1304
+ * (the quill authoring contract). `new Document(ref)` is the same document
1305
+ * reached by spelling the reference.
1306
+ * @returns {Document}
1307
+ */
1308
+ emptyDocument() {
1309
+ const ret = wasm.quill_emptyDocument(this.__wbg_ptr);
1310
+ return Document.__wrap(ret);
1311
+ }
1300
1312
  /**
1301
1313
  * Build a quill from a file tree. Pure: the declared backend is resolved
1302
1314
  * later, at render time. Accepts a `Map<string, Uint8Array>` or a plain
@@ -1546,6 +1558,44 @@ export function exportMarkdown(rt) {
1546
1558
  }
1547
1559
  }
1548
1560
 
1561
+ /**
1562
+ * Render a diagnostic as the CLI and Python's `str(diagnostic)` render it:
1563
+ * the severity tag and the message, the code parenthesised after them, then
1564
+ * location, path and hint each on an indented line of its own. The engine
1565
+ * owns the one printer, so a consumer surfacing diagnostics reads it here
1566
+ * rather than keeping a copy of the layout that drifts from the CLI's.
1567
+ *
1568
+ * Takes the `Diagnostic` shape every read hands back. Throws on a value that
1569
+ * is not one — a missing `severity` or `message`, or a `severity` outside the
1570
+ * two-value ladder.
1571
+ * @param {Diagnostic} diagnostic
1572
+ * @returns {string}
1573
+ */
1574
+ export function formatDiagnostic(diagnostic) {
1575
+ let deferred2_0;
1576
+ let deferred2_1;
1577
+ try {
1578
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1579
+ wasm.formatDiagnostic(retptr, addHeapObject(diagnostic));
1580
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1581
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1582
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
1583
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
1584
+ var ptr1 = r0;
1585
+ var len1 = r1;
1586
+ if (r3) {
1587
+ ptr1 = 0; len1 = 0;
1588
+ throw takeObject(r2);
1589
+ }
1590
+ deferred2_0 = ptr1;
1591
+ deferred2_1 = len1;
1592
+ return getStringFromWasm0(ptr1, len1);
1593
+ } finally {
1594
+ wasm.__wbindgen_add_to_stack_pointer(16);
1595
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
1596
+ }
1597
+ }
1598
+
1549
1599
  /**
1550
1600
  * Serialize structured [`DocPathSeg`] segments back to the canonical path
1551
1601
  * string: the inverse of `parseDocPath`. Throws on a segment array the
package/core/wasm_bg.wasm CHANGED
Binary file
@@ -52,6 +52,7 @@ export const document_toMarkdown: (a: number, b: number) => void;
52
52
  export const document_toStored: (a: number, b: number) => void;
53
53
  export const document_warnings: (a: number, b: number) => void;
54
54
  export const exportMarkdown: (a: number, b: number) => void;
55
+ export const formatDiagnostic: (a: number, b: number) => void;
55
56
  export const formatDocPath: (a: number, b: number) => void;
56
57
  export const importMarkdown: (a: number, b: number, c: number) => void;
57
58
  export const mapMarks: (a: number, b: number, c: number) => void;
@@ -61,6 +62,7 @@ export const quill__resolve: (a: number, b: number, c: number) => void;
61
62
  export const quill_backendId: (a: number, b: number) => void;
62
63
  export const quill_blueprint: (a: number, b: number) => void;
63
64
  export const quill_conform: (a: number, b: number, c: number) => void;
65
+ export const quill_emptyDocument: (a: number) => number;
64
66
  export const quill_fromTree: (a: number, b: number) => void;
65
67
  export const quill_metadata: (a: number, b: number) => void;
66
68
  export const quill_parse: (a: number, b: number, c: number, d: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.113.0",
3
+ "version": "0.115.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/render/wasm.d.ts CHANGED
@@ -73,9 +73,9 @@ export interface Content {
73
73
  }
74
74
 
75
75
  /** One `\n`-separated segment of `Content.text`, in order. `kind` is a closed
76
- * set: a role outside it is refused wherever content is decoded. Every role
77
- * spells its payload in `attrs`, so `kind === "heading"` narrows `attrs` to
78
- * `{ level: number }` with no guard. */
76
+ * set: `island` reads as `para`, and any other role outside it is refused
77
+ * wherever content is decoded. Every role spells its payload in `attrs`, so
78
+ * `kind === "heading"` narrows `attrs` to `{ level: number }` with no guard. */
79
79
  export type ContentLine = {
80
80
  containers: ContentContainer[];
81
81
  /** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
@@ -87,7 +87,6 @@ export type ContentLineKind =
87
87
  | { kind: "para" }
88
88
  | { kind: "heading"; attrs: { level: number } }
89
89
  | { kind: "code"; attrs?: { lang?: string } }
90
- | { kind: "island" }
91
90
  | { kind: "rule" };
92
91
 
93
92
  /** An ancestor block a line nests inside, outermost first. Closed like
@@ -158,14 +157,10 @@ export interface ImageProps {
158
157
  alt: string;
159
158
  }
160
159
 
161
- /** How faithfully the markdown projection can carry an island. */
162
- export type ContentLossClass = "lossless" | "degraded" | "unrepresentable";
163
-
164
160
  /** A structured object occupying one island slot in `Content.text`. `type` is a
165
161
  * closed set, so `type === "table"` narrows `props` to `TableProps`. */
166
162
  export type ContentIsland = {
167
163
  id: string;
168
- loss: ContentLossClass;
169
164
  } & (
170
165
  | { type: "table"; props: TableProps }
171
166
  | { type: "image"; props: ImageProps }
@@ -237,10 +232,12 @@ export type MarkOp =
237
232
  * metadata. `setContinues` sets or clears a line's within-block hard-break flag
238
233
  * (`ContentLine.continues`); `continues: true` lands as `false` on line 0, which
239
234
  * nothing precedes, on a line whose containers differ from the line above, and
240
- * on one following a heading, island or rule, each a block of one line.
241
- * `setKind` lands a kind the line's text contradicts — `island` or `rule` over
235
+ * on one following a heading, a rule or a block island's line, each a block of
236
+ * one line. `setKind` lands a kind the line's text contradicts — `rule` over
242
237
  * prose, `code` over a slot — as `para`, which is what re-importing the line's
243
- * own markdown yields. Read the content back to see where an op settled.
238
+ * own markdown yields, and reads an `island` the union does not offer as the
239
+ * `para` that spelling projects. Read the content back to see where an op
240
+ * settled.
244
241
  */
245
242
  export type LineOp =
246
243
  | { op: "split"; at: number }
@@ -264,22 +261,16 @@ export type LineOp =
264
261
  * one `insert` per slot.
265
262
  *
266
263
  * Deleting an island needs no op: a `delta` that removes its slot drops the
267
- * island whole, and a block island's line demotes to `para`. Re-landing it is an
268
- * `insert` of the full island under its original id; a pasted copy of a live
269
- * island mints a fresh one.
270
- *
271
- * A `set` stores the `loss` it is given; nothing re-derives the class from the
272
- * new `props`.
264
+ * island whole. Re-landing it is an `insert` of the full island under its
265
+ * original id; a pasted copy of a live island mints a fresh one.
273
266
  *
274
267
  * An island is *inline* (a slot inside a paragraph) or a **block** (that slot
275
- * alone on a line under `kind: "island"`), and for a slot alone on a line the
276
- * type settles which: markdown writes a `table` as a block and an image inline,
277
- * so the line's `kind` is read off the type and a `setKind` spelling it
278
- * otherwise does not survive. Landing a block island is one bundle of all three
279
- * channels, in the order they apply: `delta` inserts the `\n` that opens the
280
- * line, `islandOps` inserts the slot, `lineOps` tags the line
281
- * `{ op: "setKind", kind: "island" }`. `{ op: "split" }` cannot open that line,
282
- * since line ops run after island ops.
268
+ * alone on its own line), and the type settles which: markdown writes a `table`
269
+ * as a block and an image inline. Both lines read `kind: "para"` — the block is
270
+ * the slot's markup, not a role the line carries — so landing a block island is
271
+ * two channels: `delta` inserts the `\n` that opens the line, `islandOps`
272
+ * inserts the slot. `{ op: "split" }` cannot open that line, since line ops run
273
+ * after island ops.
283
274
  *
284
275
  * A `table` has no inline placement: markdown writes it as a block, so an
285
276
  * `insert` whose `at` is not an empty line throws, as does a `set` retyping an
@@ -452,6 +443,12 @@ export interface QuillFieldUi {
452
443
  /** Label for an `enum`'s blank option. Absent, the consumer supplies a
453
444
  * conventional label of its own. */
454
445
  blank_title?: string;
446
+ /** The control the field asks for, where the shape admits more than one.
447
+ * `"table"` is valid only on an `array` whose `items` is an `object`.
448
+ * Every column is a leaf, a contract refused at load as
449
+ * `quill::table_column_not_flat`. Drawing the grid is a request: a
450
+ * consumer that cannot falls back to its own choice for the type. */
451
+ layout?: "table";
455
452
  }
456
453
 
457
454
  /** One entry in a card's `ui.groups` registry: a display-label override for the
@@ -503,21 +500,31 @@ export interface QuillCardBody {
503
500
  * gates render: an absent field blank-fills.
504
501
  */
505
502
  export interface QuillFieldSchema {
506
- type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
503
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum" | "matrix";
507
504
  description?: string;
508
505
  default?: unknown;
509
506
  example?: unknown;
510
507
  /** The closed set of allowed values. Required on `type: "enum"`, and valid
511
508
  * nowhere else. */
512
509
  values?: string[];
513
- /** Per-member field sets on a card-level `type: "enum"` field, keyed by
514
- * member: the fields that exist only where the discriminant holds that
515
- * member. Declaring it makes the field rest as a container,
516
- * `{value: <member>, …that member's fields}`, rather than a bare string. */
510
+ /** Per-member field sets on a `type: "enum"` field that is a card's own
511
+ * field or a typed dictionary's property, keyed by member: the fields that
512
+ * exist only where the discriminant holds that member. Declaring it makes
513
+ * the field rest as a container, `{value: <member>, …that member's
514
+ * fields}`, rather than a bare string. */
517
515
  variants?: Record<string, Record<string, QuillFieldSchema>>;
516
+ /** The roster of a `type: "matrix"` field, required there and valid
517
+ * nowhere else: member id to display title, key order the display order.
518
+ * Each member is an object of `held` plus the field's `properties`
519
+ * (the columns), addressed as `<field>.<member id>.held`. */
520
+ members?: Record<string, string>;
518
521
  ui?: QuillFieldUi;
519
522
  properties?: Record<string, QuillFieldSchema>;
520
523
  items?: QuillFieldSchema;
524
+ /** The element count past which an `array` overflows the page it is laid
525
+ * out on. Valid only on an `array`. Never gates render: a document over
526
+ * the cap warns `validation::cardinality` and renders. */
527
+ max?: number;
521
528
  /** `true` on a `richtext` or `plaintext` field declared `inline`: the
522
529
  * single-paragraph, container-free, island-free constraint. */
523
530
  inline?: boolean;
@@ -1108,6 +1115,14 @@ export class Quill {
1108
1115
  * mutation.
1109
1116
  */
1110
1117
  conform(doc: Document): Diagnostic[];
1118
+ /**
1119
+ * The empty document: a main card carrying this quill's `$quill` and
1120
+ * `$kind: main`, no field committed, no composable card, no body. The
1121
+ * type-minimal valid input, and the document a plate is bound to render
1122
+ * (the quill authoring contract). `new Document(ref)` is the same document
1123
+ * reached by spelling the reference.
1124
+ */
1125
+ emptyDocument(): Document;
1111
1126
  /**
1112
1127
  * Build a quill from a file tree. Pure: the declared backend is resolved
1113
1128
  * later, at render time. Accepts a `Map<string, Uint8Array>` or a plain
@@ -1225,6 +1240,19 @@ export class Quillmark {
1225
1240
  */
1226
1241
  export function exportMarkdown(rt: Content): string;
1227
1242
 
1243
+ /**
1244
+ * Render a diagnostic as the CLI and Python's `str(diagnostic)` render it:
1245
+ * the severity tag and the message, the code parenthesised after them, then
1246
+ * location, path and hint each on an indented line of its own. The engine
1247
+ * owns the one printer, so a consumer surfacing diagnostics reads it here
1248
+ * rather than keeping a copy of the layout that drifts from the CLI's.
1249
+ *
1250
+ * Takes the `Diagnostic` shape every read hands back. Throws on a value that
1251
+ * is not one — a missing `severity` or `message`, or a `severity` outside the
1252
+ * two-value ladder.
1253
+ */
1254
+ export function formatDiagnostic(diagnostic: Diagnostic): string;
1255
+
1228
1256
  /**
1229
1257
  * Serialize structured [`DocPathSeg`] segments back to the canonical path
1230
1258
  * string: the inverse of `parseDocPath`. Throws on a segment array the
@@ -1347,6 +1375,7 @@ export interface InitOutput {
1347
1375
  readonly document_toStored: (a: number, b: number) => void;
1348
1376
  readonly document_warnings: (a: number, b: number) => void;
1349
1377
  readonly exportMarkdown: (a: number, b: number) => void;
1378
+ readonly formatDiagnostic: (a: number, b: number) => void;
1350
1379
  readonly formatDocPath: (a: number, b: number) => void;
1351
1380
  readonly importMarkdown: (a: number, b: number, c: number) => void;
1352
1381
  readonly livesession_backendId: (a: number, b: number) => void;
@@ -1368,6 +1397,7 @@ export interface InitOutput {
1368
1397
  readonly quill_backendId: (a: number, b: number) => void;
1369
1398
  readonly quill_blueprint: (a: number, b: number) => void;
1370
1399
  readonly quill_conform: (a: number, b: number, c: number) => void;
1400
+ readonly quill_emptyDocument: (a: number) => number;
1371
1401
  readonly quill_fromTree: (a: number, b: number) => void;
1372
1402
  readonly quill_metadata: (a: number, b: number) => void;
1373
1403
  readonly quill_parse: (a: number, b: number, c: number, d: number) => void;
package/render/wasm.js CHANGED
@@ -1620,6 +1620,18 @@ export class Quill {
1620
1620
  wasm.__wbindgen_add_to_stack_pointer(16);
1621
1621
  }
1622
1622
  }
1623
+ /**
1624
+ * The empty document: a main card carrying this quill's `$quill` and
1625
+ * `$kind: main`, no field committed, no composable card, no body. The
1626
+ * type-minimal valid input, and the document a plate is bound to render
1627
+ * (the quill authoring contract). `new Document(ref)` is the same document
1628
+ * reached by spelling the reference.
1629
+ * @returns {Document}
1630
+ */
1631
+ emptyDocument() {
1632
+ const ret = wasm.quill_emptyDocument(this.__wbg_ptr);
1633
+ return Document.__wrap(ret);
1634
+ }
1623
1635
  /**
1624
1636
  * Build a quill from a file tree. Pure: the declared backend is resolved
1625
1637
  * later, at render time. Accepts a `Map<string, Uint8Array>` or a plain
@@ -1965,6 +1977,44 @@ export function exportMarkdown(rt) {
1965
1977
  }
1966
1978
  }
1967
1979
 
1980
+ /**
1981
+ * Render a diagnostic as the CLI and Python's `str(diagnostic)` render it:
1982
+ * the severity tag and the message, the code parenthesised after them, then
1983
+ * location, path and hint each on an indented line of its own. The engine
1984
+ * owns the one printer, so a consumer surfacing diagnostics reads it here
1985
+ * rather than keeping a copy of the layout that drifts from the CLI's.
1986
+ *
1987
+ * Takes the `Diagnostic` shape every read hands back. Throws on a value that
1988
+ * is not one — a missing `severity` or `message`, or a `severity` outside the
1989
+ * two-value ladder.
1990
+ * @param {Diagnostic} diagnostic
1991
+ * @returns {string}
1992
+ */
1993
+ export function formatDiagnostic(diagnostic) {
1994
+ let deferred2_0;
1995
+ let deferred2_1;
1996
+ try {
1997
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1998
+ wasm.formatDiagnostic(retptr, addHeapObject(diagnostic));
1999
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2000
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2001
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
2002
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
2003
+ var ptr1 = r0;
2004
+ var len1 = r1;
2005
+ if (r3) {
2006
+ ptr1 = 0; len1 = 0;
2007
+ throw takeObject(r2);
2008
+ }
2009
+ deferred2_0 = ptr1;
2010
+ deferred2_1 = len1;
2011
+ return getStringFromWasm0(ptr1, len1);
2012
+ } finally {
2013
+ wasm.__wbindgen_add_to_stack_pointer(16);
2014
+ wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
2015
+ }
2016
+ }
2017
+
1968
2018
  /**
1969
2019
  * Serialize structured [`DocPathSeg`] segments back to the canonical path
1970
2020
  * string: the inverse of `parseDocPath`. Throws on a segment array the
Binary file
@@ -54,6 +54,7 @@ export const document_toMarkdown: (a: number, b: number) => void;
54
54
  export const document_toStored: (a: number, b: number) => void;
55
55
  export const document_warnings: (a: number, b: number) => void;
56
56
  export const exportMarkdown: (a: number, b: number) => void;
57
+ export const formatDiagnostic: (a: number, b: number) => void;
57
58
  export const formatDocPath: (a: number, b: number) => void;
58
59
  export const importMarkdown: (a: number, b: number, c: number) => void;
59
60
  export const livesession_backendId: (a: number, b: number) => void;
@@ -75,6 +76,7 @@ export const quill__resolve: (a: number, b: number, c: number) => void;
75
76
  export const quill_backendId: (a: number, b: number) => void;
76
77
  export const quill_blueprint: (a: number, b: number) => void;
77
78
  export const quill_conform: (a: number, b: number, c: number) => void;
79
+ export const quill_emptyDocument: (a: number) => number;
78
80
  export const quill_fromTree: (a: number, b: number) => void;
79
81
  export const quill_metadata: (a: number, b: number) => void;
80
82
  export const quill_parse: (a: number, b: number, c: number, d: number) => void;
@@ -22,7 +22,8 @@ import type {
22
22
  mapPos,
23
23
  mapMarks,
24
24
  parseDocPath,
25
- formatDocPath
25
+ formatDocPath,
26
+ formatDiagnostic
26
27
  } from '../core/wasm.js';
27
28
 
28
29
  /**
@@ -42,6 +43,7 @@ export interface CoreSurface {
42
43
  mapMarks: typeof mapMarks;
43
44
  parseDocPath: typeof parseDocPath;
44
45
  formatDocPath: typeof formatDocPath;
46
+ formatDiagnostic: typeof formatDiagnostic;
45
47
  }
46
48
 
47
49
  /**
@@ -179,9 +181,9 @@ export declare function isQuillmarkError(e: unknown): e is QuillmarkError;
179
181
 
180
182
  import type { ContentContainer } from '../core/wasm.js';
181
183
 
182
- // `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`,
183
- // `ContentContainer.container` and an island's `loss` are closed sets, so a
184
- // bare discriminant check narrows the payload on its own:
184
+ // `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind` and
185
+ // `ContentContainer.container` are closed sets, so a bare discriminant check
186
+ // narrows the payload on its own:
185
187
  // `line.kind === 'heading'` reaches `line.attrs.level`, with no guard to call.
186
188
 
187
189
  /**
@@ -20,7 +20,7 @@ import initCore, { Quill, Document } from '../core/wasm.js';
20
20
  // Resolution-time, so `node:fs` never enters a browser graph.
21
21
  import { toModuleSource } from '#quillmark-env';
22
22
  import { importMarkdown, exportMarkdown, rebase, mapPos, mapMarks } from '../core/wasm.js';
23
- import { parseDocPath, formatDocPath } from '../core/wasm.js';
23
+ import { parseDocPath, formatDocPath, formatDiagnostic } from '../core/wasm.js';
24
24
 
25
25
  // A `--target web` build exports its classes synchronously but carries no wasm
26
26
  // instance until something instantiates it. This module owns that for core,
@@ -50,7 +50,8 @@ const CORE_SURFACE = Object.freeze({
50
50
  mapPos,
51
51
  mapMarks,
52
52
  parseDocPath,
53
- formatDocPath
53
+ formatDocPath,
54
+ formatDiagnostic
54
55
  });
55
56
 
56
57
  /** The in-flight or settled core instantiation, resolving to `CORE_SURFACE`.
@@ -173,7 +174,7 @@ const HANDLE_KINDS = {
173
174
  },
174
175
  Document: {
175
176
  code: 'runtime::not_a_document',
176
- hint: 'Pass a Document built by Document.fromMarkdown / fromStored or quill.seedDocument. A Document from another copy of @quillmark/wasm is refused too — each copy is its own WASM linear memory and its own class — so run `npm ls @quillmark/wasm` and dedupe to one.'
177
+ hint: 'Pass a Document built by Document.fromMarkdown / fromStored or quill.seedDocument / emptyDocument. A Document from another copy of @quillmark/wasm is refused too — each copy is its own WASM linear memory and its own class — so run `npm ls @quillmark/wasm` and dedupe to one.'
177
178
  }
178
179
  };
179
180
 
@@ -218,9 +219,15 @@ function requireLocalQuill(quill, method) {
218
219
  // WELD_KEYS is the rule `Container::same_weld` owns upstream: which `attrs`
219
220
  // entries two adjacent runs must share for the markdown projection to read them
220
221
  // as one. `start` is not among them, since CommonMark reads only a list's first
221
- // number — a subset, which is why every container needs its own entry. A table
222
- // rather than a switch, so `tests/known_names_drift.rs` can pin it against the
223
- // Rust predicate.
222
+ // number — a subset, which is why every container needs its own entry.
223
+ //
224
+ // Re-spelled here rather than called, because `assignInstances` answers before
225
+ // wasm initializes (`prose/canon/BINDINGS.md` § "WebAssembly"). What holds the
226
+ // two spellings equal is running the rule, not comparing their sources:
227
+ // `runtime.test.js` § "container run boundaries" stamps a pair and re-imports
228
+ // it, where `Content::normalize` re-mints against the Rust predicate. A tag the
229
+ // table omits welds with nothing, so `tests/known_names_drift.rs` pins that
230
+ // every container has an entry.
224
231
 
225
232
  const WELD_KEYS = { list_item: ['ordered'], quote: [] };
226
233