@neta-art/cohub 10.0.0 → 11.1.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 (155) hide show
  1. package/README.md +42 -7
  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 +7 -3
  7. package/dist/board/editor/creation.js +5 -4
  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 +22 -7
  12. package/dist/board/editor/editor.js +12 -2
  13. package/dist/board/editor/index.d.ts +2 -2
  14. package/dist/board/editor/interaction.d.ts +8 -2
  15. package/dist/board/editor/interaction.js +34 -1
  16. package/dist/board/editor/query.d.ts +5 -5
  17. package/dist/board/editor/query.js +14 -12
  18. package/dist/board/editor/selection.js +2 -1
  19. package/dist/board/editor/snapshots.d.ts +0 -2
  20. package/dist/board/editor/snapshots.js +4 -20
  21. package/dist/board/export/index.d.ts +25 -4
  22. package/dist/board/export/index.js +45 -12
  23. package/dist/board/export/node/index.d.ts +5 -4
  24. package/dist/board/export/node/index.js +16 -11
  25. package/dist/board/export/node/scripts.d.ts +16 -0
  26. package/dist/board/export/node/scripts.js +258 -0
  27. package/dist/board/export/scene.d.ts +2 -1
  28. package/dist/board/export/scene.js +24 -7
  29. package/dist/board/model/animation.d.ts +4 -1
  30. package/dist/board/model/animation.js +34 -7
  31. package/dist/board/model/authoring.d.ts +20 -0
  32. package/dist/board/model/authoring.js +185 -0
  33. package/dist/board/model/export-plan.js +1 -0
  34. package/dist/board/model/file-preview.js +1 -1
  35. package/dist/board/model/file-snapshot.js +3 -2
  36. package/dist/board/model/image-key.js +7 -3
  37. package/dist/board/model/index.d.ts +15 -7
  38. package/dist/board/model/index.js +16 -9
  39. package/dist/board/model/items/builtin.js +2 -2
  40. package/dist/board/model/items/create.d.ts +19 -4
  41. package/dist/board/model/items/create.js +130 -9
  42. package/dist/board/model/items/index.d.ts +2 -2
  43. package/dist/board/model/items/index.js +2 -2
  44. package/dist/board/model/media-playback.d.ts +2 -1
  45. package/dist/board/model/media-playback.js +15 -5
  46. package/dist/board/model/media.js +7 -1
  47. package/dist/board/model/overlay-placement.d.ts +25 -0
  48. package/dist/board/model/overlay-placement.js +35 -0
  49. package/dist/board/model/replay.d.ts +1 -1
  50. package/dist/board/model/replay.js +1 -0
  51. package/dist/board/model/sampling.d.ts +19 -0
  52. package/dist/board/model/sampling.js +96 -0
  53. package/dist/board/model/script-example.d.ts +4 -0
  54. package/dist/board/model/script-example.js +21 -0
  55. package/dist/board/model/script-hit.d.ts +27 -0
  56. package/dist/board/model/script-hit.js +109 -0
  57. package/dist/board/model/script-index.d.ts +7 -0
  58. package/dist/board/model/script-index.js +15 -0
  59. package/dist/board/model/script-observers.d.ts +18 -0
  60. package/dist/board/model/script-observers.js +92 -0
  61. package/dist/board/model/script-scene.d.ts +24 -0
  62. package/dist/board/model/script-scene.js +294 -0
  63. package/dist/board/model/script.d.ts +149 -0
  64. package/dist/board/model/script.js +1 -0
  65. package/dist/board/render/board-background.js +5 -4
  66. package/dist/board/render/css-color.d.ts +6 -1
  67. package/dist/board/render/css-color.js +34 -20
  68. package/dist/board/render/diagnostics.d.ts +20 -0
  69. package/dist/board/render/diagnostics.js +117 -0
  70. package/dist/board/render/index.d.ts +9 -6
  71. package/dist/board/render/index.js +10 -7
  72. package/dist/board/render/media-interaction.js +1 -1
  73. package/dist/board/render/palette.d.ts +2 -1
  74. package/dist/board/render/palette.js +6 -2
  75. package/dist/board/render/renderers/arrow-card-renderer.js +3 -2
  76. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  77. package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -5
  78. package/dist/board/render/renderers/board-renderer-registry.js +11 -4
  79. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  80. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +18 -16
  81. package/dist/board/render/renderers/draw-card-renderer.js +3 -2
  82. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  83. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  84. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  85. package/dist/board/render/renderers/shape-card-renderer.js +10 -27
  86. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  87. package/dist/board/render/renderers/task-card-renderer.js +220 -71
  88. package/dist/board/render/renderers/text-card-renderer.js +5 -3
  89. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  90. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  91. package/dist/board/render/sampling.d.ts +22 -0
  92. package/dist/board/render/sampling.js +21 -0
  93. package/dist/board/render/script-export.d.ts +11 -0
  94. package/dist/board/render/script-export.js +86 -0
  95. package/dist/board/render/script-host.d.ts +31 -0
  96. package/dist/board/render/script-host.js +446 -0
  97. package/dist/board/render/script-modules.d.ts +2 -0
  98. package/dist/board/render/script-modules.js +2 -0
  99. package/dist/board/render/script-runtime.d.ts +29 -0
  100. package/dist/board/render/script-runtime.js +223 -0
  101. package/dist/board/render/script-sandbox.d.ts +4 -0
  102. package/dist/board/render/script-sandbox.js +173 -0
  103. package/dist/board/render/script-texture.d.ts +11 -0
  104. package/dist/board/render/script-texture.js +33 -0
  105. package/dist/board/render/stroke.d.ts +2 -1
  106. package/dist/board/render/stroke.js +22 -1
  107. package/dist/board/render/text-measurement.js +1 -1
  108. package/dist/board/stage/assets.js +7 -2
  109. package/dist/board/stage/maintenance.d.ts +5 -0
  110. package/dist/board/stage/maintenance.js +11 -0
  111. package/dist/board/stage/scene.d.ts +2 -0
  112. package/dist/board/stage/scene.js +21 -2
  113. package/dist/board/stage/script-pointer.d.ts +20 -0
  114. package/dist/board/stage/script-pointer.js +41 -0
  115. package/dist/board/stage/stage.d.ts +21 -3
  116. package/dist/board/stage/stage.js +350 -47
  117. package/dist/board/stage/texture-pool.js +2 -3
  118. package/dist/chunks/environment.d.ts +305 -27
  119. package/dist/chunks/environment.js +12 -4
  120. package/dist/chunks/host-bridge.d.ts +2 -2
  121. package/dist/chunks/http.d.ts +90 -7
  122. package/dist/chunks/http.js +157 -64
  123. package/dist/chunks/transport.js +4 -2
  124. package/dist/chunks/websocket.d.ts +9 -1
  125. package/dist/chunks/websocket.js +16 -2
  126. package/dist/host-bridge.js +2 -1
  127. package/dist/http.d.ts +3 -3
  128. package/dist/index.d.ts +182 -4
  129. package/dist/index.js +1559 -299
  130. package/dist/protocol/dist/board-assets.d.ts +1 -0
  131. package/dist/protocol/dist/board-constants.js +2 -2
  132. package/dist/protocol/dist/board-model.d.ts +236 -11
  133. package/dist/protocol/dist/board-model.js +46 -17
  134. package/dist/protocol/dist/board-patch.js +2 -1
  135. package/dist/protocol/dist/board-schema.d.ts +4 -2
  136. package/dist/protocol/dist/board-schema.js +18 -4
  137. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  138. package/dist/protocol/dist/board-script-modules.js +127 -0
  139. package/dist/protocol/dist/board-task.d.ts +22 -0
  140. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  141. package/dist/protocol/dist/display/index.d.ts +1 -0
  142. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  143. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  144. package/dist/protocol/dist/index.d.ts +7 -2
  145. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  146. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  147. package/dist/protocol/dist/task/index.d.ts +4 -0
  148. package/package.json +2 -1
  149. package/dist/board/export/node/sketch.d.ts +0 -19
  150. package/dist/board/export/node/sketch.js +0 -146
  151. package/dist/board/model/task.d.ts +0 -11
  152. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  153. package/dist/board/render/sketch-host.d.ts +0 -14
  154. package/dist/board/render/sketch-host.js +0 -216
  155. package/dist/types.d.ts +0 -36
@@ -1,4 +1,5 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { getMediaResourceTitle } from "../../model/media.js";
2
3
  import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
3
4
  import { positionShell } from "./base-card-renderer.js";
4
5
  import { drawFarPlate } from "./far-plate.js";
@@ -23,7 +24,7 @@ function sync(container, item, context) {
23
24
  const selected = context.selectedIds.has(item.id);
24
25
  const hovered = context.hoveredId === item.id;
25
26
  const resizing = context.resizingIds.has(item.id);
26
- const title = item.props.snapshot?.title ?? item.props.src.split("/").pop() ?? "Video";
27
+ const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
27
28
  const key = context.assetKey(item);
28
29
  const texture = key ? context.getTexture(key) : null;
29
30
  const failed = Boolean(key && !texture && context.hasError(key));
@@ -0,0 +1,22 @@
1
+ import { BoardDocument } from "../../protocol/dist/board-model.js";
2
+ import { BoardFrame } from "../../protocol/dist/board-layout.js";
3
+ import "../../protocol/dist/index.js";
4
+ import { BoardScene } from "../model/scene.js";
5
+ import { BoardEvaluation } from "../model/animation.js";
6
+ import { BoardShapeColors } from "../model/palette.js";
7
+ import { BoardRenderPalette } from "./renderers/board-renderer-registry.js";
8
+ //#region src/board/render/sampling.d.ts
9
+ declare function createBoardRenderSampler(document: BoardDocument, animation: string | null, options?: {
10
+ colorScheme?: "dark" | "light";
11
+ colors?: BoardShapeColors;
12
+ palette?: Partial<BoardRenderPalette>;
13
+ }): {
14
+ frameAt(id: string, time: number): BoardFrame | undefined;
15
+ at(time: number): {
16
+ document: BoardDocument;
17
+ evaluation: BoardEvaluation;
18
+ scene: BoardScene;
19
+ };
20
+ };
21
+ //#endregion
22
+ export { createBoardRenderSampler };
@@ -0,0 +1,21 @@
1
+ import { createBoardAnimationSampler } from "../model/sampling.js";
2
+ import { buildFallbackShapeColors } from "../model/palette.js";
3
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
4
+ //#region src/board/render/sampling.ts
5
+ function createBoardRenderSampler(document, animation, options = {}) {
6
+ const colorScheme = options.colorScheme ?? "dark";
7
+ const context = {
8
+ colorScheme,
9
+ colors: options.colors ?? buildFallbackShapeColors(colorScheme),
10
+ palette: {
11
+ ...defaultBoardPalette(colorScheme),
12
+ ...options.palette
13
+ }
14
+ };
15
+ return createBoardAnimationSampler(document, animation, { resolveColor: (color) => ({
16
+ color: itemColor(context, color, "brand", "fill"),
17
+ alpha: itemAlpha(context, color)
18
+ }) });
19
+ }
20
+ //#endregion
21
+ export { createBoardRenderSampler };
@@ -0,0 +1,11 @@
1
+ import { BoardDocument } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { BoardScriptSnapshot } from "../model/script.js";
4
+ import { BoardScriptHost } from "./renderers/board-renderer-registry.js";
5
+ //#region src/board/render/script-export.d.ts
6
+ declare function prepareBrowserBoardScripts(document: BoardDocument, time: number, readModule: (path: string) => Promise<string>, snapshots?: Readonly<Record<string, BoardScriptSnapshot>>): Promise<{
7
+ host: BoardScriptHost;
8
+ dispose(): void;
9
+ }>;
10
+ //#endregion
11
+ export { prepareBrowserBoardScripts };
@@ -0,0 +1,86 @@
1
+ import { composeBoardScriptItems } from "../model/script-scene.js";
2
+ import { buildBoardScene } from "../model/scene.js";
3
+ import { createBoardScriptHost } from "./script-host.js";
4
+ //#region src/board/render/script-export.ts
5
+ async function prepareBrowserBoardScripts(document, time, readModule, snapshots) {
6
+ if (!Number.isFinite(time) || time < 0 || time > 6e5) throw new Error("Script export time must be between 0 and 600000 ms.");
7
+ const element = globalThis.document.createElement("div");
8
+ element.style.cssText = "position:fixed;left:-10000px;top:0;width:1px;height:1px;visibility:hidden;pointer-events:none";
9
+ globalThis.document.body.appendChild(element);
10
+ const outputs = /* @__PURE__ */ new Map();
11
+ const scene = buildBoardScene(document);
12
+ const ids = Object.entries(document.items).filter(([, item]) => item.script?.enabled).map(([id]) => id);
13
+ let schedule = () => {};
14
+ const host = createBoardScriptHost({
15
+ host: element,
16
+ readModule,
17
+ export: true,
18
+ snapshots,
19
+ timeoutMs: 15e3,
20
+ onFrame: () => schedule(),
21
+ onOutput(id, output) {
22
+ if (output) outputs.set(id, output);
23
+ else outputs.delete(id);
24
+ }
25
+ });
26
+ const dispose = () => {
27
+ schedule = () => {};
28
+ host.destroy();
29
+ element.remove();
30
+ };
31
+ try {
32
+ await new Promise((resolve, reject) => {
33
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Script export preparation timed out.")), 2e4);
34
+ let queued = false;
35
+ const sync = () => {
36
+ queued = false;
37
+ try {
38
+ host.sync(document, scene, {
39
+ x: 0,
40
+ y: 0,
41
+ zoom: 1
42
+ }, time, false);
43
+ for (const id of ids) {
44
+ const error = host.error(id);
45
+ if (error) throw new Error(error);
46
+ }
47
+ if (ids.every((id) => host.ready?.(id))) {
48
+ clearTimeout(timer);
49
+ schedule = () => {};
50
+ resolve();
51
+ }
52
+ } catch (error) {
53
+ clearTimeout(timer);
54
+ schedule = () => {};
55
+ reject(error);
56
+ }
57
+ };
58
+ schedule = () => {
59
+ if (!queued) {
60
+ queued = true;
61
+ queueMicrotask(sync);
62
+ }
63
+ };
64
+ schedule();
65
+ });
66
+ return {
67
+ host: {
68
+ ...host,
69
+ document(input) {
70
+ const items = { ...input.items };
71
+ for (const [id, item] of composeBoardScriptItems(input, buildBoardScene(input), outputs)) items[id] = item;
72
+ return {
73
+ ...input,
74
+ items
75
+ };
76
+ }
77
+ },
78
+ dispose
79
+ };
80
+ } catch (error) {
81
+ dispose();
82
+ throw error;
83
+ }
84
+ }
85
+ //#endregion
86
+ export { prepareBrowserBoardScripts };
@@ -0,0 +1,31 @@
1
+ import { BoardDocument } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { BoardViewport } from "../model/geometry.js";
4
+ import { BoardScriptEvent, BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
5
+ import { BoardScene } from "../model/scene.js";
6
+ import { BoardScriptHost } from "./renderers/board-renderer-registry.js";
7
+ //#region src/board/render/script-host.d.ts
8
+ type BoardScriptHostOptions = {
9
+ readModule(path: string): Promise<string>;
10
+ host: HTMLElement;
11
+ onFrame(): void;
12
+ onOutput(id: string, output: BoardScriptOutput | null): void;
13
+ capabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
14
+ timeoutMs?: number;
15
+ export?: boolean;
16
+ snapshots?: Readonly<Record<string, BoardScriptSnapshot>>;
17
+ };
18
+ type BrowserBoardScriptHost = BoardScriptHost & {
19
+ sync(document: BoardDocument, scene: BoardScene, camera: BoardViewport, time: number, interactive: boolean, visibleIds?: ReadonlySet<string> | null): void;
20
+ event(id: string, event: BoardScriptEvent): void;
21
+ output(id: string): BoardScriptOutput | null;
22
+ snapshot(id: string): Promise<BoardScriptSnapshot | null>;
23
+ restore(id: string, snapshot: BoardScriptSnapshot): void;
24
+ fault(id: string, message: string): void;
25
+ readonly animated: boolean;
26
+ invalidate(src?: string): void;
27
+ destroy(): void;
28
+ };
29
+ declare function createBoardScriptHost(options: BoardScriptHostOptions): BrowserBoardScriptHost;
30
+ //#endregion
31
+ export { BoardScriptHostOptions, BrowserBoardScriptHost, createBoardScriptHost };
@@ -0,0 +1,446 @@
1
+ import { boardScriptItems } from "../model/script-index.js";
2
+ import { boardScriptScope } from "../model/script-scene.js";
3
+ import { boardScriptHitVisible, resolveBoardScriptHit } from "../model/script-hit.js";
4
+ import { BoardScriptTexture } from "./script-texture.js";
5
+ import { createBoardScriptModuleReader, loadBoardScriptSources } from "../../protocol/dist/board-script-modules.js";
6
+ import { boardScriptFrameSource } from "./script-sandbox.js";
7
+ //#region src/board/render/script-host.ts
8
+ function createBoardScriptHost(options) {
9
+ const revisions = /* @__PURE__ */ new Map();
10
+ const entries = /* @__PURE__ */ new Map();
11
+ const restored = new Map(Object.entries(options.snapshots ?? {}));
12
+ const sources = /* @__PURE__ */ new Map();
13
+ const dependencies = /* @__PURE__ */ new Map();
14
+ const modules = createBoardScriptModuleReader(options.readModule);
15
+ const tokens = /* @__PURE__ */ new Map();
16
+ let nextToken = 0;
17
+ let brokerReady = false;
18
+ let broker = null;
19
+ const signatures = /* @__PURE__ */ new WeakMap();
20
+ function entrySignature(item) {
21
+ let value = signatures.get(item);
22
+ if (value === void 0) {
23
+ value = JSON.stringify(item.script);
24
+ signatures.set(item, value);
25
+ }
26
+ return value;
27
+ }
28
+ const focusTargets = /* @__PURE__ */ new Map();
29
+ const focusStyles = /* @__PURE__ */ new WeakMap();
30
+ const layer = document.createElement("div");
31
+ let interactive = false;
32
+ layer.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:2";
33
+ layer.dataset.boardScripts = "";
34
+ options.host.appendChild(layer);
35
+ let disposed = false;
36
+ const retiredTextures = /* @__PURE__ */ new Set();
37
+ let retirementFrame = 0;
38
+ function retireTexture(texture) {
39
+ if (!texture.current) return;
40
+ retiredTextures.add(texture);
41
+ if (retirementFrame) return;
42
+ retirementFrame = requestAnimationFrame(() => {
43
+ retirementFrame = requestAnimationFrame(() => {
44
+ retirementFrame = 0;
45
+ for (const retired of retiredTextures) retired.destroy();
46
+ retiredTextures.clear();
47
+ });
48
+ });
49
+ }
50
+ function ensureBroker() {
51
+ if (broker) return;
52
+ broker = document.createElement("iframe");
53
+ broker.setAttribute("sandbox", "allow-scripts");
54
+ broker.setAttribute("aria-hidden", "true");
55
+ broker.tabIndex = -1;
56
+ broker.style.cssText = "position:absolute;visibility:hidden;width:0;height:0;border:0";
57
+ broker.srcdoc = boardScriptFrameSource();
58
+ layer.appendChild(broker);
59
+ }
60
+ function removeFrame(entry) {
61
+ if (entry.capture) {
62
+ clearTimeout(entry.capture.timer);
63
+ entry.capture.reject(/* @__PURE__ */ new Error("Script stopped before its snapshot completed."));
64
+ entry.capture = void 0;
65
+ }
66
+ tokens.delete(entry.token);
67
+ broker?.contentWindow?.postMessage({
68
+ token: entry.token,
69
+ data: { kind: "terminate" }
70
+ }, "*");
71
+ }
72
+ function release(entry) {
73
+ clearTimeout(entry.timer);
74
+ if (entry.ready && !entry.error) {
75
+ post(entry, { kind: "dispose" });
76
+ entry.timer = setTimeout(() => removeFrame(entry), 100);
77
+ } else removeFrame(entry);
78
+ retireTexture(entry.texture);
79
+ entries.delete(entry.id);
80
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
81
+ options.onOutput(entry.id, null);
82
+ }
83
+ function fail(entry, error) {
84
+ clearTimeout(entry.timer);
85
+ entry.error = error;
86
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
87
+ entry.pending = false;
88
+ removeFrame(entry);
89
+ options.onOutput(entry.id, null);
90
+ options.onFrame();
91
+ }
92
+ function post(entry, data) {
93
+ broker?.contentWindow?.postMessage({
94
+ token: entry.token,
95
+ data
96
+ }, "*");
97
+ }
98
+ const onMessage = (event) => {
99
+ const data = event.data;
100
+ if (!data || typeof data !== "object") return;
101
+ const fromBroker = broker !== null && event.source === broker.contentWindow;
102
+ if (fromBroker && data.kind === "ready" && data.token === void 0) {
103
+ brokerReady = true;
104
+ for (const entry of tokens.values()) entry.ready = true;
105
+ options.onFrame();
106
+ return;
107
+ }
108
+ if (!fromBroker) return;
109
+ const entry = tokens.get(data.token);
110
+ if (!entry || entry.error) {
111
+ if (fromBroker || entry) data.bitmap?.close?.();
112
+ return;
113
+ }
114
+ if (data.kind === "disposed") {
115
+ clearTimeout(entry.timer);
116
+ removeFrame(entry);
117
+ return;
118
+ }
119
+ if (entries.get(entry.id) !== entry) {
120
+ data.bitmap?.close?.();
121
+ return;
122
+ }
123
+ if (data.kind === "ready") {
124
+ entry.ready = true;
125
+ options.onFrame();
126
+ return;
127
+ }
128
+ if (data.kind === "dirty") {
129
+ entry.dirty = true;
130
+ options.onFrame();
131
+ return;
132
+ }
133
+ if (data.kind === "call") {
134
+ const capability = typeof data.name === "string" && options.capabilities && Object.hasOwn(options.capabilities, data.name) ? options.capabilities[data.name] : void 0;
135
+ Promise.resolve().then(() => {
136
+ if (!capability) throw new Error(`Capability unavailable: ${data.name}`);
137
+ return capability(entry.id, data.input);
138
+ }).then((value) => post(entry, {
139
+ kind: "result",
140
+ call: data.call,
141
+ value
142
+ }), (error) => post(entry, {
143
+ kind: "result",
144
+ call: data.call,
145
+ error: error instanceof Error ? error.message : String(error)
146
+ }));
147
+ return;
148
+ }
149
+ if (data.kind === "snapshot") {
150
+ const capture = entry.capture;
151
+ if (!capture) return;
152
+ clearTimeout(capture.timer);
153
+ entry.capture = void 0;
154
+ if (data.error) capture.reject(new Error(String(data.error)));
155
+ else capture.resolve(data.snapshot ? {
156
+ ...data.snapshot,
157
+ configuration: entry.signature
158
+ } : null);
159
+ return;
160
+ }
161
+ if (data.kind === "error") {
162
+ fail(entry, String(data.error));
163
+ return;
164
+ }
165
+ if (data.kind !== "frame" || data.request !== entry.request) {
166
+ data.bitmap?.close?.();
167
+ return;
168
+ }
169
+ clearTimeout(entry.timer);
170
+ entry.pending = false;
171
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
172
+ if (data.bitmap) entry.texture.update(data.bitmap);
173
+ try {
174
+ const output = data.output;
175
+ if (output) {
176
+ options.onOutput(entry.id, output);
177
+ entry.output = output;
178
+ }
179
+ } catch (error) {
180
+ fail(entry, error instanceof Error ? error.message : String(error));
181
+ return;
182
+ }
183
+ options.onFrame();
184
+ };
185
+ window.addEventListener("message", onMessage);
186
+ return {
187
+ revision: (id) => revisions.get(id) ?? 0,
188
+ ready: (id) => entries.get(id)?.output !== null && entries.get(id)?.output !== void 0,
189
+ frame: (item) => entries.get(item.id)?.texture.current ?? null,
190
+ error: (id) => entries.get(id)?.error ?? null,
191
+ release: () => {},
192
+ fault(id, message) {
193
+ const entry = entries.get(id);
194
+ if (entry) fail(entry, message);
195
+ },
196
+ snapshot(id) {
197
+ const entry = entries.get(id);
198
+ if (!entry?.output || entry.error) return Promise.resolve(null);
199
+ if (entry.capture) return entry.capture.promise;
200
+ let resolve = () => {};
201
+ let reject = () => {};
202
+ const promise = new Promise((yes, no) => {
203
+ resolve = yes;
204
+ reject = no;
205
+ });
206
+ const timer = setTimeout(() => {
207
+ entry.capture = void 0;
208
+ reject(/* @__PURE__ */ new Error("Script snapshot timed out."));
209
+ }, options.timeoutMs ?? 2e3);
210
+ entry.capture = {
211
+ promise,
212
+ resolve,
213
+ reject,
214
+ timer
215
+ };
216
+ post(entry, {
217
+ kind: "snapshot",
218
+ events: entry.events.splice(0)
219
+ });
220
+ return promise;
221
+ },
222
+ restore(id, snapshot) {
223
+ const entry = entries.get(id);
224
+ if (entry && snapshot.configuration && snapshot.configuration !== entry.signature) throw new Error("Snapshot configuration does not match the script.");
225
+ if (!Number.isFinite(snapshot.time) || snapshot.time < 0 || !Number.isInteger(snapshot.random)) throw new Error("Invalid script snapshot.");
226
+ restored.set(id, structuredClone(snapshot));
227
+ if (entry) release(entry);
228
+ options.onFrame();
229
+ },
230
+ output: (id) => entries.get(id)?.output ?? null,
231
+ get animated() {
232
+ return [...entries.values()].some((entry) => !entry.error && entry.output?.animated && (entry.visible || entry.output.updating) && !entry.pending);
233
+ },
234
+ sync(board, scene, camera, time, inputEnabled, visibleIds) {
235
+ if (disposed) return;
236
+ interactive = inputEnabled;
237
+ const alive = /* @__PURE__ */ new Set();
238
+ const liveFocusTargets = /* @__PURE__ */ new Set();
239
+ let focusSurface;
240
+ for (const [id, item] of boardScriptItems(board)) {
241
+ if (!item.script?.enabled) continue;
242
+ alive.add(id);
243
+ const signature = entrySignature(item);
244
+ let entry = entries.get(id);
245
+ if (entry && entry.signature !== signature) {
246
+ release(entry);
247
+ entry = void 0;
248
+ }
249
+ const box = scene.layout.box(id);
250
+ const scoped = entry?.inputDocument === board ? entry.input.items : { [id]: item };
251
+ if (entry?.inputDocument !== board) for (const child of boardScriptScope(board, id)) {
252
+ const value = board.items[child];
253
+ if (value) scoped[child] = value;
254
+ }
255
+ const input = entry?.inputDocument === board && entry.input.width === box.width && entry.input.height === box.height ? entry.input : {
256
+ id,
257
+ item,
258
+ items: scoped,
259
+ width: box.width,
260
+ height: box.height,
261
+ capabilities: Object.keys(options.capabilities ?? {}),
262
+ snapshot: restored.get(id)
263
+ };
264
+ if (!entry) {
265
+ ensureBroker();
266
+ entry = {
267
+ id,
268
+ signature,
269
+ token: ++nextToken,
270
+ ready: brokerReady,
271
+ sources: null,
272
+ input,
273
+ inputKey: "",
274
+ inputDocument: null,
275
+ startedAt: (options.export ? 0 : time) - (input.snapshot?.time ?? 0),
276
+ texture: new BoardScriptTexture(),
277
+ error: null,
278
+ output: null,
279
+ events: [],
280
+ dirty: true,
281
+ request: 0,
282
+ pending: false,
283
+ time: -1,
284
+ sentSources: false,
285
+ visible: true
286
+ };
287
+ entries.set(id, entry);
288
+ restored.delete(id);
289
+ tokens.set(entry.token, entry);
290
+ const current = entry;
291
+ let loading = sources.get(item.script.src);
292
+ if (!loading) {
293
+ const paths = /* @__PURE__ */ new Set([item.script.src]);
294
+ dependencies.set(item.script.src, paths);
295
+ loading = loadBoardScriptSources(item.script.src, (path) => {
296
+ paths.add(path);
297
+ return modules.read(path);
298
+ });
299
+ sources.set(item.script.src, loading);
300
+ }
301
+ current.timer = setTimeout(() => fail(current, "Script loading timed out."), 15e3);
302
+ loading.then((value) => {
303
+ if (disposed || entries.get(id) !== current) return;
304
+ current.sources = value;
305
+ options.onFrame();
306
+ }, (error) => {
307
+ if (!disposed && entries.get(id) === current) fail(current, String(error));
308
+ });
309
+ }
310
+ if (entry.error) continue;
311
+ const visible = !visibleIds || visibleIds.has(id);
312
+ if (entry.visible !== visible) {
313
+ entry.visible = visible;
314
+ if (visible) entry.dirty = true;
315
+ }
316
+ if (entry.input !== input || entry.inputDocument !== board) {
317
+ const inputKey = JSON.stringify(input);
318
+ if (entry.inputKey !== inputKey) {
319
+ entry.inputKey = inputKey;
320
+ entry.input = input;
321
+ entry.dirty = true;
322
+ }
323
+ entry.inputDocument = board;
324
+ }
325
+ if (entry.output && !options.export) {
326
+ const hits = Object.entries(entry.output.hits).filter(([, hit]) => hit.label);
327
+ for (const [target, hit] of hits) {
328
+ const resolved = resolveBoardScriptHit(scene, id, hit);
329
+ if (!resolved) continue;
330
+ const { rect } = resolved;
331
+ if (interactive) focusSurface ??= {
332
+ width: options.host.clientWidth,
333
+ height: options.host.clientHeight
334
+ };
335
+ const focusable = Boolean(focusSurface && interactive && boardScriptHitVisible(scene, resolved, camera, focusSurface));
336
+ const key = `${id}/${target}`;
337
+ liveFocusTargets.add(key);
338
+ let button = focusTargets.get(key);
339
+ if (!button) {
340
+ button = document.createElement("button");
341
+ button.type = "button";
342
+ const emit = (type, key) => {
343
+ const current = entries.get(id);
344
+ if (!interactive || !current || current.error || !current.output?.listens.includes(type)) return;
345
+ current.events.push({
346
+ type,
347
+ target,
348
+ key
349
+ });
350
+ current.dirty = true;
351
+ options.onFrame();
352
+ };
353
+ button.onfocus = () => emit("focus");
354
+ button.onblur = () => emit("blur");
355
+ button.onclick = () => emit("click");
356
+ button.onkeydown = (event) => emit("keydown", event.key);
357
+ button.onkeyup = (event) => emit("keyup", event.key);
358
+ focusTargets.set(key, button);
359
+ layer.appendChild(button);
360
+ }
361
+ const label = hit.label ?? target;
362
+ if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label);
363
+ const tabIndex = focusable ? 0 : -1;
364
+ if (button.tabIndex !== tabIndex) button.tabIndex = tabIndex;
365
+ const [a, b, c, d, x, y] = scene.layout.matrix(resolved.id);
366
+ const style = `position:absolute;pointer-events:none;background:transparent;border:0;padding:0;transform-origin:0 0;width:${rect.width}px;height:${rect.height}px;transform:matrix(${a * camera.zoom},${b * camera.zoom},${c * camera.zoom},${d * camera.zoom},${(x + a * rect.x + c * rect.y) * camera.zoom + camera.x},${(y + b * rect.x + d * rect.y) * camera.zoom + camera.y})`;
367
+ if (focusStyles.get(button) !== style) {
368
+ button.style.cssText = style;
369
+ focusStyles.set(button, style);
370
+ }
371
+ }
372
+ }
373
+ if (!entry.ready || !entry.sources || entry.pending || !entry.dirty && (!entry.output?.animated || !visible && !entry.output.updating || entry.time === time)) continue;
374
+ entry.pending = true;
375
+ entry.dirty = false;
376
+ entry.time = time;
377
+ entry.request++;
378
+ const current = entry;
379
+ clearTimeout(entry.timer);
380
+ entry.timer = setTimeout(() => fail(current, "Script execution timed out."), options.timeoutMs ?? 2e3);
381
+ post(entry, {
382
+ request: entry.request,
383
+ input: entry.sentInput === entry.input ? void 0 : entry.input,
384
+ sources: entry.sentSources ? void 0 : entry.sources,
385
+ draw: visible,
386
+ t: time - entry.startedAt,
387
+ scale: Math.min(2, 2048 / Math.max(1, box.width, box.height)),
388
+ events: entry.events.splice(0)
389
+ });
390
+ entry.sentInput = entry.input;
391
+ entry.sentSources = true;
392
+ }
393
+ for (const [key, button] of focusTargets) if (!liveFocusTargets.has(key)) {
394
+ button.remove();
395
+ focusTargets.delete(key);
396
+ }
397
+ for (const entry of entries.values()) if (!alive.has(entry.id)) release(entry);
398
+ },
399
+ event(id, event) {
400
+ const entry = entries.get(id);
401
+ if (!entry || entry.error || entry.output && !entry.output.listens.includes(event.type)) return;
402
+ if (event.type === "viewchange" || event.type === "selectionchange") {
403
+ const queued = entry.events.findIndex((pending) => pending.type === event.type);
404
+ if (queued >= 0) entry.events.splice(queued, 1);
405
+ }
406
+ if (event.type === "pointermove" && entry.events.at(-1)?.type === "pointermove") entry.events.pop();
407
+ if (entry.events.length >= 128) return;
408
+ entry.events.push(event);
409
+ entry.dirty = true;
410
+ options.onFrame();
411
+ },
412
+ invalidate(path) {
413
+ modules.invalidate(path);
414
+ const affected = new Set([...sources.keys()].filter((src) => !path || src === path || dependencies.get(src)?.has(path)));
415
+ for (const src of affected) {
416
+ sources.delete(src);
417
+ dependencies.delete(src);
418
+ }
419
+ let changed = false;
420
+ for (const entry of entries.values()) {
421
+ if (path && !affected.has(entry.input.item.script?.src ?? "")) continue;
422
+ restored.delete(entry.id);
423
+ release(entry);
424
+ changed = true;
425
+ }
426
+ if (!path) restored.clear();
427
+ if (changed) options.onFrame();
428
+ },
429
+ destroy() {
430
+ disposed = true;
431
+ window.removeEventListener("message", onMessage);
432
+ for (const entry of entries.values()) release(entry);
433
+ layer.remove();
434
+ sources.clear();
435
+ dependencies.clear();
436
+ modules.destroy();
437
+ focusTargets.clear();
438
+ restored.clear();
439
+ tokens.clear();
440
+ revisions.clear();
441
+ broker = null;
442
+ }
443
+ };
444
+ }
445
+ //#endregion
446
+ export { createBoardScriptHost };
@@ -0,0 +1,2 @@
1
+ import { BOARD_SCRIPT_MAX_BYTES, BoardScriptSource, BoardScriptSources, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ export { BOARD_SCRIPT_MAX_BYTES, BoardScriptSource, BoardScriptSources, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls };
@@ -0,0 +1,2 @@
1
+ import { BOARD_SCRIPT_MAX_BYTES, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ export { BOARD_SCRIPT_MAX_BYTES, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls };
@@ -0,0 +1,29 @@
1
+ import { BoardItem } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { BoardScriptEvent, BoardScriptModule, BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
4
+ //#region src/board/render/script-runtime.d.ts
5
+ type ScriptRuntimeInput = {
6
+ id: string;
7
+ item: BoardItem;
8
+ items: Record<string, BoardItem>;
9
+ width: number;
10
+ height: number;
11
+ capabilities?: string[];
12
+ snapshot?: BoardScriptSnapshot;
13
+ };
14
+ /** Self-contained so browser workers and Node execute exactly the same kernel. */
15
+ declare function createScriptRuntime(module: BoardScriptModule, initial: ScriptRuntimeInput, host: {
16
+ call(name: string, input: unknown): Promise<unknown>;
17
+ invalidate(): void;
18
+ }): {
19
+ readonly animated: boolean;
20
+ update(next: ScriptRuntimeInput): void;
21
+ step(t: number): void;
22
+ event(event: BoardScriptEvent): void;
23
+ draw(canvas: CanvasRenderingContext2D): void;
24
+ output(): BoardScriptOutput;
25
+ snapshot(includeEffects?: boolean, clone?: boolean): BoardScriptSnapshot;
26
+ destroy(): void;
27
+ };
28
+ //#endregion
29
+ export { ScriptRuntimeInput, createScriptRuntime };