@neta-art/cohub 6.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/board/animation.d.ts +2 -1
- package/dist/board/animation.js +0 -1
- package/dist/board/core/connections.js +4 -4
- package/dist/board/core/export-plan.js +1 -1
- package/dist/board/core/shape-types.js +0 -1
- package/dist/board/index.d.ts +6 -5
- package/dist/board/index.js +7 -7
- package/dist/board/mutation.d.ts +2 -11
- package/dist/board/mutation.js +2 -56
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/semantic-document.d.ts +30 -0
- package/dist/board/semantic-document.js +271 -0
- package/dist/board/semantic-mutation.d.ts +10 -0
- package/dist/board/semantic-mutation.js +203 -0
- package/dist/chunks/http.d.ts +340 -317
- package/dist/chunks/http.js +477 -1784
- package/dist/chunks/transport.js +1 -1
- package/dist/chunks/websocket.d.ts +3677 -3151
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +165 -119
- package/dist/index.js +1419 -446
- package/dist/protocol/dist/board-authoring.d.ts +1305 -1
- package/dist/protocol/dist/board-authoring.js +80 -6
- package/dist/protocol/dist/board-codec.d.ts +2 -2
- package/dist/protocol/dist/board-codec.js +19 -4
- package/dist/protocol/dist/board-composition.d.ts +126 -1
- package/dist/protocol/dist/board-composition.js +1 -9
- package/dist/protocol/dist/board-connection.d.ts +16 -16
- package/dist/protocol/dist/board-connection.js +16 -16
- package/dist/protocol/dist/board-document.d.ts +34 -3
- package/dist/protocol/dist/board-document.js +3 -1
- package/dist/protocol/dist/board-effect.d.ts +94 -0
- package/dist/protocol/dist/board-effect.js +53 -0
- package/dist/protocol/dist/board-json.js +16 -0
- package/dist/protocol/dist/board.d.ts +34 -164
- package/dist/protocol/dist/board.js +44 -45
- package/dist/protocol/dist/index.d.ts +8 -8
- package/dist/protocol/dist/realtime/types.d.ts +1 -1
- package/dist/types.d.ts +3 -1
- package/package.json +1 -2
- package/dist/board/codec.d.ts +0 -27
- package/dist/board/codec.js +0 -277
- package/dist/protocol/dist/board-content.js +0 -26
- package/dist/protocol/dist/board-upgrade.js +0 -2
- package/dist/protocol/dist/identifiers.js +0 -10
- package/dist/protocol/dist/index.js +0 -19
- package/dist/protocol/dist/provenance.js +0 -15
- package/dist/protocol/dist/public-identifiers.js +0 -38
- package/dist/protocol/dist/realtime/board-awareness.js +0 -119
- package/dist/protocol/dist/ui-command.js +0 -2
- package/dist/protocol/dist/work-promotion-stats.js +0 -11
- package/dist/protocol/dist/work-surface.js +0 -2
- package/dist/protocol/dist/work-view-stats.js +0 -3
- package/dist/protocol/dist/work.d.ts +0 -1
- /package/dist/protocol/dist/{board-upgrade.d.ts → app.d.ts} +0 -0
package/dist/chunks/http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as
|
|
1
|
+
import { D as getRealtimeSpaceRoom, E as getRealtimeBoardRoom, n as HttpTransport, t as HttpError, y as isUuid } from "./transport.js";
|
|
2
2
|
import { a as resolveApiBaseUrl } from "./environment.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/apis/channels.ts
|
|
@@ -467,10 +467,6 @@ const BOARD_BUILTIN_CLIP_KINDS = [
|
|
|
467
467
|
const BOARD_BUILTIN_EFFECT_KINDS = ["effects.pulse", "effects.float"];
|
|
468
468
|
const BOARD_ARROW_STROKE_SIZE = 2.5;
|
|
469
469
|
const BOARD_CONNECTION_STROKE_SIZE = 2.5;
|
|
470
|
-
function clampBoardStrokeSize(size) {
|
|
471
|
-
if (!Number.isFinite(size)) return BOARD_CONNECTION_STROKE_SIZE;
|
|
472
|
-
return Math.min(64, Math.max(1, size));
|
|
473
|
-
}
|
|
474
470
|
function clipSchema(id) {
|
|
475
471
|
switch (id) {
|
|
476
472
|
case "motion.path": return { params: { points: {
|
|
@@ -546,12 +542,11 @@ const BoardConnectionAnchorSchema = z.union([
|
|
|
546
542
|
ny: z.number().finite().min(0).max(1)
|
|
547
543
|
})
|
|
548
544
|
]);
|
|
549
|
-
const AUTO_BOARD_CONNECTION_ANCHOR = { kind: "auto" };
|
|
550
545
|
const BoardConnectionEndpointSchema = z.object({
|
|
551
|
-
|
|
546
|
+
itemId: z.string().min(1).max(160),
|
|
552
547
|
/** Optional semantic port. Older connections omit it and remain valid. */
|
|
553
548
|
portId: z.string().min(1).max(120).optional(),
|
|
554
|
-
anchor: BoardConnectionAnchorSchema.default(
|
|
549
|
+
anchor: BoardConnectionAnchorSchema.default({ kind: "auto" })
|
|
555
550
|
});
|
|
556
551
|
/**
|
|
557
552
|
* Which ends carry an arrowhead.
|
|
@@ -626,50 +621,18 @@ const BoardConnectionSchema = z.object({
|
|
|
626
621
|
routing: BoardConnectionRoutingSchema.default(DEFAULT_BOARD_CONNECTION_ROUTING),
|
|
627
622
|
style: BoardConnectionStyleSchema.default(DEFAULT_BOARD_CONNECTION_STYLE),
|
|
628
623
|
metadata: z.record(z.string(), z.unknown()).default({})
|
|
629
|
-
});
|
|
630
|
-
BoardConnectionSchema.omit({ id: true }).partial();
|
|
631
|
-
function normalizeBoardConnectionStyle(style) {
|
|
632
|
-
return {
|
|
633
|
-
color: style?.color?.trim() || DEFAULT_BOARD_CONNECTION_STYLE.color,
|
|
634
|
-
size: clampBoardStrokeSize(typeof style?.size === "number" && Number.isFinite(style.size) ? style.size : DEFAULT_BOARD_CONNECTION_STYLE.size),
|
|
635
|
-
line: style?.line ?? DEFAULT_BOARD_CONNECTION_STYLE.line
|
|
636
|
-
};
|
|
637
|
-
}
|
|
624
|
+
}).strict();
|
|
638
625
|
/**
|
|
639
|
-
*
|
|
626
|
+
* A patch to an existing connection.
|
|
640
627
|
*
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
* for the same intent.
|
|
628
|
+
* `id` is excluded: a connection's identity never changes, and re-pointing both
|
|
629
|
+
* endpoints is an edit of the same relation, not a new one.
|
|
644
630
|
*/
|
|
645
|
-
|
|
646
|
-
return {
|
|
647
|
-
id: input.id,
|
|
648
|
-
source: {
|
|
649
|
-
nodeId: input.sourceNodeId,
|
|
650
|
-
...input.sourcePortId ? { portId: input.sourcePortId } : {},
|
|
651
|
-
anchor: input.sourceAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
|
|
652
|
-
},
|
|
653
|
-
target: {
|
|
654
|
-
nodeId: input.targetNodeId,
|
|
655
|
-
...input.targetPortId ? { portId: input.targetPortId } : {},
|
|
656
|
-
anchor: input.targetAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
|
|
657
|
-
},
|
|
658
|
-
relation: input.relation ?? "related",
|
|
659
|
-
direction: input.direction ?? "forward",
|
|
660
|
-
label: input.label ?? "",
|
|
661
|
-
routing: {
|
|
662
|
-
...DEFAULT_BOARD_CONNECTION_ROUTING,
|
|
663
|
-
...input.routing
|
|
664
|
-
},
|
|
665
|
-
style: normalizeBoardConnectionStyle(input.style),
|
|
666
|
-
metadata: input.metadata ?? {}
|
|
667
|
-
};
|
|
668
|
-
}
|
|
631
|
+
const BoardConnectionPatchSchema = BoardConnectionSchema.omit({ id: true }).partial();
|
|
669
632
|
//#endregion
|
|
670
633
|
//#region ../protocol/dist/board-composition.js
|
|
671
|
-
const idSchema$
|
|
672
|
-
const finiteSchema$
|
|
634
|
+
const idSchema$2 = z.string().min(1).max(160);
|
|
635
|
+
const finiteSchema$1 = z.number().finite();
|
|
673
636
|
const jsonObjectSchema$2 = z.record(z.string(), z.unknown());
|
|
674
637
|
const BoardEasingSchema = z.enum([
|
|
675
638
|
"linear",
|
|
@@ -685,22 +648,22 @@ const BoardEasingSchema = z.enum([
|
|
|
685
648
|
const BoardAnimationTargetSchema = z.discriminatedUnion("type", [
|
|
686
649
|
z.object({
|
|
687
650
|
type: z.literal("item"),
|
|
688
|
-
itemId: idSchema$
|
|
651
|
+
itemId: idSchema$2
|
|
689
652
|
}).strict(),
|
|
690
653
|
z.object({
|
|
691
654
|
type: z.literal("effect"),
|
|
692
|
-
effectId: idSchema$
|
|
655
|
+
effectId: idSchema$2
|
|
693
656
|
}).strict(),
|
|
694
657
|
z.object({ type: z.literal("camera") }).strict(),
|
|
695
658
|
z.object({ type: z.literal("board") }).strict()
|
|
696
659
|
]);
|
|
697
660
|
const vector2Schema = z.object({
|
|
698
|
-
x: finiteSchema$
|
|
699
|
-
y: finiteSchema$
|
|
661
|
+
x: finiteSchema$1,
|
|
662
|
+
y: finiteSchema$1
|
|
700
663
|
}).strict();
|
|
701
|
-
const scaleSchema = z.union([finiteSchema$
|
|
702
|
-
x: finiteSchema$
|
|
703
|
-
y: finiteSchema$
|
|
664
|
+
const scaleSchema = z.union([finiteSchema$1.positive(), z.object({
|
|
665
|
+
x: finiteSchema$1.positive(),
|
|
666
|
+
y: finiteSchema$1.positive()
|
|
704
667
|
}).strict()]);
|
|
705
668
|
const BOARD_ANIMATION_CHANNELS = {
|
|
706
669
|
"transform.translation": {
|
|
@@ -712,7 +675,7 @@ const BOARD_ANIMATION_CHANNELS = {
|
|
|
712
675
|
},
|
|
713
676
|
"transform.rotation": {
|
|
714
677
|
targets: ["item"],
|
|
715
|
-
value: finiteSchema$
|
|
678
|
+
value: finiteSchema$1,
|
|
716
679
|
interpolations: ["linear", "step"],
|
|
717
680
|
unit: "radian"
|
|
718
681
|
},
|
|
@@ -724,18 +687,18 @@ const BOARD_ANIMATION_CHANNELS = {
|
|
|
724
687
|
},
|
|
725
688
|
"style.opacity": {
|
|
726
689
|
targets: ["item"],
|
|
727
|
-
value: finiteSchema$
|
|
690
|
+
value: finiteSchema$1.min(0).max(1),
|
|
728
691
|
interpolations: ["linear", "step"],
|
|
729
692
|
unit: "ratio"
|
|
730
693
|
}
|
|
731
694
|
};
|
|
732
695
|
const BoardTrackKeyframeSchema = z.object({
|
|
733
|
-
time: finiteSchema$
|
|
696
|
+
time: finiteSchema$1.nonnegative(),
|
|
734
697
|
value: z.unknown(),
|
|
735
698
|
easing: BoardEasingSchema.optional()
|
|
736
699
|
}).strict();
|
|
737
700
|
const BoardTrackSchema = z.object({
|
|
738
|
-
id: idSchema$
|
|
701
|
+
id: idSchema$2,
|
|
739
702
|
target: BoardAnimationTargetSchema,
|
|
740
703
|
channel: z.string().min(1).max(160),
|
|
741
704
|
channelVersion: z.number().int().positive().default(1),
|
|
@@ -796,12 +759,12 @@ const BoardTrackSchema = z.object({
|
|
|
796
759
|
});
|
|
797
760
|
const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
798
761
|
const BoardProceduralClipSchema = z.object({
|
|
799
|
-
id: idSchema$
|
|
762
|
+
id: idSchema$2,
|
|
800
763
|
kind: extensionKindSchema,
|
|
801
764
|
kindVersion: z.number().int().positive(),
|
|
802
765
|
target: BoardAnimationTargetSchema,
|
|
803
|
-
start: finiteSchema$
|
|
804
|
-
duration: finiteSchema$
|
|
766
|
+
start: finiteSchema$1.nonnegative(),
|
|
767
|
+
duration: finiteSchema$1.positive(),
|
|
805
768
|
layer: z.enum([
|
|
806
769
|
"behind",
|
|
807
770
|
"content",
|
|
@@ -821,17 +784,17 @@ const BoardProceduralClipSchema = z.object({
|
|
|
821
784
|
ref: z.string().min(1).max(4096),
|
|
822
785
|
digest: z.string().min(16).max(160).optional()
|
|
823
786
|
}).strict()).default([]),
|
|
824
|
-
seed: idSchema$
|
|
787
|
+
seed: idSchema$2,
|
|
825
788
|
metadata: jsonObjectSchema$2.default({})
|
|
826
789
|
}).strict();
|
|
827
790
|
const BoardTimelineMarkerSchema = z.object({
|
|
828
|
-
id: idSchema$
|
|
829
|
-
time: finiteSchema$
|
|
830
|
-
duration: finiteSchema$
|
|
791
|
+
id: idSchema$2,
|
|
792
|
+
time: finiteSchema$1.nonnegative(),
|
|
793
|
+
duration: finiteSchema$1.nonnegative().optional(),
|
|
831
794
|
metadata: jsonObjectSchema$2.default({})
|
|
832
795
|
}).strict();
|
|
833
796
|
const BoardTimelineSchema = z.object({
|
|
834
|
-
duration: finiteSchema$
|
|
797
|
+
duration: finiteSchema$1.nonnegative(),
|
|
835
798
|
tracks: z.array(BoardTrackSchema).max(5e4).default([]),
|
|
836
799
|
clips: z.array(BoardProceduralClipSchema).max(5e4).default([]),
|
|
837
800
|
markers: z.array(BoardTimelineMarkerSchema).max(1e4).default([])
|
|
@@ -919,16 +882,16 @@ const BoardCompositionPlaybackSchema = z.object({
|
|
|
919
882
|
z.object({ mode: z.literal("base") }).strict(),
|
|
920
883
|
z.object({
|
|
921
884
|
mode: z.literal("time"),
|
|
922
|
-
time: finiteSchema$
|
|
885
|
+
time: finiteSchema$1.nonnegative()
|
|
923
886
|
}).strict(),
|
|
924
887
|
z.object({
|
|
925
888
|
mode: z.literal("marker"),
|
|
926
|
-
markerId: idSchema$
|
|
889
|
+
markerId: idSchema$2
|
|
927
890
|
}).strict()
|
|
928
891
|
]).default({ mode: "base" })
|
|
929
892
|
}).strict();
|
|
930
893
|
const compositionFields = {
|
|
931
|
-
id: idSchema$
|
|
894
|
+
id: idSchema$2,
|
|
932
895
|
name: z.string().min(1).max(255),
|
|
933
896
|
timeline: BoardTimelineSchema,
|
|
934
897
|
playback: BoardCompositionPlaybackSchema.default({
|
|
@@ -964,11 +927,23 @@ const BoardCompositionSchema = z.object({
|
|
|
964
927
|
...compositionFields,
|
|
965
928
|
revision: z.number().int().nonnegative().default(0)
|
|
966
929
|
}).strict().superRefine(validateCompositionFallback);
|
|
967
|
-
/**
|
|
930
|
+
/**
|
|
931
|
+
* Accept inspect/get output as apply input while stripping server-owned
|
|
932
|
+
* `revision`. Anything else that looks server-owned (or simply unknown) fails
|
|
933
|
+
* with a pointer at the offending key, so a get → edit → apply round-trip that
|
|
934
|
+
* accidentally carries runtime fields names the culprit instead of a bare
|
|
935
|
+
* "Unrecognized key".
|
|
936
|
+
*/
|
|
968
937
|
function parseBoardCompositionInput(value) {
|
|
969
938
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
970
939
|
const { revision: _revision, ...input } = value;
|
|
971
|
-
|
|
940
|
+
const result = BoardCompositionInputSchema.safeParse(input);
|
|
941
|
+
if (!result.success) {
|
|
942
|
+
const issue = result.error.issues.find((item) => item.code === "unrecognized_keys");
|
|
943
|
+
if (issue) throw new Error(`unknown field in composition input: ${issue.message}. Server-owned fields such as revision are stripped automatically; remove other fields not part of the composition schema`);
|
|
944
|
+
throw result.error;
|
|
945
|
+
}
|
|
946
|
+
return result.data;
|
|
972
947
|
}
|
|
973
948
|
return BoardCompositionInputSchema.parse(value);
|
|
974
949
|
}
|
|
@@ -982,8 +957,67 @@ const BOARD_ANIMATION_CHANNEL_CAPABILITIES = Object.entries(BOARD_ANIMATION_CHAN
|
|
|
982
957
|
valueSchema: z.toJSONSchema(definition.value)
|
|
983
958
|
}));
|
|
984
959
|
//#endregion
|
|
960
|
+
//#region ../protocol/dist/board-effect.js
|
|
961
|
+
/**
|
|
962
|
+
* Board effect schema, isolated so `board-authoring.ts` can reuse the exact
|
|
963
|
+
* definition without a runtime cycle through `board.ts`. One definition, one
|
|
964
|
+
* strictness level — semantic mutations and Board create must not drift apart.
|
|
965
|
+
*/
|
|
966
|
+
const idSchema$1 = z.string().min(1).max(160);
|
|
967
|
+
const extensionIdSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
968
|
+
const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
|
|
969
|
+
const BoardAssetRefSchema = z.object({
|
|
970
|
+
type: z.enum(["space-file", "extension"]),
|
|
971
|
+
ref: z.string().min(1).max(4096),
|
|
972
|
+
digest: z.string().min(16).max(160).optional()
|
|
973
|
+
}).strict();
|
|
974
|
+
const BoardEffectSchema = z.object({
|
|
975
|
+
id: idSchema$1,
|
|
976
|
+
boardId: z.string().uuid(),
|
|
977
|
+
target: z.discriminatedUnion("type", [z.object({
|
|
978
|
+
type: z.literal("item"),
|
|
979
|
+
itemId: idSchema$1
|
|
980
|
+
}).strict(), z.object({ type: z.literal("board") }).strict()]),
|
|
981
|
+
kind: extensionIdSchema,
|
|
982
|
+
kindVersion: z.number().int().positive(),
|
|
983
|
+
enabled: z.boolean().default(true),
|
|
984
|
+
lifecycle: z.enum([
|
|
985
|
+
"persistent",
|
|
986
|
+
"when-visible",
|
|
987
|
+
"manual"
|
|
988
|
+
]),
|
|
989
|
+
timeOrigin: z.enum([
|
|
990
|
+
"board",
|
|
991
|
+
"visible",
|
|
992
|
+
"activation"
|
|
993
|
+
]),
|
|
994
|
+
layer: z.enum([
|
|
995
|
+
"behind",
|
|
996
|
+
"front",
|
|
997
|
+
"screen"
|
|
998
|
+
]).default("front"),
|
|
999
|
+
seed: z.string().min(1).max(160),
|
|
1000
|
+
params: jsonObjectSchema$1.default({}),
|
|
1001
|
+
assetRefs: z.array(BoardAssetRefSchema).default([]),
|
|
1002
|
+
metadata: jsonObjectSchema$1.default({}),
|
|
1003
|
+
revision: z.number().int().nonnegative()
|
|
1004
|
+
}).strict();
|
|
1005
|
+
/** The client-authored effect form: no server-owned `boardId`/`revision`. */
|
|
1006
|
+
const BoardEffectInputSchema = BoardEffectSchema.omit({
|
|
1007
|
+
boardId: true,
|
|
1008
|
+
revision: true
|
|
1009
|
+
});
|
|
1010
|
+
/** Accept a read projection in a write command while stripping server fields. */
|
|
1011
|
+
function parseBoardEffectInput(value) {
|
|
1012
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1013
|
+
const { boardId: _boardId, revision: _revision, ...input } = value;
|
|
1014
|
+
return BoardEffectInputSchema.parse(input);
|
|
1015
|
+
}
|
|
1016
|
+
return BoardEffectInputSchema.parse(value);
|
|
1017
|
+
}
|
|
1018
|
+
//#endregion
|
|
985
1019
|
//#region ../protocol/dist/board-authoring.js
|
|
986
|
-
const
|
|
1020
|
+
const BOARD_AUTHORING_COLOR_IDS = [
|
|
987
1021
|
"brand",
|
|
988
1022
|
"neutral",
|
|
989
1023
|
"black",
|
|
@@ -994,43 +1028,43 @@ const BOARD_COLOR_IDS$1 = [
|
|
|
994
1028
|
"violet",
|
|
995
1029
|
"rose"
|
|
996
1030
|
];
|
|
997
|
-
const
|
|
1031
|
+
const BOARD_AUTHORING_GEO_KINDS = [
|
|
998
1032
|
"rectangle",
|
|
999
1033
|
"rounded",
|
|
1000
1034
|
"ellipse",
|
|
1001
1035
|
"diamond",
|
|
1002
1036
|
"triangle"
|
|
1003
1037
|
];
|
|
1004
|
-
const idSchema
|
|
1005
|
-
const jsonObjectSchema
|
|
1006
|
-
const finiteSchema
|
|
1038
|
+
const idSchema = z.string().min(1).max(160);
|
|
1039
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
1040
|
+
const finiteSchema = z.number().finite();
|
|
1007
1041
|
const extensionTypeSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
1008
1042
|
const BoardAuthoringFrameSchema = z.object({
|
|
1009
|
-
x: finiteSchema
|
|
1010
|
-
y: finiteSchema
|
|
1011
|
-
width: finiteSchema
|
|
1012
|
-
height: finiteSchema
|
|
1013
|
-
rotation: finiteSchema
|
|
1043
|
+
x: finiteSchema,
|
|
1044
|
+
y: finiteSchema,
|
|
1045
|
+
width: finiteSchema.positive(),
|
|
1046
|
+
height: finiteSchema.positive(),
|
|
1047
|
+
rotation: finiteSchema.default(0)
|
|
1014
1048
|
}).strict();
|
|
1015
|
-
const pointSchema
|
|
1016
|
-
x: finiteSchema
|
|
1017
|
-
y: finiteSchema
|
|
1018
|
-
p: finiteSchema
|
|
1049
|
+
const pointSchema = z.object({
|
|
1050
|
+
x: finiteSchema,
|
|
1051
|
+
y: finiteSchema,
|
|
1052
|
+
p: finiteSchema.min(0).max(1).default(.5)
|
|
1019
1053
|
}).strict();
|
|
1020
|
-
const worldPointSchema
|
|
1021
|
-
x: finiteSchema
|
|
1022
|
-
y: finiteSchema
|
|
1054
|
+
const worldPointSchema = z.object({
|
|
1055
|
+
x: finiteSchema,
|
|
1056
|
+
y: finiteSchema
|
|
1023
1057
|
}).strict();
|
|
1024
1058
|
const cropSchema = z.object({
|
|
1025
|
-
x: finiteSchema
|
|
1026
|
-
y: finiteSchema
|
|
1027
|
-
w: finiteSchema
|
|
1028
|
-
h: finiteSchema
|
|
1059
|
+
x: finiteSchema.min(0).max(1),
|
|
1060
|
+
y: finiteSchema.min(0).max(1),
|
|
1061
|
+
w: finiteSchema.min(0).max(1),
|
|
1062
|
+
h: finiteSchema.min(0).max(1)
|
|
1029
1063
|
}).strict();
|
|
1030
|
-
const BoardItemStyleSchema
|
|
1031
|
-
color: z.enum(
|
|
1032
|
-
strokeWidth: finiteSchema
|
|
1033
|
-
fillOpacity: finiteSchema
|
|
1064
|
+
const BoardItemStyleSchema = z.object({
|
|
1065
|
+
color: z.enum(BOARD_AUTHORING_COLOR_IDS).optional(),
|
|
1066
|
+
strokeWidth: finiteSchema.min(1).max(64).optional(),
|
|
1067
|
+
fillOpacity: finiteSchema.min(0).max(1).optional()
|
|
1034
1068
|
}).strict();
|
|
1035
1069
|
const sourceSnapshotSchema = z.record(z.string(), z.unknown());
|
|
1036
1070
|
function isSafeBoardSourcePath(value) {
|
|
@@ -1042,44 +1076,44 @@ const BoardItemSourceSchema = z.object({
|
|
|
1042
1076
|
snapshot: sourceSnapshotSchema.optional()
|
|
1043
1077
|
}).strict();
|
|
1044
1078
|
const baseFields = {
|
|
1045
|
-
id: idSchema
|
|
1079
|
+
id: idSchema,
|
|
1046
1080
|
frame: BoardAuthoringFrameSchema,
|
|
1047
|
-
parentId: idSchema
|
|
1081
|
+
parentId: idSchema.nullable().optional(),
|
|
1048
1082
|
locked: z.boolean().optional(),
|
|
1049
|
-
metadata: jsonObjectSchema
|
|
1083
|
+
metadata: jsonObjectSchema.optional()
|
|
1050
1084
|
};
|
|
1051
1085
|
const styledBaseFields = {
|
|
1052
1086
|
...baseFields,
|
|
1053
|
-
style: BoardItemStyleSchema
|
|
1087
|
+
style: BoardItemStyleSchema.optional()
|
|
1054
1088
|
};
|
|
1055
1089
|
const BoardTextAuthoringItemSchema = z.object({
|
|
1056
1090
|
...styledBaseFields,
|
|
1057
1091
|
type: z.literal("text"),
|
|
1058
1092
|
props: z.object({
|
|
1059
1093
|
text: z.string().default(""),
|
|
1060
|
-
fontSize: finiteSchema
|
|
1094
|
+
fontSize: finiteSchema.min(2).max(512).default(24)
|
|
1061
1095
|
}).strict()
|
|
1062
1096
|
}).strict();
|
|
1063
1097
|
const BoardGeoAuthoringItemSchema = z.object({
|
|
1064
1098
|
...styledBaseFields,
|
|
1065
1099
|
type: z.literal("geo"),
|
|
1066
1100
|
props: z.object({
|
|
1067
|
-
shape: z.enum(
|
|
1101
|
+
shape: z.enum(BOARD_AUTHORING_GEO_KINDS).default("rectangle"),
|
|
1068
1102
|
text: z.string().default("")
|
|
1069
1103
|
}).strict()
|
|
1070
1104
|
}).strict();
|
|
1071
1105
|
const BoardDrawAuthoringItemSchema = z.object({
|
|
1072
1106
|
...styledBaseFields,
|
|
1073
1107
|
type: z.literal("draw"),
|
|
1074
|
-
props: z.object({ points: z.array(pointSchema
|
|
1108
|
+
props: z.object({ points: z.array(pointSchema).min(1) }).strict()
|
|
1075
1109
|
}).strict();
|
|
1076
1110
|
const BoardArrowAuthoringItemSchema = z.object({
|
|
1077
1111
|
...styledBaseFields,
|
|
1078
1112
|
type: z.literal("arrow"),
|
|
1079
1113
|
props: z.object({
|
|
1080
|
-
start: worldPointSchema
|
|
1081
|
-
end: worldPointSchema
|
|
1082
|
-
bend: finiteSchema
|
|
1114
|
+
start: worldPointSchema,
|
|
1115
|
+
end: worldPointSchema,
|
|
1116
|
+
bend: finiteSchema.min(-.85).max(.85).default(0),
|
|
1083
1117
|
arrowStart: z.boolean().default(false),
|
|
1084
1118
|
arrowEnd: z.boolean().default(true),
|
|
1085
1119
|
label: z.string().default("")
|
|
@@ -1136,1264 +1170,178 @@ const BoardExtensionAuthoringItemSchema = z.object({
|
|
|
1136
1170
|
...baseFields,
|
|
1137
1171
|
type: extensionTypeSchema,
|
|
1138
1172
|
kindVersion: z.number().int().positive(),
|
|
1139
|
-
props: jsonObjectSchema
|
|
1140
|
-
style: jsonObjectSchema
|
|
1173
|
+
props: jsonObjectSchema,
|
|
1174
|
+
style: jsonObjectSchema.optional(),
|
|
1141
1175
|
source: z.object({
|
|
1142
1176
|
kind: z.string().min(1).max(80),
|
|
1143
1177
|
ref: z.string().min(1).max(4096),
|
|
1144
|
-
snapshot: jsonObjectSchema
|
|
1178
|
+
snapshot: jsonObjectSchema.optional()
|
|
1145
1179
|
}).strict().optional()
|
|
1146
1180
|
}).strict();
|
|
1147
1181
|
const BoardAuthoringItemSchema = z.union([BoardBuiltinAuthoringItemSchema, BoardExtensionAuthoringItemSchema]);
|
|
1148
1182
|
const framePatchSchema = z.object({
|
|
1149
|
-
x: finiteSchema
|
|
1150
|
-
y: finiteSchema
|
|
1151
|
-
width: finiteSchema
|
|
1152
|
-
height: finiteSchema
|
|
1153
|
-
rotation: finiteSchema
|
|
1183
|
+
x: finiteSchema.optional(),
|
|
1184
|
+
y: finiteSchema.optional(),
|
|
1185
|
+
width: finiteSchema.positive().optional(),
|
|
1186
|
+
height: finiteSchema.positive().optional(),
|
|
1187
|
+
rotation: finiteSchema.optional()
|
|
1154
1188
|
}).strict();
|
|
1155
1189
|
/** JSON Merge Patch semantics, constrained to the stable Item envelope. */
|
|
1156
1190
|
const BoardItemPatchSchema = z.object({
|
|
1157
1191
|
frame: framePatchSchema.optional(),
|
|
1158
|
-
parentId: idSchema
|
|
1192
|
+
parentId: idSchema.nullable().optional(),
|
|
1159
1193
|
locked: z.boolean().nullable().optional(),
|
|
1160
|
-
props: jsonObjectSchema
|
|
1161
|
-
style: jsonObjectSchema
|
|
1162
|
-
source: jsonObjectSchema
|
|
1163
|
-
metadata: jsonObjectSchema
|
|
1194
|
+
props: jsonObjectSchema.optional(),
|
|
1195
|
+
style: jsonObjectSchema.nullable().optional(),
|
|
1196
|
+
source: jsonObjectSchema.nullable().optional(),
|
|
1197
|
+
metadata: jsonObjectSchema.nullable().optional()
|
|
1164
1198
|
}).strict().refine((patch) => Object.keys(patch).length > 0, "item patch is empty");
|
|
1199
|
+
const BoardConnectionInputSchema = BoardConnectionSchema;
|
|
1200
|
+
const stripServerFields = (value) => {
|
|
1201
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1202
|
+
const { boardId: _boardId, revision: _revision, ...input } = value;
|
|
1203
|
+
return input;
|
|
1204
|
+
};
|
|
1205
|
+
const BoardEffectCommandInputSchema = z.preprocess(stripServerFields, BoardEffectInputSchema);
|
|
1206
|
+
const BoardCompositionCommandInputSchema = z.preprocess(stripServerFields, BoardCompositionInputSchema);
|
|
1165
1207
|
const BoardSemanticCommandSchema = z.discriminatedUnion("type", [
|
|
1208
|
+
z.object({
|
|
1209
|
+
type: z.literal("board.patch"),
|
|
1210
|
+
patch: z.object({
|
|
1211
|
+
title: z.string().min(1).max(255).optional(),
|
|
1212
|
+
metadata: jsonObjectSchema.nullable().optional(),
|
|
1213
|
+
metadataPatch: jsonObjectSchema.optional()
|
|
1214
|
+
}).strict().refine((patch) => Object.keys(patch).length > 0, "board patch is empty")
|
|
1215
|
+
}).strict(),
|
|
1166
1216
|
z.object({
|
|
1167
1217
|
type: z.literal("item.create"),
|
|
1168
1218
|
item: BoardAuthoringItemSchema
|
|
1169
1219
|
}).strict(),
|
|
1170
1220
|
z.object({
|
|
1171
1221
|
type: z.literal("item.patch"),
|
|
1172
|
-
itemId: idSchema
|
|
1222
|
+
itemId: idSchema,
|
|
1173
1223
|
patch: BoardItemPatchSchema
|
|
1174
1224
|
}).strict(),
|
|
1175
1225
|
z.object({
|
|
1176
1226
|
type: z.literal("item.replace"),
|
|
1177
|
-
itemId: idSchema
|
|
1227
|
+
itemId: idSchema,
|
|
1178
1228
|
item: BoardAuthoringItemSchema
|
|
1179
1229
|
}).strict(),
|
|
1180
1230
|
z.object({
|
|
1181
1231
|
type: z.literal("item.delete"),
|
|
1182
|
-
itemId: idSchema
|
|
1232
|
+
itemId: idSchema,
|
|
1183
1233
|
cascade: z.boolean().default(false)
|
|
1184
|
-
}).strict()
|
|
1185
|
-
]);
|
|
1186
|
-
z.object({
|
|
1187
|
-
mutationId: idSchema$2,
|
|
1188
|
-
baseVersion: z.number().int().nonnegative(),
|
|
1189
|
-
clientId: idSchema$2.optional(),
|
|
1190
|
-
undoGroupId: idSchema$2.optional(),
|
|
1191
|
-
commands: z.array(BoardSemanticCommandSchema).min(1).max(5e4)
|
|
1192
|
-
}).strict();
|
|
1193
|
-
//#endregion
|
|
1194
|
-
//#region ../protocol/dist/board.js
|
|
1195
|
-
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
1196
|
-
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
1197
|
-
const idSchema$1 = z.string().min(1).max(160);
|
|
1198
|
-
const extensionIdSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
1199
|
-
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
1200
|
-
const finiteSchema$1 = z.number().finite();
|
|
1201
|
-
z.object({
|
|
1202
|
-
kind: z.literal(BOARD_MANIFEST_KIND),
|
|
1203
|
-
version: z.literal(1),
|
|
1204
|
-
boardId: z.string().uuid(),
|
|
1205
|
-
title: z.string().min(1).max(255)
|
|
1206
|
-
});
|
|
1207
|
-
z.object({
|
|
1208
|
-
centerX: finiteSchema$1,
|
|
1209
|
-
centerY: finiteSchema$1,
|
|
1210
|
-
zoom: finiteSchema$1.positive()
|
|
1211
|
-
});
|
|
1212
|
-
const BoardCameraFocusSchema = z.discriminatedUnion("type", [
|
|
1213
|
-
z.object({
|
|
1214
|
-
type: z.literal("rect"),
|
|
1215
|
-
rect: z.object({
|
|
1216
|
-
x: finiteSchema$1,
|
|
1217
|
-
y: finiteSchema$1,
|
|
1218
|
-
width: finiteSchema$1.positive(),
|
|
1219
|
-
height: finiteSchema$1.positive()
|
|
1220
|
-
})
|
|
1221
|
-
}),
|
|
1222
|
-
z.object({
|
|
1223
|
-
type: z.literal("item"),
|
|
1224
|
-
itemId: idSchema$1
|
|
1225
|
-
}),
|
|
1226
|
-
z.object({
|
|
1227
|
-
type: z.literal("items"),
|
|
1228
|
-
itemIds: z.array(idSchema$1).min(1).max(1e3)
|
|
1229
|
-
}),
|
|
1230
|
-
z.object({
|
|
1231
|
-
type: z.literal("frame"),
|
|
1232
|
-
frameId: idSchema$1
|
|
1233
|
-
})
|
|
1234
|
-
]);
|
|
1235
|
-
const BoardCameraFocusParamsSchema = z.object({
|
|
1236
|
-
focus: BoardCameraFocusSchema,
|
|
1237
|
-
fit: z.enum(["contain", "cover"]).default("contain"),
|
|
1238
|
-
padding: finiteSchema$1.nonnegative().default(32),
|
|
1239
|
-
minZoom: finiteSchema$1.positive().optional(),
|
|
1240
|
-
maxZoom: finiteSchema$1.positive().optional()
|
|
1241
|
-
}).superRefine((value, context) => {
|
|
1242
|
-
if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
|
|
1243
|
-
code: "custom",
|
|
1244
|
-
message: "minZoom must not exceed maxZoom",
|
|
1245
|
-
path: ["minZoom"]
|
|
1246
|
-
});
|
|
1247
|
-
});
|
|
1248
|
-
const BoardAssetRefSchema = z.object({
|
|
1249
|
-
type: z.enum(["space-file", "extension"]),
|
|
1250
|
-
ref: z.string().min(1).max(4096),
|
|
1251
|
-
digest: z.string().min(16).max(160).optional()
|
|
1252
|
-
});
|
|
1253
|
-
const BoardEffectSchema = z.object({
|
|
1254
|
-
id: idSchema$1,
|
|
1255
|
-
boardId: z.string().uuid(),
|
|
1256
|
-
target: z.discriminatedUnion("type", [z.object({
|
|
1257
|
-
type: z.literal("item"),
|
|
1258
|
-
itemId: idSchema$1
|
|
1259
|
-
}).strict(), z.object({ type: z.literal("board") }).strict()]),
|
|
1260
|
-
kind: extensionIdSchema,
|
|
1261
|
-
kindVersion: z.number().int().positive(),
|
|
1262
|
-
enabled: z.boolean().default(true),
|
|
1263
|
-
lifecycle: z.enum([
|
|
1264
|
-
"persistent",
|
|
1265
|
-
"when-visible",
|
|
1266
|
-
"manual"
|
|
1267
|
-
]),
|
|
1268
|
-
timeOrigin: z.enum([
|
|
1269
|
-
"board",
|
|
1270
|
-
"visible",
|
|
1271
|
-
"activation"
|
|
1272
|
-
]),
|
|
1273
|
-
layer: z.enum([
|
|
1274
|
-
"behind",
|
|
1275
|
-
"front",
|
|
1276
|
-
"screen"
|
|
1277
|
-
]).default("front"),
|
|
1278
|
-
seed: z.string().min(1).max(160),
|
|
1279
|
-
params: jsonObjectSchema.default({}),
|
|
1280
|
-
assetRefs: z.array(BoardAssetRefSchema).default([]),
|
|
1281
|
-
metadata: jsonObjectSchema.default({}),
|
|
1282
|
-
revision: z.number().int().nonnegative()
|
|
1283
|
-
});
|
|
1284
|
-
z.object({
|
|
1285
|
-
nodeId: idSchema$1,
|
|
1286
|
-
type: z.string().min(1).max(40),
|
|
1287
|
-
parentId: idSchema$1.nullable(),
|
|
1288
|
-
orderKey: z.string().max(4096).nullable(),
|
|
1289
|
-
x: finiteSchema$1,
|
|
1290
|
-
y: finiteSchema$1,
|
|
1291
|
-
width: finiteSchema$1.positive(),
|
|
1292
|
-
height: finiteSchema$1.positive(),
|
|
1293
|
-
rotation: finiteSchema$1,
|
|
1294
|
-
refKind: z.string().max(40).nullable(),
|
|
1295
|
-
refPath: z.string().max(4096).nullable(),
|
|
1296
|
-
refUrl: z.string().max(4096).nullable(),
|
|
1297
|
-
view: jsonObjectSchema,
|
|
1298
|
-
style: jsonObjectSchema,
|
|
1299
|
-
data: jsonObjectSchema
|
|
1300
|
-
});
|
|
1301
|
-
z.object({
|
|
1302
|
-
path: z.string().min(1),
|
|
1303
|
-
/** Reused by clients when board creation is interrupted and retried. */
|
|
1304
|
-
mutationId: z.string().max(128).optional(),
|
|
1305
|
-
title: z.string().min(1).max(255).optional(),
|
|
1306
|
-
metadata: jsonObjectSchema.optional(),
|
|
1307
|
-
items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
|
|
1308
|
-
connections: z.array(BoardConnectionSchema).max(5e4).optional(),
|
|
1309
|
-
effects: z.array(BoardEffectSchema.omit({
|
|
1310
|
-
boardId: true,
|
|
1311
|
-
revision: true
|
|
1312
|
-
})).optional(),
|
|
1313
|
-
compositions: z.array(BoardCompositionInputSchema).optional()
|
|
1314
|
-
});
|
|
1315
|
-
z.object({
|
|
1316
|
-
include: z.array(z.enum([
|
|
1317
|
-
"nodes",
|
|
1318
|
-
"connections",
|
|
1319
|
-
"effects",
|
|
1320
|
-
"compositions",
|
|
1321
|
-
"playback"
|
|
1322
|
-
])).optional(),
|
|
1323
|
-
viewport: z.object({
|
|
1324
|
-
x: finiteSchema$1,
|
|
1325
|
-
y: finiteSchema$1,
|
|
1326
|
-
width: finiteSchema$1.positive(),
|
|
1327
|
-
height: finiteSchema$1.positive()
|
|
1328
|
-
}).optional()
|
|
1329
|
-
});
|
|
1330
|
-
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
1331
|
-
const BoardPlaybackPolicySchema = z.object({
|
|
1332
|
-
compositionId: idSchema$1,
|
|
1333
|
-
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
1334
|
-
delayMs: finiteSchema$1.nonnegative().default(0)
|
|
1335
|
-
});
|
|
1336
|
-
function parseBoardPlaybackPolicy(metadata) {
|
|
1337
|
-
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
1338
|
-
return parsed.success ? parsed.data : null;
|
|
1339
|
-
}
|
|
1340
|
-
z.discriminatedUnion("type", [
|
|
1341
|
-
z.object({
|
|
1342
|
-
commandId: idSchema$1,
|
|
1343
|
-
type: z.literal("play"),
|
|
1344
|
-
compositionId: idSchema$1,
|
|
1345
|
-
position: finiteSchema$1.nonnegative().optional(),
|
|
1346
|
-
timeScale: finiteSchema$1.positive().max(4).optional(),
|
|
1347
|
-
shared: z.boolean().optional(),
|
|
1348
|
-
seed: idSchema$1.optional()
|
|
1349
|
-
}),
|
|
1350
|
-
z.object({
|
|
1351
|
-
commandId: idSchema$1,
|
|
1352
|
-
type: z.literal("pause"),
|
|
1353
|
-
playbackId: z.string().uuid()
|
|
1354
|
-
}),
|
|
1355
|
-
z.object({
|
|
1356
|
-
commandId: idSchema$1,
|
|
1357
|
-
type: z.literal("seek"),
|
|
1358
|
-
playbackId: z.string().uuid(),
|
|
1359
|
-
position: finiteSchema$1.nonnegative()
|
|
1360
|
-
}),
|
|
1361
|
-
z.object({
|
|
1362
|
-
commandId: idSchema$1,
|
|
1363
|
-
type: z.literal("stop"),
|
|
1364
|
-
playbackId: z.string().uuid()
|
|
1365
|
-
})
|
|
1366
|
-
]);
|
|
1367
|
-
//#endregion
|
|
1368
|
-
//#region ../protocol/dist/board-capability-registry.js
|
|
1369
|
-
const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
|
|
1370
|
-
new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
1371
|
-
const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1372
|
-
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
1373
|
-
function validateBuiltinBoardClip(clip, path = "clip") {
|
|
1374
|
-
if (!CLIPS.has(clip.kind)) return [];
|
|
1375
|
-
const errors = [];
|
|
1376
|
-
const error = (message, suffix, coordinateSpace, code = "INVALID_BOARD_CLIP") => {
|
|
1377
|
-
errors.push({
|
|
1378
|
-
severity: "error",
|
|
1379
|
-
code,
|
|
1380
|
-
message,
|
|
1381
|
-
path: `${path}.${suffix}`,
|
|
1382
|
-
...coordinateSpace ? { coordinateSpace } : {}
|
|
1383
|
-
});
|
|
1384
|
-
};
|
|
1385
|
-
if ((clip.kind.startsWith("motion.") || clip.kind.startsWith("draw.") || clip.kind === "text.reveal" || clip.kind === "effects.trail") && clip.target.type !== "item") error(`${clip.kind} must target an item`, "target");
|
|
1386
|
-
if (clip.kind.startsWith("camera.") && clip.target.type !== "camera") error(`${clip.kind} must target the camera`, "target");
|
|
1387
|
-
if (clip.kind === "motion.path") {
|
|
1388
|
-
const points = clip.params.points;
|
|
1389
|
-
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");
|
|
1390
|
-
else for (const [index, point] of points.entries()) if (!record(point) || !finite(point.x) || !finite(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
|
|
1391
|
-
}
|
|
1392
|
-
if (clip.kind === "effects.particles") {
|
|
1393
|
-
const count = clip.params.count;
|
|
1394
|
-
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");
|
|
1395
|
-
const bounds = clip.params.bounds;
|
|
1396
|
-
if (!record(bounds) || !finite(bounds.x) || !finite(bounds.y) || !finite(bounds.width) || !finite(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
|
|
1397
|
-
}
|
|
1398
|
-
if (clip.kind === "camera.focus") {
|
|
1399
|
-
const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
|
|
1400
|
-
if (!parsed.success) error(parsed.error.issues[0]?.message ?? "invalid camera focus", "params", "world");
|
|
1401
|
-
}
|
|
1402
|
-
return errors;
|
|
1403
|
-
}
|
|
1404
|
-
function estimateBuiltinBoardClipCost(clip) {
|
|
1405
|
-
switch (clip.kind) {
|
|
1406
|
-
case "effects.particles": {
|
|
1407
|
-
const count = Number.isSafeInteger(clip.params.count) ? Math.max(0, clip.params.count) : 0;
|
|
1408
|
-
return {
|
|
1409
|
-
particles: count,
|
|
1410
|
-
vertices: count * 4,
|
|
1411
|
-
dynamicVertices: count * 4,
|
|
1412
|
-
drawCalls: 1,
|
|
1413
|
-
bufferBytes: count * 48,
|
|
1414
|
-
simulationSteps: count
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
case "effects.trail": return {
|
|
1418
|
-
vertices: 32,
|
|
1419
|
-
dynamicVertices: 32,
|
|
1420
|
-
drawCalls: 1,
|
|
1421
|
-
bufferBytes: 1024,
|
|
1422
|
-
simulationSteps: 16
|
|
1423
|
-
};
|
|
1424
|
-
case "effects.impact":
|
|
1425
|
-
case "effects.flash": return {
|
|
1426
|
-
vertices: 64,
|
|
1427
|
-
drawCalls: 1
|
|
1428
|
-
};
|
|
1429
|
-
case "draw.reveal":
|
|
1430
|
-
case "draw.handwrite": return {
|
|
1431
|
-
drawCalls: 1,
|
|
1432
|
-
dynamicVertices: 1
|
|
1433
|
-
};
|
|
1434
|
-
case "motion.path": return { simulationSteps: Array.isArray(clip.params.points) ? clip.params.points.length : 0 };
|
|
1435
|
-
default: return {};
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
//#endregion
|
|
1439
|
-
//#region ../protocol/dist/board-url.js
|
|
1440
|
-
const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
1441
|
-
function parseIpv4(host) {
|
|
1442
|
-
const parts = host.split(".").map(Number);
|
|
1443
|
-
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
1444
|
-
}
|
|
1445
|
-
function isBlockedIpv4(host) {
|
|
1446
|
-
const parts = parseIpv4(host);
|
|
1447
|
-
if (!parts) return false;
|
|
1448
|
-
const [first, second, third] = parts;
|
|
1449
|
-
if (first === 0 || first === 10 || first === 127) return true;
|
|
1450
|
-
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
1451
|
-
if (first === 169 && second === 254) return true;
|
|
1452
|
-
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
1453
|
-
if (first === 192 && second === 168) return true;
|
|
1454
|
-
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
1455
|
-
if (first === 192 && second === 88 && third === 99) return true;
|
|
1456
|
-
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
1457
|
-
if (first === 198 && second === 51 && third === 100) return true;
|
|
1458
|
-
if (first === 203 && second === 0 && third === 113) return true;
|
|
1459
|
-
return first >= 224;
|
|
1460
|
-
}
|
|
1461
|
-
function expandIpv6(host) {
|
|
1462
|
-
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
1463
|
-
if (extra !== void 0) return null;
|
|
1464
|
-
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
1465
|
-
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
1466
|
-
const missing = 8 - headParts.length - tailParts.length;
|
|
1467
|
-
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
1468
|
-
const parts = [
|
|
1469
|
-
...headParts,
|
|
1470
|
-
...Array.from({ length: missing }, () => "0"),
|
|
1471
|
-
...tailParts
|
|
1472
|
-
];
|
|
1473
|
-
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
1474
|
-
return parts.map((part) => part.padStart(4, "0"));
|
|
1475
|
-
}
|
|
1476
|
-
function isBlockedIpv6(host) {
|
|
1477
|
-
const parts = expandIpv6(host);
|
|
1478
|
-
if (!parts) return true;
|
|
1479
|
-
if (parts.every((part) => part === "0000")) return true;
|
|
1480
|
-
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
1481
|
-
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
1482
|
-
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
1483
|
-
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
1484
|
-
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
1485
|
-
}
|
|
1486
|
-
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
1487
|
-
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
1488
|
-
if ((first & 65024) === 64512) return true;
|
|
1489
|
-
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
1490
|
-
if ((first & 65280) === 65280) return true;
|
|
1491
|
-
return parts[0] === "2001" && parts[1] === "0db8";
|
|
1492
|
-
}
|
|
1493
|
-
function isPublicBoardRemoteAddress(value) {
|
|
1494
|
-
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
1495
|
-
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
1496
|
-
return address.includes(":") && !isBlockedIpv6(address);
|
|
1497
|
-
}
|
|
1498
|
-
function isBlockedHost(hostname) {
|
|
1499
|
-
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
1500
|
-
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
1501
|
-
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
1502
|
-
return false;
|
|
1503
|
-
}
|
|
1504
|
-
/**
|
|
1505
|
-
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
1506
|
-
* addresses; any future server-side fetcher must additionally validate DNS
|
|
1507
|
-
* resolution to defend against rebinding.
|
|
1508
|
-
*/
|
|
1509
|
-
function normalizeBoardRemoteUrl(value) {
|
|
1510
|
-
if (typeof value !== "string") return void 0;
|
|
1511
|
-
const input = value.trim();
|
|
1512
|
-
if (!input || input.length > 4096) return void 0;
|
|
1513
|
-
try {
|
|
1514
|
-
const url = new URL(input);
|
|
1515
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
1516
|
-
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
1517
|
-
const normalized = url.toString();
|
|
1518
|
-
return normalized.length <= 4096 ? normalized : void 0;
|
|
1519
|
-
} catch {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
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" });
|
|
1524
|
-
//#endregion
|
|
1525
|
-
//#region ../protocol/dist/board-document.js
|
|
1526
|
-
const BoardFrameSchema = z.object({
|
|
1527
|
-
x: z.number().finite(),
|
|
1528
|
-
y: z.number().finite(),
|
|
1529
|
-
width: z.number().finite().positive(),
|
|
1530
|
-
height: z.number().finite().positive(),
|
|
1531
|
-
rotation: z.number().finite().default(0)
|
|
1532
|
-
});
|
|
1533
|
-
/**
|
|
1534
|
-
* The board camera. This is local UI state, not synced content: semantic ops
|
|
1535
|
-
* (see diffBoardDocuments) never describe the viewport, and the editor holds
|
|
1536
|
-
* the live camera separately from the persisted document. Here it only serves
|
|
1537
|
-
* as an initial camera hint when a document is first loaded.
|
|
1538
|
-
*/
|
|
1539
|
-
const BoardViewportSchema = z.object({
|
|
1540
|
-
x: z.number().finite(),
|
|
1541
|
-
y: z.number().finite(),
|
|
1542
|
-
zoom: z.number().finite().min(.05).max(8)
|
|
1543
|
-
});
|
|
1544
|
-
const BoardAppearanceSchema = z.object({
|
|
1545
|
-
theme: z.string().min(1).default("clean"),
|
|
1546
|
-
background: z.object({
|
|
1547
|
-
kind: z.enum([
|
|
1548
|
-
"solid",
|
|
1549
|
-
"dots",
|
|
1550
|
-
"grid",
|
|
1551
|
-
"image",
|
|
1552
|
-
"shader",
|
|
1553
|
-
"custom"
|
|
1554
|
-
]).default("dots"),
|
|
1555
|
-
color: z.string().optional(),
|
|
1556
|
-
imageUrl: BoardRemoteUrlSchema.optional(),
|
|
1557
|
-
fit: z.enum([
|
|
1558
|
-
"cover",
|
|
1559
|
-
"contain",
|
|
1560
|
-
"repeat"
|
|
1561
|
-
]).optional(),
|
|
1562
|
-
position: z.enum([
|
|
1563
|
-
"center",
|
|
1564
|
-
"top",
|
|
1565
|
-
"bottom",
|
|
1566
|
-
"left",
|
|
1567
|
-
"right"
|
|
1568
|
-
]).optional(),
|
|
1569
|
-
opacity: z.number().finite().min(0).max(1).optional()
|
|
1570
|
-
}).default({ kind: "solid" }),
|
|
1571
|
-
grid: z.object({
|
|
1572
|
-
visible: z.boolean().default(false),
|
|
1573
|
-
size: z.number().finite().min(4).default(24),
|
|
1574
|
-
opacity: z.number().finite().min(0).max(1).default(.12)
|
|
1575
|
-
}).default({
|
|
1576
|
-
visible: false,
|
|
1577
|
-
size: 24,
|
|
1578
|
-
opacity: .12
|
|
1579
|
-
}),
|
|
1580
|
-
mood: z.enum([
|
|
1581
|
-
"clean",
|
|
1582
|
-
"playful",
|
|
1583
|
-
"arcane",
|
|
1584
|
-
"cyber",
|
|
1585
|
-
"natural"
|
|
1586
|
-
]).default("clean")
|
|
1587
|
-
});
|
|
1588
|
-
/** Optional visual chrome still carried by a few older shapes. */
|
|
1589
|
-
const BoardItemStyleSchema = z.object({
|
|
1590
|
-
variant: z.string().min(1).default("default"),
|
|
1591
|
-
theme: z.string().min(1).optional(),
|
|
1592
|
-
accentColor: z.string().optional(),
|
|
1593
|
-
size: z.enum([
|
|
1594
|
-
"sm",
|
|
1595
|
-
"md",
|
|
1596
|
-
"lg"
|
|
1597
|
-
]).default("md"),
|
|
1598
|
-
emphasis: z.enum([
|
|
1599
|
-
"normal",
|
|
1600
|
-
"rare",
|
|
1601
|
-
"epic",
|
|
1602
|
-
"legendary"
|
|
1603
|
-
]).default("normal"),
|
|
1604
|
-
effects: z.array(z.string().min(1)).default([])
|
|
1605
|
-
});
|
|
1606
|
-
const SpaceFileRefSchema = z.object({
|
|
1607
|
-
kind: z.literal("space-file"),
|
|
1608
|
-
path: z.string().min(1)
|
|
1609
|
-
});
|
|
1610
|
-
const BoardMediaSnapshotSchema = z.object({
|
|
1611
|
-
title: z.string().optional(),
|
|
1612
|
-
mimeType: z.string().optional(),
|
|
1613
|
-
size: z.number().finite().nonnegative().optional(),
|
|
1614
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1615
|
-
/** Intrinsic pixel size once known. */
|
|
1616
|
-
naturalWidth: z.number().finite().positive().optional(),
|
|
1617
|
-
naturalHeight: z.number().finite().positive().optional()
|
|
1618
|
-
});
|
|
1619
|
-
const BoardItemBaseSchema = z.object({
|
|
1620
|
-
id: z.string().min(1),
|
|
1621
|
-
frame: BoardFrameSchema,
|
|
1622
|
-
/** When true the shape cannot be moved, resized, or deleted. */
|
|
1623
|
-
locked: z.boolean().optional(),
|
|
1624
|
-
style: BoardItemStyleSchema.optional(),
|
|
1625
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1626
|
-
});
|
|
1627
|
-
/** Freestanding text — no card chrome; its frame always follows the glyphs. */
|
|
1628
|
-
const BoardTextItemSchema = BoardItemBaseSchema.extend({
|
|
1629
|
-
type: z.literal("text"),
|
|
1630
|
-
text: z.string().default(""),
|
|
1631
|
-
color: z.string().min(1).default("neutral"),
|
|
1632
|
-
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
1633
|
-
});
|
|
1634
|
-
const BoardGeoItemSchema = BoardItemBaseSchema.extend({
|
|
1635
|
-
type: z.literal("geo"),
|
|
1636
|
-
geo: z.string().min(1).default("rectangle"),
|
|
1637
|
-
text: z.string().default(""),
|
|
1638
|
-
color: z.string().min(1).default("brand"),
|
|
1639
|
-
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
1640
|
-
});
|
|
1641
|
-
/** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
|
|
1642
|
-
const DrawPointSchema = z.object({
|
|
1643
|
-
x: z.number().finite(),
|
|
1644
|
-
y: z.number().finite(),
|
|
1645
|
-
p: z.number().finite().min(0).max(1).default(.5)
|
|
1646
|
-
});
|
|
1647
|
-
/** A world-space point. */
|
|
1648
|
-
const BoardPointSchema = z.object({
|
|
1649
|
-
x: z.number().finite(),
|
|
1650
|
-
y: z.number().finite()
|
|
1651
|
-
});
|
|
1652
|
-
const BoardDrawItemSchema = BoardItemBaseSchema.extend({
|
|
1653
|
-
type: z.literal("draw"),
|
|
1654
|
-
points: z.array(DrawPointSchema).default([]),
|
|
1655
|
-
color: z.string().min(1).default("brand"),
|
|
1656
|
-
size: z.number().finite().positive().default(4)
|
|
1657
|
-
});
|
|
1658
|
-
/**
|
|
1659
|
-
* A free arrow — a standalone annotation stroke between two world points.
|
|
1660
|
-
*
|
|
1661
|
-
* Arrows do not relate nodes. A relation between two nodes is a
|
|
1662
|
-
* `BoardConnection`, which is stored separately and resolves its geometry from
|
|
1663
|
-
* the live node frames. Keeping the two apart is what lets an arrow be a plain
|
|
1664
|
-
* shape (its own frame, freely movable) while a connection stays purely
|
|
1665
|
-
* semantic — neither has to pretend to be the other.
|
|
1666
|
-
*/
|
|
1667
|
-
const BoardArrowItemSchema = BoardItemBaseSchema.extend({
|
|
1668
|
-
type: z.literal("arrow"),
|
|
1669
|
-
start: BoardPointSchema,
|
|
1670
|
-
end: BoardPointSchema,
|
|
1671
|
-
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
1672
|
-
color: z.string().min(1).default("brand"),
|
|
1673
|
-
size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
|
|
1674
|
-
arrowStart: z.boolean().default(false),
|
|
1675
|
-
arrowEnd: z.boolean().default(true),
|
|
1676
|
-
label: z.string().default("")
|
|
1677
|
-
});
|
|
1678
|
-
/** A frame container for organising shapes. */
|
|
1679
|
-
const BoardFrameItemSchema = BoardItemBaseSchema.extend({
|
|
1680
|
-
type: z.literal("frame"),
|
|
1681
|
-
label: z.string().default("Frame"),
|
|
1682
|
-
color: z.string().min(1).default("neutral")
|
|
1683
|
-
});
|
|
1684
|
-
/** Image node — space file only, natural aspect, no chrome. */
|
|
1685
|
-
const BoardImageItemSchema = BoardItemBaseSchema.extend({
|
|
1686
|
-
type: z.literal("image"),
|
|
1687
|
-
ref: SpaceFileRefSchema,
|
|
1688
|
-
snapshot: BoardMediaSnapshotSchema.optional(),
|
|
1689
|
-
/** Optional normalized crop in source image space (0..1). */
|
|
1690
|
-
crop: z.object({
|
|
1691
|
-
x: z.number().finite().min(0).max(1).default(0),
|
|
1692
|
-
y: z.number().finite().min(0).max(1).default(0),
|
|
1693
|
-
w: z.number().finite().min(0).max(1).default(1),
|
|
1694
|
-
h: z.number().finite().min(0).max(1).default(1)
|
|
1695
|
-
}).optional()
|
|
1696
|
-
});
|
|
1697
|
-
/** Video node — space file only. Playback state is local UI, never synced. */
|
|
1698
|
-
const BoardVideoItemSchema = BoardItemBaseSchema.extend({
|
|
1699
|
-
type: z.literal("video"),
|
|
1700
|
-
ref: SpaceFileRefSchema,
|
|
1701
|
-
snapshot: BoardMediaSnapshotSchema.optional()
|
|
1702
|
-
});
|
|
1703
|
-
/** Audio node — space file only. Playback state is local UI, never synced. */
|
|
1704
|
-
const BoardAudioItemSchema = BoardItemBaseSchema.extend({
|
|
1705
|
-
type: z.literal("audio"),
|
|
1706
|
-
ref: SpaceFileRefSchema,
|
|
1707
|
-
snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
|
|
1708
|
-
});
|
|
1709
|
-
/**
|
|
1710
|
-
* Cached display facts for a file card.
|
|
1711
|
-
*
|
|
1712
|
-
* Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
|
|
1713
|
-
* cache and never a second source of truth: the workspace file remains
|
|
1714
|
-
* authoritative, and a stale snapshot is detectable rather than silently wrong.
|
|
1715
|
-
* It is stored so that opening a board renders complete cards immediately, with
|
|
1716
|
-
* no per-node file read on the first paint.
|
|
1717
|
-
*/
|
|
1718
|
-
const BoardFileSnapshotSchema = z.object({
|
|
1719
|
-
title: z.string().optional(),
|
|
1720
|
-
mimeType: z.string().optional(),
|
|
1721
|
-
size: z.number().finite().nonnegative().optional(),
|
|
1722
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1723
|
-
/** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
|
|
1724
|
-
excerpt: z.string().optional(),
|
|
1725
|
-
/** Cover image inside the space, already resolved to a workspace path. */
|
|
1726
|
-
coverPath: z.string().optional(),
|
|
1727
|
-
/** Cover image at an absolute https URL, as declared by the file itself. */
|
|
1728
|
-
coverUrl: z.string().optional()
|
|
1729
|
-
});
|
|
1730
|
-
/**
|
|
1731
|
-
* File node — a thumbnail entry point to any workspace file.
|
|
1732
|
-
*
|
|
1733
|
-
* This is the fallback for every dropped file that is not natively an image or
|
|
1734
|
-
* video, including binaries and unknown extensions: a board should never refuse
|
|
1735
|
-
* a file, only present it with less detail. Presentation tiers are derived from
|
|
1736
|
-
* the snapshot (see filePreviewKind), not stored, so there is no display state
|
|
1737
|
-
* to drift from the facts.
|
|
1738
|
-
*/
|
|
1739
|
-
const BoardFileItemSchema = BoardItemBaseSchema.extend({
|
|
1740
|
-
type: z.literal("file"),
|
|
1741
|
-
ref: SpaceFileRefSchema,
|
|
1742
|
-
snapshot: BoardFileSnapshotSchema.optional()
|
|
1743
|
-
});
|
|
1744
|
-
const BoardTaskMediaArtifactFields = {
|
|
1745
|
-
id: z.string().min(1).max(240),
|
|
1746
|
-
title: z.string().max(240).optional(),
|
|
1747
|
-
url: BoardRemoteUrlSchema,
|
|
1748
|
-
mimeType: z.string().max(160).optional()
|
|
1749
|
-
};
|
|
1750
|
-
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
1751
|
-
z.object({
|
|
1752
|
-
...BoardTaskMediaArtifactFields,
|
|
1753
|
-
type: z.literal("image"),
|
|
1754
|
-
naturalWidth: z.number().positive().optional(),
|
|
1755
|
-
naturalHeight: z.number().positive().optional()
|
|
1756
1234
|
}).strict(),
|
|
1757
1235
|
z.object({
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
durationMs: z.number().int().positive().optional(),
|
|
1762
|
-
naturalWidth: z.number().positive().optional(),
|
|
1763
|
-
naturalHeight: z.number().positive().optional()
|
|
1236
|
+
type: z.literal("item.reorder"),
|
|
1237
|
+
itemId: idSchema,
|
|
1238
|
+
index: z.number().int().nonnegative()
|
|
1764
1239
|
}).strict(),
|
|
1765
1240
|
z.object({
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
durationMs: z.number().int().positive().optional()
|
|
1770
|
-
}).strict(),
|
|
1771
|
-
z.object({
|
|
1772
|
-
id: z.string().min(1).max(240),
|
|
1773
|
-
type: z.literal("text"),
|
|
1774
|
-
title: z.string().max(240).optional(),
|
|
1775
|
-
textExcerpt: z.string().min(1).max(480)
|
|
1776
|
-
}).strict()
|
|
1777
|
-
]);
|
|
1778
|
-
/**
|
|
1779
|
-
* Cached task facts used for an immediate first paint. The task run remains the
|
|
1780
|
-
* source of truth and live clients refresh this projection by `taskRunId`.
|
|
1781
|
-
*/
|
|
1782
|
-
const BoardTaskSnapshotSchema = z.object({
|
|
1783
|
-
taskType: z.string().min(1).max(120),
|
|
1784
|
-
status: z.enum([
|
|
1785
|
-
"pending",
|
|
1786
|
-
"running",
|
|
1787
|
-
"completed",
|
|
1788
|
-
"failed"
|
|
1789
|
-
]),
|
|
1790
|
-
title: z.string().min(1).max(240),
|
|
1791
|
-
model: z.string().max(160).optional(),
|
|
1792
|
-
promptExcerpt: z.string().max(480).optional(),
|
|
1793
|
-
artifactCount: z.number().int().nonnegative(),
|
|
1794
|
-
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
1795
|
-
updatedAt: z.string().optional()
|
|
1796
|
-
}).strict();
|
|
1797
|
-
/** A stable reference to a task run with a small, replaceable display cache. */
|
|
1798
|
-
const BoardTaskItemSchema = BoardItemBaseSchema.extend({
|
|
1799
|
-
type: z.literal("task"),
|
|
1800
|
-
taskRunId: z.string().min(1),
|
|
1801
|
-
snapshot: BoardTaskSnapshotSchema
|
|
1802
|
-
});
|
|
1803
|
-
/**
|
|
1804
|
-
* A forward-compatible carrier for shape types this client does not recognise.
|
|
1805
|
-
* Its discriminant is the literal `"unknown"` so the item union still narrows
|
|
1806
|
-
* cleanly on `type`. The real type string and every original field are preserved
|
|
1807
|
-
* verbatim in `raw`.
|
|
1808
|
-
*/
|
|
1809
|
-
const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
|
|
1810
|
-
const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
|
|
1811
|
-
/**
|
|
1812
|
-
* Parse a single item leniently: known types are validated and normalised;
|
|
1813
|
-
* anything else becomes a lossless unknown item. Never throws on shape data —
|
|
1814
|
-
* a malformed known item degrades to unknown rather than failing the document.
|
|
1815
|
-
*/
|
|
1816
|
-
function parseBoardItemLoose(raw) {
|
|
1817
|
-
if (!raw || typeof raw !== "object") return makeUnknownItem(raw);
|
|
1818
|
-
switch (raw.type) {
|
|
1819
|
-
case "image": {
|
|
1820
|
-
const parsed = BoardImageItemSchema.safeParse(raw);
|
|
1821
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1822
|
-
}
|
|
1823
|
-
case "video": {
|
|
1824
|
-
const parsed = BoardVideoItemSchema.safeParse(raw);
|
|
1825
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1826
|
-
}
|
|
1827
|
-
case "audio": {
|
|
1828
|
-
const parsed = BoardAudioItemSchema.safeParse(raw);
|
|
1829
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1830
|
-
}
|
|
1831
|
-
case "file": {
|
|
1832
|
-
const parsed = BoardFileItemSchema.safeParse(raw);
|
|
1833
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1834
|
-
}
|
|
1835
|
-
case "task": {
|
|
1836
|
-
const parsed = BoardTaskItemSchema.safeParse(raw);
|
|
1837
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1838
|
-
}
|
|
1839
|
-
case "text": {
|
|
1840
|
-
const parsed = BoardTextItemSchema.safeParse(raw);
|
|
1841
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1842
|
-
}
|
|
1843
|
-
case "geo": {
|
|
1844
|
-
const parsed = BoardGeoItemSchema.safeParse(raw);
|
|
1845
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1846
|
-
}
|
|
1847
|
-
case "draw": {
|
|
1848
|
-
const parsed = BoardDrawItemSchema.safeParse(raw);
|
|
1849
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1850
|
-
}
|
|
1851
|
-
case "arrow": {
|
|
1852
|
-
const parsed = BoardArrowItemSchema.safeParse(raw);
|
|
1853
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1854
|
-
}
|
|
1855
|
-
case "frame": {
|
|
1856
|
-
const parsed = BoardFrameItemSchema.safeParse(raw);
|
|
1857
|
-
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1858
|
-
}
|
|
1859
|
-
default: return makeUnknownItem(raw);
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
function makeUnknownItem(raw) {
|
|
1863
|
-
const record = raw && typeof raw === "object" ? raw : {};
|
|
1864
|
-
const frameParsed = BoardFrameSchema.safeParse(record.frame);
|
|
1865
|
-
const styleParsed = BoardItemStyleSchema.safeParse(record.style);
|
|
1866
|
-
return {
|
|
1867
|
-
id: typeof record.id === "string" && record.id ? record.id : "unknown",
|
|
1868
|
-
type: UNKNOWN_BOARD_ITEM_TYPE,
|
|
1869
|
-
frame: frameParsed.success ? frameParsed.data : {
|
|
1870
|
-
x: 0,
|
|
1871
|
-
y: 0,
|
|
1872
|
-
width: 120,
|
|
1873
|
-
height: 80,
|
|
1874
|
-
rotation: 0
|
|
1875
|
-
},
|
|
1876
|
-
...record.locked === true ? { locked: true } : {},
|
|
1877
|
-
...styleParsed.success && record.style ? { style: styleParsed.data } : {},
|
|
1878
|
-
...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
|
|
1879
|
-
raw: record
|
|
1880
|
-
};
|
|
1881
|
-
}
|
|
1882
|
-
z.object({
|
|
1883
|
-
kind: z.literal(BOARD_DOCUMENT_KIND),
|
|
1884
|
-
version: z.literal(1),
|
|
1885
|
-
appearance: BoardAppearanceSchema.default({
|
|
1886
|
-
theme: "clean",
|
|
1887
|
-
background: { kind: "solid" },
|
|
1888
|
-
grid: {
|
|
1889
|
-
visible: false,
|
|
1890
|
-
size: 24,
|
|
1891
|
-
opacity: .12
|
|
1892
|
-
},
|
|
1893
|
-
mood: "clean"
|
|
1894
|
-
}),
|
|
1895
|
-
viewport: BoardViewportSchema,
|
|
1896
|
-
items: z.array(BoardItemSchema),
|
|
1897
|
-
/**
|
|
1898
|
-
* Node relations. Separate from `items` because a connection has no frame of
|
|
1899
|
-
* its own — its geometry is derived from the nodes it joins, so it is a
|
|
1900
|
-
* relation over the item set rather than a member of it.
|
|
1901
|
-
*
|
|
1902
|
-
* Connections referencing a missing node are dropped on parse: a relation to
|
|
1903
|
-
* nothing is not a relation, and keeping one would let an invisible dangling
|
|
1904
|
-
* edge accumulate silently. Callers that need to know write through the
|
|
1905
|
-
* transaction API, which reports the reference error instead.
|
|
1906
|
-
*/
|
|
1907
|
-
connections: z.array(BoardConnectionSchema).default([])
|
|
1908
|
-
});
|
|
1909
|
-
//#endregion
|
|
1910
|
-
//#region ../protocol/dist/board-node.js
|
|
1911
|
-
const BOARD_COLOR_IDS = [
|
|
1912
|
-
"brand",
|
|
1913
|
-
"neutral",
|
|
1914
|
-
"black",
|
|
1915
|
-
"white",
|
|
1916
|
-
"blue",
|
|
1917
|
-
"green",
|
|
1918
|
-
"amber",
|
|
1919
|
-
"violet",
|
|
1920
|
-
"rose"
|
|
1921
|
-
];
|
|
1922
|
-
const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
|
|
1923
|
-
const BOARD_GEO_KINDS = [
|
|
1924
|
-
"rectangle",
|
|
1925
|
-
"rounded",
|
|
1926
|
-
"ellipse",
|
|
1927
|
-
"diamond",
|
|
1928
|
-
"triangle"
|
|
1929
|
-
];
|
|
1930
|
-
const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
|
|
1931
|
-
const BOARD_NATIVE_NODE_TYPES = [
|
|
1932
|
-
"image",
|
|
1933
|
-
"video",
|
|
1934
|
-
"audio",
|
|
1935
|
-
"file",
|
|
1936
|
-
"task",
|
|
1937
|
-
"text",
|
|
1938
|
-
"geo",
|
|
1939
|
-
"draw",
|
|
1940
|
-
"arrow",
|
|
1941
|
-
"frame"
|
|
1942
|
-
];
|
|
1943
|
-
const metadataSchema = z.record(z.string(), z.unknown());
|
|
1944
|
-
const commonData = {
|
|
1945
|
-
locked: z.boolean().optional(),
|
|
1946
|
-
metadata: metadataSchema.optional()
|
|
1947
|
-
};
|
|
1948
|
-
const pointSchema = z.object({
|
|
1949
|
-
x: z.number().finite(),
|
|
1950
|
-
y: z.number().finite(),
|
|
1951
|
-
p: z.number().finite().min(0).max(1).default(.5)
|
|
1952
|
-
}).strict();
|
|
1953
|
-
const worldPointSchema = z.object({
|
|
1954
|
-
x: z.number().finite(),
|
|
1955
|
-
y: z.number().finite()
|
|
1956
|
-
}).strict();
|
|
1957
|
-
const strokeSizeSchema = z.number().finite().min(1).max(64);
|
|
1958
|
-
const mediaViewSchema = z.object({
|
|
1959
|
-
title: z.string().optional(),
|
|
1960
|
-
mimeType: z.string().optional(),
|
|
1961
|
-
size: z.number().finite().nonnegative().optional(),
|
|
1962
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1963
|
-
naturalWidth: z.number().finite().positive().optional(),
|
|
1964
|
-
naturalHeight: z.number().finite().positive().optional()
|
|
1965
|
-
}).strict();
|
|
1966
|
-
const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
|
|
1967
|
-
const dataSchemas = {
|
|
1968
|
-
text: z.object({
|
|
1969
|
-
...commonData,
|
|
1970
|
-
text: z.string().default(""),
|
|
1971
|
-
color: BoardColorIdSchema.default("neutral"),
|
|
1972
|
-
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
1973
|
-
}).strict(),
|
|
1974
|
-
geo: z.object({
|
|
1975
|
-
...commonData,
|
|
1976
|
-
geo: BoardGeoKindSchema.default("rectangle"),
|
|
1977
|
-
text: z.string().default(""),
|
|
1978
|
-
color: BoardColorIdSchema.default("brand"),
|
|
1979
|
-
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
1980
|
-
}).strict(),
|
|
1981
|
-
draw: z.object({
|
|
1982
|
-
...commonData,
|
|
1983
|
-
points: z.array(pointSchema).min(1),
|
|
1984
|
-
color: BoardColorIdSchema.default("brand"),
|
|
1985
|
-
size: strokeSizeSchema.default(4)
|
|
1986
|
-
}).strict(),
|
|
1987
|
-
arrow: z.object({
|
|
1988
|
-
...commonData,
|
|
1989
|
-
start: worldPointSchema,
|
|
1990
|
-
end: worldPointSchema,
|
|
1991
|
-
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
1992
|
-
color: BoardColorIdSchema.default("brand"),
|
|
1993
|
-
size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
|
|
1994
|
-
arrowStart: z.boolean().default(false),
|
|
1995
|
-
arrowEnd: z.boolean().default(true),
|
|
1996
|
-
label: z.string().default("")
|
|
1997
|
-
}).strict(),
|
|
1998
|
-
frame: z.object({
|
|
1999
|
-
...commonData,
|
|
2000
|
-
label: z.string().default("Frame"),
|
|
2001
|
-
color: BoardColorIdSchema.default("neutral")
|
|
2002
|
-
}).strict(),
|
|
2003
|
-
image: z.object({
|
|
2004
|
-
...commonData,
|
|
2005
|
-
crop: z.object({
|
|
2006
|
-
x: z.number().finite().min(0).max(1),
|
|
2007
|
-
y: z.number().finite().min(0).max(1),
|
|
2008
|
-
w: z.number().finite().min(0).max(1),
|
|
2009
|
-
h: z.number().finite().min(0).max(1)
|
|
2010
|
-
}).strict().optional()
|
|
2011
|
-
}).strict(),
|
|
2012
|
-
video: z.object(commonData).strict(),
|
|
2013
|
-
audio: z.object(commonData).strict(),
|
|
2014
|
-
file: z.object(commonData).strict(),
|
|
2015
|
-
task: z.object({
|
|
2016
|
-
...commonData,
|
|
2017
|
-
taskRunId: z.string().min(1)
|
|
2018
|
-
}).strict()
|
|
2019
|
-
};
|
|
2020
|
-
const fileViewSchema = z.object({
|
|
2021
|
-
title: z.string().optional(),
|
|
2022
|
-
mimeType: z.string().optional(),
|
|
2023
|
-
size: z.number().finite().nonnegative().optional(),
|
|
2024
|
-
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
2025
|
-
excerpt: z.string().optional(),
|
|
2026
|
-
coverPath: z.string().optional(),
|
|
2027
|
-
coverUrl: z.string().url().optional()
|
|
2028
|
-
}).strict();
|
|
2029
|
-
const taskViewSchema = BoardTaskSnapshotSchema;
|
|
2030
|
-
const emptyViewSchema = z.object({}).strict();
|
|
2031
|
-
const viewSchemas = {
|
|
2032
|
-
image: mediaViewSchema,
|
|
2033
|
-
video: mediaViewSchema,
|
|
2034
|
-
audio: audioViewSchema,
|
|
2035
|
-
file: fileViewSchema,
|
|
2036
|
-
task: taskViewSchema,
|
|
2037
|
-
text: emptyViewSchema,
|
|
2038
|
-
geo: emptyViewSchema,
|
|
2039
|
-
draw: emptyViewSchema,
|
|
2040
|
-
arrow: emptyViewSchema,
|
|
2041
|
-
frame: emptyViewSchema
|
|
2042
|
-
};
|
|
2043
|
-
const nodeEnvelopeSchema = z.object({
|
|
2044
|
-
nodeId: z.string().min(1).max(160),
|
|
2045
|
-
type: z.string().min(1).max(40),
|
|
2046
|
-
parentId: z.string().min(1).max(160).nullable(),
|
|
2047
|
-
orderKey: z.string().max(4096).nullable(),
|
|
2048
|
-
x: z.number().finite(),
|
|
2049
|
-
y: z.number().finite(),
|
|
2050
|
-
width: z.number().finite().positive(),
|
|
2051
|
-
height: z.number().finite().positive(),
|
|
2052
|
-
rotation: z.number().finite(),
|
|
2053
|
-
refKind: z.string().max(40).nullable(),
|
|
2054
|
-
refPath: z.string().max(4096).nullable(),
|
|
2055
|
-
refUrl: z.string().max(4096).nullable(),
|
|
2056
|
-
view: z.record(z.string(), z.unknown()),
|
|
2057
|
-
style: z.record(z.string(), z.unknown()),
|
|
2058
|
-
data: z.record(z.string(), z.unknown())
|
|
2059
|
-
}).strict();
|
|
2060
|
-
function jsonSchema(schema) {
|
|
2061
|
-
return z.toJSONSchema(schema);
|
|
2062
|
-
}
|
|
2063
|
-
const BOARD_NODE_CONTRACT = {
|
|
2064
|
-
types: BOARD_NATIVE_NODE_TYPES,
|
|
2065
|
-
colors: BOARD_COLOR_IDS,
|
|
2066
|
-
geos: BOARD_GEO_KINDS,
|
|
2067
|
-
coordinates: {
|
|
2068
|
-
frame: "world",
|
|
2069
|
-
drawPoints: "frame-local",
|
|
2070
|
-
arrowEndpoints: "world"
|
|
2071
|
-
},
|
|
2072
|
-
references: {
|
|
2073
|
-
nodeTypes: [
|
|
2074
|
-
"image",
|
|
2075
|
-
"video",
|
|
2076
|
-
"audio",
|
|
2077
|
-
"file"
|
|
2078
|
-
],
|
|
2079
|
-
kind: "space_file",
|
|
2080
|
-
pathField: "refPath"
|
|
2081
|
-
},
|
|
2082
|
-
schemas: {
|
|
2083
|
-
envelope: jsonSchema(nodeEnvelopeSchema),
|
|
2084
|
-
data: Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])),
|
|
2085
|
-
view: Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]))
|
|
2086
|
-
}
|
|
2087
|
-
};
|
|
2088
|
-
function issueDiagnostic(issue, path) {
|
|
2089
|
-
const fullPath = [path, ...issue.path].join(".");
|
|
2090
|
-
const values = "values" in issue && Array.isArray(issue.values) ? issue.values.filter((value) => typeof value === "string") : void 0;
|
|
2091
|
-
return {
|
|
2092
|
-
severity: "error",
|
|
2093
|
-
code: "INVALID_BOARD_NODE",
|
|
2094
|
-
message: `${fullPath}: ${issue.message}`,
|
|
2095
|
-
path: fullPath,
|
|
2096
|
-
...values?.length ? { allowedValues: values } : {}
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
function drawGeometryDiagnostic(node, data, path) {
|
|
2100
|
-
let minX = Number.POSITIVE_INFINITY;
|
|
2101
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
2102
|
-
let maxX = Number.NEGATIVE_INFINITY;
|
|
2103
|
-
let maxY = Number.NEGATIVE_INFINITY;
|
|
2104
|
-
for (const point of data.points) {
|
|
2105
|
-
const radius = Math.max(.5, data.size / 2 * (.5 + point.p));
|
|
2106
|
-
minX = Math.min(minX, point.x - radius);
|
|
2107
|
-
minY = Math.min(minY, point.y - radius);
|
|
2108
|
-
maxX = Math.max(maxX, point.x + radius);
|
|
2109
|
-
maxY = Math.max(maxY, point.y + radius);
|
|
2110
|
-
}
|
|
2111
|
-
const width = Math.max(1, maxX - minX);
|
|
2112
|
-
const height = Math.max(1, maxY - minY);
|
|
2113
|
-
const tolerance = Math.max(.01, node.width * 1e-6, node.height * 1e-6);
|
|
2114
|
-
if (Math.abs(minX) <= tolerance && Math.abs(minY) <= tolerance && Math.abs(width - node.width) <= tolerance && Math.abs(height - node.height) <= tolerance) return null;
|
|
2115
|
-
return {
|
|
2116
|
-
severity: "error",
|
|
2117
|
-
code: "INVALID_BOARD_GEOMETRY",
|
|
2118
|
-
message: `${path}.data.points must use frame-local coordinates and match the node frame`,
|
|
2119
|
-
path: `${path}.data.points`,
|
|
2120
|
-
expected: "frame-local points with bounds matching width and height",
|
|
2121
|
-
coordinateSpace: "frame-local"
|
|
2122
|
-
};
|
|
2123
|
-
}
|
|
2124
|
-
function validateBoardNodeInput(node, path = "node") {
|
|
2125
|
-
const envelopeResult = nodeEnvelopeSchema.safeParse(node);
|
|
2126
|
-
if (!envelopeResult.success) return envelopeResult.error.issues.map((issue) => issueDiagnostic(issue, path));
|
|
2127
|
-
if (typeof node.type !== "string" || !BOARD_NATIVE_NODE_TYPES.includes(node.type)) return [{
|
|
2128
|
-
severity: "error",
|
|
2129
|
-
code: "INVALID_BOARD_NODE",
|
|
2130
|
-
message: `${path}.type is not supported`,
|
|
2131
|
-
path: `${path}.type`,
|
|
2132
|
-
expected: "BoardNativeNodeType",
|
|
2133
|
-
received: node.type,
|
|
2134
|
-
allowedValues: BOARD_NATIVE_NODE_TYPES
|
|
2135
|
-
}];
|
|
2136
|
-
const type = node.type;
|
|
2137
|
-
const dataResult = dataSchemas[type].safeParse(node.data ?? {});
|
|
2138
|
-
if (!dataResult.success) return dataResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.data`));
|
|
2139
|
-
const viewResult = viewSchemas[type].safeParse(node.view ?? {});
|
|
2140
|
-
if (!viewResult.success) return viewResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.view`));
|
|
2141
|
-
if (type === "image" || type === "video" || type === "audio" || type === "file") {
|
|
2142
|
-
if (node.refKind !== "space_file" || typeof node.refPath !== "string" || !node.refPath) return [{
|
|
2143
|
-
severity: "error",
|
|
2144
|
-
code: "INVALID_BOARD_NODE",
|
|
2145
|
-
message: `${path} requires a space file reference`,
|
|
2146
|
-
path: `${path}.refPath`,
|
|
2147
|
-
expected: "non-empty refPath with refKind space_file"
|
|
2148
|
-
}];
|
|
2149
|
-
}
|
|
2150
|
-
if (type === "draw") {
|
|
2151
|
-
const diagnostic = drawGeometryDiagnostic(node, dataResult.data, path);
|
|
2152
|
-
return diagnostic ? [diagnostic] : [];
|
|
2153
|
-
}
|
|
2154
|
-
if (type === "arrow") {
|
|
2155
|
-
const data = dataResult.data;
|
|
2156
|
-
const inside = (point) => point.x >= node.x && point.x <= node.x + node.width && point.y >= node.y && point.y <= node.y + node.height;
|
|
2157
|
-
if (!inside(data.start) || !inside(data.end)) return [{
|
|
2158
|
-
severity: "error",
|
|
2159
|
-
code: "INVALID_BOARD_GEOMETRY",
|
|
2160
|
-
message: `${path}.data endpoints must be covered by the node frame`,
|
|
2161
|
-
path: `${path}.data`,
|
|
2162
|
-
expected: "world-space endpoints inside the node frame",
|
|
2163
|
-
coordinateSpace: "world"
|
|
2164
|
-
}];
|
|
2165
|
-
}
|
|
2166
|
-
return [];
|
|
2167
|
-
}
|
|
2168
|
-
const BoardContentKindSchema = z.enum([
|
|
2169
|
-
"text",
|
|
2170
|
-
"image",
|
|
2171
|
-
"video",
|
|
2172
|
-
"audio",
|
|
2173
|
-
"file",
|
|
2174
|
-
"json",
|
|
2175
|
-
"collection"
|
|
2176
|
-
]);
|
|
2177
|
-
const BoardPortSchema = z.object({
|
|
2178
|
-
id: z.string().min(1).max(120),
|
|
2179
|
-
kind: BoardContentKindSchema,
|
|
2180
|
-
role: z.string().min(1).max(80).optional(),
|
|
2181
|
-
required: z.boolean().optional(),
|
|
2182
|
-
multiple: z.boolean().optional(),
|
|
2183
|
-
maxItems: z.number().int().positive().optional()
|
|
2184
|
-
}).strict();
|
|
2185
|
-
z.object({
|
|
2186
|
-
inputs: z.array(BoardPortSchema),
|
|
2187
|
-
outputs: z.array(BoardPortSchema)
|
|
2188
|
-
}).strict();
|
|
2189
|
-
//#endregion
|
|
2190
|
-
//#region ../protocol/dist/realtime/board-awareness.js
|
|
2191
|
-
const idSchema = z.string().min(1).max(160);
|
|
2192
|
-
const finiteSchema = z.number().finite();
|
|
2193
|
-
const BoardAwarenessPointSchema = z.object({
|
|
2194
|
-
x: finiteSchema,
|
|
2195
|
-
y: finiteSchema
|
|
2196
|
-
});
|
|
2197
|
-
const BoardAwarenessDrawPointSchema = BoardAwarenessPointSchema.extend({ p: finiteSchema.min(0).max(1) });
|
|
2198
|
-
const BoardAwarenessFrameSchema = z.object({
|
|
2199
|
-
x: finiteSchema,
|
|
2200
|
-
y: finiteSchema,
|
|
2201
|
-
width: finiteSchema.positive(),
|
|
2202
|
-
height: finiteSchema.positive(),
|
|
2203
|
-
rotation: finiteSchema
|
|
2204
|
-
});
|
|
2205
|
-
const BoardAwarenessNodePreviewSchema = z.object({
|
|
2206
|
-
nodeId: idSchema,
|
|
2207
|
-
frame: BoardAwarenessFrameSchema,
|
|
2208
|
-
/** Live endpoints of a free arrow being dragged. */
|
|
2209
|
-
arrow: z.object({
|
|
2210
|
-
start: BoardAwarenessPointSchema,
|
|
2211
|
-
end: BoardAwarenessPointSchema,
|
|
2212
|
-
bend: finiteSchema
|
|
2213
|
-
}).optional()
|
|
2214
|
-
});
|
|
2215
|
-
const BoardAwarenessStateUpdateSchema = z.object({
|
|
2216
|
-
type: z.literal("state"),
|
|
2217
|
-
client: z.object({ formFactor: z.enum(["desktop", "mobile"]) }).optional(),
|
|
2218
|
-
cursor: BoardAwarenessPointSchema.extend({ pointerType: z.enum([
|
|
2219
|
-
"mouse",
|
|
2220
|
-
"pen",
|
|
2221
|
-
"touch"
|
|
2222
|
-
]) }).nullable(),
|
|
2223
|
-
tool: z.string().min(1).max(40),
|
|
2224
|
-
selection: z.object({
|
|
2225
|
-
ids: z.array(idSchema).max(64),
|
|
2226
|
-
count: z.number().int().nonnegative().max(5e4),
|
|
2227
|
-
bounds: BoardAwarenessFrameSchema.nullable()
|
|
2228
|
-
}),
|
|
2229
|
-
editingId: idSchema.nullable()
|
|
2230
|
-
});
|
|
2231
|
-
const BoardAwarenessGestureSchema = z.discriminatedUnion("kind", [
|
|
2232
|
-
z.object({
|
|
2233
|
-
kind: z.literal("draw"),
|
|
2234
|
-
id: idSchema,
|
|
2235
|
-
nodeId: idSchema,
|
|
2236
|
-
color: z.string().min(1).max(64),
|
|
2237
|
-
size: finiteSchema.positive().max(256),
|
|
2238
|
-
from: z.number().int().nonnegative().max(1e5),
|
|
2239
|
-
points: z.array(BoardAwarenessDrawPointSchema).min(1).max(64)
|
|
2240
|
-
}),
|
|
2241
|
-
z.object({
|
|
2242
|
-
kind: z.literal("arrow"),
|
|
2243
|
-
id: idSchema,
|
|
2244
|
-
nodeId: idSchema,
|
|
2245
|
-
start: BoardAwarenessPointSchema,
|
|
2246
|
-
current: BoardAwarenessPointSchema,
|
|
2247
|
-
color: z.string().min(1).max(64),
|
|
2248
|
-
size: finiteSchema.positive().max(256).default(BOARD_ARROW_STROKE_SIZE)
|
|
2249
|
-
}),
|
|
1241
|
+
type: z.literal("connection.create"),
|
|
1242
|
+
connection: BoardConnectionInputSchema
|
|
1243
|
+
}).strict(),
|
|
2250
1244
|
z.object({
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
start: BoardAwarenessPointSchema,
|
|
2256
|
-
current: BoardAwarenessPointSchema,
|
|
2257
|
-
color: z.string().min(1).max(64),
|
|
2258
|
-
geo: z.string().min(1).max(40)
|
|
2259
|
-
}),
|
|
1245
|
+
type: z.literal("connection.patch"),
|
|
1246
|
+
connectionId: idSchema,
|
|
1247
|
+
patch: BoardConnectionPatchSchema
|
|
1248
|
+
}).strict(),
|
|
2260
1249
|
z.object({
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
targetNodeId: idSchema.nullable(),
|
|
2265
|
-
current: BoardAwarenessPointSchema,
|
|
2266
|
-
color: z.string().min(1).max(64),
|
|
2267
|
-
size: finiteSchema.positive().max(256).default(BOARD_CONNECTION_STROKE_SIZE)
|
|
2268
|
-
}),
|
|
1250
|
+
type: z.literal("connection.delete"),
|
|
1251
|
+
connectionId: idSchema
|
|
1252
|
+
}).strict(),
|
|
2269
1253
|
z.object({
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
"translate",
|
|
2274
|
-
"resize",
|
|
2275
|
-
"rotate",
|
|
2276
|
-
"arrow",
|
|
2277
|
-
"connection"
|
|
2278
|
-
]),
|
|
2279
|
-
nodes: z.array(BoardAwarenessNodePreviewSchema).max(64),
|
|
2280
|
-
bounds: BoardAwarenessFrameSchema.nullable()
|
|
2281
|
-
})
|
|
2282
|
-
]);
|
|
2283
|
-
const BoardAwarenessUpdateSchema = z.discriminatedUnion("type", [
|
|
2284
|
-
BoardAwarenessStateUpdateSchema,
|
|
1254
|
+
type: z.literal("effect.apply"),
|
|
1255
|
+
effect: BoardEffectCommandInputSchema
|
|
1256
|
+
}).strict(),
|
|
2285
1257
|
z.object({
|
|
2286
|
-
type: z.literal("
|
|
2287
|
-
|
|
2288
|
-
}),
|
|
1258
|
+
type: z.literal("effect.delete"),
|
|
1259
|
+
effectId: idSchema
|
|
1260
|
+
}).strict(),
|
|
2289
1261
|
z.object({
|
|
2290
|
-
type: z.literal("
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
}),
|
|
1262
|
+
type: z.literal("composition.apply"),
|
|
1263
|
+
composition: BoardCompositionCommandInputSchema
|
|
1264
|
+
}).strict(),
|
|
2294
1265
|
z.object({
|
|
2295
|
-
type: z.literal("
|
|
2296
|
-
|
|
2297
|
-
})
|
|
2298
|
-
]);
|
|
2299
|
-
const BoardAwarenessClientPayloadSchema = z.object({
|
|
2300
|
-
spaceId: z.string().uuid(),
|
|
2301
|
-
boardId: z.string().uuid(),
|
|
2302
|
-
seq: z.number().int().nonnegative(),
|
|
2303
|
-
update: BoardAwarenessUpdateSchema
|
|
2304
|
-
});
|
|
2305
|
-
//#endregion
|
|
2306
|
-
//#region ../protocol/dist/public-identifiers.js
|
|
2307
|
-
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
2308
|
-
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
2309
|
-
/**
|
|
2310
|
-
* Platform-owned path segments that must not be newly assigned to public
|
|
2311
|
-
* identities. Existing stored values remain readable through parse helpers.
|
|
2312
|
-
*/
|
|
2313
|
-
const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
2314
|
-
"admin",
|
|
2315
|
-
"api",
|
|
2316
|
-
"assets",
|
|
2317
|
-
"auth",
|
|
2318
|
-
"callback",
|
|
2319
|
-
"changelog",
|
|
2320
|
-
"docs",
|
|
2321
|
-
"explore",
|
|
2322
|
-
"invite",
|
|
2323
|
-
"landing",
|
|
2324
|
-
"login",
|
|
2325
|
-
"logout",
|
|
2326
|
-
"new",
|
|
2327
|
-
"org",
|
|
2328
|
-
"pricing",
|
|
2329
|
-
"pwa",
|
|
2330
|
-
"referrals",
|
|
2331
|
-
"sessions",
|
|
2332
|
-
"settings",
|
|
2333
|
-
"spaces",
|
|
2334
|
-
"static",
|
|
2335
|
-
"teams",
|
|
2336
|
-
"trending",
|
|
2337
|
-
"u",
|
|
2338
|
-
"user",
|
|
2339
|
-
"users",
|
|
2340
|
-
"work-auth"
|
|
1266
|
+
type: z.literal("composition.delete"),
|
|
1267
|
+
compositionId: idSchema
|
|
1268
|
+
}).strict()
|
|
2341
1269
|
]);
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
1270
|
+
const BoardSemanticMutationSchema = z.object({
|
|
1271
|
+
mutationId: idSchema,
|
|
1272
|
+
baseVersion: z.number().int().nonnegative(),
|
|
1273
|
+
clientId: idSchema.optional(),
|
|
1274
|
+
undoGroupId: idSchema.optional(),
|
|
1275
|
+
/** Validate (including server-side reference checks) without writing. */
|
|
1276
|
+
dryRun: z.boolean().default(false),
|
|
1277
|
+
commands: z.array(BoardSemanticCommandSchema).min(1).max(5e4)
|
|
1278
|
+
}).strict();
|
|
1279
|
+
z.object({
|
|
1280
|
+
include: z.array(z.enum([
|
|
1281
|
+
"items",
|
|
1282
|
+
"connections",
|
|
1283
|
+
"effects",
|
|
1284
|
+
"compositions",
|
|
1285
|
+
"playback"
|
|
1286
|
+
])).optional(),
|
|
1287
|
+
itemIds: z.array(idSchema).max(5e4).optional(),
|
|
1288
|
+
connectionIds: z.array(idSchema).max(5e4).optional(),
|
|
1289
|
+
effectIds: z.array(idSchema).max(5e4).optional(),
|
|
1290
|
+
compositionIds: z.array(idSchema).max(5e4).optional(),
|
|
1291
|
+
viewport: z.object({
|
|
1292
|
+
x: finiteSchema,
|
|
1293
|
+
y: finiteSchema,
|
|
1294
|
+
width: finiteSchema.positive(),
|
|
1295
|
+
height: finiteSchema.positive()
|
|
1296
|
+
}).optional()
|
|
1297
|
+
}).strict();
|
|
2354
1298
|
//#endregion
|
|
2355
|
-
//#region ../protocol/dist/
|
|
1299
|
+
//#region ../protocol/dist/desktop-command.js
|
|
2356
1300
|
/**
|
|
2357
|
-
* Lets an agent drive the Cohub
|
|
1301
|
+
* Lets an agent drive the Cohub desktop that originated the app. Routing comes
|
|
2358
1302
|
* from request provenance, never a caller-supplied target, so a command only
|
|
2359
1303
|
* reaches the actor's own instances.
|
|
1304
|
+
*
|
|
1305
|
+
* The canonical command is `desktop.open`. The legacy `preview.show` shape is
|
|
1306
|
+
* accepted on input and normalized, so older clients keep working, but commands
|
|
1307
|
+
* are always stored and dispatched in the canonical form.
|
|
2360
1308
|
*/
|
|
2361
|
-
const
|
|
1309
|
+
const DESKTOP_COMMAND_VERSION = 1;
|
|
2362
1310
|
/** Persisted and broadcast, so every field is capped; MAX_BYTES bounds the whole. */
|
|
2363
|
-
const
|
|
2364
|
-
const
|
|
2365
|
-
const
|
|
2366
|
-
const
|
|
2367
|
-
const
|
|
2368
|
-
const
|
|
1311
|
+
const DESKTOP_COMMAND_PAYLOAD_MAX_BYTES = 32 * 1024;
|
|
1312
|
+
const DESKTOP_COMMAND_MAX_BYTES = 40 * 1024;
|
|
1313
|
+
const DESKTOP_COMMAND_LAUNCH_MAX_LENGTH = 2048;
|
|
1314
|
+
const DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS = 600 * 1e3;
|
|
1315
|
+
const DESKTOP_COMMAND_MAX_TIMEOUT_MS = 720 * 60 * 1e3;
|
|
1316
|
+
const DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS = 600;
|
|
2369
1317
|
/** Keeps pending commands reportable for the full wait window plus settlement grace. */
|
|
2370
|
-
const
|
|
2371
|
-
const
|
|
2372
|
-
const
|
|
1318
|
+
const DESKTOP_COMMAND_PENDING_TTL_SECONDS = 43800;
|
|
1319
|
+
const DESKTOP_COMMAND_TERMINAL_TTL_SECONDS = 1800;
|
|
1320
|
+
const DESKTOP_COMMAND_TERMINAL_STATUSES = [
|
|
2373
1321
|
"applied",
|
|
2374
1322
|
"no_active_client",
|
|
2375
|
-
"
|
|
1323
|
+
"desktop_host_unavailable",
|
|
2376
1324
|
"rejected",
|
|
2377
1325
|
"unsupported",
|
|
2378
1326
|
"timeout"
|
|
2379
1327
|
];
|
|
2380
|
-
const
|
|
1328
|
+
const isTerminalDesktopCommandStatus = (status) => DESKTOP_COMMAND_TERMINAL_STATUSES.includes(status);
|
|
2381
1329
|
const METHOD_RE = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
2382
|
-
const
|
|
2383
|
-
const
|
|
2384
|
-
const
|
|
1330
|
+
const isDesktopCallMethod = (value) => typeof value === "string" && METHOD_RE.test(value);
|
|
1331
|
+
const DESKTOP_COMMAND_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
1332
|
+
const parseDesktopCommandId = (value) => {
|
|
2385
1333
|
if (typeof value !== "string") return null;
|
|
2386
1334
|
const trimmed = value.trim();
|
|
2387
|
-
return
|
|
1335
|
+
return DESKTOP_COMMAND_ID_RE.test(trimmed) ? trimmed : null;
|
|
2388
1336
|
};
|
|
2389
|
-
const isRecord$
|
|
1337
|
+
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2390
1338
|
const asTrimmed = (value) => {
|
|
2391
1339
|
if (typeof value !== "string") return null;
|
|
2392
1340
|
const trimmed = value.trim();
|
|
2393
1341
|
return trimmed ? trimmed : null;
|
|
2394
1342
|
};
|
|
2395
1343
|
const parseLaunch = (value) => {
|
|
2396
|
-
if (!isRecord$
|
|
1344
|
+
if (!isRecord$1(value)) return void 0;
|
|
2397
1345
|
const search = asTrimmed(value.search);
|
|
2398
1346
|
const hash = asTrimmed(value.hash);
|
|
2399
1347
|
if (!search && !hash) return void 0;
|
|
@@ -2402,7 +1350,7 @@ const parseLaunch = (value) => {
|
|
|
2402
1350
|
...hash ? { hash: hash.startsWith("#") ? hash : `#${hash}` } : {}
|
|
2403
1351
|
};
|
|
2404
1352
|
};
|
|
2405
|
-
const
|
|
1353
|
+
const measureDesktopCommandPayload = (value) => {
|
|
2406
1354
|
if (value === void 0) return 0;
|
|
2407
1355
|
try {
|
|
2408
1356
|
return new TextEncoder().encode(JSON.stringify(value) ?? "").length;
|
|
@@ -2410,118 +1358,131 @@ const measureUiCommandPayload = (value) => {
|
|
|
2410
1358
|
return null;
|
|
2411
1359
|
}
|
|
2412
1360
|
};
|
|
2413
|
-
|
|
2414
|
-
|
|
1361
|
+
/**
|
|
1362
|
+
* Parse a desktop command from untrusted input.
|
|
1363
|
+
*
|
|
1364
|
+
* Accepts both the canonical `desktop.open` shape (`target` / `call`) and the
|
|
1365
|
+
* legacy `preview.show` shape (`preview` / `request`); the result is always
|
|
1366
|
+
* canonical so every later stage handles a single form.
|
|
1367
|
+
*/
|
|
1368
|
+
const parseDesktopCommand = (input) => {
|
|
1369
|
+
if (!isRecord$1(input)) return {
|
|
2415
1370
|
command: null,
|
|
2416
1371
|
error: "command must be an object"
|
|
2417
1372
|
};
|
|
2418
|
-
|
|
1373
|
+
const legacy = input.type === "preview.show";
|
|
1374
|
+
if (input.type !== "desktop.open" && !legacy) return {
|
|
2419
1375
|
command: null,
|
|
2420
|
-
error: "command.type must be one of:
|
|
1376
|
+
error: "command.type must be one of: desktop.open"
|
|
2421
1377
|
};
|
|
2422
|
-
const
|
|
2423
|
-
if (!
|
|
1378
|
+
const target = isRecord$1(input.target) ? input.target : isRecord$1(input.preview) ? input.preview : null;
|
|
1379
|
+
if (!target) return {
|
|
2424
1380
|
command: null,
|
|
2425
|
-
error: "command.
|
|
1381
|
+
error: "command.target is required"
|
|
2426
1382
|
};
|
|
2427
|
-
|
|
1383
|
+
const kind = target.kind === "app" || legacy && target.kind === "work" ? "app" : target.kind === "file" ? "file" : null;
|
|
1384
|
+
if (!kind) return {
|
|
2428
1385
|
command: null,
|
|
2429
|
-
error: "command.
|
|
1386
|
+
error: "command.target.kind must be one of: app, file"
|
|
2430
1387
|
};
|
|
2431
|
-
if (
|
|
2432
|
-
const path = asTrimmed(
|
|
1388
|
+
if (kind === "file") {
|
|
1389
|
+
const path = asTrimmed(target.path);
|
|
2433
1390
|
if (!path) return {
|
|
2434
1391
|
command: null,
|
|
2435
|
-
error: "command.
|
|
1392
|
+
error: "command.target.path is required"
|
|
2436
1393
|
};
|
|
2437
1394
|
if (path.length > 2048 || path.startsWith("/") || path.includes("\0") || path.includes("\\") || path.split("/").some((segment) => segment === "..")) return {
|
|
2438
1395
|
command: null,
|
|
2439
|
-
error: "command.
|
|
1396
|
+
error: "command.target.path must be a relative Space file path"
|
|
2440
1397
|
};
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
kind: "file",
|
|
2445
|
-
path
|
|
2446
|
-
}
|
|
1398
|
+
if (input.call !== void 0 && input.call !== null) return {
|
|
1399
|
+
command: null,
|
|
1400
|
+
error: "command.call is only supported for app targets"
|
|
2447
1401
|
};
|
|
2448
|
-
if (input.request !== void 0 && input.request !== null) return {
|
|
1402
|
+
if (legacy && input.request !== void 0 && input.request !== null) return {
|
|
2449
1403
|
command: null,
|
|
2450
|
-
error: "command.
|
|
1404
|
+
error: "command.call is only supported for app targets"
|
|
2451
1405
|
};
|
|
2452
1406
|
return {
|
|
2453
|
-
command
|
|
1407
|
+
command: {
|
|
1408
|
+
type: "desktop.open",
|
|
1409
|
+
target: {
|
|
1410
|
+
kind: "file",
|
|
1411
|
+
path
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
2454
1414
|
error: null
|
|
2455
1415
|
};
|
|
2456
1416
|
}
|
|
2457
|
-
const
|
|
2458
|
-
if (!
|
|
1417
|
+
const appId = asTrimmed(target.appId ?? target.workId);
|
|
1418
|
+
if (!appId) return {
|
|
2459
1419
|
command: null,
|
|
2460
|
-
error: "command.
|
|
1420
|
+
error: "command.target.appId is required"
|
|
2461
1421
|
};
|
|
2462
|
-
if (!isUuid(
|
|
1422
|
+
if (!isUuid(appId)) return {
|
|
2463
1423
|
command: null,
|
|
2464
|
-
error: "command.
|
|
1424
|
+
error: "command.target.appId must be an App id"
|
|
2465
1425
|
};
|
|
2466
|
-
const label = asTrimmed(
|
|
1426
|
+
const label = asTrimmed(target.label);
|
|
2467
1427
|
if (label && label.length > 200) return {
|
|
2468
1428
|
command: null,
|
|
2469
|
-
error: `command.
|
|
1429
|
+
error: `command.target.label exceeds 200 characters`
|
|
2470
1430
|
};
|
|
2471
|
-
const launch = parseLaunch(
|
|
1431
|
+
const launch = parseLaunch(target.launch);
|
|
2472
1432
|
if (launch) {
|
|
2473
1433
|
for (const [field, value] of [["search", launch.search], ["hash", launch.hash]]) if (value && value.length > 2048) return {
|
|
2474
1434
|
command: null,
|
|
2475
|
-
error: `command.
|
|
1435
|
+
error: `command.target.launch.${field} exceeds ${DESKTOP_COMMAND_LAUNCH_MAX_LENGTH} characters`
|
|
2476
1436
|
};
|
|
2477
1437
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
1438
|
+
const callSource = input.call !== void 0 ? input.call : input.request;
|
|
1439
|
+
let call;
|
|
1440
|
+
if (callSource !== void 0 && callSource !== null) {
|
|
1441
|
+
if (!isRecord$1(callSource)) return {
|
|
2481
1442
|
command: null,
|
|
2482
|
-
error: "command.
|
|
1443
|
+
error: "command.call must be an object"
|
|
2483
1444
|
};
|
|
2484
|
-
const method = asTrimmed(
|
|
1445
|
+
const method = asTrimmed(callSource.method);
|
|
2485
1446
|
if (!method) return {
|
|
2486
1447
|
command: null,
|
|
2487
|
-
error: "command.
|
|
1448
|
+
error: "command.call.method is required"
|
|
2488
1449
|
};
|
|
2489
|
-
if (!
|
|
1450
|
+
if (!isDesktopCallMethod(method)) return {
|
|
2490
1451
|
command: null,
|
|
2491
|
-
error: "command.
|
|
1452
|
+
error: "command.call.method has an unsupported format"
|
|
2492
1453
|
};
|
|
2493
|
-
const size =
|
|
1454
|
+
const size = measureDesktopCommandPayload(callSource.input);
|
|
2494
1455
|
if (size === null) return {
|
|
2495
1456
|
command: null,
|
|
2496
|
-
error: "command.
|
|
1457
|
+
error: "command.call.input must be JSON-serializable"
|
|
2497
1458
|
};
|
|
2498
1459
|
if (size > 32768) return {
|
|
2499
1460
|
command: null,
|
|
2500
|
-
error: `command.
|
|
1461
|
+
error: `command.call.input exceeds ${DESKTOP_COMMAND_PAYLOAD_MAX_BYTES} bytes`
|
|
2501
1462
|
};
|
|
2502
|
-
|
|
1463
|
+
call = {
|
|
2503
1464
|
method,
|
|
2504
|
-
...
|
|
1465
|
+
...callSource.input === void 0 ? {} : { input: callSource.input }
|
|
2505
1466
|
};
|
|
2506
1467
|
}
|
|
2507
1468
|
const command = {
|
|
2508
|
-
type: "
|
|
2509
|
-
|
|
2510
|
-
kind: "
|
|
2511
|
-
|
|
1469
|
+
type: "desktop.open",
|
|
1470
|
+
target: {
|
|
1471
|
+
kind: "app",
|
|
1472
|
+
appId,
|
|
2512
1473
|
...label ? { label } : {},
|
|
2513
1474
|
...launch ? { launch } : {}
|
|
2514
1475
|
},
|
|
2515
|
-
...
|
|
1476
|
+
...call ? { call } : {}
|
|
2516
1477
|
};
|
|
2517
|
-
const totalSize =
|
|
1478
|
+
const totalSize = measureDesktopCommandPayload(command);
|
|
2518
1479
|
if (totalSize === null) return {
|
|
2519
1480
|
command: null,
|
|
2520
1481
|
error: "command must be JSON-serializable"
|
|
2521
1482
|
};
|
|
2522
1483
|
if (totalSize > 40960) return {
|
|
2523
1484
|
command: null,
|
|
2524
|
-
error: `command exceeds ${
|
|
1485
|
+
error: `command exceeds ${DESKTOP_COMMAND_MAX_BYTES} bytes`
|
|
2525
1486
|
};
|
|
2526
1487
|
return {
|
|
2527
1488
|
command,
|
|
@@ -2529,123 +1490,6 @@ const parseUiCommand = (input) => {
|
|
|
2529
1490
|
};
|
|
2530
1491
|
};
|
|
2531
1492
|
//#endregion
|
|
2532
|
-
//#region ../protocol/dist/work-surface.js
|
|
2533
|
-
const WORK_SURFACE_PROTOCOL = "cohub.surface";
|
|
2534
|
-
const WORK_SURFACE_READY_TIMEOUT_MS = 1e4;
|
|
2535
|
-
const WORK_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
|
|
2536
|
-
const WORK_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
|
|
2537
|
-
const WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
|
|
2538
|
-
const WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
|
|
2539
|
-
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2540
|
-
const isSurfaceEnvelope = (value) => isRecord$1(value) && value.protocol === "cohub.surface" && value.version === 1;
|
|
2541
|
-
const parseWorkSurfaceReady = (value) => {
|
|
2542
|
-
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
2543
|
-
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
2544
|
-
return {
|
|
2545
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2546
|
-
version: 1,
|
|
2547
|
-
type: "ready",
|
|
2548
|
-
methods
|
|
2549
|
-
};
|
|
2550
|
-
};
|
|
2551
|
-
const parseWorkSurfaceResponse = (value) => {
|
|
2552
|
-
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
2553
|
-
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
2554
|
-
const error = isRecord$1(value.error) ? {
|
|
2555
|
-
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
2556
|
-
message: typeof value.error.message === "string" ? value.error.message : "Work surface call failed"
|
|
2557
|
-
} : void 0;
|
|
2558
|
-
return {
|
|
2559
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2560
|
-
version: 1,
|
|
2561
|
-
type: "response",
|
|
2562
|
-
requestId: value.requestId,
|
|
2563
|
-
ok: value.ok === true,
|
|
2564
|
-
...error ? { error } : {}
|
|
2565
|
-
};
|
|
2566
|
-
};
|
|
2567
|
-
const parseComposerChipKey = (value) => {
|
|
2568
|
-
if (typeof value !== "string") return null;
|
|
2569
|
-
const key = value.trim();
|
|
2570
|
-
if (!key || key.length > 80) return null;
|
|
2571
|
-
return key;
|
|
2572
|
-
};
|
|
2573
|
-
const parseWorkComposerChipSet = (value) => {
|
|
2574
|
-
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$1(value.chip)) return null;
|
|
2575
|
-
const key = parseComposerChipKey(value.chip.key);
|
|
2576
|
-
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
2577
|
-
const label = value.chip.label.trim();
|
|
2578
|
-
if (!label || label.length > 120) return null;
|
|
2579
|
-
if (!value.chip.content.trim()) return null;
|
|
2580
|
-
if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
|
|
2581
|
-
return {
|
|
2582
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2583
|
-
version: 1,
|
|
2584
|
-
type: "composer.chip.set",
|
|
2585
|
-
chip: {
|
|
2586
|
-
key,
|
|
2587
|
-
label,
|
|
2588
|
-
content: value.chip.content
|
|
2589
|
-
}
|
|
2590
|
-
};
|
|
2591
|
-
};
|
|
2592
|
-
const parseWorkComposerChipClear = (value) => {
|
|
2593
|
-
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
|
|
2594
|
-
const key = parseComposerChipKey(value.key);
|
|
2595
|
-
return key ? {
|
|
2596
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2597
|
-
version: 1,
|
|
2598
|
-
type: "composer.chip.clear",
|
|
2599
|
-
key
|
|
2600
|
-
} : null;
|
|
2601
|
-
};
|
|
2602
|
-
const parseWorkSurfaceRequest = (value) => {
|
|
2603
|
-
if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
|
|
2604
|
-
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
2605
|
-
if (typeof value.method !== "string" || !value.method) return null;
|
|
2606
|
-
const commandId = parseUiCommandId(value.commandId);
|
|
2607
|
-
if (!commandId) return null;
|
|
2608
|
-
return {
|
|
2609
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2610
|
-
version: 1,
|
|
2611
|
-
type: "request",
|
|
2612
|
-
requestId: value.requestId,
|
|
2613
|
-
method: value.method,
|
|
2614
|
-
...value.input === void 0 ? {} : { input: value.input },
|
|
2615
|
-
commandId
|
|
2616
|
-
};
|
|
2617
|
-
};
|
|
2618
|
-
const buildWorkSurfaceReady = (methods) => ({
|
|
2619
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2620
|
-
version: 1,
|
|
2621
|
-
type: "ready",
|
|
2622
|
-
methods: [...methods]
|
|
2623
|
-
});
|
|
2624
|
-
const buildWorkSurfaceRequest = (input) => ({
|
|
2625
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2626
|
-
version: 1,
|
|
2627
|
-
type: "request",
|
|
2628
|
-
...input
|
|
2629
|
-
});
|
|
2630
|
-
const buildWorkSurfaceResponse = (input) => ({
|
|
2631
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2632
|
-
version: 1,
|
|
2633
|
-
type: "response",
|
|
2634
|
-
...input
|
|
2635
|
-
});
|
|
2636
|
-
const buildWorkComposerChipSet = (chip) => ({
|
|
2637
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2638
|
-
version: 1,
|
|
2639
|
-
type: "composer.chip.set",
|
|
2640
|
-
chip
|
|
2641
|
-
});
|
|
2642
|
-
const buildWorkComposerChipClear = (key) => ({
|
|
2643
|
-
protocol: WORK_SURFACE_PROTOCOL,
|
|
2644
|
-
version: 1,
|
|
2645
|
-
type: "composer.chip.clear",
|
|
2646
|
-
key
|
|
2647
|
-
});
|
|
2648
|
-
//#endregion
|
|
2649
1493
|
//#region src/realtime.ts
|
|
2650
1494
|
function ensureRealtimeConnected(websocketClient) {
|
|
2651
1495
|
if (websocketClient.state === "open" || websocketClient.state === "connecting" || websocketClient.state === "reconnecting") return;
|
|
@@ -3664,26 +2508,6 @@ const getFilenameFromContentDisposition = (value) => {
|
|
|
3664
2508
|
return value.match(/filename="?([^";]+)"?/i)?.[1] ?? null;
|
|
3665
2509
|
};
|
|
3666
2510
|
/**
|
|
3667
|
-
* A board transaction rejected by the server. `status`/`code` let callers
|
|
3668
|
-
* distinguish a recoverable version conflict (409 / "VERSION_CONFLICT") from
|
|
3669
|
-
* transient failures, so they can rebase and retry instead of surfacing an error.
|
|
3670
|
-
*/
|
|
3671
|
-
var BoardTransactionError = class extends Error {
|
|
3672
|
-
status;
|
|
3673
|
-
code;
|
|
3674
|
-
body;
|
|
3675
|
-
constructor(message, status, code, body, options) {
|
|
3676
|
-
super(message, options);
|
|
3677
|
-
this.status = status;
|
|
3678
|
-
this.code = code;
|
|
3679
|
-
this.body = body;
|
|
3680
|
-
this.name = "BoardTransactionError";
|
|
3681
|
-
}
|
|
3682
|
-
get isVersionConflict() {
|
|
3683
|
-
return this.code === "VERSION_CONFLICT";
|
|
3684
|
-
}
|
|
3685
|
-
};
|
|
3686
|
-
/**
|
|
3687
2511
|
* Identifier for a client-authored Board entity (connection id, transaction id).
|
|
3688
2512
|
*
|
|
3689
2513
|
* `crypto.randomUUID` is used where available and falls back to a random string
|
|
@@ -4298,7 +3122,7 @@ var SpaceEventsApi = class {
|
|
|
4298
3122
|
handler(event);
|
|
4299
3123
|
return;
|
|
4300
3124
|
}
|
|
4301
|
-
if (type === "board.
|
|
3125
|
+
if (type === "board.changed" && event.type === "board.changed") {
|
|
4302
3126
|
handler(event);
|
|
4303
3127
|
return;
|
|
4304
3128
|
}
|
|
@@ -4306,7 +3130,7 @@ var SpaceEventsApi = class {
|
|
|
4306
3130
|
handler(event);
|
|
4307
3131
|
return;
|
|
4308
3132
|
}
|
|
4309
|
-
if (type === "
|
|
3133
|
+
if (type === "app.version.published" && event.type === "app.version.published") {
|
|
4310
3134
|
handler(event);
|
|
4311
3135
|
return;
|
|
4312
3136
|
}
|
|
@@ -4656,10 +3480,10 @@ var BoardRealtimeClient = class {
|
|
|
4656
3480
|
const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId), getRealtimeBoardRoom(this.boardId)]);
|
|
4657
3481
|
const unsubscribe = this.websocketClient.on("event", (event) => {
|
|
4658
3482
|
if (event.spaceId !== this.spaceId) return;
|
|
4659
|
-
if (event.type === "board.
|
|
4660
|
-
const
|
|
4661
|
-
handlers.event?.(
|
|
4662
|
-
handlers.
|
|
3483
|
+
if (event.type === "board.changed" && event.payload.boardId === this.boardId) {
|
|
3484
|
+
const changedEvent = event;
|
|
3485
|
+
handlers.event?.(changedEvent);
|
|
3486
|
+
handlers.changed?.(changedEvent);
|
|
4663
3487
|
}
|
|
4664
3488
|
if (event.type === "board.awareness.updated" && event.payload.boardId === this.boardId) {
|
|
4665
3489
|
const awarenessEvent = event;
|
|
@@ -4680,7 +3504,7 @@ var BoardRealtimeClient = class {
|
|
|
4680
3504
|
};
|
|
4681
3505
|
}
|
|
4682
3506
|
on(type, handler) {
|
|
4683
|
-
if (type === "
|
|
3507
|
+
if (type === "changed") return this.subscribe({ changed: handler });
|
|
4684
3508
|
if (type === "awareness") return this.subscribe({ awareness: handler });
|
|
4685
3509
|
return this.subscribe({ playback: handler });
|
|
4686
3510
|
}
|
|
@@ -4698,67 +3522,20 @@ var BoardClient = class {
|
|
|
4698
3522
|
this.boards = new SpaceBoardsApi(transport, spaceId, websocketClient);
|
|
4699
3523
|
this.realtime = new BoardRealtimeClient(websocketClient, spaceId, id);
|
|
4700
3524
|
}
|
|
4701
|
-
inspect(input = {}, customFetch) {
|
|
4702
|
-
return this.boards.inspect(this.id, input, customFetch);
|
|
4703
|
-
}
|
|
4704
3525
|
capabilities(customFetch) {
|
|
4705
3526
|
return this.boards.capabilities(this.id, customFetch);
|
|
4706
3527
|
}
|
|
4707
3528
|
summary(customFetch) {
|
|
4708
3529
|
return this.boards.summary(this.id, customFetch);
|
|
4709
3530
|
}
|
|
4710
|
-
authoring(customFetch) {
|
|
4711
|
-
return this.boards.authoring(this.id, customFetch);
|
|
3531
|
+
authoring(input = {}, customFetch) {
|
|
3532
|
+
return this.boards.authoring(this.id, input, customFetch);
|
|
4712
3533
|
}
|
|
4713
3534
|
mutateSemantic(input) {
|
|
4714
3535
|
return this.boards.mutateSemantic(this.id, {
|
|
4715
3536
|
...input,
|
|
4716
|
-
mutationId: input.mutationId ?? randomBoardId()
|
|
4717
|
-
|
|
4718
|
-
}
|
|
4719
|
-
async mutate(input) {
|
|
4720
|
-
const retries = input.retries ?? 1;
|
|
4721
|
-
if (!Number.isSafeInteger(retries) || retries < 0 || retries > 3) throw new RangeError("Board mutation retries must be an integer from 0 to 3");
|
|
4722
|
-
for (let attempt = 0;; attempt += 1) {
|
|
4723
|
-
const current = await this.inspect({ include: input.include ?? [] });
|
|
4724
|
-
const operations = await input.build(current);
|
|
4725
|
-
if (operations.length === 0) return {
|
|
4726
|
-
mutationId: randomBoardId(),
|
|
4727
|
-
status: "validated",
|
|
4728
|
-
replayed: false,
|
|
4729
|
-
board: {
|
|
4730
|
-
id: current.board.id,
|
|
4731
|
-
version: current.board.version
|
|
4732
|
-
},
|
|
4733
|
-
changed: {
|
|
4734
|
-
items: [],
|
|
4735
|
-
connections: [],
|
|
4736
|
-
effects: [],
|
|
4737
|
-
compositions: [],
|
|
4738
|
-
board: false
|
|
4739
|
-
}
|
|
4740
|
-
};
|
|
4741
|
-
try {
|
|
4742
|
-
return await this.apply({
|
|
4743
|
-
txId: randomBoardId(),
|
|
4744
|
-
baseVersion: current.board.version,
|
|
4745
|
-
operations
|
|
4746
|
-
});
|
|
4747
|
-
} catch (cause) {
|
|
4748
|
-
if (!(cause instanceof BoardTransactionError) || !cause.isVersionConflict || attempt >= retries) throw cause;
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
4752
|
-
validate(transaction) {
|
|
4753
|
-
return this.boards.validate({
|
|
4754
|
-
...transaction,
|
|
4755
|
-
boardId: this.id
|
|
4756
|
-
});
|
|
4757
|
-
}
|
|
4758
|
-
apply(transaction) {
|
|
4759
|
-
return this.boards.apply({
|
|
4760
|
-
...transaction,
|
|
4761
|
-
boardId: this.id
|
|
3537
|
+
mutationId: input.mutationId ?? randomBoardId(),
|
|
3538
|
+
dryRun: input.dryRun ?? false
|
|
4762
3539
|
});
|
|
4763
3540
|
}
|
|
4764
3541
|
updateAwareness(seq, update) {
|
|
@@ -4770,91 +3547,6 @@ var BoardClient = class {
|
|
|
4770
3547
|
update
|
|
4771
3548
|
});
|
|
4772
3549
|
}
|
|
4773
|
-
playback(command) {
|
|
4774
|
-
return this.boards.playback(this.id, command);
|
|
4775
|
-
}
|
|
4776
|
-
/**
|
|
4777
|
-
* Read the Board's relations.
|
|
4778
|
-
*
|
|
4779
|
-
* A dedicated read rather than a filter over `inspect()`: a caller that wants
|
|
4780
|
-
* the graph should not have to fetch every node's geometry to get it.
|
|
4781
|
-
*/
|
|
4782
|
-
async connections(customFetch) {
|
|
4783
|
-
return (await this.boards.inspect(this.id, { include: ["connections"] }, customFetch)).connections;
|
|
4784
|
-
}
|
|
4785
|
-
/**
|
|
4786
|
-
* Connections touching a node, in either direction.
|
|
4787
|
-
*
|
|
4788
|
-
* Filtered client-side from the Board's relation set, which is a single read and
|
|
4789
|
-
* bounded by the Board rather than by the node's degree.
|
|
4790
|
-
*/
|
|
4791
|
-
async connectionsForNode(nodeId, customFetch) {
|
|
4792
|
-
return (await this.connections(customFetch)).filter((connection) => connection.source.nodeId === nodeId || connection.target.nodeId === nodeId);
|
|
4793
|
-
}
|
|
4794
|
-
/**
|
|
4795
|
-
* Connect two nodes.
|
|
4796
|
-
*
|
|
4797
|
-
* Wraps the transaction so the common case is one call: the caller supplies the
|
|
4798
|
-
* two nodes and, optionally, the relation. `baseVersion` still has to be the
|
|
4799
|
-
* version the caller last read, because a relation is only meaningful against
|
|
4800
|
-
* the node set it was authored on.
|
|
4801
|
-
*/
|
|
4802
|
-
connect(input) {
|
|
4803
|
-
const connection = createBoardConnection({
|
|
4804
|
-
id: input.id ?? randomBoardId(),
|
|
4805
|
-
sourceNodeId: input.sourceNodeId,
|
|
4806
|
-
targetNodeId: input.targetNodeId,
|
|
4807
|
-
...input.relation === void 0 ? {} : { relation: input.relation },
|
|
4808
|
-
...input.direction === void 0 ? {} : { direction: input.direction },
|
|
4809
|
-
...input.label === void 0 ? {} : { label: input.label },
|
|
4810
|
-
...input.sourcePortId === void 0 ? {} : { sourcePortId: input.sourcePortId },
|
|
4811
|
-
...input.targetPortId === void 0 ? {} : { targetPortId: input.targetPortId }
|
|
4812
|
-
});
|
|
4813
|
-
return this.apply({
|
|
4814
|
-
txId: input.txId ?? randomBoardId(),
|
|
4815
|
-
baseVersion: input.baseVersion,
|
|
4816
|
-
operations: [{
|
|
4817
|
-
type: "connection.create",
|
|
4818
|
-
payload: { connection }
|
|
4819
|
-
}]
|
|
4820
|
-
});
|
|
4821
|
-
}
|
|
4822
|
-
/** Remove a connection. The nodes it joined are untouched. */
|
|
4823
|
-
disconnect(input) {
|
|
4824
|
-
return this.apply({
|
|
4825
|
-
txId: input.txId ?? randomBoardId(),
|
|
4826
|
-
baseVersion: input.baseVersion,
|
|
4827
|
-
operations: [{
|
|
4828
|
-
type: "connection.delete",
|
|
4829
|
-
payload: { connectionId: input.connectionId }
|
|
4830
|
-
}]
|
|
4831
|
-
});
|
|
4832
|
-
}
|
|
4833
|
-
/**
|
|
4834
|
-
* Delete a node together with every relation that names it.
|
|
4835
|
-
*
|
|
4836
|
-
* The server refuses to orphan a relation, so the cascade is explicit and lands
|
|
4837
|
-
* in one transaction: one undo step restores the node and its edges together.
|
|
4838
|
-
* `connections` is the relation set the caller already read, so this stays a
|
|
4839
|
-
* single round-trip.
|
|
4840
|
-
*/
|
|
4841
|
-
deleteNodeWithConnections(input) {
|
|
4842
|
-
const incident = input.connections.filter((connection) => connection.source.nodeId === input.nodeId || connection.target.nodeId === input.nodeId);
|
|
4843
|
-
return this.apply({
|
|
4844
|
-
txId: input.txId ?? randomBoardId(),
|
|
4845
|
-
baseVersion: input.baseVersion,
|
|
4846
|
-
operations: [...incident.map((connection) => ({
|
|
4847
|
-
type: "connection.delete",
|
|
4848
|
-
payload: {
|
|
4849
|
-
connectionId: connection.id,
|
|
4850
|
-
reason: "node-cascade"
|
|
4851
|
-
}
|
|
4852
|
-
})), {
|
|
4853
|
-
type: "node.delete",
|
|
4854
|
-
payload: { nodeId: input.nodeId }
|
|
4855
|
-
}]
|
|
4856
|
-
});
|
|
4857
|
-
}
|
|
4858
3550
|
play(command) {
|
|
4859
3551
|
return this.boards.play(this.id, command);
|
|
4860
3552
|
}
|
|
@@ -4871,7 +3563,7 @@ var BoardClient = class {
|
|
|
4871
3563
|
return this.realtime.subscribe(handlers);
|
|
4872
3564
|
}
|
|
4873
3565
|
on(type, handler) {
|
|
4874
|
-
if (type === "
|
|
3566
|
+
if (type === "changed") return this.realtime.on("changed", handler);
|
|
4875
3567
|
if (type === "awareness") return this.realtime.on("awareness", handler);
|
|
4876
3568
|
return this.realtime.on("playback", handler);
|
|
4877
3569
|
}
|
|
@@ -4888,7 +3580,7 @@ var SpaceBoardsApi = class {
|
|
|
4888
3580
|
byId(boardId) {
|
|
4889
3581
|
return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
|
|
4890
3582
|
}
|
|
4891
|
-
create(input) {
|
|
3583
|
+
async create(input) {
|
|
4892
3584
|
for (const item of input.items ?? []) BoardAuthoringItemSchema.parse(item);
|
|
4893
3585
|
return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
|
|
4894
3586
|
method: "POST",
|
|
@@ -4896,15 +3588,19 @@ var SpaceBoardsApi = class {
|
|
|
4896
3588
|
body: JSON.stringify(input)
|
|
4897
3589
|
});
|
|
4898
3590
|
}
|
|
4899
|
-
|
|
3591
|
+
authoring(boardId, input = {}, customFetch) {
|
|
4900
3592
|
const params = new URLSearchParams();
|
|
4901
|
-
|
|
3593
|
+
if (input.include) {
|
|
3594
|
+
for (const section of input.include) params.append("include", section);
|
|
3595
|
+
if (input.include.length === 0) params.set("include", "");
|
|
3596
|
+
}
|
|
3597
|
+
if (input.itemIds?.length) params.set("itemIds", input.itemIds.join(","));
|
|
3598
|
+
if (input.connectionIds?.length) params.set("connectionIds", input.connectionIds.join(","));
|
|
3599
|
+
if (input.effectIds?.length) params.set("effectIds", input.effectIds.join(","));
|
|
3600
|
+
if (input.compositionIds?.length) params.set("compositionIds", input.compositionIds.join(","));
|
|
4902
3601
|
if (input.viewport) params.set("viewport", JSON.stringify(input.viewport));
|
|
4903
3602
|
const query = params.toString();
|
|
4904
|
-
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
4905
|
-
}
|
|
4906
|
-
authoring(boardId, customFetch) {
|
|
4907
|
-
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring`, { fetch: customFetch });
|
|
3603
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
4908
3604
|
}
|
|
4909
3605
|
mutateSemantic(boardId, mutation) {
|
|
4910
3606
|
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/mutations`, {
|
|
@@ -4919,25 +3615,6 @@ var SpaceBoardsApi = class {
|
|
|
4919
3615
|
capabilities(boardId, customFetch) {
|
|
4920
3616
|
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/capabilities`, { fetch: customFetch });
|
|
4921
3617
|
}
|
|
4922
|
-
validate(transaction) {
|
|
4923
|
-
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${transaction.boardId}/validate`, {
|
|
4924
|
-
method: "POST",
|
|
4925
|
-
headers: { "Content-Type": "application/json" },
|
|
4926
|
-
body: JSON.stringify(transaction)
|
|
4927
|
-
});
|
|
4928
|
-
}
|
|
4929
|
-
async apply(transaction) {
|
|
4930
|
-
try {
|
|
4931
|
-
return await this.transport.request(`/api/spaces/${this.spaceId}/boards/${transaction.boardId}/transactions`, {
|
|
4932
|
-
method: "POST",
|
|
4933
|
-
headers: { "Content-Type": "application/json" },
|
|
4934
|
-
body: JSON.stringify(transaction)
|
|
4935
|
-
});
|
|
4936
|
-
} catch (cause) {
|
|
4937
|
-
if (cause instanceof HttpError) throw new BoardTransactionError(cause.message, cause.status, cause.code ?? void 0, cause.body, { cause });
|
|
4938
|
-
throw cause;
|
|
4939
|
-
}
|
|
4940
|
-
}
|
|
4941
3618
|
playback(boardId, command) {
|
|
4942
3619
|
return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/playback`, {
|
|
4943
3620
|
method: "POST",
|
|
@@ -5340,79 +4017,6 @@ var TasksApi = class {
|
|
|
5340
4017
|
}
|
|
5341
4018
|
};
|
|
5342
4019
|
//#endregion
|
|
5343
|
-
//#region src/apis/ui-commands.ts
|
|
5344
|
-
const DEFAULT_POLL_INTERVAL_MS = 300;
|
|
5345
|
-
const resolveTimeoutMs = (timeoutMs) => {
|
|
5346
|
-
const value = timeoutMs ?? 6e5;
|
|
5347
|
-
if (!Number.isFinite(value) || value <= 0 || value > 432e5) throw new RangeError(`timeoutMs must be between 1 and ${UI_COMMAND_MAX_TIMEOUT_MS} milliseconds`);
|
|
5348
|
-
return value;
|
|
5349
|
-
};
|
|
5350
|
-
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
5351
|
-
if (signal?.aborted) {
|
|
5352
|
-
reject(/* @__PURE__ */ new Error("aborted"));
|
|
5353
|
-
return;
|
|
5354
|
-
}
|
|
5355
|
-
const timer = setTimeout(() => {
|
|
5356
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5357
|
-
resolve();
|
|
5358
|
-
}, ms);
|
|
5359
|
-
const onAbort = () => {
|
|
5360
|
-
clearTimeout(timer);
|
|
5361
|
-
reject(/* @__PURE__ */ new Error("aborted"));
|
|
5362
|
-
};
|
|
5363
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5364
|
-
});
|
|
5365
|
-
var UiCommandsApi = class {
|
|
5366
|
-
transport;
|
|
5367
|
-
constructor(transport) {
|
|
5368
|
-
this.transport = transport;
|
|
5369
|
-
}
|
|
5370
|
-
create(input) {
|
|
5371
|
-
return this.transport.request("/api/ui/commands", {
|
|
5372
|
-
method: "POST",
|
|
5373
|
-
headers: { "Content-Type": "application/json" },
|
|
5374
|
-
body: JSON.stringify(input)
|
|
5375
|
-
});
|
|
5376
|
-
}
|
|
5377
|
-
get(commandId) {
|
|
5378
|
-
return this.transport.request(`/api/ui/commands/${encodeURIComponent(commandId)}`);
|
|
5379
|
-
}
|
|
5380
|
-
reportResult(commandId, input) {
|
|
5381
|
-
return this.transport.request(`/api/ui/commands/${encodeURIComponent(commandId)}/result`, {
|
|
5382
|
-
method: "POST",
|
|
5383
|
-
headers: { "Content-Type": "application/json" },
|
|
5384
|
-
body: JSON.stringify(input)
|
|
5385
|
-
});
|
|
5386
|
-
}
|
|
5387
|
-
async run(input, options = {}) {
|
|
5388
|
-
const { command } = await this.create(input);
|
|
5389
|
-
if (isTerminalUiCommandStatus(command.status)) return command;
|
|
5390
|
-
return this.wait(command.commandId, options);
|
|
5391
|
-
}
|
|
5392
|
-
async wait(commandId, options = {}) {
|
|
5393
|
-
const timeoutMs = resolveTimeoutMs(options.timeoutMs);
|
|
5394
|
-
const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
5395
|
-
const deadline = Date.now() + timeoutMs;
|
|
5396
|
-
let latest = (await this.get(commandId)).command;
|
|
5397
|
-
while (!isTerminalUiCommandStatus(latest.status)) {
|
|
5398
|
-
const remaining = deadline - Date.now();
|
|
5399
|
-
if (remaining <= 0) break;
|
|
5400
|
-
await sleep(Math.min(pollIntervalMs, remaining), options.signal);
|
|
5401
|
-
latest = (await this.get(commandId)).command;
|
|
5402
|
-
}
|
|
5403
|
-
if (isTerminalUiCommandStatus(latest.status)) return latest;
|
|
5404
|
-
return {
|
|
5405
|
-
...latest,
|
|
5406
|
-
status: "timeout",
|
|
5407
|
-
error: {
|
|
5408
|
-
code: "timeout",
|
|
5409
|
-
message: "No Cohub frontend reported a result before the timeout."
|
|
5410
|
-
},
|
|
5411
|
-
settledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5412
|
-
};
|
|
5413
|
-
}
|
|
5414
|
-
};
|
|
5415
|
-
//#endregion
|
|
5416
4020
|
//#region src/apis/user.ts
|
|
5417
4021
|
const usageDate = (value) => value instanceof Date ? value.toISOString() : value;
|
|
5418
4022
|
var UserApi = class {
|
|
@@ -5540,131 +4144,208 @@ var ReferralsApi = class {
|
|
|
5540
4144
|
}
|
|
5541
4145
|
};
|
|
5542
4146
|
//#endregion
|
|
5543
|
-
//#region src/apis/
|
|
5544
|
-
var
|
|
4147
|
+
//#region src/apis/apps.ts
|
|
4148
|
+
var AppsApi = class {
|
|
5545
4149
|
transport;
|
|
5546
4150
|
constructor(transport) {
|
|
5547
4151
|
this.transport = transport;
|
|
5548
4152
|
}
|
|
5549
4153
|
listBySpace(spaceId) {
|
|
5550
|
-
return this.transport.request(`/api/
|
|
4154
|
+
return this.transport.request(`/api/apps/space/${spaceId}`);
|
|
5551
4155
|
}
|
|
5552
4156
|
get(id) {
|
|
5553
|
-
return this.transport.request(`/api/
|
|
4157
|
+
return this.transport.request(`/api/apps/${id}`);
|
|
5554
4158
|
}
|
|
5555
4159
|
/**
|
|
5556
|
-
* Loads a published
|
|
5557
|
-
* Used by the standalone
|
|
4160
|
+
* Loads a published app's metadata + owner info by id (public access model).
|
|
4161
|
+
* Used by the standalone app auth broker page.
|
|
5558
4162
|
*/
|
|
5559
4163
|
getPublicById(id) {
|
|
5560
|
-
return this.transport.request(`/api/
|
|
4164
|
+
return this.transport.request(`/api/apps/${id}/public`);
|
|
5561
4165
|
}
|
|
5562
|
-
getBySlug(username, spaceSlug,
|
|
5563
|
-
return this.transport.request(`/api/
|
|
4166
|
+
getBySlug(username, spaceSlug, appSlug, options) {
|
|
4167
|
+
return this.transport.request(`/api/apps/by-slug/${encodeURIComponent(username)}/${encodeURIComponent(spaceSlug)}/${encodeURIComponent(appSlug)}`, options?.signal ? { signal: options.signal } : void 0);
|
|
5564
4168
|
}
|
|
5565
4169
|
create(input) {
|
|
5566
|
-
return this.transport.request("/api/
|
|
4170
|
+
return this.transport.request("/api/apps", {
|
|
5567
4171
|
method: "POST",
|
|
5568
4172
|
headers: { "Content-Type": "application/json" },
|
|
5569
4173
|
body: JSON.stringify(input)
|
|
5570
4174
|
});
|
|
5571
4175
|
}
|
|
5572
4176
|
update(id, input) {
|
|
5573
|
-
return this.transport.request(`/api/
|
|
4177
|
+
return this.transport.request(`/api/apps/${id}`, {
|
|
5574
4178
|
method: "PATCH",
|
|
5575
4179
|
headers: { "Content-Type": "application/json" },
|
|
5576
4180
|
body: JSON.stringify(input)
|
|
5577
4181
|
});
|
|
5578
4182
|
}
|
|
5579
4183
|
delete(id) {
|
|
5580
|
-
return this.transport.request(`/api/
|
|
4184
|
+
return this.transport.request(`/api/apps/${id}`, { method: "DELETE" });
|
|
5581
4185
|
}
|
|
5582
|
-
getStats(
|
|
5583
|
-
return this.transport.request(`/api/
|
|
4186
|
+
getStats(appId) {
|
|
4187
|
+
return this.transport.request(`/api/apps/${appId}/stats`);
|
|
5584
4188
|
}
|
|
5585
|
-
listPromotions(
|
|
5586
|
-
return this.transport.request(`/api/
|
|
4189
|
+
listPromotions(appId) {
|
|
4190
|
+
return this.transport.request(`/api/apps/${appId}/promotions`);
|
|
5587
4191
|
}
|
|
5588
|
-
createPromotion(
|
|
5589
|
-
return this.transport.request(`/api/
|
|
4192
|
+
createPromotion(appId, input) {
|
|
4193
|
+
return this.transport.request(`/api/apps/${appId}/promotions`, {
|
|
5590
4194
|
method: "POST",
|
|
5591
4195
|
headers: { "Content-Type": "application/json" },
|
|
5592
4196
|
body: JSON.stringify(input)
|
|
5593
4197
|
});
|
|
5594
4198
|
}
|
|
5595
|
-
getPromotionStats(
|
|
5596
|
-
return this.transport.request(`/api/
|
|
4199
|
+
getPromotionStats(appId, promotionId) {
|
|
4200
|
+
return this.transport.request(`/api/apps/${appId}/promotions/${promotionId}/stats`);
|
|
5597
4201
|
}
|
|
5598
|
-
recordPromotionEvent(
|
|
5599
|
-
return this.transport.request(`/api/
|
|
4202
|
+
recordPromotionEvent(appId, promotionId, input) {
|
|
4203
|
+
return this.transport.request(`/api/apps/${appId}/promotions/${promotionId}/events`, {
|
|
5600
4204
|
method: "POST",
|
|
5601
4205
|
headers: { "Content-Type": "application/json" },
|
|
5602
4206
|
body: JSON.stringify(input)
|
|
5603
4207
|
});
|
|
5604
4208
|
}
|
|
5605
|
-
recordPromotionRegistration(
|
|
5606
|
-
return this.transport.request(`/api/
|
|
4209
|
+
recordPromotionRegistration(appId, promotionId, input) {
|
|
4210
|
+
return this.transport.request(`/api/apps/${appId}/promotions/${promotionId}/registration`, {
|
|
5607
4211
|
method: "POST",
|
|
5608
4212
|
headers: input ? { "Content-Type": "application/json" } : void 0,
|
|
5609
4213
|
body: input ? JSON.stringify(input) : void 0
|
|
5610
4214
|
});
|
|
5611
4215
|
}
|
|
5612
|
-
listVersions(
|
|
5613
|
-
return this.transport.request(`/api/
|
|
4216
|
+
listVersions(appId) {
|
|
4217
|
+
return this.transport.request(`/api/apps/${appId}/versions`);
|
|
5614
4218
|
}
|
|
5615
|
-
publishVersion(
|
|
5616
|
-
return this.transport.request(`/api/
|
|
4219
|
+
publishVersion(appId, input) {
|
|
4220
|
+
return this.transport.request(`/api/apps/${appId}/versions`, {
|
|
5617
4221
|
method: "POST",
|
|
5618
4222
|
headers: input ? { "Content-Type": "application/json" } : void 0,
|
|
5619
4223
|
body: input ? JSON.stringify(input) : void 0
|
|
5620
4224
|
});
|
|
5621
4225
|
}
|
|
5622
|
-
createSession(
|
|
5623
|
-
return this.transport.request(`/api/
|
|
4226
|
+
createSession(appId) {
|
|
4227
|
+
return this.transport.request(`/api/apps/${appId}/session`, { method: "POST" });
|
|
4228
|
+
}
|
|
4229
|
+
authorize(appId, input) {
|
|
4230
|
+
return this.transport.request(`/api/apps/${appId}/authorize`, {
|
|
4231
|
+
method: "POST",
|
|
4232
|
+
headers: { "Content-Type": "application/json" },
|
|
4233
|
+
body: JSON.stringify(input)
|
|
4234
|
+
});
|
|
4235
|
+
}
|
|
4236
|
+
};
|
|
4237
|
+
//#endregion
|
|
4238
|
+
//#region src/apis/desktop-commands.ts
|
|
4239
|
+
const DEFAULT_POLL_INTERVAL_MS = 300;
|
|
4240
|
+
const resolveTimeoutMs = (timeoutMs) => {
|
|
4241
|
+
const value = timeoutMs ?? 6e5;
|
|
4242
|
+
if (!Number.isFinite(value) || value <= 0 || value > 432e5) throw new RangeError(`timeoutMs must be between 1 and ${DESKTOP_COMMAND_MAX_TIMEOUT_MS} milliseconds`);
|
|
4243
|
+
return value;
|
|
4244
|
+
};
|
|
4245
|
+
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
4246
|
+
if (signal?.aborted) {
|
|
4247
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
4248
|
+
return;
|
|
4249
|
+
}
|
|
4250
|
+
const timer = setTimeout(() => {
|
|
4251
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4252
|
+
resolve();
|
|
4253
|
+
}, ms);
|
|
4254
|
+
const onAbort = () => {
|
|
4255
|
+
clearTimeout(timer);
|
|
4256
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
4257
|
+
};
|
|
4258
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4259
|
+
});
|
|
4260
|
+
var DesktopCommandsApi = class {
|
|
4261
|
+
transport;
|
|
4262
|
+
constructor(transport) {
|
|
4263
|
+
this.transport = transport;
|
|
4264
|
+
}
|
|
4265
|
+
create(input) {
|
|
4266
|
+
return this.transport.request("/api/desktop/commands", {
|
|
4267
|
+
method: "POST",
|
|
4268
|
+
headers: { "Content-Type": "application/json" },
|
|
4269
|
+
body: JSON.stringify(input)
|
|
4270
|
+
});
|
|
4271
|
+
}
|
|
4272
|
+
get(commandId) {
|
|
4273
|
+
return this.transport.request(`/api/desktop/commands/${encodeURIComponent(commandId)}`);
|
|
5624
4274
|
}
|
|
5625
|
-
|
|
5626
|
-
return this.transport.request(`/api/
|
|
4275
|
+
reportResult(commandId, input) {
|
|
4276
|
+
return this.transport.request(`/api/desktop/commands/${encodeURIComponent(commandId)}/result`, {
|
|
5627
4277
|
method: "POST",
|
|
5628
4278
|
headers: { "Content-Type": "application/json" },
|
|
5629
4279
|
body: JSON.stringify(input)
|
|
5630
4280
|
});
|
|
5631
4281
|
}
|
|
4282
|
+
async run(input, options = {}) {
|
|
4283
|
+
const { command } = await this.create(input);
|
|
4284
|
+
if (isTerminalDesktopCommandStatus(command.status)) return command;
|
|
4285
|
+
return this.wait(command.commandId, options);
|
|
4286
|
+
}
|
|
4287
|
+
async wait(commandId, options = {}) {
|
|
4288
|
+
const timeoutMs = resolveTimeoutMs(options.timeoutMs);
|
|
4289
|
+
const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
4290
|
+
const deadline = Date.now() + timeoutMs;
|
|
4291
|
+
let latest = (await this.get(commandId)).command;
|
|
4292
|
+
while (!isTerminalDesktopCommandStatus(latest.status)) {
|
|
4293
|
+
const remaining = deadline - Date.now();
|
|
4294
|
+
if (remaining <= 0) break;
|
|
4295
|
+
await sleep(Math.min(pollIntervalMs, remaining), options.signal);
|
|
4296
|
+
latest = (await this.get(commandId)).command;
|
|
4297
|
+
}
|
|
4298
|
+
if (isTerminalDesktopCommandStatus(latest.status)) return latest;
|
|
4299
|
+
return {
|
|
4300
|
+
...latest,
|
|
4301
|
+
status: "timeout",
|
|
4302
|
+
error: {
|
|
4303
|
+
code: "timeout",
|
|
4304
|
+
message: "No Cohub desktop reported a result before the timeout."
|
|
4305
|
+
},
|
|
4306
|
+
settledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
5632
4309
|
};
|
|
4310
|
+
/** @deprecated Use `DesktopCommandsApi`. */
|
|
4311
|
+
var UiCommandsApi = class extends DesktopCommandsApi {};
|
|
5633
4312
|
//#endregion
|
|
5634
|
-
//#region src/apis/
|
|
5635
|
-
var
|
|
4313
|
+
//#region src/apis/app-commerce.ts
|
|
4314
|
+
var AppCommerceApi = class {
|
|
5636
4315
|
transport;
|
|
5637
4316
|
constructor(transport) {
|
|
5638
4317
|
this.transport = transport;
|
|
5639
4318
|
}
|
|
5640
|
-
resolveProducts(
|
|
5641
|
-
return this.transport.request(`/api/
|
|
4319
|
+
resolveProducts(appId, input) {
|
|
4320
|
+
return this.transport.request(`/api/apps/${encodeURIComponent(appId)}/commerce/products/resolve`, {
|
|
5642
4321
|
method: "POST",
|
|
5643
4322
|
headers: { "Content-Type": "application/json" },
|
|
5644
4323
|
body: JSON.stringify(input)
|
|
5645
4324
|
});
|
|
5646
4325
|
}
|
|
5647
|
-
getEntitlements(
|
|
5648
|
-
return this.transport.request(`/api/
|
|
4326
|
+
getEntitlements(appId) {
|
|
4327
|
+
return this.transport.request(`/api/apps/${encodeURIComponent(appId)}/commerce/entitlements`);
|
|
5649
4328
|
}
|
|
5650
|
-
consumeCredits(
|
|
5651
|
-
return this.transport.request(`/api/
|
|
4329
|
+
consumeCredits(appId, input) {
|
|
4330
|
+
return this.transport.request(`/api/apps/${encodeURIComponent(appId)}/commerce/credits/consume`, {
|
|
5652
4331
|
method: "POST",
|
|
5653
4332
|
headers: { "Content-Type": "application/json" },
|
|
5654
4333
|
body: JSON.stringify(input)
|
|
5655
4334
|
});
|
|
5656
4335
|
}
|
|
5657
|
-
purchase(
|
|
5658
|
-
return this.transport.request(`/api/
|
|
4336
|
+
purchase(appId, input) {
|
|
4337
|
+
return this.transport.request(`/api/apps/${encodeURIComponent(appId)}/commerce/purchase`, {
|
|
5659
4338
|
method: "POST",
|
|
5660
4339
|
headers: { "Content-Type": "application/json" },
|
|
5661
4340
|
body: JSON.stringify(input)
|
|
5662
4341
|
});
|
|
5663
4342
|
}
|
|
5664
|
-
getOrder(
|
|
5665
|
-
return this.transport.request(`/api/
|
|
4343
|
+
getOrder(appId, orderId) {
|
|
4344
|
+
return this.transport.request(`/api/apps/${encodeURIComponent(appId)}/commerce/orders/${encodeURIComponent(orderId)}`);
|
|
5666
4345
|
}
|
|
5667
4346
|
};
|
|
4347
|
+
/** @deprecated Use `AppCommerceApi`. */
|
|
4348
|
+
var WorkCommerceApi = class extends AppCommerceApi {};
|
|
5668
4349
|
//#endregion
|
|
5669
4350
|
//#region src/http.ts
|
|
5670
4351
|
var CohubHttpClient = class {
|
|
@@ -5684,9 +4365,21 @@ var CohubHttpClient = class {
|
|
|
5684
4365
|
cronJobs;
|
|
5685
4366
|
invite;
|
|
5686
4367
|
referrals;
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
4368
|
+
apps;
|
|
4369
|
+
appCommerce;
|
|
4370
|
+
/** @deprecated Use `apps`. */
|
|
4371
|
+
get works() {
|
|
4372
|
+
return this.apps;
|
|
4373
|
+
}
|
|
4374
|
+
/** @deprecated Use `appCommerce`. */
|
|
4375
|
+
get workCommerce() {
|
|
4376
|
+
return this.appCommerce;
|
|
4377
|
+
}
|
|
4378
|
+
desktop;
|
|
4379
|
+
/** @deprecated Use `desktop`. */
|
|
4380
|
+
get ui() {
|
|
4381
|
+
return this.desktop;
|
|
4382
|
+
}
|
|
5690
4383
|
transport;
|
|
5691
4384
|
constructor(options = {}) {
|
|
5692
4385
|
const apiBaseUrl = resolveApiBaseUrl(options);
|
|
@@ -5707,9 +4400,9 @@ var CohubHttpClient = class {
|
|
|
5707
4400
|
this.cronJobs = new CronJobsApi(this.transport);
|
|
5708
4401
|
this.invite = new PublicInviteApi(this.transport);
|
|
5709
4402
|
this.referrals = new ReferralsApi(this.transport);
|
|
5710
|
-
this.
|
|
5711
|
-
this.
|
|
5712
|
-
this.
|
|
4403
|
+
this.apps = new AppsApi(this.transport);
|
|
4404
|
+
this.appCommerce = new AppCommerceApi(this.transport);
|
|
4405
|
+
this.desktop = new DesktopCommandsApi(this.transport);
|
|
5713
4406
|
}
|
|
5714
4407
|
space(spaceId) {
|
|
5715
4408
|
return new SpaceClient(spaceId, this.transport, null);
|
|
@@ -5717,4 +4410,4 @@ var CohubHttpClient = class {
|
|
|
5717
4410
|
};
|
|
5718
4411
|
const createHttpClient = (options) => new CohubHttpClient(options);
|
|
5719
4412
|
//#endregion
|
|
5720
|
-
export {
|
|
4413
|
+
export { BOARD_BUILTIN_EFFECT_KINDS as $, DESKTOP_COMMAND_TERMINAL_TTL_SECONDS as A, BoardEffectSchema as B, createSessionPatchReducer as C, DESKTOP_COMMAND_PAYLOAD_MAX_BYTES as D, DESKTOP_COMMAND_MAX_TIMEOUT_MS as E, parseDesktopCommandId as F, BoardCompositionSchema as G, BOARD_ANIMATION_CHANNELS as H, BoardAuthoringItemSchema as I, parseBoardCompositionInput as J, BoardProceduralClipSchema as K, BoardItemPatchSchema as L, isDesktopCallMethod as M, isTerminalDesktopCommandStatus as N, DESKTOP_COMMAND_PENDING_TTL_SECONDS as O, parseDesktopCommand as P, BOARD_BUILTIN_CLIP_KINDS as Q, BoardSemanticMutationSchema as R, SessionPatchReducer as S, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS as T, BOARD_ANIMATION_CHANNEL_CAPABILITIES as U, parseBoardEffectInput as V, BoardCompositionInputSchema as W, BOARD_ARROW_STROKE_SIZE as X, BoardConnectionSchema as Y, BOARD_BUILTIN_CAPABILITIES as Z, buildSpaceInvitePath as _, DesktopCommandsApi as a, SkillsApi as at, createSessionGenerationStreamClient as b, ReferralsApi as c, GenerationsApi as ct, TasksApi as d, DEFAULT_BOARD_RENDER_LIMITS as et, BoardClient as f, PublicInviteApi as g, SpacesApi as h, WorkCommerceApi as i, PublicAssetsApi as it, DESKTOP_COMMAND_VERSION as j, DESKTOP_COMMAND_SETTLEMENT_GRACE_SECONDS as k, UsersApi as l, CronJobsApi as lt, SpacePublicFilesApi as m, createHttpClient as n, ReferencesApi as nt, UiCommandsApi as o, PromptsApi as ot, SpaceClient as p, BoardTrackSchema as q, AppCommerceApi as r, SearchApi as rt, AppsApi as s, ModelsApi as st, CohubHttpClient as t, SessionAccessApi as tt, UserApi as u, ChannelsApi as ut, buildSpacePath as v, ensureRealtimeConnected as w, parseAssistantMessageCommit as x, SessionGenerationStreamClient as y, BoardEffectInputSchema as z };
|