@quillmark/wasm 0.95.1 → 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,5 +1,40 @@
1
1
  # Changelog
2
2
 
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
+
3
38
  ## v0.95.1 - 2026-07-19
4
39
 
5
40
 
@@ -30,49 +65,6 @@
30
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)
31
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)
32
67
 
33
- <!-- seed: commits since v0.94.0 — confirm the entries above cover them, then delete this comment
34
- - chore: prune redundant logic and duplicate tests (post-0.94.0 residue scan) (#996)
35
- - release: unbreak the crates.io publish lane; fold curated notes into the seed (#995)
36
- - Emit date fields as click-to-edit value-objects (#990) (#994)
37
- - Split `datetime` into strict `date` and `datetime` types (#991) (#993)
38
- - Python binding: commit to the Tier-1 surface (#970) (#992)
39
- - audit #982: complete the Content-genus residual sweep (retire "corpus") (#989)
40
- - feat: schema-bound read view — `quill.view(doc)` and `TypedReader` (#988)
41
- - Rebase marks through line ops; collapse bundle normalize (#987)
42
- - core: retire the V0_81_0 and V0_82_0 storage read shims (#929) (#986)
43
- - Document binding build performance guidance in CLAUDE.md (#984)
44
- - richtext: to_markdown projects a value, not a file — no trailing newline (#965) (#977)
45
- - docs(markdown-spec): scope $body wire claim, fix lossless→lossy projection (#983)
46
- - Delete prose/review directory
47
- - rename: content genus off its codec's name — RichText → Content, crate → quillmark-content (#976) (#981)
48
- - Add note to not run cargo fmt (#980)
49
- - fix(core,wasm,python): getMarkdown surfaces present-but-not-richtext instead of blanking (#968) (#979)
50
- - docs: purge rogue .qmd file-extension mentions (#975)
51
- - Rewrite CLAUDE.md for density (#974)
52
- - docs: fix mkdocs strict build — drop cross-tree link to prose/canon
53
- - wasm: name the main-card address (MAIN_CARD_ADDR), reject unknown addr keys (#969)
54
- - core: collapse the two parse functions into one `Document::parse` -> `Parsed` (#964)
55
- - core,wasm: writer-level reviseField; hide the quill-taking Document ABI (#966)
56
- - core,wasm,docs: dense-prose pass over the #963 write surface
57
- - docs: document the write-surface reshape (#955, #957, #960)
58
- - python: rename opaque store verbs set_* → store_* (#960)
59
- - wasm: unify Document on Addr addressing; store_* verbs; reviseChecked (#955, #960, #957)
60
- - core: rename opaque store verbs set_* → store_*; add revise_field_checked (#960, #957)
61
- - core: collapse the Payload insert helpers; tighten prose
62
- - core: use plain code spans for pub(crate) refs in Payload::insert docs
63
- - docs: 0.94→0.95 migration guide and BINDINGS parity refresh (#956, #958, #959, #961)
64
- - wasm,core: writer/card-surface parity cleanups (#961)
65
- - wasm,core: single-card, $id, and seed-overlay reads (#956)
66
- - core: enforce field invariants at the Payload::insert boundary (#958)
67
- - core: make ParseOutput the single owner of parse warnings (#959)
68
- - docs(bindings): densify the card-read doc comments
69
- - feat(wasm,python): keyed card reads mirroring the card write verbs (#953)
70
- - docs: remove prose/simplifications backlog for greenfield re-analysis
71
- - Add setContinues line op so hard breaks lower op-wise (#949)
72
- - Re-export corpus edit vocabulary from @quillmark/wasm root (#948)
73
- -->
74
-
75
-
76
68
  ## v0.94.0 - 2026-07-15
77
69
 
78
70
  These notes cover everything since v0.92.1. No 0.93.x was separately
package/README.md CHANGED
@@ -258,7 +258,7 @@ main card's `qty`, `doc.storeField({ card: 2, field: "qty" }, 3)` a composable
258
258
  card's. Reads are total over the field axis (`get` → `undefined`, `isFill` → `false` for
259
259
  an absent field; only an out-of-range card throws); field writes throw on a body
260
260
  address. `getMarkdown` is the body markdown read (a `CardAddr`; a field's
261
- markdown is read through `quill.view(doc).get(field)`). Card-scoped verbs take a
261
+ markdown is read through `quill.reader(doc).get(field)`). Card-scoped verbs take a
262
262
  `CardAddr` (`{ card? }`) first: `doc.getExt({ card: 2 })`, and the batch below.
263
263
 
264
264
  Batch mutation: `doc.storeFields({}, {...})` / `doc.storeFields({ card: index }, {...})`
@@ -462,10 +462,11 @@ compilation failures. The same shape applies to every throw site:
462
462
 
463
463
  - `Document.fromMarkdown` — parse errors (missing root `$quill` metadata, YAML
464
464
  errors, `parse::input_too_large` for inputs > 10 MB).
465
- - `Document` mutators (`storeField`, the writer's `set`, etc.) — `EditError`
466
- variants (`InvalidFieldName`, `InvalidKindName`, `ReservedKind`,
467
- `IndexOutOfRange`, `ValueTooDeep`, `Import`) appear in `diagnostics[0].message`
468
- with 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.
469
470
  - `engine.render` / `session.render` — backend compilation failures and
470
471
  validation errors.
471
472
 
@@ -110,14 +110,44 @@ export type ContentMark = { start: number; end: number } & (
110
110
  | { type: string; attrs: unknown }
111
111
  );
112
112
 
113
- /** A structured object (table, figure, …) occupying one island slot in `Content.text`. */
114
- export interface ContentIsland {
113
+ /** A cell in a `TableProps` its plain `text` plus the `marks` over it. `marks`
114
+ * rides the same wire shape as prose `ContentMark`, but each mark's `start`/`end`
115
+ * are USV offsets into this cell's `text` (`0..text.length`), not into
116
+ * `Content.text`. */
117
+ export interface TableCell {
118
+ text: string;
119
+ marks: ContentMark[];
120
+ }
121
+
122
+ /** `props` of a `type: "table"` island: a pipe table normalized to one column
123
+ * count that `header`, every row of `rows`, and `aligns` all share. */
124
+ export interface TableProps {
125
+ header: TableCell[];
126
+ rows: TableCell[][];
127
+ /** Per-column alignment, one entry per column. */
128
+ aligns: ("none" | "left" | "center" | "right")[];
129
+ }
130
+
131
+ /** `props` of a `type: "image"` island. */
132
+ export interface ImageProps {
133
+ url: string;
134
+ alt: string;
135
+ }
136
+
137
+ /** A structured object occupying one island slot in `Content.text`. `type` is an
138
+ * open set: the engine pins `props` as `TableProps` for `table` and `ImageProps`
139
+ * for `image`; an island of any other type round-trips with opaque `props`. Like
140
+ * `ContentMark`, the open `type` arm means a discriminant check does not itself
141
+ * narrow `props` — key off `type` and read `props` as the matching shape. */
142
+ export type ContentIsland = {
115
143
  id: string;
116
- type: string;
117
- props: unknown;
118
144
  /** How faithfully the markdown projection can carry this island. */
119
145
  loss: "lossless" | "degraded" | "unrepresentable";
120
- }
146
+ } & (
147
+ | { type: "table"; props: TableProps }
148
+ | { type: "image"; props: ImageProps }
149
+ | { type: string; props: unknown }
150
+ );
121
151
 
122
152
  /**
123
153
  * A write address — one navigation concept for the whole `Document` surface. An
@@ -203,6 +233,21 @@ export interface ChangeBundle {
203
233
 
204
234
 
205
235
 
236
+ /**
237
+ * One segment of a parsed `Diagnostic.path` (see `parseDocPath`). The head
238
+ * carries the document-model root — `main` (only before `body`), a `card`
239
+ * (`kind: null` is the unknown-kind `cards[i]` form), or a `field`; the tail is
240
+ * `field` / `index` / a terminal `body`.
241
+ */
242
+ export type DocPathSeg =
243
+ | { seg: "main" }
244
+ | { seg: "card"; kind: string | null; index: number }
245
+ | { seg: "field"; name: string }
246
+ | { seg: "index"; index: number }
247
+ | { seg: "body" };
248
+
249
+
250
+
206
251
  /**
207
252
  * Page dimensions in points (1 pt = 1/72 inch). Typst measures in Typst
208
253
  * points; pdfform measures in PDF points — the same unit.
@@ -288,6 +333,55 @@ export interface PaintResult {
288
333
 
289
334
 
290
335
 
336
+ /** The commitment-ladder rung that produced a `ResolvedField.value`. */
337
+ export type FieldSource = "authored" | "default" | "zero";
338
+
339
+ /**
340
+ * One resolved row: its `name`, the value the render projection would use, and
341
+ * the `FieldSource` rung it came from. Rows are an ordered array — declaration
342
+ * order is structural, not object-key order. The card body is a `body` sibling
343
+ * on its card, never a row in `fields`. Diagnostics stay `Quill.validate`'s;
344
+ * schema guidance (`example:`, labels) reads from `Quill.schema`.
345
+ */
346
+ export interface ResolvedField {
347
+ name: string;
348
+ value: unknown;
349
+ source: FieldSource;
350
+ }
351
+
352
+ /**
353
+ * The main card's resolved rows in declaration order, plus its body row —
354
+ * `null` when the main enables no body.
355
+ */
356
+ export interface ResolvedMain {
357
+ fields: ResolvedField[];
358
+ body: ResolvedField | null;
359
+ }
360
+
361
+ /**
362
+ * One composable card's resolved rows in declaration order, with its authored
363
+ * `kind` (`null` for an unknown-kind card), its document-array `index`, and its
364
+ * body row — `null` when the kind enables no body.
365
+ */
366
+ export interface ResolvedCard {
367
+ kind: string | null;
368
+ index: number;
369
+ fields: ResolvedField[];
370
+ body: ResolvedField | null;
371
+ }
372
+
373
+ /**
374
+ * The resolved-value view (`Quill.resolve`): the main card and every
375
+ * composable card. Value and provenance only — completeness and errors stay
376
+ * `Quill.validate`.
377
+ */
378
+ export interface Resolved {
379
+ main: ResolvedMain;
380
+ cards: ResolvedCard[];
381
+ }
382
+
383
+
384
+
291
385
  /** UI layout hints for a single field. Field display order is not a hint:
292
386
  * key order in the schema's `fields`/`properties` objects is declaration
293
387
  * order, the ordering contract. */
@@ -298,9 +392,23 @@ export interface QuillFieldUi {
298
392
  multiline?: boolean;
299
393
  }
300
394
 
395
+ /** One entry in a card's `ui.groups` registry: a display-label override for the
396
+ * group id (the map key). An empty object carries no override — the consumer
397
+ * derives the label from the id (`memo_for` → "Memo For"), as it does a field
398
+ * label from its key. */
399
+ export interface QuillGroupUi {
400
+ title?: string;
401
+ }
402
+
301
403
  /** UI layout hints for a card (main or named card kind). */
302
404
  export interface QuillCardUi {
303
405
  title?: string;
406
+ /** The card's group registry: the ordered table of contents naming every
407
+ * group a field's `ui.group` may reference. The map key is the group id, and
408
+ * key order is declaration order — the display-order contract, the same one
409
+ * `fields` key order carries. Absent when the card declares no groups (or
410
+ * uses the deprecated implicit-group form). */
411
+ groups?: Record<string, QuillGroupUi>;
304
412
  }
305
413
 
306
414
  /** Body namespace for a card (main or named card kind). */
@@ -467,7 +575,7 @@ export class Document {
467
575
  * A single composable card by index — the whole `Card`, the card-indexed
468
576
  * twin of the [`main`](Self::main) getter, so reading one card need not
469
577
  * materialize every card via [`cards`](Self::cards). An out-of-range
470
- * `index` throws `[EditError::IndexOutOfRange]`, matching the card write
578
+ * `index` throws `edit::index_out_of_range`, matching the card write
471
579
  * verbs.
472
580
  */
473
581
  card(index: number): Card;
@@ -523,7 +631,7 @@ export class Document {
523
631
  * the **body content** when `addr.field` is absent. A bare string is `Addr`
524
632
  * shorthand for `{ field }`. Reads are total over the field axis: an absent
525
633
  * field is `undefined`; only an out-of-range `addr.card` throws
526
- * `[EditError::IndexOutOfRange]`. Reads need no schema, so they live on
634
+ * `edit::index_out_of_range`. Reads need no schema, so they live on
527
635
  * `Document`, not the typed writer; for the markdown projection of a
528
636
  * richtext value use [`getMarkdown`](Self::get_markdown).
529
637
  */
@@ -550,7 +658,7 @@ export class Document {
550
658
  *
551
659
  * `addr` is an optional **card address** (`{ card }`, absent = main). A
552
660
  * present `field` throws — a field's markdown is read through the
553
- * schema-plane `quill.view(doc).get(field)`, which interprets by declared
661
+ * schema-plane `quill.reader(doc).get(field)`, which interprets by declared
554
662
  * type (#978). An out-of-range `addr.card` throws.
555
663
  */
556
664
  getMarkdown(addr?: CardAddr): string;
@@ -808,8 +916,8 @@ export class LiveSession {
808
916
  /**
809
917
  * The schema field whose content is under a point on `page` — the
810
918
  * forward (click → field) direction: hit-test a click against the
811
- * compiled document and get back the field address to focus in the
812
- * editor, or `undefined` off any field's ink. `x`/`y` are PDF points
919
+ * compiled document and get back the `DocPath` field address to focus in
920
+ * the editor, or `undefined` off any field's ink. `x`/`y` are PDF points
813
921
  * with a **bottom-left** origin, the same space as `FieldRegion.rect` —
814
922
  * from a canvas click, invert the overlay transform documented on
815
923
  * `FieldRegion`: `x = clickPx.x / renderScale`,
@@ -873,11 +981,12 @@ export class LiveSession {
873
981
  /**
874
982
  * Schema-field geometry for this compiled session — each content field's
875
983
  * **first placement** (one region per page it touches) plus widget and
876
- * scalar-reference-site regions, keyed on the quill schema field path; a
877
- * field may still appear more than once (group by `field`, see
878
- * `FieldRegion`). A session-level query: no render, no byte artifact. An
879
- * interactive preview reads it to scroll to / highlight the focused
880
- * field over a `paint`-ed canvas; the click direction is `fieldAt`.
984
+ * scalar-reference-site regions, keyed on the canonical `DocPath` address
985
+ * (`parseDocPath`-routable; the session resolves the backend's plate-space
986
+ * per-kind ordinal to it); a field may still appear more than once (group
987
+ * by `field`, see `FieldRegion`). A session-level query: no render, no byte
988
+ * artifact. An interactive preview reads it to scroll to / highlight the
989
+ * focused field over a `paint`-ed canvas; the click direction is `fieldAt`.
881
990
  * Empty for backends that place no schema fields.
882
991
  */
883
992
  regions(): FieldRegion[];
@@ -916,6 +1025,17 @@ export class Quill {
916
1025
  * canonical shape.
917
1026
  */
918
1027
  static fromTree(tree: Map<string, Uint8Array>): Quill;
1028
+ /**
1029
+ * The resolved-value view of `doc` against this quill's schema — for every
1030
+ * declared field the value the render projection would use and the
1031
+ * `FieldSource` rung it came from (`"authored" | "default" | "zero"`), in
1032
+ * one call. The card body rides the `fields` map under the `$body` key.
1033
+ *
1034
+ * Value and provenance only: completeness and errors stay `validate`'s
1035
+ * (a consumer merges it with its own diagnostic producers regardless), and
1036
+ * schema guidance reads from `Quill.schema`.
1037
+ */
1038
+ resolve(doc: Document): Resolved;
919
1039
  /**
920
1040
  * Seed a starter composable `Card` of the given kind (carries `$kind`),
921
1041
  * layering an optional per-kind seed `overlay` over the schema-example
@@ -1035,6 +1155,15 @@ export class Quillmark {
1035
1155
  */
1036
1156
  export function exportMarkdown(rt: Content): string;
1037
1157
 
1158
+ /**
1159
+ * Serialize structured [`DocPathSeg`] segments back to the canonical path
1160
+ * string — the inverse of `parseDocPath`, for a consumer that builds a path
1161
+ * rather than reads one. Throws on a segment array the deserializer rejects,
1162
+ * and on an empty segment array (symmetric with `parseDocPath("")`, which
1163
+ * throws "empty path").
1164
+ */
1165
+ export function formatDocPath(segs: DocPathSeg[]): string;
1166
+
1038
1167
  /**
1039
1168
  * Import a markdown string to a canonical `Content` content — the pure,
1040
1169
  * document-free codec. Pair with `install(addr, importMarkdown(md))` to spell
@@ -1056,6 +1185,15 @@ export function init(): void;
1056
1185
  */
1057
1186
  export function mapPos(delta: Delta, pos: number, assoc: Assoc): number;
1058
1187
 
1188
+ /**
1189
+ * Parse a canonical document-model `Diagnostic.path`
1190
+ * (`cards.<kind>[<i>].<field>`, `main.body`, `recipients[0].name`) into its
1191
+ * structured [`DocPathSeg`] segments — the exported inverse of the engine's
1192
+ * one path serializer, so a consumer routes on segments instead of regexing
1193
+ * the string. Throws on a malformed path.
1194
+ */
1195
+ export function parseDocPath(path: string): DocPathSeg[];
1196
+
1059
1197
  /**
1060
1198
  * Rebase `markdown` onto a `base` content — the pure, document-free twin of
1061
1199
  * `revise`: cold-import + `diff_import`, returning the new `content` and the
@@ -5,5 +5,5 @@ import { __wbg_set_wasm } from "./wasm_bg.js";
5
5
  __wbg_set_wasm(wasm);
6
6
  wasm.__wbindgen_start();
7
7
  export {
8
- Document, LiveSession, Quill, Quillmark, exportMarkdown, importMarkdown, init, mapPos, rebase
8
+ Document, LiveSession, Quill, Quillmark, exportMarkdown, formatDocPath, importMarkdown, init, mapPos, parseDocPath, rebase
9
9
  } from "./wasm_bg.js";