@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/core/wasm.d.ts CHANGED
@@ -17,7 +17,7 @@ export type PayloadItem =
17
17
  /**
18
18
  * Paths to `!must_fill` markers nested *inside* `value` (the `value`
19
19
  * projection itself is fill-free). Absent when the field has no nested
20
- * placeholders. Preserved across `pushCard` / `makeCard`.
20
+ * placeholders. Preserved across `insertCard` / `makeCard`.
21
21
  */
22
22
  nestedFills?: PathStep[][];
23
23
  }
@@ -27,7 +27,7 @@ export type PayloadItem =
27
27
  * A single card block, as read back from a document: returned by
28
28
  * `Document.main` / `Document.cards` / `Document.removeCard` / `Quill.seedCard`
29
29
  * / `Document.makeCard`. To feed a card *into* a document use `CardInput`
30
- * (which `pushCard` / `insertCard` accept); every `Card` is a valid `CardInput`,
30
+ * (which `insertCard` accepts); every `Card` is a valid `CardInput`,
31
31
  * so a card read from one document pushes straight into another.
32
32
  *
33
33
  * `$` system entries are hoisted to named fields: `kind` (the `$kind`, empty
@@ -44,19 +44,19 @@ export interface Card {
44
44
  seed?: Record<string, unknown>;
45
45
  payloadItems: PayloadItem[];
46
46
  /**
47
- * The card body as canonical `RichText` — the source-of-truth content model.
48
- * Always this corpus shape on read, never a markdown string. For the markdown
47
+ * The card body as canonical `Content` — the source-of-truth content model.
48
+ * Always this content shape on read, never a markdown string. For the markdown
49
49
  * projection call the codec `exportMarkdown(card.body)`. Write a body back
50
50
  * with `doc.install(addr, rt)` / `doc.revise(addr, md)`, or via `CardInput.body`.
51
51
  */
52
- body: RichText;
52
+ body: Content;
53
53
  }
54
54
 
55
55
  /**
56
56
  * A card written *into* a document — the input twin of `Card`, accepted by
57
- * `Document.pushCard` / `Document.insertCard`. Like `Card` but `body` also
58
- * takes a markdown `string` (imported to the corpus, so a markdown / LLM writer
59
- * needn't build the `RichText` shape), and every field but `kind` is optional —
57
+ * `Document.insertCard`. Like `Card` but `body` also
58
+ * takes a markdown `string` (imported to the content, so a markdown / LLM writer
59
+ * needn't build the `Content` shape), and every field but `kind` is optional —
60
60
  * an absent field defaults (no payload items, an empty body). Write one inline
61
61
  * (`{ kind, body }`) or build it with `Document.makeCard`.
62
62
  */
@@ -67,26 +67,26 @@ export interface CardInput {
67
67
  ext?: Record<string, unknown>;
68
68
  seed?: Record<string, unknown>;
69
69
  payloadItems?: PayloadItem[];
70
- body?: RichText | string;
70
+ body?: Content | string;
71
71
  }
72
72
 
73
73
  /**
74
- * Canonical richtext corpus — the content model for a card body (and richtext
74
+ * Canonical richtext content — the content model for a card body (and richtext
75
75
  * fields). One text sequence over a single coordinate space (Unicode scalar
76
76
  * values): `text` plus line attributes, anchored `marks`, and embedded
77
77
  * `islands`. Every edit is a splice; markdown is a projection, not the model.
78
- * Mirrors `quillmark_richtext::serial`'s canonical JSON encoding.
78
+ * Mirrors `quillmark_content::serial`'s canonical JSON encoding.
79
79
  */
80
- export interface RichText {
80
+ export interface Content {
81
81
  text: string;
82
- lines: RichTextLine[];
83
- marks: RichTextMark[];
84
- islands: RichTextIsland[];
82
+ lines: ContentLine[];
83
+ marks: ContentMark[];
84
+ islands: ContentIsland[];
85
85
  }
86
86
 
87
- /** One `\n`-separated segment of `RichText.text`, in order. */
88
- export type RichTextLine = {
89
- containers: RichTextContainer[];
87
+ /** One `\n`-separated segment of `Content.text`, in order. */
88
+ export type ContentLine = {
89
+ containers: ContentContainer[];
90
90
  /** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
91
91
  continues?: boolean;
92
92
  } & (
@@ -98,32 +98,68 @@ export type RichTextLine = {
98
98
  );
99
99
 
100
100
  /** An ancestor block a line nests inside, outermost first. */
101
- export type RichTextContainer =
101
+ export type ContentContainer =
102
102
  | { container: "list_item"; ordered: boolean; start: number; ordinal: number }
103
103
  | { container: "quote" };
104
104
 
105
- /** A mark over char range `[start, end)` into `RichText.text`. */
106
- export type RichTextMark = { start: number; end: number } & (
105
+ /** A mark over char range `[start, end)` into `Content.text`. */
106
+ export type ContentMark = { start: number; end: number } & (
107
107
  | { type: "strong" | "emph" | "underline" | "strike" | "code" }
108
108
  | { type: "link"; url: string }
109
109
  | { type: "anchor"; id: string }
110
110
  | { type: string; attrs: unknown }
111
111
  );
112
112
 
113
- /** A structured object (table, figure, …) occupying one island slot in `RichText.text`. */
114
- export interface RichTextIsland {
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
- * A richtext write address. An absent `field` targets the card body; an absent
124
- * `card` targets the main card. `{}` is the main-card body; `{ card: 2 }` the
125
- * body of the composable card at index 2; `{ field: "intro" }` the main card's
126
- * `intro` richtext field; `{ card: 2, field: "intro" }` a card field.
153
+ * A write address one navigation concept for the whole `Document` surface. An
154
+ * absent `field` targets the card body; an absent `card` targets the main card.
155
+ * `{}` is the main-card body; `{ card: 2 }` the body of the composable card at
156
+ * index 2; `{ field: "intro" }` the main card's `intro` field; `{ card: 2,
157
+ * field: "intro" }` a card field.
158
+ *
159
+ * On the `Addr`-taking verbs a **bare string** is shorthand for `{ field: name }`
160
+ * — `doc.storeField("qty", 3)`, `doc.revise("intro", md)` — the one coercion
161
+ * rule. A bare number is *not* an addr (`{ card: 2 }` is the self-documenting
162
+ * spelling), so no third navigation idiom re-fragments the surface.
127
163
  */
128
164
  export interface Addr {
129
165
  card?: number;
@@ -131,7 +167,17 @@ export interface Addr {
131
167
  }
132
168
 
133
169
  /**
134
- * A text-splice change set over the USV corpus (CodeMirror `ChangeSet`
170
+ * A card-only address the axis the card-scoped verbs (`storeFields`,
171
+ * `storeExt`, `getExt`, `commitFields`, …) take. An absent `card` targets the
172
+ * main card. A present `field` throws: a card address takes only `card`, and a
173
+ * would-be nested write is a bug the error names rather than silently ignores.
174
+ */
175
+ export interface CardAddr {
176
+ card?: number;
177
+ }
178
+
179
+ /**
180
+ * A text-splice change set over the USV content (CodeMirror `ChangeSet`
135
181
  * semantics) — plain, structured-clone-able data. Returned by `revise` and by
136
182
  * the `rebase` codec; map a stored position through it with `mapPos`.
137
183
  */
@@ -143,9 +189,9 @@ export interface Delta {
143
189
  export type Assoc = "before" | "after";
144
190
 
145
191
  /**
146
- * A mark edit in post-text-delta coordinates. `add` / `remove` carry the
147
- * `RichTextMark` vocabulary (`{ type, … }`); `removeAnchor` drops one identity
148
- * anchor by id.
192
+ * A mark edit in final-text coordinates (post-delta, post-line-op). `add` /
193
+ * `remove` carry the `ContentMark` vocabulary (`{ type, … }`); `removeAnchor`
194
+ * drops one identity anchor by id.
149
195
  */
150
196
  export type MarkOp =
151
197
  | ({ op: "add" | "remove"; start: number; end: number } & (
@@ -156,7 +202,13 @@ export type MarkOp =
156
202
  ))
157
203
  | { op: "removeAnchor"; id: string };
158
204
 
159
- /** A line/block edit. `split`/`join` splice `\n`; `setKind`/`setContainers` touch metadata. */
205
+ /**
206
+ * A line/block edit. `split`/`join` splice `\n`; `setKind`/`setContainers`/
207
+ * `setContinues` touch metadata. `setContinues` sets/clears a line's within-block
208
+ * hard-break flag (`ContentLine.continues`) — the op-grained way to lower a
209
+ * Shift+Enter hard break or a new code-fence interior line; `continues: true` on
210
+ * line 0 is rejected (nothing precedes it to continue).
211
+ */
160
212
  export type LineOp =
161
213
  | { op: "split"; at: number }
162
214
  | { op: "join"; line: number }
@@ -165,10 +217,11 @@ export type LineOp =
165
217
  | { kind: "heading"; level: number }
166
218
  | { kind: "code"; lang?: string }
167
219
  ))
168
- | { op: "setContainers"; line: number; containers: RichTextContainer[] };
220
+ | { op: "setContainers"; line: number; containers: ContentContainer[] }
221
+ | { op: "setContinues"; line: number; continues: boolean };
169
222
 
170
223
  /**
171
- * A committed corpus edit bundle for `applyChange`: a text `delta` (default no
224
+ * A committed content edit bundle for `applyChange`: a text `delta` (default no
172
225
  * text change), then `lineOps`, then `markOps` (mark ranges are in post-delta
173
226
  * coordinates). Every field is optional.
174
227
  */
@@ -180,6 +233,70 @@ export interface ChangeBundle {
180
233
 
181
234
 
182
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
+
251
+ /** The commitment-ladder rung that produced a `ResolvedField.value`. */
252
+ export type FieldSource = "authored" | "default" | "zero";
253
+
254
+ /**
255
+ * One resolved row: its `name`, the value the render projection would use, and
256
+ * the `FieldSource` rung it came from. Rows are an ordered array — declaration
257
+ * order is structural, not object-key order. The card body is a `body` sibling
258
+ * on its card, never a row in `fields`. Diagnostics stay `Quill.validate`'s;
259
+ * schema guidance (`example:`, labels) reads from `Quill.schema`.
260
+ */
261
+ export interface ResolvedField {
262
+ name: string;
263
+ value: unknown;
264
+ source: FieldSource;
265
+ }
266
+
267
+ /**
268
+ * The main card's resolved rows in declaration order, plus its body row —
269
+ * `null` when the main enables no body.
270
+ */
271
+ export interface ResolvedMain {
272
+ fields: ResolvedField[];
273
+ body: ResolvedField | null;
274
+ }
275
+
276
+ /**
277
+ * One composable card's resolved rows in declaration order, with its authored
278
+ * `kind` (`null` for an unknown-kind card), its document-array `index`, and its
279
+ * body row — `null` when the kind enables no body.
280
+ */
281
+ export interface ResolvedCard {
282
+ kind: string | null;
283
+ index: number;
284
+ fields: ResolvedField[];
285
+ body: ResolvedField | null;
286
+ }
287
+
288
+ /**
289
+ * The resolved-value view (`Quill.resolve`): the main card and every
290
+ * composable card. Value and provenance only — completeness and errors stay
291
+ * `Quill.validate`.
292
+ */
293
+ export interface Resolved {
294
+ main: ResolvedMain;
295
+ cards: ResolvedCard[];
296
+ }
297
+
298
+
299
+
183
300
  /** UI layout hints for a single field. Field display order is not a hint:
184
301
  * key order in the schema's `fields`/`properties` objects is declaration
185
302
  * order, the ordering contract. */
@@ -190,9 +307,23 @@ export interface QuillFieldUi {
190
307
  multiline?: boolean;
191
308
  }
192
309
 
310
+ /** One entry in a card's `ui.groups` registry: a display-label override for the
311
+ * group id (the map key). An empty object carries no override — the consumer
312
+ * derives the label from the id (`memo_for` → "Memo For"), as it does a field
313
+ * label from its key. */
314
+ export interface QuillGroupUi {
315
+ title?: string;
316
+ }
317
+
193
318
  /** UI layout hints for a card (main or named card kind). */
194
319
  export interface QuillCardUi {
195
320
  title?: string;
321
+ /** The card's group registry: the ordered table of contents naming every
322
+ * group a field's `ui.group` may reference. The map key is the group id, and
323
+ * key order is declaration order — the display-order contract, the same one
324
+ * `fields` key order carries. Absent when the card declares no groups (or
325
+ * uses the deprecated implicit-group form). */
326
+ groups?: Record<string, QuillGroupUi>;
196
327
  }
197
328
 
198
329
  /** Body namespace for a card (main or named card kind). */
@@ -213,7 +344,7 @@ export interface QuillCardBody {
213
344
  * zero-fills the field). There is no separate `required` axis.
214
345
  */
215
346
  export interface QuillFieldSchema {
216
- type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "datetime" | "richtext" | "plaintext" | "enum";
347
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "date" | "datetime" | "richtext" | "plaintext" | "enum";
217
348
  description?: string;
218
349
  default?: unknown;
219
350
  example?: unknown;
@@ -294,28 +425,16 @@ export class Document {
294
425
  free(): void;
295
426
  [Symbol.dispose](): void;
296
427
  /**
297
- * Build a composable card of `kind`, typed-commit `fields` onto it, set its
298
- * body from optional markdown, and append it — the ABI under
299
- * `writer.addCard`. Fuses `makeCard` + typed commit + `pushCard`
300
- * transactionally: the card is committed in full before it joins the
301
- * document, so a rejected field (or an invalid kind or body) leaves the
302
- * document untouched. Field errors throw the same per-field diagnostic
303
- * bundle as [`commitFields`](Self::commit_fields), including an
304
- * `[EditError::UnknownField]` per undeclared name; an invalid kind or body
305
- * throws a single-entry bundle keyed `$kind` / `$body`.
306
- */
307
- addCard(quill: Quill, kind: string, fields?: Record<string, unknown>, body?: string): void;
308
- /**
309
- * **Apply** a committed corpus edit `bundle` (`{ delta?, lineOps?, markOps? }`)
428
+ * **Apply** a committed content edit `bundle` (`{ delta?, lineOps?, markOps? }`)
310
429
  * at `addr` — the editor splice: text delta first, then line ops, then mark
311
- * ops (mark ranges in post-delta coordinates), each all-or-nothing. An absent
430
+ * ops (mark ranges in final-text coordinates), each all-or-nothing. An absent
312
431
  * `addr.field` targets the body, an absent `addr.card` the main card.
313
432
  *
314
433
  * Throws on an out-of-range card, a field that is not richtext, a malformed
315
434
  * bundle, or an op that applies out of bounds (the value is unchanged on a
316
435
  * failed apply).
317
436
  */
318
- applyChange(addr: Addr, bundle: ChangeBundle): void;
437
+ applyChange(addr: Addr | string, bundle: ChangeBundle): void;
319
438
  /**
320
439
  * Authoring-ergonomics header introducing a blueprint to an LLM/MCP
321
440
  * consumer for the given `quillName`. Re-exposes core's canonical text for
@@ -323,61 +442,22 @@ export class Document {
323
442
  * uniform.
324
443
  */
325
444
  static blueprintInstruction(quill_name: string): string;
326
- clone(): Document;
327
445
  /**
328
- * Typed field write on the composable card at `index` — the card-indexed
329
- * twin of [`commitField`](Document::commit_field). Resolves the field's
330
- * type from the card's `$kind` schema in `quill` and strict-commits it.
331
- *
332
- * Throws `[EditError::IndexOutOfRange]` when `index` is out of range, and
333
- * the same typed-mismatch / name errors as `commitField` — including
334
- * `[EditError::UnknownField]` for a field the card-kind schema does not
335
- * declare (an unknown `$kind` has no schema, so every field is undeclared).
336
- */
337
- commitCardField(quill: Quill, index: number, name: string, value: any): void;
338
- /**
339
- * Batched twin of [`commitCardField`](Document::commit_card_field):
340
- * typed-commit several fields on the card at `index` atomically, resolving
341
- * each field's type from the card's `$kind` schema in `quill`. All-or-nothing
342
- * with the same per-field-diagnostic contract as
343
- * [`commitFields`](Document::commit_fields), including an
344
- * `[EditError::UnknownField]` diagnostic per undeclared name. Throws
345
- * `[EditError::IndexOutOfRange]` when `index` is out of range.
346
- */
347
- commitCardFields(quill: Quill, index: number, fields: Record<string, unknown>): void;
348
- /**
349
- * Typed field write on the main card, resolving the field's schema `type`
350
- * from `quill` — the one write verb for **every** field type (richtext,
351
- * scalar, array, object). The schema carries the `inline` constraint, so no
352
- * type token or flag is passed. A richtext-typed field stores the canonical
353
- * corpus, so identity marks (anchors, island ids) and corpus-only marks
354
- * (e.g. `underline`) live on it and survive compiles and the storage DTO.
355
- * Values use the encoding the seam already speaks: a corpus object
356
- * or markdown string for richtext, a scalar/array/object otherwise.
357
- *
358
- * A field declared in the schema is strict-committed — a mismatch throws
359
- * now, not at render. A name the schema does not declare throws
360
- * `[EditError::UnknownField]` rather than falling to the opaque store: on
361
- * the typed path it is a typo. Use [`setField`](Document::set_field) when
362
- * opaque storage is the intent. Also throws `[EditError::FieldConform]` /
363
- * `[EditError::FieldRichtextDecode]` / `[EditError::FieldRichtextNotInline]`
364
- * on a typed mismatch and `[EditError::InvalidFieldName]` on a malformed
365
- * name.
366
- *
367
- * The `quill` handle is passed per call because a `Document` carries only a
368
- * `$quill` reference, not the resolved schema.
446
+ * A single composable card by index — the whole `Card`, the card-indexed
447
+ * twin of the [`main`](Self::main) getter, so reading one card need not
448
+ * materialize every card via [`cards`](Self::cards). An out-of-range
449
+ * `index` throws `edit::index_out_of_range`, matching the card write
450
+ * verbs.
369
451
  */
370
- commitField(quill: Quill, name: string, value: any): void;
452
+ card(index: number): Card;
371
453
  /**
372
- * Batched twin of [`commitField`](Document::commit_field): typed-commit
373
- * several main-card fields atomically, resolving each field's schema `type`
374
- * from `quill`. All-or-nothing with the same per-field-diagnostic error
375
- * contract as [`setFields`](Document::set_fields) nothing is applied on
376
- * error and the thrown error's `diagnostics` carry one entry per offending
377
- * field, including an `[EditError::UnknownField]` for any name the schema
378
- * does not declare, so a whole-form submit sees every typo in one pass.
454
+ * The index of the first composable card whose `$id` equals `id`, or
455
+ * `undefined` when none carries it. Resolves the canonical durable address
456
+ * without a hand-rolled scan over [`cards`](Self::cards); `$id` is
457
+ * non-unique by design, so the first match wins.
379
458
  */
380
- commitFields(quill: Quill, fields: Record<string, unknown>): void;
459
+ cardIndexById(id: string): number | undefined;
460
+ clone(): Document;
381
461
  /**
382
462
  * Schema version this build writes via [`toJson`](Document::to_json).
383
463
  * Tracks the `Document` model version (not the running crate version):
@@ -417,30 +497,54 @@ export class Document {
417
497
  */
418
498
  static fromMarkdown(markdown: string): Document;
419
499
  /**
420
- * Read a main-card field's stored value — the raw payload value (a corpus
421
- * object for a richtext field, a scalar/array/object otherwise), or
422
- * `undefined` when the field is absent. The quill-free read: reads need no
423
- * schema, so they live on `Document`, not the typed writer. For the markdown
424
- * projection of a richtext value use [`getMarkdown`](Self::get_markdown).
500
+ * Read the value at `addr` — the raw stored payload value of a field (a
501
+ * content object for a richtext field, a scalar/array/object otherwise), or
502
+ * the **body content** when `addr.field` is absent. A bare string is `Addr`
503
+ * shorthand for `{ field }`. Reads are total over the field axis: an absent
504
+ * field is `undefined`; only an out-of-range `addr.card` throws
505
+ * `edit::index_out_of_range`. Reads need no schema, so they live on
506
+ * `Document`, not the typed writer; for the markdown projection of a
507
+ * richtext value use [`getMarkdown`](Self::get_markdown).
508
+ */
509
+ get(addr: Addr | string): unknown;
510
+ /**
511
+ * The whole `$ext` map at `addr` (a card address, absent `card` = main), or
512
+ * `undefined` when the card carries none. The fine-grained `$ext` read —
513
+ * your own state without serializing the whole card. Throws on a present
514
+ * `field` (a card address takes only `card`) or an out-of-range card.
425
515
  */
426
- get(name: string): any;
516
+ getExt(addr?: CardAddr): Record<string, unknown> | undefined;
427
517
  /**
428
- * The markdown projection of a main-card field (`name` given) or the main
429
- * body (`name` omitted) the on-demand, lossy export (corpus-only marks do
430
- * not survive markdown), returning `""` for an absent field. Re-coins,
431
- * lazily and by name, the projection the eager `fieldMarkdown` /
432
- * `bodyMarkdown` getters dropped in #925; call it only when markdown is what
433
- * you need out.
518
+ * The value stored under `$ext[ns]` at `addr` (a card address, absent `card`
519
+ * = main), or `undefined`. The namespace-scoped `$ext` read your own slot
520
+ * without a whole-card serialize, and non-destructive (unlike
521
+ * `removeExtNamespace`). Throws on a present `field` or an out-of-range card.
434
522
  */
435
- getMarkdown(name?: string): string;
523
+ getExtNamespace(addr: CardAddr, ns: string): unknown;
436
524
  /**
437
- * Insert a card at `index` (must be in `0..=cards.length`). Accepts a
438
- * `CardInput` (see [`pushCard`](Self::push_card)).
525
+ * The **body** markdown projection the main body, or a composable card's
526
+ * body (`{ card }`) — the on-demand, lossy export (content-only marks do not
527
+ * survive markdown). A body's type is a format fact, not a schema fact, so
528
+ * this read stays quill-free; a body is never absent.
529
+ *
530
+ * `addr` is an optional **card address** (`{ card }`, absent = main). A
531
+ * present `field` throws — a field's markdown is read through the
532
+ * schema-plane `quill.reader(doc).get(field)`, which interprets by declared
533
+ * type (#978). An out-of-range `addr.card` throws.
439
534
  */
440
- insertCard(index: number, card: CardInput): void;
535
+ getMarkdown(addr?: CardAddr): string;
441
536
  /**
442
- * **Install** a richtext value at `addr` **value semantics**, corpus only.
443
- * Stores exactly `rt` (a canonical `RichText` corpus object); the identity
537
+ * Insert a card the single insertion verb: `at` absent appends, a number
538
+ * inserts at that index (must be in `0..=cards.length`). Accepts a
539
+ * `CardInput` — a card read back (`cards` / `removeCard` / `quill.seedCard`),
540
+ * a [`makeCard`](Document::make_card) result, or a bare `{ kind, body }`
541
+ * (every returned `Card` is a valid `CardInput`). Throws if `card.kind` is
542
+ * not a valid kind name, or if `at` is out of range.
543
+ */
544
+ insertCard(card: CardInput, at?: number): void;
545
+ /**
546
+ * **Install** a richtext value at `addr` — **value semantics**, content only.
547
+ * Stores exactly `rt` (a canonical `Content` content object); the identity
444
548
  * anchors of any previous value are gone. An absent `addr.field` targets the
445
549
  * body, an absent `addr.card` the main card. For "here's new markdown," use
446
550
  * [`revise`](Document::revise); the cold-import path is spelled at the call
@@ -448,9 +552,16 @@ export class Document {
448
552
  * source.
449
553
  *
450
554
  * Throws on an out-of-range card, a malformed field name, or an `rt` that is
451
- * not a canonical corpus object.
555
+ * not a canonical content object.
556
+ */
557
+ install(addr: Addr | string, rt: Content): void;
558
+ /**
559
+ * Whether the field at `addr` is marked `!must_fill`. A bare string is `Addr`
560
+ * shorthand for `{ field }`. `false` for an absent field (truthful — it isn't
561
+ * marked) and for a body address (a body is never a fill). Only an
562
+ * out-of-range `addr.card` throws.
452
563
  */
453
- install(addr: Addr, rt: RichText): void;
564
+ isFill(addr: Addr | string): boolean;
454
565
  /**
455
566
  * Replace this document's contents **in place** from a versioned storage
456
567
  * DTO string — the mutating twin of the static
@@ -465,10 +576,10 @@ export class Document {
465
576
  loadJson(json: string): void;
466
577
  /**
467
578
  * Build a fresh `Card` from a kind and a flat field map — the ergonomic
468
- * constructor for `pushCard` / `insertCard`. `fields` is an optional
579
+ * constructor for `insertCard`. `fields` is an optional
469
580
  * `Record<string, unknown>` (each entry becomes a card field, in
470
581
  * insertion order); `body` defaults to `""`. Kind validity is checked by
471
- * `pushCard` / `insertCard`, not here.
582
+ * `insertCard`, not here.
472
583
  */
473
584
  static makeCard(kind: string, fields?: Record<string, unknown>, body?: string): Card;
474
585
  /**
@@ -484,14 +595,6 @@ export class Document {
484
595
  * Throws on an invalid quill reference. Mirrors Python `Document(quill_ref)`.
485
596
  */
486
597
  constructor(quill_ref: string);
487
- /**
488
- * Append a card to the end of the card list. Accepts a `CardInput` — a card
489
- * read back (`cards` / `removeCard` / `quill.seedCard`), a
490
- * [`makeCard`](Document::make_card) result, or a bare `{ kind, body }`
491
- * (every returned `Card` is a valid `CardInput`). Throws if `card.kind` is
492
- * not a valid kind name.
493
- */
494
- pushCard(card: CardInput): void;
495
598
  /**
496
599
  * The canonical `$quill` reference grammar as author-facing text. Core is
497
600
  * the single source of truth: drive schema `describe` and validation
@@ -502,54 +605,32 @@ export class Document {
502
605
  static quillRefHint(): string;
503
606
  removeCard(index: number): Card | undefined;
504
607
  /**
505
- * Remove the `$ext` map from the composable card at `index` *entirely*,
506
- * returning the previous map or `undefined`. Throws if out of range.
507
- * Prefer `removeCardExtNamespace` to clear only one consumer's slot.
508
- */
509
- removeCardExt(index: number): Record<string, unknown> | undefined;
510
- /**
511
- * Remove `namespace` from the composable card's `$ext` map, returning the
512
- * value stored there or `undefined`; clears `$ext` entirely once empty.
513
- * The card-indexed twin of `removeExtNamespace`. Throws if out of range.
514
- */
515
- removeCardExtNamespace(index: number, namespace: string): any;
516
- /**
517
- * Remove a field on the card at `index`. Returns the removed value or
518
- * `undefined`. Throws if `index` is out of range or `name` is invalid.
519
- */
520
- removeCardField(index: number, name: string): any;
521
- /**
522
- * Remove the `$ext` map from the main card *entirely*, returning the
523
- * previous map or `undefined`. This is a blunt escape hatch that discards
524
- * every namespace at once — prefer `removeExtNamespace` to clear only your
525
- * own slot while leaving sibling consumers' state intact.
608
+ * Remove the `$ext` map on the card `addr` targets *entirely*, returning the
609
+ * previous map or `undefined` a blunt escape hatch that discards every
610
+ * namespace at once (prefer `removeExtNamespace`). `addr` is a card address
611
+ * (absent = main). Throws on a present `field` or an out-of-range card.
526
612
  */
527
- removeExt(): Record<string, unknown> | undefined;
613
+ removeExt(addr?: CardAddr): Record<string, unknown> | undefined;
528
614
  /**
529
- * Remove `namespace` from the main card's `$ext` map, returning the value
530
- * stored there or `undefined`. This is the recommended way to clear `$ext`
531
- * state: sibling namespaces survive, and when the last namespace is removed
532
- * the `$ext` entry is dropped entirely (not left as `$ext: {}`).
615
+ * Remove `$ext[ns]` on the card `addr` targets, returning its value or
616
+ * `undefined`; drops `$ext` once empty. `addr` is a card address (absent =
617
+ * main). Preserves sibling namespaces. Throws on a present `field` or an
618
+ * out-of-range card.
533
619
  */
534
- removeExtNamespace(namespace: string): any;
620
+ removeExtNamespace(addr: CardAddr, ns: string): any;
535
621
  /**
536
- * Remove a payload field on the main card, returning the removed value or
537
- * `undefined`. Throws if `name` does not match `[A-Za-z_][A-Za-z0-9_]*`.
622
+ * Remove a field at `addr`, returning the removed value or `undefined`. A
623
+ * bare string is `Addr` shorthand for `{ field }`. One `remove` verb serves
624
+ * every write lane. A body address throws; throws on an out-of-range card or
625
+ * a malformed name.
538
626
  */
539
- removeField(name: string): any;
627
+ removeField(addr: Addr | string): any;
540
628
  /**
541
629
  * Remove `cardKind` from the main card's `$seed` map, returning its
542
- * overlay or `undefined`; drops `$seed` entirely once empty. Sibling
543
- * kinds survive.
630
+ * overlay or `undefined`; drops `$seed` entirely once empty. Sibling kinds
631
+ * survive. `$seed` is main-only, so this takes no address.
544
632
  */
545
633
  removeSeedNamespace(card_kind: string): any;
546
- /**
547
- * **Deprecated** — alias for `revise({}, markdown)`, kept one release cycle.
548
- * Revise the main card's body from a markdown string (edit semantics: a
549
- * `diff_import` that rebases surviving anchors). Discards the text delta;
550
- * call [`revise`](Document::revise) to receive it.
551
- */
552
- replaceBody(body: string): void;
553
634
  /**
554
635
  * **Revise** the richtext value at `addr` from a markdown string — **edit
555
636
  * semantics**, the default write path, returning the text [`Delta`]. Imports
@@ -559,9 +640,9 @@ export class Document {
559
640
  * absent `addr.card` the main card; an absent field cold-imports from empty.
560
641
  *
561
642
  * Throws on an out-of-range card, a malformed field name, a present
562
- * non-corpus field value, or an over-nested markdown input.
643
+ * non-content field value, or an over-nested markdown input.
563
644
  */
564
- revise(addr: Addr, markdown: string): Delta;
645
+ revise(addr: Addr | string, markdown: string): Delta;
565
646
  /**
566
647
  * Read the `schema` version tag from a raw storage DTO string without a
567
648
  * full parse, or `undefined`. Returns unknown future versions as-is —
@@ -570,30 +651,13 @@ export class Document {
570
651
  */
571
652
  static schemaVersionOf(json: string): string | undefined;
572
653
  /**
573
- * Replace the `$ext` map on the composable card at `index`. Throws if out
574
- * of range or `value` is not a plain object. Named to mirror `setExt` on
575
- * the main card; `setCardExtNamespace` is the sibling-safe alternative.
576
- */
577
- setCardExt(index: number, value: any): void;
578
- /**
579
- * Merge `value` into the composable card's `$ext` map under `namespace`,
580
- * preserving sibling namespaces. The card-indexed twin of `setExtNamespace`.
581
- * Throws if out of range or `value` cannot be serialized.
582
- */
583
- setCardExtNamespace(index: number, namespace: string, value: any): void;
584
- /**
585
- * Set a field on the card at `index` — the card-indexed twin of
586
- * [`setField`](Document::set_field). Stores the value opaquely.
587
- * Throws if `index` is out of range, `name` is reserved or invalid.
588
- */
589
- setCardField(index: number, name: string, value: any): void;
590
- /**
591
- * Batched twin of [`setCardField`](Document::set_card_field): set
592
- * several fields on the card at `index` atomically. Same all-or-nothing,
593
- * one-diagnostic-per-field contract as [`setFields`](Document::set_fields).
594
- * Throws if `index` is out of range.
654
+ * The main card's `$seed` overlay object for `kind` (the `$seed[kind]`
655
+ * entry), or `undefined` when absent. The cheap read that feeds
656
+ * `quill.seedCard(kind, overlay)` without serializing the whole main card
657
+ * via [`main`](Self::main) to fish out one key — and it keeps `seedCard`
658
+ * pure: the quill still never reads the document.
595
659
  */
596
- setCardFields(index: number, fields: Record<string, unknown>): void;
660
+ seedOverlay(kind: string): Record<string, unknown> | undefined;
597
661
  /**
598
662
  * Replace the kind of the card at `index`. Payload and body are untouched;
599
663
  * schema-aware migration is the caller's responsibility.
@@ -601,50 +665,61 @@ export class Document {
601
665
  */
602
666
  setCardKind(index: number, new_kind: string): void;
603
667
  /**
604
- * Replace the opaque `$ext` map on the main card. `value` must be a plain
605
- * object; throws otherwise. `$ext` carries out-of-band consumer state and
606
- * never reaches the rendered output. Pass `{}` to record an explicit
607
- * empty `$ext`.
668
+ * Replace the QUILL reference string. Throws if `ref_str` is invalid.
608
669
  */
609
- setExt(value: any): void;
670
+ setQuillRef(ref_str: string): void;
610
671
  /**
611
- * Merge `value` into the main card's `$ext` map under `namespace`, creating
612
- * the map when absent and replacing any existing value at that key. Sibling
613
- * namespaces are preserved, so independent consumers (`$ext.editor`,
614
- * `$ext.agent`, …) don't clobber each other.
672
+ * Replace the opaque `$ext` map on the card `addr` targets (a card address,
673
+ * absent `card` = main). `value` must be a plain object. `$ext` carries
674
+ * out-of-band consumer state and never reaches the rendered output; pass
675
+ * `{}` for an explicit empty `$ext`. Quill-free and verbatim an opaque
676
+ * `store` verb. Throws on a present `field` or an out-of-range card.
615
677
  */
616
- setExtNamespace(namespace: string, value: any): void;
678
+ storeExt(addr: CardAddr, value: any): void;
617
679
  /**
618
- * Update a payload field on the main card. Clears any existing `!must_fill` marker.
619
- *
620
- * Throws if `name` does not match `[A-Za-z_][A-Za-z0-9_]*`.
680
+ * Merge `value` into `$ext[ns]` on the card `addr` targets, preserving
681
+ * sibling namespaces — the recommended `$ext` write. `addr` is a card
682
+ * address (absent = main). Quill-free and verbatim — an opaque `store` verb.
683
+ * Throws on a present `field` or an out-of-range card.
621
684
  */
622
- setField(name: string, value: any): void;
685
+ storeExtNamespace(addr: CardAddr, ns: string, value: any): void;
623
686
  /**
624
- * Set several main-card payload fields atomically from a plain object,
625
- * clearing any `!must_fill` marker on each key. Nothing is applied on
626
- * error; the thrown error's `diagnostics` array carries one entry per
627
- * offending field (`path` = field name), so externally-sourced names
628
- * (database columns, form keys) surface every violation in one pass.
629
- * Mirrors Python `set_fields`.
687
+ * Store a field verbatim at `addr` the opaque store (**store** = verbatim,
688
+ * coercion deferred to render; the typed write is
689
+ * [`commitField`](Document::commit_field)). A bare string is `Addr`
690
+ * shorthand for `{ field }`, so `doc.storeField("qty", 3)` reads as written;
691
+ * `{ card: 2, field: "qty" }` targets a composable card. Clears any
692
+ * `!must_fill` marker. A body address (no `field`) throws — a body is never
693
+ * opaque; write it with `revise` / `install` / `writer.setBody`. Throws on
694
+ * an out-of-range card or a malformed name.
630
695
  */
631
- setFields(fields: Record<string, unknown>): void;
696
+ storeField(addr: Addr | string, value: any): void;
632
697
  /**
633
- * Update a payload field on the main card and mark it as `!must_fill`.
634
- * Throws on invalid name (see [`setField`](Document::set_field)).
698
+ * Store several fields verbatim and atomically on the card `addr` targets
699
+ * the opaque store's batch. `addr` is a **card address** (`{ card }`, absent
700
+ * = main); a present `field` throws. The batch verb takes the address first
701
+ * and is never shape-overloaded, because `card` is a legal field name:
702
+ * `storeFields({}, fields)` is the main card, `storeFields({ card: 2 },
703
+ * fields)` a composable one — never ambiguous with "set field `card`".
704
+ * Nothing is applied on error; the thrown error's `diagnostics` carry one
705
+ * entry per offending field. Throws on an out-of-range card.
635
706
  */
636
- setFill(name: string, value: any): void;
707
+ storeFields(addr: CardAddr, fields: Record<string, unknown>): void;
637
708
  /**
638
- * Replace the QUILL reference string. Throws if `ref_str` is invalid.
709
+ * Store a field verbatim at `addr` and mark it `!must_fill` the opaque
710
+ * store's fill variant, card-capable (a bare string or `{ field }` for main,
711
+ * `{ card, field }` for a composable card). A body address throws. Same
712
+ * validation as [`storeField`](Document::store_field).
639
713
  */
640
- setQuillRef(ref_str: string): void;
714
+ storeFill(addr: Addr | string, value: any): void;
641
715
  /**
642
- * Merge a card-kind's seed `overlay` into the main card's `$seed` map
643
- * under `cardKind`, preserving sibling kinds. Sets the starting values
644
- * new cards of that kind spawn with. Throws if `overlay` cannot be
645
- * serialized or nests too deep.
716
+ * Merge a card-kind's seed `overlay` into the **main** card's `$seed` map
717
+ * under `cardKind`, preserving sibling kinds `$seed` lives on the main
718
+ * card by model, so this takes no address. Sets the starting values new
719
+ * cards of that kind spawn with. Quill-free and verbatim — an opaque `store`
720
+ * verb. Throws if `overlay` cannot be serialized or nests too deep.
646
721
  */
647
- setSeedNamespace(card_kind: string, overlay: any): void;
722
+ storeSeedNamespace(card_kind: string, overlay: any): void;
648
723
  /**
649
724
  * Serialize this document to a versioned storage DTO string.
650
725
  *
@@ -697,14 +772,25 @@ export class Quill {
697
772
  * canonical shape.
698
773
  */
699
774
  static fromTree(tree: Map<string, Uint8Array>): Quill;
775
+ /**
776
+ * The resolved-value view of `doc` against this quill's schema — for every
777
+ * declared field the value the render projection would use and the
778
+ * `FieldSource` rung it came from (`"authored" | "default" | "zero"`), in
779
+ * one call. The card body rides the `fields` map under the `$body` key.
780
+ *
781
+ * Value and provenance only: completeness and errors stay `validate`'s
782
+ * (a consumer merges it with its own diagnostic producers regardless), and
783
+ * schema guidance reads from `Quill.schema`.
784
+ */
785
+ resolve(doc: Document): Resolved;
700
786
  /**
701
787
  * Seed a starter composable `Card` of the given kind (carries `$kind`),
702
788
  * layering an optional per-kind seed `overlay` over the schema-example
703
789
  * base (`overlay › example › absent`). Returns `undefined` if `cardKind`
704
790
  * is not declared in this quill's schema, else a `Card` that feeds
705
- * straight into `Document.pushCard` / `insertCard`.
791
+ * straight into `Document.insertCard`.
706
792
  *
707
- * Pass `document.main.seed?.[cardKind]` as `overlay` so a card added to a
793
+ * Pass `document.seedOverlay(cardKind)` as `overlay` so a card added to a
708
794
  * template-derived document inherits its curated starting values; omit it
709
795
  * (or pass `undefined` / `null`) for the bare schema seed. `overlay` is a
710
796
  * plain object — this reads the document, it does not mutate it.
@@ -776,19 +862,28 @@ export class Quill {
776
862
  }
777
863
 
778
864
  /**
779
- * Export a canonical `RichText` corpus to its markdown projection — the pure
780
- * codec that replaces the eager `bodyMarkdown` / `fieldMarkdown` precomputes
781
- * (`exportMarkdown(card.body)`). Throws if `rt` is not a canonical corpus.
865
+ * Export a canonical `Content` content to its markdown projection — the pure
866
+ * on-demand codec behind `exportMarkdown(card.body)`. Throws if `rt` is not a
867
+ * canonical content.
782
868
  */
783
- export function exportMarkdown(rt: RichText): string;
869
+ export function exportMarkdown(rt: Content): string;
784
870
 
785
871
  /**
786
- * Import a markdown string to a canonical `RichText` corpus — the pure,
872
+ * Serialize structured [`DocPathSeg`] segments back to the canonical path
873
+ * string — the inverse of `parseDocPath`, for a consumer that builds a path
874
+ * rather than reads one. Throws on a segment array the deserializer rejects,
875
+ * and on an empty segment array (symmetric with `parseDocPath("")`, which
876
+ * throws "empty path").
877
+ */
878
+ export function formatDocPath(segs: DocPathSeg[]): string;
879
+
880
+ /**
881
+ * Import a markdown string to a canonical `Content` content — the pure,
787
882
  * document-free codec. Pair with `install(addr, importMarkdown(md))` to spell
788
883
  * the cold (anchor-losing) write at the call site; prefer `revise` for edit
789
884
  * semantics. Throws on an over-nested input.
790
885
  */
791
- export function importMarkdown(markdown: string): RichText;
886
+ export function importMarkdown(markdown: string): Content;
792
887
 
793
888
  /**
794
889
  * Initialize the WASM module with panic hooks for better error messages
@@ -796,7 +891,7 @@ export function importMarkdown(markdown: string): RichText;
796
891
  export function init(): void;
797
892
 
798
893
  /**
799
- * Map a base corpus position through a `delta` to its new position — the pure
894
+ * Map a base content position through a `delta` to its new position — the pure
800
895
  * position-mapping codec an editor bridge composes to hold a caret stable
801
896
  * across a `revise`. `assoc` decides the side of a same-position insertion
802
897
  * (`"after"` moves past it). Throws on a malformed `delta`.
@@ -804,10 +899,19 @@ export function init(): void;
804
899
  export function mapPos(delta: Delta, pos: number, assoc: Assoc): number;
805
900
 
806
901
  /**
807
- * Rebase `markdown` onto a `base` corpus — the pure, document-free twin of
808
- * `revise`: cold-import + `diff_import`, returning the new `corpus` and the
902
+ * Parse a canonical document-model `Diagnostic.path`
903
+ * (`cards.<kind>[<i>].<field>`, `main.body`, `recipients[0].name`) into its
904
+ * structured [`DocPathSeg`] segments — the exported inverse of the engine's
905
+ * one path serializer, so a consumer routes on segments instead of regexing
906
+ * the string. Throws on a malformed path.
907
+ */
908
+ export function parseDocPath(path: string): DocPathSeg[];
909
+
910
+ /**
911
+ * Rebase `markdown` onto a `base` content — the pure, document-free twin of
912
+ * `revise`: cold-import + `diff_import`, returning the new `content` and the
809
913
  * text `delta` (surviving anchors rebased). Use it to compute a revise without
810
914
  * a document in hand; `revise(addr, md)` fuses this with the store for
811
- * atomicity. Throws on an over-nested markdown input or a non-corpus `base`.
915
+ * atomicity. Throws on an over-nested markdown input or a non-content `base`.
812
916
  */
813
- export function rebase(base: RichText, markdown: string): { corpus: RichText; delta: Delta };
917
+ export function rebase(base: Content, markdown: string): { content: Content; delta: Delta };