@neta-art/cohub 8.11.0 → 8.12.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 (43) hide show
  1. package/dist/board/animation.js +14 -2
  2. package/dist/board/core/file-preview.d.ts +2 -2
  3. package/dist/board/core/file-preview.js +2 -2
  4. package/dist/board/index.d.ts +2 -1
  5. package/dist/board/index.js +2 -1
  6. package/dist/board/mutation.js +2 -1
  7. package/dist/board/render/board-background.d.ts +16 -0
  8. package/dist/board/render/{themes/clean-theme.js → board-background.js} +28 -35
  9. package/dist/board/render/index.d.ts +3 -3
  10. package/dist/board/render/index.js +2 -2
  11. package/dist/board/render/renderers/file-card-renderer.js +3 -32
  12. package/dist/board/replay.d.ts +52 -0
  13. package/dist/board/replay.js +261 -0
  14. package/dist/board/semantic-document.d.ts +9 -2
  15. package/dist/board/semantic-document.js +1 -3
  16. package/dist/chunks/environment.d.ts +272 -7
  17. package/dist/chunks/environment.js +1 -0
  18. package/dist/chunks/http.d.ts +76 -5
  19. package/dist/chunks/http.js +47 -10
  20. package/dist/chunks/websocket.d.ts +1 -1
  21. package/dist/http.d.ts +3 -3
  22. package/dist/index.d.ts +9 -4
  23. package/dist/index.js +167 -46
  24. package/dist/protocol/dist/board-animation.d.ts +1 -0
  25. package/dist/protocol/dist/board-animation.js +34 -0
  26. package/dist/protocol/dist/board-authoring.d.ts +2 -1
  27. package/dist/protocol/dist/board-capability-registry.js +53 -3
  28. package/dist/protocol/dist/board-codec.js +149 -2
  29. package/dist/protocol/dist/board-constants.js +29 -7
  30. package/dist/protocol/dist/board-document.d.ts +30 -16
  31. package/dist/protocol/dist/board-document.js +15 -12
  32. package/dist/protocol/dist/board-effect.d.ts +4 -2
  33. package/dist/protocol/dist/board-effect.js +3 -2
  34. package/dist/protocol/dist/board.d.ts +148 -3
  35. package/dist/protocol/dist/board.js +7 -0
  36. package/dist/protocol/dist/index.d.ts +4 -2
  37. package/dist/protocol/dist/provenance.d.ts +21 -0
  38. package/dist/types.d.ts +1 -1
  39. package/docs/app-runtime-guide.md +7 -4
  40. package/package.json +1 -1
  41. package/dist/board/render/themes/board-theme-registry.d.ts +0 -22
  42. package/dist/board/render/themes/board-theme-registry.js +0 -13
  43. package/dist/board/render/themes/clean-theme.d.ts +0 -5
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import { z } from "zod";
8
8
  const BOARD_DOCUMENT_KIND = "cohub.board";
9
9
  const BOARD_MANIFEST_KIND = "cohub.board.manifest";
10
10
  const idSchema = z.string().min(1).max(160);
11
- const jsonObjectSchema = z.record(z.string(), z.unknown());
12
- const finiteSchema = z.number().finite();
11
+ const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
12
+ const finiteSchema$1 = z.number().finite();
13
13
  z.object({
14
14
  kind: z.literal(BOARD_MANIFEST_KIND),
15
15
  version: z.literal(1),
@@ -17,18 +17,18 @@ z.object({
17
17
  title: z.string().min(1).max(255)
18
18
  });
19
19
  z.object({
20
- centerX: finiteSchema,
21
- centerY: finiteSchema,
22
- zoom: finiteSchema.positive()
20
+ centerX: finiteSchema$1,
21
+ centerY: finiteSchema$1,
22
+ zoom: finiteSchema$1.positive()
23
23
  });
24
24
  const BoardCameraFocusSchema = z.discriminatedUnion("type", [
25
25
  z.object({
26
26
  type: z.literal("rect"),
27
27
  rect: z.object({
28
- x: finiteSchema,
29
- y: finiteSchema,
30
- width: finiteSchema.positive(),
31
- height: finiteSchema.positive()
28
+ x: finiteSchema$1,
29
+ y: finiteSchema$1,
30
+ width: finiteSchema$1.positive(),
31
+ height: finiteSchema$1.positive()
32
32
  })
33
33
  }),
34
34
  z.object({
@@ -47,9 +47,9 @@ const BoardCameraFocusSchema = z.discriminatedUnion("type", [
47
47
  const BoardCameraFocusParamsSchema = z.object({
48
48
  focus: BoardCameraFocusSchema,
49
49
  fit: z.enum(["contain", "cover"]).default("contain"),
50
- padding: finiteSchema.nonnegative().default(32),
51
- minZoom: finiteSchema.positive().optional(),
52
- maxZoom: finiteSchema.positive().optional()
50
+ padding: finiteSchema$1.nonnegative().default(32),
51
+ minZoom: finiteSchema$1.positive().optional(),
52
+ maxZoom: finiteSchema$1.positive().optional()
53
53
  }).superRefine((value, context) => {
54
54
  if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
55
55
  code: "custom",
@@ -62,17 +62,17 @@ z.object({
62
62
  type: z.string().min(1).max(40),
63
63
  parentId: idSchema.nullable(),
64
64
  orderKey: z.string().max(4096).nullable(),
65
- x: finiteSchema,
66
- y: finiteSchema,
67
- width: finiteSchema.positive(),
68
- height: finiteSchema.positive(),
69
- rotation: finiteSchema,
65
+ x: finiteSchema$1,
66
+ y: finiteSchema$1,
67
+ width: finiteSchema$1.positive(),
68
+ height: finiteSchema$1.positive(),
69
+ rotation: finiteSchema$1,
70
70
  refKind: z.string().max(40).nullable(),
71
71
  refPath: z.string().max(4096).nullable(),
72
72
  refUrl: z.string().max(4096).nullable(),
73
- view: jsonObjectSchema,
74
- style: jsonObjectSchema,
75
- data: jsonObjectSchema
73
+ view: jsonObjectSchema$1,
74
+ style: jsonObjectSchema$1,
75
+ data: jsonObjectSchema$1
76
76
  });
77
77
  const stripBoardServerFields = (value) => {
78
78
  if (!value || typeof value !== "object" || Array.isArray(value)) return value;
@@ -86,7 +86,7 @@ const BoardCreateInputSchema = z.object({
86
86
  /** Reused by clients when board creation is interrupted and retried. */
87
87
  mutationId: z.string().max(128).optional(),
88
88
  title: z.string().min(1).max(255).optional(),
89
- metadata: jsonObjectSchema.optional(),
89
+ metadata: jsonObjectSchema$1.optional(),
90
90
  items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
91
91
  connections: z.array(BoardConnectionSchema).max(5e4).optional(),
92
92
  effects: z.array(BoardCreateEffectInputSchema).max(5e4).optional(),
@@ -132,17 +132,24 @@ z.object({
132
132
  effectIds: z.array(idSchema).max(5e4).optional(),
133
133
  compositionIds: z.array(idSchema).max(5e4).optional(),
134
134
  viewport: z.object({
135
- x: finiteSchema,
136
- y: finiteSchema,
137
- width: finiteSchema.positive(),
138
- height: finiteSchema.positive()
135
+ x: finiteSchema$1,
136
+ y: finiteSchema$1,
137
+ width: finiteSchema$1.positive(),
138
+ height: finiteSchema$1.positive()
139
139
  }).optional()
140
140
  });
141
+ z.object({
142
+ /** Return transactions with `version < before`. Omit for the newest page. */
143
+ before: z.number().int().positive().optional(),
144
+ limit: z.number().int().min(1).max(500).default(200),
145
+ /** Include the current rows on the newest page (default true). Tail refreshes turn this off. */
146
+ snapshot: z.boolean().default(true)
147
+ });
141
148
  /** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
142
149
  const BoardPlaybackPolicySchema = z.object({
143
150
  compositionId: idSchema,
144
151
  /** Delay before the first local playback after opening the Board, in milliseconds. */
145
- delayMs: finiteSchema.nonnegative().default(0)
152
+ delayMs: finiteSchema$1.nonnegative().default(0)
146
153
  });
147
154
  function parseBoardPlaybackPolicy(metadata) {
148
155
  const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
@@ -153,8 +160,8 @@ z.discriminatedUnion("type", [
153
160
  commandId: idSchema,
154
161
  type: z.literal("play"),
155
162
  compositionId: idSchema,
156
- position: finiteSchema.nonnegative().optional(),
157
- timeScale: finiteSchema.positive().max(4).optional(),
163
+ position: finiteSchema$1.nonnegative().optional(),
164
+ timeScale: finiteSchema$1.positive().max(4).optional(),
158
165
  shared: z.boolean().optional(),
159
166
  seed: idSchema.optional()
160
167
  }),
@@ -167,7 +174,7 @@ z.discriminatedUnion("type", [
167
174
  commandId: idSchema,
168
175
  type: z.literal("seek"),
169
176
  playbackId: z.string().uuid(),
170
- position: finiteSchema.nonnegative()
177
+ position: finiteSchema$1.nonnegative()
171
178
  }),
172
179
  z.object({
173
180
  commandId: idSchema,
@@ -176,9 +183,49 @@ z.discriminatedUnion("type", [
176
183
  })
177
184
  ]);
178
185
  //#endregion
186
+ //#region ../protocol/dist/board-animation.js
187
+ const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
188
+ const jsonObjectSchema = z.record(z.string(), z.unknown());
189
+ const finiteSchema = z.number().finite();
190
+ /** Parameters shared by the built-in deal entrance preset. */
191
+ const BoardDealParamsSchema = z.object({
192
+ lift: finiteSchema.nonnegative().max(2e3).optional(),
193
+ swing: finiteSchema.nonnegative().max(2e3).optional(),
194
+ curve: finiteSchema.nonnegative().max(2e3).optional(),
195
+ tilt: finiteSchema.nonnegative().max(180).optional(),
196
+ scale: finiteSchema.positive().max(2).optional(),
197
+ duration: finiteSchema.positive().max(1e4).optional(),
198
+ landing: finiteSchema.nonnegative().max(1e4).optional()
199
+ }).strict();
200
+ /** A versioned, reusable animation preset reference. */
201
+ const BoardAnimationSpecSchema = z.object({
202
+ kind: extensionKindSchema,
203
+ kindVersion: z.number().int().positive().default(1),
204
+ params: jsonObjectSchema.default({})
205
+ }).strict().superRefine((spec, context) => {
206
+ if (spec.kind !== "effects.deal" || spec.kindVersion !== 1) return;
207
+ const parsed = BoardDealParamsSchema.safeParse(spec.params);
208
+ if (parsed.success) return;
209
+ context.addIssue({
210
+ code: "custom",
211
+ message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
212
+ path: ["params", ...parsed.error.issues[0]?.path ?? []]
213
+ });
214
+ });
215
+ /** Board-wide default motion policies. Omitted policies mean no motion. */
216
+ const BoardMotionSchema = z.object({ enter: BoardAnimationSpecSchema.optional() }).strict();
217
+ //#endregion
179
218
  //#region ../protocol/dist/board-capability-registry.js
180
219
  const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
181
- new Set(BOARD_BUILTIN_EFFECT_KINDS);
220
+ const EFFECTS = new Set(BOARD_BUILTIN_EFFECT_KINDS);
221
+ const BUILTIN_VERSIONS = new Set(BOARD_BUILTIN_CAPABILITIES.map((c) => `${c.kind}:${c.id}@${c.version}`));
222
+ /**
223
+ * True when a built-in renderer exists for exactly this kind@version. A known
224
+ * kind at an unknown version is not built-in: it must not pass as one.
225
+ */
226
+ function isBuiltinBoardCapability(kind, id, version) {
227
+ return BUILTIN_VERSIONS.has(`${kind}:${id}@${version}`);
228
+ }
182
229
  const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
183
230
  const finite = (value) => typeof value === "number" && Number.isFinite(value);
184
231
  function validateBuiltinBoardClip(clip, path = "clip") {
@@ -212,6 +259,47 @@ function validateBuiltinBoardClip(clip, path = "clip") {
212
259
  }
213
260
  return errors;
214
261
  }
262
+ function validateBuiltinBoardEffect(effect, path = "effect") {
263
+ const diagnostics = [];
264
+ if (effect.lifecycle === "on-enter") {
265
+ if (effect.target.type !== "item") diagnostics.push({
266
+ severity: "error",
267
+ code: "INVALID_BOARD_EFFECT",
268
+ message: "on-enter effects must target an item",
269
+ path: `${path}.target`
270
+ });
271
+ if (effect.timeOrigin !== "activation") diagnostics.push({
272
+ severity: "error",
273
+ code: "INVALID_BOARD_EFFECT",
274
+ message: "on-enter effects must use activation time",
275
+ path: `${path}.timeOrigin`
276
+ });
277
+ } else if (EFFECTS.has(effect.kind) && effect.target.type !== "item") diagnostics.push({
278
+ severity: "error",
279
+ code: "INVALID_BOARD_EFFECT",
280
+ message: `${effect.kind} must target an item`,
281
+ path: `${path}.target`
282
+ });
283
+ if (effect.kind === "effects.deal" && effect.kindVersion === 1) {
284
+ if (effect.lifecycle !== "on-enter") diagnostics.push({
285
+ severity: "error",
286
+ code: "INVALID_BOARD_EFFECT",
287
+ message: "effects.deal must use the on-enter lifecycle",
288
+ path: `${path}.lifecycle`
289
+ });
290
+ const parsed = BoardDealParamsSchema.safeParse(effect.params);
291
+ if (!parsed.success) diagnostics.push({
292
+ severity: "error",
293
+ code: "INVALID_BOARD_EFFECT",
294
+ message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
295
+ path: `${path}.params`
296
+ });
297
+ }
298
+ return diagnostics;
299
+ }
300
+ function estimateBuiltinBoardEffectCost(effect) {
301
+ return isBuiltinBoardCapability("effect", effect.kind, effect.kindVersion) ? { drawCalls: 1 } : {};
302
+ }
215
303
  function estimateBuiltinBoardClipCost(clip) {
216
304
  switch (clip.kind) {
217
305
  case "effects.particles": {
@@ -352,7 +440,18 @@ const BoardViewportSchema = z.object({
352
440
  zoom: z.number().finite().min(.05).max(8)
353
441
  });
354
442
  const BoardAppearanceSchema = z.object({
355
- theme: z.string().min(1).default("clean"),
443
+ /** Legacy field retained when reading older Board documents; not used for rendering. */
444
+ theme: z.string().min(1).optional(),
445
+ /** Board-wide motion policies. Omitted policies mean no motion. */
446
+ motion: BoardMotionSchema.optional(),
447
+ /** Legacy field retained when reading older Board documents; not used for rendering. */
448
+ mood: z.enum([
449
+ "clean",
450
+ "playful",
451
+ "arcane",
452
+ "cyber",
453
+ "natural"
454
+ ]).optional(),
356
455
  background: z.object({
357
456
  kind: z.enum([
358
457
  "solid",
@@ -386,14 +485,7 @@ const BoardAppearanceSchema = z.object({
386
485
  visible: false,
387
486
  size: 24,
388
487
  opacity: .12
389
- }),
390
- mood: z.enum([
391
- "clean",
392
- "playful",
393
- "arcane",
394
- "cyber",
395
- "natural"
396
- ]).default("clean")
488
+ })
397
489
  });
398
490
  /** Optional visual chrome still carried by a few older shapes. */
399
491
  const BoardItemStyleSchema = z.object({
@@ -695,14 +787,12 @@ z.object({
695
787
  kind: z.literal(BOARD_DOCUMENT_KIND),
696
788
  version: z.literal(1),
697
789
  appearance: BoardAppearanceSchema.default({
698
- theme: "clean",
699
790
  background: { kind: "solid" },
700
791
  grid: {
701
792
  visible: false,
702
793
  size: 24,
703
794
  opacity: .12
704
- },
705
- mood: "clean"
795
+ }
706
796
  }),
707
797
  viewport: BoardViewportSchema,
708
798
  items: z.array(BoardItemSchema),
@@ -1294,6 +1384,12 @@ const buildAppRuntimeCloseRequest = () => ({
1294
1384
  version: 1,
1295
1385
  type: "close.request"
1296
1386
  });
1387
+ const buildAppRuntimeConfigureRequest = (input) => ({
1388
+ protocol: APP_RUNTIME_PROTOCOL,
1389
+ version: 1,
1390
+ type: "configure.request",
1391
+ ...input
1392
+ });
1297
1393
  //#endregion
1298
1394
  //#region ../protocol/dist/app-navigation.js
1299
1395
  const APP_NAVIGATION_PROTOCOL = "cohub.app.navigation";
@@ -2452,6 +2548,14 @@ var AppRuntimeApi = class {
2452
2548
  requestClose() {
2453
2549
  this.transport.notify?.(buildAppRuntimeCloseRequest());
2454
2550
  }
2551
+ /**
2552
+ * Requests the host to update the overlay's geometry or pointer hit regions.
2553
+ * Only meaningful when the App was opened as an `overlay` surface; the host
2554
+ * is free to clamp or ignore values that violate its layout policy.
2555
+ */
2556
+ requestConfigure(input) {
2557
+ this.transport.notify?.(buildAppRuntimeConfigureRequest(input));
2558
+ }
2455
2559
  async getAccessToken(options) {
2456
2560
  await this.ensureStorageKeys();
2457
2561
  if (this.token && !options?.forceRefresh) return this.token;
@@ -2553,7 +2657,7 @@ var AppRuntimeApi = class {
2553
2657
  * One consent: create a viewer-owned Space (full `CreateSpaceInput`, same
2554
2658
  * as `spaces.create`) and grant the scopes on it. Never silent — each
2555
2659
  * confirm mints a new Space. The host creates with the viewer's account
2556
- * token; the app never calls `POST /api/spaces` itself.
2660
+ * token.
2557
2661
  * `{ granted: false, space }` means the Space was created but not provisioned;
2558
2662
  * no grant was issued. A viewer deny is `{ granted: false, space: null }`.
2559
2663
  */
@@ -2766,6 +2870,11 @@ var CohubClient = class {
2766
2870
  onContextChanged: (listener) => this.appRuntime.onContextChanged(listener),
2767
2871
  /** Ask the host to close this App's surface. */
2768
2872
  requestClose: () => this.appRuntime.requestClose(),
2873
+ /**
2874
+ * Request that the host update this overlay's geometry or pointer hit
2875
+ * regions. Only meaningful for `overlay` surfaces; ignored otherwise.
2876
+ */
2877
+ requestConfigure: (input) => this.appRuntime.requestConfigure(input),
2769
2878
  embed: {
2770
2879
  /** Host another App's public page in an iframe and forward the shell location to it. */
2771
2880
  attach: attachAppEmbed },
@@ -3352,7 +3461,7 @@ function createAppBridgeCore(config) {
3352
3461
  return Array.from(bySpace.values());
3353
3462
  }
3354
3463
  function allowsOwnerAutoAuthorization() {
3355
- return authorizationContext.surface === "background" || authorizationContext.surface === "app";
3464
+ return authorizationContext.surface === "background" || authorizationContext.surface === "app" || authorizationContext.surface === "overlay";
3356
3465
  }
3357
3466
  async function ensureBaseToken(forceRefresh = false) {
3358
3467
  if (appToken && !forceRefresh) return appToken;
@@ -4033,6 +4142,15 @@ var BoardExtensionRegistry = class {
4033
4142
  const diagnostics = [];
4034
4143
  const composition = parsed.data;
4035
4144
  const cost = { ...ZERO_COST };
4145
+ for (const effect of input.effects ?? []) {
4146
+ diagnostics.push(...validateBuiltinBoardEffect(effect, `effects.${effect.id}`));
4147
+ if (!this.#extensions.has(`effect:${effect.kind}@${effect.kindVersion}`)) diagnostics.push({
4148
+ severity: "warning",
4149
+ code: "UNKNOWN_EFFECT",
4150
+ message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
4151
+ path: `effects.${effect.id}`
4152
+ });
4153
+ }
4036
4154
  const events = [];
4037
4155
  for (const clip of composition.timeline.clips) {
4038
4156
  const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
@@ -4066,7 +4184,10 @@ var BoardExtensionRegistry = class {
4066
4184
  active[key] += event.cost[key] * event.direction;
4067
4185
  cost[key] = Math.max(cost[key], active[key]);
4068
4186
  }
4069
- for (const effect of input.effects ?? []) if (effect.kind === "effects.pulse" || effect.kind === "effects.float") cost.drawCalls += 1;
4187
+ for (const effect of input.effects ?? []) {
4188
+ const estimate = estimateBuiltinBoardEffectCost(effect);
4189
+ for (const key of Object.keys(cost)) cost[key] += estimate[key] ?? 0;
4190
+ }
4070
4191
  const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
4071
4192
  for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
4072
4193
  severity: "warning",
@@ -4087,4 +4208,4 @@ function createBoardExtensionRegistry() {
4087
4208
  }
4088
4209
  const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
4089
4210
  //#endregion
4090
- export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };
4211
+ export { APP_COMPOSER_CHIP_CONTENT_MAX_BYTES, APP_COMPOSER_CHIP_KEY_MAX_LENGTH, APP_COMPOSER_CHIP_LABEL_MAX_LENGTH, APP_SURFACE_READY_TIMEOUT_MS, APP_SURFACE_REQUEST_TIMEOUT_MS, AppCommerceApi, AppRealtimeApi, AppRefParseError, AppRoom, AppRuntimeApi, AppRuntimeApi as WorkRuntimeApi, AppSurfaceApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAnimationSpecSchema, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardDealParamsSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, BoardSemanticCommandSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, DESKTOP_COMMAND_PENDING_TTL_SECONDS, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, DESKTOP_COMMAND_VERSION, DesktopCommandsApi, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatAppRef, formatWorkRef, getAllowedGenerationModelIds, getCohubContext, hasGrantedAppScopes, hasRequestSourceIdentity, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, setGrantedAppScopes, track };
@@ -0,0 +1 @@
1
+ import { z } from "zod";
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ //#region ../protocol/dist/board-animation.js
3
+ const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
4
+ const jsonObjectSchema = z.record(z.string(), z.unknown());
5
+ const finiteSchema = z.number().finite();
6
+ /** Parameters shared by the built-in deal entrance preset. */
7
+ const BoardDealParamsSchema = z.object({
8
+ lift: finiteSchema.nonnegative().max(2e3).optional(),
9
+ swing: finiteSchema.nonnegative().max(2e3).optional(),
10
+ curve: finiteSchema.nonnegative().max(2e3).optional(),
11
+ tilt: finiteSchema.nonnegative().max(180).optional(),
12
+ scale: finiteSchema.positive().max(2).optional(),
13
+ duration: finiteSchema.positive().max(1e4).optional(),
14
+ landing: finiteSchema.nonnegative().max(1e4).optional()
15
+ }).strict();
16
+ /** A versioned, reusable animation preset reference. */
17
+ const BoardAnimationSpecSchema = z.object({
18
+ kind: extensionKindSchema,
19
+ kindVersion: z.number().int().positive().default(1),
20
+ params: jsonObjectSchema.default({})
21
+ }).strict().superRefine((spec, context) => {
22
+ if (spec.kind !== "effects.deal" || spec.kindVersion !== 1) return;
23
+ const parsed = BoardDealParamsSchema.safeParse(spec.params);
24
+ if (parsed.success) return;
25
+ context.addIssue({
26
+ code: "custom",
27
+ message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
28
+ path: ["params", ...parsed.error.issues[0]?.path ?? []]
29
+ });
30
+ });
31
+ /** Board-wide default motion policies. Omitted policies mean no motion. */
32
+ const BoardMotionSchema = z.object({ enter: BoardAnimationSpecSchema.optional() }).strict();
33
+ //#endregion
34
+ export { BoardAnimationSpecSchema, BoardDealParamsSchema, BoardMotionSchema };
@@ -1140,10 +1140,11 @@ declare const BoardSemanticCommandSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1140
1140
  type: z.ZodLiteral<"board">;
1141
1141
  }, z.core.$strict>], "type">;
1142
1142
  kind: z.ZodString;
1143
- kindVersion: z.ZodNumber;
1143
+ kindVersion: z.ZodDefault<z.ZodNumber>;
1144
1144
  enabled: z.ZodDefault<z.ZodBoolean>;
1145
1145
  lifecycle: z.ZodEnum<{
1146
1146
  manual: "manual";
1147
+ "on-enter": "on-enter";
1147
1148
  persistent: "persistent";
1148
1149
  "when-visible": "when-visible";
1149
1150
  }>;
@@ -1,8 +1,17 @@
1
- import { BOARD_BUILTIN_CLIP_KINDS, BOARD_BUILTIN_EFFECT_KINDS, DEFAULT_BOARD_RENDER_LIMITS } from "./board-constants.js";
1
+ import { BOARD_BUILTIN_CAPABILITIES, BOARD_BUILTIN_CLIP_KINDS, BOARD_BUILTIN_EFFECT_KINDS, DEFAULT_BOARD_RENDER_LIMITS } from "./board-constants.js";
2
2
  import { BoardCameraFocusParamsSchema } from "./board.js";
3
+ import { BoardDealParamsSchema } from "./board-animation.js";
3
4
  //#region ../protocol/dist/board-capability-registry.js
4
5
  const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
5
- new Set(BOARD_BUILTIN_EFFECT_KINDS);
6
+ const EFFECTS = new Set(BOARD_BUILTIN_EFFECT_KINDS);
7
+ const BUILTIN_VERSIONS = new Set(BOARD_BUILTIN_CAPABILITIES.map((c) => `${c.kind}:${c.id}@${c.version}`));
8
+ /**
9
+ * True when a built-in renderer exists for exactly this kind@version. A known
10
+ * kind at an unknown version is not built-in: it must not pass as one.
11
+ */
12
+ function isBuiltinBoardCapability(kind, id, version) {
13
+ return BUILTIN_VERSIONS.has(`${kind}:${id}@${version}`);
14
+ }
6
15
  const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
7
16
  const finite = (value) => typeof value === "number" && Number.isFinite(value);
8
17
  function validateBuiltinBoardClip(clip, path = "clip") {
@@ -36,6 +45,47 @@ function validateBuiltinBoardClip(clip, path = "clip") {
36
45
  }
37
46
  return errors;
38
47
  }
48
+ function validateBuiltinBoardEffect(effect, path = "effect") {
49
+ const diagnostics = [];
50
+ if (effect.lifecycle === "on-enter") {
51
+ if (effect.target.type !== "item") diagnostics.push({
52
+ severity: "error",
53
+ code: "INVALID_BOARD_EFFECT",
54
+ message: "on-enter effects must target an item",
55
+ path: `${path}.target`
56
+ });
57
+ if (effect.timeOrigin !== "activation") diagnostics.push({
58
+ severity: "error",
59
+ code: "INVALID_BOARD_EFFECT",
60
+ message: "on-enter effects must use activation time",
61
+ path: `${path}.timeOrigin`
62
+ });
63
+ } else if (EFFECTS.has(effect.kind) && effect.target.type !== "item") diagnostics.push({
64
+ severity: "error",
65
+ code: "INVALID_BOARD_EFFECT",
66
+ message: `${effect.kind} must target an item`,
67
+ path: `${path}.target`
68
+ });
69
+ if (effect.kind === "effects.deal" && effect.kindVersion === 1) {
70
+ if (effect.lifecycle !== "on-enter") diagnostics.push({
71
+ severity: "error",
72
+ code: "INVALID_BOARD_EFFECT",
73
+ message: "effects.deal must use the on-enter lifecycle",
74
+ path: `${path}.lifecycle`
75
+ });
76
+ const parsed = BoardDealParamsSchema.safeParse(effect.params);
77
+ if (!parsed.success) diagnostics.push({
78
+ severity: "error",
79
+ code: "INVALID_BOARD_EFFECT",
80
+ message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
81
+ path: `${path}.params`
82
+ });
83
+ }
84
+ return diagnostics;
85
+ }
86
+ function estimateBuiltinBoardEffectCost(effect) {
87
+ return isBuiltinBoardCapability("effect", effect.kind, effect.kindVersion) ? { drawCalls: 1 } : {};
88
+ }
39
89
  function estimateBuiltinBoardClipCost(clip) {
40
90
  switch (clip.kind) {
41
91
  case "effects.particles": {
@@ -70,4 +120,4 @@ function estimateBuiltinBoardClipCost(clip) {
70
120
  }
71
121
  }
72
122
  //#endregion
73
- export { estimateBuiltinBoardClipCost, validateBuiltinBoardClip };
123
+ export { estimateBuiltinBoardClipCost, estimateBuiltinBoardEffectCost, isBuiltinBoardCapability, validateBuiltinBoardClip, validateBuiltinBoardEffect };
@@ -1,6 +1,6 @@
1
1
  import { BoardAuthoringItemSchema, BoardItemPatchSchema } from "./board-authoring.js";
2
2
  import { BoardNodeInputSchema } from "./board.js";
3
- import { boardArrowFrame, boardDrawBounds, boardDrawPointsToLocal } from "./board-geometry.js";
3
+ import { boardArrowFrame, boardDrawBounds, boardDrawPointsToLocal, boardDrawPointsToWorld } from "./board-geometry.js";
4
4
  import { BOARD_NATIVE_NODE_TYPES, validateBoardNodeInput } from "./board-node.js";
5
5
  //#region ../protocol/dist/board-codec.js
6
6
  var BoardItemValidationError = class extends Error {
@@ -30,6 +30,153 @@ function authoringDiagnostic(diagnostic, path) {
30
30
  };
31
31
  }
32
32
  const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
33
+ function baseItem(node) {
34
+ return {
35
+ id: node.nodeId,
36
+ position: {
37
+ x: node.x,
38
+ y: node.y
39
+ },
40
+ size: {
41
+ width: node.width,
42
+ height: node.height
43
+ },
44
+ rotation: node.rotation,
45
+ ...node.parentId ? { parentId: node.parentId } : {},
46
+ ...node.data.locked === true ? { locked: true } : {},
47
+ ...isRecord(node.data.metadata) ? { metadata: node.data.metadata } : {}
48
+ };
49
+ }
50
+ function filePath(node) {
51
+ if (!node.refPath) throw new Error(`Board item ${node.nodeId} is missing its source path`);
52
+ return node.refPath;
53
+ }
54
+ function style(node) {
55
+ const value = {};
56
+ if (typeof node.data.color === "string") value.color = node.data.color;
57
+ if (typeof node.data.size === "number") value.strokeWidth = node.data.size;
58
+ if (typeof node.data.fillOpacity === "number") value.fillOpacity = node.data.fillOpacity;
59
+ return Object.keys(value).length ? value : void 0;
60
+ }
61
+ function boardNodeToAuthoringItem(node) {
62
+ const base = baseItem(node);
63
+ if (node.type === "draw" || node.type === "arrow") {
64
+ delete base.size;
65
+ delete base.position;
66
+ }
67
+ const visual = style(node);
68
+ let candidate;
69
+ switch (node.type) {
70
+ case "text":
71
+ candidate = {
72
+ ...base,
73
+ type: "text",
74
+ props: {
75
+ text: node.data.text ?? "",
76
+ fontSize: node.data.fontSize ?? 24
77
+ },
78
+ ...visual ? { style: visual } : {}
79
+ };
80
+ break;
81
+ case "geo":
82
+ candidate = {
83
+ ...base,
84
+ type: "geo",
85
+ props: {
86
+ shape: node.data.geo ?? "rectangle",
87
+ text: node.data.text ?? ""
88
+ },
89
+ ...visual ? { style: visual } : {}
90
+ };
91
+ break;
92
+ case "draw":
93
+ candidate = {
94
+ ...base,
95
+ type: "draw",
96
+ props: { points: Array.isArray(node.data.points) ? boardDrawPointsToWorld(node.data.points, node.x, node.y) : [] },
97
+ ...visual ? { style: visual } : {}
98
+ };
99
+ break;
100
+ case "arrow":
101
+ candidate = {
102
+ ...base,
103
+ type: "arrow",
104
+ props: {
105
+ start: node.data.start,
106
+ end: node.data.end,
107
+ bend: node.data.bend ?? 0,
108
+ arrowStart: node.data.arrowStart ?? false,
109
+ arrowEnd: node.data.arrowEnd ?? true,
110
+ label: node.data.label ?? ""
111
+ },
112
+ ...visual ? { style: visual } : {}
113
+ };
114
+ break;
115
+ case "frame":
116
+ candidate = {
117
+ ...base,
118
+ type: "frame",
119
+ props: { label: node.data.label ?? "Frame" },
120
+ ...visual ? { style: visual } : {}
121
+ };
122
+ break;
123
+ case "image":
124
+ candidate = {
125
+ ...base,
126
+ type: "image",
127
+ props: isRecord(node.data.crop) ? { crop: node.data.crop } : {},
128
+ source: {
129
+ kind: "space-file",
130
+ path: filePath(node),
131
+ ...Object.keys(node.view).length ? { snapshot: node.view } : {}
132
+ }
133
+ };
134
+ break;
135
+ case "video":
136
+ case "audio":
137
+ case "file":
138
+ candidate = {
139
+ ...base,
140
+ type: node.type,
141
+ props: {},
142
+ source: {
143
+ kind: "space-file",
144
+ path: filePath(node),
145
+ ...Object.keys(node.view).length ? { snapshot: node.view } : {}
146
+ }
147
+ };
148
+ break;
149
+ case "task":
150
+ candidate = {
151
+ ...base,
152
+ type: "task",
153
+ props: {
154
+ taskRunId: node.data.taskRunId,
155
+ snapshot: node.view
156
+ }
157
+ };
158
+ break;
159
+ default: {
160
+ const props = { ...node.data };
161
+ delete props.locked;
162
+ delete props.metadata;
163
+ delete props.kindVersion;
164
+ candidate = {
165
+ ...base,
166
+ type: node.type,
167
+ kindVersion: typeof node.data.kindVersion === "number" ? node.data.kindVersion : 1,
168
+ props,
169
+ ...Object.keys(node.style).length ? { style: node.style } : {},
170
+ ...node.refPath ? { source: {
171
+ kind: node.refKind ?? "space-file",
172
+ ref: node.refPath,
173
+ ...Object.keys(node.view).length ? { snapshot: node.view } : {}
174
+ } } : {}
175
+ };
176
+ }
177
+ }
178
+ return BoardAuthoringItemSchema.parse(candidate);
179
+ }
33
180
  function commonData(item) {
34
181
  return {
35
182
  ...item.locked ? { locked: true } : {},
@@ -233,4 +380,4 @@ function mergePatch(current, patch) {
233
380
  return result;
234
381
  }
235
382
  //#endregion
236
- export { BoardItemValidationError, applyBoardItemPatch, authoringSchemaDiagnostics, boardAuthoringItemToNode };
383
+ export { BoardItemValidationError, applyBoardItemPatch, authoringSchemaDiagnostics, boardAuthoringItemToNode, boardNodeToAuthoringItem };