@quillmark/wasm 0.97.0 → 0.98.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 +81 -0
- package/backends/pdfform/wasm.d.ts +28 -13
- package/backends/pdfform/wasm_bg.js +10 -2
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/typst/wasm.d.ts +28 -13
- package/backends/typst/wasm_bg.js +10 -2
- package/backends/typst/wasm_bg.wasm +0 -0
- package/core/wasm.d.ts +27 -12
- package/core/wasm_bg.js +10 -2
- package/core/wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/runtime/runtime.d.ts +39 -10
- package/runtime/runtime.js +35 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.98.0 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
Five breaking changes, all covered by `docs/migrations/0.97-to-0.98.md`.
|
|
6
|
+
Stored documents are unaffected: a `0.97` blob loads byte-identically and
|
|
7
|
+
`0.98` writes the same bytes for the same content.
|
|
8
|
+
|
|
9
|
+
- feat(core,content,wasm)!: the block vocabulary opens. A line's `kind` and a
|
|
10
|
+
container's name join the mark `type` and island `type` as open sets — an
|
|
11
|
+
unrecognized value round-trips opaque and renders as its nearest safe
|
|
12
|
+
neighbor instead of failing the load, so adding a block construct is no
|
|
13
|
+
longer a document schema-version event. `LineKind` and `Container` each gain
|
|
14
|
+
an `Unknown { tag, attrs }` variant (exhaustive matches need an arm) and
|
|
15
|
+
`Line` / `LineKind` / `Container` drop their `Eq` derive, matching `MarkKind`;
|
|
16
|
+
`Invariant` gains `ReservedUnknownLineKind` / `ReservedUnknownContainer`, so
|
|
17
|
+
an unknown may not reuse a built-in name. `ContentLine.kind` and
|
|
18
|
+
`ContentContainer.container` gain an open TS arm, so a bare discriminant check
|
|
19
|
+
no longer narrows — `isHeadingLine` / `isCodeLine` / `isListItemContainer`
|
|
20
|
+
join the existing guards in `@quillmark/wasm/runtime`. `Loss` describes
|
|
21
|
+
fidelity; it does not gate export (#1054). See
|
|
22
|
+
`docs/migrations/0.97-to-0.98.md`
|
|
23
|
+
- refactor(core,wasm,python,cli)!: `OutputFormat::Txt` is removed — the Rust
|
|
24
|
+
variant, Python's `OutputFormat.TXT`, the TS `'txt'` arm. No backend listed it
|
|
25
|
+
in `SUPPORTED_FORMATS`, so every path reaching it failed at render time;
|
|
26
|
+
`--format txt` and `from_str("txt")` now fail at the argument instead, and
|
|
27
|
+
there is no replacement format (#1058). See
|
|
28
|
+
`docs/migrations/0.97-to-0.98.md`
|
|
29
|
+
- refactor(typst,pdfform)!: `format_not_supported` was three codes for one
|
|
30
|
+
condition. Both backends now emit `backend::format_not_supported`, alongside
|
|
31
|
+
the sibling `backend::apply_unsupported` — route on the namespaced code
|
|
32
|
+
(#1057). See `docs/migrations/0.97-to-0.98.md`
|
|
33
|
+
- refactor(core)!: dead public surface is removed — `ReadValue::as_text` /
|
|
34
|
+
`as_value` (match the variants), `Quill::list_files` / `list_subdirectories`
|
|
35
|
+
(`quill.files()` owns the walk), and `QuillValue`'s eight `Deref`-shadowed
|
|
36
|
+
accessors (`QuillValue: Deref<Target = serde_json::Value>` resolves each call
|
|
37
|
+
unchanged — no action). `prescan`'s helpers and `reader::err` drop to
|
|
38
|
+
`pub(crate)` (#1066, #1064). See `docs/migrations/0.97-to-0.98.md`
|
|
39
|
+
- fix(python)!: rendered regions cross the boundary as `DocPath`. Python
|
|
40
|
+
returned plate-space addresses no document API accepts; the plate→`DocPath`
|
|
41
|
+
translation now lives in core as `regions_to_doc_path` and both bindings call
|
|
42
|
+
it, so `RenderResult.regions[].field` reads `main.body` /
|
|
43
|
+
`cards.<kind>[<i>].<field>` as WASM already did (#1063). See
|
|
44
|
+
`docs/migrations/0.97-to-0.98.md`
|
|
45
|
+
- fix(content): three codec holes close. A mark spanning an island slot no
|
|
46
|
+
longer swallows it; a `LineKind` that disagrees with its segment is caught
|
|
47
|
+
(`Invariant::LineKindMismatch`, carrying a `LineKindMismatch` reason); and
|
|
48
|
+
decode is bounded — `MAX_NESTING_DEPTH` is enforced at the door, and a wire
|
|
49
|
+
position is read checked rather than `as usize`, which on wasm32 landed
|
|
50
|
+
`2^32 + 5` at position `5` (#1051)
|
|
51
|
+
- fix(content): export's verify-and-drop safety net is bounded per line rather
|
|
52
|
+
than per mark; `CONVERT.md` states the import↔export coupling the net implies
|
|
53
|
+
(#1052)
|
|
54
|
+
- fix(content): markdown import keeps a literal `*` that abuts strong or
|
|
55
|
+
emphasis — `a***a**` imports as `a`, `*`, strong `a`, where the deleted fixup
|
|
56
|
+
dropped the typed star. Removing it also fixes a backslash escape or entity in
|
|
57
|
+
such a span re-entering the content as literal source bytes (#1053)
|
|
58
|
+
- feat(core): `FileTreeNode`, `QuillIgnore`, `QuillConfig`, the schema types,
|
|
59
|
+
and `ValidationError` are nameable from `quillmark` and from `quillmark_core`'s
|
|
60
|
+
root, so in-memory quill construction and schema reading need no direct core
|
|
61
|
+
dependency (#1055)
|
|
62
|
+
- refactor(core,pdfform): `From<PdfError> for RenderError` replaces two
|
|
63
|
+
byte-identical `map_pdf_err` copies; `lopdf`, `js-sys` and `wasm-bindgen`
|
|
64
|
+
hoist into `[workspace.dependencies]`, the no-op `default-features = false`
|
|
65
|
+
pins drop, and `publish` is explicit on every crate (#1070, #1064)
|
|
66
|
+
- docs(core): the card-kind validation line is documented where it lives —
|
|
67
|
+
construction (`Document::make_card`, `TryFrom<CardWire>`) is permissive
|
|
68
|
+
data-shaping and insertion is the gate, returning `edit::invalid_kind_name`
|
|
69
|
+
- docs(canon): `ERROR.md` stops documenting a `fmt_pretty_with_source()` that
|
|
70
|
+
does not exist and states what carries the source chain: serialization to
|
|
71
|
+
both bindings, no Rust formatter. `error.rs`'s divergent path-grammar copy
|
|
72
|
+
gives way to a pointer at `path.rs` and the canon anchor table (#1061, #1069)
|
|
73
|
+
- docs(canon): prune — canon stops re-documenting what `docs/` owns and states
|
|
74
|
+
each fact once; the lint checks truth rather than shape, and the CI job table
|
|
75
|
+
lists every job
|
|
76
|
+
- test: test clusters go table-driven, wrong-altitude binding tests move down to
|
|
77
|
+
core, tests that pin foreign behavior or assert nothing are dropped, and one
|
|
78
|
+
walker and one quill builder replace the per-suite copies (#1060, #1056,
|
|
79
|
+
#1065, #1062, #1067)
|
|
80
|
+
- ci: the zero-backend configuration is built and tested — `--workspace
|
|
81
|
+
--all-features` forces `typst` on, so that branch never compiled (#1068)
|
|
82
|
+
|
|
83
|
+
|
|
3
84
|
## v0.97.0 - 2026-07-24
|
|
4
85
|
|
|
5
86
|
- core: one take_item primitive behind the three payload removers
|
|
@@ -84,23 +84,34 @@ export interface Content {
|
|
|
84
84
|
islands: ContentIsland[];
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** One `\n`-separated segment of `Content.text`, in order.
|
|
87
|
+
/** One `\n`-separated segment of `Content.text`, in order. `kind` is an open set
|
|
88
|
+
* (as on `ContentIsland` and `ContentMark`): a role this build does not know
|
|
89
|
+
* round-trips with opaque `attrs` and renders as a paragraph, so a document
|
|
90
|
+
* carrying a future block construct still opens. The open arm blocks discriminant
|
|
91
|
+
* narrowing, so read `level`/`lang` behind a check of the arm you want. */
|
|
88
92
|
export type ContentLine = {
|
|
89
93
|
containers: ContentContainer[];
|
|
90
94
|
/** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
|
|
91
95
|
continues?: boolean;
|
|
92
|
-
} &
|
|
96
|
+
} & ContentLineKind;
|
|
97
|
+
|
|
98
|
+
/** A line's block role, declared once for `ContentLine` and the `setKind` op —
|
|
99
|
+
* a new role is one edit here, as for `ContentContainer`. */
|
|
100
|
+
export type ContentLineKind =
|
|
93
101
|
| { kind: "para" }
|
|
94
102
|
| { kind: "heading"; level: number }
|
|
95
103
|
| { kind: "code"; lang?: string }
|
|
96
104
|
| { kind: "island" }
|
|
97
105
|
| { kind: "rule" }
|
|
98
|
-
|
|
106
|
+
| { kind: string; attrs: unknown };
|
|
99
107
|
|
|
100
|
-
/** An ancestor block a line nests inside, outermost first.
|
|
108
|
+
/** An ancestor block a line nests inside, outermost first. Open like
|
|
109
|
+
* `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
|
|
110
|
+
* and renders transparently (its lines sit at the enclosing level). */
|
|
101
111
|
export type ContentContainer =
|
|
102
112
|
| { container: "list_item"; ordered: boolean; start: number; ordinal: number }
|
|
103
|
-
| { container: "quote" }
|
|
113
|
+
| { container: "quote" }
|
|
114
|
+
| { container: string; attrs: unknown };
|
|
104
115
|
|
|
105
116
|
/** A mark over char range `[start, end)` into `Content.text`. The open `type`
|
|
106
117
|
* arm blocks discriminant narrowing (as on `ContentIsland`), so read a
|
|
@@ -223,11 +234,7 @@ export type MarkOp =
|
|
|
223
234
|
export type LineOp =
|
|
224
235
|
| { op: "split"; at: number }
|
|
225
236
|
| { op: "join"; line: number }
|
|
226
|
-
| ({ op: "setKind"; line: number } &
|
|
227
|
-
| { kind: "para" | "island" | "rule" }
|
|
228
|
-
| { kind: "heading"; level: number }
|
|
229
|
-
| { kind: "code"; lang?: string }
|
|
230
|
-
))
|
|
237
|
+
| ({ op: "setKind"; line: number } & ContentLineKind)
|
|
231
238
|
| { op: "setContainers"; line: number; containers: ContentContainer[] }
|
|
232
239
|
| { op: "setContinues"; line: number; continues: boolean };
|
|
233
240
|
|
|
@@ -553,7 +560,7 @@ export interface RenderResult {
|
|
|
553
560
|
|
|
554
561
|
export type HitGranularity = "cluster" | "segment";
|
|
555
562
|
|
|
556
|
-
export type OutputFormat = "pdf" | "svg" | "
|
|
563
|
+
export type OutputFormat = "pdf" | "svg" | "png";
|
|
557
564
|
|
|
558
565
|
export type Severity = "error" | "warning";
|
|
559
566
|
|
|
@@ -720,8 +727,16 @@ export class Document {
|
|
|
720
727
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
721
728
|
* constructor for `insertCard`. `fields` is an optional
|
|
722
729
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
723
|
-
* insertion order); `body` defaults to `""`.
|
|
724
|
-
*
|
|
730
|
+
* insertion order); `body` defaults to `""`.
|
|
731
|
+
*
|
|
732
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
733
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
734
|
+
* here.
|
|
735
|
+
*
|
|
736
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
737
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
738
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
739
|
+
* any kind string is accepted here.
|
|
725
740
|
*/
|
|
726
741
|
static makeCard(kind: string, fields?: Record<string, unknown>, body?: string): Card;
|
|
727
742
|
/**
|
|
@@ -687,8 +687,16 @@ export class Document {
|
|
|
687
687
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
688
688
|
* constructor for `insertCard`. `fields` is an optional
|
|
689
689
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
690
|
-
* insertion order); `body` defaults to `""`.
|
|
691
|
-
*
|
|
690
|
+
* insertion order); `body` defaults to `""`.
|
|
691
|
+
*
|
|
692
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
693
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
694
|
+
* here.
|
|
695
|
+
*
|
|
696
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
697
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
698
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
699
|
+
* any kind string is accepted here.
|
|
692
700
|
* @param {string} kind
|
|
693
701
|
* @param {Record<string, unknown>} [fields]
|
|
694
702
|
* @param {string} [body]
|
|
Binary file
|
package/backends/typst/wasm.d.ts
CHANGED
|
@@ -84,23 +84,34 @@ export interface Content {
|
|
|
84
84
|
islands: ContentIsland[];
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** One `\n`-separated segment of `Content.text`, in order.
|
|
87
|
+
/** One `\n`-separated segment of `Content.text`, in order. `kind` is an open set
|
|
88
|
+
* (as on `ContentIsland` and `ContentMark`): a role this build does not know
|
|
89
|
+
* round-trips with opaque `attrs` and renders as a paragraph, so a document
|
|
90
|
+
* carrying a future block construct still opens. The open arm blocks discriminant
|
|
91
|
+
* narrowing, so read `level`/`lang` behind a check of the arm you want. */
|
|
88
92
|
export type ContentLine = {
|
|
89
93
|
containers: ContentContainer[];
|
|
90
94
|
/** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
|
|
91
95
|
continues?: boolean;
|
|
92
|
-
} &
|
|
96
|
+
} & ContentLineKind;
|
|
97
|
+
|
|
98
|
+
/** A line's block role, declared once for `ContentLine` and the `setKind` op —
|
|
99
|
+
* a new role is one edit here, as for `ContentContainer`. */
|
|
100
|
+
export type ContentLineKind =
|
|
93
101
|
| { kind: "para" }
|
|
94
102
|
| { kind: "heading"; level: number }
|
|
95
103
|
| { kind: "code"; lang?: string }
|
|
96
104
|
| { kind: "island" }
|
|
97
105
|
| { kind: "rule" }
|
|
98
|
-
|
|
106
|
+
| { kind: string; attrs: unknown };
|
|
99
107
|
|
|
100
|
-
/** An ancestor block a line nests inside, outermost first.
|
|
108
|
+
/** An ancestor block a line nests inside, outermost first. Open like
|
|
109
|
+
* `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
|
|
110
|
+
* and renders transparently (its lines sit at the enclosing level). */
|
|
101
111
|
export type ContentContainer =
|
|
102
112
|
| { container: "list_item"; ordered: boolean; start: number; ordinal: number }
|
|
103
|
-
| { container: "quote" }
|
|
113
|
+
| { container: "quote" }
|
|
114
|
+
| { container: string; attrs: unknown };
|
|
104
115
|
|
|
105
116
|
/** A mark over char range `[start, end)` into `Content.text`. The open `type`
|
|
106
117
|
* arm blocks discriminant narrowing (as on `ContentIsland`), so read a
|
|
@@ -223,11 +234,7 @@ export type MarkOp =
|
|
|
223
234
|
export type LineOp =
|
|
224
235
|
| { op: "split"; at: number }
|
|
225
236
|
| { op: "join"; line: number }
|
|
226
|
-
| ({ op: "setKind"; line: number } &
|
|
227
|
-
| { kind: "para" | "island" | "rule" }
|
|
228
|
-
| { kind: "heading"; level: number }
|
|
229
|
-
| { kind: "code"; lang?: string }
|
|
230
|
-
))
|
|
237
|
+
| ({ op: "setKind"; line: number } & ContentLineKind)
|
|
231
238
|
| { op: "setContainers"; line: number; containers: ContentContainer[] }
|
|
232
239
|
| { op: "setContinues"; line: number; continues: boolean };
|
|
233
240
|
|
|
@@ -553,7 +560,7 @@ export interface RenderResult {
|
|
|
553
560
|
|
|
554
561
|
export type HitGranularity = "cluster" | "segment";
|
|
555
562
|
|
|
556
|
-
export type OutputFormat = "pdf" | "svg" | "
|
|
563
|
+
export type OutputFormat = "pdf" | "svg" | "png";
|
|
557
564
|
|
|
558
565
|
export type Severity = "error" | "warning";
|
|
559
566
|
|
|
@@ -720,8 +727,16 @@ export class Document {
|
|
|
720
727
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
721
728
|
* constructor for `insertCard`. `fields` is an optional
|
|
722
729
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
723
|
-
* insertion order); `body` defaults to `""`.
|
|
724
|
-
*
|
|
730
|
+
* insertion order); `body` defaults to `""`.
|
|
731
|
+
*
|
|
732
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
733
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
734
|
+
* here.
|
|
735
|
+
*
|
|
736
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
737
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
738
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
739
|
+
* any kind string is accepted here.
|
|
725
740
|
*/
|
|
726
741
|
static makeCard(kind: string, fields?: Record<string, unknown>, body?: string): Card;
|
|
727
742
|
/**
|
|
@@ -687,8 +687,16 @@ export class Document {
|
|
|
687
687
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
688
688
|
* constructor for `insertCard`. `fields` is an optional
|
|
689
689
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
690
|
-
* insertion order); `body` defaults to `""`.
|
|
691
|
-
*
|
|
690
|
+
* insertion order); `body` defaults to `""`.
|
|
691
|
+
*
|
|
692
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
693
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
694
|
+
* here.
|
|
695
|
+
*
|
|
696
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
697
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
698
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
699
|
+
* any kind string is accepted here.
|
|
692
700
|
* @param {string} kind
|
|
693
701
|
* @param {Record<string, unknown>} [fields]
|
|
694
702
|
* @param {string} [body]
|
|
Binary file
|
package/core/wasm.d.ts
CHANGED
|
@@ -84,23 +84,34 @@ export interface Content {
|
|
|
84
84
|
islands: ContentIsland[];
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** One `\n`-separated segment of `Content.text`, in order.
|
|
87
|
+
/** One `\n`-separated segment of `Content.text`, in order. `kind` is an open set
|
|
88
|
+
* (as on `ContentIsland` and `ContentMark`): a role this build does not know
|
|
89
|
+
* round-trips with opaque `attrs` and renders as a paragraph, so a document
|
|
90
|
+
* carrying a future block construct still opens. The open arm blocks discriminant
|
|
91
|
+
* narrowing, so read `level`/`lang` behind a check of the arm you want. */
|
|
88
92
|
export type ContentLine = {
|
|
89
93
|
containers: ContentContainer[];
|
|
90
94
|
/** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
|
|
91
95
|
continues?: boolean;
|
|
92
|
-
} &
|
|
96
|
+
} & ContentLineKind;
|
|
97
|
+
|
|
98
|
+
/** A line's block role, declared once for `ContentLine` and the `setKind` op —
|
|
99
|
+
* a new role is one edit here, as for `ContentContainer`. */
|
|
100
|
+
export type ContentLineKind =
|
|
93
101
|
| { kind: "para" }
|
|
94
102
|
| { kind: "heading"; level: number }
|
|
95
103
|
| { kind: "code"; lang?: string }
|
|
96
104
|
| { kind: "island" }
|
|
97
105
|
| { kind: "rule" }
|
|
98
|
-
|
|
106
|
+
| { kind: string; attrs: unknown };
|
|
99
107
|
|
|
100
|
-
/** An ancestor block a line nests inside, outermost first.
|
|
108
|
+
/** An ancestor block a line nests inside, outermost first. Open like
|
|
109
|
+
* `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
|
|
110
|
+
* and renders transparently (its lines sit at the enclosing level). */
|
|
101
111
|
export type ContentContainer =
|
|
102
112
|
| { container: "list_item"; ordered: boolean; start: number; ordinal: number }
|
|
103
|
-
| { container: "quote" }
|
|
113
|
+
| { container: "quote" }
|
|
114
|
+
| { container: string; attrs: unknown };
|
|
104
115
|
|
|
105
116
|
/** A mark over char range `[start, end)` into `Content.text`. The open `type`
|
|
106
117
|
* arm blocks discriminant narrowing (as on `ContentIsland`), so read a
|
|
@@ -223,11 +234,7 @@ export type MarkOp =
|
|
|
223
234
|
export type LineOp =
|
|
224
235
|
| { op: "split"; at: number }
|
|
225
236
|
| { op: "join"; line: number }
|
|
226
|
-
| ({ op: "setKind"; line: number } &
|
|
227
|
-
| { kind: "para" | "island" | "rule" }
|
|
228
|
-
| { kind: "heading"; level: number }
|
|
229
|
-
| { kind: "code"; lang?: string }
|
|
230
|
-
))
|
|
237
|
+
| ({ op: "setKind"; line: number } & ContentLineKind)
|
|
231
238
|
| { op: "setContainers"; line: number; containers: ContentContainer[] }
|
|
232
239
|
| { op: "setContinues"; line: number; continues: boolean };
|
|
233
240
|
|
|
@@ -591,8 +598,16 @@ export class Document {
|
|
|
591
598
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
592
599
|
* constructor for `insertCard`. `fields` is an optional
|
|
593
600
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
594
|
-
* insertion order); `body` defaults to `""`.
|
|
595
|
-
*
|
|
601
|
+
* insertion order); `body` defaults to `""`.
|
|
602
|
+
*
|
|
603
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
604
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
605
|
+
* here.
|
|
606
|
+
*
|
|
607
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
608
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
609
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
610
|
+
* any kind string is accepted here.
|
|
596
611
|
*/
|
|
597
612
|
static makeCard(kind: string, fields?: Record<string, unknown>, body?: string): Card;
|
|
598
613
|
/**
|
package/core/wasm_bg.js
CHANGED
|
@@ -687,8 +687,16 @@ export class Document {
|
|
|
687
687
|
* Build a fresh `Card` from a kind and a flat field map — the ergonomic
|
|
688
688
|
* constructor for `insertCard`. `fields` is an optional
|
|
689
689
|
* `Record<string, unknown>` (each entry becomes a card field, in
|
|
690
|
-
* insertion order); `body` defaults to `""`.
|
|
691
|
-
*
|
|
690
|
+
* insertion order); `body` defaults to `""`.
|
|
691
|
+
*
|
|
692
|
+
* Sugar, not a required step: `insertCard` takes any `Card` object, and
|
|
693
|
+
* `removeCard` returns one, so a card round-trips without passing through
|
|
694
|
+
* here.
|
|
695
|
+
*
|
|
696
|
+
* Checks only what a detached card can decide alone: field-name grammar
|
|
697
|
+
* and value depth. Kind validity is positional — `main` is right for the
|
|
698
|
+
* root, reserved for a composable card — so `insertCard` is its gate, and
|
|
699
|
+
* any kind string is accepted here.
|
|
692
700
|
* @param {string} kind
|
|
693
701
|
* @param {Record<string, unknown>} [fields]
|
|
694
702
|
* @param {string} [body]
|
package/core/wasm_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime/runtime.d.ts
CHANGED
|
@@ -122,15 +122,24 @@ export interface QuillmarkError extends Error {
|
|
|
122
122
|
export declare function isQuillmarkError(e: unknown): e is QuillmarkError;
|
|
123
123
|
|
|
124
124
|
// ── Open-set discriminant guards ────────────────────────────────────────────
|
|
125
|
-
// `ContentIsland.type
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
// `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
|
|
126
|
+
// `ContentContainer.container` are open sets — each union has a residual
|
|
127
|
+
// `{ …: string; … }` arm, so a bare discriminant check never narrows the payload
|
|
128
|
+
// (TS keeps the residual arm live, since a `string` can equal the literal).
|
|
129
|
+
// These guards are the checked narrowing path for the pinned arms; an
|
|
130
|
+
// unrecognized discriminant fails every guard and keeps its opaque payload. Only
|
|
131
|
+
// the payload-carrying arms get a guard — the bare marks
|
|
132
|
+
// (`strong`/`emph`/`underline`/`strike`/`code`), the payload-free lines
|
|
133
|
+
// (`para`/`island`/`rule`), and `quote` narrow to nothing.
|
|
134
|
+
|
|
135
|
+
import type {
|
|
136
|
+
ContentIsland,
|
|
137
|
+
TableProps,
|
|
138
|
+
ImageProps,
|
|
139
|
+
ContentMark,
|
|
140
|
+
ContentLine,
|
|
141
|
+
ContentContainer
|
|
142
|
+
} from '../core/wasm.js';
|
|
134
143
|
|
|
135
144
|
/** Narrow a {@link ContentIsland} to the pinned `table` arm (`props: TableProps`). */
|
|
136
145
|
export declare function isTableIsland(
|
|
@@ -152,6 +161,26 @@ export declare function isAnchorMark(
|
|
|
152
161
|
mark: ContentMark
|
|
153
162
|
): mark is ContentMark & { type: 'anchor'; id: string };
|
|
154
163
|
|
|
164
|
+
/** Narrow a {@link ContentLine} to the `heading` arm (carries `level`). */
|
|
165
|
+
export declare function isHeadingLine(
|
|
166
|
+
line: ContentLine
|
|
167
|
+
): line is ContentLine & { kind: 'heading'; level: number };
|
|
168
|
+
|
|
169
|
+
/** Narrow a {@link ContentLine} to the `code` arm (carries `lang`). */
|
|
170
|
+
export declare function isCodeLine(
|
|
171
|
+
line: ContentLine
|
|
172
|
+
): line is ContentLine & { kind: 'code'; lang?: string };
|
|
173
|
+
|
|
174
|
+
/** Narrow a {@link ContentContainer} to the `list_item` arm (carries its shape). */
|
|
175
|
+
export declare function isListItemContainer(
|
|
176
|
+
container: ContentContainer
|
|
177
|
+
): container is ContentContainer & {
|
|
178
|
+
container: 'list_item';
|
|
179
|
+
ordered: boolean;
|
|
180
|
+
start: number;
|
|
181
|
+
ordinal: number;
|
|
182
|
+
};
|
|
183
|
+
|
|
155
184
|
// ── Canonical render-side types ─────────────────────────────────────────────
|
|
156
185
|
// These are the BACKEND-NEUTRAL render contract of the plural-backend API. They
|
|
157
186
|
// are defined HERE (not re-exported from one private backend) because no single
|
|
@@ -291,7 +320,7 @@ export interface RenderResult {
|
|
|
291
320
|
}
|
|
292
321
|
|
|
293
322
|
/** Canonical contract every backend build must satisfy. The emittable formats. */
|
|
294
|
-
export type OutputFormat = 'pdf' | 'svg' | '
|
|
323
|
+
export type OutputFormat = 'pdf' | 'svg' | 'png';
|
|
295
324
|
|
|
296
325
|
/**
|
|
297
326
|
* Canonical contract every backend build must satisfy. Page geometry in pt.
|
package/runtime/runtime.js
CHANGED
|
@@ -99,15 +99,19 @@ export function isQuillmarkError(e) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// ── Open-set discriminant guards ────────────────────────────────────────────
|
|
102
|
-
// `ContentIsland.type
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
102
|
+
// `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
|
|
103
|
+
// `ContentContainer.container` are OPEN sets: each union carries a residual
|
|
104
|
+
// `{ …: string; … }` arm, so a bare `x.type === 'table'` check never narrows the
|
|
105
|
+
// payload — TS keeps the residual arm live (a `string` can be `'table'`),
|
|
106
|
+
// leaving `props` / the mark payload / `level` opaque at every consumer. These
|
|
106
107
|
// are the checked narrowing path: on the true branch the payload's pinned shape
|
|
107
108
|
// is asserted. Only the payload-carrying arms get a guard — an island always
|
|
108
|
-
// carries `props`, a `link` mark carries `url`, an `anchor` mark carries `id
|
|
109
|
-
//
|
|
110
|
-
//
|
|
109
|
+
// carries `props`, a `link` mark carries `url`, an `anchor` mark carries `id`, a
|
|
110
|
+
// `heading` line carries `level` and a `code` line `lang`, a `list_item`
|
|
111
|
+
// container its shape; the payload-free arms (`strong`/`emph`/`underline`/
|
|
112
|
+
// `strike`/`code` marks, `para`/`island`/`rule` lines, `quote`) narrow to
|
|
113
|
+
// nothing. An unrecognized discriminant fails every guard and keeps its opaque
|
|
114
|
+
// `attrs`/`props`.
|
|
111
115
|
|
|
112
116
|
/**
|
|
113
117
|
* @param {import('../core/wasm.js').ContentIsland} island
|
|
@@ -141,6 +145,30 @@ export function isAnchorMark(mark) {
|
|
|
141
145
|
return mark.type === 'anchor';
|
|
142
146
|
}
|
|
143
147
|
|
|
148
|
+
/**
|
|
149
|
+
* @param {import('../core/wasm.js').ContentLine} line
|
|
150
|
+
* @returns {line is import('../core/wasm.js').ContentLine & { kind: 'heading'; level: number }}
|
|
151
|
+
*/
|
|
152
|
+
export function isHeadingLine(line) {
|
|
153
|
+
return line.kind === 'heading';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {import('../core/wasm.js').ContentLine} line
|
|
158
|
+
* @returns {line is import('../core/wasm.js').ContentLine & { kind: 'code'; lang?: string }}
|
|
159
|
+
*/
|
|
160
|
+
export function isCodeLine(line) {
|
|
161
|
+
return line.kind === 'code';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {import('../core/wasm.js').ContentContainer} container
|
|
166
|
+
* @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; ordered: boolean; start: number; ordinal: number }}
|
|
167
|
+
*/
|
|
168
|
+
export function isListItemContainer(container) {
|
|
169
|
+
return container.container === 'list_item';
|
|
170
|
+
}
|
|
171
|
+
|
|
144
172
|
// Backend builds are NEVER statically imported here — that would pull a
|
|
145
173
|
// multi-MB binary into the eager graph and defeat lazy loading. Each entry is a
|
|
146
174
|
// DESCRIPTOR: `load` is a thunk returning a dynamic `import()` (a backend's
|