@quillmark/wasm 0.92.1 → 0.94.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 +536 -0
- package/README.md +147 -62
- package/backends/pdfform/wasm.d.ts +1099 -0
- package/backends/pdfform/wasm.js +9 -0
- package/backends/pdfform/wasm_bg.js +2670 -0
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/pdfform/wasm_bg.wasm.d.ts +101 -0
- package/backends/typst/wasm.d.ts +544 -94
- package/backends/typst/wasm.js +1 -1
- package/backends/typst/wasm_bg.js +893 -227
- package/backends/typst/wasm_bg.wasm +0 -0
- package/backends/typst/wasm_bg.wasm.d.ts +34 -11
- package/core/wasm.d.ts +372 -37
- package/core/wasm.js +1 -1
- package/core/wasm_bg.js +562 -59
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +20 -3
- package/package.json +4 -3
- package/runtime/runtime.d.ts +327 -21
- package/runtime/runtime.js +355 -30
package/README.md
CHANGED
|
@@ -14,13 +14,13 @@ The package exposes **one import surface**:
|
|
|
14
14
|
an `Engine` that renders them.
|
|
15
15
|
|
|
16
16
|
`Quill` and `Document` are re-exported verbatim from the internal Typst-less
|
|
17
|
-
core build, so
|
|
17
|
+
core build, so editor/validation code (`Quill.fromTree`,
|
|
18
18
|
`Document.fromMarkdown`) loads only that small core binary — no backend is
|
|
19
|
-
loaded until you render. The `Engine` hides everything else: each backend
|
|
20
|
-
|
|
21
|
-
loaded on the first render. The Engine clones a `Quill` /
|
|
22
|
-
backend's memory as data and frees the clones — you never
|
|
23
|
-
or cross a memory boundary yourself.
|
|
19
|
+
loaded until you render. The `Engine` hides everything else: each backend
|
|
20
|
+
(`typst`, `pdfform`) is a separate, private WASM binary with its own linear
|
|
21
|
+
memory, lazily loaded on the first render. The Engine clones a `Quill` /
|
|
22
|
+
`Document` into the backend's memory as data and frees the clones — you never
|
|
23
|
+
hold a backend object or cross a memory boundary yourself.
|
|
24
24
|
|
|
25
25
|
## Build
|
|
26
26
|
|
|
@@ -28,9 +28,10 @@ or cross a memory boundary yourself.
|
|
|
28
28
|
bash scripts/build-wasm.sh
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The script builds
|
|
32
|
-
(
|
|
33
|
-
(see
|
|
31
|
+
The script builds three variants — the core (no backend), the Typst backend
|
|
32
|
+
(default features), and the Typst-free pdfform backend (`pdfform` feature) —
|
|
33
|
+
each with `--target bundler` and `--weak-refs` enabled (see
|
|
34
|
+
[Lifecycle](#lifecycle)).
|
|
34
35
|
|
|
35
36
|
## Test
|
|
36
37
|
|
|
@@ -46,7 +47,7 @@ npm test
|
|
|
46
47
|
```ts
|
|
47
48
|
import { Document, Quill, Engine } from "@quillmark/wasm";
|
|
48
49
|
|
|
49
|
-
const quill = Quill.fromTree(tree); // engine
|
|
50
|
+
const quill = Quill.fromTree(tree); // no engine needed: build + validate
|
|
50
51
|
const engine = new Engine(); // loads a backend lazily on first render
|
|
51
52
|
|
|
52
53
|
const markdown = `~~~
|
|
@@ -84,6 +85,14 @@ never cloning the quill. Use them as non-failing pre-render probes.
|
|
|
84
85
|
Build + validate a `Quill` from an in-memory tree. Pure — the declared backend
|
|
85
86
|
is resolved at render time, not here. Loads no backend binary.
|
|
86
87
|
|
|
88
|
+
### `new Document(quillRef)`
|
|
89
|
+
A blank document: a main card carrying only `$quill`, an empty body, and no
|
|
90
|
+
composable cards — the programmatic blank canvas. Absent fields resolve at
|
|
91
|
+
render time (schema `default`, else type-empty zero), so nothing the caller
|
|
92
|
+
did not set reaches the output. Build it up with `setFields` / `pushCard`.
|
|
93
|
+
For an example-filled starter use `quill.seedDocument()`. Throws on an
|
|
94
|
+
invalid quill reference.
|
|
95
|
+
|
|
87
96
|
### `Document.fromMarkdown(markdown)`
|
|
88
97
|
Parse markdown to a parsed document. Throws a JS `Error` (with `.diagnostics`
|
|
89
98
|
attached, see [Errors](#errors)) on any parse failure, including a missing
|
|
@@ -145,9 +154,9 @@ genuinely malformed Markdown.
|
|
|
145
154
|
|
|
146
155
|
### Storage compatibility across versions
|
|
147
156
|
|
|
148
|
-
The `schema` value (`quillmark/document@0.
|
|
157
|
+
The `schema` value (`quillmark/document@0.93.0`) is the **model version**,
|
|
149
158
|
not the running crate version. It is a hand-set constant, bumped only when
|
|
150
|
-
the `Document` model itself changes — so every `0.
|
|
159
|
+
the `Document` model itself changes — so every `0.93.x` patch release reads
|
|
151
160
|
and writes that same value.
|
|
152
161
|
|
|
153
162
|
- **Upgrading is safe.** A newer build always reads documents written by an
|
|
@@ -188,15 +197,15 @@ and compare instead of re-parsing on every keystroke.
|
|
|
188
197
|
### `doc.cardCount`
|
|
189
198
|
O(1) getter for the number of composable cards (excluding the main card).
|
|
190
199
|
Use this to validate indices before calling card mutators (`removeCard`,
|
|
191
|
-
`
|
|
200
|
+
`setCardField`, etc.) without allocating the full `cards` array.
|
|
192
201
|
|
|
193
202
|
### `quill.validate(doc)`
|
|
194
203
|
|
|
195
204
|
Returns `Diagnostic[]` — the document validated against the quill schema,
|
|
196
205
|
without invoking the backend. An empty array means the document is valid.
|
|
197
206
|
Each diagnostic carries the canonical `validation::*` `code`, `path`, and
|
|
198
|
-
`hint`. Includes the non-fatal `validation::
|
|
199
|
-
|
|
207
|
+
`hint`. Includes the non-fatal `validation::must_fill` warning for each
|
|
208
|
+
`!must_fill` marker left in the document (render zero-fills these rather
|
|
200
209
|
than failing), so filter by `severity`/`code` for blockers vs. hints:
|
|
201
210
|
|
|
202
211
|
```ts
|
|
@@ -204,9 +213,9 @@ const diagnostics = quill.validate(Document.fromMarkdown(markdown));
|
|
|
204
213
|
const errors = diagnostics.filter(d => d.severity === "error");
|
|
205
214
|
```
|
|
206
215
|
|
|
207
|
-
To render a form editor, read field definitions from `quill.schema` (
|
|
208
|
-
fields
|
|
209
|
-
`Document` payload — there is no separate form-view projection.
|
|
216
|
+
To render a form editor, read field definitions from `quill.schema` (walk
|
|
217
|
+
`fields` in key order — declaration order is display order) and the authored
|
|
218
|
+
values from the `Document` payload — there is no separate form-view projection.
|
|
210
219
|
|
|
211
220
|
### `quill.seedDocument()`
|
|
212
221
|
|
|
@@ -223,51 +232,112 @@ const markdown = doc.toMarkdown();
|
|
|
223
232
|
|
|
224
233
|
For per-card seeding, `quill.seedMain()` returns just the `$kind: main` card
|
|
225
234
|
and `quill.seedCard(kind)` returns a starter composable card (or `undefined`
|
|
226
|
-
if the kind is not declared). Both return the
|
|
235
|
+
if the kind is not declared). Both return the read `Card` shape of
|
|
227
236
|
`doc.main` / `doc.cards`, which `doc.pushCard` / `doc.insertCard` accept
|
|
228
237
|
directly:
|
|
229
238
|
|
|
230
239
|
```ts
|
|
231
240
|
doc.pushCard(quill.seedCard("note")); // seed → push
|
|
232
241
|
doc.pushCard(Document.makeCard("note", { x: 1 })); // build from a flat map
|
|
242
|
+
doc.pushCard({ kind: "note", body: "Plain **markdown**." }); // bare inline
|
|
233
243
|
```
|
|
234
244
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
245
|
+
Reads and writes are two aligned shapes. A read `Card` always has `body:
|
|
246
|
+
RichText` (canonical corpus, never a raw string) — no narrowing, no guessing
|
|
247
|
+
whether the body was normalized. The write shape `CardInput` widens `body` to
|
|
248
|
+
`RichText | string` (a markdown string imports to the corpus) and makes every
|
|
249
|
+
field but `kind` optional. Every `Card` is a valid `CardInput`, so `pushCard` /
|
|
250
|
+
`insertCard` still take exactly what `cards` / `removeCard` / `seedCard` return.
|
|
251
|
+
Build a fresh card from a flat field map with
|
|
252
|
+
`Document.makeCard(kind, fields?, body?)`.
|
|
253
|
+
|
|
254
|
+
Batch mutation: `doc.setFields({...})` / `doc.setCardFields(index, {...})`
|
|
255
|
+
apply a whole object atomically — on any invalid field nothing is applied and
|
|
256
|
+
the thrown error carries one diagnostic per offending field (`path` = field
|
|
257
|
+
name).
|
|
258
|
+
|
|
259
|
+
### Typed writes: `commit*` is the default, `set*` is the quill-free primitive
|
|
260
|
+
|
|
261
|
+
A `Document` holds only a `$quill` *reference*, not the resolved schema, so it
|
|
262
|
+
mutates through two layers:
|
|
263
|
+
|
|
264
|
+
- **`commit*` — the schema-bound default whenever a quill is in hand.**
|
|
265
|
+
`doc.commitField(quill, name, value)` / `doc.commitFields(quill, {...})` (and
|
|
266
|
+
the `commitCard*` twins) resolve each field's schema `type`, coerce the value
|
|
267
|
+
to its canonical form (`"3"` → `3`, a markdown string → a richtext corpus),
|
|
268
|
+
and **fail now** on a mismatch instead of at render. A name the schema does
|
|
269
|
+
not declare throws `UnknownField` rather than falling to the opaque store — on
|
|
270
|
+
the typed path an undeclared name is a typo, not a fallback. The batch form is
|
|
271
|
+
all-or-nothing: an undeclared name aborts the whole write and its per-field
|
|
272
|
+
diagnostics name every offending field, so a whole-form submit surfaces every
|
|
273
|
+
typo `setFields` would silently absorb.
|
|
274
|
+
|
|
275
|
+
- **`set*` — the deliberate quill-free primitive.** `doc.setField(name, value)`
|
|
276
|
+
/ `doc.setFields({...})` (and the `setCard*` twins) validate only the field
|
|
277
|
+
name/depth/kind and store the value verbatim, no quill required. Reach for it
|
|
278
|
+
on purpose when you *want* the opaque store: quill-agnostic storage/migration
|
|
279
|
+
infra that has no bundle and must write regardless of a drifted schema;
|
|
280
|
+
store-now-validate-later editors holding in-progress input that `commit`
|
|
281
|
+
would reject; or verbatim passthrough of fields the schema doesn't own. It is
|
|
282
|
+
the lower layer, not a lighter `commit` — a typo'd field name stores silently
|
|
283
|
+
and only surfaces at `quill.validate` / render.
|
|
284
|
+
|
|
285
|
+
Per-keystroke cost is the same either way (both mutate the in-memory `Document`
|
|
286
|
+
in place; no seam is crossed), so steering to `commit*` buys the type check for
|
|
287
|
+
free.
|
|
288
|
+
|
|
289
|
+
#### `DocumentWriter` / `CardWriter` — bind the quill once
|
|
290
|
+
|
|
291
|
+
The `commit*` verbs take the `quill` handle per call (the document carries no
|
|
292
|
+
schema). When you hold both a quill and a document — a form editor, an MCP
|
|
293
|
+
writer — bind them once with the writer sugar and issue bare verbs:
|
|
238
294
|
|
|
239
|
-
|
|
295
|
+
```ts
|
|
296
|
+
import { DocumentWriter } from "@quillmark/wasm";
|
|
297
|
+
|
|
298
|
+
const ed = new DocumentWriter(quill, doc); // JS twin of Rust `quill.writer(doc)`
|
|
299
|
+
ed.set("subject", "Q3 results"); // strict-committed to the schema type
|
|
300
|
+
ed.setAll({ qty: "3", subject: "Q3" }); // all-or-nothing batch
|
|
301
|
+
ed.set("titel", "x"); // throws UnknownField — a typo, not a fallback
|
|
302
|
+
ed.card(2).set("body", "**note**"); // composable card, resolved by its $kind
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`DocumentWriter` / `CardWriter` are pure JS holding references to your existing
|
|
306
|
+
`quill` and `doc` — no WASM handle of their own, nothing to `free()`. `card(i)`
|
|
307
|
+
is lazy: it never throws; an out-of-range index throws `IndexOutOfRange` at the
|
|
308
|
+
write.
|
|
240
309
|
|
|
241
|
-
|
|
242
|
-
> `RenderSession`, `paint`, `PaintOptions`, `PaintResult`, `PageSize`, and the
|
|
243
|
-
> `supportsCanvas` probe — ships ahead of its first production consumer and
|
|
244
|
-
> may change shape in any 0.x release. `engine.render` is the stable path.
|
|
310
|
+
### `engine.render(quill, parsed, opts?)` vs. `engine.open(quill, parsed)`
|
|
245
311
|
|
|
246
312
|
Use **`engine.render`** for one-shot exports (PDF/SVG/PNG) — compiles, emits
|
|
247
|
-
artifacts, done. Use **`
|
|
248
|
-
reactive previews
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
313
|
+
artifacts, done. Use **`LiveSession`** (returned by `engine.open`) for
|
|
314
|
+
reactive previews: the session is a persistent compiler. `paint` / `render` /
|
|
315
|
+
`regions` / `fieldAt` read its current compile without recompiling, and `apply(doc)`
|
|
316
|
+
recompiles in place on each edit, returning a `ChangeSet` whose `dirtyPages`
|
|
317
|
+
tells you which pages to repaint (`dirty ∩ visible`). Apply is transactional —
|
|
318
|
+
on throw, every read keeps serving the last-good compile. Don't open a session
|
|
319
|
+
per export, and don't re-open per edit — `apply` instead.
|
|
252
320
|
|
|
253
321
|
### `engine.render(quill, parsed, opts?)`
|
|
254
|
-
Render a pre-parsed `Document` against `quill`. Throws
|
|
255
|
-
no registered backend matches the quill's
|
|
322
|
+
Render a pre-parsed `Document` against `quill`. Throws an
|
|
323
|
+
`engine::backend_not_found` error if no registered backend matches the quill's
|
|
324
|
+
declared backend.
|
|
256
325
|
|
|
257
326
|
### `engine.open(quill, parsed)` + `session.render(opts?)`
|
|
258
327
|
Open once, render all or selected pages (`opts.pages`).
|
|
259
328
|
|
|
260
329
|
The session also exposes `pageCount`, `backendId`, `supportsCanvas`,
|
|
261
|
-
`warnings` (
|
|
262
|
-
|
|
263
|
-
|
|
330
|
+
`warnings` (non-fatal diagnostics of the current compile — set at `open`,
|
|
331
|
+
refreshed by each committed `apply`),
|
|
332
|
+
`apply(doc)` for in-place recompiles, `pageSize(page)`, and
|
|
333
|
+
`paint(ctx, page, opts?)` for canvas previews. See below.
|
|
264
334
|
|
|
265
335
|
A document that compiles to zero pages still produces a valid session
|
|
266
336
|
(`pageCount === 0`); `paint(ctx, 0)` and `pageSize(0)` then throw
|
|
267
337
|
`page index 0 out of range (pageCount=0)`. Branch on `pageCount === 0` to
|
|
268
338
|
render a "no pages to preview" UI without relying on the throw.
|
|
269
339
|
|
|
270
|
-
### Canvas Preview
|
|
340
|
+
### Canvas Preview
|
|
271
341
|
|
|
272
342
|
`session.paint(ctx, page, opts?)` rasterizes a page directly into a
|
|
273
343
|
`CanvasRenderingContext2D` (main thread) or
|
|
@@ -281,7 +351,7 @@ sets them) and read `layoutWidth` / `layoutHeight` from the returned
|
|
|
281
351
|
|
|
282
352
|
```ts
|
|
283
353
|
const result = session.paint(canvas.getContext("2d"), 0, {
|
|
284
|
-
layoutScale: 1, // layout px per
|
|
354
|
+
layoutScale: 1, // layout px per pt (page geometry unit)
|
|
285
355
|
densityScale: window.devicePixelRatio, // backing-store density
|
|
286
356
|
});
|
|
287
357
|
|
|
@@ -291,17 +361,26 @@ canvas.style.height = `${result.layoutHeight}px`;
|
|
|
291
361
|
|
|
292
362
|
- `layoutScale` (default 1) sets the canvas's display-box size:
|
|
293
363
|
`layoutWidth = widthPt * layoutScale`. For on-screen canvases this is
|
|
294
|
-
CSS pixels per
|
|
364
|
+
CSS pixels per point. Defaults to 1 (one CSS pixel per pt).
|
|
295
365
|
- `densityScale` (default 1) is the backing-store density multiplier.
|
|
296
366
|
Fold `window.devicePixelRatio`, in-app zoom, and `visualViewport.scale`
|
|
297
367
|
(pinch-zoom) into a single value here. Pass `devicePixelRatio` for
|
|
298
368
|
crisp output on high-DPI displays.
|
|
299
369
|
- The effective rasterization scale is `layoutScale * densityScale`. If
|
|
300
370
|
that would exceed the safe maximum (16384 px per side), `densityScale`
|
|
301
|
-
is clamped proportionally;
|
|
302
|
-
`
|
|
371
|
+
is clamped proportionally; `result.clamped` reports it and
|
|
372
|
+
`result.effectiveDensityScale` is the density actually applied. A
|
|
373
|
+
clamped page renders soft at the same `canvas.style` size.
|
|
374
|
+
- `paint` writes the whole backing store with `putImageData`, which
|
|
375
|
+
ignores the 2D context transform, `globalAlpha`, and clip. Give each
|
|
376
|
+
visible page its own `` element — you cannot composite two pages,
|
|
377
|
+
a sub-rect, or a context transform through `paint`.
|
|
303
378
|
- `paint` is always a full repaint — setting the backing-store width /
|
|
304
|
-
height clears it. No `clearRect` required.
|
|
379
|
+
height clears it. No `clearRect` required. Each call re-rasterizes from
|
|
380
|
+
scratch (no per-page raster cache), so keep a page's canvas alive while
|
|
381
|
+
it stays near the viewport rather than pooling one canvas across pages:
|
|
382
|
+
an idle canvas retains its pixels for free, whereas reusing a canvas on
|
|
383
|
+
scroll re-runs a full render.
|
|
305
384
|
- `pageCount` and `pageSize(page)` are stable for the session's
|
|
306
385
|
lifetime (immutable snapshot) — cache them.
|
|
307
386
|
- Worker support: pass an `OffscreenCanvasRenderingContext2D` and the
|
|
@@ -319,19 +398,19 @@ canvas.style.height = `${result.layoutHeight}px`;
|
|
|
319
398
|
|
|
320
399
|
A field's *cell* is inferred from whether its schema declares a `default:`:
|
|
321
400
|
|
|
322
|
-
- **Unendorsed** (no `default:`) — `quill.blueprint` renders
|
|
323
|
-
in the value cell
|
|
324
|
-
|
|
325
|
-
silently. A
|
|
326
|
-
|
|
327
|
-
|
|
401
|
+
- **Unendorsed** (no `default:`) — `quill.blueprint` renders the
|
|
402
|
+
`!must_fill` marker in the value cell (carrying the field's `example` as a
|
|
403
|
+
suggested value when one exists). An absent Unendorsed field zero-fills
|
|
404
|
+
silently. A `!must_fill` marker left in the document is non-fatal: it emits
|
|
405
|
+
the `validation::must_fill` warning and still renders. Partial documents
|
|
406
|
+
are accepted; `engine.render(quill, doc)` only throws for malformed
|
|
407
|
+
input.
|
|
328
408
|
- **Endorsed** (with `default:`) — `quill.blueprint` renders the
|
|
329
|
-
default value
|
|
330
|
-
is used when the document omits the field.
|
|
409
|
+
default value with a type-only `# <type>` annotation (shippable as-is),
|
|
410
|
+
and the default is used when the document omits the field.
|
|
331
411
|
|
|
332
|
-
`QuillFieldSchema` has no `required` axis.
|
|
333
|
-
|
|
334
|
-
`validation::must_fill_sentinel`.
|
|
412
|
+
`QuillFieldSchema` has no `required` axis. A `!must_fill` marker left in the
|
|
413
|
+
document emits the non-fatal `validation::must_fill` warning.
|
|
335
414
|
|
|
336
415
|
### Errors
|
|
337
416
|
|
|
@@ -367,20 +446,26 @@ compilation failures. The same shape applies to every throw site:
|
|
|
367
446
|
|
|
368
447
|
- `Document.fromMarkdown` — parse errors (missing root `$quill` metadata, YAML
|
|
369
448
|
errors, `parse::input_too_large` for inputs > 10 MB).
|
|
370
|
-
- `Document` mutators (`setField`, `
|
|
449
|
+
- `Document` mutators (`setField`, `setCardField`, etc.) — `EditError`
|
|
371
450
|
variants (`InvalidFieldName`, `InvalidKindName`, `ReservedKind`,
|
|
372
|
-
`IndexOutOfRange`) appear in `diagnostics[0].message` with
|
|
373
|
-
`[EditError::<Variant>]` prefix.
|
|
451
|
+
`IndexOutOfRange`, `ValueTooDeep`) appear in `diagnostics[0].message` with
|
|
452
|
+
the `[EditError::<Variant>]` prefix.
|
|
374
453
|
- `engine.render` / `session.render` — backend compilation failures and
|
|
375
454
|
validation errors.
|
|
376
455
|
|
|
377
456
|
### Lifecycle
|
|
378
457
|
|
|
379
458
|
The wasm bindings are built with `--weak-refs`, so dropped `Document`,
|
|
380
|
-
`Quill`, and `
|
|
459
|
+
`Quill`, and `LiveSession` handles are reclaimed by `FinalizationRegistry`
|
|
381
460
|
without manual `.free()` discipline. `.free()` is still emitted as an eager
|
|
382
461
|
teardown hook for callers that want deterministic release.
|
|
383
462
|
|
|
463
|
+
`engine.render` and `engine.open` read the `quill` and `doc` handles
|
|
464
|
+
synchronously, before their first await, so freeing a handle as soon as the
|
|
465
|
+
call returns — `try { return engine.render(quill, doc); } finally
|
|
466
|
+
{ doc.free(); }` — is safe even on the first render, while the backend
|
|
467
|
+
binary is still loading.
|
|
468
|
+
|
|
384
469
|
The package floor is Node 22+ (`engines: { node: ">=22" }`) and current
|
|
385
470
|
evergreen browsers; `--weak-refs` itself only needs Node 14.6+. The `using`
|
|
386
471
|
sugar shown below ([explicit resource management][erm]) needs Node 24, but is
|
|
@@ -390,7 +475,7 @@ For environments where `using` (the [explicit resource management][erm]
|
|
|
390
475
|
proposal) hasn't landed, use an explicit `try` / `finally`:
|
|
391
476
|
|
|
392
477
|
```ts
|
|
393
|
-
const session = engine.open(quill, doc);
|
|
478
|
+
const session = await engine.open(quill, doc);
|
|
394
479
|
try {
|
|
395
480
|
for (let p = 0; p < session.pageCount; p++) {
|
|
396
481
|
session.paint(ctx, p);
|
|
@@ -405,7 +490,7 @@ try {
|
|
|
405
490
|
## Notes
|
|
406
491
|
|
|
407
492
|
- Parsed markdown requires a root `~~~` block (a bare three-tilde fence;
|
|
408
|
-
|
|
493
|
+
`~~~card-yaml` is also accepted as a non-canonical alias)
|
|
409
494
|
with a `$quill` system-metadata line. Empty input surfaces a dedicated
|
|
410
495
|
"Empty markdown input cannot be parsed" message.
|
|
411
496
|
- A `$quill` mismatch during `engine.render(quill, parsed)` is a thrown error, not a warning: rendering with a quill whose *name* differs (`quill::name_mismatch`) or whose *version* falls outside the selector (`quill::version_mismatch`) is rejected.
|
|
@@ -413,8 +498,8 @@ try {
|
|
|
413
498
|
|
|
414
499
|
## Changelog
|
|
415
500
|
|
|
416
|
-
See the [changelog](https://github.com/
|
|
417
|
-
and the [GitHub Releases](https://github.com/
|
|
501
|
+
See the [changelog](https://github.com/borb-sh/quillmark/blob/main/CHANGELOG.md)
|
|
502
|
+
and the [GitHub Releases](https://github.com/borb-sh/quillmark/releases) page for
|
|
418
503
|
release notes and version history.
|
|
419
504
|
|
|
420
505
|
## License
|