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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts CHANGED
@@ -412,6 +412,144 @@ declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
412
412
  declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
413
413
  declare function formatTimer(ms: number): string;
414
414
 
415
+ declare const SHAPE_MIN_SIZE = 0.5;
416
+ declare const SHAPE_DEFAULT_STROKE = "#1C1C1E";
417
+ declare const SHAPE_DEFAULT_STROKE_WIDTH = 0.12;
418
+ interface RectangleObject extends Lockable {
419
+ id: string;
420
+ x: number;
421
+ y: number;
422
+ width: number;
423
+ height: number;
424
+ fill?: string;
425
+ stroke?: string;
426
+ strokeWidth?: number;
427
+ /** Corner radius in board units; clamped to at most half the shorter side at render time. */
428
+ cornerRadius?: number;
429
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
430
+ opacity?: number;
431
+ /**
432
+ * Radians, about the shape's own center `(x + width/2, y - height/2)`.
433
+ * Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
434
+ * shape's own unrotated local frame — rotation is a separate, applied-last
435
+ * transform, not baked into them, matching how Stroke/CustomBoardObject
436
+ * keep geometry and placement independent via their own `matrix`.
437
+ */
438
+ rotation?: number;
439
+ }
440
+ interface EllipseObject extends Lockable {
441
+ id: string;
442
+ x: number;
443
+ y: number;
444
+ width: number;
445
+ height: number;
446
+ fill?: string;
447
+ stroke?: string;
448
+ strokeWidth?: number;
449
+ /** `[0, 1]`; undefined means fully opaque (Phase 4). */
450
+ opacity?: number;
451
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
452
+ rotation?: number;
453
+ }
454
+ declare function cloneRectangle(rect: RectangleObject): RectangleObject;
455
+ declare function cloneEllipse(ellipse: EllipseObject): EllipseObject;
456
+ /** `"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. */
457
+ type ArrowHeadStyle = "triangle" | "none";
458
+ interface LineObject extends Lockable {
459
+ id: string;
460
+ start: BoardPoint;
461
+ end: BoardPoint;
462
+ stroke?: string;
463
+ strokeWidth?: number;
464
+ opacity?: number;
465
+ }
466
+ interface ArrowObject extends Lockable {
467
+ id: string;
468
+ start: BoardPoint;
469
+ end: BoardPoint;
470
+ head?: ArrowHeadStyle;
471
+ stroke?: string;
472
+ strokeWidth?: number;
473
+ opacity?: number;
474
+ }
475
+ /**
476
+ * Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
477
+ * type instead of five near-duplicate interfaces — a regular N-gon
478
+ * inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
479
+ * Rectangle already uses, parameterized by `sides`. Diamond is exactly a
480
+ * 4-sided regular polygon with vertex 0 pointing right (not up, like
481
+ * Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
482
+ * `polygonStartAngle`, which encodes each side count's own vertex
483
+ * orientation so the outline always matches the legacy drag-preview shape.
484
+ */
485
+ interface PolygonObject extends Lockable {
486
+ id: string;
487
+ x: number;
488
+ y: number;
489
+ width: number;
490
+ height: number;
491
+ sides: 3 | 4 | 5 | 6 | 8;
492
+ fill?: string;
493
+ stroke?: string;
494
+ strokeWidth?: number;
495
+ opacity?: number;
496
+ /** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
497
+ rotation?: number;
498
+ }
499
+ declare function clonePolygon(polygon: PolygonObject): PolygonObject;
500
+ /** 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`). */
501
+ interface StarObject extends Lockable {
502
+ id: string;
503
+ x: number;
504
+ y: number;
505
+ width: number;
506
+ height: number;
507
+ /** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
508
+ points: number;
509
+ /** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
510
+ innerRadiusRatio: number;
511
+ fill?: string;
512
+ stroke?: string;
513
+ strokeWidth?: number;
514
+ opacity?: number;
515
+ rotation?: number;
516
+ }
517
+ declare function cloneStar(star: StarObject): StarObject;
518
+ /** 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. */
519
+ interface HeartObject extends Lockable {
520
+ id: string;
521
+ x: number;
522
+ y: number;
523
+ width: number;
524
+ height: number;
525
+ fill?: string;
526
+ stroke?: string;
527
+ strokeWidth?: number;
528
+ opacity?: number;
529
+ rotation?: number;
530
+ }
531
+ declare function cloneHeart(heart: HeartObject): HeartObject;
532
+ declare function cloneLine(line: LineObject): LineObject;
533
+ declare function cloneArrow(arrow: ArrowObject): ArrowObject;
534
+ /**
535
+ * A logical grouping of other board objects (Phase 3 — Selection,
536
+ * Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
537
+ * own — a group's bounds are always derived on demand from its (recursively
538
+ * resolved) children, and "moving/rotating/scaling the group" is exactly a
539
+ * multi-object transform applied to those children, nothing more. A group
540
+ * has no renderer/mesh of its own; its only visual presence is the
541
+ * selection gizmo's bounding box while it's the current selection.
542
+ *
543
+ * `children` may itself contain other group ids (nested groups) — expanding
544
+ * a group into its leaf members is always done by the caller (recursively,
545
+ * with cycle protection), never assumed here.
546
+ */
547
+ interface GroupObject extends Lockable {
548
+ id: string;
549
+ children: string[];
550
+ }
551
+ declare function cloneGroup(group: GroupObject): GroupObject;
552
+
415
553
  interface BoardPoint {
416
554
  x: number;
417
555
  y: number;
@@ -477,6 +615,31 @@ interface SerializedDocument {
477
615
  timers?: KitchenTimer[];
478
616
  /** Absent in documents saved before Custom board objects existed (ticket #22). */
479
617
  customObjects?: CustomBoardObject[];
618
+ /** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
619
+ rectangles?: RectangleObject[];
620
+ /** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
621
+ ellipses?: EllipseObject[];
622
+ /** Absent in documents saved before Groups existed (Phase 3). */
623
+ groups?: GroupObject[];
624
+ /** Absent in documents saved before semantic Line objects existed (Phase 4). */
625
+ lines?: LineObject[];
626
+ /** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
627
+ arrows?: ArrowObject[];
628
+ /** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
629
+ polygons?: PolygonObject[];
630
+ /** Absent in documents saved before semantic Star objects existed (Phase 4). */
631
+ stars?: StarObject[];
632
+ /** Absent in documents saved before semantic Heart objects existed (Phase 4). */
633
+ hearts?: HeartObject[];
634
+ /**
635
+ * Every content-object id (every type above except comments, which are
636
+ * host-synced and never enter this schema) in paint order, back to front.
637
+ * Absent in documents saved before per-object z-order existed (Phase 3) —
638
+ * migration synthesizes a default order preserving the old fixed-Z-band
639
+ * visual stacking exactly, so an existing document never visibly changes
640
+ * on load; only an explicit reorder action touches this from then on.
641
+ */
642
+ objectOrder?: string[];
480
643
  }
481
644
  declare const INK_COLORS: {
482
645
  readonly black: "#1C1C1E";
@@ -728,6 +891,60 @@ interface DocumentChange {
728
891
  /** Ids of removed custom objects. */
729
892
  customObjectsRemoved: string[];
730
893
  customObjectsUpdated: CustomBoardObject[];
894
+ /** Semantic Rectangle objects (Phase 2). */
895
+ rectanglesAdded: RectangleObject[];
896
+ /** Ids of removed rectangles. */
897
+ rectanglesRemoved: string[];
898
+ rectanglesUpdated: RectangleObject[];
899
+ /** Semantic Ellipse objects (Phase 2). */
900
+ ellipsesAdded: EllipseObject[];
901
+ /** Ids of removed ellipses. */
902
+ ellipsesRemoved: string[];
903
+ ellipsesUpdated: EllipseObject[];
904
+ /** Groups (Phase 3). */
905
+ groupsAdded: GroupObject[];
906
+ /** Ids of removed groups (ungrouping, or deleting a group). */
907
+ groupsRemoved: string[];
908
+ groupsUpdated: GroupObject[];
909
+ /** Semantic Line objects (Phase 4). */
910
+ linesAdded: LineObject[];
911
+ /** Ids of removed lines. */
912
+ linesRemoved: string[];
913
+ linesUpdated: LineObject[];
914
+ /** Semantic Arrow objects (Phase 4). */
915
+ arrowsAdded: ArrowObject[];
916
+ /** Ids of removed arrows. */
917
+ arrowsRemoved: string[];
918
+ arrowsUpdated: ArrowObject[];
919
+ /** Semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
920
+ polygonsAdded: PolygonObject[];
921
+ /** Ids of removed polygons. */
922
+ polygonsRemoved: string[];
923
+ polygonsUpdated: PolygonObject[];
924
+ /** Semantic Star objects (Phase 4). */
925
+ starsAdded: StarObject[];
926
+ /** Ids of removed stars. */
927
+ starsRemoved: string[];
928
+ starsUpdated: StarObject[];
929
+ /** Semantic Heart objects (Phase 4). */
930
+ heartsAdded: HeartObject[];
931
+ /** Ids of removed hearts. */
932
+ heartsRemoved: string[];
933
+ heartsUpdated: HeartObject[];
934
+ /**
935
+ * The full current paint order (back to front) of every flat content
936
+ * object — strokes, texts, tables, images, rectangles, ellipses, lines,
937
+ * arrows, custom objects, and groups. Populated whenever `objectOrder`
938
+ * actually changed:
939
+ * an explicit reorder (`bringForward` etc.), or any add/remove that
940
+ * touches it — a removal shifts every id after it down one rank, not
941
+ * just the removed one, so renderers need this to resync everyone, not
942
+ * only the ids the same change's own `*Added`/`*Removed`/`*Updated`
943
+ * fields name. Notes (their own `zOffset` peel depth) and Kitchen Timers
944
+ * (genuine 3D objects, not a flat layer) are intentionally not part of
945
+ * this order at all.
946
+ */
947
+ orderChanged: readonly string[];
731
948
  }
732
949
  type Listener = (change: DocumentChange) => void;
733
950
  declare class BoardDocument {
@@ -742,12 +959,63 @@ declare class BoardDocument {
742
959
  private readonly timers;
743
960
  /** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
744
961
  private readonly customObjects;
962
+ private readonly rectangles;
963
+ private readonly ellipses;
964
+ private readonly groups;
965
+ private readonly lines;
966
+ private readonly arrows;
967
+ private readonly polygons;
968
+ private readonly stars;
969
+ private readonly hearts;
745
970
  private readonly bboxes;
746
971
  private readonly listeners;
972
+ /** Paint order (back to front) of every flat content object — see `DocumentChange.orderChanged`'s doc comment. */
973
+ private objectOrder;
974
+ private orderIndex;
747
975
  constructor(id: DocumentId);
976
+ private reindexOrder;
977
+ /** The full current paint order, back to front. */
978
+ order(): readonly string[];
979
+ /** This object's rank in the paint order, or -1 if it doesn't participate (unknown id, a note, or a timer). */
980
+ orderRank(id: string): number;
981
+ private bringForward;
982
+ private sendBackward;
983
+ private bringToFront;
984
+ private sendToBack;
985
+ /** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
986
+ reorder(id: string, direction: "forward" | "backward" | "front" | "back"): void;
987
+ /**
988
+ * Overwrites the paint order directly — used only when loading a document
989
+ * that already carries a persisted `objectOrder`; every other order
990
+ * mutation goes through `reorder`/the automatic append-on-add tracking in
991
+ * `emit`. Ids not present in the document are dropped; ids present in the
992
+ * document but missing from `order` are appended at the back, so a
993
+ * partially-stale order (e.g. from a schema migration) never silently
994
+ * drops an object from paint order entirely.
995
+ */
996
+ private setOrder;
748
997
  get(id: string): Stroke | undefined;
749
998
  all(): IterableIterator<Stroke>;
750
- bbox(id: string): BBox | undefined;
999
+ /**
1000
+ * World-space bounds for any content object, of any type. Strokes hit
1001
+ * their cached-on-mutation fast path (`bboxes`, populated by
1002
+ * `addStrokes`/`transformStrokes` — many points, worth caching); every
1003
+ * other type computes on demand via `objectBounds.ts` (cheap arithmetic,
1004
+ * no caching needed). A group's bounds are the union of its (recursively
1005
+ * resolved) children — `seen` guards against a cycle in nested groups.
1006
+ */
1007
+ bbox(id: string, seen?: Set<string>): BBox | undefined;
1008
+ /**
1009
+ * True if `id` exists and is locked, for any type — the same per-type
1010
+ * probe pattern as `bbox`, for interactive gestures (drag/transform) that
1011
+ * need to gate on lock state regardless of what's selected. Custom
1012
+ * objects are deliberately excluded: their `lock` field is a different
1013
+ * shape (`{holderId, acquiredAt}`, no display name) with no interactive
1014
+ * lock UI yet, matching the existing, deliberate "always unlockable,
1015
+ * never gates a drag" treatment already established elsewhere (e.g.
1016
+ * `getSelectedItemInfo`'s custom branch hardcodes `isLocked: false`).
1017
+ */
1018
+ isLocked(id: string): boolean;
751
1019
  subscribe(listener: Listener): () => void;
752
1020
  addStrokes(strokes: Stroke[]): void;
753
1021
  removeStrokes(ids: string[]): void;
@@ -790,6 +1058,54 @@ declare class BoardDocument {
790
1058
  removeCustomObjects(ids: string[]): void;
791
1059
  /** Replace a custom object's contents under the same id. */
792
1060
  setCustomObject(object: CustomBoardObject): void;
1061
+ getRectangle(id: string): RectangleObject | undefined;
1062
+ allRectangles(): IterableIterator<RectangleObject>;
1063
+ addRectangles(rectangles: RectangleObject[]): void;
1064
+ removeRectangles(ids: string[]): void;
1065
+ /** Replace a rectangle's contents (move, resize, restyle) under the same id. */
1066
+ setRectangle(rect: RectangleObject): void;
1067
+ getEllipse(id: string): EllipseObject | undefined;
1068
+ allEllipses(): IterableIterator<EllipseObject>;
1069
+ addEllipses(ellipses: EllipseObject[]): void;
1070
+ removeEllipses(ids: string[]): void;
1071
+ /** Replace an ellipse's contents (move, resize, restyle) under the same id. */
1072
+ setEllipse(ellipse: EllipseObject): void;
1073
+ getGroup(id: string): GroupObject | undefined;
1074
+ allGroups(): IterableIterator<GroupObject>;
1075
+ addGroups(groups: GroupObject[]): void;
1076
+ removeGroups(ids: string[]): void;
1077
+ /** Replace a group's contents (its children list) under the same id. */
1078
+ setGroup(group: GroupObject): void;
1079
+ getLine(id: string): LineObject | undefined;
1080
+ allLines(): IterableIterator<LineObject>;
1081
+ addLines(lines: LineObject[]): void;
1082
+ removeLines(ids: string[]): void;
1083
+ /** Replace a line's contents (move, restyle) under the same id. */
1084
+ setLine(line: LineObject): void;
1085
+ getArrow(id: string): ArrowObject | undefined;
1086
+ allArrows(): IterableIterator<ArrowObject>;
1087
+ addArrows(arrows: ArrowObject[]): void;
1088
+ removeArrows(ids: string[]): void;
1089
+ /** Replace an arrow's contents (move, restyle, change head) under the same id. */
1090
+ setArrow(arrow: ArrowObject): void;
1091
+ getPolygon(id: string): PolygonObject | undefined;
1092
+ allPolygons(): IterableIterator<PolygonObject>;
1093
+ addPolygons(polygons: PolygonObject[]): void;
1094
+ removePolygons(ids: string[]): void;
1095
+ /** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
1096
+ setPolygon(polygon: PolygonObject): void;
1097
+ getStar(id: string): StarObject | undefined;
1098
+ allStars(): IterableIterator<StarObject>;
1099
+ addStars(stars: StarObject[]): void;
1100
+ removeStars(ids: string[]): void;
1101
+ /** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
1102
+ setStar(star: StarObject): void;
1103
+ getHeart(id: string): HeartObject | undefined;
1104
+ allHearts(): IterableIterator<HeartObject>;
1105
+ addHearts(hearts: HeartObject[]): void;
1106
+ removeHearts(ids: string[]): void;
1107
+ /** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
1108
+ setHeart(heart: HeartObject): void;
793
1109
  setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
794
1110
  setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
795
1111
  setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
@@ -797,18 +1113,58 @@ declare class BoardDocument {
797
1113
  setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
798
1114
  setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
799
1115
  setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1116
+ setRectangleLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1117
+ setEllipseLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1118
+ setGroupLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1119
+ setLineLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1120
+ setArrowLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1121
+ setPolygonLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1122
+ setStarLocked(id: string, locked: boolean, by?: LockHolder | null): void;
1123
+ setHeartLocked(id: string, locked: boolean, by?: LockHolder | null): void;
800
1124
  /** Replace all content (initial load). Does not touch `version`. */
801
- replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]): void;
1125
+ replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[], rectangles?: RectangleObject[], ellipses?: EllipseObject[], groups?: GroupObject[], lines?: LineObject[], arrows?: ArrowObject[], polygons?: PolygonObject[], stars?: StarObject[], hearts?: HeartObject[],
1126
+ /** Persisted paint order; absent for a document saved before Phase 3, in which case one is synthesized (see `ORDERED_ADDED_FIELDS`'s doc comment). */
1127
+ objectOrder?: readonly string[]): void;
802
1128
  /** Apply incremental real-time change received from a remote collaborator over WebSocket. */
803
1129
  applyRemoteChange(change: Partial<DocumentChange>): void;
804
1130
  toJSON(): SerializedDocument;
1131
+ static deserializeGroups(data: SerializedDocument): GroupObject[];
1132
+ static deserializeLines(data: SerializedDocument): LineObject[];
1133
+ static deserializeArrows(data: SerializedDocument): ArrowObject[];
1134
+ static deserializePolygons(data: SerializedDocument): PolygonObject[];
1135
+ static deserializeStars(data: SerializedDocument): StarObject[];
1136
+ static deserializeHearts(data: SerializedDocument): HeartObject[];
805
1137
  static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
1138
+ static deserializeRectangles(data: SerializedDocument): RectangleObject[];
1139
+ static deserializeEllipses(data: SerializedDocument): EllipseObject[];
806
1140
  static deserializeImages(data: SerializedDocument): ImageBlock[];
807
1141
  static deserializeTimers(data: SerializedDocument): KitchenTimer[];
808
1142
  static deserializeNotes(data: SerializedDocument): StickyNote[];
809
1143
  static deserializeTexts(data: SerializedDocument): TextBlock[];
810
1144
  static deserializeTables(data: SerializedDocument): TableBlock[];
811
1145
  static deserializeStrokes(data: SerializedDocument): Stroke[];
1146
+ /**
1147
+ * Every mutation funnels through here, so paint-order tracking lives in
1148
+ * exactly one place rather than at every individual add/remove call site
1149
+ * (18 of them, times `replaceAll`/`applyRemoteChange`) — new ids are
1150
+ * appended to the back (front-most) of `objectOrder`, removed ids are
1151
+ * spliced out. An explicit reorder (`reorder`/`setOrder`) updates
1152
+ * `objectOrder` itself before calling this, so this step is a no-op for
1153
+ * ids already tracked (idempotent by construction: `orderIndex.has` gates
1154
+ * every append).
1155
+ *
1156
+ * `orderChanged` is populated here whenever `objectOrder` actually
1157
+ * changed — not just for an explicit reorder, but for any add/remove too.
1158
+ * A pure append never shifts an existing id's rank (new ids land at the
1159
+ * tail), but a removal splices a middle id out, which *does* shift every
1160
+ * id after it down by one — a renderer that only resynced Z on an
1161
+ * explicit reorder would silently render those with a stale rank until
1162
+ * something else happened to touch them, eventually colliding with a
1163
+ * freshly-added object's freshly-computed Z. Firing this on every
1164
+ * order-touching change (not just removes) is simpler than special-
1165
+ * casing which kind of change actually needs it, at the cost of a
1166
+ * redundant same-value resync on a pure append.
1167
+ */
812
1168
  private emit;
813
1169
  }
814
1170
 
@@ -844,6 +1200,21 @@ interface Command {
844
1200
  apply(doc: BoardDocument): void;
845
1201
  revert(doc: BoardDocument): void;
846
1202
  }
1203
+ /**
1204
+ * Several commands applied/reverted together as one undo step (Phase 3
1205
+ * consolidation — this exact class used to be hand-duplicated as a private
1206
+ * `CommandBatch` in `controller-internal.ts` and an exported
1207
+ * `ExtensionCommandBatch` in `interaction/tools/customTool.ts`; both now
1208
+ * import this one instead). Revert runs in reverse order, so a batch that
1209
+ * depends on ordering (e.g. add-then-reference) undoes cleanly.
1210
+ */
1211
+ declare class CommandBatch implements Command {
1212
+ readonly label: string;
1213
+ private readonly commands;
1214
+ constructor(label: string, commands: readonly Command[]);
1215
+ apply(doc: BoardDocument): void;
1216
+ revert(doc: BoardDocument): void;
1217
+ }
847
1218
  /** How a command reached the document — undo/redo are audited distinctly. */
848
1219
  type CommandKind = "do" | "undo" | "redo";
849
1220
  declare class History {
@@ -908,6 +1279,40 @@ declare class TransformCommand implements Command {
908
1279
  revert(doc: BoardDocument): void;
909
1280
  private compose;
910
1281
  }
1282
+ /**
1283
+ * One move gesture over a mixed-type selection (Phase 3) — the general
1284
+ * successor to `TransformCommand` for translation. `TransformCommand`
1285
+ * itself stays as-is (still used for scale/rotate, which remain stroke-only
1286
+ * this phase — see `interaction/tools/selectTool.ts`'s `TransformState`):
1287
+ * its own per-id `doc.get(id)` check already no-ops safely for any id that
1288
+ * isn't a stroke, so it doesn't need touching for that narrower case.
1289
+ *
1290
+ * `delta` here is always a pure translation (never scale/rotate), so
1291
+ * `apply(delta, point)` is a safe, uniform way to move every position-only
1292
+ * type's `x`/`y` — translating commutes trivially regardless of a shape's
1293
+ * own rotation. Matrix-carrying types (stroke, Custom) instead compose
1294
+ * `delta` onto their existing matrix/transform, matching `TransformCommand`.
1295
+ *
1296
+ * A group id in `ids` is expanded into its (recursively resolved, cycle-
1297
+ * safe) children every time `compose` runs — deterministic, since group
1298
+ * membership never changes mid-command — so moving a selected group means
1299
+ * moving every one of its members by the same delta. This expansion is
1300
+ * `TransformObjectsCommand`'s own responsibility precisely so a caller that
1301
+ * doesn't itself expand groups (e.g. `ScrawlEngine.nudgeSelection`) still
1302
+ * gets correct behavior; `interaction/tools/selectTool.ts`'s `TransformState`
1303
+ * separately expands for its own reason (live per-child drag preview), which
1304
+ * makes this a no-op re-expansion for that caller, not a conflict.
1305
+ */
1306
+ declare class TransformObjectsCommand implements Command {
1307
+ private readonly ids;
1308
+ private readonly delta;
1309
+ readonly label = "move selection";
1310
+ private readonly inverse;
1311
+ constructor(ids: readonly string[], delta: Mat2x3);
1312
+ apply(doc: BoardDocument): void;
1313
+ revert(doc: BoardDocument): void;
1314
+ private compose;
1315
+ }
911
1316
  declare class AddNoteCommand implements Command {
912
1317
  readonly label = "add note";
913
1318
  private readonly note;
@@ -1029,8 +1434,213 @@ declare class DeleteTimerCommand implements Command {
1029
1434
  apply(doc: BoardDocument): void;
1030
1435
  revert(doc: BoardDocument): void;
1031
1436
  }
1437
+ /** Add/update/delete for semantic Rectangle objects (Phase 2). */
1438
+ declare class AddRectangleCommand implements Command {
1439
+ readonly label = "add rectangle";
1440
+ private readonly rect;
1441
+ constructor(rect: RectangleObject);
1442
+ apply(doc: BoardDocument): void;
1443
+ revert(doc: BoardDocument): void;
1444
+ }
1445
+ declare class UpdateRectangleCommand implements Command {
1446
+ readonly label = "update rectangle";
1447
+ private readonly before;
1448
+ private readonly after;
1449
+ constructor(before: RectangleObject, after: RectangleObject);
1450
+ apply(doc: BoardDocument): void;
1451
+ revert(doc: BoardDocument): void;
1452
+ }
1453
+ declare class DeleteRectangleCommand implements Command {
1454
+ readonly label = "delete rectangle";
1455
+ private readonly rect;
1456
+ constructor(rect: RectangleObject);
1457
+ apply(doc: BoardDocument): void;
1458
+ revert(doc: BoardDocument): void;
1459
+ }
1460
+ /** Add/update/delete for semantic Ellipse objects (Phase 2). */
1461
+ declare class AddEllipseCommand implements Command {
1462
+ readonly label = "add ellipse";
1463
+ private readonly ellipse;
1464
+ constructor(ellipse: EllipseObject);
1465
+ apply(doc: BoardDocument): void;
1466
+ revert(doc: BoardDocument): void;
1467
+ }
1468
+ declare class UpdateEllipseCommand implements Command {
1469
+ readonly label = "update ellipse";
1470
+ private readonly before;
1471
+ private readonly after;
1472
+ constructor(before: EllipseObject, after: EllipseObject);
1473
+ apply(doc: BoardDocument): void;
1474
+ revert(doc: BoardDocument): void;
1475
+ }
1476
+ declare class DeleteEllipseCommand implements Command {
1477
+ readonly label = "delete ellipse";
1478
+ private readonly ellipse;
1479
+ constructor(ellipse: EllipseObject);
1480
+ apply(doc: BoardDocument): void;
1481
+ revert(doc: BoardDocument): void;
1482
+ }
1483
+ /** Add/update/delete for semantic Line objects (Phase 4). */
1484
+ declare class AddLineCommand implements Command {
1485
+ readonly label = "add line";
1486
+ private readonly line;
1487
+ constructor(line: LineObject);
1488
+ apply(doc: BoardDocument): void;
1489
+ revert(doc: BoardDocument): void;
1490
+ }
1491
+ declare class UpdateLineCommand implements Command {
1492
+ readonly label = "update line";
1493
+ private readonly before;
1494
+ private readonly after;
1495
+ constructor(before: LineObject, after: LineObject);
1496
+ apply(doc: BoardDocument): void;
1497
+ revert(doc: BoardDocument): void;
1498
+ }
1499
+ declare class DeleteLineCommand implements Command {
1500
+ readonly label = "delete line";
1501
+ private readonly line;
1502
+ constructor(line: LineObject);
1503
+ apply(doc: BoardDocument): void;
1504
+ revert(doc: BoardDocument): void;
1505
+ }
1506
+ /** Add/update/delete for semantic Arrow objects (Phase 4). */
1507
+ declare class AddArrowCommand implements Command {
1508
+ readonly label = "add arrow";
1509
+ private readonly arrow;
1510
+ constructor(arrow: ArrowObject);
1511
+ apply(doc: BoardDocument): void;
1512
+ revert(doc: BoardDocument): void;
1513
+ }
1514
+ declare class UpdateArrowCommand implements Command {
1515
+ readonly label = "update arrow";
1516
+ private readonly before;
1517
+ private readonly after;
1518
+ constructor(before: ArrowObject, after: ArrowObject);
1519
+ apply(doc: BoardDocument): void;
1520
+ revert(doc: BoardDocument): void;
1521
+ }
1522
+ declare class DeleteArrowCommand implements Command {
1523
+ readonly label = "delete arrow";
1524
+ private readonly arrow;
1525
+ constructor(arrow: ArrowObject);
1526
+ apply(doc: BoardDocument): void;
1527
+ revert(doc: BoardDocument): void;
1528
+ }
1529
+ /** Add/update/delete for semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
1530
+ declare class AddPolygonCommand implements Command {
1531
+ readonly label = "add polygon";
1532
+ private readonly polygon;
1533
+ constructor(polygon: PolygonObject);
1534
+ apply(doc: BoardDocument): void;
1535
+ revert(doc: BoardDocument): void;
1536
+ }
1537
+ declare class UpdatePolygonCommand implements Command {
1538
+ readonly label = "update polygon";
1539
+ private readonly before;
1540
+ private readonly after;
1541
+ constructor(before: PolygonObject, after: PolygonObject);
1542
+ apply(doc: BoardDocument): void;
1543
+ revert(doc: BoardDocument): void;
1544
+ }
1545
+ declare class DeletePolygonCommand implements Command {
1546
+ readonly label = "delete polygon";
1547
+ private readonly polygon;
1548
+ constructor(polygon: PolygonObject);
1549
+ apply(doc: BoardDocument): void;
1550
+ revert(doc: BoardDocument): void;
1551
+ }
1552
+ /** Add/update/delete for semantic Star objects (Phase 4). */
1553
+ declare class AddStarCommand implements Command {
1554
+ readonly label = "add star";
1555
+ private readonly star;
1556
+ constructor(star: StarObject);
1557
+ apply(doc: BoardDocument): void;
1558
+ revert(doc: BoardDocument): void;
1559
+ }
1560
+ declare class UpdateStarCommand implements Command {
1561
+ readonly label = "update star";
1562
+ private readonly before;
1563
+ private readonly after;
1564
+ constructor(before: StarObject, after: StarObject);
1565
+ apply(doc: BoardDocument): void;
1566
+ revert(doc: BoardDocument): void;
1567
+ }
1568
+ declare class DeleteStarCommand implements Command {
1569
+ readonly label = "delete star";
1570
+ private readonly star;
1571
+ constructor(star: StarObject);
1572
+ apply(doc: BoardDocument): void;
1573
+ revert(doc: BoardDocument): void;
1574
+ }
1575
+ /** Add/update/delete for semantic Heart objects (Phase 4). */
1576
+ declare class AddHeartCommand implements Command {
1577
+ readonly label = "add heart";
1578
+ private readonly heart;
1579
+ constructor(heart: HeartObject);
1580
+ apply(doc: BoardDocument): void;
1581
+ revert(doc: BoardDocument): void;
1582
+ }
1583
+ declare class UpdateHeartCommand implements Command {
1584
+ readonly label = "update heart";
1585
+ private readonly before;
1586
+ private readonly after;
1587
+ constructor(before: HeartObject, after: HeartObject);
1588
+ apply(doc: BoardDocument): void;
1589
+ revert(doc: BoardDocument): void;
1590
+ }
1591
+ declare class DeleteHeartCommand implements Command {
1592
+ readonly label = "delete heart";
1593
+ private readonly heart;
1594
+ constructor(heart: HeartObject);
1595
+ apply(doc: BoardDocument): void;
1596
+ revert(doc: BoardDocument): void;
1597
+ }
1598
+ /** Add/update/delete for Groups (Phase 3). */
1599
+ declare class AddGroupCommand implements Command {
1600
+ readonly label = "group";
1601
+ private readonly group;
1602
+ constructor(group: GroupObject);
1603
+ apply(doc: BoardDocument): void;
1604
+ revert(doc: BoardDocument): void;
1605
+ }
1606
+ declare class UpdateGroupCommand implements Command {
1607
+ readonly label = "update group";
1608
+ private readonly before;
1609
+ private readonly after;
1610
+ constructor(before: GroupObject, after: GroupObject);
1611
+ apply(doc: BoardDocument): void;
1612
+ revert(doc: BoardDocument): void;
1613
+ }
1614
+ declare class DeleteGroupCommand implements Command {
1615
+ readonly label = "ungroup";
1616
+ private readonly group;
1617
+ constructor(group: GroupObject);
1618
+ apply(doc: BoardDocument): void;
1619
+ revert(doc: BoardDocument): void;
1620
+ }
1621
+ type ReorderDirection = "forward" | "backward" | "front" | "back";
1622
+ /**
1623
+ * Bring-forward / send-backward / bring-to-front / send-to-back (Phase 3) —
1624
+ * one command family covering all four directions rather than four
1625
+ * near-identical classes, since the only difference between them is which
1626
+ * `BoardDocument.reorder` direction to replay. Captures the full paint order
1627
+ * on first `apply` rather than in the constructor — `BoardDocument.order()`
1628
+ * needs the doc, which a `Command` only ever receives via `apply`/`revert` —
1629
+ * so `revert` can restore it exactly; redo re-runs the same `reorder` call,
1630
+ * which is deterministic because `revert` always restores the identical
1631
+ * starting order first.
1632
+ */
1633
+ declare class ReorderObjectCommand implements Command {
1634
+ private readonly id;
1635
+ private readonly direction;
1636
+ readonly label: string;
1637
+ private before;
1638
+ constructor(id: string, direction: ReorderDirection);
1639
+ apply(doc: BoardDocument): void;
1640
+ revert(doc: BoardDocument): void;
1641
+ }
1032
1642
  interface LockTarget {
1033
- type: "stroke" | "note" | "text" | "table" | "image" | "timer";
1643
+ type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart";
1034
1644
  id: string;
1035
1645
  locked: boolean;
1036
1646
  lockedBy?: string;
@@ -1047,7 +1657,7 @@ declare class LockItemsCommand implements Command {
1047
1657
  private applyLock;
1048
1658
  }
1049
1659
 
1050
- type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
1660
+ type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "rectangles" | "ellipses" | "groups" | "lines" | "arrows" | "polygons" | "stars" | "hearts" | "customObjects";
1051
1661
  type Op = {
1052
1662
  kind: "upsert";
1053
1663
  collection: OpCollection;
@@ -1091,10 +1701,10 @@ declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn
1091
1701
  declare class SpatialIndex {
1092
1702
  private readonly doc;
1093
1703
  private readonly cells;
1094
- private readonly strokeCells;
1704
+ private readonly objectCells;
1095
1705
  private readonly unsubscribe;
1096
1706
  constructor(doc: BoardDocument);
1097
- /** Ids of strokes whose bbox may overlap the query rect. */
1707
+ /** Ids of content objects (any type except groups) whose bbox may overlap the query rect. */
1098
1708
  query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
1099
1709
  dispose(): void;
1100
1710
  private insert;
@@ -1176,5 +1786,5 @@ type BoardStroke = Stroke;
1176
1786
  type SerializedBoardStroke = SerializedStroke;
1177
1787
  type SerializedBoardDocument = CurrentSerializedDocument;
1178
1788
 
1179
- export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, UpdateImageCommand, UpdateNoteCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneCustomObject, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
1180
- export type { AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardKeyInput, BoardPoint, BoardPointerInput, BoardScene, BoardStroke, ClusterIdFactory, Command, CommandKind, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DocumentChange, DocumentId, DocumentLoadResult, DocumentRecoveryCode, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, ImageBlock, InputModifiers, JsonObject, JsonValue, KitchenTimer, LockHolder, LockTarget, Lockable, Mat2x3, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PresencePlacement, QueryableBoardObject, ReadonlyCustomObject, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlExtension, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };
1789
+ export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddArrowCommand, AddEllipseCommand, AddGroupCommand, AddHeartCommand, AddImageCommand, AddLineCommand, AddNoteCommand, AddPolygonCommand, AddRectangleCommand, AddStarCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, CommandBatch, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, ReorderObjectCommand, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, SHAPE_DEFAULT_STROKE, SHAPE_DEFAULT_STROKE_WIDTH, SHAPE_MIN_SIZE, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, TransformObjectsCommand, UpdateArrowCommand, UpdateEllipseCommand, UpdateGroupCommand, UpdateHeartCommand, UpdateImageCommand, UpdateLineCommand, UpdateNoteCommand, UpdatePolygonCommand, UpdateRectangleCommand, UpdateStarCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneArrow, cloneCustomObject, cloneEllipse, cloneGroup, cloneHeart, cloneImage, cloneLine, cloneNote, clonePolygon, cloneRectangle, cloneStar, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
1790
+ export type { ArrowHeadStyle, ArrowObject, AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardKeyInput, BoardPoint, BoardPointerInput, BoardScene, BoardStroke, ClusterIdFactory, Command, CommandKind, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DocumentChange, DocumentId, DocumentLoadResult, DocumentRecoveryCode, EllipseObject, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, GroupObject, HeartObject, ImageBlock, InputModifiers, JsonObject, JsonValue, KitchenTimer, LineObject, LockHolder, LockTarget, Lockable, Mat2x3, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PolygonObject, PresencePlacement, QueryableBoardObject, ReadonlyCustomObject, RectangleObject, ReorderDirection, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlExtension, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StarObject, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };