@neta-art/cohub 8.23.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.
Files changed (159) hide show
  1. package/dist/board/animation.d.ts +57 -181
  2. package/dist/board/animation.js +374 -220
  3. package/dist/board/core/arrow-geometry.d.ts +33 -18
  4. package/dist/board/core/arrow-geometry.js +283 -52
  5. package/dist/board/core/draw-geometry.d.ts +22 -36
  6. package/dist/board/core/draw-geometry.js +148 -85
  7. package/dist/board/core/export-assets.d.ts +3 -6
  8. package/dist/board/core/export-assets.js +0 -2
  9. package/dist/board/core/export-plan.d.ts +12 -63
  10. package/dist/board/core/export-plan.js +55 -94
  11. package/dist/board/core/palette.d.ts +4 -21
  12. package/dist/board/core/palette.js +8 -13
  13. package/dist/board/core/scene.d.ts +33 -0
  14. package/dist/board/core/scene.js +302 -0
  15. package/dist/board/core/shape-definition.d.ts +12 -24
  16. package/dist/board/core/shape-definition.js +0 -19
  17. package/dist/board/core/shape-types.d.ts +6 -120
  18. package/dist/board/core/shape-types.js +5 -5
  19. package/dist/board/core/text-metrics.d.ts +8 -11
  20. package/dist/board/core/text-metrics.js +5 -34
  21. package/dist/board/core/tool-styles.d.ts +6 -16
  22. package/dist/board/core/tool-styles.js +17 -25
  23. package/dist/board/export/index.d.ts +10 -17
  24. package/dist/board/export/index.js +22 -21
  25. package/dist/board/export/scene.d.ts +10 -16
  26. package/dist/board/export/scene.js +17 -30
  27. package/dist/board/geometry.d.ts +27 -56
  28. package/dist/board/geometry.js +7 -67
  29. package/dist/board/headless/index.d.ts +7 -27
  30. package/dist/board/headless/index.js +20 -19
  31. package/dist/board/headless/sketch.d.ts +19 -0
  32. package/dist/board/headless/sketch.js +146 -0
  33. package/dist/board/image-key.d.ts +2 -12
  34. package/dist/board/image-key.js +3 -13
  35. package/dist/board/index.d.ts +18 -20
  36. package/dist/board/index.js +17 -17
  37. package/dist/board/media-playback.d.ts +3 -6
  38. package/dist/board/media-playback.js +6 -6
  39. package/dist/board/presets.d.ts +150 -0
  40. package/dist/board/presets.js +226 -0
  41. package/dist/board/render/board-background.d.ts +5 -3
  42. package/dist/board/render/board-background.js +8 -7
  43. package/dist/board/render/clip.d.ts +14 -0
  44. package/dist/board/render/clip.js +47 -0
  45. package/dist/board/render/index.d.ts +8 -5
  46. package/dist/board/render/index.js +7 -4
  47. package/dist/board/render/media-interaction.d.ts +4 -5
  48. package/dist/board/render/media-interaction.js +1 -2
  49. package/dist/board/render/palette.d.ts +6 -2
  50. package/dist/board/render/palette.js +7 -1
  51. package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
  52. package/dist/board/render/renderers/audio-card-renderer.js +3 -3
  53. package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
  54. package/dist/board/render/renderers/base-card-renderer.js +1 -24
  55. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
  56. package/dist/board/render/renderers/board-renderer-registry.js +6 -7
  57. package/dist/board/render/renderers/draw-card-renderer.js +89 -56
  58. package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
  59. package/dist/board/render/renderers/effect-card-renderer.js +197 -0
  60. package/dist/board/render/renderers/far-plate.d.ts +4 -21
  61. package/dist/board/render/renderers/far-plate.js +15 -34
  62. package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
  63. package/dist/board/render/renderers/file-card-renderer.js +8 -58
  64. package/dist/board/render/renderers/frame-card-renderer.js +28 -28
  65. package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
  66. package/dist/board/render/renderers/image-card-renderer.js +0 -9
  67. package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
  68. package/dist/board/render/renderers/shape-card-renderer.js +240 -0
  69. package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
  70. package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
  71. package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
  72. package/dist/board/render/renderers/task-card-renderer.js +14 -16
  73. package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
  74. package/dist/board/render/renderers/text-card-renderer.js +56 -46
  75. package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
  76. package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
  77. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  78. package/dist/board/render/sketch-host.d.ts +14 -0
  79. package/dist/board/render/sketch-host.js +216 -0
  80. package/dist/board/render/stroke.d.ts +12 -0
  81. package/dist/board/render/stroke.js +90 -0
  82. package/dist/board/render/text-measurement.d.ts +6 -26
  83. package/dist/board/render/text-measurement.js +11 -30
  84. package/dist/board/replay.d.ts +6 -31
  85. package/dist/board/replay.js +45 -203
  86. package/dist/board/runtime/board-player.d.ts +24 -0
  87. package/dist/board/runtime/board-player.js +161 -0
  88. package/dist/board/task.d.ts +2 -10
  89. package/dist/board/task.js +1 -10
  90. package/dist/chunks/environment.d.ts +2296 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +238 -58
  93. package/dist/chunks/http.js +1061 -1034
  94. package/dist/chunks/transport.js +113 -59
  95. package/dist/chunks/websocket.d.ts +1 -1
  96. package/dist/chunks/websocket.js +1 -1
  97. package/dist/host-bridge.d.ts +54 -0
  98. package/dist/host-bridge.js +322 -0
  99. package/dist/http.d.ts +3 -3
  100. package/dist/index.d.ts +76 -193
  101. package/dist/index.js +2037 -1191
  102. package/dist/protocol/dist/board-constants.d.ts +1 -29
  103. package/dist/protocol/dist/board-constants.js +2 -115
  104. package/dist/protocol/dist/board-layout.d.ts +59 -0
  105. package/dist/protocol/dist/board-layout.js +482 -0
  106. package/dist/protocol/dist/board-model.d.ts +2052 -0
  107. package/dist/protocol/dist/board-model.js +592 -0
  108. package/dist/protocol/dist/board-patch.d.ts +86 -0
  109. package/dist/protocol/dist/board-patch.js +734 -0
  110. package/dist/protocol/dist/board-schema.d.ts +33 -0
  111. package/dist/protocol/dist/board-schema.js +59 -0
  112. package/dist/protocol/dist/board-upgrade.d.ts +1 -0
  113. package/dist/protocol/dist/board-url.d.ts +1 -3
  114. package/dist/protocol/dist/board-url.js +2 -2
  115. package/dist/protocol/dist/board.d.ts +37 -232
  116. package/dist/protocol/dist/board.js +62 -160
  117. package/dist/protocol/dist/display/actions.d.ts +1 -0
  118. package/dist/protocol/dist/display/index.d.ts +2 -0
  119. package/dist/protocol/dist/index.d.ts +11 -12
  120. package/dist/protocol/dist/model/metrics.d.ts +2 -0
  121. package/dist/protocol/dist/model/session.d.ts +2 -1
  122. package/dist/protocol/dist/realtime/types.d.ts +2 -2
  123. package/dist/protocol/dist/runtime/index.d.ts +1 -0
  124. package/dist/space-picker.d.ts +1 -0
  125. package/dist/space-picker.js +1 -0
  126. package/dist/types.d.ts +2 -5
  127. package/package.json +9 -1
  128. package/dist/board/core/connections.d.ts +0 -108
  129. package/dist/board/core/connections.js +0 -455
  130. package/dist/board/mutation.d.ts +0 -6
  131. package/dist/board/mutation.js +0 -14
  132. package/dist/board/render/connection-layer.d.ts +0 -53
  133. package/dist/board/render/connection-layer.js +0 -375
  134. package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
  135. package/dist/board/render/renderers/geo-card-renderer.js +0 -127
  136. package/dist/board/semantic-document.d.ts +0 -37
  137. package/dist/board/semantic-document.js +0 -288
  138. package/dist/board/semantic-mutation.d.ts +0 -10
  139. package/dist/board/semantic-mutation.js +0 -209
  140. package/dist/protocol/dist/board-animation.js +0 -34
  141. package/dist/protocol/dist/board-authoring.d.ts +0 -1326
  142. package/dist/protocol/dist/board-authoring.js +0 -302
  143. package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
  144. package/dist/protocol/dist/board-capability-registry.js +0 -123
  145. package/dist/protocol/dist/board-codec.d.ts +0 -3
  146. package/dist/protocol/dist/board-codec.js +0 -383
  147. package/dist/protocol/dist/board-composition.d.ts +0 -383
  148. package/dist/protocol/dist/board-composition.js +0 -310
  149. package/dist/protocol/dist/board-connection.d.ts +0 -317
  150. package/dist/protocol/dist/board-connection.js +0 -219
  151. package/dist/protocol/dist/board-document.d.ts +0 -1566
  152. package/dist/protocol/dist/board-document.js +0 -456
  153. package/dist/protocol/dist/board-effect.d.ts +0 -96
  154. package/dist/protocol/dist/board-effect.js +0 -54
  155. package/dist/protocol/dist/board-geometry.d.ts +0 -15
  156. package/dist/protocol/dist/board-geometry.js +0 -137
  157. package/dist/protocol/dist/board-node.d.ts +0 -8
  158. package/dist/protocol/dist/board-node.js +0 -252
  159. /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
@@ -0,0 +1,98 @@
1
+ import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
+ import { getBoardResolution } from "../text-resolution.js";
3
+ import { positionShell } from "./base-card-renderer.js";
4
+ import { drawFarPlate } from "./far-plate.js";
5
+ import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
6
+ //#region src/board/render/renderers/sketch-card-renderer.ts
7
+ const partsByContainer = /* @__PURE__ */ new WeakMap();
8
+ function sync(container, item, context) {
9
+ const parts = partsByContainer.get(container);
10
+ if (!parts) return;
11
+ if (parts.id !== item.id) {
12
+ context.sketches?.release(parts.id);
13
+ parts.id = item.id;
14
+ }
15
+ positionShell(parts.root, item);
16
+ const { width, height } = item.frame;
17
+ const resolution = Math.min(4, Math.max(.25, context.zoom * getBoardResolution()));
18
+ const texture = context.sketches?.frame(item, context.time, resolution) ?? null;
19
+ const error = context.sketches?.error(item.id) ?? null;
20
+ parts.sprite.visible = Boolean(texture);
21
+ if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
22
+ parts.sprite.width = width;
23
+ parts.sprite.height = height;
24
+ const selected = context.selectedIds.has(item.id);
25
+ const message = texture ? "" : error ? `${item.props.src}\n${error}` : item.props.src;
26
+ const sig = [
27
+ width,
28
+ height,
29
+ selected,
30
+ Boolean(texture),
31
+ message,
32
+ context.palette.border,
33
+ context.palette.muted
34
+ ].join("|");
35
+ if (sig === parts.sig) return;
36
+ parts.sig = sig;
37
+ parts.placeholder.clear();
38
+ if (!texture || selected) parts.placeholder.rect(0, 0, width, height).stroke({
39
+ color: selected ? context.palette.brand : context.palette.border,
40
+ width: selected ? 2 : 1,
41
+ alpha: .8
42
+ });
43
+ parts.label.visible = !texture;
44
+ parts.label.text = message;
45
+ parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
46
+ parts.label.style.wordWrapWidth = Math.max(40, width - 16);
47
+ parts.label.position.set(width / 2, height / 2);
48
+ }
49
+ const sketchCardRenderer = {
50
+ id: "sketch-card",
51
+ canRender: (item) => item.type === "sketch",
52
+ animated: true,
53
+ create: (item, context) => {
54
+ const root = new Container();
55
+ const sprite = new Sprite(Texture.EMPTY);
56
+ const placeholder = new Graphics();
57
+ const label = new Text({
58
+ text: "",
59
+ style: {
60
+ fontFamily: BOARD_MONO_FONT_STACK,
61
+ fontSize: 12,
62
+ align: "center",
63
+ wordWrap: true,
64
+ breakWords: true
65
+ },
66
+ resolution: getBoardResolution()
67
+ });
68
+ label.anchor.set(.5);
69
+ root.addChild(sprite, placeholder, label);
70
+ partsByContainer.set(root, {
71
+ id: item.id,
72
+ root,
73
+ sprite,
74
+ placeholder,
75
+ label,
76
+ sig: ""
77
+ });
78
+ if (item.type === "sketch") sync(root, item, context);
79
+ return root;
80
+ },
81
+ update: (container, item, context) => {
82
+ if (item.type === "sketch") sync(container, item, context);
83
+ },
84
+ renderFar: (graphics, item, context) => {
85
+ drawFarPlate(graphics, item.frame, {
86
+ fill: context.palette.muted,
87
+ fillAlpha: .12
88
+ });
89
+ },
90
+ destroy: (container, context) => {
91
+ const parts = partsByContainer.get(container);
92
+ if (parts) context.sketches?.release(parts.id);
93
+ parts?.root.destroy({ children: true });
94
+ partsByContainer.delete(container);
95
+ }
96
+ };
97
+ //#endregion
98
+ export { sketchCardRenderer };
@@ -1,7 +1,6 @@
1
1
  import { BoardCardRenderer } from "./board-renderer-registry.js";
2
2
  //#region src/board/render/renderers/task-card-renderer.d.ts
3
3
  declare const TASK_CARD_FULL_DETAIL_ZOOM = 0.55;
4
- /** Scale a texture into a frame without cropping or distortion. */
5
4
  declare function containTaskPreviewRect(width: number, height: number, imageWidth: number, imageHeight: number): {
6
5
  x: number;
7
6
  y: number;
@@ -1,7 +1,7 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { featuredTaskArtifact } from "../../task.js";
3
- import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
3
  import { drawAudioWaveform } from "../audio-waveform.js";
4
+ import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
5
5
  import { positionShell } from "./base-card-renderer.js";
6
6
  import { drawFarPlate } from "./far-plate.js";
7
7
  import { fitTextToLines } from "./file-card-renderer.js";
@@ -21,13 +21,13 @@ function previewKindFor(artifact) {
21
21
  }
22
22
  function stateSurfaceFor(item, kind, hasTexture, previewFailed) {
23
23
  if (kind === "texture" && !hasTexture) {
24
- if (item.snapshot.status === "failed") return "failed";
24
+ if (item.props.snapshot.status === "failed") return "failed";
25
25
  return previewFailed ? "unavailable" : "media-loading";
26
26
  }
27
27
  if (kind !== "empty") return null;
28
- if (item.snapshot.status === "failed") return "failed";
29
- if (item.snapshot.status === "running") return "generating";
30
- if (item.snapshot.status === "pending") return "queued";
28
+ if (item.props.snapshot.status === "failed") return "failed";
29
+ if (item.props.snapshot.status === "running") return "generating";
30
+ if (item.props.snapshot.status === "pending") return "queued";
31
31
  return "empty";
32
32
  }
33
33
  function stateLabel(surface) {
@@ -41,16 +41,15 @@ function stateLabel(surface) {
41
41
  }
42
42
  }
43
43
  function statusColor(item, context) {
44
- if (item.snapshot.status === "failed") return context.colors.rose.stroke;
45
- if (item.snapshot.status === "running") return context.colors.brand.stroke;
44
+ if (item.props.snapshot.status === "failed") return context.colors.rose.stroke;
45
+ if (item.props.snapshot.status === "running") return context.colors.brand.stroke;
46
46
  return context.colors.neutral.stroke;
47
47
  }
48
48
  function metadata(item) {
49
- const { artifactCount, artifacts } = item.snapshot;
49
+ const { artifactCount, artifacts } = item.props.snapshot;
50
50
  const extra = artifactCount > artifacts.length ? `${artifacts.length}/${artifactCount}` : artifactCount > 1 ? `+${artifactCount - 1}` : null;
51
- return [item.snapshot.model, extra].filter(Boolean).join(" · ");
51
+ return [item.props.snapshot.model, extra].filter(Boolean).join(" · ");
52
52
  }
53
- /** Scale a texture into a frame without cropping or distortion. */
54
53
  function containTaskPreviewRect(width, height, imageWidth, imageHeight) {
55
54
  if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
56
55
  x: 0,
@@ -82,7 +81,6 @@ function drawPlayBadge(graphics, rect, context) {
82
81
  alpha: .94
83
82
  });
84
83
  }
85
- /** Static state marks avoid implying measurable progress or keeping Pixi awake. */
86
84
  function drawStateMark(graphics, surface, cx, cy, color) {
87
85
  if (surface === "generating" || surface === "media-loading") {
88
86
  graphics.circle(cx, cy, 4).fill({
@@ -169,7 +167,7 @@ function sync(container, item, context) {
169
167
  }
170
168
  const texture = key ? context.getTexture(key) : null;
171
169
  const previewFailed = Boolean(key && !texture && context.hasError(key));
172
- const artifact = featuredTaskArtifact(item.snapshot.artifacts);
170
+ const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
173
171
  const kind = previewKindFor(artifact);
174
172
  const surface = stateSurfaceFor(item, kind, Boolean(texture), previewFailed);
175
173
  const frame = {
@@ -180,7 +178,7 @@ function sync(container, item, context) {
180
178
  };
181
179
  const metaText = artifact ? metadata(item) : "";
182
180
  const showMeta = full && Boolean(metaText);
183
- const failedWithOutput = item.snapshot.status === "failed" && kind !== "empty";
181
+ const failedWithOutput = item.props.snapshot.status === "failed" && kind !== "empty";
184
182
  syncTextResolution(parts.body, parts, context.zoom);
185
183
  syncTextResolution(parts.meta, parts, context.zoom);
186
184
  const visualSig = [
@@ -196,7 +194,7 @@ function sync(container, item, context) {
196
194
  failedWithOutput,
197
195
  artifact?.type ?? "none",
198
196
  artifact?.id ?? "none",
199
- item.taskRunId,
197
+ item.props.taskRunId,
200
198
  texture ? `${texture.width}x${texture.height}` : "none",
201
199
  context.palette.surface,
202
200
  context.palette.hover,
@@ -222,7 +220,7 @@ function sync(container, item, context) {
222
220
  parts.previewArt.clear();
223
221
  if (kind === "waveform") {
224
222
  const bottomInset = showMeta ? META_HEIGHT : 0;
225
- drawAudioWaveform(parts.previewArt, item.taskRunId, {
223
+ drawAudioWaveform(parts.previewArt, item.props.taskRunId, {
226
224
  x: frame.x + PADDING,
227
225
  y: frame.y + PADDING,
228
226
  width: Math.max(1, frame.width - 24),
@@ -335,7 +333,7 @@ const taskCardRenderer = {
335
333
  },
336
334
  renderFar: (graphics, item, context) => {
337
335
  if (item.type !== "task") return;
338
- const active = item.snapshot.status === "failed" || item.snapshot.status === "running" || item.snapshot.status === "pending";
336
+ const active = item.props.snapshot.status === "failed" || item.props.snapshot.status === "running" || item.props.snapshot.status === "pending";
339
337
  drawFarPlate(graphics, item.frame, {
340
338
  fill: context.palette.surface,
341
339
  fillAlpha: .82,
@@ -1,7 +1,5 @@
1
- import { BoardItem, BoardTextItem } from "../../../protocol/dist/board-document.js";
2
1
  import { BoardCardRenderer } from "./board-renderer-registry.js";
3
2
  //#region src/board/render/renderers/text-card-renderer.d.ts
4
3
  declare const textCardRenderer: BoardCardRenderer;
5
- declare function isTextItem(item: BoardItem): item is BoardTextItem;
6
4
  //#endregion
7
- export { isTextItem, textCardRenderer };
5
+ export { textCardRenderer };
@@ -1,38 +1,66 @@
1
- import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { pickBoardColor } from "../../core/palette.js";
3
- import { TEXT_FONT_SIZE, boardTextLineHeight, clampBoardTextFontSize, measureBoardText } from "../../core/text-metrics.js";
1
+ import { layoutBoardText } from "../../../protocol/dist/board-layout.js";
2
+ import { boardFontFamily } from "../../core/text-metrics.js";
4
3
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
5
4
  import { positionShell } from "./base-card-renderer.js";
6
5
  import { drawFarPlate } from "./far-plate.js";
7
- import { Container, Text } from "pixi.js";
6
+ import { itemColor } from "../palette.js";
7
+ import { BOARD_FONT_STACKS } from "../text-measurement.js";
8
+ import { Container, Graphics, Text } from "pixi.js";
8
9
  //#region src/board/render/renderers/text-card-renderer.ts
9
10
  const partsByContainer = /* @__PURE__ */ new WeakMap();
11
+ function revealed(text, reveal) {
12
+ if (reveal >= 1) return text;
13
+ return [...text].slice(0, Math.round(text.length * Math.max(0, reveal))).join("");
14
+ }
15
+ function syncCaret(parts, item, context) {
16
+ const { props } = item;
17
+ const visible = props.caret && props.reveal < 1 && Math.floor(context.time / 500) % 2 === 0;
18
+ parts.caret.visible = visible;
19
+ if (!visible) return;
20
+ const anchorX = props.align === "center" ? .5 : props.align === "right" ? 1 : 0;
21
+ const x = parts.body.position.x + parts.body.width * (1 - anchorX) + 2;
22
+ parts.caret.clear().rect(x, 1, Math.max(1, 1.5 / Math.max(.25, context.zoom)), Math.max(12, parts.body.height - 2)).fill({ color: itemColor(context, item.style.fill, "neutral") });
23
+ }
10
24
  function sync(container, item, context) {
11
25
  const parts = partsByContainer.get(container);
12
26
  if (!parts) return;
13
27
  positionShell(parts.root, item);
14
- const fontSize = clampBoardTextFontSize(item.fontSize);
15
- const measured = measureBoardText(item.text, fontSize);
16
- const previewScale = Math.max(1e-4, item.frame.width / Math.max(1e-4, measured.width));
28
+ const { props } = item;
29
+ const layout = layoutBoardText(props);
30
+ const previewScale = Math.max(1e-4, item.frame.width / Math.max(1e-4, layout.width));
17
31
  parts.body.scale.set(previewScale);
18
- parts.body.position.set(item.frame.width / 2, item.frame.height / 2);
32
+ const anchorX = props.align === "center" ? .5 : props.align === "right" ? 1 : 0;
33
+ parts.body.anchor.set(anchorX, 0);
34
+ parts.body.position.set(item.frame.width * anchorX, 0);
19
35
  syncTextResolution(parts.body, parts, context.zoom * Math.max(1, previewScale));
20
- const color = pickBoardColor(context.colors, item.color || "neutral", context.colorScheme);
21
- const ink = item.color === "neutral" || !item.color ? context.palette.text : color.stroke;
36
+ const ink = itemColor(context, item.style.fill, "neutral");
37
+ const text = revealed(props.text, props.reveal);
22
38
  const contentSig = [
23
- item.text,
24
- item.color,
25
- fontSize,
26
- context.colorScheme,
27
- context.colors.brand.stroke,
28
- context.palette.text
39
+ text,
40
+ ink,
41
+ props.fontSize,
42
+ props.fontWeight,
43
+ props.font,
44
+ props.align,
45
+ props.lineHeight,
46
+ props.width ?? ""
29
47
  ].join("|");
30
- if (contentSig === parts.contentSig) return;
31
- parts.contentSig = contentSig;
32
- if (parts.body.text !== item.text) parts.body.text = item.text || "";
33
- parts.body.style.fill = ink;
34
- parts.body.style.fontSize = fontSize;
35
- parts.body.style.lineHeight = boardTextLineHeight(fontSize);
48
+ if (contentSig !== parts.contentSig) {
49
+ parts.contentSig = contentSig;
50
+ parts.body.text = text;
51
+ Object.assign(parts.body.style, {
52
+ fill: ink,
53
+ fontSize: props.fontSize,
54
+ fontWeight: String(Math.round(props.fontWeight / 100) * 100),
55
+ fontFamily: boardFontFamily(props.font, BOARD_FONT_STACKS),
56
+ align: props.align,
57
+ lineHeight: layout.lineHeight,
58
+ wordWrap: Boolean(props.width),
59
+ wordWrapWidth: props.width ?? 0,
60
+ breakWords: true
61
+ });
62
+ }
63
+ syncCaret(parts, item, context);
36
64
  }
37
65
  const textCardRenderer = {
38
66
  id: "text-card",
@@ -40,26 +68,18 @@ const textCardRenderer = {
40
68
  create: (item, context) => {
41
69
  const root = new Container();
42
70
  const resolution = textResolutionForZoom(context.zoom);
43
- const color = pickBoardColor(context.colors, item.type === "text" ? item.color || "neutral" : "neutral", context.colorScheme);
44
- const ink = item.type === "text" && (item.color === "neutral" || !item.color) ? context.palette.text : color.stroke;
45
71
  const body = new Text({
46
72
  text: "",
47
- style: {
48
- fill: ink,
49
- fontFamily: BOARD_FONT_STACK,
50
- fontSize: TEXT_FONT_SIZE,
51
- fontWeight: "500",
52
- lineHeight: boardTextLineHeight(TEXT_FONT_SIZE),
53
- wordWrap: false
54
- },
73
+ style: { fontFamily: BOARD_FONT_STACKS.sans },
55
74
  resolution,
56
75
  roundPixels: true
57
76
  });
58
- body.anchor.set(.5);
59
- root.addChild(body);
77
+ const caret = new Graphics();
78
+ root.addChild(body, caret);
60
79
  partsByContainer.set(root, {
61
80
  root,
62
81
  body,
82
+ caret,
63
83
  resolution,
64
84
  contentSig: ""
65
85
  });
@@ -69,17 +89,10 @@ const textCardRenderer = {
69
89
  update: (container, item, context) => {
70
90
  if (item.type === "text") sync(container, item, context);
71
91
  },
72
- /**
73
- * Far LOD: a low-alpha ink bar. Body text is well under a pixel tall at these
74
- * zooms, so a rasterised glyph texture would only ever resolve to a smudge —
75
- * this approximates that smudge for free and skips the texture entirely.
76
- */
77
92
  renderFar: (graphics, item, context) => {
78
93
  if (item.type !== "text") return;
79
- const color = pickBoardColor(context.colors, item.color || "neutral", context.colorScheme);
80
- const ink = item.color === "neutral" || !item.color ? context.palette.text : color.stroke;
81
94
  drawFarPlate(graphics, item.frame, {
82
- fill: ink,
95
+ fill: itemColor(context, item.style.fill, "neutral"),
83
96
  fillAlpha: .35
84
97
  });
85
98
  },
@@ -88,8 +101,5 @@ const textCardRenderer = {
88
101
  partsByContainer.delete(container);
89
102
  }
90
103
  };
91
- function isTextItem(item) {
92
- return item.type === "text";
93
- }
94
104
  //#endregion
95
- export { isTextItem, textCardRenderer };
105
+ export { textCardRenderer };
@@ -1,5 +1,4 @@
1
1
  import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { unknownRealType } from "../../../protocol/dist/board-document.js";
3
2
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
3
  import { positionShell } from "./base-card-renderer.js";
5
4
  import { drawFarPlate } from "./far-plate.js";
@@ -7,18 +6,13 @@ import { Container, Graphics, Text } from "pixi.js";
7
6
  //#region src/board/render/renderers/unknown-card-renderer.ts
8
7
  const RADIUS = 10;
9
8
  const partsByContainer = /* @__PURE__ */ new WeakMap();
10
- /**
11
- * Neutral placeholder for shape types this client does not recognise. The shape
12
- * stays fully interactive (select/move/resize via the generic box definition) and
13
- * its data is preserved elsewhere — this is only its display.
14
- */
15
9
  function sync(container, item, context) {
16
10
  const parts = partsByContainer.get(container);
17
11
  if (!parts) return;
18
12
  positionShell(parts.root, item);
19
13
  const { width, height } = item.frame;
20
14
  const selected = context.selectedIds.has(item.id);
21
- const realType = unknownRealType(item);
15
+ const realType = item.type;
22
16
  syncTextResolution(parts.label, parts, context.zoom);
23
17
  const visualSig = [
24
18
  width,
@@ -56,7 +50,7 @@ function sync(container, item, context) {
56
50
  }
57
51
  const unknownCardRenderer = {
58
52
  id: "unknown-card",
59
- canRender: (item) => "raw" in item,
53
+ canRender: () => true,
60
54
  create: (item, context) => {
61
55
  const root = new Container();
62
56
  const box = new Graphics();
@@ -81,11 +75,11 @@ const unknownCardRenderer = {
81
75
  textSig: "",
82
76
  resolution
83
77
  });
84
- if ("raw" in item) sync(root, item, context);
78
+ sync(root, item, context);
85
79
  return root;
86
80
  },
87
81
  update: (container, item, context) => {
88
- if ("raw" in item) sync(container, item, context);
82
+ sync(container, item, context);
89
83
  },
90
84
  renderFar: (graphics, item, context) => {
91
85
  drawFarPlate(graphics, item.frame, {
@@ -1,7 +1,9 @@
1
- import { BoardItem, BoardVideoItem } from "../../../protocol/dist/board-document.js";
1
+ import { BoardVideoItem } from "../../../protocol/dist/board-model.js";
2
+ import "../../../protocol/dist/index.js";
3
+ import { BoardSceneItem, SceneItem } from "../../core/scene.js";
2
4
  import { BoardCardRenderer } from "./board-renderer-registry.js";
3
5
  //#region src/board/render/renderers/video-card-renderer.d.ts
4
6
  declare const videoCardRenderer: BoardCardRenderer;
5
- declare function isVideoItem(item: BoardItem): item is BoardVideoItem;
7
+ declare function isVideoItem(item: BoardSceneItem): item is SceneItem<BoardVideoItem>;
6
8
  //#endregion
7
9
  export { isVideoItem, videoCardRenderer };
@@ -23,7 +23,7 @@ function sync(container, item, context) {
23
23
  const selected = context.selectedIds.has(item.id);
24
24
  const hovered = context.hoveredId === item.id;
25
25
  const resizing = context.resizingIds.has(item.id);
26
- const title = item.snapshot?.title ?? item.ref.path.split("/").pop() ?? "Video";
26
+ const title = item.props.snapshot?.title ?? item.props.src.split("/").pop() ?? "Video";
27
27
  const key = context.assetKey(item);
28
28
  const texture = key ? context.getTexture(key) : null;
29
29
  const failed = Boolean(key && !texture && context.hasError(key));
@@ -40,6 +40,7 @@ function sync(container, item, context) {
40
40
  hovered,
41
41
  failed,
42
42
  title,
43
+ item.props.time ?? 0,
43
44
  parts.label.height,
44
45
  context.palette.surface,
45
46
  context.palette.brand,
@@ -0,0 +1,14 @@
1
+ import { BoardSketchHost } from "./renderers/board-renderer-registry.js";
2
+ //#region src/board/render/sketch-host.d.ts
3
+ type BoardSketchHostOptions = {
4
+ readModule: (src: string) => Promise<string>;
5
+ onFrame?: () => void;
6
+ timeoutMs?: number;
7
+ };
8
+ type BrowserBoardSketchHost = BoardSketchHost & {
9
+ invalidate: (src?: string) => void;
10
+ destroy: () => void;
11
+ };
12
+ declare function createBoardSketchHost(options: BoardSketchHostOptions): BrowserBoardSketchHost;
13
+ //#endregion
14
+ export { BoardSketchHostOptions, BrowserBoardSketchHost, createBoardSketchHost };
@@ -0,0 +1,216 @@
1
+ import { Texture } from "pixi.js";
2
+ //#region src/board/render/sketch-host.ts
3
+ const MAX_EDGE = 2048;
4
+ const FRAME_SOURCE = `<!doctype html><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' blob:; worker-src blob:">
5
+ <script>
6
+ const workerUrl = URL.createObjectURL(new Blob([${JSON.stringify(`
7
+ const modules = new Map();
8
+ for (const name of ["fetch", "XMLHttpRequest", "WebSocket", "EventSource", "importScripts", "indexedDB", "caches"]) {
9
+ try { Object.defineProperty(self, name, { value: undefined, configurable: false }); } catch {}
10
+ }
11
+ self.onmessage = async (event) => {
12
+ const { id, src, code, t, width, height, scale, seed, params, request } = event.data;
13
+ try {
14
+ let entry = modules.get(id);
15
+ if (!entry || entry.code !== code) {
16
+ const url = URL.createObjectURL(new Blob([code], { type: "text/javascript" }));
17
+ try { entry = { code, module: await import(url) }; } finally { URL.revokeObjectURL(url); }
18
+ modules.set(id, entry);
19
+ }
20
+ if (typeof entry.module.draw !== "function") throw new Error(src + " does not export draw(ctx, frame)");
21
+ const canvas = new OffscreenCanvas(Math.max(1, Math.round(width * scale)), Math.max(1, Math.round(height * scale)));
22
+ const ctx = canvas.getContext("2d");
23
+ ctx.scale(scale, scale);
24
+ await entry.module.draw(ctx, { t, width, height, seed, params });
25
+ const bitmap = canvas.transferToImageBitmap();
26
+ self.postMessage({ id, request, bitmap }, [bitmap]);
27
+ } catch (error) {
28
+ self.postMessage({ id, request, error: String(error && error.message || error) });
29
+ }
30
+ };
31
+ `)}], { type: "text/javascript" }));
32
+ const makeWorker = () => {
33
+ const next = new Worker(workerUrl, { type: "module" });
34
+ next.onmessage = (event) => parent.postMessage(event.data, "*", event.data.bitmap ? [event.data.bitmap] : []);
35
+ return next;
36
+ };
37
+ let worker = makeWorker();
38
+ addEventListener("message", (event) => {
39
+ if (event.source !== parent) return;
40
+ if (event.data?.reset) {
41
+ worker.terminate();
42
+ worker = makeWorker();
43
+ return;
44
+ }
45
+ worker.postMessage(event.data);
46
+ });
47
+ parent.postMessage({ ready: true }, "*");
48
+ <\/script>`;
49
+ function frameKey(item, time, width, height, scale) {
50
+ return `${time}|${width}|${height}|${scale}|${item.props.seed ?? ""}|${JSON.stringify(item.props.params)}`;
51
+ }
52
+ function createBoardSketchHost(options) {
53
+ const timeoutMs = options.timeoutMs ?? 2e3;
54
+ const entries = /* @__PURE__ */ new Map();
55
+ const sources = /* @__PURE__ */ new Map();
56
+ const iframe = document.createElement("iframe");
57
+ iframe.setAttribute("sandbox", "allow-scripts");
58
+ iframe.setAttribute("aria-hidden", "true");
59
+ iframe.style.cssText = "position:fixed;width:0;height:0;border:0;visibility:hidden";
60
+ iframe.srcdoc = FRAME_SOURCE;
61
+ let ready = false;
62
+ const queue = [];
63
+ const post = (message) => {
64
+ if (ready) iframe.contentWindow?.postMessage(message, "*");
65
+ else queue.push(message);
66
+ };
67
+ const resetWorker = () => {
68
+ for (const entry of entries.values()) {
69
+ if (entry.pending === null) continue;
70
+ if (entry.timeout) clearTimeout(entry.timeout);
71
+ entry.timeout = null;
72
+ entry.pending = null;
73
+ entry.shown = "";
74
+ entry.request += 1;
75
+ }
76
+ post({ reset: true });
77
+ };
78
+ const onMessage = (event) => {
79
+ if (event.source !== iframe.contentWindow) return;
80
+ const data = event.data;
81
+ if (data.ready) {
82
+ ready = true;
83
+ for (const message of queue.splice(0)) iframe.contentWindow?.postMessage(message, "*");
84
+ return;
85
+ }
86
+ const entry = data.id ? entries.get(data.id) : void 0;
87
+ if (!entry || data.request !== entry.request) {
88
+ data.bitmap?.close();
89
+ return;
90
+ }
91
+ if (data.bitmap) {
92
+ if (entry.timeout) clearTimeout(entry.timeout);
93
+ entry.timeout = null;
94
+ entry.texture?.destroy(true);
95
+ entry.texture = Texture.from(data.bitmap);
96
+ entry.error = null;
97
+ } else {
98
+ if (entry.timeout) clearTimeout(entry.timeout);
99
+ entry.timeout = null;
100
+ entry.error = data.error ?? "Sketch failed.";
101
+ }
102
+ entry.shown = entry.pending ?? entry.shown;
103
+ entry.pending = null;
104
+ options.onFrame?.();
105
+ };
106
+ window.addEventListener("message", onMessage);
107
+ document.body.appendChild(iframe);
108
+ function source(src) {
109
+ let promise = sources.get(src);
110
+ if (!promise) {
111
+ promise = options.readModule(src);
112
+ sources.set(src, promise);
113
+ promise.catch(() => sources.delete(src));
114
+ }
115
+ return promise;
116
+ }
117
+ return {
118
+ frame(item, time, resolution) {
119
+ let entry = entries.get(item.id);
120
+ if (!entry) {
121
+ entry = {
122
+ texture: null,
123
+ error: null,
124
+ shown: "",
125
+ pending: null,
126
+ request: 0,
127
+ code: null,
128
+ loading: false,
129
+ timeout: null,
130
+ sourceError: null
131
+ };
132
+ entries.set(item.id, entry);
133
+ }
134
+ const { width, height } = item.frame;
135
+ const scale = Math.min(resolution, MAX_EDGE / Math.max(1, width), MAX_EDGE / Math.max(1, height));
136
+ const key = frameKey(item, time, width, height, scale);
137
+ if (key !== entry.shown && entry.pending === null) {
138
+ const current = entry;
139
+ if (current.code === null) {
140
+ if (!current.sourceError && !current.loading) {
141
+ current.loading = true;
142
+ source(item.props.src).then((code) => {
143
+ current.code = code;
144
+ current.sourceError = null;
145
+ current.loading = false;
146
+ options.onFrame?.();
147
+ }, (error) => {
148
+ current.loading = false;
149
+ current.sourceError = error instanceof Error ? error.message : String(error);
150
+ current.error = current.sourceError;
151
+ options.onFrame?.();
152
+ });
153
+ }
154
+ } else {
155
+ current.pending = key;
156
+ current.request += 1;
157
+ const request = current.request;
158
+ current.timeout = setTimeout(() => {
159
+ if (current.request !== request || current.pending !== key) return;
160
+ resetWorker();
161
+ current.shown = key;
162
+ current.pending = null;
163
+ current.timeout = null;
164
+ current.error = `${item.props.src} exceeded the ${timeoutMs}ms sketch timeout.`;
165
+ options.onFrame?.();
166
+ }, timeoutMs);
167
+ post({
168
+ id: item.id,
169
+ src: item.props.src,
170
+ code: current.code,
171
+ t: time,
172
+ width,
173
+ height,
174
+ scale,
175
+ seed: item.props.seed ?? item.id,
176
+ params: item.props.params,
177
+ request: current.request
178
+ });
179
+ }
180
+ }
181
+ return entry.texture;
182
+ },
183
+ error: (id) => entries.get(id)?.error ?? null,
184
+ release(id) {
185
+ const entry = entries.get(id);
186
+ if (entry?.timeout) clearTimeout(entry.timeout);
187
+ entry?.texture?.destroy(true);
188
+ entries.delete(id);
189
+ },
190
+ invalidate(src) {
191
+ if (src) sources.delete(src);
192
+ else sources.clear();
193
+ for (const entry of entries.values()) {
194
+ entry.code = null;
195
+ entry.sourceError = null;
196
+ entry.pending = null;
197
+ entry.request += 1;
198
+ if (entry.timeout) clearTimeout(entry.timeout);
199
+ entry.timeout = null;
200
+ entry.shown = "";
201
+ }
202
+ options.onFrame?.();
203
+ },
204
+ destroy() {
205
+ window.removeEventListener("message", onMessage);
206
+ for (const entry of entries.values()) {
207
+ if (entry.timeout) clearTimeout(entry.timeout);
208
+ entry.texture?.destroy(true);
209
+ }
210
+ entries.clear();
211
+ iframe.remove();
212
+ }
213
+ };
214
+ }
215
+ //#endregion
216
+ export { createBoardSketchHost };