@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/react.d.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ComponentType, CSSProperties } from 'react';
3
3
 
4
- declare const strokeIdBrand: unique symbol;
5
- type StrokeId = string & {
6
- readonly [strokeIdBrand]: "StrokeId";
7
- };
8
-
9
4
  /** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
10
5
  type AssetRef = string;
11
6
  declare function isAssetRef(value: unknown): value is AssetRef;
@@ -155,6 +150,21 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
155
150
  /** One pure, synchronous step per consecutive schema version. */
156
151
  migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
157
152
  describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
153
+ /**
154
+ * Optional point-level hit-test precision (Phase 8). Every custom object
155
+ * hit-tests against its bounding box (`fallback.bounds`) by default — this
156
+ * lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
157
+ * reject a point that's inside that box but outside its actual visible
158
+ * silhouette, tightening a click/marquee/raycast hit to the shape's real
159
+ * outline. `point` is in this object's own local space — the same
160
+ * untransformed space `describe`'s returned geometry already lives in
161
+ * (the caller inverse-transforms the pointer's board point through
162
+ * `object.transform` before calling this). Absent means every point
163
+ * inside the bounding box hits, matching pre-Phase-8 behavior exactly.
164
+ * Rejecting a point here does not fall through to whatever's underneath —
165
+ * the gesture simply misses this object, same as clicking empty space.
166
+ */
167
+ hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
158
168
  }
159
169
  interface SceneNodeBase {
160
170
  key: string;
@@ -188,6 +198,15 @@ interface SceneGroup extends SceneNodeBase {
188
198
  kind: "group";
189
199
  children: readonly BoardScene[];
190
200
  }
201
+ /**
202
+ * **No renderer or SVG-export interpreter exists for this node kind yet**
203
+ * (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
204
+ * `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
205
+ * exports nothing, and contributes no hit-test bounds — it neither errors
206
+ * nor emits a diagnostic. Until an interpreter ships, build custom shapes
207
+ * from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
208
+ * instead.
209
+ */
191
210
  interface ScenePath extends SceneNodeBase {
192
211
  kind: "path";
193
212
  /** SVG-style path data, board-local coordinates. */
@@ -343,8 +362,23 @@ interface Lockable {
343
362
  lockedByName?: string;
344
363
  }
345
364
 
365
+ /**
366
+ * Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
367
+ * pattern exactly, but simpler: unlike a lock, hidden state carries no
368
+ * holder/ownership concept, so there's no analogue to `LockHolder`/
369
+ * `canUnlockItem`. A hidden object stays fully present in the Document
370
+ * (still serializes, persists, syncs, undoes/redoes) — it just skips
371
+ * rendering and hit-testing/selection candidacy. `hidden` absent or
372
+ * `false` means visible; this keeps every pre-Phase-8 document (which has
373
+ * no `hidden` field on any object at all) implicitly fully visible with
374
+ * zero migration needed.
375
+ */
376
+ interface Hideable {
377
+ hidden?: boolean;
378
+ }
379
+
346
380
  /** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
347
- interface KitchenTimer extends Lockable {
381
+ interface KitchenTimer extends Lockable, Hideable {
348
382
  id: string;
349
383
  x: number;
350
384
  y: number;
@@ -358,6 +392,146 @@ interface KitchenTimer extends Lockable {
358
392
  runningSince?: number;
359
393
  }
360
394
 
395
+ interface RectangleObject extends Lockable, Hideable {
396
+ id: string;
397
+ x: number;
398
+ y: number;
399
+ width: number;
400
+ height: number;
401
+ fill?: string;
402
+ stroke?: string;
403
+ strokeWidth?: number;
404
+ /** Corner radius in board units; clamped to at most half the shorter side at render time. */
405
+ cornerRadius?: number;
406
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
407
+ opacity?: number;
408
+ /**
409
+ * Radians, about the shape's own center `(x + width/2, y - height/2)`.
410
+ * Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
411
+ * shape's own unrotated local frame — rotation is a separate, applied-last
412
+ * transform, not baked into them, matching how Stroke/CustomBoardObject
413
+ * keep geometry and placement independent via their own `matrix`.
414
+ */
415
+ rotation?: number;
416
+ }
417
+ interface EllipseObject extends Lockable, Hideable {
418
+ id: string;
419
+ x: number;
420
+ y: number;
421
+ width: number;
422
+ height: number;
423
+ fill?: string;
424
+ stroke?: string;
425
+ strokeWidth?: number;
426
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
427
+ opacity?: number;
428
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
429
+ rotation?: number;
430
+ }
431
+ /** `"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. */
432
+ type ArrowHeadStyle = "triangle" | "none";
433
+ interface LineObject extends Lockable, Hideable {
434
+ id: string;
435
+ start: BoardPoint;
436
+ end: BoardPoint;
437
+ stroke?: string;
438
+ strokeWidth?: number;
439
+ opacity?: number;
440
+ }
441
+ /** An endpoint anchored to a normalized position on another object's bounds. */
442
+ interface ConnectorBinding {
443
+ objectId: string;
444
+ /** Horizontal and vertical fractions of the target's axis-aligned bounds. */
445
+ x: number;
446
+ y: number;
447
+ }
448
+ interface ArrowObject extends Lockable, Hideable {
449
+ /** Omitted for legacy arrows. Connectors share Arrow's persistence/history contract. */
450
+ routing?: "straight" | "curved" | "polyline";
451
+ /** Intermediate board-space vertices for a multi-point connector. */
452
+ waypoints?: BoardPoint[];
453
+ startBinding?: ConnectorBinding;
454
+ endBinding?: ConnectorBinding;
455
+ id: string;
456
+ start: BoardPoint;
457
+ end: BoardPoint;
458
+ head?: ArrowHeadStyle;
459
+ stroke?: string;
460
+ strokeWidth?: number;
461
+ opacity?: number;
462
+ }
463
+ /**
464
+ * Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
465
+ * type instead of five near-duplicate interfaces — a regular N-gon
466
+ * inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
467
+ * Rectangle already uses, parameterized by `sides`. Diamond is exactly a
468
+ * 4-sided regular polygon with vertex 0 pointing right (not up, like
469
+ * Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
470
+ * `polygonStartAngle`, which encodes each side count's own vertex
471
+ * orientation so the outline always matches the legacy drag-preview shape.
472
+ */
473
+ interface PolygonObject extends Lockable, Hideable {
474
+ id: string;
475
+ x: number;
476
+ y: number;
477
+ width: number;
478
+ height: number;
479
+ sides: 3 | 4 | 5 | 6 | 8;
480
+ fill?: string;
481
+ stroke?: string;
482
+ strokeWidth?: number;
483
+ opacity?: number;
484
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
485
+ rotation?: number;
486
+ }
487
+ /** 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`). */
488
+ interface StarObject extends Lockable, Hideable {
489
+ id: string;
490
+ x: number;
491
+ y: number;
492
+ width: number;
493
+ height: number;
494
+ /** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
495
+ points: number;
496
+ /** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
497
+ innerRadiusRatio: number;
498
+ fill?: string;
499
+ stroke?: string;
500
+ strokeWidth?: number;
501
+ opacity?: number;
502
+ rotation?: number;
503
+ }
504
+ /** 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. */
505
+ interface HeartObject extends Lockable, Hideable {
506
+ id: string;
507
+ x: number;
508
+ y: number;
509
+ width: number;
510
+ height: number;
511
+ fill?: string;
512
+ stroke?: string;
513
+ strokeWidth?: number;
514
+ opacity?: number;
515
+ rotation?: number;
516
+ }
517
+ /**
518
+ * A logical grouping of other board objects (Phase 3 — Selection,
519
+ * Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
520
+ * own — a group's bounds are always derived on demand from its (recursively
521
+ * resolved) children, and "moving/rotating/scaling the group" is exactly a
522
+ * multi-object transform applied to those children, nothing more. A group
523
+ * has no renderer/mesh of its own; its only visual presence is the
524
+ * selection gizmo's bounding box while it's the current selection.
525
+ *
526
+ * `children` may itself contain other group ids (nested groups) — expanding
527
+ * a group into its leaf members is always done by the caller (recursively,
528
+ * with cycle protection), never assumed here.
529
+ */
530
+ interface GroupObject extends Lockable, Hideable {
531
+ id: string;
532
+ children: string[];
533
+ }
534
+
361
535
  interface BoardPoint {
362
536
  x: number;
363
537
  y: number;
@@ -371,9 +545,14 @@ interface StrokePoint extends BoardPoint {
371
545
  */
372
546
  erase?: number;
373
547
  }
374
- /** Which drawing tool made a stroke; undefined means marker (back-compat). */
375
- type StrokeTool = "marker" | "highlighter";
376
- interface Stroke extends Lockable {
548
+ /**
549
+ * Which drawing tool made a stroke; undefined means marker (back-compat).
550
+ * `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
551
+ * exact configured width with no pressure variance or end taper — a
552
+ * geometric outline, not an expressive ink mark.
553
+ */
554
+ type StrokeTool = "marker" | "highlighter" | "shape";
555
+ interface Stroke extends Lockable, Hideable {
377
556
  id: string;
378
557
  color: string;
379
558
  baseWidth: number;
@@ -389,7 +568,7 @@ interface Stroke extends Lockable {
389
568
  clusterId?: string;
390
569
  }
391
570
  type SerializedPoint = [number, number, number, number];
392
- interface SerializedStroke extends Lockable {
571
+ interface SerializedStroke extends Lockable, Hideable {
393
572
  id: string;
394
573
  color: string;
395
574
  baseWidth: number;
@@ -399,7 +578,36 @@ interface SerializedStroke extends Lockable {
399
578
  matrix?: [number, number, number, number, number, number];
400
579
  clusterId?: string;
401
580
  }
581
+ /** A named notebook containing sections. */
582
+ interface BoardNotebook {
583
+ readonly id: string;
584
+ readonly name: string;
585
+ readonly color: string;
586
+ }
587
+ /** A named section containing pages. */
588
+ interface BoardSection {
589
+ readonly id: string;
590
+ readonly notebookId: string;
591
+ readonly name: string;
592
+ readonly color: string;
593
+ }
594
+ /** An additional page. The first page remains in the document's top-level collections. */
595
+ interface SerializedBoardPage {
596
+ id: string;
597
+ sectionId?: string;
598
+ name: string;
599
+ content: SerializedDocument;
600
+ }
402
601
  interface SerializedDocument {
602
+ /** Notebook and section metadata. Older boards use an implicit default of each. */
603
+ notebooks?: BoardNotebook[];
604
+ sections?: BoardSection[];
605
+ /** Section of the original first page; defaults to `default`. */
606
+ pageSectionId?: string;
607
+ /** Display name of the first page (id `default`). */
608
+ pageName?: string;
609
+ /** Additional pages, in sidebar order. Nested pages are not allowed. */
610
+ pages?: SerializedBoardPage[];
403
611
  /** Absent in every historical document; current saves always write 1. */
404
612
  schemaVersion?: 1;
405
613
  strokes: SerializedStroke[];
@@ -415,6 +623,31 @@ interface SerializedDocument {
415
623
  timers?: KitchenTimer[];
416
624
  /** Absent in documents saved before Custom board objects existed (ticket #22). */
417
625
  customObjects?: CustomBoardObject[];
626
+ /** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
627
+ rectangles?: RectangleObject[];
628
+ /** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
629
+ ellipses?: EllipseObject[];
630
+ /** Absent in documents saved before Groups existed (Phase 3). */
631
+ groups?: GroupObject[];
632
+ /** Absent in documents saved before semantic Line objects existed (Phase 4). */
633
+ lines?: LineObject[];
634
+ /** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
635
+ arrows?: ArrowObject[];
636
+ /** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
637
+ polygons?: PolygonObject[];
638
+ /** Absent in documents saved before semantic Star objects existed (Phase 4). */
639
+ stars?: StarObject[];
640
+ /** Absent in documents saved before semantic Heart objects existed (Phase 4). */
641
+ hearts?: HeartObject[];
642
+ /**
643
+ * Every content-object id (every type above except comments, which are
644
+ * host-synced and never enter this schema) in paint order, back to front.
645
+ * Absent in documents saved before per-object z-order existed (Phase 3) —
646
+ * migration synthesizes a default order preserving the old fixed-Z-band
647
+ * visual stacking exactly, so an existing document never visibly changes
648
+ * on load; only an explicit reorder action touches this from then on.
649
+ */
650
+ objectOrder?: string[];
418
651
  }
419
652
  /**
420
653
  * One collaborator's vote on a note. One per person; toggling removes it.
@@ -428,7 +661,7 @@ interface NoteVote {
428
661
  * A sticky note: content floating above the board at a z-offset (pillar 3 —
429
662
  * depth as an organizational axis). Center position in board space.
430
663
  */
431
- interface StickyNote extends Lockable {
664
+ interface StickyNote extends Lockable, Hideable {
432
665
  id: string;
433
666
  x: number;
434
667
  y: number;
@@ -446,7 +679,7 @@ interface StickyNote extends Lockable {
446
679
  * top-left corner; lines flow downward (-y). Text joins the clustering
447
680
  * system like handwriting (build prompt §6.4).
448
681
  */
449
- interface TextBlock extends Lockable {
682
+ interface TextBlock extends Lockable, Hideable {
450
683
  id: string;
451
684
  x: number;
452
685
  y: number;
@@ -460,7 +693,7 @@ interface TextBlock extends Lockable {
460
693
  * Interactive structured table on the board. Position (x, y) is top-left in board units.
461
694
  * Cells are indexed as `${row},${col}` keys mapping to cell text content.
462
695
  */
463
- interface TableBlock extends Lockable {
696
+ interface TableBlock extends Lockable, Hideable {
464
697
  id: string;
465
698
  x: number;
466
699
  y: number;
@@ -477,7 +710,7 @@ interface TableBlock extends Lockable {
477
710
  * An imported image block on the board plane.
478
711
  * Coordinates (x, y) represent the center of the image in board space.
479
712
  */
480
- interface ImageBlock extends Lockable {
713
+ interface ImageBlock extends Lockable, Hideable {
481
714
  id: string;
482
715
  /**
483
716
  * A legacy, read-only data URL (or, historically, an arbitrary string) —
@@ -500,13 +733,23 @@ interface ImageBlock extends Lockable {
500
733
  stamp?: string;
501
734
  }
502
735
 
736
+ declare const strokeIdBrand: unique symbol;
737
+ type StrokeId = string & {
738
+ readonly [strokeIdBrand]: "StrokeId";
739
+ };
740
+
503
741
  declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
504
742
  type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
505
743
  id: StrokeId;
506
744
  };
507
- interface CurrentSerializedDocument extends Required<SerializedDocument> {
745
+ interface CurrentSerializedDocument extends Required<Omit<SerializedDocument, "pages" | "pageName" | "notebooks" | "sections" | "pageSectionId">> {
508
746
  schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
509
747
  strokes: CurrentSerializedStroke[];
748
+ pageName?: string;
749
+ notebooks?: BoardNotebook[];
750
+ sections?: BoardSection[];
751
+ pageSectionId?: string;
752
+ pages?: SerializedBoardPage[];
510
753
  }
511
754
 
512
755
  interface SearchableComment {
@@ -532,6 +775,490 @@ interface SearchHit {
532
775
  /** A stamp is a small sticker dropped on the board — not ink, not a photo. */
533
776
  type StampKind = "star" | "check" | "ship" | "heart" | "plus" | "fire";
534
777
 
778
+ declare const ICON_LIBRARY: readonly [{
779
+ readonly name: "lucide:check";
780
+ readonly label: "Check";
781
+ readonly category: "Status";
782
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>";
783
+ }, {
784
+ readonly name: "lucide:x";
785
+ readonly label: "Cross";
786
+ readonly category: "Status";
787
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>";
788
+ }, {
789
+ readonly name: "lucide:star";
790
+ readonly label: "Star";
791
+ readonly category: "Status";
792
+ 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\"/>";
793
+ }, {
794
+ readonly name: "lucide:heart";
795
+ readonly label: "Heart";
796
+ readonly category: "Status";
797
+ 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\"/>";
798
+ }, {
799
+ readonly name: "lucide:flag";
800
+ readonly label: "Flag";
801
+ readonly category: "Status";
802
+ 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\"/>";
803
+ }, {
804
+ readonly name: "lucide:bookmark";
805
+ readonly label: "Bookmark";
806
+ readonly category: "Status";
807
+ 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\"/>";
808
+ }, {
809
+ readonly name: "lucide:bell";
810
+ readonly label: "Bell";
811
+ readonly category: "Status";
812
+ 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\"/>";
813
+ }, {
814
+ readonly name: "lucide:info";
815
+ readonly label: "Info";
816
+ readonly category: "Status";
817
+ 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>";
818
+ }, {
819
+ readonly name: "lucide:circle-alert";
820
+ readonly label: "Alert";
821
+ readonly category: "Status";
822
+ 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>";
823
+ }, {
824
+ readonly name: "lucide:circle-question-mark";
825
+ readonly label: "Question";
826
+ readonly category: "Status";
827
+ 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>";
828
+ }, {
829
+ readonly name: "lucide:thumbs-up";
830
+ readonly label: "Thumbs up";
831
+ readonly category: "Status";
832
+ 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\"/>";
833
+ }, {
834
+ readonly name: "lucide:thumbs-down";
835
+ readonly label: "Thumbs down";
836
+ readonly category: "Status";
837
+ 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\"/>";
838
+ }, {
839
+ readonly name: "lucide:arrow-up";
840
+ readonly label: "Arrow up";
841
+ readonly category: "Arrows";
842
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>";
843
+ }, {
844
+ readonly name: "lucide:arrow-down";
845
+ readonly label: "Arrow down";
846
+ readonly category: "Arrows";
847
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>";
848
+ }, {
849
+ readonly name: "lucide:arrow-left";
850
+ readonly label: "Arrow left";
851
+ readonly category: "Arrows";
852
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"/>";
853
+ }, {
854
+ readonly name: "lucide:arrow-right";
855
+ readonly label: "Arrow right";
856
+ readonly category: "Arrows";
857
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"/>";
858
+ }, {
859
+ readonly name: "lucide:arrow-up-right";
860
+ readonly label: "Arrow up-right";
861
+ readonly category: "Arrows";
862
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h10v10M7 17L17 7\"/>";
863
+ }, {
864
+ readonly name: "lucide:refresh-cw";
865
+ readonly label: "Refresh";
866
+ readonly category: "Arrows";
867
+ 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>";
868
+ }, {
869
+ readonly name: "lucide:move";
870
+ readonly label: "Move";
871
+ readonly category: "Arrows";
872
+ 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\"/>";
873
+ }, {
874
+ readonly name: "lucide:shuffle";
875
+ readonly label: "Shuffle";
876
+ readonly category: "Arrows";
877
+ 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>";
878
+ }, {
879
+ readonly name: "lucide:user";
880
+ readonly label: "User";
881
+ readonly category: "People";
882
+ 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>";
883
+ }, {
884
+ readonly name: "lucide:users";
885
+ readonly label: "Users";
886
+ readonly category: "People";
887
+ 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>";
888
+ }, {
889
+ readonly name: "lucide:briefcase";
890
+ readonly label: "Briefcase";
891
+ readonly category: "People";
892
+ 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>";
893
+ }, {
894
+ readonly name: "lucide:building";
895
+ readonly label: "Building";
896
+ readonly category: "People";
897
+ 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>";
898
+ }, {
899
+ readonly name: "lucide:calendar";
900
+ readonly label: "Calendar";
901
+ readonly category: "People";
902
+ 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>";
903
+ }, {
904
+ readonly name: "lucide:clock";
905
+ readonly label: "Clock";
906
+ readonly category: "People";
907
+ 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>";
908
+ }, {
909
+ readonly name: "lucide:mail";
910
+ readonly label: "Mail";
911
+ readonly category: "People";
912
+ 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>";
913
+ }, {
914
+ readonly name: "lucide:message-circle";
915
+ readonly label: "Message";
916
+ readonly category: "People";
917
+ 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\"/>";
918
+ }, {
919
+ readonly name: "lucide:file";
920
+ readonly label: "File";
921
+ readonly category: "Objects";
922
+ 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>";
923
+ }, {
924
+ readonly name: "lucide:folder";
925
+ readonly label: "Folder";
926
+ readonly category: "Objects";
927
+ 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\"/>";
928
+ }, {
929
+ readonly name: "lucide:image";
930
+ readonly label: "Image";
931
+ readonly category: "Objects";
932
+ 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>";
933
+ }, {
934
+ readonly name: "lucide:link";
935
+ readonly label: "Link";
936
+ readonly category: "Objects";
937
+ 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>";
938
+ }, {
939
+ readonly name: "lucide:paperclip";
940
+ readonly label: "Paperclip";
941
+ readonly category: "Objects";
942
+ 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\"/>";
943
+ }, {
944
+ readonly name: "lucide:pin";
945
+ readonly label: "Pin";
946
+ readonly category: "Objects";
947
+ 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\"/>";
948
+ }, {
949
+ readonly name: "lucide:map-pin";
950
+ readonly label: "Map pin";
951
+ readonly category: "Objects";
952
+ 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>";
953
+ }, {
954
+ readonly name: "lucide:search";
955
+ readonly label: "Search";
956
+ readonly category: "Objects";
957
+ 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>";
958
+ }, {
959
+ readonly name: "lucide:settings";
960
+ readonly label: "Settings";
961
+ readonly category: "Objects";
962
+ 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>";
963
+ }, {
964
+ readonly name: "lucide:trash-2";
965
+ readonly label: "Trash";
966
+ readonly category: "Objects";
967
+ 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\"/>";
968
+ }, {
969
+ readonly name: "lucide:lock";
970
+ readonly label: "Lock";
971
+ readonly category: "Objects";
972
+ 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>";
973
+ }, {
974
+ readonly name: "lucide:key";
975
+ readonly label: "Key";
976
+ readonly category: "Objects";
977
+ 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>";
978
+ }, {
979
+ readonly name: "lucide:code";
980
+ readonly label: "Code";
981
+ readonly category: "Tech";
982
+ 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\"/>";
983
+ }, {
984
+ readonly name: "lucide:database";
985
+ readonly label: "Database";
986
+ readonly category: "Tech";
987
+ 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>";
988
+ }, {
989
+ readonly name: "lucide:server";
990
+ readonly label: "Server";
991
+ readonly category: "Tech";
992
+ 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>";
993
+ }, {
994
+ readonly name: "lucide:cloud";
995
+ readonly label: "Cloud";
996
+ readonly category: "Tech";
997
+ 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\"/>";
998
+ }, {
999
+ readonly name: "lucide:git-branch";
1000
+ readonly label: "Branch";
1001
+ readonly category: "Tech";
1002
+ 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>";
1003
+ }, {
1004
+ readonly name: "lucide:terminal";
1005
+ readonly label: "Terminal";
1006
+ readonly category: "Tech";
1007
+ readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19h8M4 17l6-6l-6-6\"/>";
1008
+ }, {
1009
+ readonly name: "lucide:cpu";
1010
+ readonly label: "CPU";
1011
+ readonly category: "Tech";
1012
+ 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>";
1013
+ }, {
1014
+ readonly name: "lucide:bug";
1015
+ readonly label: "Bug";
1016
+ readonly category: "Tech";
1017
+ 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>";
1018
+ }, {
1019
+ readonly name: "lucide:lightbulb";
1020
+ readonly label: "Idea";
1021
+ readonly category: "Ideas";
1022
+ 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\"/>";
1023
+ }, {
1024
+ readonly name: "lucide:zap";
1025
+ readonly label: "Zap";
1026
+ readonly category: "Ideas";
1027
+ 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\"/>";
1028
+ }, {
1029
+ readonly name: "lucide:target";
1030
+ readonly label: "Target";
1031
+ readonly category: "Ideas";
1032
+ 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>";
1033
+ }, {
1034
+ readonly name: "lucide:rocket";
1035
+ readonly label: "Rocket";
1036
+ readonly category: "Ideas";
1037
+ 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>";
1038
+ }, {
1039
+ readonly name: "lucide:trophy";
1040
+ readonly label: "Trophy";
1041
+ readonly category: "Ideas";
1042
+ 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>";
1043
+ }, {
1044
+ readonly name: "lucide:gift";
1045
+ readonly label: "Gift";
1046
+ readonly category: "Ideas";
1047
+ 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>";
1048
+ }, {
1049
+ readonly name: "lucide:coffee";
1050
+ readonly label: "Coffee";
1051
+ readonly category: "Ideas";
1052
+ 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\"/>";
1053
+ }, {
1054
+ readonly name: "lucide:smile";
1055
+ readonly label: "Smile";
1056
+ readonly category: "Ideas";
1057
+ 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>";
1058
+ }];
1059
+ /** Every vendored icon's id, as a literal union. */
1060
+ type IconName = (typeof ICON_LIBRARY)[number]["name"];
1061
+
1062
+ declare const EMOJI_LIBRARY: readonly [{
1063
+ readonly name: "emoji:grinning";
1064
+ readonly label: "Grinning face";
1065
+ readonly category: "Smileys";
1066
+ readonly char: "😀";
1067
+ }, {
1068
+ readonly name: "emoji:joy";
1069
+ readonly label: "Tears of joy";
1070
+ readonly category: "Smileys";
1071
+ readonly char: "😂";
1072
+ }, {
1073
+ readonly name: "emoji:smiling";
1074
+ readonly label: "Smiling face";
1075
+ readonly category: "Smileys";
1076
+ readonly char: "😊";
1077
+ }, {
1078
+ readonly name: "emoji:heart-eyes";
1079
+ readonly label: "Heart eyes";
1080
+ readonly category: "Smileys";
1081
+ readonly char: "😍";
1082
+ }, {
1083
+ readonly name: "emoji:thinking";
1084
+ readonly label: "Thinking face";
1085
+ readonly category: "Smileys";
1086
+ readonly char: "🤔";
1087
+ }, {
1088
+ readonly name: "emoji:sunglasses";
1089
+ readonly label: "Sunglasses";
1090
+ readonly category: "Smileys";
1091
+ readonly char: "😎";
1092
+ }, {
1093
+ readonly name: "emoji:crying";
1094
+ readonly label: "Crying face";
1095
+ readonly category: "Smileys";
1096
+ readonly char: "😢";
1097
+ }, {
1098
+ readonly name: "emoji:angry";
1099
+ readonly label: "Angry face";
1100
+ readonly category: "Smileys";
1101
+ readonly char: "😡";
1102
+ }, {
1103
+ readonly name: "emoji:sleeping";
1104
+ readonly label: "Sleeping face";
1105
+ readonly category: "Smileys";
1106
+ readonly char: "😴";
1107
+ }, {
1108
+ readonly name: "emoji:mind-blown";
1109
+ readonly label: "Mind blown";
1110
+ readonly category: "Smileys";
1111
+ readonly char: "🤯";
1112
+ }, {
1113
+ readonly name: "emoji:grimacing";
1114
+ readonly label: "Grimacing face";
1115
+ readonly category: "Smileys";
1116
+ readonly char: "😬";
1117
+ }, {
1118
+ readonly name: "emoji:partying";
1119
+ readonly label: "Partying face";
1120
+ readonly category: "Smileys";
1121
+ readonly char: "🥳";
1122
+ }, {
1123
+ readonly name: "emoji:thumbs-up";
1124
+ readonly label: "Thumbs up emoji";
1125
+ readonly category: "Gestures";
1126
+ readonly char: "👍";
1127
+ }, {
1128
+ readonly name: "emoji:thumbs-down";
1129
+ readonly label: "Thumbs down emoji";
1130
+ readonly category: "Gestures";
1131
+ readonly char: "👎";
1132
+ }, {
1133
+ readonly name: "emoji:clap";
1134
+ readonly label: "Clapping hands";
1135
+ readonly category: "Gestures";
1136
+ readonly char: "👏";
1137
+ }, {
1138
+ readonly name: "emoji:raised-hands";
1139
+ readonly label: "Raised hands";
1140
+ readonly category: "Gestures";
1141
+ readonly char: "🙌";
1142
+ }, {
1143
+ readonly name: "emoji:folded-hands";
1144
+ readonly label: "Folded hands";
1145
+ readonly category: "Gestures";
1146
+ readonly char: "🙏";
1147
+ }, {
1148
+ readonly name: "emoji:eyes";
1149
+ readonly label: "Eyes";
1150
+ readonly category: "Gestures";
1151
+ readonly char: "👀";
1152
+ }, {
1153
+ readonly name: "emoji:muscle";
1154
+ readonly label: "Flexed biceps";
1155
+ readonly category: "Gestures";
1156
+ readonly char: "💪";
1157
+ }, {
1158
+ readonly name: "emoji:wave";
1159
+ readonly label: "Waving hand";
1160
+ readonly category: "Gestures";
1161
+ readonly char: "👋";
1162
+ }, {
1163
+ readonly name: "emoji:red-heart";
1164
+ readonly label: "Red heart";
1165
+ readonly category: "Symbols";
1166
+ readonly char: "❤️";
1167
+ }, {
1168
+ readonly name: "emoji:fire";
1169
+ readonly label: "Flame";
1170
+ readonly category: "Symbols";
1171
+ readonly char: "🔥";
1172
+ }, {
1173
+ readonly name: "emoji:star";
1174
+ readonly label: "Medium star";
1175
+ readonly category: "Symbols";
1176
+ readonly char: "⭐";
1177
+ }, {
1178
+ readonly name: "emoji:check";
1179
+ readonly label: "Check mark button";
1180
+ readonly category: "Symbols";
1181
+ readonly char: "✅";
1182
+ }, {
1183
+ readonly name: "emoji:cross";
1184
+ readonly label: "Cross mark";
1185
+ readonly category: "Symbols";
1186
+ readonly char: "❌";
1187
+ }, {
1188
+ readonly name: "emoji:warning";
1189
+ readonly label: "Warning sign";
1190
+ readonly category: "Symbols";
1191
+ readonly char: "⚠️";
1192
+ }, {
1193
+ readonly name: "emoji:question";
1194
+ readonly label: "Red question mark";
1195
+ readonly category: "Symbols";
1196
+ readonly char: "❓";
1197
+ }, {
1198
+ readonly name: "emoji:hundred";
1199
+ readonly label: "Hundred points";
1200
+ readonly category: "Symbols";
1201
+ readonly char: "💯";
1202
+ }, {
1203
+ readonly name: "emoji:light-bulb";
1204
+ readonly label: "Light bulb";
1205
+ readonly category: "Symbols";
1206
+ readonly char: "💡";
1207
+ }, {
1208
+ readonly name: "emoji:bullseye";
1209
+ readonly label: "Bullseye";
1210
+ readonly category: "Symbols";
1211
+ readonly char: "🎯";
1212
+ }, {
1213
+ readonly name: "emoji:party-popper";
1214
+ readonly label: "Party popper";
1215
+ readonly category: "Objects";
1216
+ readonly char: "🎉";
1217
+ }, {
1218
+ readonly name: "emoji:rocket";
1219
+ readonly label: "Rocket emoji";
1220
+ readonly category: "Objects";
1221
+ readonly char: "🚀";
1222
+ }, {
1223
+ readonly name: "emoji:trophy";
1224
+ readonly label: "Trophy emoji";
1225
+ readonly category: "Objects";
1226
+ readonly char: "🏆";
1227
+ }, {
1228
+ readonly name: "emoji:pushpin";
1229
+ readonly label: "Pushpin";
1230
+ readonly category: "Objects";
1231
+ readonly char: "📌";
1232
+ }, {
1233
+ readonly name: "emoji:memo";
1234
+ readonly label: "Memo";
1235
+ readonly category: "Objects";
1236
+ readonly char: "📝";
1237
+ }, {
1238
+ readonly name: "emoji:calendar";
1239
+ readonly label: "Tear-off calendar";
1240
+ readonly category: "Objects";
1241
+ readonly char: "📅";
1242
+ }, {
1243
+ readonly name: "emoji:alarm-clock";
1244
+ readonly label: "Alarm clock";
1245
+ readonly category: "Objects";
1246
+ readonly char: "⏰";
1247
+ }, {
1248
+ readonly name: "emoji:speech-balloon";
1249
+ readonly label: "Speech balloon";
1250
+ readonly category: "Objects";
1251
+ readonly char: "💬";
1252
+ }];
1253
+ type EmojiName = (typeof EMOJI_LIBRARY)[number]["name"];
1254
+
1255
+ /**
1256
+ * What the stamp tool will drop next: one of the six built-in stamp-pad
1257
+ * stickers, an emoji, or any vendored icon. All land on the board the same way
1258
+ * — as an image whose `stamp` field records which one it was (see `placeStamp`).
1259
+ */
1260
+ type StickerKind = StampKind | EmojiName | IconName;
1261
+
535
1262
  type SerializedBoardDocument = CurrentSerializedDocument;
536
1263
 
537
1264
  interface AssetExportFailure {
@@ -572,16 +1299,37 @@ interface BoardStyle {
572
1299
  readonly noteColor: string;
573
1300
  readonly tableRows: number;
574
1301
  readonly tableCols: number;
575
- readonly stampKind: StampKind;
1302
+ /** A stamp-pad kind or any vendored icon name (see `ICON_LIBRARY`). */
1303
+ readonly stampKind: StickerKind;
576
1304
  readonly timerDurationMs: number;
577
1305
  }
1306
+ /** A page listed in the Board navigator. `default` is the original first page. */
1307
+ interface BoardPage {
1308
+ readonly id: string;
1309
+ readonly name: string;
1310
+ readonly sectionId: string;
1311
+ }
578
1312
  interface BoardSnapshot {
1313
+ readonly notebooks: readonly BoardNotebook[];
1314
+ readonly sections: readonly BoardSection[];
1315
+ readonly activeNotebookId: string;
1316
+ readonly activeSectionId: string;
1317
+ readonly pages: readonly BoardPage[];
1318
+ readonly activePageId: string;
579
1319
  readonly status: "loading" | "ready" | "disposed";
580
1320
  readonly documentId: string;
581
1321
  readonly tool: BuiltInTool | (string & {});
582
1322
  readonly zoom: number;
583
1323
  readonly readOnly: boolean;
584
1324
  readonly selection: readonly string[];
1325
+ readonly focusedItem: FocusedItem | null;
1326
+ /**
1327
+ * What an inline editor currently covers, as last set through
1328
+ * `view.setInlineEditing` — a text block (`{ id }`) or a table cell
1329
+ * (`{ id, row, col }`), else null. Chrome uses it to stay out of an open
1330
+ * editor's way.
1331
+ */
1332
+ readonly inlineEditing: InlineEditingTarget | null;
585
1333
  readonly strokeCount: number;
586
1334
  readonly objectCount: number;
587
1335
  readonly canUndo: boolean;
@@ -592,6 +1340,39 @@ interface BoardSnapshot {
592
1340
  readonly collaboration: CollaborationSnapshot;
593
1341
  };
594
1342
  }
1343
+ /**
1344
+ * `selection` collapsed into the one thing a selected-object toolbar needs:
1345
+ * what's selected, its lock state, and where to anchor above it. `null`
1346
+ * when nothing is selected, or when the selection mixes types/objects that
1347
+ * don't resolve to a single focus (anything but one object, or several
1348
+ * strokes sharing a `clusterId` — a multi-stroke shape).
1349
+ *
1350
+ * Raw lock fields, not a derived `canUnlock` — this API has no notion of
1351
+ * "the local user" to judge that against (see `canUnlockItem` in `../core`,
1352
+ * which takes a `userId` the Host already owns). A Custom object's lock
1353
+ * shape doesn't carry a holder name, so it always reports `locked: false`
1354
+ * here, matching the engine's own internal selection-badge behavior.
1355
+ */
1356
+ /** Axis-aligned bounds in board space (y up). */
1357
+ interface BoardBounds {
1358
+ readonly minX: number;
1359
+ readonly minY: number;
1360
+ readonly maxX: number;
1361
+ readonly maxY: number;
1362
+ }
1363
+ interface InlineEditingTarget {
1364
+ readonly id: string;
1365
+ readonly row?: number;
1366
+ readonly col?: number;
1367
+ }
1368
+ interface FocusedItem {
1369
+ readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
1370
+ readonly id: string;
1371
+ readonly locked: boolean;
1372
+ readonly lockedBy?: string;
1373
+ readonly lockedByName?: string;
1374
+ readonly screenPosition?: ScreenPoint;
1375
+ }
595
1376
  interface BoardControllerError {
596
1377
  source: "controller" | "renderer" | "persistence" | "collaboration";
597
1378
  code: string;
@@ -600,6 +1381,9 @@ interface BoardControllerError {
600
1381
  }
601
1382
  interface BoardEventMap {
602
1383
  change: BoardSnapshot;
1384
+ "page-changing": {
1385
+ pageId: string;
1386
+ };
603
1387
  "tool-change": {
604
1388
  tool: string;
605
1389
  };
@@ -666,6 +1450,8 @@ interface BoardEventMap {
666
1450
  "asset-diagnostic": AssetDiagnostic;
667
1451
  /** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
668
1452
  "persistence-diagnostic": PersistenceDiagnostic;
1453
+ /** 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. */
1454
+ "collaboration-ops-acknowledged": CollaborationAckDiagnostic;
669
1455
  audit: unknown;
670
1456
  error: BoardControllerError;
671
1457
  disposed: undefined;
@@ -685,6 +1471,27 @@ interface BoardView {
685
1471
  y: number;
686
1472
  zoom: number;
687
1473
  }
1474
+ /**
1475
+ * The rendered board surface's color, reference grid, and shape stroke
1476
+ * width — the subset of {@link ScrawlTheme} that reaches the rendering
1477
+ * engine directly (everything else is UI-chrome-only, applied as CSS). Any
1478
+ * field left unset keeps its current value.
1479
+ */
1480
+ interface BoardThemeOptions {
1481
+ /** The board/canvas background color — distinct from UI chrome panels. */
1482
+ surface?: string;
1483
+ /** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
1484
+ surfaceTexture?: "flat" | "textured";
1485
+ /** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
1486
+ gridMode?: "none" | "line" | "dot";
1487
+ gridColor?: string;
1488
+ /** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
1489
+ gridSpacing?: number;
1490
+ /** On-screen grid line/dot width in CSS pixels — stays this width at any zoom, since the grid is a reference aid, not content. */
1491
+ gridLineWidth?: number;
1492
+ /** Shape (rectangle, ellipse, arrow, ...) border width, in board units — scales with zoom like ink, since it's part of the drawn content. */
1493
+ shapeStrokeWidth?: number;
1494
+ }
688
1495
  /**
689
1496
  * A Host-owned comment, summarized for Board-side search and marker
690
1497
  * rendering. Comments are not Document content — they carry no undo
@@ -708,9 +1515,12 @@ interface PresenceView {
708
1515
  readonly height: number;
709
1516
  }
710
1517
  /**
711
- * A Host-owned collaborator, synced in for cursor/roster rendering only.
712
- * Presence is ephemeral — it never touches the Document, Ops, undo/redo,
713
- * or persistence — so this is a read/query capability, not an adapter.
1518
+ * A collaborator, synced in for cursor/roster rendering only. Presence is
1519
+ * ephemeral — it never touches the Document, Ops, undo/redo, or persistence
1520
+ * (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
1521
+ * directly, or a `CollaborationAdapter`'s optional presence channel —
1522
+ * Phase 6, ADR 0015) both feed the exact same read/query capability below;
1523
+ * a Host picks one, not both, for a given controller.
714
1524
  */
715
1525
  interface PresenceUser {
716
1526
  readonly id: string;
@@ -719,6 +1529,14 @@ interface PresenceUser {
719
1529
  readonly tool?: string;
720
1530
  readonly cursor?: PresenceCursor;
721
1531
  readonly view?: PresenceView;
1532
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
1533
+ readonly metadata?: Record<string, unknown>;
1534
+ }
1535
+ /** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
1536
+ interface LocalPresence {
1537
+ readonly cursor?: PresenceCursor | null;
1538
+ readonly view?: PresenceView | null;
1539
+ readonly tool?: string;
722
1540
  }
723
1541
  /**
724
1542
  * The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
@@ -746,6 +1564,22 @@ type BoardObject = ({
746
1564
  } & ImageBlock) | ({
747
1565
  type: "timer";
748
1566
  } & KitchenTimer) | ({
1567
+ type: "rectangle";
1568
+ } & RectangleObject) | ({
1569
+ type: "ellipse";
1570
+ } & EllipseObject) | ({
1571
+ type: "group";
1572
+ } & GroupObject) | ({
1573
+ type: "line";
1574
+ } & LineObject) | ({
1575
+ type: "arrow";
1576
+ } & ArrowObject) | ({
1577
+ type: "polygon";
1578
+ } & PolygonObject) | ({
1579
+ type: "star";
1580
+ } & StarObject) | ({
1581
+ type: "heart";
1582
+ } & HeartObject) | ({
749
1583
  type: "custom";
750
1584
  customType: ObjectType;
751
1585
  } & Omit<CustomBoardObject, "type">);
@@ -774,6 +1608,30 @@ type BoardObjectInput = {
774
1608
  type: "timer";
775
1609
  id?: string;
776
1610
  } & Omit<KitchenTimer, "id">) | ({
1611
+ type: "rectangle";
1612
+ id?: string;
1613
+ } & Omit<RectangleObject, "id">) | ({
1614
+ type: "ellipse";
1615
+ id?: string;
1616
+ } & Omit<EllipseObject, "id">) | ({
1617
+ type: "group";
1618
+ id?: string;
1619
+ } & Omit<GroupObject, "id">) | ({
1620
+ type: "line";
1621
+ id?: string;
1622
+ } & Omit<LineObject, "id">) | ({
1623
+ type: "arrow";
1624
+ id?: string;
1625
+ } & Omit<ArrowObject, "id">) | ({
1626
+ type: "polygon";
1627
+ id?: string;
1628
+ } & Omit<PolygonObject, "id">) | ({
1629
+ type: "star";
1630
+ id?: string;
1631
+ } & Omit<StarObject, "id">) | ({
1632
+ type: "heart";
1633
+ id?: string;
1634
+ } & Omit<HeartObject, "id">) | ({
777
1635
  type: "custom";
778
1636
  id?: string;
779
1637
  customType: ObjectType;
@@ -804,10 +1662,34 @@ type LoadResult = {
804
1662
  } | {
805
1663
  state: "missing";
806
1664
  };
1665
+ /**
1666
+ * Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
1667
+ * "Whole-document writes survive only for create, clear-board and import,
1668
+ * where replacing everything is the actual intent"). Revision-gated, unlike
1669
+ * `applyOps` — `conflict` means `baseRevision` was stale (someone else's
1670
+ * write landed first); the caller must reload and never overwrites blind.
1671
+ */
1672
+ type ReplaceResult = {
1673
+ state: "applied";
1674
+ revision: string;
1675
+ } | {
1676
+ state: "conflict";
1677
+ currentRevision: string;
1678
+ };
1679
+ /**
1680
+ * The one sanctioned seam for persisting a Board's Document to a Host's own
1681
+ * storage — implement this against a database, an HTTP API, IndexedDB
1682
+ * (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
1683
+ * anything else. `load()` fetches the current state on connect; `applyOps()`
1684
+ * streams incremental Ops as edits happen; `replace()` is only for
1685
+ * whole-document writes (create, clear-board, import — see ADR 0006) and is
1686
+ * revision-gated so a stale write never silently clobbers a newer one.
1687
+ * Passed via `createBoardController({ adapters: { persistence } })`.
1688
+ */
807
1689
  interface PersistenceAdapter {
808
1690
  load(context: DocumentContext): Promise<LoadResult>;
809
1691
  applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
810
- replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<unknown>;
1692
+ replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
811
1693
  }
812
1694
  /**
813
1695
  * `"reconcile"` (ticket #24) means the server authoritatively resolved the
@@ -831,14 +1713,61 @@ interface PersistenceDiagnostic {
831
1713
  rejectedOpIds: readonly string[];
832
1714
  revision: string;
833
1715
  }
1716
+ /** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
1717
+ interface CollaborationAckDiagnostic {
1718
+ opIds: readonly string[];
1719
+ }
834
1720
  interface ControllerOp {
835
1721
  id: string;
836
1722
  schemaVersion: 1;
837
1723
  kind: "upsert" | "restore" | "remove";
838
- objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom";
1724
+ objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
1725
+ /**
1726
+ * A whole-document paint-order sync (Phase 3), not a per-object type —
1727
+ * `objectId` is always the fixed sentinel `"order"` and `payload` is
1728
+ * `{ order: string[] }`. The only `objectType` with no matching
1729
+ * `BoardObject`/document collection; kept in this same union (rather
1730
+ * than a separate wire message) so it flows through the existing
1731
+ * `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
1732
+ */
1733
+ | "order" | "page" | "notebook" | "section";
1734
+ /** Absent for the original page; scopes content and order Ops to an additional page. */
1735
+ pageId?: string;
839
1736
  objectId: string;
840
1737
  payload?: unknown;
1738
+ /**
1739
+ * This op's position in its own originating client's local sequence
1740
+ * (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
1741
+ * (an opaque, globally-unique identifier used for dedup/ack, not
1742
+ * ordering) and from a server's own authoritative ordering (e.g.
1743
+ * `referenceCollaborationServer.ts`'s per-room `version` counter).
1744
+ * Present on every op this SDK originates locally; a remote peer's op
1745
+ * carries whatever its own origin set, unchanged — never renumbered in
1746
+ * transit. Absent on an op minted by decoding the legacy wire envelope
1747
+ * (`scrawlOpEnvelope.ts`), which predates this field and has no
1748
+ * per-client sequence concept of its own.
1749
+ */
1750
+ clientSequence?: number;
1751
+ /**
1752
+ * The `CollaboratorIdentity.id` of this op's originating client (Phase
1753
+ * 7) — set for every op this SDK originates locally when `identity` is
1754
+ * configured, omitted entirely otherwise (never sent as `undefined`).
1755
+ * The explicit foundation for a future per-author undo filter (a local
1756
+ * user's own undo should only ever touch their own ops) — no undo-stack
1757
+ * behavior itself changes this phase.
1758
+ */
1759
+ clientId?: string;
841
1760
  }
1761
+ /**
1762
+ * The one sanctioned seam for real-time multiplayer — implement this against
1763
+ * a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
1764
+ * `connect()` is called once per controller with the local user's
1765
+ * `identity` and a `receive` callback the adapter invokes with incoming
1766
+ * Ops, presence updates, acks, and connection status; it resolves with a
1767
+ * `CollaborationSession` the controller uses to send local Ops and presence
1768
+ * back out. Passed via `createBoardController({ adapters: { collaboration } })`;
1769
+ * omit it entirely to run single-player.
1770
+ */
842
1771
  interface CollaborationAdapter {
843
1772
  connect(options: DocumentContext & {
844
1773
  identity: CollaboratorIdentity;
@@ -849,14 +1778,72 @@ interface CollaboratorIdentity {
849
1778
  id: string;
850
1779
  name: string;
851
1780
  color?: string;
1781
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
1782
+ metadata?: Record<string, unknown>;
852
1783
  }
853
1784
  interface CollaborationReceiver {
854
1785
  ops(ops: readonly ControllerOp[]): void;
1786
+ /**
1787
+ * The current presence roster (Phase 6, ADR 0015) — always a full
1788
+ * replacement, never a delta, matching `presence.sync`'s existing
1789
+ * semantics exactly (an adapter that aggregates wire deltas into a full
1790
+ * roster before calling this is the adapter's own job, not the
1791
+ * controller's). Required on this interface (not optional) because a
1792
+ * Host only ever *consumes* `CollaborationReceiver` — never implements
1793
+ * it — so adding a required method here cannot break an existing custom
1794
+ * `CollaborationAdapter`. An adapter with no presence support simply
1795
+ * never calls it.
1796
+ */
1797
+ presence(users: readonly PresenceUser[]): void;
1798
+ /**
1799
+ * The server has confirmed receipt of these op ids (Phase 7) —
1800
+ * distinguishes "sent" from "server accepted," which `sendOps` alone
1801
+ * (fire-and-forget) cannot. Required for the same reason `presence` is:
1802
+ * Hosts only ever consume this interface, never implement it, so this
1803
+ * cannot break an existing custom `CollaborationAdapter`. An adapter with
1804
+ * no ack support simply never calls it — the collaboration pipe still
1805
+ * works exactly as it did before this existed, just without the
1806
+ * bookkeeping/observability this enables.
1807
+ */
1808
+ acknowledged(opIds: readonly string[]): void;
855
1809
  status(state: "online" | "reconnecting" | "offline"): void;
856
1810
  error(cause: unknown): void;
857
1811
  }
1812
+ /**
1813
+ * Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
1814
+ * the adapter's own live-pipe cache fully covered the gap since the
1815
+ * caller's last known revision — apply `ops` and the client is caught up,
1816
+ * no persistence reload needed. `"unavailable"` means it couldn't (gap too
1817
+ * large, server restarted, or the adapter has no retained history at all)
1818
+ * — the caller must fall back to a persistence-backed reload. This is a
1819
+ * best-effort *liveness* cache, deliberately never a durable source of
1820
+ * truth (ADR 0006's "collaboration is never a second source of document
1821
+ * truth" — see ADR 0015's own extension of that principle to presence,
1822
+ * now extended once more, the same way, to this).
1823
+ */
1824
+ type CollaborationSyncResult = {
1825
+ state: "ops";
1826
+ ops: readonly ControllerOp[];
1827
+ serverRevision: string;
1828
+ } | {
1829
+ state: "unavailable";
1830
+ };
858
1831
  interface CollaborationSession {
859
1832
  sendOps(ops: readonly ControllerOp[]): void;
1833
+ /**
1834
+ * Publishes this client's own local presence (Phase 6, ADR 0015) —
1835
+ * best-effort, unordered, never persisted, never an Op. Optional: an
1836
+ * adapter that doesn't support presence simply omits this method, and
1837
+ * `presence.broadcast()` becomes a silent no-op.
1838
+ */
1839
+ updatePresence?(presence: LocalPresence): void;
1840
+ /**
1841
+ * Requests an incremental catch-up after a reconnect (Phase 7) — optional;
1842
+ * an adapter that doesn't support this simply omits the method, and the
1843
+ * caller (`resyncAfterReconnect`) goes straight to its existing
1844
+ * persistence-backed full reload, unchanged from Phase 6.
1845
+ */
1846
+ requestSync?(): Promise<CollaborationSyncResult>;
860
1847
  close(): Promise<void>;
861
1848
  }
862
1849
  interface CreateBoardControllerOptions {
@@ -895,14 +1882,114 @@ interface CreateBoardControllerOptions {
895
1882
  * changes via `boardTheme.set` below — a headless/browser-tier Host that
896
1883
  * doesn't use the React theme system can set this directly instead.
897
1884
  */
898
- boardTheme?: {
899
- surface?: string;
900
- gridMode?: "none" | "line" | "dot";
901
- gridColor?: string;
902
- gridSpacing?: number;
1885
+ boardTheme?: BoardThemeOptions;
1886
+ /**
1887
+ * Debounced auto-flush of pending persistence Ops after document changes
1888
+ * settle (Phase 5). Enabled by default (1000ms debounce) whenever
1889
+ * `adapters.persistence` is configured — today, without this, a Host must
1890
+ * call `flush()` manually after every edit for anything to persist. Pass
1891
+ * `false` to opt out entirely and drive `flush()` yourself, preserving
1892
+ * prior behavior exactly. Never fires on a per-change basis — rapid edits
1893
+ * coalesce into one flush of their final state (ADR 0006).
1894
+ */
1895
+ autosave?: boolean | {
1896
+ debounceMs?: number;
903
1897
  };
1898
+ /**
1899
+ * Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
1900
+ * interval between outgoing presence updates sent via the configured
1901
+ * `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
1902
+ * latest value passed to `broadcast()` always eventually sends, even if
1903
+ * calls arrive faster than this interval.
1904
+ */
1905
+ presenceThrottleMs?: number;
1906
+ /**
1907
+ * Caps how many `ControllerOp`s can sit queued, unsent, for the
1908
+ * persistence pipe (`pendingOps`) or the collaboration pipe
1909
+ * (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
1910
+ * independently. Prevents unbounded memory growth from a long-lived
1911
+ * offline session or a stuck adapter. Exceeding it never fails or drops
1912
+ * the local edit itself (the Document already applied it optimistically)
1913
+ * — only queueing for that one pipe is skipped, and a
1914
+ * `{code:"queue-overflow", retryable:false}` error is emitted so a Host
1915
+ * can react. Defaults to 1000 — the Phase 9 collaboration coalescing
1916
+ * above (`collaborationCoalesceMs`) already keeps a busy drag from
1917
+ * approaching this on its own, so hitting it in practice means a pipe
1918
+ * has been offline/stuck for a genuinely long editing session.
1919
+ *
1920
+ * **Recovery** (Phase 9): the dropped op itself is gone from that one
1921
+ * pipe's queue — there is no automatic backfill, and the live
1922
+ * controller keeps running with that pipe now silently missing one
1923
+ * edit. Two things stay true regardless: (1) the in-memory Document is
1924
+ * never affected — a queue-overflow can never corrupt or roll back a
1925
+ * local edit, only skip sending it; (2) staleness is per-object, not
1926
+ * permanent — any *later* edit to that same object produces a brand
1927
+ * new, undropped Op carrying its full current state, which naturally
1928
+ * supersedes the gap (the Op model is already last-write-wins/
1929
+ * idempotent, so a superseding Op doesn't need the earlier one to have
1930
+ * arrived). The real risk is an object that's dropped and never edited
1931
+ * again before the controller is disposed or the page reloads — a Host
1932
+ * that needs strict durability should treat `queue-overflow` as a
1933
+ * signal to check `persistence.state`/`pendingOps` pressure (via
1934
+ * `usePersistenceStatus`/`getSnapshot().connection.persistence`)
1935
+ * before disposing, not assume disposing and reconnecting alone
1936
+ * repairs the gap (a fresh `load()` only returns what the backend
1937
+ * already has, which is exactly what's missing the dropped edit).
1938
+ */
1939
+ maxPendingOps?: number;
1940
+ /**
1941
+ * Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
1942
+ * trailing-throttle shape as `presenceThrottleMs`: the first Op after an
1943
+ * idle period sends immediately, and subsequent Ops for the *same*
1944
+ * object within this window replace each other (latest value wins,
1945
+ * matching the already-idempotent Op model) rather than each triggering
1946
+ * its own send. A multi-second drag that previously sent one full Op per
1947
+ * pointer-move now sends at most one per window per touched object.
1948
+ * Persistence (`pendingOps`) is unaffected — it already debounces via
1949
+ * `autosave`, so this option only changes live collaboration traffic.
1950
+ * Defaults to 50ms.
1951
+ */
1952
+ collaborationCoalesceMs?: number;
1953
+ /**
1954
+ * Caps how many resolved objects a single `content.copy`/`content.cut`
1955
+ * (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
1956
+ * in-memory clipboard at once (Phase 9) — `expandSelection` recursively
1957
+ * expands groups, so an unbounded selection (a huge group, or thousands
1958
+ * of individually selected strokes) could otherwise clone and retain an
1959
+ * arbitrarily large snapshot indefinitely, until the next copy/cut
1960
+ * replaces it. Exceeding it rejects the whole copy/cut (nothing is
1961
+ * cloned, and — for cut — nothing is removed from the Document either,
1962
+ * never a partial copy of an arbitrary subset) and emits a
1963
+ * `{code:"clipboard-overflow", retryable:false}` error. Defaults to
1964
+ * 5000.
1965
+ */
1966
+ maxClipboardItems?: number;
904
1967
  }
905
1968
  interface BoardController {
1969
+ readonly notebooks: {
1970
+ /** Create a notebook containing one section and an empty page; select it. */
1971
+ add(name?: string): string;
1972
+ select(id: string): void;
1973
+ rename(id: string, name: string): void;
1974
+ list(): readonly BoardNotebook[];
1975
+ current(): string;
1976
+ };
1977
+ readonly sections: {
1978
+ /** Create a section in the current or specified notebook, with one empty page. */
1979
+ add(name?: string, notebookId?: string): string;
1980
+ select(id: string): void;
1981
+ rename(id: string, name: string): void;
1982
+ list(notebookId?: string): readonly BoardSection[];
1983
+ current(): string;
1984
+ };
1985
+ readonly pages: {
1986
+ /** Add an empty page and switch to it. */
1987
+ add(name?: string, sectionId?: string): string;
1988
+ select(id: string): void;
1989
+ rename(id: string, name: string): void;
1990
+ list(sectionId?: string): readonly BoardPage[];
1991
+ current(): string;
1992
+ };
906
1993
  readonly document: ReadonlyBoardDocument;
907
1994
  readonly tools: {
908
1995
  select(tool: BuiltInTool | (string & {})): void;
@@ -917,7 +2004,7 @@ interface BoardController {
917
2004
  noteColor: string;
918
2005
  tableRows: number;
919
2006
  tableCols: number;
920
- stampKind: StampKind;
2007
+ stampKind: StickerKind;
921
2008
  timerDurationMs: number;
922
2009
  }>): void;
923
2010
  current(): BoardStyle;
@@ -927,18 +2014,34 @@ interface BoardController {
927
2014
  redo(): void;
928
2015
  };
929
2016
  readonly boardTheme: {
930
- /** Live update of the board surface color/grid — the controller's identity stays fixed across theme changes. */
931
- set(theme: {
932
- surface?: string;
933
- gridMode?: "none" | "line" | "dot";
934
- gridColor?: string;
935
- gridSpacing?: number;
936
- }): void;
2017
+ /** Live update of the board surface color/grid/shape-stroke-width — the controller's identity stays fixed across theme changes. */
2018
+ set(theme: BoardThemeOptions): void;
937
2019
  };
938
2020
  readonly view: {
939
2021
  fit(): void;
2022
+ /**
2023
+ * Frame the current selection (Phase 8), the same way `fit()` frames the
2024
+ * whole board. A no-op with nothing selected — deliberately doesn't fall
2025
+ * back to `fit()`'s "frame everything," which would be a surprising
2026
+ * result for an empty selection. On a headless board this can only
2027
+ * re-center the view (no viewport to compute a real zoom-to-fit from),
2028
+ * matching `fit()`'s own headless limitation exactly.
2029
+ */
2030
+ zoomToSelection(): void;
940
2031
  zoomTo(value: number): void;
941
2032
  centerOn(point: BoardPoint): void;
2033
+ /**
2034
+ * Tell the canvas an inline text editor is open over a text block (`{ id }`)
2035
+ * or a table cell (`{ id, row, col }`), so it stops drawing that text until
2036
+ * the editor closes (`null`) and the two copies don't overlap. The default
2037
+ * UI's inline editors call this; a Host with its own editors should too.
2038
+ * Presentation only — never touches the Document. A no-op when headless.
2039
+ */
2040
+ setInlineEditing(target: {
2041
+ id: string;
2042
+ row?: number;
2043
+ col?: number;
2044
+ } | null): void;
942
2045
  get(): BoardView;
943
2046
  boardToScreen(point: BoardPoint): ScreenPoint;
944
2047
  screenToBoard(point: ScreenPoint): BoardPoint;
@@ -947,6 +2050,75 @@ interface BoardController {
947
2050
  add(input: BoardObjectInput): string;
948
2051
  update(id: string, patch: BoardObjectPatch): void;
949
2052
  remove(ids: readonly string[]): void;
2053
+ /**
2054
+ * Clone each given object as a new, unlocked copy offset by a small
2055
+ * fixed cascade (matching the Duplicate affordance's established Host
2056
+ * convention), as one undoable step. Order-preserving: `result[i]` is
2057
+ * the clone of `ids[i]`. Strokes that share a `clusterId` among the
2058
+ * given ids get a single fresh shared `clusterId` in the result, so
2059
+ * duplicating a whole multi-stroke shape (e.g. an arrow's shaft + head)
2060
+ * keeps it one shape — pass every member's id together, not just one.
2061
+ * Unknown ids are silently skipped, matching `remove`'s convention.
2062
+ */
2063
+ duplicate(ids: readonly string[]): readonly string[];
2064
+ /**
2065
+ * Creates a new Group referencing `ids` as its children and returns its
2066
+ * id, as one undoable step. Unknown ids are silently skipped, matching
2067
+ * `duplicate`/`remove`'s convention. A child id that's itself a group
2068
+ * makes a nested group — expanding nested groups into their leaf
2069
+ * members is always the caller's job, never assumed here (matches the
2070
+ * document-model `GroupObject` itself).
2071
+ */
2072
+ group(ids: readonly string[]): string;
2073
+ /**
2074
+ * Dissolves one group, returning its immediate children's ids (a nested
2075
+ * subgroup among them stays intact, itself still a group) — the group
2076
+ * record itself is removed, the children are untouched. A no-op
2077
+ * (returns `[]`) if `groupId` isn't a group.
2078
+ */
2079
+ ungroup(groupId: string): readonly string[];
2080
+ /**
2081
+ * Aligns every given object's matching edge/center to the corresponding
2082
+ * edge/center of their combined bounding box, as one undoable step.
2083
+ * `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
2084
+ * the larger Y). Ids that don't resolve, or resolve to a Group (which
2085
+ * has no position of its own), are skipped. A no-op under 2 resolvable
2086
+ * ids — there's nothing to align relative to.
2087
+ */
2088
+ align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
2089
+ /**
2090
+ * Spaces the middle objects' centers evenly between the first and last
2091
+ * (sorted along `axis`), as one undoable step — the two endpoints don't
2092
+ * move. Ids that don't resolve, or resolve to a Group, are skipped. A
2093
+ * no-op under 3 resolvable ids — there's no "middle" to distribute.
2094
+ */
2095
+ distribute(ids: readonly string[], axis: "x" | "y"): void;
2096
+ /**
2097
+ * Snapshots `ids` (recursively expanded through any group, same as
2098
+ * `duplicate`) into an internal in-memory clipboard — never
2099
+ * `navigator.clipboard`, scoped to this one controller instance and
2100
+ * replaced wholesale by the next `copy`/`cut`. Read-only; works even
2101
+ * on a read-only board.
2102
+ */
2103
+ copy(ids: readonly string[]): void;
2104
+ /** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
2105
+ cut(ids: readonly string[]): void;
2106
+ /**
2107
+ * Clones the current clipboard contents onto the board as one undoable
2108
+ * step, offset the same small cascade `duplicate` uses (no cursor
2109
+ * position to paste relative to yet). Returns the new top-level ids —
2110
+ * a pasted group's own id stands for its (also-pasted) children, which
2111
+ * aren't listed separately. `[]` when the clipboard is empty.
2112
+ */
2113
+ paste(): readonly string[];
2114
+ /**
2115
+ * Select every top-level object (Phase 8) — a group's own id stands for
2116
+ * its children, which aren't selected separately, matching `paste`'s own
2117
+ * "what the user sees" id list. Hidden objects are excluded, consistent
2118
+ * with them already being excluded from marquee selection. Works with
2119
+ * no canvas/engine, same as {@link toggleSelectionVisibility}.
2120
+ */
2121
+ selectAll(): void;
950
2122
  table: {
951
2123
  addRow(tableId: string): void;
952
2124
  addCol(tableId: string): void;
@@ -957,15 +2129,42 @@ interface BoardController {
957
2129
  };
958
2130
  select(ids: readonly string[]): void;
959
2131
  import(document: SerializedBoardDocument): readonly string[];
2132
+ /**
2133
+ * Toggle lock state for the current selection (or focused note/text/
2134
+ * table/image/timer), matching whatever a single Host lock/unlock
2135
+ * control already does per object type. A no-op with nothing selected,
2136
+ * on a headless board, or when every actionable target is locked by
2137
+ * another collaborator who isn't the current lock holder.
2138
+ */
2139
+ toggleSelectionLock(): void;
2140
+ /**
2141
+ * Toggle hidden state for the current selection, as one undo entry
2142
+ * (Phase 8). If any selected object is hidden, shows every selected
2143
+ * object; otherwise hides them all — same "any wins" semantics as
2144
+ * {@link toggleSelectionLock}. Hidden objects stay fully present in the
2145
+ * document (they still serialize, persist, sync, undo/redo) — they just
2146
+ * stop rendering and stop being hit-testable/selectable via pointer
2147
+ * interaction. Unlike `toggleSelectionLock`, this works on a headless
2148
+ * board too: it only touches `selection`/the document, no canvas or
2149
+ * engine involved. Custom objects have no visibility concept (no
2150
+ * `Hideable` field) and are silently skipped, matching how
2151
+ * `toggleSelectionLock` already excludes them. A no-op with nothing
2152
+ * selected or when the selection is only custom objects.
2153
+ */
2154
+ toggleSelectionVisibility(): void;
960
2155
  };
961
2156
  readonly query: {
962
2157
  get(id: string): DeepReadonly<BoardObject> | undefined;
963
2158
  all(): readonly DeepReadonly<BoardObject>[];
964
2159
  search(query: string): readonly SearchHit[];
2160
+ /** An object's axis-aligned board-space bounds (y up), or undefined for an unknown id. */
2161
+ bounds(id: string): BoardBounds | undefined;
965
2162
  };
966
2163
  /** Host-owned comments, synced in for search and marker rendering only — see CommentMarker. */
967
2164
  readonly comments: {
968
2165
  sync(comments: readonly CommentMarker[]): void;
2166
+ /** The markers last passed to `sync`, in the order given. */
2167
+ list(): readonly CommentMarker[];
969
2168
  open(id: string): void;
970
2169
  setActive(id: string | null): void;
971
2170
  };
@@ -985,6 +2184,15 @@ interface BoardController {
985
2184
  follow(view: PresenceView): void;
986
2185
  /** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
987
2186
  gather(view: PresenceView): boolean;
2187
+ /**
2188
+ * Publishes this client's own cursor/tool/view for other collaborators
2189
+ * (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
2190
+ * throttled internally (`presenceThrottleMs` option, default 50ms) so
2191
+ * a raw pointermove stream never becomes a message-per-event flood. A
2192
+ * no-op if no collaboration adapter is configured, or if the
2193
+ * configured one doesn't implement `updatePresence`.
2194
+ */
2195
+ broadcast(local: LocalPresence): void;
988
2196
  };
989
2197
  readonly export: {
990
2198
  svg(): string;
@@ -1010,6 +2218,7 @@ interface BoardController {
1010
2218
  }>;
1011
2219
  dispose(): Promise<void>;
1012
2220
  }
2221
+ /** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
1013
2222
  type LocalBoardSnapshot = {
1014
2223
  documentId: string;
1015
2224
  selectedStrokeId: string | null;
@@ -1018,6 +2227,7 @@ type LocalBoardSnapshot = {
1018
2227
  canRedo: boolean;
1019
2228
  disposed: boolean;
1020
2229
  };
2230
+ /** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
1021
2231
  type LocalBoard = {
1022
2232
  drawStroke(stroke: Stroke): void;
1023
2233
  selectAt(point: BoardPoint): string | null;
@@ -1032,37 +2242,82 @@ type LocalBoard = {
1032
2242
  type ScrawlThemePreset = "light" | "dark";
1033
2243
  type ScrawlDensity = "comfortable" | "compact";
1034
2244
  type ScrawlGridMode = "none" | "line" | "dot";
2245
+ type ScrawlSurfaceTexture = "flat" | "textured";
2246
+ /**
2247
+ * Every field is optional — anything you don't set falls back to the
2248
+ * chosen `preset` ("light" or "dark", see {@link resolveScrawlTheme}).
2249
+ * Overrides are semantic, board-local runtime configuration: they never
2250
+ * get written into the Document or into exports, so switching themes is
2251
+ * always non-destructive.
2252
+ */
1035
2253
  interface ScrawlTheme {
2254
+ /** UI chrome background — toolbar/panel base surface. Distinct from `boardSurface` (the canvas itself). */
1036
2255
  surface?: string;
2256
+ /** UI chrome background, one step up from `surface` — popovers, dropdowns, elevated panels. */
1037
2257
  surfaceRaised?: string;
2258
+ /** UI chrome background, one step down from `surface` — subtle fills, hover states. */
1038
2259
  surfaceMuted?: string;
2260
+ /** Primary UI text color. Checked for contrast against `surface`. */
1039
2261
  text?: string;
2262
+ /** Secondary/de-emphasized UI text color. Checked for contrast against `surface`. */
1040
2263
  textMuted?: string;
2264
+ /** Borders and dividers between UI chrome elements. */
1041
2265
  edge?: string;
2266
+ /**
2267
+ * Brand accent color. Drives the active/selected state of toolbar
2268
+ * controls (e.g. the active tool button): its icon/text render in this
2269
+ * color, and its background is automatically derived as a light tint of
2270
+ * it (via `color-mix`) — set this one token and both follow.
2271
+ */
2272
+ primary?: string;
2273
+ /** Focus ring color. Checked for contrast against `surface`. */
1042
2274
  focus?: string;
2275
+ /** Selection highlight color (e.g. selected list items, not board object selection). */
1043
2276
  selection?: string;
2277
+ /** Destructive/error state color (delete confirmations, error text). */
1044
2278
  danger?: string;
2279
+ /** Warning state color. */
1045
2280
  warning?: string;
2281
+ /** Success/confirmation state color. */
1046
2282
  success?: string;
2283
+ /** Font stack for UI chrome (toolbar labels, menus, dialogs). */
1047
2284
  uiFontFamily?: string;
2285
+ /** Font stack for board content and data (e.g. table cell text). */
1048
2286
  dataFontFamily?: string;
2287
+ /** Base UI font size in px. Range: 12–24. */
1049
2288
  baseFontSize?: number;
2289
+ /** Regular UI font weight. Range: 300–900. */
1050
2290
  regularWeight?: number;
2291
+ /** Emphasized UI font weight (headings, active states). Range: 300–900. */
1051
2292
  strongWeight?: number;
2293
+ /** Corner radius for small controls (buttons, inputs) in px. Range: 0–32. */
1052
2294
  controlRadius?: number;
2295
+ /** Corner radius for panels/dialogs in px. Range: 0–32. */
1053
2296
  panelRadius?: number;
2297
+ /** CSS `box-shadow` value for subtle elevation (e.g. toolbar). */
1054
2298
  elevationLow?: string;
2299
+ /** CSS `box-shadow` value for prominent elevation (e.g. modals). */
1055
2300
  elevationHigh?: string;
2301
+ /** UI transition duration in ms. Range: 0–500. */
1056
2302
  motionDuration?: number;
2303
+ /** CSS easing function for UI transitions. */
1057
2304
  motionEasing?: string;
2305
+ /** UI chrome spacing/sizing scale. */
1058
2306
  density?: ScrawlDensity;
1059
2307
  /** The rendered board/canvas surface color — distinct from `surface` (UI chrome panels). */
1060
2308
  boardSurface?: string;
2309
+ /** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
2310
+ boardSurfaceTexture?: ScrawlSurfaceTexture;
1061
2311
  /** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
1062
2312
  gridMode?: ScrawlGridMode;
2313
+ /** Grid line/dot color. Ignored when `gridMode` is `"none"`. */
1063
2314
  gridColor?: string;
1064
2315
  /** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
1065
2316
  gridSpacing?: number;
2317
+ /** 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. */
2318
+ gridLineWidth?: number;
2319
+ /** 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. */
2320
+ shapeStrokeWidth?: number;
1066
2321
  }
1067
2322
  type ResolvedScrawlTheme = Required<ScrawlTheme>;
1068
2323
  interface ScrawlThemeDiagnostic {
@@ -1079,6 +2334,67 @@ declare const scrawlThemePresets: Readonly<Record<ScrawlThemePreset, Readonly<Re
1079
2334
  declare function validateScrawlTheme(theme: ScrawlTheme | Record<string, unknown>): ScrawlThemeDiagnostic[];
1080
2335
  declare function resolveScrawlTheme(preset?: ScrawlThemePreset, theme?: ScrawlTheme | Record<string, unknown>): ScrawlResolvedTheme;
1081
2336
 
2337
+ /** Access levels, shown as View / Edit / Admin. Map your backend's roles onto these. */
2338
+ type BoardRole = "viewer" | "editor" | "admin";
2339
+ interface BoardMember {
2340
+ id: string;
2341
+ name: string;
2342
+ email?: string;
2343
+ avatarUrl?: string;
2344
+ online?: boolean;
2345
+ role: BoardRole;
2346
+ }
2347
+ interface BoardRoleChange {
2348
+ memberId: string;
2349
+ role: BoardRole;
2350
+ }
2351
+ interface BoardPermissions {
2352
+ members: readonly BoardMember[];
2353
+ /** Marks "(you)" and keeps your own access level from being changed here. */
2354
+ currentUserId?: string;
2355
+ /** Whether "Manage permissions" is offered at all — your rule, from your backend. */
2356
+ canManage: boolean;
2357
+ /**
2358
+ * Called with every pending change when Save is pressed. Resolve once your
2359
+ * backend accepted them; reject to show the error and keep them pending.
2360
+ * Nothing changes until your backend says so — update `members` afterwards.
2361
+ */
2362
+ onSave(changes: readonly BoardRoleChange[]): Promise<void>;
2363
+ }
2364
+ type BoardActivityKind = "added" | "removed" | "updated" | "commented" | "shared" | "exported";
2365
+ interface BoardActivityEntry {
2366
+ id: string;
2367
+ kind: BoardActivityKind;
2368
+ /** e.g. "Added a sticky note". */
2369
+ title: string;
2370
+ actorName?: string;
2371
+ /** When it happened (ms since epoch or ISO string). */
2372
+ at: number | string;
2373
+ /** Board point to jump to, if the action has a place on the board. */
2374
+ location?: BoardPoint;
2375
+ }
2376
+ interface BoardActivityFeed {
2377
+ /** Newest first. Omit to show this session's own actions instead. */
2378
+ entries?: readonly BoardActivityEntry[];
2379
+ unreadCount?: number;
2380
+ onMarkAllRead?(): void;
2381
+ }
2382
+ interface BoardCommentFeed {
2383
+ unreadCount?: number;
2384
+ onMarkAllRead?(): void;
2385
+ }
2386
+ interface BoardMenuItem {
2387
+ id: string;
2388
+ label: string;
2389
+ icon?: ReactNode;
2390
+ onSelect(): void;
2391
+ }
2392
+
2393
+ interface CommentAuthor {
2394
+ name: string;
2395
+ color?: string;
2396
+ }
2397
+
1082
2398
  interface DefaultBoardChromeProps {
1083
2399
  controller: BoardController;
1084
2400
  snapshot: BoardSnapshot;
@@ -1087,8 +2403,42 @@ interface DefaultBoardChromeProps {
1087
2403
  style?: React.CSSProperties;
1088
2404
  regions?: Partial<Record<DefaultUIRegion, boolean>>;
1089
2405
  slots?: DefaultUISlots;
2406
+ /**
2407
+ * Override any built-in tool icon. Tools without an override keep the
2408
+ * SDK's outline icon. Accessible labels and shortcut tooltips are preserved.
2409
+ */
2410
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
2411
+ /** Name (and pin colour) shown on comments left through the default comment UI. Defaults to "You". */
2412
+ commentAuthor?: CommentAuthor;
2413
+ /**
2414
+ * Sharing is the Host's own (links, permissions, backends), so the board
2415
+ * actions only offer a Share button when this is supplied.
2416
+ */
2417
+ onShare?(): void;
2418
+ /**
2419
+ * Your backend's activity feed for the Recent Activity drawer. Without
2420
+ * `entries`, the drawer lists this session's own board actions.
2421
+ */
2422
+ activity?: BoardActivityFeed;
2423
+ /** Unread state for the Comments drawer's badge and "Mark all as read". */
2424
+ commentFeed?: BoardCommentFeed;
2425
+ /**
2426
+ * Members and access levels from your backend, for the permissions drawer
2427
+ * (offered in the More menu when `canManage`). The SDK never changes a role
2428
+ * itself: Save calls `onSave`, and your backend decides.
2429
+ */
2430
+ permissions?: BoardPermissions;
2431
+ /** Extra More-menu actions your app provides (board settings, version history, duplicate…). */
2432
+ menuItems?: readonly BoardMenuItem[];
1090
2433
  }
1091
- type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf";
2434
+
2435
+ /**
2436
+ * `comments` is the built-in comment composer/thread, which keeps comments for
2437
+ * the session; `images` is the Image tool's file picker plus drag-and-drop.
2438
+ * Turn either off when the Host handles `comment-draft-request`/
2439
+ * `comment-open-request` or `import-request` itself.
2440
+ */
2441
+ type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf" | "focusedItemToolbar" | "emptyState" | "pages" | "comments" | "images" | "activity";
1092
2442
  /** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
1093
2443
  interface BoardSlotProps {
1094
2444
  controller: BoardController;
@@ -1116,7 +2466,7 @@ interface DefaultUISlots {
1116
2466
  */
1117
2467
  contextMenu?: ComponentType<BoardSlotProps> | null;
1118
2468
  }
1119
- declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
2469
+ declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }: DefaultBoardChromeProps): react.JSX.Element;
1120
2470
 
1121
2471
  interface InlineEditorsProps {
1122
2472
  controller: BoardController;
@@ -1141,12 +2491,70 @@ interface MultiplayerCursorsProps {
1141
2491
  */
1142
2492
  declare function MultiplayerCursors({ controller, onSelectUser, onJumpToUser }: MultiplayerCursorsProps): react.JSX.Element | null;
1143
2493
 
2494
+ interface FocusedItemToolbarProps {
2495
+ controller: BoardController;
2496
+ snapshot: BoardSnapshot;
2497
+ }
2498
+ /**
2499
+ * Floating toolbar above the focused note, shape, text block, or semantic
2500
+ * Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
2501
+ * (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Images (photos and
2502
+ * placed emoji/icons) and tables get Lock/Duplicate/Delete only — each
2503
+ * already has its own resize affordance (the native gizmo for images, edge
2504
+ * handles for tables), and a table's row/column actions live in its cell
2505
+ * editor. Timer/custom objects and plain (non-shape) ink strokes never get a
2506
+ * toolbar here — out of scope for this destination.
2507
+ *
2508
+ * Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
2509
+ * the legacy ink-stroke shape below: both draw from the same toolbar
2510
+ * buttons and look the same to a user, but a Rectangle/Ellipse is a real
2511
+ * BoardObject with its own resize handles (independent width/height,
2512
+ * `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
2513
+ * native selection gizmo. This duality is deliberate and temporary — see
2514
+ * docs/reports/phase-2-document-object-model.md.
2515
+ *
2516
+ * Lock/Unlock carries no per-user ownership gating: nothing else in this
2517
+ * SDK enforces lock ownership either (`content.update` never checks
2518
+ * `locked`, and the engine's own internal unlock check has no way for a
2519
+ * Host to ever supply a real user id) — locking is advisory UI state
2520
+ * throughout, and this toolbar matches that rather than inventing an
2521
+ * enforcement story alone.
2522
+ *
2523
+ * A shape can be several strokes sharing one `clusterId` (e.g. an arrow's
2524
+ * shaft + head) — every action here applies to the whole cluster. Resolved
2525
+ * via `query.all()` + `clusterId`, not `snapshot.selection`: a canvas click
2526
+ * on one member selects every member internally, but the public selection
2527
+ * bridge only ever reports one id (`FocusedItem.id` is singular by design),
2528
+ * so reconstructing the cluster from the document is the reliable path
2529
+ * regardless of how the selection was made.
2530
+ *
2531
+ * A focused note also gets drag-to-resize corner handles — notes have no
2532
+ * gizmo of their own (the native selection gizmo is ink-stroke/shape-only),
2533
+ * so this is that capability's default-ui-owned equivalent. Proportional
2534
+ * (always-square) resize from the note's own centre, clamped to
2535
+ * `[MIN_SIZE, MAX_SIZE]`, independent of which corner is grabbed.
2536
+ */
2537
+ declare function FocusedItemToolbar({ controller, snapshot }: FocusedItemToolbarProps): react.JSX.Element | null;
2538
+
1144
2539
  interface StyleShelfProps {
1145
2540
  controller: BoardController;
1146
2541
  snapshot: BoardSnapshot;
2542
+ /**
2543
+ * Root-relative point beside the active tool. DefaultBoardChrome supplies
2544
+ * this automatically and keeps the shelf within the board bounds.
2545
+ */
2546
+ anchor?: {
2547
+ x: number;
2548
+ y: number;
2549
+ };
2550
+ /**
2551
+ * Called after an option is picked (not while typing table dimensions or a
2552
+ * sticker search). DefaultBoardChrome uses it to close the shelf.
2553
+ */
2554
+ onChoose?(): void;
1147
2555
  }
1148
2556
  /** Contextual per-tool style controls — visible while a styleable tool is active. */
1149
- declare function StyleShelf({ controller, snapshot }: StyleShelfProps): react.JSX.Element | null;
2557
+ declare function StyleShelf({ controller, snapshot, anchor, onChoose }: StyleShelfProps): react.JSX.Element | null;
1150
2558
 
1151
2559
  type ThemeStyle = CSSProperties & Record<`--scrawl-${string}`, string | number | undefined>;
1152
2560
  interface ScrawlProviderProps {
@@ -1160,6 +2568,16 @@ interface ScrawlProviderProps {
1160
2568
  className?: string;
1161
2569
  style?: ThemeStyle;
1162
2570
  }
2571
+ /**
2572
+ * Wraps a Board `controller` you created yourself (via `createBoardController`)
2573
+ * so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
2574
+ * and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
2575
+ * Prefer `Scrawl` unless you need to construct or own the controller's
2576
+ * lifecycle yourself (e.g. you create it outside React, or need it before
2577
+ * first render). Set `disposeOnUnmount` to have this provider call
2578
+ * `controller.dispose()` on unmount; otherwise disposal remains your own
2579
+ * responsibility.
2580
+ */
1163
2581
  declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
1164
2582
  interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1165
2583
  children?: ReactNode;
@@ -1173,8 +2591,35 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1173
2591
  onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
1174
2592
  /** Provide null for a headless Board, or an existing canvas to control its identity. */
1175
2593
  canvas?: HTMLCanvasElement | null;
2594
+ /**
2595
+ * Per-tool icon override for the default toolbar and More tools buttons —
2596
+ * only applies when you don't supply `children` (i.e. you're using the
2597
+ * SDK's default UI). See DefaultBoardChromeProps.icons.
2598
+ */
2599
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
2600
+ /** Offer a Share button in the default UI's board actions — see DefaultBoardChromeProps.onShare. */
2601
+ onShare?(): void;
2602
+ /** Your backend's activity feed — see DefaultBoardChromeProps.activity. */
2603
+ activity?: BoardActivityFeed;
2604
+ /** Unread state for the Comments drawer — see DefaultBoardChromeProps.commentFeed. */
2605
+ commentFeed?: BoardCommentFeed;
2606
+ /** Members and access levels from your backend — see DefaultBoardChromeProps.permissions. */
2607
+ permissions?: BoardPermissions;
2608
+ /** Extra More-menu actions — see DefaultBoardChromeProps.menuItems. */
2609
+ menuItems?: readonly BoardMenuItem[];
1176
2610
  }
1177
- declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }: ScrawlProps): react.JSX.Element;
2611
+ /**
2612
+ * The fastest path to an embedded Board: creates and owns a
2613
+ * `BoardController` for you (constructed once, disposed on unmount) and
2614
+ * renders it into a canvas. Render with no `children` to get the SDK's
2615
+ * default toolbar/UI chrome, or supply your own `children` (using the
2616
+ * `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
2617
+ * `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
2618
+ * top of the same controller. Accepts every `CreateBoardControllerOptions`
2619
+ * field except `canvas` (pass `canvas={null}` for a headless board, or an
2620
+ * existing `<canvas>` element to control its identity yourself).
2621
+ */
2622
+ 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;
1178
2623
  interface ScrawlCanvasProps {
1179
2624
  element?: HTMLCanvasElement;
1180
2625
  className?: string;
@@ -1189,14 +2634,60 @@ interface ScrawlDefaultUIProps {
1189
2634
  regions?: Partial<Record<DefaultUIRegion, boolean>>;
1190
2635
  /** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
1191
2636
  slots?: DefaultUISlots;
1192
- }
1193
- declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
2637
+ /** Per-tool icon override for the toolbar and More tools buttons — see DefaultBoardChromeProps.icons. */
2638
+ icons?: Partial<Record<BuiltInTool, ReactNode>>;
2639
+ /** Author shown on comments left through the default comment UI — see DefaultBoardChromeProps.commentAuthor. */
2640
+ commentAuthor?: CommentAuthor;
2641
+ /** Supply to offer a Share button in the board actions — see DefaultBoardChromeProps.onShare. */
2642
+ onShare?(): void;
2643
+ /** Your backend's activity feed — see DefaultBoardChromeProps.activity. */
2644
+ activity?: BoardActivityFeed;
2645
+ /** Unread state for the Comments drawer — see DefaultBoardChromeProps.commentFeed. */
2646
+ commentFeed?: BoardCommentFeed;
2647
+ /** Members and access levels from your backend — see DefaultBoardChromeProps.permissions. */
2648
+ permissions?: BoardPermissions;
2649
+ /** Extra More-menu actions — see DefaultBoardChromeProps.menuItems. */
2650
+ menuItems?: readonly BoardMenuItem[];
2651
+ }
2652
+ declare function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor, onShare, activity, commentFeed, permissions, menuItems }: ScrawlDefaultUIProps): react.JSX.Element;
1194
2653
  declare function ScrawlPortal({ children }: {
1195
2654
  children: ReactNode;
1196
2655
  }): react.ReactPortal | null;
1197
2656
  declare function useScrawlController(): BoardController;
1198
2657
  declare function useScrawlTheme(): ScrawlResolvedTheme;
1199
2658
  declare function useScrawlSnapshot(): BoardSnapshot;
2659
+ /**
2660
+ * A thin, purely-derived convenience over
2661
+ * `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
2662
+ * component that only cares about save status (e.g. a
2663
+ * "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
2664
+ * this same field access itself. Adds no state and no behavior of its
2665
+ * own: React still owns none of persistence, exactly as before — this
2666
+ * hook only re-reads what the controller already tracks.
2667
+ *
2668
+ * Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
2669
+ * `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
2670
+ * `connection.persistence` object — on every document mutation, even ones
2671
+ * that never touch persistence at all, so a component using only this
2672
+ * hook would otherwise re-render on every stroke draw. `useSyncStatus`
2673
+ * below memoizes on the value (`state`/`error`), not just the object
2674
+ * reference, and returns the same cached value across renders where
2675
+ * nothing relevant changed — the same shape `usePresence` already uses
2676
+ * for its own independently-scoped store.
2677
+ */
2678
+ declare function usePersistenceStatus(): PersistenceSnapshot;
2679
+ /** 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). */
2680
+ declare function useCollaborationStatus(): CollaborationSnapshot;
2681
+ /**
2682
+ * The current presence roster (Phase 6), live-updating — wraps
2683
+ * `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
2684
+ * wraps the controller's main snapshot, via `useSyncExternalStore`. Does
2685
+ * not re-render on every remote pointer move by itself; it re-renders
2686
+ * whenever the roster the controller already tracks changes, at whatever
2687
+ * rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
2688
+ */
2689
+ declare function usePresence(): readonly PresenceUser[];
2690
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1200
2691
  type ScrawlBoardProps = {
1201
2692
  documentId: string;
1202
2693
  initialDocument?: SerializedBoardDocument;
@@ -1204,7 +2695,8 @@ type ScrawlBoardProps = {
1204
2695
  className?: string;
1205
2696
  style?: CSSProperties;
1206
2697
  };
2698
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1207
2699
  declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
1208
2700
 
1209
- export { AssetResolutionError, DefaultBoardChrome, InlineEditors, MultiplayerCursors, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, assetRef, clampAssetCacheBytes, cloneCustomObject, isAssetRef, resolveScrawlTheme, scrawlThemePresets, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
1210
- export type { AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardController, BoardKeyInput, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStyle, CommentMarker, CreateBoardControllerOptions, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, LocalBoard, LocalBoardSnapshot, Mat2x3, MultiplayerCursorsProps, ObjectDescribeContext, ObjectIntent, ObjectType, PresenceCursor, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyCustomObject, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, StyleShelfProps, SupportedAssetMediaType, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId };
2701
+ export { AssetResolutionError, DefaultBoardChrome, FocusedItemToolbar, InlineEditors, MultiplayerCursors, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, assetRef, clampAssetCacheBytes, cloneCustomObject, isAssetRef, resolveScrawlTheme, scrawlThemePresets, useCollaborationStatus, usePersistenceStatus, usePresence, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
2702
+ export type { AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardActivityEntry, BoardActivityFeed, BoardActivityKind, BoardBounds, BoardCommentFeed, BoardController, BoardKeyInput, BoardMember, BoardMenuItem, BoardNotebook, BoardPage, BoardPermissions, BoardPoint, BoardPointerInput, BoardRole, BoardRoleChange, BoardScene, BoardSection, BoardSlotProps, BoardSnapshot, BoardStyle, BoardThemeOptions, CollaborationSnapshot, CommentAuthor, CommentMarker, CreateBoardControllerOptions, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, ExportDocumentSVGOptions, ExportDocumentSVGResult, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, LocalBoard, LocalBoardSnapshot, LocalPresence, Mat2x3, MultiplayerCursorsProps, ObjectDescribeContext, ObjectIntent, ObjectType, PresenceCursor, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyCustomObject, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlSurfaceTexture, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, SearchHit, SearchHitKind, SearchableComment, StyleShelfProps, SupportedAssetMediaType, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId };