@neta-art/cohub 5.10.0 → 7.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 (62) hide show
  1. package/README.md +47 -50
  2. package/dist/board/animation.d.ts +157 -67
  3. package/dist/board/animation.js +161 -306
  4. package/dist/board/core/connections.js +4 -4
  5. package/dist/board/core/export-plan.js +1 -1
  6. package/dist/board/core/shape-types.js +0 -1
  7. package/dist/board/geometry.js +4 -4
  8. package/dist/board/index.d.ts +8 -7
  9. package/dist/board/index.js +8 -9
  10. package/dist/board/mutation.d.ts +2 -17
  11. package/dist/board/mutation.js +2 -93
  12. package/dist/board/render/renderers/text-card-renderer.js +1 -1
  13. package/dist/board/semantic-document.d.ts +30 -0
  14. package/dist/board/semantic-document.js +271 -0
  15. package/dist/board/semantic-mutation.d.ts +10 -0
  16. package/dist/board/semantic-mutation.js +203 -0
  17. package/dist/chunks/http.d.ts +27 -95
  18. package/dist/chunks/http.js +628 -1610
  19. package/dist/chunks/transport.js +1 -1
  20. package/dist/chunks/websocket.d.ts +3462 -533
  21. package/dist/chunks/websocket.js +1 -1
  22. package/dist/http.d.ts +3 -3
  23. package/dist/index.d.ts +158 -299
  24. package/dist/index.js +1207 -499
  25. package/dist/protocol/dist/board-authoring.d.ts +1305 -0
  26. package/dist/protocol/dist/board-authoring.js +291 -0
  27. package/dist/protocol/dist/board-capability-registry.d.ts +2 -0
  28. package/dist/protocol/dist/board-capability-registry.js +74 -0
  29. package/dist/protocol/dist/board-codec.d.ts +2 -0
  30. package/dist/protocol/dist/board-codec.js +19 -0
  31. package/dist/protocol/dist/board-composition.d.ts +383 -0
  32. package/dist/protocol/dist/board-composition.js +310 -0
  33. package/dist/protocol/dist/board-connection.d.ts +16 -16
  34. package/dist/protocol/dist/board-connection.js +16 -16
  35. package/dist/protocol/dist/board-constants.js +0 -25
  36. package/dist/protocol/dist/board-document.d.ts +34 -3
  37. package/dist/protocol/dist/board-document.js +3 -1
  38. package/dist/protocol/dist/board-effect.d.ts +94 -0
  39. package/dist/protocol/dist/board-effect.js +53 -0
  40. package/dist/protocol/dist/board-json.js +16 -0
  41. package/dist/protocol/dist/board-node.d.ts +1 -12
  42. package/dist/protocol/dist/board-node.js +1 -81
  43. package/dist/protocol/dist/board.d.ts +42 -245
  44. package/dist/protocol/dist/board.js +56 -117
  45. package/dist/protocol/dist/index.d.ts +9 -4
  46. package/dist/types.d.ts +4 -1
  47. package/docs/work-runtime-guide.md +19 -3
  48. package/package.json +2 -2
  49. package/dist/board/codec.d.ts +0 -27
  50. package/dist/board/codec.js +0 -277
  51. package/dist/board/nodes.d.ts +0 -113
  52. package/dist/board/nodes.js +0 -154
  53. package/dist/protocol/dist/board-content.js +0 -26
  54. package/dist/protocol/dist/identifiers.js +0 -10
  55. package/dist/protocol/dist/index.js +0 -14
  56. package/dist/protocol/dist/provenance.js +0 -15
  57. package/dist/protocol/dist/public-identifiers.js +0 -38
  58. package/dist/protocol/dist/realtime/board-awareness.js +0 -119
  59. package/dist/protocol/dist/ui-command.js +0 -2
  60. package/dist/protocol/dist/work-promotion-stats.js +0 -11
  61. package/dist/protocol/dist/work-surface.js +0 -2
  62. package/dist/protocol/dist/work-view-stats.js +0 -3
@@ -1,282 +1,147 @@
1
1
  import { BOARD_BUILTIN_CAPABILITIES, DEFAULT_BOARD_RENDER_LIMITS } from "../protocol/dist/board-constants.js";
2
- import { BoardCameraFocusParamsSchema } from "../protocol/dist/board.js";
3
- import "../protocol/dist/index.js";
2
+ import { BOARD_ANIMATION_CHANNELS, BoardCompositionSchema, BoardProceduralClipSchema, BoardTrackSchema } from "../protocol/dist/board-composition.js";
3
+ import { estimateBuiltinBoardClipCost, validateBuiltinBoardClip } from "../protocol/dist/board-capability-registry.js";
4
4
  //#region src/board/animation.ts
5
- const ZERO_COST = {
6
- particles: 0,
7
- vertices: 0,
8
- dynamicVertices: 0,
9
- drawCalls: 0,
10
- filterPasses: 0,
11
- renderTexturePixels: 0,
12
- textureBytes: 0,
13
- bufferBytes: 0,
14
- simulationSteps: 0
15
- };
16
- const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
17
- const BUILTIN_CAPABILITIES = BOARD_BUILTIN_CAPABILITIES;
18
- function numericParam(params, key, fallback) {
19
- const value = params[key];
20
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
5
+ function track(input) {
6
+ return BoardTrackSchema.parse(input);
21
7
  }
22
- function builtinDefinition(capability) {
23
- return {
24
- ...capability,
25
- validate(params) {
26
- const diagnostics = [];
27
- if (capability.id === "effects.particles") {
28
- const count = params.count;
29
- const bounds = params.bounds;
30
- if (!Number.isSafeInteger(count) || count < 1 || count > DEFAULT_BOARD_LIMITS.particles) diagnostics.push({
31
- severity: "error",
32
- code: "INVALID_PARTICLE_COUNT",
33
- message: `count must be an integer between 1 and ${DEFAULT_BOARD_LIMITS.particles}`,
34
- path: "params.count"
35
- });
36
- if (!bounds || typeof bounds !== "object" || Array.isArray(bounds)) diagnostics.push({
37
- severity: "error",
38
- code: "PARTICLE_BOUNDS_REQUIRED",
39
- message: "particles require finite world bounds",
40
- path: "params.bounds",
41
- coordinateSpace: "world"
42
- });
43
- else {
44
- const value = bounds;
45
- if (![
46
- value.x,
47
- value.y,
48
- value.width,
49
- value.height
50
- ].every((item) => typeof item === "number" && Number.isFinite(item)) || value.width <= 0 || value.height <= 0) diagnostics.push({
51
- severity: "error",
52
- code: "INVALID_PARTICLE_BOUNDS",
53
- message: "particle bounds must have a positive finite size",
54
- path: "params.bounds",
55
- coordinateSpace: "world"
56
- });
57
- }
58
- }
59
- if (capability.id === "motion.path") {
60
- const points = params.points;
61
- if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) diagnostics.push({
62
- severity: "error",
63
- code: "INVALID_MOTION_PATH",
64
- message: "motion path must contain 2 to 10000 world-offset points",
65
- path: "params.points",
66
- coordinateSpace: "world-offset"
67
- });
68
- }
69
- if (capability.id === "camera.focus") {
70
- const parsed = BoardCameraFocusParamsSchema.safeParse(params);
71
- if (!parsed.success) diagnostics.push({
72
- severity: "error",
73
- code: "INVALID_CAMERA_FOCUS",
74
- message: parsed.error.issues[0]?.message ?? "invalid camera focus",
75
- path: "params",
76
- coordinateSpace: "world"
77
- });
78
- }
79
- return diagnostics;
8
+ function proceduralClip(input) {
9
+ return BoardProceduralClipSchema.parse({
10
+ ...input,
11
+ kindVersion: input.kindVersion ?? 1
12
+ });
13
+ }
14
+ function composition(input) {
15
+ return BoardCompositionSchema.parse(input);
16
+ }
17
+ function compileComposition(input) {
18
+ return composition({
19
+ id: input.id,
20
+ name: input.name,
21
+ timeline: {
22
+ duration: input.duration,
23
+ tracks: (input.tracks ?? []).map(track),
24
+ clips: (input.clips ?? []).map(proceduralClip),
25
+ markers: input.markers ?? []
80
26
  },
81
- getBounds(params) {
82
- const bounds = params.bounds;
83
- if (!bounds || typeof bounds !== "object" || Array.isArray(bounds)) return null;
84
- const value = bounds;
85
- return [
86
- value.x,
87
- value.y,
88
- value.width,
89
- value.height
90
- ].every((item) => typeof item === "number" && Number.isFinite(item)) ? value : null;
27
+ playback: input.playback ?? {
28
+ loop: false,
29
+ endBehavior: "hold",
30
+ reducedMotion: { mode: "base" }
91
31
  },
92
- estimateCost(params) {
93
- switch (capability.id) {
94
- case "effects.particles": {
95
- const particles = Math.max(0, Math.floor(numericParam(params, "count", 0)));
96
- return {
97
- particles,
98
- vertices: particles * 4,
99
- dynamicVertices: particles * 4,
100
- drawCalls: 1,
101
- bufferBytes: particles * 48,
102
- simulationSteps: particles
103
- };
104
- }
105
- case "effects.trail": return {
106
- vertices: 32,
107
- dynamicVertices: 32,
108
- drawCalls: 1,
109
- bufferBytes: 1024,
110
- simulationSteps: 16
111
- };
112
- case "effects.impact":
113
- case "effects.flash": return {
114
- vertices: 64,
115
- drawCalls: 1
116
- };
117
- case "effects.color": return {
118
- drawCalls: 1,
119
- filterPasses: 1
120
- };
121
- case "draw.reveal":
122
- case "draw.handwrite": return {
123
- drawCalls: 1,
124
- dynamicVertices: 1
125
- };
126
- default: return {};
127
- }
128
- }
129
- };
32
+ metadata: input.metadata ?? {},
33
+ revision: 0
34
+ });
130
35
  }
131
- function stableHash(value) {
132
- let hash = 2166136261;
133
- for (let index = 0; index < value.length; index += 1) {
134
- hash ^= value.charCodeAt(index);
135
- hash = Math.imul(hash, 16777619);
36
+ const clamp01 = (value) => Math.max(0, Math.min(1, value));
37
+ function sampleEasing(easing, value) {
38
+ const t = clamp01(value);
39
+ switch (easing) {
40
+ case "ease-in-quad": return t * t;
41
+ case "ease-out-quad": return 1 - (1 - t) ** 2;
42
+ case "ease-in-out-quad": return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
43
+ case "ease-in-cubic": return t ** 3;
44
+ case "ease-out-cubic": return 1 - (1 - t) ** 3;
45
+ case "ease-in-out-cubic": return t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
46
+ case "ease-out-quart": return 1 - (1 - t) ** 4;
47
+ case "ease-out-expo": return t === 1 ? 1 : 1 - 2 ** (-10 * t);
48
+ default: return t;
136
49
  }
137
- return (hash >>> 0).toString(36).padStart(7, "0");
138
- }
139
- function stableId(prefix, seed, path) {
140
- return `${prefix}_${stableHash(`${seed}:${path}`)}`;
141
- }
142
- function assertDuration(value, field) {
143
- if (!Number.isFinite(value) || value < 0) throw new TypeError(`${field} must be a finite non-negative number`);
144
50
  }
145
- const timeline = {
146
- clip(clip) {
147
- return {
148
- type: "clip",
149
- clip
150
- };
151
- },
152
- parallel(...children) {
153
- return {
154
- type: "parallel",
155
- children
156
- };
157
- },
158
- sequence(...children) {
159
- return {
160
- type: "sequence",
161
- children
162
- };
163
- },
164
- stagger(each, ...children) {
165
- assertDuration(each, "stagger each");
166
- return {
167
- type: "stagger",
168
- each,
169
- children
170
- };
171
- },
172
- delay(duration, child) {
173
- assertDuration(duration, "delay duration");
174
- return {
175
- type: "delay",
176
- duration,
177
- child
178
- };
179
- },
180
- repeat(count, child) {
181
- if (!Number.isSafeInteger(count) || count < 1 || count > 1e3) throw new TypeError("repeat count must be an integer between 1 and 1000");
182
- return {
183
- type: "repeat",
184
- count,
185
- child
186
- };
51
+ function interpolate(left, right, progress) {
52
+ if (typeof left === "number" && typeof right === "number") return left + (right - left) * progress;
53
+ if (left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right)) {
54
+ const a = left;
55
+ const b = right;
56
+ const result = {};
57
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) result[key] = interpolate(a[key], b[key], progress);
58
+ return result;
187
59
  }
188
- };
189
- function clip(input) {
190
- assertDuration(input.duration, "clip duration");
191
- if (input.duration === 0) throw new TypeError("clip duration must be greater than zero");
192
- return timeline.clip({
193
- id: input.id,
194
- kind: input.kind,
195
- kindVersion: input.kindVersion ?? 1,
196
- target: input.target,
197
- duration: input.duration,
198
- layer: input.layer ?? "content",
199
- fill: input.fill ?? "none",
200
- easing: input.easing ?? "linear",
201
- params: input.params ?? {},
202
- keyframes: input.keyframes ?? [],
203
- assetRefs: input.assetRefs ?? [],
204
- seed: input.seed,
205
- metadata: input.metadata ?? {}
206
- });
60
+ return progress < 1 ? left : right;
207
61
  }
208
- function flatten(input, offset, sequenceSeed, path) {
209
- if (input.type === "clip") {
210
- const clipSeed = input.clip.seed ?? `${sequenceSeed}:${path}`;
62
+ /** Deterministically sample one validated Track without touching scene state. */
63
+ function sampleTrack(trackValue, time) {
64
+ const frames = trackValue.keyframes;
65
+ const first = frames[0];
66
+ const last = frames.at(-1);
67
+ if (!first || !last) return null;
68
+ if (time < first.time) {
69
+ if (trackValue.fill !== "backwards" && trackValue.fill !== "both") return null;
211
70
  return {
212
- duration: input.clip.duration,
213
- clips: [{
214
- ...input.clip,
215
- id: input.clip.id ?? stableId("clip", sequenceSeed, path),
216
- start: offset,
217
- seed: clipSeed
218
- }]
71
+ target: trackValue.target,
72
+ channel: trackValue.channel,
73
+ value: first.value
219
74
  };
220
75
  }
221
- if (input.type === "delay") {
222
- const nested = flatten(input.child, offset + input.duration, sequenceSeed, `${path}.child`);
76
+ if (time > last.time) {
77
+ if (trackValue.fill !== "forwards" && trackValue.fill !== "both") return null;
223
78
  return {
224
- duration: input.duration + nested.duration,
225
- clips: nested.clips
79
+ target: trackValue.target,
80
+ channel: trackValue.channel,
81
+ value: last.value
226
82
  };
227
83
  }
228
- if (input.type === "repeat") {
229
- const clips = [];
230
- let cursor = offset;
231
- let total = 0;
232
- for (let index = 0; index < input.count; index += 1) {
233
- const nested = flatten(input.child, cursor, sequenceSeed, `${path}.${index}`);
234
- clips.push(...nested.clips);
235
- cursor += nested.duration;
236
- total += nested.duration;
237
- }
238
- return {
239
- duration: total,
240
- clips
241
- };
242
- }
243
- const clips = [];
244
- let duration = 0;
245
- for (const [index, child] of input.children.entries()) {
246
- const childOffset = input.type === "sequence" ? duration : input.type === "stagger" ? input.each * index : 0;
247
- const nested = flatten(child, offset + childOffset, sequenceSeed, `${path}.${index}`);
248
- clips.push(...nested.clips);
249
- duration = input.type === "sequence" ? duration + nested.duration : Math.max(duration, childOffset + nested.duration);
84
+ let low = 0;
85
+ let high = frames.length;
86
+ while (low < high) {
87
+ const middle = low + high >>> 1;
88
+ if ((frames[middle]?.time ?? Number.POSITIVE_INFINITY) <= time) low = middle + 1;
89
+ else high = middle;
250
90
  }
91
+ const left = frames[Math.max(0, low - 1)] ?? first;
92
+ const right = frames[Math.min(frames.length - 1, low)] ?? last;
93
+ if (left === right || trackValue.interpolation === "step") return {
94
+ target: trackValue.target,
95
+ channel: trackValue.channel,
96
+ value: left.value
97
+ };
98
+ const span = Math.max(Number.EPSILON, right.time - left.time);
99
+ const progress = sampleEasing(right.easing ?? "linear", (time - left.time) / span);
251
100
  return {
252
- duration,
253
- clips
101
+ target: trackValue.target,
102
+ channel: trackValue.channel,
103
+ value: interpolate(left.value, right.value, progress)
254
104
  };
255
105
  }
256
- function compileSequence(input) {
257
- const flattened = flatten(input.timeline, 0, input.seed, "root");
258
- flattened.clips.sort((left, right) => left.start - right.start || left.id.localeCompare(right.id));
259
- const refs = /* @__PURE__ */ new Map();
260
- for (const item of flattened.clips) for (const ref of item.assetRefs) refs.set(`${ref.type}:${ref.ref}:${ref.digest ?? ""}`, ref);
106
+ function sampleCompositionTracks(value, time) {
107
+ const position = Math.max(0, Math.min(value.timeline.duration, time));
108
+ return value.timeline.tracks.flatMap((item) => {
109
+ const sampled = sampleTrack(item, position);
110
+ return sampled ? [sampled] : [];
111
+ });
112
+ }
113
+ const ZERO_COST = {
114
+ particles: 0,
115
+ vertices: 0,
116
+ dynamicVertices: 0,
117
+ drawCalls: 0,
118
+ filterPasses: 0,
119
+ renderTexturePixels: 0,
120
+ textureBytes: 0,
121
+ bufferBytes: 0,
122
+ simulationSteps: 0
123
+ };
124
+ const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
125
+ function builtinDefinition(capability) {
261
126
  return {
262
- sequence: {
263
- id: input.id,
264
- name: input.name,
265
- duration: flattened.duration,
266
- seed: input.seed,
267
- restPose: input.restPose ?? {},
268
- metadata: input.metadata ?? {}
127
+ ...capability,
128
+ validate() {
129
+ return [];
269
130
  },
270
- clips: flattened.clips,
271
- assetRefs: [...refs.values()]
131
+ estimateCost(params) {
132
+ return estimateBuiltinBoardClipCost({
133
+ kind: capability.id,
134
+ params
135
+ });
136
+ }
272
137
  };
273
138
  }
274
- function addCost(target, source) {
275
- for (const key of Object.keys(target)) target[key] += source[key] ?? 0;
276
- }
277
139
  var BoardExtensionRegistry = class {
278
140
  #extensions = /* @__PURE__ */ new Map();
279
141
  #presets = /* @__PURE__ */ new Map();
142
+ constructor() {
143
+ for (const capability of BOARD_BUILTIN_CAPABILITIES) this.register(builtinDefinition(capability));
144
+ }
280
145
  register(definition) {
281
146
  const key = `${definition.kind}:${definition.id}@${definition.version}`;
282
147
  const target = definition.kind === "preset" ? this.#presets : this.#extensions;
@@ -285,9 +150,15 @@ var BoardExtensionRegistry = class {
285
150
  return this;
286
151
  }
287
152
  capabilities() {
288
- const extensions = [...this.#extensions.values()].map(({ validate: _validate, getBounds: _bounds, getAssetRefs: _refs, estimateCost: _cost, ...definition }) => definition);
289
- const presets = [...this.#presets.values()].map(({ compile: _compile, ...definition }) => definition);
290
- return [...extensions, ...presets];
153
+ return [...this.#extensions.values(), ...this.#presets.values()].map((definition) => ({
154
+ kind: definition.kind,
155
+ id: definition.id,
156
+ version: definition.version,
157
+ ...definition.digest ? { digest: definition.digest } : {},
158
+ ...definition.renderers ? { renderers: definition.renderers } : {},
159
+ ...definition.fallbackId ? { fallbackId: definition.fallbackId } : {},
160
+ ...definition.schema ? { schema: definition.schema } : {}
161
+ }));
291
162
  }
292
163
  compilePreset(id, version, params) {
293
164
  const preset = this.#presets.get(`preset:${id}@${version}`);
@@ -295,88 +166,72 @@ var BoardExtensionRegistry = class {
295
166
  return preset.compile(params);
296
167
  }
297
168
  validate(input) {
169
+ const parsed = BoardCompositionSchema.safeParse(input.composition);
170
+ if (!parsed.success) return {
171
+ valid: false,
172
+ diagnostics: parsed.error.issues.map((issue) => ({
173
+ severity: "error",
174
+ code: "INVALID_COMPOSITION",
175
+ message: issue.message,
176
+ path: issue.path.join(".")
177
+ })),
178
+ peakCost: { ...ZERO_COST }
179
+ };
298
180
  const diagnostics = [];
181
+ const composition = parsed.data;
182
+ const cost = { ...ZERO_COST };
299
183
  const events = [];
300
- const profile = input.profile ?? "high";
301
- const persistentCost = { ...ZERO_COST };
302
- for (const [index, effect] of (input.effects ?? []).entries()) {
303
- const definition = this.#extensions.get(`effect:${effect.kind}@${effect.kindVersion}`);
304
- if (!definition) {
305
- diagnostics.push({
306
- severity: "warning",
307
- code: "UNKNOWN_EFFECT",
308
- message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
309
- path: `effects.${index}`
310
- });
311
- continue;
312
- }
313
- diagnostics.push(...definition.validate?.(effect.params) ?? []);
314
- addCost(persistentCost, definition.estimateCost(effect.params, profile));
315
- }
316
- for (const [index, clip] of input.clips.entries()) {
184
+ for (const clip of composition.timeline.clips) {
317
185
  const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
318
186
  if (!definition) {
319
187
  diagnostics.push({
320
188
  severity: "warning",
321
189
  code: "UNKNOWN_CLIP",
322
- message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`,
323
- path: `clips.${index}`
190
+ message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`
324
191
  });
325
192
  continue;
326
193
  }
194
+ diagnostics.push(...validateBuiltinBoardClip(clip, `composition.timeline.clips.${clip.id}`));
327
195
  diagnostics.push(...definition.validate?.(clip.params) ?? []);
328
- const cost = { ...ZERO_COST };
329
- addCost(cost, definition.estimateCost(clip.params, profile));
196
+ const estimate = definition.estimateCost(clip.params, input.profile ?? "high");
197
+ const clipCost = { ...ZERO_COST };
198
+ for (const key of Object.keys(clipCost)) clipCost[key] = estimate[key] ?? 0;
330
199
  events.push({
331
200
  at: clip.start,
332
201
  direction: 1,
333
- cost
334
- }, {
202
+ cost: clipCost
203
+ });
204
+ events.push({
335
205
  at: clip.start + clip.duration,
336
206
  direction: -1,
337
- cost
338
- });
339
- }
340
- const cameraFocusClips = input.clips.map((clip, index) => ({
341
- clip,
342
- index
343
- })).filter(({ clip }) => clip.kind === "camera.focus").sort((left, right) => left.clip.start - right.clip.start);
344
- for (let index = 1; index < cameraFocusClips.length; index += 1) {
345
- const previous = cameraFocusClips[index - 1];
346
- const currentFocus = cameraFocusClips[index];
347
- if (previous && currentFocus && currentFocus.clip.start < previous.clip.start + previous.clip.duration) diagnostics.push({
348
- severity: "error",
349
- code: "OVERLAPPING_CAMERA_FOCUS",
350
- message: "camera.focus clips must not overlap",
351
- path: `clips.${currentFocus.index}.start`
207
+ cost: clipCost
352
208
  });
353
209
  }
354
210
  events.sort((left, right) => left.at - right.at || left.direction - right.direction);
355
- const current = { ...persistentCost };
356
- const peak = { ...persistentCost };
357
- for (const event of events) for (const key of Object.keys(current)) {
358
- current[key] += event.cost[key] * event.direction;
359
- peak[key] = Math.max(peak[key], current[key]);
211
+ const active = { ...ZERO_COST };
212
+ for (const event of events) for (const key of Object.keys(active)) {
213
+ active[key] += event.cost[key] * event.direction;
214
+ cost[key] = Math.max(cost[key], active[key]);
360
215
  }
216
+ for (const effect of input.effects ?? []) if (effect.kind === "effects.pulse" || effect.kind === "effects.float") cost.drawCalls += 1;
361
217
  const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
362
- for (const key of Object.keys(limits)) if (peak[key] > limits[key]) diagnostics.push({
218
+ for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
363
219
  severity: "warning",
364
220
  code: "RENDER_BUDGET_EXCEEDED",
365
- message: `${key} peaks at ${peak[key]}, above ${limits[key]}`,
221
+ message: `${key} peaks at ${cost[key]}, above ${limits[key]}`,
366
222
  path: key,
367
223
  adaptation: { quality: "lower" }
368
224
  });
369
225
  return {
370
226
  valid: !diagnostics.some((item) => item.severity === "error"),
371
227
  diagnostics,
372
- peakCost: peak
228
+ peakCost: cost
373
229
  };
374
230
  }
375
231
  };
376
- function createBoardExtensionRegistry(input = {}) {
377
- const registry = new BoardExtensionRegistry();
378
- if (input.builtins !== false) for (const capability of BUILTIN_CAPABILITIES) registry.register(builtinDefinition(capability));
379
- return registry;
232
+ function createBoardExtensionRegistry() {
233
+ return new BoardExtensionRegistry();
380
234
  }
235
+ const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
381
236
  //#endregion
382
- export { BoardExtensionRegistry, DEFAULT_BOARD_LIMITS, clip, compileSequence, createBoardExtensionRegistry, timeline };
237
+ export { BOARD_CHANNELS, BoardExtensionRegistry, DEFAULT_BOARD_LIMITS, compileComposition, composition, createBoardExtensionRegistry, proceduralClip, sampleCompositionTracks, sampleEasing, sampleTrack, track };
@@ -238,11 +238,11 @@ const CURVE_SEGMENTS = 20;
238
238
  * fetched.
239
239
  */
240
240
  function resolveConnection(connection, getFrame, options = {}) {
241
- const sourceFrame = getFrame(connection.source.nodeId);
242
- const targetFrame = getFrame(connection.target.nodeId);
241
+ const sourceFrame = getFrame(connection.source.itemId);
242
+ const targetFrame = getFrame(connection.target.itemId);
243
243
  if (!sourceFrame || !targetFrame) return null;
244
244
  const gap = options.gap ?? 4;
245
- if (connection.source.nodeId === connection.target.nodeId) {
245
+ if (connection.source.itemId === connection.target.itemId) {
246
246
  const path = selfLoopPath(sourceFrame, gap);
247
247
  const first = path[0];
248
248
  const last = path[path.length - 1];
@@ -398,7 +398,7 @@ function createConnectionIndex(connections) {
398
398
  const byId = /* @__PURE__ */ new Map();
399
399
  for (const connection of connections) {
400
400
  byId.set(connection.id, connection);
401
- for (const nodeId of /* @__PURE__ */ new Set([connection.source.nodeId, connection.target.nodeId])) {
401
+ for (const nodeId of /* @__PURE__ */ new Set([connection.source.itemId, connection.target.itemId])) {
402
402
  const list = byNode.get(nodeId);
403
403
  if (list) list.push(connection.id);
404
404
  else byNode.set(nodeId, [connection.id]);
@@ -55,7 +55,7 @@ function exportConnectionBounds(connection, getFrame) {
55
55
  function connectionsWithin(document, items) {
56
56
  if (document.connections.length === 0) return [];
57
57
  const present = new Set(items.map((item) => item.id));
58
- return document.connections.filter((connection) => present.has(connection.source.nodeId) && present.has(connection.target.nodeId));
58
+ return document.connections.filter((connection) => present.has(connection.source.itemId) && present.has(connection.target.itemId));
59
59
  }
60
60
  function resolveRegion(document, region, getFrame) {
61
61
  switch (region.kind) {
@@ -1,5 +1,4 @@
1
1
  import { BOARD_GEO_KINDS } from "../../protocol/dist/board-node.js";
2
- import "../../protocol/dist/index.js";
3
2
  //#region src/board/core/shape-types.ts
4
3
  function resizeModeForCapabilities(capabilities) {
5
4
  if (!capabilities.canResize) return "none";
@@ -538,19 +538,19 @@ function cameraForRect(content, surface, options = {
538
538
  }
539
539
  function rectForCameraFocus(focus, getFrame) {
540
540
  if (focus.type === "rect") return focus.rect;
541
- if (focus.type === "node") {
542
- const frame = getFrame(focus.nodeId);
541
+ if (focus.type === "item") {
542
+ const frame = getFrame(focus.itemId);
543
543
  return frame ? itemBounds(frame) : null;
544
544
  }
545
545
  if (focus.type === "frame") {
546
546
  const frame = getFrame(focus.frameId);
547
547
  return frame ? itemBounds(frame) : null;
548
548
  }
549
- const frames = focus.nodeIds.flatMap((id) => {
549
+ const frames = focus.itemIds.flatMap((id) => {
550
550
  const frame = getFrame(id);
551
551
  return frame ? [frame] : [];
552
552
  });
553
- return frames.length === focus.nodeIds.length ? selectionBounds(frames) : null;
553
+ return frames.length === focus.itemIds.length ? selectionBounds(frames) : null;
554
554
  }
555
555
  function cameraForFocus(params, getFrame, surface) {
556
556
  const rect = rectForCameraFocus(params.focus, getFrame);