@quillmark/wasm 0.104.0 → 0.105.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.
@@ -1,22 +1,16 @@
1
1
  // @quillmark/wasm/runtime: canonical consumer API.
2
2
  //
3
- // Render-side types (`RenderResult`, `RenderOptions`, `Artifact`,
4
- // `OutputFormat`, `PageSize`, `PaintOptions`, `PaintResult`) are defined HERE as
5
- // the canonical, backend-neutral render contract: NOT sourced from any one
6
- // private backend build. A type-level drift guard (`runtime.types.test-d.ts`,
7
- // via `npm run typecheck`) asserts they stay mutually assignable with the Typst
8
- // backend's generated declarations. `Engine` is the render dispatcher that hides
9
- // the cross-WASM-memory seam.
10
-
11
- // CANONICAL INVARIANT: the `Quill`/`Document` `init` resolves to ARE the core
12
- // build's classes, their full surface, never wrappers. There is exactly one
13
- // public entry point, so this is a structural fact. Handing out a wrapper is a
14
- // breaking design change, not a refactor. See runtime.js.
3
+ // The render-side types are defined HERE as the backend-neutral render contract,
4
+ // not sourced from any one private backend build; `runtime.types.test-d.ts`
5
+ // asserts they stay mutually assignable with the Typst backend's generated
6
+ // declarations.
15
7
  //
16
- // ONE COPY PER PROCESS: two copies of this package are two WASM linear memories
17
- // and two `Quill`/`Document` classes. Every method taking a handle refuses one
18
- // belonging to another copy, with a `QuillmarkError` naming `npm ls
19
- // @quillmark/wasm`. Errors are the exception: `isQuillmarkError` is structural.
8
+ // The `Quill`/`Document` `init` resolves to ARE the core build's classes, never
9
+ // wrappers. Two copies of this package are two WASM linear memories and two
10
+ // `Quill`/`Document` classes, so every method taking a handle refuses one
11
+ // belonging to another copy, with a `QuillmarkError` naming
12
+ // `npm ls @quillmark/wasm`. Errors are the exception: `isQuillmarkError` is
13
+ // structural.
20
14
 
21
15
  // The instance types, so an annotation (`let q: Quill`) needs no await. Their
22
16
  // values are `CoreSurface`'s.
@@ -35,13 +29,11 @@ import type {
35
29
  } from '../core/wasm.js';
36
30
 
37
31
  /**
38
- * The core build's surface: what its WASM instance stands behind, and therefore
39
- * what `init` resolves to. Exported nowhere statically, so awaiting is the only
40
- * way to hold one.
32
+ * The core build's surface, and therefore what `init` resolves to. Exported
33
+ * nowhere statically, so awaiting is the only way to hold one.
41
34
  *
42
- * `Quill` and `Document` here are the classes, statics included
43
- * (`Quill.fromTree`, `Document.fromMarkdown`), not the instance types above.
44
- * Each member carries the core build's own declaration, docs and all.
35
+ * `Quill` and `Document` here are the classes, statics included, not the
36
+ * instance types above.
45
37
  */
46
38
  export interface CoreSurface {
47
39
  Quill: typeof CoreQuill;
@@ -62,24 +54,17 @@ export interface CoreSurface {
62
54
  * const { Quill, Document } = await init();
63
55
  * ```
64
56
  *
65
- * The builds are `--target web`: classes export synchronously, the instance
66
- * behind them arrives here. Identical in every environment: the binary is
67
- * fetched and streamed in a browser, read off disk under Node, and the call
68
- * site is the same line.
57
+ * The same line works everywhere: the binary streams from a URL in a browser
58
+ * and is read off disk under Node.
69
59
  *
70
- * THE ONLY DOOR to `Quill`, `Document` and the free functions, so the pre-init
60
+ * The only door to `Quill`, `Document` and the free functions, so the pre-init
71
61
  * mistake is not expressible. Destructure at each entry point (route loader,
72
62
  * hydration path, worker) rather than threading one result around: the gate is
73
- * memoized and concurrency-safe, so every await after the first is free. A
74
- * failed init clears the memo, so a retry is possible. Per realm: a Worker
75
- * loads and initializes its own copy.
63
+ * memoized and concurrency-safe, so every await after the first is free, and a
64
+ * failed init clears the memo. Each realm initializes its own copy.
76
65
  *
77
- * Both failure codes REJECT, so one `catch` covers the gate. Delivery follows
78
- * the function kind across this surface: a sync verb throws, a
79
- * promise-returning verb rejects, and nothing does both.
80
- *
81
- * Backends are NOT initialized here. `Engine` instantiates a backend inside its
82
- * lazy load, on first render against it.
66
+ * Both failure codes reject, so one `catch` covers the gate. Backends are not
67
+ * initialized here: `Engine` instantiates one on first render against it.
83
68
  *
84
69
  * @param source override the binary's source (bytes, a `Response`, a
85
70
  * `WebAssembly.Module`, a URL) for hosts that route assets themselves or
@@ -92,11 +77,9 @@ export declare function init(source?: InitInput): Promise<CoreSurface>;
92
77
  import type { CardAddr } from '../core/wasm.js';
93
78
 
94
79
  /**
95
- * The main card's address: the default target of the card-scoped verbs
96
- * (`storeFields` / `storeExt` / `commitFields` / …). A named, {@link CardAddr}-typed
97
- * alias for the empty address `{}`, so a main-card write names its target:
98
- * `doc.storeFields(MAIN_CARD_ADDR, fields)`. It IS `{}` (frozen at runtime), a
99
- * pure alias: `{}` and `undefined` stay equally valid. A card selector only,
80
+ * The main card's address: a named, {@link CardAddr}-typed alias for the empty
81
+ * address `{}`, so a main-card write names its target. It *is* `{}` (frozen at
82
+ * runtime), so `{}` and `undefined` stay equally valid. A card selector only,
100
83
  * never a field address.
101
84
  */
102
85
  export declare const MAIN_CARD_ADDR: CardAddr;
@@ -193,23 +176,17 @@ export interface QuillmarkError extends Error {
193
176
  /**
194
177
  * Narrow an unknown caught value to {@link QuillmarkError}. Structural
195
178
  * (`Error` carrying a `diagnostics` array), so it narrows errors from any build
196
- * or WASM instance in the page. Handles are the opposite: a `Quill` or
197
- * `Document` from a second copy of this package is rejected wherever it is
198
- * passed, since two copies are two linear memories. An error is data, not a
199
- * handle, so nothing is gained by refusing one that crossed.
179
+ * or WASM instance in the page unlike a handle, which is refused when it
180
+ * comes from a second copy of this package.
200
181
  */
201
182
  export declare function isQuillmarkError(e: unknown): e is QuillmarkError;
202
183
 
203
- // ── Open-set discriminant guards ────────────────────────────────────────────
204
184
  // `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
205
185
  // `ContentContainer.container` are open sets: each union has a residual
206
186
  // `{ …: string; … }` arm, so a bare discriminant check never narrows the payload
207
187
  // (TS keeps the residual arm live, since a `string` can equal the literal).
208
- // These guards are the checked narrowing path for the pinned arms; an
209
- // unrecognized discriminant fails every guard and keeps its opaque payload. Only
210
- // the payload-carrying arms get a guard: the bare marks
211
- // (`strong`/`emph`/`underline`/`strike`/`code`), the payload-free lines
212
- // (`para`/`island`/`rule`), and `quote` narrow to nothing.
188
+ // These guards are the checked narrowing path for the pinned arms; only the
189
+ // payload-carrying arms get one, since the rest narrow to nothing.
213
190
 
214
191
  import type {
215
192
  ContentIsland,
@@ -260,14 +237,11 @@ export declare function isListItemContainer(
260
237
  ordinal: number;
261
238
  };
262
239
 
263
- // ── Open-set membership guards ──────────────────────────────────────────────
264
- // The guards above answer "is this arm X", one pinned arm at a time. These four
265
- // answer "is this a value this build knows?": the question a read-modify-write
266
- // consumer must ask, since lowering an edit restates every line's kind and
267
- // containers, and a construct the consumer cannot hold is gone on write-back
268
- // unless it is carried inertly. Without a predicate a consumer enumerates the
269
- // built-in names itself and re-couples to a closed set, going wrong at the first
270
- // release that adds one.
240
+ // The guards above answer "is this arm X". These four answer "is this a value
241
+ // this build knows?", the question a read-modify-write consumer must ask: an
242
+ // edit restates every line's kind and containers, so a construct the consumer
243
+ // cannot hold is gone on write-back unless carried inertly, and enumerating the
244
+ // built-in names by hand re-couples to a closed set.
271
245
  //
272
246
  // They classify unknown TAGS, not unknown payloads on known tags: a future
273
247
  // `kind: "footnote"` carrying a sibling `ref` loses `ref` at any consumer that
@@ -293,36 +267,30 @@ export declare function isUnknownIsland(
293
267
  island: ContentIsland
294
268
  ): island is ContentIsland & { type: string; props: unknown };
295
269
 
296
- // ── Canonical render-side types ─────────────────────────────────────────────
297
- // These are the BACKEND-NEUTRAL render contract of the plural-backend API. They
298
- // are defined HERE (not re-exported from one private backend) because no single
299
- // backend build owns the canonical API's types. Every backend build MUST satisfy
300
- // these shapes; that they match the Typst backend's generated declarations is
301
- // enforced by the type-level drift guard `crates/bindings/wasm/runtime.types.test-d.ts`
302
- // (run via `npm run typecheck`), so these and the generated
303
- // `pkg/backends/typst/wasm.d.ts` cannot silently diverge.
270
+ // The backend-neutral render contract, defined here rather than re-exported from
271
+ // one private backend because no single backend owns the canonical API's types.
272
+ // Every backend build must satisfy these shapes; `runtime.types.test-d.ts` keeps
273
+ // them from diverging from the generated `pkg/backends/typst/wasm.d.ts`.
304
274
 
305
275
  import type { Quill, Document, Card } from '../core/wasm.js';
306
276
  import type { Diagnostic } from '../core/wasm.js';
307
277
 
308
- /** Canonical contract every backend build must satisfy. One emitted output. */
278
+ /** One emitted output. */
309
279
  export interface Artifact {
310
280
  format: OutputFormat;
311
281
  bytes: Uint8Array;
312
282
  mimeType: string;
313
283
  }
314
284
 
315
- /** Canonical contract every backend build must satisfy. Options for one render. */
285
+ /** Options for one render. */
316
286
  export interface RenderOptions {
317
287
  format?: OutputFormat;
318
288
  ppi?: number;
319
289
  pages?: number[];
320
290
  producer?: string;
321
291
  /**
322
- * Populate {@link RenderResult.regions} with the schema-field geometry
323
- * sidecar (the same entries {@link LiveSession.regions} serves), for
324
- * consumers without a live session; e.g. overlays over a one-shot SVG
325
- * export. Defaults to `false`: exports pay no introspection cost.
292
+ * Populate {@link RenderResult.regions} with schema-field geometry, for
293
+ * consumers without a live session. Defaults to `false`.
326
294
  */
327
295
  regions?: boolean;
328
296
  }
@@ -332,12 +300,10 @@ export interface RenderOptions {
332
300
  * to decide whether to trust the offset. Never sub-cluster: `'cluster'` is the
333
301
  * finest, `'segment'` the floor it degrades to on origin-less ink.
334
302
  *
335
- * - `'cluster'`: `pos` is the first content char of the cluster under the point
336
- * (an escaped/CJK/shaping cluster floors to its first char). Place the caret
337
- * at `pos` directly.
338
- * - `'segment'`: the point hit origin-less ink (list markers, numbering, a
339
- * multi-line code fence's interior), so `pos` degraded to the containing
340
- * segment's start. Treat `pos` as the selected segment, not a caret.
303
+ * - `'cluster'`: `pos` is the first content char of the cluster under the point.
304
+ * Place the caret there directly.
305
+ * - `'segment'`: the point hit origin-less ink (list markers, numbering, a code
306
+ * fence's interior), so `pos` is the containing segment's start, not a caret.
341
307
  */
342
308
  export type HitGranularity = 'cluster' | 'segment';
343
309
 
@@ -358,24 +324,19 @@ export interface ContentHit {
358
324
 
359
325
  /**
360
326
  * A rendered field region: the canonical `DocPath` field address (`field`) plus
361
- * its geometry (`rect`) on the page. Emitted by backends that place schema fields
362
- * (`pdfform` AcroForm widgets; Typst form-fields and span-tracked content:
363
- * richtext bodies, `richtext[]` elements, card content fields, direct scalar
364
- * references). Only fields with a schema address produce a region: a
365
- * backend-only widget produces none, and the backend widget name never
327
+ * its geometry (`rect`) on the page. Only fields with a schema address produce
328
+ * one: a backend-only widget produces none, and the backend widget name never
366
329
  * appears.
367
330
  *
368
- * Use it to scroll to / highlight the focused field's rect; for the click
331
+ * Use it to scroll to or highlight the focused field's rect; for the click
369
332
  * direction use {@link LiveSession.fieldAt}, which resolves a point on *any*
370
- * placement, not just the first one surfaced here. Geometry only:
371
- * `LiveSession.paint` already bakes every value into the raster (see
372
- * {@link LiveSession}), so a region is never a compositing input.
333
+ * placement, not just the first one surfaced here.
373
334
  *
374
335
  * COORDINATE TRANSFORM. `rect` is in PDF points with a **bottom-left** origin.
375
336
  *
376
337
  * For an **HTML/CSS overlay** on a `width:100%` canvas, position hotspots as
377
- * percentages of the page; they track the displayed size across DPI and pane
378
- * resize for free, and only the Y axis flips:
338
+ * percentages of the page, so they track the displayed size across DPI and pane
339
+ * resize for free; only the Y axis flips:
379
340
  *
380
341
  * ```js
381
342
  * const [x0, y0, x1, y1] = region.rect; // PDF pt, bottom-left origin
@@ -396,12 +357,10 @@ export interface ContentHit {
396
357
  export interface FieldRegion {
397
358
  /**
398
359
  * The field's canonical `DocPath` address (`parseDocPath`-routable), not a
399
- * backend widget name: `main.signature_block` for a main field,
400
- * `cards.<kind>[<i>].signature_block` for a card field (`cards[<i>].…` when the
401
- * card's kind is unknown). Nested addresses spell out in full, an array
402
- * element bracketed and a key dotted `main.references[0]`,
403
- * `cards.<kind>[<i>].addr.city` — the same spelling `Diagnostic.path` uses,
404
- * so the two join on string equality.
360
+ * backend widget name: `main.signature_block`,
361
+ * `cards.<kind>[<i>].signature_block` (`cards[<i>].…` when the card's kind is
362
+ * unknown), an array element bracketed and a key dotted. The same spelling
363
+ * `Diagnostic.path` uses, so the two join on string equality.
405
364
  */
406
365
  field: string;
407
366
  /** 0-based page index. */
@@ -418,67 +377,54 @@ export interface FieldRegion {
418
377
  span?: [number, number];
419
378
  }
420
379
 
421
- /** Canonical contract every backend build must satisfy. Result of one render. */
380
+ /** Result of one render. */
422
381
  export interface RenderResult {
423
382
  artifacts: Artifact[];
424
383
  warnings: Diagnostic[];
425
384
  outputFormat: OutputFormat;
426
385
  renderTimeMs: number;
427
386
  /**
428
- * Schema-field geometry sidecar: populated only when
429
- * {@link RenderOptions.regions} requested it; empty otherwise. The same
430
- * entries {@link LiveSession.regions} serves, for consumers without a live
431
- * session. Page indices are document-space even under a `pages` subset
432
- * render.
387
+ * Schema-field geometry, populated only when {@link RenderOptions.regions}
388
+ * asked for it. Page indices are document-space even under a `pages` subset.
433
389
  */
434
390
  regions: FieldRegion[];
435
391
  }
436
392
 
437
- /** Canonical contract every backend build must satisfy. The emittable formats. */
393
+ /** The emittable formats. */
438
394
  export type OutputFormat = 'pdf' | 'svg' | 'png';
439
395
 
440
- /**
441
- * Canonical contract every backend build must satisfy. Page geometry in pt.
442
- */
396
+ /** Page geometry, in points. */
443
397
  export interface PageSize {
444
398
  widthPt: number;
445
399
  heightPt: number;
446
400
  }
447
401
 
448
- /**
449
- * Canonical contract every backend build must satisfy. Inputs to `paint`.
450
- */
402
+ /** Inputs to `paint`. */
451
403
  export interface PaintOptions {
452
404
  layoutScale?: number;
453
405
  densityScale?: number;
454
406
  }
455
407
 
456
- /**
457
- * Canonical contract every backend build must satisfy. Output of `paint`.
458
- */
408
+ /** Output of `paint`. */
459
409
  export interface PaintResult {
460
410
  layoutWidth: number; // canvas.style.width target; independent of densityScale
461
411
  layoutHeight: number;
462
412
  pixelWidth: number; // canvas.width the painter wrote (clamped at 16384)
463
413
  pixelHeight: number;
464
414
  /**
465
- * True when `MAX_BACKING_DIMENSION` forced `densityScale` down: the page is
466
- * painted at fewer device pixels than requested and renders soft at the same
467
- * `canvas.style` size. Reads the clamp off the return value instead of the
468
- * `pixelWidth < round(layoutWidth × densityScale)` derivation.
415
+ * True when the backing-store clamp forced `densityScale` down: the page
416
+ * renders soft at the same `canvas.style` size.
469
417
  */
470
418
  clamped: boolean;
471
419
  /**
472
- * The `densityScale` actually applied: equal to the requested value unless
473
- * `clamped`, then reduced proportionally. `layoutScale × effectiveDensityScale`
474
- * is the scale the backing store was rasterized at.
420
+ * The `densityScale` actually applied, reduced proportionally when
421
+ * `clamped`. `layoutScale × effectiveDensityScale` is the rasterized scale.
475
422
  */
476
423
  effectiveDensityScale: number;
477
424
  }
478
425
 
479
426
  /**
480
- * Canonical contract every backend build must satisfy. Output of
481
- * {@link LiveSession.update}: `dirtyPages` lists the pages whose rendered
427
+ * Output of {@link LiveSession.update}: `dirtyPages` lists the pages whose
482
428
  * content differs from the previous compile, including added pages; removed
483
429
  * pages are implied by `pageCount`. Repaint `dirty ∩ visible`.
484
430
  */
@@ -488,12 +434,11 @@ export interface ChangeSet {
488
434
  }
489
435
 
490
436
  /**
491
- * A backend registry entry. `load` is the lazy thunk returning the dynamically-
492
- * imported backend build module; `formats`/`canvas` are the REQUIRED static
493
- * capability manifest. That manifest is what makes
494
- * `Engine.supportedFormats`/`Engine.supportsCanvas` always FREE: they answer
495
- * from it directly, no backend binary is loaded and no quill is cloned into
496
- * backend memory. A malformed descriptor throws at `new Engine(...)`.
437
+ * A backend registry entry. `load` is the lazy thunk returning the
438
+ * dynamically-imported backend build module; `formats`/`canvas` are the required
439
+ * static capability manifest, which is what makes `Engine.supportedFormats` and
440
+ * `Engine.supportsCanvas` free: they answer from it without loading a backend
441
+ * binary or cloning a quill. A malformed descriptor throws at `new Engine(...)`.
497
442
  */
498
443
  export interface BackendDescriptor {
499
444
  load: () => Promise<unknown>;
@@ -538,23 +483,20 @@ export declare class Engine {
538
483
  open(quill: Quill, doc: Document): Promise<LiveSession>;
539
484
 
540
485
  /**
541
- * Output formats `quill`'s backend can emit. An ALWAYS-free pre-render probe:
542
- * it answers from the descriptor's required `formats` manifest WITHOUT loading
543
- * the backend binary or cloning the quill. Async for API stability.
486
+ * Output formats `quill`'s backend can emit. An always-free pre-render probe:
487
+ * it answers from the descriptor's `formats` manifest without loading the
488
+ * backend binary or cloning the quill. Async for API stability.
544
489
  */
545
490
  supportedFormats(quill: Quill): Promise<OutputFormat[]>;
546
491
 
547
492
  /**
548
- * Whether `quill`'s BACKEND can paint sessions to a canvas: a pre-session
549
- * ESTIMATE, not a fact about any particular compile. Same always-free probe
550
- * as `supportedFormats`: answered from the descriptor's required `canvas`
551
- * manifest, no binary load and no quill clone. Both the Typst and pdfform
552
- * backends report `true` here unconditionally; each paints a complete page
553
- * raster (see {@link LiveSession.paint}), but a specific compile can still
554
- * refuse to paint (e.g. a 0-page document), so this can answer `true` while
555
- * the resulting {@link LiveSession.supportsCanvas} answers `false`. Gate
556
- * mounting a canvas UI on this; gate the actual `paint` call on the session's
557
- * getter once `open()` has run.
493
+ * Whether `quill`'s backend can paint sessions to a canvas: a pre-session
494
+ * estimate, not a fact about any particular compile, answered from the
495
+ * descriptor's `canvas` manifest like `supportedFormats`. A specific compile
496
+ * can still refuse to paint (a 0-page document, say), so this can answer
497
+ * `true` while the resulting {@link LiveSession.supportsCanvas} answers
498
+ * `false`. Gate mounting a canvas UI on this, and the `paint` call itself on
499
+ * the session's getter.
558
500
  */
559
501
  supportsCanvas(quill: Quill): Promise<boolean>;
560
502
  }
@@ -562,14 +504,11 @@ export declare class Engine {
562
504
  /**
563
505
  * Iterative render session over a compiled snapshot. `free()` when done.
564
506
  *
565
- * CANVAS PAINT IS COMPLETE. {@link LiveSession.paint} writes a complete page
566
- * raster: every piece of page content is already visible in the painted
567
- * pixels, with NO compositing required by the caller. Both backends that
568
- * support canvas satisfy this: Typst rasterizes its laid-out page natively;
569
- * pdfform pre-flattens bound field values into the page content and rasterizes
570
- * that, so field values appear in the raster on their own.
571
- * {@link LiveSession.regions} carries schema-field geometry for interactive
572
- * overlays / cross-navigation drawn on top of the raster; it is never needed to
507
+ * CANVAS PAINT IS COMPLETE: {@link LiveSession.paint} writes a whole page
508
+ * raster, every piece of page content already visible in the painted pixels,
509
+ * with no compositing required by the caller pdfform pre-flattens bound field
510
+ * values into the page content to satisfy this. {@link LiveSession.regions}
511
+ * carries schema-field geometry for overlays drawn on top; it is never needed to
573
512
  * complete the picture.
574
513
  */
575
514
  export declare class LiveSession {
@@ -578,21 +517,18 @@ export declare class LiveSession {
578
517
  readonly backendId: string;
579
518
  /**
580
519
  * `true` iff `paint`/`pageSize` will succeed for THIS compile: the
581
- * authoritative answer, derived from the session's canvas seam, so it can
582
- * never disagree with what `paint` actually does. This can be `false` even
583
- * when {@link Engine.supportsCanvas} answered `true` for the same `quill`
584
- * (that probe is a pre-session backend estimate; e.g. a canvas-capable
585
- * backend compiled to a 0-page document has nothing to paint). Re-check
586
- * this getter after `open()` rather than relying on the engine hint alone.
520
+ * authoritative answer, which can be `false` even where
521
+ * {@link Engine.supportsCanvas} answered `true` for the same `quill` (a
522
+ * canvas-capable backend compiled to a 0-page document has nothing to paint).
523
+ * Re-check it after `open()` rather than relying on the engine hint.
587
524
  */
588
525
  readonly supportsCanvas: boolean;
589
526
  readonly warnings: Diagnostic[];
590
527
  /**
591
528
  * Recompile the session against `doc`: the edit verb of a live preview.
592
- * Transactional: on throw every read (`render`, `paint`, `pageSize`,
593
- * `regions`) keeps serving the last-good compile, and the session recovers
594
- * on the next successful `update`. On success reads serve the new compile;
595
- * repaint `dirtyPages ∩ visible`.
529
+ * Transactional on throw every read keeps serving the last-good compile and
530
+ * the session recovers on the next successful `update`. On success, repaint
531
+ * `dirtyPages visible`.
596
532
  */
597
533
  update(doc: Document): ChangeSet;
598
534
  render(options?: RenderOptions): RenderResult;
@@ -603,37 +539,29 @@ export declare class LiveSession {
603
539
  * field over a `paint`-ed canvas; the click direction is {@link fieldAt}.
604
540
  * Empty for backends that place no schema fields.
605
541
  *
606
- * `field` is **not** unique: a content field surfaces its **first
607
- * placement** as one {@link FieldRegion} per page that placement touches
608
- * (so a highlight covers continuation pages); a scalar referenced at
609
- * several plate sites surfaces each site; tracked content plus a
610
- * `field:`-bound widget yields both, widget ordered first. Group by
611
- * `field`: every entry routes to that field. Later placements of one
612
- * content value are not enumerated; {@link fieldAt} still resolves
613
- * clicks on them.
542
+ * `field` is **not** unique: a content field surfaces its **first placement**
543
+ * as one {@link FieldRegion} per page that placement touches, a scalar
544
+ * referenced at several plate sites surfaces each site, and tracked content
545
+ * plus a `field:`-bound widget yields both, widget first. Group by `field`.
546
+ * Later placements of one content value are not enumerated; {@link fieldAt}
547
+ * still resolves clicks on them.
614
548
  */
615
549
  regions(): FieldRegion[];
616
550
  /**
617
551
  * The whole-field highlight boxes for `field` (a canonical `DocPath` address,
618
- * as {@link regions} keys): one union rect per page, over the field's
619
- * `span`-bearing content segments (the "highlight the focused field"
620
- * quantity). Owns the union {@link regions} leaves derived
621
- * (span-filter + per-page union), keeping `regions()` the low-level disjoint
622
- * truth, so a consumer stops reimplementing it. **Content only**: a field
623
- * placed solely as a scalar reference or a bound widget carries no `span`
624
- * and returns `[]`; its box is a single {@link regions} rect. Reflects the
625
- * current compile, like `regions()`.
552
+ * as {@link regions} keys): one union rect per page over the field's
553
+ * `span`-bearing content segments, the union {@link regions} leaves derived.
554
+ * **Content only**: a field placed solely as a scalar reference or a bound
555
+ * widget carries no `span` and returns `[]`, its box being a single
556
+ * {@link regions} rect. Reflects the current compile.
626
557
  */
627
558
  fieldBoxes(field: string): FieldRegion[];
628
559
  /**
629
- * The schema field whose content is under a point on `page`, the forward
630
- * (click field) direction: hit-test a click against the compiled
631
- * document and get back the canonical `DocPath` field address
632
- * (`parseDocPath`-routable) to focus in the editor, or `undefined` off any
633
- * field's ink. `x`/`y` are PDF points with a **bottom-left** origin, the
634
- * same space as {@link FieldRegion.rect}, from a canvas click, invert the
635
- * overlay transform documented there:
636
- * `x = clickPx.x / renderScale`,
560
+ * The schema field whose content is under a point on `page`: the canonical
561
+ * `DocPath` address to focus in the editor, or `undefined` off any field's
562
+ * ink. `x`/`y` are PDF points with a **bottom-left** origin, the same space as
563
+ * {@link FieldRegion.rect}, so from a canvas click invert the overlay
564
+ * transform documented there: `x = clickPx.x / renderScale`,
637
565
  * `y = pageHeightPt - clickPx.y / renderScale`. Unlike {@link regions},
638
566
  * *every* placement answers, not just the first.
639
567
  */
@@ -651,24 +579,19 @@ export declare class LiveSession {
651
579
  /** Page geometry in points (1/72″). Report-only; the painter sizes the canvas. */
652
580
  pageSize(page: number): PageSize;
653
581
  /**
654
- * Paint `page` into a 2D canvas context, sizing the backing store itself
655
- * (it owns `canvas.width`/`height`; the caller owns `canvas.style.*`). The
656
- * painted raster is COMPLETE: all page content visible, no caller-side
657
- * compositing (Typst rasterizes natively; pdfform rasterizes its
658
- * pre-flattened page). Effective rasterization scale is
659
- * `layoutScale × densityScale`, clamped so neither backing dimension exceeds
660
- * 16384 px: {@link PaintResult.clamped} reports the clamp and
661
- * {@link PaintResult.effectiveDensityScale} the density actually applied.
582
+ * Paint `page` into a 2D canvas context, sizing the backing store itself (it
583
+ * owns `canvas.width`/`height`; the caller owns `canvas.style.*`). The
584
+ * rasterization scale is `layoutScale × densityScale`, clamped so neither
585
+ * backing dimension exceeds 16384 px; {@link PaintResult.clamped} reports the
586
+ * clamp and {@link PaintResult.effectiveDensityScale} the density applied.
662
587
  *
663
588
  * The write is a whole-backing-store `putImageData`, which bypasses the 2D
664
- * context transform, `globalAlpha`, and clip: the painter owns the entire
665
- * canvas, so give each visible page its own `` element. You cannot
666
- * paint two pages into one canvas, paint into a sub-rect, or apply a context
667
- * transform through this call: the raster is complete precisely so you never
668
- * need to. Keep the per-page canvases alive while their pages stay near the
669
- * viewport: each `paint` re-rasterizes from scratch, so reusing (pooling) a
670
- * canvas across pages on scroll re-runs a full render, whereas an idle canvas
671
- * retains its pixels for free.
589
+ * context transform, `globalAlpha`, and clip, so give each visible page its
590
+ * own canvas: no compositing, sub-rect, or transform reaches through this
591
+ * call, and the raster is complete precisely so none is needed. Keep the
592
+ * per-page canvases alive while their pages stay near the viewport: each
593
+ * `paint` re-rasterizes from scratch, whereas an idle canvas retains its
594
+ * pixels for free.
672
595
  */
673
596
  paint(
674
597
  ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
@@ -686,11 +609,9 @@ export declare class LiveSession {
686
609
  declare module '../core/wasm.js' {
687
610
  interface Quill {
688
611
  /**
689
- * Bind this quill's schema to `doc` for typed writes: the documented
690
- * front door, mirroring core's `quill.writer(&mut doc)`. The schema grants
691
- * the typing, so the quill is the factory. The returned writer holds both
692
- * handles by reference and owns neither (nothing to `free()`); it is
693
- * ephemeral by convention: bind, write, discard.
612
+ * Bind this quill's schema to `doc` for typed writes. The returned writer
613
+ * holds both handles by reference and owns neither (nothing to `free()`);
614
+ * it is ephemeral by convention: bind, write, discard.
694
615
  */
695
616
  writer(doc: Document): DocumentWriter;
696
617
  /**
@@ -707,18 +628,16 @@ declare module '../core/wasm.js' {
707
628
  }
708
629
 
709
630
  /**
710
- * A `Document` bound to its `Quill` for typed writes: the schema-bound writer,
711
- * constructed via {@link Quill.writer}. Speaks names, values, and markdown. Bare
712
- * `set` / `setAll` / `reviseBody` / `reviseField` / `addCard` / `card(i).set`
713
- * instead of threading the `quill` handle through the underscored ABI. Holds both
714
- * handles by reference and owns neither: nothing to `free()`.
631
+ * A `Document` bound to its `Quill` for typed writes, from {@link Quill.writer}.
632
+ * Speaks names, values, and markdown, so bare `set` / `setAll` / `reviseBody` /
633
+ * `reviseField` / `addCard` / `card(i).set` replace threading the `quill` handle
634
+ * through the underscored ABI. Holds both handles by reference and owns neither.
715
635
  *
716
636
  * Typed commit is the default whenever a quill is in hand: it resolves each
717
- * field's schema type and strict-commits it, throwing `UnknownField` for a name
718
- * the schema does not declare, on the typed path an undeclared name is a typo,
719
- * not a fallback. The raw `Document.storeField` / `storeFields` verbs remain the
720
- * deliberate quill-free primitive (standalone data, storage/migration infra, or
721
- * holding not-yet-conforming in-progress input).
637
+ * field's schema type and strict-commits it, throwing `UnknownField` for an
638
+ * undeclared name rather than falling back. The raw `Document.storeField` /
639
+ * `storeFields` verbs remain the deliberate quill-free primitive (standalone
640
+ * data, storage/migration infra, or not-yet-conforming in-progress input).
722
641
  */
723
642
  export declare class DocumentWriter {
724
643
  constructor(quill: Quill, doc: Document);
@@ -736,10 +655,9 @@ export declare class DocumentWriter {
736
655
  */
737
656
  setAll(fields: Record<string, unknown>): void;
738
657
  /**
739
- * Revise the main body from markdown (edit semantics: anchors rebase),
740
- * returning the text `Delta`. The content lane's `revise` reached through the
741
- * writer: a body carries no field schema to type against, so the receipt is
742
- * the content lane's.
658
+ * Revise the main body from markdown; anchors rebase. Returns the text
659
+ * `Delta`: a body carries no field schema to type against, so this is the
660
+ * content lane's `revise` reached through the writer.
743
661
  */
744
662
  reviseBody(markdown: string): Delta;
745
663
  /**
@@ -755,12 +673,10 @@ export declare class DocumentWriter {
755
673
  reviseField(name: string, text: string): Delta;
756
674
  /**
757
675
  * Build a composable card of `kind`, typed-commit `fields` onto it, set its
758
- * body from optional markdown, and place it: the fused `makeCard` + typed
759
- * commit + insertion. `at` picks the position: omitted appends, a number
760
- * inserts at that index, so a positioned typed insert is one atomic call
761
- * rather than `addCard` + `moveCard`. Transactional: a rejected field (throws
762
- * a per-field diagnostic bundle) or an invalid kind/body/position leaves the
763
- * document untouched.
676
+ * body from optional markdown, and place it. `at` omitted appends, a number
677
+ * inserts at that index. Transactional: a rejected field (throwing a per-field
678
+ * diagnostic bundle) or an invalid kind, body, or position leaves the document
679
+ * untouched.
764
680
  */
765
681
  addCard(kind: string, fields?: Record<string, unknown>, body?: string, at?: number): void;
766
682
  /** Remove the composable card at `index`, returning it (or `undefined`). */
@@ -786,9 +702,8 @@ export declare class CardWriter {
786
702
  /** The bound card index. */
787
703
  readonly index: number;
788
704
  /**
789
- * The bound card's `$kind` (empty string when it carries none), read through
790
- * the document: mirrors core `CardWriter::kind()`. Throws `IndexOutOfRange`
791
- * if the bound index is out of range.
705
+ * The bound card's `$kind`, empty string when it carries none. Throws
706
+ * `IndexOutOfRange` for a bad bound index.
792
707
  */
793
708
  readonly kind: string;
794
709
  set(name: string, value: unknown): void;
@@ -796,34 +711,29 @@ export declare class CardWriter {
796
711
  /** Revise this card's body from markdown (edit semantics), returning the text `Delta`. */
797
712
  reviseBody(markdown: string): Delta;
798
713
  /**
799
- * Revise the content field `name` on this card from authored text: typed *and*
800
- * anchor-preserving; the card twin of {@link DocumentWriter.reviseField},
801
- * codec included. Throws `UnknownField` for an undeclared name and
802
- * `IndexOutOfRange` if the bound index is out of range. Returns the `Delta`.
714
+ * The card twin of {@link DocumentWriter.reviseField}. Throws `UnknownField`
715
+ * for an undeclared name and `IndexOutOfRange` for a bad bound index.
803
716
  */
804
717
  reviseField(name: string, text: string): Delta;
805
718
  }
806
719
 
807
720
  /**
808
- * A `Document` bound to its `Quill` for interpreted reads: the schema-plane read
809
- * surface, constructed via {@link Quill.reader} and the read twin of
810
- * {@link DocumentWriter}. One `get` reads each field by its declared type: a
811
- * richtext field to its markdown projection, a plaintext field to its literal
812
- * text, every other type its canonical value verbatim. Holds both handles by
813
- * reference and owns neither: nothing to `free()`.
721
+ * A `Document` bound to its `Quill` for interpreted reads, from
722
+ * {@link Quill.reader}: the read twin of {@link DocumentWriter}. One `get` reads
723
+ * each field by its declared type a richtext field to its markdown projection,
724
+ * a plaintext field to its literal text, every other type verbatim.
814
725
  *
815
- * The schema authority is the point: unlike the quill-free transport `Document.getStored`,
816
- * a name the schema does not declare throws `UnknownField` (a typo) rather than
817
- * reading back `undefined`, and a content field holding a value that does not
818
- * decode throws `FieldDecode`. A field's markdown lives here, not on the
819
- * body-only `Document.bodyMarkdown`. The body read stays quill-free (a body's type
820
- * is a format fact) and never throws.
726
+ * The schema authority is the point: unlike the quill-free `Document.getStored`,
727
+ * an undeclared name throws `UnknownField` rather than reading back `undefined`,
728
+ * and an undecodable content value throws `FieldDecode`. A field's markdown lives
729
+ * here, not on the body-only `Document.bodyMarkdown`; the body read stays
730
+ * quill-free and never throws.
821
731
  *
822
732
  * `getContent` is the same read at the other end of the codec, returning the
823
733
  * `Content` rather than the projection. It binds the quill for the same reason
824
- * `get` does: a `richtext` string is markdown and a `plaintext` string is
825
- * literal text, so the same stored bytes decode two ways and only the declared
826
- * type says which.
734
+ * `get` does: the same stored bytes decode two ways, and only the declared type
735
+ * says which. `getContentAt` is that read one axis further in, for a `Content`
736
+ * nested inside a composite field.
827
737
  */
828
738
  export declare class DocumentReader {
829
739
  constructor(quill: Quill, doc: Document);
@@ -839,16 +749,31 @@ export declare class DocumentReader {
839
749
  */
840
750
  get(addr: Addr | string): unknown;
841
751
  /**
842
- * Read the content field at `addr` as its canonical `Content`: the
843
- * `Content` twin of {@link get}, which projects. Decodes through the codec the
844
- * declared type names (`richtext` as markdown, `plaintext` as literal text),
845
- * so a committed field and a parsed one read back the same `Content` and the
846
- * storage form stops being the caller's business. An absent `addr.field`
847
- * reads the body `Content`. `undefined` for an absent field; throws
848
- * `UnknownField`, `FieldNotContent` for a declared type that is not a content
849
- * leaf, `FieldDecode` for an undecodable value, and `IndexOutOfRange`.
752
+ * Read the content field at `addr` as canonical `Content`: the twin of
753
+ * {@link get}, which projects. Decodes through the codec the declared type
754
+ * names, so a committed field and a parsed one read back the same `Content`.
755
+ * An absent `addr.field` reads the body `Content`. `undefined` for an absent
756
+ * field; throws `UnknownField`, `FieldNotContent` for a declared type that is
757
+ * not a content leaf, `FieldDecode` for an undecodable value, and
758
+ * `IndexOutOfRange`.
850
759
  */
851
760
  getContent(addr: Addr | string): Content | undefined;
761
+ /**
762
+ * Read the `Content` nested inside the composite field at `addr`, at `path`:
763
+ * `[0]` an element of an `array<richtext>`, `["motto"]` an object's content
764
+ * property, `[1, "notes"]` a leaf under both. The codec is the leaf's declared
765
+ * type's, resolved through the field schema's `items` / `properties`, so the
766
+ * element's storage form is not the caller's business. The empty path is
767
+ * {@link getContent}.
768
+ *
769
+ * `undefined` for an absent field and for a path that names nothing in the
770
+ * stored value: a repeater's row index goes stale between derive and read,
771
+ * so absence there is a read, not a fault. Throws `UnknownField` for an
772
+ * undeclared name at any depth, `FieldNotContent` when `path` resolves to no
773
+ * content leaf, `FieldDecode` anchored at the addressed path, and
774
+ * `IndexOutOfRange` for a bad `addr.card`.
775
+ */
776
+ getContentAt(addr: Addr | string, path: PathStep[]): Content | undefined;
852
777
  /** The main body's markdown: the quill-free body read. Equals `get({})`. */
853
778
  bodyMarkdown(): string;
854
779
  /**
@@ -882,10 +807,11 @@ export declare class CardReader {
882
807
  */
883
808
  get(name: string): unknown;
884
809
  /**
885
- * Read the content field `name` on this card as its canonical `Content`
886
- * `Content`: the card twin of {@link DocumentReader.getContent}.
810
+ * The card twin of {@link DocumentReader.getContent}.
887
811
  */
888
812
  getContent(name: string): Content | undefined;
813
+ /** The card twin of {@link DocumentReader.getContentAt}. */
814
+ getContentAt(name: string, path: PathStep[]): Content | undefined;
889
815
  /** This card's body markdown: the card twin of {@link DocumentReader.bodyMarkdown}. */
890
816
  bodyMarkdown(): string;
891
817
  }