@neta-art/cohub 8.24.0 → 9.0.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/dist/board/animation.d.ts +57 -181
- package/dist/board/animation.js +374 -220
- package/dist/board/core/arrow-geometry.d.ts +33 -18
- package/dist/board/core/arrow-geometry.js +283 -52
- package/dist/board/core/draw-geometry.d.ts +22 -36
- package/dist/board/core/draw-geometry.js +148 -85
- package/dist/board/core/export-assets.d.ts +3 -6
- package/dist/board/core/export-assets.js +0 -2
- package/dist/board/core/export-plan.d.ts +12 -63
- package/dist/board/core/export-plan.js +55 -94
- package/dist/board/core/palette.d.ts +4 -21
- package/dist/board/core/palette.js +8 -13
- package/dist/board/core/scene.d.ts +33 -0
- package/dist/board/core/scene.js +302 -0
- package/dist/board/core/shape-definition.d.ts +12 -24
- package/dist/board/core/shape-definition.js +0 -19
- package/dist/board/core/shape-types.d.ts +6 -120
- package/dist/board/core/shape-types.js +5 -5
- package/dist/board/core/text-metrics.d.ts +8 -11
- package/dist/board/core/text-metrics.js +5 -34
- package/dist/board/core/tool-styles.d.ts +6 -16
- package/dist/board/core/tool-styles.js +17 -25
- package/dist/board/export/index.d.ts +10 -17
- package/dist/board/export/index.js +22 -21
- package/dist/board/export/scene.d.ts +10 -16
- package/dist/board/export/scene.js +17 -30
- package/dist/board/geometry.d.ts +27 -56
- package/dist/board/geometry.js +7 -67
- package/dist/board/headless/index.d.ts +6 -27
- package/dist/board/headless/index.js +4 -19
- package/dist/board/headless/sketch.d.ts +19 -0
- package/dist/board/headless/sketch.js +146 -0
- package/dist/board/image-key.d.ts +2 -12
- package/dist/board/image-key.js +3 -13
- package/dist/board/index.d.ts +18 -20
- package/dist/board/index.js +17 -17
- package/dist/board/media-playback.d.ts +3 -6
- package/dist/board/media-playback.js +6 -6
- package/dist/board/presets.d.ts +150 -0
- package/dist/board/presets.js +226 -0
- package/dist/board/render/board-background.d.ts +5 -3
- package/dist/board/render/board-background.js +8 -7
- package/dist/board/render/clip.d.ts +14 -0
- package/dist/board/render/clip.js +47 -0
- package/dist/board/render/index.d.ts +8 -5
- package/dist/board/render/index.js +7 -4
- package/dist/board/render/media-interaction.d.ts +4 -5
- package/dist/board/render/media-interaction.js +1 -2
- package/dist/board/render/palette.d.ts +6 -2
- package/dist/board/render/palette.js +7 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
- package/dist/board/render/renderers/audio-card-renderer.js +3 -3
- package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
- package/dist/board/render/renderers/base-card-renderer.js +1 -24
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
- package/dist/board/render/renderers/board-renderer-registry.js +6 -7
- package/dist/board/render/renderers/draw-card-renderer.js +89 -56
- package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
- package/dist/board/render/renderers/effect-card-renderer.js +197 -0
- package/dist/board/render/renderers/far-plate.d.ts +4 -21
- package/dist/board/render/renderers/far-plate.js +15 -34
- package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
- package/dist/board/render/renderers/file-card-renderer.js +8 -58
- package/dist/board/render/renderers/frame-card-renderer.js +28 -28
- package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
- package/dist/board/render/renderers/image-card-renderer.js +0 -9
- package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
- package/dist/board/render/renderers/shape-card-renderer.js +240 -0
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
- package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
- package/dist/board/render/renderers/task-card-renderer.js +14 -16
- package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
- package/dist/board/render/renderers/text-card-renderer.js +56 -46
- package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
- package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sketch-host.d.ts +14 -0
- package/dist/board/render/sketch-host.js +216 -0
- package/dist/board/render/stroke.d.ts +12 -0
- package/dist/board/render/stroke.js +90 -0
- package/dist/board/render/text-measurement.d.ts +6 -26
- package/dist/board/render/text-measurement.js +11 -30
- package/dist/board/replay.d.ts +6 -31
- package/dist/board/replay.js +45 -203
- package/dist/board/runtime/board-player.d.ts +24 -0
- package/dist/board/runtime/board-player.js +161 -0
- package/dist/board/task.d.ts +2 -10
- package/dist/board/task.js +1 -10
- package/dist/chunks/environment.d.ts +2291 -3864
- package/dist/chunks/host-bridge.d.ts +253 -0
- package/dist/chunks/http.d.ts +233 -58
- package/dist/chunks/http.js +1046 -1028
- package/dist/chunks/transport.js +113 -59
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/chunks/websocket.js +1 -1
- package/dist/host-bridge.d.ts +54 -0
- package/dist/host-bridge.js +322 -0
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +74 -191
- package/dist/index.js +2032 -1186
- package/dist/protocol/dist/board-constants.d.ts +1 -29
- package/dist/protocol/dist/board-constants.js +2 -115
- package/dist/protocol/dist/board-layout.d.ts +59 -0
- package/dist/protocol/dist/board-layout.js +482 -0
- package/dist/protocol/dist/board-model.d.ts +2052 -0
- package/dist/protocol/dist/board-model.js +592 -0
- package/dist/protocol/dist/board-patch.d.ts +86 -0
- package/dist/protocol/dist/board-patch.js +734 -0
- package/dist/protocol/dist/board-schema.d.ts +33 -0
- package/dist/protocol/dist/board-schema.js +59 -0
- package/dist/protocol/dist/board-upgrade.d.ts +1 -0
- package/dist/protocol/dist/board-url.d.ts +1 -3
- package/dist/protocol/dist/board-url.js +2 -2
- package/dist/protocol/dist/board.d.ts +37 -232
- package/dist/protocol/dist/board.js +62 -160
- package/dist/protocol/dist/display/actions.d.ts +1 -0
- package/dist/protocol/dist/display/index.d.ts +2 -0
- package/dist/protocol/dist/index.d.ts +11 -12
- package/dist/protocol/dist/model/metrics.d.ts +2 -0
- package/dist/protocol/dist/model/session.d.ts +2 -1
- package/dist/protocol/dist/realtime/types.d.ts +2 -2
- package/dist/protocol/dist/runtime/index.d.ts +1 -0
- package/dist/space-picker.d.ts +1 -0
- package/dist/space-picker.js +1 -0
- package/dist/types.d.ts +2 -5
- package/package.json +9 -1
- package/dist/board/core/connections.d.ts +0 -108
- package/dist/board/core/connections.js +0 -455
- package/dist/board/mutation.d.ts +0 -6
- package/dist/board/mutation.js +0 -14
- package/dist/board/render/connection-layer.d.ts +0 -53
- package/dist/board/render/connection-layer.js +0 -375
- package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
- package/dist/board/render/renderers/geo-card-renderer.js +0 -127
- package/dist/board/semantic-document.d.ts +0 -37
- package/dist/board/semantic-document.js +0 -288
- package/dist/board/semantic-mutation.d.ts +0 -10
- package/dist/board/semantic-mutation.js +0 -209
- package/dist/protocol/dist/board-animation.js +0 -34
- package/dist/protocol/dist/board-authoring.d.ts +0 -1326
- package/dist/protocol/dist/board-authoring.js +0 -302
- package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
- package/dist/protocol/dist/board-capability-registry.js +0 -123
- package/dist/protocol/dist/board-codec.d.ts +0 -3
- package/dist/protocol/dist/board-codec.js +0 -383
- package/dist/protocol/dist/board-composition.d.ts +0 -383
- package/dist/protocol/dist/board-composition.js +0 -310
- package/dist/protocol/dist/board-connection.d.ts +0 -317
- package/dist/protocol/dist/board-connection.js +0 -219
- package/dist/protocol/dist/board-document.d.ts +0 -1566
- package/dist/protocol/dist/board-document.js +0 -456
- package/dist/protocol/dist/board-effect.d.ts +0 -96
- package/dist/protocol/dist/board-effect.js +0 -54
- package/dist/protocol/dist/board-geometry.d.ts +0 -15
- package/dist/protocol/dist/board-geometry.js +0 -137
- package/dist/protocol/dist/board-node.d.ts +0 -8
- package/dist/protocol/dist/board-node.js +0 -252
- /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BoardDash } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import "../../protocol/dist/index.js";
|
|
3
|
+
import { WorldPoint } from "../geometry.js";
|
|
4
|
+
import { Graphics } from "pixi.js";
|
|
5
|
+
//#region src/board/render/stroke.d.ts
|
|
6
|
+
declare function dashPattern(dash: BoardDash | undefined, width: number): [number, number] | null;
|
|
7
|
+
declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefined): readonly WorldPoint[];
|
|
8
|
+
declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
|
|
9
|
+
declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
|
|
10
|
+
declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { worldPoint } from "../geometry.js";
|
|
2
|
+
//#region src/board/render/stroke.ts
|
|
3
|
+
const HEAD_SPREAD = Math.PI / 6;
|
|
4
|
+
function dashPattern(dash, width) {
|
|
5
|
+
if (dash === "dashed") return [Math.max(6, width * 4), Math.max(4, width * 3)];
|
|
6
|
+
if (dash === "dotted") return [Math.max(.5, width * .01), Math.max(3, width * 2)];
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
function trimPolyline(path, trim) {
|
|
10
|
+
if (trim === void 0 || trim >= 1 || path.length < 2) return path;
|
|
11
|
+
if (trim <= 0) return [];
|
|
12
|
+
let total = 0;
|
|
13
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
14
|
+
const a = path[index - 1];
|
|
15
|
+
const b = path[index];
|
|
16
|
+
total += Math.hypot(b.x - a.x, b.y - a.y);
|
|
17
|
+
}
|
|
18
|
+
let remaining = total * trim;
|
|
19
|
+
const out = [path[0]];
|
|
20
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
21
|
+
const a = path[index - 1];
|
|
22
|
+
const b = path[index];
|
|
23
|
+
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
24
|
+
if (length >= remaining) {
|
|
25
|
+
const t = length > 0 ? remaining / length : 0;
|
|
26
|
+
out.push(worldPoint(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t));
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
out.push(b);
|
|
30
|
+
remaining -= length;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function tracePolyline(graphics, path, pattern, closed = false) {
|
|
35
|
+
const points = closed && path.length > 2 ? [...path, path[0]] : path;
|
|
36
|
+
if (points.length < 2) return;
|
|
37
|
+
if (!pattern) {
|
|
38
|
+
const first = points[0];
|
|
39
|
+
graphics.moveTo(first.x, first.y);
|
|
40
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
41
|
+
const point = points[index];
|
|
42
|
+
graphics.lineTo(point.x, point.y);
|
|
43
|
+
}
|
|
44
|
+
if (closed) graphics.closePath();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const [dash, gap] = pattern;
|
|
48
|
+
let penDown = true;
|
|
49
|
+
let fresh = true;
|
|
50
|
+
let remaining = dash;
|
|
51
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
52
|
+
const a = points[index - 1];
|
|
53
|
+
const b = points[index];
|
|
54
|
+
const dx = b.x - a.x;
|
|
55
|
+
const dy = b.y - a.y;
|
|
56
|
+
const length = Math.hypot(dx, dy);
|
|
57
|
+
let travelled = 0;
|
|
58
|
+
while (length - travelled > 1e-4) {
|
|
59
|
+
const step = Math.min(length - travelled, remaining);
|
|
60
|
+
if (penDown) {
|
|
61
|
+
const t0 = travelled / length;
|
|
62
|
+
const t1 = (travelled + step) / length;
|
|
63
|
+
if (fresh) graphics.moveTo(a.x + dx * t0, a.y + dy * t0);
|
|
64
|
+
fresh = false;
|
|
65
|
+
graphics.lineTo(a.x + dx * t1, a.y + dy * t1);
|
|
66
|
+
}
|
|
67
|
+
travelled += step;
|
|
68
|
+
remaining -= step;
|
|
69
|
+
if (remaining <= 1e-4) {
|
|
70
|
+
penDown = !penDown;
|
|
71
|
+
fresh = penDown;
|
|
72
|
+
remaining = penDown ? dash : gap;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function traceArrowhead(graphics, tip, angle, size) {
|
|
78
|
+
graphics.moveTo(tip.x - size * Math.cos(angle - HEAD_SPREAD), tip.y - size * Math.sin(angle - HEAD_SPREAD)).lineTo(tip.x, tip.y).lineTo(tip.x - size * Math.cos(angle + HEAD_SPREAD), tip.y - size * Math.sin(angle + HEAD_SPREAD));
|
|
79
|
+
}
|
|
80
|
+
function endAngle(path, atStart) {
|
|
81
|
+
if (path.length < 2) return null;
|
|
82
|
+
const tip = atStart ? path[0] : path[path.length - 1];
|
|
83
|
+
for (let offset = 1; offset < path.length; offset += 1) {
|
|
84
|
+
const other = atStart ? path[offset] : path[path.length - 1 - offset];
|
|
85
|
+
if (Math.hypot(tip.x - other.x, tip.y - other.y) > .001) return Math.atan2(tip.y - other.y, tip.x - other.x);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
|
|
@@ -1,30 +1,10 @@
|
|
|
1
1
|
//#region src/board/render/text-measurement.d.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* module owns the layout maths and only borrows this measurer, which is what
|
|
8
|
-
* lets `@neta-art/cohub/board` lay text out with no PixiJS installed.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Route board text measurement through the active Pixi DOM adapter.
|
|
12
|
-
*
|
|
13
|
-
* Call this after swapping the adapter (see `../headless`), which drops the
|
|
14
|
-
* cached measuring context. Anything that goes through `getBoardCardRenderer`
|
|
15
|
-
* is already covered by `ensureBoardTextMeasurement`.
|
|
16
|
-
*/
|
|
2
|
+
declare const BOARD_FONT_STACKS: {
|
|
3
|
+
sans: string;
|
|
4
|
+
serif: string;
|
|
5
|
+
mono: string;
|
|
6
|
+
};
|
|
17
7
|
declare function installBoardTextMeasurement(): void;
|
|
18
|
-
/**
|
|
19
|
-
* Install the canvas measurer once, unless it already is.
|
|
20
|
-
*
|
|
21
|
-
* The core metrics module falls back to a per-character estimate when no
|
|
22
|
-
* measurer is set, which is off by ~90% for real text — wrong enough to
|
|
23
|
-
* misplace every label. Rather than make that a step consumers can forget, the
|
|
24
|
-
* renderer registry calls this before handing a renderer out. It cannot be an
|
|
25
|
-
* import-time side effect: the package is marked side-effect free, so a bundler
|
|
26
|
-
* is entitled to drop a module whose only job happens at load time.
|
|
27
|
-
*/
|
|
28
8
|
declare function ensureBoardTextMeasurement(): void;
|
|
29
9
|
//#endregion
|
|
30
|
-
export { ensureBoardTextMeasurement, installBoardTextMeasurement };
|
|
10
|
+
export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
-
import { setBoardTextMeasurer } from "
|
|
1
|
+
import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../protocol/dist/board-constants.js";
|
|
2
|
+
import { setBoardTextMeasurer } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { boardFontFamily } from "../core/text-metrics.js";
|
|
3
4
|
import { DOMAdapter } from "pixi.js";
|
|
4
5
|
//#region src/board/render/text-measurement.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* module owns the layout maths and only borrows this measurer, which is what
|
|
11
|
-
* lets `@neta-art/cohub/board` lay text out with no PixiJS installed.
|
|
12
|
-
*/
|
|
6
|
+
const BOARD_FONT_STACKS = {
|
|
7
|
+
sans: BOARD_FONT_STACK,
|
|
8
|
+
serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", serif",
|
|
9
|
+
mono: BOARD_MONO_FONT_STACK
|
|
10
|
+
};
|
|
13
11
|
let measureContext;
|
|
14
12
|
let installed = false;
|
|
15
13
|
function getMeasureContext() {
|
|
@@ -21,37 +19,20 @@ function getMeasureContext() {
|
|
|
21
19
|
}
|
|
22
20
|
return measureContext;
|
|
23
21
|
}
|
|
24
|
-
function measureText(text, fontSize) {
|
|
22
|
+
function measureText(text, fontSize, fontWeight, font) {
|
|
25
23
|
const context = getMeasureContext();
|
|
26
24
|
if (!context) return null;
|
|
27
|
-
context.font =
|
|
25
|
+
context.font = `${fontWeight} ${fontSize}px ${boardFontFamily(font, BOARD_FONT_STACKS)}`;
|
|
28
26
|
return context.measureText(text).width;
|
|
29
27
|
}
|
|
30
|
-
/**
|
|
31
|
-
* Route board text measurement through the active Pixi DOM adapter.
|
|
32
|
-
*
|
|
33
|
-
* Call this after swapping the adapter (see `../headless`), which drops the
|
|
34
|
-
* cached measuring context. Anything that goes through `getBoardCardRenderer`
|
|
35
|
-
* is already covered by `ensureBoardTextMeasurement`.
|
|
36
|
-
*/
|
|
37
28
|
function installBoardTextMeasurement() {
|
|
38
29
|
measureContext = void 0;
|
|
39
30
|
installed = true;
|
|
40
31
|
setBoardTextMeasurer(measureText);
|
|
41
32
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Install the canvas measurer once, unless it already is.
|
|
44
|
-
*
|
|
45
|
-
* The core metrics module falls back to a per-character estimate when no
|
|
46
|
-
* measurer is set, which is off by ~90% for real text — wrong enough to
|
|
47
|
-
* misplace every label. Rather than make that a step consumers can forget, the
|
|
48
|
-
* renderer registry calls this before handing a renderer out. It cannot be an
|
|
49
|
-
* import-time side effect: the package is marked side-effect free, so a bundler
|
|
50
|
-
* is entitled to drop a module whose only job happens at load time.
|
|
51
|
-
*/
|
|
52
33
|
function ensureBoardTextMeasurement() {
|
|
53
34
|
if (installed) return;
|
|
54
35
|
installBoardTextMeasurement();
|
|
55
36
|
}
|
|
56
37
|
//#endregion
|
|
57
|
-
export { ensureBoardTextMeasurement, installBoardTextMeasurement };
|
|
38
|
+
export { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement };
|
package/dist/board/replay.d.ts
CHANGED
|
@@ -1,52 +1,27 @@
|
|
|
1
|
+
import { BoardDocument } from "../protocol/dist/board-model.js";
|
|
1
2
|
import { RequestSource } from "../protocol/dist/provenance.js";
|
|
2
|
-
import {
|
|
3
|
-
import { BoardDocument } from "../protocol/dist/board-document.js";
|
|
3
|
+
import { BoardHistoryPage } from "../protocol/dist/board.js";
|
|
4
4
|
import "../protocol/dist/index.js";
|
|
5
5
|
//#region src/board/replay.d.ts
|
|
6
6
|
type BoardReplayActorKind = "human" | "cli" | "agent";
|
|
7
|
-
/** One step on the replay timeline. */
|
|
8
7
|
type BoardReplayEntry = {
|
|
9
8
|
version: number;
|
|
10
9
|
actorId: string;
|
|
11
10
|
kind: BoardReplayActorKind;
|
|
12
|
-
/** Epoch milliseconds. */
|
|
13
11
|
at: number;
|
|
14
|
-
/** Whether the step changed anything visible in the render document. */
|
|
15
|
-
visual: boolean;
|
|
16
12
|
};
|
|
17
13
|
type BoardReplayPlayer = ReturnType<typeof createBoardReplayPlayer>;
|
|
18
14
|
declare function boardReplayActorKind(source: RequestSource | null): BoardReplayActorKind;
|
|
19
|
-
|
|
20
|
-
* Create a player positioned at the live version described by the first page.
|
|
21
|
-
*
|
|
22
|
-
* `transactions` are newest-first as served; the player keeps them oldest-first.
|
|
23
|
-
* Older pages can be added with `prepend`, and newer transactions that arrive
|
|
24
|
-
* live can be added with `append`, so the timeline grows in both directions
|
|
25
|
-
* without a reset.
|
|
26
|
-
*/
|
|
27
|
-
declare function createBoardReplayPlayer(page: BoardTransactionsPage): {
|
|
28
|
-
/** Oldest version the loaded transactions can rewind to. */
|
|
15
|
+
declare function createBoardReplayPlayer(live: BoardDocument, page: BoardHistoryPage): {
|
|
29
16
|
readonly floor: number;
|
|
30
|
-
/** Newest version known to the player. */
|
|
31
17
|
readonly head: number;
|
|
32
18
|
readonly entries: readonly BoardReplayEntry[];
|
|
33
|
-
/** Current position. */
|
|
34
19
|
readonly version: number;
|
|
35
20
|
seek: (version: number) => number;
|
|
36
|
-
documentAt
|
|
37
|
-
/** Item ids touched by the transaction that produced `version`, for camera follow. */
|
|
21
|
+
documentAt(version: number): BoardDocument;
|
|
38
22
|
changedItemIds(version: number): string[];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Add transactions that landed after the current head. Forward payloads are
|
|
43
|
-
* enough to extend the timeline; the scrub position is untouched.
|
|
44
|
-
*
|
|
45
|
-
* Returns false when the page does not reach back to the head: the chain
|
|
46
|
-
* would have a hole, so nothing is appended and the caller must fetch an
|
|
47
|
-
* older page (`before` = the page's oldest version) and try again.
|
|
48
|
-
*/
|
|
49
|
-
append(latest: BoardTransactionsPage): boolean;
|
|
23
|
+
prepend(older: BoardHistoryPage): void;
|
|
24
|
+
append(latest: BoardHistoryPage): boolean;
|
|
50
25
|
};
|
|
51
26
|
//#endregion
|
|
52
27
|
export { BoardReplayActorKind, BoardReplayEntry, BoardReplayPlayer, boardReplayActorKind, createBoardReplayPlayer };
|
package/dist/board/replay.js
CHANGED
|
@@ -1,162 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BoardAppearanceSchema, parseBoardDocument } from "../protocol/dist/board-document.js";
|
|
3
|
-
import { boardNodeToAuthoringItem } from "../protocol/dist/board-codec.js";
|
|
4
|
-
import { DEFAULT_BOARD_APPEARANCE, boardAuthoringItemToDocumentItem } from "./semantic-document.js";
|
|
1
|
+
import { applyBoardDelta } from "../protocol/dist/board-patch.js";
|
|
5
2
|
//#region src/board/replay.ts
|
|
6
3
|
function boardReplayActorKind(source) {
|
|
7
4
|
if (!source) return "human";
|
|
8
5
|
if (source.toolCallId) return "agent";
|
|
9
6
|
return source.via === "cli" ? "cli" : "human";
|
|
10
7
|
}
|
|
11
|
-
function isRecord(value) {
|
|
12
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
|
-
}
|
|
14
|
-
/** Apply one stored operation (forward payload or inverse) to the mutable state. */
|
|
15
|
-
function applyOperation(state, type, payload) {
|
|
16
|
-
switch (type) {
|
|
17
|
-
case "board.patch": {
|
|
18
|
-
const patch = isRecord(payload.patch) ? payload.patch : {};
|
|
19
|
-
if (isRecord(patch.metadata)) state.metadata = patch.metadata;
|
|
20
|
-
if (isRecord(patch.metadataPatch)) state.metadata = {
|
|
21
|
-
...state.metadata,
|
|
22
|
-
...patch.metadataPatch
|
|
23
|
-
};
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
case "node.create": {
|
|
27
|
-
const node = payload.node;
|
|
28
|
-
if (node) state.nodes.set(node.nodeId, node);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
case "node.patch": {
|
|
32
|
-
const nodeId = payload.nodeId;
|
|
33
|
-
const current = state.nodes.get(nodeId);
|
|
34
|
-
if (current && isRecord(payload.patch)) state.nodes.set(nodeId, {
|
|
35
|
-
...current,
|
|
36
|
-
...payload.patch,
|
|
37
|
-
nodeId
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
case "node.delete":
|
|
42
|
-
state.nodes.delete(payload.nodeId);
|
|
43
|
-
return;
|
|
44
|
-
case "connection.create": {
|
|
45
|
-
const connection = payload.connection;
|
|
46
|
-
if (connection) state.connections.set(connection.id, connection);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
case "connection.patch": {
|
|
50
|
-
const connectionId = payload.connectionId;
|
|
51
|
-
const current = state.connections.get(connectionId);
|
|
52
|
-
if (current && isRecord(payload.patch)) state.connections.set(connectionId, {
|
|
53
|
-
...current,
|
|
54
|
-
...payload.patch,
|
|
55
|
-
id: connectionId
|
|
56
|
-
});
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
case "connection.delete":
|
|
60
|
-
state.connections.delete(payload.connectionId);
|
|
61
|
-
return;
|
|
62
|
-
default: return;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Inverses are stored either as a full operation (`{ type, payload }`) or, for
|
|
67
|
-
* `board.patch`, as a bare `{ patch }`. Normalise both to `(type, payload)`.
|
|
68
|
-
*/
|
|
69
|
-
function inverseOf(operation) {
|
|
70
|
-
const inverse = operation.inverse;
|
|
71
|
-
if (!inverse) return null;
|
|
72
|
-
if (typeof inverse.type === "string" && isRecord(inverse.payload)) return {
|
|
73
|
-
type: inverse.type,
|
|
74
|
-
payload: inverse.payload
|
|
75
|
-
};
|
|
76
|
-
if (operation.type === "board.patch" && isRecord(inverse.patch)) return {
|
|
77
|
-
type: "board.patch",
|
|
78
|
-
payload: { patch: inverse.patch }
|
|
79
|
-
};
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
function operationItemIds(operation) {
|
|
83
|
-
const payload = operation.payload;
|
|
84
|
-
switch (operation.type) {
|
|
85
|
-
case "node.create": return isRecord(payload.node) && typeof payload.node.nodeId === "string" ? [payload.node.nodeId] : [];
|
|
86
|
-
case "node.patch":
|
|
87
|
-
case "node.delete": return typeof payload.nodeId === "string" ? [payload.nodeId] : [];
|
|
88
|
-
default: return [];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const VISUAL_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
92
|
-
"board.patch",
|
|
93
|
-
"node.create",
|
|
94
|
-
"node.patch",
|
|
95
|
-
"node.delete",
|
|
96
|
-
"connection.create",
|
|
97
|
-
"connection.patch",
|
|
98
|
-
"connection.delete"
|
|
99
|
-
]);
|
|
100
8
|
function entryOf(transaction) {
|
|
101
9
|
return {
|
|
102
10
|
version: transaction.version,
|
|
103
11
|
actorId: transaction.actorId,
|
|
104
12
|
kind: boardReplayActorKind(transaction.source),
|
|
105
|
-
at: Date.parse(transaction.createdAt)
|
|
106
|
-
visual: transaction.operations.some((operation) => VISUAL_OPERATION_TYPES.has(operation.type))
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
function appearanceOf(metadata) {
|
|
110
|
-
const parsed = BoardAppearanceSchema.safeParse(metadata.appearance);
|
|
111
|
-
return parsed.success ? parsed.data : DEFAULT_BOARD_APPEARANCE;
|
|
112
|
-
}
|
|
113
|
-
function project(state) {
|
|
114
|
-
const nodes = [...state.nodes.values()].sort((a, b) => (a.orderKey ?? "").localeCompare(b.orderKey ?? ""));
|
|
115
|
-
return parseBoardDocument({
|
|
116
|
-
kind: BOARD_DOCUMENT_KIND,
|
|
117
|
-
version: 1,
|
|
118
|
-
appearance: appearanceOf(state.metadata),
|
|
119
|
-
viewport: {
|
|
120
|
-
x: 0,
|
|
121
|
-
y: 0,
|
|
122
|
-
zoom: 1
|
|
123
|
-
},
|
|
124
|
-
items: nodes.map((node) => boardAuthoringItemToDocumentItem(boardNodeToAuthoringItem(node))),
|
|
125
|
-
connections: [...state.connections.values()]
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
function stateFromSnapshot(snapshot) {
|
|
129
|
-
return {
|
|
130
|
-
nodes: new Map(snapshot.nodes.map(({ boardId: _boardId, version: _version, createdAt: _c, updatedAt: _u, ...node }) => [node.nodeId, node])),
|
|
131
|
-
connections: new Map(snapshot.connections.map(({ boardId: _boardId, revision: _revision, createdAt: _c, updatedAt: _u, ...connection }) => [connection.id, connection])),
|
|
132
|
-
metadata: snapshot.board.metadata
|
|
13
|
+
at: Date.parse(transaction.createdAt)
|
|
133
14
|
};
|
|
134
15
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
* Index of the first transaction whose version is `>= version`. Versions are
|
|
157
|
-
* strictly increasing but not dense (a no-op mutation leaves a gap), so
|
|
158
|
-
* binary-search by value rather than indexing by offset.
|
|
159
|
-
*/
|
|
16
|
+
function createBoardReplayPlayer(live, page) {
|
|
17
|
+
let transactions = [];
|
|
18
|
+
let entries = [];
|
|
19
|
+
let floor = page.version;
|
|
20
|
+
let document = live;
|
|
21
|
+
let cursor = page.version;
|
|
22
|
+
const documents = /* @__PURE__ */ new Map([[cursor, live]]);
|
|
23
|
+
function addOlder(older) {
|
|
24
|
+
const usable = [];
|
|
25
|
+
for (const transaction of [...older].sort((a, b) => b.version - a.version)) {
|
|
26
|
+
if (transaction.version > floor) continue;
|
|
27
|
+
if (!transaction.before || !transaction.after) break;
|
|
28
|
+
if (transaction.version !== floor) break;
|
|
29
|
+
usable.push(transaction);
|
|
30
|
+
floor = transaction.baseVersion;
|
|
31
|
+
}
|
|
32
|
+
usable.reverse();
|
|
33
|
+
transactions = [...usable, ...transactions];
|
|
34
|
+
entries = [...usable.map(entryOf), ...entries];
|
|
35
|
+
}
|
|
36
|
+
addOlder(page.transactions);
|
|
160
37
|
function lowerBound(version) {
|
|
161
38
|
let low = 0;
|
|
162
39
|
let high = transactions.length;
|
|
@@ -167,85 +44,50 @@ function createBoardReplayPlayer(page) {
|
|
|
167
44
|
}
|
|
168
45
|
return low;
|
|
169
46
|
}
|
|
170
|
-
|
|
171
|
-
const candidate = transactions[lowerBound(version)];
|
|
172
|
-
return candidate?.version === version ? candidate : void 0;
|
|
173
|
-
}
|
|
174
|
-
/** Undo the transaction that produced `cursor`. */
|
|
175
|
-
function stepBackward() {
|
|
176
|
-
const transaction = transactionProducing(cursor);
|
|
177
|
-
if (!transaction) return false;
|
|
178
|
-
for (let index = transaction.operations.length - 1; index >= 0; index -= 1) {
|
|
179
|
-
const inverse = inverseOf(transaction.operations[index]);
|
|
180
|
-
if (inverse) applyOperation(state, inverse.type, inverse.payload);
|
|
181
|
-
}
|
|
182
|
-
cursor = transaction.baseVersion;
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
function stepForward(next) {
|
|
186
|
-
for (const operation of next.operations) applyOperation(state, operation.type, operation.payload);
|
|
187
|
-
cursor = next.version;
|
|
188
|
-
}
|
|
189
|
-
function head() {
|
|
190
|
-
return transactions.at(-1)?.version ?? liveVersion;
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Move to `version`, clamped to the loaded range. Returns the version reached,
|
|
194
|
-
* which is the nearest loaded version at or below the request.
|
|
195
|
-
*/
|
|
47
|
+
const head = () => transactions.at(-1)?.version ?? page.version;
|
|
196
48
|
function seek(version) {
|
|
197
49
|
const target = Math.max(floor, Math.min(head(), version));
|
|
198
|
-
while (cursor > target
|
|
199
|
-
|
|
50
|
+
while (cursor > target) {
|
|
51
|
+
const transaction = transactions[lowerBound(cursor)];
|
|
52
|
+
if (!transaction || transaction.version !== cursor || !transaction.before) break;
|
|
53
|
+
document = applyBoardDelta(document, transaction.before);
|
|
54
|
+
cursor = transaction.baseVersion;
|
|
55
|
+
}
|
|
56
|
+
for (let next = transactions[lowerBound(cursor + 1)]; next && next.version <= target; next = transactions[lowerBound(cursor + 1)]) {
|
|
57
|
+
if (!next.after) break;
|
|
58
|
+
document = applyBoardDelta(document, next.after);
|
|
59
|
+
cursor = next.version;
|
|
60
|
+
}
|
|
200
61
|
return cursor;
|
|
201
62
|
}
|
|
202
|
-
function documentAt(version) {
|
|
203
|
-
const reached = seek(version);
|
|
204
|
-
const cached = documents.get(reached);
|
|
205
|
-
if (cached) return cached;
|
|
206
|
-
const document = project(state);
|
|
207
|
-
documents.set(reached, document);
|
|
208
|
-
return document;
|
|
209
|
-
}
|
|
210
63
|
return {
|
|
211
|
-
/** Oldest version the loaded transactions can rewind to. */
|
|
212
64
|
get floor() {
|
|
213
65
|
return floor;
|
|
214
66
|
},
|
|
215
|
-
/** Newest version known to the player. */
|
|
216
67
|
get head() {
|
|
217
68
|
return head();
|
|
218
69
|
},
|
|
219
70
|
get entries() {
|
|
220
71
|
return entries;
|
|
221
72
|
},
|
|
222
|
-
/** Current position. */
|
|
223
73
|
get version() {
|
|
224
74
|
return cursor;
|
|
225
75
|
},
|
|
226
76
|
seek,
|
|
227
|
-
documentAt
|
|
228
|
-
|
|
77
|
+
documentAt(version) {
|
|
78
|
+
const reached = seek(version);
|
|
79
|
+
const cached = documents.get(reached);
|
|
80
|
+
if (cached) return cached;
|
|
81
|
+
documents.set(reached, document);
|
|
82
|
+
return document;
|
|
83
|
+
},
|
|
229
84
|
changedItemIds(version) {
|
|
230
|
-
|
|
85
|
+
const transaction = transactions[lowerBound(version)];
|
|
86
|
+
return transaction?.version === version ? Object.keys(transaction.after?.items ?? {}) : [];
|
|
231
87
|
},
|
|
232
|
-
/** Add an older page (as served, newest-first). */
|
|
233
88
|
prepend(older) {
|
|
234
|
-
|
|
235
|
-
const fresh = older.transactions.filter((transaction) => transaction.version <= current).sort((a, b) => a.version - b.version);
|
|
236
|
-
if (fresh.length === 0) return;
|
|
237
|
-
transactions = [...fresh, ...transactions];
|
|
238
|
-
entries = [...fresh.map(entryOf), ...entries];
|
|
239
|
-
floor = fresh[0].baseVersion;
|
|
89
|
+
addOlder(older.transactions);
|
|
240
90
|
},
|
|
241
|
-
/**
|
|
242
|
-
* Add transactions that landed after the current head. Forward payloads are
|
|
243
|
-
* enough to extend the timeline; the scrub position is untouched.
|
|
244
|
-
*
|
|
245
|
-
* Returns false when the page does not reach back to the head: the chain
|
|
246
|
-
* would have a hole, so nothing is appended and the caller must fetch an
|
|
247
|
-
* older page (`before` = the page's oldest version) and try again.
|
|
248
|
-
*/
|
|
249
91
|
append(latest) {
|
|
250
92
|
const current = head();
|
|
251
93
|
const fresh = latest.transactions.filter((transaction) => transaction.version > current).sort((a, b) => a.version - b.version);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BoardDocument, BoardItem } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardPlaybackCommand, BoardPlaybackSnapshot } from "../../protocol/dist/board.js";
|
|
3
|
+
import "../../protocol/dist/index.js";
|
|
4
|
+
import { BoardCameraOverride, BoardColorResolver } from "../animation.js";
|
|
5
|
+
//#region src/board/runtime/board-player.d.ts
|
|
6
|
+
type BoardPlayerFrame = {
|
|
7
|
+
items: ReadonlyMap<string, BoardItem>;
|
|
8
|
+
camera: BoardCameraOverride;
|
|
9
|
+
time: number;
|
|
10
|
+
playbackTime: number | null;
|
|
11
|
+
running: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare function playbackTimeAt(playback: BoardPlaybackSnapshot, document: BoardDocument, now: number): number | null;
|
|
14
|
+
declare function createBoardPlayer(options?: {
|
|
15
|
+
resolveColor?: BoardColorResolver;
|
|
16
|
+
}): {
|
|
17
|
+
reset(): void;
|
|
18
|
+
enter(ids: readonly string[], current: BoardDocument): void;
|
|
19
|
+
frame(current: BoardDocument, playback: BoardPlaybackSnapshot | null, now: number, wallNow: number, exclude: string | null): BoardPlayerFrame;
|
|
20
|
+
};
|
|
21
|
+
type BoardPlayer = ReturnType<typeof createBoardPlayer>;
|
|
22
|
+
declare function nextLocalPlayback(current: BoardPlaybackSnapshot | null, command: BoardPlaybackCommand, document: BoardDocument, now?: number): BoardPlaybackSnapshot | null;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { BoardPlayer, BoardPlayerFrame, createBoardPlayer, nextLocalPlayback, playbackTimeAt };
|