@quillmark/wasm 0.92.1 → 0.95.1

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.
@@ -14,6 +14,20 @@
14
14
  // public entry point, so this is a structural fact. Replacing the re-export
15
15
  // with a wrapper is a breaking design change, not a refactor. See runtime.js.
16
16
  export { Quill, Document, init } from '../core/wasm.js';
17
+ // The document-free content codec, re-exported from the core build.
18
+ export { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js';
19
+
20
+ import type { CardAddr } from '../core/wasm.js';
21
+
22
+ /**
23
+ * The main card's address — the default target of the card-scoped verbs
24
+ * (`storeFields` / `storeExt` / `commitFields` / …). A named, {@link CardAddr}-typed
25
+ * alias for the empty address `{}`, so a main-card write names its target:
26
+ * `doc.storeFields(MAIN_CARD_ADDR, fields)`. It IS `{}` (frozen at runtime), a
27
+ * pure alias — `{}` and `undefined` stay equally valid. A card selector only,
28
+ * never a field address.
29
+ */
30
+ export declare const MAIN_CARD_ADDR: CardAddr;
17
31
 
18
32
  // Core-build types consumers read off `Quill`/`Document`.
19
33
  export type {
@@ -31,6 +45,31 @@ export type {
31
45
  QuillMetadata
32
46
  } from '../core/wasm.js';
33
47
 
48
+ // Content edit vocabulary — the op-grained content model `Document`'s methods
49
+ // speak (`applyChange(addr, bundle)`, `install(addr, rt)`, `revise(…) => Delta`).
50
+ // Declared in the core build; re-exported here so the single public entry point
51
+ // names every type its own re-exported surface already references — `Card.body`
52
+ // is a `Content`, `PayloadItem.nestedFills` a `PathStep[][]`, `CardInput.body` a
53
+ // `Content | string` — rather than forcing consumers to derive them structurally
54
+ // off the `Document` handle. The content write path (a ProseMirror↔content codec)
55
+ // must name all of them; they are its correctness core, not edge types.
56
+ export type {
57
+ Content,
58
+ ContentLine,
59
+ ContentContainer,
60
+ ContentMark,
61
+ ContentIsland,
62
+ CardInput,
63
+ PathStep,
64
+ Addr,
65
+ CardAddr,
66
+ Delta,
67
+ Assoc,
68
+ LineOp,
69
+ MarkOp,
70
+ ChangeBundle
71
+ } from '../core/wasm.js';
72
+
34
73
  // ── Error contract ──────────────────────────────────────────────────────────
35
74
 
36
75
  /**
@@ -68,7 +107,7 @@ export declare function isQuillmarkError(e: unknown): e is QuillmarkError;
68
107
  // (run via `npm run typecheck`), so these and the generated
69
108
  // `pkg/backends/typst/wasm.d.ts` cannot silently diverge.
70
109
 
71
- import type { Quill, Document } from '../core/wasm.js';
110
+ import type { Quill, Document, Card } from '../core/wasm.js';
72
111
  import type { Diagnostic } from '../core/wasm.js';
73
112
 
74
113
  /** Canonical contract every backend build must satisfy. One emitted output. */
@@ -84,6 +123,92 @@ export interface RenderOptions {
84
123
  ppi?: number;
85
124
  pages?: number[];
86
125
  producer?: string;
126
+ /**
127
+ * Populate {@link RenderResult.regions} with the schema-field geometry
128
+ * sidecar (the same entries {@link LiveSession.regions} serves), for
129
+ * consumers without a live session — e.g. overlays over a one-shot SVG
130
+ * export. Defaults to `false`: exports pay no introspection cost.
131
+ */
132
+ regions?: boolean;
133
+ }
134
+
135
+ /**
136
+ * How precisely a {@link ContentHit.pos} resolved — the marker a caret UI reads
137
+ * to decide whether to trust the offset. Never sub-cluster: `'cluster'` is the
138
+ * finest, `'segment'` the floor it degrades to on origin-less ink.
139
+ *
140
+ * - `'cluster'` — `pos` is the first content char of the cluster under the point
141
+ * (an escaped/CJK/shaping cluster floors to its first char). Place the caret
142
+ * at `pos` directly.
143
+ * - `'segment'` — the point hit origin-less ink (list markers, numbering, a
144
+ * multi-line code fence's interior), so `pos` degraded to the containing
145
+ * segment's start. Treat `pos` as the selected segment, not a caret.
146
+ */
147
+ export type HitGranularity = 'cluster' | 'segment';
148
+
149
+ /** A click resolved to a field and USV offset into its Content. */
150
+ export interface ContentHit {
151
+ field: string;
152
+ pos: number;
153
+ /**
154
+ * Whether {@link pos} is cluster-exact or floored to the segment start
155
+ * ({@link HitGranularity}). Absent when the backend does not report it.
156
+ */
157
+ granularity?: HitGranularity;
158
+ }
159
+
160
+ /**
161
+ * A rendered field region: the quill schema field address (`field`) plus its
162
+ * geometry (`rect`) on the page. Emitted by backends that place schema fields
163
+ * (`pdfform` AcroForm widgets; Typst form-fields and span-tracked content —
164
+ * richtext bodies, `richtext[]` elements, card content fields, direct scalar
165
+ * references). Only fields with a schema address produce a region — a
166
+ * backend-only widget produces none, and the backend widget name never
167
+ * appears.
168
+ *
169
+ * Use it to scroll to / highlight the focused field's rect; for the click
170
+ * direction use {@link LiveSession.fieldAt}, which resolves a point on *any*
171
+ * placement, not just the first one surfaced here. Geometry only —
172
+ * `LiveSession.paint` already bakes every value into the raster (see
173
+ * {@link LiveSession}), so a region is never a compositing input.
174
+ *
175
+ * COORDINATE TRANSFORM. `rect` is in PDF points with a **bottom-left** origin.
176
+ *
177
+ * For an **HTML/CSS overlay** on a `width:100%` canvas, position hotspots as
178
+ * percentages of the page — they track the displayed size across DPI and pane
179
+ * resize for free, and only the Y axis flips:
180
+ *
181
+ * ```js
182
+ * const [x0, y0, x1, y1] = region.rect; // PDF pt, bottom-left origin
183
+ * const left = (x0 / pageWidthPt) * 100; // % of page (from PageSize.widthPt)
184
+ * const top = (1 - y1 / pageHeightPt) * 100; // % — flip Y (from PageSize.heightPt)
185
+ * const width = ((x1 - x0) / pageWidthPt) * 100;
186
+ * const height = ((y1 - y0) / pageHeightPt) * 100;
187
+ * ```
188
+ *
189
+ * For painting **into a raster** at `renderScale` (= `layoutScale × densityScale`),
190
+ * use the device-pixel form instead:
191
+ *
192
+ * ```js
193
+ * const left = x0 * renderScale;
194
+ * const top = (pageHeightPt - y1) * renderScale; // flip Y
195
+ * ```
196
+ */
197
+ export interface FieldRegion {
198
+ /** Quill schema field path (e.g. `"signature_block"`), not a backend widget name. */
199
+ field: string;
200
+ /** 0-based page index. */
201
+ page: number;
202
+ /** `[x0, y0, x1, y1]` in PDF points (1/72″), bottom-left origin. */
203
+ rect: [number, number, number, number];
204
+ /**
205
+ * The content slice this box covers — USV `[start, end)` into the field's
206
+ * `Content` for content ink (one segment), absent for a scalar reference
207
+ * site or widget. Consumers key segment highlights on it;
208
+ * {@link LiveSession.fieldBoxes} unions same-page segments for the
209
+ * whole-field box.
210
+ */
211
+ span?: [number, number];
87
212
  }
88
213
 
89
214
  /** Canonical contract every backend build must satisfy. Result of one render. */
@@ -92,6 +217,14 @@ export interface RenderResult {
92
217
  warnings: Diagnostic[];
93
218
  outputFormat: OutputFormat;
94
219
  renderTimeMs: number;
220
+ /**
221
+ * Schema-field geometry sidecar — populated only when
222
+ * {@link RenderOptions.regions} requested it; empty otherwise. The same
223
+ * entries {@link LiveSession.regions} serves, for consumers without a live
224
+ * session. Page indices are document-space even under a `pages` subset
225
+ * render.
226
+ */
227
+ regions: FieldRegion[];
95
228
  }
96
229
 
97
230
  /** Canonical contract every backend build must satisfy. The emittable formats. */
@@ -99,7 +232,6 @@ export type OutputFormat = 'pdf' | 'svg' | 'txt' | 'png';
99
232
 
100
233
  /**
101
234
  * Canonical contract every backend build must satisfy. Page geometry in pt.
102
- * @experimental Part of the iterative-session/canvas surface — see {@link RenderSession}.
103
235
  */
104
236
  export interface PageSize {
105
237
  widthPt: number;
@@ -108,7 +240,6 @@ export interface PageSize {
108
240
 
109
241
  /**
110
242
  * Canonical contract every backend build must satisfy. Inputs to `paint`.
111
- * @experimental Part of the iterative-session/canvas surface — see {@link RenderSession}.
112
243
  */
113
244
  export interface PaintOptions {
114
245
  layoutScale?: number;
@@ -117,13 +248,36 @@ export interface PaintOptions {
117
248
 
118
249
  /**
119
250
  * Canonical contract every backend build must satisfy. Output of `paint`.
120
- * @experimental Part of the iterative-session/canvas surface — see {@link RenderSession}.
121
251
  */
122
252
  export interface PaintResult {
123
- layoutWidth: number;
253
+ layoutWidth: number; // canvas.style.width target; independent of densityScale
124
254
  layoutHeight: number;
125
- pixelWidth: number;
255
+ pixelWidth: number; // canvas.width the painter wrote (clamped at 16384)
126
256
  pixelHeight: number;
257
+ /**
258
+ * True when `MAX_BACKING_DIMENSION` forced `densityScale` down: the page is
259
+ * painted at fewer device pixels than requested and renders soft at the same
260
+ * `canvas.style` size. Reads the clamp off the return value instead of the
261
+ * `pixelWidth < round(layoutWidth × densityScale)` derivation.
262
+ */
263
+ clamped: boolean;
264
+ /**
265
+ * The `densityScale` actually applied — equal to the requested value unless
266
+ * `clamped`, then reduced proportionally. `layoutScale × effectiveDensityScale`
267
+ * is the scale the backing store was rasterized at.
268
+ */
269
+ effectiveDensityScale: number;
270
+ }
271
+
272
+ /**
273
+ * Canonical contract every backend build must satisfy. Output of
274
+ * {@link LiveSession.apply}: `dirtyPages` lists the pages whose rendered
275
+ * content differs from the previous compile, including added pages; removed
276
+ * pages are implied by `pageCount`. Repaint `dirty ∩ visible`.
277
+ */
278
+ export interface ChangeSet {
279
+ pageCount: number;
280
+ dirtyPages: number[];
127
281
  }
128
282
 
129
283
  /**
@@ -161,16 +315,20 @@ export interface EngineOptions {
161
315
  export declare class Engine {
162
316
  constructor(options?: EngineOptions);
163
317
 
164
- /** Render `doc` against `quill` in one shot. */
318
+ /**
319
+ * Render `doc` against `quill` in one shot. Both handles are read
320
+ * synchronously before the first await, so the caller may `free()` them as
321
+ * soon as this call returns.
322
+ */
165
323
  render(quill: Quill, doc: Document, options?: RenderOptions): Promise<RenderResult>;
166
324
 
167
325
  /**
168
- * Open an iterative render session (canvas preview / per-page paint).
169
- * @experimental Ships ahead of its first production consumer (the designed
170
- * canvas live-preview path see `prose/canon/PREVIEW.md`). The session/paint
171
- * surface may change in any 0.x release; `render()` is the stable path.
326
+ * Open a live render session (canvas preview / per-page paint / `apply`).
327
+ * The `quill` and `doc` handles are read synchronously before the first
328
+ * await, so the caller may `free()` them as soon as this call returns; the
329
+ * caller owns the returned session and must `.free()` it.
172
330
  */
173
- open(quill: Quill, doc: Document): Promise<RenderSession>;
331
+ open(quill: Quill, doc: Document): Promise<LiveSession>;
174
332
 
175
333
  /**
176
334
  * Output formats `quill`'s backend can emit. An ALWAYS-free pre-render probe:
@@ -180,29 +338,126 @@ export declare class Engine {
180
338
  supportedFormats(quill: Quill): Promise<OutputFormat[]>;
181
339
 
182
340
  /**
183
- * Whether `quill`'s backend can paint sessions to a canvas. Same always-free
184
- * probe as `supportedFormats`: answered from the descriptor's required
185
- * `canvas` manifest, no binary load and no quill clone.
186
- * @experimental Probes the experimental session/canvas surface see {@link RenderSession}.
341
+ * Whether `quill`'s BACKEND can paint sessions to a canvas a pre-session
342
+ * ESTIMATE, not a fact about any particular compile. Same always-free probe
343
+ * as `supportedFormats`: answered from the descriptor's required `canvas`
344
+ * manifest, no binary load and no quill clone. Both the Typst and pdfform
345
+ * backends report `true` here unconditionally; each paints a complete page
346
+ * raster (see {@link LiveSession.paint}) — but a specific compile can still
347
+ * refuse to paint (e.g. a 0-page document), so this can answer `true` while
348
+ * the resulting {@link LiveSession.supportsCanvas} answers `false`. Gate
349
+ * mounting a canvas UI on this; gate the actual `paint` call on the session's
350
+ * getter once `open()` has run.
187
351
  */
188
352
  supportsCanvas(quill: Quill): Promise<boolean>;
189
353
  }
190
354
 
191
355
  /**
192
356
  * Iterative render session over a compiled snapshot. `free()` when done.
193
- * @experimental The whole session/canvas-paint surface (`Engine.open`,
194
- * `RenderSession`, `PaintOptions`, `PaintResult`, `PageSize`) ships ahead of
195
- * its first production consumer and may change shape in any 0.x release.
196
- * The stable render path is `Engine.render`.
357
+ *
358
+ * CANVAS PAINT IS COMPLETE. {@link LiveSession.paint} writes a complete page
359
+ * raster every piece of page content is already visible in the painted
360
+ * pixels, with NO compositing required by the caller. Both backends that
361
+ * support canvas satisfy this: Typst rasterizes its laid-out page natively;
362
+ * pdfform pre-flattens bound field values into the page content and rasterizes
363
+ * that, so field values appear in the raster on their own.
364
+ * {@link LiveSession.regions} carries schema-field geometry for interactive
365
+ * overlays / cross-navigation drawn on top of the raster; it is never needed to
366
+ * complete the picture.
197
367
  */
198
- export declare class RenderSession {
368
+ export declare class LiveSession {
199
369
  private constructor();
200
370
  readonly pageCount: number;
201
371
  readonly backendId: string;
372
+ /**
373
+ * `true` iff `paint`/`pageSize` will succeed for THIS compile — the
374
+ * authoritative answer, derived from the session's canvas seam, so it can
375
+ * never disagree with what `paint` actually does. This can be `false` even
376
+ * when {@link Engine.supportsCanvas} answered `true` for the same `quill`
377
+ * (that probe is a pre-session backend estimate; e.g. a canvas-capable
378
+ * backend compiled to a 0-page document has nothing to paint). Re-check
379
+ * this getter after `open()` rather than relying on the engine hint alone.
380
+ */
202
381
  readonly supportsCanvas: boolean;
203
382
  readonly warnings: Diagnostic[];
383
+ /**
384
+ * Recompile the session against `doc` — the edit verb of a live preview.
385
+ * Transactional: on throw every read (`render`, `paint`, `pageSize`,
386
+ * `regions`) keeps serving the last-good compile, and the session recovers
387
+ * on the next successful `apply`. On success reads serve the new compile;
388
+ * repaint `dirtyPages ∩ visible`.
389
+ */
390
+ apply(doc: Document): ChangeSet;
204
391
  render(options?: RenderOptions): RenderResult;
392
+ /**
393
+ * Schema-field geometry for this compiled session, keyed on quill schema
394
+ * field path. A session-level query: no render, no byte artifact. Read it
395
+ * to scroll to / highlight the focused field over a `paint`-ed canvas;
396
+ * the click direction is {@link fieldAt}. Empty for backends that place
397
+ * no schema fields.
398
+ *
399
+ * `field` is **not** unique: a content field surfaces its **first
400
+ * placement** as one {@link FieldRegion} per page that placement touches
401
+ * (so a highlight covers continuation pages); a scalar referenced at
402
+ * several plate sites surfaces each site; tracked content plus a
403
+ * `field:`-bound widget yields both, widget ordered first. Group by
404
+ * `field` — every entry routes to that field. Later placements of one
405
+ * content value are not enumerated; {@link fieldAt} still resolves
406
+ * clicks on them.
407
+ */
408
+ regions(): FieldRegion[];
409
+ /**
410
+ * The whole-field highlight boxes for `field` — one union rect per page,
411
+ * over the field's `span`-bearing content segments (the "highlight the
412
+ * focused field" quantity). Owns the union {@link regions} leaves derived
413
+ * (span-filter + per-page union), keeping `regions()` the low-level disjoint
414
+ * truth, so a consumer stops reimplementing it. **Content only** — a field
415
+ * placed solely as a scalar reference or a bound widget carries no `span`
416
+ * and returns `[]`; its box is a single {@link regions} rect. Reflects the
417
+ * current compile, like `regions()`.
418
+ */
419
+ fieldBoxes(field: string): FieldRegion[];
420
+ /**
421
+ * The schema field whose content is under a point on `page` — the forward
422
+ * (click → field) direction: hit-test a click against the compiled
423
+ * document and get back the field address to focus in the editor, or
424
+ * `undefined` off any field's ink. `x`/`y` are PDF points with a
425
+ * **bottom-left** origin, the same space as {@link FieldRegion.rect} —
426
+ * from a canvas click, invert the overlay transform documented there:
427
+ * `x = clickPx.x / renderScale`,
428
+ * `y = pageHeightPt - clickPx.y / renderScale`. Unlike {@link regions},
429
+ * *every* placement answers, not just the first.
430
+ */
431
+ fieldAt(page: number, x: number, y: number): string | undefined;
432
+ /**
433
+ * Fine-grained click → content position (caret placement). Same PDF-point
434
+ * space as {@link fieldAt}; `undefined` off all content ink.
435
+ */
436
+ positionAt(page: number, x: number, y: number): ContentHit | undefined;
437
+ /** Content position → caret rect — reverse of {@link positionAt}. */
438
+ locate(field: string, pos: number): FieldRegion | undefined;
439
+ /** Page geometry in points (1/72″). Report-only; the painter sizes the canvas. */
205
440
  pageSize(page: number): PageSize;
441
+ /**
442
+ * Paint `page` into a 2D canvas context, sizing the backing store itself
443
+ * (it owns `canvas.width`/`height`; the caller owns `canvas.style.*`). The
444
+ * painted raster is COMPLETE — all page content visible, no caller-side
445
+ * compositing (Typst rasterizes natively; pdfform rasterizes its
446
+ * pre-flattened page). Effective rasterization scale is
447
+ * `layoutScale × densityScale`, clamped so neither backing dimension exceeds
448
+ * 16384 px — {@link PaintResult.clamped} reports the clamp and
449
+ * {@link PaintResult.effectiveDensityScale} the density actually applied.
450
+ *
451
+ * The write is a whole-backing-store `putImageData`, which bypasses the 2D
452
+ * context transform, `globalAlpha`, and clip: the painter owns the entire
453
+ * canvas, so give each visible page its own `` element. You cannot
454
+ * paint two pages into one canvas, paint into a sub-rect, or apply a context
455
+ * transform through this call — the raster is complete precisely so you never
456
+ * need to. Keep the per-page canvases alive while their pages stay near the
457
+ * viewport: each `paint` re-rasterizes from scratch, so reusing (pooling) a
458
+ * canvas across pages on scroll re-runs a full render, whereas an idle canvas
459
+ * retains its pixels for free.
460
+ */
206
461
  paint(
207
462
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
208
463
  page: number,
@@ -210,3 +465,186 @@ export declare class RenderSession {
210
465
  ): PaintResult;
211
466
  free(): void;
212
467
  }
468
+
469
+ // ── Typed writer — the schema-bound front door ───────────────────────────────
470
+
471
+ // `quill.writer(doc)` is patched onto the re-exported `Quill` prototype (the
472
+ // class is re-exported verbatim, so the method is declared by merging into the
473
+ // core module's `Quill` rather than redeclaring the class).
474
+ declare module '../core/wasm.js' {
475
+ interface Quill {
476
+ /**
477
+ * Bind this quill's schema to `doc` for typed writes — the documented
478
+ * front door, mirroring core's `quill.writer(&mut doc)`. The schema grants
479
+ * the typing, so the quill is the factory. The returned writer holds both
480
+ * handles by reference and owns neither (nothing to `free()`); it is
481
+ * ephemeral by convention — bind, write, discard.
482
+ */
483
+ writer(doc: Document): DocumentWriter;
484
+ /**
485
+ * Bind this quill's schema to `doc` for interpreted reads — the read twin of
486
+ * {@link Quill.writer}, mirroring core's `quill.view(&doc)`. Each field is
487
+ * read by its declared type (a richtext field to markdown, every other type
488
+ * verbatim) with schema authority, so a name the schema does not declare
489
+ * throws rather than reading back `undefined`. Holds both handles by
490
+ * reference and owns neither (nothing to `free()`); ephemeral by convention —
491
+ * bind, read, discard.
492
+ */
493
+ view(doc: Document): DocumentView;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * A `Document` bound to its `Quill` for typed writes — the schema-bound writer,
499
+ * constructed via {@link Quill.writer}. Speaks names, values, and markdown. Bare
500
+ * `set` / `setAll` / `setBody` / `reviseField` / `addCard` / `card(i).set`
501
+ * instead of threading the `quill` handle through the underscored ABI. Holds both
502
+ * handles by reference and owns neither — nothing to `free()`.
503
+ *
504
+ * Typed commit is the default whenever a quill is in hand: it resolves each
505
+ * field's schema type and strict-commits it, throwing `UnknownField` for a name
506
+ * the schema does not declare — on the typed path an undeclared name is a typo,
507
+ * not a fallback. The raw `Document.storeField` / `storeFields` verbs remain the
508
+ * deliberate quill-free primitive (standalone data, storage/migration infra, or
509
+ * holding not-yet-conforming in-progress input).
510
+ */
511
+ export declare class DocumentWriter {
512
+ constructor(quill: Quill, doc: Document);
513
+ /** The bound document — the instance passed in, mutated in place. */
514
+ readonly document: Document;
515
+ /**
516
+ * Typed-commit one main-card field (strict coerce, mismatch throws now).
517
+ * Throws `UnknownField` for a name the schema does not declare.
518
+ */
519
+ set(name: string, value: unknown): void;
520
+ /**
521
+ * Typed-commit several main-card fields atomically — nothing is applied on
522
+ * error (throws a {@link QuillmarkError} carrying one diagnostic per
523
+ * offending field, including an `UnknownField` for each undeclared name).
524
+ */
525
+ setAll(fields: Record<string, unknown>): void;
526
+ /**
527
+ * Set the main body from markdown (edit semantics: anchors rebase), discarding
528
+ * the delta — the receipt-free body write. Use `doc.revise({}, md)` for the
529
+ * `Delta` receipt.
530
+ */
531
+ setBody(markdown: string): void;
532
+ /**
533
+ * Revise the richtext main-card field `name` from markdown — typed *and*
534
+ * anchor-preserving. Surviving anchors rebase, then the diffed result is
535
+ * schema-conformed (`richtext(inline)` rejects a multi-block result). Throws
536
+ * `UnknownField` for a name the schema does not declare. Returns the `Delta`.
537
+ */
538
+ reviseField(name: string, markdown: string): Delta;
539
+ /**
540
+ * Build a composable card of `kind`, typed-commit `fields` onto it, set its
541
+ * body from optional markdown, and place it — the fused `makeCard` + typed
542
+ * commit + insertion. `at` picks the position: omitted appends, a number
543
+ * inserts at that index, so a positioned typed insert is one atomic call
544
+ * rather than `addCard` + `moveCard`. Transactional: a rejected field (throws
545
+ * a per-field diagnostic bundle) or an invalid kind/body/position leaves the
546
+ * document untouched.
547
+ */
548
+ addCard(kind: string, fields?: Record<string, unknown>, body?: string, at?: number): void;
549
+ /** Remove the composable card at `index`, returning it (or `undefined`). */
550
+ removeCard(index: number): Card | undefined;
551
+ /**
552
+ * A {@link CardWriter} for the composable card at `index`. Index validity is
553
+ * checked lazily at commit time, so this never throws. The cursor is
554
+ * ephemeral — a `removeCard`/`addCard` between binding and writing silently
555
+ * retargets it; for durable addressing stamp `$id` and re-resolve at write.
556
+ */
557
+ card(index: number): CardWriter;
558
+ }
559
+
560
+ /**
561
+ * A composable card bound to its `Quill` for typed writes, from
562
+ * {@link DocumentWriter.card}. Same verbs as {@link DocumentWriter}, targeting
563
+ * the card at its bound index; each write throws `IndexOutOfRange` if that index
564
+ * is out of range.
565
+ */
566
+ export declare class CardWriter {
567
+ constructor(quill: Quill, doc: Document, index: number);
568
+ /** The bound card index. */
569
+ readonly index: number;
570
+ /**
571
+ * The bound card's `$kind` (empty string when it carries none), read through
572
+ * the document — mirrors core `CardWriter::kind()`. Throws `IndexOutOfRange`
573
+ * if the bound index is out of range.
574
+ */
575
+ readonly kind: string;
576
+ set(name: string, value: unknown): void;
577
+ setAll(fields: Record<string, unknown>): void;
578
+ /** Set this card's body from markdown (edit semantics), discarding the delta. */
579
+ setBody(markdown: string): void;
580
+ /**
581
+ * Revise the richtext field `name` on this card from markdown — typed *and*
582
+ * anchor-preserving; the card twin of {@link DocumentWriter.reviseField}.
583
+ * Throws `UnknownField` for an undeclared name and `IndexOutOfRange` if the
584
+ * bound index is out of range. Returns the `Delta`.
585
+ */
586
+ reviseField(name: string, markdown: string): Delta;
587
+ }
588
+
589
+ /**
590
+ * A `Document` bound to its `Quill` for interpreted reads — the schema-plane read
591
+ * view, constructed via {@link Quill.view} and the read twin of
592
+ * {@link DocumentWriter}. One `get` reads each field by its declared type: a
593
+ * richtext field to its markdown projection, a plaintext field to its literal
594
+ * text, every other type its canonical value verbatim. Holds both handles by
595
+ * reference and owns neither — nothing to `free()`.
596
+ *
597
+ * The schema authority is the point: unlike the quill-free transport `Document.get`,
598
+ * a name the schema does not declare throws `UnknownField` (a typo) rather than
599
+ * reading back `undefined`, and a content field holding a value that does not
600
+ * decode throws `FieldRichtextDecode`. A field's markdown lives here, not on the
601
+ * body-only `Document.getMarkdown`. The body read stays quill-free (a body's type
602
+ * is a format fact) and never throws.
603
+ */
604
+ export declare class DocumentView {
605
+ constructor(quill: Quill, doc: Document);
606
+ /** The bound document — the instance passed in. */
607
+ readonly document: Document;
608
+ /**
609
+ * Read the value at `addr`, interpreted by its declared type: a richtext field
610
+ * to markdown, every other type verbatim. A bare string is `Addr` shorthand for
611
+ * `{ field }`; an absent `addr.field` reads the body markdown. `undefined` for
612
+ * an absent field; throws `UnknownField` for a name the schema does not declare,
613
+ * `FieldRichtextDecode` for a richtext field holding an undecodable value, and
614
+ * `IndexOutOfRange` for a bad `addr.card`.
615
+ */
616
+ get(addr: Addr | string): unknown;
617
+ /** The main body's markdown — the quill-free body read. Equals `get({})`. */
618
+ getBody(): string;
619
+ /**
620
+ * A {@link CardView} for the composable card at `index`. Index validity is
621
+ * checked lazily at read time, so this never throws. The cursor is ephemeral —
622
+ * a `removeCard`/`addCard` between binding and reading silently retargets it.
623
+ */
624
+ card(index: number): CardView;
625
+ }
626
+
627
+ /**
628
+ * A composable card bound to its `Quill` for interpreted reads, from
629
+ * {@link DocumentView.card}. Same verbs as {@link DocumentView}, reading the card
630
+ * at its bound index; each read throws `IndexOutOfRange` if that index is out of
631
+ * range.
632
+ */
633
+ export declare class CardView {
634
+ constructor(quill: Quill, doc: Document, index: number);
635
+ /** The bound card index. */
636
+ readonly index: number;
637
+ /**
638
+ * The bound card's `$kind` (empty string when it carries none). Throws
639
+ * `IndexOutOfRange` if the bound index is out of range.
640
+ */
641
+ readonly kind: string;
642
+ /**
643
+ * Read the field `name` on this card, interpreted by its declared type.
644
+ * `undefined` when absent; throws `UnknownField` for an undeclared name and
645
+ * `IndexOutOfRange` for a bad index.
646
+ */
647
+ get(name: string): unknown;
648
+ /** This card's body markdown — the card twin of {@link DocumentView.getBody}. */
649
+ getBody(): string;
650
+ }