@scrawl-board/board 0.1.0-beta.0 → 0.1.0-beta.10

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/dist/core.d.ts CHANGED
@@ -1,16 +1,78 @@
1
- declare const documentIdBrand: unique symbol;
2
- declare const strokeIdBrand: unique symbol;
3
- type DocumentId = string & {
4
- readonly [documentIdBrand]: "DocumentId";
5
- };
6
- type StrokeId = string & {
7
- readonly [strokeIdBrand]: "StrokeId";
8
- };
9
- declare function documentId(value: string): DocumentId;
10
- declare function strokeId(value: string): StrokeId;
11
-
12
1
  /** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
13
2
  type AssetRef = string;
3
+ declare const ASSET_REF_PATTERN: RegExp;
4
+ /** Hard ceiling on a reference's own wire length — independent of any resource limit below. */
5
+ declare const ASSET_REF_MAX_BYTES = 512;
6
+ declare function isAssetRef(value: unknown): value is AssetRef;
7
+ /** Throws on malformed input; use `isAssetRef` where a boolean is wanted instead. */
8
+ declare function assetRef(value: string): AssetRef;
9
+ type AssetKind = "image";
10
+ type AssetPurpose = "render" | "thumbnail" | "export";
11
+ interface AssetResolveRequest {
12
+ ref: AssetRef;
13
+ kind: AssetKind;
14
+ purpose: AssetPurpose;
15
+ signal: AbortSignal;
16
+ }
17
+ interface AssetResolveResult {
18
+ bytes: Uint8Array;
19
+ mediaType: string;
20
+ }
21
+ /**
22
+ * Host-owned lookup. Scrawl passes only the reference, kind, purpose, and
23
+ * signal — never Document contents, an Extension instance, or credentials.
24
+ * The returned bytes are copied into SDK-owned storage and independently
25
+ * validated before use; a resolver's claimed `mediaType` is never trusted
26
+ * on its own (see `assetValidation.ts`).
27
+ */
28
+ interface AssetResolver {
29
+ resolve(request: AssetResolveRequest): Promise<AssetResolveResult>;
30
+ }
31
+ interface AssetIngestRequest {
32
+ bytes: Uint8Array;
33
+ mediaType: string;
34
+ name?: string;
35
+ signal: AbortSignal;
36
+ }
37
+ interface AssetIngestResult {
38
+ ref: AssetRef;
39
+ }
40
+ /**
41
+ * Host-owned upload/creation path. Scrawl validates the candidate against
42
+ * SDK limits before calling this, and validates the returned reference
43
+ * before it can enter any command. Cancellation or failure creates no
44
+ * Document object, Op, or history entry.
45
+ */
46
+ interface AssetIngestor {
47
+ ingest(request: AssetIngestRequest): Promise<AssetIngestResult>;
48
+ }
49
+ type AssetResolutionErrorCode = "resolver-unavailable" | "not-found" | "forbidden" | "offline" | "unsupported-type" | "too-large" | "invalid-content" | "decode-failed" | "budget-exceeded" | "aborted" | "unknown";
50
+ declare class AssetResolutionError extends Error {
51
+ readonly code: AssetResolutionErrorCode;
52
+ readonly retryable: boolean;
53
+ readonly ref?: AssetRef | undefined;
54
+ constructor(code: AssetResolutionErrorCode, retryable: boolean, message: string, ref?: AssetRef | undefined);
55
+ }
56
+ /** Runtime event for a resolution/ingestion failure — never carries credentials or a fetchable location. */
57
+ interface AssetDiagnostic {
58
+ code: AssetResolutionErrorCode;
59
+ ref?: AssetRef;
60
+ objectId: string;
61
+ objectKind: "image" | "custom";
62
+ retryable: boolean;
63
+ }
64
+ declare const ASSET_MAX_ENCODED_BYTES: number;
65
+ declare const ASSET_MAX_DIMENSION_PX = 8192;
66
+ declare const ASSET_MAX_DECODED_MEGAPIXELS = 40;
67
+ declare const ASSET_MAX_CONCURRENT_RESOLUTIONS = 6;
68
+ declare const ASSET_CACHE_BYTES_DEFAULT: number;
69
+ declare const ASSET_CACHE_BYTES_MIN: number;
70
+ declare const ASSET_CACHE_BYTES_MAX: number;
71
+ declare const ASSET_EXPORT_MAX_ENCODED_BYTES: number;
72
+ declare const ASSET_EXPORT_MAX_DECODED_MEGAPIXELS = 100;
73
+ declare const SUPPORTED_ASSET_MEDIA_TYPES: readonly ["image/png", "image/jpeg", "image/webp"];
74
+ type SupportedAssetMediaType = (typeof SUPPORTED_ASSET_MEDIA_TYPES)[number];
75
+ declare function clampAssetCacheBytes(value: number | undefined): number;
14
76
 
15
77
  type Mat2x3 = [number, number, number, number, number, number];
16
78
  declare const IDENTITY: Mat2x3;
@@ -69,6 +131,7 @@ interface CustomBoardObject {
69
131
  };
70
132
  props: JsonValue;
71
133
  }
134
+ declare function cloneCustomObject(object: CustomBoardObject): CustomBoardObject;
72
135
  /**
73
136
  * The read-only view handed to `describe`. Deep-readonly by construction
74
137
  * (not derived via a shallow `Readonly<>`) because `describe` must treat its
@@ -106,7 +169,22 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
106
169
  parse(input: unknown, schemaVersion: number): Props;
107
170
  /** One pure, synchronous step per consecutive schema version. */
108
171
  migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
109
- describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene$1;
172
+ describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
173
+ /**
174
+ * Optional point-level hit-test precision (Phase 8). Every custom object
175
+ * hit-tests against its bounding box (`fallback.bounds`) by default — this
176
+ * lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
177
+ * reject a point that's inside that box but outside its actual visible
178
+ * silhouette, tightening a click/marquee/raycast hit to the shape's real
179
+ * outline. `point` is in this object's own local space — the same
180
+ * untransformed space `describe`'s returned geometry already lives in
181
+ * (the caller inverse-transforms the pointer's board point through
182
+ * `object.transform` before calling this). Absent means every point
183
+ * inside the bounding box hits, matching pre-Phase-8 behavior exactly.
184
+ * Rejecting a point here does not fall through to whatever's underneath —
185
+ * the gesture simply misses this object, same as clicking empty space.
186
+ */
187
+ hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
110
188
  }
111
189
  interface SceneNodeBase {
112
190
  key: string;
@@ -138,9 +216,18 @@ interface SceneText extends SceneNodeBase {
138
216
  }
139
217
  interface SceneGroup extends SceneNodeBase {
140
218
  kind: "group";
141
- children: readonly BoardScene$1[];
219
+ children: readonly BoardScene[];
142
220
  }
143
- interface ScenePath$1 extends SceneNodeBase {
221
+ /**
222
+ * **No renderer or SVG-export interpreter exists for this node kind yet**
223
+ * (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
224
+ * `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
225
+ * exports nothing, and contributes no hit-test bounds — it neither errors
226
+ * nor emits a diagnostic. Until an interpreter ships, build custom shapes
227
+ * from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
228
+ * instead.
229
+ */
230
+ interface ScenePath extends SceneNodeBase {
144
231
  kind: "path";
145
232
  /** SVG-style path data, board-local coordinates. */
146
233
  d: string;
@@ -173,7 +260,7 @@ interface SceneEllipse extends SceneNodeBase {
173
260
  stroke?: string;
174
261
  strokeWidth?: number;
175
262
  }
176
- type BoardScene$1 = SceneGroup | ScenePath$1 | SceneText | SceneImage | SceneRect | SceneEllipse;
263
+ type BoardScene = SceneGroup | ScenePath | SceneText | SceneImage | SceneRect | SceneEllipse;
177
264
  type ToolCursor = "default" | "crosshair" | "pointer" | "grab" | "grabbing" | "text";
178
265
  type ToolCancelReason = "escape-key" | "tool-switched" | "pointer-lost" | "error";
179
266
  interface InputModifiers {
@@ -269,7 +356,7 @@ interface ToolCapabilities {
269
356
  };
270
357
  /** Session-only geometry — never enters Document/history/persistence/collaboration. */
271
358
  preview: {
272
- set(scene: BoardScene$1): void;
359
+ set(scene: BoardScene): void;
273
360
  clear(): void;
274
361
  };
275
362
  /** Constructs, validates, and commits one atomic command; controller derives Ops. */
@@ -279,6 +366,15 @@ interface ToolCapabilities {
279
366
  cancel(): void;
280
367
  };
281
368
  }
369
+ interface ExtensionDiagnostic {
370
+ extensionId: ExtensionId;
371
+ phase: "parse" | "migrate" | "describe" | "tool-lifecycle" | "react-companion" | "asset-resolution";
372
+ toolId?: ToolId;
373
+ objectId?: string;
374
+ objectType?: ObjectType;
375
+ recoverable: boolean;
376
+ cause: unknown;
377
+ }
282
378
 
283
379
  /** Who holds a lock. Stored on the item so only they can take it off. */
284
380
  interface LockHolder {
@@ -303,8 +399,23 @@ declare function canUnlockItem(item: Lockable | undefined, userId: string | unde
303
399
  /** Wire fields for a locked item; omitted entirely when unlocked. */
304
400
  declare function serializeLock(item: Lockable): Lockable;
305
401
 
402
+ /**
403
+ * Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
404
+ * pattern exactly, but simpler: unlike a lock, hidden state carries no
405
+ * holder/ownership concept, so there's no analogue to `LockHolder`/
406
+ * `canUnlockItem`. A hidden object stays fully present in the Document
407
+ * (still serializes, persists, syncs, undoes/redoes) — it just skips
408
+ * rendering and hit-testing/selection candidacy. `hidden` absent or
409
+ * `false` means visible; this keeps every pre-Phase-8 document (which has
410
+ * no `hidden` field on any object at all) implicitly fully visible with
411
+ * zero migration needed.
412
+ */
413
+ interface Hideable {
414
+ hidden?: boolean;
415
+ }
416
+
306
417
  /** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
307
- interface KitchenTimer extends Lockable {
418
+ interface KitchenTimer extends Lockable, Hideable {
308
419
  id: string;
309
420
  x: number;
310
421
  y: number;
@@ -329,6 +440,173 @@ declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
329
440
  declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
330
441
  declare function formatTimer(ms: number): string;
331
442
 
443
+ declare const SHAPE_MIN_SIZE = 0.5;
444
+ /**
445
+ * Centralized shape style defaults (Phase 8) — one object a Host can read
446
+ * to know (or, by not relying on the standalone constants below, override
447
+ * via its own UI state) what a newly drawn Rectangle/Ellipse/Line/Arrow/
448
+ * Polygon/Star/Heart starts with when the user hasn't picked a stroke/width
449
+ * yet. `shapeTool.ts` (commit-time), `renderer/shapes/lines.ts` (render-time
450
+ * fallback for an object missing these fields), and
451
+ * `persistence/serialization/svg.ts` (export-time fallback) all read from
452
+ * here — the two standalone constants below are kept for source
453
+ * compatibility and simply mirror this object's values, not a second
454
+ * source of truth.
455
+ */
456
+ declare const SHAPE_STYLE_DEFAULTS: {
457
+ readonly stroke: "#1C1C1E";
458
+ readonly strokeWidth: 0.3;
459
+ };
460
+ declare const SHAPE_DEFAULT_STROKE: "#1C1C1E";
461
+ declare const SHAPE_DEFAULT_STROKE_WIDTH: 0.3;
462
+ interface RectangleObject extends Lockable, Hideable {
463
+ id: string;
464
+ x: number;
465
+ y: number;
466
+ width: number;
467
+ height: number;
468
+ fill?: string;
469
+ stroke?: string;
470
+ strokeWidth?: number;
471
+ /** Corner radius in board units; clamped to at most half the shorter side at render time. */
472
+ cornerRadius?: number;
473
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
474
+ opacity?: number;
475
+ /**
476
+ * Radians, about the shape's own center `(x + width/2, y - height/2)`.
477
+ * Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
478
+ * shape's own unrotated local frame — rotation is a separate, applied-last
479
+ * transform, not baked into them, matching how Stroke/CustomBoardObject
480
+ * keep geometry and placement independent via their own `matrix`.
481
+ */
482
+ rotation?: number;
483
+ }
484
+ interface EllipseObject extends Lockable, Hideable {
485
+ id: string;
486
+ x: number;
487
+ y: number;
488
+ width: number;
489
+ height: number;
490
+ fill?: string;
491
+ stroke?: string;
492
+ strokeWidth?: number;
493
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
494
+ opacity?: number;
495
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
496
+ rotation?: number;
497
+ }
498
+ declare function cloneRectangle(rect: RectangleObject): RectangleObject;
499
+ declare function cloneEllipse(ellipse: EllipseObject): EllipseObject;
500
+ /** `"none"` is a plain line with no arrowhead; today's only real head shape is `"triangle"`. New head shapes extend this union without touching `ArrowObject`'s own fields. */
501
+ type ArrowHeadStyle = "triangle" | "none";
502
+ interface LineObject extends Lockable, Hideable {
503
+ id: string;
504
+ start: BoardPoint;
505
+ end: BoardPoint;
506
+ stroke?: string;
507
+ strokeWidth?: number;
508
+ opacity?: number;
509
+ }
510
+ /** An endpoint anchored to a normalized position on another object's bounds. */
511
+ interface ConnectorBinding {
512
+ objectId: string;
513
+ /** Horizontal and vertical fractions of the target's axis-aligned bounds. */
514
+ x: number;
515
+ y: number;
516
+ }
517
+ interface ArrowObject extends Lockable, Hideable {
518
+ /** Omitted for legacy arrows. Connectors share Arrow's persistence/history contract. */
519
+ routing?: "straight" | "curved" | "polyline";
520
+ /** Intermediate board-space vertices for a multi-point connector. */
521
+ waypoints?: BoardPoint[];
522
+ startBinding?: ConnectorBinding;
523
+ endBinding?: ConnectorBinding;
524
+ id: string;
525
+ start: BoardPoint;
526
+ end: BoardPoint;
527
+ head?: ArrowHeadStyle;
528
+ stroke?: string;
529
+ strokeWidth?: number;
530
+ opacity?: number;
531
+ }
532
+ /**
533
+ * Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
534
+ * type instead of five near-duplicate interfaces — a regular N-gon
535
+ * inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
536
+ * Rectangle already uses, parameterized by `sides`. Diamond is exactly a
537
+ * 4-sided regular polygon with vertex 0 pointing right (not up, like
538
+ * Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
539
+ * `polygonStartAngle`, which encodes each side count's own vertex
540
+ * orientation so the outline always matches the legacy drag-preview shape.
541
+ */
542
+ interface PolygonObject extends Lockable, Hideable {
543
+ id: string;
544
+ x: number;
545
+ y: number;
546
+ width: number;
547
+ height: number;
548
+ sides: 3 | 4 | 5 | 6 | 8;
549
+ fill?: string;
550
+ stroke?: string;
551
+ strokeWidth?: number;
552
+ opacity?: number;
553
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
554
+ rotation?: number;
555
+ }
556
+ declare function clonePolygon(polygon: PolygonObject): PolygonObject;
557
+ /** Same bounding-box/rotation convention as Rectangle; a 5-pointed star with a tuned inner-radius ratio, matching the legacy tool's own default (see `polygonGeometry.ts`'s `starPoints`). */
558
+ interface StarObject extends Lockable, Hideable {
559
+ id: string;
560
+ x: number;
561
+ y: number;
562
+ width: number;
563
+ height: number;
564
+ /** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
565
+ points: number;
566
+ /** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
567
+ innerRadiusRatio: number;
568
+ fill?: string;
569
+ stroke?: string;
570
+ strokeWidth?: number;
571
+ opacity?: number;
572
+ rotation?: number;
573
+ }
574
+ declare function cloneStar(star: StarObject): StarObject;
575
+ /** Same bounding-box/rotation convention as Rectangle; the standard parametric heart curve (see `polygonGeometry.ts`'s `heartPoints`), no extra parameters beyond the shared shape fields. */
576
+ interface HeartObject extends Lockable, Hideable {
577
+ id: string;
578
+ x: number;
579
+ y: number;
580
+ width: number;
581
+ height: number;
582
+ fill?: string;
583
+ stroke?: string;
584
+ strokeWidth?: number;
585
+ opacity?: number;
586
+ rotation?: number;
587
+ }
588
+ declare function cloneHeart(heart: HeartObject): HeartObject;
589
+ declare function cloneLine(line: LineObject): LineObject;
590
+ declare function cloneArrow(arrow: ArrowObject): ArrowObject;
591
+ /**
592
+ * A logical grouping of other board objects (Phase 3 — Selection,
593
+ * Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
594
+ * own — a group's bounds are always derived on demand from its (recursively
595
+ * resolved) children, and "moving/rotating/scaling the group" is exactly a
596
+ * multi-object transform applied to those children, nothing more. A group
597
+ * has no renderer/mesh of its own; its only visual presence is the
598
+ * selection gizmo's bounding box while it's the current selection.
599
+ *
600
+ * `children` may itself contain other group ids (nested groups) — expanding
601
+ * a group into its leaf members is always done by the caller (recursively,
602
+ * with cycle protection), never assumed here.
603
+ */
604
+ interface GroupObject extends Lockable, Hideable {
605
+ id: string;
606
+ children: string[];
607
+ }
608
+ declare function cloneGroup(group: GroupObject): GroupObject;
609
+
332
610
  interface BoardPoint {
333
611
  x: number;
334
612
  y: number;
@@ -342,9 +620,14 @@ interface StrokePoint extends BoardPoint {
342
620
  */
343
621
  erase?: number;
344
622
  }
345
- /** Which drawing tool made a stroke; undefined means marker (back-compat). */
346
- type StrokeTool = "marker" | "highlighter";
347
- interface Stroke extends Lockable {
623
+ /**
624
+ * Which drawing tool made a stroke; undefined means marker (back-compat).
625
+ * `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
626
+ * exact configured width with no pressure variance or end taper — a
627
+ * geometric outline, not an expressive ink mark.
628
+ */
629
+ type StrokeTool = "marker" | "highlighter" | "shape";
630
+ interface Stroke extends Lockable, Hideable {
348
631
  id: string;
349
632
  color: string;
350
633
  baseWidth: number;
@@ -363,7 +646,7 @@ interface Stroke extends Lockable {
363
646
  declare const ERASE_THRESHOLD = 0.95;
364
647
  declare function cloneStroke(stroke: Stroke): Stroke;
365
648
  type SerializedPoint = [number, number, number, number];
366
- interface SerializedStroke extends Lockable {
649
+ interface SerializedStroke extends Lockable, Hideable {
367
650
  id: string;
368
651
  color: string;
369
652
  baseWidth: number;
@@ -373,7 +656,36 @@ interface SerializedStroke extends Lockable {
373
656
  matrix?: [number, number, number, number, number, number];
374
657
  clusterId?: string;
375
658
  }
659
+ /** A named notebook containing sections. */
660
+ interface BoardNotebook {
661
+ readonly id: string;
662
+ readonly name: string;
663
+ readonly color: string;
664
+ }
665
+ /** A named section containing pages. */
666
+ interface BoardSection {
667
+ readonly id: string;
668
+ readonly notebookId: string;
669
+ readonly name: string;
670
+ readonly color: string;
671
+ }
672
+ /** An additional page. The first page remains in the document's top-level collections. */
673
+ interface SerializedBoardPage {
674
+ id: string;
675
+ sectionId?: string;
676
+ name: string;
677
+ content: SerializedDocument;
678
+ }
376
679
  interface SerializedDocument {
680
+ /** Notebook and section metadata. Older boards use an implicit default of each. */
681
+ notebooks?: BoardNotebook[];
682
+ sections?: BoardSection[];
683
+ /** Section of the original first page; defaults to `default`. */
684
+ pageSectionId?: string;
685
+ /** Display name of the first page (id `default`). */
686
+ pageName?: string;
687
+ /** Additional pages, in sidebar order. Nested pages are not allowed. */
688
+ pages?: SerializedBoardPage[];
377
689
  /** Absent in every historical document; current saves always write 1. */
378
690
  schemaVersion?: 1;
379
691
  strokes: SerializedStroke[];
@@ -389,6 +701,31 @@ interface SerializedDocument {
389
701
  timers?: KitchenTimer[];
390
702
  /** Absent in documents saved before Custom board objects existed (ticket #22). */
391
703
  customObjects?: CustomBoardObject[];
704
+ /** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
705
+ rectangles?: RectangleObject[];
706
+ /** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
707
+ ellipses?: EllipseObject[];
708
+ /** Absent in documents saved before Groups existed (Phase 3). */
709
+ groups?: GroupObject[];
710
+ /** Absent in documents saved before semantic Line objects existed (Phase 4). */
711
+ lines?: LineObject[];
712
+ /** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
713
+ arrows?: ArrowObject[];
714
+ /** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
715
+ polygons?: PolygonObject[];
716
+ /** Absent in documents saved before semantic Star objects existed (Phase 4). */
717
+ stars?: StarObject[];
718
+ /** Absent in documents saved before semantic Heart objects existed (Phase 4). */
719
+ hearts?: HeartObject[];
720
+ /**
721
+ * Every content-object id (every type above except comments, which are
722
+ * host-synced and never enter this schema) in paint order, back to front.
723
+ * Absent in documents saved before per-object z-order existed (Phase 3) —
724
+ * migration synthesizes a default order preserving the old fixed-Z-band
725
+ * visual stacking exactly, so an existing document never visibly changes
726
+ * on load; only an explicit reorder action touches this from then on.
727
+ */
728
+ objectOrder?: string[];
392
729
  }
393
730
  declare const INK_COLORS: {
394
731
  readonly black: "#1C1C1E";
@@ -397,16 +734,28 @@ declare const INK_COLORS: {
397
734
  readonly green: "#16A34A";
398
735
  };
399
736
  declare const HIGHLIGHT_COLORS: {
737
+ readonly yellow: "#FACC15";
738
+ readonly green: "#4ADE80";
739
+ readonly pink: "#F472B6";
740
+ readonly blue: "#60A5FA";
741
+ };
742
+ declare const NOTE_COLORS: {
400
743
  readonly yellow: "#FDE047";
401
- readonly green: "#86EFAC";
744
+ readonly amber: "#FACC15";
745
+ readonly orange: "#FB923C";
746
+ readonly peach: "#FDBA74";
747
+ readonly red: "#FB7185";
748
+ readonly rose: "#FDA4AF";
402
749
  readonly pink: "#F9A8D4";
750
+ readonly magenta: "#F472B6";
751
+ readonly purple: "#C084FC";
752
+ readonly lavender: "#C4B5FD";
403
753
  readonly blue: "#93C5FD";
404
- };
405
- declare const NOTE_COLORS: {
406
- readonly yellow: "#FDE68A";
407
- readonly pink: "#FBCFE8";
408
- readonly blue: "#BFDBFE";
409
- readonly green: "#BBF7D0";
754
+ readonly cornflower: "#60A5FA";
755
+ readonly sky: "#38BDF8";
756
+ readonly cyan: "#67E8F9";
757
+ readonly green: "#86EFAC";
758
+ readonly mint: "#4ADE80";
410
759
  };
411
760
  /**
412
761
  * One collaborator's vote on a note. One per person; toggling removes it.
@@ -420,7 +769,7 @@ interface NoteVote {
420
769
  * A sticky note: content floating above the board at a z-offset (pillar 3 —
421
770
  * depth as an organizational axis). Center position in board space.
422
771
  */
423
- interface StickyNote extends Lockable {
772
+ interface StickyNote extends Lockable, Hideable {
424
773
  id: string;
425
774
  x: number;
426
775
  y: number;
@@ -438,7 +787,7 @@ interface StickyNote extends Lockable {
438
787
  * top-left corner; lines flow downward (-y). Text joins the clustering
439
788
  * system like handwriting (build prompt §6.4).
440
789
  */
441
- interface TextBlock extends Lockable {
790
+ interface TextBlock extends Lockable, Hideable {
442
791
  id: string;
443
792
  x: number;
444
793
  y: number;
@@ -468,7 +817,7 @@ declare function cloneNote(note: StickyNote): StickyNote;
468
817
  * Interactive structured table on the board. Position (x, y) is top-left in board units.
469
818
  * Cells are indexed as `${row},${col}` keys mapping to cell text content.
470
819
  */
471
- interface TableBlock extends Lockable {
820
+ interface TableBlock extends Lockable, Hideable {
472
821
  id: string;
473
822
  x: number;
474
823
  y: number;
@@ -489,13 +838,12 @@ declare function measureTable(table: TableBlock): {
489
838
  width: number;
490
839
  height: number;
491
840
  };
492
- declare const BOARD_COLOR = "#FFFFFF";
493
841
  declare const FOG_COLOR = "#FFFFFF";
494
842
  /**
495
843
  * An imported image block on the board plane.
496
844
  * Coordinates (x, y) represent the center of the image in board space.
497
845
  */
498
- interface ImageBlock extends Lockable {
846
+ interface ImageBlock extends Lockable, Hideable {
499
847
  id: string;
500
848
  /**
501
849
  * A legacy, read-only data URL (or, historically, an arbitrary string) —
@@ -519,13 +867,29 @@ interface ImageBlock extends Lockable {
519
867
  }
520
868
  declare function cloneImage(img: ImageBlock): ImageBlock;
521
869
 
870
+ declare const documentIdBrand: unique symbol;
871
+ declare const strokeIdBrand: unique symbol;
872
+ type DocumentId = string & {
873
+ readonly [documentIdBrand]: "DocumentId";
874
+ };
875
+ type StrokeId = string & {
876
+ readonly [strokeIdBrand]: "StrokeId";
877
+ };
878
+ declare function documentId(value: string): DocumentId;
879
+ declare function strokeId(value: string): StrokeId;
880
+
522
881
  declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
523
882
  type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
524
883
  id: StrokeId;
525
884
  };
526
- interface CurrentSerializedDocument extends Required<SerializedDocument> {
885
+ interface CurrentSerializedDocument extends Required<Omit<SerializedDocument, "pages" | "pageName" | "notebooks" | "sections" | "pageSectionId">> {
527
886
  schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
528
887
  strokes: CurrentSerializedStroke[];
888
+ pageName?: string;
889
+ notebooks?: BoardNotebook[];
890
+ sections?: BoardSection[];
891
+ pageSectionId?: string;
892
+ pages?: SerializedBoardPage[];
529
893
  }
530
894
  type DocumentRecoveryCode = "DOCUMENT_JSON_INVALID" | "DOCUMENT_VALIDATION_FAILED" | "DOCUMENT_VERSION_UNSUPPORTED";
531
895
  declare class DocumentRecoveryError extends Error {
@@ -545,6 +909,15 @@ type DocumentLoadResult = {
545
909
  declare function loadDocumentBytes(originalBytes: string): DocumentLoadResult;
546
910
  declare function migrateDocument(raw: unknown): DocumentLoadResult;
547
911
  declare function serializeDocument(document: unknown): string;
912
+ /**
913
+ * A Document's serialized size in bytes (Phase 5) — UTF-8, not UTF-16
914
+ * `string.length`, since a Document with non-ASCII note/text content (most
915
+ * of them, eventually) would otherwise under-report. Useful for a Host
916
+ * deciding when to warn about an unusually large board, or for logging/
917
+ * telemetry around save size — not consulted by anything inside this
918
+ * package itself, which has no size limit of its own.
919
+ */
920
+ declare function documentSize(document: CurrentSerializedDocument): number;
548
921
 
549
922
  interface SearchableComment {
550
923
  id: string;
@@ -579,6 +952,18 @@ interface SearchableBoard {
579
952
  */
580
953
  declare function searchBoard(query: string, board: SearchableBoard): SearchHit[];
581
954
 
955
+ interface ConnectorBox {
956
+ minX: number;
957
+ minY: number;
958
+ maxX: number;
959
+ maxY: number;
960
+ }
961
+ /** A bindable object's footprint: its bounds and whether its outline is the box or the ellipse inside it. */
962
+ interface ConnectorTarget {
963
+ box: ConnectorBox;
964
+ outline: "box" | "ellipse";
965
+ }
966
+
582
967
  /**
583
968
  * The one place a live Stroke becomes its wire representation — in
584
969
  * particular, points collapse from {x,y,pressure,erase?} objects into
@@ -629,6 +1014,60 @@ interface DocumentChange {
629
1014
  /** Ids of removed custom objects. */
630
1015
  customObjectsRemoved: string[];
631
1016
  customObjectsUpdated: CustomBoardObject[];
1017
+ /** Semantic Rectangle objects (Phase 2). */
1018
+ rectanglesAdded: RectangleObject[];
1019
+ /** Ids of removed rectangles. */
1020
+ rectanglesRemoved: string[];
1021
+ rectanglesUpdated: RectangleObject[];
1022
+ /** Semantic Ellipse objects (Phase 2). */
1023
+ ellipsesAdded: EllipseObject[];
1024
+ /** Ids of removed ellipses. */
1025
+ ellipsesRemoved: string[];
1026
+ ellipsesUpdated: EllipseObject[];
1027
+ /** Groups (Phase 3). */
1028
+ groupsAdded: GroupObject[];
1029
+ /** Ids of removed groups (ungrouping, or deleting a group). */
1030
+ groupsRemoved: string[];
1031
+ groupsUpdated: GroupObject[];
1032
+ /** Semantic Line objects (Phase 4). */
1033
+ linesAdded: LineObject[];
1034
+ /** Ids of removed lines. */
1035
+ linesRemoved: string[];
1036
+ linesUpdated: LineObject[];
1037
+ /** Semantic Arrow objects (Phase 4). */
1038
+ arrowsAdded: ArrowObject[];
1039
+ /** Ids of removed arrows. */
1040
+ arrowsRemoved: string[];
1041
+ arrowsUpdated: ArrowObject[];
1042
+ /** Semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
1043
+ polygonsAdded: PolygonObject[];
1044
+ /** Ids of removed polygons. */
1045
+ polygonsRemoved: string[];
1046
+ polygonsUpdated: PolygonObject[];
1047
+ /** Semantic Star objects (Phase 4). */
1048
+ starsAdded: StarObject[];
1049
+ /** Ids of removed stars. */
1050
+ starsRemoved: string[];
1051
+ starsUpdated: StarObject[];
1052
+ /** Semantic Heart objects (Phase 4). */
1053
+ heartsAdded: HeartObject[];
1054
+ /** Ids of removed hearts. */
1055
+ heartsRemoved: string[];
1056
+ heartsUpdated: HeartObject[];
1057
+ /**
1058
+ * The full current paint order (back to front) of every flat content
1059
+ * object — strokes, texts, tables, images, rectangles, ellipses, lines,
1060
+ * arrows, custom objects, and groups. Populated whenever `objectOrder`
1061
+ * actually changed:
1062
+ * an explicit reorder (`bringForward` etc.), or any add/remove that
1063
+ * touches it — a removal shifts every id after it down one rank, not
1064
+ * just the removed one, so renderers need this to resync everyone, not
1065
+ * only the ids the same change's own `*Added`/`*Removed`/`*Updated`
1066
+ * fields name. Notes (their own `zOffset` peel depth) and Kitchen Timers
1067
+ * (genuine 3D objects, not a flat layer) are intentionally not part of
1068
+ * this order at all.
1069
+ */
1070
+ orderChanged: readonly string[];
632
1071
  }
633
1072
  type Listener = (change: DocumentChange) => void;
634
1073
  declare class BoardDocument {
@@ -643,12 +1082,78 @@ declare class BoardDocument {
643
1082
  private readonly timers;
644
1083
  /** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
645
1084
  private readonly customObjects;
1085
+ private readonly rectangles;
1086
+ private readonly ellipses;
1087
+ private readonly groups;
1088
+ private readonly lines;
1089
+ private readonly arrows;
1090
+ private readonly polygons;
1091
+ private readonly stars;
1092
+ private readonly hearts;
646
1093
  private readonly bboxes;
647
1094
  private readonly listeners;
1095
+ /** Paint order (back to front) of every flat content object — see `DocumentChange.orderChanged`'s doc comment. */
1096
+ private objectOrder;
1097
+ private orderIndex;
648
1098
  constructor(id: DocumentId);
1099
+ private reindexOrder;
1100
+ /** The full current paint order, back to front. */
1101
+ order(): readonly string[];
1102
+ /** This object's rank in the paint order, or -1 if it doesn't participate (unknown id, a note, or a timer). */
1103
+ orderRank(id: string): number;
1104
+ private bringForward;
1105
+ private sendBackward;
1106
+ private bringToFront;
1107
+ private sendToBack;
1108
+ /** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
1109
+ reorder(id: string, direction: "forward" | "backward" | "front" | "back"): void;
1110
+ /**
1111
+ * Overwrites the paint order directly — used only when loading a document
1112
+ * that already carries a persisted `objectOrder`; every other order
1113
+ * mutation goes through `reorder`/the automatic append-on-add tracking in
1114
+ * `emit`. Ids not present in the document are dropped; ids present in the
1115
+ * document but missing from `order` are appended at the back, so a
1116
+ * partially-stale order (e.g. from a schema migration) never silently
1117
+ * drops an object from paint order entirely.
1118
+ */
1119
+ private setOrder;
649
1120
  get(id: string): Stroke | undefined;
650
1121
  all(): IterableIterator<Stroke>;
651
- bbox(id: string): BBox | undefined;
1122
+ /**
1123
+ * World-space bounds for any content object, of any type. Strokes hit
1124
+ * their cached-on-mutation fast path (`bboxes`, populated by
1125
+ * `addStrokes`/`transformStrokes` — many points, worth caching); every
1126
+ * other type computes on demand via `objectBounds.ts` (cheap arithmetic,
1127
+ * no caching needed). A group's bounds are the union of its (recursively
1128
+ * resolved) children — `seen` guards against a cycle in nested groups.
1129
+ */
1130
+ /**
1131
+ * What a connector can attach to at `id`: its bounds and outline shape.
1132
+ * Connectors, lines and groups aren't targets (that would allow cycles).
1133
+ */
1134
+ connectorTarget(id: string): ConnectorTarget | undefined;
1135
+ bbox(id: string, seen?: Set<string>): BBox | undefined;
1136
+ /**
1137
+ * True if `id` exists and is locked, for any type — the same per-type
1138
+ * probe pattern as `bbox`, for interactive gestures (drag/transform) that
1139
+ * need to gate on lock state regardless of what's selected. Custom
1140
+ * objects are deliberately excluded: their `lock` field is a different
1141
+ * shape (`{holderId, acquiredAt}`, no display name) with no interactive
1142
+ * lock UI yet, matching the existing, deliberate "always unlockable,
1143
+ * never gates a drag" treatment already established elsewhere (e.g.
1144
+ * `getSelectedItemInfo`'s custom branch hardcodes `isLocked: false`).
1145
+ */
1146
+ isLocked(id: string): boolean;
1147
+ /**
1148
+ * True if `id` exists and is hidden, for any type — the same per-type
1149
+ * probe pattern as {@link isLocked} (Phase 8). Custom objects are
1150
+ * excluded for the same reason `isLocked` excludes them: they have no
1151
+ * `Hideable` field at all, so "hidden" isn't a concept that applies to
1152
+ * them yet. Used by marquee selection (`selectTool.ts`) to keep a hidden
1153
+ * object out of a rubber-band selection even for object types whose own
1154
+ * renderer doesn't yet suppress click-based hit-testing.
1155
+ */
1156
+ isHidden(id: string): boolean;
652
1157
  subscribe(listener: Listener): () => void;
653
1158
  addStrokes(strokes: Stroke[]): void;
654
1159
  removeStrokes(ids: string[]): void;
@@ -691,6 +1196,54 @@ declare class BoardDocument {
691
1196
  removeCustomObjects(ids: string[]): void;
692
1197
  /** Replace a custom object's contents under the same id. */
693
1198
  setCustomObject(object: CustomBoardObject): void;
1199
+ getRectangle(id: string): RectangleObject | undefined;
1200
+ allRectangles(): IterableIterator<RectangleObject>;
1201
+ addRectangles(rectangles: RectangleObject[]): void;
1202
+ removeRectangles(ids: string[]): void;
1203
+ /** Replace a rectangle's contents (move, resize, restyle) under the same id. */
1204
+ setRectangle(rect: RectangleObject): void;
1205
+ getEllipse(id: string): EllipseObject | undefined;
1206
+ allEllipses(): IterableIterator<EllipseObject>;
1207
+ addEllipses(ellipses: EllipseObject[]): void;
1208
+ removeEllipses(ids: string[]): void;
1209
+ /** Replace an ellipse's contents (move, resize, restyle) under the same id. */
1210
+ setEllipse(ellipse: EllipseObject): void;
1211
+ getGroup(id: string): GroupObject | undefined;
1212
+ allGroups(): IterableIterator<GroupObject>;
1213
+ addGroups(groups: GroupObject[]): void;
1214
+ removeGroups(ids: string[]): void;
1215
+ /** Replace a group's contents (its children list) under the same id. */
1216
+ setGroup(group: GroupObject): void;
1217
+ getLine(id: string): LineObject | undefined;
1218
+ allLines(): IterableIterator<LineObject>;
1219
+ addLines(lines: LineObject[]): void;
1220
+ removeLines(ids: string[]): void;
1221
+ /** Replace a line's contents (move, restyle) under the same id. */
1222
+ setLine(line: LineObject): void;
1223
+ getArrow(id: string): ArrowObject | undefined;
1224
+ allArrows(): IterableIterator<ArrowObject>;
1225
+ addArrows(arrows: ArrowObject[]): void;
1226
+ removeArrows(ids: string[]): void;
1227
+ /** Replace an arrow's contents (move, restyle, change head) under the same id. */
1228
+ setArrow(arrow: ArrowObject): void;
1229
+ getPolygon(id: string): PolygonObject | undefined;
1230
+ allPolygons(): IterableIterator<PolygonObject>;
1231
+ addPolygons(polygons: PolygonObject[]): void;
1232
+ removePolygons(ids: string[]): void;
1233
+ /** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
1234
+ setPolygon(polygon: PolygonObject): void;
1235
+ getStar(id: string): StarObject | undefined;
1236
+ allStars(): IterableIterator<StarObject>;
1237
+ addStars(stars: StarObject[]): void;
1238
+ removeStars(ids: string[]): void;
1239
+ /** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
1240
+ setStar(star: StarObject): void;
1241
+ getHeart(id: string): HeartObject | undefined;
1242
+ allHearts(): IterableIterator<HeartObject>;
1243
+ addHearts(hearts: HeartObject[]): void;
1244
+ removeHearts(ids: string[]): void;
1245
+ /** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
1246
+ setHeart(heart: HeartObject): void;
694
1247
  setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
695
1248
  setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
696
1249
  setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
@@ -698,18 +1251,69 @@ declare class BoardDocument {
698
1251
  setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
699
1252
  setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
700
1253
  setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1254
+ setRectangleLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1255
+ setEllipseLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1256
+ setGroupLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1257
+ setLineLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1258
+ setArrowLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1259
+ setPolygonLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1260
+ setStarLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1261
+ setHeartLocked(id: string, locked: boolean, by?: LockHolder | null): void;
701
1262
  /** Replace all content (initial load). Does not touch `version`. */
702
- replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]): void;
1263
+ replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[], rectangles?: RectangleObject[], ellipses?: EllipseObject[], groups?: GroupObject[], lines?: LineObject[], arrows?: ArrowObject[], polygons?: PolygonObject[], stars?: StarObject[], hearts?: HeartObject[],
1264
+ /** Persisted paint order; absent for a document saved before Phase 3, in which case one is synthesized (see `ORDERED_ADDED_FIELDS`'s doc comment). */
1265
+ objectOrder?: readonly string[]): void;
703
1266
  /** Apply incremental real-time change received from a remote collaborator over WebSocket. */
704
1267
  applyRemoteChange(change: Partial<DocumentChange>): void;
705
1268
  toJSON(): SerializedDocument;
1269
+ static deserializeGroups(data: SerializedDocument): GroupObject[];
1270
+ static deserializeLines(data: SerializedDocument): LineObject[];
1271
+ static deserializeArrows(data: SerializedDocument): ArrowObject[];
1272
+ static deserializePolygons(data: SerializedDocument): PolygonObject[];
1273
+ static deserializeStars(data: SerializedDocument): StarObject[];
1274
+ static deserializeHearts(data: SerializedDocument): HeartObject[];
706
1275
  static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
1276
+ static deserializeRectangles(data: SerializedDocument): RectangleObject[];
1277
+ static deserializeEllipses(data: SerializedDocument): EllipseObject[];
707
1278
  static deserializeImages(data: SerializedDocument): ImageBlock[];
708
1279
  static deserializeTimers(data: SerializedDocument): KitchenTimer[];
709
1280
  static deserializeNotes(data: SerializedDocument): StickyNote[];
710
1281
  static deserializeTexts(data: SerializedDocument): TextBlock[];
711
1282
  static deserializeTables(data: SerializedDocument): TableBlock[];
712
- static deserializeStrokes(data: SerializedDocument): Stroke[];
1283
+ /**
1284
+ * `onSkip` (Phase 9) replaces an unconditional `console.error` — `core`
1285
+ * must never do raw console I/O (no dev-gate, no way for a Host to
1286
+ * suppress or redirect it), so a skipped stroke is now reported only if
1287
+ * the caller asks for it, via whatever diagnostic channel it already
1288
+ * has (e.g. `controller-internal.ts` routes this into the same typed
1289
+ * `"error"` event every other diagnostic already uses). Silent by
1290
+ * default, matching how every other `deserialize*` method here already
1291
+ * behaves (no diagnostics at all).
1292
+ */
1293
+ static deserializeStrokes(data: SerializedDocument, onSkip?: (id: string, cause: unknown) => void): Stroke[];
1294
+ /**
1295
+ * Every mutation funnels through here, so paint-order tracking lives in
1296
+ * exactly one place rather than at every individual add/remove call site
1297
+ * (18 of them, times `replaceAll`/`applyRemoteChange`) — new ids are
1298
+ * appended to the back (front-most) of `objectOrder`, removed ids are
1299
+ * spliced out. An explicit reorder (`reorder`/`setOrder`) updates
1300
+ * `objectOrder` itself before calling this, so this step is a no-op for
1301
+ * ids already tracked (idempotent by construction: `orderIndex.has` gates
1302
+ * every append).
1303
+ *
1304
+ * `orderChanged` (Phase 9) reports exactly the ids whose rank actually
1305
+ * changed, using `orderIndex` throughout instead of `indexOf` — a pure
1306
+ * append never shifts any existing id's rank (new ids land at the tail,
1307
+ * already covered by this same change's own `Added` field, so
1308
+ * `orderChanged` stays unset), while a removal shifts every id at-or-
1309
+ * after the lowest removed rank down by one, computed in a single O(n)
1310
+ * filter pass (not one `indexOf`+`splice` per removed id) regardless of
1311
+ * how many ids this one change removes. Every renderer's `onChange` now
1312
+ * looks up only the ids actually in `orderChanged` instead of walking
1313
+ * its entire mesh map on any order-touching change — a broad, unfiltered
1314
+ * `orderChanged` here would silently defeat that fix, not just waste
1315
+ * cycles here.
1316
+ */
713
1317
  private emit;
714
1318
  }
715
1319
 
@@ -745,12 +1349,33 @@ interface Command {
745
1349
  apply(doc: BoardDocument): void;
746
1350
  revert(doc: BoardDocument): void;
747
1351
  }
1352
+ /**
1353
+ * Several commands applied/reverted together as one undo step (Phase 3
1354
+ * consolidation — this exact class used to be hand-duplicated as a private
1355
+ * `CommandBatch` in `controller-internal.ts` and an exported
1356
+ * `ExtensionCommandBatch` in `interaction/tools/customTool.ts`; both now
1357
+ * import this one instead). Revert runs in reverse order, so a batch that
1358
+ * depends on ordering (e.g. add-then-reference) undoes cleanly.
1359
+ */
1360
+ declare class CommandBatch implements Command {
1361
+ readonly label: string;
1362
+ private readonly commands;
1363
+ constructor(label: string, commands: readonly Command[]);
1364
+ apply(doc: BoardDocument): void;
1365
+ revert(doc: BoardDocument): void;
1366
+ }
748
1367
  /** How a command reached the document — undo/redo are audited distinctly. */
749
1368
  type CommandKind = "do" | "undo" | "redo";
750
1369
  declare class History {
751
1370
  private readonly doc;
752
1371
  private readonly undoStack;
753
1372
  private readonly redoStack;
1373
+ private scope;
1374
+ private readonly scopes;
1375
+ /** Switch independent page histories while retaining the same document instance. */
1376
+ setScope(id: string): void;
1377
+ /** Discard all page histories after replacing a saved document. */
1378
+ clearScopes(): void;
754
1379
  /**
755
1380
  * Observer for every mutation, in one place: all board edits funnel
756
1381
  * through record/undo/redo. The audit trail listens here.
@@ -809,6 +1434,40 @@ declare class TransformCommand implements Command {
809
1434
  revert(doc: BoardDocument): void;
810
1435
  private compose;
811
1436
  }
1437
+ /**
1438
+ * One move gesture over a mixed-type selection (Phase 3) — the general
1439
+ * successor to `TransformCommand` for translation. `TransformCommand`
1440
+ * itself stays as-is (still used for scale/rotate, which remain stroke-only
1441
+ * this phase — see `interaction/tools/selectTool.ts`'s `TransformState`):
1442
+ * its own per-id `doc.get(id)` check already no-ops safely for any id that
1443
+ * isn't a stroke, so it doesn't need touching for that narrower case.
1444
+ *
1445
+ * `delta` here is always a pure translation (never scale/rotate), so
1446
+ * `apply(delta, point)` is a safe, uniform way to move every position-only
1447
+ * type's `x`/`y` — translating commutes trivially regardless of a shape's
1448
+ * own rotation. Matrix-carrying types (stroke, Custom) instead compose
1449
+ * `delta` onto their existing matrix/transform, matching `TransformCommand`.
1450
+ *
1451
+ * A group id in `ids` is expanded into its (recursively resolved, cycle-
1452
+ * safe) children every time `compose` runs — deterministic, since group
1453
+ * membership never changes mid-command — so moving a selected group means
1454
+ * moving every one of its members by the same delta. This expansion is
1455
+ * `TransformObjectsCommand`'s own responsibility precisely so a caller that
1456
+ * doesn't itself expand groups (e.g. `ScrawlEngine.nudgeSelection`) still
1457
+ * gets correct behavior; `interaction/tools/selectTool.ts`'s `TransformState`
1458
+ * separately expands for its own reason (live per-child drag preview), which
1459
+ * makes this a no-op re-expansion for that caller, not a conflict.
1460
+ */
1461
+ declare class TransformObjectsCommand implements Command {
1462
+ private readonly ids;
1463
+ private readonly delta;
1464
+ readonly label = "move selection";
1465
+ private readonly inverse;
1466
+ constructor(ids: readonly string[], delta: Mat2x3);
1467
+ apply(doc: BoardDocument): void;
1468
+ revert(doc: BoardDocument): void;
1469
+ private compose;
1470
+ }
812
1471
  declare class AddNoteCommand implements Command {
813
1472
  readonly label = "add note";
814
1473
  private readonly note;
@@ -930,8 +1589,213 @@ declare class DeleteTimerCommand implements Command {
930
1589
  apply(doc: BoardDocument): void;
931
1590
  revert(doc: BoardDocument): void;
932
1591
  }
1592
+ /** Add/update/delete for semantic Rectangle objects (Phase 2). */
1593
+ declare class AddRectangleCommand implements Command {
1594
+ readonly label = "add rectangle";
1595
+ private readonly rect;
1596
+ constructor(rect: RectangleObject);
1597
+ apply(doc: BoardDocument): void;
1598
+ revert(doc: BoardDocument): void;
1599
+ }
1600
+ declare class UpdateRectangleCommand implements Command {
1601
+ readonly label = "update rectangle";
1602
+ private readonly before;
1603
+ private readonly after;
1604
+ constructor(before: RectangleObject, after: RectangleObject);
1605
+ apply(doc: BoardDocument): void;
1606
+ revert(doc: BoardDocument): void;
1607
+ }
1608
+ declare class DeleteRectangleCommand implements Command {
1609
+ readonly label = "delete rectangle";
1610
+ private readonly rect;
1611
+ constructor(rect: RectangleObject);
1612
+ apply(doc: BoardDocument): void;
1613
+ revert(doc: BoardDocument): void;
1614
+ }
1615
+ /** Add/update/delete for semantic Ellipse objects (Phase 2). */
1616
+ declare class AddEllipseCommand implements Command {
1617
+ readonly label = "add ellipse";
1618
+ private readonly ellipse;
1619
+ constructor(ellipse: EllipseObject);
1620
+ apply(doc: BoardDocument): void;
1621
+ revert(doc: BoardDocument): void;
1622
+ }
1623
+ declare class UpdateEllipseCommand implements Command {
1624
+ readonly label = "update ellipse";
1625
+ private readonly before;
1626
+ private readonly after;
1627
+ constructor(before: EllipseObject, after: EllipseObject);
1628
+ apply(doc: BoardDocument): void;
1629
+ revert(doc: BoardDocument): void;
1630
+ }
1631
+ declare class DeleteEllipseCommand implements Command {
1632
+ readonly label = "delete ellipse";
1633
+ private readonly ellipse;
1634
+ constructor(ellipse: EllipseObject);
1635
+ apply(doc: BoardDocument): void;
1636
+ revert(doc: BoardDocument): void;
1637
+ }
1638
+ /** Add/update/delete for semantic Line objects (Phase 4). */
1639
+ declare class AddLineCommand implements Command {
1640
+ readonly label = "add line";
1641
+ private readonly line;
1642
+ constructor(line: LineObject);
1643
+ apply(doc: BoardDocument): void;
1644
+ revert(doc: BoardDocument): void;
1645
+ }
1646
+ declare class UpdateLineCommand implements Command {
1647
+ readonly label = "update line";
1648
+ private readonly before;
1649
+ private readonly after;
1650
+ constructor(before: LineObject, after: LineObject);
1651
+ apply(doc: BoardDocument): void;
1652
+ revert(doc: BoardDocument): void;
1653
+ }
1654
+ declare class DeleteLineCommand implements Command {
1655
+ readonly label = "delete line";
1656
+ private readonly line;
1657
+ constructor(line: LineObject);
1658
+ apply(doc: BoardDocument): void;
1659
+ revert(doc: BoardDocument): void;
1660
+ }
1661
+ /** Add/update/delete for semantic Arrow objects (Phase 4). */
1662
+ declare class AddArrowCommand implements Command {
1663
+ readonly label = "add arrow";
1664
+ private readonly arrow;
1665
+ constructor(arrow: ArrowObject);
1666
+ apply(doc: BoardDocument): void;
1667
+ revert(doc: BoardDocument): void;
1668
+ }
1669
+ declare class UpdateArrowCommand implements Command {
1670
+ readonly label = "update arrow";
1671
+ private readonly before;
1672
+ private readonly after;
1673
+ constructor(before: ArrowObject, after: ArrowObject);
1674
+ apply(doc: BoardDocument): void;
1675
+ revert(doc: BoardDocument): void;
1676
+ }
1677
+ declare class DeleteArrowCommand implements Command {
1678
+ readonly label = "delete arrow";
1679
+ private readonly arrow;
1680
+ constructor(arrow: ArrowObject);
1681
+ apply(doc: BoardDocument): void;
1682
+ revert(doc: BoardDocument): void;
1683
+ }
1684
+ /** Add/update/delete for semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
1685
+ declare class AddPolygonCommand implements Command {
1686
+ readonly label = "add polygon";
1687
+ private readonly polygon;
1688
+ constructor(polygon: PolygonObject);
1689
+ apply(doc: BoardDocument): void;
1690
+ revert(doc: BoardDocument): void;
1691
+ }
1692
+ declare class UpdatePolygonCommand implements Command {
1693
+ readonly label = "update polygon";
1694
+ private readonly before;
1695
+ private readonly after;
1696
+ constructor(before: PolygonObject, after: PolygonObject);
1697
+ apply(doc: BoardDocument): void;
1698
+ revert(doc: BoardDocument): void;
1699
+ }
1700
+ declare class DeletePolygonCommand implements Command {
1701
+ readonly label = "delete polygon";
1702
+ private readonly polygon;
1703
+ constructor(polygon: PolygonObject);
1704
+ apply(doc: BoardDocument): void;
1705
+ revert(doc: BoardDocument): void;
1706
+ }
1707
+ /** Add/update/delete for semantic Star objects (Phase 4). */
1708
+ declare class AddStarCommand implements Command {
1709
+ readonly label = "add star";
1710
+ private readonly star;
1711
+ constructor(star: StarObject);
1712
+ apply(doc: BoardDocument): void;
1713
+ revert(doc: BoardDocument): void;
1714
+ }
1715
+ declare class UpdateStarCommand implements Command {
1716
+ readonly label = "update star";
1717
+ private readonly before;
1718
+ private readonly after;
1719
+ constructor(before: StarObject, after: StarObject);
1720
+ apply(doc: BoardDocument): void;
1721
+ revert(doc: BoardDocument): void;
1722
+ }
1723
+ declare class DeleteStarCommand implements Command {
1724
+ readonly label = "delete star";
1725
+ private readonly star;
1726
+ constructor(star: StarObject);
1727
+ apply(doc: BoardDocument): void;
1728
+ revert(doc: BoardDocument): void;
1729
+ }
1730
+ /** Add/update/delete for semantic Heart objects (Phase 4). */
1731
+ declare class AddHeartCommand implements Command {
1732
+ readonly label = "add heart";
1733
+ private readonly heart;
1734
+ constructor(heart: HeartObject);
1735
+ apply(doc: BoardDocument): void;
1736
+ revert(doc: BoardDocument): void;
1737
+ }
1738
+ declare class UpdateHeartCommand implements Command {
1739
+ readonly label = "update heart";
1740
+ private readonly before;
1741
+ private readonly after;
1742
+ constructor(before: HeartObject, after: HeartObject);
1743
+ apply(doc: BoardDocument): void;
1744
+ revert(doc: BoardDocument): void;
1745
+ }
1746
+ declare class DeleteHeartCommand implements Command {
1747
+ readonly label = "delete heart";
1748
+ private readonly heart;
1749
+ constructor(heart: HeartObject);
1750
+ apply(doc: BoardDocument): void;
1751
+ revert(doc: BoardDocument): void;
1752
+ }
1753
+ /** Add/update/delete for Groups (Phase 3). */
1754
+ declare class AddGroupCommand implements Command {
1755
+ readonly label = "group";
1756
+ private readonly group;
1757
+ constructor(group: GroupObject);
1758
+ apply(doc: BoardDocument): void;
1759
+ revert(doc: BoardDocument): void;
1760
+ }
1761
+ declare class UpdateGroupCommand implements Command {
1762
+ readonly label = "update group";
1763
+ private readonly before;
1764
+ private readonly after;
1765
+ constructor(before: GroupObject, after: GroupObject);
1766
+ apply(doc: BoardDocument): void;
1767
+ revert(doc: BoardDocument): void;
1768
+ }
1769
+ declare class DeleteGroupCommand implements Command {
1770
+ readonly label = "ungroup";
1771
+ private readonly group;
1772
+ constructor(group: GroupObject);
1773
+ apply(doc: BoardDocument): void;
1774
+ revert(doc: BoardDocument): void;
1775
+ }
1776
+ type ReorderDirection = "forward" | "backward" | "front" | "back";
1777
+ /**
1778
+ * Bring-forward / send-backward / bring-to-front / send-to-back (Phase 3) —
1779
+ * one command family covering all four directions rather than four
1780
+ * near-identical classes, since the only difference between them is which
1781
+ * `BoardDocument.reorder` direction to replay. Captures the full paint order
1782
+ * on first `apply` rather than in the constructor — `BoardDocument.order()`
1783
+ * needs the doc, which a `Command` only ever receives via `apply`/`revert` —
1784
+ * so `revert` can restore it exactly; redo re-runs the same `reorder` call,
1785
+ * which is deterministic because `revert` always restores the identical
1786
+ * starting order first.
1787
+ */
1788
+ declare class ReorderObjectCommand implements Command {
1789
+ private readonly id;
1790
+ private readonly direction;
1791
+ readonly label: string;
1792
+ private before;
1793
+ constructor(id: string, direction: ReorderDirection);
1794
+ apply(doc: BoardDocument): void;
1795
+ revert(doc: BoardDocument): void;
1796
+ }
933
1797
  interface LockTarget {
934
- type: "stroke" | "note" | "text" | "table" | "image" | "timer";
1798
+ type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart";
935
1799
  id: string;
936
1800
  locked: boolean;
937
1801
  lockedBy?: string;
@@ -948,7 +1812,7 @@ declare class LockItemsCommand implements Command {
948
1812
  private applyLock;
949
1813
  }
950
1814
 
951
- type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
1815
+ type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "rectangles" | "ellipses" | "groups" | "lines" | "arrows" | "polygons" | "stars" | "hearts" | "customObjects";
952
1816
  type Op = {
953
1817
  kind: "upsert";
954
1818
  collection: OpCollection;
@@ -979,15 +1843,23 @@ interface RibbonEdgePoint {
979
1843
  /** 1 = intact ink, 0 = fully erased (from the erasure channel). */
980
1844
  alpha: number;
981
1845
  }
982
- declare function ribbonEdges(points: StrokePoint[], baseWidth: number): RibbonEdgePoint[];
1846
+ /**
1847
+ * @param handDrawn Default `true`: ink's organic feel — width follows
1848
+ * per-point pressure, and both ends taper. Pass `false` for a geometric
1849
+ * shape outline, which has no real pressure signal and isn't a pen stroke
1850
+ * with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
1851
+ * taper at its start/end (which, for a closed shape, is the same point —
1852
+ * tapering it would visibly pinch just that one corner).
1853
+ */
1854
+ declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn?: boolean): RibbonEdgePoint[];
983
1855
 
984
1856
  declare class SpatialIndex {
985
1857
  private readonly doc;
986
1858
  private readonly cells;
987
- private readonly strokeCells;
1859
+ private readonly objectCells;
988
1860
  private readonly unsubscribe;
989
1861
  constructor(doc: BoardDocument);
990
- /** Ids of strokes whose bbox may overlap the query rect. */
1862
+ /** Ids of content objects (any type except groups) whose bbox may overlap the query rect. */
991
1863
  query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
992
1864
  dispose(): void;
993
1865
  private insert;
@@ -1036,6 +1908,539 @@ declare function placePresenceBeacon(screen: {
1036
1908
  height: number;
1037
1909
  }, inset?: ViewportInset): PresencePlacement;
1038
1910
 
1911
+ interface IconDefinition {
1912
+ /** Stable, set-prefixed id (`lucide:rocket`), also what an ImageBlock stores in its `stamp` field. */
1913
+ readonly name: string;
1914
+ readonly label: string;
1915
+ readonly category: string;
1916
+ /** Inner SVG markup for a 24x24 viewBox; strokes use `currentColor`. */
1917
+ readonly body: string;
1918
+ }
1919
+ declare const ICON_LIBRARY: readonly [{
1920
+ readonly name: "lucide:check";
1921
+ readonly label: "Check";
1922
+ readonly category: "Status";
1923
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>";
1924
+ }, {
1925
+ readonly name: "lucide:x";
1926
+ readonly label: "Cross";
1927
+ readonly category: "Status";
1928
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>";
1929
+ }, {
1930
+ readonly name: "lucide:star";
1931
+ readonly label: "Star";
1932
+ readonly category: "Status";
1933
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\"/>";
1934
+ }, {
1935
+ readonly name: "lucide:heart";
1936
+ readonly label: "Heart";
1937
+ readonly category: "Status";
1938
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2 9.5a5.5 5.5 0 0 1 9.591-3.676a.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5\"/>";
1939
+ }, {
1940
+ readonly name: "lucide:flag";
1941
+ readonly label: "Flag";
1942
+ readonly category: "Status";
1943
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528\"/>";
1944
+ }, {
1945
+ readonly name: "lucide:bookmark";
1946
+ readonly label: "Bookmark";
1947
+ readonly category: "Status";
1948
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z\"/>";
1949
+ }, {
1950
+ readonly name: "lucide:bell";
1951
+ readonly label: "Bell";
1952
+ readonly category: "Status";
1953
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10.268 21a2 2 0 0 0 3.464 0m-10.47-5.674A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\"/>";
1954
+ }, {
1955
+ readonly name: "lucide:info";
1956
+ readonly label: "Info";
1957
+ readonly category: "Status";
1958
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4m0-4h.01\"/></g>";
1959
+ }, {
1960
+ readonly name: "lucide:circle-alert";
1961
+ readonly label: "Alert";
1962
+ readonly category: "Status";
1963
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></g>";
1964
+ }, {
1965
+ readonly name: "lucide:circle-question-mark";
1966
+ readonly label: "Question";
1967
+ readonly category: "Status";
1968
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01\"/></g>";
1969
+ }, {
1970
+ readonly name: "lucide:thumbs-up";
1971
+ readonly label: "Thumbs up";
1972
+ readonly category: "Status";
1973
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 5.88L14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88M7 10v12\"/>";
1974
+ }, {
1975
+ readonly name: "lucide:thumbs-down";
1976
+ readonly label: "Thumbs down";
1977
+ readonly category: "Status";
1978
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 18.12L10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88M17 14V2\"/>";
1979
+ }, {
1980
+ readonly name: "lucide:arrow-up";
1981
+ readonly label: "Arrow up";
1982
+ readonly category: "Arrows";
1983
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>";
1984
+ }, {
1985
+ readonly name: "lucide:arrow-down";
1986
+ readonly label: "Arrow down";
1987
+ readonly category: "Arrows";
1988
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>";
1989
+ }, {
1990
+ readonly name: "lucide:arrow-left";
1991
+ readonly label: "Arrow left";
1992
+ readonly category: "Arrows";
1993
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"/>";
1994
+ }, {
1995
+ readonly name: "lucide:arrow-right";
1996
+ readonly label: "Arrow right";
1997
+ readonly category: "Arrows";
1998
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"/>";
1999
+ }, {
2000
+ readonly name: "lucide:arrow-up-right";
2001
+ readonly label: "Arrow up-right";
2002
+ readonly category: "Arrows";
2003
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h10v10M7 17L17 7\"/>";
2004
+ }, {
2005
+ readonly name: "lucide:refresh-cw";
2006
+ readonly label: "Refresh";
2007
+ readonly category: "Arrows";
2008
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M3 12a9 9 0 0 1 9-9a9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5m5 4a9 9 0 0 1-9 9a9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></g>";
2009
+ }, {
2010
+ readonly name: "lucide:move";
2011
+ readonly label: "Move";
2012
+ readonly category: "Arrows";
2013
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 2v20m3-3l-3 3l-3-3M19 9l3 3l-3 3M2 12h20M5 9l-3 3l3 3M9 5l3-3l3 3\"/>";
2014
+ }, {
2015
+ readonly name: "lucide:shuffle";
2016
+ readonly label: "Shuffle";
2017
+ readonly category: "Arrows";
2018
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m18 14l4 4l-4 4m0-20l4 4l-4 4\"/><path d=\"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22M2 6h1.972a4 4 0 0 1 3.6 2.2M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45\"/></g>";
2019
+ }, {
2020
+ readonly name: "lucide:user";
2021
+ readonly label: "User";
2022
+ readonly category: "People";
2023
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></g>";
2024
+ }, {
2025
+ readonly name: "lucide:users";
2026
+ readonly label: "Users";
2027
+ readonly category: "People";
2028
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M16 3.128a4 4 0 0 1 0 7.744M22 21v-2a4 4 0 0 0-3-3.87\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></g>";
2029
+ }, {
2030
+ readonly name: "lucide:briefcase";
2031
+ readonly label: "Briefcase";
2032
+ readonly category: "People";
2033
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\"/><rect width=\"20\" height=\"14\" x=\"2\" y=\"6\" rx=\"2\"/></g>";
2034
+ }, {
2035
+ readonly name: "lucide:building";
2036
+ readonly label: "Building";
2037
+ readonly category: "People";
2038
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 10h.01M12 14h.01M12 6h.01M16 10h.01M16 14h.01M16 6h.01M8 10h.01M8 14h.01M8 6h.01M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"/><rect width=\"16\" height=\"20\" x=\"4\" y=\"2\" rx=\"2\"/></g>";
2039
+ }, {
2040
+ readonly name: "lucide:calendar";
2041
+ readonly label: "Calendar";
2042
+ readonly category: "People";
2043
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M8 2v3m8-3v3\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/></g>";
2044
+ }, {
2045
+ readonly name: "lucide:clock";
2046
+ readonly label: "Clock";
2047
+ readonly category: "People";
2048
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></g>";
2049
+ }, {
2050
+ readonly name: "lucide:mail";
2051
+ readonly label: "Mail";
2052
+ readonly category: "People";
2053
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"/><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/></g>";
2054
+ }, {
2055
+ readonly name: "lucide:message-circle";
2056
+ readonly label: "Message";
2057
+ readonly category: "People";
2058
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092a10 10 0 1 0-4.777-4.719\"/>";
2059
+ }, {
2060
+ readonly name: "lucide:file";
2061
+ readonly label: "File";
2062
+ readonly category: "Objects";
2063
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/></g>";
2064
+ }, {
2065
+ readonly name: "lucide:folder";
2066
+ readonly label: "Folder";
2067
+ readonly category: "Objects";
2068
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>";
2069
+ }, {
2070
+ readonly name: "lucide:image";
2071
+ readonly label: "Image";
2072
+ readonly category: "Objects";
2073
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></g>";
2074
+ }, {
2075
+ readonly name: "lucide:link";
2076
+ readonly label: "Link";
2077
+ readonly category: "Objects";
2078
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></g>";
2079
+ }, {
2080
+ readonly name: "lucide:paperclip";
2081
+ readonly label: "Paperclip";
2082
+ readonly category: "Objects";
2083
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 6l-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\"/>";
2084
+ }, {
2085
+ readonly name: "lucide:pin";
2086
+ readonly label: "Pin";
2087
+ readonly category: "Objects";
2088
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 17v5M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4a1 1 0 0 1 1 1z\"/>";
2089
+ }, {
2090
+ readonly name: "lucide:map-pin";
2091
+ readonly label: "Map pin";
2092
+ readonly category: "Objects";
2093
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/></g>";
2094
+ }, {
2095
+ readonly name: "lucide:search";
2096
+ readonly label: "Search";
2097
+ readonly category: "Objects";
2098
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m21 21l-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></g>";
2099
+ }, {
2100
+ readonly name: "lucide:settings";
2101
+ readonly label: "Settings";
2102
+ readonly category: "Objects";
2103
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></g>";
2104
+ }, {
2105
+ readonly name: "lucide:trash-2";
2106
+ readonly label: "Trash";
2107
+ readonly category: "Objects";
2108
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>";
2109
+ }, {
2110
+ readonly name: "lucide:lock";
2111
+ readonly label: "Lock";
2112
+ readonly category: "Objects";
2113
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></g>";
2114
+ }, {
2115
+ readonly name: "lucide:key";
2116
+ readonly label: "Key";
2117
+ readonly category: "Objects";
2118
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m15.5 7.5l2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4m2-2l-9.6 9.6\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/></g>";
2119
+ }, {
2120
+ readonly name: "lucide:code";
2121
+ readonly label: "Code";
2122
+ readonly category: "Tech";
2123
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 18l6-6l-6-6M8 6l-6 6l6 6\"/>";
2124
+ }, {
2125
+ readonly name: "lucide:database";
2126
+ readonly label: "Database";
2127
+ readonly category: "Tech";
2128
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5v14a9 3 0 0 0 18 0V5\"/><path d=\"M3 12a9 3 0 0 0 18 0\"/></g>";
2129
+ }, {
2130
+ readonly name: "lucide:server";
2131
+ readonly label: "Server";
2132
+ readonly category: "Tech";
2133
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/></g>";
2134
+ }, {
2135
+ readonly name: "lucide:cloud";
2136
+ readonly label: "Cloud";
2137
+ readonly category: "Tech";
2138
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9\"/>";
2139
+ }, {
2140
+ readonly name: "lucide:git-branch";
2141
+ readonly label: "Branch";
2142
+ readonly category: "Tech";
2143
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M15 6a9 9 0 0 0-9 9V3\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/></g>";
2144
+ }, {
2145
+ readonly name: "lucide:terminal";
2146
+ readonly label: "Terminal";
2147
+ readonly category: "Tech";
2148
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19h8M4 17l6-6l-6-6\"/>";
2149
+ }, {
2150
+ readonly name: "lucide:cpu";
2151
+ readonly label: "CPU";
2152
+ readonly category: "Tech";
2153
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v2m0-20v2m5 16v2m0-20v2M2 12h2m-2 5h2M2 7h2m16 5h2m-2 5h2M20 7h2M7 20v2M7 2v2\"/><rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"/><rect width=\"8\" height=\"8\" x=\"8\" y=\"8\" rx=\"1\"/></g>";
2154
+ }, {
2155
+ readonly name: "lucide:bug";
2156
+ readonly label: "Bug";
2157
+ readonly category: "Tech";
2158
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v-9m2-4a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4zm.12-3.12L16 2\"/><path d=\"M21 21a4 4 0 0 0-3.81-4M21 5a4 4 0 0 1-3.55 3.97M22 13h-4M3 21a4 4 0 0 1 3.81-4M3 5a4 4 0 0 0 3.55 3.97M6 13H2M8 2l1.88 1.88M9 7.13V6a3 3 0 1 1 6 0v1.13\"/></g>";
2159
+ }, {
2160
+ readonly name: "lucide:lightbulb";
2161
+ readonly label: "Idea";
2162
+ readonly category: "Ideas";
2163
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\"/>";
2164
+ }, {
2165
+ readonly name: "lucide:zap";
2166
+ readonly label: "Zap";
2167
+ readonly category: "Ideas";
2168
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15.914 4a1.5 1.5 0 0 0-2.474-1.561l-9 9A1.5 1.5 0 0 0 5.5 14h4.002a.5.5 0 0 1 .471.666L8.086 20a1.5 1.5 0 0 0 2.475 1.56l9-9A1.5 1.5 0 0 0 18.5 10h-3.997a.5.5 0 0 1-.472-.667z\"/>";
2169
+ }, {
2170
+ readonly name: "lucide:target";
2171
+ readonly label: "Target";
2172
+ readonly category: "Ideas";
2173
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></g>";
2174
+ }, {
2175
+ readonly name: "lucide:rocket";
2176
+ readonly label: "Rocket";
2177
+ readonly category: "Ideas";
2178
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09\"/><path d=\"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z\"/><path d=\"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05\"/></g>";
2179
+ }, {
2180
+ readonly name: "lucide:trophy";
2181
+ readonly label: "Trophy";
2182
+ readonly category: "Ideas";
2183
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 14.66V17a1 1 0 0 1-1 1a2 2 0 0 0-2 2v2m7-7.34V17a1 1 0 0 0 1 1a2 2 0 0 1 2 2v2m.916-12H19.5A2.5 2.5 0 0 0 22 7.5V5a1 1 0 0 0-1-1h-3M4 22h16\"/><path d=\"M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z\"/><path d=\"M6.084 10H4.5A2.5 2.5 0 0 1 2 7.5V5a1 1 0 0 1 1-1h3\"/></g>";
2184
+ }, {
2185
+ readonly name: "lucide:gift";
2186
+ readonly label: "Gift";
2187
+ readonly category: "Ideas";
2188
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 7v14m8-10v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8m3.5-4a1 1 0 0 1 0-5A4.8 8 0 0 1 12 7a4.8 8 0 0 1 4.5-5a1 1 0 0 1 0 5\"/><rect width=\"18\" height=\"4\" x=\"3\" y=\"7\" rx=\"1\"/></g>";
2189
+ }, {
2190
+ readonly name: "lucide:coffee";
2191
+ readonly label: "Coffee";
2192
+ readonly category: "Ideas";
2193
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 2v2m4-2v2m2 4a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1M6 2v2\"/>";
2194
+ }, {
2195
+ readonly name: "lucide:smile";
2196
+ readonly label: "Smile";
2197
+ readonly category: "Ideas";
2198
+ readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M8 14s1.5 2 4 2s4-2 4-2M9 9h.01M15 9h.01\"/></g>";
2199
+ }];
2200
+ /** Every vendored icon's id, as a literal union. */
2201
+ type IconName = (typeof ICON_LIBRARY)[number]["name"];
2202
+
2203
+ /**
2204
+ * Emoji stickers. Unlike the icon library there's no artwork to vendor: each
2205
+ * is a Unicode character, drawn by the platform's own colour-emoji font when
2206
+ * its SVG is rasterised — so an emoji looks native to each viewer's OS
2207
+ * (Apple, Segoe, Noto), the same way it would in a chat app.
2208
+ */
2209
+ interface EmojiDefinition {
2210
+ /** Stable, set-prefixed id (`emoji:fire`), also what an ImageBlock stores in its `stamp` field. */
2211
+ readonly name: string;
2212
+ readonly label: string;
2213
+ readonly category: string;
2214
+ readonly char: string;
2215
+ }
2216
+ declare const EMOJI_LIBRARY: readonly [{
2217
+ readonly name: "emoji:grinning";
2218
+ readonly label: "Grinning face";
2219
+ readonly category: "Smileys";
2220
+ readonly char: "😀";
2221
+ }, {
2222
+ readonly name: "emoji:joy";
2223
+ readonly label: "Tears of joy";
2224
+ readonly category: "Smileys";
2225
+ readonly char: "😂";
2226
+ }, {
2227
+ readonly name: "emoji:smiling";
2228
+ readonly label: "Smiling face";
2229
+ readonly category: "Smileys";
2230
+ readonly char: "😊";
2231
+ }, {
2232
+ readonly name: "emoji:heart-eyes";
2233
+ readonly label: "Heart eyes";
2234
+ readonly category: "Smileys";
2235
+ readonly char: "😍";
2236
+ }, {
2237
+ readonly name: "emoji:thinking";
2238
+ readonly label: "Thinking face";
2239
+ readonly category: "Smileys";
2240
+ readonly char: "🤔";
2241
+ }, {
2242
+ readonly name: "emoji:sunglasses";
2243
+ readonly label: "Sunglasses";
2244
+ readonly category: "Smileys";
2245
+ readonly char: "😎";
2246
+ }, {
2247
+ readonly name: "emoji:crying";
2248
+ readonly label: "Crying face";
2249
+ readonly category: "Smileys";
2250
+ readonly char: "😢";
2251
+ }, {
2252
+ readonly name: "emoji:angry";
2253
+ readonly label: "Angry face";
2254
+ readonly category: "Smileys";
2255
+ readonly char: "😡";
2256
+ }, {
2257
+ readonly name: "emoji:sleeping";
2258
+ readonly label: "Sleeping face";
2259
+ readonly category: "Smileys";
2260
+ readonly char: "😴";
2261
+ }, {
2262
+ readonly name: "emoji:mind-blown";
2263
+ readonly label: "Mind blown";
2264
+ readonly category: "Smileys";
2265
+ readonly char: "🤯";
2266
+ }, {
2267
+ readonly name: "emoji:grimacing";
2268
+ readonly label: "Grimacing face";
2269
+ readonly category: "Smileys";
2270
+ readonly char: "😬";
2271
+ }, {
2272
+ readonly name: "emoji:partying";
2273
+ readonly label: "Partying face";
2274
+ readonly category: "Smileys";
2275
+ readonly char: "🥳";
2276
+ }, {
2277
+ readonly name: "emoji:thumbs-up";
2278
+ readonly label: "Thumbs up emoji";
2279
+ readonly category: "Gestures";
2280
+ readonly char: "👍";
2281
+ }, {
2282
+ readonly name: "emoji:thumbs-down";
2283
+ readonly label: "Thumbs down emoji";
2284
+ readonly category: "Gestures";
2285
+ readonly char: "👎";
2286
+ }, {
2287
+ readonly name: "emoji:clap";
2288
+ readonly label: "Clapping hands";
2289
+ readonly category: "Gestures";
2290
+ readonly char: "👏";
2291
+ }, {
2292
+ readonly name: "emoji:raised-hands";
2293
+ readonly label: "Raised hands";
2294
+ readonly category: "Gestures";
2295
+ readonly char: "🙌";
2296
+ }, {
2297
+ readonly name: "emoji:folded-hands";
2298
+ readonly label: "Folded hands";
2299
+ readonly category: "Gestures";
2300
+ readonly char: "🙏";
2301
+ }, {
2302
+ readonly name: "emoji:eyes";
2303
+ readonly label: "Eyes";
2304
+ readonly category: "Gestures";
2305
+ readonly char: "👀";
2306
+ }, {
2307
+ readonly name: "emoji:muscle";
2308
+ readonly label: "Flexed biceps";
2309
+ readonly category: "Gestures";
2310
+ readonly char: "💪";
2311
+ }, {
2312
+ readonly name: "emoji:wave";
2313
+ readonly label: "Waving hand";
2314
+ readonly category: "Gestures";
2315
+ readonly char: "👋";
2316
+ }, {
2317
+ readonly name: "emoji:red-heart";
2318
+ readonly label: "Red heart";
2319
+ readonly category: "Symbols";
2320
+ readonly char: "❤️";
2321
+ }, {
2322
+ readonly name: "emoji:fire";
2323
+ readonly label: "Flame";
2324
+ readonly category: "Symbols";
2325
+ readonly char: "🔥";
2326
+ }, {
2327
+ readonly name: "emoji:star";
2328
+ readonly label: "Medium star";
2329
+ readonly category: "Symbols";
2330
+ readonly char: "⭐";
2331
+ }, {
2332
+ readonly name: "emoji:check";
2333
+ readonly label: "Check mark button";
2334
+ readonly category: "Symbols";
2335
+ readonly char: "✅";
2336
+ }, {
2337
+ readonly name: "emoji:cross";
2338
+ readonly label: "Cross mark";
2339
+ readonly category: "Symbols";
2340
+ readonly char: "❌";
2341
+ }, {
2342
+ readonly name: "emoji:warning";
2343
+ readonly label: "Warning sign";
2344
+ readonly category: "Symbols";
2345
+ readonly char: "⚠️";
2346
+ }, {
2347
+ readonly name: "emoji:question";
2348
+ readonly label: "Red question mark";
2349
+ readonly category: "Symbols";
2350
+ readonly char: "❓";
2351
+ }, {
2352
+ readonly name: "emoji:hundred";
2353
+ readonly label: "Hundred points";
2354
+ readonly category: "Symbols";
2355
+ readonly char: "💯";
2356
+ }, {
2357
+ readonly name: "emoji:light-bulb";
2358
+ readonly label: "Light bulb";
2359
+ readonly category: "Symbols";
2360
+ readonly char: "💡";
2361
+ }, {
2362
+ readonly name: "emoji:bullseye";
2363
+ readonly label: "Bullseye";
2364
+ readonly category: "Symbols";
2365
+ readonly char: "🎯";
2366
+ }, {
2367
+ readonly name: "emoji:party-popper";
2368
+ readonly label: "Party popper";
2369
+ readonly category: "Objects";
2370
+ readonly char: "🎉";
2371
+ }, {
2372
+ readonly name: "emoji:rocket";
2373
+ readonly label: "Rocket emoji";
2374
+ readonly category: "Objects";
2375
+ readonly char: "🚀";
2376
+ }, {
2377
+ readonly name: "emoji:trophy";
2378
+ readonly label: "Trophy emoji";
2379
+ readonly category: "Objects";
2380
+ readonly char: "🏆";
2381
+ }, {
2382
+ readonly name: "emoji:pushpin";
2383
+ readonly label: "Pushpin";
2384
+ readonly category: "Objects";
2385
+ readonly char: "📌";
2386
+ }, {
2387
+ readonly name: "emoji:memo";
2388
+ readonly label: "Memo";
2389
+ readonly category: "Objects";
2390
+ readonly char: "📝";
2391
+ }, {
2392
+ readonly name: "emoji:calendar";
2393
+ readonly label: "Tear-off calendar";
2394
+ readonly category: "Objects";
2395
+ readonly char: "📅";
2396
+ }, {
2397
+ readonly name: "emoji:alarm-clock";
2398
+ readonly label: "Alarm clock";
2399
+ readonly category: "Objects";
2400
+ readonly char: "⏰";
2401
+ }, {
2402
+ readonly name: "emoji:speech-balloon";
2403
+ readonly label: "Speech balloon";
2404
+ readonly category: "Objects";
2405
+ readonly char: "💬";
2406
+ }];
2407
+ type EmojiName = (typeof EMOJI_LIBRARY)[number]["name"];
2408
+ declare function isEmojiName(value: unknown): value is EmojiName;
2409
+ declare function emojiDefinition(name: string): EmojiDefinition | undefined;
2410
+ /** An emoji as a standalone SVG data URL, sized to rasterise sharply (see icons.ts). */
2411
+ declare function emojiDataUrl(name: string): string;
2412
+
2413
+ /**
2414
+ * What the stamp tool will drop next: one of the six built-in stamp-pad
2415
+ * stickers, an emoji, or any vendored icon. All land on the board the same way
2416
+ * — as an image whose `stamp` field records which one it was (see `placeStamp`).
2417
+ */
2418
+ type StickerKind = StampKind | EmojiName | IconName;
2419
+ declare function isIconName(value: unknown): value is IconName;
2420
+ declare function iconDefinition(name: string): IconDefinition | undefined;
2421
+ /** The vendored icons grouped for a picker, in the order they were vendored. */
2422
+ declare function iconCategories(): {
2423
+ category: string;
2424
+ icons: readonly IconDefinition[];
2425
+ }[];
2426
+ /**
2427
+ * An icon as a standalone SVG data URL, drawn in `color`.
2428
+ *
2429
+ * The library's art strokes in `currentColor`, which has no meaning once the
2430
+ * markup is detached into a data URL — an `<img>`/texture has no inherited
2431
+ * colour to resolve it against — so the colour is substituted in rather than
2432
+ * inherited.
2433
+ */
2434
+ declare function iconDataUrl(name: string, color: string): string;
2435
+ declare function isStickerKind(value: unknown): value is StickerKind;
2436
+ /** Human-readable name for any sticker kind — what a placed sticker is called and searched by. */
2437
+ declare function stickerLabel(kind: StickerKind): string;
2438
+ /**
2439
+ * The image `src` for whichever sticker kind is selected. Stamp-pad stamps
2440
+ * and emoji carry their own colours, so only icons take the Host's colour.
2441
+ */
2442
+ declare function stickerDataUrl(kind: StickerKind, color: string): string;
2443
+
1039
2444
  interface ExtensionRegistry {
1040
2445
  readonly extensions: readonly ScrawlExtension[];
1041
2446
  tool(id: ToolId): CustomToolDefinition | undefined;
@@ -1048,6 +2453,11 @@ interface ExtensionRegistry {
1048
2453
  * isn't in it), Custom objects still export via the standard fallback
1049
2454
  * placeholder (`fallback.bounds`/`label`), never silently dropped.
1050
2455
  *
2456
+ * `backgroundColor` should be the Host's actual resolved `boardTheme.surface`
2457
+ * so an export matches what was on screen; defaults to the theme system's
2458
+ * own light-preset default when the caller doesn't have one on hand. The
2459
+ * board's reference grid, if any, is a screen-only aid and never exported.
2460
+ *
1051
2461
  * `resolvedAssets` (ticket #23) maps an Asset reference to an already-
1052
2462
  * resolved `data:` URI — see `assetExport.ts`'s `exportDocumentSVGWithAssets`,
1053
2463
  * which is the only intended caller that ever passes one. Without it, every
@@ -1055,30 +2465,14 @@ interface ExtensionRegistry {
1055
2465
  * placeholder instead of guessing at a URL; a legacy `src`-backed image is
1056
2466
  * unaffected either way.
1057
2467
  */
1058
- declare function documentToSVG(doc: SerializedDocument, now?: number, registry?: ExtensionRegistry, resolvedAssets?: ReadonlyMap<AssetRef, string>): string;
2468
+ declare function documentToSVG(doc: SerializedDocument, now?: number, registry?: ExtensionRegistry, backgroundColor?: string, resolvedAssets?: ReadonlyMap<AssetRef, string>): string;
1059
2469
 
1060
2470
  declare const SDK_PACKAGE_NAME = "@scrawl-board/board";
1061
2471
  declare const SDK_DEVELOPMENT_VERSION = "0.0.0-development";
1062
2472
 
1063
- type BoardBounds = {
1064
- minX: number;
1065
- minY: number;
1066
- maxX: number;
1067
- maxY: number;
1068
- };
1069
- type ScenePath = {
1070
- id: string;
1071
- color: string;
1072
- width: number;
1073
- points: readonly BoardPoint[];
1074
- };
1075
- type BoardScene = {
1076
- bounds: BoardBounds;
1077
- paths: readonly ScenePath[];
1078
- };
1079
2473
  type BoardStroke = Stroke;
1080
2474
  type SerializedBoardStroke = SerializedStroke;
1081
2475
  type SerializedBoardDocument = CurrentSerializedDocument;
1082
2476
 
1083
- export { AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, BEACON_INSET, BOARD_COLOR, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, STAMPS, STAMP_SIZE, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, UpdateImageCommand, UpdateNoteCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, avgScale, canUnlockItem, changeToOps, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentToSVG, formatTimer, invert, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
1084
- export type { BBox, BoardBounds, BoardPoint, BoardScene, BoardStroke, ClusterIdFactory, Command, CommandKind, CurrentSerializedDocument, CurrentSerializedStroke, DocumentChange, DocumentId, DocumentLoadResult, DocumentRecoveryCode, ImageBlock, KitchenTimer, LockHolder, LockTarget, Lockable, Mat2x3, NoteVote, Op, OpCollection, PresencePlacement, RibbonEdgePoint, ScenePath, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, TableBlock, TextBlock, ViewportInset };
2477
+ export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddArrowCommand, AddEllipseCommand, AddGroupCommand, AddHeartCommand, AddImageCommand, AddLineCommand, AddNoteCommand, AddPolygonCommand, AddRectangleCommand, AddStarCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, CommandBatch, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, EMOJI_LIBRARY, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, ICON_LIBRARY, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, ReorderObjectCommand, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, SHAPE_DEFAULT_STROKE, SHAPE_DEFAULT_STROKE_WIDTH, SHAPE_MIN_SIZE, SHAPE_STYLE_DEFAULTS, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, TransformObjectsCommand, UpdateArrowCommand, UpdateEllipseCommand, UpdateGroupCommand, UpdateHeartCommand, UpdateImageCommand, UpdateLineCommand, UpdateNoteCommand, UpdatePolygonCommand, UpdateRectangleCommand, UpdateStarCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneArrow, cloneCustomObject, cloneEllipse, cloneGroup, cloneHeart, cloneImage, cloneLine, cloneNote, clonePolygon, cloneRectangle, cloneStar, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentSize, documentToSVG, emojiDataUrl, emojiDefinition, formatTimer, iconCategories, iconDataUrl, iconDefinition, invert, isAssetRef, isEmojiName, isIconName, isIdentity, isStampKind, isStickerKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, stickerDataUrl, stickerLabel, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
2478
+ export type { ArrowHeadStyle, ArrowObject, AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardKeyInput, BoardNotebook, BoardPoint, BoardPointerInput, BoardScene, BoardSection, BoardStroke, ClusterIdFactory, Command, CommandKind, ConnectorBinding, ConnectorBox, ConnectorTarget, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DocumentChange, DocumentId, DocumentLoadResult, DocumentRecoveryCode, EllipseObject, EmojiDefinition, EmojiName, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, GroupObject, HeartObject, IconDefinition, IconName, ImageBlock, InputModifiers, JsonObject, JsonValue, KitchenTimer, LineObject, LockHolder, LockTarget, Lockable, Mat2x3, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PolygonObject, PresencePlacement, QueryableBoardObject, ReadonlyCustomObject, RectangleObject, ReorderDirection, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlExtension, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardPage, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StarObject, StickerKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };