@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
@@ -1,15 +1,8 @@
1
- import { boardDrawBounds, boardDrawSampleRadius } from "../../protocol/dist/board-geometry.js";
1
+ import { drawPointsBounds, drawSampleRadius } from "../../protocol/dist/board-layout.js";
2
2
  import { getStroke } from "perfect-freehand";
3
3
  //#region src/board/core/draw-geometry.ts
4
- /** Radius of a sample in world units given the stroke size and pressure. */
5
- const sampleRadius = boardDrawSampleRadius;
6
- /** Axis-aligned bounds of a stroke in its local space, padded by stroke width. */
7
- const computeDrawBounds = boardDrawBounds;
8
- /**
9
- * Ramer–Douglas–Peucker simplification. Reduces point count for low-zoom
10
- * rendering without touching the persisted raw samples. Returns indices into
11
- * the input so callers can keep pressure alongside the simplified path.
12
- */
4
+ const sampleRadius = drawSampleRadius;
5
+ const computeDrawBounds = drawPointsBounds;
13
6
  function simplifyDrawIndices(points, tolerance) {
14
7
  const n = points.length;
15
8
  if (n <= 2 || tolerance <= 0) return points.map((_, i) => i);
@@ -54,11 +47,36 @@ function perpendicularDistance(point, a, b) {
54
47
  const projY = a.y + t * dy;
55
48
  return Math.hypot(point.x - projX, point.y - projY);
56
49
  }
57
- /**
58
- * Build a closed outline for callers that need a path representation.
59
- * Interactive rendering uses `buildStrokeRibbonGeometry` below because a single
60
- * outline is unsafe when a freehand path folds back over itself.
61
- */
50
+ function trimDrawPoints(points, trim) {
51
+ if (trim === void 0 || trim >= 1) return points;
52
+ if (trim <= 0) return [];
53
+ if (points.length < 2) return points;
54
+ let total = 0;
55
+ for (let i = 1; i < points.length; i += 1) {
56
+ const a = points[i - 1];
57
+ const b = points[i];
58
+ total += Math.hypot(b.x - a.x, b.y - a.y);
59
+ }
60
+ let remaining = total * trim;
61
+ const out = [points[0]];
62
+ for (let i = 1; i < points.length; i += 1) {
63
+ const a = points[i - 1];
64
+ const b = points[i];
65
+ const length = Math.hypot(b.x - a.x, b.y - a.y);
66
+ if (length >= remaining) {
67
+ const t = length > 0 ? remaining / length : 0;
68
+ out.push({
69
+ x: a.x + (b.x - a.x) * t,
70
+ y: a.y + (b.y - a.y) * t,
71
+ p: a.p + (b.p - a.p) * t
72
+ });
73
+ return out;
74
+ }
75
+ out.push(b);
76
+ remaining -= length;
77
+ }
78
+ return out;
79
+ }
62
80
  function buildStrokeOutline(points, size) {
63
81
  const n = points.length;
64
82
  if (n === 0) return [];
@@ -122,7 +140,6 @@ function buildStrokeOutline(points, size) {
122
140
  }));
123
141
  }
124
142
  const RIBBON_CIRCLE_SIDES = 8;
125
- /** Whether a sample needs a round join rather than the neighboring segment caps. */
126
143
  function isStrokeCorner(points, index) {
127
144
  if (index === 0 || index === points.length - 1) return true;
128
145
  const before = points[index - 1];
@@ -138,83 +155,129 @@ function isStrokeCorner(points, index) {
138
155
  if (aLength < 1e-6 || bLength < 1e-6) return true;
139
156
  return (ax * bx + ay * by) / (aLength * bLength) < .92;
140
157
  }
141
- /**
142
- * Tessellate a freehand stroke as independent, convex primitives.
143
- *
144
- * A whole-path polygon is deliberately avoided: a path that folds back can make
145
- * its outline self-intersect, and GPU polygon triangulation then creates a large
146
- * accidental fill. Segment quads plus round point joins overlap safely instead.
147
- */
148
- function buildStrokeRibbonGeometry(points, size) {
149
- const positions = [];
150
- const indices = [];
151
- const uvs = [];
152
- const progress = [];
153
- if (points.length === 0) return {
154
- positions: /* @__PURE__ */ new Float32Array(),
155
- indices: /* @__PURE__ */ new Uint32Array(),
156
- uvs: /* @__PURE__ */ new Float32Array(),
157
- progress: /* @__PURE__ */ new Float32Array()
158
+ const SAMPLE_VERTICES = 13;
159
+ function segmentNormal(points, index) {
160
+ const from = points[index];
161
+ const to = points[index + 1];
162
+ if (!from || !to) return null;
163
+ const length = Math.hypot(to.x - from.x, to.y - from.y);
164
+ return length < 1e-6 ? null : {
165
+ x: -(to.y - from.y) / length,
166
+ y: (to.x - from.x) / length
158
167
  };
159
- const lengths = new Array(points.length).fill(0);
160
- for (let i = 1; i < points.length; i += 1) {
161
- const from = points[i - 1];
162
- const to = points[i];
163
- if (from && to) lengths[i] = (lengths[i - 1] ?? 0) + Math.hypot(to.x - from.x, to.y - from.y);
164
- }
165
- const total = Math.max(lengths.at(-1) ?? 0, 1e-6);
166
- const addVertex = (x, y, at) => {
167
- const normalized = at / total;
168
- positions.push(x, y);
169
- uvs.push(normalized, 0);
170
- progress.push(normalized);
171
- return positions.length / 2 - 1;
168
+ }
169
+ function edgeOffset(points, index, own) {
170
+ if (isStrokeCorner(points, index)) return own;
171
+ const before = segmentNormal(points, index - 1);
172
+ const after = segmentNormal(points, index);
173
+ if (!before || !after) return own;
174
+ const x = before.x + after.x;
175
+ const y = before.y + after.y;
176
+ const length = Math.hypot(x, y);
177
+ if (length < 1e-6) return own;
178
+ const scale = 1 / Math.max(.5, (x * own.x + y * own.y) / length);
179
+ return {
180
+ x: x / length * scale,
181
+ y: y / length * scale
172
182
  };
173
- const addTriangle = (a, b, c) => indices.push(a, b, c);
174
- const addRoundPoint = (point, at) => {
183
+ }
184
+ /** A growing stroke re-emits only its last two samples. */
185
+ function createStrokeRibbonBuilder(size) {
186
+ let positions = /* @__PURE__ */ new Float32Array(1664);
187
+ let distances = /* @__PURE__ */ new Float32Array(832);
188
+ let indices = /* @__PURE__ */ new Uint32Array(1920);
189
+ let lengths = /* @__PURE__ */ new Float64Array(64);
190
+ let marks = /* @__PURE__ */ new Uint32Array(128);
191
+ let source = null;
192
+ let built = 0;
193
+ let vertexCount = 0;
194
+ let indexCount = 0;
195
+ function grow(buffer, needed) {
196
+ if (buffer.length >= needed) return buffer;
197
+ const next = new buffer.constructor(Math.max(needed, buffer.length * 2));
198
+ next.set(buffer);
199
+ return next;
200
+ }
201
+ function vertex(x, y, at) {
202
+ positions[vertexCount * 2] = x;
203
+ positions[vertexCount * 2 + 1] = y;
204
+ distances[vertexCount] = at;
205
+ return vertexCount++;
206
+ }
207
+ function triangle(a, b, c) {
208
+ indices[indexCount++] = a;
209
+ indices[indexCount++] = b;
210
+ indices[indexCount++] = c;
211
+ }
212
+ function emit(points, index) {
213
+ positions = grow(positions, (vertexCount + SAMPLE_VERTICES) * 2);
214
+ distances = grow(distances, vertexCount + SAMPLE_VERTICES);
215
+ indices = grow(indices, indexCount + 30);
216
+ const point = points[index];
217
+ const at = lengths[index] ?? 0;
175
218
  const radius = sampleRadius(size, point.p);
176
- const center = addVertex(point.x, point.y, at);
177
- const circle = [];
178
- for (let side = 0; side < RIBBON_CIRCLE_SIDES; side += 1) {
179
- const angle = side / RIBBON_CIRCLE_SIDES * Math.PI * 2;
180
- circle.push(addVertex(point.x + Math.cos(angle) * radius, point.y + Math.sin(angle) * radius, at));
219
+ if (isStrokeCorner(points, index)) {
220
+ const center = vertex(point.x, point.y, at);
221
+ for (let side = 0; side < RIBBON_CIRCLE_SIDES; side += 1) {
222
+ const angle = side / RIBBON_CIRCLE_SIDES * Math.PI * 2;
223
+ vertex(point.x + Math.cos(angle) * radius, point.y + Math.sin(angle) * radius, at);
224
+ }
225
+ for (let side = 0; side < RIBBON_CIRCLE_SIDES; side += 1) triangle(center, center + 1 + side, center + 1 + (side + 1) % RIBBON_CIRCLE_SIDES);
181
226
  }
182
- for (let side = 0; side < RIBBON_CIRCLE_SIDES; side += 1) addTriangle(center, circle[side], circle[(side + 1) % RIBBON_CIRCLE_SIDES]);
183
- };
184
- for (let i = 0; i < points.length; i += 1) {
185
- const point = points[i];
186
- if (!point) continue;
187
- if (isStrokeCorner(points, i)) addRoundPoint(point, lengths[i] ?? 0);
188
- if (i === points.length - 1) continue;
189
- const next = points[i + 1];
190
- if (!next) continue;
191
- const dx = next.x - point.x;
192
- const dy = next.y - point.y;
193
- const length = Math.hypot(dx, dy);
194
- if (length < 1e-6) continue;
195
- const normalX = -dy / length;
196
- const normalY = dx / length;
197
- const radius = sampleRadius(size, point.p);
198
- const leftA = addVertex(point.x + normalX * radius, point.y + normalY * radius, lengths[i] ?? 0);
199
- const rightA = addVertex(point.x - normalX * radius, point.y - normalY * radius, lengths[i] ?? 0);
227
+ const next = points[index + 1];
228
+ const normal = segmentNormal(points, index);
229
+ if (!next || !normal) return;
230
+ const start = edgeOffset(points, index, normal);
231
+ const end = edgeOffset(points, index + 1, normal);
232
+ const nextAt = lengths[index + 1] ?? at;
200
233
  const nextRadius = sampleRadius(size, next.p);
201
- const leftB = addVertex(next.x + normalX * nextRadius, next.y + normalY * nextRadius, lengths[i + 1] ?? 0);
202
- const rightB = addVertex(next.x - normalX * nextRadius, next.y - normalY * nextRadius, lengths[i + 1] ?? 0);
203
- addTriangle(leftA, rightA, leftB);
204
- addTriangle(rightA, rightB, leftB);
234
+ const leftA = vertex(point.x + start.x * radius, point.y + start.y * radius, at);
235
+ const rightA = vertex(point.x - start.x * radius, point.y - start.y * radius, at);
236
+ const leftB = vertex(next.x + end.x * nextRadius, next.y + end.y * nextRadius, nextAt);
237
+ const rightB = vertex(next.x - end.x * nextRadius, next.y - end.y * nextRadius, nextAt);
238
+ triangle(leftA, rightA, leftB);
239
+ triangle(rightA, rightB, leftB);
205
240
  }
241
+ return { build(points) {
242
+ const count = points.length;
243
+ const from = points === source && count >= built ? Math.max(0, built - 2) : 0;
244
+ source = points;
245
+ built = count;
246
+ lengths = grow(lengths, count);
247
+ marks = grow(marks, count * 2);
248
+ vertexCount = from > 0 ? marks[from * 2] : 0;
249
+ indexCount = from > 0 ? marks[from * 2 + 1] : 0;
250
+ for (let index = from; index < count; index += 1) {
251
+ const before = points[index - 1];
252
+ const point = points[index];
253
+ lengths[index] = before ? lengths[index - 1] + Math.hypot(point.x - before.x, point.y - before.y) : 0;
254
+ }
255
+ for (let index = from; index < count; index += 1) {
256
+ marks[index * 2] = vertexCount;
257
+ marks[index * 2 + 1] = indexCount;
258
+ emit(points, index);
259
+ }
260
+ return {
261
+ positions: positions.subarray(0, vertexCount * 2),
262
+ indices: indices.subarray(0, indexCount),
263
+ distances: distances.subarray(0, vertexCount),
264
+ length: count ? lengths[count - 1] : 0
265
+ };
266
+ } };
267
+ }
268
+ function buildStrokeRibbonGeometry(points, size) {
269
+ const mesh = createStrokeRibbonBuilder(size).build(points);
270
+ const total = Math.max(mesh.length, 1e-6);
271
+ const progress = mesh.distances.map((at) => at / total);
272
+ const uvs = new Float32Array(progress.length * 2);
273
+ for (let index = 0; index < progress.length; index += 1) uvs[index * 2] = progress[index];
206
274
  return {
207
- positions: new Float32Array(positions),
208
- indices: new Uint32Array(indices),
209
- uvs: new Float32Array(uvs),
210
- progress: new Float32Array(progress)
275
+ positions: mesh.positions.slice(),
276
+ indices: mesh.indices.slice(),
277
+ uvs,
278
+ progress
211
279
  };
212
280
  }
213
- /**
214
- * Distance from a world point to the stroke's polyline, in the shape's local
215
- * space. Used for hit testing: a hit registers within half the stroke width plus
216
- * a small tolerance. `local` is the point expressed in the draw item's frame.
217
- */
218
281
  function distanceToStroke(points, local) {
219
282
  if (points.length === 0) return Number.POSITIVE_INFINITY;
220
283
  const first = points[0];
@@ -235,4 +298,4 @@ function distanceToStroke(points, local) {
235
298
  return min;
236
299
  }
237
300
  //#endregion
238
- export { buildStrokeOutline, buildStrokeRibbonGeometry, computeDrawBounds, distanceToStroke, isStrokeCorner, sampleRadius, simplifyDrawIndices };
301
+ export { buildStrokeOutline, buildStrokeRibbonGeometry, computeDrawBounds, createStrokeRibbonBuilder, distanceToStroke, isStrokeCorner, sampleRadius, simplifyDrawIndices, trimDrawPoints };
@@ -1,14 +1,11 @@
1
- import { BoardItem } from "../../protocol/dist/board-document.js";
1
+ import { BoardSceneItem } from "./scene.js";
2
2
  //#region src/board/core/export-assets.d.ts
3
- /** Bound source textures independently from the final output bitmap budget. */
4
3
  declare const BOARD_EXPORT_MAX_TEXTURES = 64;
5
4
  type BoardExportAssetSelection = {
6
- /** One representative item for each selected preview key. */
7
- items: BoardItem[];
5
+ items: BoardSceneItem[];
8
6
  keys: string[];
9
7
  omittedKeys: string[];
10
8
  };
11
- /** Select a stable, document-ordered set of unique preview assets to load. */
12
- declare function selectBoardExportAssets(items: BoardItem[], assetKey: (item: BoardItem) => string | null, maxTextures?: number): BoardExportAssetSelection;
9
+ declare function selectBoardExportAssets(items: BoardSceneItem[], assetKey: (item: BoardSceneItem) => string | null, maxTextures?: number): BoardExportAssetSelection;
13
10
  //#endregion
14
11
  export { BOARD_EXPORT_MAX_TEXTURES, BoardExportAssetSelection, selectBoardExportAssets };
@@ -1,7 +1,5 @@
1
1
  //#region src/board/core/export-assets.ts
2
- /** Bound source textures independently from the final output bitmap budget. */
3
2
  const BOARD_EXPORT_MAX_TEXTURES = 64;
4
- /** Select a stable, document-ordered set of unique preview assets to load. */
5
3
  function selectBoardExportAssets(items, assetKey, maxTextures = 64) {
6
4
  const limit = Number.isSafeInteger(maxTextures) ? Math.max(0, maxTextures) : 64;
7
5
  const selected = /* @__PURE__ */ new Map();
@@ -1,95 +1,44 @@
1
- import { BoardConnection } from "../../protocol/dist/board-connection.js";
2
- import { BoardDocument, BoardItem } from "../../protocol/dist/board-document.js";
1
+ import { BoardDocument } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
3
  import { Rect } from "../geometry.js";
4
- import { FrameLookup } from "./connections.js";
4
+ import { BoardScene, BoardSceneItem } from "./scene.js";
5
5
  //#region src/board/core/export-plan.d.ts
6
- /** What part of the board to capture. */
7
- type BoardExportRegion =
8
- /** Everything in the document. */
9
- {
6
+ type BoardExportRegion = {
10
7
  kind: "all";
11
- } |
12
- /** Exactly these items, whatever else overlaps them. */
13
- {
8
+ } | {
14
9
  kind: "items";
15
10
  ids: string[];
16
- } |
17
- /** A frame item's rect, treated as a page: its chrome is excluded. */
18
- {
11
+ } | {
19
12
  kind: "frame";
20
13
  id: string;
21
- } |
22
- /** An explicit world-space rect. */
23
- {
14
+ } | {
24
15
  kind: "rect";
25
16
  rect: Rect;
26
17
  };
27
18
  type BoardExportPlanInput = {
28
- document: BoardDocument;
19
+ scene: BoardScene;
29
20
  region: BoardExportRegion;
30
- /** Output pixels per world unit. Defaults to 2 (retina-grade). */
31
21
  scale?: number;
32
- /** World-space breathing room around the content. Frames use 0. */
33
22
  padding?: number;
34
23
  maxEdge?: number;
35
24
  maxPixels?: number;
36
25
  };
37
26
  type BoardExportPlan = {
38
- /** World-space rect captured, padding included. */
39
27
  world: Rect;
40
- /** Scale actually used, after clamping. */
41
28
  scale: number;
42
- /** Scale asked for, before clamping. */
43
29
  requestedScale: number;
44
- /** Output size in pixels. */
45
30
  width: number;
46
31
  height: number;
47
- /** Items to draw, in document order. */
48
- items: BoardItem[];
49
- /**
50
- * Connections to draw.
51
- *
52
- * A relation is only included when both of its nodes are in `items`: half a
53
- * connection would render as a line into empty space, which reads as a defect
54
- * rather than as a clipped edge.
55
- */
56
- connections: BoardConnection[];
57
- /** True when `scale` had to be reduced to fit the size budget. */
32
+ items: BoardSceneItem[];
58
33
  clamped: boolean;
59
34
  };
60
- /**
61
- * Normalise a document before it reaches the renderers.
62
- *
63
- * Renderers read shape fields directly and assume schema defaults have been
64
- * applied — a hand-built or partially-migrated document would otherwise reach
65
- * them with missing fields. Parsing here means every entry point (CLI, web,
66
- * tests) gets the same guarantee, and an invalid document fails with a schema
67
- * error instead of a render-time crash.
68
- */
69
- declare function normalizeBoardDocument(document: BoardDocument): BoardDocument;
70
- /**
71
- * Hard ceilings.
72
- *
73
- * `MAX_EDGE` stays under the 8192 texture limit that essentially every WebGL2
74
- * and Canvas2D backend guarantees. `MAX_PIXELS` bounds peak memory instead of
75
- * edge length — a 8192×8192 RGBA buffer alone is 256MB, which is enough to kill
76
- * a browser tab, so the total is capped well below the square of the edge.
77
- */
35
+ declare function normalizeBoardDocument(document: unknown): BoardDocument;
78
36
  declare const BOARD_EXPORT_MAX_EDGE = 8192;
79
37
  declare const BOARD_EXPORT_MAX_PIXELS = 32000000;
80
38
  declare const BOARD_EXPORT_DEFAULT_SCALE = 2;
81
39
  declare const BOARD_EXPORT_DEFAULT_PADDING = 32;
82
- /** Above this, an export is still produced but the caller is warned. */
83
40
  declare const BOARD_EXPORT_ITEM_WARN_THRESHOLD = 2000;
84
- declare function boardFrameLookup(document: BoardDocument): FrameLookup;
85
- /** Bounds of a single item. Arrows resolve through their own curve geometry. */
86
- declare function exportItemBounds(item: BoardItem, _getFrame?: FrameLookup): Rect;
87
- /** Bounds of a connection, resolved against the document's node frames. */
88
- declare function exportConnectionBounds(connection: BoardConnection, getFrame: FrameLookup): Rect | null;
89
- /**
90
- * Resolve a region into a concrete capture plan, or null when there is nothing
91
- * to draw. Callers treat null as "empty selection", not as an error.
92
- */
41
+ declare function exportItemBounds(item: BoardSceneItem, scene: BoardScene): Rect | null;
93
42
  declare function planBoardExport(input: BoardExportPlanInput): BoardExportPlan | null;
94
43
  //#endregion
95
- export { BOARD_EXPORT_DEFAULT_PADDING, BOARD_EXPORT_DEFAULT_SCALE, BOARD_EXPORT_ITEM_WARN_THRESHOLD, BOARD_EXPORT_MAX_EDGE, BOARD_EXPORT_MAX_PIXELS, BoardExportPlan, BoardExportPlanInput, BoardExportRegion, boardFrameLookup, exportConnectionBounds, exportItemBounds, normalizeBoardDocument, planBoardExport };
44
+ export { BOARD_EXPORT_DEFAULT_PADDING, BOARD_EXPORT_DEFAULT_SCALE, BOARD_EXPORT_ITEM_WARN_THRESHOLD, BOARD_EXPORT_MAX_EDGE, BOARD_EXPORT_MAX_PIXELS, BoardExportPlan, BoardExportPlanInput, BoardExportRegion, exportItemBounds, normalizeBoardDocument, planBoardExport };
@@ -1,141 +1,103 @@
1
- import { parseBoardDocument } from "../../protocol/dist/board-document.js";
1
+ import { arrowBindings } from "../../protocol/dist/board-layout.js";
2
+ import { parseBoardDocument } from "../../protocol/dist/board-patch.js";
2
3
  import { itemBounds, rectsIntersect, unionRects } from "../geometry.js";
3
- import { arrowBounds } from "./arrow-geometry.js";
4
- import { connectionBounds, resolveConnection } from "./connections.js";
4
+ import { arrowPathBounds, resolveSceneArrow } from "./arrow-geometry.js";
5
5
  //#region src/board/core/export-plan.ts
6
- /**
7
- * Export planning — pure geometry, no renderer.
8
- *
9
- * Every platform (browser, CLI) resolves *what* to capture and *how large* the
10
- * result may be through this module, so a `--scale 4` on a huge board behaves
11
- * identically everywhere and no caller can accidentally ask the GPU for a
12
- * texture it cannot allocate.
13
- */
14
- /**
15
- * Normalise a document before it reaches the renderers.
16
- *
17
- * Renderers read shape fields directly and assume schema defaults have been
18
- * applied — a hand-built or partially-migrated document would otherwise reach
19
- * them with missing fields. Parsing here means every entry point (CLI, web,
20
- * tests) gets the same guarantee, and an invalid document fails with a schema
21
- * error instead of a render-time crash.
22
- */
23
6
  function normalizeBoardDocument(document) {
24
- return parseBoardDocument(document);
7
+ const parsed = parseBoardDocument(document);
8
+ if (!parsed.ok) throw new Error(`Invalid Board document: ${parsed.diagnostics.map((entry) => `${entry.path}: ${entry.message}`).join("; ")}`);
9
+ return parsed.document;
25
10
  }
26
- /**
27
- * Hard ceilings.
28
- *
29
- * `MAX_EDGE` stays under the 8192 texture limit that essentially every WebGL2
30
- * and Canvas2D backend guarantees. `MAX_PIXELS` bounds peak memory instead of
31
- * edge length — a 8192×8192 RGBA buffer alone is 256MB, which is enough to kill
32
- * a browser tab, so the total is capped well below the square of the edge.
33
- */
34
11
  const BOARD_EXPORT_MAX_EDGE = 8192;
35
12
  const BOARD_EXPORT_MAX_PIXELS = 32e6;
36
13
  const BOARD_EXPORT_DEFAULT_SCALE = 2;
37
14
  const BOARD_EXPORT_DEFAULT_PADDING = 32;
38
- /** Above this, an export is still produced but the caller is warned. */
39
15
  const BOARD_EXPORT_ITEM_WARN_THRESHOLD = 2e3;
40
- function boardFrameLookup(document) {
41
- const frames = new Map(document.items.map((item) => [item.id, item.frame]));
42
- return (id) => frames.get(id);
43
- }
44
- /** Bounds of a single item. Arrows resolve through their own curve geometry. */
45
- function exportItemBounds(item, _getFrame) {
46
- if (item.type === "arrow") return arrowBounds(item);
47
- return itemBounds(item.frame);
48
- }
49
- /** Bounds of a connection, resolved against the document's node frames. */
50
- function exportConnectionBounds(connection, getFrame) {
51
- const resolved = resolveConnection(connection, getFrame);
52
- return resolved ? connectionBounds(resolved, connection.style.size) : null;
16
+ function exportItemBounds(item, scene) {
17
+ let bounds = item.type === "arrow" ? arrowPathBounds(resolveSceneArrow(item, scene), item.style.strokeWidth) : itemBounds(item.frame);
18
+ let cursor = item.parent;
19
+ for (let depth = 0; cursor && bounds && depth < 64; depth += 1) {
20
+ const parent = scene.get(cursor);
21
+ if (!parent) break;
22
+ if (parent.type === "frame" && parent.props.clip !== false) bounds = intersectRects(bounds, itemBounds(parent.frame));
23
+ cursor = parent.parent;
24
+ }
25
+ return bounds;
53
26
  }
54
- /** Connections whose endpoints are both inside the given item set. */
55
- function connectionsWithin(document, items) {
56
- if (document.connections.length === 0) return [];
57
- const present = new Set(items.map((item) => item.id));
58
- return document.connections.filter((connection) => present.has(connection.source.itemId) && present.has(connection.target.itemId));
27
+ function intersectRects(a, b) {
28
+ const x = Math.max(a.x, b.x);
29
+ const y = Math.max(a.y, b.y);
30
+ const right = Math.min(a.x + a.width, b.x + b.width);
31
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
32
+ return right > x && bottom > y ? {
33
+ x,
34
+ y,
35
+ width: right - x,
36
+ height: bottom - y
37
+ } : null;
59
38
  }
60
- function resolveRegion(document, region, getFrame) {
39
+ function resolveRegion(scene, region) {
40
+ const boundsOf = /* @__PURE__ */ new Map();
41
+ const bounds = (item) => {
42
+ if (!boundsOf.has(item.id)) boundsOf.set(item.id, exportItemBounds(item, scene));
43
+ return boundsOf.get(item.id) ?? null;
44
+ };
45
+ const visible = (item, rect) => {
46
+ const box = bounds(item);
47
+ return box !== null && rectsIntersect(box, rect);
48
+ };
49
+ const union = (items) => unionRects(items.flatMap((item) => bounds(item) ?? []));
61
50
  switch (region.kind) {
62
- case "all": {
63
- const connections = document.connections;
64
- return {
65
- items: document.items,
66
- connections,
67
- rect: unionRects([...document.items.map((item) => exportItemBounds(item)), ...connectionBoundsList(connections, getFrame)]),
68
- padding: null
69
- };
70
- }
51
+ case "all": return {
52
+ items: [...scene.items],
53
+ rect: union(scene.items),
54
+ padding: null
55
+ };
71
56
  case "items": {
72
- const wanted = new Set(region.ids);
73
- const items = document.items.filter((item) => wanted.has(item.id));
74
- const connections = connectionsWithin(document, items);
57
+ const wanted = new Set(region.ids.flatMap((id) => scene.get(id) ? [id, ...scene.descendants(id)] : []));
58
+ for (const id of [...wanted]) for (const arrowId of scene.binders(id)) {
59
+ const arrow = scene.get(arrowId);
60
+ if (arrow && arrowBindings(arrow).every((bound) => wanted.has(bound))) wanted.add(arrowId);
61
+ }
62
+ const items = scene.items.filter((item) => wanted.has(item.id));
75
63
  return {
76
64
  items,
77
- connections,
78
- rect: unionRects([...items.map((item) => exportItemBounds(item)), ...connectionBoundsList(connections, getFrame)]),
65
+ rect: union(items),
79
66
  padding: null
80
67
  };
81
68
  }
82
69
  case "frame": {
83
- const frame = document.items.find((item) => item.id === region.id);
70
+ const frame = scene.get(region.id);
84
71
  if (!frame) return {
85
72
  items: [],
86
- connections: [],
87
73
  rect: null,
88
74
  padding: null
89
75
  };
90
76
  const rect = itemBounds(frame.frame);
91
- const items = document.items.filter((item) => item.id !== region.id && rectsIntersect(exportItemBounds(item), rect));
92
77
  return {
93
- items,
94
- connections: connectionsWithin(document, items),
78
+ items: scene.items.filter((item) => item.id !== region.id && visible(item, rect)),
95
79
  rect,
96
80
  padding: 0
97
81
  };
98
82
  }
99
83
  case "rect": {
100
84
  const rect = region.rect;
101
- const items = document.items.filter((item) => rectsIntersect(exportItemBounds(item), rect));
102
85
  return {
103
- items,
104
- connections: connectionsWithin(document, items),
86
+ items: scene.items.filter((item) => visible(item, rect)),
105
87
  rect,
106
88
  padding: 0
107
89
  };
108
90
  }
109
91
  }
110
92
  }
111
- function connectionBoundsList(connections, getFrame) {
112
- const rects = [];
113
- for (const connection of connections) {
114
- const bounds = exportConnectionBounds(connection, getFrame);
115
- if (bounds) rects.push(bounds);
116
- }
117
- return rects;
118
- }
119
- /**
120
- * Largest scale that keeps the output inside both the edge and pixel budgets.
121
- *
122
- * There is deliberately no lower bound: a floor here would silently let a very
123
- * large world exceed the budgets it exists to enforce (a 1e6×1e6 region at a
124
- * 0.01 floor is 1e8 pixels). Callers see the reduction via `plan.clamped`.
125
- */
126
93
  function clampScale(requested, world, maxEdge, maxPixels) {
127
94
  const byEdge = Math.min(maxEdge / world.width, maxEdge / world.height);
128
95
  const byArea = Math.sqrt(maxPixels / (world.width * world.height));
129
96
  return Math.min(requested, byEdge, byArea);
130
97
  }
131
- /**
132
- * Resolve a region into a concrete capture plan, or null when there is nothing
133
- * to draw. Callers treat null as "empty selection", not as an error.
134
- */
135
98
  function planBoardExport(input) {
136
99
  const { region, scale: requestedScale = 2, maxEdge = BOARD_EXPORT_MAX_EDGE, maxPixels = BOARD_EXPORT_MAX_PIXELS } = input;
137
- const document = normalizeBoardDocument(input.document);
138
- const resolved = resolveRegion(document, region, boardFrameLookup(document));
100
+ const resolved = resolveRegion(input.scene, region);
139
101
  if (!resolved.rect) return null;
140
102
  const padding = input.padding ?? resolved.padding ?? 32;
141
103
  const world = {
@@ -153,9 +115,8 @@ function planBoardExport(input) {
153
115
  width: Math.max(1, Math.floor(world.width * scale)),
154
116
  height: Math.max(1, Math.floor(world.height * scale)),
155
117
  items: resolved.items,
156
- connections: resolved.connections,
157
118
  clamped: scale < safeRequest - 1e-6
158
119
  };
159
120
  }
160
121
  //#endregion
161
- export { BOARD_EXPORT_DEFAULT_PADDING, BOARD_EXPORT_DEFAULT_SCALE, BOARD_EXPORT_ITEM_WARN_THRESHOLD, BOARD_EXPORT_MAX_EDGE, BOARD_EXPORT_MAX_PIXELS, boardFrameLookup, exportConnectionBounds, exportItemBounds, normalizeBoardDocument, planBoardExport };
122
+ export { BOARD_EXPORT_DEFAULT_PADDING, BOARD_EXPORT_DEFAULT_SCALE, BOARD_EXPORT_ITEM_WARN_THRESHOLD, BOARD_EXPORT_MAX_EDGE, BOARD_EXPORT_MAX_PIXELS, exportItemBounds, normalizeBoardDocument, planBoardExport };
@@ -1,22 +1,10 @@
1
- import { BoardColorId } from "../../protocol/dist/board-node.js";
1
+ import { BoardColor, BoardColorToken } from "../../protocol/dist/board-model.js";
2
2
  import "../../protocol/dist/index.js";
3
3
  //#region src/board/core/palette.d.ts
4
- /**
5
- * Board color palette — a small, named set of shape colors shared by text,
6
- * geo, draw and arrow shapes. Colors are stored by id in shape props (never
7
- * raw hex), so themes and space `.cohub/theme.css` can remap them via CSS
8
- * tokens while persisted data stays compact and forward-compatible.
9
- *
10
- * Concrete values are resolved from CSS variables at render time:
11
- * --board-color-{id}-stroke | -fill | -label
12
- * with hard-coded light/dark tables as offline / export fallbacks.
13
- */
4
+ type BoardColorId = BoardColorToken;
14
5
  type BoardColorValue = {
15
- /** Stroke / accent color (sRGB hex). */
16
6
  stroke: number;
17
- /** Translucent fill used behind shapes (sRGB hex). */
18
7
  fill: number;
19
- /** Readable label color on top of the fill. */
20
8
  label: number;
21
9
  };
22
10
  type BoardColorEntry = {
@@ -29,15 +17,10 @@ declare const BOARD_COLORS: readonly BoardColorEntry[];
29
17
  declare const DEFAULT_BOARD_COLOR: BoardColorId;
30
18
  declare function isBoardColorId(value: unknown): value is BoardColorId;
31
19
  declare function boardColorCssVar(id: BoardColorId, part: keyof BoardColorValue): string;
32
- /** Resolve a color id to concrete values for a color mode. Unknown → brand. */
33
20
  declare function resolveBoardColor(id: unknown, mode: "dark" | "light"): BoardColorValue;
34
21
  type BoardShapeColors = Record<BoardColorId, BoardColorValue>;
35
- /** Build a full shape-color table from hard-coded fallbacks (export / SSR). */
36
22
  declare function buildFallbackShapeColors(mode: "dark" | "light"): BoardShapeColors;
37
- /**
38
- * Pick a concrete color from a live shape-color table, falling back to the
39
- * default brand entry when the id is unknown.
40
- */
41
23
  declare function pickBoardColor(colors: BoardShapeColors | null | undefined, id: unknown, mode?: "dark" | "light"): BoardColorValue;
24
+ declare function resolveItemColor(color: BoardColor | undefined, fallback: BoardColorId, colors: BoardShapeColors | null | undefined, mode: "dark" | "light", parseCss: (value: string) => number | null, part?: keyof BoardColorValue): number;
42
25
  //#endregion
43
- export { BOARD_COLORS, BoardColorEntry, type BoardColorId, BoardColorValue, BoardShapeColors, DEFAULT_BOARD_COLOR, boardColorCssVar, buildFallbackShapeColors, isBoardColorId, pickBoardColor, resolveBoardColor };
26
+ export { BOARD_COLORS, BoardColorEntry, BoardColorId, BoardColorValue, BoardShapeColors, DEFAULT_BOARD_COLOR, boardColorCssVar, buildFallbackShapeColors, isBoardColorId, pickBoardColor, resolveBoardColor, resolveItemColor };