@quillmark/wasm 0.97.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,308 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.99.0 - 2026-08-01
4
+
5
+ The 1.0.0 API freeze lands ahead of the tag, and the content codec closes its
6
+ last open gaps. All breaking changes are covered by
7
+ `docs/migrations/0.98-to-0.99.md`. Stored documents are unaffected: a `0.98` blob
8
+ loads byte-identically and `0.99` writes the same bytes for the same content.
9
+
10
+ - refactor(core,content,pdf,pdfform)!: the public API opens. 75 public types take
11
+ `#[non_exhaustive]` — nothing in the workspace carried it before — so an
12
+ exhaustive `match` needs a `_` arm and a struct literal gives way to `new` plus
13
+ `with_*` setters. `Backend` is sealed, `OutputFormat::ALL` and
14
+ `Content::RESERVED_{MARK_TYPES,LINE_KINDS,CONTAINERS}` become slices, and
15
+ `RenderOptions { .., ..Default::default() }` becomes
16
+ `RenderOptions::default().with_output_format(fmt)`. Four stay exhaustive and
17
+ say so: the storage DTOs, frozen per schema version, plus
18
+ `quillmark_pdf::FieldType`, `KnownIslandType`, and `Fidelity`, where an
19
+ out-of-crate `_` arm is silent (a field that draws nothing, an island dropped
20
+ from the projection, a fidelity rung nothing warns about). The rules are
21
+ canonized in `prose/canon/COMPATIBILITY.md` (#1090, #1103). See
22
+ `docs/migrations/0.98-to-0.99.md`
23
+ - refactor(core)!: the YAML engine leaves the public API. `QuillValue::from_yaml_str`
24
+ and `QuillConfig::schema_yaml` return `quillmark_core::YamlError` instead of
25
+ `serde_saphyr` types, so a `0.0.x` dependency release is no longer a break to
26
+ `quillmark-core`. The message is sanitized (the engine's own Rust API names are
27
+ stripped), and `from_yaml_str` gains the `MAX_YAML_DEPTH` budget its siblings
28
+ already carried (#1099, #1101). See `docs/migrations/0.98-to-0.99.md`
29
+ - fix(content)!: the reserved-name rule reaches the wire. `attrs` beside a
30
+ built-in discriminator resolved to the built-in and dropped the payload in
31
+ silence; the authored lane now refuses it on all four axes, where a host writes
32
+ it. Reading never got stricter — a blob from before a promotion still opens.
33
+ A table cell keeps its own unknown keys too, canonicalization now rewriting it
34
+ in place rather than minting a fresh `{text, marks}` (#1084, #1085, #1086,
35
+ #1092). See `docs/migrations/0.98-to-0.99.md`
36
+ - fix(content)!: opaque payload depth is bounded at `MAX_JSON_DEPTH` (128) on the
37
+ `Value` lane, where an unbounded one took the WASM module down with a
38
+ stack-overflow trap rather than a catchable error. The WASM guard sits on the
39
+ JS side of the boundary, since `serde_wasm_bindgen` recurses while building the
40
+ value, and covers every door that takes opaque host JSON: `install` and
41
+ `applyChange`, plus `makeCard`'s field values and `insertCard`'s payload items
42
+ (#1093). See `docs/migrations/0.98-to-0.99.md`
43
+ - fix(content)!: island `loss` becomes the fifth open set. An unrecognized class
44
+ round-trips verbatim instead of being rewritten to `unrepresentable`, so merely
45
+ opening a document no longer moves its content hash. `Loss` opens on the island
46
+ `type` axis' terms rather than the block axes': it becomes an opaque string
47
+ wrapper with `LOSSLESS` / `DEGRADED` / `UNREPRESENTABLE` consts, one value per
48
+ wire string, so a built-in's name has no second spelling and needs no
49
+ reserved-name rule. `Fidelity` is the closed view `Loss::fidelity` returns, and
50
+ is where a consumer switches; `Loss` consequently loses its `Copy` derive
51
+ (#1091, #1142). See `docs/migrations/0.98-to-0.99.md`
52
+ - refactor(core,typst,pdf)!: workspace-internal seams leave the published
53
+ surface. `quillmark-pdf`'s `reader`/`writer` modules and `quillmark_typst::emit`
54
+ become `#[doc(hidden)]`; the op-wire encoders emit an unknown's `attrs` in
55
+ caller key order, the redundant per-encoder sort having been dropped (canonical
56
+ content bytes are unchanged, the terminal sort still running) (#1095). See
57
+ `docs/migrations/0.98-to-0.99.md`
58
+ - fix(wasm)!: a `Quill` or `Document` from a second copy of `@quillmark/wasm` is
59
+ refused everywhere, as a `QuillmarkError` coded `runtime::foreign_handle` that
60
+ names the cause and hints `npm ls @quillmark/wasm`. 0.98 half-worked there:
61
+ `Engine` was duck-typed, so a quill from copy A rendered on an engine from copy
62
+ B at a per-copy clone cache nobody could see, while `Document.equals`,
63
+ `Quill.validate`, `Quill.resolve` and the typed writer met wasm-bindgen's bare
64
+ `expected instance of Document` at a value that *is* a `Document`. The check
65
+ covers `Engine` (`render`, `open`, `supportedFormats`, `supportsCanvas`),
66
+ `LiveSession.apply`, the writer and reader binds, and the three by-reference
67
+ core methods; a value that is not a handle at all keeps its own
68
+ `runtime::not_a_document` / `runtime::not_a_quill`. Nothing changes for a
69
+ one-copy install (#1132, #1136). See `docs/migrations/0.98-to-0.99.md`
70
+ - feat(wasm): `isUnknownLine` / `isUnknownContainer` / `isUnknownMark` /
71
+ `isUnknownIsland` answer known-vs-unknown on each open set, so a consumer no
72
+ longer enumerates built-in names in its own source. `ContentLineKind` is
73
+ re-exported from the package entry point, so a `setKind` op type-checks without
74
+ a cast
75
+ - feat(python): the Tier-1 gaps close. `doc.card(i)`, `doc.card_index_by_id(id)`,
76
+ and `doc.seed_overlay(kind)` are the single-card, `$id`, and seed reads WASM
77
+ already had, and the wheel ships `py.typed` plus stubs, so mypy and Pyright see
78
+ real signatures where the surface used to resolve to `Any` (#1011)
79
+ - fix(typst): four quill-load defects — a skipped asset, an unparseable
80
+ `typst.toml`, a skipped package file, a declared-but-absent entrypoint —
81
+ become `RenderResult` warnings (`typst::path_skipped`,
82
+ `typst::package_manifest`, `typst::package_entrypoint_missing`) instead of
83
+ `eprintln!` that wasm32 has nowhere to print (#1102)
84
+ - fix(wasm): the npm package states the license the workspace actually grants.
85
+ `package.json` declared `MIT OR Apache-2.0` where every Rust crate, the
86
+ workspace manifest, and the only `LICENSE` file in the tree are `Apache-2.0`,
87
+ and the package shipped no license text at all: `build-wasm.sh` copied
88
+ `LICENSE-MIT` and `LICENSE-APACHE`, neither of which exists. It now copies
89
+ `LICENSE`, or refuses to produce a package
90
+ - ci: the release gates the tag actually needs. New `package` (builds every
91
+ publishable crate from its own archive and asserts each ships its `LICENSE`),
92
+ `msrv` (holds `rust-version` to something true), and `audit` (bare `cargo
93
+ audit` over the lockfile) jobs; the workspace moves to edition 2024 and
94
+ declares MSRV 1.92. The `semver` job is dropped — it compared the tree's
95
+ unbumped version against itself — and `COMPATIBILITY.md` names the writer and
96
+ reviewer as what holds the promise instead (#1105, #1106, #1107, #1108)
97
+ - ci: the rustdoc gate covers the whole workspace. A bare `cargo doc` walks
98
+ default-members and never lints a crate outside it — the blind spot that let
99
+ the `Delta` links rot on the WASM surface and four more in the published
100
+ `quillmark-content`. `--workspace` needs no `--exclude` and covers the next
101
+ such crate on the day it lands
102
+ - test(fuzz): the four JSON decode lanes the bindings expose gain coverage
103
+ (#1104)
104
+ - docs(canon): `COMPATIBILITY.md` states the crate-API promise — what
105
+ `#[non_exhaustive]` does and does not buy, when to mark an enum, and what no
106
+ attribute sweep catches
107
+ - docs(all): the em-dash leaves comments and prose, folded to a colon, comma,
108
+ semicolon, or parentheses across ~2900 sites. `dense-prose` banned it while
109
+ every exemplar it named used it; the corpus now matches the rule. A handful of
110
+ diagnostic and CLI message strings repunctuate with it (`edit::body_only`,
111
+ `validation::must_fill`, the pdfform bind errors, `--help`); codes, severities,
112
+ and paths are unchanged. The character stays where it is the subject rather
113
+ than punctuation: the WinAnsi encoding table, the YAML en/em-dash fixtures, and
114
+ `docs/migrations/` (#1135)
115
+ - chore(core): `serde-saphyr` moves to `1.0`. The two call sites that built
116
+ `Options`/`SerializerOptions` with struct-literal-plus-`..Default::default()`
117
+ now go through the crate's own `options!`/`ser_options!`/`budget!` macros,
118
+ which the 1.0 release requires since both structs are `#[non_exhaustive]`.
119
+ `serde_saphyr` types stay out of `quillmark-core`'s public API (see the YAML
120
+ engine entry above), so nothing downstream moves
121
+
122
+ <!-- seed: commits since v0.98.0, confirm the entries above cover them, then delete this comment
123
+ - chore(core): move the time format descriptions to parse_borrowed::<3>
124
+ - build(deps): bump lopdf from 0.42.0 to 0.44.0
125
+ - build(deps): bump thiserror in the cargo group across 1 directory
126
+ - build(deps): bump taiki-e/install-action
127
+ - chore(core): bump serde-saphyr to 1.0
128
+ - Split lint back out of test
129
+ - Link every shipped artifact in CI, and carry v0.99.0's notes forward
130
+ - dense-prose pass over the CI scope
131
+ - Cut CI to one job per shipped surface
132
+ - Stop the release from pushing to a branch that takes only PRs
133
+ - Fix three claims the independent review falsified
134
+ - Correct the release notes' exhaustive-type count
135
+ - dense-prose pass over the review's scope
136
+ - Close the two payload doors the depth guard missed
137
+ - Ship the license the workspace actually grants
138
+ - Correct the release docs against the tree they describe
139
+ - CLAUDE.md: tighten the Tests section to house voice
140
+ - CLAUDE.md: drop `uv run` from the Python binding flow
141
+ - CLAUDE.md: gate local binding builds, name the cheap profiles
142
+ - chore(release): v0.99.0
143
+ - Split serial.rs's fixed-point claim into the two it conflated
144
+ - Make Fidelity the one place a loss class is spelled
145
+ - Make an island's loss class injective instead of guarded
146
+ - dense-prose and simplify passes over the handle-check work
147
+ - Keep the colon out of embedded YAML values
148
+ - Sweep the em-dash out of comments and prose
149
+ - Refuse a core handle from a second copy, everywhere
150
+ - Replace the em-dash ban with the rule the exemplars follow
151
+ - dense-prose pass over the foreign-handle prose
152
+ - Extend the policy to the writer/reader lane, and draw the read/write line
153
+ - Keep the patched method names, and stop over-claiming in the warning
154
+ - Tolerate foreign core handles on the by-reference methods
155
+ - Correct the quill file-size cap doc and MiB units
156
+ - Collapse the rustdoc gate onto --workspace
157
+ - dense-prose pass over the release-readiness fixes
158
+ - Close the release-readiness gaps found against v0.98.0
159
+ - Drop the semver job
160
+ - Trim the Python typing work to its load-bearing parts
161
+ - Revert "Record KnownIslandType::ALL's slice shape in the 0.99 guide"
162
+ - Record KnownIslandType::ALL's slice shape in the 0.99 guide
163
+ - Close the Python Tier-1 gaps: single-card reads and a typed surface
164
+ - Drop the top-level-await plugin the vite 8 test config cannot load
165
+ - Shape the reserved-name lists as slices
166
+ - Shape the island ALL as a slice; list the 0.99 guide as published
167
+ - Prune rottable duplication from CLAUDE.md
168
+ - Adopt the editor's dense-prose rewrite; compress CLAUDE.md
169
+ - build(deps): bump the cargo group across 1 directory with 12 updates
170
+ - build(deps): bump the actions group across 1 directory with 11 updates
171
+ - build(deps): bump tsify from 0.4.5 to 0.5.6
172
+ - build(deps): bump toml from 0.8.23 to 1.1.3+spec-1.1.0
173
+ - build(deps): bump similar from 2.7.0 to 3.1.1
174
+ - build(deps): bump serde-saphyr from 0.0.23 to 0.0.29
175
+ - Run the semver job on release PRs, not on every one
176
+ - Drop the dead check-cfg rustflag that crashed the semver job
177
+ - docs: dense-prose pass over the new test comments
178
+ - dense-prose pass over the branch
179
+ - Gate the compatibility promise mechanically, and write it down
180
+ - test(bindings): cover the WASM and Python exports nothing exercised
181
+ - Close the struct half of the 1.0.0 non_exhaustive sweep, and seal Backend
182
+ - test(pdfform): drive unbound text/checkbox/choice through the fixture
183
+ - Move the workspace to edition 2024
184
+ - build(deps-dev): bump vitest in /crates/bindings/wasm
185
+ - build(deps-dev): bump typescript in /crates/bindings/wasm
186
+ - prose: drop the readiness proposal and SECURITY.md
187
+ - ci: drop the Windows matrix from the test job
188
+ - Finish the coerce_value_strict cleanup in the fuzz crate
189
+ - dense-prose pass across the code and docs
190
+ - Revert the wasm job to `npm install`
191
+ - Trim CI to the checks v1.0.0 actually needs
192
+ - Cut the two CI costs that buy nothing
193
+ - Clear the RUSTSEC vulnerabilities the audit job actually found
194
+ - Pin the tree to LF so the Windows runner reads what the test wrote
195
+ - Cleanup pass: cut the machinery the fix did not need
196
+ - prose: record what landed against the v1.0.0 readiness findings
197
+ - fuzz: cover the four JSON decode lanes the bindings expose
198
+ - Declare and hold an MSRV
199
+ - Ship the license text, verify the packages, scan the dependencies
200
+ - Open the engine crates' public enums ahead of the 1.0.0 freeze
201
+ - Hide the workspace-internal seams from the published surface
202
+ - typst: quill-load defects become session warnings, not stderr
203
+ - core: own the YAML boundary error, and budget the third entry point
204
+ - prose: index the v1.0.0 readiness findings against their issues
205
+ - prose: v1.0.0 production-readiness review
206
+ - Drop issue markers; dense-prose the unreleased 0.99 guide
207
+ - dense-prose pass over the PR 1097 scope
208
+ - docs: record the op-wire key-order change in the unreleased guide
209
+ - content: carry the `<u>` bit on the event, not in a shared set
210
+ - content: one key sorter for the canonical form
211
+ - content: bound opaque JSON payload depth on the Value lane
212
+ - content: make a vocabulary promotion carry its own legacy form
213
+ - content: simplify pass over the codec-gap changes
214
+ - content: house voice on the codec-gap comments
215
+ - content: close the three v1.0.0 codec gaps before the freeze
216
+ - content: cut the enumerated halves of the two open-set tests
217
+ - content: prune tests that assert what a neighbour already pins
218
+ - content: fold the authored-lane scan into the shared cell walk
219
+ - content: split the wire lanes on reserved-name reuse (#1084, #1085, #1086)
220
+ - docs: trim the migration index to a routing table; drop duplicate links
221
+ - refactor(typst): re-export span_scan/extract instead of forwarding
222
+ -->
223
+
224
+
225
+ ## v0.98.0 - 2026-07-28
226
+
227
+ Five breaking changes, all covered by `docs/migrations/0.97-to-0.98.md`.
228
+ Stored documents are unaffected: a `0.97` blob loads byte-identically and
229
+ `0.98` writes the same bytes for the same content.
230
+
231
+ - feat(core,content,wasm)!: the block vocabulary opens. A line's `kind` and a
232
+ container's name join the mark `type` and island `type` as open sets — an
233
+ unrecognized value round-trips opaque and renders as its nearest safe
234
+ neighbor instead of failing the load, so adding a block construct is no
235
+ longer a document schema-version event. `LineKind` and `Container` each gain
236
+ an `Unknown { tag, attrs }` variant (exhaustive matches need an arm) and
237
+ `Line` / `LineKind` / `Container` drop their `Eq` derive, matching `MarkKind`;
238
+ `Invariant` gains `ReservedUnknownLineKind` / `ReservedUnknownContainer`, so
239
+ an unknown may not reuse a built-in name. `ContentLine.kind` and
240
+ `ContentContainer.container` gain an open TS arm, so a bare discriminant check
241
+ no longer narrows — `isHeadingLine` / `isCodeLine` / `isListItemContainer`
242
+ join the existing guards in `@quillmark/wasm/runtime`. `Loss` describes
243
+ fidelity; it does not gate export (#1054). See
244
+ `docs/migrations/0.97-to-0.98.md`
245
+ - refactor(core,wasm,python,cli)!: `OutputFormat::Txt` is removed — the Rust
246
+ variant, Python's `OutputFormat.TXT`, the TS `'txt'` arm. No backend listed it
247
+ in `SUPPORTED_FORMATS`, so every path reaching it failed at render time;
248
+ `--format txt` and `from_str("txt")` now fail at the argument instead, and
249
+ there is no replacement format (#1058). See
250
+ `docs/migrations/0.97-to-0.98.md`
251
+ - refactor(typst,pdfform)!: `format_not_supported` was three codes for one
252
+ condition. Both backends now emit `backend::format_not_supported`, alongside
253
+ the sibling `backend::apply_unsupported` — route on the namespaced code
254
+ (#1057). See `docs/migrations/0.97-to-0.98.md`
255
+ - refactor(core)!: dead public surface is removed — `ReadValue::as_text` /
256
+ `as_value` (match the variants), `Quill::list_files` / `list_subdirectories`
257
+ (`quill.files()` owns the walk), and `QuillValue`'s eight `Deref`-shadowed
258
+ accessors (`QuillValue: Deref<Target = serde_json::Value>` resolves each call
259
+ unchanged — no action). `prescan`'s helpers and `reader::err` drop to
260
+ `pub(crate)` (#1066, #1064). See `docs/migrations/0.97-to-0.98.md`
261
+ - fix(python)!: rendered regions cross the boundary as `DocPath`. Python
262
+ returned plate-space addresses no document API accepts; the plate→`DocPath`
263
+ translation now lives in core as `regions_to_doc_path` and both bindings call
264
+ it, so `RenderResult.regions[].field` reads `main.body` /
265
+ `cards.<kind>[<i>].<field>` as WASM already did (#1063). See
266
+ `docs/migrations/0.97-to-0.98.md`
267
+ - fix(content): three codec holes close. A mark spanning an island slot no
268
+ longer swallows it; a `LineKind` that disagrees with its segment is caught
269
+ (`Invariant::LineKindMismatch`, carrying a `LineKindMismatch` reason); and
270
+ decode is bounded — `MAX_NESTING_DEPTH` is enforced at the door, and a wire
271
+ position is read checked rather than `as usize`, which on wasm32 landed
272
+ `2^32 + 5` at position `5` (#1051)
273
+ - fix(content): export's verify-and-drop safety net is bounded per line rather
274
+ than per mark; `CONVERT.md` states the import↔export coupling the net implies
275
+ (#1052)
276
+ - fix(content): markdown import keeps a literal `*` that abuts strong or
277
+ emphasis — `a***a**` imports as `a`, `*`, strong `a`, where the deleted fixup
278
+ dropped the typed star. Removing it also fixes a backslash escape or entity in
279
+ such a span re-entering the content as literal source bytes (#1053)
280
+ - feat(core): `FileTreeNode`, `QuillIgnore`, `QuillConfig`, the schema types,
281
+ and `ValidationError` are nameable from `quillmark` and from `quillmark_core`'s
282
+ root, so in-memory quill construction and schema reading need no direct core
283
+ dependency (#1055)
284
+ - refactor(core,pdfform): `From<PdfError> for RenderError` replaces two
285
+ byte-identical `map_pdf_err` copies; `lopdf`, `js-sys` and `wasm-bindgen`
286
+ hoist into `[workspace.dependencies]`, the no-op `default-features = false`
287
+ pins drop, and `publish` is explicit on every crate (#1070, #1064)
288
+ - docs(core): the card-kind validation line is documented where it lives —
289
+ construction (`Document::make_card`, `TryFrom<CardWire>`) is permissive
290
+ data-shaping and insertion is the gate, returning `edit::invalid_kind_name`
291
+ - docs(canon): `ERROR.md` stops documenting a `fmt_pretty_with_source()` that
292
+ does not exist and states what carries the source chain: serialization to
293
+ both bindings, no Rust formatter. `error.rs`'s divergent path-grammar copy
294
+ gives way to a pointer at `path.rs` and the canon anchor table (#1061, #1069)
295
+ - docs(canon): prune — canon stops re-documenting what `docs/` owns and states
296
+ each fact once; the lint checks truth rather than shape, and the CI job table
297
+ lists every job
298
+ - test: test clusters go table-driven, wrong-altitude binding tests move down to
299
+ core, tests that pin foreign behavior or assert nothing are dropped, and one
300
+ walker and one quill builder replace the per-suite copies (#1060, #1056,
301
+ #1065, #1062, #1067)
302
+ - ci: the zero-backend configuration is built and tested — `--workspace
303
+ --all-features` forces `typst` on, so that branch never compiled (#1068)
304
+
305
+
3
306
  ## v0.97.0 - 2026-07-24
4
307
 
5
308
  - core: one take_item primitive behind the three payload removers
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2025 Nibs
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.