@scrawl-board/board 0.1.0-beta.1 → 0.1.0-beta.11

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/index.d.ts CHANGED
@@ -1,17 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ComponentType, CSSProperties } from 'react';
3
3
 
4
- declare const documentIdBrand: unique symbol;
5
- declare const strokeIdBrand: unique symbol;
6
- type DocumentId = string & {
7
- readonly [documentIdBrand]: "DocumentId";
8
- };
9
- type StrokeId = string & {
10
- readonly [strokeIdBrand]: "StrokeId";
11
- };
12
- declare function documentId(value: string): DocumentId;
13
- declare function strokeId(value: string): StrokeId;
14
-
15
4
  /** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
16
5
  type AssetRef = string;
17
6
  declare const ASSET_REF_PATTERN: RegExp;
@@ -184,6 +173,21 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
184
173
  /** One pure, synchronous step per consecutive schema version. */
185
174
  migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
186
175
  describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
176
+ /**
177
+ * Optional point-level hit-test precision (Phase 8). Every custom object
178
+ * hit-tests against its bounding box (`fallback.bounds`) by default — this
179
+ * lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
180
+ * reject a point that's inside that box but outside its actual visible
181
+ * silhouette, tightening a click/marquee/raycast hit to the shape's real
182
+ * outline. `point` is in this object's own local space — the same
183
+ * untransformed space `describe`'s returned geometry already lives in
184
+ * (the caller inverse-transforms the pointer's board point through
185
+ * `object.transform` before calling this). Absent means every point
186
+ * inside the bounding box hits, matching pre-Phase-8 behavior exactly.
187
+ * Rejecting a point here does not fall through to whatever's underneath —
188
+ * the gesture simply misses this object, same as clicking empty space.
189
+ */
190
+ hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
187
191
  }
188
192
  interface SceneNodeBase {
189
193
  key: string;
@@ -217,6 +221,15 @@ interface SceneGroup extends SceneNodeBase {
217
221
  kind: "group";
218
222
  children: readonly BoardScene[];
219
223
  }
224
+ /**
225
+ * **No renderer or SVG-export interpreter exists for this node kind yet**
226
+ * (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
227
+ * `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
228
+ * exports nothing, and contributes no hit-test bounds — it neither errors
229
+ * nor emits a diagnostic. Until an interpreter ships, build custom shapes
230
+ * from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
231
+ * instead.
232
+ */
220
233
  interface ScenePath extends SceneNodeBase {
221
234
  kind: "path";
222
235
  /** SVG-style path data, board-local coordinates. */
@@ -389,8 +402,23 @@ declare function canUnlockItem(item: Lockable | undefined, userId: string | unde
389
402
  /** Wire fields for a locked item; omitted entirely when unlocked. */
390
403
  declare function serializeLock(item: Lockable): Lockable;
391
404
 
405
+ /**
406
+ * Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
407
+ * pattern exactly, but simpler: unlike a lock, hidden state carries no
408
+ * holder/ownership concept, so there's no analogue to `LockHolder`/
409
+ * `canUnlockItem`. A hidden object stays fully present in the Document
410
+ * (still serializes, persists, syncs, undoes/redoes) — it just skips
411
+ * rendering and hit-testing/selection candidacy. `hidden` absent or
412
+ * `false` means visible; this keeps every pre-Phase-8 document (which has
413
+ * no `hidden` field on any object at all) implicitly fully visible with
414
+ * zero migration needed.
415
+ */
416
+ interface Hideable {
417
+ hidden?: boolean;
418
+ }
419
+
392
420
  /** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
393
- interface KitchenTimer extends Lockable {
421
+ interface KitchenTimer extends Lockable, Hideable {
394
422
  id: string;
395
423
  x: number;
396
424
  y: number;
@@ -415,6 +443,173 @@ declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
415
443
  declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
416
444
  declare function formatTimer(ms: number): string;
417
445
 
446
+ declare const SHAPE_MIN_SIZE = 0.5;
447
+ /**
448
+ * Centralized shape style defaults (Phase 8) — one object a Host can read
449
+ * to know (or, by not relying on the standalone constants below, override
450
+ * via its own UI state) what a newly drawn Rectangle/Ellipse/Line/Arrow/
451
+ * Polygon/Star/Heart starts with when the user hasn't picked a stroke/width
452
+ * yet. `shapeTool.ts` (commit-time), `renderer/shapes/lines.ts` (render-time
453
+ * fallback for an object missing these fields), and
454
+ * `persistence/serialization/svg.ts` (export-time fallback) all read from
455
+ * here — the two standalone constants below are kept for source
456
+ * compatibility and simply mirror this object's values, not a second
457
+ * source of truth.
458
+ */
459
+ declare const SHAPE_STYLE_DEFAULTS: {
460
+ readonly stroke: "#1C1C1E";
461
+ readonly strokeWidth: 0.3;
462
+ };
463
+ declare const SHAPE_DEFAULT_STROKE: "#1C1C1E";
464
+ declare const SHAPE_DEFAULT_STROKE_WIDTH: 0.3;
465
+ interface RectangleObject extends Lockable, Hideable {
466
+ id: string;
467
+ x: number;
468
+ y: number;
469
+ width: number;
470
+ height: number;
471
+ fill?: string;
472
+ stroke?: string;
473
+ strokeWidth?: number;
474
+ /** Corner radius in board units; clamped to at most half the shorter side at render time. */
475
+ cornerRadius?: number;
476
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
477
+ opacity?: number;
478
+ /**
479
+ * Radians, about the shape's own center `(x + width/2, y - height/2)`.
480
+ * Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
481
+ * shape's own unrotated local frame — rotation is a separate, applied-last
482
+ * transform, not baked into them, matching how Stroke/CustomBoardObject
483
+ * keep geometry and placement independent via their own `matrix`.
484
+ */
485
+ rotation?: number;
486
+ }
487
+ interface EllipseObject extends Lockable, Hideable {
488
+ id: string;
489
+ x: number;
490
+ y: number;
491
+ width: number;
492
+ height: number;
493
+ fill?: string;
494
+ stroke?: string;
495
+ strokeWidth?: number;
496
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
497
+ opacity?: number;
498
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
499
+ rotation?: number;
500
+ }
501
+ declare function cloneRectangle(rect: RectangleObject): RectangleObject;
502
+ declare function cloneEllipse(ellipse: EllipseObject): EllipseObject;
503
+ /** `"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. */
504
+ type ArrowHeadStyle = "triangle" | "none";
505
+ interface LineObject extends Lockable, Hideable {
506
+ id: string;
507
+ start: BoardPoint;
508
+ end: BoardPoint;
509
+ stroke?: string;
510
+ strokeWidth?: number;
511
+ opacity?: number;
512
+ }
513
+ /** An endpoint anchored to a normalized position on another object's bounds. */
514
+ interface ConnectorBinding {
515
+ objectId: string;
516
+ /** Horizontal and vertical fractions of the target's axis-aligned bounds. */
517
+ x: number;
518
+ y: number;
519
+ }
520
+ interface ArrowObject extends Lockable, Hideable {
521
+ /** Omitted for legacy arrows. Connectors share Arrow's persistence/history contract. */
522
+ routing?: "straight" | "curved" | "polyline";
523
+ /** Intermediate board-space vertices for a multi-point connector. */
524
+ waypoints?: BoardPoint[];
525
+ startBinding?: ConnectorBinding;
526
+ endBinding?: ConnectorBinding;
527
+ id: string;
528
+ start: BoardPoint;
529
+ end: BoardPoint;
530
+ head?: ArrowHeadStyle;
531
+ stroke?: string;
532
+ strokeWidth?: number;
533
+ opacity?: number;
534
+ }
535
+ /**
536
+ * Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
537
+ * type instead of five near-duplicate interfaces — a regular N-gon
538
+ * inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
539
+ * Rectangle already uses, parameterized by `sides`. Diamond is exactly a
540
+ * 4-sided regular polygon with vertex 0 pointing right (not up, like
541
+ * Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
542
+ * `polygonStartAngle`, which encodes each side count's own vertex
543
+ * orientation so the outline always matches the legacy drag-preview shape.
544
+ */
545
+ interface PolygonObject extends Lockable, Hideable {
546
+ id: string;
547
+ x: number;
548
+ y: number;
549
+ width: number;
550
+ height: number;
551
+ sides: 3 | 4 | 5 | 6 | 8;
552
+ fill?: string;
553
+ stroke?: string;
554
+ strokeWidth?: number;
555
+ opacity?: number;
556
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
557
+ rotation?: number;
558
+ }
559
+ declare function clonePolygon(polygon: PolygonObject): PolygonObject;
560
+ /** 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`). */
561
+ interface StarObject extends Lockable, Hideable {
562
+ id: string;
563
+ x: number;
564
+ y: number;
565
+ width: number;
566
+ height: number;
567
+ /** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
568
+ points: number;
569
+ /** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
570
+ innerRadiusRatio: number;
571
+ fill?: string;
572
+ stroke?: string;
573
+ strokeWidth?: number;
574
+ opacity?: number;
575
+ rotation?: number;
576
+ }
577
+ declare function cloneStar(star: StarObject): StarObject;
578
+ /** 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. */
579
+ interface HeartObject extends Lockable, Hideable {
580
+ id: string;
581
+ x: number;
582
+ y: number;
583
+ width: number;
584
+ height: number;
585
+ fill?: string;
586
+ stroke?: string;
587
+ strokeWidth?: number;
588
+ opacity?: number;
589
+ rotation?: number;
590
+ }
591
+ declare function cloneHeart(heart: HeartObject): HeartObject;
592
+ declare function cloneLine(line: LineObject): LineObject;
593
+ declare function cloneArrow(arrow: ArrowObject): ArrowObject;
594
+ /**
595
+ * A logical grouping of other board objects (Phase 3 — Selection,
596
+ * Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
597
+ * own — a group's bounds are always derived on demand from its (recursively
598
+ * resolved) children, and "moving/rotating/scaling the group" is exactly a
599
+ * multi-object transform applied to those children, nothing more. A group
600
+ * has no renderer/mesh of its own; its only visual presence is the
601
+ * selection gizmo's bounding box while it's the current selection.
602
+ *
603
+ * `children` may itself contain other group ids (nested groups) — expanding
604
+ * a group into its leaf members is always done by the caller (recursively,
605
+ * with cycle protection), never assumed here.
606
+ */
607
+ interface GroupObject extends Lockable, Hideable {
608
+ id: string;
609
+ children: string[];
610
+ }
611
+ declare function cloneGroup(group: GroupObject): GroupObject;
612
+
418
613
  interface BoardPoint {
419
614
  x: number;
420
615
  y: number;
@@ -428,9 +623,14 @@ interface StrokePoint extends BoardPoint {
428
623
  */
429
624
  erase?: number;
430
625
  }
431
- /** Which drawing tool made a stroke; undefined means marker (back-compat). */
432
- type StrokeTool = "marker" | "highlighter";
433
- interface Stroke extends Lockable {
626
+ /**
627
+ * Which drawing tool made a stroke; undefined means marker (back-compat).
628
+ * `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
629
+ * exact configured width with no pressure variance or end taper — a
630
+ * geometric outline, not an expressive ink mark.
631
+ */
632
+ type StrokeTool = "marker" | "highlighter" | "shape";
633
+ interface Stroke extends Lockable, Hideable {
434
634
  id: string;
435
635
  color: string;
436
636
  baseWidth: number;
@@ -449,7 +649,7 @@ interface Stroke extends Lockable {
449
649
  declare const ERASE_THRESHOLD = 0.95;
450
650
  declare function cloneStroke(stroke: Stroke): Stroke;
451
651
  type SerializedPoint = [number, number, number, number];
452
- interface SerializedStroke extends Lockable {
652
+ interface SerializedStroke extends Lockable, Hideable {
453
653
  id: string;
454
654
  color: string;
455
655
  baseWidth: number;
@@ -459,7 +659,36 @@ interface SerializedStroke extends Lockable {
459
659
  matrix?: [number, number, number, number, number, number];
460
660
  clusterId?: string;
461
661
  }
662
+ /** A named notebook containing sections. */
663
+ interface BoardNotebook {
664
+ readonly id: string;
665
+ readonly name: string;
666
+ readonly color: string;
667
+ }
668
+ /** A named section containing pages. */
669
+ interface BoardSection {
670
+ readonly id: string;
671
+ readonly notebookId: string;
672
+ readonly name: string;
673
+ readonly color: string;
674
+ }
675
+ /** An additional page. The first page remains in the document's top-level collections. */
676
+ interface SerializedBoardPage {
677
+ id: string;
678
+ sectionId?: string;
679
+ name: string;
680
+ content: SerializedDocument;
681
+ }
462
682
  interface SerializedDocument {
683
+ /** Notebook and section metadata. Older boards use an implicit default of each. */
684
+ notebooks?: BoardNotebook[];
685
+ sections?: BoardSection[];
686
+ /** Section of the original first page; defaults to `default`. */
687
+ pageSectionId?: string;
688
+ /** Display name of the first page (id `default`). */
689
+ pageName?: string;
690
+ /** Additional pages, in sidebar order. Nested pages are not allowed. */
691
+ pages?: SerializedBoardPage[];
463
692
  /** Absent in every historical document; current saves always write 1. */
464
693
  schemaVersion?: 1;
465
694
  strokes: SerializedStroke[];
@@ -475,6 +704,31 @@ interface SerializedDocument {
475
704
  timers?: KitchenTimer[];
476
705
  /** Absent in documents saved before Custom board objects existed (ticket #22). */
477
706
  customObjects?: CustomBoardObject[];
707
+ /** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
708
+ rectangles?: RectangleObject[];
709
+ /** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
710
+ ellipses?: EllipseObject[];
711
+ /** Absent in documents saved before Groups existed (Phase 3). */
712
+ groups?: GroupObject[];
713
+ /** Absent in documents saved before semantic Line objects existed (Phase 4). */
714
+ lines?: LineObject[];
715
+ /** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
716
+ arrows?: ArrowObject[];
717
+ /** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
718
+ polygons?: PolygonObject[];
719
+ /** Absent in documents saved before semantic Star objects existed (Phase 4). */
720
+ stars?: StarObject[];
721
+ /** Absent in documents saved before semantic Heart objects existed (Phase 4). */
722
+ hearts?: HeartObject[];
723
+ /**
724
+ * Every content-object id (every type above except comments, which are
725
+ * host-synced and never enter this schema) in paint order, back to front.
726
+ * Absent in documents saved before per-object z-order existed (Phase 3) —
727
+ * migration synthesizes a default order preserving the old fixed-Z-band
728
+ * visual stacking exactly, so an existing document never visibly changes
729
+ * on load; only an explicit reorder action touches this from then on.
730
+ */
731
+ objectOrder?: string[];
478
732
  }
479
733
  declare const INK_COLORS: {
480
734
  readonly black: "#1C1C1E";
@@ -483,16 +737,28 @@ declare const INK_COLORS: {
483
737
  readonly green: "#16A34A";
484
738
  };
485
739
  declare const HIGHLIGHT_COLORS: {
740
+ readonly yellow: "#FACC15";
741
+ readonly green: "#4ADE80";
742
+ readonly pink: "#F472B6";
743
+ readonly blue: "#60A5FA";
744
+ };
745
+ declare const NOTE_COLORS: {
486
746
  readonly yellow: "#FDE047";
487
- readonly green: "#86EFAC";
747
+ readonly amber: "#FACC15";
748
+ readonly orange: "#FB923C";
749
+ readonly peach: "#FDBA74";
750
+ readonly red: "#FB7185";
751
+ readonly rose: "#FDA4AF";
488
752
  readonly pink: "#F9A8D4";
753
+ readonly magenta: "#F472B6";
754
+ readonly purple: "#C084FC";
755
+ readonly lavender: "#C4B5FD";
489
756
  readonly blue: "#93C5FD";
490
- };
491
- declare const NOTE_COLORS: {
492
- readonly yellow: "#FDE68A";
493
- readonly pink: "#FBCFE8";
494
- readonly blue: "#BFDBFE";
495
- readonly green: "#BBF7D0";
757
+ readonly cornflower: "#60A5FA";
758
+ readonly sky: "#38BDF8";
759
+ readonly cyan: "#67E8F9";
760
+ readonly green: "#86EFAC";
761
+ readonly mint: "#4ADE80";
496
762
  };
497
763
  /**
498
764
  * One collaborator's vote on a note. One per person; toggling removes it.
@@ -506,7 +772,7 @@ interface NoteVote {
506
772
  * A sticky note: content floating above the board at a z-offset (pillar 3 —
507
773
  * depth as an organizational axis). Center position in board space.
508
774
  */
509
- interface StickyNote extends Lockable {
775
+ interface StickyNote extends Lockable, Hideable {
510
776
  id: string;
511
777
  x: number;
512
778
  y: number;
@@ -524,7 +790,7 @@ interface StickyNote extends Lockable {
524
790
  * top-left corner; lines flow downward (-y). Text joins the clustering
525
791
  * system like handwriting (build prompt §6.4).
526
792
  */
527
- interface TextBlock extends Lockable {
793
+ interface TextBlock extends Lockable, Hideable {
528
794
  id: string;
529
795
  x: number;
530
796
  y: number;
@@ -554,7 +820,7 @@ declare function cloneNote(note: StickyNote): StickyNote;
554
820
  * Interactive structured table on the board. Position (x, y) is top-left in board units.
555
821
  * Cells are indexed as `${row},${col}` keys mapping to cell text content.
556
822
  */
557
- interface TableBlock extends Lockable {
823
+ interface TableBlock extends Lockable, Hideable {
558
824
  id: string;
559
825
  x: number;
560
826
  y: number;
@@ -580,7 +846,7 @@ declare const FOG_COLOR = "#FFFFFF";
580
846
  * An imported image block on the board plane.
581
847
  * Coordinates (x, y) represent the center of the image in board space.
582
848
  */
583
- interface ImageBlock extends Lockable {
849
+ interface ImageBlock extends Lockable, Hideable {
584
850
  id: string;
585
851
  /**
586
852
  * A legacy, read-only data URL (or, historically, an arbitrary string) —
@@ -604,13 +870,29 @@ interface ImageBlock extends Lockable {
604
870
  }
605
871
  declare function cloneImage(img: ImageBlock): ImageBlock;
606
872
 
873
+ declare const documentIdBrand: unique symbol;
874
+ declare const strokeIdBrand: unique symbol;
875
+ type DocumentId = string & {
876
+ readonly [documentIdBrand]: "DocumentId";
877
+ };
878
+ type StrokeId = string & {
879
+ readonly [strokeIdBrand]: "StrokeId";
880
+ };
881
+ declare function documentId(value: string): DocumentId;
882
+ declare function strokeId(value: string): StrokeId;
883
+
607
884
  declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
608
885
  type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
609
886
  id: StrokeId;
610
887
  };
611
- interface CurrentSerializedDocument extends Required<SerializedDocument> {
888
+ interface CurrentSerializedDocument extends Required<Omit<SerializedDocument, "pages" | "pageName" | "notebooks" | "sections" | "pageSectionId">> {
612
889
  schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
613
890
  strokes: CurrentSerializedStroke[];
891
+ pageName?: string;
892
+ notebooks?: BoardNotebook[];
893
+ sections?: BoardSection[];
894
+ pageSectionId?: string;
895
+ pages?: SerializedBoardPage[];
614
896
  }
615
897
  type DocumentRecoveryCode = "DOCUMENT_JSON_INVALID" | "DOCUMENT_VALIDATION_FAILED" | "DOCUMENT_VERSION_UNSUPPORTED";
616
898
  declare class DocumentRecoveryError extends Error {
@@ -630,6 +912,15 @@ type DocumentLoadResult = {
630
912
  declare function loadDocumentBytes(originalBytes: string): DocumentLoadResult;
631
913
  declare function migrateDocument(raw: unknown): DocumentLoadResult;
632
914
  declare function serializeDocument(document: unknown): string;
915
+ /**
916
+ * A Document's serialized size in bytes (Phase 5) — UTF-8, not UTF-16
917
+ * `string.length`, since a Document with non-ASCII note/text content (most
918
+ * of them, eventually) would otherwise under-report. Useful for a Host
919
+ * deciding when to warn about an unusually large board, or for logging/
920
+ * telemetry around save size — not consulted by anything inside this
921
+ * package itself, which has no size limit of its own.
922
+ */
923
+ declare function documentSize(document: CurrentSerializedDocument): number;
633
924
 
634
925
  interface SearchableComment {
635
926
  id: string;
@@ -664,6 +955,18 @@ interface SearchableBoard {
664
955
  */
665
956
  declare function searchBoard(query: string, board: SearchableBoard): SearchHit[];
666
957
 
958
+ interface ConnectorBox {
959
+ minX: number;
960
+ minY: number;
961
+ maxX: number;
962
+ maxY: number;
963
+ }
964
+ /** A bindable object's footprint: its bounds and whether its outline is the box or the ellipse inside it. */
965
+ interface ConnectorTarget {
966
+ box: ConnectorBox;
967
+ outline: "box" | "ellipse";
968
+ }
969
+
667
970
  /**
668
971
  * The one place a live Stroke becomes its wire representation — in
669
972
  * particular, points collapse from {x,y,pressure,erase?} objects into
@@ -714,6 +1017,60 @@ interface DocumentChange {
714
1017
  /** Ids of removed custom objects. */
715
1018
  customObjectsRemoved: string[];
716
1019
  customObjectsUpdated: CustomBoardObject[];
1020
+ /** Semantic Rectangle objects (Phase 2). */
1021
+ rectanglesAdded: RectangleObject[];
1022
+ /** Ids of removed rectangles. */
1023
+ rectanglesRemoved: string[];
1024
+ rectanglesUpdated: RectangleObject[];
1025
+ /** Semantic Ellipse objects (Phase 2). */
1026
+ ellipsesAdded: EllipseObject[];
1027
+ /** Ids of removed ellipses. */
1028
+ ellipsesRemoved: string[];
1029
+ ellipsesUpdated: EllipseObject[];
1030
+ /** Groups (Phase 3). */
1031
+ groupsAdded: GroupObject[];
1032
+ /** Ids of removed groups (ungrouping, or deleting a group). */
1033
+ groupsRemoved: string[];
1034
+ groupsUpdated: GroupObject[];
1035
+ /** Semantic Line objects (Phase 4). */
1036
+ linesAdded: LineObject[];
1037
+ /** Ids of removed lines. */
1038
+ linesRemoved: string[];
1039
+ linesUpdated: LineObject[];
1040
+ /** Semantic Arrow objects (Phase 4). */
1041
+ arrowsAdded: ArrowObject[];
1042
+ /** Ids of removed arrows. */
1043
+ arrowsRemoved: string[];
1044
+ arrowsUpdated: ArrowObject[];
1045
+ /** Semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
1046
+ polygonsAdded: PolygonObject[];
1047
+ /** Ids of removed polygons. */
1048
+ polygonsRemoved: string[];
1049
+ polygonsUpdated: PolygonObject[];
1050
+ /** Semantic Star objects (Phase 4). */
1051
+ starsAdded: StarObject[];
1052
+ /** Ids of removed stars. */
1053
+ starsRemoved: string[];
1054
+ starsUpdated: StarObject[];
1055
+ /** Semantic Heart objects (Phase 4). */
1056
+ heartsAdded: HeartObject[];
1057
+ /** Ids of removed hearts. */
1058
+ heartsRemoved: string[];
1059
+ heartsUpdated: HeartObject[];
1060
+ /**
1061
+ * The full current paint order (back to front) of every flat content
1062
+ * object — strokes, texts, tables, images, rectangles, ellipses, lines,
1063
+ * arrows, custom objects, and groups. Populated whenever `objectOrder`
1064
+ * actually changed:
1065
+ * an explicit reorder (`bringForward` etc.), or any add/remove that
1066
+ * touches it — a removal shifts every id after it down one rank, not
1067
+ * just the removed one, so renderers need this to resync everyone, not
1068
+ * only the ids the same change's own `*Added`/`*Removed`/`*Updated`
1069
+ * fields name. Notes (their own `zOffset` peel depth) and Kitchen Timers
1070
+ * (genuine 3D objects, not a flat layer) are intentionally not part of
1071
+ * this order at all.
1072
+ */
1073
+ orderChanged: readonly string[];
717
1074
  }
718
1075
  type Listener = (change: DocumentChange) => void;
719
1076
  declare class BoardDocument {
@@ -728,12 +1085,78 @@ declare class BoardDocument {
728
1085
  private readonly timers;
729
1086
  /** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
730
1087
  private readonly customObjects;
1088
+ private readonly rectangles;
1089
+ private readonly ellipses;
1090
+ private readonly groups;
1091
+ private readonly lines;
1092
+ private readonly arrows;
1093
+ private readonly polygons;
1094
+ private readonly stars;
1095
+ private readonly hearts;
731
1096
  private readonly bboxes;
732
1097
  private readonly listeners;
1098
+ /** Paint order (back to front) of every flat content object — see `DocumentChange.orderChanged`'s doc comment. */
1099
+ private objectOrder;
1100
+ private orderIndex;
733
1101
  constructor(id: DocumentId);
1102
+ private reindexOrder;
1103
+ /** The full current paint order, back to front. */
1104
+ order(): readonly string[];
1105
+ /** This object's rank in the paint order, or -1 if it doesn't participate (unknown id, a note, or a timer). */
1106
+ orderRank(id: string): number;
1107
+ private bringForward;
1108
+ private sendBackward;
1109
+ private bringToFront;
1110
+ private sendToBack;
1111
+ /** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
1112
+ reorder(id: string, direction: "forward" | "backward" | "front" | "back"): void;
1113
+ /**
1114
+ * Overwrites the paint order directly — used only when loading a document
1115
+ * that already carries a persisted `objectOrder`; every other order
1116
+ * mutation goes through `reorder`/the automatic append-on-add tracking in
1117
+ * `emit`. Ids not present in the document are dropped; ids present in the
1118
+ * document but missing from `order` are appended at the back, so a
1119
+ * partially-stale order (e.g. from a schema migration) never silently
1120
+ * drops an object from paint order entirely.
1121
+ */
1122
+ private setOrder;
734
1123
  get(id: string): Stroke | undefined;
735
1124
  all(): IterableIterator<Stroke>;
736
- bbox(id: string): BBox | undefined;
1125
+ /**
1126
+ * World-space bounds for any content object, of any type. Strokes hit
1127
+ * their cached-on-mutation fast path (`bboxes`, populated by
1128
+ * `addStrokes`/`transformStrokes` — many points, worth caching); every
1129
+ * other type computes on demand via `objectBounds.ts` (cheap arithmetic,
1130
+ * no caching needed). A group's bounds are the union of its (recursively
1131
+ * resolved) children — `seen` guards against a cycle in nested groups.
1132
+ */
1133
+ /**
1134
+ * What a connector can attach to at `id`: its bounds and outline shape.
1135
+ * Connectors, lines and groups aren't targets (that would allow cycles).
1136
+ */
1137
+ connectorTarget(id: string): ConnectorTarget | undefined;
1138
+ bbox(id: string, seen?: Set<string>): BBox | undefined;
1139
+ /**
1140
+ * True if `id` exists and is locked, for any type — the same per-type
1141
+ * probe pattern as `bbox`, for interactive gestures (drag/transform) that
1142
+ * need to gate on lock state regardless of what's selected. Custom
1143
+ * objects are deliberately excluded: their `lock` field is a different
1144
+ * shape (`{holderId, acquiredAt}`, no display name) with no interactive
1145
+ * lock UI yet, matching the existing, deliberate "always unlockable,
1146
+ * never gates a drag" treatment already established elsewhere (e.g.
1147
+ * `getSelectedItemInfo`'s custom branch hardcodes `isLocked: false`).
1148
+ */
1149
+ isLocked(id: string): boolean;
1150
+ /**
1151
+ * True if `id` exists and is hidden, for any type — the same per-type
1152
+ * probe pattern as {@link isLocked} (Phase 8). Custom objects are
1153
+ * excluded for the same reason `isLocked` excludes them: they have no
1154
+ * `Hideable` field at all, so "hidden" isn't a concept that applies to
1155
+ * them yet. Used by marquee selection (`selectTool.ts`) to keep a hidden
1156
+ * object out of a rubber-band selection even for object types whose own
1157
+ * renderer doesn't yet suppress click-based hit-testing.
1158
+ */
1159
+ isHidden(id: string): boolean;
737
1160
  subscribe(listener: Listener): () => void;
738
1161
  addStrokes(strokes: Stroke[]): void;
739
1162
  removeStrokes(ids: string[]): void;
@@ -776,6 +1199,54 @@ declare class BoardDocument {
776
1199
  removeCustomObjects(ids: string[]): void;
777
1200
  /** Replace a custom object's contents under the same id. */
778
1201
  setCustomObject(object: CustomBoardObject): void;
1202
+ getRectangle(id: string): RectangleObject | undefined;
1203
+ allRectangles(): IterableIterator<RectangleObject>;
1204
+ addRectangles(rectangles: RectangleObject[]): void;
1205
+ removeRectangles(ids: string[]): void;
1206
+ /** Replace a rectangle's contents (move, resize, restyle) under the same id. */
1207
+ setRectangle(rect: RectangleObject): void;
1208
+ getEllipse(id: string): EllipseObject | undefined;
1209
+ allEllipses(): IterableIterator<EllipseObject>;
1210
+ addEllipses(ellipses: EllipseObject[]): void;
1211
+ removeEllipses(ids: string[]): void;
1212
+ /** Replace an ellipse's contents (move, resize, restyle) under the same id. */
1213
+ setEllipse(ellipse: EllipseObject): void;
1214
+ getGroup(id: string): GroupObject | undefined;
1215
+ allGroups(): IterableIterator<GroupObject>;
1216
+ addGroups(groups: GroupObject[]): void;
1217
+ removeGroups(ids: string[]): void;
1218
+ /** Replace a group's contents (its children list) under the same id. */
1219
+ setGroup(group: GroupObject): void;
1220
+ getLine(id: string): LineObject | undefined;
1221
+ allLines(): IterableIterator<LineObject>;
1222
+ addLines(lines: LineObject[]): void;
1223
+ removeLines(ids: string[]): void;
1224
+ /** Replace a line's contents (move, restyle) under the same id. */
1225
+ setLine(line: LineObject): void;
1226
+ getArrow(id: string): ArrowObject | undefined;
1227
+ allArrows(): IterableIterator<ArrowObject>;
1228
+ addArrows(arrows: ArrowObject[]): void;
1229
+ removeArrows(ids: string[]): void;
1230
+ /** Replace an arrow's contents (move, restyle, change head) under the same id. */
1231
+ setArrow(arrow: ArrowObject): void;
1232
+ getPolygon(id: string): PolygonObject | undefined;
1233
+ allPolygons(): IterableIterator<PolygonObject>;
1234
+ addPolygons(polygons: PolygonObject[]): void;
1235
+ removePolygons(ids: string[]): void;
1236
+ /** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
1237
+ setPolygon(polygon: PolygonObject): void;
1238
+ getStar(id: string): StarObject | undefined;
1239
+ allStars(): IterableIterator<StarObject>;
1240
+ addStars(stars: StarObject[]): void;
1241
+ removeStars(ids: string[]): void;
1242
+ /** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
1243
+ setStar(star: StarObject): void;
1244
+ getHeart(id: string): HeartObject | undefined;
1245
+ allHearts(): IterableIterator<HeartObject>;
1246
+ addHearts(hearts: HeartObject[]): void;
1247
+ removeHearts(ids: string[]): void;
1248
+ /** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
1249
+ setHeart(heart: HeartObject): void;
779
1250
  setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
780
1251
  setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
781
1252
  setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
@@ -783,18 +1254,69 @@ declare class BoardDocument {
783
1254
  setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
784
1255
  setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
785
1256
  setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1257
+ setRectangleLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1258
+ setEllipseLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1259
+ setGroupLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1260
+ setLineLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1261
+ setArrowLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1262
+ setPolygonLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1263
+ setStarLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1264
+ setHeartLocked(id: string, locked: boolean, by?: LockHolder | null): void;
786
1265
  /** Replace all content (initial load). Does not touch `version`. */
787
- replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]): void;
1266
+ 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[],
1267
+ /** Persisted paint order; absent for a document saved before Phase 3, in which case one is synthesized (see `ORDERED_ADDED_FIELDS`'s doc comment). */
1268
+ objectOrder?: readonly string[]): void;
788
1269
  /** Apply incremental real-time change received from a remote collaborator over WebSocket. */
789
1270
  applyRemoteChange(change: Partial<DocumentChange>): void;
790
1271
  toJSON(): SerializedDocument;
1272
+ static deserializeGroups(data: SerializedDocument): GroupObject[];
1273
+ static deserializeLines(data: SerializedDocument): LineObject[];
1274
+ static deserializeArrows(data: SerializedDocument): ArrowObject[];
1275
+ static deserializePolygons(data: SerializedDocument): PolygonObject[];
1276
+ static deserializeStars(data: SerializedDocument): StarObject[];
1277
+ static deserializeHearts(data: SerializedDocument): HeartObject[];
791
1278
  static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
1279
+ static deserializeRectangles(data: SerializedDocument): RectangleObject[];
1280
+ static deserializeEllipses(data: SerializedDocument): EllipseObject[];
792
1281
  static deserializeImages(data: SerializedDocument): ImageBlock[];
793
1282
  static deserializeTimers(data: SerializedDocument): KitchenTimer[];
794
1283
  static deserializeNotes(data: SerializedDocument): StickyNote[];
795
1284
  static deserializeTexts(data: SerializedDocument): TextBlock[];
796
1285
  static deserializeTables(data: SerializedDocument): TableBlock[];
797
- static deserializeStrokes(data: SerializedDocument): Stroke[];
1286
+ /**
1287
+ * `onSkip` (Phase 9) replaces an unconditional `console.error` — `core`
1288
+ * must never do raw console I/O (no dev-gate, no way for a Host to
1289
+ * suppress or redirect it), so a skipped stroke is now reported only if
1290
+ * the caller asks for it, via whatever diagnostic channel it already
1291
+ * has (e.g. `controller-internal.ts` routes this into the same typed
1292
+ * `"error"` event every other diagnostic already uses). Silent by
1293
+ * default, matching how every other `deserialize*` method here already
1294
+ * behaves (no diagnostics at all).
1295
+ */
1296
+ static deserializeStrokes(data: SerializedDocument, onSkip?: (id: string, cause: unknown) => void): Stroke[];
1297
+ /**
1298
+ * Every mutation funnels through here, so paint-order tracking lives in
1299
+ * exactly one place rather than at every individual add/remove call site
1300
+ * (18 of them, times `replaceAll`/`applyRemoteChange`) — new ids are
1301
+ * appended to the back (front-most) of `objectOrder`, removed ids are
1302
+ * spliced out. An explicit reorder (`reorder`/`setOrder`) updates
1303
+ * `objectOrder` itself before calling this, so this step is a no-op for
1304
+ * ids already tracked (idempotent by construction: `orderIndex.has` gates
1305
+ * every append).
1306
+ *
1307
+ * `orderChanged` (Phase 9) reports exactly the ids whose rank actually
1308
+ * changed, using `orderIndex` throughout instead of `indexOf` — a pure
1309
+ * append never shifts any existing id's rank (new ids land at the tail,
1310
+ * already covered by this same change's own `Added` field, so
1311
+ * `orderChanged` stays unset), while a removal shifts every id at-or-
1312
+ * after the lowest removed rank down by one, computed in a single O(n)
1313
+ * filter pass (not one `indexOf`+`splice` per removed id) regardless of
1314
+ * how many ids this one change removes. Every renderer's `onChange` now
1315
+ * looks up only the ids actually in `orderChanged` instead of walking
1316
+ * its entire mesh map on any order-touching change — a broad, unfiltered
1317
+ * `orderChanged` here would silently defeat that fix, not just waste
1318
+ * cycles here.
1319
+ */
798
1320
  private emit;
799
1321
  }
800
1322
 
@@ -830,12 +1352,33 @@ interface Command {
830
1352
  apply(doc: BoardDocument): void;
831
1353
  revert(doc: BoardDocument): void;
832
1354
  }
1355
+ /**
1356
+ * Several commands applied/reverted together as one undo step (Phase 3
1357
+ * consolidation — this exact class used to be hand-duplicated as a private
1358
+ * `CommandBatch` in `controller-internal.ts` and an exported
1359
+ * `ExtensionCommandBatch` in `interaction/tools/customTool.ts`; both now
1360
+ * import this one instead). Revert runs in reverse order, so a batch that
1361
+ * depends on ordering (e.g. add-then-reference) undoes cleanly.
1362
+ */
1363
+ declare class CommandBatch implements Command {
1364
+ readonly label: string;
1365
+ private readonly commands;
1366
+ constructor(label: string, commands: readonly Command[]);
1367
+ apply(doc: BoardDocument): void;
1368
+ revert(doc: BoardDocument): void;
1369
+ }
833
1370
  /** How a command reached the document — undo/redo are audited distinctly. */
834
1371
  type CommandKind = "do" | "undo" | "redo";
835
1372
  declare class History {
836
1373
  private readonly doc;
837
1374
  private readonly undoStack;
838
1375
  private readonly redoStack;
1376
+ private scope;
1377
+ private readonly scopes;
1378
+ /** Switch independent page histories while retaining the same document instance. */
1379
+ setScope(id: string): void;
1380
+ /** Discard all page histories after replacing a saved document. */
1381
+ clearScopes(): void;
839
1382
  /**
840
1383
  * Observer for every mutation, in one place: all board edits funnel
841
1384
  * through record/undo/redo. The audit trail listens here.
@@ -894,6 +1437,40 @@ declare class TransformCommand implements Command {
894
1437
  revert(doc: BoardDocument): void;
895
1438
  private compose;
896
1439
  }
1440
+ /**
1441
+ * One move gesture over a mixed-type selection (Phase 3) — the general
1442
+ * successor to `TransformCommand` for translation. `TransformCommand`
1443
+ * itself stays as-is (still used for scale/rotate, which remain stroke-only
1444
+ * this phase — see `interaction/tools/selectTool.ts`'s `TransformState`):
1445
+ * its own per-id `doc.get(id)` check already no-ops safely for any id that
1446
+ * isn't a stroke, so it doesn't need touching for that narrower case.
1447
+ *
1448
+ * `delta` here is always a pure translation (never scale/rotate), so
1449
+ * `apply(delta, point)` is a safe, uniform way to move every position-only
1450
+ * type's `x`/`y` — translating commutes trivially regardless of a shape's
1451
+ * own rotation. Matrix-carrying types (stroke, Custom) instead compose
1452
+ * `delta` onto their existing matrix/transform, matching `TransformCommand`.
1453
+ *
1454
+ * A group id in `ids` is expanded into its (recursively resolved, cycle-
1455
+ * safe) children every time `compose` runs — deterministic, since group
1456
+ * membership never changes mid-command — so moving a selected group means
1457
+ * moving every one of its members by the same delta. This expansion is
1458
+ * `TransformObjectsCommand`'s own responsibility precisely so a caller that
1459
+ * doesn't itself expand groups (e.g. `ScrawlEngine.nudgeSelection`) still
1460
+ * gets correct behavior; `interaction/tools/selectTool.ts`'s `TransformState`
1461
+ * separately expands for its own reason (live per-child drag preview), which
1462
+ * makes this a no-op re-expansion for that caller, not a conflict.
1463
+ */
1464
+ declare class TransformObjectsCommand implements Command {
1465
+ private readonly ids;
1466
+ private readonly delta;
1467
+ readonly label = "move selection";
1468
+ private readonly inverse;
1469
+ constructor(ids: readonly string[], delta: Mat2x3);
1470
+ apply(doc: BoardDocument): void;
1471
+ revert(doc: BoardDocument): void;
1472
+ private compose;
1473
+ }
897
1474
  declare class AddNoteCommand implements Command {
898
1475
  readonly label = "add note";
899
1476
  private readonly note;
@@ -1015,8 +1592,213 @@ declare class DeleteTimerCommand implements Command {
1015
1592
  apply(doc: BoardDocument): void;
1016
1593
  revert(doc: BoardDocument): void;
1017
1594
  }
1595
+ /** Add/update/delete for semantic Rectangle objects (Phase 2). */
1596
+ declare class AddRectangleCommand implements Command {
1597
+ readonly label = "add rectangle";
1598
+ private readonly rect;
1599
+ constructor(rect: RectangleObject);
1600
+ apply(doc: BoardDocument): void;
1601
+ revert(doc: BoardDocument): void;
1602
+ }
1603
+ declare class UpdateRectangleCommand implements Command {
1604
+ readonly label = "update rectangle";
1605
+ private readonly before;
1606
+ private readonly after;
1607
+ constructor(before: RectangleObject, after: RectangleObject);
1608
+ apply(doc: BoardDocument): void;
1609
+ revert(doc: BoardDocument): void;
1610
+ }
1611
+ declare class DeleteRectangleCommand implements Command {
1612
+ readonly label = "delete rectangle";
1613
+ private readonly rect;
1614
+ constructor(rect: RectangleObject);
1615
+ apply(doc: BoardDocument): void;
1616
+ revert(doc: BoardDocument): void;
1617
+ }
1618
+ /** Add/update/delete for semantic Ellipse objects (Phase 2). */
1619
+ declare class AddEllipseCommand implements Command {
1620
+ readonly label = "add ellipse";
1621
+ private readonly ellipse;
1622
+ constructor(ellipse: EllipseObject);
1623
+ apply(doc: BoardDocument): void;
1624
+ revert(doc: BoardDocument): void;
1625
+ }
1626
+ declare class UpdateEllipseCommand implements Command {
1627
+ readonly label = "update ellipse";
1628
+ private readonly before;
1629
+ private readonly after;
1630
+ constructor(before: EllipseObject, after: EllipseObject);
1631
+ apply(doc: BoardDocument): void;
1632
+ revert(doc: BoardDocument): void;
1633
+ }
1634
+ declare class DeleteEllipseCommand implements Command {
1635
+ readonly label = "delete ellipse";
1636
+ private readonly ellipse;
1637
+ constructor(ellipse: EllipseObject);
1638
+ apply(doc: BoardDocument): void;
1639
+ revert(doc: BoardDocument): void;
1640
+ }
1641
+ /** Add/update/delete for semantic Line objects (Phase 4). */
1642
+ declare class AddLineCommand implements Command {
1643
+ readonly label = "add line";
1644
+ private readonly line;
1645
+ constructor(line: LineObject);
1646
+ apply(doc: BoardDocument): void;
1647
+ revert(doc: BoardDocument): void;
1648
+ }
1649
+ declare class UpdateLineCommand implements Command {
1650
+ readonly label = "update line";
1651
+ private readonly before;
1652
+ private readonly after;
1653
+ constructor(before: LineObject, after: LineObject);
1654
+ apply(doc: BoardDocument): void;
1655
+ revert(doc: BoardDocument): void;
1656
+ }
1657
+ declare class DeleteLineCommand implements Command {
1658
+ readonly label = "delete line";
1659
+ private readonly line;
1660
+ constructor(line: LineObject);
1661
+ apply(doc: BoardDocument): void;
1662
+ revert(doc: BoardDocument): void;
1663
+ }
1664
+ /** Add/update/delete for semantic Arrow objects (Phase 4). */
1665
+ declare class AddArrowCommand implements Command {
1666
+ readonly label = "add arrow";
1667
+ private readonly arrow;
1668
+ constructor(arrow: ArrowObject);
1669
+ apply(doc: BoardDocument): void;
1670
+ revert(doc: BoardDocument): void;
1671
+ }
1672
+ declare class UpdateArrowCommand implements Command {
1673
+ readonly label = "update arrow";
1674
+ private readonly before;
1675
+ private readonly after;
1676
+ constructor(before: ArrowObject, after: ArrowObject);
1677
+ apply(doc: BoardDocument): void;
1678
+ revert(doc: BoardDocument): void;
1679
+ }
1680
+ declare class DeleteArrowCommand implements Command {
1681
+ readonly label = "delete arrow";
1682
+ private readonly arrow;
1683
+ constructor(arrow: ArrowObject);
1684
+ apply(doc: BoardDocument): void;
1685
+ revert(doc: BoardDocument): void;
1686
+ }
1687
+ /** Add/update/delete for semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
1688
+ declare class AddPolygonCommand implements Command {
1689
+ readonly label = "add polygon";
1690
+ private readonly polygon;
1691
+ constructor(polygon: PolygonObject);
1692
+ apply(doc: BoardDocument): void;
1693
+ revert(doc: BoardDocument): void;
1694
+ }
1695
+ declare class UpdatePolygonCommand implements Command {
1696
+ readonly label = "update polygon";
1697
+ private readonly before;
1698
+ private readonly after;
1699
+ constructor(before: PolygonObject, after: PolygonObject);
1700
+ apply(doc: BoardDocument): void;
1701
+ revert(doc: BoardDocument): void;
1702
+ }
1703
+ declare class DeletePolygonCommand implements Command {
1704
+ readonly label = "delete polygon";
1705
+ private readonly polygon;
1706
+ constructor(polygon: PolygonObject);
1707
+ apply(doc: BoardDocument): void;
1708
+ revert(doc: BoardDocument): void;
1709
+ }
1710
+ /** Add/update/delete for semantic Star objects (Phase 4). */
1711
+ declare class AddStarCommand implements Command {
1712
+ readonly label = "add star";
1713
+ private readonly star;
1714
+ constructor(star: StarObject);
1715
+ apply(doc: BoardDocument): void;
1716
+ revert(doc: BoardDocument): void;
1717
+ }
1718
+ declare class UpdateStarCommand implements Command {
1719
+ readonly label = "update star";
1720
+ private readonly before;
1721
+ private readonly after;
1722
+ constructor(before: StarObject, after: StarObject);
1723
+ apply(doc: BoardDocument): void;
1724
+ revert(doc: BoardDocument): void;
1725
+ }
1726
+ declare class DeleteStarCommand implements Command {
1727
+ readonly label = "delete star";
1728
+ private readonly star;
1729
+ constructor(star: StarObject);
1730
+ apply(doc: BoardDocument): void;
1731
+ revert(doc: BoardDocument): void;
1732
+ }
1733
+ /** Add/update/delete for semantic Heart objects (Phase 4). */
1734
+ declare class AddHeartCommand implements Command {
1735
+ readonly label = "add heart";
1736
+ private readonly heart;
1737
+ constructor(heart: HeartObject);
1738
+ apply(doc: BoardDocument): void;
1739
+ revert(doc: BoardDocument): void;
1740
+ }
1741
+ declare class UpdateHeartCommand implements Command {
1742
+ readonly label = "update heart";
1743
+ private readonly before;
1744
+ private readonly after;
1745
+ constructor(before: HeartObject, after: HeartObject);
1746
+ apply(doc: BoardDocument): void;
1747
+ revert(doc: BoardDocument): void;
1748
+ }
1749
+ declare class DeleteHeartCommand implements Command {
1750
+ readonly label = "delete heart";
1751
+ private readonly heart;
1752
+ constructor(heart: HeartObject);
1753
+ apply(doc: BoardDocument): void;
1754
+ revert(doc: BoardDocument): void;
1755
+ }
1756
+ /** Add/update/delete for Groups (Phase 3). */
1757
+ declare class AddGroupCommand implements Command {
1758
+ readonly label = "group";
1759
+ private readonly group;
1760
+ constructor(group: GroupObject);
1761
+ apply(doc: BoardDocument): void;
1762
+ revert(doc: BoardDocument): void;
1763
+ }
1764
+ declare class UpdateGroupCommand implements Command {
1765
+ readonly label = "update group";
1766
+ private readonly before;
1767
+ private readonly after;
1768
+ constructor(before: GroupObject, after: GroupObject);
1769
+ apply(doc: BoardDocument): void;
1770
+ revert(doc: BoardDocument): void;
1771
+ }
1772
+ declare class DeleteGroupCommand implements Command {
1773
+ readonly label = "ungroup";
1774
+ private readonly group;
1775
+ constructor(group: GroupObject);
1776
+ apply(doc: BoardDocument): void;
1777
+ revert(doc: BoardDocument): void;
1778
+ }
1779
+ type ReorderDirection = "forward" | "backward" | "front" | "back";
1780
+ /**
1781
+ * Bring-forward / send-backward / bring-to-front / send-to-back (Phase 3) —
1782
+ * one command family covering all four directions rather than four
1783
+ * near-identical classes, since the only difference between them is which
1784
+ * `BoardDocument.reorder` direction to replay. Captures the full paint order
1785
+ * on first `apply` rather than in the constructor — `BoardDocument.order()`
1786
+ * needs the doc, which a `Command` only ever receives via `apply`/`revert` —
1787
+ * so `revert` can restore it exactly; redo re-runs the same `reorder` call,
1788
+ * which is deterministic because `revert` always restores the identical
1789
+ * starting order first.
1790
+ */
1791
+ declare class ReorderObjectCommand implements Command {
1792
+ private readonly id;
1793
+ private readonly direction;
1794
+ readonly label: string;
1795
+ private before;
1796
+ constructor(id: string, direction: ReorderDirection);
1797
+ apply(doc: BoardDocument): void;
1798
+ revert(doc: BoardDocument): void;
1799
+ }
1018
1800
  interface LockTarget {
1019
- type: "stroke" | "note" | "text" | "table" | "image" | "timer";
1801
+ type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart";
1020
1802
  id: string;
1021
1803
  locked: boolean;
1022
1804
  lockedBy?: string;
@@ -1033,7 +1815,7 @@ declare class LockItemsCommand implements Command {
1033
1815
  private applyLock;
1034
1816
  }
1035
1817
 
1036
- type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
1818
+ type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "rectangles" | "ellipses" | "groups" | "lines" | "arrows" | "polygons" | "stars" | "hearts" | "customObjects";
1037
1819
  type Op = {
1038
1820
  kind: "upsert";
1039
1821
  collection: OpCollection;
@@ -1064,15 +1846,23 @@ interface RibbonEdgePoint {
1064
1846
  /** 1 = intact ink, 0 = fully erased (from the erasure channel). */
1065
1847
  alpha: number;
1066
1848
  }
1067
- declare function ribbonEdges(points: StrokePoint[], baseWidth: number): RibbonEdgePoint[];
1849
+ /**
1850
+ * @param handDrawn Default `true`: ink's organic feel — width follows
1851
+ * per-point pressure, and both ends taper. Pass `false` for a geometric
1852
+ * shape outline, which has no real pressure signal and isn't a pen stroke
1853
+ * with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
1854
+ * taper at its start/end (which, for a closed shape, is the same point —
1855
+ * tapering it would visibly pinch just that one corner).
1856
+ */
1857
+ declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn?: boolean): RibbonEdgePoint[];
1068
1858
 
1069
1859
  declare class SpatialIndex {
1070
1860
  private readonly doc;
1071
1861
  private readonly cells;
1072
- private readonly strokeCells;
1862
+ private readonly objectCells;
1073
1863
  private readonly unsubscribe;
1074
1864
  constructor(doc: BoardDocument);
1075
- /** Ids of strokes whose bbox may overlap the query rect. */
1865
+ /** Ids of content objects (any type except groups) whose bbox may overlap the query rect. */
1076
1866
  query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
1077
1867
  dispose(): void;
1078
1868
  private insert;
@@ -1121,6 +1911,539 @@ declare function placePresenceBeacon(screen: {
1121
1911
  height: number;
1122
1912
  }, inset?: ViewportInset): PresencePlacement;
1123
1913
 
1914
+ interface IconDefinition {
1915
+ /** Stable, set-prefixed id (`lucide:rocket`), also what an ImageBlock stores in its `stamp` field. */
1916
+ readonly name: string;
1917
+ readonly label: string;
1918
+ readonly category: string;
1919
+ /** Inner SVG markup for a 24x24 viewBox; strokes use `currentColor`. */
1920
+ readonly body: string;
1921
+ }
1922
+ declare const ICON_LIBRARY: readonly [{
1923
+ readonly name: "lucide:check";
1924
+ readonly label: "Check";
1925
+ readonly category: "Status";
1926
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>";
1927
+ }, {
1928
+ readonly name: "lucide:x";
1929
+ readonly label: "Cross";
1930
+ readonly category: "Status";
1931
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>";
1932
+ }, {
1933
+ readonly name: "lucide:star";
1934
+ readonly label: "Star";
1935
+ readonly category: "Status";
1936
+ 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\"/>";
1937
+ }, {
1938
+ readonly name: "lucide:heart";
1939
+ readonly label: "Heart";
1940
+ readonly category: "Status";
1941
+ 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\"/>";
1942
+ }, {
1943
+ readonly name: "lucide:flag";
1944
+ readonly label: "Flag";
1945
+ readonly category: "Status";
1946
+ 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\"/>";
1947
+ }, {
1948
+ readonly name: "lucide:bookmark";
1949
+ readonly label: "Bookmark";
1950
+ readonly category: "Status";
1951
+ 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\"/>";
1952
+ }, {
1953
+ readonly name: "lucide:bell";
1954
+ readonly label: "Bell";
1955
+ readonly category: "Status";
1956
+ 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\"/>";
1957
+ }, {
1958
+ readonly name: "lucide:info";
1959
+ readonly label: "Info";
1960
+ readonly category: "Status";
1961
+ 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>";
1962
+ }, {
1963
+ readonly name: "lucide:circle-alert";
1964
+ readonly label: "Alert";
1965
+ readonly category: "Status";
1966
+ 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>";
1967
+ }, {
1968
+ readonly name: "lucide:circle-question-mark";
1969
+ readonly label: "Question";
1970
+ readonly category: "Status";
1971
+ 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>";
1972
+ }, {
1973
+ readonly name: "lucide:thumbs-up";
1974
+ readonly label: "Thumbs up";
1975
+ readonly category: "Status";
1976
+ 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\"/>";
1977
+ }, {
1978
+ readonly name: "lucide:thumbs-down";
1979
+ readonly label: "Thumbs down";
1980
+ readonly category: "Status";
1981
+ 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\"/>";
1982
+ }, {
1983
+ readonly name: "lucide:arrow-up";
1984
+ readonly label: "Arrow up";
1985
+ readonly category: "Arrows";
1986
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>";
1987
+ }, {
1988
+ readonly name: "lucide:arrow-down";
1989
+ readonly label: "Arrow down";
1990
+ readonly category: "Arrows";
1991
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>";
1992
+ }, {
1993
+ readonly name: "lucide:arrow-left";
1994
+ readonly label: "Arrow left";
1995
+ readonly category: "Arrows";
1996
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"/>";
1997
+ }, {
1998
+ readonly name: "lucide:arrow-right";
1999
+ readonly label: "Arrow right";
2000
+ readonly category: "Arrows";
2001
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"/>";
2002
+ }, {
2003
+ readonly name: "lucide:arrow-up-right";
2004
+ readonly label: "Arrow up-right";
2005
+ readonly category: "Arrows";
2006
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h10v10M7 17L17 7\"/>";
2007
+ }, {
2008
+ readonly name: "lucide:refresh-cw";
2009
+ readonly label: "Refresh";
2010
+ readonly category: "Arrows";
2011
+ 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>";
2012
+ }, {
2013
+ readonly name: "lucide:move";
2014
+ readonly label: "Move";
2015
+ readonly category: "Arrows";
2016
+ 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\"/>";
2017
+ }, {
2018
+ readonly name: "lucide:shuffle";
2019
+ readonly label: "Shuffle";
2020
+ readonly category: "Arrows";
2021
+ 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>";
2022
+ }, {
2023
+ readonly name: "lucide:user";
2024
+ readonly label: "User";
2025
+ readonly category: "People";
2026
+ 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>";
2027
+ }, {
2028
+ readonly name: "lucide:users";
2029
+ readonly label: "Users";
2030
+ readonly category: "People";
2031
+ 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>";
2032
+ }, {
2033
+ readonly name: "lucide:briefcase";
2034
+ readonly label: "Briefcase";
2035
+ readonly category: "People";
2036
+ 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>";
2037
+ }, {
2038
+ readonly name: "lucide:building";
2039
+ readonly label: "Building";
2040
+ readonly category: "People";
2041
+ 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>";
2042
+ }, {
2043
+ readonly name: "lucide:calendar";
2044
+ readonly label: "Calendar";
2045
+ readonly category: "People";
2046
+ 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>";
2047
+ }, {
2048
+ readonly name: "lucide:clock";
2049
+ readonly label: "Clock";
2050
+ readonly category: "People";
2051
+ 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>";
2052
+ }, {
2053
+ readonly name: "lucide:mail";
2054
+ readonly label: "Mail";
2055
+ readonly category: "People";
2056
+ 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>";
2057
+ }, {
2058
+ readonly name: "lucide:message-circle";
2059
+ readonly label: "Message";
2060
+ readonly category: "People";
2061
+ 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\"/>";
2062
+ }, {
2063
+ readonly name: "lucide:file";
2064
+ readonly label: "File";
2065
+ readonly category: "Objects";
2066
+ 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>";
2067
+ }, {
2068
+ readonly name: "lucide:folder";
2069
+ readonly label: "Folder";
2070
+ readonly category: "Objects";
2071
+ 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\"/>";
2072
+ }, {
2073
+ readonly name: "lucide:image";
2074
+ readonly label: "Image";
2075
+ readonly category: "Objects";
2076
+ 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>";
2077
+ }, {
2078
+ readonly name: "lucide:link";
2079
+ readonly label: "Link";
2080
+ readonly category: "Objects";
2081
+ 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>";
2082
+ }, {
2083
+ readonly name: "lucide:paperclip";
2084
+ readonly label: "Paperclip";
2085
+ readonly category: "Objects";
2086
+ 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\"/>";
2087
+ }, {
2088
+ readonly name: "lucide:pin";
2089
+ readonly label: "Pin";
2090
+ readonly category: "Objects";
2091
+ 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\"/>";
2092
+ }, {
2093
+ readonly name: "lucide:map-pin";
2094
+ readonly label: "Map pin";
2095
+ readonly category: "Objects";
2096
+ 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>";
2097
+ }, {
2098
+ readonly name: "lucide:search";
2099
+ readonly label: "Search";
2100
+ readonly category: "Objects";
2101
+ 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>";
2102
+ }, {
2103
+ readonly name: "lucide:settings";
2104
+ readonly label: "Settings";
2105
+ readonly category: "Objects";
2106
+ 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>";
2107
+ }, {
2108
+ readonly name: "lucide:trash-2";
2109
+ readonly label: "Trash";
2110
+ readonly category: "Objects";
2111
+ 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\"/>";
2112
+ }, {
2113
+ readonly name: "lucide:lock";
2114
+ readonly label: "Lock";
2115
+ readonly category: "Objects";
2116
+ 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>";
2117
+ }, {
2118
+ readonly name: "lucide:key";
2119
+ readonly label: "Key";
2120
+ readonly category: "Objects";
2121
+ 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>";
2122
+ }, {
2123
+ readonly name: "lucide:code";
2124
+ readonly label: "Code";
2125
+ readonly category: "Tech";
2126
+ 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\"/>";
2127
+ }, {
2128
+ readonly name: "lucide:database";
2129
+ readonly label: "Database";
2130
+ readonly category: "Tech";
2131
+ 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>";
2132
+ }, {
2133
+ readonly name: "lucide:server";
2134
+ readonly label: "Server";
2135
+ readonly category: "Tech";
2136
+ 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>";
2137
+ }, {
2138
+ readonly name: "lucide:cloud";
2139
+ readonly label: "Cloud";
2140
+ readonly category: "Tech";
2141
+ 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\"/>";
2142
+ }, {
2143
+ readonly name: "lucide:git-branch";
2144
+ readonly label: "Branch";
2145
+ readonly category: "Tech";
2146
+ 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>";
2147
+ }, {
2148
+ readonly name: "lucide:terminal";
2149
+ readonly label: "Terminal";
2150
+ readonly category: "Tech";
2151
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19h8M4 17l6-6l-6-6\"/>";
2152
+ }, {
2153
+ readonly name: "lucide:cpu";
2154
+ readonly label: "CPU";
2155
+ readonly category: "Tech";
2156
+ 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>";
2157
+ }, {
2158
+ readonly name: "lucide:bug";
2159
+ readonly label: "Bug";
2160
+ readonly category: "Tech";
2161
+ 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>";
2162
+ }, {
2163
+ readonly name: "lucide:lightbulb";
2164
+ readonly label: "Idea";
2165
+ readonly category: "Ideas";
2166
+ 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\"/>";
2167
+ }, {
2168
+ readonly name: "lucide:zap";
2169
+ readonly label: "Zap";
2170
+ readonly category: "Ideas";
2171
+ 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\"/>";
2172
+ }, {
2173
+ readonly name: "lucide:target";
2174
+ readonly label: "Target";
2175
+ readonly category: "Ideas";
2176
+ 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>";
2177
+ }, {
2178
+ readonly name: "lucide:rocket";
2179
+ readonly label: "Rocket";
2180
+ readonly category: "Ideas";
2181
+ 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>";
2182
+ }, {
2183
+ readonly name: "lucide:trophy";
2184
+ readonly label: "Trophy";
2185
+ readonly category: "Ideas";
2186
+ 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>";
2187
+ }, {
2188
+ readonly name: "lucide:gift";
2189
+ readonly label: "Gift";
2190
+ readonly category: "Ideas";
2191
+ 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>";
2192
+ }, {
2193
+ readonly name: "lucide:coffee";
2194
+ readonly label: "Coffee";
2195
+ readonly category: "Ideas";
2196
+ 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\"/>";
2197
+ }, {
2198
+ readonly name: "lucide:smile";
2199
+ readonly label: "Smile";
2200
+ readonly category: "Ideas";
2201
+ 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>";
2202
+ }];
2203
+ /** Every vendored icon's id, as a literal union. */
2204
+ type IconName = (typeof ICON_LIBRARY)[number]["name"];
2205
+
2206
+ /**
2207
+ * Emoji stickers. Unlike the icon library there's no artwork to vendor: each
2208
+ * is a Unicode character, drawn by the platform's own colour-emoji font when
2209
+ * its SVG is rasterised — so an emoji looks native to each viewer's OS
2210
+ * (Apple, Segoe, Noto), the same way it would in a chat app.
2211
+ */
2212
+ interface EmojiDefinition {
2213
+ /** Stable, set-prefixed id (`emoji:fire`), also what an ImageBlock stores in its `stamp` field. */
2214
+ readonly name: string;
2215
+ readonly label: string;
2216
+ readonly category: string;
2217
+ readonly char: string;
2218
+ }
2219
+ declare const EMOJI_LIBRARY: readonly [{
2220
+ readonly name: "emoji:grinning";
2221
+ readonly label: "Grinning face";
2222
+ readonly category: "Smileys";
2223
+ readonly char: "😀";
2224
+ }, {
2225
+ readonly name: "emoji:joy";
2226
+ readonly label: "Tears of joy";
2227
+ readonly category: "Smileys";
2228
+ readonly char: "😂";
2229
+ }, {
2230
+ readonly name: "emoji:smiling";
2231
+ readonly label: "Smiling face";
2232
+ readonly category: "Smileys";
2233
+ readonly char: "😊";
2234
+ }, {
2235
+ readonly name: "emoji:heart-eyes";
2236
+ readonly label: "Heart eyes";
2237
+ readonly category: "Smileys";
2238
+ readonly char: "😍";
2239
+ }, {
2240
+ readonly name: "emoji:thinking";
2241
+ readonly label: "Thinking face";
2242
+ readonly category: "Smileys";
2243
+ readonly char: "🤔";
2244
+ }, {
2245
+ readonly name: "emoji:sunglasses";
2246
+ readonly label: "Sunglasses";
2247
+ readonly category: "Smileys";
2248
+ readonly char: "😎";
2249
+ }, {
2250
+ readonly name: "emoji:crying";
2251
+ readonly label: "Crying face";
2252
+ readonly category: "Smileys";
2253
+ readonly char: "😢";
2254
+ }, {
2255
+ readonly name: "emoji:angry";
2256
+ readonly label: "Angry face";
2257
+ readonly category: "Smileys";
2258
+ readonly char: "😡";
2259
+ }, {
2260
+ readonly name: "emoji:sleeping";
2261
+ readonly label: "Sleeping face";
2262
+ readonly category: "Smileys";
2263
+ readonly char: "😴";
2264
+ }, {
2265
+ readonly name: "emoji:mind-blown";
2266
+ readonly label: "Mind blown";
2267
+ readonly category: "Smileys";
2268
+ readonly char: "🤯";
2269
+ }, {
2270
+ readonly name: "emoji:grimacing";
2271
+ readonly label: "Grimacing face";
2272
+ readonly category: "Smileys";
2273
+ readonly char: "😬";
2274
+ }, {
2275
+ readonly name: "emoji:partying";
2276
+ readonly label: "Partying face";
2277
+ readonly category: "Smileys";
2278
+ readonly char: "🥳";
2279
+ }, {
2280
+ readonly name: "emoji:thumbs-up";
2281
+ readonly label: "Thumbs up emoji";
2282
+ readonly category: "Gestures";
2283
+ readonly char: "👍";
2284
+ }, {
2285
+ readonly name: "emoji:thumbs-down";
2286
+ readonly label: "Thumbs down emoji";
2287
+ readonly category: "Gestures";
2288
+ readonly char: "👎";
2289
+ }, {
2290
+ readonly name: "emoji:clap";
2291
+ readonly label: "Clapping hands";
2292
+ readonly category: "Gestures";
2293
+ readonly char: "👏";
2294
+ }, {
2295
+ readonly name: "emoji:raised-hands";
2296
+ readonly label: "Raised hands";
2297
+ readonly category: "Gestures";
2298
+ readonly char: "🙌";
2299
+ }, {
2300
+ readonly name: "emoji:folded-hands";
2301
+ readonly label: "Folded hands";
2302
+ readonly category: "Gestures";
2303
+ readonly char: "🙏";
2304
+ }, {
2305
+ readonly name: "emoji:eyes";
2306
+ readonly label: "Eyes";
2307
+ readonly category: "Gestures";
2308
+ readonly char: "👀";
2309
+ }, {
2310
+ readonly name: "emoji:muscle";
2311
+ readonly label: "Flexed biceps";
2312
+ readonly category: "Gestures";
2313
+ readonly char: "💪";
2314
+ }, {
2315
+ readonly name: "emoji:wave";
2316
+ readonly label: "Waving hand";
2317
+ readonly category: "Gestures";
2318
+ readonly char: "👋";
2319
+ }, {
2320
+ readonly name: "emoji:red-heart";
2321
+ readonly label: "Red heart";
2322
+ readonly category: "Symbols";
2323
+ readonly char: "❤️";
2324
+ }, {
2325
+ readonly name: "emoji:fire";
2326
+ readonly label: "Flame";
2327
+ readonly category: "Symbols";
2328
+ readonly char: "🔥";
2329
+ }, {
2330
+ readonly name: "emoji:star";
2331
+ readonly label: "Medium star";
2332
+ readonly category: "Symbols";
2333
+ readonly char: "⭐";
2334
+ }, {
2335
+ readonly name: "emoji:check";
2336
+ readonly label: "Check mark button";
2337
+ readonly category: "Symbols";
2338
+ readonly char: "✅";
2339
+ }, {
2340
+ readonly name: "emoji:cross";
2341
+ readonly label: "Cross mark";
2342
+ readonly category: "Symbols";
2343
+ readonly char: "❌";
2344
+ }, {
2345
+ readonly name: "emoji:warning";
2346
+ readonly label: "Warning sign";
2347
+ readonly category: "Symbols";
2348
+ readonly char: "⚠️";
2349
+ }, {
2350
+ readonly name: "emoji:question";
2351
+ readonly label: "Red question mark";
2352
+ readonly category: "Symbols";
2353
+ readonly char: "❓";
2354
+ }, {
2355
+ readonly name: "emoji:hundred";
2356
+ readonly label: "Hundred points";
2357
+ readonly category: "Symbols";
2358
+ readonly char: "💯";
2359
+ }, {
2360
+ readonly name: "emoji:light-bulb";
2361
+ readonly label: "Light bulb";
2362
+ readonly category: "Symbols";
2363
+ readonly char: "💡";
2364
+ }, {
2365
+ readonly name: "emoji:bullseye";
2366
+ readonly label: "Bullseye";
2367
+ readonly category: "Symbols";
2368
+ readonly char: "🎯";
2369
+ }, {
2370
+ readonly name: "emoji:party-popper";
2371
+ readonly label: "Party popper";
2372
+ readonly category: "Objects";
2373
+ readonly char: "🎉";
2374
+ }, {
2375
+ readonly name: "emoji:rocket";
2376
+ readonly label: "Rocket emoji";
2377
+ readonly category: "Objects";
2378
+ readonly char: "🚀";
2379
+ }, {
2380
+ readonly name: "emoji:trophy";
2381
+ readonly label: "Trophy emoji";
2382
+ readonly category: "Objects";
2383
+ readonly char: "🏆";
2384
+ }, {
2385
+ readonly name: "emoji:pushpin";
2386
+ readonly label: "Pushpin";
2387
+ readonly category: "Objects";
2388
+ readonly char: "📌";
2389
+ }, {
2390
+ readonly name: "emoji:memo";
2391
+ readonly label: "Memo";
2392
+ readonly category: "Objects";
2393
+ readonly char: "📝";
2394
+ }, {
2395
+ readonly name: "emoji:calendar";
2396
+ readonly label: "Tear-off calendar";
2397
+ readonly category: "Objects";
2398
+ readonly char: "📅";
2399
+ }, {
2400
+ readonly name: "emoji:alarm-clock";
2401
+ readonly label: "Alarm clock";
2402
+ readonly category: "Objects";
2403
+ readonly char: "⏰";
2404
+ }, {
2405
+ readonly name: "emoji:speech-balloon";
2406
+ readonly label: "Speech balloon";
2407
+ readonly category: "Objects";
2408
+ readonly char: "💬";
2409
+ }];
2410
+ type EmojiName = (typeof EMOJI_LIBRARY)[number]["name"];
2411
+ declare function isEmojiName(value: unknown): value is EmojiName;
2412
+ declare function emojiDefinition(name: string): EmojiDefinition | undefined;
2413
+ /** An emoji as a standalone SVG data URL, sized to rasterise sharply (see icons.ts). */
2414
+ declare function emojiDataUrl(name: string): string;
2415
+
2416
+ /**
2417
+ * What the stamp tool will drop next: one of the six built-in stamp-pad
2418
+ * stickers, an emoji, or any vendored icon. All land on the board the same way
2419
+ * — as an image whose `stamp` field records which one it was (see `placeStamp`).
2420
+ */
2421
+ type StickerKind = StampKind | EmojiName | IconName;
2422
+ declare function isIconName(value: unknown): value is IconName;
2423
+ declare function iconDefinition(name: string): IconDefinition | undefined;
2424
+ /** The vendored icons grouped for a picker, in the order they were vendored. */
2425
+ declare function iconCategories(): {
2426
+ category: string;
2427
+ icons: readonly IconDefinition[];
2428
+ }[];
2429
+ /**
2430
+ * An icon as a standalone SVG data URL, drawn in `color`.
2431
+ *
2432
+ * The library's art strokes in `currentColor`, which has no meaning once the
2433
+ * markup is detached into a data URL — an `<img>`/texture has no inherited
2434
+ * colour to resolve it against — so the colour is substituted in rather than
2435
+ * inherited.
2436
+ */
2437
+ declare function iconDataUrl(name: string, color: string): string;
2438
+ declare function isStickerKind(value: unknown): value is StickerKind;
2439
+ /** Human-readable name for any sticker kind — what a placed sticker is called and searched by. */
2440
+ declare function stickerLabel(kind: StickerKind): string;
2441
+ /**
2442
+ * The image `src` for whichever sticker kind is selected. Stamp-pad stamps
2443
+ * and emoji carry their own colours, so only icons take the Host's colour.
2444
+ */
2445
+ declare function stickerDataUrl(kind: StickerKind, color: string): string;
2446
+
1124
2447
  interface ExtensionRegistry {
1125
2448
  readonly extensions: readonly ScrawlExtension[];
1126
2449
  tool(id: ToolId): CustomToolDefinition | undefined;
@@ -1192,16 +2515,37 @@ interface BoardStyle {
1192
2515
  readonly noteColor: string;
1193
2516
  readonly tableRows: number;
1194
2517
  readonly tableCols: number;
1195
- readonly stampKind: StampKind;
2518
+ /** A stamp-pad kind or any vendored icon name (see `ICON_LIBRARY`). */
2519
+ readonly stampKind: StickerKind;
1196
2520
  readonly timerDurationMs: number;
1197
2521
  }
2522
+ /** A page listed in the Board navigator. `default` is the original first page. */
2523
+ interface BoardPage {
2524
+ readonly id: string;
2525
+ readonly name: string;
2526
+ readonly sectionId: string;
2527
+ }
1198
2528
  interface BoardSnapshot {
2529
+ readonly notebooks: readonly BoardNotebook[];
2530
+ readonly sections: readonly BoardSection[];
2531
+ readonly activeNotebookId: string;
2532
+ readonly activeSectionId: string;
2533
+ readonly pages: readonly BoardPage[];
2534
+ readonly activePageId: string;
1199
2535
  readonly status: "loading" | "ready" | "disposed";
1200
2536
  readonly documentId: string;
1201
2537
  readonly tool: BuiltInTool | (string & {});
1202
2538
  readonly zoom: number;
1203
2539
  readonly readOnly: boolean;
1204
2540
  readonly selection: readonly string[];
2541
+ readonly focusedItem: FocusedItem | null;
2542
+ /**
2543
+ * What an inline editor currently covers, as last set through
2544
+ * `view.setInlineEditing` — a text block (`{ id }`) or a table cell
2545
+ * (`{ id, row, col }`), else null. Chrome uses it to stay out of an open
2546
+ * editor's way.
2547
+ */
2548
+ readonly inlineEditing: InlineEditingTarget | null;
1205
2549
  readonly strokeCount: number;
1206
2550
  readonly objectCount: number;
1207
2551
  readonly canUndo: boolean;
@@ -1212,6 +2556,39 @@ interface BoardSnapshot {
1212
2556
  readonly collaboration: CollaborationSnapshot;
1213
2557
  };
1214
2558
  }
2559
+ /**
2560
+ * `selection` collapsed into the one thing a selected-object toolbar needs:
2561
+ * what's selected, its lock state, and where to anchor above it. `null`
2562
+ * when nothing is selected, or when the selection mixes types/objects that
2563
+ * don't resolve to a single focus (anything but one object, or several
2564
+ * strokes sharing a `clusterId` — a multi-stroke shape).
2565
+ *
2566
+ * Raw lock fields, not a derived `canUnlock` — this API has no notion of
2567
+ * "the local user" to judge that against (see `canUnlockItem` in `../core`,
2568
+ * which takes a `userId` the Host already owns). A Custom object's lock
2569
+ * shape doesn't carry a holder name, so it always reports `locked: false`
2570
+ * here, matching the engine's own internal selection-badge behavior.
2571
+ */
2572
+ /** Axis-aligned bounds in board space (y up). */
2573
+ interface BoardBounds {
2574
+ readonly minX: number;
2575
+ readonly minY: number;
2576
+ readonly maxX: number;
2577
+ readonly maxY: number;
2578
+ }
2579
+ interface InlineEditingTarget {
2580
+ readonly id: string;
2581
+ readonly row?: number;
2582
+ readonly col?: number;
2583
+ }
2584
+ interface FocusedItem {
2585
+ readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
2586
+ readonly id: string;
2587
+ readonly locked: boolean;
2588
+ readonly lockedBy?: string;
2589
+ readonly lockedByName?: string;
2590
+ readonly screenPosition?: ScreenPoint;
2591
+ }
1215
2592
  interface BoardControllerError {
1216
2593
  source: "controller" | "renderer" | "persistence" | "collaboration";
1217
2594
  code: string;
@@ -1220,6 +2597,9 @@ interface BoardControllerError {
1220
2597
  }
1221
2598
  interface BoardEventMap {
1222
2599
  change: BoardSnapshot;
2600
+ "page-changing": {
2601
+ pageId: string;
2602
+ };
1223
2603
  "tool-change": {
1224
2604
  tool: string;
1225
2605
  };
@@ -1286,6 +2666,8 @@ interface BoardEventMap {
1286
2666
  "asset-diagnostic": AssetDiagnostic;
1287
2667
  /** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
1288
2668
  "persistence-diagnostic": PersistenceDiagnostic;
2669
+ /** The collaboration server has confirmed receipt of these op ids (Phase 7) — observability only; the Document was already correct via optimistic local apply before this ever fires. */
2670
+ "collaboration-ops-acknowledged": CollaborationAckDiagnostic;
1289
2671
  audit: unknown;
1290
2672
  error: BoardControllerError;
1291
2673
  disposed: undefined;
@@ -1305,6 +2687,27 @@ interface BoardView {
1305
2687
  y: number;
1306
2688
  zoom: number;
1307
2689
  }
2690
+ /**
2691
+ * The rendered board surface's color, reference grid, and shape stroke
2692
+ * width — the subset of {@link ScrawlTheme} that reaches the rendering
2693
+ * engine directly (everything else is UI-chrome-only, applied as CSS). Any
2694
+ * field left unset keeps its current value.
2695
+ */
2696
+ interface BoardThemeOptions {
2697
+ /** The board/canvas background color — distinct from UI chrome panels. */
2698
+ surface?: string;
2699
+ /** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
2700
+ surfaceTexture?: "flat" | "textured";
2701
+ /** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
2702
+ gridMode?: "none" | "line" | "dot";
2703
+ gridColor?: string;
2704
+ /** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
2705
+ gridSpacing?: number;
2706
+ /** On-screen grid line/dot width in CSS pixels — stays this width at any zoom, since the grid is a reference aid, not content. */
2707
+ gridLineWidth?: number;
2708
+ /** Shape (rectangle, ellipse, arrow, ...) border width, in board units — scales with zoom like ink, since it's part of the drawn content. */
2709
+ shapeStrokeWidth?: number;
2710
+ }
1308
2711
  /**
1309
2712
  * A Host-owned comment, summarized for Board-side search and marker
1310
2713
  * rendering. Comments are not Document content — they carry no undo
@@ -1328,9 +2731,12 @@ interface PresenceView {
1328
2731
  readonly height: number;
1329
2732
  }
1330
2733
  /**
1331
- * A Host-owned collaborator, synced in for cursor/roster rendering only.
1332
- * Presence is ephemeral — it never touches the Document, Ops, undo/redo,
1333
- * or persistence — so this is a read/query capability, not an adapter.
2734
+ * A collaborator, synced in for cursor/roster rendering only. Presence is
2735
+ * ephemeral — it never touches the Document, Ops, undo/redo, or persistence
2736
+ * (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
2737
+ * directly, or a `CollaborationAdapter`'s optional presence channel —
2738
+ * Phase 6, ADR 0015) both feed the exact same read/query capability below;
2739
+ * a Host picks one, not both, for a given controller.
1334
2740
  */
1335
2741
  interface PresenceUser {
1336
2742
  readonly id: string;
@@ -1339,6 +2745,14 @@ interface PresenceUser {
1339
2745
  readonly tool?: string;
1340
2746
  readonly cursor?: PresenceCursor;
1341
2747
  readonly view?: PresenceView;
2748
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
2749
+ readonly metadata?: Record<string, unknown>;
2750
+ }
2751
+ /** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
2752
+ interface LocalPresence {
2753
+ readonly cursor?: PresenceCursor | null;
2754
+ readonly view?: PresenceView | null;
2755
+ readonly tool?: string;
1342
2756
  }
1343
2757
  /**
1344
2758
  * The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
@@ -1366,6 +2780,22 @@ type BoardObject = ({
1366
2780
  } & ImageBlock) | ({
1367
2781
  type: "timer";
1368
2782
  } & KitchenTimer) | ({
2783
+ type: "rectangle";
2784
+ } & RectangleObject) | ({
2785
+ type: "ellipse";
2786
+ } & EllipseObject) | ({
2787
+ type: "group";
2788
+ } & GroupObject) | ({
2789
+ type: "line";
2790
+ } & LineObject) | ({
2791
+ type: "arrow";
2792
+ } & ArrowObject) | ({
2793
+ type: "polygon";
2794
+ } & PolygonObject) | ({
2795
+ type: "star";
2796
+ } & StarObject) | ({
2797
+ type: "heart";
2798
+ } & HeartObject) | ({
1369
2799
  type: "custom";
1370
2800
  customType: ObjectType;
1371
2801
  } & Omit<CustomBoardObject, "type">);
@@ -1394,6 +2824,30 @@ type BoardObjectInput = {
1394
2824
  type: "timer";
1395
2825
  id?: string;
1396
2826
  } & Omit<KitchenTimer, "id">) | ({
2827
+ type: "rectangle";
2828
+ id?: string;
2829
+ } & Omit<RectangleObject, "id">) | ({
2830
+ type: "ellipse";
2831
+ id?: string;
2832
+ } & Omit<EllipseObject, "id">) | ({
2833
+ type: "group";
2834
+ id?: string;
2835
+ } & Omit<GroupObject, "id">) | ({
2836
+ type: "line";
2837
+ id?: string;
2838
+ } & Omit<LineObject, "id">) | ({
2839
+ type: "arrow";
2840
+ id?: string;
2841
+ } & Omit<ArrowObject, "id">) | ({
2842
+ type: "polygon";
2843
+ id?: string;
2844
+ } & Omit<PolygonObject, "id">) | ({
2845
+ type: "star";
2846
+ id?: string;
2847
+ } & Omit<StarObject, "id">) | ({
2848
+ type: "heart";
2849
+ id?: string;
2850
+ } & Omit<HeartObject, "id">) | ({
1397
2851
  type: "custom";
1398
2852
  id?: string;
1399
2853
  customType: ObjectType;
@@ -1424,10 +2878,34 @@ type LoadResult = {
1424
2878
  } | {
1425
2879
  state: "missing";
1426
2880
  };
2881
+ /**
2882
+ * Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
2883
+ * "Whole-document writes survive only for create, clear-board and import,
2884
+ * where replacing everything is the actual intent"). Revision-gated, unlike
2885
+ * `applyOps` — `conflict` means `baseRevision` was stale (someone else's
2886
+ * write landed first); the caller must reload and never overwrites blind.
2887
+ */
2888
+ type ReplaceResult = {
2889
+ state: "applied";
2890
+ revision: string;
2891
+ } | {
2892
+ state: "conflict";
2893
+ currentRevision: string;
2894
+ };
2895
+ /**
2896
+ * The one sanctioned seam for persisting a Board's Document to a Host's own
2897
+ * storage — implement this against a database, an HTTP API, IndexedDB
2898
+ * (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
2899
+ * anything else. `load()` fetches the current state on connect; `applyOps()`
2900
+ * streams incremental Ops as edits happen; `replace()` is only for
2901
+ * whole-document writes (create, clear-board, import — see ADR 0006) and is
2902
+ * revision-gated so a stale write never silently clobbers a newer one.
2903
+ * Passed via `createBoardController({ adapters: { persistence } })`.
2904
+ */
1427
2905
  interface PersistenceAdapter {
1428
2906
  load(context: DocumentContext): Promise<LoadResult>;
1429
2907
  applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
1430
- replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<unknown>;
2908
+ replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
1431
2909
  }
1432
2910
  /**
1433
2911
  * `"reconcile"` (ticket #24) means the server authoritatively resolved the
@@ -1451,14 +2929,61 @@ interface PersistenceDiagnostic {
1451
2929
  rejectedOpIds: readonly string[];
1452
2930
  revision: string;
1453
2931
  }
2932
+ /** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
2933
+ interface CollaborationAckDiagnostic {
2934
+ opIds: readonly string[];
2935
+ }
1454
2936
  interface ControllerOp {
1455
2937
  id: string;
1456
2938
  schemaVersion: 1;
1457
2939
  kind: "upsert" | "restore" | "remove";
1458
- objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom";
2940
+ objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
2941
+ /**
2942
+ * A whole-document paint-order sync (Phase 3), not a per-object type —
2943
+ * `objectId` is always the fixed sentinel `"order"` and `payload` is
2944
+ * `{ order: string[] }`. The only `objectType` with no matching
2945
+ * `BoardObject`/document collection; kept in this same union (rather
2946
+ * than a separate wire message) so it flows through the existing
2947
+ * `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
2948
+ */
2949
+ | "order" | "page" | "notebook" | "section";
2950
+ /** Absent for the original page; scopes content and order Ops to an additional page. */
2951
+ pageId?: string;
1459
2952
  objectId: string;
1460
2953
  payload?: unknown;
2954
+ /**
2955
+ * This op's position in its own originating client's local sequence
2956
+ * (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
2957
+ * (an opaque, globally-unique identifier used for dedup/ack, not
2958
+ * ordering) and from a server's own authoritative ordering (e.g.
2959
+ * `referenceCollaborationServer.ts`'s per-room `version` counter).
2960
+ * Present on every op this SDK originates locally; a remote peer's op
2961
+ * carries whatever its own origin set, unchanged — never renumbered in
2962
+ * transit. Absent on an op minted by decoding the legacy wire envelope
2963
+ * (`scrawlOpEnvelope.ts`), which predates this field and has no
2964
+ * per-client sequence concept of its own.
2965
+ */
2966
+ clientSequence?: number;
2967
+ /**
2968
+ * The `CollaboratorIdentity.id` of this op's originating client (Phase
2969
+ * 7) — set for every op this SDK originates locally when `identity` is
2970
+ * configured, omitted entirely otherwise (never sent as `undefined`).
2971
+ * The explicit foundation for a future per-author undo filter (a local
2972
+ * user's own undo should only ever touch their own ops) — no undo-stack
2973
+ * behavior itself changes this phase.
2974
+ */
2975
+ clientId?: string;
1461
2976
  }
2977
+ /**
2978
+ * The one sanctioned seam for real-time multiplayer — implement this against
2979
+ * a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
2980
+ * `connect()` is called once per controller with the local user's
2981
+ * `identity` and a `receive` callback the adapter invokes with incoming
2982
+ * Ops, presence updates, acks, and connection status; it resolves with a
2983
+ * `CollaborationSession` the controller uses to send local Ops and presence
2984
+ * back out. Passed via `createBoardController({ adapters: { collaboration } })`;
2985
+ * omit it entirely to run single-player.
2986
+ */
1462
2987
  interface CollaborationAdapter {
1463
2988
  connect(options: DocumentContext & {
1464
2989
  identity: CollaboratorIdentity;
@@ -1469,14 +2994,72 @@ interface CollaboratorIdentity {
1469
2994
  id: string;
1470
2995
  name: string;
1471
2996
  color?: string;
2997
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
2998
+ metadata?: Record<string, unknown>;
1472
2999
  }
1473
3000
  interface CollaborationReceiver {
1474
3001
  ops(ops: readonly ControllerOp[]): void;
3002
+ /**
3003
+ * The current presence roster (Phase 6, ADR 0015) — always a full
3004
+ * replacement, never a delta, matching `presence.sync`'s existing
3005
+ * semantics exactly (an adapter that aggregates wire deltas into a full
3006
+ * roster before calling this is the adapter's own job, not the
3007
+ * controller's). Required on this interface (not optional) because a
3008
+ * Host only ever *consumes* `CollaborationReceiver` — never implements
3009
+ * it — so adding a required method here cannot break an existing custom
3010
+ * `CollaborationAdapter`. An adapter with no presence support simply
3011
+ * never calls it.
3012
+ */
3013
+ presence(users: readonly PresenceUser[]): void;
3014
+ /**
3015
+ * The server has confirmed receipt of these op ids (Phase 7) —
3016
+ * distinguishes "sent" from "server accepted," which `sendOps` alone
3017
+ * (fire-and-forget) cannot. Required for the same reason `presence` is:
3018
+ * Hosts only ever consume this interface, never implement it, so this
3019
+ * cannot break an existing custom `CollaborationAdapter`. An adapter with
3020
+ * no ack support simply never calls it — the collaboration pipe still
3021
+ * works exactly as it did before this existed, just without the
3022
+ * bookkeeping/observability this enables.
3023
+ */
3024
+ acknowledged(opIds: readonly string[]): void;
1475
3025
  status(state: "online" | "reconnecting" | "offline"): void;
1476
3026
  error(cause: unknown): void;
1477
3027
  }
3028
+ /**
3029
+ * Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
3030
+ * the adapter's own live-pipe cache fully covered the gap since the
3031
+ * caller's last known revision — apply `ops` and the client is caught up,
3032
+ * no persistence reload needed. `"unavailable"` means it couldn't (gap too
3033
+ * large, server restarted, or the adapter has no retained history at all)
3034
+ * — the caller must fall back to a persistence-backed reload. This is a
3035
+ * best-effort *liveness* cache, deliberately never a durable source of
3036
+ * truth (ADR 0006's "collaboration is never a second source of document
3037
+ * truth" — see ADR 0015's own extension of that principle to presence,
3038
+ * now extended once more, the same way, to this).
3039
+ */
3040
+ type CollaborationSyncResult = {
3041
+ state: "ops";
3042
+ ops: readonly ControllerOp[];
3043
+ serverRevision: string;
3044
+ } | {
3045
+ state: "unavailable";
3046
+ };
1478
3047
  interface CollaborationSession {
1479
3048
  sendOps(ops: readonly ControllerOp[]): void;
3049
+ /**
3050
+ * Publishes this client's own local presence (Phase 6, ADR 0015) —
3051
+ * best-effort, unordered, never persisted, never an Op. Optional: an
3052
+ * adapter that doesn't support presence simply omits this method, and
3053
+ * `presence.broadcast()` becomes a silent no-op.
3054
+ */
3055
+ updatePresence?(presence: LocalPresence): void;
3056
+ /**
3057
+ * Requests an incremental catch-up after a reconnect (Phase 7) — optional;
3058
+ * an adapter that doesn't support this simply omits the method, and the
3059
+ * caller (`resyncAfterReconnect`) goes straight to its existing
3060
+ * persistence-backed full reload, unchanged from Phase 6.
3061
+ */
3062
+ requestSync?(): Promise<CollaborationSyncResult>;
1480
3063
  close(): Promise<void>;
1481
3064
  }
1482
3065
  interface CreateBoardControllerOptions {
@@ -1515,14 +3098,114 @@ interface CreateBoardControllerOptions {
1515
3098
  * changes via `boardTheme.set` below — a headless/browser-tier Host that
1516
3099
  * doesn't use the React theme system can set this directly instead.
1517
3100
  */
1518
- boardTheme?: {
1519
- surface?: string;
1520
- gridMode?: "none" | "line" | "dot";
1521
- gridColor?: string;
1522
- gridSpacing?: number;
3101
+ boardTheme?: BoardThemeOptions;
3102
+ /**
3103
+ * Debounced auto-flush of pending persistence Ops after document changes
3104
+ * settle (Phase 5). Enabled by default (1000ms debounce) whenever
3105
+ * `adapters.persistence` is configured — today, without this, a Host must
3106
+ * call `flush()` manually after every edit for anything to persist. Pass
3107
+ * `false` to opt out entirely and drive `flush()` yourself, preserving
3108
+ * prior behavior exactly. Never fires on a per-change basis — rapid edits
3109
+ * coalesce into one flush of their final state (ADR 0006).
3110
+ */
3111
+ autosave?: boolean | {
3112
+ debounceMs?: number;
1523
3113
  };
3114
+ /**
3115
+ * Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
3116
+ * interval between outgoing presence updates sent via the configured
3117
+ * `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
3118
+ * latest value passed to `broadcast()` always eventually sends, even if
3119
+ * calls arrive faster than this interval.
3120
+ */
3121
+ presenceThrottleMs?: number;
3122
+ /**
3123
+ * Caps how many `ControllerOp`s can sit queued, unsent, for the
3124
+ * persistence pipe (`pendingOps`) or the collaboration pipe
3125
+ * (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
3126
+ * independently. Prevents unbounded memory growth from a long-lived
3127
+ * offline session or a stuck adapter. Exceeding it never fails or drops
3128
+ * the local edit itself (the Document already applied it optimistically)
3129
+ * — only queueing for that one pipe is skipped, and a
3130
+ * `{code:"queue-overflow", retryable:false}` error is emitted so a Host
3131
+ * can react. Defaults to 1000 — the Phase 9 collaboration coalescing
3132
+ * above (`collaborationCoalesceMs`) already keeps a busy drag from
3133
+ * approaching this on its own, so hitting it in practice means a pipe
3134
+ * has been offline/stuck for a genuinely long editing session.
3135
+ *
3136
+ * **Recovery** (Phase 9): the dropped op itself is gone from that one
3137
+ * pipe's queue — there is no automatic backfill, and the live
3138
+ * controller keeps running with that pipe now silently missing one
3139
+ * edit. Two things stay true regardless: (1) the in-memory Document is
3140
+ * never affected — a queue-overflow can never corrupt or roll back a
3141
+ * local edit, only skip sending it; (2) staleness is per-object, not
3142
+ * permanent — any *later* edit to that same object produces a brand
3143
+ * new, undropped Op carrying its full current state, which naturally
3144
+ * supersedes the gap (the Op model is already last-write-wins/
3145
+ * idempotent, so a superseding Op doesn't need the earlier one to have
3146
+ * arrived). The real risk is an object that's dropped and never edited
3147
+ * again before the controller is disposed or the page reloads — a Host
3148
+ * that needs strict durability should treat `queue-overflow` as a
3149
+ * signal to check `persistence.state`/`pendingOps` pressure (via
3150
+ * `usePersistenceStatus`/`getSnapshot().connection.persistence`)
3151
+ * before disposing, not assume disposing and reconnecting alone
3152
+ * repairs the gap (a fresh `load()` only returns what the backend
3153
+ * already has, which is exactly what's missing the dropped edit).
3154
+ */
3155
+ maxPendingOps?: number;
3156
+ /**
3157
+ * Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
3158
+ * trailing-throttle shape as `presenceThrottleMs`: the first Op after an
3159
+ * idle period sends immediately, and subsequent Ops for the *same*
3160
+ * object within this window replace each other (latest value wins,
3161
+ * matching the already-idempotent Op model) rather than each triggering
3162
+ * its own send. A multi-second drag that previously sent one full Op per
3163
+ * pointer-move now sends at most one per window per touched object.
3164
+ * Persistence (`pendingOps`) is unaffected — it already debounces via
3165
+ * `autosave`, so this option only changes live collaboration traffic.
3166
+ * Defaults to 50ms.
3167
+ */
3168
+ collaborationCoalesceMs?: number;
3169
+ /**
3170
+ * Caps how many resolved objects a single `content.copy`/`content.cut`
3171
+ * (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
3172
+ * in-memory clipboard at once (Phase 9) — `expandSelection` recursively
3173
+ * expands groups, so an unbounded selection (a huge group, or thousands
3174
+ * of individually selected strokes) could otherwise clone and retain an
3175
+ * arbitrarily large snapshot indefinitely, until the next copy/cut
3176
+ * replaces it. Exceeding it rejects the whole copy/cut (nothing is
3177
+ * cloned, and — for cut — nothing is removed from the Document either,
3178
+ * never a partial copy of an arbitrary subset) and emits a
3179
+ * `{code:"clipboard-overflow", retryable:false}` error. Defaults to
3180
+ * 5000.
3181
+ */
3182
+ maxClipboardItems?: number;
1524
3183
  }
1525
3184
  interface BoardController {
3185
+ readonly notebooks: {
3186
+ /** Create a notebook containing one section and an empty page; select it. */
3187
+ add(name?: string): string;
3188
+ select(id: string): void;
3189
+ rename(id: string, name: string): void;
3190
+ list(): readonly BoardNotebook[];
3191
+ current(): string;
3192
+ };
3193
+ readonly sections: {
3194
+ /** Create a section in the current or specified notebook, with one empty page. */
3195
+ add(name?: string, notebookId?: string): string;
3196
+ select(id: string): void;
3197
+ rename(id: string, name: string): void;
3198
+ list(notebookId?: string): readonly BoardSection[];
3199
+ current(): string;
3200
+ };
3201
+ readonly pages: {
3202
+ /** Add an empty page and switch to it. */
3203
+ add(name?: string, sectionId?: string): string;
3204
+ select(id: string): void;
3205
+ rename(id: string, name: string): void;
3206
+ list(sectionId?: string): readonly BoardPage[];
3207
+ current(): string;
3208
+ };
1526
3209
  readonly document: ReadonlyBoardDocument;
1527
3210
  readonly tools: {
1528
3211
  select(tool: BuiltInTool | (string & {})): void;
@@ -1537,7 +3220,7 @@ interface BoardController {
1537
3220
  noteColor: string;
1538
3221
  tableRows: number;
1539
3222
  tableCols: number;
1540
- stampKind: StampKind;
3223
+ stampKind: StickerKind;
1541
3224
  timerDurationMs: number;
1542
3225
  }>): void;
1543
3226
  current(): BoardStyle;
@@ -1547,18 +3230,34 @@ interface BoardController {
1547
3230
  redo(): void;
1548
3231
  };
1549
3232
  readonly boardTheme: {
1550
- /** Live update of the board surface color/grid — the controller's identity stays fixed across theme changes. */
1551
- set(theme: {
1552
- surface?: string;
1553
- gridMode?: "none" | "line" | "dot";
1554
- gridColor?: string;
1555
- gridSpacing?: number;
1556
- }): void;
3233
+ /** Live update of the board surface color/grid/shape-stroke-width — the controller's identity stays fixed across theme changes. */
3234
+ set(theme: BoardThemeOptions): void;
1557
3235
  };
1558
3236
  readonly view: {
1559
3237
  fit(): void;
3238
+ /**
3239
+ * Frame the current selection (Phase 8), the same way `fit()` frames the
3240
+ * whole board. A no-op with nothing selected — deliberately doesn't fall
3241
+ * back to `fit()`'s "frame everything," which would be a surprising
3242
+ * result for an empty selection. On a headless board this can only
3243
+ * re-center the view (no viewport to compute a real zoom-to-fit from),
3244
+ * matching `fit()`'s own headless limitation exactly.
3245
+ */
3246
+ zoomToSelection(): void;
1560
3247
  zoomTo(value: number): void;
1561
3248
  centerOn(point: BoardPoint): void;
3249
+ /**
3250
+ * Tell the canvas an inline text editor is open over a text block (`{ id }`)
3251
+ * or a table cell (`{ id, row, col }`), so it stops drawing that text until
3252
+ * the editor closes (`null`) and the two copies don't overlap. The default
3253
+ * UI's inline editors call this; a Host with its own editors should too.
3254
+ * Presentation only — never touches the Document. A no-op when headless.
3255
+ */
3256
+ setInlineEditing(target: {
3257
+ id: string;
3258
+ row?: number;
3259
+ col?: number;
3260
+ } | null): void;
1562
3261
  get(): BoardView;
1563
3262
  boardToScreen(point: BoardPoint): ScreenPoint;
1564
3263
  screenToBoard(point: ScreenPoint): BoardPoint;
@@ -1567,6 +3266,75 @@ interface BoardController {
1567
3266
  add(input: BoardObjectInput): string;
1568
3267
  update(id: string, patch: BoardObjectPatch): void;
1569
3268
  remove(ids: readonly string[]): void;
3269
+ /**
3270
+ * Clone each given object as a new, unlocked copy offset by a small
3271
+ * fixed cascade (matching the Duplicate affordance's established Host
3272
+ * convention), as one undoable step. Order-preserving: `result[i]` is
3273
+ * the clone of `ids[i]`. Strokes that share a `clusterId` among the
3274
+ * given ids get a single fresh shared `clusterId` in the result, so
3275
+ * duplicating a whole multi-stroke shape (e.g. an arrow's shaft + head)
3276
+ * keeps it one shape — pass every member's id together, not just one.
3277
+ * Unknown ids are silently skipped, matching `remove`'s convention.
3278
+ */
3279
+ duplicate(ids: readonly string[]): readonly string[];
3280
+ /**
3281
+ * Creates a new Group referencing `ids` as its children and returns its
3282
+ * id, as one undoable step. Unknown ids are silently skipped, matching
3283
+ * `duplicate`/`remove`'s convention. A child id that's itself a group
3284
+ * makes a nested group — expanding nested groups into their leaf
3285
+ * members is always the caller's job, never assumed here (matches the
3286
+ * document-model `GroupObject` itself).
3287
+ */
3288
+ group(ids: readonly string[]): string;
3289
+ /**
3290
+ * Dissolves one group, returning its immediate children's ids (a nested
3291
+ * subgroup among them stays intact, itself still a group) — the group
3292
+ * record itself is removed, the children are untouched. A no-op
3293
+ * (returns `[]`) if `groupId` isn't a group.
3294
+ */
3295
+ ungroup(groupId: string): readonly string[];
3296
+ /**
3297
+ * Aligns every given object's matching edge/center to the corresponding
3298
+ * edge/center of their combined bounding box, as one undoable step.
3299
+ * `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
3300
+ * the larger Y). Ids that don't resolve, or resolve to a Group (which
3301
+ * has no position of its own), are skipped. A no-op under 2 resolvable
3302
+ * ids — there's nothing to align relative to.
3303
+ */
3304
+ align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
3305
+ /**
3306
+ * Spaces the middle objects' centers evenly between the first and last
3307
+ * (sorted along `axis`), as one undoable step — the two endpoints don't
3308
+ * move. Ids that don't resolve, or resolve to a Group, are skipped. A
3309
+ * no-op under 3 resolvable ids — there's no "middle" to distribute.
3310
+ */
3311
+ distribute(ids: readonly string[], axis: "x" | "y"): void;
3312
+ /**
3313
+ * Snapshots `ids` (recursively expanded through any group, same as
3314
+ * `duplicate`) into an internal in-memory clipboard — never
3315
+ * `navigator.clipboard`, scoped to this one controller instance and
3316
+ * replaced wholesale by the next `copy`/`cut`. Read-only; works even
3317
+ * on a read-only board.
3318
+ */
3319
+ copy(ids: readonly string[]): void;
3320
+ /** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
3321
+ cut(ids: readonly string[]): void;
3322
+ /**
3323
+ * Clones the current clipboard contents onto the board as one undoable
3324
+ * step, offset the same small cascade `duplicate` uses (no cursor
3325
+ * position to paste relative to yet). Returns the new top-level ids —
3326
+ * a pasted group's own id stands for its (also-pasted) children, which
3327
+ * aren't listed separately. `[]` when the clipboard is empty.
3328
+ */
3329
+ paste(): readonly string[];
3330
+ /**
3331
+ * Select every top-level object (Phase 8) — a group's own id stands for
3332
+ * its children, which aren't selected separately, matching `paste`'s own
3333
+ * "what the user sees" id list. Hidden objects are excluded, consistent
3334
+ * with them already being excluded from marquee selection. Works with
3335
+ * no canvas/engine, same as {@link toggleSelectionVisibility}.
3336
+ */
3337
+ selectAll(): void;
1570
3338
  table: {
1571
3339
  addRow(tableId: string): void;
1572
3340
  addCol(tableId: string): void;
@@ -1577,15 +3345,42 @@ interface BoardController {
1577
3345
  };
1578
3346
  select(ids: readonly string[]): void;
1579
3347
  import(document: SerializedBoardDocument): readonly string[];
3348
+ /**
3349
+ * Toggle lock state for the current selection (or focused note/text/
3350
+ * table/image/timer), matching whatever a single Host lock/unlock
3351
+ * control already does per object type. A no-op with nothing selected,
3352
+ * on a headless board, or when every actionable target is locked by
3353
+ * another collaborator who isn't the current lock holder.
3354
+ */
3355
+ toggleSelectionLock(): void;
3356
+ /**
3357
+ * Toggle hidden state for the current selection, as one undo entry
3358
+ * (Phase 8). If any selected object is hidden, shows every selected
3359
+ * object; otherwise hides them all — same "any wins" semantics as
3360
+ * {@link toggleSelectionLock}. Hidden objects stay fully present in the
3361
+ * document (they still serialize, persist, sync, undo/redo) — they just
3362
+ * stop rendering and stop being hit-testable/selectable via pointer
3363
+ * interaction. Unlike `toggleSelectionLock`, this works on a headless
3364
+ * board too: it only touches `selection`/the document, no canvas or
3365
+ * engine involved. Custom objects have no visibility concept (no
3366
+ * `Hideable` field) and are silently skipped, matching how
3367
+ * `toggleSelectionLock` already excludes them. A no-op with nothing
3368
+ * selected or when the selection is only custom objects.
3369
+ */
3370
+ toggleSelectionVisibility(): void;
1580
3371
  };
1581
3372
  readonly query: {
1582
3373
  get(id: string): DeepReadonly<BoardObject> | undefined;
1583
3374
  all(): readonly DeepReadonly<BoardObject>[];
1584
3375
  search(query: string): readonly SearchHit[];
3376
+ /** An object's axis-aligned board-space bounds (y up), or undefined for an unknown id. */
3377
+ bounds(id: string): BoardBounds | undefined;
1585
3378
  };
1586
3379
  /** Host-owned comments, synced in for search and marker rendering only — see CommentMarker. */
1587
3380
  readonly comments: {
1588
3381
  sync(comments: readonly CommentMarker[]): void;
3382
+ /** The markers last passed to `sync`, in the order given. */
3383
+ list(): readonly CommentMarker[];
1589
3384
  open(id: string): void;
1590
3385
  setActive(id: string | null): void;
1591
3386
  };
@@ -1605,6 +3400,15 @@ interface BoardController {
1605
3400
  follow(view: PresenceView): void;
1606
3401
  /** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
1607
3402
  gather(view: PresenceView): boolean;
3403
+ /**
3404
+ * Publishes this client's own cursor/tool/view for other collaborators
3405
+ * (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
3406
+ * throttled internally (`presenceThrottleMs` option, default 50ms) so
3407
+ * a raw pointermove stream never becomes a message-per-event flood. A
3408
+ * no-op if no collaboration adapter is configured, or if the
3409
+ * configured one doesn't implement `updatePresence`.
3410
+ */
3411
+ broadcast(local: LocalPresence): void;
1608
3412
  };
1609
3413
  readonly export: {
1610
3414
  svg(): string;
@@ -1630,7 +3434,19 @@ interface BoardController {
1630
3434
  }>;
1631
3435
  dispose(): Promise<void>;
1632
3436
  }
3437
+ /**
3438
+ * Creates a {@link BoardController} — the SDK's canonical, capability-grouped
3439
+ * entry point (`content`, `tools`, `style`, `history`, `view`, `query`,
3440
+ * `comments`, `presence`, `export`, `assets`, plus top-level `getSnapshot`/
3441
+ * `subscribe`/`on`/`setReadOnly`/`flush`/`dispose`) for driving a Board
3442
+ * imperatively from any JS/TS runtime. Supply a `canvas` to render, or omit
3443
+ * it to run headless (SSR, tests, or a document/history-only integration).
3444
+ * Persistence and Collaboration are opt-in via `options.adapters` — without
3445
+ * them the controller runs entirely in memory. Call `dispose()` when done to
3446
+ * release the renderer, adapters, and any pending timers.
3447
+ */
1633
3448
  declare function createBoardController(options: CreateBoardControllerOptions): BoardController;
3449
+ /** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
1634
3450
  type LocalBoardSnapshot = {
1635
3451
  documentId: string;
1636
3452
  selectedStrokeId: string | null;
@@ -1639,10 +3455,12 @@ type LocalBoardSnapshot = {
1639
3455
  canRedo: boolean;
1640
3456
  disposed: boolean;
1641
3457
  };
3458
+ /** @deprecated Use `CreateBoardControllerOptions` with `createBoardController` instead. */
1642
3459
  type LocalBoardOptions = {
1643
3460
  documentId: string;
1644
3461
  initialDocument?: SerializedBoardDocument;
1645
3462
  };
3463
+ /** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
1646
3464
  type LocalBoard = {
1647
3465
  drawStroke(stroke: Stroke): void;
1648
3466
  selectAt(point: BoardPoint): string | null;
@@ -1653,42 +3471,100 @@ type LocalBoard = {
1653
3471
  subscribe(listener: () => void): () => void;
1654
3472
  dispose(): Promise<void>;
1655
3473
  };
3474
+ /** @deprecated Use `createBoardController` instead — this is a thin, stroke-only wrapper kept for the original Phase 1 tracer's compatibility. */
1656
3475
  declare function createLocalBoard(options: LocalBoardOptions): LocalBoard;
1657
3476
 
3477
+ interface CreateMemoryPersistenceOptions {
3478
+ /** Pre-seeded documents, keyed by document id — as if a prior session had already saved them. Seeded documents start at revision "1". */
3479
+ seed?: Record<string, CurrentSerializedDocument>;
3480
+ }
3481
+ /**
3482
+ * Creates a real, in-memory `PersistenceAdapter`. One instance can back
3483
+ * multiple documents (keyed by `DocumentContext.documentId`, like every
3484
+ * other adapter in this package). State lives only in this instance —
3485
+ * discarded on garbage collection, never written to disk.
3486
+ */
3487
+ declare function createMemoryPersistence(options?: CreateMemoryPersistenceOptions): PersistenceAdapter;
3488
+
1658
3489
  type ScrawlThemePreset = "light" | "dark";
1659
3490
  type ScrawlDensity = "comfortable" | "compact";
1660
3491
  type ScrawlGridMode = "none" | "line" | "dot";
3492
+ type ScrawlSurfaceTexture = "flat" | "textured";
3493
+ /**
3494
+ * Every field is optional — anything you don't set falls back to the
3495
+ * chosen `preset` ("light" or "dark", see {@link resolveScrawlTheme}).
3496
+ * Overrides are semantic, board-local runtime configuration: they never
3497
+ * get written into the Document or into exports, so switching themes is
3498
+ * always non-destructive.
3499
+ */
1661
3500
  interface ScrawlTheme {
3501
+ /** UI chrome background — toolbar/panel base surface. Distinct from `boardSurface` (the canvas itself). */
1662
3502
  surface?: string;
3503
+ /** UI chrome background, one step up from `surface` — popovers, dropdowns, elevated panels. */
1663
3504
  surfaceRaised?: string;
3505
+ /** UI chrome background, one step down from `surface` — subtle fills, hover states. */
1664
3506
  surfaceMuted?: string;
3507
+ /** Primary UI text color. Checked for contrast against `surface`. */
1665
3508
  text?: string;
3509
+ /** Secondary/de-emphasized UI text color. Checked for contrast against `surface`. */
1666
3510
  textMuted?: string;
3511
+ /** Borders and dividers between UI chrome elements. */
1667
3512
  edge?: string;
3513
+ /**
3514
+ * Brand accent color. Drives the active/selected state of toolbar
3515
+ * controls (e.g. the active tool button): its icon/text render in this
3516
+ * color, and its background is automatically derived as a light tint of
3517
+ * it (via `color-mix`) — set this one token and both follow.
3518
+ */
3519
+ primary?: string;
3520
+ /** Focus ring color. Checked for contrast against `surface`. */
1668
3521
  focus?: string;
3522
+ /** Selection highlight color (e.g. selected list items, not board object selection). */
1669
3523
  selection?: string;
3524
+ /** Destructive/error state color (delete confirmations, error text). */
1670
3525
  danger?: string;
3526
+ /** Warning state color. */
1671
3527
  warning?: string;
3528
+ /** Success/confirmation state color. */
1672
3529
  success?: string;
3530
+ /** Font stack for UI chrome (toolbar labels, menus, dialogs). */
1673
3531
  uiFontFamily?: string;
3532
+ /** Font stack for board content and data (e.g. table cell text). */
1674
3533
  dataFontFamily?: string;
3534
+ /** Base UI font size in px. Range: 12–24. */
1675
3535
  baseFontSize?: number;
3536
+ /** Regular UI font weight. Range: 300–900. */
1676
3537
  regularWeight?: number;
3538
+ /** Emphasized UI font weight (headings, active states). Range: 300–900. */
1677
3539
  strongWeight?: number;
3540
+ /** Corner radius for small controls (buttons, inputs) in px. Range: 0–32. */
1678
3541
  controlRadius?: number;
3542
+ /** Corner radius for panels/dialogs in px. Range: 0–32. */
1679
3543
  panelRadius?: number;
3544
+ /** CSS `box-shadow` value for subtle elevation (e.g. toolbar). */
1680
3545
  elevationLow?: string;
3546
+ /** CSS `box-shadow` value for prominent elevation (e.g. modals). */
1681
3547
  elevationHigh?: string;
3548
+ /** UI transition duration in ms. Range: 0–500. */
1682
3549
  motionDuration?: number;
3550
+ /** CSS easing function for UI transitions. */
1683
3551
  motionEasing?: string;
3552
+ /** UI chrome spacing/sizing scale. */
1684
3553
  density?: ScrawlDensity;
1685
3554
  /** The rendered board/canvas surface color — distinct from `surface` (UI chrome panels). */
1686
3555
  boardSurface?: string;
3556
+ /** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
3557
+ boardSurfaceTexture?: ScrawlSurfaceTexture;
1687
3558
  /** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
1688
3559
  gridMode?: ScrawlGridMode;
3560
+ /** Grid line/dot color. Ignored when `gridMode` is `"none"`. */
1689
3561
  gridColor?: string;
1690
3562
  /** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
1691
3563
  gridSpacing?: number;
3564
+ /** On-screen grid line/dot width in CSS pixels. Range: 0.5–8. A reference aid, so unlike shape/ink strokes it stays this width at any zoom. */
3565
+ gridLineWidth?: number;
3566
+ /** Shape (rectangle, ellipse, arrow, ...) border width, in board units. Range: 0.01–5. Scales with zoom like ink, since it's part of the drawn content. */
3567
+ shapeStrokeWidth?: number;
1692
3568
  }
1693
3569
  type ResolvedScrawlTheme = Required<ScrawlTheme>;
1694
3570
  interface ScrawlThemeDiagnostic {
@@ -1705,6 +3581,67 @@ declare const scrawlThemePresets: Readonly<Record<ScrawlThemePreset, Readonly<Re
1705
3581
  declare function validateScrawlTheme(theme: ScrawlTheme | Record<string, unknown>): ScrawlThemeDiagnostic[];
1706
3582
  declare function resolveScrawlTheme(preset?: ScrawlThemePreset, theme?: ScrawlTheme | Record<string, unknown>): ScrawlResolvedTheme;
1707
3583
 
3584
+ /** Access levels, shown as View / Edit / Admin. Map your backend's roles onto these. */
3585
+ type BoardRole = "viewer" | "editor" | "admin";
3586
+ interface BoardMember {
3587
+ id: string;
3588
+ name: string;
3589
+ email?: string;
3590
+ avatarUrl?: string;
3591
+ online?: boolean;
3592
+ role: BoardRole;
3593
+ }
3594
+ interface BoardRoleChange {
3595
+ memberId: string;
3596
+ role: BoardRole;
3597
+ }
3598
+ interface BoardPermissions {
3599
+ members: readonly BoardMember[];
3600
+ /** Marks "(you)" and keeps your own access level from being changed here. */
3601
+ currentUserId?: string;
3602
+ /** Whether "Manage permissions" is offered at all — your rule, from your backend. */
3603
+ canManage: boolean;
3604
+ /**
3605
+ * Called with every pending change when Save is pressed. Resolve once your
3606
+ * backend accepted them; reject to show the error and keep them pending.
3607
+ * Nothing changes until your backend says so — update `members` afterwards.
3608
+ */
3609
+ onSave(changes: readonly BoardRoleChange[]): Promise<void>;
3610
+ }
3611
+ type BoardActivityKind = "added" | "removed" | "updated" | "commented" | "shared" | "exported";
3612
+ interface BoardActivityEntry {
3613
+ id: string;
3614
+ kind: BoardActivityKind;
3615
+ /** e.g. "Added a sticky note". */
3616
+ title: string;
3617
+ actorName?: string;
3618
+ /** When it happened (ms since epoch or ISO string). */
3619
+ at: number | string;
3620
+ /** Board point to jump to, if the action has a place on the board. */
3621
+ location?: BoardPoint;
3622
+ }
3623
+ interface BoardActivityFeed {
3624
+ /** Newest first. Omit to show this session's own actions instead. */
3625
+ entries?: readonly BoardActivityEntry[];
3626
+ unreadCount?: number;
3627
+ onMarkAllRead?(): void;
3628
+ }
3629
+ interface BoardCommentFeed {
3630
+ unreadCount?: number;
3631
+ onMarkAllRead?(): void;
3632
+ }
3633
+ interface BoardMenuItem {
3634
+ id: string;
3635
+ label: string;
3636
+ icon?: ReactNode;
3637
+ onSelect(): void;
3638
+ }
3639
+
3640
+ interface CommentAuthor {
3641
+ name: string;
3642
+ color?: string;
3643
+ }
3644
+
1708
3645
  interface DefaultBoardChromeProps {
1709
3646
  controller: BoardController;
1710
3647
  snapshot: BoardSnapshot;
@@ -1713,8 +3650,42 @@ interface DefaultBoardChromeProps {
1713
3650
  style?: React.CSSProperties;
1714
3651
  regions?: Partial<Record<DefaultUIRegion, boolean>>;
1715
3652
  slots?: DefaultUISlots;
3653
+ /**
3654
+ * Override any built-in tool icon. Tools without an override keep the
3655
+ * SDK's outline icon. Accessible labels and shortcut tooltips are preserved.
3656
+ */
3657
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
3658
+ /** Name (and pin colour) shown on comments left through the default comment UI. Defaults to "You". */
3659
+ commentAuthor?: CommentAuthor;
3660
+ /**
3661
+ * Sharing is the Host's own (links, permissions, backends), so the board
3662
+ * actions only offer a Share button when this is supplied.
3663
+ */
3664
+ onShare?(): void;
3665
+ /**
3666
+ * Your backend's activity feed for the Recent Activity drawer. Without
3667
+ * `entries`, the drawer lists this session's own board actions.
3668
+ */
3669
+ activity?: BoardActivityFeed;
3670
+ /** Unread state for the Comments drawer's badge and "Mark all as read". */
3671
+ commentFeed?: BoardCommentFeed;
3672
+ /**
3673
+ * Members and access levels from your backend, for the permissions drawer
3674
+ * (offered in the More menu when `canManage`). The SDK never changes a role
3675
+ * itself: Save calls `onSave`, and your backend decides.
3676
+ */
3677
+ permissions?: BoardPermissions;
3678
+ /** Extra More-menu actions your app provides (board settings, version history, duplicate…). */
3679
+ menuItems?: readonly BoardMenuItem[];
1716
3680
  }
1717
- type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf";
3681
+
3682
+ /**
3683
+ * `comments` is the built-in comment composer/thread, which keeps comments for
3684
+ * the session; `images` is the Image tool's file picker plus drag-and-drop.
3685
+ * Turn either off when the Host handles `comment-draft-request`/
3686
+ * `comment-open-request` or `import-request` itself.
3687
+ */
3688
+ type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf" | "focusedItemToolbar" | "emptyState" | "pages" | "comments" | "images" | "activity";
1718
3689
  /** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
1719
3690
  interface BoardSlotProps {
1720
3691
  controller: BoardController;
@@ -1742,7 +3713,7 @@ interface DefaultUISlots {
1742
3713
  */
1743
3714
  contextMenu?: ComponentType<BoardSlotProps> | null;
1744
3715
  }
1745
- declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
3716
+ declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }: DefaultBoardChromeProps): react.JSX.Element;
1746
3717
 
1747
3718
  interface InlineEditorsProps {
1748
3719
  controller: BoardController;
@@ -1767,12 +3738,70 @@ interface MultiplayerCursorsProps {
1767
3738
  */
1768
3739
  declare function MultiplayerCursors({ controller, onSelectUser, onJumpToUser }: MultiplayerCursorsProps): react.JSX.Element | null;
1769
3740
 
3741
+ interface FocusedItemToolbarProps {
3742
+ controller: BoardController;
3743
+ snapshot: BoardSnapshot;
3744
+ }
3745
+ /**
3746
+ * Floating toolbar above the focused note, shape, text block, or semantic
3747
+ * Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
3748
+ * (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Images (photos and
3749
+ * placed emoji/icons) and tables get Lock/Duplicate/Delete only — each
3750
+ * already has its own resize affordance (the native gizmo for images, edge
3751
+ * handles for tables), and a table's row/column actions live in its cell
3752
+ * editor. Timer/custom objects and plain (non-shape) ink strokes never get a
3753
+ * toolbar here — out of scope for this destination.
3754
+ *
3755
+ * Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
3756
+ * the legacy ink-stroke shape below: both draw from the same toolbar
3757
+ * buttons and look the same to a user, but a Rectangle/Ellipse is a real
3758
+ * BoardObject with its own resize handles (independent width/height,
3759
+ * `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
3760
+ * native selection gizmo. This duality is deliberate and temporary — see
3761
+ * docs/reports/phase-2-document-object-model.md.
3762
+ *
3763
+ * Lock/Unlock carries no per-user ownership gating: nothing else in this
3764
+ * SDK enforces lock ownership either (`content.update` never checks
3765
+ * `locked`, and the engine's own internal unlock check has no way for a
3766
+ * Host to ever supply a real user id) — locking is advisory UI state
3767
+ * throughout, and this toolbar matches that rather than inventing an
3768
+ * enforcement story alone.
3769
+ *
3770
+ * A shape can be several strokes sharing one `clusterId` (e.g. an arrow's
3771
+ * shaft + head) — every action here applies to the whole cluster. Resolved
3772
+ * via `query.all()` + `clusterId`, not `snapshot.selection`: a canvas click
3773
+ * on one member selects every member internally, but the public selection
3774
+ * bridge only ever reports one id (`FocusedItem.id` is singular by design),
3775
+ * so reconstructing the cluster from the document is the reliable path
3776
+ * regardless of how the selection was made.
3777
+ *
3778
+ * A focused note also gets drag-to-resize corner handles — notes have no
3779
+ * gizmo of their own (the native selection gizmo is ink-stroke/shape-only),
3780
+ * so this is that capability's default-ui-owned equivalent. Proportional
3781
+ * (always-square) resize from the note's own centre, clamped to
3782
+ * `[MIN_SIZE, MAX_SIZE]`, independent of which corner is grabbed.
3783
+ */
3784
+ declare function FocusedItemToolbar({ controller, snapshot }: FocusedItemToolbarProps): react.JSX.Element | null;
3785
+
1770
3786
  interface StyleShelfProps {
1771
3787
  controller: BoardController;
1772
3788
  snapshot: BoardSnapshot;
3789
+ /**
3790
+ * Root-relative point beside the active tool. DefaultBoardChrome supplies
3791
+ * this automatically and keeps the shelf within the board bounds.
3792
+ */
3793
+ anchor?: {
3794
+ x: number;
3795
+ y: number;
3796
+ };
3797
+ /**
3798
+ * Called after an option is picked (not while typing table dimensions or a
3799
+ * sticker search). DefaultBoardChrome uses it to close the shelf.
3800
+ */
3801
+ onChoose?(): void;
1773
3802
  }
1774
3803
  /** Contextual per-tool style controls — visible while a styleable tool is active. */
1775
- declare function StyleShelf({ controller, snapshot }: StyleShelfProps): react.JSX.Element | null;
3804
+ declare function StyleShelf({ controller, snapshot, anchor, onChoose }: StyleShelfProps): react.JSX.Element | null;
1776
3805
 
1777
3806
  type ThemeStyle = CSSProperties & Record<`--scrawl-${string}`, string | number | undefined>;
1778
3807
  interface ScrawlProviderProps {
@@ -1786,6 +3815,16 @@ interface ScrawlProviderProps {
1786
3815
  className?: string;
1787
3816
  style?: ThemeStyle;
1788
3817
  }
3818
+ /**
3819
+ * Wraps a Board `controller` you created yourself (via `createBoardController`)
3820
+ * so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
3821
+ * and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
3822
+ * Prefer `Scrawl` unless you need to construct or own the controller's
3823
+ * lifecycle yourself (e.g. you create it outside React, or need it before
3824
+ * first render). Set `disposeOnUnmount` to have this provider call
3825
+ * `controller.dispose()` on unmount; otherwise disposal remains your own
3826
+ * responsibility.
3827
+ */
1789
3828
  declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
1790
3829
  interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1791
3830
  children?: ReactNode;
@@ -1799,8 +3838,35 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1799
3838
  onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
1800
3839
  /** Provide null for a headless Board, or an existing canvas to control its identity. */
1801
3840
  canvas?: HTMLCanvasElement | null;
3841
+ /**
3842
+ * Per-tool icon override for the default toolbar and More tools buttons —
3843
+ * only applies when you don't supply `children` (i.e. you're using the
3844
+ * SDK's default UI). See DefaultBoardChromeProps.icons.
3845
+ */
3846
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
3847
+ /** Offer a Share button in the default UI's board actions — see DefaultBoardChromeProps.onShare. */
3848
+ onShare?(): void;
3849
+ /** Your backend's activity feed — see DefaultBoardChromeProps.activity. */
3850
+ activity?: BoardActivityFeed;
3851
+ /** Unread state for the Comments drawer — see DefaultBoardChromeProps.commentFeed. */
3852
+ commentFeed?: BoardCommentFeed;
3853
+ /** Members and access levels from your backend — see DefaultBoardChromeProps.permissions. */
3854
+ permissions?: BoardPermissions;
3855
+ /** Extra More-menu actions — see DefaultBoardChromeProps.menuItems. */
3856
+ menuItems?: readonly BoardMenuItem[];
1802
3857
  }
1803
- declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }: ScrawlProps): react.JSX.Element;
3858
+ /**
3859
+ * The fastest path to an embedded Board: creates and owns a
3860
+ * `BoardController` for you (constructed once, disposed on unmount) and
3861
+ * renders it into a canvas. Render with no `children` to get the SDK's
3862
+ * default toolbar/UI chrome, or supply your own `children` (using the
3863
+ * `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
3864
+ * `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
3865
+ * top of the same controller. Accepts every `CreateBoardControllerOptions`
3866
+ * field except `canvas` (pass `canvas={null}` for a headless board, or an
3867
+ * existing `<canvas>` element to control its identity yourself).
3868
+ */
3869
+ declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, onShare, activity, commentFeed, permissions, menuItems, ...options }: ScrawlProps): react.JSX.Element;
1804
3870
  interface ScrawlCanvasProps {
1805
3871
  element?: HTMLCanvasElement;
1806
3872
  className?: string;
@@ -1815,14 +3881,60 @@ interface ScrawlDefaultUIProps {
1815
3881
  regions?: Partial<Record<DefaultUIRegion, boolean>>;
1816
3882
  /** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
1817
3883
  slots?: DefaultUISlots;
1818
- }
1819
- declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
3884
+ /** Per-tool icon override for the toolbar and More tools buttons — see DefaultBoardChromeProps.icons. */
3885
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
3886
+ /** Author shown on comments left through the default comment UI — see DefaultBoardChromeProps.commentAuthor. */
3887
+ commentAuthor?: CommentAuthor;
3888
+ /** Supply to offer a Share button in the board actions — see DefaultBoardChromeProps.onShare. */
3889
+ onShare?(): void;
3890
+ /** Your backend's activity feed — see DefaultBoardChromeProps.activity. */
3891
+ activity?: BoardActivityFeed;
3892
+ /** Unread state for the Comments drawer — see DefaultBoardChromeProps.commentFeed. */
3893
+ commentFeed?: BoardCommentFeed;
3894
+ /** Members and access levels from your backend — see DefaultBoardChromeProps.permissions. */
3895
+ permissions?: BoardPermissions;
3896
+ /** Extra More-menu actions — see DefaultBoardChromeProps.menuItems. */
3897
+ menuItems?: readonly BoardMenuItem[];
3898
+ }
3899
+ declare function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }: ScrawlDefaultUIProps): react.JSX.Element;
1820
3900
  declare function ScrawlPortal({ children }: {
1821
3901
  children: ReactNode;
1822
3902
  }): react.ReactPortal | null;
1823
3903
  declare function useScrawlController(): BoardController;
1824
3904
  declare function useScrawlTheme(): ScrawlResolvedTheme;
1825
3905
  declare function useScrawlSnapshot(): BoardSnapshot;
3906
+ /**
3907
+ * A thin, purely-derived convenience over
3908
+ * `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
3909
+ * component that only cares about save status (e.g. a
3910
+ * "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
3911
+ * this same field access itself. Adds no state and no behavior of its
3912
+ * own: React still owns none of persistence, exactly as before — this
3913
+ * hook only re-reads what the controller already tracks.
3914
+ *
3915
+ * Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
3916
+ * `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
3917
+ * `connection.persistence` object — on every document mutation, even ones
3918
+ * that never touch persistence at all, so a component using only this
3919
+ * hook would otherwise re-render on every stroke draw. `useSyncStatus`
3920
+ * below memoizes on the value (`state`/`error`), not just the object
3921
+ * reference, and returns the same cached value across renders where
3922
+ * nothing relevant changed — the same shape `usePresence` already uses
3923
+ * for its own independently-scoped store.
3924
+ */
3925
+ declare function usePersistenceStatus(): PersistenceSnapshot;
3926
+ /** The `CollaborationSnapshot` equivalent of `usePersistenceStatus` (Phase 6) — same value-memoized convenience over `useScrawlSnapshot().connection.collaboration` (Phase 9: see `usePersistenceStatus`'s doc comment for why it doesn't derive from the full snapshot). */
3927
+ declare function useCollaborationStatus(): CollaborationSnapshot;
3928
+ /**
3929
+ * The current presence roster (Phase 6), live-updating — wraps
3930
+ * `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
3931
+ * wraps the controller's main snapshot, via `useSyncExternalStore`. Does
3932
+ * not re-render on every remote pointer move by itself; it re-renders
3933
+ * whenever the roster the controller already tracks changes, at whatever
3934
+ * rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
3935
+ */
3936
+ declare function usePresence(): readonly PresenceUser[];
3937
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1826
3938
  type ScrawlBoardProps = {
1827
3939
  documentId: string;
1828
3940
  initialDocument?: SerializedBoardDocument;
@@ -1830,7 +3942,8 @@ type ScrawlBoardProps = {
1830
3942
  className?: string;
1831
3943
  style?: CSSProperties;
1832
3944
  };
3945
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1833
3946
  declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
1834
3947
 
1835
- 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, AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DefaultBoardChrome, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, InlineEditors, LockItemsCommand, MIN_WIDTH_FACTOR, MultiplayerCursors, 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, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, SpatialIndex, StyleShelf, 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, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneCustomObject, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, createBoardController, createLocalBoard, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
1836
- export type { ApplyOpsResult, AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardController, BoardControllerError, BoardEventMap, BoardKeyInput, BoardObject, BoardObjectInput, BoardObjectPatch, BoardPoint, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStroke, BoardStyle, BoardView, BuiltInTool, ClusterIdFactory, CollaborationAdapter, CollaborationReceiver, CollaborationSession, CollaborationSnapshot, CollaboratorIdentity, Command, CommandKind, CommentMarker, ControllerOp, CreateBoardControllerOptions, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DeepReadonly, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, DocumentChange, DocumentContext, DocumentId, DocumentLoadResult, DocumentRecoveryCode, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, ImageBlock, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, KitchenTimer, LoadResult, LocalBoard, LocalBoardOptions, LocalBoardSnapshot, LockHolder, LockTarget, Lockable, Mat2x3, MultiplayerCursorsProps, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PersistenceAdapter, PersistenceDiagnostic, PersistenceSnapshot, PresenceCursor, PresencePlacement, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyBoardDocument, ReadonlyCustomObject, ReadonlyDocumentChange, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, ScreenPoint, ScreenRect, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, StyleShelfProps, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };
3948
+ 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, DefaultBoardChrome, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, EMOJI_LIBRARY, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, FocusedItemToolbar, HIGHLIGHT_COLORS, History, ICON_LIBRARY, IDENTITY, INK_COLORS, InlineEditors, LockItemsCommand, MIN_WIDTH_FACTOR, MultiplayerCursors, 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, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, SpatialIndex, StyleShelf, 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, createBoardController, createLocalBoard, createMemoryPersistence, documentId, documentSize, documentToSVG, emojiDataUrl, emojiDefinition, formatTimer, iconCategories, iconDataUrl, iconDefinition, invert, isAssetRef, isEmojiName, isIconName, isIdentity, isStampKind, isStickerKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, stickerDataUrl, stickerLabel, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useCollaborationStatus, usePersistenceStatus, usePresence, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
3949
+ export type { ApplyOpsResult, ArrowHeadStyle, ArrowObject, AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardActivityEntry, BoardActivityFeed, BoardActivityKind, BoardBounds, BoardCommentFeed, BoardController, BoardControllerError, BoardEventMap, BoardKeyInput, BoardMember, BoardMenuItem, BoardNotebook, BoardObject, BoardObjectInput, BoardObjectPatch, BoardPage, BoardPermissions, BoardPoint, BoardPointerInput, BoardRole, BoardRoleChange, BoardScene, BoardSection, BoardSlotProps, BoardSnapshot, BoardStroke, BoardStyle, BoardThemeOptions, BoardView, BuiltInTool, ClusterIdFactory, CollaborationAckDiagnostic, CollaborationAdapter, CollaborationReceiver, CollaborationSession, CollaborationSnapshot, CollaborationSyncResult, CollaboratorIdentity, Command, CommandKind, CommentAuthor, CommentMarker, ConnectorBinding, ConnectorBox, ConnectorTarget, ControllerOp, CreateBoardControllerOptions, CreateMemoryPersistenceOptions, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DeepReadonly, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, DocumentChange, DocumentContext, DocumentId, DocumentLoadResult, DocumentRecoveryCode, EllipseObject, EmojiDefinition, EmojiName, ExportDocumentSVGOptions, ExportDocumentSVGResult, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, GroupObject, HeartObject, IconDefinition, IconName, ImageBlock, InlineEditingTarget, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, KitchenTimer, LineObject, LoadResult, LocalBoard, LocalBoardOptions, LocalBoardSnapshot, LocalPresence, LockHolder, LockTarget, Lockable, Mat2x3, MultiplayerCursorsProps, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PersistenceAdapter, PersistenceDiagnostic, PersistenceSnapshot, PolygonObject, PresenceCursor, PresencePlacement, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyBoardDocument, ReadonlyCustomObject, ReadonlyDocumentChange, RectangleObject, ReorderDirection, ReplaceResult, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlSurfaceTexture, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, ScreenPoint, ScreenRect, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardPage, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StarObject, StickerKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, StyleShelfProps, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };