@quillmark/wasm 0.123.1 → 0.124.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/render/wasm.js CHANGED
@@ -26,11 +26,14 @@ export class Document {
26
26
  * leaves the document untouched. Field errors throw the same per-field
27
27
  * bundle as [`commitFields`](Self::commit_fields); an invalid kind or body,
28
28
  * or a bad position, throws a single-entry bundle keyed `$kind` / `$body`.
29
+ * Returns the body import's `parse::dropped_construct` warnings, anchored
30
+ * at the placed card's body.
29
31
  * @param {Quill} quill
30
32
  * @param {string} kind
31
33
  * @param {Record<string, unknown>} [fields]
32
34
  * @param {string} [body]
33
35
  * @param {number} [at]
36
+ * @returns {Diagnostic[]}
34
37
  */
35
38
  _addCard(quill, kind, fields, body, at) {
36
39
  try {
@@ -43,9 +46,11 @@ export class Document {
43
46
  wasm.document__addCard(retptr, this.__wbg_ptr, quill.__wbg_ptr, ptr0, len0, isLikeNone(fields) ? 0 : addHeapObject(fields), ptr1, len1, isLikeNone(at) ? Number.MAX_SAFE_INTEGER : (at) >>> 0);
44
47
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
45
48
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
46
- if (r1) {
47
- throw takeObject(r0);
49
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
50
+ if (r2) {
51
+ throw takeObject(r1);
48
52
  }
53
+ return takeObject(r0);
49
54
  } finally {
50
55
  wasm.__wbindgen_add_to_stack_pointer(16);
51
56
  }
@@ -228,14 +233,40 @@ export class Document {
228
233
  wasm.__wbindgen_add_to_stack_pointer(16);
229
234
  }
230
235
  }
236
+ /**
237
+ * The ABI under `writer.reviseDocument`: a whole-document revise, then
238
+ * conform against `quill`. Throws when `markdown` declares a `$quill` this quill
239
+ * does not answer to, before any mutation.
240
+ * @param {Quill} quill
241
+ * @param {string} markdown
242
+ * @returns {DocumentRevised}
243
+ */
244
+ _reviseDocument(quill, markdown) {
245
+ try {
246
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
247
+ _assertClass(quill, Quill);
248
+ const ptr0 = passStringToWasm0(markdown, wasm.__wbindgen_export, wasm.__wbindgen_export2);
249
+ const len0 = WASM_VECTOR_LEN;
250
+ wasm.document__reviseDocument(retptr, this.__wbg_ptr, quill.__wbg_ptr, ptr0, len0);
251
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
252
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
253
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
254
+ if (r2) {
255
+ throw takeObject(r1);
256
+ }
257
+ return takeObject(r0);
258
+ } finally {
259
+ wasm.__wbindgen_add_to_stack_pointer(16);
260
+ }
261
+ }
231
262
  /**
232
263
  * Revise the content field at `addr` from authored text, typed *and*
233
264
  * anchor-preserving: the ABI under `writer.reviseField`. Surviving anchors
234
265
  * rebase as in [`revise`](Self::revise), then the diffed result is
235
266
  * schema-conformed, so a `richtext(inline)` field rejects a multi-block
236
- * result with `edit::field_not_inline`. Returns the text `Delta`. The codec
237
- * is the declared type's: `richtext` diffs markdown, `plaintext` literal
238
- * text.
267
+ * result with `edit::field_not_inline`. Returns the `Revised` receipt, its
268
+ * warnings anchored at the field. The codec is the declared type's:
269
+ * `richtext` diffs markdown, `plaintext` literal text, which drops nothing.
239
270
  *
240
271
  * `addr` must name a field; a body address throws, since a body carries no
241
272
  * field schema (use [`revise`](Self::revise)). An undeclared name throws
@@ -243,7 +274,7 @@ export class Document {
243
274
  * @param {Quill} quill
244
275
  * @param {Addr | string} addr
245
276
  * @param {string} text
246
- * @returns {Delta}
277
+ * @returns {Revised}
247
278
  */
248
279
  _reviseField(quill, addr, text) {
249
280
  try {
@@ -861,16 +892,18 @@ export class Document {
861
892
  /**
862
893
  * **Revise** the richtext value at `addr` from a markdown string: the
863
894
  * default write path. Imports the markdown, diffs it against the current
864
- * value, rebases surviving identity anchors, and returns the text `Delta` an
865
- * editor bridge maps its own positions through (`mapPos`). An absent
866
- * `addr.field` targets the body, an absent `addr.card` the main card; an
867
- * absent field cold-imports from empty.
895
+ * value, rebases surviving identity anchors, and returns the `Revised`
896
+ * receipt: the text `delta` an editor bridge maps its own positions through
897
+ * (`mapPos`), and a `parse::dropped_construct` warning per construct the
898
+ * import dropped, its `path` the address written. An absent `addr.field`
899
+ * targets the body, an absent `addr.card` the main card; an absent field
900
+ * cold-imports from empty.
868
901
  *
869
902
  * Throws on an out-of-range card, a malformed field name, a present
870
903
  * non-content field value, or an over-nested markdown input.
871
904
  * @param {Addr | string} addr
872
905
  * @param {string} markdown
873
- * @returns {Delta}
906
+ * @returns {Revised}
874
907
  */
875
908
  revise(addr, markdown) {
876
909
  try {
@@ -1051,8 +1084,33 @@ export class Document {
1051
1084
  }
1052
1085
  }
1053
1086
  /**
1054
- * Emit canonical Quillmark Markdown. Round-trip safe: re-parsing the
1055
- * result produces a `Document` equal to `self` by value and by type.
1087
+ * `toMarkdown` with each prose anchor of every body and content field
1088
+ * spelled read-only at its start where its line can hold the tag, and
1089
+ * listed with its field's path and line either way. An import drops every
1090
+ * tag, so `writer.reviseDocument` with the markdown keeps the anchors it keeps
1091
+ * with `toMarkdown`'s.
1092
+ * @returns {AnnotatedMarkdown}
1093
+ */
1094
+ toAnnotatedMarkdown() {
1095
+ try {
1096
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1097
+ wasm.document_toAnnotatedMarkdown(retptr, this.__wbg_ptr);
1098
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1099
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1100
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
1101
+ if (r2) {
1102
+ throw takeObject(r1);
1103
+ }
1104
+ return takeObject(r0);
1105
+ } finally {
1106
+ wasm.__wbindgen_add_to_stack_pointer(16);
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Emit canonical Quillmark Markdown. Re-parsing the result produces a
1111
+ * `Document` equal to `self` by value and by type, but for content: a body
1112
+ * or content field holds what its markdown spells, so its anchors drop and
1113
+ * its island ids re-mint. `toStored` keeps both.
1056
1114
  * @returns {string}
1057
1115
  */
1058
1116
  toMarkdown() {
@@ -1098,7 +1156,7 @@ export class Document {
1098
1156
  * The non-fatal diagnostics of the load that produced this document: parse
1099
1157
  * warnings, plus `conform::*` warnings when it came through `quill.parse`.
1100
1158
  * Session state, not document value: `equals` and the storage DTO exclude
1101
- * it, and `fromStored` / `loadStored` clear it.
1159
+ * it, and `fromStored` / `loadStored` / `writer.reviseDocument` clear it.
1102
1160
  * @returns {Diagnostic[]}
1103
1161
  */
1104
1162
  get warnings() {
@@ -1763,8 +1821,8 @@ export class Quill {
1763
1821
  return takeObject(ret);
1764
1822
  }
1765
1823
  /**
1766
- * Validate `doc` against this quill's schema, returning every diagnostic
1767
- * (empty when the document is valid). Forwards the canonical
1824
+ * Validate `doc` against this quill's schema, returning every diagnostic:
1825
+ * an error blocks a render, a warning does not. Forwards the canonical
1768
1826
  * `validation::*` diagnostics the engine emits.
1769
1827
  * @param {Document} doc
1770
1828
  * @returns {Diagnostic[]}
@@ -1859,9 +1917,11 @@ export class Quillmark {
1859
1917
  }
1860
1918
  /**
1861
1919
  * Render `doc` against `quill` in one shot: `open` + `LiveSession.render`,
1862
- * with `doc.warnings`, then every `quill.validate` warning, ahead of the
1863
- * compile's in `warnings`. An unset `output_format` falls back to the
1864
- * backend's first supported format. `today` reads as on `open`.
1920
+ * with every `quill.validate` warning but `validation::declined_construct`,
1921
+ * which the compile raises as `backend::declined_construct`, ahead of the
1922
+ * compile's in `warnings`; the load's stay on `doc.warnings`. An unset
1923
+ * `output_format` falls back to the backend's first supported format.
1924
+ * `today` reads as on `open`.
1865
1925
  * @param {Quill} quill
1866
1926
  * @param {Document} doc
1867
1927
  * @param {RenderOptions | null} [opts]
@@ -1983,7 +2043,7 @@ export function formatDiagnostic(diagnostic) {
1983
2043
  }
1984
2044
 
1985
2045
  /**
1986
- * Serialize structured [`DocPathSeg`] segments back to the canonical path
2046
+ * Serialize structured `DocPathSeg` segments back to the canonical path
1987
2047
  * string: the inverse of `parseDocPath`. Throws on a segment array the
1988
2048
  * deserializer rejects, and on an empty one.
1989
2049
  * @param {DocPathSeg[]} segs
@@ -2015,11 +2075,13 @@ export function formatDocPath(segs) {
2015
2075
  }
2016
2076
 
2017
2077
  /**
2018
- * Import a markdown string to canonical `Content`: the pure, document-free
2019
- * codec. `overwrite(addr, importMarkdown(md))` spells the cold, anchor-losing
2020
- * write; prefer `revise` for edit semantics. Throws on an over-nested input.
2078
+ * Import a markdown string: the pure, document-free codec. Returns the
2079
+ * canonical `content` and a `parse::dropped_construct` warning per construct
2080
+ * the import dropped, with no `path`. `overwrite(addr, importMarkdown(md).content)`
2081
+ * spells the cold, anchor-losing write; prefer `revise` for edit semantics.
2082
+ * Throws on an over-nested input.
2021
2083
  * @param {string} markdown
2022
- * @returns {Content}
2084
+ * @returns {Imported}
2023
2085
  */
2024
2086
  export function importMarkdown(markdown) {
2025
2087
  try {
@@ -2152,7 +2214,7 @@ export function mapPos(delta, pos, assoc) {
2152
2214
 
2153
2215
  /**
2154
2216
  * Parse a canonical document-model `Diagnostic.path` (`cards.<kind>[<i>].<field>`,
2155
- * `main.body`, `recipients[0].name`) into structured [`DocPathSeg`] segments, so
2217
+ * `main.body`, `recipients[0].name`) into structured `DocPathSeg` segments, so
2156
2218
  * a consumer routes on segments instead of regexing the string. Throws on a
2157
2219
  * malformed path.
2158
2220
  * @param {string} path
@@ -2178,12 +2240,13 @@ export function parseDocPath(path) {
2178
2240
 
2179
2241
  /**
2180
2242
  * Rebase `markdown` onto a `base` content: the document-free twin of `revise`,
2181
- * returning the new `content` and the text `delta` (offsets are USV indices into
2182
- * `Content.text`, surviving anchors rebased). Throws on an over-nested markdown
2183
- * input or a non-content `base`.
2243
+ * returning the new `content`, the text `delta` (offsets are USV indices into
2244
+ * `Content.text`, surviving anchors rebased), and the import's
2245
+ * `parse::dropped_construct` warnings, with no `path`. Throws on an
2246
+ * over-nested markdown input or a non-content `base`.
2184
2247
  * @param {Content} base
2185
2248
  * @param {string} markdown
2186
- * @returns {{ content: Content; delta: Delta }}
2249
+ * @returns {{ content: Content; delta: Delta; warnings: Diagnostic[] }}
2187
2250
  */
2188
2251
  export function rebase(base, markdown) {
2189
2252
  try {
@@ -2352,10 +2415,6 @@ function __wbg_get_imports() {
2352
2415
  const ret = getObject(arg0).getMonth();
2353
2416
  return ret;
2354
2417
  },
2355
- __wbg_getOwnPropertyNames_f8299644de14134c: function(arg0) {
2356
- const ret = Object.getOwnPropertyNames(getObject(arg0));
2357
- return addHeapObject(ret);
2358
- },
2359
2418
  __wbg_getPrototypeOf_4e7d85b5bf38d546: function() { return handleError(function (arg0) {
2360
2419
  const ret = Reflect.getPrototypeOf(getObject(arg0));
2361
2420
  return addHeapObject(ret);
@@ -2396,6 +2455,16 @@ function __wbg_get_imports() {
2396
2455
  const ret = result;
2397
2456
  return ret;
2398
2457
  },
2458
+ __wbg_instanceof_Error_80a725f81f2e102d: function(arg0) {
2459
+ let result;
2460
+ try {
2461
+ result = getObject(arg0) instanceof Error;
2462
+ } catch (_) {
2463
+ result = false;
2464
+ }
2465
+ const ret = result;
2466
+ return ret;
2467
+ },
2399
2468
  __wbg_instanceof_Map_b2611749102d7ba3: function(arg0) {
2400
2469
  let result;
2401
2470
  try {
@@ -2446,6 +2515,10 @@ function __wbg_get_imports() {
2446
2515
  const ret = getObject(arg0).length;
2447
2516
  return ret;
2448
2517
  },
2518
+ __wbg_message_5f8387f0c32b90a7: function(arg0) {
2519
+ const ret = getObject(arg0).message;
2520
+ return addHeapObject(ret);
2521
+ },
2449
2522
  __wbg_new_0_72d020f0c63443d4: function() {
2450
2523
  const ret = new Date();
2451
2524
  return addHeapObject(ret);
@@ -2490,6 +2563,10 @@ function __wbg_get_imports() {
2490
2563
  const ret = getObject(arg0).next();
2491
2564
  return addHeapObject(ret);
2492
2565
  }, arguments); },
2566
+ __wbg_ownKeys_7f46b96dc7311fe8: function() { return handleError(function (arg0) {
2567
+ const ret = Reflect.ownKeys(getObject(arg0));
2568
+ return addHeapObject(ret);
2569
+ }, arguments); },
2493
2570
  __wbg_prototypesetcall_bc27214492979395: function(arg0, arg1, arg2) {
2494
2571
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), getObject(arg2));
2495
2572
  },
Binary file
@@ -11,6 +11,7 @@ export const document__commitFields: (a: number, b: number, c: number, d: number
11
11
  export const document__readerGet: (a: number, b: number, c: number, d: number) => void;
12
12
  export const document__readerGetContent: (a: number, b: number, c: number, d: number) => void;
13
13
  export const document__readerGetContentAt: (a: number, b: number, c: number, d: number, e: number) => void;
14
+ export const document__reviseDocument: (a: number, b: number, c: number, d: number, e: number) => void;
14
15
  export const document__reviseField: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
15
16
  export const document_applyChange: (a: number, b: number, c: number, d: number) => void;
16
17
  export const document_bodyMarkdown: (a: number, b: number, c: number) => void;
@@ -47,6 +48,7 @@ export const document_storeExt: (a: number, b: number, c: number, d: number) =>
47
48
  export const document_storeField: (a: number, b: number, c: number, d: number) => void;
48
49
  export const document_storeFields: (a: number, b: number, c: number, d: number) => void;
49
50
  export const document_storeSeedOverlay: (a: number, b: number, c: number, d: number, e: number) => void;
51
+ export const document_toAnnotatedMarkdown: (a: number, b: number) => void;
50
52
  export const document_toMarkdown: (a: number, b: number) => void;
51
53
  export const document_toStored: (a: number, b: number) => void;
52
54
  export const document_warnings: (a: number, b: number) => void;
@@ -117,7 +117,7 @@ export type {
117
117
  } from '../core/wasm.js';
118
118
 
119
119
  // Content edit vocabulary: the op-grained content model `Document`'s methods
120
- // speak (`applyChange(addr, bundle)`, `overwrite(addr, rt)`, `revise(…) => Delta`).
120
+ // speak (`applyChange(addr, bundle)`, `overwrite(addr, rt)`, `revise(…) => Revised`).
121
121
  // `ContentLineKind` is the shared half of `ContentLine` and `setKind`, and
122
122
  // `ContentMarkKind` of `ContentMark` and a mark op's `add` / `remove`: lifting a
123
123
  // read value's kind whole — destructure off the envelope, spread the rest into
@@ -139,6 +139,10 @@ export type {
139
139
  Addr,
140
140
  CardAddr,
141
141
  Delta,
142
+ Revised,
143
+ DocumentRevised,
144
+ AnnotatedMarkdown,
145
+ Imported,
142
146
  Assoc,
143
147
  IslandOp,
144
148
  LineOp,
@@ -410,11 +414,12 @@ export declare class Engine {
410
414
  * synchronously before the first await, so the caller may `free()` them as
411
415
  * soon as this call returns.
412
416
  *
413
- * {@link RenderResult.warnings} carries the whole pipeline's, in order:
414
- * `doc.warnings` (parse and `conform::*`), every `quill.validate(doc)`
415
- * warning, then the compile's own. A {@link LiveSession} outlives the
416
- * document it opened from, so {@link LiveSession.render} carries the
417
- * compile's alone: read `doc.warnings` and `quill.validate(doc)` beside it.
417
+ * {@link RenderResult.warnings} carries every `quill.validate(doc)` warning
418
+ * but `validation::declined_construct`, which the compile raises as
419
+ * `backend::declined_construct`, then the compile's own. The load's (parse
420
+ * and `conform::*`) stay on `doc.warnings`, and a revise's on its receipt.
421
+ * A {@link LiveSession} carries the compile's alone: read
422
+ * `quill.validate(doc)` beside it.
418
423
  *
419
424
  * `today` reads as on {@link open}. An `options` key outside
420
425
  * {@link RenderOptions} rejects, `today` among them.
@@ -591,7 +596,7 @@ declare module '../core/wasm.js' {
591
596
  }
592
597
  }
593
598
 
594
- import type { Addr, Content, Delta, PathStep, Resolved } from '../core/wasm.js';
599
+ import type { Addr, Content, DocumentRevised, PathStep, Resolved, Revised } from '../core/wasm.js';
595
600
 
596
601
  /**
597
602
  * A `Document` bound to its `Quill` for typed writes, from {@link Quill.writer}.
@@ -621,30 +626,44 @@ export declare class DocumentWriter {
621
626
  */
622
627
  setAll(fields: Record<string, unknown>): void;
623
628
  /**
624
- * Revise the main body from markdown; anchors rebase. Returns the text
625
- * `Delta`: a body carries no field schema to type against, so this is the
626
- * content lane's `revise` reached through the writer.
629
+ * Revise the main body from markdown; anchors rebase. Returns the
630
+ * `Revised` receipt, its warnings at `main.body`: a body carries no field
631
+ * schema to type against, so this is the content lane's `revise` reached
632
+ * through the writer.
627
633
  */
628
- reviseBody(markdown: string): Delta;
634
+ reviseBody(markdown: string): Revised;
629
635
  /**
630
636
  * Revise the content main-card field `name` from authored text: typed *and*
631
637
  * anchor-preserving. Surviving anchors rebase, then the diffed result is
632
638
  * schema-conformed (`richtext(inline)` rejects a multi-block result). Throws
633
- * `UnknownField` for a name the schema does not declare. Returns the `Delta`.
639
+ * `UnknownField` for a name the schema does not declare. Returns the
640
+ * `Revised` receipt, its warnings at the field.
634
641
  *
635
642
  * The codec comes from the declared type: `richtext` diffs markdown, while
636
643
  * `plaintext` diffs the literal text and never imports markdown, so a
637
644
  * byte-identical revise of a value carrying escapes is a byte no-op.
638
645
  */
639
- reviseField(name: string, text: string): Delta;
646
+ reviseField(name: string, text: string): Revised;
647
+ /**
648
+ * Replace the bound document with `markdown`, then conform it. Composable
649
+ * cards align to the stored ones by `$kind` and text similarity, an aligned
650
+ * card's body and stored content fields revise, their anchors rebasing as
651
+ * {@link reviseBody}'s do, and every declared content field lands at rest.
652
+ * Returns the `DocumentRevised` receipt, its `warnings` ending with the
653
+ * `conform::*` ones, and clears `doc.warnings`. Throws when `markdown`
654
+ * declares a `$quill` this quill does not answer to, or does not parse,
655
+ * leaving the document unchanged.
656
+ */
657
+ reviseDocument(markdown: string): DocumentRevised;
640
658
  /**
641
659
  * Build a composable card of `kind`, typed-commit `fields` onto it, set its
642
660
  * body from optional markdown, and place it. `at` omitted appends, a number
643
661
  * inserts at that index. Transactional: a rejected field (throwing a per-field
644
662
  * diagnostic bundle) or an invalid kind, body, or position leaves the document
645
- * untouched.
663
+ * untouched. Returns the body import's `parse::dropped_construct` warnings,
664
+ * anchored at the placed card's body.
646
665
  */
647
- addCard(kind: string, fields?: Record<string, unknown>, body?: string, at?: number): void;
666
+ addCard(kind: string, fields?: Record<string, unknown>, body?: string, at?: number): Diagnostic[];
648
667
  /** Remove the composable card at `index`, returning it (or `undefined`). */
649
668
  removeCard(index: number): Card | undefined;
650
669
  /**
@@ -674,13 +693,16 @@ export declare class CardWriter {
674
693
  readonly kind: string;
675
694
  set(name: string, value: unknown): void;
676
695
  setAll(fields: Record<string, unknown>): void;
677
- /** Revise this card's body from markdown (edit semantics), returning the text `Delta`. */
678
- reviseBody(markdown: string): Delta;
696
+ /**
697
+ * Revise this card's body from markdown (edit semantics), returning the
698
+ * `Revised` receipt, its warnings at the card's body.
699
+ */
700
+ reviseBody(markdown: string): Revised;
679
701
  /**
680
702
  * The card twin of {@link DocumentWriter.reviseField}. Throws `UnknownField`
681
703
  * for an undeclared name and `IndexOutOfRange` for a bad bound index.
682
704
  */
683
- reviseField(name: string, text: string): Delta;
705
+ reviseField(name: string, text: string): Revised;
684
706
  }
685
707
 
686
708
  /**
@@ -162,11 +162,11 @@ function quillmarkError(code, message, hint) {
162
162
  }
163
163
 
164
164
  /**
165
- * Throw on options that are not a plain object, or on an own key of them
166
- * outside `known`. The JS twin of the binding's `reject_unknown_keys`, in the
167
- * uncoded shape it throws: a misspelled optional key would otherwise read as
168
- * absent, and a read reaches a key up the prototype chain that no own-key walk
169
- * sees.
165
+ * Throw on options that are not a plain object, or on a key of them, or of a
166
+ * null-prototype object they inherit from, outside `known`. The JS twin of the
167
+ * binding's `reject_unknown_keys`, in the uncoded shape it throws: a misspelled
168
+ * optional key would otherwise read as absent, and a read reaches a key up the
169
+ * prototype chain that no own-key walk sees.
170
170
  * @param {unknown} options
171
171
  * @param {string[]} known
172
172
  * @param {string} what the options' name, for the message
@@ -174,8 +174,28 @@ function quillmarkError(code, message, hint) {
174
174
  */
175
175
  function rejectUnknownKeys(options, known, what) {
176
176
  if (options == null) return;
177
- const proto = Object.getPrototypeOf(options);
178
177
  let message;
178
+ try {
179
+ message = unknownKeyRefusal(options, known, what);
180
+ } catch (e) {
181
+ const cause = e instanceof Error ? e.message : String(e);
182
+ message = `${what} must be a plain object, not one whose read throws: ${cause}`;
183
+ }
184
+ if (message === undefined) return;
185
+ const err = /** @type {any} */ (new Error(message));
186
+ err.diagnostics = [{ severity: 'error', message }];
187
+ throw err;
188
+ }
189
+
190
+ /**
191
+ * The refusal `rejectUnknownKeys` throws, if any. A `Proxy` trap may throw.
192
+ * @param {unknown} options
193
+ * @param {string[]} known
194
+ * @param {string} what
195
+ * @returns {string | undefined}
196
+ */
197
+ function unknownKeyRefusal(options, known, what) {
198
+ const proto = Object.getPrototypeOf(options);
179
199
  if (proto !== null && Object.getPrototypeOf(proto) !== null) {
180
200
  const passed =
181
201
  options instanceof Map
@@ -183,15 +203,16 @@ function rejectUnknownKeys(options, known, what) {
183
203
  : typeof options === 'object'
184
204
  ? 'one inheriting from another object'
185
205
  : `a ${typeof options}`;
186
- message = `${what} must be a plain object, not ${passed}`;
187
- } else {
188
- const key = Object.getOwnPropertyNames(options).find((k) => !known.includes(k));
189
- if (key === undefined) return;
190
- message = `${what} have unknown key \`${key}\`; ${what} take only \`${known.join('`, `')}\``;
206
+ return `${what} must be a plain object, not ${passed}`;
191
207
  }
192
- const err = /** @type {any} */ (new Error(message));
193
- err.diagnostics = [{ severity: 'error', message }];
194
- throw err;
208
+ const keys = Object.getOwnPropertyNames(options);
209
+ if (proto !== null) {
210
+ const builtin = Object.getOwnPropertyNames(Object.prototype);
211
+ keys.push(...Object.getOwnPropertyNames(proto).filter((k) => !builtin.includes(k)));
212
+ }
213
+ const key = keys.find((k) => !known.includes(k));
214
+ if (key === undefined) return undefined;
215
+ return `${what} have unknown key \`${key}\`; ${what} take only \`${known.join('`, `')}\``;
195
216
  }
196
217
 
197
218
  // These checks deliver the ERROR, not the rejection, at the seams that cross
@@ -263,9 +284,10 @@ function requireLocalQuill(quill, method) {
263
284
  // `runtime.test.js` § "container run boundaries" stamps a pair and re-imports
264
285
  // it, where `Content::normalize` re-mints against the Rust predicate. A tag the
265
286
  // table omits welds with nothing, so `tests/known_names_drift.rs` pins that
266
- // every container has an entry.
287
+ // every container has an entry. An entry of `'*'` welds on the whole bag: an
288
+ // element's attributes are open, its name among them.
267
289
 
268
- const WELD_KEYS = { list_item: ['ordered'], quote: [] };
290
+ const WELD_KEYS = { list_item: ['ordered'], quote: [], element: '*' };
269
291
 
270
292
  /**
271
293
  * @param {import('../core/wasm.js').ContentContainer} a
@@ -278,7 +300,20 @@ function weldsWith(a, b) {
278
300
  // `hasOwn`, so a tag colliding with an `Object.prototype` member answers
279
301
  // `false` rather than reaching a function.
280
302
  if (!Object.hasOwn(WELD_KEYS, a.container)) return false;
281
- return WELD_KEYS[a.container].every((k) => a.attrs?.[k] === b.attrs?.[k]);
303
+ const keys = WELD_KEYS[a.container];
304
+ if (keys === '*') return sameBag(a.attrs, b.attrs);
305
+ return keys.every((k) => a.attrs?.[k] === b.attrs?.[k]);
306
+ }
307
+
308
+ /**
309
+ * @param {Record<string, unknown> | undefined} a
310
+ * @param {Record<string, unknown> | undefined} b
311
+ * @returns {boolean}
312
+ */
313
+ function sameBag(a, b) {
314
+ const [x, y] = [a ?? {}, b ?? {}];
315
+ const keys = Object.keys(x);
316
+ return keys.length === Object.keys(y).length && keys.every((k) => Object.hasOwn(y, k) && x[k] === y[k]);
282
317
  }
283
318
 
284
319
  /**
@@ -510,16 +545,11 @@ export class Engine {
510
545
  * memory and run `fn` against the backend engine. Only `render`/`open` call
511
546
  * this, so `doc` is always present.
512
547
  *
513
- * OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()` and
514
- * `doc.warnings`, and `quill.toTree()` on a clone-cache miss) BEFORE the first
515
- * await. The backend load below is a real suspension point, so reading the
548
+ * OWNERSHIP WINDOW: both caller handles are snapshotted (`doc.toStored()`,
549
+ * and `quill.toTree()` on a clone-cache miss) BEFORE the first await. The backend load below is a real suspension point, so reading the
516
550
  * handles after it would race a caller that `free()`s them as soon as this
517
551
  * call returns its promise ("null pointer passed to rust").
518
552
  *
519
- * `docWarnings` rides the context because the storage DTO does not carry
520
- * them: `fromStored` clears the load's warnings, so the backend clone knows
521
- * nothing of them and `render` splices the snapshot back in.
522
- *
523
553
  * Clone lifetimes differ by design: the `doc` clone is TRANSIENT, freed in
524
554
  * the `finally` of every call, while the `quill` clone is CACHED and is not
525
555
  * freed here — a `Quill` instance's contents never change after
@@ -528,13 +558,12 @@ export class Engine {
528
558
  * @param {string} method the caller's name, for the rejection message
529
559
  * @param {Quill} quill
530
560
  * @param {Document} doc
531
- * @param {(ctx: { mod: any, engine: any, quill: any, doc: any, docWarnings: any[] }) => any} fn
561
+ * @param {(ctx: { mod: any, engine: any, quill: any, doc: any }) => any} fn
532
562
  */
533
563
  async #withClones(method, quill, doc, fn) {
534
564
  const backendId = this.#backendOf(quill, method);
535
565
  requireLocalDoc(doc, method);
536
566
  const docJson = doc.toStored();
537
- const docWarnings = doc.warnings;
538
567
  const quillTree = this.#quillClones.get(this.#descriptorFor(backendId).load)?.has(quill)
539
568
  ? null
540
569
  : quill.toTree();
@@ -546,7 +575,7 @@ export class Engine {
546
575
  let backendDoc = null;
547
576
  try {
548
577
  backendDoc = mod.Document.fromStored(docJson);
549
- return fn({ mod, engine, quill: backendQuill, doc: backendDoc, docWarnings });
578
+ return fn({ mod, engine, quill: backendQuill, doc: backendDoc });
550
579
  } finally {
551
580
  backendDoc?.free();
552
581
  }
@@ -564,11 +593,7 @@ export class Engine {
564
593
  'engine.render(quill, doc)',
565
594
  quill,
566
595
  doc,
567
- ({ engine, quill: q, doc: d, docWarnings }) => {
568
- const result = engine.render(q, d, options ?? undefined, today);
569
- result.warnings = docWarnings.concat(result.warnings);
570
- return result;
571
- }
596
+ ({ engine, quill: q, doc: d }) => engine.render(q, d, options ?? undefined, today)
572
597
  );
573
598
  }
574
599
 
@@ -756,7 +781,7 @@ export class DocumentWriter {
756
781
  }
757
782
  /**
758
783
  * @param {string} markdown
759
- * @returns {import('../core/wasm.js').Delta}
784
+ * @returns {import('../core/wasm.js').Revised}
760
785
  */
761
786
  reviseBody(markdown) {
762
787
  return this.#doc.revise({}, markdown);
@@ -764,17 +789,24 @@ export class DocumentWriter {
764
789
  /**
765
790
  * @param {string} name
766
791
  * @param {string} text
767
- * @returns {import('../core/wasm.js').Delta}
792
+ * @returns {import('../core/wasm.js').Revised}
768
793
  */
769
794
  reviseField(name, text) {
770
795
  return this.#doc._reviseField(this.#quill, name, text);
771
796
  }
797
+ /**
798
+ * @param {string} markdown
799
+ * @returns {import('../core/wasm.js').DocumentRevised}
800
+ */
801
+ reviseDocument(markdown) {
802
+ return this.#doc._reviseDocument(this.#quill, markdown);
803
+ }
772
804
  /**
773
805
  * @param {string} kind
774
806
  * @param {Record<string, unknown>} [fields]
775
807
  * @param {string} [body]
776
808
  * @param {number} [at] insertion index; appends when omitted
777
- * @returns {void}
809
+ * @returns {import('../core/wasm.js').Diagnostic[]}
778
810
  */
779
811
  addCard(kind, fields, body, at) {
780
812
  return this.#doc._addCard(this.#quill, kind, fields, body, at);
@@ -837,7 +869,7 @@ export class CardWriter {
837
869
  }
838
870
  /**
839
871
  * @param {string} markdown
840
- * @returns {import('../core/wasm.js').Delta}
872
+ * @returns {import('../core/wasm.js').Revised}
841
873
  */
842
874
  reviseBody(markdown) {
843
875
  return this.#doc.revise({ card: this.#index }, markdown);
@@ -845,7 +877,7 @@ export class CardWriter {
845
877
  /**
846
878
  * @param {string} name
847
879
  * @param {string} text
848
- * @returns {import('../core/wasm.js').Delta}
880
+ * @returns {import('../core/wasm.js').Revised}
849
881
  */
850
882
  reviseField(name, text) {
851
883
  return this.#doc._reviseField(this.#quill, { card: this.#index, field: name }, text);