@quillmark/wasm 0.109.1 → 0.110.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,53 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.110.0 - 2026-08-26
4
+
5
+ - **breaking** wasm: **the seam spells a container's `instance`, so the read
6
+ type can require it and every write lane reports an omission.** 0.109 gave
7
+ `Container` the discriminator that tells one container from an adjacent
8
+ sibling of identical shape, and left it an obligation no checker asked for;
9
+ the op lane got a requirement, the whole-`Content` lane — `overwrite`,
10
+ `CardInput.body`, the one a codec flattening a tree writes through — did not.
11
+ It could not: one encoder served storage and the bindings, writing the key
12
+ only where it was non-zero, and a field a read may omit is a field a write
13
+ cannot require. `serial::to_seam_value` is that encoder with every `instance`
14
+ spelled, and the bindings take it on every lane typed `Content`
15
+ (`reader.getContent{,At}`, `getStored` on a body, `importMarkdown`, `rebase`,
16
+ and the card wire behind `document.main` / `cards` / `card(i)` / `removeCard`
17
+ / `makeCard` / `seedMain` / `seedCard`). `ContentContainer.instance` becomes
18
+ required, `ContentContainerInput` is deleted, and `LineOp.setContainers` takes
19
+ `ContentContainer` — a net-smaller surface than 0.109's. The break is every
20
+ hand-built container literal, which is the one a type checker does report.
21
+ A checker still cannot report a `0` stamped on every run, which is the write
22
+ that welds them: `assignInstances` is the rule, not the type. Storage keeps
23
+ the omission and stored blobs re-encode byte for byte; so do the render lanes,
24
+ and so does what the seam types `unknown` (`getStored` on a field,
25
+ `PayloadItem.value`), which answers with the stored bytes verbatim. Content
26
+ parsed from a stored document is the one shape that needs a cast.
27
+ [Guide](docs/migrations/0.109-to-0.110.md)
28
+ - fix(core): **`Quill::validate` refuses only what the render floor refuses.**
29
+ Validation ran its own read-side type dispatch over the authored value while
30
+ the render door validated the *coerced* one, so five of seven types had values
31
+ that rendered and were fatally `validation::type_mismatch` at once — a bare
32
+ scalar for an `array`, `"3"` for an `integer`, `1` for a `boolean`, a
33
+ length-1 array for a `string` or `date`. `airmark`'s `usaf_memo@0.2` declares
34
+ `letterhead_caption` as an `array`, and the bare scalar a starter template
35
+ spells it with — a valid spelling of a one-element list — audited as fatally
36
+ invalid across every document seeded from it, each rendering correctly.
37
+
38
+ `validate_value` now conforms each document value through `conform_value` at
39
+ `Leniency::Render` before judging it, so a type has one predicate and **a
40
+ fatal `validation::*` diagnostic means the document does not render**.
41
+ Conforming runs per node, so one refused element no longer mistypes its
42
+ siblings: `counts: [true, "abc"]` under `integer` items is one mismatch, at
43
+ `counts[1]`. Two consequences for a consumer routing on codes: a value the
44
+ floor adopts raises nothing where it previously raised
45
+ `validation::type_mismatch`, and a bare scalar the floor stringifies into an
46
+ `enum` field is now domain-checked on that string, so `grade: 5` against
47
+ `values: [alpha, beta]` is `validation::enum_violation` where it was
48
+ previously silent — the diagnostic the render door already raised. Schema
49
+ literals (`example:`, `default:`) stay strict.
50
+
3
51
  ## v0.109.1 - 2026-08-24
4
52
 
5
53
  - fix(typst): **a `field-region` claim around inline content no longer widens
@@ -11,17 +59,6 @@
11
59
  `form-field`'s marker carries the same shape and drops the space too, rather
12
60
  than keep depending on the `box` that follows it.
13
61
 
14
- <!-- seed: commits since v0.109.0, confirm the entries above cover them, then delete this comment
15
- - docs: dense-prose pass over the branch
16
- - fix(typst): a marker's label is not a space in the inline flow
17
- - refactor(wasm): keep the weld rule internal, and pin what states it
18
- - docs: dense-prose pass over the branch
19
- - docs: classify 0.109's container break by consumer role
20
- - feat(wasm)!: make a written container path spell its instance
21
- - feat(content): name the rule a container's discriminator is minted against
22
- -->
23
-
24
-
25
62
  ## v0.109.0 - 2026-08-24
26
63
 
27
64
  - **breaking** content: **`Normalized` is the precondition the projections
@@ -100,33 +100,24 @@ export type ContentLineKind =
100
100
  * shape — two consecutive quotes, two consecutive lists — which contiguity
101
101
  * alone reads as one.
102
102
  *
103
- * **A writer owes it.** Give adjacent sibling runs of one shape distinct
104
- * values, or they arrive as one: a second list's items come back as
105
- * continuation paragraphs of the first, markers gone. Nothing reports that,
106
- * since the flat form cannot tell a boundary you meant from one you did not. A
107
- * codec flattening a tree stamps the field with `assignInstances` from
108
- * `@quillmark/wasm/runtime` rather than by hand. Any distinct pair of values
109
- * works; a write is canonicalized to `0`/`1`.
103
+ * **A writer owes a distinct value per adjacent sibling run**, not merely a
104
+ * value. Runs of one shape sharing one arrive as one: a second list's items come
105
+ * back as continuation paragraphs of the first, markers gone. The field is
106
+ * required, so a checker reports the omission; it cannot report a `0` stamped on
107
+ * both, which is the same write. A codec flattening a tree takes them from
108
+ * `assignInstances` in `@quillmark/wasm/runtime` rather than by hand. Any
109
+ * distinct pair works; a write is canonicalized to `0`/`1`.
110
110
  *
111
- * Reading is not the mirror of writing. The field is absent where it is `0`,
112
- * and it appears on pairs no writer had to spell. `1.` beside a list starting
113
- * at `3` differs by `start`, so those runs arrive apart with nothing written
114
- * and the canonical form spends a discriminator anyway, because Markdown reads
115
- * only a list's first number. */
116
- export type ContentContainer =
117
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
118
- | { container: "quote"; instance?: number }
119
- | { container: string; attrs: unknown; instance?: number };
120
-
121
- /** A container path on a lane that only ever carries host-built values:
122
- * `ContentContainer` with `instance` spelled out rather than defaulted. The
123
- * field that decides whether two adjacent runs weld cannot be omitted by
124
- * accident here. `assignInstances` returns this shape, and a path copied off a
125
- * line carries its own instance through — `{ ...c, instance: c.instance ?? 0 }`.
111
+ * Reading is not the mirror of writing. Every read spells the field, the `0` on
112
+ * a container with nothing to be told apart from included. A read also carries a
113
+ * discriminator on pairs no writer had to spell: `1.` beside a list starting at
114
+ * `3` differs by `start`, so those runs arrive apart with nothing written, and
115
+ * the canonical form spends one anyway because Markdown reads only a list's
116
+ * first number.
126
117
  *
127
- * `Content` itself keeps the optional field: it is a read shape as much as a
128
- * write one, and the wire omits a zero. */
129
- export type ContentContainerInput =
118
+ * Content parsed from a stored document is the one shape that arrives without
119
+ * it storage omits a zero — and needs a cast. */
120
+ export type ContentContainer =
130
121
  | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance: number }
131
122
  | { container: "quote"; instance: number }
132
123
  | { container: string; attrs: unknown; instance: number };
@@ -256,7 +247,7 @@ export type LineOp =
256
247
  | { op: "split"; at: number }
257
248
  | { op: "join"; line: number }
258
249
  | ({ op: "setKind"; line: number } & ContentLineKind)
259
- | { op: "setContainers"; line: number; containers: ContentContainerInput[] }
250
+ | { op: "setContainers"; line: number; containers: ContentContainer[] }
260
251
  | { op: "setContinues"; line: number; continues: boolean };
261
252
 
262
253
  /**
@@ -818,6 +809,10 @@ export class Document {
818
809
  * is at rest; one from the transport door may rest as authored until it is
819
810
  * conformed, and this read reports what is there. For the `Content` either
820
811
  * way use `reader.getContent`.
812
+ *
813
+ * The body arm is typed `Content` and answers in the seam form, spelling
814
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
815
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
821
816
  */
822
817
  getStored(addr: Addr | string): unknown;
823
818
  /**
@@ -608,6 +608,10 @@ export class Document {
608
608
  * is at rest; one from the transport door may rest as authored until it is
609
609
  * conformed, and this read reports what is there. For the `Content` either
610
610
  * way use `reader.getContent`.
611
+ *
612
+ * The body arm is typed `Content` and answers in the seam form, spelling
613
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
614
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
611
615
  * @param {Addr | string} addr
612
616
  * @returns {unknown}
613
617
  */
Binary file
@@ -100,33 +100,24 @@ export type ContentLineKind =
100
100
  * shape — two consecutive quotes, two consecutive lists — which contiguity
101
101
  * alone reads as one.
102
102
  *
103
- * **A writer owes it.** Give adjacent sibling runs of one shape distinct
104
- * values, or they arrive as one: a second list's items come back as
105
- * continuation paragraphs of the first, markers gone. Nothing reports that,
106
- * since the flat form cannot tell a boundary you meant from one you did not. A
107
- * codec flattening a tree stamps the field with `assignInstances` from
108
- * `@quillmark/wasm/runtime` rather than by hand. Any distinct pair of values
109
- * works; a write is canonicalized to `0`/`1`.
103
+ * **A writer owes a distinct value per adjacent sibling run**, not merely a
104
+ * value. Runs of one shape sharing one arrive as one: a second list's items come
105
+ * back as continuation paragraphs of the first, markers gone. The field is
106
+ * required, so a checker reports the omission; it cannot report a `0` stamped on
107
+ * both, which is the same write. A codec flattening a tree takes them from
108
+ * `assignInstances` in `@quillmark/wasm/runtime` rather than by hand. Any
109
+ * distinct pair works; a write is canonicalized to `0`/`1`.
110
110
  *
111
- * Reading is not the mirror of writing. The field is absent where it is `0`,
112
- * and it appears on pairs no writer had to spell. `1.` beside a list starting
113
- * at `3` differs by `start`, so those runs arrive apart with nothing written
114
- * and the canonical form spends a discriminator anyway, because Markdown reads
115
- * only a list's first number. */
116
- export type ContentContainer =
117
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
118
- | { container: "quote"; instance?: number }
119
- | { container: string; attrs: unknown; instance?: number };
120
-
121
- /** A container path on a lane that only ever carries host-built values:
122
- * `ContentContainer` with `instance` spelled out rather than defaulted. The
123
- * field that decides whether two adjacent runs weld cannot be omitted by
124
- * accident here. `assignInstances` returns this shape, and a path copied off a
125
- * line carries its own instance through — `{ ...c, instance: c.instance ?? 0 }`.
111
+ * Reading is not the mirror of writing. Every read spells the field, the `0` on
112
+ * a container with nothing to be told apart from included. A read also carries a
113
+ * discriminator on pairs no writer had to spell: `1.` beside a list starting at
114
+ * `3` differs by `start`, so those runs arrive apart with nothing written, and
115
+ * the canonical form spends one anyway because Markdown reads only a list's
116
+ * first number.
126
117
  *
127
- * `Content` itself keeps the optional field: it is a read shape as much as a
128
- * write one, and the wire omits a zero. */
129
- export type ContentContainerInput =
118
+ * Content parsed from a stored document is the one shape that arrives without
119
+ * it storage omits a zero — and needs a cast. */
120
+ export type ContentContainer =
130
121
  | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance: number }
131
122
  | { container: "quote"; instance: number }
132
123
  | { container: string; attrs: unknown; instance: number };
@@ -256,7 +247,7 @@ export type LineOp =
256
247
  | { op: "split"; at: number }
257
248
  | { op: "join"; line: number }
258
249
  | ({ op: "setKind"; line: number } & ContentLineKind)
259
- | { op: "setContainers"; line: number; containers: ContentContainerInput[] }
250
+ | { op: "setContainers"; line: number; containers: ContentContainer[] }
260
251
  | { op: "setContinues"; line: number; continues: boolean };
261
252
 
262
253
  /**
@@ -818,6 +809,10 @@ export class Document {
818
809
  * is at rest; one from the transport door may rest as authored until it is
819
810
  * conformed, and this read reports what is there. For the `Content` either
820
811
  * way use `reader.getContent`.
812
+ *
813
+ * The body arm is typed `Content` and answers in the seam form, spelling
814
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
815
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
821
816
  */
822
817
  getStored(addr: Addr | string): unknown;
823
818
  /**
@@ -608,6 +608,10 @@ export class Document {
608
608
  * is at rest; one from the transport door may rest as authored until it is
609
609
  * conformed, and this read reports what is there. For the `Content` either
610
610
  * way use `reader.getContent`.
611
+ *
612
+ * The body arm is typed `Content` and answers in the seam form, spelling
613
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
614
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
611
615
  * @param {Addr | string} addr
612
616
  * @returns {unknown}
613
617
  */
Binary file
package/core/wasm.d.ts CHANGED
@@ -100,33 +100,24 @@ export type ContentLineKind =
100
100
  * shape — two consecutive quotes, two consecutive lists — which contiguity
101
101
  * alone reads as one.
102
102
  *
103
- * **A writer owes it.** Give adjacent sibling runs of one shape distinct
104
- * values, or they arrive as one: a second list's items come back as
105
- * continuation paragraphs of the first, markers gone. Nothing reports that,
106
- * since the flat form cannot tell a boundary you meant from one you did not. A
107
- * codec flattening a tree stamps the field with `assignInstances` from
108
- * `@quillmark/wasm/runtime` rather than by hand. Any distinct pair of values
109
- * works; a write is canonicalized to `0`/`1`.
103
+ * **A writer owes a distinct value per adjacent sibling run**, not merely a
104
+ * value. Runs of one shape sharing one arrive as one: a second list's items come
105
+ * back as continuation paragraphs of the first, markers gone. The field is
106
+ * required, so a checker reports the omission; it cannot report a `0` stamped on
107
+ * both, which is the same write. A codec flattening a tree takes them from
108
+ * `assignInstances` in `@quillmark/wasm/runtime` rather than by hand. Any
109
+ * distinct pair works; a write is canonicalized to `0`/`1`.
110
110
  *
111
- * Reading is not the mirror of writing. The field is absent where it is `0`,
112
- * and it appears on pairs no writer had to spell. `1.` beside a list starting
113
- * at `3` differs by `start`, so those runs arrive apart with nothing written
114
- * and the canonical form spends a discriminator anyway, because Markdown reads
115
- * only a list's first number. */
116
- export type ContentContainer =
117
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance?: number }
118
- | { container: "quote"; instance?: number }
119
- | { container: string; attrs: unknown; instance?: number };
120
-
121
- /** A container path on a lane that only ever carries host-built values:
122
- * `ContentContainer` with `instance` spelled out rather than defaulted. The
123
- * field that decides whether two adjacent runs weld cannot be omitted by
124
- * accident here. `assignInstances` returns this shape, and a path copied off a
125
- * line carries its own instance through — `{ ...c, instance: c.instance ?? 0 }`.
111
+ * Reading is not the mirror of writing. Every read spells the field, the `0` on
112
+ * a container with nothing to be told apart from included. A read also carries a
113
+ * discriminator on pairs no writer had to spell: `1.` beside a list starting at
114
+ * `3` differs by `start`, so those runs arrive apart with nothing written, and
115
+ * the canonical form spends one anyway because Markdown reads only a list's
116
+ * first number.
126
117
  *
127
- * `Content` itself keeps the optional field: it is a read shape as much as a
128
- * write one, and the wire omits a zero. */
129
- export type ContentContainerInput =
118
+ * Content parsed from a stored document is the one shape that arrives without
119
+ * it storage omits a zero — and needs a cast. */
120
+ export type ContentContainer =
130
121
  | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance: number }
131
122
  | { container: "quote"; instance: number }
132
123
  | { container: string; attrs: unknown; instance: number };
@@ -256,7 +247,7 @@ export type LineOp =
256
247
  | { op: "split"; at: number }
257
248
  | { op: "join"; line: number }
258
249
  | ({ op: "setKind"; line: number } & ContentLineKind)
259
- | { op: "setContainers"; line: number; containers: ContentContainerInput[] }
250
+ | { op: "setContainers"; line: number; containers: ContentContainer[] }
260
251
  | { op: "setContinues"; line: number; continues: boolean };
261
252
 
262
253
  /**
@@ -634,6 +625,10 @@ export class Document {
634
625
  * is at rest; one from the transport door may rest as authored until it is
635
626
  * conformed, and this read reports what is there. For the `Content` either
636
627
  * way use `reader.getContent`.
628
+ *
629
+ * The body arm is typed `Content` and answers in the seam form, spelling
630
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
631
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
637
632
  */
638
633
  getStored(addr: Addr | string): unknown;
639
634
  /**
package/core/wasm.js CHANGED
@@ -608,6 +608,10 @@ export class Document {
608
608
  * is at rest; one from the transport door may rest as authored until it is
609
609
  * conformed, and this read reports what is there. For the `Content` either
610
610
  * way use `reader.getContent`.
611
+ *
612
+ * The body arm is typed `Content` and answers in the seam form, spelling
613
+ * every `ContentContainer.instance`. A field arm echoes the stored bytes,
614
+ * which omit a zero: verbatim is the contract, and is why it is `unknown`.
611
615
  * @param {Addr | string} addr
612
616
  * @returns {unknown}
613
617
  */
package/core/wasm_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.109.1",
3
+ "version": "0.110.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -130,7 +130,6 @@ export type {
130
130
  ContentLine,
131
131
  ContentLineKind,
132
132
  ContentContainer,
133
- ContentContainerInput,
134
133
  ContentMark,
135
134
  ContentIsland,
136
135
  TableProps,
@@ -205,8 +204,7 @@ import type {
205
204
  ImageProps,
206
205
  ContentMark,
207
206
  ContentLine,
208
- ContentContainer,
209
- ContentContainerInput
207
+ ContentContainer
210
208
  } from '../core/wasm.js';
211
209
 
212
210
  /** Narrow a {@link ContentIsland} to the pinned `table` arm (`props: TableProps`). */
@@ -247,7 +245,7 @@ export declare function isListItemContainer(
247
245
  ordered: boolean;
248
246
  start: number;
249
247
  ordinal: number;
250
- instance?: number;
248
+ instance: number;
251
249
  };
252
250
 
253
251
  // The guards above answer "is this arm X". These four answer "is this a value
@@ -280,11 +278,11 @@ export declare function isUnknownIsland(
280
278
  island: ContentIsland
281
279
  ): island is ContentIsland & { type: string; props: unknown };
282
280
 
283
- // `ContentContainer.instance` is a field a writer owes and, outside
284
- // `ContentContainerInput`, no checker asks for. Adjacent runs of one shape that
285
- // share it arrive welded. Nothing reports that: the flat `containers` form
286
- // cannot tell it from one container spanning two paragraphs. This carries the
287
- // rule a codec would otherwise re-derive.
281
+ // `ContentContainer.instance` is required, so a checker reports an omission; it
282
+ // cannot report a `0` stamped on every run, which is the same write. Adjacent
283
+ // runs of one shape sharing a value arrive welded, and nothing reports that
284
+ // either: the flat `containers` form cannot tell it from one container spanning
285
+ // two paragraphs. This carries the rule a codec would otherwise re-derive.
288
286
 
289
287
  /**
290
288
  * Stamp `instance` across one parent's blocks at one depth, in document order,
@@ -313,7 +311,7 @@ export declare function isUnknownIsland(
313
311
  */
314
312
  export declare function assignInstances(
315
313
  runs: (ContentContainer | null)[]
316
- ): (ContentContainerInput | null)[];
314
+ ): (ContentContainer | null)[];
317
315
 
318
316
  // The backend-neutral render contract, defined here rather than re-exported from
319
317
  // one private backend because no single backend owns the canonical API's types.
@@ -553,7 +553,7 @@ function weldsWith(a, b) {
553
553
 
554
554
  /**
555
555
  * @param {(import('../core/wasm.js').ContentContainer | null)[]} runs
556
- * @returns {(import('../core/wasm.js').ContentContainerInput | null)[]}
556
+ * @returns {(import('../core/wasm.js').ContentContainer | null)[]}
557
557
  */
558
558
  export function assignInstances(runs) {
559
559
  let prev = null;