@quillmark/wasm 0.94.0 → 0.96.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,324 +1,71 @@
1
1
  # Changelog
2
2
 
3
- ## v0.94.0 - 2026-07-15
4
-
5
- - Delete prose/plans directory
6
- - feat(wasm): promote the live-session/canvas surface to stable
7
- - docs(release): reconcile changelog and guides for 0.94.0 (skip 0.93)
8
- - test(core,quillmark): remove redundant and low-value tests
9
- - core: drop public→private rustdoc link in UiFieldSchema docs
10
- - core/seed: collapse merge-then-sort into one declaration-order pass
11
- - docs: declaration order is the field ordering contract; ui.order removed
12
- - core, bindings: update tests, golden, and doc surfaces for structural order
13
- - core: field declaration order becomes structural (IndexMap), ui.order retired
14
- - Simplify group-order sort key and ui.order stamping
15
- - Add card-level ui.groups registry for group identity and ordering
16
- - pdfform: place widget geometry once at bind, not per render
17
- - pdfform: gate form.json version before field-shape parse
18
- - pdfform: derive widget intrinsics from the quill schema (form@0.2.0, #940)
19
- - Reject nested ui.group; auto-order nested object properties
20
- - docs: densify the plaintext/enum section of the 0.93->0.94 guide
21
- - core, richtext: follow simplification cascades from plaintext
22
- - docs: extend canon (BLUEPRINT, PLATE_DATA) for plaintext and enum
23
- - docs: document plaintext and enum field types, narrow string
24
- - quillmark: end-to-end render test for plaintext through typst
25
- - core: test plaintext codec and enum promotion end-to-end
26
- - core: add plaintext and enum first-class field types
27
- - richtext: add from_plaintext literal codec and is_plain predicate
28
- - fix(typst/overlay): stop underline/strike decoration ink truncating $body regions
29
- - chore: migrate org references quillmark-org borb-sh
30
- - docs: fix cross-tree link that broke mkdocs --strict
31
- - simplify: fix stale receiver refs and facade-prose misses after rename
32
- - rename typed-write facade editor→writer; unify on quill.writer(doc)
33
- - python: delete redundant commit_* verbs (subsumed by the editor)
34
- - docs: two-tier binding surface parity table + strata/ephemerality (#932)
35
- - python: Document.editor(quill) front door + Editor/CardEditor + reads
36
- - wasm: quill.editor(doc) front door + tier-1 gap verbs + reads
37
- - wasm+python: delete addressed commit(addr) verb (subsumed by editor)
38
- - core: TypedEditor set_body + add_card (tier-1 mirror verbs)
39
- - simplify: follow the #925 cascades
40
- - docs: finish migration index entry; tidy wasm doc link
41
- - python: mirrored addressed write surface + corpus codec
42
- - wasm: addressed write surface (install/revise/applyChange/commit) + codec
43
- - richtext+core: pure Delta codec + install/revise body & field verbs
44
- - test(wasm): update runtime.test.js editor sugar to the strict contract
45
- - feat(core,wasm,python)!: typed field writes reject unknown names (#918)
46
- - wasm: split Card read type from CardInput write type (#917)
47
- - richtext: MarkOp::Remove subtracts the range instead of dropping the whole mark
48
- - fix(quillmark-pdf): dict/array depth walkers skip comments and strings
49
- - fix(richtext): markdown export never leaks a delimiter into the corpus
50
- - fix(richtext): saturate ordered-list marker; doc/hint cleanups from final review
51
- - #913: mirror the WASM typed batch on Python (commit_fields + set/commit reframe)
52
- - #911: finish exposing the typed editor in WASM (commitFields batch + JS editor sugar + reframe)
53
- - test(wasm): warm the lazy Typst backend load before timed Engine renders
54
- - test: consolidate generator-arm round-trips and costume proptests (tier 3-4)
55
- - test: remove orphan fixtures and redundant/low-value tests (tier 1-2)
56
- - docs: audit of legacy/redundant/low-value tests, fixtures, logic
57
- - richtext/core/bindings: address issues #902–#906
58
- - richtext: escape image alt/URL and link URL on markdown export (#900)
59
- - richtext: strip \r and bidi controls from apply_text_delta inserts
60
- - Remove prose/proposals/: 893 proposal has landed
61
- - Unify binding write-verb grammar: mechanical card-write twins (#895)
62
- - Hard cutover to commit_field: remove legacy richtext writers
63
- - wip: bindings + tests + docs for typed writes (pre-cutover)
64
- - Typed field writes: conform dispatch, commit_field, TypedEditor (core)
65
- - Review typed-field-writes proposal against codebase; resolve open questions
66
- - Add proposal: typed field writes via schema-carried types (#893)
67
- - feat(wasm): setCardBody — corpus writer for a non-main card body (#892)
68
- - remove(core,richtext,wasm)!: delete the incremental-edit surface (#886)
69
- - test(wasm): fix two applyFieldDelta tests left stale by the #881 un-gate
70
- - feat(regions): field_boxes helper + CorpusHit granularity signal (#884)
71
- - fix(core,wasm): resolve lint CI failure + re-sync applyFieldDelta type after merge
72
- - wasm/canvas: report clamp on PaintResult; document paint compositing constraints
73
- - fix(wasm): make applyFieldDelta and supportsCanvas types honest about their domain
74
- - docs: migration guide for the richtext field write-surface unification (#881)
75
- - richtext: un-gate applyFieldDelta to richtext fields (step 6)
76
- - wasm: setRichtextField / updateCardRichtextField + per-field markdown getter (step 5)
77
- - richtext: emit projects corpus fields to markdown; wire/DTO lossless (steps 2-4)
78
- - richtext: Card::set_field_richtext writer + corpus read-back (step 1)
79
- - fixtures: table_demo Quill + end-to-end table render test; document pdfform limit
80
- - richtext: property-test structured tables through normalize + round-trip
81
- - richtext: in-cell images degrade honestly instead of silent-Lossless
82
- - richtext: canonical table shape — validate invariants, repair in normalize
83
- - test: drop two redundant inline tests; dense-prose the runtime edit-surface note
84
- - Prune landed simplification entries from the backlog
85
- - Silence clippy nits in span_scan walk refactor
86
- - test(wasm): keep runtime edit-surface guard focused on the #876 methods
87
- - docs: fix rustdoc intra-doc links to satisfy the -Dwarnings lint gate
88
- - Filter locate's glyph walk to the target segment
89
- - Share frame-walk geometry between region scan and corpus walk
90
- - feat(wasm): expose incremental editor edit surface through runtime (#876)
91
- - Skip props re-clone in normalize when already key-sorted; document R2 hazard
92
- - Carry the <u>/Strong distinction instead of re-sniffing source
93
- - feat(typst): add plaintext(field) projection helper + document richtext migration hazard (#873)
94
- - Lift island mark-dispatch out of model into serial
95
- - Build canonical richtext tree once via move-based key sort
96
- - feat(wasm): add setBody(corpus) mutator on Document (#874)
97
- - Dedup emit_inline mark partition into wraps_and_codes
98
- - fix(typst): lower richtext(inline) fields to inline content (#872); fix wasm TS types (#875)
99
- - Remove unused ChangeLog/session forward surface
100
- - Cut over-engineered simplification findings; refresh drifted citations
101
- - docs(simplifications): remove the addressed findings
102
- - docs(simplifications): record the sync-twins deferral rationale
103
- - refactor(typst,richtext): dedup container dispatch and the #846 clip
104
- - refactor(core): one shared decoder for the dual-shape richtext seam
105
- - refactor(richtext): make apply_field_change all-or-nothing
106
- - refactor(core): single carrier for the richtext inline flag
107
- - refactor(richtext): drop unused usv UTF-16 helpers and content_key alias
108
- - refactor: apply low-risk simplifications from prose/simplifications
109
- - docs: record simplification-review findings in prose/simplifications/
110
- - fix(richtext): apply a short field delta leniently (auto-append remainder)
111
- - Trim unused test-only public methods from core
112
- - docs(core,typst): dense-prose pass on comments
113
- - docs(richtext): dense-prose pass on comments
114
- - docs(core): update QuillValue fill-setter reference after set_fill removal
115
- - Remove more dead/redundant logic
116
- - docs: remove stale info across canon, docs, and comments
117
- - test: remove redundant and low-value tests across the workspace
118
- - Remove dead and redundant logic
119
- - fix(docs): resolve broken intra-doc links (cargo doc -Dwarnings)
120
- - typst/richtext: address #855 cleanup findings
121
- - Fix #851 follow-up: transactional applyFieldDelta and fail-closed invalidation
122
- - richtext: fix issue #854 cleanup findings
123
- - fix(session): close the three delta-protocol lifecycle gaps (#851)
124
- - Fix low-risk API-surface findings from #856
125
- - fix(wasm): strip unreachable delta-API types from public runtime.d.ts (#850)
126
- - chore: ignore dist/, .vite/, test-results/ to prevent build-artifact commits
127
- - fix(richtext): cap single-line char diff to avoid quadratic blowup (#849)
128
- - fix(richtext): balance markdown export for overlap, escape `&` and trailing heading `#` (#848)
129
- - docs: fix richtext drift found in #853
130
- - fix(richtext): keep islands in sync on text-channel edits (#847)
131
- - fix(typst): balance markup for overlapping wrap+code marks (#846)
132
- - Render thematic breaks (---/***/___) instead of dropping them
133
- - fix(richtext): correct ChangeLog::map_pos staleness and future-revision handling
134
- - chore(richtext): drop richtext-spikes from integration branch
135
- - Move richtext inline shape to `inline: true` schema field.
136
- - feat(richtext): PR-H findings — fixture, runtime nav, docs (PR-H)
137
- - cargo fmt
138
- - feat(richtext): preview revision stamp + WASM session delta API (PR-F/PR-G)
139
- - feat(richtext): fallible document body mutators (PR-E)
140
- - docs(richtext): drop .qmd and Quill-Delta from phase-3 plan
141
- - docs(richtext): record PR-C/D landing in phase-3 plan
142
- - feat(richtext): add mark/line op channels and apply bundle (PR-D)
143
- - feat(richtext): add revision counter and bounded change log (PR-C)
144
- - feat(richtext): replace coarse delta::diff with Myers/LCS (PR-B)
145
- - Remove husky pre-commit hook
146
- - cargo fmt
147
- - docs(richtext): record phase-3 spike findings on integration branch
148
- - docs(richtext): align canon + code comments to PR-G, wrap up phase 2
149
- - fix(wasm): add span to canonical FieldRegion contract
150
- - richtext PR-G: richtext(inline), load-time example cache, alias cutover
151
- - Delete prose/review directory
152
- - docs(richtext): trim phase plans to landed reality, fold spike findings
153
- - test(richtext): wasm binding test for positionAt/locate round-trip (#829)
154
- - PR-F: regions + navigation — two-tier segment scan, position_at/locate (#829)
155
- - spike(richtext): PR-F de-risking — run-machine transparency + glyph.span.1 precision
156
- - fix(richtext): accept bodyMarkdown in the wasm card field allowlist
157
- - feat(richtext): phase-2 PR-E — bindings expose corpus body + bodyMarkdown
158
- - docs(richtext): clarify hard vs soft break in the CONVERT lowering table
159
- - docs(richtext): phase-2 PR-E canon rework for the corpus seam
160
- - feat(richtext): phase-2 PR-E — pdfform lowers richtext to plaintext + fixture
161
- - feat(richtext): phase-2 PR-E part 2 — delete the markdown oracle
162
- - feat(richtext): phase-2 PR-E part 1 — seam flip + typst consumes corpus
163
- - docs(richtext): reconcile Typst-emit section with structured cells
164
- - docs(richtext): PR-E handover + phase-2 status refresh
165
- - docs(richtext): remove the .qmd file-extension concept
166
- - feat(richtext): structured table cells — markdown a pure projection
167
- - feat(richtext): phase-2 PR-D — typst corpus emitter + segment source maps
168
- - feat(richtext): phase-2 PR-C — storage cutover to quillmark/document@0.93.0
169
- - fix(richtext): resolve broken RichText::validate intra-doc link
170
- - fix(core): quote payload scalars that would re-parse as a non-string
171
- - docs(richtext): PR-B landing log + PR-C handover on the phase-2 plan
172
- - fix(richtext): clear rustdoc -Dwarnings lint on phase-2 PR-B docs
173
- - test(bindings): update body expectations for corpus canonicalization
174
- - feat(richtext): phase-2 PR-B — Card.body is RichText, markdown a projection
175
- - docs(richtext): lock separate-crate topology + retire Quill-Delta framing in plan
176
- - refactor(richtext): make quillmark-richtext a leaf crate core depends on
177
- - docs(richtext): phase-2 plan — engine consumes RichText, delivers #829
178
- - docs(richtext): note block-quote emit decision for phase-2 handover
179
- - docs(richtext): phase-1 handover doc + integration HQ updates
180
- - fix(richtext): address phase-1 review — freeze determinism, codecs, anchors
181
- - feat(richtext): phase 1 — RichText corpus model, codecs, canonical serialization
182
- - docs(richtext): land phase-0 spike findings on the integration HQ
183
- - docs(richtext): make integration HQ canonical, self-contained
184
- - docs(richtext): open integration HQ plan for the content-model rework
185
- - Sync usaf_memo/0.2.0 fixture with airmark-quiver template
186
- - #824: dedup bindings tests + add two coverage guards
187
- - #823 items 2-9: dedup core + integration tests
188
- - #821 follow-up: fix rustdoc private-intra-doc-link in winansi_encode
189
- - #822: dedup typst backend + PDF crate tests (~280 lines)
190
- - #825: cache only the wasm cargo build dir, always regenerate pkg/
191
- - #806: resolve overlapping widget field_at by paint order, not name
192
- - Dead-code sweep from minimize-0.92.1-main review (#817–#821, #823, #825)
193
- - Ground #809/#810: memoize page checks, unify dict-splice + field-name filters (#816)
194
- - Tidy deferred backlog: dedup error-summary, pdfform flatten-limit docs, cleanup (#804, #807, #810, #812) (#815)
195
- - Fix silent-data-loss regression and blueprint/error/docs gaps (#803, #805, #808, #811) (#814)
196
- - Land LiveSession.fieldAt delegation; harden page_hashes against source spans (#801) (#813)
197
- - Helper codegen v2: markup blocks + generated data literal, retiring eval() and json() (#800)
198
- - Disable clippy in lint CI job (#798)
199
- - Satisfy clippy's manual_contains in the scalar-window walker
200
- - Drop the #797 spike modules from the branch
201
- - Harden the span scan against the review findings
202
- - Delete .claude/skills/simplification-cascades directory
203
- - Spike: generated data literal is Typst-equal to the json() blob
204
- - Spike: content fields as markup blocks resolve with per-node spans
205
- - Document the span-based region contract across canon, core, and the migration guide
206
- - Expose fieldAt on the WASM session; align binding docs with the span contract
207
- - Migrate usaf_memo off tagged(); rewrite region tests for the span contract
208
- - Span-based region tracking: codegen'd eval windows + glyph-span scan
209
- - Omit $body from the transform schema for body-disabled kinds
210
- - feat(regions): any array is element-addressable; SchemaMeta drops its unreachable Option
211
- - test(typst): own the widget-region test's schema instead of borrowing the fixture's
212
- - refactor(typst): collapse SchemaMeta per-kind table boilerplate, test-owned widget width
213
- - docs(migrations): document the compile-time region-binding validation in the working guide
214
- - fix(ci): hash every pkg/ input in the wasm cache key, check key sets in the drift guard
215
- - fix(wasm): sync canonical runtime.d.ts with the regions sidecar, wire typecheck into CI
216
- - feat(regions): validate form-field paths, reject scalar index-tagging, cache schema meta
217
- - docs(migrations): fix stale region-uniqueness line in 0.92→0.93 guide
218
- - Reconcile canon and migration guide with the reworked error system
219
- - Update binding surfaces to the current-compile warnings contract
220
- - Thread Typst compile warnings through the session as current-compile state
221
- - Collapse RenderError to a single diagnostics-carrying struct; drop Severity::Note
222
- - Amend error-rework proposal per adversarial review
223
- - Propose error system rework: one failure type, warnings on the compile seam
224
- - dense-prose: drop issue reference and history narration from regions test
225
- - feat(regions): tag the usaf_memo plate; canon + migration guide for the new region contract
226
- - feat(regions): per-placement regions, public tagged() helper, one-shot sidecar
227
- - docs: accuracy pass + dense-prose sweep across docs/, prose/canon/, comments, READMEs
228
- - build-wasm.sh: dev builds stamp next-patch -dev.<sha>, releases stamp verbatim
229
- - wasm runtime: snapshot caller handles before the first await
230
- - ci: enable the Clippy lint gate
231
- - Python: batched-error message embeds the first diagnostic
232
- - docs(core): note apply's $quill-check boundary on LiveSession::apply (#778)
233
- - perf(wasm): LiveSession retains QuillConfig, not the whole Quill (#778)
234
- - docs: canonize LiveSession — live preview owns apply/ChangeSet (#778)
235
- - feat(wasm): LiveSession.apply(doc) -> ChangeSet (#778)
236
- - Canon: endorse programmatic document construction
237
- - refactor!: collapse RenderSession into LiveSession (#778)
238
- - Add blank-canvas Document constructor in core and all bindings
239
- - feat: transactional apply() + ChangeSet on the render session (#778)
240
- - Add scalar From impls for QuillValue; mutators take Into<QuillValue>
241
- - Expose batched set_fields across Python, WASM, and .NET bindings
242
- - feat(typst): persist the World in the session; evict comemo after each compile (#778)
243
- - Add atomic batched Card::set_fields
244
- - remove(dotnet)!: drop the .NET binding entirely
245
- - Update README.md
246
- - Update README.md
247
- - Update README.md
248
- - Update README.md
249
- - Update README.md
250
- - Update README.md
251
- - Update README.md
252
- - fix: update WASM region test for content auto-tag; fix doc links
253
- - docs(region): tighten regions() doc per dense-prose
254
- - feat(region): one region per logical schema field
255
- - docs(region): fix grouping guidance and note blank/empty no-region case
256
- - refactor(typst): expose a region only for schema-addressable fields
257
- - feat(typst): auto-region tagging for content fields (#775)
258
- - refactor(core)!: move field regions from RenderResult to RenderSession::regions()
259
- - refactor(core)!: key regions on the quill schema field, not the backend widget
260
- - docs: fold prune-evolutionary-info into dense-prose
261
- - docs: canonize comment/doc style as the dense-prose skill
262
- - refactor(test): drop dangling BACKLOG.md reference
263
- - refactor(core): tighten doc-comments, prune evolutionary phrasing
264
- - refactor(bindings): neutralize marketing and reframe legacy phrasing
265
- - refactor(quillmark,cli,fuzz): tighten comments, drop bug-history narration
266
- - refactor(typst): tighten module and item doc-comments
267
- - docs(canon): prune evolutionary and marketing phrasing
268
- - docs: neutralize marketing and reframe legacy aliases in consumer docs
269
- - feat(pdfform): export PNG and SVG; remove the preview feature gate
270
- - fix(ci): resolve lint and docs CI failures on pdfform branch
271
- - fix(quillmark-pdf): make find_dict_value key/value-position aware
272
- - Make the plate a Typst-backend concern, not a universal one
273
- - Address branch-review findings: spine robustness, coercion parity, doc accuracy
274
- - Prune resolved review items 01–06; trim 07 to remaining gaps
275
- - Rework item 2: derive canvas capability, delete supports_canvas flag
276
- - Address branch-review item 3: total FieldRegion round-trip
277
- - Address branch-review items 1, 2, 4, 5, 6, 7
278
- - docs(review): write up unaddressed branch-review items for discussion
279
- - test(pdfform): restore flatten byte-level coverage at the unit level (preview-gated)
280
- - refactor(pdfform): PDF output always AcroForm; remove the public flatten knob
281
- - Update sample_form PDF title and field labels
282
- - Make pdfform framing industry-neutral
283
- - docs: add PDF Form backend page under quills section
284
- - refactor(pdfform): remove the form.json -> Quill.yaml scaffold
285
- - refactor(pdf): extract the incremental-update envelope into PdfUpdate
286
- - pdfform: WinAnsi flatten encoding, value clipping, shared PDF writer
287
- - review: resolve low-risk findings from pdfform branch review
288
- - Update README.md
289
- - Remove qualification layer and page composition: pdfform fills static forms only
290
- - feat(quillmark-pdf): page-merge primitive + continuation foundation (#757)
291
- - refactor(pdfform): centralize flatten value typography as one source of truth (#752)
292
- - feat(wasm): ship pdfform canvas + make the per-backend canvas contract explicit (#755)
293
- - feat(typst): generalize the plate API to arbitrary form-field(...) (#758)
294
- - feat(pdfform): card-instance value addressing in the resolver (#757)
295
- - feat(qualify): add quillmark-qualify — AcroForm PDF → form.pdf + form.json (#753)
296
- - chore(fmt): apply rustfmt line-wrapping to pre-existing drift
297
- - feat(pdfform,cli): scaffold Quill.yaml from a form.json field spec (#756)
298
- - fix(pdfform): build the preview feature against hayro's vello_cpu re-export
299
- - Expose form-field regions from stamped AcroForm backends (#759)
300
- - Implement `pdfform` backend + shared `quillmark-pdf` stamping spine (V1) (#750)
301
- - docs(proposal): design for pdfform backend + shared AcroForm spine (#744) (#748)
302
- - fix(python): count container levels, not the scalar leaf, in depth bound (#742) (#746)
303
- - Add generic hint for unresolvable Typst eval errors (issue #745) (#747)
304
- - docs: fix prose/canon, docs, and code-comment consistency drift (#743)
305
- - fix(core): count empty containers in json_depth_exceeds (#741)
306
- - Update README.md
307
- - docs: position Quillmark as a "schema-driven document engine" (#739) (#740)
308
- - review(0.93.0): fix stale comments, document scalar coercion, cover card markers (#738)
309
- - Unify blueprint emission on Document::to_markdown (#737)
310
- - docs(blueprint): fix $-line comment inaccuracies in canon (#735)
311
- - core: graciously coerce bare scalars into string fields (#733)
312
- - docs(spike): propose unifying blueprint placeholder on the !must_fill tag (#732)
313
-
314
-
315
- ## v0.92.1 - 2026-06-22
316
-
317
- - Accept uppercase field names; reserve only `$`-prefixed keys (#730)
318
- - docs: canonize $ext.editor.title as per-card display name slot (#729)
3
+ ## v0.96.0 - 2026-07-23
4
+
5
+ - docs(spec): keep plate JSON out of the markdown spec
6
+ - docs(core): fix doc-lint and mkdocs-strict CI failures
7
+ - core: gate plate $body at construction, not post-hoc strip
8
+ - docs(core): trim duplicated $kind comment in to_plate_json
9
+ - core: plate $body/$kind absent on undefined (#1030)
10
+ - core: compile_data conforms once ladder consumes the gate's coerced output
11
+ - rename: view()→reader(), fieldStates()→resolve() across core and bindings
12
+ - core: one shared resolver behind compile_data and field_states
13
+ - content: dense-prose + simplify pass on the #1002 decision
14
+ - content: decide island-id content-hash determinism (#1002)
15
+ - Re-key runtime.d.ts geometry docs onto the canonical DocPath form
16
+ - Canon lint cleanup: dedup spine restatements, fold CI job, shape-based anchor check
17
+ - Canon docs infra: single-source the spine in prose/README.md, enforce in CI
18
+ - release: strip the seed coverage comment by construction; fix blank-notes fallback
19
+ - Reject signed years in date/datetime grammar (#1008)
20
+ - core: silence unused_must_use on Payload::insert in tests
21
+ - DocPath: root every main-field address at `main`
22
+ - fieldStates: ordered rows carrying `name`, body as a sibling (drop `$body`)
23
+ - Descope phase 5: cut the conformance suite, contractVersion, and phase-plan docs
24
+ - Contract phase 5 conformance suite + contractVersion
25
+ - Contract phase 4 typed-surface completion: QuillCardUi.groups + island props
26
+ - Phase 3 doc: separate tested value-parity from structural source-rung claim
27
+ - Fix rustdoc intra-doc links on EditError::doc_path / RenderedRegion.field
28
+ - simplify pass: hoist kinds vec, unify render-sidecar addressing, merge doc_path arms
29
+ - Contract phases 2–3: geometry DocPath unification, mutator paths, lean fieldStates
30
+ - Repivot the contract rework around consumer evidence (phase-doc rewrite)
31
+ - Qualify card fill diagnostics by schema-declared kind only (#1014)
32
+ - Contract phase 2 canonical DocPath (+ phase 1 wasm test fix) (#1012)
33
+ - edit:: diagnostic codes on mutator failures (contract phase 1) (#1006)
34
+ - Add document-contract rework phase plan (#1005)
35
+ - content: centralize island type dispatch behind KnownIslandType (#985) (#1001)
36
+
37
+
38
+ ## v0.95.1 - 2026-07-19
39
+
40
+
41
+
42
+ ## v0.95.0 - 2026-07-19
43
+
44
+ - release: re-cut to finish binding dists after the first publish run cancelled mid-flight
45
+ - **breaking** typst: a present `date` / `datetime` field lowers to a click-to-edit value-object — `(value: datetime(..), display: (..args) => text(value.display(..args)))` — instead of a bare Typst `datetime`, so the rendered glyphs are born at a generated `text(..)` node carrying a region keyed on the field's schema path: a date placed by a vendored package, or a card's date riding a shared loop variable, is click-to-edit. A blank date stays `none`, so `!= none` guards hold. Plates migrate two shapes: `(data.f.display)("…")` (paren form — the stored `display` is a closure on a dict, not a method) and `data.f.value` for anything native (comparison, `.year()`-family components, datetime-consuming packages). The tonguetoquill flagship quills' `display-date` dispatches on `type(date)`, since their `datetime.today()` blank-date fallback stays a native datetime (#990)
46
+ - **breaking** core,typst,pdfform: split the `datetime` field type into strict `date` and `datetime` (#717/#799 resolved) — `date` accepts a bare `YYYY-MM-DD` and rejects any time component; `datetime` accepts offset-less wall-clock `YYYY-MM-DDThh:mm[:ss]` (seconds zero-filled) and rejects timezone offsets, the space separator, fractional seconds, and bare dates. Offsets are rejected, never dropped (the engine does no zone math); storage stays verbatim; no truncation in either direction. A `date` lowers to the three-component Typst `datetime(year:, month:, day:)` (unchanged emission) and a `datetime` to the six-component constructor, carrying the wall-clock time. The transform schema marks `date` as `format: "date"` (keeping `format: "date-time"` for `datetime`), and the blueprint reads `date<YYYY-MM-DD>` / `datetime<YYYY-MM-DDThh:mm[:ss]>`. Most deployed `datetime` fields hold a bare date and migrate to `type: date` with byte-identical data; the fixtures (usaf_memo, cmu_letter) do so. No deprecation alias — `datetime` rejecting a bare date is the decided end-state (#991)
47
+ - **breaking** python: the binding commits to the typed lanes field I/O flows through `quill.writer(doc)` / `quill.view(doc)` exclusively and `Document` is quill-free data and structure. Removed (WASM-only by scope, not lag — their audience is not a Python audience): the opaque field store (`store_field` / `store_fields` / `store_fill` and card twins), the content lane (`install` / `revise` / `apply_change`), the quill-free field reads (`get` / `get_card_field`), and the module codec fns (`import_markdown` / `export_markdown` / `rebase` / `map_pos`). The composable `$ext` / field-remove card twins fold onto a trailing `card=None` selector and `push_card` folds into `insert_card(card, at=None)`. Mirrored additions: `writer.revise_field` (the `Delta` receipt is not surfaced), `writer.add_card(kind, fields=None, body=None, at=None)`, `writer.card(i).kind`, and the `Writer` / `CardWriter` / `View` / `CardView` handle classes exported from the package (#970)
48
+ - **breaking** core,wasm,python: complete the `RichText` → `Content` residual sweep (#976 folds into #982) — retire the last informal-`corpus` / model-`RichText` *identifiers* the mechanical rename missed: `CorpusHit` → `ContentHit`, `EditError::CorpusApply` → `EditError::ContentApply`, `RichtextDecodeError::NotCorpus` → `NotContent` (the codec-specific `RichtextDecodeError` type itself is kept), storage DTO `CanonicalRichText` → `CanonicalContent` (serde is unchanged — no wire migration), and the model-generic Typst emitter `emit_richtext` / `emit_richtext_inline` `emit_content` / `emit_content_inline` (they lower any `Content`, richtext *or* plaintext). Also fixes the `ParseError` display strings `richtext json …` → `content json …`, the doubled-word find-replace debris (`content content model` → `content model`), and stale prose/comments across canon and rustdoc. Schema/codec names are untouched — `richtext` / `plaintext` tokens, `FieldType::{RichText,PlainText}`, `field_richtext` / `FieldRichtext*` / `apply_field_richtext_change`, `richtext(inline)` — they name codecs, not the model. "corpus" is purged from the tree entirely, including the ordinary-English test-set names that meant a *collection* of fixtures (`fixture_corpus` → `fixtures`, `synthetic_corpus` → `synthetic_inputs`) (#982)
49
+ - **breaking** core,wasm,python: a schema-bound read view — `Quill::view(&doc)` / `quill.view(doc)`, the read twin of `quill.writer(doc)`. `view.get(addr)` interprets each field by its declared type (a `richtext` field → markdown, a `plaintext` field → its literal text via the plaintext codec, every other type → its canonical value verbatim), returns absent as `undefined` / `None`, and — the authority the quill-free `getMarkdown` lacks — throws `UnknownField` for a name the schema does not declare and `FieldRichtextDecode` for a content field holding an undecodable value. Core `TypedReader::get` returns a `ReadValue` (`Markdown`/`Plaintext`/`Value`); `view.card(i)` is the card cursor; core adds `Card::field_plaintext` (the `to_plaintext` twin of `field_markdown`). **`getMarkdown`'s field half retires**: `getMarkdown` / `get_markdown` / `get_card_markdown` are now body-only (WASM `getMarkdown` takes a `CardAddr`, a present `field` throws; Python drops the `name` parameter) — a field's markdown is read through `view.get`. The quill-free body projection stays on `Document` (#978)
50
+ - **breaking** content: one delta-application contract — implicit trailing retain is `try_apply`'s semantics (a short delta retains the untouched remainder; the error is over-consumption only), `apply` panics on an over-long delta instead of clamping (clamping is silent corruption), and `extend_to_base` is removed. `split_line` / `join_line` rebase marks through their one-char `\n` splice with `map_pos` — the same mapping the text-delta channel uses — so marks no longer drift across line ops and `apply_field_change` canonicalizes once (a single terminal normalize instead of one per stage); line sync rebuilds in one forward pass instead of per-`\n` `Vec` splices. Mark ops are specified in final-text coordinates (post-delta, post-line-op — the frame they validate against) (#926, #987)
51
+ - **breaking** core: storage blobs tagged `@0.81.0` / `@0.82.0` fail as an unknown schema version — the read-only `V0_81_0` / `V0_82_0` DTO trees and their forward migrations are retired (nothing persisted on this lineage predates `@0.92.0`; `0.82.0` was yanked). `V0_92_0` stays the oldest shape read, and its payload types back the current write path. DOCUMENT_STORAGE.md records variant retirement as the policy when no stored population remains (#929)
52
+ - **breaking** core,wasm,python: the markdown projection stops appending a trailing newline — `to_markdown` projects a *value*, not a file, so `field_markdown` / `body_markdown` (WASM `getMarkdown` / `exportMarkdown`, Python `export_markdown` / `get_markdown`) no longer grow a `\n`; `writer.set("subject", "Hello")` reads back as `"Hello"`, not `"Hello\n"`. `.qmd` files still end in one newline (owned by `Document::to_markdown`, the file writer) and the content fixed point is unchanged (import is newline-insensitive) (#965)
53
+ - **breaking** all: rename the content genus off its codec's name — crate `quillmark-richtext` → `quillmark-content`, type `RichText` → `Content` (and `RichTextLine`/`RichTextContainer`/`RichTextMark`/`RichTextIsland` → `ContentLine`/…), const `RICHTEXT_MEDIA_TYPE` → `CONTENT_MEDIA_TYPE` and its wire string `application/quillmark-richtext+json` → `application/quillmark-content+json`, `#[serde(skip)]` companion caches `FieldSchema::{default,example}_corpus` → `_content`, `SegmentMap.corpus: Range<usize>` `.content`, Typst-emitter `EmittedContent` `Emission` (it is markup + source map, not a Typst `content` value). Schema tokens `richtext` / `plaintext`, `FieldType::{RichText,PlainText}` variants, and the codec-specific `field_richtext` / `FieldRichtext*` / `apply_field_richtext_change` / `richtext(inline)` surface are unchanged — those name codecs, not the model. Canonical body JSON is nameless, so stored documents don't migrate; `contentMediaType` consumers pin to the new spelling. Retires the informal "corpus" noun to end the code/prose split (#976)
54
+ - **breaking** core,wasm,python: `getMarkdown` / `get_markdown` / `get_card_markdown` stop conflating an absent field with a present-but-not-richtext one a present field that does not decode as richtext (a scalar/array/object a `storeField` wrote) now throws `FieldRichtextDecode` instead of reading back `undefined` / `""`; absence still returns the absent shape. Core `Card::field_markdown` becomes `Option<Result<String, RichtextDecodeError>>` (the projection twin of `field_richtext`). Rule: absence returns, mismatch raises; read the raw value with `get` (#968)
55
+ - feat(core,wasm): typed, anchor-preserving field revise — `TypedWriter::revise_field` / `CardWriter::revise_field` and `writer.reviseField` / `writer.card(i).reviseField` wrap core `Card::revise_field_checked` (diff-rebase surviving anchors, then schema-conform the result); the schema-bound verb lives on the writer, where the schema is (#957, #966)
56
+ - **breaking** wasm: the quill-taking `Document` methods become the hidden ABI under the writer — `commitField` / `commitFields` / `addCard` → `_commitField` / `_commitFields` / `_addCard`, dropped from the `.d.ts`; remove `doc.reviseChecked` (no runtime consumer — use `writer.reviseField`). The visible `Document` class then carries zero quill-taking methods (#966)
57
+ - **breaking** core: rename `EditError::BodyImport` → `EditError::Import` (message `body import failed:` → `markdown import failed:`) — the variant also fires on field-path imports (`revise_field`), where "body" misnamed it (#966)
58
+ - **breaking** wasm: fold `pushCard` into `insertCard(card, at?)` one insertion verb per lane, absent `at` appends; `insertCard`'s parameters reorder to `(card, at?)`. Delete the deprecated `replaceBody` alias (use `revise({}, md)` or `writer.setBody`) (#961)
59
+ - feat(core,wasm): positioned card insert — `TypedWriter::add_card` / `writer.addCard` and the `addCard` ABI take an `at` position, so a positioned typed insert is one atomic call instead of `addCard` + `moveCard`; add `TypedWriter::remove_card` (mirrors JS `writer.removeCard`) and a JS `CardWriter.kind` getter (mirrors core `CardWriter::kind()`) (#961)
60
+ - **breaking** core: `Payload::insert` / `insert_fill` now validate the field-name and value-depth invariant at the boundary and return `Result<_, FieldViolation>`, closing the `payload_mut().insert(...)` hole that let a direct caller build an invalid document; pre-validated internal callers use the new `pub(crate)` `insert_unchecked` / `insert_fill_unchecked` (#958)
61
+ - feat(wasm,core): single-card reads `doc.card(i)` (throws out of range), `doc.cardIndexById(id)` (first match; `$id` is non-unique), and `doc.seedOverlay(kind)`, backed by core `Document::card(i)` / `find_card(id)`. Reading one card, resolving a `$id`, or fetching a `$seed` overlay no longer serializes the whole `cards` array or main card (#956)
62
+ - **breaking** core: parse warnings live only on `ParseOutput` — the redundant `Document::warnings` field + `warnings()` getter are dropped and `Document::from_main_and_cards` no longer takes a `warnings` param (`Document` `PartialEq` is now a plain derive) (#959)
63
+ - **breaking** core: collapse the two parse functions into one entry — `Document::from_markdown` and `Document::from_markdown_with_warnings` are removed in favor of `Document::parse(md) -> Result<Parsed, ParseError>`, and `ParseOutput` is renamed `Parsed`. A document-only caller writes `parse(md)?.document`. Bindings are unaffected: WASM `Document.fromMarkdown` / Python `Document.from_markdown` keep their names and their `doc.warnings` getter (#964)
64
+ - feat(wasm,python): keyed card reads `getCardField(index, name)` / `getCardMarkdown(index, name?)` (py `get_card_field` / `get_card_markdown`) — the card-indexed twins of `get` / `getMarkdown`, mirroring the `commitCardField` / `setCardField` write verbs so card reads no longer require a `payloadItems` walk (#953)
65
+ - feat(content,wasm,python): `LineOp::SetContinues { line, continues }` — hard breaks lower op-wise. Split, join, and a text-delta `\n` all mint `continues: false` lines, so a within-block hard break (a paragraph hard break, a code fence's interior line) had no op and fell back to a whole-install, losing that edit's identity anchors. Threaded through the wire codec into WASM `applyChange` (TS union updated) and Python; `continues: true` on line 0 is rejected with `ApplyError::FirstLineContinues` before the write, leaving the content untouched (#949)
66
+ - feat(wasm): the runtime root re-exports the edit vocabulary its own signatures reference — `Content` / `ContentLine` / `ContentContainer` / `ContentMark` / `ContentIsland`, `Addr` / `Delta` / `Assoc` / `LineOp` / `MarkOp` / `ChangeBundle`, `CardInput` / `PathStep` — as type-only exports (single entry point preserved; no `/core` subpath), with a presence guard so a dropped re-export fails `npm run typecheck` (#948)
319
67
 
320
-
321
- ## Unreleased
68
+ ## v0.94.0 - 2026-07-15
322
69
 
323
70
  These notes cover everything since v0.92.1. No 0.93.x was separately
324
71
  published — the 0.93 milestone folds into this release, so the upgrade path
@@ -546,6 +293,12 @@ from 0.92.1 is the `0.92-to-0.93` and `0.93-to-0.94` guides read in sequence.
546
293
  `--features typst` where they used `--features render`; the published JS API is
547
294
  unchanged. See `docs/migrations/0.92-to-0.93.md`
548
295
 
296
+ ## v0.92.1 - 2026-06-22
297
+
298
+ - Accept uppercase field names; reserve only `$`-prefixed keys (#730)
299
+ - docs: canonize $ext.editor.title as per-card display name slot (#729)
300
+
301
+
549
302
  ## v0.92.0 - 2026-06-22
550
303
 
551
304
  - 0.92 technical-debt sweep: correctness, $seed hardening, de-duplication (#727)
package/README.md CHANGED
@@ -89,7 +89,7 @@ is resolved at render time, not here. Loads no backend binary.
89
89
  A blank document: a main card carrying only `$quill`, an empty body, and no
90
90
  composable cards — the programmatic blank canvas. Absent fields resolve at
91
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`.
92
+ did not set reaches the output. Build it up with `storeFields` / `insertCard`.
93
93
  For an example-filled starter use `quill.seedDocument()`. Throws on an
94
94
  invalid quill reference.
95
95
 
@@ -197,7 +197,7 @@ and compare instead of re-parsing on every keystroke.
197
197
  ### `doc.cardCount`
198
198
  O(1) getter for the number of composable cards (excluding the main card).
199
199
  Use this to validate indices before calling card mutators (`removeCard`,
200
- `setCardField`, etc.) without allocating the full `cards` array.
200
+ `storeField({ card, field }, …)`, etc.) without allocating the full `cards` array.
201
201
 
202
202
  ### `quill.validate(doc)`
203
203
 
@@ -233,47 +233,64 @@ const markdown = doc.toMarkdown();
233
233
  For per-card seeding, `quill.seedMain()` returns just the `$kind: main` card
234
234
  and `quill.seedCard(kind)` returns a starter composable card (or `undefined`
235
235
  if the kind is not declared). Both return the read `Card` shape of
236
- `doc.main` / `doc.cards`, which `doc.pushCard` / `doc.insertCard` accept
237
- directly:
236
+ `doc.main` / `doc.cards`, which `doc.insertCard` accepts directly:
238
237
 
239
238
  ```ts
240
- doc.pushCard(quill.seedCard("note")); // seed → push
241
- doc.pushCard(Document.makeCard("note", { x: 1 })); // build from a flat map
242
- doc.pushCard({ kind: "note", body: "Plain **markdown**." }); // bare inline
239
+ doc.insertCard(quill.seedCard("note")); // seed → append
240
+ doc.insertCard(Document.makeCard("note", { x: 1 })); // build from a flat map
241
+ doc.insertCard({ kind: "note", body: "Plain **markdown**." }); // bare inline
242
+ doc.insertCard({ kind: "note" }, 0); // insert at index 0
243
243
  ```
244
244
 
245
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
246
+ Content` (canonical content, never a raw string) — no narrowing, no guessing
247
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.
248
+ `Content | string` (a markdown string imports to the content) and makes every
249
+ field but `kind` optional. Every `Card` is a valid `CardInput`, so `insertCard`
250
+ still takes exactly what `cards` / `removeCard` / `seedCard` return.
251
251
  Build a fresh card from a flat field map with
252
252
  `Document.makeCard(kind, fields?, body?)`.
253
253
 
254
- Batch mutation: `doc.setFields({...})` / `doc.setCardFields(index, {...})`
254
+ **One address for the whole surface.** Reads and writes navigate by an `Addr`
255
+ `{ card?, field? }`, absent `card` = main, absent `field` = body — and a bare
256
+ string is shorthand for `{ field }`. So `doc.storeField("qty", 3)` targets the
257
+ main card's `qty`, `doc.storeField({ card: 2, field: "qty" }, 3)` a composable
258
+ card's. Reads are total over the field axis (`get` → `undefined`, `isFill` → `false` for
259
+ an absent field; only an out-of-range card throws); field writes throw on a body
260
+ address. `getMarkdown` is the body markdown read (a `CardAddr`; a field's
261
+ markdown is read through `quill.reader(doc).get(field)`). Card-scoped verbs take a
262
+ `CardAddr` (`{ card? }`) first: `doc.getExt({ card: 2 })`, and the batch below.
263
+
264
+ Batch mutation: `doc.storeFields({}, {...})` / `doc.storeFields({ card: index }, {...})`
255
265
  apply a whole object atomically — on any invalid field nothing is applied and
256
266
  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
267
+ name). The address is first (never shape-overloaded, since `card` is a legal
268
+ field name), and parses strictly — a stray key throws rather than silently
269
+ reading as `{}`. The main card is `{}`, or **`MAIN_CARD_ADDR`** (from
270
+ `@quillmark/wasm/runtime`), a frozen alias that spells the intent:
271
+ `doc.storeFields(MAIN_CARD_ADDR, {...})`.
272
+
273
+ ### Typed writes: `commit*` is the default, `store*` is the quill-free primitive
274
+
275
+ A `Document` holds only a `$quill` *reference*, not the resolved schema, so typed
276
+ writes go through the schema-bound writer while the quill-free opaque store sits
277
+ on `Document` itself (**store** = verbatim, **set** = typed):
278
+
279
+ - **`quill.writer(doc)` the typed door whenever a quill is in hand.** Bind the
280
+ schema once and issue bare `set` / `setAll` / `setBody` / `reviseField` /
281
+ `addCard` / `card(i)`. Each resolves the field's schema `type`, coerces the
282
+ value to its canonical form (`"3"` → `3`, a markdown string a richtext
283
+ content), and **fails now** on a mismatch instead of at render. A name the schema
284
+ does not declare throws `UnknownField` rather than falling to the opaque store —
285
+ on the typed path an undeclared name is a typo, not a fallback. The batch form
286
+ (`setAll`) is all-or-nothing: an undeclared name aborts the whole write and its
287
+ per-field diagnostics name every offending field, so a whole-form submit
288
+ surfaces every typo `storeFields` would silently absorb. (The raw wasm class
289
+ carries the quill-taking `_commitField` / `_commitFields` / `_addCard` /
290
+ `_reviseField` ABI the writer delegates to, hidden from the `.d.ts`.)
291
+
292
+ - **`store*` — the deliberate quill-free primitive.** `doc.storeField(addr, value)`
293
+ / `doc.storeFields(cardAddr, {...})` (and `storeFill`) validate only the field
277
294
  name/depth/kind and store the value verbatim, no quill required. Reach for it
278
295
  on purpose when you *want* the opaque store: quill-agnostic storage/migration
279
296
  infra that has no bundle and must write regardless of a drifted schema;
@@ -283,21 +300,20 @@ mutates through two layers:
283
300
  and only surfaces at `quill.validate` / render.
284
301
 
285
302
  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
303
+ in place; no seam is crossed), so steering to the writer buys the type check for
287
304
  free.
288
305
 
289
306
  #### `DocumentWriter` / `CardWriter` — bind the quill once
290
307
 
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:
308
+ `quill.writer(doc)` binds the quill's schema to the document once, so a form
309
+ editor or MCP writer that holds both issues bare verbs (the writer forwards to
310
+ the per-call `_commit*` ABI):
294
311
 
295
312
  ```ts
296
- import { DocumentWriter } from "@quillmark/wasm";
297
-
298
- const ed = new DocumentWriter(quill, doc); // JS twin of Rust `quill.writer(doc)`
313
+ const ed = quill.writer(doc); // Rust `quill.writer(doc)` twin; new DocumentWriter(quill, doc) also works
299
314
  ed.set("subject", "Q3 results"); // strict-committed to the schema type
300
315
  ed.setAll({ qty: "3", subject: "Q3" }); // all-or-nothing batch
316
+ ed.reviseField("subject", "Q3 **results**"); // typed AND anchor-preserving; returns a Delta
301
317
  ed.set("titel", "x"); // throws UnknownField — a typo, not a fallback
302
318
  ed.card(2).set("body", "**note**"); // composable card, resolved by its $kind
303
319
  ```
@@ -446,10 +462,11 @@ compilation failures. The same shape applies to every throw site:
446
462
 
447
463
  - `Document.fromMarkdown` — parse errors (missing root `$quill` metadata, YAML
448
464
  errors, `parse::input_too_large` for inputs > 10 MB).
449
- - `Document` mutators (`setField`, `setCardField`, etc.) — `EditError`
450
- variants (`InvalidFieldName`, `InvalidKindName`, `ReservedKind`,
451
- `IndexOutOfRange`, `ValueTooDeep`) appear in `diagnostics[0].message` with
452
- the `[EditError::<Variant>]` prefix.
465
+ - `Document` mutators (`storeField`, the writer's `set`, etc.) — mutator
466
+ failures carry a namespaced `edit::*` `code` on `diagnostics[0]`
467
+ (`edit::invalid_field_name`, `edit::unknown_field`, `edit::index_out_of_range`,
468
+ `edit::field_conform`, …). Route on `diagnostics[0].code`, never on message
469
+ text.
453
470
  - `engine.render` / `session.render` — backend compilation failures and
454
471
  validation errors.
455
472