@scrawl-board/board 0.1.0-beta.6 → 0.1.0-beta.8

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
@@ -155,6 +155,21 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
155
155
  /** One pure, synchronous step per consecutive schema version. */
156
156
  migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
157
157
  describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
158
+ /**
159
+ * Optional point-level hit-test precision (Phase 8). Every custom object
160
+ * hit-tests against its bounding box (`fallback.bounds`) by default — this
161
+ * lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
162
+ * reject a point that's inside that box but outside its actual visible
163
+ * silhouette, tightening a click/marquee/raycast hit to the shape's real
164
+ * outline. `point` is in this object's own local space — the same
165
+ * untransformed space `describe`'s returned geometry already lives in
166
+ * (the caller inverse-transforms the pointer's board point through
167
+ * `object.transform` before calling this). Absent means every point
168
+ * inside the bounding box hits, matching pre-Phase-8 behavior exactly.
169
+ * Rejecting a point here does not fall through to whatever's underneath —
170
+ * the gesture simply misses this object, same as clicking empty space.
171
+ */
172
+ hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
158
173
  }
159
174
  interface SceneNodeBase {
160
175
  key: string;
@@ -188,6 +203,15 @@ interface SceneGroup extends SceneNodeBase {
188
203
  kind: "group";
189
204
  children: readonly BoardScene[];
190
205
  }
206
+ /**
207
+ * **No renderer or SVG-export interpreter exists for this node kind yet**
208
+ * (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
209
+ * `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
210
+ * exports nothing, and contributes no hit-test bounds — it neither errors
211
+ * nor emits a diagnostic. Until an interpreter ships, build custom shapes
212
+ * from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
213
+ * instead.
214
+ */
191
215
  interface ScenePath extends SceneNodeBase {
192
216
  kind: "path";
193
217
  /** SVG-style path data, board-local coordinates. */
@@ -343,8 +367,23 @@ interface Lockable {
343
367
  lockedByName?: string;
344
368
  }
345
369
 
370
+ /**
371
+ * Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
372
+ * pattern exactly, but simpler: unlike a lock, hidden state carries no
373
+ * holder/ownership concept, so there's no analogue to `LockHolder`/
374
+ * `canUnlockItem`. A hidden object stays fully present in the Document
375
+ * (still serializes, persists, syncs, undoes/redoes) — it just skips
376
+ * rendering and hit-testing/selection candidacy. `hidden` absent or
377
+ * `false` means visible; this keeps every pre-Phase-8 document (which has
378
+ * no `hidden` field on any object at all) implicitly fully visible with
379
+ * zero migration needed.
380
+ */
381
+ interface Hideable {
382
+ hidden?: boolean;
383
+ }
384
+
346
385
  /** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
347
- interface KitchenTimer extends Lockable {
386
+ interface KitchenTimer extends Lockable, Hideable {
348
387
  id: string;
349
388
  x: number;
350
389
  y: number;
@@ -358,6 +397,133 @@ interface KitchenTimer extends Lockable {
358
397
  runningSince?: number;
359
398
  }
360
399
 
400
+ interface RectangleObject extends Lockable, Hideable {
401
+ id: string;
402
+ x: number;
403
+ y: number;
404
+ width: number;
405
+ height: number;
406
+ fill?: string;
407
+ stroke?: string;
408
+ strokeWidth?: number;
409
+ /** Corner radius in board units; clamped to at most half the shorter side at render time. */
410
+ cornerRadius?: number;
411
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
412
+ opacity?: number;
413
+ /**
414
+ * Radians, about the shape's own center `(x + width/2, y - height/2)`.
415
+ * Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
416
+ * shape's own unrotated local frame — rotation is a separate, applied-last
417
+ * transform, not baked into them, matching how Stroke/CustomBoardObject
418
+ * keep geometry and placement independent via their own `matrix`.
419
+ */
420
+ rotation?: number;
421
+ }
422
+ interface EllipseObject extends Lockable, Hideable {
423
+ id: string;
424
+ x: number;
425
+ y: number;
426
+ width: number;
427
+ height: number;
428
+ fill?: string;
429
+ stroke?: string;
430
+ strokeWidth?: number;
431
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
432
+ opacity?: number;
433
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
434
+ rotation?: number;
435
+ }
436
+ /** `"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. */
437
+ type ArrowHeadStyle = "triangle" | "none";
438
+ interface LineObject extends Lockable, Hideable {
439
+ id: string;
440
+ start: BoardPoint;
441
+ end: BoardPoint;
442
+ stroke?: string;
443
+ strokeWidth?: number;
444
+ opacity?: number;
445
+ }
446
+ interface ArrowObject extends Lockable, Hideable {
447
+ id: string;
448
+ start: BoardPoint;
449
+ end: BoardPoint;
450
+ head?: ArrowHeadStyle;
451
+ stroke?: string;
452
+ strokeWidth?: number;
453
+ opacity?: number;
454
+ }
455
+ /**
456
+ * Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
457
+ * type instead of five near-duplicate interfaces — a regular N-gon
458
+ * inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
459
+ * Rectangle already uses, parameterized by `sides`. Diamond is exactly a
460
+ * 4-sided regular polygon with vertex 0 pointing right (not up, like
461
+ * Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
462
+ * `polygonStartAngle`, which encodes each side count's own vertex
463
+ * orientation so the outline always matches the legacy drag-preview shape.
464
+ */
465
+ interface PolygonObject extends Lockable, Hideable {
466
+ id: string;
467
+ x: number;
468
+ y: number;
469
+ width: number;
470
+ height: number;
471
+ sides: 3 | 4 | 5 | 6 | 8;
472
+ fill?: string;
473
+ stroke?: string;
474
+ strokeWidth?: number;
475
+ opacity?: number;
476
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
477
+ rotation?: number;
478
+ }
479
+ /** 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`). */
480
+ interface StarObject extends Lockable, Hideable {
481
+ id: string;
482
+ x: number;
483
+ y: number;
484
+ width: number;
485
+ height: number;
486
+ /** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
487
+ points: number;
488
+ /** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
489
+ innerRadiusRatio: number;
490
+ fill?: string;
491
+ stroke?: string;
492
+ strokeWidth?: number;
493
+ opacity?: number;
494
+ rotation?: number;
495
+ }
496
+ /** 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. */
497
+ interface HeartObject extends Lockable, Hideable {
498
+ id: string;
499
+ x: number;
500
+ y: number;
501
+ width: number;
502
+ height: number;
503
+ fill?: string;
504
+ stroke?: string;
505
+ strokeWidth?: number;
506
+ opacity?: number;
507
+ rotation?: number;
508
+ }
509
+ /**
510
+ * A logical grouping of other board objects (Phase 3 — Selection,
511
+ * Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
512
+ * own — a group's bounds are always derived on demand from its (recursively
513
+ * resolved) children, and "moving/rotating/scaling the group" is exactly a
514
+ * multi-object transform applied to those children, nothing more. A group
515
+ * has no renderer/mesh of its own; its only visual presence is the
516
+ * selection gizmo's bounding box while it's the current selection.
517
+ *
518
+ * `children` may itself contain other group ids (nested groups) — expanding
519
+ * a group into its leaf members is always done by the caller (recursively,
520
+ * with cycle protection), never assumed here.
521
+ */
522
+ interface GroupObject extends Lockable, Hideable {
523
+ id: string;
524
+ children: string[];
525
+ }
526
+
361
527
  interface BoardPoint {
362
528
  x: number;
363
529
  y: number;
@@ -378,7 +544,7 @@ interface StrokePoint extends BoardPoint {
378
544
  * geometric outline, not an expressive ink mark.
379
545
  */
380
546
  type StrokeTool = "marker" | "highlighter" | "shape";
381
- interface Stroke extends Lockable {
547
+ interface Stroke extends Lockable, Hideable {
382
548
  id: string;
383
549
  color: string;
384
550
  baseWidth: number;
@@ -394,7 +560,7 @@ interface Stroke extends Lockable {
394
560
  clusterId?: string;
395
561
  }
396
562
  type SerializedPoint = [number, number, number, number];
397
- interface SerializedStroke extends Lockable {
563
+ interface SerializedStroke extends Lockable, Hideable {
398
564
  id: string;
399
565
  color: string;
400
566
  baseWidth: number;
@@ -420,6 +586,31 @@ interface SerializedDocument {
420
586
  timers?: KitchenTimer[];
421
587
  /** Absent in documents saved before Custom board objects existed (ticket #22). */
422
588
  customObjects?: CustomBoardObject[];
589
+ /** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
590
+ rectangles?: RectangleObject[];
591
+ /** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
592
+ ellipses?: EllipseObject[];
593
+ /** Absent in documents saved before Groups existed (Phase 3). */
594
+ groups?: GroupObject[];
595
+ /** Absent in documents saved before semantic Line objects existed (Phase 4). */
596
+ lines?: LineObject[];
597
+ /** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
598
+ arrows?: ArrowObject[];
599
+ /** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
600
+ polygons?: PolygonObject[];
601
+ /** Absent in documents saved before semantic Star objects existed (Phase 4). */
602
+ stars?: StarObject[];
603
+ /** Absent in documents saved before semantic Heart objects existed (Phase 4). */
604
+ hearts?: HeartObject[];
605
+ /**
606
+ * Every content-object id (every type above except comments, which are
607
+ * host-synced and never enter this schema) in paint order, back to front.
608
+ * Absent in documents saved before per-object z-order existed (Phase 3) —
609
+ * migration synthesizes a default order preserving the old fixed-Z-band
610
+ * visual stacking exactly, so an existing document never visibly changes
611
+ * on load; only an explicit reorder action touches this from then on.
612
+ */
613
+ objectOrder?: string[];
423
614
  }
424
615
  /**
425
616
  * One collaborator's vote on a note. One per person; toggling removes it.
@@ -433,7 +624,7 @@ interface NoteVote {
433
624
  * A sticky note: content floating above the board at a z-offset (pillar 3 —
434
625
  * depth as an organizational axis). Center position in board space.
435
626
  */
436
- interface StickyNote extends Lockable {
627
+ interface StickyNote extends Lockable, Hideable {
437
628
  id: string;
438
629
  x: number;
439
630
  y: number;
@@ -451,7 +642,7 @@ interface StickyNote extends Lockable {
451
642
  * top-left corner; lines flow downward (-y). Text joins the clustering
452
643
  * system like handwriting (build prompt §6.4).
453
644
  */
454
- interface TextBlock extends Lockable {
645
+ interface TextBlock extends Lockable, Hideable {
455
646
  id: string;
456
647
  x: number;
457
648
  y: number;
@@ -465,7 +656,7 @@ interface TextBlock extends Lockable {
465
656
  * Interactive structured table on the board. Position (x, y) is top-left in board units.
466
657
  * Cells are indexed as `${row},${col}` keys mapping to cell text content.
467
658
  */
468
- interface TableBlock extends Lockable {
659
+ interface TableBlock extends Lockable, Hideable {
469
660
  id: string;
470
661
  x: number;
471
662
  y: number;
@@ -482,7 +673,7 @@ interface TableBlock extends Lockable {
482
673
  * An imported image block on the board plane.
483
674
  * Coordinates (x, y) represent the center of the image in board space.
484
675
  */
485
- interface ImageBlock extends Lockable {
676
+ interface ImageBlock extends Lockable, Hideable {
486
677
  id: string;
487
678
  /**
488
679
  * A legacy, read-only data URL (or, historically, an arbitrary string) —
@@ -612,7 +803,7 @@ interface BoardSnapshot {
612
803
  * here, matching the engine's own internal selection-badge behavior.
613
804
  */
614
805
  interface FocusedItem {
615
- readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom";
806
+ readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
616
807
  readonly id: string;
617
808
  readonly locked: boolean;
618
809
  readonly lockedBy?: string;
@@ -693,6 +884,8 @@ interface BoardEventMap {
693
884
  "asset-diagnostic": AssetDiagnostic;
694
885
  /** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
695
886
  "persistence-diagnostic": PersistenceDiagnostic;
887
+ /** 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. */
888
+ "collaboration-ops-acknowledged": CollaborationAckDiagnostic;
696
889
  audit: unknown;
697
890
  error: BoardControllerError;
698
891
  disposed: undefined;
@@ -756,9 +949,12 @@ interface PresenceView {
756
949
  readonly height: number;
757
950
  }
758
951
  /**
759
- * A Host-owned collaborator, synced in for cursor/roster rendering only.
760
- * Presence is ephemeral — it never touches the Document, Ops, undo/redo,
761
- * or persistence — so this is a read/query capability, not an adapter.
952
+ * A collaborator, synced in for cursor/roster rendering only. Presence is
953
+ * ephemeral — it never touches the Document, Ops, undo/redo, or persistence
954
+ * (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
955
+ * directly, or a `CollaborationAdapter`'s optional presence channel —
956
+ * Phase 6, ADR 0015) both feed the exact same read/query capability below;
957
+ * a Host picks one, not both, for a given controller.
762
958
  */
763
959
  interface PresenceUser {
764
960
  readonly id: string;
@@ -767,6 +963,14 @@ interface PresenceUser {
767
963
  readonly tool?: string;
768
964
  readonly cursor?: PresenceCursor;
769
965
  readonly view?: PresenceView;
966
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
967
+ readonly metadata?: Record<string, unknown>;
968
+ }
969
+ /** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
970
+ interface LocalPresence {
971
+ readonly cursor?: PresenceCursor | null;
972
+ readonly view?: PresenceView | null;
973
+ readonly tool?: string;
770
974
  }
771
975
  /**
772
976
  * The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
@@ -794,6 +998,22 @@ type BoardObject = ({
794
998
  } & ImageBlock) | ({
795
999
  type: "timer";
796
1000
  } & KitchenTimer) | ({
1001
+ type: "rectangle";
1002
+ } & RectangleObject) | ({
1003
+ type: "ellipse";
1004
+ } & EllipseObject) | ({
1005
+ type: "group";
1006
+ } & GroupObject) | ({
1007
+ type: "line";
1008
+ } & LineObject) | ({
1009
+ type: "arrow";
1010
+ } & ArrowObject) | ({
1011
+ type: "polygon";
1012
+ } & PolygonObject) | ({
1013
+ type: "star";
1014
+ } & StarObject) | ({
1015
+ type: "heart";
1016
+ } & HeartObject) | ({
797
1017
  type: "custom";
798
1018
  customType: ObjectType;
799
1019
  } & Omit<CustomBoardObject, "type">);
@@ -822,6 +1042,30 @@ type BoardObjectInput = {
822
1042
  type: "timer";
823
1043
  id?: string;
824
1044
  } & Omit<KitchenTimer, "id">) | ({
1045
+ type: "rectangle";
1046
+ id?: string;
1047
+ } & Omit<RectangleObject, "id">) | ({
1048
+ type: "ellipse";
1049
+ id?: string;
1050
+ } & Omit<EllipseObject, "id">) | ({
1051
+ type: "group";
1052
+ id?: string;
1053
+ } & Omit<GroupObject, "id">) | ({
1054
+ type: "line";
1055
+ id?: string;
1056
+ } & Omit<LineObject, "id">) | ({
1057
+ type: "arrow";
1058
+ id?: string;
1059
+ } & Omit<ArrowObject, "id">) | ({
1060
+ type: "polygon";
1061
+ id?: string;
1062
+ } & Omit<PolygonObject, "id">) | ({
1063
+ type: "star";
1064
+ id?: string;
1065
+ } & Omit<StarObject, "id">) | ({
1066
+ type: "heart";
1067
+ id?: string;
1068
+ } & Omit<HeartObject, "id">) | ({
825
1069
  type: "custom";
826
1070
  id?: string;
827
1071
  customType: ObjectType;
@@ -852,10 +1096,34 @@ type LoadResult = {
852
1096
  } | {
853
1097
  state: "missing";
854
1098
  };
1099
+ /**
1100
+ * Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
1101
+ * "Whole-document writes survive only for create, clear-board and import,
1102
+ * where replacing everything is the actual intent"). Revision-gated, unlike
1103
+ * `applyOps` — `conflict` means `baseRevision` was stale (someone else's
1104
+ * write landed first); the caller must reload and never overwrites blind.
1105
+ */
1106
+ type ReplaceResult = {
1107
+ state: "applied";
1108
+ revision: string;
1109
+ } | {
1110
+ state: "conflict";
1111
+ currentRevision: string;
1112
+ };
1113
+ /**
1114
+ * The one sanctioned seam for persisting a Board's Document to a Host's own
1115
+ * storage — implement this against a database, an HTTP API, IndexedDB
1116
+ * (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
1117
+ * anything else. `load()` fetches the current state on connect; `applyOps()`
1118
+ * streams incremental Ops as edits happen; `replace()` is only for
1119
+ * whole-document writes (create, clear-board, import — see ADR 0006) and is
1120
+ * revision-gated so a stale write never silently clobbers a newer one.
1121
+ * Passed via `createBoardController({ adapters: { persistence } })`.
1122
+ */
855
1123
  interface PersistenceAdapter {
856
1124
  load(context: DocumentContext): Promise<LoadResult>;
857
1125
  applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
858
- replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<unknown>;
1126
+ replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
859
1127
  }
860
1128
  /**
861
1129
  * `"reconcile"` (ticket #24) means the server authoritatively resolved the
@@ -879,14 +1147,59 @@ interface PersistenceDiagnostic {
879
1147
  rejectedOpIds: readonly string[];
880
1148
  revision: string;
881
1149
  }
1150
+ /** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
1151
+ interface CollaborationAckDiagnostic {
1152
+ opIds: readonly string[];
1153
+ }
882
1154
  interface ControllerOp {
883
1155
  id: string;
884
1156
  schemaVersion: 1;
885
1157
  kind: "upsert" | "restore" | "remove";
886
- objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom";
1158
+ objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
1159
+ /**
1160
+ * A whole-document paint-order sync (Phase 3), not a per-object type —
1161
+ * `objectId` is always the fixed sentinel `"order"` and `payload` is
1162
+ * `{ order: string[] }`. The only `objectType` with no matching
1163
+ * `BoardObject`/document collection; kept in this same union (rather
1164
+ * than a separate wire message) so it flows through the existing
1165
+ * `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
1166
+ */
1167
+ | "order";
887
1168
  objectId: string;
888
1169
  payload?: unknown;
1170
+ /**
1171
+ * This op's position in its own originating client's local sequence
1172
+ * (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
1173
+ * (an opaque, globally-unique identifier used for dedup/ack, not
1174
+ * ordering) and from a server's own authoritative ordering (e.g.
1175
+ * `referenceCollaborationServer.ts`'s per-room `version` counter).
1176
+ * Present on every op this SDK originates locally; a remote peer's op
1177
+ * carries whatever its own origin set, unchanged — never renumbered in
1178
+ * transit. Absent on an op minted by decoding the legacy wire envelope
1179
+ * (`scrawlOpEnvelope.ts`), which predates this field and has no
1180
+ * per-client sequence concept of its own.
1181
+ */
1182
+ clientSequence?: number;
1183
+ /**
1184
+ * The `CollaboratorIdentity.id` of this op's originating client (Phase
1185
+ * 7) — set for every op this SDK originates locally when `identity` is
1186
+ * configured, omitted entirely otherwise (never sent as `undefined`).
1187
+ * The explicit foundation for a future per-author undo filter (a local
1188
+ * user's own undo should only ever touch their own ops) — no undo-stack
1189
+ * behavior itself changes this phase.
1190
+ */
1191
+ clientId?: string;
889
1192
  }
1193
+ /**
1194
+ * The one sanctioned seam for real-time multiplayer — implement this against
1195
+ * a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
1196
+ * `connect()` is called once per controller with the local user's
1197
+ * `identity` and a `receive` callback the adapter invokes with incoming
1198
+ * Ops, presence updates, acks, and connection status; it resolves with a
1199
+ * `CollaborationSession` the controller uses to send local Ops and presence
1200
+ * back out. Passed via `createBoardController({ adapters: { collaboration } })`;
1201
+ * omit it entirely to run single-player.
1202
+ */
890
1203
  interface CollaborationAdapter {
891
1204
  connect(options: DocumentContext & {
892
1205
  identity: CollaboratorIdentity;
@@ -897,14 +1210,72 @@ interface CollaboratorIdentity {
897
1210
  id: string;
898
1211
  name: string;
899
1212
  color?: string;
1213
+ /** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
1214
+ metadata?: Record<string, unknown>;
900
1215
  }
901
1216
  interface CollaborationReceiver {
902
1217
  ops(ops: readonly ControllerOp[]): void;
1218
+ /**
1219
+ * The current presence roster (Phase 6, ADR 0015) — always a full
1220
+ * replacement, never a delta, matching `presence.sync`'s existing
1221
+ * semantics exactly (an adapter that aggregates wire deltas into a full
1222
+ * roster before calling this is the adapter's own job, not the
1223
+ * controller's). Required on this interface (not optional) because a
1224
+ * Host only ever *consumes* `CollaborationReceiver` — never implements
1225
+ * it — so adding a required method here cannot break an existing custom
1226
+ * `CollaborationAdapter`. An adapter with no presence support simply
1227
+ * never calls it.
1228
+ */
1229
+ presence(users: readonly PresenceUser[]): void;
1230
+ /**
1231
+ * The server has confirmed receipt of these op ids (Phase 7) —
1232
+ * distinguishes "sent" from "server accepted," which `sendOps` alone
1233
+ * (fire-and-forget) cannot. Required for the same reason `presence` is:
1234
+ * Hosts only ever consume this interface, never implement it, so this
1235
+ * cannot break an existing custom `CollaborationAdapter`. An adapter with
1236
+ * no ack support simply never calls it — the collaboration pipe still
1237
+ * works exactly as it did before this existed, just without the
1238
+ * bookkeeping/observability this enables.
1239
+ */
1240
+ acknowledged(opIds: readonly string[]): void;
903
1241
  status(state: "online" | "reconnecting" | "offline"): void;
904
1242
  error(cause: unknown): void;
905
1243
  }
1244
+ /**
1245
+ * Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
1246
+ * the adapter's own live-pipe cache fully covered the gap since the
1247
+ * caller's last known revision — apply `ops` and the client is caught up,
1248
+ * no persistence reload needed. `"unavailable"` means it couldn't (gap too
1249
+ * large, server restarted, or the adapter has no retained history at all)
1250
+ * — the caller must fall back to a persistence-backed reload. This is a
1251
+ * best-effort *liveness* cache, deliberately never a durable source of
1252
+ * truth (ADR 0006's "collaboration is never a second source of document
1253
+ * truth" — see ADR 0015's own extension of that principle to presence,
1254
+ * now extended once more, the same way, to this).
1255
+ */
1256
+ type CollaborationSyncResult = {
1257
+ state: "ops";
1258
+ ops: readonly ControllerOp[];
1259
+ serverRevision: string;
1260
+ } | {
1261
+ state: "unavailable";
1262
+ };
906
1263
  interface CollaborationSession {
907
1264
  sendOps(ops: readonly ControllerOp[]): void;
1265
+ /**
1266
+ * Publishes this client's own local presence (Phase 6, ADR 0015) —
1267
+ * best-effort, unordered, never persisted, never an Op. Optional: an
1268
+ * adapter that doesn't support presence simply omits this method, and
1269
+ * `presence.broadcast()` becomes a silent no-op.
1270
+ */
1271
+ updatePresence?(presence: LocalPresence): void;
1272
+ /**
1273
+ * Requests an incremental catch-up after a reconnect (Phase 7) — optional;
1274
+ * an adapter that doesn't support this simply omits the method, and the
1275
+ * caller (`resyncAfterReconnect`) goes straight to its existing
1276
+ * persistence-backed full reload, unchanged from Phase 6.
1277
+ */
1278
+ requestSync?(): Promise<CollaborationSyncResult>;
908
1279
  close(): Promise<void>;
909
1280
  }
910
1281
  interface CreateBoardControllerOptions {
@@ -944,6 +1315,87 @@ interface CreateBoardControllerOptions {
944
1315
  * doesn't use the React theme system can set this directly instead.
945
1316
  */
946
1317
  boardTheme?: BoardThemeOptions;
1318
+ /**
1319
+ * Debounced auto-flush of pending persistence Ops after document changes
1320
+ * settle (Phase 5). Enabled by default (1000ms debounce) whenever
1321
+ * `adapters.persistence` is configured — today, without this, a Host must
1322
+ * call `flush()` manually after every edit for anything to persist. Pass
1323
+ * `false` to opt out entirely and drive `flush()` yourself, preserving
1324
+ * prior behavior exactly. Never fires on a per-change basis — rapid edits
1325
+ * coalesce into one flush of their final state (ADR 0006).
1326
+ */
1327
+ autosave?: boolean | {
1328
+ debounceMs?: number;
1329
+ };
1330
+ /**
1331
+ * Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
1332
+ * interval between outgoing presence updates sent via the configured
1333
+ * `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
1334
+ * latest value passed to `broadcast()` always eventually sends, even if
1335
+ * calls arrive faster than this interval.
1336
+ */
1337
+ presenceThrottleMs?: number;
1338
+ /**
1339
+ * Caps how many `ControllerOp`s can sit queued, unsent, for the
1340
+ * persistence pipe (`pendingOps`) or the collaboration pipe
1341
+ * (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
1342
+ * independently. Prevents unbounded memory growth from a long-lived
1343
+ * offline session or a stuck adapter. Exceeding it never fails or drops
1344
+ * the local edit itself (the Document already applied it optimistically)
1345
+ * — only queueing for that one pipe is skipped, and a
1346
+ * `{code:"queue-overflow", retryable:false}` error is emitted so a Host
1347
+ * can react. Defaults to 1000 — the Phase 9 collaboration coalescing
1348
+ * above (`collaborationCoalesceMs`) already keeps a busy drag from
1349
+ * approaching this on its own, so hitting it in practice means a pipe
1350
+ * has been offline/stuck for a genuinely long editing session.
1351
+ *
1352
+ * **Recovery** (Phase 9): the dropped op itself is gone from that one
1353
+ * pipe's queue — there is no automatic backfill, and the live
1354
+ * controller keeps running with that pipe now silently missing one
1355
+ * edit. Two things stay true regardless: (1) the in-memory Document is
1356
+ * never affected — a queue-overflow can never corrupt or roll back a
1357
+ * local edit, only skip sending it; (2) staleness is per-object, not
1358
+ * permanent — any *later* edit to that same object produces a brand
1359
+ * new, undropped Op carrying its full current state, which naturally
1360
+ * supersedes the gap (the Op model is already last-write-wins/
1361
+ * idempotent, so a superseding Op doesn't need the earlier one to have
1362
+ * arrived). The real risk is an object that's dropped and never edited
1363
+ * again before the controller is disposed or the page reloads — a Host
1364
+ * that needs strict durability should treat `queue-overflow` as a
1365
+ * signal to check `persistence.state`/`pendingOps` pressure (via
1366
+ * `usePersistenceStatus`/`getSnapshot().connection.persistence`)
1367
+ * before disposing, not assume disposing and reconnecting alone
1368
+ * repairs the gap (a fresh `load()` only returns what the backend
1369
+ * already has, which is exactly what's missing the dropped edit).
1370
+ */
1371
+ maxPendingOps?: number;
1372
+ /**
1373
+ * Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
1374
+ * trailing-throttle shape as `presenceThrottleMs`: the first Op after an
1375
+ * idle period sends immediately, and subsequent Ops for the *same*
1376
+ * object within this window replace each other (latest value wins,
1377
+ * matching the already-idempotent Op model) rather than each triggering
1378
+ * its own send. A multi-second drag that previously sent one full Op per
1379
+ * pointer-move now sends at most one per window per touched object.
1380
+ * Persistence (`pendingOps`) is unaffected — it already debounces via
1381
+ * `autosave`, so this option only changes live collaboration traffic.
1382
+ * Defaults to 50ms.
1383
+ */
1384
+ collaborationCoalesceMs?: number;
1385
+ /**
1386
+ * Caps how many resolved objects a single `content.copy`/`content.cut`
1387
+ * (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
1388
+ * in-memory clipboard at once (Phase 9) — `expandSelection` recursively
1389
+ * expands groups, so an unbounded selection (a huge group, or thousands
1390
+ * of individually selected strokes) could otherwise clone and retain an
1391
+ * arbitrarily large snapshot indefinitely, until the next copy/cut
1392
+ * replaces it. Exceeding it rejects the whole copy/cut (nothing is
1393
+ * cloned, and — for cut — nothing is removed from the Document either,
1394
+ * never a partial copy of an arbitrary subset) and emits a
1395
+ * `{code:"clipboard-overflow", retryable:false}` error. Defaults to
1396
+ * 5000.
1397
+ */
1398
+ maxClipboardItems?: number;
947
1399
  }
948
1400
  interface BoardController {
949
1401
  readonly document: ReadonlyBoardDocument;
@@ -975,6 +1427,15 @@ interface BoardController {
975
1427
  };
976
1428
  readonly view: {
977
1429
  fit(): void;
1430
+ /**
1431
+ * Frame the current selection (Phase 8), the same way `fit()` frames the
1432
+ * whole board. A no-op with nothing selected — deliberately doesn't fall
1433
+ * back to `fit()`'s "frame everything," which would be a surprising
1434
+ * result for an empty selection. On a headless board this can only
1435
+ * re-center the view (no viewport to compute a real zoom-to-fit from),
1436
+ * matching `fit()`'s own headless limitation exactly.
1437
+ */
1438
+ zoomToSelection(): void;
978
1439
  zoomTo(value: number): void;
979
1440
  centerOn(point: BoardPoint): void;
980
1441
  get(): BoardView;
@@ -996,6 +1457,64 @@ interface BoardController {
996
1457
  * Unknown ids are silently skipped, matching `remove`'s convention.
997
1458
  */
998
1459
  duplicate(ids: readonly string[]): readonly string[];
1460
+ /**
1461
+ * Creates a new Group referencing `ids` as its children and returns its
1462
+ * id, as one undoable step. Unknown ids are silently skipped, matching
1463
+ * `duplicate`/`remove`'s convention. A child id that's itself a group
1464
+ * makes a nested group — expanding nested groups into their leaf
1465
+ * members is always the caller's job, never assumed here (matches the
1466
+ * document-model `GroupObject` itself).
1467
+ */
1468
+ group(ids: readonly string[]): string;
1469
+ /**
1470
+ * Dissolves one group, returning its immediate children's ids (a nested
1471
+ * subgroup among them stays intact, itself still a group) — the group
1472
+ * record itself is removed, the children are untouched. A no-op
1473
+ * (returns `[]`) if `groupId` isn't a group.
1474
+ */
1475
+ ungroup(groupId: string): readonly string[];
1476
+ /**
1477
+ * Aligns every given object's matching edge/center to the corresponding
1478
+ * edge/center of their combined bounding box, as one undoable step.
1479
+ * `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
1480
+ * the larger Y). Ids that don't resolve, or resolve to a Group (which
1481
+ * has no position of its own), are skipped. A no-op under 2 resolvable
1482
+ * ids — there's nothing to align relative to.
1483
+ */
1484
+ align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
1485
+ /**
1486
+ * Spaces the middle objects' centers evenly between the first and last
1487
+ * (sorted along `axis`), as one undoable step — the two endpoints don't
1488
+ * move. Ids that don't resolve, or resolve to a Group, are skipped. A
1489
+ * no-op under 3 resolvable ids — there's no "middle" to distribute.
1490
+ */
1491
+ distribute(ids: readonly string[], axis: "x" | "y"): void;
1492
+ /**
1493
+ * Snapshots `ids` (recursively expanded through any group, same as
1494
+ * `duplicate`) into an internal in-memory clipboard — never
1495
+ * `navigator.clipboard`, scoped to this one controller instance and
1496
+ * replaced wholesale by the next `copy`/`cut`. Read-only; works even
1497
+ * on a read-only board.
1498
+ */
1499
+ copy(ids: readonly string[]): void;
1500
+ /** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
1501
+ cut(ids: readonly string[]): void;
1502
+ /**
1503
+ * Clones the current clipboard contents onto the board as one undoable
1504
+ * step, offset the same small cascade `duplicate` uses (no cursor
1505
+ * position to paste relative to yet). Returns the new top-level ids —
1506
+ * a pasted group's own id stands for its (also-pasted) children, which
1507
+ * aren't listed separately. `[]` when the clipboard is empty.
1508
+ */
1509
+ paste(): readonly string[];
1510
+ /**
1511
+ * Select every top-level object (Phase 8) — a group's own id stands for
1512
+ * its children, which aren't selected separately, matching `paste`'s own
1513
+ * "what the user sees" id list. Hidden objects are excluded, consistent
1514
+ * with them already being excluded from marquee selection. Works with
1515
+ * no canvas/engine, same as {@link toggleSelectionVisibility}.
1516
+ */
1517
+ selectAll(): void;
999
1518
  table: {
1000
1519
  addRow(tableId: string): void;
1001
1520
  addCol(tableId: string): void;
@@ -1006,6 +1525,29 @@ interface BoardController {
1006
1525
  };
1007
1526
  select(ids: readonly string[]): void;
1008
1527
  import(document: SerializedBoardDocument): readonly string[];
1528
+ /**
1529
+ * Toggle lock state for the current selection (or focused note/text/
1530
+ * table/image/timer), matching whatever a single Host lock/unlock
1531
+ * control already does per object type. A no-op with nothing selected,
1532
+ * on a headless board, or when every actionable target is locked by
1533
+ * another collaborator who isn't the current lock holder.
1534
+ */
1535
+ toggleSelectionLock(): void;
1536
+ /**
1537
+ * Toggle hidden state for the current selection, as one undo entry
1538
+ * (Phase 8). If any selected object is hidden, shows every selected
1539
+ * object; otherwise hides them all — same "any wins" semantics as
1540
+ * {@link toggleSelectionLock}. Hidden objects stay fully present in the
1541
+ * document (they still serialize, persist, sync, undo/redo) — they just
1542
+ * stop rendering and stop being hit-testable/selectable via pointer
1543
+ * interaction. Unlike `toggleSelectionLock`, this works on a headless
1544
+ * board too: it only touches `selection`/the document, no canvas or
1545
+ * engine involved. Custom objects have no visibility concept (no
1546
+ * `Hideable` field) and are silently skipped, matching how
1547
+ * `toggleSelectionLock` already excludes them. A no-op with nothing
1548
+ * selected or when the selection is only custom objects.
1549
+ */
1550
+ toggleSelectionVisibility(): void;
1009
1551
  };
1010
1552
  readonly query: {
1011
1553
  get(id: string): DeepReadonly<BoardObject> | undefined;
@@ -1034,6 +1576,15 @@ interface BoardController {
1034
1576
  follow(view: PresenceView): void;
1035
1577
  /** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
1036
1578
  gather(view: PresenceView): boolean;
1579
+ /**
1580
+ * Publishes this client's own cursor/tool/view for other collaborators
1581
+ * (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
1582
+ * throttled internally (`presenceThrottleMs` option, default 50ms) so
1583
+ * a raw pointermove stream never becomes a message-per-event flood. A
1584
+ * no-op if no collaboration adapter is configured, or if the
1585
+ * configured one doesn't implement `updatePresence`.
1586
+ */
1587
+ broadcast(local: LocalPresence): void;
1037
1588
  };
1038
1589
  readonly export: {
1039
1590
  svg(): string;
@@ -1059,6 +1610,7 @@ interface BoardController {
1059
1610
  }>;
1060
1611
  dispose(): Promise<void>;
1061
1612
  }
1613
+ /** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
1062
1614
  type LocalBoardSnapshot = {
1063
1615
  documentId: string;
1064
1616
  selectedStrokeId: string | null;
@@ -1067,6 +1619,7 @@ type LocalBoardSnapshot = {
1067
1619
  canRedo: boolean;
1068
1620
  disposed: boolean;
1069
1621
  };
1622
+ /** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
1070
1623
  type LocalBoard = {
1071
1624
  drawStroke(stroke: Stroke): void;
1072
1625
  selectAt(point: BoardPoint): string | null;
@@ -1253,10 +1806,19 @@ interface FocusedItemToolbarProps {
1253
1806
  snapshot: BoardSnapshot;
1254
1807
  }
1255
1808
  /**
1256
- * Floating toolbar above the focused note, shape, or text block — Colour,
1257
- * Size (note/text) or Width (shape), Lock/Unlock, Duplicate, Delete.
1258
- * Table/image/timer/custom objects and plain (non-shape) ink strokes never
1259
- * get a toolbar here — out of scope for this destination.
1809
+ * Floating toolbar above the focused note, shape, text block, or semantic
1810
+ * Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
1811
+ * (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Table/image/timer/
1812
+ * custom objects and plain (non-shape) ink strokes never get a toolbar here
1813
+ * — out of scope for this destination.
1814
+ *
1815
+ * Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
1816
+ * the legacy ink-stroke shape below: both draw from the same toolbar
1817
+ * buttons and look the same to a user, but a Rectangle/Ellipse is a real
1818
+ * BoardObject with its own resize handles (independent width/height,
1819
+ * `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
1820
+ * native selection gizmo. This duality is deliberate and temporary — see
1821
+ * docs/reports/phase-2-document-object-model.md.
1260
1822
  *
1261
1823
  * Lock/Unlock carries no per-user ownership gating: nothing else in this
1262
1824
  * SDK enforces lock ownership either (`content.update` never checks
@@ -1311,6 +1873,16 @@ interface ScrawlProviderProps {
1311
1873
  className?: string;
1312
1874
  style?: ThemeStyle;
1313
1875
  }
1876
+ /**
1877
+ * Wraps a Board `controller` you created yourself (via `createBoardController`)
1878
+ * so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
1879
+ * and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
1880
+ * Prefer `Scrawl` unless you need to construct or own the controller's
1881
+ * lifecycle yourself (e.g. you create it outside React, or need it before
1882
+ * first render). Set `disposeOnUnmount` to have this provider call
1883
+ * `controller.dispose()` on unmount; otherwise disposal remains your own
1884
+ * responsibility.
1885
+ */
1314
1886
  declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
1315
1887
  interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1316
1888
  children?: ReactNode;
@@ -1331,6 +1903,17 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
1331
1903
  */
1332
1904
  icons?: Partial<Record<BuiltInTool, ReactNode>>;
1333
1905
  }
1906
+ /**
1907
+ * The fastest path to an embedded Board: creates and owns a
1908
+ * `BoardController` for you (constructed once, disposed on unmount) and
1909
+ * renders it into a canvas. Render with no `children` to get the SDK's
1910
+ * default toolbar/UI chrome, or supply your own `children` (using the
1911
+ * `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
1912
+ * `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
1913
+ * top of the same controller. Accepts every `CreateBoardControllerOptions`
1914
+ * field except `canvas` (pass `canvas={null}` for a headless board, or an
1915
+ * existing `<canvas>` element to control its identity yourself).
1916
+ */
1334
1917
  declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
1335
1918
  interface ScrawlCanvasProps {
1336
1919
  element?: HTMLCanvasElement;
@@ -1356,6 +1939,38 @@ declare function ScrawlPortal({ children }: {
1356
1939
  declare function useScrawlController(): BoardController;
1357
1940
  declare function useScrawlTheme(): ScrawlResolvedTheme;
1358
1941
  declare function useScrawlSnapshot(): BoardSnapshot;
1942
+ /**
1943
+ * A thin, purely-derived convenience over
1944
+ * `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
1945
+ * component that only cares about save status (e.g. a
1946
+ * "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
1947
+ * this same field access itself. Adds no state and no behavior of its
1948
+ * own: React still owns none of persistence, exactly as before — this
1949
+ * hook only re-reads what the controller already tracks.
1950
+ *
1951
+ * Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
1952
+ * `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
1953
+ * `connection.persistence` object — on every document mutation, even ones
1954
+ * that never touch persistence at all, so a component using only this
1955
+ * hook would otherwise re-render on every stroke draw. `useSyncStatus`
1956
+ * below memoizes on the value (`state`/`error`), not just the object
1957
+ * reference, and returns the same cached value across renders where
1958
+ * nothing relevant changed — the same shape `usePresence` already uses
1959
+ * for its own independently-scoped store.
1960
+ */
1961
+ declare function usePersistenceStatus(): PersistenceSnapshot;
1962
+ /** 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). */
1963
+ declare function useCollaborationStatus(): CollaborationSnapshot;
1964
+ /**
1965
+ * The current presence roster (Phase 6), live-updating — wraps
1966
+ * `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
1967
+ * wraps the controller's main snapshot, via `useSyncExternalStore`. Does
1968
+ * not re-render on every remote pointer move by itself; it re-renders
1969
+ * whenever the roster the controller already tracks changes, at whatever
1970
+ * rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
1971
+ */
1972
+ declare function usePresence(): readonly PresenceUser[];
1973
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1359
1974
  type ScrawlBoardProps = {
1360
1975
  documentId: string;
1361
1976
  initialDocument?: SerializedBoardDocument;
@@ -1363,7 +1978,8 @@ type ScrawlBoardProps = {
1363
1978
  className?: string;
1364
1979
  style?: CSSProperties;
1365
1980
  };
1981
+ /** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
1366
1982
  declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
1367
1983
 
1368
- export { AssetResolutionError, DefaultBoardChrome, FocusedItemToolbar, InlineEditors, MultiplayerCursors, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, assetRef, clampAssetCacheBytes, cloneCustomObject, isAssetRef, resolveScrawlTheme, scrawlThemePresets, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
1369
- export type { AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardController, BoardKeyInput, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStyle, BoardThemeOptions, CommentMarker, CreateBoardControllerOptions, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, 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, ScrawlSurfaceTexture, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, StyleShelfProps, SupportedAssetMediaType, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId };
1984
+ 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 };
1985
+ export type { AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardController, BoardKeyInput, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStyle, BoardThemeOptions, CollaborationSnapshot, 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 };