@neta-art/cohub 5.9.0 → 6.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.
- package/README.md +42 -46
- package/dist/board/animation.d.ts +156 -67
- package/dist/board/animation.js +162 -277
- package/dist/board/codec.js +1 -1
- package/dist/board/export/index.d.ts +6 -0
- package/dist/board/export/index.js +6 -3
- package/dist/board/export/scene.d.ts +6 -0
- package/dist/board/export/scene.js +26 -2
- package/dist/board/geometry.d.ts +27 -1
- package/dist/board/geometry.js +92 -1
- package/dist/board/headless/index.d.ts +7 -1
- package/dist/board/headless/index.js +6 -2
- package/dist/board/index.d.ts +8 -6
- package/dist/board/index.js +7 -6
- package/dist/board/mutation.d.ts +15 -0
- package/dist/board/mutation.js +67 -0
- package/dist/board/render/css-color.d.ts +4 -0
- package/dist/board/render/css-color.js +36 -0
- package/dist/board/render/index.d.ts +2 -1
- package/dist/board/render/index.js +2 -1
- package/dist/board/render/renderers/base-card-renderer.js +3 -20
- package/dist/board/render/themes/clean-theme.js +6 -3
- package/dist/chunks/http.d.ts +30 -7
- package/dist/chunks/http.js +785 -289
- package/dist/chunks/websocket.d.ts +2675 -187
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +158 -299
- package/dist/index.js +164 -279
- package/dist/protocol/dist/board-authoring.d.ts +1 -0
- package/dist/protocol/dist/board-authoring.js +217 -0
- package/dist/protocol/dist/board-capability-registry.d.ts +2 -0
- package/dist/protocol/dist/board-capability-registry.js +74 -0
- package/dist/protocol/dist/board-codec.d.ts +2 -0
- package/dist/protocol/dist/board-codec.js +4 -0
- package/dist/protocol/dist/board-composition.d.ts +258 -0
- package/dist/protocol/dist/board-composition.js +318 -0
- package/dist/protocol/dist/board-constants.d.ts +2 -1
- package/dist/protocol/dist/board-constants.js +40 -10
- package/dist/protocol/dist/board-document.d.ts +28 -2
- package/dist/protocol/dist/board-document.js +16 -3
- package/dist/protocol/dist/board-node.d.ts +1 -11
- package/dist/protocol/dist/board-node.js +1 -81
- package/dist/protocol/dist/board-upgrade.d.ts +1 -0
- package/dist/protocol/dist/board-upgrade.js +2 -0
- package/dist/protocol/dist/board-url.d.ts +2 -1
- package/dist/protocol/dist/board-url.js +8 -3
- package/dist/protocol/dist/board.d.ts +135 -76
- package/dist/protocol/dist/board.js +50 -69
- package/dist/protocol/dist/index.d.ts +9 -4
- package/dist/protocol/dist/index.js +9 -4
- package/dist/types.d.ts +3 -2
- package/docs/work-runtime-guide.md +19 -3
- package/package.json +3 -2
- package/dist/board/nodes.d.ts +0 -113
- package/dist/board/nodes.js +0 -154
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region ../protocol/dist/board-composition.js
|
|
3
|
+
const idSchema = z.string().min(1).max(160);
|
|
4
|
+
const finiteSchema = z.number().finite();
|
|
5
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
6
|
+
const BOARD_EASINGS = [
|
|
7
|
+
"linear",
|
|
8
|
+
"ease-in-quad",
|
|
9
|
+
"ease-out-quad",
|
|
10
|
+
"ease-in-out-quad",
|
|
11
|
+
"ease-in-cubic",
|
|
12
|
+
"ease-out-cubic",
|
|
13
|
+
"ease-in-out-cubic",
|
|
14
|
+
"ease-out-quart",
|
|
15
|
+
"ease-out-expo"
|
|
16
|
+
];
|
|
17
|
+
const BoardEasingSchema = z.enum(BOARD_EASINGS);
|
|
18
|
+
const BoardAnimationTargetSchema = z.discriminatedUnion("type", [
|
|
19
|
+
z.object({
|
|
20
|
+
type: z.literal("item"),
|
|
21
|
+
itemId: idSchema
|
|
22
|
+
}).strict(),
|
|
23
|
+
z.object({
|
|
24
|
+
type: z.literal("effect"),
|
|
25
|
+
effectId: idSchema
|
|
26
|
+
}).strict(),
|
|
27
|
+
z.object({ type: z.literal("camera") }).strict(),
|
|
28
|
+
z.object({ type: z.literal("board") }).strict()
|
|
29
|
+
]);
|
|
30
|
+
const vector2Schema = z.object({
|
|
31
|
+
x: finiteSchema,
|
|
32
|
+
y: finiteSchema
|
|
33
|
+
}).strict();
|
|
34
|
+
const scaleSchema = z.union([finiteSchema.positive(), z.object({
|
|
35
|
+
x: finiteSchema.positive(),
|
|
36
|
+
y: finiteSchema.positive()
|
|
37
|
+
}).strict()]);
|
|
38
|
+
const BOARD_ANIMATION_CHANNELS = {
|
|
39
|
+
"transform.translation": {
|
|
40
|
+
targets: ["item"],
|
|
41
|
+
value: vector2Schema,
|
|
42
|
+
interpolations: ["linear", "step"],
|
|
43
|
+
coordinateSpace: "world-offset",
|
|
44
|
+
unit: "board"
|
|
45
|
+
},
|
|
46
|
+
"transform.rotation": {
|
|
47
|
+
targets: ["item"],
|
|
48
|
+
value: finiteSchema,
|
|
49
|
+
interpolations: ["linear", "step"],
|
|
50
|
+
unit: "radian"
|
|
51
|
+
},
|
|
52
|
+
"transform.scale": {
|
|
53
|
+
targets: ["item"],
|
|
54
|
+
value: scaleSchema,
|
|
55
|
+
interpolations: ["linear", "step"],
|
|
56
|
+
unit: "ratio"
|
|
57
|
+
},
|
|
58
|
+
"style.opacity": {
|
|
59
|
+
targets: ["item"],
|
|
60
|
+
value: finiteSchema.min(0).max(1),
|
|
61
|
+
interpolations: ["linear", "step"],
|
|
62
|
+
unit: "ratio"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const BoardTrackKeyframeSchema = z.object({
|
|
66
|
+
time: finiteSchema.nonnegative(),
|
|
67
|
+
value: z.unknown(),
|
|
68
|
+
easing: BoardEasingSchema.optional()
|
|
69
|
+
}).strict();
|
|
70
|
+
const BoardTrackSchema = z.object({
|
|
71
|
+
id: idSchema,
|
|
72
|
+
target: BoardAnimationTargetSchema,
|
|
73
|
+
channel: z.string().min(1).max(160),
|
|
74
|
+
channelVersion: z.number().int().positive().default(1),
|
|
75
|
+
interpolation: z.enum(["linear", "step"]).default("linear"),
|
|
76
|
+
fill: z.enum([
|
|
77
|
+
"none",
|
|
78
|
+
"backwards",
|
|
79
|
+
"forwards",
|
|
80
|
+
"both"
|
|
81
|
+
]).default("none"),
|
|
82
|
+
keyframes: z.array(BoardTrackKeyframeSchema).min(1).max(1e5),
|
|
83
|
+
metadata: jsonObjectSchema.default({})
|
|
84
|
+
}).strict().superRefine((track, context) => {
|
|
85
|
+
let previous = -1;
|
|
86
|
+
for (const [index, keyframe] of track.keyframes.entries()) {
|
|
87
|
+
if (keyframe.time <= previous) context.addIssue({
|
|
88
|
+
code: "custom",
|
|
89
|
+
message: "keyframe times must be strictly increasing",
|
|
90
|
+
path: [
|
|
91
|
+
"keyframes",
|
|
92
|
+
index,
|
|
93
|
+
"time"
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
previous = keyframe.time;
|
|
97
|
+
}
|
|
98
|
+
const channel = BOARD_ANIMATION_CHANNELS[track.channel];
|
|
99
|
+
if (!channel) {
|
|
100
|
+
if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+){2,}$/.test(track.channel)) context.addIssue({
|
|
101
|
+
code: "custom",
|
|
102
|
+
message: `unknown channel: ${track.channel}`,
|
|
103
|
+
path: ["channel"]
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!channel.targets.includes(track.target.type)) context.addIssue({
|
|
108
|
+
code: "custom",
|
|
109
|
+
message: `${track.channel} cannot target ${track.target.type}`,
|
|
110
|
+
path: ["target"]
|
|
111
|
+
});
|
|
112
|
+
if (!channel.interpolations.includes(track.interpolation)) context.addIssue({
|
|
113
|
+
code: "custom",
|
|
114
|
+
message: `${track.channel} does not support ${track.interpolation} interpolation`,
|
|
115
|
+
path: ["interpolation"]
|
|
116
|
+
});
|
|
117
|
+
for (const [index, keyframe] of track.keyframes.entries()) {
|
|
118
|
+
const parsed = channel.value.safeParse(keyframe.value);
|
|
119
|
+
if (!parsed.success) context.addIssue({
|
|
120
|
+
code: "custom",
|
|
121
|
+
message: parsed.error.issues[0]?.message ?? `invalid value for ${track.channel}`,
|
|
122
|
+
path: [
|
|
123
|
+
"keyframes",
|
|
124
|
+
index,
|
|
125
|
+
"value"
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
131
|
+
const BoardProceduralClipSchema = z.object({
|
|
132
|
+
id: idSchema,
|
|
133
|
+
kind: extensionKindSchema,
|
|
134
|
+
kindVersion: z.number().int().positive(),
|
|
135
|
+
target: BoardAnimationTargetSchema,
|
|
136
|
+
start: finiteSchema.nonnegative(),
|
|
137
|
+
duration: finiteSchema.positive(),
|
|
138
|
+
layer: z.enum([
|
|
139
|
+
"behind",
|
|
140
|
+
"content",
|
|
141
|
+
"front",
|
|
142
|
+
"screen"
|
|
143
|
+
]).default("content"),
|
|
144
|
+
fill: z.enum([
|
|
145
|
+
"none",
|
|
146
|
+
"backwards",
|
|
147
|
+
"forwards",
|
|
148
|
+
"both"
|
|
149
|
+
]).default("none"),
|
|
150
|
+
easing: BoardEasingSchema.default("linear"),
|
|
151
|
+
params: jsonObjectSchema.default({}),
|
|
152
|
+
assetRefs: z.array(z.object({
|
|
153
|
+
type: z.enum(["space-file", "extension"]),
|
|
154
|
+
ref: z.string().min(1).max(4096),
|
|
155
|
+
digest: z.string().min(16).max(160).optional()
|
|
156
|
+
}).strict()).default([]),
|
|
157
|
+
seed: idSchema,
|
|
158
|
+
metadata: jsonObjectSchema.default({})
|
|
159
|
+
}).strict();
|
|
160
|
+
const BoardTimelineMarkerSchema = z.object({
|
|
161
|
+
id: idSchema,
|
|
162
|
+
time: finiteSchema.nonnegative(),
|
|
163
|
+
duration: finiteSchema.nonnegative().optional(),
|
|
164
|
+
metadata: jsonObjectSchema.default({})
|
|
165
|
+
}).strict();
|
|
166
|
+
const BoardTimelineSchema = z.object({
|
|
167
|
+
duration: finiteSchema.nonnegative(),
|
|
168
|
+
tracks: z.array(BoardTrackSchema).max(5e4).default([]),
|
|
169
|
+
clips: z.array(BoardProceduralClipSchema).max(5e4).default([]),
|
|
170
|
+
markers: z.array(BoardTimelineMarkerSchema).max(1e4).default([])
|
|
171
|
+
}).strict().superRefine((timeline, context) => {
|
|
172
|
+
const ids = /* @__PURE__ */ new Set();
|
|
173
|
+
const channels = /* @__PURE__ */ new Set();
|
|
174
|
+
for (const [index, track] of timeline.tracks.entries()) {
|
|
175
|
+
if (ids.has(track.id)) context.addIssue({
|
|
176
|
+
code: "custom",
|
|
177
|
+
message: `duplicate timeline id: ${track.id}`,
|
|
178
|
+
path: [
|
|
179
|
+
"tracks",
|
|
180
|
+
index,
|
|
181
|
+
"id"
|
|
182
|
+
]
|
|
183
|
+
});
|
|
184
|
+
ids.add(track.id);
|
|
185
|
+
const targetId = track.target.type === "item" ? track.target.itemId : track.target.type === "effect" ? track.target.effectId : track.target.type;
|
|
186
|
+
const key = `${track.target.type}:${targetId}:${track.channel}`;
|
|
187
|
+
if (channels.has(key)) context.addIssue({
|
|
188
|
+
code: "custom",
|
|
189
|
+
message: `multiple tracks control ${key}`,
|
|
190
|
+
path: [
|
|
191
|
+
"tracks",
|
|
192
|
+
index,
|
|
193
|
+
"channel"
|
|
194
|
+
]
|
|
195
|
+
});
|
|
196
|
+
channels.add(key);
|
|
197
|
+
for (const [keyframeIndex, keyframe] of track.keyframes.entries()) if (keyframe.time > timeline.duration) context.addIssue({
|
|
198
|
+
code: "custom",
|
|
199
|
+
message: "keyframe exceeds timeline duration",
|
|
200
|
+
path: [
|
|
201
|
+
"tracks",
|
|
202
|
+
index,
|
|
203
|
+
"keyframes",
|
|
204
|
+
keyframeIndex,
|
|
205
|
+
"time"
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
for (const [index, clip] of timeline.clips.entries()) {
|
|
210
|
+
if (ids.has(clip.id)) context.addIssue({
|
|
211
|
+
code: "custom",
|
|
212
|
+
message: `duplicate timeline id: ${clip.id}`,
|
|
213
|
+
path: [
|
|
214
|
+
"clips",
|
|
215
|
+
index,
|
|
216
|
+
"id"
|
|
217
|
+
]
|
|
218
|
+
});
|
|
219
|
+
ids.add(clip.id);
|
|
220
|
+
if (clip.start + clip.duration > timeline.duration) context.addIssue({
|
|
221
|
+
code: "custom",
|
|
222
|
+
message: "clip exceeds timeline duration",
|
|
223
|
+
path: [
|
|
224
|
+
"clips",
|
|
225
|
+
index,
|
|
226
|
+
"duration"
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
for (const [index, marker] of timeline.markers.entries()) {
|
|
231
|
+
if (ids.has(marker.id)) context.addIssue({
|
|
232
|
+
code: "custom",
|
|
233
|
+
message: `duplicate timeline id: ${marker.id}`,
|
|
234
|
+
path: [
|
|
235
|
+
"markers",
|
|
236
|
+
index,
|
|
237
|
+
"id"
|
|
238
|
+
]
|
|
239
|
+
});
|
|
240
|
+
ids.add(marker.id);
|
|
241
|
+
if (marker.time + (marker.duration ?? 0) > timeline.duration) context.addIssue({
|
|
242
|
+
code: "custom",
|
|
243
|
+
message: "marker exceeds timeline duration",
|
|
244
|
+
path: ["markers", index]
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
const BoardCompositionPlaybackSchema = z.object({
|
|
249
|
+
loop: z.boolean().default(false),
|
|
250
|
+
endBehavior: z.enum(["hold", "reset"]).default("hold"),
|
|
251
|
+
reducedMotion: z.discriminatedUnion("mode", [
|
|
252
|
+
z.object({ mode: z.literal("base") }).strict(),
|
|
253
|
+
z.object({
|
|
254
|
+
mode: z.literal("time"),
|
|
255
|
+
time: finiteSchema.nonnegative()
|
|
256
|
+
}).strict(),
|
|
257
|
+
z.object({
|
|
258
|
+
mode: z.literal("marker"),
|
|
259
|
+
markerId: idSchema
|
|
260
|
+
}).strict()
|
|
261
|
+
]).default({ mode: "base" })
|
|
262
|
+
}).strict();
|
|
263
|
+
const compositionFields = {
|
|
264
|
+
id: idSchema,
|
|
265
|
+
name: z.string().min(1).max(255),
|
|
266
|
+
timeline: BoardTimelineSchema,
|
|
267
|
+
playback: BoardCompositionPlaybackSchema.default({
|
|
268
|
+
loop: false,
|
|
269
|
+
endBehavior: "hold",
|
|
270
|
+
reducedMotion: { mode: "base" }
|
|
271
|
+
}),
|
|
272
|
+
metadata: jsonObjectSchema.default({})
|
|
273
|
+
};
|
|
274
|
+
function validateCompositionFallback(composition, context) {
|
|
275
|
+
const fallback = composition.playback.reducedMotion;
|
|
276
|
+
if (fallback.mode === "time" && fallback.time > composition.timeline.duration) context.addIssue({
|
|
277
|
+
code: "custom",
|
|
278
|
+
message: "reduced-motion time exceeds timeline duration",
|
|
279
|
+
path: [
|
|
280
|
+
"playback",
|
|
281
|
+
"reducedMotion",
|
|
282
|
+
"time"
|
|
283
|
+
]
|
|
284
|
+
});
|
|
285
|
+
if (fallback.mode === "marker" && !composition.timeline.markers.some((marker) => marker.id === fallback.markerId)) context.addIssue({
|
|
286
|
+
code: "custom",
|
|
287
|
+
message: `marker does not exist: ${fallback.markerId}`,
|
|
288
|
+
path: [
|
|
289
|
+
"playback",
|
|
290
|
+
"reducedMotion",
|
|
291
|
+
"markerId"
|
|
292
|
+
]
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const BoardCompositionInputSchema = z.object(compositionFields).strict().superRefine(validateCompositionFallback);
|
|
296
|
+
const BoardCompositionSchema = z.object({
|
|
297
|
+
...compositionFields,
|
|
298
|
+
revision: z.number().int().nonnegative().default(0)
|
|
299
|
+
}).strict().superRefine(validateCompositionFallback);
|
|
300
|
+
/** Accept inspect/get output as apply input while stripping server-owned revision. */
|
|
301
|
+
function parseBoardCompositionInput(value) {
|
|
302
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
303
|
+
const { revision: _revision, ...input } = value;
|
|
304
|
+
return BoardCompositionInputSchema.parse(input);
|
|
305
|
+
}
|
|
306
|
+
return BoardCompositionInputSchema.parse(value);
|
|
307
|
+
}
|
|
308
|
+
Object.entries(BOARD_ANIMATION_CHANNELS).map(([id, definition]) => ({
|
|
309
|
+
id,
|
|
310
|
+
version: 1,
|
|
311
|
+
targets: definition.targets,
|
|
312
|
+
interpolations: definition.interpolations,
|
|
313
|
+
..."coordinateSpace" in definition ? { coordinateSpace: definition.coordinateSpace } : {},
|
|
314
|
+
..."unit" in definition ? { unit: definition.unit } : {},
|
|
315
|
+
valueSchema: z.toJSONSchema(definition.value)
|
|
316
|
+
}));
|
|
317
|
+
//#endregion
|
|
318
|
+
export { BOARD_ANIMATION_CHANNELS, BOARD_EASINGS, BoardAnimationTargetSchema, BoardCompositionInputSchema, BoardCompositionPlaybackSchema, BoardCompositionSchema, BoardEasingSchema, BoardProceduralClipSchema, BoardTimelineMarkerSchema, BoardTimelineSchema, BoardTrackSchema, parseBoardCompositionInput };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region ../protocol/dist/board-constants.d.ts
|
|
2
|
+
type BoardCoordinateSpace = "world" | "frame-local" | "world-offset" | "screen" | "screen-offset" | "normalized";
|
|
2
3
|
type BoardRenderCost = {
|
|
3
4
|
particles: number;
|
|
4
5
|
vertices: number;
|
|
@@ -30,4 +31,4 @@ declare const BOARD_STROKE_MIN_SIZE = 1;
|
|
|
30
31
|
declare const BOARD_STROKE_MAX_SIZE = 64;
|
|
31
32
|
declare function clampBoardStrokeSize(size: number): number;
|
|
32
33
|
//#endregion
|
|
33
|
-
export { BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BoardCapability, BoardRenderCost, clampBoardStrokeSize };
|
|
34
|
+
export { BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BoardCapability, BoardCoordinateSpace, BoardRenderCost, clampBoardStrokeSize };
|
|
@@ -11,7 +11,6 @@ const DEFAULT_BOARD_RENDER_LIMITS = {
|
|
|
11
11
|
simulationSteps: 1e5
|
|
12
12
|
};
|
|
13
13
|
const BOARD_BUILTIN_CLIP_KINDS = [
|
|
14
|
-
"motion.keyframes",
|
|
15
14
|
"motion.path",
|
|
16
15
|
"draw.reveal",
|
|
17
16
|
"draw.handwrite",
|
|
@@ -20,9 +19,7 @@ const BOARD_BUILTIN_CLIP_KINDS = [
|
|
|
20
19
|
"effects.trail",
|
|
21
20
|
"effects.impact",
|
|
22
21
|
"effects.flash",
|
|
23
|
-
"
|
|
24
|
-
"camera.pan",
|
|
25
|
-
"camera.zoom",
|
|
22
|
+
"camera.focus",
|
|
26
23
|
"camera.shake"
|
|
27
24
|
];
|
|
28
25
|
const BOARD_BUILTIN_EFFECT_KINDS = ["effects.pulse", "effects.float"];
|
|
@@ -60,12 +57,45 @@ function clampBoardStrokeSize(size) {
|
|
|
60
57
|
*/
|
|
61
58
|
const BOARD_FONT_STACK = "\"Geist\", system-ui, -apple-system, \"Noto Sans CJK SC\", \"Noto Sans SC\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif";
|
|
62
59
|
const BOARD_MONO_FONT_STACK = "\"Geist Mono\", \"Fira Code\", ui-monospace, \"Noto Sans Mono CJK SC\", monospace";
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
60
|
+
function clipSchema(id) {
|
|
61
|
+
switch (id) {
|
|
62
|
+
case "motion.path": return { params: { points: {
|
|
63
|
+
coordinateSpace: "world-offset",
|
|
64
|
+
unit: "board"
|
|
65
|
+
} } };
|
|
66
|
+
case "effects.particles": return { params: { bounds: {
|
|
67
|
+
coordinateSpace: "world",
|
|
68
|
+
unit: "board"
|
|
69
|
+
} } };
|
|
70
|
+
case "camera.focus": return { params: {
|
|
71
|
+
focus: {
|
|
72
|
+
coordinateSpace: "world",
|
|
73
|
+
unit: "board"
|
|
74
|
+
},
|
|
75
|
+
padding: {
|
|
76
|
+
coordinateSpace: "screen",
|
|
77
|
+
unit: "css-px"
|
|
78
|
+
},
|
|
79
|
+
minZoom: { unit: "ratio" },
|
|
80
|
+
maxZoom: { unit: "ratio" }
|
|
81
|
+
} };
|
|
82
|
+
case "camera.shake": return { params: { amount: {
|
|
83
|
+
coordinateSpace: "screen-offset",
|
|
84
|
+
unit: "css-px"
|
|
85
|
+
} } };
|
|
86
|
+
default: return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const BOARD_BUILTIN_CAPABILITIES = [...BOARD_BUILTIN_CLIP_KINDS.map((id) => {
|
|
90
|
+
const schema = clipSchema(id);
|
|
91
|
+
return {
|
|
92
|
+
kind: "clip",
|
|
93
|
+
id,
|
|
94
|
+
version: 1,
|
|
95
|
+
renderers: ["webgpu", "webgl"],
|
|
96
|
+
...schema ? { schema } : {}
|
|
97
|
+
};
|
|
98
|
+
}), ...BOARD_BUILTIN_EFFECT_KINDS.map((id) => ({
|
|
69
99
|
kind: "effect",
|
|
70
100
|
id,
|
|
71
101
|
version: 1,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION } from "./board.js";
|
|
2
|
-
import { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, normalizeBoardRemoteUrl } from "./board-url.js";
|
|
2
|
+
import { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, isPublicBoardRemoteAddress, normalizeBoardRemoteUrl } from "./board-url.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region ../protocol/dist/board-document.d.ts
|
|
5
5
|
declare const BoardFrameSchema: z.ZodObject<{
|
|
@@ -33,6 +33,19 @@ declare const BoardAppearanceSchema: z.ZodObject<{
|
|
|
33
33
|
}>>;
|
|
34
34
|
color: z.ZodOptional<z.ZodString>;
|
|
35
35
|
imageUrl: z.ZodOptional<z.ZodString>;
|
|
36
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
37
|
+
contain: "contain";
|
|
38
|
+
cover: "cover";
|
|
39
|
+
repeat: "repeat";
|
|
40
|
+
}>>;
|
|
41
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
42
|
+
bottom: "bottom";
|
|
43
|
+
center: "center";
|
|
44
|
+
left: "left";
|
|
45
|
+
right: "right";
|
|
46
|
+
top: "top";
|
|
47
|
+
}>>;
|
|
48
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
36
49
|
}, z.core.$strip>>;
|
|
37
50
|
grid: z.ZodDefault<z.ZodObject<{
|
|
38
51
|
visible: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -1024,6 +1037,19 @@ declare const BoardDocumentSchema: z.ZodObject<{
|
|
|
1024
1037
|
}>>;
|
|
1025
1038
|
color: z.ZodOptional<z.ZodString>;
|
|
1026
1039
|
imageUrl: z.ZodOptional<z.ZodString>;
|
|
1040
|
+
fit: z.ZodOptional<z.ZodEnum<{
|
|
1041
|
+
contain: "contain";
|
|
1042
|
+
cover: "cover";
|
|
1043
|
+
repeat: "repeat";
|
|
1044
|
+
}>>;
|
|
1045
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
1046
|
+
bottom: "bottom";
|
|
1047
|
+
center: "center";
|
|
1048
|
+
left: "left";
|
|
1049
|
+
right: "right";
|
|
1050
|
+
top: "top";
|
|
1051
|
+
}>>;
|
|
1052
|
+
opacity: z.ZodOptional<z.ZodNumber>;
|
|
1027
1053
|
}, z.core.$strip>>;
|
|
1028
1054
|
grid: z.ZodDefault<z.ZodObject<{
|
|
1029
1055
|
visible: z.ZodDefault<z.ZodBoolean>;
|
|
@@ -1492,4 +1518,4 @@ declare function isMediaItem(item: BoardItem): item is BoardImageItem | BoardVid
|
|
|
1492
1518
|
/** Whether an item references a workspace file (media or file card). */
|
|
1493
1519
|
declare function isFileBackedItem(item: BoardItem): item is BoardImageItem | BoardVideoItem | BoardAudioItem | BoardFileItem;
|
|
1494
1520
|
//#endregion
|
|
1495
|
-
export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_REMOTE_URL_MAX_LENGTH, BOARD_TASK_ARTIFACT_LIMIT, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardAudioItem, BoardAudioItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardPoint, BoardPointSchema, BoardRemoteUrlSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
|
|
1521
|
+
export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_REMOTE_URL_MAX_LENGTH, BOARD_TASK_ARTIFACT_LIMIT, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardAudioItem, BoardAudioItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardPoint, BoardPointSchema, BoardRemoteUrlSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isPublicBoardRemoteAddress, isUnknownItem, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BOARD_ARROW_STROKE_SIZE } from "./board-constants.js";
|
|
2
2
|
import { BoardConnectionSchema } from "./board-connection.js";
|
|
3
3
|
import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION } from "./board.js";
|
|
4
|
-
import { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, normalizeBoardRemoteUrl } from "./board-url.js";
|
|
4
|
+
import { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, isPublicBoardRemoteAddress, normalizeBoardRemoteUrl } from "./board-url.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region ../protocol/dist/board-document.js
|
|
7
7
|
const BoardFrameSchema = z.object({
|
|
@@ -34,7 +34,20 @@ const BoardAppearanceSchema = z.object({
|
|
|
34
34
|
"custom"
|
|
35
35
|
]).default("dots"),
|
|
36
36
|
color: z.string().optional(),
|
|
37
|
-
imageUrl:
|
|
37
|
+
imageUrl: BoardRemoteUrlSchema.optional(),
|
|
38
|
+
fit: z.enum([
|
|
39
|
+
"cover",
|
|
40
|
+
"contain",
|
|
41
|
+
"repeat"
|
|
42
|
+
]).optional(),
|
|
43
|
+
position: z.enum([
|
|
44
|
+
"center",
|
|
45
|
+
"top",
|
|
46
|
+
"bottom",
|
|
47
|
+
"left",
|
|
48
|
+
"right"
|
|
49
|
+
]).optional(),
|
|
50
|
+
opacity: z.number().finite().min(0).max(1).optional()
|
|
38
51
|
}).default({ kind: "solid" }),
|
|
39
52
|
grid: z.object({
|
|
40
53
|
visible: z.boolean().default(false),
|
|
@@ -435,4 +448,4 @@ function isFileBackedItem(item) {
|
|
|
435
448
|
return item.type === "image" || item.type === "video" || item.type === "audio" || item.type === "file";
|
|
436
449
|
}
|
|
437
450
|
//#endregion
|
|
438
|
-
export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_REMOTE_URL_MAX_LENGTH, BOARD_TASK_ARTIFACT_LIMIT, BoardAppearanceSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardPointSchema, BoardRemoteUrlSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
|
|
451
|
+
export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_REMOTE_URL_MAX_LENGTH, BOARD_TASK_ARTIFACT_LIMIT, BoardAppearanceSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardPointSchema, BoardRemoteUrlSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isPublicBoardRemoteAddress, isUnknownItem, normalizeBoardRemoteUrl, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
|
|
@@ -4,15 +4,5 @@ declare const BOARD_COLOR_IDS: readonly ["brand", "neutral", "black", "white", "
|
|
|
4
4
|
type BoardColorId = (typeof BOARD_COLOR_IDS)[number];
|
|
5
5
|
declare const BOARD_GEO_KINDS: readonly ["rectangle", "rounded", "ellipse", "diamond", "triangle"];
|
|
6
6
|
type BoardGeoKind = (typeof BOARD_GEO_KINDS)[number];
|
|
7
|
-
type BoardNodeValidationDiagnostic = {
|
|
8
|
-
severity: "error";
|
|
9
|
-
code: "INVALID_BOARD_NODE" | "INVALID_BOARD_GEOMETRY";
|
|
10
|
-
message: string;
|
|
11
|
-
path: string;
|
|
12
|
-
expected?: string;
|
|
13
|
-
received?: unknown;
|
|
14
|
-
allowedValues?: readonly string[];
|
|
15
|
-
coordinateSpace?: "frame-local" | "world";
|
|
16
|
-
};
|
|
17
7
|
//#endregion
|
|
18
|
-
export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BoardColorId, BoardGeoKind
|
|
8
|
+
export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BoardColorId, BoardGeoKind };
|
|
@@ -155,85 +155,5 @@ function jsonSchema(schema) {
|
|
|
155
155
|
return z.toJSONSchema(schema);
|
|
156
156
|
}
|
|
157
157
|
jsonSchema(nodeEnvelopeSchema), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]));
|
|
158
|
-
function issueDiagnostic(issue, path) {
|
|
159
|
-
const fullPath = [path, ...issue.path].join(".");
|
|
160
|
-
const values = "values" in issue && Array.isArray(issue.values) ? issue.values.filter((value) => typeof value === "string") : void 0;
|
|
161
|
-
return {
|
|
162
|
-
severity: "error",
|
|
163
|
-
code: "INVALID_BOARD_NODE",
|
|
164
|
-
message: `${fullPath}: ${issue.message}`,
|
|
165
|
-
path: fullPath,
|
|
166
|
-
...values?.length ? { allowedValues: values } : {}
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function drawGeometryDiagnostic(node, data, path) {
|
|
170
|
-
let minX = Number.POSITIVE_INFINITY;
|
|
171
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
172
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
173
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
174
|
-
for (const point of data.points) {
|
|
175
|
-
const radius = Math.max(.5, data.size / 2 * (.5 + point.p));
|
|
176
|
-
minX = Math.min(minX, point.x - radius);
|
|
177
|
-
minY = Math.min(minY, point.y - radius);
|
|
178
|
-
maxX = Math.max(maxX, point.x + radius);
|
|
179
|
-
maxY = Math.max(maxY, point.y + radius);
|
|
180
|
-
}
|
|
181
|
-
const width = Math.max(1, maxX - minX);
|
|
182
|
-
const height = Math.max(1, maxY - minY);
|
|
183
|
-
const tolerance = Math.max(.01, node.width * 1e-6, node.height * 1e-6);
|
|
184
|
-
if (Math.abs(minX) <= tolerance && Math.abs(minY) <= tolerance && Math.abs(width - node.width) <= tolerance && Math.abs(height - node.height) <= tolerance) return null;
|
|
185
|
-
return {
|
|
186
|
-
severity: "error",
|
|
187
|
-
code: "INVALID_BOARD_GEOMETRY",
|
|
188
|
-
message: `${path}.data.points must use frame-local coordinates and match the node frame`,
|
|
189
|
-
path: `${path}.data.points`,
|
|
190
|
-
expected: "frame-local points with bounds matching width and height",
|
|
191
|
-
coordinateSpace: "frame-local"
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function validateBoardNodeInput(node, path = "node") {
|
|
195
|
-
const envelopeResult = nodeEnvelopeSchema.safeParse(node);
|
|
196
|
-
if (!envelopeResult.success) return envelopeResult.error.issues.map((issue) => issueDiagnostic(issue, path));
|
|
197
|
-
if (typeof node.type !== "string" || !BOARD_NATIVE_NODE_TYPES.includes(node.type)) return [{
|
|
198
|
-
severity: "error",
|
|
199
|
-
code: "INVALID_BOARD_NODE",
|
|
200
|
-
message: `${path}.type is not supported`,
|
|
201
|
-
path: `${path}.type`,
|
|
202
|
-
expected: "BoardNativeNodeType",
|
|
203
|
-
received: node.type,
|
|
204
|
-
allowedValues: BOARD_NATIVE_NODE_TYPES
|
|
205
|
-
}];
|
|
206
|
-
const type = node.type;
|
|
207
|
-
const dataResult = dataSchemas[type].safeParse(node.data ?? {});
|
|
208
|
-
if (!dataResult.success) return dataResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.data`));
|
|
209
|
-
const viewResult = viewSchemas[type].safeParse(node.view ?? {});
|
|
210
|
-
if (!viewResult.success) return viewResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.view`));
|
|
211
|
-
if (type === "image" || type === "video" || type === "audio" || type === "file") {
|
|
212
|
-
if (node.refKind !== "space_file" || typeof node.refPath !== "string" || !node.refPath) return [{
|
|
213
|
-
severity: "error",
|
|
214
|
-
code: "INVALID_BOARD_NODE",
|
|
215
|
-
message: `${path} requires a space file reference`,
|
|
216
|
-
path: `${path}.refPath`,
|
|
217
|
-
expected: "non-empty refPath with refKind space_file"
|
|
218
|
-
}];
|
|
219
|
-
}
|
|
220
|
-
if (type === "draw") {
|
|
221
|
-
const diagnostic = drawGeometryDiagnostic(node, dataResult.data, path);
|
|
222
|
-
return diagnostic ? [diagnostic] : [];
|
|
223
|
-
}
|
|
224
|
-
if (type === "arrow") {
|
|
225
|
-
const data = dataResult.data;
|
|
226
|
-
const inside = (point) => point.x >= node.x && point.x <= node.x + node.width && point.y >= node.y && point.y <= node.y + node.height;
|
|
227
|
-
if (!inside(data.start) || !inside(data.end)) return [{
|
|
228
|
-
severity: "error",
|
|
229
|
-
code: "INVALID_BOARD_GEOMETRY",
|
|
230
|
-
message: `${path}.data endpoints must be covered by the node frame`,
|
|
231
|
-
path: `${path}.data`,
|
|
232
|
-
expected: "world-space endpoints inside the node frame",
|
|
233
|
-
coordinateSpace: "world"
|
|
234
|
-
}];
|
|
235
|
-
}
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
158
|
//#endregion
|
|
239
|
-
export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BoardColorIdSchema, BoardGeoKindSchema
|
|
159
|
+
export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BoardColorIdSchema, BoardGeoKindSchema };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./board.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
//#region ../protocol/dist/board-url.d.ts
|
|
3
3
|
declare const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
4
|
+
declare function isPublicBoardRemoteAddress(value: string): boolean;
|
|
4
5
|
/**
|
|
5
6
|
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
6
7
|
* addresses; any future server-side fetcher must additionally validate DNS
|
|
@@ -9,4 +10,4 @@ declare const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
|
9
10
|
declare function normalizeBoardRemoteUrl(value: unknown): string | undefined;
|
|
10
11
|
declare const BoardRemoteUrlSchema: z.ZodString;
|
|
11
12
|
//#endregion
|
|
12
|
-
export { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, normalizeBoardRemoteUrl };
|
|
13
|
+
export { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, isPublicBoardRemoteAddress, normalizeBoardRemoteUrl };
|
|
@@ -53,11 +53,16 @@ function isBlockedIpv6(host) {
|
|
|
53
53
|
if ((first & 65280) === 65280) return true;
|
|
54
54
|
return parts[0] === "2001" && parts[1] === "0db8";
|
|
55
55
|
}
|
|
56
|
+
function isPublicBoardRemoteAddress(value) {
|
|
57
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
58
|
+
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
59
|
+
return address.includes(":") && !isBlockedIpv6(address);
|
|
60
|
+
}
|
|
56
61
|
function isBlockedHost(hostname) {
|
|
57
62
|
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
58
63
|
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
59
|
-
if (parseIpv4(host)) return
|
|
60
|
-
return
|
|
64
|
+
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
65
|
+
return false;
|
|
61
66
|
}
|
|
62
67
|
/**
|
|
63
68
|
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
@@ -80,4 +85,4 @@ function normalizeBoardRemoteUrl(value) {
|
|
|
80
85
|
}
|
|
81
86
|
const BoardRemoteUrlSchema = z.string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
82
87
|
//#endregion
|
|
83
|
-
export { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, normalizeBoardRemoteUrl };
|
|
88
|
+
export { BOARD_REMOTE_URL_MAX_LENGTH, BoardRemoteUrlSchema, isPublicBoardRemoteAddress, normalizeBoardRemoteUrl };
|