@neta-art/cohub 5.9.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +42 -46
  2. package/dist/board/animation.d.ts +156 -67
  3. package/dist/board/animation.js +162 -277
  4. package/dist/board/codec.js +1 -1
  5. package/dist/board/export/index.d.ts +6 -0
  6. package/dist/board/export/index.js +6 -3
  7. package/dist/board/export/scene.d.ts +6 -0
  8. package/dist/board/export/scene.js +26 -2
  9. package/dist/board/geometry.d.ts +27 -1
  10. package/dist/board/geometry.js +92 -1
  11. package/dist/board/headless/index.d.ts +7 -1
  12. package/dist/board/headless/index.js +6 -2
  13. package/dist/board/index.d.ts +8 -6
  14. package/dist/board/index.js +7 -6
  15. package/dist/board/mutation.d.ts +15 -0
  16. package/dist/board/mutation.js +67 -0
  17. package/dist/board/render/css-color.d.ts +4 -0
  18. package/dist/board/render/css-color.js +36 -0
  19. package/dist/board/render/index.d.ts +2 -1
  20. package/dist/board/render/index.js +2 -1
  21. package/dist/board/render/renderers/base-card-renderer.js +3 -20
  22. package/dist/board/render/themes/clean-theme.js +6 -3
  23. package/dist/chunks/http.d.ts +30 -7
  24. package/dist/chunks/http.js +785 -289
  25. package/dist/chunks/websocket.d.ts +2675 -187
  26. package/dist/http.d.ts +3 -3
  27. package/dist/index.d.ts +158 -299
  28. package/dist/index.js +164 -279
  29. package/dist/protocol/dist/board-authoring.d.ts +1 -0
  30. package/dist/protocol/dist/board-authoring.js +217 -0
  31. package/dist/protocol/dist/board-capability-registry.d.ts +2 -0
  32. package/dist/protocol/dist/board-capability-registry.js +74 -0
  33. package/dist/protocol/dist/board-codec.d.ts +2 -0
  34. package/dist/protocol/dist/board-codec.js +4 -0
  35. package/dist/protocol/dist/board-composition.d.ts +258 -0
  36. package/dist/protocol/dist/board-composition.js +318 -0
  37. package/dist/protocol/dist/board-constants.d.ts +2 -1
  38. package/dist/protocol/dist/board-constants.js +40 -10
  39. package/dist/protocol/dist/board-document.d.ts +28 -2
  40. package/dist/protocol/dist/board-document.js +16 -3
  41. package/dist/protocol/dist/board-node.d.ts +1 -11
  42. package/dist/protocol/dist/board-node.js +1 -81
  43. package/dist/protocol/dist/board-upgrade.d.ts +1 -0
  44. package/dist/protocol/dist/board-upgrade.js +2 -0
  45. package/dist/protocol/dist/board-url.d.ts +2 -1
  46. package/dist/protocol/dist/board-url.js +8 -3
  47. package/dist/protocol/dist/board.d.ts +135 -76
  48. package/dist/protocol/dist/board.js +50 -69
  49. package/dist/protocol/dist/index.d.ts +9 -4
  50. package/dist/protocol/dist/index.js +9 -4
  51. package/dist/types.d.ts +3 -2
  52. package/docs/work-runtime-guide.md +19 -3
  53. package/package.json +3 -2
  54. package/dist/board/nodes.d.ts +0 -113
  55. package/dist/board/nodes.js +0 -154
@@ -1,7 +1,6 @@
1
1
  import { D as getRealtimeBoardRoom, O as getRealtimeSpaceRoom, 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
- import "perfect-freehand";
5
4
  //#region src/apis/channels.ts
6
5
  var ChannelsApi = class {
7
6
  transport;
@@ -454,7 +453,6 @@ const DEFAULT_BOARD_RENDER_LIMITS = {
454
453
  simulationSteps: 1e5
455
454
  };
456
455
  const BOARD_BUILTIN_CLIP_KINDS = [
457
- "motion.keyframes",
458
456
  "motion.path",
459
457
  "draw.reveal",
460
458
  "draw.handwrite",
@@ -463,9 +461,7 @@ const BOARD_BUILTIN_CLIP_KINDS = [
463
461
  "effects.trail",
464
462
  "effects.impact",
465
463
  "effects.flash",
466
- "effects.color",
467
- "camera.pan",
468
- "camera.zoom",
464
+ "camera.focus",
469
465
  "camera.shake"
470
466
  ];
471
467
  const BOARD_BUILTIN_EFFECT_KINDS = ["effects.pulse", "effects.float"];
@@ -475,12 +471,45 @@ function clampBoardStrokeSize(size) {
475
471
  if (!Number.isFinite(size)) return BOARD_CONNECTION_STROKE_SIZE;
476
472
  return Math.min(64, Math.max(1, size));
477
473
  }
478
- const BOARD_BUILTIN_CAPABILITIES = [...BOARD_BUILTIN_CLIP_KINDS.map((id) => ({
479
- kind: "clip",
480
- id,
481
- version: 1,
482
- renderers: ["webgpu", "webgl"]
483
- })), ...BOARD_BUILTIN_EFFECT_KINDS.map((id) => ({
474
+ function clipSchema(id) {
475
+ switch (id) {
476
+ case "motion.path": return { params: { points: {
477
+ coordinateSpace: "world-offset",
478
+ unit: "board"
479
+ } } };
480
+ case "effects.particles": return { params: { bounds: {
481
+ coordinateSpace: "world",
482
+ unit: "board"
483
+ } } };
484
+ case "camera.focus": return { params: {
485
+ focus: {
486
+ coordinateSpace: "world",
487
+ unit: "board"
488
+ },
489
+ padding: {
490
+ coordinateSpace: "screen",
491
+ unit: "css-px"
492
+ },
493
+ minZoom: { unit: "ratio" },
494
+ maxZoom: { unit: "ratio" }
495
+ } };
496
+ case "camera.shake": return { params: { amount: {
497
+ coordinateSpace: "screen-offset",
498
+ unit: "css-px"
499
+ } } };
500
+ default: return;
501
+ }
502
+ }
503
+ const BOARD_BUILTIN_CAPABILITIES = [...BOARD_BUILTIN_CLIP_KINDS.map((id) => {
504
+ const schema = clipSchema(id);
505
+ return {
506
+ kind: "clip",
507
+ id,
508
+ version: 1,
509
+ renderers: ["webgpu", "webgl"],
510
+ ...schema ? { schema } : {}
511
+ };
512
+ }), ...BOARD_BUILTIN_EFFECT_KINDS.map((id) => ({
484
513
  kind: "effect",
485
514
  id,
486
515
  version: 1,
@@ -638,13 +667,529 @@ function createBoardConnection(input) {
638
667
  };
639
668
  }
640
669
  //#endregion
641
- //#region src/realtime.ts
642
- function ensureRealtimeConnected(websocketClient) {
643
- if (websocketClient.state === "open" || websocketClient.state === "connecting" || websocketClient.state === "reconnecting") return;
644
- websocketClient.connect().catch((error) => {
645
- console.error("[CohubClient] Failed to connect realtime websocket:", error);
670
+ //#region ../protocol/dist/board-composition.js
671
+ const idSchema$3 = z.string().min(1).max(160);
672
+ const finiteSchema$3 = z.number().finite();
673
+ const jsonObjectSchema$2 = z.record(z.string(), z.unknown());
674
+ const BoardEasingSchema = z.enum([
675
+ "linear",
676
+ "ease-in-quad",
677
+ "ease-out-quad",
678
+ "ease-in-out-quad",
679
+ "ease-in-cubic",
680
+ "ease-out-cubic",
681
+ "ease-in-out-cubic",
682
+ "ease-out-quart",
683
+ "ease-out-expo"
684
+ ]);
685
+ const BoardAnimationTargetSchema = z.discriminatedUnion("type", [
686
+ z.object({
687
+ type: z.literal("item"),
688
+ itemId: idSchema$3
689
+ }).strict(),
690
+ z.object({
691
+ type: z.literal("effect"),
692
+ effectId: idSchema$3
693
+ }).strict(),
694
+ z.object({ type: z.literal("camera") }).strict(),
695
+ z.object({ type: z.literal("board") }).strict()
696
+ ]);
697
+ const vector2Schema = z.object({
698
+ x: finiteSchema$3,
699
+ y: finiteSchema$3
700
+ }).strict();
701
+ const scaleSchema = z.union([finiteSchema$3.positive(), z.object({
702
+ x: finiteSchema$3.positive(),
703
+ y: finiteSchema$3.positive()
704
+ }).strict()]);
705
+ const BOARD_ANIMATION_CHANNELS = {
706
+ "transform.translation": {
707
+ targets: ["item"],
708
+ value: vector2Schema,
709
+ interpolations: ["linear", "step"],
710
+ coordinateSpace: "world-offset",
711
+ unit: "board"
712
+ },
713
+ "transform.rotation": {
714
+ targets: ["item"],
715
+ value: finiteSchema$3,
716
+ interpolations: ["linear", "step"],
717
+ unit: "radian"
718
+ },
719
+ "transform.scale": {
720
+ targets: ["item"],
721
+ value: scaleSchema,
722
+ interpolations: ["linear", "step"],
723
+ unit: "ratio"
724
+ },
725
+ "style.opacity": {
726
+ targets: ["item"],
727
+ value: finiteSchema$3.min(0).max(1),
728
+ interpolations: ["linear", "step"],
729
+ unit: "ratio"
730
+ }
731
+ };
732
+ const BoardTrackKeyframeSchema = z.object({
733
+ time: finiteSchema$3.nonnegative(),
734
+ value: z.unknown(),
735
+ easing: BoardEasingSchema.optional()
736
+ }).strict();
737
+ const BoardTrackSchema = z.object({
738
+ id: idSchema$3,
739
+ target: BoardAnimationTargetSchema,
740
+ channel: z.string().min(1).max(160),
741
+ channelVersion: z.number().int().positive().default(1),
742
+ interpolation: z.enum(["linear", "step"]).default("linear"),
743
+ fill: z.enum([
744
+ "none",
745
+ "backwards",
746
+ "forwards",
747
+ "both"
748
+ ]).default("none"),
749
+ keyframes: z.array(BoardTrackKeyframeSchema).min(1).max(1e5),
750
+ metadata: jsonObjectSchema$2.default({})
751
+ }).strict().superRefine((track, context) => {
752
+ let previous = -1;
753
+ for (const [index, keyframe] of track.keyframes.entries()) {
754
+ if (keyframe.time <= previous) context.addIssue({
755
+ code: "custom",
756
+ message: "keyframe times must be strictly increasing",
757
+ path: [
758
+ "keyframes",
759
+ index,
760
+ "time"
761
+ ]
762
+ });
763
+ previous = keyframe.time;
764
+ }
765
+ const channel = BOARD_ANIMATION_CHANNELS[track.channel];
766
+ if (!channel) {
767
+ if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+){2,}$/.test(track.channel)) context.addIssue({
768
+ code: "custom",
769
+ message: `unknown channel: ${track.channel}`,
770
+ path: ["channel"]
771
+ });
772
+ return;
773
+ }
774
+ if (!channel.targets.includes(track.target.type)) context.addIssue({
775
+ code: "custom",
776
+ message: `${track.channel} cannot target ${track.target.type}`,
777
+ path: ["target"]
646
778
  });
779
+ if (!channel.interpolations.includes(track.interpolation)) context.addIssue({
780
+ code: "custom",
781
+ message: `${track.channel} does not support ${track.interpolation} interpolation`,
782
+ path: ["interpolation"]
783
+ });
784
+ for (const [index, keyframe] of track.keyframes.entries()) {
785
+ const parsed = channel.value.safeParse(keyframe.value);
786
+ if (!parsed.success) context.addIssue({
787
+ code: "custom",
788
+ message: parsed.error.issues[0]?.message ?? `invalid value for ${track.channel}`,
789
+ path: [
790
+ "keyframes",
791
+ index,
792
+ "value"
793
+ ]
794
+ });
795
+ }
796
+ });
797
+ const extensionKindSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
798
+ const BoardProceduralClipSchema = z.object({
799
+ id: idSchema$3,
800
+ kind: extensionKindSchema,
801
+ kindVersion: z.number().int().positive(),
802
+ target: BoardAnimationTargetSchema,
803
+ start: finiteSchema$3.nonnegative(),
804
+ duration: finiteSchema$3.positive(),
805
+ layer: z.enum([
806
+ "behind",
807
+ "content",
808
+ "front",
809
+ "screen"
810
+ ]).default("content"),
811
+ fill: z.enum([
812
+ "none",
813
+ "backwards",
814
+ "forwards",
815
+ "both"
816
+ ]).default("none"),
817
+ easing: BoardEasingSchema.default("linear"),
818
+ params: jsonObjectSchema$2.default({}),
819
+ assetRefs: z.array(z.object({
820
+ type: z.enum(["space-file", "extension"]),
821
+ ref: z.string().min(1).max(4096),
822
+ digest: z.string().min(16).max(160).optional()
823
+ }).strict()).default([]),
824
+ seed: idSchema$3,
825
+ metadata: jsonObjectSchema$2.default({})
826
+ }).strict();
827
+ const BoardTimelineMarkerSchema = z.object({
828
+ id: idSchema$3,
829
+ time: finiteSchema$3.nonnegative(),
830
+ duration: finiteSchema$3.nonnegative().optional(),
831
+ metadata: jsonObjectSchema$2.default({})
832
+ }).strict();
833
+ const BoardTimelineSchema = z.object({
834
+ duration: finiteSchema$3.nonnegative(),
835
+ tracks: z.array(BoardTrackSchema).max(5e4).default([]),
836
+ clips: z.array(BoardProceduralClipSchema).max(5e4).default([]),
837
+ markers: z.array(BoardTimelineMarkerSchema).max(1e4).default([])
838
+ }).strict().superRefine((timeline, context) => {
839
+ const ids = /* @__PURE__ */ new Set();
840
+ const channels = /* @__PURE__ */ new Set();
841
+ for (const [index, track] of timeline.tracks.entries()) {
842
+ if (ids.has(track.id)) context.addIssue({
843
+ code: "custom",
844
+ message: `duplicate timeline id: ${track.id}`,
845
+ path: [
846
+ "tracks",
847
+ index,
848
+ "id"
849
+ ]
850
+ });
851
+ ids.add(track.id);
852
+ const targetId = track.target.type === "item" ? track.target.itemId : track.target.type === "effect" ? track.target.effectId : track.target.type;
853
+ const key = `${track.target.type}:${targetId}:${track.channel}`;
854
+ if (channels.has(key)) context.addIssue({
855
+ code: "custom",
856
+ message: `multiple tracks control ${key}`,
857
+ path: [
858
+ "tracks",
859
+ index,
860
+ "channel"
861
+ ]
862
+ });
863
+ channels.add(key);
864
+ for (const [keyframeIndex, keyframe] of track.keyframes.entries()) if (keyframe.time > timeline.duration) context.addIssue({
865
+ code: "custom",
866
+ message: "keyframe exceeds timeline duration",
867
+ path: [
868
+ "tracks",
869
+ index,
870
+ "keyframes",
871
+ keyframeIndex,
872
+ "time"
873
+ ]
874
+ });
875
+ }
876
+ for (const [index, clip] of timeline.clips.entries()) {
877
+ if (ids.has(clip.id)) context.addIssue({
878
+ code: "custom",
879
+ message: `duplicate timeline id: ${clip.id}`,
880
+ path: [
881
+ "clips",
882
+ index,
883
+ "id"
884
+ ]
885
+ });
886
+ ids.add(clip.id);
887
+ if (clip.start + clip.duration > timeline.duration) context.addIssue({
888
+ code: "custom",
889
+ message: "clip exceeds timeline duration",
890
+ path: [
891
+ "clips",
892
+ index,
893
+ "duration"
894
+ ]
895
+ });
896
+ }
897
+ for (const [index, marker] of timeline.markers.entries()) {
898
+ if (ids.has(marker.id)) context.addIssue({
899
+ code: "custom",
900
+ message: `duplicate timeline id: ${marker.id}`,
901
+ path: [
902
+ "markers",
903
+ index,
904
+ "id"
905
+ ]
906
+ });
907
+ ids.add(marker.id);
908
+ if (marker.time + (marker.duration ?? 0) > timeline.duration) context.addIssue({
909
+ code: "custom",
910
+ message: "marker exceeds timeline duration",
911
+ path: ["markers", index]
912
+ });
913
+ }
914
+ });
915
+ const BoardCompositionPlaybackSchema = z.object({
916
+ loop: z.boolean().default(false),
917
+ endBehavior: z.enum(["hold", "reset"]).default("hold"),
918
+ reducedMotion: z.discriminatedUnion("mode", [
919
+ z.object({ mode: z.literal("base") }).strict(),
920
+ z.object({
921
+ mode: z.literal("time"),
922
+ time: finiteSchema$3.nonnegative()
923
+ }).strict(),
924
+ z.object({
925
+ mode: z.literal("marker"),
926
+ markerId: idSchema$3
927
+ }).strict()
928
+ ]).default({ mode: "base" })
929
+ }).strict();
930
+ const compositionFields = {
931
+ id: idSchema$3,
932
+ name: z.string().min(1).max(255),
933
+ timeline: BoardTimelineSchema,
934
+ playback: BoardCompositionPlaybackSchema.default({
935
+ loop: false,
936
+ endBehavior: "hold",
937
+ reducedMotion: { mode: "base" }
938
+ }),
939
+ metadata: jsonObjectSchema$2.default({})
940
+ };
941
+ function validateCompositionFallback(composition, context) {
942
+ const fallback = composition.playback.reducedMotion;
943
+ if (fallback.mode === "time" && fallback.time > composition.timeline.duration) context.addIssue({
944
+ code: "custom",
945
+ message: "reduced-motion time exceeds timeline duration",
946
+ path: [
947
+ "playback",
948
+ "reducedMotion",
949
+ "time"
950
+ ]
951
+ });
952
+ if (fallback.mode === "marker" && !composition.timeline.markers.some((marker) => marker.id === fallback.markerId)) context.addIssue({
953
+ code: "custom",
954
+ message: `marker does not exist: ${fallback.markerId}`,
955
+ path: [
956
+ "playback",
957
+ "reducedMotion",
958
+ "markerId"
959
+ ]
960
+ });
961
+ }
962
+ const BoardCompositionInputSchema = z.object(compositionFields).strict().superRefine(validateCompositionFallback);
963
+ const BoardCompositionSchema = z.object({
964
+ ...compositionFields,
965
+ revision: z.number().int().nonnegative().default(0)
966
+ }).strict().superRefine(validateCompositionFallback);
967
+ /** Accept inspect/get output as apply input while stripping server-owned revision. */
968
+ function parseBoardCompositionInput(value) {
969
+ if (value && typeof value === "object" && !Array.isArray(value)) {
970
+ const { revision: _revision, ...input } = value;
971
+ return BoardCompositionInputSchema.parse(input);
972
+ }
973
+ return BoardCompositionInputSchema.parse(value);
647
974
  }
975
+ const BOARD_ANIMATION_CHANNEL_CAPABILITIES = Object.entries(BOARD_ANIMATION_CHANNELS).map(([id, definition]) => ({
976
+ id,
977
+ version: 1,
978
+ targets: definition.targets,
979
+ interpolations: definition.interpolations,
980
+ ..."coordinateSpace" in definition ? { coordinateSpace: definition.coordinateSpace } : {},
981
+ ..."unit" in definition ? { unit: definition.unit } : {},
982
+ valueSchema: z.toJSONSchema(definition.value)
983
+ }));
984
+ //#endregion
985
+ //#region ../protocol/dist/board-authoring.js
986
+ const BOARD_COLOR_IDS$1 = [
987
+ "brand",
988
+ "neutral",
989
+ "black",
990
+ "white",
991
+ "blue",
992
+ "green",
993
+ "amber",
994
+ "violet",
995
+ "rose"
996
+ ];
997
+ const BOARD_GEO_KINDS$1 = [
998
+ "rectangle",
999
+ "rounded",
1000
+ "ellipse",
1001
+ "diamond",
1002
+ "triangle"
1003
+ ];
1004
+ const idSchema$2 = z.string().min(1).max(160);
1005
+ const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
1006
+ const finiteSchema$2 = z.number().finite();
1007
+ const extensionTypeSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
1008
+ const BoardAuthoringFrameSchema = z.object({
1009
+ x: finiteSchema$2,
1010
+ y: finiteSchema$2,
1011
+ width: finiteSchema$2.positive(),
1012
+ height: finiteSchema$2.positive(),
1013
+ rotation: finiteSchema$2.default(0)
1014
+ }).strict();
1015
+ const pointSchema$1 = z.object({
1016
+ x: finiteSchema$2,
1017
+ y: finiteSchema$2,
1018
+ p: finiteSchema$2.min(0).max(1).default(.5)
1019
+ }).strict();
1020
+ const worldPointSchema$1 = z.object({
1021
+ x: finiteSchema$2,
1022
+ y: finiteSchema$2
1023
+ }).strict();
1024
+ const cropSchema = z.object({
1025
+ x: finiteSchema$2.min(0).max(1),
1026
+ y: finiteSchema$2.min(0).max(1),
1027
+ w: finiteSchema$2.min(0).max(1),
1028
+ h: finiteSchema$2.min(0).max(1)
1029
+ }).strict();
1030
+ const BoardItemStyleSchema$1 = z.object({
1031
+ color: z.enum(BOARD_COLOR_IDS$1).optional(),
1032
+ strokeWidth: finiteSchema$2.min(1).max(64).optional(),
1033
+ fillOpacity: finiteSchema$2.min(0).max(1).optional()
1034
+ }).strict();
1035
+ const sourceSnapshotSchema = z.record(z.string(), z.unknown());
1036
+ function isSafeBoardSourcePath(value) {
1037
+ return value.length > 0 && value.length <= 4096 && !value.startsWith("/") && !value.startsWith("\\") && !value.includes("\\") && value.split("/").every((part) => part.length > 0 && part !== "." && part !== "..");
1038
+ }
1039
+ const BoardItemSourceSchema = z.object({
1040
+ kind: z.literal("space-file"),
1041
+ path: z.string().refine(isSafeBoardSourcePath, "source path must be a safe relative Board file path"),
1042
+ snapshot: sourceSnapshotSchema.optional()
1043
+ }).strict();
1044
+ const baseFields = {
1045
+ id: idSchema$2,
1046
+ frame: BoardAuthoringFrameSchema,
1047
+ parentId: idSchema$2.nullable().optional(),
1048
+ locked: z.boolean().optional(),
1049
+ metadata: jsonObjectSchema$1.optional()
1050
+ };
1051
+ const styledBaseFields = {
1052
+ ...baseFields,
1053
+ style: BoardItemStyleSchema$1.optional()
1054
+ };
1055
+ const BoardTextAuthoringItemSchema = z.object({
1056
+ ...styledBaseFields,
1057
+ type: z.literal("text"),
1058
+ props: z.object({
1059
+ text: z.string().default(""),
1060
+ fontSize: finiteSchema$2.min(2).max(512).default(24)
1061
+ }).strict()
1062
+ }).strict();
1063
+ const BoardGeoAuthoringItemSchema = z.object({
1064
+ ...styledBaseFields,
1065
+ type: z.literal("geo"),
1066
+ props: z.object({
1067
+ shape: z.enum(BOARD_GEO_KINDS$1).default("rectangle"),
1068
+ text: z.string().default("")
1069
+ }).strict()
1070
+ }).strict();
1071
+ const BoardDrawAuthoringItemSchema = z.object({
1072
+ ...styledBaseFields,
1073
+ type: z.literal("draw"),
1074
+ props: z.object({ points: z.array(pointSchema$1).min(1) }).strict()
1075
+ }).strict();
1076
+ const BoardArrowAuthoringItemSchema = z.object({
1077
+ ...styledBaseFields,
1078
+ type: z.literal("arrow"),
1079
+ props: z.object({
1080
+ start: worldPointSchema$1,
1081
+ end: worldPointSchema$1,
1082
+ bend: finiteSchema$2.min(-.85).max(.85).default(0),
1083
+ arrowStart: z.boolean().default(false),
1084
+ arrowEnd: z.boolean().default(true),
1085
+ label: z.string().default("")
1086
+ }).strict()
1087
+ }).strict();
1088
+ const BoardFrameAuthoringItemSchema = z.object({
1089
+ ...styledBaseFields,
1090
+ type: z.literal("frame"),
1091
+ props: z.object({ label: z.string().default("Frame") }).strict()
1092
+ }).strict();
1093
+ const fileBackedFields = {
1094
+ ...baseFields,
1095
+ style: z.object({}).strict().optional(),
1096
+ source: BoardItemSourceSchema
1097
+ };
1098
+ const builtinItemSchemas = [
1099
+ BoardTextAuthoringItemSchema,
1100
+ BoardGeoAuthoringItemSchema,
1101
+ BoardDrawAuthoringItemSchema,
1102
+ BoardArrowAuthoringItemSchema,
1103
+ BoardFrameAuthoringItemSchema,
1104
+ z.object({
1105
+ ...fileBackedFields,
1106
+ type: z.literal("image"),
1107
+ props: z.object({ crop: cropSchema.optional() }).strict()
1108
+ }).strict(),
1109
+ z.object({
1110
+ ...fileBackedFields,
1111
+ type: z.literal("video"),
1112
+ props: z.object({}).strict()
1113
+ }).strict(),
1114
+ z.object({
1115
+ ...fileBackedFields,
1116
+ type: z.literal("audio"),
1117
+ props: z.object({}).strict()
1118
+ }).strict(),
1119
+ z.object({
1120
+ ...fileBackedFields,
1121
+ type: z.literal("file"),
1122
+ props: z.object({}).strict()
1123
+ }).strict(),
1124
+ z.object({
1125
+ ...baseFields,
1126
+ type: z.literal("task"),
1127
+ props: z.object({
1128
+ taskRunId: z.string().min(1),
1129
+ snapshot: z.record(z.string(), z.unknown())
1130
+ }).strict(),
1131
+ style: z.object({}).strict().optional()
1132
+ }).strict()
1133
+ ];
1134
+ const BoardBuiltinAuthoringItemSchema = z.discriminatedUnion("type", builtinItemSchemas);
1135
+ const BoardExtensionAuthoringItemSchema = z.object({
1136
+ ...baseFields,
1137
+ type: extensionTypeSchema,
1138
+ kindVersion: z.number().int().positive(),
1139
+ props: jsonObjectSchema$1,
1140
+ style: jsonObjectSchema$1.optional(),
1141
+ source: z.object({
1142
+ kind: z.string().min(1).max(80),
1143
+ ref: z.string().min(1).max(4096),
1144
+ snapshot: jsonObjectSchema$1.optional()
1145
+ }).strict().optional()
1146
+ }).strict();
1147
+ const BoardAuthoringItemSchema = z.union([BoardBuiltinAuthoringItemSchema, BoardExtensionAuthoringItemSchema]);
1148
+ const framePatchSchema = z.object({
1149
+ x: finiteSchema$2.optional(),
1150
+ y: finiteSchema$2.optional(),
1151
+ width: finiteSchema$2.positive().optional(),
1152
+ height: finiteSchema$2.positive().optional(),
1153
+ rotation: finiteSchema$2.optional()
1154
+ }).strict();
1155
+ /** JSON Merge Patch semantics, constrained to the stable Item envelope. */
1156
+ const BoardItemPatchSchema = z.object({
1157
+ frame: framePatchSchema.optional(),
1158
+ parentId: idSchema$2.nullable().optional(),
1159
+ locked: z.boolean().nullable().optional(),
1160
+ props: jsonObjectSchema$1.optional(),
1161
+ style: jsonObjectSchema$1.nullable().optional(),
1162
+ source: jsonObjectSchema$1.nullable().optional(),
1163
+ metadata: jsonObjectSchema$1.nullable().optional()
1164
+ }).strict().refine((patch) => Object.keys(patch).length > 0, "item patch is empty");
1165
+ const BoardSemanticCommandSchema = z.discriminatedUnion("type", [
1166
+ z.object({
1167
+ type: z.literal("item.create"),
1168
+ item: BoardAuthoringItemSchema
1169
+ }).strict(),
1170
+ z.object({
1171
+ type: z.literal("item.patch"),
1172
+ itemId: idSchema$2,
1173
+ patch: BoardItemPatchSchema
1174
+ }).strict(),
1175
+ z.object({
1176
+ type: z.literal("item.replace"),
1177
+ itemId: idSchema$2,
1178
+ item: BoardAuthoringItemSchema
1179
+ }).strict(),
1180
+ z.object({
1181
+ type: z.literal("item.delete"),
1182
+ itemId: idSchema$2,
1183
+ 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();
648
1193
  //#endregion
649
1194
  //#region ../protocol/dist/board.js
650
1195
  const BOARD_DOCUMENT_KIND = "cohub.board";
@@ -659,62 +1204,59 @@ z.object({
659
1204
  boardId: z.string().uuid(),
660
1205
  title: z.string().min(1).max(255)
661
1206
  });
662
- const BoardTargetSchema = z.discriminatedUnion("type", [
1207
+ z.object({
1208
+ centerX: finiteSchema$1,
1209
+ centerY: finiteSchema$1,
1210
+ zoom: finiteSchema$1.positive()
1211
+ });
1212
+ const BoardCameraFocusSchema = z.discriminatedUnion("type", [
663
1213
  z.object({
664
- type: z.literal("node"),
665
- nodeId: idSchema$1
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
+ })
666
1221
  }),
667
1222
  z.object({
668
- type: z.literal("effect"),
669
- effectId: idSchema$1
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)
670
1229
  }),
671
- z.object({ type: z.literal("board") }),
672
- z.object({ type: z.literal("camera") })
1230
+ z.object({
1231
+ type: z.literal("frame"),
1232
+ frameId: idSchema$1
1233
+ })
673
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
+ });
674
1248
  const BoardAssetRefSchema = z.object({
675
1249
  type: z.enum(["space-file", "extension"]),
676
1250
  ref: z.string().min(1).max(4096),
677
1251
  digest: z.string().min(16).max(160).optional()
678
1252
  });
679
- const BoardKeyframeSchema = z.object({
680
- at: finiteSchema$1.nonnegative(),
681
- value: z.unknown(),
682
- easing: z.string().min(1).max(80).optional()
683
- });
684
- const BoardClipSchema = z.object({
685
- id: idSchema$1,
686
- sequenceId: idSchema$1,
687
- kind: extensionIdSchema,
688
- kindVersion: z.number().int().positive(),
689
- target: BoardTargetSchema,
690
- start: finiteSchema$1.nonnegative(),
691
- duration: finiteSchema$1.positive(),
692
- layer: z.enum([
693
- "behind",
694
- "content",
695
- "front",
696
- "screen"
697
- ]).default("content"),
698
- fill: z.enum([
699
- "none",
700
- "backwards",
701
- "forwards",
702
- "both"
703
- ]).default("none"),
704
- easing: z.string().min(1).max(80).default("linear"),
705
- params: jsonObjectSchema.default({}),
706
- keyframes: z.array(BoardKeyframeSchema).default([]),
707
- assetRefs: z.array(BoardAssetRefSchema).default([]),
708
- seed: z.string().min(1).max(160),
709
- metadata: jsonObjectSchema.default({})
710
- });
711
1253
  const BoardEffectSchema = z.object({
712
1254
  id: idSchema$1,
713
1255
  boardId: z.string().uuid(),
714
1256
  target: z.discriminatedUnion("type", [z.object({
715
- type: z.literal("node"),
716
- nodeId: idSchema$1
717
- }), z.object({ type: z.literal("board") })]),
1257
+ type: z.literal("item"),
1258
+ itemId: idSchema$1
1259
+ }).strict(), z.object({ type: z.literal("board") }).strict()]),
718
1260
  kind: extensionIdSchema,
719
1261
  kindVersion: z.number().int().positive(),
720
1262
  enabled: z.boolean().default(true),
@@ -739,17 +1281,7 @@ const BoardEffectSchema = z.object({
739
1281
  metadata: jsonObjectSchema.default({}),
740
1282
  revision: z.number().int().nonnegative()
741
1283
  });
742
- const BoardSequenceSchema = z.object({
743
- id: idSchema$1,
744
- boardId: z.string().uuid(),
745
- name: z.string().min(1).max(255),
746
- duration: finiteSchema$1.nonnegative(),
747
- seed: z.string().min(1).max(160),
748
- restPose: jsonObjectSchema.default({}),
749
- metadata: jsonObjectSchema.default({}),
750
- revision: z.number().int().nonnegative()
751
- });
752
- const BoardNodeInputSchema = z.object({
1284
+ z.object({
753
1285
  nodeId: idSchema$1,
754
1286
  type: z.string().min(1).max(40),
755
1287
  parentId: idSchema$1.nullable(),
@@ -772,27 +1304,20 @@ z.object({
772
1304
  mutationId: z.string().max(128).optional(),
773
1305
  title: z.string().min(1).max(255).optional(),
774
1306
  metadata: jsonObjectSchema.optional(),
775
- nodes: z.array(BoardNodeInputSchema).max(5e4).optional(),
1307
+ items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
776
1308
  connections: z.array(BoardConnectionSchema).max(5e4).optional(),
777
1309
  effects: z.array(BoardEffectSchema.omit({
778
1310
  boardId: true,
779
1311
  revision: true
780
1312
  })).optional(),
781
- sequences: z.array(z.object({
782
- sequence: BoardSequenceSchema.omit({
783
- boardId: true,
784
- revision: true
785
- }),
786
- clips: z.array(BoardClipSchema.omit({ sequenceId: true }))
787
- })).optional()
1313
+ compositions: z.array(BoardCompositionInputSchema).optional()
788
1314
  });
789
1315
  z.object({
790
1316
  include: z.array(z.enum([
791
1317
  "nodes",
792
1318
  "connections",
793
1319
  "effects",
794
- "sequences",
795
- "clips",
1320
+ "compositions",
796
1321
  "playback"
797
1322
  ])).optional(),
798
1323
  viewport: z.object({
@@ -804,10 +1329,9 @@ z.object({
804
1329
  });
805
1330
  /** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
806
1331
  const BoardPlaybackPolicySchema = z.object({
807
- sequenceId: idSchema$1,
1332
+ compositionId: idSchema$1,
808
1333
  /** Delay before the first local playback after opening the Board, in milliseconds. */
809
- delayMs: finiteSchema$1.nonnegative().default(0),
810
- loop: z.boolean().default(false)
1334
+ delayMs: finiteSchema$1.nonnegative().default(0)
811
1335
  });
812
1336
  function parseBoardPlaybackPolicy(metadata) {
813
1337
  const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
@@ -817,7 +1341,7 @@ z.discriminatedUnion("type", [
817
1341
  z.object({
818
1342
  commandId: idSchema$1,
819
1343
  type: z.literal("play"),
820
- sequenceId: idSchema$1,
1344
+ compositionId: idSchema$1,
821
1345
  position: finiteSchema$1.nonnegative().optional(),
822
1346
  timeScale: finiteSchema$1.positive().max(4).optional(),
823
1347
  shared: z.boolean().optional(),
@@ -840,27 +1364,77 @@ z.discriminatedUnion("type", [
840
1364
  playbackId: z.string().uuid()
841
1365
  })
842
1366
  ]);
843
- const BoardContentKindSchema = z.enum([
844
- "text",
845
- "image",
846
- "video",
847
- "audio",
848
- "file",
849
- "json",
850
- "collection"
851
- ]);
852
- const BoardPortSchema = z.object({
853
- id: z.string().min(1).max(120),
854
- kind: BoardContentKindSchema,
855
- role: z.string().min(1).max(80).optional(),
856
- required: z.boolean().optional(),
857
- multiple: z.boolean().optional(),
858
- maxItems: z.number().int().positive().optional()
859
- }).strict();
860
- z.object({
861
- inputs: z.array(BoardPortSchema),
862
- outputs: z.array(BoardPortSchema)
863
- }).strict();
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
+ }
864
1438
  //#endregion
865
1439
  //#region ../protocol/dist/board-url.js
866
1440
  const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
@@ -916,11 +1490,16 @@ function isBlockedIpv6(host) {
916
1490
  if ((first & 65280) === 65280) return true;
917
1491
  return parts[0] === "2001" && parts[1] === "0db8";
918
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
+ }
919
1498
  function isBlockedHost(hostname) {
920
1499
  const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
921
1500
  if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
922
- if (parseIpv4(host)) return isBlockedIpv4(host);
923
- return host.includes(":") && isBlockedIpv6(host);
1501
+ if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
1502
+ return false;
924
1503
  }
925
1504
  /**
926
1505
  * Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
@@ -974,7 +1553,20 @@ const BoardAppearanceSchema = z.object({
974
1553
  "custom"
975
1554
  ]).default("dots"),
976
1555
  color: z.string().optional(),
977
- imageUrl: z.string().url().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()
978
1570
  }).default({ kind: "solid" }),
979
1571
  grid: z.object({
980
1572
  visible: z.boolean().default(false),
@@ -1573,6 +2165,27 @@ function validateBoardNodeInput(node, path = "node") {
1573
2165
  }
1574
2166
  return [];
1575
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();
1576
2189
  //#endregion
1577
2190
  //#region ../protocol/dist/realtime/board-awareness.js
1578
2191
  const idSchema = z.string().min(1).max(160);
@@ -2033,186 +2646,12 @@ const buildWorkComposerChipClear = (key) => ({
2033
2646
  key
2034
2647
  });
2035
2648
  //#endregion
2036
- //#region src/board/core/draw-geometry.ts
2037
- /** Radius of a sample in world units given the stroke size and pressure. */
2038
- function sampleRadius(size, pressure) {
2039
- const clamped = Math.min(1, Math.max(0, pressure));
2040
- return Math.max(.5, size / 2 * (.5 + clamped));
2041
- }
2042
- /** Axis-aligned bounds of a stroke in its local space, padded by stroke width. */
2043
- function computeDrawBounds(points, size) {
2044
- if (points.length === 0) return {
2045
- x: 0,
2046
- y: 0,
2047
- width: 1,
2048
- height: 1
2049
- };
2050
- let minX = Number.POSITIVE_INFINITY;
2051
- let minY = Number.POSITIVE_INFINITY;
2052
- let maxX = Number.NEGATIVE_INFINITY;
2053
- let maxY = Number.NEGATIVE_INFINITY;
2054
- for (const point of points) {
2055
- const r = sampleRadius(size, point.p);
2056
- minX = Math.min(minX, point.x - r);
2057
- minY = Math.min(minY, point.y - r);
2058
- maxX = Math.max(maxX, point.x + r);
2059
- maxY = Math.max(maxY, point.y + r);
2060
- }
2061
- return {
2062
- x: minX,
2063
- y: minY,
2064
- width: Math.max(1, maxX - minX),
2065
- height: Math.max(1, maxY - minY)
2066
- };
2067
- }
2068
- //#endregion
2069
- //#region src/board/nodes.ts
2070
- var BoardInputError = class extends Error {
2071
- code = "INVALID_BOARD_NODE";
2072
- diagnostics;
2073
- body;
2074
- constructor(diagnostics) {
2075
- const message = diagnostics[0]?.message ?? "Invalid Board node";
2076
- super(message);
2077
- this.name = "BoardInputError";
2078
- this.diagnostics = diagnostics;
2079
- this.body = {
2080
- code: this.code,
2081
- message,
2082
- diagnostics
2083
- };
2084
- }
2085
- };
2086
- function baseNode(spec, frame) {
2087
- return {
2088
- nodeId: spec.id,
2089
- type: spec.type,
2090
- parentId: spec.parentId ?? null,
2091
- orderKey: spec.orderKey ?? null,
2092
- x: frame.x,
2093
- y: frame.y,
2094
- width: frame.width,
2095
- height: frame.height,
2096
- rotation: frame.rotation ?? 0,
2097
- refKind: null,
2098
- refPath: null,
2099
- refUrl: null,
2100
- view: {},
2101
- style: spec.style ?? {},
2102
- data: {}
2103
- };
2104
- }
2105
- function arrowFrame(start, end) {
2106
- const padding = 16;
2107
- return {
2108
- x: Math.min(start.x, end.x) - padding,
2109
- y: Math.min(start.y, end.y) - padding,
2110
- width: Math.max(1, Math.abs(end.x - start.x) + padding * 2),
2111
- height: Math.max(1, Math.abs(end.y - start.y) + padding * 2)
2112
- };
2113
- }
2114
- /**
2115
- * Create one validated wire node from semantic Board input.
2116
- *
2117
- * Box nodes take an explicit world-space frame. Draw samples and arrow endpoints
2118
- * take world coordinates; the builder derives their frame and local storage form.
2119
- */
2120
- function createBoardNode(spec) {
2121
- let node;
2122
- if (spec.type === "draw") {
2123
- const size = spec.size ?? 4;
2124
- const worldPoints = spec.points.map((point) => ({
2125
- x: point.x,
2126
- y: point.y,
2127
- p: point.p ?? .5
2128
- }));
2129
- const bounds = computeDrawBounds(worldPoints, size);
2130
- node = baseNode(spec, bounds);
2131
- node.data = {
2132
- points: worldPoints.map((point) => ({
2133
- x: point.x - bounds.x,
2134
- y: point.y - bounds.y,
2135
- p: point.p
2136
- })),
2137
- color: spec.color ?? "brand",
2138
- size
2139
- };
2140
- } else if (spec.type === "arrow") {
2141
- node = baseNode(spec, arrowFrame(spec.start, spec.end));
2142
- node.data = {
2143
- start: spec.start,
2144
- end: spec.end,
2145
- bend: spec.bend ?? 0,
2146
- color: spec.color ?? "brand",
2147
- size: spec.size ?? 2.5,
2148
- arrowStart: spec.arrowStart ?? false,
2149
- arrowEnd: spec.arrowEnd ?? true,
2150
- label: spec.label ?? ""
2151
- };
2152
- } else {
2153
- node = baseNode(spec, spec.frame);
2154
- switch (spec.type) {
2155
- case "text":
2156
- node.data = {
2157
- text: spec.text ?? "",
2158
- color: spec.color ?? "neutral",
2159
- fontSize: spec.fontSize ?? 24
2160
- };
2161
- break;
2162
- case "geo":
2163
- node.data = {
2164
- geo: spec.geo ?? "rectangle",
2165
- text: spec.text ?? "",
2166
- color: spec.color ?? "brand",
2167
- fillOpacity: spec.fillOpacity ?? 0
2168
- };
2169
- break;
2170
- case "frame":
2171
- node.data = {
2172
- label: spec.label ?? "Frame",
2173
- color: spec.color ?? "neutral"
2174
- };
2175
- break;
2176
- case "image":
2177
- node.refKind = "space_file";
2178
- node.refPath = spec.path;
2179
- node.view = spec.snapshot ?? {};
2180
- node.data = spec.crop ? { crop: spec.crop } : {};
2181
- break;
2182
- case "video":
2183
- case "audio":
2184
- node.refKind = "space_file";
2185
- node.refPath = spec.path;
2186
- node.view = spec.snapshot ?? {};
2187
- break;
2188
- case "file":
2189
- node.refKind = "space_file";
2190
- node.refPath = spec.path;
2191
- node.view = spec.snapshot ?? {};
2192
- break;
2193
- case "task":
2194
- node.view = spec.snapshot;
2195
- node.data = { taskRunId: spec.taskRunId };
2196
- break;
2197
- }
2198
- }
2199
- assertBoardNodes([node]);
2200
- return node;
2201
- }
2202
- function validateBoardNodes(nodes, path = "nodes") {
2203
- return nodes.flatMap((node, index) => validateBoardNodeInput(node, `${path}.${index}`));
2204
- }
2205
- function assertBoardNodes(nodes, path = "nodes") {
2206
- const diagnostics = validateBoardNodes(nodes, path);
2207
- if (diagnostics.length > 0) throw new BoardInputError(diagnostics);
2208
- }
2209
- function assertBoardTransactionNodeCreates(operations) {
2210
- const diagnostics = operations.flatMap((operation, index) => {
2211
- if (operation.type !== "node.create") return [];
2212
- const payload = operation.payload;
2213
- return payload.node ? validateBoardNodeInput(payload.node, `operations.${index}.payload.node`) : [];
2649
+ //#region src/realtime.ts
2650
+ function ensureRealtimeConnected(websocketClient) {
2651
+ if (websocketClient.state === "open" || websocketClient.state === "connecting" || websocketClient.state === "reconnecting") return;
2652
+ websocketClient.connect().catch((error) => {
2653
+ console.error("[CohubClient] Failed to connect realtime websocket:", error);
2214
2654
  });
2215
- if (diagnostics.length > 0) throw new BoardInputError(diagnostics);
2216
2655
  }
2217
2656
  //#endregion
2218
2657
  //#region src/session-patch-reducer.ts
@@ -4265,6 +4704,51 @@ var BoardClient = class {
4265
4704
  capabilities(customFetch) {
4266
4705
  return this.boards.capabilities(this.id, customFetch);
4267
4706
  }
4707
+ summary(customFetch) {
4708
+ return this.boards.summary(this.id, customFetch);
4709
+ }
4710
+ authoring(customFetch) {
4711
+ return this.boards.authoring(this.id, customFetch);
4712
+ }
4713
+ mutateSemantic(input) {
4714
+ return this.boards.mutateSemantic(this.id, {
4715
+ ...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
+ }
4268
4752
  validate(transaction) {
4269
4753
  return this.boards.validate({
4270
4754
  ...transaction,
@@ -4405,7 +4889,7 @@ var SpaceBoardsApi = class {
4405
4889
  return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
4406
4890
  }
4407
4891
  create(input) {
4408
- assertBoardNodes(input.nodes ?? []);
4892
+ for (const item of input.items ?? []) BoardAuthoringItemSchema.parse(item);
4409
4893
  return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
4410
4894
  method: "POST",
4411
4895
  headers: { "Content-Type": "application/json" },
@@ -4419,6 +4903,19 @@ var SpaceBoardsApi = class {
4419
4903
  const query = params.toString();
4420
4904
  return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}${query ? `?${query}` : ""}`, { fetch: customFetch });
4421
4905
  }
4906
+ authoring(boardId, customFetch) {
4907
+ return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring`, { fetch: customFetch });
4908
+ }
4909
+ mutateSemantic(boardId, mutation) {
4910
+ return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/mutations`, {
4911
+ method: "POST",
4912
+ headers: { "Content-Type": "application/json" },
4913
+ body: JSON.stringify(mutation)
4914
+ });
4915
+ }
4916
+ summary(boardId, customFetch) {
4917
+ return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/summary`, { fetch: customFetch });
4918
+ }
4422
4919
  capabilities(boardId, customFetch) {
4423
4920
  return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/capabilities`, { fetch: customFetch });
4424
4921
  }
@@ -4430,7 +4927,6 @@ var SpaceBoardsApi = class {
4430
4927
  });
4431
4928
  }
4432
4929
  async apply(transaction) {
4433
- assertBoardTransactionNodeCreates(transaction.operations);
4434
4930
  try {
4435
4931
  return await this.transport.request(`/api/spaces/${this.spaceId}/boards/${transaction.boardId}/transactions`, {
4436
4932
  method: "POST",
@@ -5221,4 +5717,4 @@ var CohubHttpClient = class {
5221
5717
  };
5222
5718
  const createHttpClient = (options) => new CohubHttpClient(options);
5223
5719
  //#endregion
5224
- export { parseUsername as $, WORK_SURFACE_READY_TIMEOUT_MS as A, parseWorkSurfaceRequest as B, BoardInputError as C, WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES as D, validateBoardNodes as E, buildWorkSurfaceRequest as F, UI_COMMAND_PENDING_TTL_SECONDS as G, UI_COMMAND_DEFAULT_TIMEOUT_MS as H, buildWorkSurfaceResponse as I, UI_COMMAND_VERSION as J, UI_COMMAND_SETTLEMENT_GRACE_SECONDS as K, parseWorkComposerChipClear as L, buildWorkComposerChipClear as M, buildWorkComposerChipSet as N, WORK_COMPOSER_CHIP_KEY_MAX_LENGTH as O, buildWorkSurfaceReady as P, parseSpaceSlug as Q, parseWorkComposerChipSet as R, createSessionPatchReducer as S, createBoardNode as T, UI_COMMAND_MAX_TIMEOUT_MS as U, parseWorkSurfaceResponse as V, UI_COMMAND_PAYLOAD_MAX_BYTES as W, isUiSurfaceMethod as X, isTerminalUiCommandStatus as Y, parseUiCommand as Z, buildSpacePath as _, ModelsApi as _t, ReferralsApi as a, validateBoardNodeInput as at, parseAssistantMessageCommit as b, ChannelsApi as bt, UiCommandsApi as c, ensureRealtimeConnected as ct, BoardTransactionError as d, SessionAccessApi as dt, BoardAwarenessClientPayloadSchema as et, SpaceClient as f, ReferencesApi as ft, buildSpaceInvitePath as g, PromptsApi as gt, PublicInviteApi as h, SkillsApi as ht, WorksApi as i, BOARD_NODE_CONTRACT as it, WORK_SURFACE_REQUEST_TIMEOUT_MS as j, WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH as k, TasksApi as l, BOARD_BUILTIN_CAPABILITIES as lt, SpacesApi as m, PublicAssetsApi as mt, createHttpClient as n, BOARD_GEO_KINDS as nt, UsersApi as o, BoardPlaybackPolicySchema as ot, SpacePublicFilesApi as p, SearchApi as pt, UI_COMMAND_TERMINAL_TTL_SECONDS as q, WorkCommerceApi as r, BOARD_NATIVE_NODE_TYPES as rt, UserApi as s, parseBoardPlaybackPolicy as st, CohubHttpClient as t, BOARD_COLOR_IDS as tt, BoardClient as u, DEFAULT_BOARD_RENDER_LIMITS as ut, SessionGenerationStreamClient as v, GenerationsApi as vt, assertBoardNodes as w, SessionPatchReducer as x, createSessionGenerationStreamClient as y, CronJobsApi as yt, parseWorkSurfaceReady as z };
5720
+ export { BOARD_GEO_KINDS as $, buildWorkComposerChipSet as A, UI_COMMAND_MAX_TIMEOUT_MS as B, ensureRealtimeConnected as C, SkillsApi as Ct, WORK_SURFACE_READY_TIMEOUT_MS as D, CronJobsApi as Dt, WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH as E, GenerationsApi as Et, parseWorkComposerChipSet as F, UI_COMMAND_VERSION as G, UI_COMMAND_PENDING_TTL_SECONDS as H, parseWorkSurfaceReady as I, parseUiCommand as J, isTerminalUiCommandStatus as K, parseWorkSurfaceRequest as L, buildWorkSurfaceRequest as M, buildWorkSurfaceResponse as N, WORK_SURFACE_REQUEST_TIMEOUT_MS as O, ChannelsApi as Ot, parseWorkComposerChipClear as P, BOARD_COLOR_IDS as Q, parseWorkSurfaceResponse as R, createSessionPatchReducer as S, PublicAssetsApi as St, WORK_COMPOSER_CHIP_KEY_MAX_LENGTH as T, ModelsApi as Tt, UI_COMMAND_SETTLEMENT_GRACE_SECONDS as U, UI_COMMAND_PAYLOAD_MAX_BYTES as V, UI_COMMAND_TERMINAL_TTL_SECONDS as W, parseUsername as X, parseSpaceSlug as Y, BoardAwarenessClientPayloadSchema as Z, buildSpacePath as _, BOARD_BUILTIN_CAPABILITIES as _t, ReferralsApi as a, BoardEffectSchema as at, parseAssistantMessageCommit as b, ReferencesApi as bt, UiCommandsApi as c, BoardAuthoringItemSchema as ct, BoardTransactionError as d, BOARD_ANIMATION_CHANNEL_CAPABILITIES as dt, BOARD_NATIVE_NODE_TYPES as et, SpaceClient as f, BoardCompositionInputSchema as ft, buildSpaceInvitePath as g, parseBoardCompositionInput as gt, PublicInviteApi as h, BoardTrackSchema as ht, WorksApi as i, validateBuiltinBoardClip as it, buildWorkSurfaceReady as j, buildWorkComposerChipClear as k, TasksApi as l, BoardItemPatchSchema as lt, SpacesApi as m, BoardProceduralClipSchema as mt, createHttpClient as n, validateBoardNodeInput as nt, UsersApi as o, BoardPlaybackPolicySchema as ot, SpacePublicFilesApi as p, BoardCompositionSchema as pt, isUiSurfaceMethod as q, WorkCommerceApi as r, estimateBuiltinBoardClipCost as rt, UserApi as s, parseBoardPlaybackPolicy as st, CohubHttpClient as t, BOARD_NODE_CONTRACT as tt, BoardClient as u, BOARD_ANIMATION_CHANNELS as ut, SessionGenerationStreamClient as v, DEFAULT_BOARD_RENDER_LIMITS as vt, WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES as w, PromptsApi as wt, SessionPatchReducer as x, SearchApi as xt, createSessionGenerationStreamClient as y, SessionAccessApi as yt, UI_COMMAND_DEFAULT_TIMEOUT_MS as z };