@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 +297 -0
- package/README.md +6 -4
- package/core/wasm.d.ts +60 -30
- package/core/wasm.js +50 -0
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +2 -0
- package/package.json +1 -1
- package/render/wasm.d.ts +60 -30
- package/render/wasm.js +50 -0
- package/render/wasm_bg.wasm +0 -0
- package/render/wasm_bg.wasm.d.ts +2 -0
- package/runtime/runtime.d.ts +6 -4
- package/runtime/runtime.js +13 -6
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 `|  |` 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.
|
|
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.
|
|
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
|
|
191
|
-
|
|
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:
|
|
77
|
-
* spells its payload in `attrs`, so
|
|
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,
|
|
241
|
-
* `setKind` lands a kind the line's text contradicts — `
|
|
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
|
|
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
|
|
268
|
-
*
|
|
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
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
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
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* `{value: <member>, …that member's
|
|
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
|
package/core/wasm_bg.wasm.d.ts
CHANGED
|
@@ -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
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:
|
|
77
|
-
* spells its payload in `attrs`, so
|
|
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,
|
|
241
|
-
* `setKind` lands a kind the line's text contradicts — `
|
|
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
|
|
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
|
|
268
|
-
*
|
|
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
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
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
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
* `{value: <member>, …that member's
|
|
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
|
package/render/wasm_bg.wasm
CHANGED
|
Binary file
|
package/render/wasm_bg.wasm.d.ts
CHANGED
|
@@ -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;
|
package/runtime/runtime.d.ts
CHANGED
|
@@ -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`
|
|
184
|
-
//
|
|
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
|
/**
|
package/runtime/runtime.js
CHANGED
|
@@ -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.
|
|
222
|
-
//
|
|
223
|
-
//
|
|
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
|
|