@neta-art/cohub 8.23.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board/animation.d.ts +57 -181
- package/dist/board/animation.js +374 -220
- package/dist/board/core/arrow-geometry.d.ts +33 -18
- package/dist/board/core/arrow-geometry.js +283 -52
- package/dist/board/core/draw-geometry.d.ts +22 -36
- package/dist/board/core/draw-geometry.js +148 -85
- package/dist/board/core/export-assets.d.ts +3 -6
- package/dist/board/core/export-assets.js +0 -2
- package/dist/board/core/export-plan.d.ts +12 -63
- package/dist/board/core/export-plan.js +55 -94
- package/dist/board/core/palette.d.ts +4 -21
- package/dist/board/core/palette.js +8 -13
- package/dist/board/core/scene.d.ts +33 -0
- package/dist/board/core/scene.js +302 -0
- package/dist/board/core/shape-definition.d.ts +12 -24
- package/dist/board/core/shape-definition.js +0 -19
- package/dist/board/core/shape-types.d.ts +6 -120
- package/dist/board/core/shape-types.js +5 -5
- package/dist/board/core/text-metrics.d.ts +8 -11
- package/dist/board/core/text-metrics.js +5 -34
- package/dist/board/core/tool-styles.d.ts +6 -16
- package/dist/board/core/tool-styles.js +17 -25
- package/dist/board/export/index.d.ts +10 -17
- package/dist/board/export/index.js +22 -21
- package/dist/board/export/scene.d.ts +10 -16
- package/dist/board/export/scene.js +17 -30
- package/dist/board/geometry.d.ts +27 -56
- package/dist/board/geometry.js +7 -67
- package/dist/board/headless/index.d.ts +7 -27
- package/dist/board/headless/index.js +20 -19
- package/dist/board/headless/sketch.d.ts +19 -0
- package/dist/board/headless/sketch.js +146 -0
- package/dist/board/image-key.d.ts +2 -12
- package/dist/board/image-key.js +3 -13
- package/dist/board/index.d.ts +18 -20
- package/dist/board/index.js +17 -17
- package/dist/board/media-playback.d.ts +3 -6
- package/dist/board/media-playback.js +6 -6
- package/dist/board/presets.d.ts +150 -0
- package/dist/board/presets.js +226 -0
- package/dist/board/render/board-background.d.ts +5 -3
- package/dist/board/render/board-background.js +8 -7
- package/dist/board/render/clip.d.ts +14 -0
- package/dist/board/render/clip.js +47 -0
- package/dist/board/render/index.d.ts +8 -5
- package/dist/board/render/index.js +7 -4
- package/dist/board/render/media-interaction.d.ts +4 -5
- package/dist/board/render/media-interaction.js +1 -2
- package/dist/board/render/palette.d.ts +6 -2
- package/dist/board/render/palette.js +7 -1
- package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
- package/dist/board/render/renderers/audio-card-renderer.js +3 -3
- package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
- package/dist/board/render/renderers/base-card-renderer.js +1 -24
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
- package/dist/board/render/renderers/board-renderer-registry.js +6 -7
- package/dist/board/render/renderers/draw-card-renderer.js +89 -56
- package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
- package/dist/board/render/renderers/effect-card-renderer.js +197 -0
- package/dist/board/render/renderers/far-plate.d.ts +4 -21
- package/dist/board/render/renderers/far-plate.js +15 -34
- package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
- package/dist/board/render/renderers/file-card-renderer.js +8 -58
- package/dist/board/render/renderers/frame-card-renderer.js +28 -28
- package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
- package/dist/board/render/renderers/image-card-renderer.js +0 -9
- package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
- package/dist/board/render/renderers/shape-card-renderer.js +240 -0
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
- package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
- package/dist/board/render/renderers/task-card-renderer.js +14 -16
- package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
- package/dist/board/render/renderers/text-card-renderer.js +56 -46
- package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
- package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sketch-host.d.ts +14 -0
- package/dist/board/render/sketch-host.js +216 -0
- package/dist/board/render/stroke.d.ts +12 -0
- package/dist/board/render/stroke.js +90 -0
- package/dist/board/render/text-measurement.d.ts +6 -26
- package/dist/board/render/text-measurement.js +11 -30
- package/dist/board/replay.d.ts +6 -31
- package/dist/board/replay.js +45 -203
- package/dist/board/runtime/board-player.d.ts +24 -0
- package/dist/board/runtime/board-player.js +161 -0
- package/dist/board/task.d.ts +2 -10
- package/dist/board/task.js +1 -10
- package/dist/chunks/environment.d.ts +2296 -3864
- package/dist/chunks/host-bridge.d.ts +253 -0
- package/dist/chunks/http.d.ts +238 -58
- package/dist/chunks/http.js +1061 -1034
- package/dist/chunks/transport.js +113 -59
- package/dist/chunks/websocket.d.ts +1 -1
- package/dist/chunks/websocket.js +1 -1
- package/dist/host-bridge.d.ts +54 -0
- package/dist/host-bridge.js +322 -0
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +76 -193
- package/dist/index.js +2037 -1191
- package/dist/protocol/dist/board-constants.d.ts +1 -29
- package/dist/protocol/dist/board-constants.js +2 -115
- package/dist/protocol/dist/board-layout.d.ts +59 -0
- package/dist/protocol/dist/board-layout.js +482 -0
- package/dist/protocol/dist/board-model.d.ts +2052 -0
- package/dist/protocol/dist/board-model.js +592 -0
- package/dist/protocol/dist/board-patch.d.ts +86 -0
- package/dist/protocol/dist/board-patch.js +734 -0
- package/dist/protocol/dist/board-schema.d.ts +33 -0
- package/dist/protocol/dist/board-schema.js +59 -0
- package/dist/protocol/dist/board-upgrade.d.ts +1 -0
- package/dist/protocol/dist/board-url.d.ts +1 -3
- package/dist/protocol/dist/board-url.js +2 -2
- package/dist/protocol/dist/board.d.ts +37 -232
- package/dist/protocol/dist/board.js +62 -160
- package/dist/protocol/dist/display/actions.d.ts +1 -0
- package/dist/protocol/dist/display/index.d.ts +2 -0
- package/dist/protocol/dist/index.d.ts +11 -12
- package/dist/protocol/dist/model/metrics.d.ts +2 -0
- package/dist/protocol/dist/model/session.d.ts +2 -1
- package/dist/protocol/dist/realtime/types.d.ts +2 -2
- package/dist/protocol/dist/runtime/index.d.ts +1 -0
- package/dist/space-picker.d.ts +1 -0
- package/dist/space-picker.js +1 -0
- package/dist/types.d.ts +2 -5
- package/package.json +9 -1
- package/dist/board/core/connections.d.ts +0 -108
- package/dist/board/core/connections.js +0 -455
- package/dist/board/mutation.d.ts +0 -6
- package/dist/board/mutation.js +0 -14
- package/dist/board/render/connection-layer.d.ts +0 -53
- package/dist/board/render/connection-layer.js +0 -375
- package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
- package/dist/board/render/renderers/geo-card-renderer.js +0 -127
- package/dist/board/semantic-document.d.ts +0 -37
- package/dist/board/semantic-document.js +0 -288
- package/dist/board/semantic-mutation.d.ts +0 -10
- package/dist/board/semantic-mutation.js +0 -209
- package/dist/protocol/dist/board-animation.js +0 -34
- package/dist/protocol/dist/board-authoring.d.ts +0 -1326
- package/dist/protocol/dist/board-authoring.js +0 -302
- package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
- package/dist/protocol/dist/board-capability-registry.js +0 -123
- package/dist/protocol/dist/board-codec.d.ts +0 -3
- package/dist/protocol/dist/board-codec.js +0 -383
- package/dist/protocol/dist/board-composition.d.ts +0 -383
- package/dist/protocol/dist/board-composition.js +0 -310
- package/dist/protocol/dist/board-connection.d.ts +0 -317
- package/dist/protocol/dist/board-connection.js +0 -219
- package/dist/protocol/dist/board-document.d.ts +0 -1566
- package/dist/protocol/dist/board-document.js +0 -456
- package/dist/protocol/dist/board-effect.d.ts +0 -96
- package/dist/protocol/dist/board-effect.js +0 -54
- package/dist/protocol/dist/board-geometry.d.ts +0 -15
- package/dist/protocol/dist/board-geometry.js +0 -137
- package/dist/protocol/dist/board-node.d.ts +0 -8
- package/dist/protocol/dist/board-node.js +0 -252
- /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import {
|
|
1
|
+
import { $ as resolveOpenSurface, A as ReferencesApi, B as DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, C as SessionPatchReducer, D as connectDisplay, E as DisplayConnection, F as ModelsApi, G as DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS, H as DESKTOP_COMMAND_OPEN_TIMEOUT_MS, I as GenerationsApi, J as defaultDesktopCommandTimeoutMs, K as DESKTOP_COMMAND_TERMINAL_TTL_SECONDS, L as CronJobsApi, M as PublicAssetsApi, N as SkillsApi, O as ensureRealtimeConnected, P as PromptsApi, Q as parseDesktopCommandId, R as ChannelsApi, S as parseAssistantMessageCommit, T as SpaceDisplaysApi, U as DESKTOP_COMMAND_PAYLOAD_MAX_BYTES, V as DESKTOP_COMMAND_MAX_TIMEOUT_MS, W as DESKTOP_COMMAND_PENDING_TTL_SECONDS, X as isTerminalDesktopCommandStatus, Y as isDesktopCallMethod, Z as parseDesktopCommand, _ as PublicInviteApi, a as DesktopCommandsApi, at as normalizeDisplayPoint, b as SessionGenerationStreamClient, c as ReferralsApi, d as TasksApi, et as NAVIGATION_ERROR_MESSAGE_MAX_LENGTH, f as BoardClient, g as SpacesApi, h as SpaceWebhooksApi, i as WorkCommerceApi, it as DISPLAY_SYSTEM_ACTIONS, j as SearchApi, k as SessionAccessApi, l as UsersApi, m as SpacePublicFilesApi, n as createHttpClient, o as UiCommandsApi, ot as splitDisplayText, p as SpaceClient, q as DESKTOP_COMMAND_VERSION, r as AppCommerceApi, rt as DISPLAY_INPUT_MAX_TEXT, s as AppsApi, t as CohubHttpClient, tt as DISPLAY_ELEMENT_REF_PATTERN, u as UserApi, v as buildSpaceInvitePath, w as createSessionPatchReducer, x as createSessionGenerationStreamClient, y as buildSpacePath, z as DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS } from "./chunks/http.js";
|
|
2
|
+
import { A as isUuid, C as isRequestSourceUuid, D as readRequestSourceFromEnv, E as parseRequestSourceFromHeaders, M as isUuidOrShortUuid, O as requestSourceToHeaders, S as isRequestSourceEmpty, T as normalizeRequestSource, _ as COHUB_SOURCE_HEADER, a as sanitizeAccessToken, b as hasRequestSourceIdentity, i as matchesUnauthorizedErrorToken, j as isUuidLike, k as resolveRequestSourceChannel, n as HttpTransport, o as REALTIME_ROOM_EVENT_NAME_PATTERN, r as joinApiUrl, t as HttpError, v as COHUB_SOURCE_HEADER_NAMES, w as mergeRequestSourceIntoMeta, x as isRequestSourceClientId, y as REQUEST_SOURCE_VIA_MAX_LENGTH } from "./chunks/transport.js";
|
|
3
3
|
import { a as normalizeWebsocketUrl, c as resolveExecutionAppId, d as resolveWebBaseUrl, f as resolveWebsocketUrl, g as scopeListHasPermission, h as isUserLevelPermission, i as normalizeVoiceInputWebsocketUrl, l as resolveExecutionToken, m as USER_LEVEL_PERMISSIONS, n as getCohubContext, o as resolveApiBaseUrl, p as PERMISSIONS, r as normalizeBaseUrl, s as resolveCohubEnvironment, t as COHUB_ENVIRONMENTS, u as resolveVoiceInputWebsocketUrl } from "./chunks/environment.js";
|
|
4
4
|
import { a as extractBillingPayload, c as isFeatureNotEntitledError, i as FEATURE_NOT_ENTITLED_ERROR_CODE, l as isHttpErrorCode, n as createWebsocketClient, o as isBillingAccessBlockedCode, r as BILLING_ACCESS_BLOCKED_ERROR_CODE, s as isBillingAccessBlockedError, t as WebsocketClient } from "./chunks/websocket.js";
|
|
5
5
|
import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
|
|
@@ -71,335 +71,1579 @@ const appAuthorizationResultSchema = z.discriminatedUnion("status", [
|
|
|
71
71
|
code: z.string()
|
|
72
72
|
})
|
|
73
73
|
]);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
74
|
+
const GiB = 1073741824;
|
|
75
|
+
/** Maximum size of a single uploaded file. */
|
|
76
|
+
const UPLOAD_MAX_FILE_BYTES = 1 * GiB;
|
|
77
|
+
/** Maximum combined size of one upload batch. */
|
|
78
|
+
const UPLOAD_MAX_BATCH_BYTES = 2 * GiB;
|
|
79
|
+
/** Maximum file count of one upload batch. */
|
|
80
|
+
const UPLOAD_MAX_BATCH_FILES = 2e3;
|
|
81
|
+
const BOARD_CAMERA_TARGET = "camera";
|
|
82
|
+
const BoardIdSchema = z.string().min(1).max(160).regex(/^[^\s/]+$/, "ids must not contain whitespace or '/'").refine((value) => value !== BOARD_CAMERA_TARGET, "\"camera\" is reserved");
|
|
83
|
+
const finite$1 = z.number().finite();
|
|
84
|
+
const nonNegative = finite$1.nonnegative();
|
|
85
|
+
const ratio = finite$1.min(0).max(1);
|
|
86
|
+
const BoardVec2Schema = z.object({
|
|
87
|
+
x: finite$1,
|
|
88
|
+
y: finite$1
|
|
89
|
+
}).strict();
|
|
90
|
+
const BoardSizeSchema = z.object({
|
|
91
|
+
width: finite$1.positive(),
|
|
92
|
+
height: finite$1.positive()
|
|
93
|
+
}).strict();
|
|
94
|
+
const BOARD_COLOR_TOKENS = [
|
|
95
|
+
"brand",
|
|
96
|
+
"neutral",
|
|
97
|
+
"black",
|
|
98
|
+
"white",
|
|
99
|
+
"blue",
|
|
100
|
+
"green",
|
|
101
|
+
"amber",
|
|
102
|
+
"violet",
|
|
103
|
+
"rose"
|
|
104
|
+
];
|
|
105
|
+
const CSS_NAMED_COLORS = new Set("transparent aliceblue antiquewhite aqua aquamarine azure beige bisque blanchedalmond blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray grey greenyellow honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat whitesmoke yellow yellowgreen".split(" "));
|
|
106
|
+
function isBoardColorString(value) {
|
|
107
|
+
const color = value.trim().toLowerCase();
|
|
108
|
+
return BOARD_COLOR_TOKENS.includes(color) || CSS_NAMED_COLORS.has(color) || /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(color) || /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/.test(color);
|
|
109
|
+
}
|
|
110
|
+
const colorString = z.string().max(120).refine(isBoardColorString, "expected a palette token or a CSS color");
|
|
111
|
+
const BoardColorSchema = z.union([colorString, z.object({
|
|
112
|
+
light: colorString,
|
|
113
|
+
dark: colorString
|
|
114
|
+
}).strict()]);
|
|
115
|
+
const BoardStyleSchema = z.object({
|
|
116
|
+
fill: BoardColorSchema.optional(),
|
|
117
|
+
fillOpacity: ratio.optional(),
|
|
118
|
+
stroke: BoardColorSchema.optional(),
|
|
119
|
+
strokeWidth: finite$1.min(0).max(64).optional(),
|
|
120
|
+
dash: z.enum([
|
|
121
|
+
"solid",
|
|
122
|
+
"dashed",
|
|
123
|
+
"dotted"
|
|
124
|
+
]).optional(),
|
|
125
|
+
radius: nonNegative.optional(),
|
|
126
|
+
trim: ratio.optional()
|
|
127
|
+
}).strict();
|
|
128
|
+
function isSafeBoardSourcePath(value) {
|
|
129
|
+
return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
|
|
130
|
+
}
|
|
131
|
+
const srcSchema = z.string().refine(isSafeBoardSourcePath, "src must be a relative Space file path");
|
|
132
|
+
const itemEnvelope = {
|
|
133
|
+
parent: BoardIdSchema.optional(),
|
|
134
|
+
z: finite$1.optional(),
|
|
135
|
+
position: BoardVec2Schema.default({
|
|
136
|
+
x: 0,
|
|
137
|
+
y: 0
|
|
138
|
+
}),
|
|
139
|
+
rotation: finite$1.default(0),
|
|
140
|
+
scale: z.union([finite$1, BoardVec2Schema]).default(1),
|
|
141
|
+
origin: BoardVec2Schema.default({
|
|
142
|
+
x: .5,
|
|
143
|
+
y: .5
|
|
144
|
+
}),
|
|
145
|
+
opacity: ratio.default(1),
|
|
146
|
+
style: BoardStyleSchema.default({}),
|
|
147
|
+
locked: z.boolean().optional(),
|
|
148
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
149
|
+
};
|
|
150
|
+
const boxed = (width, height) => ({
|
|
151
|
+
...itemEnvelope,
|
|
152
|
+
size: BoardSizeSchema.default({
|
|
153
|
+
width,
|
|
154
|
+
height
|
|
155
|
+
})
|
|
91
156
|
});
|
|
92
|
-
const
|
|
157
|
+
const unsized = { ...itemEnvelope };
|
|
158
|
+
const fontSize = finite$1.min(2).max(512);
|
|
159
|
+
const BOARD_TEXT_ALIGNS = [
|
|
160
|
+
"left",
|
|
161
|
+
"center",
|
|
162
|
+
"right"
|
|
163
|
+
];
|
|
164
|
+
const BOARD_SHAPE_GEOMETRIES = [
|
|
165
|
+
"rectangle",
|
|
166
|
+
"rounded",
|
|
167
|
+
"ellipse",
|
|
168
|
+
"diamond",
|
|
169
|
+
"triangle",
|
|
170
|
+
"path"
|
|
171
|
+
];
|
|
172
|
+
const BoardFrameItemSchema = z.object({
|
|
173
|
+
...boxed(480, 320),
|
|
174
|
+
type: z.literal("frame"),
|
|
175
|
+
props: z.object({
|
|
176
|
+
label: z.string().max(280).default(""),
|
|
177
|
+
clip: z.boolean().default(true)
|
|
178
|
+
}).strict().prefault({})
|
|
179
|
+
}).strict();
|
|
180
|
+
const BoardTextItemSchema = z.object({
|
|
181
|
+
...unsized,
|
|
182
|
+
type: z.literal("text"),
|
|
183
|
+
props: z.object({
|
|
184
|
+
text: z.string().default(""),
|
|
185
|
+
fontSize: fontSize.default(24),
|
|
186
|
+
fontWeight: finite$1.min(100).max(900).default(500),
|
|
187
|
+
font: z.string().min(1).max(120).default("sans"),
|
|
188
|
+
align: z.enum(BOARD_TEXT_ALIGNS).default("left"),
|
|
189
|
+
lineHeight: finite$1.min(.5).max(4).default(4 / 3),
|
|
190
|
+
width: finite$1.positive().optional(),
|
|
191
|
+
reveal: ratio.default(1),
|
|
192
|
+
caret: z.boolean().default(false)
|
|
193
|
+
}).strict().prefault({})
|
|
194
|
+
}).strict();
|
|
195
|
+
const BoardShapeItemSchema = z.object({
|
|
196
|
+
...boxed(240, 160),
|
|
197
|
+
type: z.literal("shape"),
|
|
198
|
+
props: z.object({
|
|
199
|
+
geometry: z.enum(BOARD_SHAPE_GEOMETRIES).default("rectangle"),
|
|
200
|
+
path: z.string().min(1).max(2e5).optional(),
|
|
201
|
+
text: z.string().default(""),
|
|
202
|
+
fontSize: fontSize.default(20),
|
|
203
|
+
align: z.enum(BOARD_TEXT_ALIGNS).default("center")
|
|
204
|
+
}).strict().refine((props) => props.geometry !== "path" || Boolean(props.path), {
|
|
205
|
+
message: "path geometry requires props.path",
|
|
206
|
+
path: ["path"]
|
|
207
|
+
}).prefault({})
|
|
208
|
+
}).strict();
|
|
209
|
+
const BoardDrawPointSchema = z.object({
|
|
210
|
+
x: finite$1,
|
|
211
|
+
y: finite$1,
|
|
212
|
+
p: ratio.default(.5)
|
|
213
|
+
}).strict();
|
|
214
|
+
const BoardDrawItemSchema = z.object({
|
|
215
|
+
...unsized,
|
|
216
|
+
type: z.literal("draw"),
|
|
217
|
+
props: z.object({ points: z.array(BoardDrawPointSchema).min(1).max(1e5) }).strict()
|
|
218
|
+
}).strict();
|
|
219
|
+
const BoardArrowAnchorSchema = z.union([
|
|
220
|
+
z.literal("auto"),
|
|
93
221
|
z.object({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}),
|
|
222
|
+
side: z.enum([
|
|
223
|
+
"top",
|
|
224
|
+
"right",
|
|
225
|
+
"bottom",
|
|
226
|
+
"left"
|
|
227
|
+
]),
|
|
228
|
+
offset: ratio.default(.5)
|
|
229
|
+
}).strict(),
|
|
102
230
|
z.object({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
231
|
+
x: ratio,
|
|
232
|
+
y: ratio
|
|
233
|
+
}).strict()
|
|
234
|
+
]);
|
|
235
|
+
const BoardArrowBindingSchema = z.object({
|
|
236
|
+
item: BoardIdSchema,
|
|
237
|
+
anchor: BoardArrowAnchorSchema.default("auto"),
|
|
238
|
+
port: z.string().min(1).max(120).optional()
|
|
239
|
+
}).strict();
|
|
240
|
+
const BoardArrowEndSchema = z.union([BoardVec2Schema, BoardArrowBindingSchema]);
|
|
241
|
+
const BOARD_ARROW_ROUTES = [
|
|
242
|
+
"straight",
|
|
243
|
+
"curve",
|
|
244
|
+
"orthogonal"
|
|
245
|
+
];
|
|
246
|
+
const BoardArrowItemSchema = z.object({
|
|
247
|
+
...unsized,
|
|
248
|
+
type: z.literal("arrow"),
|
|
249
|
+
props: z.object({
|
|
250
|
+
start: BoardArrowEndSchema,
|
|
251
|
+
end: BoardArrowEndSchema,
|
|
252
|
+
route: z.enum(BOARD_ARROW_ROUTES).default("straight"),
|
|
253
|
+
bend: finite$1.min(-.85).max(.85).default(0),
|
|
254
|
+
waypoints: z.array(BoardVec2Schema).max(64).default([]),
|
|
255
|
+
arrowStart: z.boolean().default(false),
|
|
256
|
+
arrowEnd: z.boolean().default(true),
|
|
257
|
+
label: z.string().max(280).default(""),
|
|
258
|
+
fontSize: fontSize.default(14),
|
|
259
|
+
relation: z.string().max(64).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, "relation must be a lowercase slug").optional()
|
|
260
|
+
}).strict()
|
|
261
|
+
}).strict();
|
|
262
|
+
const fileProps = {
|
|
263
|
+
src: srcSchema,
|
|
264
|
+
snapshot: z.object({
|
|
265
|
+
title: z.string().optional(),
|
|
266
|
+
mimeType: z.string().optional(),
|
|
267
|
+
size: nonNegative.optional(),
|
|
268
|
+
mtimeMs: nonNegative.optional(),
|
|
269
|
+
naturalWidth: finite$1.positive().optional(),
|
|
270
|
+
naturalHeight: finite$1.positive().optional(),
|
|
271
|
+
durationMs: nonNegative.optional(),
|
|
272
|
+
excerpt: z.string().optional(),
|
|
273
|
+
coverPath: z.string().optional(),
|
|
274
|
+
coverUrl: z.string().optional()
|
|
275
|
+
}).strict().optional()
|
|
276
|
+
};
|
|
277
|
+
const timedFileProps = {
|
|
278
|
+
...fileProps,
|
|
279
|
+
time: nonNegative.optional()
|
|
280
|
+
};
|
|
281
|
+
const BoardCropSchema = z.object({
|
|
282
|
+
x: ratio,
|
|
283
|
+
y: ratio,
|
|
284
|
+
w: ratio,
|
|
285
|
+
h: ratio
|
|
286
|
+
}).strict();
|
|
287
|
+
const BoardImageItemSchema = z.object({
|
|
288
|
+
...boxed(640, 360),
|
|
289
|
+
type: z.literal("image"),
|
|
290
|
+
props: z.object({
|
|
291
|
+
...fileProps,
|
|
292
|
+
crop: BoardCropSchema.optional()
|
|
293
|
+
}).strict()
|
|
294
|
+
}).strict();
|
|
295
|
+
const BoardVideoItemSchema = z.object({
|
|
296
|
+
...boxed(640, 360),
|
|
297
|
+
type: z.literal("video"),
|
|
298
|
+
props: z.object(timedFileProps).strict()
|
|
299
|
+
}).strict();
|
|
300
|
+
const BoardAudioItemSchema = z.object({
|
|
301
|
+
...boxed(480, 96),
|
|
302
|
+
type: z.literal("audio"),
|
|
303
|
+
props: z.object(timedFileProps).strict()
|
|
304
|
+
}).strict();
|
|
305
|
+
const BoardFileItemSchema = z.object({
|
|
306
|
+
...boxed(360, 220),
|
|
307
|
+
type: z.literal("file"),
|
|
308
|
+
props: z.object(fileProps).strict()
|
|
309
|
+
}).strict();
|
|
310
|
+
const httpsUrl = z.string().max(4096).url();
|
|
311
|
+
const artifactBase = {
|
|
312
|
+
id: z.string().min(1).max(240),
|
|
313
|
+
title: z.string().max(240).optional(),
|
|
314
|
+
url: httpsUrl,
|
|
315
|
+
mimeType: z.string().max(160).optional()
|
|
316
|
+
};
|
|
317
|
+
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
106
318
|
z.object({
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
319
|
+
...artifactBase,
|
|
320
|
+
type: z.literal("image"),
|
|
321
|
+
naturalWidth: finite$1.positive().optional(),
|
|
322
|
+
naturalHeight: finite$1.positive().optional()
|
|
323
|
+
}).strict(),
|
|
110
324
|
z.object({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
325
|
+
...artifactBase,
|
|
326
|
+
type: z.literal("video"),
|
|
327
|
+
previewUrl: httpsUrl.optional(),
|
|
328
|
+
durationMs: z.number().int().positive().optional(),
|
|
329
|
+
naturalWidth: finite$1.positive().optional(),
|
|
330
|
+
naturalHeight: finite$1.positive().optional()
|
|
331
|
+
}).strict(),
|
|
332
|
+
z.object({
|
|
333
|
+
...artifactBase,
|
|
334
|
+
type: z.literal("audio"),
|
|
335
|
+
previewUrl: httpsUrl.optional(),
|
|
336
|
+
durationMs: z.number().int().positive().optional()
|
|
337
|
+
}).strict(),
|
|
338
|
+
z.object({
|
|
339
|
+
id: z.string().min(1).max(240),
|
|
340
|
+
type: z.literal("text"),
|
|
341
|
+
title: z.string().max(240).optional(),
|
|
342
|
+
textExcerpt: z.string().min(1).max(480)
|
|
343
|
+
}).strict()
|
|
344
|
+
]);
|
|
345
|
+
const BoardTaskSnapshotSchema = z.object({
|
|
346
|
+
taskType: z.string().min(1).max(120),
|
|
347
|
+
status: z.enum([
|
|
348
|
+
"pending",
|
|
349
|
+
"running",
|
|
350
|
+
"completed",
|
|
351
|
+
"failed"
|
|
352
|
+
]),
|
|
353
|
+
title: z.string().min(1).max(240),
|
|
354
|
+
model: z.string().max(160).optional(),
|
|
355
|
+
promptExcerpt: z.string().max(480).optional(),
|
|
356
|
+
artifactCount: z.number().int().nonnegative(),
|
|
357
|
+
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
358
|
+
updatedAt: z.string().optional()
|
|
359
|
+
}).strict();
|
|
360
|
+
const BoardTaskItemSchema = z.object({
|
|
361
|
+
...boxed(420, 240),
|
|
362
|
+
type: z.literal("task"),
|
|
363
|
+
props: z.object({
|
|
364
|
+
taskRunId: z.string().min(1).max(160),
|
|
365
|
+
snapshot: BoardTaskSnapshotSchema
|
|
366
|
+
}).strict()
|
|
367
|
+
}).strict();
|
|
368
|
+
const BOARD_EFFECT_KINDS = [
|
|
369
|
+
"particles",
|
|
370
|
+
"trail",
|
|
371
|
+
"impact",
|
|
372
|
+
"flash",
|
|
373
|
+
"glow"
|
|
374
|
+
];
|
|
375
|
+
const BoardEffectItemSchema = z.object({
|
|
376
|
+
...boxed(240, 240),
|
|
377
|
+
type: z.literal("effect"),
|
|
378
|
+
props: z.object({
|
|
379
|
+
kind: z.enum(BOARD_EFFECT_KINDS),
|
|
380
|
+
rate: nonNegative.max(2e4).default(40),
|
|
381
|
+
life: finite$1.positive().max(6e4).default(1200),
|
|
382
|
+
particleSize: finite$1.positive().max(512).default(6),
|
|
383
|
+
speed: nonNegative.max(1e4).default(120),
|
|
384
|
+
direction: finite$1.default(-90),
|
|
385
|
+
spread: finite$1.min(0).max(360).default(360),
|
|
386
|
+
gravity: finite$1.default(0),
|
|
387
|
+
intensity: nonNegative.max(10).default(1),
|
|
388
|
+
follow: BoardIdSchema.optional(),
|
|
389
|
+
seed: z.string().min(1).max(160).optional()
|
|
390
|
+
}).strict()
|
|
391
|
+
}).strict();
|
|
392
|
+
const BoardSketchItemSchema = z.object({
|
|
393
|
+
...boxed(480, 320),
|
|
394
|
+
type: z.literal("sketch"),
|
|
395
|
+
props: z.object({
|
|
396
|
+
src: srcSchema.refine((path) => /\.m?js$/.test(path), "sketch src must be a .js or .mjs module"),
|
|
397
|
+
params: z.record(z.string(), z.unknown()).default({}),
|
|
398
|
+
seed: z.string().min(1).max(160).optional()
|
|
399
|
+
}).strict()
|
|
400
|
+
}).strict();
|
|
401
|
+
const BOARD_EXTENSION_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
402
|
+
const BoardExtensionItemSchema = z.object({
|
|
403
|
+
...itemEnvelope,
|
|
404
|
+
size: BoardSizeSchema.default({
|
|
405
|
+
width: 240,
|
|
406
|
+
height: 160
|
|
407
|
+
}),
|
|
408
|
+
type: z.string().regex(BOARD_EXTENSION_TYPE_PATTERN),
|
|
409
|
+
props: z.record(z.string(), z.unknown()).default({})
|
|
410
|
+
}).strict();
|
|
411
|
+
const BOARD_ITEM_SCHEMAS = {
|
|
412
|
+
frame: BoardFrameItemSchema,
|
|
413
|
+
text: BoardTextItemSchema,
|
|
414
|
+
shape: BoardShapeItemSchema,
|
|
415
|
+
draw: BoardDrawItemSchema,
|
|
416
|
+
arrow: BoardArrowItemSchema,
|
|
417
|
+
image: BoardImageItemSchema,
|
|
418
|
+
video: BoardVideoItemSchema,
|
|
419
|
+
audio: BoardAudioItemSchema,
|
|
420
|
+
file: BoardFileItemSchema,
|
|
421
|
+
task: BoardTaskItemSchema,
|
|
422
|
+
effect: BoardEffectItemSchema,
|
|
423
|
+
sketch: BoardSketchItemSchema
|
|
424
|
+
};
|
|
425
|
+
const BOARD_ITEM_TYPES = Object.keys(BOARD_ITEM_SCHEMAS);
|
|
426
|
+
function isBuiltinItemType(type) {
|
|
427
|
+
return Object.hasOwn(BOARD_ITEM_SCHEMAS, type);
|
|
428
|
+
}
|
|
429
|
+
function boardItemSchema(type) {
|
|
430
|
+
if (isBuiltinItemType(type)) return BOARD_ITEM_SCHEMAS[type];
|
|
431
|
+
return BOARD_EXTENSION_TYPE_PATTERN.test(type) ? BoardExtensionItemSchema : null;
|
|
432
|
+
}
|
|
433
|
+
function issuesToDiagnostics(error, path) {
|
|
434
|
+
return error.issues.map((issue) => {
|
|
435
|
+
return {
|
|
436
|
+
severity: "error",
|
|
437
|
+
code: "INVALID_ITEM",
|
|
438
|
+
path: [path, ...issue.path.map(String)].filter(Boolean).join("."),
|
|
439
|
+
message: issue.code === "unrecognized_keys" ? `unknown ${issue.keys.length === 1 ? "property" : "properties"} ${issue.keys.join(", ")}` : issue.message
|
|
440
|
+
};
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
function parseBoardItem(value, path = "item") {
|
|
444
|
+
const type = value && typeof value === "object" && !Array.isArray(value) ? value.type : void 0;
|
|
445
|
+
if (typeof type !== "string") return {
|
|
446
|
+
ok: false,
|
|
447
|
+
diagnostics: [{
|
|
448
|
+
severity: "error",
|
|
449
|
+
code: "INVALID_ITEM",
|
|
450
|
+
path: `${path}.type`,
|
|
451
|
+
message: "type is required"
|
|
452
|
+
}]
|
|
453
|
+
};
|
|
454
|
+
const schema = boardItemSchema(type);
|
|
455
|
+
if (!schema) return {
|
|
456
|
+
ok: false,
|
|
457
|
+
diagnostics: [{
|
|
458
|
+
severity: "error",
|
|
459
|
+
code: "INVALID_ITEM",
|
|
460
|
+
path: `${path}.type`,
|
|
461
|
+
message: `unknown type ${type}; expected ${BOARD_ITEM_TYPES.join(", ")} or a namespaced extension type`
|
|
462
|
+
}]
|
|
463
|
+
};
|
|
464
|
+
const parsed = schema.safeParse(value);
|
|
465
|
+
return parsed.success ? {
|
|
466
|
+
ok: true,
|
|
467
|
+
item: parsed.data
|
|
468
|
+
} : {
|
|
469
|
+
ok: false,
|
|
470
|
+
diagnostics: issuesToDiagnostics(parsed.error, path)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const CSS_EASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
474
|
+
"linear",
|
|
475
|
+
"ease",
|
|
476
|
+
"ease-in",
|
|
477
|
+
"ease-out",
|
|
478
|
+
"ease-in-out",
|
|
479
|
+
"step-start",
|
|
480
|
+
"step-end"
|
|
114
481
|
]);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
482
|
+
function isBoardEase(value) {
|
|
483
|
+
const ease = value.trim();
|
|
484
|
+
if (CSS_EASE_KEYWORDS.has(ease)) return true;
|
|
485
|
+
const bezier = /^cubic-bezier\(\s*([^,()]+),\s*([^,()]+),\s*([^,()]+),\s*([^,()]+)\)$/.exec(ease);
|
|
486
|
+
if (bezier) {
|
|
487
|
+
const [x1, , x2] = bezier.slice(1).map(Number);
|
|
488
|
+
return bezier.slice(1).every((part) => Number.isFinite(Number(part))) && x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1;
|
|
489
|
+
}
|
|
490
|
+
return /^steps\(\s*[1-9]\d*\s*(?:,\s*(?:start|end|jump-start|jump-end|jump-none|jump-both)\s*)?\)$/.test(ease);
|
|
491
|
+
}
|
|
492
|
+
const BoardEaseSchema = z.string().max(120).refine(isBoardEase, "expected a CSS easing: linear, ease-in-out, cubic-bezier(…) or steps(…)");
|
|
493
|
+
const BoardKeyframeSchema = z.object({
|
|
494
|
+
at: nonNegative,
|
|
495
|
+
value: z.unknown(),
|
|
496
|
+
ease: BoardEaseSchema.optional()
|
|
497
|
+
}).strict();
|
|
498
|
+
const BoardTrackSchema = z.object({
|
|
499
|
+
target: z.string().min(1).max(160),
|
|
500
|
+
property: z.string().min(1).max(200),
|
|
501
|
+
keyframes: z.array(BoardKeyframeSchema).min(1).max(1e5),
|
|
502
|
+
composite: z.enum(["replace", "add"]).default("replace"),
|
|
503
|
+
interpolation: z.enum([
|
|
504
|
+
"auto",
|
|
505
|
+
"step",
|
|
506
|
+
"spline"
|
|
507
|
+
]).default("auto"),
|
|
508
|
+
orient: z.boolean().optional()
|
|
509
|
+
}).strict().superRefine((track, context) => {
|
|
510
|
+
for (let index = 1; index < track.keyframes.length; index += 1) if ((track.keyframes[index]?.at ?? 0) < (track.keyframes[index - 1]?.at ?? 0)) context.addIssue({
|
|
123
511
|
code: "custom",
|
|
124
|
-
message: "
|
|
125
|
-
path: [
|
|
512
|
+
message: "keyframes must be ordered by at",
|
|
513
|
+
path: [
|
|
514
|
+
"keyframes",
|
|
515
|
+
index,
|
|
516
|
+
"at"
|
|
517
|
+
]
|
|
126
518
|
});
|
|
127
519
|
});
|
|
128
|
-
z.object({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
520
|
+
const BoardMarkerSchema = z.object({
|
|
521
|
+
at: nonNegative,
|
|
522
|
+
label: z.string().max(280).optional(),
|
|
523
|
+
pause: z.boolean().optional()
|
|
524
|
+
}).strict();
|
|
525
|
+
const BoardAnimationHeaderSchema = z.object({
|
|
526
|
+
name: z.string().max(280).optional(),
|
|
527
|
+
duration: finite$1.positive().max(864e5),
|
|
528
|
+
play: z.enum([
|
|
529
|
+
"manual",
|
|
530
|
+
"auto",
|
|
531
|
+
"always"
|
|
532
|
+
]).default("manual"),
|
|
533
|
+
delay: nonNegative.default(0),
|
|
534
|
+
loop: z.boolean().default(false),
|
|
535
|
+
end: z.enum(["hold", "reset"]).default("hold"),
|
|
536
|
+
markers: z.array(BoardMarkerSchema).max(1e4).default([])
|
|
537
|
+
}).strict();
|
|
538
|
+
const BoardAnimationSchema = BoardAnimationHeaderSchema.extend({ tracks: z.record(BoardIdSchema, BoardTrackSchema).default({}) }).strict();
|
|
539
|
+
const BOARD_ENTER_PRESETS = [
|
|
540
|
+
"fade-in",
|
|
541
|
+
"rise",
|
|
542
|
+
"drop",
|
|
543
|
+
"pop",
|
|
544
|
+
"deal"
|
|
545
|
+
];
|
|
546
|
+
const BoardBackgroundSchema = z.object({
|
|
547
|
+
kind: z.enum([
|
|
548
|
+
"solid",
|
|
549
|
+
"dots",
|
|
550
|
+
"grid",
|
|
551
|
+
"image"
|
|
552
|
+
]).default("dots"),
|
|
553
|
+
color: BoardColorSchema.optional(),
|
|
554
|
+
imageUrl: z.string().url().max(4096).optional(),
|
|
555
|
+
fit: z.enum([
|
|
556
|
+
"cover",
|
|
557
|
+
"contain",
|
|
558
|
+
"repeat"
|
|
559
|
+
]).optional(),
|
|
560
|
+
opacity: ratio.optional()
|
|
561
|
+
}).strict();
|
|
562
|
+
const BoardSettingsSchema = z.object({
|
|
156
563
|
title: z.string().min(1).max(255).optional(),
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
564
|
+
background: BoardBackgroundSchema.default({ kind: "dots" }),
|
|
565
|
+
grid: z.object({
|
|
566
|
+
visible: z.boolean().default(false),
|
|
567
|
+
size: finite$1.min(4).max(512).default(24)
|
|
568
|
+
}).strict().optional(),
|
|
569
|
+
enter: z.object({
|
|
570
|
+
preset: z.enum(BOARD_ENTER_PRESETS),
|
|
571
|
+
duration: finite$1.positive().max(1e4).optional()
|
|
572
|
+
}).strict().optional()
|
|
573
|
+
}).strict();
|
|
574
|
+
const patchObject = z.record(z.string(), z.unknown());
|
|
575
|
+
const BoardPatchSchema = z.object({
|
|
576
|
+
board: patchObject.nullable().optional(),
|
|
577
|
+
items: z.record(BoardIdSchema, patchObject.nullable()).optional(),
|
|
578
|
+
animations: z.record(BoardIdSchema, patchObject.and(z.object({ tracks: z.record(BoardIdSchema, patchObject.nullable()).optional() })).nullable()).optional()
|
|
579
|
+
}).strict();
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region ../protocol/dist/board-json.js
|
|
582
|
+
/** Structural equality for JSON-shaped semantic values. */
|
|
583
|
+
function boardJsonEquals(a, b) {
|
|
584
|
+
if (a === b) return true;
|
|
585
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return Number.isNaN(a) && Number.isNaN(b);
|
|
586
|
+
const aArray = Array.isArray(a);
|
|
587
|
+
const bArray = Array.isArray(b);
|
|
588
|
+
if (aArray !== bArray) return false;
|
|
589
|
+
if (aArray && bArray) return a.length === b.length && a.every((value, index) => boardJsonEquals(value, b[index]));
|
|
590
|
+
const aRecord = a;
|
|
591
|
+
const bRecord = b;
|
|
592
|
+
const keys = Object.keys(aRecord);
|
|
593
|
+
return keys.length === Object.keys(bRecord).length && keys.every((key) => Object.hasOwn(bRecord, key) && boardJsonEquals(aRecord[key], bRecord[key]));
|
|
594
|
+
}
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region ../protocol/dist/board-layout.js
|
|
597
|
+
const IDENTITY_MATRIX = [
|
|
598
|
+
1,
|
|
599
|
+
0,
|
|
600
|
+
0,
|
|
601
|
+
1,
|
|
602
|
+
0,
|
|
603
|
+
0
|
|
604
|
+
];
|
|
605
|
+
function multiplyMatrix(parent, child) {
|
|
606
|
+
const [a1, b1, c1, d1, tx1, ty1] = parent;
|
|
607
|
+
const [a2, b2, c2, d2, tx2, ty2] = child;
|
|
608
|
+
return [
|
|
609
|
+
a1 * a2 + c1 * b2,
|
|
610
|
+
b1 * a2 + d1 * b2,
|
|
611
|
+
a1 * c2 + c1 * d2,
|
|
612
|
+
b1 * c2 + d1 * d2,
|
|
613
|
+
a1 * tx2 + c1 * ty2 + tx1,
|
|
614
|
+
b1 * tx2 + d1 * ty2 + ty1
|
|
615
|
+
];
|
|
616
|
+
}
|
|
617
|
+
function invertMatrix(matrix) {
|
|
618
|
+
const [a, b, c, d, tx, ty] = matrix;
|
|
619
|
+
const det = a * d - b * c || 1e-12;
|
|
620
|
+
return [
|
|
621
|
+
d / det,
|
|
622
|
+
-b / det,
|
|
623
|
+
-c / det,
|
|
624
|
+
a / det,
|
|
625
|
+
(c * ty - d * tx) / det,
|
|
626
|
+
(b * tx - a * ty) / det
|
|
627
|
+
];
|
|
628
|
+
}
|
|
629
|
+
function applyMatrix(matrix, point) {
|
|
630
|
+
const [a, b, c, d, tx, ty] = matrix;
|
|
631
|
+
return {
|
|
632
|
+
x: a * point.x + c * point.y + tx,
|
|
633
|
+
y: b * point.x + d * point.y + ty
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function scaleVector(scale) {
|
|
637
|
+
return typeof scale === "number" ? {
|
|
638
|
+
x: scale,
|
|
639
|
+
y: scale
|
|
640
|
+
} : scale;
|
|
641
|
+
}
|
|
642
|
+
function isWideCharacter(code) {
|
|
643
|
+
return code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 262141;
|
|
644
|
+
}
|
|
645
|
+
function estimateTextWidth(text, fontSize) {
|
|
646
|
+
let ems = 0;
|
|
647
|
+
for (const character of text) {
|
|
648
|
+
const code = character.codePointAt(0) ?? 0;
|
|
649
|
+
if (character === " ") ems += .28;
|
|
650
|
+
else if (isWideCharacter(code)) ems += 1;
|
|
651
|
+
else if (/[il.,:;'|!]/.test(character)) ems += .3;
|
|
652
|
+
else if (/[mwMW@]/.test(character)) ems += .85;
|
|
653
|
+
else if (/[A-Z0-9]/.test(character)) ems += .64;
|
|
654
|
+
else ems += .55;
|
|
655
|
+
}
|
|
656
|
+
return ems * fontSize;
|
|
657
|
+
}
|
|
658
|
+
function measureLine(text, fontSize, fontWeight, font) {
|
|
659
|
+
return estimateTextWidth(text, fontSize);
|
|
660
|
+
}
|
|
661
|
+
function wrapParagraph(paragraph, width, measure) {
|
|
662
|
+
if (!paragraph) return [""];
|
|
663
|
+
const tokens = paragraph.match(/[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\uff00-\uff60]|[^\s\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\uff00-\uff60]+|\s+/g) ?? [paragraph];
|
|
664
|
+
const lines = [];
|
|
665
|
+
let line = "";
|
|
666
|
+
for (const token of tokens) {
|
|
667
|
+
const candidate = line + token;
|
|
668
|
+
if (!line || measure(candidate.trimEnd()) <= width) {
|
|
669
|
+
line = candidate;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
lines.push(line.trimEnd());
|
|
673
|
+
line = /^\s+$/.test(token) ? "" : token;
|
|
674
|
+
}
|
|
675
|
+
lines.push(line.trimEnd());
|
|
676
|
+
return lines;
|
|
677
|
+
}
|
|
678
|
+
const textSizeCache = /* @__PURE__ */ new Map();
|
|
679
|
+
const TEXT_CACHE_LIMIT = 4096;
|
|
680
|
+
function clampBoardFontSize(fontSize) {
|
|
681
|
+
return Math.min(512, Math.max(2, fontSize));
|
|
682
|
+
}
|
|
683
|
+
function layoutBoardText(props) {
|
|
684
|
+
const fontSize = clampBoardFontSize(props.fontSize ?? 24);
|
|
685
|
+
const key = `${fontSize}|${props.fontWeight}|${props.font}|${props.lineHeight}|${props.width ?? ""}|${props.text}`;
|
|
686
|
+
const cached = textSizeCache.get(key);
|
|
687
|
+
if (cached) return cached;
|
|
688
|
+
const measure = (text) => measureLine(text, fontSize, props.fontWeight ?? 500, props.font ?? "sans");
|
|
689
|
+
const paragraphs = (props.text || "").split("\n");
|
|
690
|
+
const lines = props.width ? paragraphs.flatMap((paragraph) => wrapParagraph(paragraph, props.width, measure)) : paragraphs;
|
|
691
|
+
const lineHeight = fontSize * (props.lineHeight ?? 4 / 3);
|
|
692
|
+
const widest = lines.reduce((max, line) => Math.max(max, measure(line)), 0);
|
|
693
|
+
const layout = {
|
|
694
|
+
width: Math.max(props.width ?? 0, widest, fontSize * .5),
|
|
695
|
+
height: Math.max(1, lines.length) * lineHeight,
|
|
696
|
+
lines,
|
|
697
|
+
lineHeight
|
|
698
|
+
};
|
|
699
|
+
if (textSizeCache.size >= TEXT_CACHE_LIMIT) textSizeCache.clear();
|
|
700
|
+
textSizeCache.set(key, layout);
|
|
701
|
+
return layout;
|
|
702
|
+
}
|
|
703
|
+
function drawSampleRadius(strokeWidth, pressure) {
|
|
704
|
+
const clamped = Math.min(1, Math.max(0, pressure));
|
|
705
|
+
return Math.max(.5, strokeWidth / 2 * (.5 + clamped));
|
|
706
|
+
}
|
|
707
|
+
function drawPointsBounds(points, strokeWidth) {
|
|
708
|
+
if (points.length === 0) return {
|
|
709
|
+
x: 0,
|
|
710
|
+
y: 0,
|
|
711
|
+
width: 1,
|
|
712
|
+
height: 1
|
|
713
|
+
};
|
|
714
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
715
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
716
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
717
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
718
|
+
for (const point of points) {
|
|
719
|
+
const radius = drawSampleRadius(strokeWidth, point.p);
|
|
720
|
+
minX = Math.min(minX, point.x - radius);
|
|
721
|
+
minY = Math.min(minY, point.y - radius);
|
|
722
|
+
maxX = Math.max(maxX, point.x + radius);
|
|
723
|
+
maxY = Math.max(maxY, point.y + radius);
|
|
724
|
+
}
|
|
725
|
+
return {
|
|
726
|
+
x: minX,
|
|
727
|
+
y: minY,
|
|
728
|
+
width: Math.max(1, maxX - minX),
|
|
729
|
+
height: Math.max(1, maxY - minY)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function pointsRect(points) {
|
|
733
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
734
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
735
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
736
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
737
|
+
for (const point of points) {
|
|
738
|
+
minX = Math.min(minX, point.x);
|
|
739
|
+
minY = Math.min(minY, point.y);
|
|
740
|
+
maxX = Math.max(maxX, point.x);
|
|
741
|
+
maxY = Math.max(maxY, point.y);
|
|
742
|
+
}
|
|
743
|
+
if (!Number.isFinite(minX)) return {
|
|
744
|
+
x: 0,
|
|
745
|
+
y: 0,
|
|
746
|
+
width: 1,
|
|
747
|
+
height: 1
|
|
748
|
+
};
|
|
749
|
+
return {
|
|
750
|
+
x: minX,
|
|
751
|
+
y: minY,
|
|
752
|
+
width: Math.max(1, maxX - minX),
|
|
753
|
+
height: Math.max(1, maxY - minY)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function isArrowBinding(end) {
|
|
757
|
+
return typeof end.item === "string";
|
|
758
|
+
}
|
|
759
|
+
function arrowBindings(item) {
|
|
760
|
+
if (item.type !== "arrow") return [];
|
|
761
|
+
const props = item.props;
|
|
762
|
+
const ids = [props.start, props.end].filter(isArrowBinding).map((end) => end.item);
|
|
763
|
+
return [...new Set(ids)];
|
|
764
|
+
}
|
|
765
|
+
function bendControl(start, end, bend) {
|
|
766
|
+
const mid = {
|
|
767
|
+
x: (start.x + end.x) / 2,
|
|
768
|
+
y: (start.y + end.y) / 2
|
|
769
|
+
};
|
|
770
|
+
if (!bend) return mid;
|
|
771
|
+
const dx = end.x - start.x;
|
|
772
|
+
const dy = end.y - start.y;
|
|
773
|
+
const length = Math.hypot(dx, dy) || 1;
|
|
774
|
+
const offset = bend * length;
|
|
775
|
+
return {
|
|
776
|
+
x: mid.x + -dy / length * offset,
|
|
777
|
+
y: mid.y + dx / length * offset
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
const MAX_DEPTH = 64;
|
|
781
|
+
function createBoardLayout(initialLookup, dependents) {
|
|
782
|
+
let lookup = initialLookup;
|
|
783
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
784
|
+
const matrices = /* @__PURE__ */ new Map();
|
|
785
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
786
|
+
function matrixOf(id, depth = 0) {
|
|
787
|
+
const cached = matrices.get(id);
|
|
788
|
+
if (cached) return cached;
|
|
789
|
+
const item = lookup(id);
|
|
790
|
+
if (!item) return IDENTITY_MATRIX;
|
|
791
|
+
const matrix = multiplyMatrix(item.parent && depth < MAX_DEPTH && lookup(item.parent) ? matrixOf(item.parent, depth + 1) : IDENTITY_MATRIX, localMatrixOf(id));
|
|
792
|
+
matrices.set(id, matrix);
|
|
793
|
+
return matrix;
|
|
794
|
+
}
|
|
795
|
+
function localMatrixOf(id) {
|
|
796
|
+
const item = lookup(id);
|
|
797
|
+
if (!item) return IDENTITY_MATRIX;
|
|
798
|
+
if (item.type === "arrow") return translate(item.position);
|
|
799
|
+
const box = boxOf(id);
|
|
800
|
+
const scale = scaleVector(item.scale);
|
|
801
|
+
const radians = item.rotation * Math.PI / 180;
|
|
802
|
+
const cos = Math.cos(radians);
|
|
803
|
+
const sin = Math.sin(radians);
|
|
804
|
+
const ox = box.x + item.origin.x * box.width;
|
|
805
|
+
const oy = box.y + item.origin.y * box.height;
|
|
806
|
+
const a = cos * scale.x;
|
|
807
|
+
const b = sin * scale.x;
|
|
808
|
+
const c = -sin * scale.y;
|
|
809
|
+
const d = cos * scale.y;
|
|
810
|
+
return [
|
|
811
|
+
a,
|
|
812
|
+
b,
|
|
813
|
+
c,
|
|
814
|
+
d,
|
|
815
|
+
item.position.x + ox - (a * ox + c * oy),
|
|
816
|
+
item.position.y + oy - (b * ox + d * oy)
|
|
817
|
+
];
|
|
818
|
+
}
|
|
819
|
+
function boxOf(id, depth = 0) {
|
|
820
|
+
const cached = boxes.get(id);
|
|
821
|
+
if (cached) return cached;
|
|
822
|
+
const item = lookup(id);
|
|
823
|
+
let box;
|
|
824
|
+
if (!item) box = {
|
|
825
|
+
x: 0,
|
|
826
|
+
y: 0,
|
|
827
|
+
width: 1,
|
|
828
|
+
height: 1
|
|
829
|
+
};
|
|
830
|
+
else if ("size" in item && item.size) box = {
|
|
831
|
+
x: 0,
|
|
832
|
+
y: 0,
|
|
833
|
+
width: item.size.width,
|
|
834
|
+
height: item.size.height
|
|
835
|
+
};
|
|
836
|
+
else if (item.type === "text") {
|
|
837
|
+
const layout = layoutBoardText(item.props);
|
|
838
|
+
box = {
|
|
839
|
+
x: 0,
|
|
840
|
+
y: 0,
|
|
841
|
+
width: layout.width,
|
|
842
|
+
height: layout.height
|
|
843
|
+
};
|
|
844
|
+
} else if (item.type === "draw") box = drawPointsBounds(item.props.points, item.style.strokeWidth ?? 4);
|
|
845
|
+
else if (item.type === "arrow") {
|
|
846
|
+
const props = item.props;
|
|
847
|
+
const toLocal = invertMatrix(multiplyMatrix(parentOf(item, depth), translate(item.position)));
|
|
848
|
+
const start = isArrowBinding(props.start) ? applyMatrix(toLocal, resolveBoundEnd(item, props.start, props.end, depth)) : props.start;
|
|
849
|
+
const end = isArrowBinding(props.end) ? applyMatrix(toLocal, resolveBoundEnd(item, props.end, props.start, depth)) : props.end;
|
|
850
|
+
const points = [
|
|
851
|
+
start,
|
|
852
|
+
end,
|
|
853
|
+
...props.waypoints
|
|
854
|
+
];
|
|
855
|
+
if (props.bend) points.push(bendControl(start, end, props.bend));
|
|
856
|
+
const rect = pointsRect(points);
|
|
857
|
+
const pad = Math.max(8, (item.style.strokeWidth ?? 2.5) * 2);
|
|
858
|
+
box = {
|
|
859
|
+
x: rect.x - pad,
|
|
860
|
+
y: rect.y - pad,
|
|
861
|
+
width: rect.width + pad * 2,
|
|
862
|
+
height: rect.height + pad * 2
|
|
863
|
+
};
|
|
864
|
+
} else box = {
|
|
865
|
+
x: 0,
|
|
866
|
+
y: 0,
|
|
867
|
+
width: 240,
|
|
868
|
+
height: 160
|
|
869
|
+
};
|
|
870
|
+
boxes.set(id, box);
|
|
871
|
+
return box;
|
|
872
|
+
}
|
|
873
|
+
function parentOf(item, depth) {
|
|
874
|
+
return item.parent && depth < MAX_DEPTH && lookup(item.parent) ? matrixOf(item.parent, depth + 1) : IDENTITY_MATRIX;
|
|
875
|
+
}
|
|
876
|
+
function resolveBoundEnd(arrow, end, other, depth) {
|
|
877
|
+
const target = lookup(end.item);
|
|
878
|
+
const arrowSpace = multiplyMatrix(parentOf(arrow, depth), translate(arrow.position));
|
|
879
|
+
if (!target || target.type === "arrow" || depth >= MAX_DEPTH) return applyMatrix(arrowSpace, {
|
|
880
|
+
x: 0,
|
|
881
|
+
y: 0
|
|
882
|
+
});
|
|
883
|
+
const frame = frameOf(end.item, depth + 1);
|
|
884
|
+
const toward = isArrowBinding(other) ? lookup(other.item) && lookup(other.item)?.type !== "arrow" ? center(frameOf(other.item, depth + 1)) : center(frame) : applyMatrix(arrowSpace, other);
|
|
885
|
+
return anchorPoint(frame, end.anchor, toward);
|
|
886
|
+
}
|
|
887
|
+
function frameOf(id, depth = 0) {
|
|
888
|
+
boxOf(id, depth);
|
|
889
|
+
const box = boxes.get(id);
|
|
890
|
+
const matrix = matrixOf(id, depth);
|
|
891
|
+
const [a, b, c, d] = matrix;
|
|
892
|
+
const centerPoint = applyMatrix(matrix, {
|
|
893
|
+
x: box.x + box.width / 2,
|
|
894
|
+
y: box.y + box.height / 2
|
|
895
|
+
});
|
|
896
|
+
const width = box.width * Math.hypot(a, b);
|
|
897
|
+
const height = box.height * Math.hypot(c, d);
|
|
898
|
+
return {
|
|
899
|
+
x: centerPoint.x - width / 2,
|
|
900
|
+
y: centerPoint.y - height / 2,
|
|
901
|
+
width,
|
|
902
|
+
height,
|
|
903
|
+
rotation: Math.atan2(b, a) * 180 / Math.PI
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function boundsOf(id) {
|
|
907
|
+
const cached = bounds.get(id);
|
|
908
|
+
if (cached) return cached;
|
|
909
|
+
const box = boxOf(id);
|
|
910
|
+
const matrix = matrixOf(id);
|
|
911
|
+
const rect = pointsRect([
|
|
912
|
+
applyMatrix(matrix, {
|
|
913
|
+
x: box.x,
|
|
914
|
+
y: box.y
|
|
915
|
+
}),
|
|
916
|
+
applyMatrix(matrix, {
|
|
917
|
+
x: box.x + box.width,
|
|
918
|
+
y: box.y
|
|
919
|
+
}),
|
|
920
|
+
applyMatrix(matrix, {
|
|
921
|
+
x: box.x + box.width,
|
|
922
|
+
y: box.y + box.height
|
|
923
|
+
}),
|
|
924
|
+
applyMatrix(matrix, {
|
|
925
|
+
x: box.x,
|
|
926
|
+
y: box.y + box.height
|
|
927
|
+
})
|
|
928
|
+
]);
|
|
929
|
+
bounds.set(id, rect);
|
|
930
|
+
return rect;
|
|
931
|
+
}
|
|
932
|
+
function setLookup(nextLookup) {
|
|
933
|
+
lookup = nextLookup;
|
|
934
|
+
}
|
|
935
|
+
function invalidate(ids) {
|
|
936
|
+
if (!ids) {
|
|
937
|
+
boxes.clear();
|
|
938
|
+
matrices.clear();
|
|
939
|
+
bounds.clear();
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const queue = [...ids];
|
|
943
|
+
const seen = /* @__PURE__ */ new Set();
|
|
944
|
+
while (queue.length) {
|
|
945
|
+
const id = queue.pop();
|
|
946
|
+
if (seen.has(id)) continue;
|
|
947
|
+
seen.add(id);
|
|
948
|
+
boxes.delete(id);
|
|
949
|
+
matrices.delete(id);
|
|
950
|
+
bounds.delete(id);
|
|
951
|
+
if (dependents) for (const next of dependents(id)) queue.push(next);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
has: (id) => lookup(id) !== void 0,
|
|
956
|
+
box: (id) => boxOf(id),
|
|
957
|
+
matrix: (id) => matrixOf(id),
|
|
958
|
+
localMatrix: (id) => localMatrixOf(id),
|
|
959
|
+
parentMatrix: (id) => {
|
|
960
|
+
const item = lookup(id);
|
|
961
|
+
return item ? parentOf(item, 0) : IDENTITY_MATRIX;
|
|
962
|
+
},
|
|
963
|
+
bounds: boundsOf,
|
|
964
|
+
frame: (id) => frameOf(id),
|
|
965
|
+
arrowEnd: (id, which) => {
|
|
966
|
+
const item = lookup(id);
|
|
967
|
+
if (item?.type !== "arrow") return {
|
|
968
|
+
x: 0,
|
|
969
|
+
y: 0
|
|
970
|
+
};
|
|
971
|
+
const arrow = item;
|
|
972
|
+
const [end, other] = which === "start" ? [arrow.props.start, arrow.props.end] : [arrow.props.end, arrow.props.start];
|
|
973
|
+
return isArrowBinding(end) ? resolveBoundEnd(arrow, end, other, 0) : applyMatrix(matrixOf(id), end);
|
|
974
|
+
},
|
|
975
|
+
invalidate,
|
|
976
|
+
setLookup
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function translate(point) {
|
|
980
|
+
return [
|
|
981
|
+
1,
|
|
982
|
+
0,
|
|
983
|
+
0,
|
|
984
|
+
1,
|
|
985
|
+
point.x,
|
|
986
|
+
point.y
|
|
987
|
+
];
|
|
988
|
+
}
|
|
989
|
+
function center(frame) {
|
|
990
|
+
return {
|
|
991
|
+
x: frame.x + frame.width / 2,
|
|
992
|
+
y: frame.y + frame.height / 2
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function rotateAround(point, pivot, degrees) {
|
|
996
|
+
if (!degrees) return point;
|
|
997
|
+
const radians = degrees * Math.PI / 180;
|
|
998
|
+
const cos = Math.cos(radians);
|
|
999
|
+
const sin = Math.sin(radians);
|
|
1000
|
+
const dx = point.x - pivot.x;
|
|
1001
|
+
const dy = point.y - pivot.y;
|
|
1002
|
+
return {
|
|
1003
|
+
x: pivot.x + dx * cos - dy * sin,
|
|
1004
|
+
y: pivot.y + dx * sin + dy * cos
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
function framePoint(frame, nx, ny) {
|
|
1008
|
+
return rotateAround({
|
|
1009
|
+
x: frame.x + nx * frame.width,
|
|
1010
|
+
y: frame.y + ny * frame.height
|
|
1011
|
+
}, center(frame), frame.rotation);
|
|
1012
|
+
}
|
|
1013
|
+
function autoAnchorSide(frame, toward) {
|
|
1014
|
+
const local = rotateAround(toward, center(frame), -frame.rotation);
|
|
1015
|
+
const c = center(frame);
|
|
1016
|
+
const dx = (local.x - c.x) / Math.max(frame.width / 2, 1e-4);
|
|
1017
|
+
const dy = (local.y - c.y) / Math.max(frame.height / 2, 1e-4);
|
|
1018
|
+
if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
|
|
1019
|
+
return dy >= 0 ? "bottom" : "top";
|
|
1020
|
+
}
|
|
1021
|
+
function sideAnchor(side, offset) {
|
|
1022
|
+
switch (side) {
|
|
1023
|
+
case "top": return {
|
|
1024
|
+
x: offset,
|
|
1025
|
+
y: 0
|
|
1026
|
+
};
|
|
1027
|
+
case "bottom": return {
|
|
1028
|
+
x: offset,
|
|
1029
|
+
y: 1
|
|
1030
|
+
};
|
|
1031
|
+
case "left": return {
|
|
1032
|
+
x: 0,
|
|
1033
|
+
y: offset
|
|
1034
|
+
};
|
|
1035
|
+
default: return {
|
|
1036
|
+
x: 1,
|
|
1037
|
+
y: offset
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function anchorPoint(frame, anchor, toward) {
|
|
1042
|
+
if (anchor === "auto") {
|
|
1043
|
+
const normalized = sideAnchor(autoAnchorSide(frame, toward), .5);
|
|
1044
|
+
return framePoint(frame, normalized.x, normalized.y);
|
|
1045
|
+
}
|
|
1046
|
+
if ("side" in anchor) {
|
|
1047
|
+
const normalized = sideAnchor(anchor.side, anchor.offset);
|
|
1048
|
+
return framePoint(frame, normalized.x, normalized.y);
|
|
1049
|
+
}
|
|
1050
|
+
return framePoint(frame, anchor.x, anchor.y);
|
|
1051
|
+
}
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region ../protocol/dist/board-patch.js
|
|
1054
|
+
function slot(map, key) {
|
|
1055
|
+
let value = map[key];
|
|
1056
|
+
if (!value) {
|
|
1057
|
+
value = {};
|
|
1058
|
+
map[key] = value;
|
|
1059
|
+
}
|
|
1060
|
+
return value;
|
|
1061
|
+
}
|
|
1062
|
+
function emptyDelta() {
|
|
1063
|
+
return {
|
|
1064
|
+
items: {},
|
|
1065
|
+
animations: {},
|
|
1066
|
+
tracks: {}
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function withoutEmpty(value) {
|
|
1070
|
+
for (const [key, entry] of Object.entries(value)) if (entry === void 0 || isRecord$6(entry) && Object.keys(entry).length === 0) delete value[key];
|
|
1071
|
+
return value;
|
|
1072
|
+
}
|
|
1073
|
+
function isRecord$6(value) {
|
|
1074
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1075
|
+
}
|
|
1076
|
+
function mergePatch(target, patch) {
|
|
1077
|
+
if (!isRecord$6(patch)) return patch;
|
|
1078
|
+
const result = isRecord$6(target) ? { ...target } : {};
|
|
1079
|
+
for (const [key, value] of Object.entries(patch)) if (value === null) delete result[key];
|
|
1080
|
+
else result[key] = mergePatch(result[key], value);
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
function def(schema) {
|
|
1084
|
+
return schema._zod.def;
|
|
1085
|
+
}
|
|
1086
|
+
function unwrap(schema) {
|
|
1087
|
+
let current = schema;
|
|
1088
|
+
for (let guard = 0; guard < 16; guard += 1) {
|
|
1089
|
+
const d = def(current);
|
|
1090
|
+
if (d.type === "optional" || d.type === "default" || d.type === "nullable" || d.type === "readonly" || d.type === "prefault") current = d.innerType;
|
|
1091
|
+
else if (d.type === "pipe") current = d.in;
|
|
1092
|
+
else return current;
|
|
1093
|
+
}
|
|
1094
|
+
return current;
|
|
1095
|
+
}
|
|
1096
|
+
function unwrapObjectShape(schema) {
|
|
1097
|
+
const d = def(unwrap(schema));
|
|
1098
|
+
return d.type === "object" ? d.shape : null;
|
|
1099
|
+
}
|
|
1100
|
+
function schemaAtPath(schema, path) {
|
|
1101
|
+
let current = schema;
|
|
1102
|
+
for (const segment of path) {
|
|
1103
|
+
const d = def(unwrap(current));
|
|
1104
|
+
if (d.type === "object") {
|
|
1105
|
+
const shape = d.shape;
|
|
1106
|
+
if (!Object.hasOwn(shape, segment)) return null;
|
|
1107
|
+
current = shape[segment];
|
|
1108
|
+
} else if (d.type === "record") current = d.valueType;
|
|
1109
|
+
else if (d.type === "union") {
|
|
1110
|
+
const branch = d.options.find((option) => unwrapObjectShape(option)?.[segment]);
|
|
1111
|
+
if (!branch) return null;
|
|
1112
|
+
current = unwrapObjectShape(branch)[segment];
|
|
1113
|
+
} else return null;
|
|
1114
|
+
}
|
|
1115
|
+
return current;
|
|
1116
|
+
}
|
|
1117
|
+
const NOT_ANIMATABLE = /* @__PURE__ */ new Set([
|
|
1118
|
+
"type",
|
|
1119
|
+
"parent",
|
|
1120
|
+
"z",
|
|
1121
|
+
"locked",
|
|
1122
|
+
"metadata"
|
|
1123
|
+
]);
|
|
1124
|
+
const CAMERA_PROPERTIES = {
|
|
1125
|
+
focus: {
|
|
1126
|
+
kind: "discrete",
|
|
1127
|
+
schema: z.union([z.string().min(1), z.object({
|
|
1128
|
+
x: z.number().finite(),
|
|
1129
|
+
y: z.number().finite(),
|
|
1130
|
+
width: z.number().finite().positive(),
|
|
1131
|
+
height: z.number().finite().positive()
|
|
1132
|
+
}).strict()])
|
|
1133
|
+
},
|
|
1134
|
+
zoom: {
|
|
1135
|
+
kind: "number",
|
|
1136
|
+
schema: z.number().finite().positive(),
|
|
1137
|
+
multiplicative: true
|
|
1138
|
+
},
|
|
1139
|
+
center: {
|
|
1140
|
+
kind: "vec2",
|
|
1141
|
+
schema: BoardVec2Schema
|
|
1142
|
+
},
|
|
1143
|
+
shake: {
|
|
1144
|
+
kind: "number",
|
|
1145
|
+
schema: z.number().finite().nonnegative()
|
|
1146
|
+
}
|
|
171
1147
|
};
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
1148
|
+
const ANIMATION_PROPERTIES = { time: {
|
|
1149
|
+
kind: "number",
|
|
1150
|
+
schema: z.number().finite().nonnegative()
|
|
1151
|
+
} };
|
|
1152
|
+
function kindOf(schema, path) {
|
|
1153
|
+
const leaf = path[path.length - 1];
|
|
1154
|
+
if (path.length === 1 && leaf === "scale") return "scale";
|
|
1155
|
+
if (schema === BoardColorSchema || unwrap(schema) === BoardColorSchema) return "color";
|
|
1156
|
+
if (def(unwrap(schema)).type === "number") return "number";
|
|
1157
|
+
const shape = unwrapObjectShape(schema);
|
|
1158
|
+
if (shape && Object.keys(shape).length === 2 && "x" in shape && "y" in shape) return "vec2";
|
|
1159
|
+
return "discrete";
|
|
1160
|
+
}
|
|
1161
|
+
function resolveBoardProperty(targetType, property) {
|
|
1162
|
+
if (targetType === "camera") {
|
|
1163
|
+
const spec = CAMERA_PROPERTIES[property];
|
|
1164
|
+
return spec ? {
|
|
1165
|
+
ok: true,
|
|
1166
|
+
spec
|
|
1167
|
+
} : {
|
|
1168
|
+
ok: false,
|
|
1169
|
+
message: `camera has no property ${property}; expected ${Object.keys(CAMERA_PROPERTIES).join(", ")}`
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
if (targetType === "animation") {
|
|
1173
|
+
const spec = ANIMATION_PROPERTIES[property];
|
|
1174
|
+
return spec ? {
|
|
1175
|
+
ok: true,
|
|
1176
|
+
spec
|
|
1177
|
+
} : {
|
|
1178
|
+
ok: false,
|
|
1179
|
+
message: `animations are driven through time`
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
const path = property.split(".");
|
|
1183
|
+
if (path.some((segment) => !segment) || NOT_ANIMATABLE.has(path[0])) return {
|
|
1184
|
+
ok: false,
|
|
1185
|
+
message: `${property} cannot be animated`
|
|
1186
|
+
};
|
|
1187
|
+
const schema = boardItemSchema(targetType);
|
|
1188
|
+
if (!schema) return {
|
|
1189
|
+
ok: false,
|
|
1190
|
+
message: `unknown item type ${targetType}`
|
|
1191
|
+
};
|
|
1192
|
+
if (!isBuiltinItemType(targetType) && path[0] === "props") return {
|
|
1193
|
+
ok: true,
|
|
1194
|
+
spec: {
|
|
1195
|
+
kind: "discrete",
|
|
1196
|
+
schema: z.unknown()
|
|
187
1197
|
}
|
|
188
|
-
}
|
|
1198
|
+
};
|
|
1199
|
+
const leaf = schemaAtPath(schema, path);
|
|
1200
|
+
if (!leaf) return {
|
|
1201
|
+
ok: false,
|
|
1202
|
+
message: `${targetType} has no property ${property}`
|
|
1203
|
+
};
|
|
1204
|
+
const kind = kindOf(leaf, path);
|
|
1205
|
+
return {
|
|
1206
|
+
ok: true,
|
|
1207
|
+
spec: {
|
|
1208
|
+
kind,
|
|
1209
|
+
schema: unwrap(leaf),
|
|
1210
|
+
...property === "opacity" || kind === "scale" ? { multiplicative: true } : {}
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function lastKeyframeAt(trackPatches) {
|
|
1215
|
+
let end = 0;
|
|
1216
|
+
for (const track of trackPatches) {
|
|
1217
|
+
const keyframes = track?.keyframes;
|
|
1218
|
+
if (!Array.isArray(keyframes)) continue;
|
|
1219
|
+
for (const keyframe of keyframes) {
|
|
1220
|
+
const at = keyframe?.at;
|
|
1221
|
+
if (typeof at === "number" && Number.isFinite(at)) end = Math.max(end, at);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return end > 0 ? end : void 0;
|
|
1225
|
+
}
|
|
1226
|
+
function trackReferences(track) {
|
|
1227
|
+
const refs = /* @__PURE__ */ new Set([track.target]);
|
|
1228
|
+
if (track.target === "camera" && track.property === "focus") {
|
|
1229
|
+
for (const keyframe of track.keyframes) if (typeof keyframe.value === "string") refs.add(keyframe.value);
|
|
1230
|
+
}
|
|
1231
|
+
refs.delete(BOARD_CAMERA_TARGET);
|
|
1232
|
+
return [...refs];
|
|
1233
|
+
}
|
|
1234
|
+
function pruneBoardTrack(track, deleted) {
|
|
1235
|
+
if (deleted.has(track.target)) return null;
|
|
1236
|
+
if (track.target !== "camera" || track.property !== "focus") return track;
|
|
1237
|
+
const keyframes = track.keyframes.filter((entry) => typeof entry.value !== "string" || !deleted.has(entry.value));
|
|
1238
|
+
if (keyframes.length === track.keyframes.length) return track;
|
|
1239
|
+
return keyframes.length ? {
|
|
1240
|
+
...track,
|
|
1241
|
+
keyframes
|
|
1242
|
+
} : null;
|
|
1243
|
+
}
|
|
1244
|
+
function error(path, code, message) {
|
|
1245
|
+
return {
|
|
1246
|
+
severity: "error",
|
|
1247
|
+
code,
|
|
1248
|
+
path,
|
|
1249
|
+
message
|
|
1250
|
+
};
|
|
189
1251
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
"effects",
|
|
195
|
-
"compositions",
|
|
196
|
-
"playback"
|
|
197
|
-
])).optional(),
|
|
198
|
-
nodeIds: z.array(idSchema).max(5e4).optional(),
|
|
199
|
-
connectionIds: z.array(idSchema).max(5e4).optional(),
|
|
200
|
-
effectIds: z.array(idSchema).max(5e4).optional(),
|
|
201
|
-
compositionIds: z.array(idSchema).max(5e4).optional(),
|
|
202
|
-
viewport: z.object({
|
|
203
|
-
x: finiteSchema$1,
|
|
204
|
-
y: finiteSchema$1,
|
|
205
|
-
width: finiteSchema$1.positive(),
|
|
206
|
-
height: finiteSchema$1.positive()
|
|
207
|
-
}).optional()
|
|
208
|
-
});
|
|
209
|
-
z.object({
|
|
210
|
-
/** Return transactions with `version < before`. Omit for the newest page. */
|
|
211
|
-
before: z.number().int().positive().optional(),
|
|
212
|
-
limit: z.number().int().min(1).max(500).default(200),
|
|
213
|
-
/** Include the current rows on the newest page (default true). Tail refreshes turn this off. */
|
|
214
|
-
snapshot: z.boolean().default(true)
|
|
215
|
-
});
|
|
216
|
-
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
217
|
-
const BoardPlaybackPolicySchema = z.object({
|
|
218
|
-
compositionId: idSchema,
|
|
219
|
-
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
220
|
-
delayMs: finiteSchema$1.nonnegative().default(0)
|
|
221
|
-
});
|
|
222
|
-
function parseBoardPlaybackPolicy(metadata) {
|
|
223
|
-
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
224
|
-
return parsed.success ? parsed.data : null;
|
|
1252
|
+
function stripId(value) {
|
|
1253
|
+
if (!("id" in value)) return value;
|
|
1254
|
+
const { id: _id, ...rest } = value;
|
|
1255
|
+
return rest;
|
|
225
1256
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
position: finiteSchema$1.nonnegative().optional(),
|
|
232
|
-
timeScale: finiteSchema$1.positive().max(4).optional(),
|
|
233
|
-
shared: z.boolean().optional(),
|
|
234
|
-
seed: idSchema.optional()
|
|
235
|
-
}),
|
|
236
|
-
z.object({
|
|
237
|
-
commandId: idSchema,
|
|
238
|
-
type: z.literal("pause"),
|
|
239
|
-
playbackId: z.string().uuid()
|
|
240
|
-
}),
|
|
241
|
-
z.object({
|
|
242
|
-
commandId: idSchema,
|
|
243
|
-
type: z.literal("seek"),
|
|
244
|
-
playbackId: z.string().uuid(),
|
|
245
|
-
position: finiteSchema$1.nonnegative()
|
|
246
|
-
}),
|
|
247
|
-
z.object({
|
|
248
|
-
commandId: idSchema,
|
|
249
|
-
type: z.literal("stop"),
|
|
250
|
-
playbackId: z.string().uuid()
|
|
251
|
-
})
|
|
252
|
-
]);
|
|
253
|
-
//#endregion
|
|
254
|
-
//#region ../protocol/dist/board-animation.js
|
|
255
|
-
const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
256
|
-
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
257
|
-
const finiteSchema = z.number().finite();
|
|
258
|
-
/** Parameters shared by the built-in deal entrance preset. */
|
|
259
|
-
const BoardDealParamsSchema = z.object({
|
|
260
|
-
lift: finiteSchema.nonnegative().max(2e3).optional(),
|
|
261
|
-
swing: finiteSchema.nonnegative().max(2e3).optional(),
|
|
262
|
-
curve: finiteSchema.nonnegative().max(2e3).optional(),
|
|
263
|
-
tilt: finiteSchema.nonnegative().max(180).optional(),
|
|
264
|
-
scale: finiteSchema.positive().max(2).optional(),
|
|
265
|
-
duration: finiteSchema.positive().max(1e4).optional(),
|
|
266
|
-
landing: finiteSchema.nonnegative().max(1e4).optional()
|
|
267
|
-
}).strict();
|
|
268
|
-
/** A versioned, reusable animation preset reference. */
|
|
269
|
-
const BoardAnimationSpecSchema = z.object({
|
|
270
|
-
kind: extensionKindSchema,
|
|
271
|
-
kindVersion: z.number().int().positive().default(1),
|
|
272
|
-
params: jsonObjectSchema.default({})
|
|
273
|
-
}).strict().superRefine((spec, context) => {
|
|
274
|
-
if (spec.kind !== "effects.deal" || spec.kindVersion !== 1) return;
|
|
275
|
-
const parsed = BoardDealParamsSchema.safeParse(spec.params);
|
|
276
|
-
if (parsed.success) return;
|
|
277
|
-
context.addIssue({
|
|
278
|
-
code: "custom",
|
|
279
|
-
message: parsed.error.issues[0]?.message ?? "invalid effects.deal parameters",
|
|
280
|
-
path: ["params", ...parsed.error.issues[0]?.path ?? []]
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
/** Board-wide default motion policies. Omitted policies mean no motion. */
|
|
284
|
-
const BoardMotionSchema = z.object({ enter: BoardAnimationSpecSchema.optional() }).strict();
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region ../protocol/dist/board-capability-registry.js
|
|
287
|
-
const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
|
|
288
|
-
const EFFECTS = new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
289
|
-
const BUILTIN_VERSIONS = new Set(BOARD_BUILTIN_CAPABILITIES.map((c) => `${c.kind}:${c.id}@${c.version}`));
|
|
290
|
-
/**
|
|
291
|
-
* True when a built-in renderer exists for exactly this kind@version. A known
|
|
292
|
-
* kind at an unknown version is not built-in: it must not pass as one.
|
|
293
|
-
*/
|
|
294
|
-
function isBuiltinBoardCapability(kind, id, version) {
|
|
295
|
-
return BUILTIN_VERSIONS.has(`${kind}:${id}@${version}`);
|
|
296
|
-
}
|
|
297
|
-
const record$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
298
|
-
const finite$1 = (value) => typeof value === "number" && Number.isFinite(value);
|
|
299
|
-
function validateBuiltinBoardClip(clip, path = "clip") {
|
|
300
|
-
if (!CLIPS.has(clip.kind)) return [];
|
|
301
|
-
const errors = [];
|
|
302
|
-
const error = (message, suffix, coordinateSpace, code = "INVALID_BOARD_CLIP") => {
|
|
303
|
-
errors.push({
|
|
304
|
-
severity: "error",
|
|
305
|
-
code,
|
|
306
|
-
message,
|
|
307
|
-
path: `${path}.${suffix}`,
|
|
308
|
-
...coordinateSpace ? { coordinateSpace } : {}
|
|
309
|
-
});
|
|
1257
|
+
function applyBoardPatch(state, input, options = {}) {
|
|
1258
|
+
const parsedPatch = BoardPatchSchema.safeParse(input);
|
|
1259
|
+
if (!parsedPatch.success) return {
|
|
1260
|
+
ok: false,
|
|
1261
|
+
diagnostics: parsedPatch.error.issues.map((issue) => error(issue.path.map(String).join("."), "INVALID_PATCH", issue.message))
|
|
310
1262
|
};
|
|
311
|
-
|
|
312
|
-
if (clip.kind.startsWith("camera.") && clip.target.type !== "camera") error(`${clip.kind} must target the camera`, "target");
|
|
313
|
-
if (clip.kind === "motion.path") {
|
|
314
|
-
const points = clip.params.points;
|
|
315
|
-
if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) error("motion path must contain 2 to 10000 world-offset points", "params.points", "world-offset");
|
|
316
|
-
else for (const [index, point] of points.entries()) if (!record$1(point) || !finite$1(point.x) || !finite$1(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
|
|
317
|
-
}
|
|
318
|
-
if (clip.kind === "effects.particles") {
|
|
319
|
-
const count = clip.params.count;
|
|
320
|
-
if (!Number.isSafeInteger(count) || count < 1 || count > DEFAULT_BOARD_RENDER_LIMITS.particles) error(`particle count must be an integer from 1 to ${DEFAULT_BOARD_RENDER_LIMITS.particles}`, "params.count", void 0, "INVALID_PARTICLE_COUNT");
|
|
321
|
-
const bounds = clip.params.bounds;
|
|
322
|
-
if (!record$1(bounds) || !finite$1(bounds.x) || !finite$1(bounds.y) || !finite$1(bounds.width) || !finite$1(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
|
|
323
|
-
}
|
|
324
|
-
if (clip.kind === "camera.focus") {
|
|
325
|
-
const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
|
|
326
|
-
if (!parsed.success) error(parsed.error.issues[0]?.message ?? "invalid camera focus", "params", "world");
|
|
327
|
-
}
|
|
328
|
-
return errors;
|
|
329
|
-
}
|
|
330
|
-
function validateBuiltinBoardEffect(effect, path = "effect") {
|
|
1263
|
+
const patch = parsedPatch.data;
|
|
331
1264
|
const diagnostics = [];
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1265
|
+
const nextItems = /* @__PURE__ */ new Map();
|
|
1266
|
+
const nextAnimations = /* @__PURE__ */ new Map();
|
|
1267
|
+
const nextTracks = /* @__PURE__ */ new Map();
|
|
1268
|
+
let nextSettings;
|
|
1269
|
+
const item = (id) => nextItems.has(id) ? nextItems.get(id) ?? void 0 : state.item(id);
|
|
1270
|
+
const animation = (id) => nextAnimations.has(id) ? nextAnimations.get(id) ?? void 0 : state.animation(id);
|
|
1271
|
+
const setTrack = (animationId, trackId, value) => {
|
|
1272
|
+
const tracks = nextTracks.get(animationId) ?? /* @__PURE__ */ new Map();
|
|
1273
|
+
tracks.set(trackId, value);
|
|
1274
|
+
nextTracks.set(animationId, tracks);
|
|
1275
|
+
};
|
|
1276
|
+
const track = (animationId, trackId) => {
|
|
1277
|
+
const pending = nextTracks.get(animationId);
|
|
1278
|
+
if (pending?.has(trackId)) return pending.get(trackId) ?? void 0;
|
|
1279
|
+
return state.track(animationId, trackId);
|
|
1280
|
+
};
|
|
1281
|
+
if (patch.board !== void 0) {
|
|
1282
|
+
const merged = patch.board === null ? {} : mergePatch(state.settings(), patch.board);
|
|
1283
|
+
const parsed = BoardSettingsSchema.safeParse(merged);
|
|
1284
|
+
if (parsed.success) nextSettings = parsed.data;
|
|
1285
|
+
else for (const issue of parsed.error.issues) diagnostics.push(error(["board", ...issue.path.map(String)].join("."), "INVALID_BOARD", issue.message));
|
|
1286
|
+
}
|
|
1287
|
+
const zByParent = /* @__PURE__ */ new Map();
|
|
1288
|
+
for (const [id, value] of Object.entries(patch.items ?? {})) {
|
|
1289
|
+
if (value === null) {
|
|
1290
|
+
if (state.item(id)) nextItems.set(id, null);
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const current = state.item(id);
|
|
1294
|
+
const body = stripId(value);
|
|
1295
|
+
const candidate = !current || typeof body.type === "string" && body.type !== current.type ? { ...body } : mergePatch(current, body);
|
|
1296
|
+
if (candidate.z === void 0) {
|
|
1297
|
+
const parent = typeof candidate.parent === "string" ? candidate.parent : void 0;
|
|
1298
|
+
const key = parent ?? "";
|
|
1299
|
+
const top = zByParent.get(key) ?? state.maxZ(parent);
|
|
1300
|
+
candidate.z = current?.z ?? top + 1;
|
|
1301
|
+
if (!current) zByParent.set(key, top + 1);
|
|
1302
|
+
}
|
|
1303
|
+
const parsed = parseBoardItem(candidate, `items.${id}`);
|
|
1304
|
+
if (!parsed.ok) {
|
|
1305
|
+
diagnostics.push(...parsed.diagnostics);
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (current && boardJsonEquals(current, parsed.item)) continue;
|
|
1309
|
+
nextItems.set(id, parsed.item);
|
|
1310
|
+
}
|
|
1311
|
+
for (const [id, value] of Object.entries(patch.animations ?? {})) {
|
|
1312
|
+
if (value === null) {
|
|
1313
|
+
if (!state.animation(id)) continue;
|
|
1314
|
+
nextAnimations.set(id, null);
|
|
1315
|
+
for (const trackId of state.trackIds(id)) setTrack(id, trackId, null);
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
const { tracks, ...headerPatch } = stripId(value);
|
|
1319
|
+
if (state.item(id) || nextItems.get(id)) {
|
|
1320
|
+
diagnostics.push(error(`animations.${id}`, "ID_CONFLICT", `${id} is already an item id`));
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const current = state.animation(id);
|
|
1324
|
+
if (!current || Object.keys(headerPatch).length > 0) {
|
|
1325
|
+
const header = current ? mergePatch(current, headerPatch) : {
|
|
1326
|
+
...headerPatch,
|
|
1327
|
+
...headerPatch.duration === void 0 ? { duration: lastKeyframeAt([...Object.values(tracks ?? {}), ...state.trackIds(id).map((trackId) => state.track(id, trackId))]) } : {}
|
|
1328
|
+
};
|
|
1329
|
+
const parsed = BoardAnimationHeaderSchema.safeParse(header);
|
|
1330
|
+
if (!parsed.success) {
|
|
1331
|
+
for (const issue of parsed.error.issues) diagnostics.push(error([
|
|
1332
|
+
"animations",
|
|
1333
|
+
id,
|
|
1334
|
+
...issue.path.map(String)
|
|
1335
|
+
].join("."), "INVALID_ANIMATION", issue.message));
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (!current || !boardJsonEquals(current, parsed.data)) nextAnimations.set(id, parsed.data);
|
|
1339
|
+
}
|
|
1340
|
+
for (const [trackId, trackPatch] of Object.entries(tracks ?? {})) {
|
|
1341
|
+
const existing = state.track(id, trackId);
|
|
1342
|
+
if (trackPatch === null) {
|
|
1343
|
+
if (existing) setTrack(id, trackId, null);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
const parsed = BoardTrackSchema.safeParse(existing ? mergePatch(existing, trackPatch) : trackPatch);
|
|
1347
|
+
if (!parsed.success) {
|
|
1348
|
+
for (const issue of parsed.error.issues) diagnostics.push(error([
|
|
1349
|
+
"animations",
|
|
1350
|
+
id,
|
|
1351
|
+
"tracks",
|
|
1352
|
+
trackId,
|
|
1353
|
+
...issue.path.map(String)
|
|
1354
|
+
].join("."), "INVALID_TRACK", issue.message));
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
if (!existing || !boardJsonEquals(existing, parsed.data)) setTrack(id, trackId, parsed.data);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
for (const id of nextItems.keys()) if (nextItems.get(id) && animation(id)) diagnostics.push(error(`items.${id}`, "ID_CONFLICT", `${id} is already an animation id`));
|
|
1361
|
+
if (diagnostics.length) return {
|
|
1362
|
+
ok: false,
|
|
1363
|
+
diagnostics
|
|
1364
|
+
};
|
|
1365
|
+
const deletedItems = [...nextItems].filter(([, value]) => value === null).map(([id]) => id);
|
|
1366
|
+
const deletionLayout = createBoardLayout((id) => item(id) ?? state.item(id));
|
|
1367
|
+
const pendingDeletes = [...deletedItems];
|
|
1368
|
+
while (pendingDeletes.length) {
|
|
1369
|
+
const id = pendingDeletes.pop();
|
|
1370
|
+
for (const child of state.children(id)) {
|
|
1371
|
+
if (nextItems.has(child) && nextItems.get(child)?.parent !== id) continue;
|
|
1372
|
+
if (options.cascade) {
|
|
1373
|
+
if (nextItems.get(child) !== null) {
|
|
1374
|
+
nextItems.set(child, null);
|
|
1375
|
+
pendingDeletes.push(child);
|
|
1376
|
+
}
|
|
1377
|
+
} else diagnostics.push(error(`items.${id}`, "ITEM_REFERENCED", `${id} still has child ${child}; delete it too or use cascade`));
|
|
1378
|
+
}
|
|
1379
|
+
for (const arrowId of state.binders(id)) {
|
|
1380
|
+
const arrow = item(arrowId);
|
|
1381
|
+
if (arrow?.type !== "arrow") continue;
|
|
1382
|
+
const props = { ...arrow.props };
|
|
1383
|
+
for (const which of ["start", "end"]) {
|
|
1384
|
+
const end = props[which];
|
|
1385
|
+
if (!isArrowBinding(end) || end.item !== id) continue;
|
|
1386
|
+
const world = options.resolveArrowEnd?.(arrowId, which) ?? deletionLayout.arrowEnd(arrowId, which);
|
|
1387
|
+
props[which] = applyMatrix(invertMatrix(deletionLayout.matrix(arrowId)), world);
|
|
1388
|
+
}
|
|
1389
|
+
nextItems.set(arrowId, {
|
|
1390
|
+
...arrow,
|
|
1391
|
+
props
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
for (const ref of state.trackRefs(id)) {
|
|
1395
|
+
const current = track(ref.animation, ref.track);
|
|
1396
|
+
if (!current || !trackReferences(current).includes(id)) continue;
|
|
1397
|
+
if (options.cascade) setTrack(ref.animation, ref.track, pruneBoardTrack(current, /* @__PURE__ */ new Set([id])));
|
|
1398
|
+
else diagnostics.push(error(`items.${id}`, "ITEM_REFERENCED", `${id} is animated by ${ref.animation}.${ref.track}; delete the track or use cascade`));
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
for (const [id, value] of nextAnimations) {
|
|
1402
|
+
if (value !== null) continue;
|
|
1403
|
+
for (const ref of state.trackRefs(id)) {
|
|
1404
|
+
if (ref.animation === id || track(ref.animation, ref.track) === void 0) continue;
|
|
1405
|
+
if (options.cascade) setTrack(ref.animation, ref.track, null);
|
|
1406
|
+
else diagnostics.push(error(`animations.${id}`, "ANIMATION_REFERENCED", `${id} is nested in ${ref.animation}.${ref.track}; delete the track or use cascade`));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const validationState = {
|
|
1410
|
+
...state,
|
|
1411
|
+
trackIds: (id) => [.../* @__PURE__ */ new Set([...state.trackIds(id), ...nextTracks.get(id)?.keys() ?? []])]
|
|
1412
|
+
};
|
|
1413
|
+
const validationItems = new Map(nextItems);
|
|
1414
|
+
const descendants = [];
|
|
1415
|
+
for (const [id, value] of nextItems) if (value && value.parent !== state.item(id)?.parent) descendants.push(...state.children(id));
|
|
1416
|
+
const seenDescendants = /* @__PURE__ */ new Set();
|
|
1417
|
+
while (descendants.length) {
|
|
1418
|
+
const id = descendants.pop();
|
|
1419
|
+
if (seenDescendants.has(id)) continue;
|
|
1420
|
+
seenDescendants.add(id);
|
|
1421
|
+
validationItems.set(id, item(id) ?? null);
|
|
1422
|
+
descendants.push(...state.children(id));
|
|
1423
|
+
}
|
|
1424
|
+
for (const [id, value] of validationItems) {
|
|
1425
|
+
if (!value) continue;
|
|
1426
|
+
if (value.parent !== void 0) {
|
|
1427
|
+
const parent = item(value.parent);
|
|
1428
|
+
if (!parent) diagnostics.push(error(`items.${id}.parent`, "PARENT_NOT_FOUND", `parent ${value.parent} does not exist`));
|
|
1429
|
+
else if (parent.type !== "frame") diagnostics.push(error(`items.${id}.parent`, "INVALID_PARENT", `parent ${value.parent} is a ${parent.type}; only frames contain items`));
|
|
1430
|
+
else {
|
|
1431
|
+
let cursor = value.parent;
|
|
1432
|
+
for (let depth = 0; cursor; depth += 1) {
|
|
1433
|
+
if (depth >= 64) {
|
|
1434
|
+
diagnostics.push(error(`items.${id}.parent`, "PARENT_DEPTH", "parent hierarchy exceeds 64 levels"));
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
if (cursor === id) {
|
|
1438
|
+
diagnostics.push(error(`items.${id}.parent`, "PARENT_CYCLE", `${id} cannot be inside itself`));
|
|
1439
|
+
break;
|
|
1440
|
+
}
|
|
1441
|
+
cursor = item(cursor)?.parent;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
for (const bound of arrowBindings(value)) {
|
|
1446
|
+
const target = item(bound);
|
|
1447
|
+
if (!target) diagnostics.push(error(`items.${id}.props`, "BINDING_NOT_FOUND", `bound item ${bound} does not exist`));
|
|
1448
|
+
else if (bound === id || target.type === "arrow") diagnostics.push(error(`items.${id}.props`, "INVALID_BINDING", "arrows bind to items other than arrows"));
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
for (const [id, value] of nextItems) {
|
|
1452
|
+
if (!value || !state.item(id) || state.item(id)?.type === value.type) continue;
|
|
1453
|
+
if (value.type !== "frame") {
|
|
1454
|
+
for (const child of state.children(id)) if (item(child)?.parent === id) diagnostics.push(error(`items.${child}.parent`, "INVALID_PARENT", `${id} is not a frame`));
|
|
1455
|
+
}
|
|
1456
|
+
if (value.type === "arrow") for (const binder of state.binders(id)) {
|
|
1457
|
+
const arrow = item(binder);
|
|
1458
|
+
if (arrow && arrowBindings(arrow).includes(id)) diagnostics.push(error(`items.${binder}.props`, "INVALID_BINDING", "arrows cannot bind to arrows"));
|
|
1459
|
+
}
|
|
1460
|
+
for (const ref of state.trackRefs(id)) {
|
|
1461
|
+
const current = track(ref.animation, ref.track);
|
|
1462
|
+
if (current) diagnostics.push(...validateTrack(`animations.${ref.animation}.tracks.${ref.track}`, ref.animation, current, item, animation, track, validationState));
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
for (const [animationId, tracks] of nextTracks) {
|
|
1466
|
+
if (!animation(animationId)) {
|
|
1467
|
+
for (const [trackId, value] of tracks) if (value) diagnostics.push(error(`animations.${animationId}.tracks.${trackId}`, "ANIMATION_NOT_FOUND", `animation ${animationId} does not exist`));
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
for (const [trackId, value] of tracks) {
|
|
1471
|
+
if (!value) continue;
|
|
1472
|
+
diagnostics.push(...validateTrack(`animations.${animationId}.tracks.${trackId}`, animationId, value, item, animation, track, validationState));
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
if (diagnostics.length) return {
|
|
1476
|
+
ok: false,
|
|
1477
|
+
diagnostics
|
|
1478
|
+
};
|
|
1479
|
+
const before = emptyDelta();
|
|
1480
|
+
const after = emptyDelta();
|
|
1481
|
+
if (nextSettings && !boardJsonEquals(nextSettings, state.settings())) {
|
|
1482
|
+
before.board = state.settings();
|
|
1483
|
+
after.board = nextSettings;
|
|
1484
|
+
}
|
|
1485
|
+
for (const [id, value] of nextItems) {
|
|
1486
|
+
const previous = state.item(id) ?? null;
|
|
1487
|
+
if (boardJsonEquals(previous, value)) continue;
|
|
1488
|
+
before.items[id] = previous;
|
|
1489
|
+
after.items[id] = value;
|
|
1490
|
+
}
|
|
1491
|
+
for (const [id, value] of nextAnimations) {
|
|
1492
|
+
before.animations[id] = state.animation(id) ?? null;
|
|
1493
|
+
after.animations[id] = value;
|
|
1494
|
+
}
|
|
1495
|
+
for (const [animationId, tracks] of nextTracks) for (const [trackId, value] of tracks) {
|
|
1496
|
+
const previous = state.track(animationId, trackId) ?? null;
|
|
1497
|
+
if (boardJsonEquals(previous, value)) continue;
|
|
1498
|
+
slot(before.tracks, animationId)[trackId] = previous;
|
|
1499
|
+
slot(after.tracks, animationId)[trackId] = value;
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
ok: true,
|
|
1503
|
+
before: withoutEmpty(before),
|
|
1504
|
+
after: withoutEmpty(after),
|
|
1505
|
+
diagnostics: []
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
function validateTrack(path, animationId, value, item, animation, track, state) {
|
|
1509
|
+
const diagnostics = [];
|
|
1510
|
+
let targetType;
|
|
1511
|
+
if (value.target === "camera") targetType = BOARD_CAMERA_TARGET;
|
|
1512
|
+
else if (item(value.target)) targetType = item(value.target).type;
|
|
1513
|
+
else if (animation(value.target)) {
|
|
1514
|
+
targetType = "animation";
|
|
1515
|
+
if (value.target === animationId || nestsInto(value.target, animationId, animation, track, state)) return [error(`${path}.target`, "ANIMATION_CYCLE", `${animationId} cannot contain itself through ${value.target}`)];
|
|
1516
|
+
} else return [error(`${path}.target`, "TARGET_NOT_FOUND", `target ${value.target} is not an item, animation or camera`)];
|
|
1517
|
+
const resolved = resolveBoardProperty(targetType, value.property);
|
|
1518
|
+
if (!resolved.ok) return [error(`${path}.property`, "INVALID_PROPERTY", resolved.message)];
|
|
1519
|
+
for (const [index, keyframe] of value.keyframes.entries()) {
|
|
1520
|
+
const parsed = resolved.spec.schema.safeParse(keyframe.value);
|
|
1521
|
+
if (!parsed.success) {
|
|
1522
|
+
diagnostics.push(error(`${path}.keyframes.${index}.value`, "INVALID_KEYFRAME", parsed.error.issues[0]?.message ?? "invalid value"));
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1525
|
+
if (value.property === "focus" && typeof keyframe.value === "string" && !item(keyframe.value)) diagnostics.push(error(`${path}.keyframes.${index}.value`, "TARGET_NOT_FOUND", `focus item ${keyframe.value} does not exist`));
|
|
365
1526
|
}
|
|
1527
|
+
if (value.interpolation === "spline" && resolved.spec.kind !== "vec2") diagnostics.push(error(`${path}.interpolation`, "INVALID_TRACK", "spline interpolation needs a position-like property"));
|
|
366
1528
|
return diagnostics;
|
|
367
1529
|
}
|
|
368
|
-
function
|
|
369
|
-
|
|
1530
|
+
function nestsInto(from, into, animation, track, state) {
|
|
1531
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1532
|
+
const stack = [from];
|
|
1533
|
+
while (stack.length) {
|
|
1534
|
+
const current = stack.pop();
|
|
1535
|
+
if (current === into) return true;
|
|
1536
|
+
if (seen.has(current) || !animation(current)) continue;
|
|
1537
|
+
seen.add(current);
|
|
1538
|
+
for (const trackId of state.trackIds(current)) {
|
|
1539
|
+
const value = track(current, trackId);
|
|
1540
|
+
if (value && value.property === "time" && animation(value.target)) stack.push(value.target);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return false;
|
|
370
1544
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
1545
|
+
function createDocumentState(document) {
|
|
1546
|
+
let children = null;
|
|
1547
|
+
let binders = null;
|
|
1548
|
+
let refs = null;
|
|
1549
|
+
const index = () => {
|
|
1550
|
+
if (children) return;
|
|
1551
|
+
children = /* @__PURE__ */ new Map();
|
|
1552
|
+
binders = /* @__PURE__ */ new Map();
|
|
1553
|
+
refs = /* @__PURE__ */ new Map();
|
|
1554
|
+
for (const [id, value] of Object.entries(document.items)) {
|
|
1555
|
+
if (value.parent) children.set(value.parent, [...children.get(value.parent) ?? [], id]);
|
|
1556
|
+
for (const bound of arrowBindings(value)) binders.set(bound, [...binders.get(bound) ?? [], id]);
|
|
383
1557
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1558
|
+
for (const [animationId, value] of Object.entries(document.animations)) for (const [trackId, track] of Object.entries(value.tracks)) for (const ref of trackReferences(track)) refs.set(ref, [...refs.get(ref) ?? [], {
|
|
1559
|
+
animation: animationId,
|
|
1560
|
+
track: trackId
|
|
1561
|
+
}]);
|
|
1562
|
+
};
|
|
1563
|
+
return {
|
|
1564
|
+
settings: () => document.board,
|
|
1565
|
+
item: (id) => document.items[id],
|
|
1566
|
+
animation: (id) => {
|
|
1567
|
+
const value = document.animations[id];
|
|
1568
|
+
if (!value) return void 0;
|
|
1569
|
+
const { tracks: _tracks, ...header } = value;
|
|
1570
|
+
return header;
|
|
1571
|
+
},
|
|
1572
|
+
track: (animationId, trackId) => document.animations[animationId]?.tracks[trackId],
|
|
1573
|
+
trackIds: (animationId) => Object.keys(document.animations[animationId]?.tracks ?? {}),
|
|
1574
|
+
children: (id) => {
|
|
1575
|
+
index();
|
|
1576
|
+
return children?.get(id) ?? [];
|
|
1577
|
+
},
|
|
1578
|
+
binders: (id) => {
|
|
1579
|
+
index();
|
|
1580
|
+
return binders?.get(id) ?? [];
|
|
1581
|
+
},
|
|
1582
|
+
trackRefs: (id) => {
|
|
1583
|
+
index();
|
|
1584
|
+
return refs?.get(id) ?? [];
|
|
1585
|
+
},
|
|
1586
|
+
maxZ: (parent) => {
|
|
1587
|
+
let max = 0;
|
|
1588
|
+
for (const value of Object.values(document.items)) if (value.parent === parent && (value.z ?? 0) > max) max = value.z ?? 0;
|
|
1589
|
+
return max;
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
function applyBoardDelta(document, delta) {
|
|
1594
|
+
const items = delta.items ? { ...document.items } : document.items;
|
|
1595
|
+
for (const [id, value] of Object.entries(delta.items ?? {})) if (value) items[id] = value;
|
|
1596
|
+
else delete items[id];
|
|
1597
|
+
const animations = delta.animations || delta.tracks ? { ...document.animations } : document.animations;
|
|
1598
|
+
for (const [id, value] of Object.entries(delta.animations ?? {})) if (value) animations[id] = {
|
|
1599
|
+
...value,
|
|
1600
|
+
tracks: animations[id]?.tracks ?? {}
|
|
1601
|
+
};
|
|
1602
|
+
else delete animations[id];
|
|
1603
|
+
for (const [animationId, tracks] of Object.entries(delta.tracks ?? {})) {
|
|
1604
|
+
const current = animations[animationId];
|
|
1605
|
+
if (!current) continue;
|
|
1606
|
+
const nextTracks = { ...current.tracks };
|
|
1607
|
+
for (const [trackId, value] of Object.entries(tracks)) if (value) nextTracks[trackId] = value;
|
|
1608
|
+
else delete nextTracks[trackId];
|
|
1609
|
+
animations[animationId] = {
|
|
1610
|
+
...current,
|
|
1611
|
+
tracks: nextTracks
|
|
399
1612
|
};
|
|
400
|
-
case "motion.path": return { simulationSteps: Array.isArray(clip.params.points) ? clip.params.points.length : 0 };
|
|
401
|
-
default: return {};
|
|
402
1613
|
}
|
|
1614
|
+
return {
|
|
1615
|
+
board: delta.board ?? document.board,
|
|
1616
|
+
items,
|
|
1617
|
+
animations
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function applyBoardPatchToDocument(document, patch, options = {}) {
|
|
1621
|
+
const result = applyBoardPatch(createDocumentState(document), patch, options);
|
|
1622
|
+
return result.ok ? {
|
|
1623
|
+
ok: true,
|
|
1624
|
+
document: applyBoardDelta(document, result.after),
|
|
1625
|
+
before: result.before,
|
|
1626
|
+
after: result.after
|
|
1627
|
+
} : result;
|
|
1628
|
+
}
|
|
1629
|
+
function parseBoardDocument(input) {
|
|
1630
|
+
if (!isRecord$6(input)) return {
|
|
1631
|
+
ok: false,
|
|
1632
|
+
diagnostics: [error("", "INVALID_DOCUMENT", "expected an object")]
|
|
1633
|
+
};
|
|
1634
|
+
const result = applyBoardPatchToDocument({
|
|
1635
|
+
board: BoardSettingsSchema.parse({}),
|
|
1636
|
+
items: {},
|
|
1637
|
+
animations: {}
|
|
1638
|
+
}, {
|
|
1639
|
+
...isRecord$6(input.board) ? { board: input.board } : {},
|
|
1640
|
+
...isRecord$6(input.items) ? { items: input.items } : {},
|
|
1641
|
+
...isRecord$6(input.animations) ? { animations: input.animations } : {}
|
|
1642
|
+
});
|
|
1643
|
+
return result.ok ? {
|
|
1644
|
+
ok: true,
|
|
1645
|
+
document: result.document
|
|
1646
|
+
} : result;
|
|
403
1647
|
}
|
|
404
1648
|
//#endregion
|
|
405
1649
|
//#region ../protocol/dist/board-url.js
|
|
@@ -449,588 +1693,343 @@ function isBlockedIpv6(host) {
|
|
|
449
1693
|
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
450
1694
|
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
451
1695
|
}
|
|
452
|
-
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
453
|
-
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
454
|
-
if ((first & 65024) === 64512) return true;
|
|
455
|
-
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
456
|
-
if ((first & 65280) === 65280) return true;
|
|
457
|
-
return parts[0] === "2001" && parts[1] === "0db8";
|
|
458
|
-
}
|
|
459
|
-
function isPublicBoardRemoteAddress(value) {
|
|
460
|
-
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
461
|
-
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
462
|
-
return address.includes(":") && !isBlockedIpv6(address);
|
|
463
|
-
}
|
|
464
|
-
function isBlockedHost(hostname) {
|
|
465
|
-
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
466
|
-
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
467
|
-
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
472
|
-
* addresses; any future server-side fetcher must additionally validate DNS
|
|
473
|
-
* resolution to defend against rebinding.
|
|
474
|
-
*/
|
|
475
|
-
function normalizeBoardRemoteUrl(value) {
|
|
476
|
-
if (typeof value !== "string") return void 0;
|
|
477
|
-
const input = value.trim();
|
|
478
|
-
if (!input || input.length > 4096) return void 0;
|
|
479
|
-
try {
|
|
480
|
-
const url = new URL(input);
|
|
481
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
482
|
-
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
483
|
-
const normalized = url.toString();
|
|
484
|
-
return normalized.length <= 4096 ? normalized : void 0;
|
|
485
|
-
} catch {
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
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" });
|
|
490
|
-
//#endregion
|
|
491
|
-
//#region ../protocol/dist/board-document.js
|
|
492
|
-
const BoardFrameSchema = z.object({
|
|
493
|
-
x: z.number().finite(),
|
|
494
|
-
y: z.number().finite(),
|
|
495
|
-
width: z.number().finite().positive(),
|
|
496
|
-
height: z.number().finite().positive(),
|
|
497
|
-
rotation: z.number().finite().default(0)
|
|
498
|
-
});
|
|
499
|
-
/**
|
|
500
|
-
* The board camera. This is local UI state, not synced content: semantic ops
|
|
501
|
-
* (see diffBoardDocuments) never describe the viewport, and the editor holds
|
|
502
|
-
* the live camera separately from the persisted document. Here it only serves
|
|
503
|
-
* as an initial camera hint when a document is first loaded.
|
|
504
|
-
*/
|
|
505
|
-
const BoardViewportSchema = z.object({
|
|
506
|
-
x: z.number().finite(),
|
|
507
|
-
y: z.number().finite(),
|
|
508
|
-
zoom: z.number().finite().min(.05).max(8)
|
|
509
|
-
});
|
|
510
|
-
const BoardAppearanceSchema = z.object({
|
|
511
|
-
/** Legacy field retained when reading older Board documents; not used for rendering. */
|
|
512
|
-
theme: z.string().min(1).optional(),
|
|
513
|
-
/** Board-wide motion policies. Omitted policies mean no motion. */
|
|
514
|
-
motion: BoardMotionSchema.optional(),
|
|
515
|
-
/** Legacy field retained when reading older Board documents; not used for rendering. */
|
|
516
|
-
mood: z.enum([
|
|
517
|
-
"clean",
|
|
518
|
-
"playful",
|
|
519
|
-
"arcane",
|
|
520
|
-
"cyber",
|
|
521
|
-
"natural"
|
|
522
|
-
]).optional(),
|
|
523
|
-
background: z.object({
|
|
524
|
-
kind: z.enum([
|
|
525
|
-
"solid",
|
|
526
|
-
"dots",
|
|
527
|
-
"grid",
|
|
528
|
-
"image",
|
|
529
|
-
"shader",
|
|
530
|
-
"custom"
|
|
531
|
-
]).default("dots"),
|
|
532
|
-
color: z.string().optional(),
|
|
533
|
-
imageUrl: BoardRemoteUrlSchema.optional(),
|
|
534
|
-
fit: z.enum([
|
|
535
|
-
"cover",
|
|
536
|
-
"contain",
|
|
537
|
-
"repeat"
|
|
538
|
-
]).optional(),
|
|
539
|
-
position: z.enum([
|
|
540
|
-
"center",
|
|
541
|
-
"top",
|
|
542
|
-
"bottom",
|
|
543
|
-
"left",
|
|
544
|
-
"right"
|
|
545
|
-
]).optional(),
|
|
546
|
-
opacity: z.number().finite().min(0).max(1).optional()
|
|
547
|
-
}).default({ kind: "solid" }),
|
|
548
|
-
grid: z.object({
|
|
549
|
-
visible: z.boolean().default(false),
|
|
550
|
-
size: z.number().finite().min(4).default(24),
|
|
551
|
-
opacity: z.number().finite().min(0).max(1).default(.12)
|
|
552
|
-
}).default({
|
|
553
|
-
visible: false,
|
|
554
|
-
size: 24,
|
|
555
|
-
opacity: .12
|
|
556
|
-
})
|
|
557
|
-
});
|
|
558
|
-
/** Optional visual chrome still carried by a few older shapes. */
|
|
559
|
-
const BoardItemStyleSchema = z.object({
|
|
560
|
-
variant: z.string().min(1).default("default"),
|
|
561
|
-
theme: z.string().min(1).optional(),
|
|
562
|
-
accentColor: z.string().optional(),
|
|
563
|
-
size: z.enum([
|
|
564
|
-
"sm",
|
|
565
|
-
"md",
|
|
566
|
-
"lg"
|
|
567
|
-
]).default("md"),
|
|
568
|
-
emphasis: z.enum([
|
|
569
|
-
"normal",
|
|
570
|
-
"rare",
|
|
571
|
-
"epic",
|
|
572
|
-
"legendary"
|
|
573
|
-
]).default("normal"),
|
|
574
|
-
effects: z.array(z.string().min(1)).default([])
|
|
575
|
-
});
|
|
576
|
-
const SpaceFileRefSchema = z.object({
|
|
577
|
-
kind: z.literal("space-file"),
|
|
578
|
-
path: z.string().min(1)
|
|
579
|
-
});
|
|
580
|
-
const BoardMediaSnapshotSchema = z.object({
|
|
581
|
-
title: z.string().optional(),
|
|
582
|
-
mimeType: z.string().optional(),
|
|
583
|
-
size: z.number().finite().nonnegative().optional(),
|
|
584
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
585
|
-
/** Intrinsic pixel size once known. */
|
|
586
|
-
naturalWidth: z.number().finite().positive().optional(),
|
|
587
|
-
naturalHeight: z.number().finite().positive().optional()
|
|
588
|
-
});
|
|
589
|
-
const BoardItemBaseSchema = z.object({
|
|
590
|
-
id: z.string().min(1),
|
|
591
|
-
frame: BoardFrameSchema,
|
|
592
|
-
parentId: z.string().min(1).nullable().optional(),
|
|
593
|
-
/** When true the shape cannot be moved, resized, or deleted. */
|
|
594
|
-
locked: z.boolean().optional(),
|
|
595
|
-
style: BoardItemStyleSchema.optional(),
|
|
596
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
597
|
-
});
|
|
598
|
-
/** Freestanding text — no card chrome; its frame always follows the glyphs. */
|
|
599
|
-
const BoardTextItemSchema = BoardItemBaseSchema.extend({
|
|
600
|
-
type: z.literal("text"),
|
|
601
|
-
text: z.string().default(""),
|
|
602
|
-
color: z.string().min(1).default("neutral"),
|
|
603
|
-
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
604
|
-
});
|
|
605
|
-
const BoardGeoItemSchema = BoardItemBaseSchema.extend({
|
|
606
|
-
type: z.literal("geo"),
|
|
607
|
-
geo: z.string().min(1).default("rectangle"),
|
|
608
|
-
text: z.string().default(""),
|
|
609
|
-
color: z.string().min(1).default("brand"),
|
|
610
|
-
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
611
|
-
});
|
|
612
|
-
/** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
|
|
613
|
-
const DrawPointSchema = z.object({
|
|
614
|
-
x: z.number().finite(),
|
|
615
|
-
y: z.number().finite(),
|
|
616
|
-
p: z.number().finite().min(0).max(1).default(.5)
|
|
617
|
-
});
|
|
618
|
-
/** A world-space point. */
|
|
619
|
-
const BoardPointSchema = z.object({
|
|
620
|
-
x: z.number().finite(),
|
|
621
|
-
y: z.number().finite()
|
|
622
|
-
});
|
|
623
|
-
const BoardDrawItemSchema = BoardItemBaseSchema.extend({
|
|
624
|
-
type: z.literal("draw"),
|
|
625
|
-
points: z.array(DrawPointSchema).default([]),
|
|
626
|
-
color: z.string().min(1).default("brand"),
|
|
627
|
-
size: z.number().finite().positive().default(4)
|
|
628
|
-
});
|
|
629
|
-
/**
|
|
630
|
-
* A free arrow — a standalone annotation stroke between two world points.
|
|
631
|
-
*
|
|
632
|
-
* Arrows do not relate nodes. A relation between two nodes is a
|
|
633
|
-
* `BoardConnection`, which is stored separately and resolves its geometry from
|
|
634
|
-
* the live node frames. Keeping the two apart is what lets an arrow be a plain
|
|
635
|
-
* shape (its own frame, freely movable) while a connection stays purely
|
|
636
|
-
* semantic — neither has to pretend to be the other.
|
|
637
|
-
*/
|
|
638
|
-
const BoardArrowItemSchema = BoardItemBaseSchema.extend({
|
|
639
|
-
type: z.literal("arrow"),
|
|
640
|
-
start: BoardPointSchema,
|
|
641
|
-
end: BoardPointSchema,
|
|
642
|
-
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
643
|
-
color: z.string().min(1).default("brand"),
|
|
644
|
-
size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
|
|
645
|
-
arrowStart: z.boolean().default(false),
|
|
646
|
-
arrowEnd: z.boolean().default(true),
|
|
647
|
-
label: z.string().default("")
|
|
648
|
-
});
|
|
649
|
-
/** A frame container for organising shapes. */
|
|
650
|
-
const BoardFrameItemSchema = BoardItemBaseSchema.extend({
|
|
651
|
-
type: z.literal("frame"),
|
|
652
|
-
label: z.string().default("Frame"),
|
|
653
|
-
color: z.string().min(1).default("neutral")
|
|
654
|
-
});
|
|
655
|
-
/** Image node — space file only, natural aspect, no chrome. */
|
|
656
|
-
const BoardImageItemSchema = BoardItemBaseSchema.extend({
|
|
657
|
-
type: z.literal("image"),
|
|
658
|
-
ref: SpaceFileRefSchema,
|
|
659
|
-
snapshot: BoardMediaSnapshotSchema.optional(),
|
|
660
|
-
/** Optional normalized crop in source image space (0..1). */
|
|
661
|
-
crop: z.object({
|
|
662
|
-
x: z.number().finite().min(0).max(1).default(0),
|
|
663
|
-
y: z.number().finite().min(0).max(1).default(0),
|
|
664
|
-
w: z.number().finite().min(0).max(1).default(1),
|
|
665
|
-
h: z.number().finite().min(0).max(1).default(1)
|
|
666
|
-
}).optional()
|
|
667
|
-
});
|
|
668
|
-
/** Video node — space file only. Playback state is local UI, never synced. */
|
|
669
|
-
const BoardVideoItemSchema = BoardItemBaseSchema.extend({
|
|
670
|
-
type: z.literal("video"),
|
|
671
|
-
ref: SpaceFileRefSchema,
|
|
672
|
-
snapshot: BoardMediaSnapshotSchema.optional()
|
|
673
|
-
});
|
|
674
|
-
/** Audio node — space file only. Playback state is local UI, never synced. */
|
|
675
|
-
const BoardAudioItemSchema = BoardItemBaseSchema.extend({
|
|
676
|
-
type: z.literal("audio"),
|
|
677
|
-
ref: SpaceFileRefSchema,
|
|
678
|
-
snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
|
|
679
|
-
});
|
|
680
|
-
/**
|
|
681
|
-
* Cached display facts for a file card.
|
|
682
|
-
*
|
|
683
|
-
* Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
|
|
684
|
-
* cache and never a second source of truth: the workspace file remains
|
|
685
|
-
* authoritative, and a stale snapshot is detectable rather than silently wrong.
|
|
686
|
-
* It is stored so that opening a board renders complete cards immediately, with
|
|
687
|
-
* no per-node file read on the first paint.
|
|
688
|
-
*/
|
|
689
|
-
const BoardFileSnapshotSchema = z.object({
|
|
690
|
-
title: z.string().optional(),
|
|
691
|
-
mimeType: z.string().optional(),
|
|
692
|
-
size: z.number().finite().nonnegative().optional(),
|
|
693
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
694
|
-
/** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
|
|
695
|
-
excerpt: z.string().optional(),
|
|
696
|
-
/** Cover image inside the space, already resolved to a workspace path. */
|
|
697
|
-
coverPath: z.string().optional(),
|
|
698
|
-
/** Cover image at an absolute https URL, as declared by the file itself. */
|
|
699
|
-
coverUrl: z.string().optional()
|
|
700
|
-
});
|
|
701
|
-
/**
|
|
702
|
-
* File node — a thumbnail entry point to any workspace file.
|
|
703
|
-
*
|
|
704
|
-
* This is the fallback for every dropped file that is not natively an image or
|
|
705
|
-
* video, including binaries and unknown extensions: a board should never refuse
|
|
706
|
-
* a file, only present it with less detail. Presentation tiers are derived from
|
|
707
|
-
* the snapshot (see filePreviewKind), not stored, so there is no display state
|
|
708
|
-
* to drift from the facts.
|
|
709
|
-
*/
|
|
710
|
-
const BoardFileItemSchema = BoardItemBaseSchema.extend({
|
|
711
|
-
type: z.literal("file"),
|
|
712
|
-
ref: SpaceFileRefSchema,
|
|
713
|
-
snapshot: BoardFileSnapshotSchema.optional()
|
|
714
|
-
});
|
|
715
|
-
const BoardTaskMediaArtifactFields = {
|
|
716
|
-
id: z.string().min(1).max(240),
|
|
717
|
-
title: z.string().max(240).optional(),
|
|
718
|
-
url: BoardRemoteUrlSchema,
|
|
719
|
-
mimeType: z.string().max(160).optional()
|
|
720
|
-
};
|
|
721
|
-
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
722
|
-
z.object({
|
|
723
|
-
...BoardTaskMediaArtifactFields,
|
|
724
|
-
type: z.literal("image"),
|
|
725
|
-
naturalWidth: z.number().positive().optional(),
|
|
726
|
-
naturalHeight: z.number().positive().optional()
|
|
727
|
-
}).strict(),
|
|
728
|
-
z.object({
|
|
729
|
-
...BoardTaskMediaArtifactFields,
|
|
730
|
-
type: z.literal("video"),
|
|
731
|
-
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
732
|
-
durationMs: z.number().int().positive().optional(),
|
|
733
|
-
naturalWidth: z.number().positive().optional(),
|
|
734
|
-
naturalHeight: z.number().positive().optional()
|
|
735
|
-
}).strict(),
|
|
736
|
-
z.object({
|
|
737
|
-
...BoardTaskMediaArtifactFields,
|
|
738
|
-
type: z.literal("audio"),
|
|
739
|
-
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
740
|
-
durationMs: z.number().int().positive().optional()
|
|
741
|
-
}).strict(),
|
|
742
|
-
z.object({
|
|
743
|
-
id: z.string().min(1).max(240),
|
|
744
|
-
type: z.literal("text"),
|
|
745
|
-
title: z.string().max(240).optional(),
|
|
746
|
-
textExcerpt: z.string().min(1).max(480)
|
|
747
|
-
}).strict()
|
|
748
|
-
]);
|
|
749
|
-
/**
|
|
750
|
-
* Cached task facts used for an immediate first paint. The task run remains the
|
|
751
|
-
* source of truth and live clients refresh this projection by `taskRunId`.
|
|
752
|
-
*/
|
|
753
|
-
const BoardTaskSnapshotSchema = z.object({
|
|
754
|
-
taskType: z.string().min(1).max(120),
|
|
755
|
-
status: z.enum([
|
|
756
|
-
"pending",
|
|
757
|
-
"running",
|
|
758
|
-
"completed",
|
|
759
|
-
"failed"
|
|
760
|
-
]),
|
|
761
|
-
title: z.string().min(1).max(240),
|
|
762
|
-
model: z.string().max(160).optional(),
|
|
763
|
-
promptExcerpt: z.string().max(480).optional(),
|
|
764
|
-
artifactCount: z.number().int().nonnegative(),
|
|
765
|
-
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
766
|
-
updatedAt: z.string().optional()
|
|
767
|
-
}).strict();
|
|
768
|
-
/** A stable reference to a task run with a small, replaceable display cache. */
|
|
769
|
-
const BoardTaskItemSchema = BoardItemBaseSchema.extend({
|
|
770
|
-
type: z.literal("task"),
|
|
771
|
-
taskRunId: z.string().min(1),
|
|
772
|
-
snapshot: BoardTaskSnapshotSchema
|
|
773
|
-
});
|
|
774
|
-
/**
|
|
775
|
-
* A forward-compatible carrier for shape types this client does not recognise.
|
|
776
|
-
* Its discriminant is the literal `"unknown"` so the item union still narrows
|
|
777
|
-
* cleanly on `type`. The real type string and every original field are preserved
|
|
778
|
-
* verbatim in `raw`.
|
|
779
|
-
*/
|
|
780
|
-
const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
|
|
781
|
-
const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
|
|
1696
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
1697
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
1698
|
+
if ((first & 65024) === 64512) return true;
|
|
1699
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
1700
|
+
if ((first & 65280) === 65280) return true;
|
|
1701
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
1702
|
+
}
|
|
1703
|
+
function isPublicBoardRemoteAddress(value) {
|
|
1704
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
1705
|
+
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
1706
|
+
return address.includes(":") && !isBlockedIpv6(address);
|
|
1707
|
+
}
|
|
1708
|
+
function isBlockedHost(hostname) {
|
|
1709
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
1710
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
1711
|
+
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
1712
|
+
return false;
|
|
1713
|
+
}
|
|
782
1714
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
1715
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
1716
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
1717
|
+
* resolution to defend against rebinding.
|
|
786
1718
|
*/
|
|
787
|
-
function
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
const parsed = BoardAudioItemSchema.safeParse(raw);
|
|
800
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
801
|
-
}
|
|
802
|
-
case "file": {
|
|
803
|
-
const parsed = BoardFileItemSchema.safeParse(raw);
|
|
804
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
805
|
-
}
|
|
806
|
-
case "task": {
|
|
807
|
-
const parsed = BoardTaskItemSchema.safeParse(raw);
|
|
808
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
809
|
-
}
|
|
810
|
-
case "text": {
|
|
811
|
-
const parsed = BoardTextItemSchema.safeParse(raw);
|
|
812
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
813
|
-
}
|
|
814
|
-
case "geo": {
|
|
815
|
-
const parsed = BoardGeoItemSchema.safeParse(raw);
|
|
816
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
817
|
-
}
|
|
818
|
-
case "draw": {
|
|
819
|
-
const parsed = BoardDrawItemSchema.safeParse(raw);
|
|
820
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
821
|
-
}
|
|
822
|
-
case "arrow": {
|
|
823
|
-
const parsed = BoardArrowItemSchema.safeParse(raw);
|
|
824
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
825
|
-
}
|
|
826
|
-
case "frame": {
|
|
827
|
-
const parsed = BoardFrameItemSchema.safeParse(raw);
|
|
828
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
829
|
-
}
|
|
830
|
-
default: return makeUnknownItem(raw);
|
|
1719
|
+
function normalizeBoardRemoteUrl(value) {
|
|
1720
|
+
if (typeof value !== "string") return void 0;
|
|
1721
|
+
const input = value.trim();
|
|
1722
|
+
if (!input || input.length > 4096) return void 0;
|
|
1723
|
+
try {
|
|
1724
|
+
const url = new URL(input);
|
|
1725
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
1726
|
+
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
1727
|
+
const normalized = url.toString();
|
|
1728
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
1729
|
+
} catch {
|
|
1730
|
+
return;
|
|
831
1731
|
}
|
|
832
1732
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1733
|
+
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" });
|
|
1734
|
+
//#endregion
|
|
1735
|
+
//#region ../protocol/dist/turn-origin.js
|
|
1736
|
+
const record$2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1737
|
+
const normalizeSessionTurnOrigin = (value) => {
|
|
1738
|
+
const input = record$2(value);
|
|
1739
|
+
if (!input || ![
|
|
1740
|
+
"prompt",
|
|
1741
|
+
"scheduled_prompt",
|
|
1742
|
+
"background_task",
|
|
1743
|
+
"hook"
|
|
1744
|
+
].includes(String(input.kind))) return null;
|
|
1745
|
+
const source = normalizeRequestSource(input);
|
|
1746
|
+
if (!source?.spaceId || !source.sessionId || !source.turnId) return null;
|
|
837
1747
|
return {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
height: 80,
|
|
845
|
-
rotation: 0
|
|
846
|
-
},
|
|
847
|
-
...typeof record.parentId === "string" || record.parentId === null ? { parentId: record.parentId } : {},
|
|
848
|
-
...record.locked === true ? { locked: true } : {},
|
|
849
|
-
...styleParsed.success && record.style ? { style: styleParsed.data } : {},
|
|
850
|
-
...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
|
|
851
|
-
raw: record
|
|
1748
|
+
kind: input.kind,
|
|
1749
|
+
spaceId: source.spaceId,
|
|
1750
|
+
sessionId: source.sessionId,
|
|
1751
|
+
turnId: source.turnId,
|
|
1752
|
+
...source.toolCallId ? { toolCallId: source.toolCallId } : {},
|
|
1753
|
+
...typeof input.depth === "number" && Number.isSafeInteger(input.depth) && input.depth > 0 ? { depth: input.depth } : {}
|
|
852
1754
|
};
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
* Node relations. Separate from `items` because a connection has no frame of
|
|
869
|
-
* its own — its geometry is derived from the nodes it joins, so it is a
|
|
870
|
-
* relation over the item set rather than a member of it.
|
|
871
|
-
*
|
|
872
|
-
* Connections referencing a missing node are dropped on parse: a relation to
|
|
873
|
-
* nothing is not a relation, and keeping one would let an invisible dangling
|
|
874
|
-
* edge accumulate silently. Callers that need to know write through the
|
|
875
|
-
* transaction API, which reports the reference error instead.
|
|
876
|
-
*/
|
|
877
|
-
connections: z.array(BoardConnectionSchema).default([])
|
|
878
|
-
});
|
|
1755
|
+
};
|
|
1756
|
+
/** Reads durable provenance; legacy background tasks only knew their caller's Session/Turn. */
|
|
1757
|
+
const readSessionTurnOrigin = (meta, spaceId) => {
|
|
1758
|
+
const input = record$2(meta);
|
|
1759
|
+
const origin = normalizeSessionTurnOrigin(input?.origin);
|
|
1760
|
+
if (origin) return origin;
|
|
1761
|
+
const context = record$2(input?.context);
|
|
1762
|
+
if (context?.kind !== "background_bash_task" || !isUuidLike(spaceId)) return null;
|
|
1763
|
+
const legacy = record$2(context.origin);
|
|
1764
|
+
return normalizeSessionTurnOrigin({
|
|
1765
|
+
...legacy,
|
|
1766
|
+
spaceId,
|
|
1767
|
+
kind: "background_task"
|
|
1768
|
+
});
|
|
1769
|
+
};
|
|
879
1770
|
//#endregion
|
|
880
|
-
//#region ../protocol/dist/
|
|
881
|
-
const
|
|
882
|
-
"
|
|
883
|
-
"
|
|
884
|
-
"
|
|
885
|
-
"
|
|
886
|
-
"blue",
|
|
887
|
-
"green",
|
|
888
|
-
"amber",
|
|
889
|
-
"violet",
|
|
890
|
-
"rose"
|
|
891
|
-
];
|
|
892
|
-
const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
|
|
893
|
-
const BOARD_GEO_KINDS = [
|
|
894
|
-
"rectangle",
|
|
895
|
-
"rounded",
|
|
896
|
-
"ellipse",
|
|
897
|
-
"diamond",
|
|
898
|
-
"triangle"
|
|
899
|
-
];
|
|
900
|
-
const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
|
|
901
|
-
const BOARD_NATIVE_NODE_TYPES = [
|
|
902
|
-
"image",
|
|
903
|
-
"video",
|
|
904
|
-
"audio",
|
|
905
|
-
"file",
|
|
906
|
-
"task",
|
|
907
|
-
"text",
|
|
908
|
-
"geo",
|
|
909
|
-
"draw",
|
|
910
|
-
"arrow",
|
|
911
|
-
"frame"
|
|
1771
|
+
//#region ../protocol/dist/sent-turns.js
|
|
1772
|
+
const KINDS = [
|
|
1773
|
+
"prompt",
|
|
1774
|
+
"scheduled_prompt",
|
|
1775
|
+
"background_task",
|
|
1776
|
+
"hook"
|
|
912
1777
|
];
|
|
913
|
-
const
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
1778
|
+
const record$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1779
|
+
const normalizeSentTurnRef = (value) => {
|
|
1780
|
+
const input = record$1(value);
|
|
1781
|
+
if (!input) return null;
|
|
1782
|
+
if (!isUuidLike(input.sessionId) || !isUuidLike(input.turnId)) return null;
|
|
1783
|
+
const kind = String(input.kind);
|
|
1784
|
+
if (!KINDS.includes(kind)) return null;
|
|
1785
|
+
const source = normalizeRequestSource({
|
|
1786
|
+
spaceId: input.spaceId,
|
|
1787
|
+
toolCallId: input.toolCallId
|
|
1788
|
+
});
|
|
1789
|
+
return {
|
|
1790
|
+
...source?.spaceId ? { spaceId: source.spaceId } : {},
|
|
1791
|
+
sessionId: input.sessionId,
|
|
1792
|
+
turnId: input.turnId,
|
|
1793
|
+
kind,
|
|
1794
|
+
...source?.toolCallId ? { toolCallId: source.toolCallId } : {}
|
|
1795
|
+
};
|
|
917
1796
|
};
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
size: z.number().finite().nonnegative().optional(),
|
|
932
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
933
|
-
naturalWidth: z.number().finite().positive().optional(),
|
|
934
|
-
naturalHeight: z.number().finite().positive().optional()
|
|
935
|
-
}).strict();
|
|
936
|
-
const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
|
|
937
|
-
const dataSchemas = {
|
|
938
|
-
text: z.object({
|
|
939
|
-
...commonData,
|
|
940
|
-
text: z.string().default(""),
|
|
941
|
-
color: BoardColorIdSchema.default("neutral"),
|
|
942
|
-
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
943
|
-
}).strict(),
|
|
944
|
-
geo: z.object({
|
|
945
|
-
...commonData,
|
|
946
|
-
geo: BoardGeoKindSchema.default("rectangle"),
|
|
947
|
-
text: z.string().default(""),
|
|
948
|
-
color: BoardColorIdSchema.default("brand"),
|
|
949
|
-
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
950
|
-
}).strict(),
|
|
951
|
-
draw: z.object({
|
|
952
|
-
...commonData,
|
|
953
|
-
points: z.array(pointSchema).min(1),
|
|
954
|
-
color: BoardColorIdSchema.default("brand"),
|
|
955
|
-
size: strokeSizeSchema.default(4)
|
|
956
|
-
}).strict(),
|
|
957
|
-
arrow: z.object({
|
|
958
|
-
...commonData,
|
|
959
|
-
start: worldPointSchema,
|
|
960
|
-
end: worldPointSchema,
|
|
961
|
-
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
962
|
-
color: BoardColorIdSchema.default("brand"),
|
|
963
|
-
size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
|
|
964
|
-
arrowStart: z.boolean().default(false),
|
|
965
|
-
arrowEnd: z.boolean().default(true),
|
|
966
|
-
label: z.string().default("")
|
|
967
|
-
}).strict(),
|
|
968
|
-
frame: z.object({
|
|
969
|
-
...commonData,
|
|
970
|
-
label: z.string().default("Frame"),
|
|
971
|
-
color: BoardColorIdSchema.default("neutral")
|
|
972
|
-
}).strict(),
|
|
973
|
-
image: z.object({
|
|
974
|
-
...commonData,
|
|
975
|
-
crop: z.object({
|
|
976
|
-
x: z.number().finite().min(0).max(1),
|
|
977
|
-
y: z.number().finite().min(0).max(1),
|
|
978
|
-
w: z.number().finite().min(0).max(1),
|
|
979
|
-
h: z.number().finite().min(0).max(1)
|
|
980
|
-
}).strict().optional()
|
|
981
|
-
}).strict(),
|
|
982
|
-
video: z.object(commonData).strict(),
|
|
983
|
-
audio: z.object(commonData).strict(),
|
|
984
|
-
file: z.object(commonData).strict(),
|
|
985
|
-
task: z.object({
|
|
986
|
-
...commonData,
|
|
987
|
-
taskRunId: z.string().min(1)
|
|
988
|
-
}).strict()
|
|
1797
|
+
/** Deduplicates by child Turn, so a retried request cannot render one twice. */
|
|
1798
|
+
const readSentTurns = (meta) => {
|
|
1799
|
+
const raw = record$1(meta)?.messagesSent;
|
|
1800
|
+
if (!Array.isArray(raw)) return [];
|
|
1801
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1802
|
+
const refs = [];
|
|
1803
|
+
for (const value of raw) {
|
|
1804
|
+
const ref = normalizeSentTurnRef(value);
|
|
1805
|
+
if (!ref || seen.has(ref.turnId)) continue;
|
|
1806
|
+
seen.add(ref.turnId);
|
|
1807
|
+
refs.push(ref);
|
|
1808
|
+
}
|
|
1809
|
+
return refs;
|
|
989
1810
|
};
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1811
|
+
//#endregion
|
|
1812
|
+
//#region ../protocol/dist/display/actions.js
|
|
1813
|
+
const DISPLAY_ACTION_TYPES = [
|
|
1814
|
+
"tap",
|
|
1815
|
+
"long_press",
|
|
1816
|
+
"swipe",
|
|
1817
|
+
"scroll",
|
|
1818
|
+
"type",
|
|
1819
|
+
"key",
|
|
1820
|
+
"system",
|
|
1821
|
+
"wait"
|
|
1822
|
+
];
|
|
1823
|
+
const ACTION_GAP_MS = 150;
|
|
1824
|
+
const TAP_MS = 60;
|
|
1825
|
+
const MULTI_TAP_GAP_MS = 80;
|
|
1826
|
+
const LONG_PRESS_MS = 800;
|
|
1827
|
+
const SWIPE_MS = 300;
|
|
1828
|
+
const SCROLL_MS = 400;
|
|
1829
|
+
const WAIT_MS = 500;
|
|
1830
|
+
const MAX_STEP_MS = 1e4;
|
|
1831
|
+
var DisplayActionError = class extends Error {
|
|
1832
|
+
constructor(message) {
|
|
1833
|
+
super(message);
|
|
1834
|
+
this.name = "DisplayActionError";
|
|
1835
|
+
}
|
|
1012
1836
|
};
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1837
|
+
const MODIFIER_ALIASES = {
|
|
1838
|
+
control: "Control",
|
|
1839
|
+
ctrl: "Control",
|
|
1840
|
+
alt: "Alt",
|
|
1841
|
+
option: "Alt",
|
|
1842
|
+
shift: "Shift",
|
|
1843
|
+
meta: "Meta",
|
|
1844
|
+
cmd: "Meta",
|
|
1845
|
+
command: "Meta",
|
|
1846
|
+
super: "Meta",
|
|
1847
|
+
win: "Meta"
|
|
1848
|
+
};
|
|
1849
|
+
function displayKeyEvents(key, modifiers = [], t) {
|
|
1850
|
+
const at = t === void 0 ? {} : { t };
|
|
1851
|
+
return [
|
|
1852
|
+
...modifiers.map((modifier) => ({
|
|
1853
|
+
type: "key",
|
|
1854
|
+
action: "down",
|
|
1855
|
+
key: modifier,
|
|
1856
|
+
...at
|
|
1857
|
+
})),
|
|
1858
|
+
{
|
|
1859
|
+
type: "key",
|
|
1860
|
+
action: "press",
|
|
1861
|
+
key,
|
|
1862
|
+
...at
|
|
1863
|
+
},
|
|
1864
|
+
...[...modifiers].reverse().map((modifier) => ({
|
|
1865
|
+
type: "key",
|
|
1866
|
+
action: "up",
|
|
1867
|
+
key: modifier,
|
|
1868
|
+
...at
|
|
1869
|
+
}))
|
|
1870
|
+
];
|
|
1871
|
+
}
|
|
1872
|
+
function parseDisplayKeyCombo(combo) {
|
|
1873
|
+
const parts = combo.length > 1 && combo.endsWith("++") ? [...combo.slice(0, -2).split("+"), "+"] : combo.length > 1 ? combo.split("+") : [combo];
|
|
1874
|
+
const key = parts.pop() ?? "";
|
|
1875
|
+
const modifiers = parts.map((part) => MODIFIER_ALIASES[part.trim().toLowerCase()]);
|
|
1876
|
+
if (!key || modifiers.some((modifier) => !modifier)) throw new DisplayActionError(`unknown key combo "${combo}"`);
|
|
1877
|
+
return {
|
|
1878
|
+
key: MODIFIER_ALIASES[key.toLowerCase()] ?? key,
|
|
1879
|
+
modifiers: [...new Set(modifiers)]
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
function compileDisplayActions(actions, width, height) {
|
|
1883
|
+
const events = [];
|
|
1884
|
+
let t = 0;
|
|
1885
|
+
const point = (action, xKey = "x", yKey = "y") => {
|
|
1886
|
+
const x = action[xKey];
|
|
1887
|
+
const y = action[yKey];
|
|
1888
|
+
if (typeof x !== "number" || typeof y !== "number" || !Number.isFinite(x) || !Number.isFinite(y)) throw new DisplayActionError(`${action.type} needs ${xKey} and ${yKey}`);
|
|
1889
|
+
if (x < 0 || y < 0 || x >= width || y >= height) throw new DisplayActionError(`${action.type} at (${x}, ${y}) is outside the ${width}×${height} image`);
|
|
1890
|
+
return normalizeDisplayPoint(x, y, width, height);
|
|
1891
|
+
};
|
|
1892
|
+
const duration = (action, fallback) => Math.round(Math.min(MAX_STEP_MS, Math.max(1, action.duration_ms ?? fallback)));
|
|
1893
|
+
const element = (ref, elementAction, text) => {
|
|
1894
|
+
if (!DISPLAY_ELEMENT_REF_PATTERN.test(ref)) throw new DisplayActionError(`"${ref}" is not an element ref such as e3.12`);
|
|
1895
|
+
events.push({
|
|
1896
|
+
type: "element",
|
|
1897
|
+
ref,
|
|
1898
|
+
action: elementAction,
|
|
1899
|
+
...text === void 0 ? {} : { text },
|
|
1900
|
+
t
|
|
1901
|
+
});
|
|
1902
|
+
t += TAP_MS;
|
|
1903
|
+
};
|
|
1904
|
+
for (const action of actions) {
|
|
1905
|
+
if (action.ref !== void 0) {
|
|
1906
|
+
if (action.type === "tap" && (action.count ?? 1) === 1 && (action.button ?? "primary") === "primary") element(action.ref, "click");
|
|
1907
|
+
else if (action.type === "long_press") element(action.ref, "longPress");
|
|
1908
|
+
else if (action.type === "type") {
|
|
1909
|
+
if (action.text === void 0 || action.text.length > 4096) throw new DisplayActionError(`type into an element needs text of at most ${DISPLAY_INPUT_MAX_TEXT} characters`);
|
|
1910
|
+
element(action.ref, "setText", action.text);
|
|
1911
|
+
} else if (action.type === "scroll") element(action.ref, action.direction === "up" || action.direction === "left" ? "scrollBackward" : "scrollForward");
|
|
1912
|
+
else throw new DisplayActionError(`${action.type} cannot take a ref; use coordinates`);
|
|
1913
|
+
t += ACTION_GAP_MS;
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
switch (action.type) {
|
|
1917
|
+
case "tap": {
|
|
1918
|
+
const at = {
|
|
1919
|
+
...point(action),
|
|
1920
|
+
...action.button && action.button !== "primary" ? { button: action.button } : {}
|
|
1921
|
+
};
|
|
1922
|
+
const count = Math.round(Math.min(3, Math.max(1, action.count ?? 1)));
|
|
1923
|
+
for (let tap = 0; tap < count; tap++) {
|
|
1924
|
+
if (tap > 0) t += MULTI_TAP_GAP_MS;
|
|
1925
|
+
events.push({
|
|
1926
|
+
type: "pointer",
|
|
1927
|
+
action: "down",
|
|
1928
|
+
...at,
|
|
1929
|
+
t
|
|
1930
|
+
}, {
|
|
1931
|
+
type: "pointer",
|
|
1932
|
+
action: "up",
|
|
1933
|
+
...at,
|
|
1934
|
+
t: t + TAP_MS
|
|
1935
|
+
});
|
|
1936
|
+
t += TAP_MS;
|
|
1937
|
+
}
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
case "long_press": {
|
|
1941
|
+
const at = point(action);
|
|
1942
|
+
const hold = duration(action, LONG_PRESS_MS);
|
|
1943
|
+
events.push({
|
|
1944
|
+
type: "pointer",
|
|
1945
|
+
action: "down",
|
|
1946
|
+
...at,
|
|
1947
|
+
t
|
|
1948
|
+
}, {
|
|
1949
|
+
type: "pointer",
|
|
1950
|
+
action: "up",
|
|
1951
|
+
...at,
|
|
1952
|
+
t: t + hold
|
|
1953
|
+
});
|
|
1954
|
+
t += hold;
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
case "swipe": {
|
|
1958
|
+
const from = point(action);
|
|
1959
|
+
const to = point(action, "to_x", "to_y");
|
|
1960
|
+
const length = duration(action, SWIPE_MS);
|
|
1961
|
+
events.push({
|
|
1962
|
+
type: "pointer",
|
|
1963
|
+
action: "down",
|
|
1964
|
+
...from,
|
|
1965
|
+
t
|
|
1966
|
+
}, {
|
|
1967
|
+
type: "pointer",
|
|
1968
|
+
action: "move",
|
|
1969
|
+
x: (from.x + to.x) / 2,
|
|
1970
|
+
y: (from.y + to.y) / 2,
|
|
1971
|
+
t: t + Math.round(length / 2)
|
|
1972
|
+
}, {
|
|
1973
|
+
type: "pointer",
|
|
1974
|
+
action: "up",
|
|
1975
|
+
...to,
|
|
1976
|
+
t: t + length
|
|
1977
|
+
});
|
|
1978
|
+
t += length;
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
case "scroll": {
|
|
1982
|
+
const at = typeof action.x === "number" || typeof action.y === "number" ? point(action) : {
|
|
1983
|
+
x: .5,
|
|
1984
|
+
y: .5
|
|
1985
|
+
};
|
|
1986
|
+
const amount = Math.min(1, Math.max(.1, action.amount ?? .5));
|
|
1987
|
+
const direction = action.direction ?? "down";
|
|
1988
|
+
const dx = direction === "right" ? amount : direction === "left" ? -amount : 0;
|
|
1989
|
+
const dy = direction === "down" ? amount : direction === "up" ? -amount : 0;
|
|
1990
|
+
events.push({
|
|
1991
|
+
type: "scroll",
|
|
1992
|
+
...at,
|
|
1993
|
+
dx,
|
|
1994
|
+
dy,
|
|
1995
|
+
t
|
|
1996
|
+
});
|
|
1997
|
+
t += SCROLL_MS;
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
case "type":
|
|
2001
|
+
if (!action.text) throw new DisplayActionError("type needs text");
|
|
2002
|
+
for (const text of splitDisplayText(action.text)) events.push({
|
|
2003
|
+
type: "text",
|
|
2004
|
+
text,
|
|
2005
|
+
t
|
|
2006
|
+
});
|
|
2007
|
+
break;
|
|
2008
|
+
case "key": {
|
|
2009
|
+
if (!action.key) throw new DisplayActionError("key needs a key");
|
|
2010
|
+
const { key, modifiers } = parseDisplayKeyCombo(action.key);
|
|
2011
|
+
events.push(...displayKeyEvents(key, modifiers, t));
|
|
2012
|
+
break;
|
|
2013
|
+
}
|
|
2014
|
+
case "system":
|
|
2015
|
+
if (!action.action) throw new DisplayActionError("system needs an action");
|
|
2016
|
+
events.push({
|
|
2017
|
+
type: "system",
|
|
2018
|
+
action: action.action,
|
|
2019
|
+
t
|
|
2020
|
+
});
|
|
2021
|
+
break;
|
|
2022
|
+
case "wait":
|
|
2023
|
+
t += duration(action, WAIT_MS);
|
|
2024
|
+
break;
|
|
2025
|
+
default: throw new DisplayActionError(`unknown action ${String(action.type)}`);
|
|
2026
|
+
}
|
|
2027
|
+
t += ACTION_GAP_MS;
|
|
2028
|
+
if (t > 6e4) throw new DisplayActionError("the actions take longer than a minute; split them up");
|
|
2029
|
+
}
|
|
2030
|
+
if (events.length === 0) throw new DisplayActionError("no action touches the display");
|
|
2031
|
+
return events;
|
|
1032
2032
|
}
|
|
1033
|
-
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])]));
|
|
1034
2033
|
//#endregion
|
|
1035
2034
|
//#region ../protocol/dist/generation/catalog.js
|
|
1036
2035
|
function isGenerationModelHidden(model) {
|
|
@@ -1936,6 +2935,54 @@ function fingerprintProjectionTurns(turns) {
|
|
|
1936
2935
|
return `${turns.length}:${fingerprint(turns)}`;
|
|
1937
2936
|
}
|
|
1938
2937
|
//#endregion
|
|
2938
|
+
//#region ../protocol/dist/core/image.js
|
|
2939
|
+
/** Pi only models inline image bytes; remote images travel through it under this MIME type with the URL as `data`. */
|
|
2940
|
+
const IMAGE_URL_MIME_TYPE = "application/x-cohub-image-url";
|
|
2941
|
+
const imageUrlContent = (url) => ({
|
|
2942
|
+
type: "image",
|
|
2943
|
+
data: url,
|
|
2944
|
+
mimeType: IMAGE_URL_MIME_TYPE
|
|
2945
|
+
});
|
|
2946
|
+
const DATA_URL_PATTERN = /^data:([^;,]*)((?:;[^;,]*)*);base64,/i;
|
|
2947
|
+
function parseBase64DataUrl(value) {
|
|
2948
|
+
const match = DATA_URL_PATTERN.exec(value.trimStart());
|
|
2949
|
+
if (!match) return null;
|
|
2950
|
+
return {
|
|
2951
|
+
mediaType: match[1]?.trim().toLowerCase() || null,
|
|
2952
|
+
data: value.trimStart().slice(match[0].length).trim()
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
const stripDataUrlPrefix = (data) => parseBase64DataUrl(data)?.data ?? data.trim();
|
|
2956
|
+
const IMAGE_SIGNATURES = [
|
|
2957
|
+
["image/png", (b) => b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71],
|
|
2958
|
+
["image/jpeg", (b) => b[0] === 255 && b[1] === 216 && b[2] === 255],
|
|
2959
|
+
["image/gif", (b) => b[0] === 71 && b[1] === 73 && b[2] === 70],
|
|
2960
|
+
["image/webp", (b) => b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80]
|
|
2961
|
+
];
|
|
2962
|
+
function sniffImageMimeType(bytes) {
|
|
2963
|
+
return IMAGE_SIGNATURES.find(([, matches]) => matches(bytes))?.[0] ?? null;
|
|
2964
|
+
}
|
|
2965
|
+
function sniffBase64ImageMimeType(data) {
|
|
2966
|
+
try {
|
|
2967
|
+
return sniffImageMimeType(Uint8Array.from(atob(stripDataUrlPrefix(data).slice(0, 16)), (char) => char.charCodeAt(0)));
|
|
2968
|
+
} catch {
|
|
2969
|
+
return null;
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
function imageBlockToPi(block) {
|
|
2973
|
+
if (block.source.type === "url") {
|
|
2974
|
+
const url = block.source.url.trim();
|
|
2975
|
+
return url ? imageUrlContent(url) : null;
|
|
2976
|
+
}
|
|
2977
|
+
const data = stripDataUrlPrefix(block.source.data);
|
|
2978
|
+
if (!data) return null;
|
|
2979
|
+
return {
|
|
2980
|
+
type: "image",
|
|
2981
|
+
data,
|
|
2982
|
+
mimeType: block.source.media_type && block.source.media_type !== "application/octet-stream" ? block.source.media_type : sniffBase64ImageMimeType(data) ?? "application/octet-stream"
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
//#endregion
|
|
1939
2986
|
//#region ../protocol/dist/runtime/context.js
|
|
1940
2987
|
const createEmptyUsage = () => ({
|
|
1941
2988
|
input: 0,
|
|
@@ -1953,19 +3000,15 @@ const createEmptyUsage = () => ({
|
|
|
1953
3000
|
});
|
|
1954
3001
|
/**
|
|
1955
3002
|
* Project one block as itself, or drop it. Nothing is ever rewritten into prompt text.
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
3003
|
+
* Remote images stay URLs for the model runtime to resolve; system notes and unknown blocks
|
|
3004
|
+
* have no native representation here, and the durable copy stays in the database.
|
|
1958
3005
|
*/
|
|
1959
|
-
function projectBlock(block) {
|
|
3006
|
+
function projectBlock(block, remoteImages = true) {
|
|
1960
3007
|
if (block.type === "text") return {
|
|
1961
3008
|
type: "text",
|
|
1962
3009
|
text: block.text
|
|
1963
3010
|
};
|
|
1964
|
-
if (block.type === "image") return block.source.type === "base64" ?
|
|
1965
|
-
type: "image",
|
|
1966
|
-
data: block.source.data,
|
|
1967
|
-
mimeType: block.source.media_type
|
|
1968
|
-
} : null;
|
|
3011
|
+
if (block.type === "image") return remoteImages || block.source.type === "base64" ? imageBlockToPi(block) : null;
|
|
1969
3012
|
if (block.type === "thinking") return {
|
|
1970
3013
|
type: "thinking",
|
|
1971
3014
|
thinking: block.thinking,
|
|
@@ -1973,7 +3016,7 @@ function projectBlock(block) {
|
|
|
1973
3016
|
};
|
|
1974
3017
|
return null;
|
|
1975
3018
|
}
|
|
1976
|
-
const projectContent = (content) => content.map(projectBlock).filter((block) => block !== null);
|
|
3019
|
+
const projectContent = (content) => content.map((block) => projectBlock(block)).filter((block) => block !== null);
|
|
1977
3020
|
const isCompaction = (message) => message.role === "system" && message.content.some((block) => block.type === "system_note" && block.note_type === "compacted");
|
|
1978
3021
|
/** A generation placeholder must never be projected as if it were a finished result. */
|
|
1979
3022
|
const isPendingGenerationResult = (message) => message.role === "assistant" && message.meta?.messageKind === "generation_result" && message.meta?.generationStatus !== "completed" && message.meta?.generationStatus !== "failed";
|
|
@@ -2030,7 +3073,7 @@ function contextToPiMessages(messages, options = {}) {
|
|
|
2030
3073
|
name: block.name,
|
|
2031
3074
|
arguments: block.input
|
|
2032
3075
|
}];
|
|
2033
|
-
const projected = projectBlock(block);
|
|
3076
|
+
const projected = projectBlock(block, false);
|
|
2034
3077
|
return projected ? [projected] : [];
|
|
2035
3078
|
});
|
|
2036
3079
|
if (content.length) result.push({
|
|
@@ -2092,7 +3135,7 @@ const resolveHarness = (meta) => {
|
|
|
2092
3135
|
return isLocalHarness(value) ? value : "cohub";
|
|
2093
3136
|
};
|
|
2094
3137
|
const runtimeCapabilitiesSchema = z.object({
|
|
2095
|
-
harnesses: z.array(z.enum(["pi", "codex"])).
|
|
3138
|
+
harnesses: z.array(z.enum(["pi", "codex"])).max(2),
|
|
2096
3139
|
models: z.array(z.object({
|
|
2097
3140
|
harness: z.enum(["pi", "codex"]),
|
|
2098
3141
|
provider: z.string().max(100),
|
|
@@ -2334,6 +3377,7 @@ const runtimeCommandSchema = z.discriminatedUnion("type", [
|
|
|
2334
3377
|
]);
|
|
2335
3378
|
//#endregion
|
|
2336
3379
|
//#region ../protocol/dist/public-identifiers.js
|
|
3380
|
+
const HOME_SPACE_SLUG = "home";
|
|
2337
3381
|
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
2338
3382
|
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
2339
3383
|
/**
|
|
@@ -4888,6 +5932,10 @@ attach: attachAppEmbed },
|
|
|
4888
5932
|
ensureRealtimeConnected(this.websocketClient);
|
|
4889
5933
|
return this.websocketClient.on("event", handler);
|
|
4890
5934
|
}
|
|
5935
|
+
/** Current realtime transport state; pair with {@link onConnection} for changes. */
|
|
5936
|
+
get connectionState() {
|
|
5937
|
+
return this.websocketClient.state;
|
|
5938
|
+
}
|
|
4891
5939
|
onConnection(handler) {
|
|
4892
5940
|
const connectingCleanup = this.websocketClient.on("connecting", (payload) => {
|
|
4893
5941
|
handler({
|
|
@@ -5027,6 +6075,34 @@ const parseWorkRef = parseAppRef;
|
|
|
5027
6075
|
/** @deprecated Use `formatAppRef`. */
|
|
5028
6076
|
const formatWorkRef = formatAppRef;
|
|
5029
6077
|
//#endregion
|
|
6078
|
+
//#region src/space-ref.ts
|
|
6079
|
+
const canonicalUuid = (value) => {
|
|
6080
|
+
const hex = value.replaceAll("-", "").toLowerCase();
|
|
6081
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
6082
|
+
};
|
|
6083
|
+
function parseSpaceRef(value) {
|
|
6084
|
+
const trimmed = value.trim();
|
|
6085
|
+
if (isUuidOrShortUuid(trimmed)) return {
|
|
6086
|
+
kind: "id",
|
|
6087
|
+
id: canonicalUuid(trimmed)
|
|
6088
|
+
};
|
|
6089
|
+
const parts = trimmed.split("/");
|
|
6090
|
+
if (parts.length === 2) {
|
|
6091
|
+
const username = parseUsername(parts[0]);
|
|
6092
|
+
const slug = parseSpaceSlug(parts[1]);
|
|
6093
|
+
return username && slug ? {
|
|
6094
|
+
kind: "public",
|
|
6095
|
+
username,
|
|
6096
|
+
slug
|
|
6097
|
+
} : null;
|
|
6098
|
+
}
|
|
6099
|
+
const slug = parts.length === 1 ? parseSpaceSlug(trimmed) : null;
|
|
6100
|
+
return slug ? {
|
|
6101
|
+
kind: "owned",
|
|
6102
|
+
slug
|
|
6103
|
+
} : null;
|
|
6104
|
+
}
|
|
6105
|
+
//#endregion
|
|
5030
6106
|
//#region src/app-grant-cache.ts
|
|
5031
6107
|
const STORAGE_PREFIX = "cohub:work-grants";
|
|
5032
6108
|
const CACHE_VERSION = 1;
|
|
@@ -5236,6 +6312,7 @@ const AUTHORIZE_TIMEOUT_MS = 3e4;
|
|
|
5236
6312
|
const MAX_SILENT_SPACE_ATTEMPTS = 4;
|
|
5237
6313
|
const timeoutSignal = (ms) => typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(ms) : void 0;
|
|
5238
6314
|
const spaceListSignal = () => timeoutSignal(SPACE_LIST_TIMEOUT_MS);
|
|
6315
|
+
const SPACE_LIST_PAGE_SIZE = 100;
|
|
5239
6316
|
var AppAuthorizationError = class extends Error {
|
|
5240
6317
|
status;
|
|
5241
6318
|
code;
|
|
@@ -5848,58 +6925,67 @@ function createAppBridgeCore(config) {
|
|
|
5848
6925
|
...typeof value.isPinned === "boolean" ? { isPinned: value.isPinned } : {}
|
|
5849
6926
|
};
|
|
5850
6927
|
}
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
* host with the viewer's own token — the app only ever learns the space
|
|
5854
|
-
* the viewer picks, never the list.
|
|
5855
|
-
*/
|
|
5856
|
-
async function listViewerSpaces() {
|
|
5857
|
-
const request = async (forceRefresh = false) => {
|
|
6928
|
+
async function viewerFetch(path, init = {}) {
|
|
6929
|
+
const send = async (forceRefresh) => {
|
|
5858
6930
|
const userToken = await getAccessToken({ forceRefresh });
|
|
5859
|
-
if (!userToken)
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
6931
|
+
if (!userToken) return null;
|
|
6932
|
+
return fetch(`${apiOrigin}${path}`, {
|
|
6933
|
+
...init,
|
|
6934
|
+
headers: {
|
|
6935
|
+
...init.headers,
|
|
6936
|
+
Authorization: `Bearer ${userToken}`
|
|
6937
|
+
},
|
|
5865
6938
|
signal: spaceListSignal()
|
|
5866
6939
|
});
|
|
5867
6940
|
};
|
|
6941
|
+
const response = await send(false);
|
|
6942
|
+
return response?.status === 401 ? send(true) : response;
|
|
6943
|
+
}
|
|
6944
|
+
/** Owned or joined Spaces only; a public Space the viewer can merely view is not theirs to grant. */
|
|
6945
|
+
async function lookupViewerSpace(spaceId) {
|
|
6946
|
+
const response = await viewerFetch(`/api/spaces/${encodeURIComponent(spaceId)}`);
|
|
6947
|
+
if (response?.status === 403 || response?.status === 404) return null;
|
|
6948
|
+
if (!response?.ok) throw new Error("Space lookup failed.");
|
|
6949
|
+
const space = await response.json();
|
|
6950
|
+
return space?.relation === "owner" || space?.relation === "member" ? toSpaceOption(space) : null;
|
|
6951
|
+
}
|
|
6952
|
+
/** The first page seeds the picker; named Spaces are looked up one by one. */
|
|
6953
|
+
async function listViewerSpaces(requested, hints) {
|
|
5868
6954
|
try {
|
|
5869
|
-
|
|
5870
|
-
if (response
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
6955
|
+
const response = await viewerFetch(`/api/spaces?limit=${SPACE_LIST_PAGE_SIZE}`);
|
|
6956
|
+
if (!response) {
|
|
6957
|
+
await startSignIn();
|
|
6958
|
+
throw new AppLoginRedirect();
|
|
6959
|
+
}
|
|
6960
|
+
if (!response.ok) return null;
|
|
6961
|
+
const body = await response.json();
|
|
6962
|
+
if (!Array.isArray(body?.items)) return null;
|
|
6963
|
+
const spaces = body.items.flatMap((space) => {
|
|
5875
6964
|
const option = toSpaceOption(space);
|
|
5876
6965
|
return option ? [option] : [];
|
|
5877
6966
|
});
|
|
6967
|
+
const missing = [.../* @__PURE__ */ new Set([requested, ...hints])].filter((id) => Boolean(id) && !spaces.some((space) => space.id === id));
|
|
6968
|
+
const found = await Promise.all(missing.map((id) => id === requested ? lookupViewerSpace(id) : lookupViewerSpace(id).catch(() => null)));
|
|
6969
|
+
return [...spaces, ...found.filter((space) => space !== null)];
|
|
5878
6970
|
} catch (error) {
|
|
5879
6971
|
if (error instanceof AppLoginRedirect) throw error;
|
|
5880
6972
|
return null;
|
|
5881
6973
|
}
|
|
5882
6974
|
}
|
|
5883
|
-
/**
|
|
5884
|
-
|
|
5885
|
-
* empty. `GET /api/spaces/default` returns their home Space, creating it on
|
|
5886
|
-
* first use — the same path taken when they first enter Cohub.
|
|
5887
|
-
*/
|
|
5888
|
-
async function ensureDefaultSpace() {
|
|
5889
|
-
const request = async (forceRefresh = false) => {
|
|
5890
|
-
const userToken = await getAccessToken({ forceRefresh });
|
|
5891
|
-
if (!userToken) return null;
|
|
5892
|
-
return fetch(`${apiOrigin}/api/spaces/default`, {
|
|
5893
|
-
headers: { Authorization: `Bearer ${userToken}` },
|
|
5894
|
-
signal: spaceListSignal()
|
|
5895
|
-
});
|
|
5896
|
-
};
|
|
6975
|
+
/** The viewer's own Home, created on first use when they have no Space. */
|
|
6976
|
+
async function ensureHomeSpace() {
|
|
5897
6977
|
try {
|
|
5898
|
-
|
|
5899
|
-
|
|
6978
|
+
const response = await viewerFetch("/api/me/spaces/home", {
|
|
6979
|
+
method: "POST",
|
|
6980
|
+
headers: {
|
|
6981
|
+
"Content-Type": "application/json",
|
|
6982
|
+
[COHUB_SOURCE_HEADER.via]: "app"
|
|
6983
|
+
},
|
|
6984
|
+
body: JSON.stringify({ appId: app.id })
|
|
6985
|
+
});
|
|
5900
6986
|
if (!response?.ok) return null;
|
|
5901
|
-
const
|
|
5902
|
-
return
|
|
6987
|
+
const space = await response.json();
|
|
6988
|
+
return space ? toSpaceOption(space) : null;
|
|
5903
6989
|
} catch {
|
|
5904
6990
|
return null;
|
|
5905
6991
|
}
|
|
@@ -5917,6 +7003,11 @@ function createAppBridgeCore(config) {
|
|
|
5917
7003
|
localStorage.setItem(pickedSpaceStorageKey, spaceId);
|
|
5918
7004
|
} catch {}
|
|
5919
7005
|
}
|
|
7006
|
+
function contextSpaceIds() {
|
|
7007
|
+
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
7008
|
+
const shell = config.getShell?.() ?? config.shell;
|
|
7009
|
+
return [invocation?.spaceId ?? void 0, shell?.space?.id ?? void 0];
|
|
7010
|
+
}
|
|
5920
7011
|
/**
|
|
5921
7012
|
* Resolves the default target for a Space-bound request to a Space the
|
|
5922
7013
|
* viewer controls — preferring wherever the app was invoked, then the last
|
|
@@ -5924,19 +7015,14 @@ function createAppBridgeCore(config) {
|
|
|
5924
7015
|
*/
|
|
5925
7016
|
function resolveDefaultSpaceId(candidates) {
|
|
5926
7017
|
if (!candidates?.length) return void 0;
|
|
5927
|
-
|
|
5928
|
-
const invocation = activeInvocation !== void 0 ? activeInvocation : config.getInvocation?.() ?? config.invocation;
|
|
5929
|
-
const shell = config.getShell?.() ?? config.shell;
|
|
5930
|
-
return accessible(invocation?.spaceId) ?? accessible(shell?.space?.id) ?? accessible(readLastPickedSpace()) ?? candidates[0]?.id;
|
|
7018
|
+
return [...contextSpaceIds(), readLastPickedSpace()].find((id) => id && candidates.some((space) => space.id === id)) ?? candidates[0]?.id;
|
|
5931
7019
|
}
|
|
5932
7020
|
/**
|
|
5933
7021
|
* The Space the app is currently in, from trusted host context only (no
|
|
5934
7022
|
* network). Used to attempt silent renewal before loading the Space list.
|
|
5935
7023
|
*/
|
|
5936
7024
|
function resolveContextSpaceId() {
|
|
5937
|
-
|
|
5938
|
-
const shell = config.getShell?.() ?? config.shell;
|
|
5939
|
-
return invocation?.spaceId ?? shell?.space?.id ?? void 0;
|
|
7025
|
+
return contextSpaceIds().find(Boolean);
|
|
5940
7026
|
}
|
|
5941
7027
|
/**
|
|
5942
7028
|
* Creates the Space with the viewer's own token — the same `POST /api/spaces`
|
|
@@ -6288,10 +7374,10 @@ function createAppBridgeCore(config) {
|
|
|
6288
7374
|
const preListTargets = !spaceLevel ? listGrantedAppSpacesForScopes(viewerUuid, app.id, scopes, app.spaceId) : spaceId === void 0 && !selectSpace && contextSpaceId && hasGrantedAppScopes(viewerUuid, app.id, scopes, contextSpaceId) ? [contextSpaceId] : [];
|
|
6289
7375
|
if (!alwaysAsk && await attemptSilentRenewal(reserved, viewerUuid, scopes, preListTargets)) return;
|
|
6290
7376
|
if (state.pendingAuth !== reserved) return;
|
|
6291
|
-
let spaces = spaceLevel ? await listViewerSpaces() : void 0;
|
|
7377
|
+
let spaces = spaceLevel ? await listViewerSpaces(spaceId, [...contextSpaceIds(), lastPicked]) : void 0;
|
|
6292
7378
|
if (state.pendingAuth !== reserved) return;
|
|
6293
7379
|
if (spaces && spaces.length === 0) {
|
|
6294
|
-
const fallback = await
|
|
7380
|
+
const fallback = await ensureHomeSpace();
|
|
6295
7381
|
if (state.pendingAuth !== reserved) return;
|
|
6296
7382
|
if (fallback) spaces = [fallback];
|
|
6297
7383
|
}
|
|
@@ -6402,9 +7488,14 @@ function createAppBridgeCore(config) {
|
|
|
6402
7488
|
* Only a Space the host actually loaded may become the target, so a UI bug
|
|
6403
7489
|
* cannot mint a grant against an arbitrary id.
|
|
6404
7490
|
*/
|
|
6405
|
-
function setSelectedSpace(
|
|
6406
|
-
|
|
6407
|
-
if (!state.
|
|
7491
|
+
function setSelectedSpace(space) {
|
|
7492
|
+
const pending = state.pendingAuth;
|
|
7493
|
+
if (!pending || state.authSaving || !state.canChangeSpace || !pending.spaces) return;
|
|
7494
|
+
const spaceId = typeof space === "string" ? space : space.id;
|
|
7495
|
+
if (!pending.spaces.some((candidate) => candidate.id === spaceId)) {
|
|
7496
|
+
if (typeof space === "string") return;
|
|
7497
|
+
pending.spaces = [...pending.spaces, space];
|
|
7498
|
+
}
|
|
6408
7499
|
if (state.selectedSpaceId === spaceId) return;
|
|
6409
7500
|
state.selectedSpaceId = spaceId;
|
|
6410
7501
|
notify();
|
|
@@ -6655,9 +7746,12 @@ const DEFAULT_MEDIA_TYPES = {
|
|
|
6655
7746
|
audio: "audio/mpeg"
|
|
6656
7747
|
};
|
|
6657
7748
|
function outputType(value) {
|
|
6658
|
-
return value === "image" || value === "video" || value === "audio"
|
|
7749
|
+
return value === "image" || value === "video" || value === "audio" ? value : null;
|
|
6659
7750
|
}
|
|
6660
|
-
/**
|
|
7751
|
+
/**
|
|
7752
|
+
* Only media blocks become outputs; text such as a revised prompt or a refusal
|
|
7753
|
+
* stays in the raw result. Covers fold into the media they depict.
|
|
7754
|
+
*/
|
|
6661
7755
|
function projectOutputs(run) {
|
|
6662
7756
|
const blocks = generationOutput(run);
|
|
6663
7757
|
const covers = generationCovers(blocks);
|
|
@@ -6670,20 +7764,17 @@ function projectOutputs(run) {
|
|
|
6670
7764
|
const type = outputType(block.type);
|
|
6671
7765
|
if (!type || coverIndexes.has(index)) return [];
|
|
6672
7766
|
const size = blockNaturalSize(block);
|
|
6673
|
-
const text = type === "text" ? blockText(block) ?? null : null;
|
|
6674
|
-
if (type === "text" && !text) return [];
|
|
6675
7767
|
return [{
|
|
6676
7768
|
index,
|
|
6677
7769
|
type,
|
|
6678
|
-
url:
|
|
7770
|
+
url: blockUrl(block) ?? null,
|
|
6679
7771
|
previewUrl: blockPreviewUrl(block) ?? coverUrl(index) ?? null,
|
|
6680
7772
|
title: blockTitle(block) ?? null,
|
|
6681
7773
|
mimeType: blockMimeType(block) ?? null,
|
|
6682
|
-
text,
|
|
6683
7774
|
width: size.naturalWidth ?? null,
|
|
6684
7775
|
height: size.naturalHeight ?? null,
|
|
6685
7776
|
durationMs: blockDurationMs(block) ?? null,
|
|
6686
|
-
deferred:
|
|
7777
|
+
deferred: isDeferred(block)
|
|
6687
7778
|
}];
|
|
6688
7779
|
});
|
|
6689
7780
|
}
|
|
@@ -6723,249 +7814,4 @@ function generationOutputSource(result, outputIndex) {
|
|
|
6723
7814
|
return `data:${declared && MEDIA_TYPE_PATTERN.test(declared) ? declared : DEFAULT_MEDIA_TYPES[String(block.type)] ?? "application/octet-stream"};base64,${data}`;
|
|
6724
7815
|
}
|
|
6725
7816
|
//#endregion
|
|
6726
|
-
|
|
6727
|
-
function track(input) {
|
|
6728
|
-
return BoardTrackSchema.parse(input);
|
|
6729
|
-
}
|
|
6730
|
-
function proceduralClip(input) {
|
|
6731
|
-
return BoardProceduralClipSchema.parse({
|
|
6732
|
-
...input,
|
|
6733
|
-
kindVersion: input.kindVersion ?? 1
|
|
6734
|
-
});
|
|
6735
|
-
}
|
|
6736
|
-
function composition(input) {
|
|
6737
|
-
return BoardCompositionSchema.parse(input);
|
|
6738
|
-
}
|
|
6739
|
-
function compileComposition(input) {
|
|
6740
|
-
return composition({
|
|
6741
|
-
id: input.id,
|
|
6742
|
-
name: input.name,
|
|
6743
|
-
timeline: {
|
|
6744
|
-
duration: input.duration,
|
|
6745
|
-
tracks: (input.tracks ?? []).map(track),
|
|
6746
|
-
clips: (input.clips ?? []).map(proceduralClip),
|
|
6747
|
-
markers: input.markers ?? []
|
|
6748
|
-
},
|
|
6749
|
-
playback: input.playback ?? {
|
|
6750
|
-
loop: false,
|
|
6751
|
-
endBehavior: "hold",
|
|
6752
|
-
reducedMotion: { mode: "base" }
|
|
6753
|
-
},
|
|
6754
|
-
metadata: input.metadata ?? {},
|
|
6755
|
-
revision: 0
|
|
6756
|
-
});
|
|
6757
|
-
}
|
|
6758
|
-
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
6759
|
-
function sampleEasing(easing, value) {
|
|
6760
|
-
const t = clamp01(value);
|
|
6761
|
-
switch (easing) {
|
|
6762
|
-
case "ease-in-quad": return t * t;
|
|
6763
|
-
case "ease-out-quad": return 1 - (1 - t) ** 2;
|
|
6764
|
-
case "ease-in-out-quad": return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
6765
|
-
case "ease-in-cubic": return t ** 3;
|
|
6766
|
-
case "ease-out-cubic": return 1 - (1 - t) ** 3;
|
|
6767
|
-
case "ease-in-out-cubic": return t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
|
|
6768
|
-
case "ease-out-quart": return 1 - (1 - t) ** 4;
|
|
6769
|
-
case "ease-out-expo": return t === 1 ? 1 : 1 - 2 ** (-10 * t);
|
|
6770
|
-
default: return t;
|
|
6771
|
-
}
|
|
6772
|
-
}
|
|
6773
|
-
function interpolate(left, right, progress) {
|
|
6774
|
-
if (typeof left === "number" && typeof right === "number") return left + (right - left) * progress;
|
|
6775
|
-
if (left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right)) {
|
|
6776
|
-
const a = left;
|
|
6777
|
-
const b = right;
|
|
6778
|
-
const result = {};
|
|
6779
|
-
for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) result[key] = interpolate(a[key], b[key], progress);
|
|
6780
|
-
return result;
|
|
6781
|
-
}
|
|
6782
|
-
return progress < 1 ? left : right;
|
|
6783
|
-
}
|
|
6784
|
-
/** Deterministically sample one validated Track without touching scene state. */
|
|
6785
|
-
function sampleTrack(trackValue, time) {
|
|
6786
|
-
const frames = trackValue.keyframes;
|
|
6787
|
-
const first = frames[0];
|
|
6788
|
-
const last = frames.at(-1);
|
|
6789
|
-
if (!first || !last) return null;
|
|
6790
|
-
if (time < first.time) {
|
|
6791
|
-
if (trackValue.fill !== "backwards" && trackValue.fill !== "both") return null;
|
|
6792
|
-
return {
|
|
6793
|
-
target: trackValue.target,
|
|
6794
|
-
channel: trackValue.channel,
|
|
6795
|
-
value: first.value
|
|
6796
|
-
};
|
|
6797
|
-
}
|
|
6798
|
-
if (time > last.time) {
|
|
6799
|
-
if (trackValue.fill !== "forwards" && trackValue.fill !== "both") return null;
|
|
6800
|
-
return {
|
|
6801
|
-
target: trackValue.target,
|
|
6802
|
-
channel: trackValue.channel,
|
|
6803
|
-
value: last.value
|
|
6804
|
-
};
|
|
6805
|
-
}
|
|
6806
|
-
let low = 0;
|
|
6807
|
-
let high = frames.length;
|
|
6808
|
-
while (low < high) {
|
|
6809
|
-
const middle = low + high >>> 1;
|
|
6810
|
-
if ((frames[middle]?.time ?? Number.POSITIVE_INFINITY) <= time) low = middle + 1;
|
|
6811
|
-
else high = middle;
|
|
6812
|
-
}
|
|
6813
|
-
const left = frames[Math.max(0, low - 1)] ?? first;
|
|
6814
|
-
const right = frames[Math.min(frames.length - 1, low)] ?? last;
|
|
6815
|
-
if (left === right || trackValue.interpolation === "step") return {
|
|
6816
|
-
target: trackValue.target,
|
|
6817
|
-
channel: trackValue.channel,
|
|
6818
|
-
value: left.value
|
|
6819
|
-
};
|
|
6820
|
-
const span = Math.max(Number.EPSILON, right.time - left.time);
|
|
6821
|
-
const progress = sampleEasing(right.easing ?? "linear", (time - left.time) / span);
|
|
6822
|
-
return {
|
|
6823
|
-
target: trackValue.target,
|
|
6824
|
-
channel: trackValue.channel,
|
|
6825
|
-
value: interpolate(left.value, right.value, progress)
|
|
6826
|
-
};
|
|
6827
|
-
}
|
|
6828
|
-
function sampleCompositionTracks(value, time) {
|
|
6829
|
-
const position = Math.max(0, Math.min(value.timeline.duration, time));
|
|
6830
|
-
return value.timeline.tracks.flatMap((item) => {
|
|
6831
|
-
const sampled = sampleTrack(item, position);
|
|
6832
|
-
return sampled ? [sampled] : [];
|
|
6833
|
-
});
|
|
6834
|
-
}
|
|
6835
|
-
const ZERO_COST = {
|
|
6836
|
-
particles: 0,
|
|
6837
|
-
vertices: 0,
|
|
6838
|
-
dynamicVertices: 0,
|
|
6839
|
-
drawCalls: 0,
|
|
6840
|
-
filterPasses: 0,
|
|
6841
|
-
renderTexturePixels: 0,
|
|
6842
|
-
textureBytes: 0,
|
|
6843
|
-
bufferBytes: 0,
|
|
6844
|
-
simulationSteps: 0
|
|
6845
|
-
};
|
|
6846
|
-
const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
|
|
6847
|
-
function builtinDefinition(capability) {
|
|
6848
|
-
return {
|
|
6849
|
-
...capability,
|
|
6850
|
-
validate() {
|
|
6851
|
-
return [];
|
|
6852
|
-
},
|
|
6853
|
-
estimateCost(params) {
|
|
6854
|
-
return estimateBuiltinBoardClipCost({
|
|
6855
|
-
kind: capability.id,
|
|
6856
|
-
params
|
|
6857
|
-
});
|
|
6858
|
-
}
|
|
6859
|
-
};
|
|
6860
|
-
}
|
|
6861
|
-
var BoardExtensionRegistry = class {
|
|
6862
|
-
#extensions = /* @__PURE__ */ new Map();
|
|
6863
|
-
#presets = /* @__PURE__ */ new Map();
|
|
6864
|
-
constructor() {
|
|
6865
|
-
for (const capability of BOARD_BUILTIN_CAPABILITIES) this.register(builtinDefinition(capability));
|
|
6866
|
-
}
|
|
6867
|
-
register(definition) {
|
|
6868
|
-
const key = `${definition.kind}:${definition.id}@${definition.version}`;
|
|
6869
|
-
const target = definition.kind === "preset" ? this.#presets : this.#extensions;
|
|
6870
|
-
if (target.has(key)) throw new Error(`Board extension is already registered: ${key}`);
|
|
6871
|
-
target.set(key, definition);
|
|
6872
|
-
return this;
|
|
6873
|
-
}
|
|
6874
|
-
capabilities() {
|
|
6875
|
-
return [...this.#extensions.values(), ...this.#presets.values()].map((definition) => ({
|
|
6876
|
-
kind: definition.kind,
|
|
6877
|
-
id: definition.id,
|
|
6878
|
-
version: definition.version,
|
|
6879
|
-
...definition.digest ? { digest: definition.digest } : {},
|
|
6880
|
-
...definition.renderers ? { renderers: definition.renderers } : {},
|
|
6881
|
-
...definition.fallbackId ? { fallbackId: definition.fallbackId } : {},
|
|
6882
|
-
...definition.schema ? { schema: definition.schema } : {}
|
|
6883
|
-
}));
|
|
6884
|
-
}
|
|
6885
|
-
compilePreset(id, version, params) {
|
|
6886
|
-
const preset = this.#presets.get(`preset:${id}@${version}`);
|
|
6887
|
-
if (!preset) throw new Error(`Unknown Board preset: ${id}@${version}`);
|
|
6888
|
-
return preset.compile(params);
|
|
6889
|
-
}
|
|
6890
|
-
validate(input) {
|
|
6891
|
-
const parsed = BoardCompositionSchema.safeParse(input.composition);
|
|
6892
|
-
if (!parsed.success) return {
|
|
6893
|
-
valid: false,
|
|
6894
|
-
diagnostics: parsed.error.issues.map((issue) => ({
|
|
6895
|
-
severity: "error",
|
|
6896
|
-
code: "INVALID_COMPOSITION",
|
|
6897
|
-
message: issue.message,
|
|
6898
|
-
path: issue.path.join(".")
|
|
6899
|
-
})),
|
|
6900
|
-
peakCost: { ...ZERO_COST }
|
|
6901
|
-
};
|
|
6902
|
-
const diagnostics = [];
|
|
6903
|
-
const composition = parsed.data;
|
|
6904
|
-
const cost = { ...ZERO_COST };
|
|
6905
|
-
for (const effect of input.effects ?? []) {
|
|
6906
|
-
diagnostics.push(...validateBuiltinBoardEffect(effect, `effects.${effect.id}`));
|
|
6907
|
-
if (!this.#extensions.has(`effect:${effect.kind}@${effect.kindVersion}`)) diagnostics.push({
|
|
6908
|
-
severity: "warning",
|
|
6909
|
-
code: "UNKNOWN_EFFECT",
|
|
6910
|
-
message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
|
|
6911
|
-
path: `effects.${effect.id}`
|
|
6912
|
-
});
|
|
6913
|
-
}
|
|
6914
|
-
const events = [];
|
|
6915
|
-
for (const clip of composition.timeline.clips) {
|
|
6916
|
-
const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
|
|
6917
|
-
if (!definition) {
|
|
6918
|
-
diagnostics.push({
|
|
6919
|
-
severity: "warning",
|
|
6920
|
-
code: "UNKNOWN_CLIP",
|
|
6921
|
-
message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`
|
|
6922
|
-
});
|
|
6923
|
-
continue;
|
|
6924
|
-
}
|
|
6925
|
-
diagnostics.push(...validateBuiltinBoardClip(clip, `composition.timeline.clips.${clip.id}`));
|
|
6926
|
-
diagnostics.push(...definition.validate?.(clip.params) ?? []);
|
|
6927
|
-
const estimate = definition.estimateCost(clip.params, input.profile ?? "high");
|
|
6928
|
-
const clipCost = { ...ZERO_COST };
|
|
6929
|
-
for (const key of Object.keys(clipCost)) clipCost[key] = estimate[key] ?? 0;
|
|
6930
|
-
events.push({
|
|
6931
|
-
at: clip.start,
|
|
6932
|
-
direction: 1,
|
|
6933
|
-
cost: clipCost
|
|
6934
|
-
});
|
|
6935
|
-
events.push({
|
|
6936
|
-
at: clip.start + clip.duration,
|
|
6937
|
-
direction: -1,
|
|
6938
|
-
cost: clipCost
|
|
6939
|
-
});
|
|
6940
|
-
}
|
|
6941
|
-
events.sort((left, right) => left.at - right.at || left.direction - right.direction);
|
|
6942
|
-
const active = { ...ZERO_COST };
|
|
6943
|
-
for (const event of events) for (const key of Object.keys(active)) {
|
|
6944
|
-
active[key] += event.cost[key] * event.direction;
|
|
6945
|
-
cost[key] = Math.max(cost[key], active[key]);
|
|
6946
|
-
}
|
|
6947
|
-
for (const effect of input.effects ?? []) {
|
|
6948
|
-
const estimate = estimateBuiltinBoardEffectCost(effect);
|
|
6949
|
-
for (const key of Object.keys(cost)) cost[key] += estimate[key] ?? 0;
|
|
6950
|
-
}
|
|
6951
|
-
const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
|
|
6952
|
-
for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
|
|
6953
|
-
severity: "warning",
|
|
6954
|
-
code: "RENDER_BUDGET_EXCEEDED",
|
|
6955
|
-
message: `${key} peaks at ${cost[key]}, above ${limits[key]}`,
|
|
6956
|
-
path: key,
|
|
6957
|
-
adaptation: { quality: "lower" }
|
|
6958
|
-
});
|
|
6959
|
-
return {
|
|
6960
|
-
valid: !diagnostics.some((item) => item.severity === "error"),
|
|
6961
|
-
diagnostics,
|
|
6962
|
-
peakCost: cost
|
|
6963
|
-
};
|
|
6964
|
-
}
|
|
6965
|
-
};
|
|
6966
|
-
function createBoardExtensionRegistry() {
|
|
6967
|
-
return new BoardExtensionRegistry();
|
|
6968
|
-
}
|
|
6969
|
-
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
6970
|
-
//#endregion
|
|
6971
|
-
export { APP_APPEARANCE_TOKENS, 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, AppRuntimeError, AppSurfaceApi, AppWindowApi, 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, NATIVE_INGEST_MAX_TURNS, NATIVE_SYNC_SOURCE, OriginBrokerTransport, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, RUNTIME_RECOVERY_BATCH_SIZE, ReferencesApi, ReferralsApi, SETTLED_TURN_STATUSES, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, SpaceWebhooksApi, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, appAppearanceVar, applyAppAppearance, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileComposition, composition, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createBoardExtensionRegistry, createCohubClient, createHttpClient, createOriginAppResolver, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, fingerprintProjectionTurns, formatAppRef, formatWorkRef, generationOutputSource, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isActiveGenerationTask, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isNativeClientTurn, isProjectionCompaction, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, nativeIngestTurnSchema, nativeRuntimeEventSchema, nativeTurnCompleteSchema, nativeTurnProgressSchema, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseWorkRef, proceduralClip, projectNativeMessageMeta, projectNativeSession, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebBaseUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeNativeStopSchema, runtimeReadySchema, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, serializeProjection, serializeProjectionRecords, setGrantedAppScopes, toGenerationTaskView, track, trimProjectionTurnsToCompaction, validateArchiveBoundary };
|
|
7817
|
+
export { APP_APPEARANCE_TOKENS, 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, AppRuntimeError, AppSurfaceApi, AppWindowApi, AppsApi, BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_COLOR_TOKENS, BOARD_ITEM_TYPES, BillingApi, BoardAnimationSchema, BoardClient, BoardPatchSchema, BoardTrackSchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DESKTOP_COMMAND_ACCEPT_TIMEOUT_MS, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, DESKTOP_COMMAND_OPEN_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, DISPLAY_ACTION_TYPES, DISPLAY_ELEMENT_REF_PATTERN, DISPLAY_SYSTEM_ACTIONS, DesktopCommandsApi, DisplayActionError, DisplayConnection, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HOME_SPACE_SLUG, HttpError, NATIVE_INGEST_MAX_TURNS, NATIVE_SYNC_SOURCE, OriginBrokerTransport, PERMISSIONS, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, RUNTIME_ARCHIVE_SEGMENT_BYTES, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, RUNTIME_RECOVERY_BATCH_SIZE, ReferencesApi, ReferralsApi, SETTLED_TURN_STATUSES, SessionGenerationStreamClient, SessionPatchReducer, SpaceDisplaysApi, SpacePublicFilesApi, SpaceWebhooksApi, UPLOAD_MAX_BATCH_BYTES, UPLOAD_MAX_BATCH_FILES, UPLOAD_MAX_FILE_BYTES, USER_LEVEL_PERMISSIONS, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkSurfaceApi, appAppearanceVar, applyAppAppearance, assertGenerationRequestAllowedByPolicy, attachAppEmbed, buildAppSurfaceRequest, buildSpaceInvitePath, buildSpacePath, clearGrantedAppScopes, compileDisplayActions, connectDisplay, contextToPiMessages, createAppBridgeCore, createAppBridgeCore as createWorkBridgeCore, createAppRuntime, createAppRuntime as createWorkRuntime, createCohubClient, createHttpClient, createOriginAppResolver, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugAppIdResolver, createSlugAppIdResolver as createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, decodeGenerationPolicy, defaultDesktopCommandTimeoutMs, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, filterSpacePickerItems, findGenerationModelPolicy, fingerprintProjectionTurns, formatAppRef, formatWorkRef, generationOutputSource, getAllowedGenerationModelIds, getCohubContext, harnessArchiveIndexSchema, hasGrantedAppScopes, hasRequestSourceIdentity, isActiveGenerationTask, isAppId, isBillingAccessBlockedCode, isBillingAccessBlockedError, isDesktopCallMethod, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isLocalHarness, isNativeClientTurn, isProjectionCompaction, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalDesktopCommandStatus, isUserLevelPermission, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, nativeIngestTurnSchema, nativeRuntimeEventSchema, nativeTurnCompleteSchema, nativeTurnProgressSchema, normalizeBaseUrl, normalizeDisplayPoint, normalizeGenerationPolicy, normalizeRequestSource, normalizeSessionTurnOrigin, normalizeSpacePickerQuery, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, orderSpacePickerItems, parseAppRef, parseAppSurfaceReady, parseAppSurfaceResponse, parseAssistantMessageCommit, parseBoardDocument, parseBoardItem, parseDesktopCommand, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseSpaceRef, parseWorkRef, projectNativeMessageMeta, projectNativeSession, readRequestSourceFromEnv, readSentTurns, readSessionTurnOrigin, requestSourceToHeaders, resolveApiBaseUrl, resolveAppTransport, resolveAppTransport as resolveWorkTransport, resolveCohubEnvironment, resolveExecutionAppId, resolveExecutionToken, resolveHarness, resolveOpenSurface, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebBaseUrl, resolveWebsocketUrl, runtimeCommandSchema, runtimeEventSchema, runtimeNativeStopSchema, runtimeReadySchema, sanitizeAccessToken, scopeListHasPermission, selectRuntimeContextMessages, selectSpacePickerItems, serializeProjection, serializeProjectionRecords, setGrantedAppScopes, toGenerationTaskView, trimProjectionTurnsToCompaction, validateArchiveBoundary };
|