@scrawl-board/board 0.1.0-beta.0
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/LICENSE +201 -0
- package/NOTICE +28 -0
- package/README.md +106 -0
- package/dist/browser.d.ts +1012 -0
- package/dist/browser.js +49190 -0
- package/dist/core.d.ts +1084 -0
- package/dist/core.js +2253 -0
- package/dist/index.d.ts +1797 -0
- package/dist/index.js +50827 -0
- package/dist/react.d.ts +1171 -0
- package/dist/react.js +50797 -0
- package/dist/styles.css +297 -0
- package/package.json +72 -0
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
declare const documentIdBrand: unique symbol;
|
|
2
|
+
declare const strokeIdBrand: unique symbol;
|
|
3
|
+
type DocumentId = string & {
|
|
4
|
+
readonly [documentIdBrand]: "DocumentId";
|
|
5
|
+
};
|
|
6
|
+
type StrokeId = string & {
|
|
7
|
+
readonly [strokeIdBrand]: "StrokeId";
|
|
8
|
+
};
|
|
9
|
+
declare function documentId(value: string): DocumentId;
|
|
10
|
+
declare function strokeId(value: string): StrokeId;
|
|
11
|
+
|
|
12
|
+
/** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
|
|
13
|
+
type AssetRef = string;
|
|
14
|
+
|
|
15
|
+
type Mat2x3 = [number, number, number, number, number, number];
|
|
16
|
+
declare const IDENTITY: Mat2x3;
|
|
17
|
+
declare function isIdentity(m: Mat2x3): boolean;
|
|
18
|
+
/** Compose: apply `first`, then `second`. */
|
|
19
|
+
declare function mul(second: Mat2x3, first: Mat2x3): Mat2x3;
|
|
20
|
+
declare function invert(m: Mat2x3): Mat2x3;
|
|
21
|
+
declare function apply(m: Mat2x3, p: BoardPoint): BoardPoint;
|
|
22
|
+
declare function translation(dx: number, dy: number): Mat2x3;
|
|
23
|
+
declare function rotationAbout(angle: number, cx: number, cy: number): Mat2x3;
|
|
24
|
+
declare function scalingAbout(sx: number, sy: number, cx: number, cy: number): Mat2x3;
|
|
25
|
+
/** Approximate uniform length scale (average of the axis scales). */
|
|
26
|
+
declare function avgScale(m: Mat2x3): number;
|
|
27
|
+
|
|
28
|
+
/** A stable namespaced string, e.g. `com.acme.kanban`. Never a display name. */
|
|
29
|
+
type ExtensionId = string;
|
|
30
|
+
/** A stable namespaced string, e.g. `com.acme.kanban/card-tool`. */
|
|
31
|
+
type ToolId = string;
|
|
32
|
+
/** A stable namespaced string, e.g. `com.acme.kanban/card`. */
|
|
33
|
+
type ObjectType = string;
|
|
34
|
+
interface ExtensionRequirement {
|
|
35
|
+
extensionId: ExtensionId;
|
|
36
|
+
extensionApiVersion: 1;
|
|
37
|
+
}
|
|
38
|
+
interface ScrawlExtension {
|
|
39
|
+
id: ExtensionId;
|
|
40
|
+
extensionApiVersion: 1;
|
|
41
|
+
requires?: readonly ExtensionRequirement[];
|
|
42
|
+
tools?: readonly CustomToolDefinition[];
|
|
43
|
+
objectTypes?: readonly CustomObjectDefinition[];
|
|
44
|
+
}
|
|
45
|
+
type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
46
|
+
readonly [key: string]: JsonValue;
|
|
47
|
+
};
|
|
48
|
+
type JsonObject = {
|
|
49
|
+
readonly [key: string]: JsonValue;
|
|
50
|
+
};
|
|
51
|
+
interface CustomBoardObject {
|
|
52
|
+
id: string;
|
|
53
|
+
type: ObjectType;
|
|
54
|
+
schemaVersion: number;
|
|
55
|
+
transform: Mat2x3;
|
|
56
|
+
/** Safe placeholder geometry, refreshed by the SDK on every valid command. */
|
|
57
|
+
fallback: {
|
|
58
|
+
bounds: {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
width: number;
|
|
62
|
+
height: number;
|
|
63
|
+
};
|
|
64
|
+
label?: string;
|
|
65
|
+
};
|
|
66
|
+
lock?: {
|
|
67
|
+
holderId: string;
|
|
68
|
+
acquiredAt: number;
|
|
69
|
+
};
|
|
70
|
+
props: JsonValue;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The read-only view handed to `describe`. Deep-readonly by construction
|
|
74
|
+
* (not derived via a shallow `Readonly<>`) because `describe` must treat its
|
|
75
|
+
* input as a pure snapshot — see the spec's "treat `describe` as a pure
|
|
76
|
+
* function" rule.
|
|
77
|
+
*/
|
|
78
|
+
type ReadonlyCustomObject<Props extends JsonValue = JsonValue> = Readonly<{
|
|
79
|
+
id: string;
|
|
80
|
+
type: ObjectType;
|
|
81
|
+
schemaVersion: number;
|
|
82
|
+
transform: Mat2x3;
|
|
83
|
+
fallback: Readonly<{
|
|
84
|
+
bounds: Readonly<{
|
|
85
|
+
x: number;
|
|
86
|
+
y: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}>;
|
|
90
|
+
label?: string;
|
|
91
|
+
}>;
|
|
92
|
+
lock?: Readonly<{
|
|
93
|
+
holderId: string;
|
|
94
|
+
acquiredAt: number;
|
|
95
|
+
}>;
|
|
96
|
+
props: Props;
|
|
97
|
+
}>;
|
|
98
|
+
interface ObjectDescribeContext {
|
|
99
|
+
/** True while this object is part of the current selection. */
|
|
100
|
+
selected: boolean;
|
|
101
|
+
}
|
|
102
|
+
interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
|
|
103
|
+
type: ObjectType;
|
|
104
|
+
currentSchemaVersion: number;
|
|
105
|
+
/** Validates untrusted persisted/imported/pasted/remote data. Must be pure. */
|
|
106
|
+
parse(input: unknown, schemaVersion: number): Props;
|
|
107
|
+
/** One pure, synchronous step per consecutive schema version. */
|
|
108
|
+
migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
|
|
109
|
+
describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene$1;
|
|
110
|
+
}
|
|
111
|
+
interface SceneNodeBase {
|
|
112
|
+
key: string;
|
|
113
|
+
transform?: Mat2x3;
|
|
114
|
+
opacity?: number;
|
|
115
|
+
/** Semantic hit-region id (e.g. `resize-handle`, `cell:2:3`); never a renderer object. */
|
|
116
|
+
interactionRegion?: string;
|
|
117
|
+
}
|
|
118
|
+
interface SceneRect extends SceneNodeBase {
|
|
119
|
+
kind: "rect";
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
cornerRadius?: number;
|
|
125
|
+
fill?: string;
|
|
126
|
+
stroke?: string;
|
|
127
|
+
strokeWidth?: number;
|
|
128
|
+
}
|
|
129
|
+
interface SceneText extends SceneNodeBase {
|
|
130
|
+
kind: "text";
|
|
131
|
+
x: number;
|
|
132
|
+
y: number;
|
|
133
|
+
text: string;
|
|
134
|
+
fontSize: number;
|
|
135
|
+
color: string;
|
|
136
|
+
/** Horizontal alignment relative to (x, y); defaults to "start". */
|
|
137
|
+
align?: "start" | "center" | "end";
|
|
138
|
+
}
|
|
139
|
+
interface SceneGroup extends SceneNodeBase {
|
|
140
|
+
kind: "group";
|
|
141
|
+
children: readonly BoardScene$1[];
|
|
142
|
+
}
|
|
143
|
+
interface ScenePath$1 extends SceneNodeBase {
|
|
144
|
+
kind: "path";
|
|
145
|
+
/** SVG-style path data, board-local coordinates. */
|
|
146
|
+
d: string;
|
|
147
|
+
fill?: string;
|
|
148
|
+
stroke?: string;
|
|
149
|
+
strokeWidth?: number;
|
|
150
|
+
}
|
|
151
|
+
interface SceneImage extends SceneNodeBase {
|
|
152
|
+
kind: "image";
|
|
153
|
+
/** Resolved through the Host asset resolver (ticket #23); never a raw Blob/File/URL. */
|
|
154
|
+
ref: AssetRef;
|
|
155
|
+
x: number;
|
|
156
|
+
y: number;
|
|
157
|
+
width: number;
|
|
158
|
+
height: number;
|
|
159
|
+
/** How the image fills its declared (x, y, width, height) box; defaults to "fill". */
|
|
160
|
+
fit?: "fill" | "contain" | "cover";
|
|
161
|
+
/** Bounded plain-text accessible label, or an explicit decorative opt-out. */
|
|
162
|
+
alt: string | {
|
|
163
|
+
readonly decorative: true;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
interface SceneEllipse extends SceneNodeBase {
|
|
167
|
+
kind: "ellipse";
|
|
168
|
+
cx: number;
|
|
169
|
+
cy: number;
|
|
170
|
+
rx: number;
|
|
171
|
+
ry: number;
|
|
172
|
+
fill?: string;
|
|
173
|
+
stroke?: string;
|
|
174
|
+
strokeWidth?: number;
|
|
175
|
+
}
|
|
176
|
+
type BoardScene$1 = SceneGroup | ScenePath$1 | SceneText | SceneImage | SceneRect | SceneEllipse;
|
|
177
|
+
type ToolCursor = "default" | "crosshair" | "pointer" | "grab" | "grabbing" | "text";
|
|
178
|
+
type ToolCancelReason = "escape-key" | "tool-switched" | "pointer-lost" | "error";
|
|
179
|
+
interface InputModifiers {
|
|
180
|
+
shift: boolean;
|
|
181
|
+
alt: boolean;
|
|
182
|
+
ctrl: boolean;
|
|
183
|
+
meta: boolean;
|
|
184
|
+
}
|
|
185
|
+
interface BoardPointerInput {
|
|
186
|
+
board: BoardPoint;
|
|
187
|
+
viewport: BoardPoint;
|
|
188
|
+
pointerId: number;
|
|
189
|
+
pointerType: "mouse" | "pen" | "touch";
|
|
190
|
+
pressure: number;
|
|
191
|
+
buttons: number;
|
|
192
|
+
modifiers: InputModifiers;
|
|
193
|
+
}
|
|
194
|
+
interface BoardKeyInput {
|
|
195
|
+
key: string;
|
|
196
|
+
modifiers: InputModifiers;
|
|
197
|
+
}
|
|
198
|
+
interface CustomToolDefinition {
|
|
199
|
+
id: ToolId;
|
|
200
|
+
label: string;
|
|
201
|
+
suggestedShortcut?: string;
|
|
202
|
+
cursor?: ToolCursor;
|
|
203
|
+
create(context: ToolCapabilities): CustomTool;
|
|
204
|
+
}
|
|
205
|
+
/** All handlers are synchronous — see the spec's "Lifecycle handlers are synchronous" rule. */
|
|
206
|
+
interface CustomTool {
|
|
207
|
+
activate?(): void;
|
|
208
|
+
pointerDown?(input: BoardPointerInput): void;
|
|
209
|
+
pointerMove?(input: BoardPointerInput): void;
|
|
210
|
+
pointerUp?(input: BoardPointerInput): void;
|
|
211
|
+
keyDown?(input: BoardKeyInput): void;
|
|
212
|
+
keyUp?(input: BoardKeyInput): void;
|
|
213
|
+
cancel?(reason: ToolCancelReason): void;
|
|
214
|
+
deactivate?(): void;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* What an Extension supplies to add a Custom board object. `id` is optional
|
|
218
|
+
* (the controller assigns one when omitted); `schemaVersion` and `fallback`
|
|
219
|
+
* defaults are derived by the controller from the object's definition.
|
|
220
|
+
*/
|
|
221
|
+
interface CustomObjectAddInput {
|
|
222
|
+
type: ObjectType;
|
|
223
|
+
id?: string;
|
|
224
|
+
transform?: Mat2x3;
|
|
225
|
+
fallback?: {
|
|
226
|
+
bounds: {
|
|
227
|
+
x: number;
|
|
228
|
+
y: number;
|
|
229
|
+
width: number;
|
|
230
|
+
height: number;
|
|
231
|
+
};
|
|
232
|
+
label?: string;
|
|
233
|
+
};
|
|
234
|
+
props: JsonValue;
|
|
235
|
+
}
|
|
236
|
+
type ObjectIntent = {
|
|
237
|
+
kind: "add";
|
|
238
|
+
object: CustomObjectAddInput;
|
|
239
|
+
} | {
|
|
240
|
+
kind: "update";
|
|
241
|
+
id: string;
|
|
242
|
+
patch: JsonObject;
|
|
243
|
+
} | {
|
|
244
|
+
kind: "remove";
|
|
245
|
+
id: string;
|
|
246
|
+
};
|
|
247
|
+
interface ExtensionCommand {
|
|
248
|
+
label?: string;
|
|
249
|
+
changes: readonly ObjectIntent[];
|
|
250
|
+
}
|
|
251
|
+
/** A structural, read-only view of any board object (built-in or Custom). */
|
|
252
|
+
type QueryableBoardObject = Readonly<{
|
|
253
|
+
id: string;
|
|
254
|
+
type: string;
|
|
255
|
+
}> & Readonly<Record<string, unknown>>;
|
|
256
|
+
interface ExtensionHitResult {
|
|
257
|
+
readonly objectId: string;
|
|
258
|
+
readonly interactionRegion?: string;
|
|
259
|
+
}
|
|
260
|
+
interface ToolCapabilities {
|
|
261
|
+
query: {
|
|
262
|
+
get(id: string): QueryableBoardObject | undefined;
|
|
263
|
+
selection(): readonly string[];
|
|
264
|
+
hitTest(point: BoardPoint): ExtensionHitResult | undefined;
|
|
265
|
+
};
|
|
266
|
+
coordinates: {
|
|
267
|
+
boardToViewport(point: BoardPoint): BoardPoint;
|
|
268
|
+
viewportToBoard(point: BoardPoint): BoardPoint;
|
|
269
|
+
};
|
|
270
|
+
/** Session-only geometry — never enters Document/history/persistence/collaboration. */
|
|
271
|
+
preview: {
|
|
272
|
+
set(scene: BoardScene$1): void;
|
|
273
|
+
clear(): void;
|
|
274
|
+
};
|
|
275
|
+
/** Constructs, validates, and commits one atomic command; controller derives Ops. */
|
|
276
|
+
submit(command: ExtensionCommand): void;
|
|
277
|
+
tools: {
|
|
278
|
+
select(toolId: ToolId | "select"): void;
|
|
279
|
+
cancel(): void;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Who holds a lock. Stored on the item so only they can take it off. */
|
|
284
|
+
interface LockHolder {
|
|
285
|
+
userId: string;
|
|
286
|
+
name: string;
|
|
287
|
+
}
|
|
288
|
+
interface Lockable {
|
|
289
|
+
locked?: boolean;
|
|
290
|
+
lockedBy?: string;
|
|
291
|
+
lockedByName?: string;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Apply or clear a lock in place. Unlock always drops the holder so a stale
|
|
295
|
+
* name cannot linger on an unlocked item.
|
|
296
|
+
*/
|
|
297
|
+
declare function applyItemLock(item: Lockable, locked: boolean, by?: LockHolder | null): void;
|
|
298
|
+
/**
|
|
299
|
+
* Only the person who locked it can unlock it. Items locked before ownership
|
|
300
|
+
* existed (no `lockedBy`) stay unlockable by anyone, so old boards don't brick.
|
|
301
|
+
*/
|
|
302
|
+
declare function canUnlockItem(item: Lockable | undefined, userId: string | undefined): boolean;
|
|
303
|
+
/** Wire fields for a locked item; omitted entirely when unlocked. */
|
|
304
|
+
declare function serializeLock(item: Lockable): Lockable;
|
|
305
|
+
|
|
306
|
+
/** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
|
|
307
|
+
interface KitchenTimer extends Lockable {
|
|
308
|
+
id: string;
|
|
309
|
+
x: number;
|
|
310
|
+
y: number;
|
|
311
|
+
/** Face diameter in board units. */
|
|
312
|
+
size: number;
|
|
313
|
+
/** What you set it to — 1, 5, 10, 15 minutes. */
|
|
314
|
+
durationMs: number;
|
|
315
|
+
/** Remaining at the last start or pause. */
|
|
316
|
+
remainingMs: number;
|
|
317
|
+
/** Wall-clock ms when the current run started. Absent means paused. */
|
|
318
|
+
runningSince?: number;
|
|
319
|
+
}
|
|
320
|
+
declare const TIMER_DEFAULT_SIZE = 12;
|
|
321
|
+
declare const TIMER_DEFAULT_DURATION_MS: number;
|
|
322
|
+
declare const TIMER_PRESETS_MS: readonly [60000, number, number, number];
|
|
323
|
+
declare function cloneTimer(timer: KitchenTimer): KitchenTimer;
|
|
324
|
+
declare function timerRemaining(timer: KitchenTimer, now: number): number;
|
|
325
|
+
declare function timerExpired(timer: KitchenTimer, now: number): boolean;
|
|
326
|
+
declare function startTimer(timer: KitchenTimer, now: number): KitchenTimer;
|
|
327
|
+
declare function pauseTimer(timer: KitchenTimer, now: number): KitchenTimer;
|
|
328
|
+
declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
|
|
329
|
+
declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
|
|
330
|
+
declare function formatTimer(ms: number): string;
|
|
331
|
+
|
|
332
|
+
interface BoardPoint {
|
|
333
|
+
x: number;
|
|
334
|
+
y: number;
|
|
335
|
+
}
|
|
336
|
+
interface StrokePoint extends BoardPoint {
|
|
337
|
+
/** Resolved pressure in [0, 1] — real stylus pressure or velocity simulation. */
|
|
338
|
+
pressure: number;
|
|
339
|
+
/**
|
|
340
|
+
* Erasure channel (ADR 0003), 0..1. Undefined means 0. At or above
|
|
341
|
+
* ERASE_THRESHOLD the point is removed and the stroke may split.
|
|
342
|
+
*/
|
|
343
|
+
erase?: number;
|
|
344
|
+
}
|
|
345
|
+
/** Which drawing tool made a stroke; undefined means marker (back-compat). */
|
|
346
|
+
type StrokeTool = "marker" | "highlighter";
|
|
347
|
+
interface Stroke extends Lockable {
|
|
348
|
+
id: string;
|
|
349
|
+
color: string;
|
|
350
|
+
baseWidth: number;
|
|
351
|
+
tool?: StrokeTool;
|
|
352
|
+
/** Points are stroke-local; `matrix` places them on the board. */
|
|
353
|
+
points: StrokePoint[];
|
|
354
|
+
/** 2D affine transform [a b c d tx ty]; undefined means identity. */
|
|
355
|
+
matrix?: [number, number, number, number, number, number];
|
|
356
|
+
/**
|
|
357
|
+
* Spatial group membership (ADR 0005). Assigned when the stroke is drawn;
|
|
358
|
+
* cluster records are derived from these ids, never stored themselves.
|
|
359
|
+
*/
|
|
360
|
+
clusterId?: string;
|
|
361
|
+
}
|
|
362
|
+
/** Erasure level at which a point counts as fully erased. */
|
|
363
|
+
declare const ERASE_THRESHOLD = 0.95;
|
|
364
|
+
declare function cloneStroke(stroke: Stroke): Stroke;
|
|
365
|
+
type SerializedPoint = [number, number, number, number];
|
|
366
|
+
interface SerializedStroke extends Lockable {
|
|
367
|
+
id: string;
|
|
368
|
+
color: string;
|
|
369
|
+
baseWidth: number;
|
|
370
|
+
tool?: StrokeTool;
|
|
371
|
+
points: SerializedPoint[];
|
|
372
|
+
/** Omitted when identity. */
|
|
373
|
+
matrix?: [number, number, number, number, number, number];
|
|
374
|
+
clusterId?: string;
|
|
375
|
+
}
|
|
376
|
+
interface SerializedDocument {
|
|
377
|
+
/** Absent in every historical document; current saves always write 1. */
|
|
378
|
+
schemaVersion?: 1;
|
|
379
|
+
strokes: SerializedStroke[];
|
|
380
|
+
/** Absent in documents saved before notes existed. */
|
|
381
|
+
notes?: StickyNote[];
|
|
382
|
+
/** Absent in documents saved before the text tool existed. */
|
|
383
|
+
textBlocks?: TextBlock[];
|
|
384
|
+
/** Absent in documents saved before interactive tables existed. */
|
|
385
|
+
tables?: TableBlock[];
|
|
386
|
+
/** Absent in documents saved before images existed. */
|
|
387
|
+
images?: ImageBlock[];
|
|
388
|
+
/** Absent in documents saved before kitchen timers existed. */
|
|
389
|
+
timers?: KitchenTimer[];
|
|
390
|
+
/** Absent in documents saved before Custom board objects existed (ticket #22). */
|
|
391
|
+
customObjects?: CustomBoardObject[];
|
|
392
|
+
}
|
|
393
|
+
declare const INK_COLORS: {
|
|
394
|
+
readonly black: "#1C1C1E";
|
|
395
|
+
readonly red: "#E0333D";
|
|
396
|
+
readonly blue: "#2563EB";
|
|
397
|
+
readonly green: "#16A34A";
|
|
398
|
+
};
|
|
399
|
+
declare const HIGHLIGHT_COLORS: {
|
|
400
|
+
readonly yellow: "#FDE047";
|
|
401
|
+
readonly green: "#86EFAC";
|
|
402
|
+
readonly pink: "#F9A8D4";
|
|
403
|
+
readonly blue: "#93C5FD";
|
|
404
|
+
};
|
|
405
|
+
declare const NOTE_COLORS: {
|
|
406
|
+
readonly yellow: "#FDE68A";
|
|
407
|
+
readonly pink: "#FBCFE8";
|
|
408
|
+
readonly blue: "#BFDBFE";
|
|
409
|
+
readonly green: "#BBF7D0";
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* One collaborator's vote on a note. One per person; toggling removes it.
|
|
413
|
+
*/
|
|
414
|
+
interface NoteVote {
|
|
415
|
+
userId: string;
|
|
416
|
+
name: string;
|
|
417
|
+
color: string;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* A sticky note: content floating above the board at a z-offset (pillar 3 —
|
|
421
|
+
* depth as an organizational axis). Center position in board space.
|
|
422
|
+
*/
|
|
423
|
+
interface StickyNote extends Lockable {
|
|
424
|
+
id: string;
|
|
425
|
+
x: number;
|
|
426
|
+
y: number;
|
|
427
|
+
/** Square side length in board units. */
|
|
428
|
+
size: number;
|
|
429
|
+
/** Height above the board surface; drives shadow offset, blur, and opacity. */
|
|
430
|
+
zOffset: number;
|
|
431
|
+
color: string;
|
|
432
|
+
text: string;
|
|
433
|
+
/** One vote per collaborator. Peel follows the count. */
|
|
434
|
+
votes?: NoteVote[];
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Typed text on the board surface, rendered as SDF glyphs. Position is the
|
|
438
|
+
* top-left corner; lines flow downward (-y). Text joins the clustering
|
|
439
|
+
* system like handwriting (build prompt §6.4).
|
|
440
|
+
*/
|
|
441
|
+
interface TextBlock extends Lockable {
|
|
442
|
+
id: string;
|
|
443
|
+
x: number;
|
|
444
|
+
y: number;
|
|
445
|
+
text: string;
|
|
446
|
+
/** Line height in board units. */
|
|
447
|
+
fontSize: number;
|
|
448
|
+
color: string;
|
|
449
|
+
clusterId?: string;
|
|
450
|
+
}
|
|
451
|
+
declare const TEXT_DEFAULT_SIZE = 1.8;
|
|
452
|
+
declare function cloneText(block: TextBlock): TextBlock;
|
|
453
|
+
/**
|
|
454
|
+
* Estimated bounds of a text block (average-glyph-width heuristic — no DOM,
|
|
455
|
+
* usable from clustering, export, and tests alike).
|
|
456
|
+
*/
|
|
457
|
+
declare function measureTextBlock(text: string, fontSize: number): {
|
|
458
|
+
width: number;
|
|
459
|
+
height: number;
|
|
460
|
+
};
|
|
461
|
+
declare const NOTE_DEFAULT_SIZE = 10;
|
|
462
|
+
declare const NOTE_DEFAULT_Z = 1;
|
|
463
|
+
declare const NOTE_MIN_Z = 0.4;
|
|
464
|
+
declare const NOTE_MAX_Z = 6;
|
|
465
|
+
declare const NOTE_PEEL_STEP = 0.7;
|
|
466
|
+
declare function cloneNote(note: StickyNote): StickyNote;
|
|
467
|
+
/**
|
|
468
|
+
* Interactive structured table on the board. Position (x, y) is top-left in board units.
|
|
469
|
+
* Cells are indexed as `${row},${col}` keys mapping to cell text content.
|
|
470
|
+
*/
|
|
471
|
+
interface TableBlock extends Lockable {
|
|
472
|
+
id: string;
|
|
473
|
+
x: number;
|
|
474
|
+
y: number;
|
|
475
|
+
rows: number;
|
|
476
|
+
cols: number;
|
|
477
|
+
colWidths: number[];
|
|
478
|
+
rowHeights: number[];
|
|
479
|
+
cells: Record<string, string>;
|
|
480
|
+
color?: string;
|
|
481
|
+
backgroundColor?: string;
|
|
482
|
+
clusterId?: string;
|
|
483
|
+
}
|
|
484
|
+
declare const TABLE_DEFAULT_CELL_WIDTH = 8;
|
|
485
|
+
declare const TABLE_DEFAULT_CELL_HEIGHT = 3.6;
|
|
486
|
+
declare const TABLE_DEFAULT_FONT_SIZE = 1.25;
|
|
487
|
+
declare function cloneTable(table: TableBlock): TableBlock;
|
|
488
|
+
declare function measureTable(table: TableBlock): {
|
|
489
|
+
width: number;
|
|
490
|
+
height: number;
|
|
491
|
+
};
|
|
492
|
+
declare const BOARD_COLOR = "#FFFFFF";
|
|
493
|
+
declare const FOG_COLOR = "#FFFFFF";
|
|
494
|
+
/**
|
|
495
|
+
* An imported image block on the board plane.
|
|
496
|
+
* Coordinates (x, y) represent the center of the image in board space.
|
|
497
|
+
*/
|
|
498
|
+
interface ImageBlock extends Lockable {
|
|
499
|
+
id: string;
|
|
500
|
+
/**
|
|
501
|
+
* A legacy, read-only data URL (or, historically, an arbitrary string) —
|
|
502
|
+
* never written by new code once `ref` exists. Ticket #23's Host-managed
|
|
503
|
+
* Assets add `ref` as the durable path going forward; `src` and `ref` are
|
|
504
|
+
* mutually exclusive in practice, but both fields exist on every
|
|
505
|
+
* `ImageBlock` so old and new objects share one shape.
|
|
506
|
+
*/
|
|
507
|
+
src: string;
|
|
508
|
+
/** Opaque Asset reference (ticket #23); when present, `src` is ignored. */
|
|
509
|
+
ref?: AssetRef;
|
|
510
|
+
x: number;
|
|
511
|
+
y: number;
|
|
512
|
+
width: number;
|
|
513
|
+
height: number;
|
|
514
|
+
aspectRatio: number;
|
|
515
|
+
name?: string;
|
|
516
|
+
createdAt?: string;
|
|
517
|
+
/** Present when this image is a stamp from the pad, not a photo. */
|
|
518
|
+
stamp?: string;
|
|
519
|
+
}
|
|
520
|
+
declare function cloneImage(img: ImageBlock): ImageBlock;
|
|
521
|
+
|
|
522
|
+
declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
|
|
523
|
+
type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
|
|
524
|
+
id: StrokeId;
|
|
525
|
+
};
|
|
526
|
+
interface CurrentSerializedDocument extends Required<SerializedDocument> {
|
|
527
|
+
schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
|
|
528
|
+
strokes: CurrentSerializedStroke[];
|
|
529
|
+
}
|
|
530
|
+
type DocumentRecoveryCode = "DOCUMENT_JSON_INVALID" | "DOCUMENT_VALIDATION_FAILED" | "DOCUMENT_VERSION_UNSUPPORTED";
|
|
531
|
+
declare class DocumentRecoveryError extends Error {
|
|
532
|
+
readonly code: DocumentRecoveryCode;
|
|
533
|
+
readonly originalBytes?: string | undefined;
|
|
534
|
+
readonly path?: string | undefined;
|
|
535
|
+
constructor(code: DocumentRecoveryCode, message: string, originalBytes?: string | undefined, path?: string | undefined);
|
|
536
|
+
}
|
|
537
|
+
type DocumentLoadResult = {
|
|
538
|
+
ok: true;
|
|
539
|
+
document: CurrentSerializedDocument;
|
|
540
|
+
migratedFrom: 0 | 1;
|
|
541
|
+
} | {
|
|
542
|
+
ok: false;
|
|
543
|
+
error: DocumentRecoveryError;
|
|
544
|
+
};
|
|
545
|
+
declare function loadDocumentBytes(originalBytes: string): DocumentLoadResult;
|
|
546
|
+
declare function migrateDocument(raw: unknown): DocumentLoadResult;
|
|
547
|
+
declare function serializeDocument(document: unknown): string;
|
|
548
|
+
|
|
549
|
+
interface SearchableComment {
|
|
550
|
+
id: string;
|
|
551
|
+
text: string;
|
|
552
|
+
authorName: string;
|
|
553
|
+
x: number;
|
|
554
|
+
y: number;
|
|
555
|
+
replies: readonly {
|
|
556
|
+
text: string;
|
|
557
|
+
}[];
|
|
558
|
+
}
|
|
559
|
+
type SearchHitKind = "note" | "text" | "table" | "comment" | "stamp";
|
|
560
|
+
interface SearchHit {
|
|
561
|
+
kind: SearchHitKind;
|
|
562
|
+
id: string;
|
|
563
|
+
title: string;
|
|
564
|
+
snippet: string;
|
|
565
|
+
x: number;
|
|
566
|
+
y: number;
|
|
567
|
+
}
|
|
568
|
+
interface SearchableBoard {
|
|
569
|
+
notes: Iterable<StickyNote>;
|
|
570
|
+
texts: Iterable<TextBlock>;
|
|
571
|
+
tables: Iterable<TableBlock>;
|
|
572
|
+
images: Iterable<ImageBlock>;
|
|
573
|
+
timers?: Iterable<KitchenTimer>;
|
|
574
|
+
comments: Iterable<SearchableComment>;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Find notes, text, table cells, stamps, and comments whose text contains
|
|
578
|
+
* `query`. Empty / whitespace queries return nothing.
|
|
579
|
+
*/
|
|
580
|
+
declare function searchBoard(query: string, board: SearchableBoard): SearchHit[];
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The one place a live Stroke becomes its wire representation — in
|
|
584
|
+
* particular, points collapse from {x,y,pressure,erase?} objects into
|
|
585
|
+
* [x,y,pressure,erase] tuples. Anything that persists a stroke (toJSON, and
|
|
586
|
+
* the incremental ops opSync.ts sends) must go through this, or the two
|
|
587
|
+
* paths drift: a stroke saved with points-as-objects looks fine until the
|
|
588
|
+
* next load, where deserializeStrokes' array-destructuring throws "object is
|
|
589
|
+
* not iterable" — exactly what shipping the live object straight into an op
|
|
590
|
+
* used to do.
|
|
591
|
+
*/
|
|
592
|
+
declare function serializeStroke(s: Stroke): SerializedStroke;
|
|
593
|
+
interface BBox {
|
|
594
|
+
minX: number;
|
|
595
|
+
minY: number;
|
|
596
|
+
maxX: number;
|
|
597
|
+
maxY: number;
|
|
598
|
+
}
|
|
599
|
+
interface DocumentChange {
|
|
600
|
+
added: Stroke[];
|
|
601
|
+
/** Ids of removed strokes. */
|
|
602
|
+
removed: string[];
|
|
603
|
+
/** Strokes whose points mutated in place (erasure decay); geometry must rebuild. */
|
|
604
|
+
updated: Stroke[];
|
|
605
|
+
/** Strokes whose matrix changed; geometry is untouched, placement moved. */
|
|
606
|
+
transformed: Stroke[];
|
|
607
|
+
notesAdded: StickyNote[];
|
|
608
|
+
/** Ids of removed notes. */
|
|
609
|
+
notesRemoved: string[];
|
|
610
|
+
notesUpdated: StickyNote[];
|
|
611
|
+
textAdded: TextBlock[];
|
|
612
|
+
/** Ids of removed text blocks. */
|
|
613
|
+
textRemoved: string[];
|
|
614
|
+
textUpdated: TextBlock[];
|
|
615
|
+
tablesAdded: TableBlock[];
|
|
616
|
+
/** Ids of removed tables. */
|
|
617
|
+
tablesRemoved: string[];
|
|
618
|
+
tablesUpdated: TableBlock[];
|
|
619
|
+
imagesAdded: ImageBlock[];
|
|
620
|
+
/** Ids of removed images. */
|
|
621
|
+
imagesRemoved: string[];
|
|
622
|
+
imagesUpdated: ImageBlock[];
|
|
623
|
+
timersAdded: KitchenTimer[];
|
|
624
|
+
/** Ids of removed timers. */
|
|
625
|
+
timersRemoved: string[];
|
|
626
|
+
timersUpdated: KitchenTimer[];
|
|
627
|
+
/** Custom board objects (ticket #22) — one map for every registered type, keyed by id. */
|
|
628
|
+
customObjectsAdded: CustomBoardObject[];
|
|
629
|
+
/** Ids of removed custom objects. */
|
|
630
|
+
customObjectsRemoved: string[];
|
|
631
|
+
customObjectsUpdated: CustomBoardObject[];
|
|
632
|
+
}
|
|
633
|
+
type Listener = (change: DocumentChange) => void;
|
|
634
|
+
declare class BoardDocument {
|
|
635
|
+
readonly id: DocumentId;
|
|
636
|
+
/** Last version acknowledged by the server; 0 = never saved. */
|
|
637
|
+
version: number;
|
|
638
|
+
private readonly strokes;
|
|
639
|
+
private readonly notes;
|
|
640
|
+
private readonly texts;
|
|
641
|
+
private readonly tables;
|
|
642
|
+
private readonly images;
|
|
643
|
+
private readonly timers;
|
|
644
|
+
/** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
|
|
645
|
+
private readonly customObjects;
|
|
646
|
+
private readonly bboxes;
|
|
647
|
+
private readonly listeners;
|
|
648
|
+
constructor(id: DocumentId);
|
|
649
|
+
get(id: string): Stroke | undefined;
|
|
650
|
+
all(): IterableIterator<Stroke>;
|
|
651
|
+
bbox(id: string): BBox | undefined;
|
|
652
|
+
subscribe(listener: Listener): () => void;
|
|
653
|
+
addStrokes(strokes: Stroke[]): void;
|
|
654
|
+
removeStrokes(ids: string[]): void;
|
|
655
|
+
/** Announce in-place point mutations (erasure decay; bbox is unchanged). */
|
|
656
|
+
touchStrokes(strokes: Stroke[]): void;
|
|
657
|
+
/** Announce matrix changes; recomputes world bboxes. */
|
|
658
|
+
transformStrokes(strokes: Stroke[]): void;
|
|
659
|
+
getNote(id: string): StickyNote | undefined;
|
|
660
|
+
allNotes(): IterableIterator<StickyNote>;
|
|
661
|
+
addNotes(notes: StickyNote[]): void;
|
|
662
|
+
removeNotes(ids: string[]): void;
|
|
663
|
+
/** Replace a note's contents (move, peel, retext) under the same id. */
|
|
664
|
+
setNote(note: StickyNote): void;
|
|
665
|
+
getText(id: string): TextBlock | undefined;
|
|
666
|
+
allTexts(): IterableIterator<TextBlock>;
|
|
667
|
+
addTexts(blocks: TextBlock[]): void;
|
|
668
|
+
removeTexts(ids: string[]): void;
|
|
669
|
+
/** Replace a text block's contents (move, retext) under the same id. */
|
|
670
|
+
setText(block: TextBlock): void;
|
|
671
|
+
getTable(id: string): TableBlock | undefined;
|
|
672
|
+
allTables(): IterableIterator<TableBlock>;
|
|
673
|
+
addTables(tables: TableBlock[]): void;
|
|
674
|
+
removeTables(ids: string[]): void;
|
|
675
|
+
/** Replace a table's contents (move, resize, change cells) under the same id. */
|
|
676
|
+
setTable(table: TableBlock): void;
|
|
677
|
+
getImage(id: string): ImageBlock | undefined;
|
|
678
|
+
allImages(): IterableIterator<ImageBlock>;
|
|
679
|
+
addImages(images: ImageBlock[]): void;
|
|
680
|
+
removeImages(ids: string[]): void;
|
|
681
|
+
/** Replace an image block's contents (move, resize) under the same id. */
|
|
682
|
+
setImage(image: ImageBlock): void;
|
|
683
|
+
getTimer(id: string): KitchenTimer | undefined;
|
|
684
|
+
allTimers(): IterableIterator<KitchenTimer>;
|
|
685
|
+
addTimers(timers: KitchenTimer[]): void;
|
|
686
|
+
removeTimers(ids: string[]): void;
|
|
687
|
+
setTimer(timer: KitchenTimer): void;
|
|
688
|
+
getCustomObject(id: string): CustomBoardObject | undefined;
|
|
689
|
+
allCustomObjects(): IterableIterator<CustomBoardObject>;
|
|
690
|
+
addCustomObjects(objects: CustomBoardObject[]): void;
|
|
691
|
+
removeCustomObjects(ids: string[]): void;
|
|
692
|
+
/** Replace a custom object's contents under the same id. */
|
|
693
|
+
setCustomObject(object: CustomBoardObject): void;
|
|
694
|
+
setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
695
|
+
setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
|
|
696
|
+
setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
697
|
+
setTextLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
698
|
+
setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
699
|
+
setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
700
|
+
setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
701
|
+
/** Replace all content (initial load). Does not touch `version`. */
|
|
702
|
+
replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]): void;
|
|
703
|
+
/** Apply incremental real-time change received from a remote collaborator over WebSocket. */
|
|
704
|
+
applyRemoteChange(change: Partial<DocumentChange>): void;
|
|
705
|
+
toJSON(): SerializedDocument;
|
|
706
|
+
static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
|
|
707
|
+
static deserializeImages(data: SerializedDocument): ImageBlock[];
|
|
708
|
+
static deserializeTimers(data: SerializedDocument): KitchenTimer[];
|
|
709
|
+
static deserializeNotes(data: SerializedDocument): StickyNote[];
|
|
710
|
+
static deserializeTexts(data: SerializedDocument): TextBlock[];
|
|
711
|
+
static deserializeTables(data: SerializedDocument): TableBlock[];
|
|
712
|
+
static deserializeStrokes(data: SerializedDocument): Stroke[];
|
|
713
|
+
private emit;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
type ClusterIdFactory = () => string;
|
|
717
|
+
declare class ClusterStore {
|
|
718
|
+
private readonly doc;
|
|
719
|
+
private readonly createId;
|
|
720
|
+
private readonly now;
|
|
721
|
+
private readonly clusters;
|
|
722
|
+
private readonly byStroke;
|
|
723
|
+
private readonly unsubscribe;
|
|
724
|
+
constructor(doc: BoardDocument, createId: ClusterIdFactory, now: () => number);
|
|
725
|
+
/**
|
|
726
|
+
* Pick (or create) the cluster for a freshly drawn stroke and stamp its
|
|
727
|
+
* clusterId. Gap threshold scales with stroke height, per the build prompt.
|
|
728
|
+
*/
|
|
729
|
+
assign(stroke: Stroke, now?: number): void;
|
|
730
|
+
/** Same assignment for a typed text block, using its estimated bounds. */
|
|
731
|
+
assignText(block: TextBlock, now?: number): void;
|
|
732
|
+
private pick;
|
|
733
|
+
/** All strokes in the same cluster; a clusterless stroke is its own group. */
|
|
734
|
+
membersOf(strokeId: string): string[];
|
|
735
|
+
dispose(): void;
|
|
736
|
+
/** Strokes and text blocks index identically; bbox source differs. */
|
|
737
|
+
private memberBBox;
|
|
738
|
+
private addMember;
|
|
739
|
+
private removeMember;
|
|
740
|
+
private recompute;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
interface Command {
|
|
744
|
+
readonly label: string;
|
|
745
|
+
apply(doc: BoardDocument): void;
|
|
746
|
+
revert(doc: BoardDocument): void;
|
|
747
|
+
}
|
|
748
|
+
/** How a command reached the document — undo/redo are audited distinctly. */
|
|
749
|
+
type CommandKind = "do" | "undo" | "redo";
|
|
750
|
+
declare class History {
|
|
751
|
+
private readonly doc;
|
|
752
|
+
private readonly undoStack;
|
|
753
|
+
private readonly redoStack;
|
|
754
|
+
/**
|
|
755
|
+
* Observer for every mutation, in one place: all board edits funnel
|
|
756
|
+
* through record/undo/redo. The audit trail listens here.
|
|
757
|
+
*/
|
|
758
|
+
onCommand: ((command: Command, kind: CommandKind) => void) | null;
|
|
759
|
+
private undoing;
|
|
760
|
+
constructor(doc: BoardDocument);
|
|
761
|
+
/** Apply a command and make it undoable. */
|
|
762
|
+
execute(command: Command): void;
|
|
763
|
+
/** Make an already-applied change undoable (e.g. a live erase swipe). */
|
|
764
|
+
record(command: Command): void;
|
|
765
|
+
/**
|
|
766
|
+
* True while a revert is in flight. Persistence reads this: re-adding
|
|
767
|
+
* something the author deleted must be sent as a `restore`, the only op the
|
|
768
|
+
* server lets past a tombstone (ADR 0006).
|
|
769
|
+
*/
|
|
770
|
+
get isUndoing(): boolean;
|
|
771
|
+
undo(): void;
|
|
772
|
+
redo(): void;
|
|
773
|
+
get canUndo(): boolean;
|
|
774
|
+
get canRedo(): boolean;
|
|
775
|
+
clear(): void;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** One drawing action — a marker stroke, or a shape's strokes as one unit. */
|
|
779
|
+
declare class AddStrokesCommand implements Command {
|
|
780
|
+
readonly label = "draw";
|
|
781
|
+
private readonly strokes;
|
|
782
|
+
constructor(strokes: Stroke[]);
|
|
783
|
+
apply(doc: BoardDocument): void;
|
|
784
|
+
revert(doc: BoardDocument): void;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* One eraser swipe (ADR 0003): `before` are the touched strokes as they were
|
|
788
|
+
* at swipe start; `after` is what survived — smudged, split, or gone.
|
|
789
|
+
*/
|
|
790
|
+
declare class EraseCommand implements Command {
|
|
791
|
+
readonly label = "erase";
|
|
792
|
+
private readonly before;
|
|
793
|
+
private readonly after;
|
|
794
|
+
constructor(before: Stroke[], after: Stroke[]);
|
|
795
|
+
apply(doc: BoardDocument): void;
|
|
796
|
+
revert(doc: BoardDocument): void;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* One transform gesture: `delta` composed onto each member's matrix
|
|
800
|
+
* (`child = delta × child`, per §6.3 — never baked into geometry).
|
|
801
|
+
*/
|
|
802
|
+
declare class TransformCommand implements Command {
|
|
803
|
+
private readonly ids;
|
|
804
|
+
private readonly delta;
|
|
805
|
+
readonly label = "transform selection";
|
|
806
|
+
private readonly inverse;
|
|
807
|
+
constructor(ids: string[], delta: Mat2x3);
|
|
808
|
+
apply(doc: BoardDocument): void;
|
|
809
|
+
revert(doc: BoardDocument): void;
|
|
810
|
+
private compose;
|
|
811
|
+
}
|
|
812
|
+
declare class AddNoteCommand implements Command {
|
|
813
|
+
readonly label = "add note";
|
|
814
|
+
private readonly note;
|
|
815
|
+
constructor(note: StickyNote);
|
|
816
|
+
apply(doc: BoardDocument): void;
|
|
817
|
+
revert(doc: BoardDocument): void;
|
|
818
|
+
}
|
|
819
|
+
/** Any note mutation — move, peel, recolor, retext — as before/after. */
|
|
820
|
+
declare class UpdateNoteCommand implements Command {
|
|
821
|
+
readonly label = "update note";
|
|
822
|
+
private readonly before;
|
|
823
|
+
private readonly after;
|
|
824
|
+
constructor(before: StickyNote, after: StickyNote);
|
|
825
|
+
apply(doc: BoardDocument): void;
|
|
826
|
+
revert(doc: BoardDocument): void;
|
|
827
|
+
}
|
|
828
|
+
declare class DeleteNoteCommand implements Command {
|
|
829
|
+
readonly label = "delete note";
|
|
830
|
+
private readonly note;
|
|
831
|
+
constructor(note: StickyNote);
|
|
832
|
+
apply(doc: BoardDocument): void;
|
|
833
|
+
revert(doc: BoardDocument): void;
|
|
834
|
+
}
|
|
835
|
+
declare class AddTextCommand implements Command {
|
|
836
|
+
readonly label = "add text";
|
|
837
|
+
private readonly block;
|
|
838
|
+
constructor(block: TextBlock);
|
|
839
|
+
apply(doc: BoardDocument): void;
|
|
840
|
+
revert(doc: BoardDocument): void;
|
|
841
|
+
}
|
|
842
|
+
/** Any text-block mutation — move or retext — as before/after. */
|
|
843
|
+
declare class UpdateTextCommand implements Command {
|
|
844
|
+
readonly label = "update text";
|
|
845
|
+
private readonly before;
|
|
846
|
+
private readonly after;
|
|
847
|
+
constructor(before: TextBlock, after: TextBlock);
|
|
848
|
+
apply(doc: BoardDocument): void;
|
|
849
|
+
revert(doc: BoardDocument): void;
|
|
850
|
+
}
|
|
851
|
+
declare class DeleteTextCommand implements Command {
|
|
852
|
+
readonly label = "delete text";
|
|
853
|
+
private readonly block;
|
|
854
|
+
constructor(block: TextBlock);
|
|
855
|
+
apply(doc: BoardDocument): void;
|
|
856
|
+
revert(doc: BoardDocument): void;
|
|
857
|
+
}
|
|
858
|
+
declare class AddTableCommand implements Command {
|
|
859
|
+
readonly label = "add table";
|
|
860
|
+
private readonly table;
|
|
861
|
+
constructor(table: TableBlock);
|
|
862
|
+
apply(doc: BoardDocument): void;
|
|
863
|
+
revert(doc: BoardDocument): void;
|
|
864
|
+
}
|
|
865
|
+
/** Any table mutation — move, resize, cell text edit — as before/after. */
|
|
866
|
+
declare class UpdateTableCommand implements Command {
|
|
867
|
+
readonly label = "update table";
|
|
868
|
+
private readonly before;
|
|
869
|
+
private readonly after;
|
|
870
|
+
constructor(before: TableBlock, after: TableBlock);
|
|
871
|
+
apply(doc: BoardDocument): void;
|
|
872
|
+
revert(doc: BoardDocument): void;
|
|
873
|
+
}
|
|
874
|
+
declare class DeleteTableCommand implements Command {
|
|
875
|
+
readonly label = "delete table";
|
|
876
|
+
private readonly table;
|
|
877
|
+
constructor(table: TableBlock);
|
|
878
|
+
apply(doc: BoardDocument): void;
|
|
879
|
+
revert(doc: BoardDocument): void;
|
|
880
|
+
}
|
|
881
|
+
declare class DeleteStrokesCommand implements Command {
|
|
882
|
+
readonly label = "delete selection";
|
|
883
|
+
private readonly strokes;
|
|
884
|
+
constructor(strokes: Stroke[]);
|
|
885
|
+
apply(doc: BoardDocument): void;
|
|
886
|
+
revert(doc: BoardDocument): void;
|
|
887
|
+
}
|
|
888
|
+
declare class AddImageCommand implements Command {
|
|
889
|
+
readonly label = "add image";
|
|
890
|
+
private readonly image;
|
|
891
|
+
constructor(image: ImageBlock);
|
|
892
|
+
apply(doc: BoardDocument): void;
|
|
893
|
+
revert(doc: BoardDocument): void;
|
|
894
|
+
}
|
|
895
|
+
/** Any image mutation — move, resize — as before/after. */
|
|
896
|
+
declare class UpdateImageCommand implements Command {
|
|
897
|
+
readonly label = "update image";
|
|
898
|
+
private readonly before;
|
|
899
|
+
private readonly after;
|
|
900
|
+
constructor(before: ImageBlock, after: ImageBlock);
|
|
901
|
+
apply(doc: BoardDocument): void;
|
|
902
|
+
revert(doc: BoardDocument): void;
|
|
903
|
+
}
|
|
904
|
+
declare class DeleteImageCommand implements Command {
|
|
905
|
+
readonly label = "delete image";
|
|
906
|
+
private readonly image;
|
|
907
|
+
constructor(image: ImageBlock);
|
|
908
|
+
apply(doc: BoardDocument): void;
|
|
909
|
+
revert(doc: BoardDocument): void;
|
|
910
|
+
}
|
|
911
|
+
declare class AddTimerCommand implements Command {
|
|
912
|
+
readonly label = "add timer";
|
|
913
|
+
private readonly timer;
|
|
914
|
+
constructor(timer: KitchenTimer);
|
|
915
|
+
apply(doc: BoardDocument): void;
|
|
916
|
+
revert(doc: BoardDocument): void;
|
|
917
|
+
}
|
|
918
|
+
declare class UpdateTimerCommand implements Command {
|
|
919
|
+
readonly label = "update timer";
|
|
920
|
+
private readonly before;
|
|
921
|
+
private readonly after;
|
|
922
|
+
constructor(before: KitchenTimer, after: KitchenTimer);
|
|
923
|
+
apply(doc: BoardDocument): void;
|
|
924
|
+
revert(doc: BoardDocument): void;
|
|
925
|
+
}
|
|
926
|
+
declare class DeleteTimerCommand implements Command {
|
|
927
|
+
readonly label = "delete timer";
|
|
928
|
+
private readonly timer;
|
|
929
|
+
constructor(timer: KitchenTimer);
|
|
930
|
+
apply(doc: BoardDocument): void;
|
|
931
|
+
revert(doc: BoardDocument): void;
|
|
932
|
+
}
|
|
933
|
+
interface LockTarget {
|
|
934
|
+
type: "stroke" | "note" | "text" | "table" | "image" | "timer";
|
|
935
|
+
id: string;
|
|
936
|
+
locked: boolean;
|
|
937
|
+
lockedBy?: string;
|
|
938
|
+
lockedByName?: string;
|
|
939
|
+
}
|
|
940
|
+
declare class LockItemsCommand implements Command {
|
|
941
|
+
readonly label = "toggle lock";
|
|
942
|
+
private readonly targets;
|
|
943
|
+
private readonly targetState;
|
|
944
|
+
private readonly actor;
|
|
945
|
+
constructor(targets: LockTarget[], targetState: boolean, actor?: LockHolder | null);
|
|
946
|
+
apply(doc: BoardDocument): void;
|
|
947
|
+
revert(doc: BoardDocument): void;
|
|
948
|
+
private applyLock;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
|
|
952
|
+
type Op = {
|
|
953
|
+
kind: "upsert";
|
|
954
|
+
collection: OpCollection;
|
|
955
|
+
object: {
|
|
956
|
+
id: string;
|
|
957
|
+
};
|
|
958
|
+
} | {
|
|
959
|
+
kind: "restore";
|
|
960
|
+
collection: OpCollection;
|
|
961
|
+
object: {
|
|
962
|
+
id: string;
|
|
963
|
+
};
|
|
964
|
+
} | {
|
|
965
|
+
kind: "remove";
|
|
966
|
+
collection: OpCollection;
|
|
967
|
+
id: string;
|
|
968
|
+
};
|
|
969
|
+
/** Convert a canonical Document change into durable, renderer-neutral Ops. */
|
|
970
|
+
declare function changeToOps(change: DocumentChange, restoring?: boolean): Op[];
|
|
971
|
+
|
|
972
|
+
declare const MIN_WIDTH_FACTOR = 0.35;
|
|
973
|
+
declare const END_TAPER = 0.55;
|
|
974
|
+
interface RibbonEdgePoint {
|
|
975
|
+
lx: number;
|
|
976
|
+
ly: number;
|
|
977
|
+
rx: number;
|
|
978
|
+
ry: number;
|
|
979
|
+
/** 1 = intact ink, 0 = fully erased (from the erasure channel). */
|
|
980
|
+
alpha: number;
|
|
981
|
+
}
|
|
982
|
+
declare function ribbonEdges(points: StrokePoint[], baseWidth: number): RibbonEdgePoint[];
|
|
983
|
+
|
|
984
|
+
declare class SpatialIndex {
|
|
985
|
+
private readonly doc;
|
|
986
|
+
private readonly cells;
|
|
987
|
+
private readonly strokeCells;
|
|
988
|
+
private readonly unsubscribe;
|
|
989
|
+
constructor(doc: BoardDocument);
|
|
990
|
+
/** Ids of strokes whose bbox may overlap the query rect. */
|
|
991
|
+
query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
|
|
992
|
+
dispose(): void;
|
|
993
|
+
private insert;
|
|
994
|
+
private remove;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/** A stamp is a small sticker dropped on the board — not ink, not a photo. */
|
|
998
|
+
type StampKind = "star" | "check" | "ship" | "heart" | "plus" | "fire";
|
|
999
|
+
declare const STAMP_SIZE = 6;
|
|
1000
|
+
declare const STAMPS: {
|
|
1001
|
+
kind: StampKind;
|
|
1002
|
+
label: string;
|
|
1003
|
+
glyph: string;
|
|
1004
|
+
}[];
|
|
1005
|
+
declare function stampDataUrl(kind: StampKind): string;
|
|
1006
|
+
declare function isStampKind(value: unknown): value is StampKind;
|
|
1007
|
+
|
|
1008
|
+
interface ViewportInset {
|
|
1009
|
+
top: number;
|
|
1010
|
+
right: number;
|
|
1011
|
+
bottom: number;
|
|
1012
|
+
left: number;
|
|
1013
|
+
}
|
|
1014
|
+
/** Keep beacons clear of typical chrome at the top and bottom of the Board. */
|
|
1015
|
+
declare const BEACON_INSET: ViewportInset;
|
|
1016
|
+
type PresencePlacement = {
|
|
1017
|
+
kind: "on-screen";
|
|
1018
|
+
x: number;
|
|
1019
|
+
y: number;
|
|
1020
|
+
} | {
|
|
1021
|
+
kind: "edge";
|
|
1022
|
+
x: number;
|
|
1023
|
+
y: number;
|
|
1024
|
+
angle: number;
|
|
1025
|
+
};
|
|
1026
|
+
/**
|
|
1027
|
+
* If a collaborator is in the usable viewport, return their screen position.
|
|
1028
|
+
* If they are outside it, clamp to the nearest edge and return the angle a
|
|
1029
|
+
* chevron should point (screen space, radians, 0 = right, clockwise).
|
|
1030
|
+
*/
|
|
1031
|
+
declare function placePresenceBeacon(screen: {
|
|
1032
|
+
x: number;
|
|
1033
|
+
y: number;
|
|
1034
|
+
}, viewport: {
|
|
1035
|
+
width: number;
|
|
1036
|
+
height: number;
|
|
1037
|
+
}, inset?: ViewportInset): PresencePlacement;
|
|
1038
|
+
|
|
1039
|
+
interface ExtensionRegistry {
|
|
1040
|
+
readonly extensions: readonly ScrawlExtension[];
|
|
1041
|
+
tool(id: ToolId): CustomToolDefinition | undefined;
|
|
1042
|
+
objectType(type: ObjectType): CustomObjectDefinition | undefined;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* `registry` is optional and only enables rendering Custom objects through
|
|
1047
|
+
* their own `describe()` — without it (or for an object whose extension
|
|
1048
|
+
* isn't in it), Custom objects still export via the standard fallback
|
|
1049
|
+
* placeholder (`fallback.bounds`/`label`), never silently dropped.
|
|
1050
|
+
*
|
|
1051
|
+
* `resolvedAssets` (ticket #23) maps an Asset reference to an already-
|
|
1052
|
+
* resolved `data:` URI — see `assetExport.ts`'s `exportDocumentSVGWithAssets`,
|
|
1053
|
+
* which is the only intended caller that ever passes one. Without it, every
|
|
1054
|
+
* `ref`-backed image (built-in or Custom `SceneImage`) renders its
|
|
1055
|
+
* placeholder instead of guessing at a URL; a legacy `src`-backed image is
|
|
1056
|
+
* unaffected either way.
|
|
1057
|
+
*/
|
|
1058
|
+
declare function documentToSVG(doc: SerializedDocument, now?: number, registry?: ExtensionRegistry, resolvedAssets?: ReadonlyMap<AssetRef, string>): string;
|
|
1059
|
+
|
|
1060
|
+
declare const SDK_PACKAGE_NAME = "@scrawl-board/board";
|
|
1061
|
+
declare const SDK_DEVELOPMENT_VERSION = "0.0.0-development";
|
|
1062
|
+
|
|
1063
|
+
type BoardBounds = {
|
|
1064
|
+
minX: number;
|
|
1065
|
+
minY: number;
|
|
1066
|
+
maxX: number;
|
|
1067
|
+
maxY: number;
|
|
1068
|
+
};
|
|
1069
|
+
type ScenePath = {
|
|
1070
|
+
id: string;
|
|
1071
|
+
color: string;
|
|
1072
|
+
width: number;
|
|
1073
|
+
points: readonly BoardPoint[];
|
|
1074
|
+
};
|
|
1075
|
+
type BoardScene = {
|
|
1076
|
+
bounds: BoardBounds;
|
|
1077
|
+
paths: readonly ScenePath[];
|
|
1078
|
+
};
|
|
1079
|
+
type BoardStroke = Stroke;
|
|
1080
|
+
type SerializedBoardStroke = SerializedStroke;
|
|
1081
|
+
type SerializedBoardDocument = CurrentSerializedDocument;
|
|
1082
|
+
|
|
1083
|
+
export { AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, BEACON_INSET, BOARD_COLOR, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, LockItemsCommand, MIN_WIDTH_FACTOR, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, STAMPS, STAMP_SIZE, SpatialIndex, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, UpdateImageCommand, UpdateNoteCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, avgScale, canUnlockItem, changeToOps, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, documentId, documentToSVG, formatTimer, invert, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, ribbonEdges, rotationAbout, scalingAbout, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation };
|
|
1084
|
+
export type { BBox, BoardBounds, BoardPoint, BoardScene, BoardStroke, ClusterIdFactory, Command, CommandKind, CurrentSerializedDocument, CurrentSerializedStroke, DocumentChange, DocumentId, DocumentLoadResult, DocumentRecoveryCode, ImageBlock, KitchenTimer, LockHolder, LockTarget, Lockable, Mat2x3, NoteVote, Op, OpCollection, PresencePlacement, RibbonEdgePoint, ScenePath, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, TableBlock, TextBlock, ViewportInset };
|