@neta-art/cohub 10.0.0 → 11.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.
Files changed (129) hide show
  1. package/README.md +11 -2
  2. package/dist/board/editor/animation.d.ts +3 -0
  3. package/dist/board/editor/animation.js +16 -1
  4. package/dist/board/editor/clipboard.js +7 -0
  5. package/dist/board/editor/copy.js +23 -18
  6. package/dist/board/editor/creation.d.ts +5 -1
  7. package/dist/board/editor/creation.js +2 -1
  8. package/dist/board/editor/deletion.js +3 -3
  9. package/dist/board/editor/document.d.ts +8 -2
  10. package/dist/board/editor/document.js +54 -7
  11. package/dist/board/editor/editor.d.ts +18 -3
  12. package/dist/board/editor/editor.js +11 -1
  13. package/dist/board/editor/query.d.ts +5 -5
  14. package/dist/board/editor/query.js +14 -12
  15. package/dist/board/editor/selection.js +2 -1
  16. package/dist/board/export/index.d.ts +25 -4
  17. package/dist/board/export/index.js +45 -12
  18. package/dist/board/export/node/index.d.ts +5 -4
  19. package/dist/board/export/node/index.js +16 -11
  20. package/dist/board/export/node/scripts.d.ts +16 -0
  21. package/dist/board/export/node/scripts.js +258 -0
  22. package/dist/board/export/scene.d.ts +2 -1
  23. package/dist/board/export/scene.js +20 -6
  24. package/dist/board/model/animation.d.ts +4 -1
  25. package/dist/board/model/animation.js +34 -7
  26. package/dist/board/model/authoring.d.ts +20 -0
  27. package/dist/board/model/authoring.js +185 -0
  28. package/dist/board/model/export-plan.js +1 -0
  29. package/dist/board/model/file-preview.js +1 -1
  30. package/dist/board/model/file-snapshot.js +3 -2
  31. package/dist/board/model/image-key.js +6 -2
  32. package/dist/board/model/index.d.ts +13 -6
  33. package/dist/board/model/index.js +14 -8
  34. package/dist/board/model/items/builtin.js +2 -2
  35. package/dist/board/model/items/create.d.ts +4 -2
  36. package/dist/board/model/items/create.js +6 -5
  37. package/dist/board/model/items/index.d.ts +2 -2
  38. package/dist/board/model/items/index.js +2 -2
  39. package/dist/board/model/media-playback.d.ts +2 -1
  40. package/dist/board/model/media-playback.js +14 -4
  41. package/dist/board/model/media.js +7 -1
  42. package/dist/board/model/sampling.d.ts +19 -0
  43. package/dist/board/model/sampling.js +96 -0
  44. package/dist/board/model/script-example.d.ts +4 -0
  45. package/dist/board/model/script-example.js +21 -0
  46. package/dist/board/model/script-hit.d.ts +10 -0
  47. package/dist/board/model/script-hit.js +52 -0
  48. package/dist/board/model/script-index.d.ts +7 -0
  49. package/dist/board/model/script-index.js +15 -0
  50. package/dist/board/model/script-scene.d.ts +24 -0
  51. package/dist/board/model/script-scene.js +267 -0
  52. package/dist/board/model/script.d.ts +105 -0
  53. package/dist/board/model/script.js +1 -0
  54. package/dist/board/render/css-color.d.ts +6 -1
  55. package/dist/board/render/css-color.js +34 -20
  56. package/dist/board/render/diagnostics.d.ts +20 -0
  57. package/dist/board/render/diagnostics.js +117 -0
  58. package/dist/board/render/index.d.ts +9 -6
  59. package/dist/board/render/index.js +9 -6
  60. package/dist/board/render/palette.d.ts +2 -1
  61. package/dist/board/render/palette.js +6 -2
  62. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  63. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  64. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -5
  65. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  66. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  67. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
  68. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  69. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  70. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  71. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  72. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  73. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  74. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  75. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  76. package/dist/board/render/sampling.d.ts +22 -0
  77. package/dist/board/render/sampling.js +21 -0
  78. package/dist/board/render/script-export.d.ts +11 -0
  79. package/dist/board/render/script-export.js +86 -0
  80. package/dist/board/render/script-host.d.ts +31 -0
  81. package/dist/board/render/script-host.js +421 -0
  82. package/dist/board/render/script-modules.d.ts +2 -0
  83. package/dist/board/render/script-modules.js +2 -0
  84. package/dist/board/render/script-runtime.d.ts +29 -0
  85. package/dist/board/render/script-runtime.js +213 -0
  86. package/dist/board/render/script-sandbox.d.ts +4 -0
  87. package/dist/board/render/script-sandbox.js +173 -0
  88. package/dist/board/render/stroke.d.ts +2 -1
  89. package/dist/board/render/stroke.js +22 -1
  90. package/dist/board/render/text-measurement.js +1 -1
  91. package/dist/board/stage/assets.js +6 -1
  92. package/dist/board/stage/maintenance.d.ts +5 -0
  93. package/dist/board/stage/maintenance.js +11 -0
  94. package/dist/board/stage/scene.js +5 -2
  95. package/dist/board/stage/script-pointer.d.ts +19 -0
  96. package/dist/board/stage/script-pointer.js +35 -0
  97. package/dist/board/stage/stage.d.ts +10 -0
  98. package/dist/board/stage/stage.js +249 -43
  99. package/dist/board/stage/texture-pool.js +2 -3
  100. package/dist/chunks/environment.d.ts +266 -23
  101. package/dist/chunks/host-bridge.d.ts +2 -2
  102. package/dist/chunks/http.d.ts +88 -7
  103. package/dist/chunks/http.js +155 -63
  104. package/dist/chunks/transport.js +3 -1
  105. package/dist/chunks/websocket.d.ts +9 -1
  106. package/dist/chunks/websocket.js +16 -2
  107. package/dist/host-bridge.js +2 -1
  108. package/dist/http.d.ts +2 -2
  109. package/dist/index.d.ts +182 -4
  110. package/dist/index.js +1374 -126
  111. package/dist/protocol/dist/board-assets.d.ts +1 -0
  112. package/dist/protocol/dist/board-constants.js +2 -2
  113. package/dist/protocol/dist/board-model.d.ts +201 -11
  114. package/dist/protocol/dist/board-model.js +33 -14
  115. package/dist/protocol/dist/board-patch.js +2 -1
  116. package/dist/protocol/dist/board-schema.d.ts +4 -2
  117. package/dist/protocol/dist/board-schema.js +18 -4
  118. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  119. package/dist/protocol/dist/board-script-modules.js +127 -0
  120. package/dist/protocol/dist/display/index.d.ts +1 -0
  121. package/dist/protocol/dist/index.d.ts +4 -2
  122. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  123. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  124. package/package.json +2 -1
  125. package/dist/board/export/node/sketch.d.ts +0 -19
  126. package/dist/board/export/node/sketch.js +0 -146
  127. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  128. package/dist/board/render/sketch-host.d.ts +0 -14
  129. package/dist/board/render/sketch-host.js +0 -216
@@ -0,0 +1,173 @@
1
+ import { scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ import { createScriptRuntime } from "./script-runtime.js";
3
+ //#region src/board/render/script-sandbox.ts
4
+ function scriptAgent(runtimeFactory, moduleUrls, load) {
5
+ let runtime = null;
6
+ let input = null;
7
+ let module = null;
8
+ let urls = null;
9
+ let outputVersion = -1;
10
+ let canvas = null;
11
+ let callId = 0;
12
+ const calls = /* @__PURE__ */ new Map();
13
+ const send = (data, transfer = []) => {
14
+ self.postMessage(data, transfer);
15
+ };
16
+ const call = (name, input) => new Promise((resolve, reject) => {
17
+ const id = ++callId;
18
+ calls.set(id, {
19
+ resolve,
20
+ reject
21
+ });
22
+ send({
23
+ kind: "call",
24
+ call: id,
25
+ name,
26
+ input
27
+ });
28
+ });
29
+ let processing = false;
30
+ const queue = [];
31
+ async function process(data) {
32
+ try {
33
+ if (data.input) input = data.input;
34
+ if (!input) throw new Error("Missing script input.");
35
+ if (!runtime) {
36
+ if (!data.sources) throw new Error("Missing script modules.");
37
+ urls = moduleUrls(data.sources);
38
+ const src = input.item.script?.src;
39
+ const url = src ? urls.urls[src] : void 0;
40
+ if (!url) throw new Error("Missing entry module.");
41
+ module = await load(url);
42
+ runtime = runtimeFactory(module, input, {
43
+ call,
44
+ invalidate: () => {
45
+ if (!processing) send({ kind: "dirty" });
46
+ }
47
+ });
48
+ }
49
+ if (data.input) runtime.update(input);
50
+ for (const event of data.events ?? []) runtime.event(event);
51
+ runtime.step(data.t);
52
+ let bitmap;
53
+ if (module?.draw && data.draw !== false) {
54
+ const width = Math.max(1, Math.round(input.width * data.scale));
55
+ const height = Math.max(1, Math.round(input.height * data.scale));
56
+ canvas ??= new OffscreenCanvas(width, height);
57
+ if (canvas.width !== width) canvas.width = width;
58
+ if (canvas.height !== height) canvas.height = height;
59
+ const context = canvas.getContext("2d");
60
+ if (!context) throw new Error("Canvas 2D is unavailable.");
61
+ context.reset();
62
+ context.scale(data.scale, data.scale);
63
+ runtime.draw(context);
64
+ bitmap = canvas.transferToImageBitmap();
65
+ }
66
+ const output = runtime.output();
67
+ const changed = output.version !== outputVersion;
68
+ outputVersion = output.version;
69
+ send({
70
+ kind: "frame",
71
+ request: data.request,
72
+ output: changed ? output : void 0,
73
+ bitmap
74
+ }, bitmap ? [bitmap] : []);
75
+ } catch (error) {
76
+ send({
77
+ kind: "error",
78
+ request: data.request,
79
+ error: error instanceof Error ? error.stack ?? error.message : String(error)
80
+ });
81
+ }
82
+ }
83
+ async function drain() {
84
+ if (processing) return;
85
+ processing = true;
86
+ while (queue.length) {
87
+ const request = queue.shift();
88
+ if (request) await process(request);
89
+ }
90
+ processing = false;
91
+ }
92
+ self.addEventListener("message", (event) => {
93
+ const data = event.data;
94
+ if (data.kind === "snapshot") {
95
+ try {
96
+ for (const event of data.events ?? []) runtime?.event(event);
97
+ send({
98
+ kind: "snapshot",
99
+ snapshot: runtime?.snapshot(true, false) ?? null
100
+ });
101
+ } catch (error) {
102
+ send({
103
+ kind: "snapshot",
104
+ error: String(error)
105
+ });
106
+ }
107
+ return;
108
+ }
109
+ if (data.kind === "dispose") {
110
+ try {
111
+ runtime?.destroy();
112
+ urls?.dispose();
113
+ } finally {
114
+ send({ kind: "disposed" });
115
+ }
116
+ return;
117
+ }
118
+ if (data.kind === "result" && data.call !== void 0) {
119
+ const pending = calls.get(data.call);
120
+ calls.delete(data.call);
121
+ if (data.error) pending?.reject(new Error(data.error));
122
+ else pending?.resolve(data.value);
123
+ return;
124
+ }
125
+ queue.push(data);
126
+ drain();
127
+ });
128
+ self.addEventListener("unhandledrejection", (event) => {
129
+ send({
130
+ kind: "error",
131
+ error: String(event.reason)
132
+ });
133
+ });
134
+ }
135
+ function boardScriptFrameSource() {
136
+ const runtime = createScriptRuntime.toString();
137
+ const urls = scriptModuleUrls.toString();
138
+ const agent = scriptAgent.toString();
139
+ const prelude = "const __name = (value, name) => Object.defineProperty(value, 'name', { value: name, configurable: true });";
140
+ const worker = `${prelude} (${agent})(${runtime}, ${urls}, url => import(url));`;
141
+ return `<!doctype html><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' blob:; worker-src blob:; connect-src 'none'; form-action 'none'; base-uri 'none'"><script>
142
+ ${prelude}
143
+ const workerUrl = URL.createObjectURL(new Blob([${JSON.stringify(worker)}], {type:'text/javascript'}));
144
+ const workers = new Map();
145
+ addEventListener('message', event => {
146
+ if(event.source !== parent) return;
147
+ const {token, data} = event.data;
148
+ if(typeof token !== 'number' || !data) return;
149
+ let entry = workers.get(token);
150
+ if(data.kind === 'terminate') {
151
+ if(entry) { clearTimeout(entry.timer); entry.worker.terminate(); workers.delete(token); }
152
+ return;
153
+ }
154
+ if(!entry) {
155
+ if(!data.sources) return;
156
+ const worker = new Worker(workerUrl);
157
+ entry = {worker}; workers.set(token, entry);
158
+ worker.onmessage = event => {
159
+ parent.postMessage({...event.data, token}, '*', event.data.bitmap ? [event.data.bitmap] : []);
160
+ if(event.data.kind === 'disposed') {
161
+ clearTimeout(entry.timer); worker.terminate(); workers.delete(token);
162
+ }
163
+ };
164
+ worker.onerror = event => parent.postMessage({token,kind:'error',error:event.message || 'Script worker failed to start.'}, '*');
165
+ }
166
+ entry.worker.postMessage(data);
167
+ if(data.kind === 'dispose') entry.timer = setTimeout(() => {entry.worker.terminate(); workers.delete(token);},100);
168
+ });
169
+ parent.postMessage({kind:'ready'}, '*');
170
+ <\/script>`;
171
+ }
172
+ //#endregion
173
+ export { boardScriptFrameSource };
@@ -8,5 +8,6 @@ declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefi
8
8
  declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
9
9
  declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
10
10
  declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
11
+ declare function roundRectPoints(width: number, height: number, radius: number): WorldPoint[];
11
12
  //#endregion
12
- export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
13
+ export { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline };
@@ -86,5 +86,26 @@ function endAngle(path, atStart) {
86
86
  }
87
87
  return null;
88
88
  }
89
+ function roundRectPoints(width, height, radius) {
90
+ const r = Math.min(radius, width / 2, height / 2);
91
+ if (r <= 0) return [
92
+ worldPoint(0, 0),
93
+ worldPoint(width, 0),
94
+ worldPoint(width, height),
95
+ worldPoint(0, height),
96
+ worldPoint(0, 0)
97
+ ];
98
+ const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
99
+ const angle = from + index / 8 * (Math.PI / 2);
100
+ return worldPoint(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
101
+ });
102
+ return [
103
+ ...corner(width - r, r, -Math.PI / 2),
104
+ ...corner(width - r, height - r, 0),
105
+ ...corner(r, height - r, Math.PI / 2),
106
+ ...corner(r, r, Math.PI),
107
+ worldPoint(width - r, 0)
108
+ ];
109
+ }
89
110
  //#endregion
90
- export { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline };
111
+ export { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline };
@@ -5,7 +5,7 @@ import { DOMAdapter } from "pixi.js";
5
5
  //#region src/board/render/text-measurement.ts
6
6
  const BOARD_FONT_STACKS = {
7
7
  sans: BOARD_FONT_STACK,
8
- serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", serif",
8
+ serif: "ui-serif, Georgia, \"Noto Serif CJK SC\", \"Songti SC\", \"Noto Sans Math\", \"DejaVu Sans\", \"Noto Sans CJK SC\", \"FreeSans\", \"FreeSerif\", sans-serif",
9
9
  mono: BOARD_MONO_FONT_STACK
10
10
  };
11
11
  let measureContext;
@@ -1,4 +1,5 @@
1
1
  import { mediaPreviewCandidates } from "../../media.js";
2
+ import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
2
3
  import { featuredTaskArtifact } from "../model/task.js";
3
4
  import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
4
5
  import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
@@ -14,7 +15,11 @@ function taskPreviewKey(artifact) {
14
15
  }
15
16
  function boardAssetKey(item) {
16
17
  if (item.type === "task") return taskPreviewKey(featuredTaskArtifact(item.props.snapshot.artifacts));
17
- if (item.type === "video") return `video:${encodeURIComponent(item.props.src)}:${item.props.snapshot?.mtimeMs ?? "unknown"}`;
18
+ if (item.type === "video") {
19
+ const source = parseBoardMediaSource(item.props.src);
20
+ if (!source) return null;
21
+ return source.kind === "url" ? `video-url:${encodeURIComponent(source.value)}` : `video:${encodeURIComponent(source.value)}:${item.props.snapshot?.mtimeMs ?? "unknown"}`;
22
+ }
18
23
  return imageAssetKey(item);
19
24
  }
20
25
  function keySource(key) {
@@ -0,0 +1,5 @@
1
+ import { Renderer } from "pixi.js";
2
+ //#region src/board/stage/maintenance.d.ts
3
+ declare function scheduleBoardMaintenance(renderer: Renderer): () => void;
4
+ //#endregion
5
+ export { scheduleBoardMaintenance };
@@ -0,0 +1,11 @@
1
+ import { Ticker } from "pixi.js";
2
+ //#region src/board/stage/maintenance.ts
3
+ function scheduleBoardMaintenance(renderer) {
4
+ renderer.events.setTargetElement(null);
5
+ const scheduler = renderer.scheduler;
6
+ Ticker.system.remove(scheduler._update, scheduler);
7
+ const timer = setInterval(() => scheduler._update(), 1e3);
8
+ return () => clearInterval(timer);
9
+ }
10
+ //#endregion
11
+ export { scheduleBoardMaintenance };
@@ -130,7 +130,8 @@ function createBoardScene(options) {
130
130
  selected: context.selectedIds.has(item.id),
131
131
  hovered: context.hoveredId === item.id,
132
132
  resizing: context.resizingIds.has(item.id),
133
- globalSig
133
+ globalSig,
134
+ scriptRevision: context.scripts?.revision?.(item.id) ?? 0
134
135
  };
135
136
  cards.set(item.id, entry);
136
137
  setHeldKey(context, item.id, context.assetKey(item));
@@ -350,7 +351,9 @@ function createBoardScene(options) {
350
351
  const selected = context.selectedIds.has(id);
351
352
  const hovered = context.hoveredId === id;
352
353
  const resizing = context.resizingIds.has(id);
353
- const changed = item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
354
+ const scriptRevision = context.scripts?.revision?.(id) ?? 0;
355
+ const changed = scriptRevision !== entry.scriptRevision || item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
356
+ entry.scriptRevision = scriptRevision;
354
357
  entry.item = item;
355
358
  entry.selected = selected;
356
359
  entry.hovered = hovered;
@@ -0,0 +1,19 @@
1
+ //#region src/board/stage/script-pointer.d.ts
2
+ type BoardScriptPointerTarget = {
3
+ owner: string;
4
+ target: string;
5
+ };
6
+ declare function createBoardScriptPointerRouter(): {
7
+ needsRouting(pointerId: number, scriptsEnabled: boolean): boolean;
8
+ owner(pointerId: number): "editor" | "script" | null;
9
+ route(phase: "down" | "move" | "up" | "leave", pointerId: number, target: BoardScriptPointerTarget | null, point: {
10
+ x: number;
11
+ y: number;
12
+ }, cancelled?: boolean): {
13
+ recipient: BoardScriptPointerTarget | null;
14
+ click: BoardScriptPointerTarget | null;
15
+ };
16
+ clear(): void;
17
+ };
18
+ //#endregion
19
+ export { BoardScriptPointerTarget, createBoardScriptPointerRouter };
@@ -0,0 +1,35 @@
1
+ //#region src/board/stage/script-pointer.ts
2
+ function createBoardScriptPointerRouter() {
3
+ const gestures = /* @__PURE__ */ new Map();
4
+ return {
5
+ needsRouting(pointerId, scriptsEnabled) {
6
+ return scriptsEnabled || gestures.has(pointerId);
7
+ },
8
+ owner(pointerId) {
9
+ const gesture = gestures.get(pointerId);
10
+ return gesture ? gesture.target ? "script" : "editor" : null;
11
+ },
12
+ route(phase, pointerId, target, point, cancelled = false) {
13
+ if (phase === "down") gestures.set(pointerId, {
14
+ target,
15
+ ...point
16
+ });
17
+ const gesture = gestures.get(pointerId);
18
+ const recipient = gesture ? gesture.target : phase === "move" ? target : null;
19
+ let click = null;
20
+ if (phase === "up") {
21
+ gestures.delete(pointerId);
22
+ if (!cancelled && gesture?.target && target?.owner === gesture.target.owner && target.target === gesture.target.target && Math.hypot(point.x - gesture.x, point.y - gesture.y) < 5) click = gesture.target;
23
+ }
24
+ return {
25
+ recipient,
26
+ click
27
+ };
28
+ },
29
+ clear() {
30
+ gestures.clear();
31
+ }
32
+ };
33
+ }
34
+ //#endregion
35
+ export { createBoardScriptPointerRouter };
@@ -1,6 +1,7 @@
1
1
  import { BoardItem } from "../../protocol/dist/board-model.js";
2
2
  import { BoardPlaybackSnapshot } from "../../protocol/dist/board.js";
3
3
  import "../../protocol/dist/index.js";
4
+ import { BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
4
5
  import { BoardViewport, WorldPoint } from "../model/geometry.js";
5
6
  import { BoardScene, BoardSceneItem } from "../model/scene.js";
6
7
  import { BoardShapeColors } from "../model/palette.js";
@@ -47,6 +48,7 @@ type BoardStageOptions = {
47
48
  height: number;
48
49
  }) => void;
49
50
  onVisibleChange?: (ids: ReadonlySet<string> | null) => void;
51
+ scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
50
52
  };
51
53
  type BoardStage = {
52
54
  readonly ready: Promise<boolean>;
@@ -54,6 +56,14 @@ type BoardStage = {
54
56
  readonly camera: BoardViewport;
55
57
  readonly theme: BoardStageTheme;
56
58
  setActive(active: boolean): void;
59
+ setScriptMode(mode: "edit" | "interact" | "paused"): void;
60
+ resetScripts(): void;
61
+ invalidateScript(path: string): void;
62
+ scriptError(id: string): string | null;
63
+ scriptOutput(id: string): BoardScriptOutput | null;
64
+ snapshotScripts(): Promise<Record<string, BoardScriptSnapshot>>;
65
+ restoreScript(id: string, snapshot: BoardScriptSnapshot): void;
66
+ subscribeScripts(listener: () => void): () => void;
57
67
  invalidate(options?: {
58
68
  cards?: boolean;
59
69
  }): void;