@neta-art/cohub 6.0.0 → 7.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 +6 -5
  2. package/dist/board/animation.d.ts +2 -1
  3. package/dist/board/animation.js +0 -1
  4. package/dist/board/core/connections.js +4 -4
  5. package/dist/board/core/export-plan.js +1 -1
  6. package/dist/board/core/shape-types.js +0 -1
  7. package/dist/board/index.d.ts +6 -5
  8. package/dist/board/index.js +7 -7
  9. package/dist/board/mutation.d.ts +2 -11
  10. package/dist/board/mutation.js +2 -56
  11. package/dist/board/render/renderers/text-card-renderer.js +1 -1
  12. package/dist/board/semantic-document.d.ts +30 -0
  13. package/dist/board/semantic-document.js +271 -0
  14. package/dist/board/semantic-mutation.d.ts +10 -0
  15. package/dist/board/semantic-mutation.js +203 -0
  16. package/dist/chunks/http.d.ts +15 -94
  17. package/dist/chunks/http.js +248 -1588
  18. package/dist/chunks/transport.js +1 -1
  19. package/dist/chunks/websocket.d.ts +3646 -3139
  20. package/dist/chunks/websocket.js +1 -1
  21. package/dist/http.d.ts +3 -3
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +1045 -195
  24. package/dist/protocol/dist/board-authoring.d.ts +1305 -1
  25. package/dist/protocol/dist/board-authoring.js +80 -6
  26. package/dist/protocol/dist/board-codec.d.ts +2 -2
  27. package/dist/protocol/dist/board-codec.js +19 -4
  28. package/dist/protocol/dist/board-composition.d.ts +126 -1
  29. package/dist/protocol/dist/board-composition.js +1 -9
  30. package/dist/protocol/dist/board-connection.d.ts +16 -16
  31. package/dist/protocol/dist/board-connection.js +16 -16
  32. package/dist/protocol/dist/board-document.d.ts +34 -3
  33. package/dist/protocol/dist/board-document.js +3 -1
  34. package/dist/protocol/dist/board-effect.d.ts +94 -0
  35. package/dist/protocol/dist/board-effect.js +53 -0
  36. package/dist/protocol/dist/board-json.js +16 -0
  37. package/dist/protocol/dist/board.d.ts +34 -164
  38. package/dist/protocol/dist/board.js +44 -45
  39. package/dist/protocol/dist/index.d.ts +7 -7
  40. package/dist/types.d.ts +3 -1
  41. package/package.json +1 -2
  42. package/dist/board/codec.d.ts +0 -27
  43. package/dist/board/codec.js +0 -277
  44. package/dist/protocol/dist/board-content.js +0 -26
  45. package/dist/protocol/dist/board-upgrade.d.ts +0 -1
  46. package/dist/protocol/dist/board-upgrade.js +0 -2
  47. package/dist/protocol/dist/identifiers.js +0 -10
  48. package/dist/protocol/dist/index.js +0 -19
  49. package/dist/protocol/dist/provenance.js +0 -15
  50. package/dist/protocol/dist/public-identifiers.js +0 -38
  51. package/dist/protocol/dist/realtime/board-awareness.js +0 -119
  52. package/dist/protocol/dist/ui-command.js +0 -2
  53. package/dist/protocol/dist/work-promotion-stats.js +0 -11
  54. package/dist/protocol/dist/work-surface.js +0 -2
  55. package/dist/protocol/dist/work-view-stats.js +0 -3
@@ -1,4 +1,4 @@
1
- import { D as getRealtimeBoardRoom, O as getRealtimeSpaceRoom, n as HttpTransport, t as HttpError, y as isUuid } from "./transport.js";
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
- nodeId: z.string().min(1).max(160),
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(AUTO_BOARD_CONNECTION_ANCHOR)
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
- * Build a connection with every default filled in.
626
+ * A patch to an existing connection.
640
627
  *
641
- * Callers only state what they mean (which nodes, and optionally the relation),
642
- * so a connection created by the editor, an agent or the CLI is byte-identical
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
- function createBoardConnection(input) {
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$3 = z.string().min(1).max(160);
672
- const finiteSchema$3 = z.number().finite();
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$3
651
+ itemId: idSchema$2
689
652
  }).strict(),
690
653
  z.object({
691
654
  type: z.literal("effect"),
692
- effectId: idSchema$3
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$3,
699
- y: finiteSchema$3
661
+ x: finiteSchema$1,
662
+ y: finiteSchema$1
700
663
  }).strict();
701
- const scaleSchema = z.union([finiteSchema$3.positive(), z.object({
702
- x: finiteSchema$3.positive(),
703
- y: finiteSchema$3.positive()
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$3,
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$3.min(0).max(1),
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$3.nonnegative(),
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$3,
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$3,
762
+ id: idSchema$2,
800
763
  kind: extensionKindSchema,
801
764
  kindVersion: z.number().int().positive(),
802
765
  target: BoardAnimationTargetSchema,
803
- start: finiteSchema$3.nonnegative(),
804
- duration: finiteSchema$3.positive(),
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$3,
787
+ seed: idSchema$2,
825
788
  metadata: jsonObjectSchema$2.default({})
826
789
  }).strict();
827
790
  const BoardTimelineMarkerSchema = z.object({
828
- id: idSchema$3,
829
- time: finiteSchema$3.nonnegative(),
830
- duration: finiteSchema$3.nonnegative().optional(),
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$3.nonnegative(),
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$3.nonnegative()
885
+ time: finiteSchema$1.nonnegative()
923
886
  }).strict(),
924
887
  z.object({
925
888
  mode: z.literal("marker"),
926
- markerId: idSchema$3
889
+ markerId: idSchema$2
927
890
  }).strict()
928
891
  ]).default({ mode: "base" })
929
892
  }).strict();
930
893
  const compositionFields = {
931
- id: idSchema$3,
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
- /** Accept inspect/get output as apply input while stripping server-owned revision. */
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
- return BoardCompositionInputSchema.parse(input);
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 BOARD_COLOR_IDS$1 = [
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 BOARD_GEO_KINDS$1 = [
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$2 = z.string().min(1).max(160);
1005
- const jsonObjectSchema$1 = z.record(z.string(), z.unknown());
1006
- const finiteSchema$2 = z.number().finite();
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$2,
1010
- y: finiteSchema$2,
1011
- width: finiteSchema$2.positive(),
1012
- height: finiteSchema$2.positive(),
1013
- rotation: finiteSchema$2.default(0)
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$1 = z.object({
1016
- x: finiteSchema$2,
1017
- y: finiteSchema$2,
1018
- p: finiteSchema$2.min(0).max(1).default(.5)
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$1 = z.object({
1021
- x: finiteSchema$2,
1022
- y: finiteSchema$2
1054
+ const worldPointSchema = z.object({
1055
+ x: finiteSchema,
1056
+ y: finiteSchema
1023
1057
  }).strict();
1024
1058
  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)
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$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()
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$2,
1079
+ id: idSchema,
1046
1080
  frame: BoardAuthoringFrameSchema,
1047
- parentId: idSchema$2.nullable().optional(),
1081
+ parentId: idSchema.nullable().optional(),
1048
1082
  locked: z.boolean().optional(),
1049
- metadata: jsonObjectSchema$1.optional()
1083
+ metadata: jsonObjectSchema.optional()
1050
1084
  };
1051
1085
  const styledBaseFields = {
1052
1086
  ...baseFields,
1053
- style: BoardItemStyleSchema$1.optional()
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$2.min(2).max(512).default(24)
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(BOARD_GEO_KINDS$1).default("rectangle"),
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$1).min(1) }).strict()
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$1,
1081
- end: worldPointSchema$1,
1082
- bend: finiteSchema$2.min(-.85).max(.85).default(0),
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,1221 +1170,131 @@ const BoardExtensionAuthoringItemSchema = z.object({
1136
1170
  ...baseFields,
1137
1171
  type: extensionTypeSchema,
1138
1172
  kindVersion: z.number().int().positive(),
1139
- props: jsonObjectSchema$1,
1140
- style: jsonObjectSchema$1.optional(),
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$1.optional()
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$2.optional(),
1150
- y: finiteSchema$2.optional(),
1151
- width: finiteSchema$2.positive().optional(),
1152
- height: finiteSchema$2.positive().optional(),
1153
- rotation: finiteSchema$2.optional()
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$2.nullable().optional(),
1192
+ parentId: idSchema.nullable().optional(),
1159
1193
  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()
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$2,
1222
+ itemId: idSchema,
1173
1223
  patch: BoardItemPatchSchema
1174
1224
  }).strict(),
1175
1225
  z.object({
1176
1226
  type: z.literal("item.replace"),
1177
- itemId: idSchema$2,
1227
+ itemId: idSchema,
1178
1228
  item: BoardAuthoringItemSchema
1179
1229
  }).strict(),
1180
1230
  z.object({
1181
1231
  type: z.literal("item.delete"),
1182
- itemId: idSchema$2,
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
- ...BoardTaskMediaArtifactFields,
1759
- type: z.literal("video"),
1760
- previewUrl: BoardRemoteUrlSchema.optional(),
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
- ...BoardTaskMediaArtifactFields,
1767
- type: z.literal("audio"),
1768
- previewUrl: BoardRemoteUrlSchema.optional(),
1769
- durationMs: z.number().int().positive().optional()
1241
+ type: z.literal("connection.create"),
1242
+ connection: BoardConnectionInputSchema
1770
1243
  }).strict(),
1771
1244
  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)
1245
+ type: z.literal("connection.patch"),
1246
+ connectionId: idSchema,
1247
+ patch: BoardConnectionPatchSchema
1980
1248
  }).strict(),
1981
- draw: z.object({
1982
- ...commonData,
1983
- points: z.array(pointSchema).min(1),
1984
- color: BoardColorIdSchema.default("brand"),
1985
- size: strokeSizeSchema.default(4)
1249
+ z.object({
1250
+ type: z.literal("connection.delete"),
1251
+ connectionId: idSchema
1986
1252
  }).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("")
1253
+ z.object({
1254
+ type: z.literal("effect.apply"),
1255
+ effect: BoardEffectCommandInputSchema
1997
1256
  }).strict(),
1998
- frame: z.object({
1999
- ...commonData,
2000
- label: z.string().default("Frame"),
2001
- color: BoardColorIdSchema.default("neutral")
1257
+ z.object({
1258
+ type: z.literal("effect.delete"),
1259
+ effectId: idSchema
2002
1260
  }).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()
1261
+ z.object({
1262
+ type: z.literal("composition.apply"),
1263
+ composition: BoardCompositionCommandInputSchema
2011
1264
  }).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)
1265
+ z.object({
1266
+ type: z.literal("composition.delete"),
1267
+ compositionId: idSchema
2018
1268
  }).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
1269
  ]);
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()
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)
2184
1278
  }).strict();
2185
1279
  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
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()
2213
1296
  }).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
- }),
2250
- z.object({
2251
- kind: z.literal("box"),
2252
- id: idSchema,
2253
- nodeId: idSchema,
2254
- shape: z.enum(["geo", "frame"]),
2255
- start: BoardAwarenessPointSchema,
2256
- current: BoardAwarenessPointSchema,
2257
- color: z.string().min(1).max(64),
2258
- geo: z.string().min(1).max(40)
2259
- }),
2260
- z.object({
2261
- kind: z.literal("connection"),
2262
- id: idSchema,
2263
- sourceNodeId: idSchema,
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
- }),
2269
- z.object({
2270
- kind: z.literal("transform"),
2271
- id: idSchema,
2272
- mode: z.enum([
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,
2285
- z.object({
2286
- type: z.literal("gesture"),
2287
- gesture: BoardAwarenessGestureSchema
2288
- }),
2289
- z.object({
2290
- type: z.literal("gesture.end"),
2291
- gestureId: idSchema,
2292
- resultingNodeIds: z.array(idSchema).max(64)
2293
- }),
2294
- z.object({
2295
- type: z.literal("gesture.cancel"),
2296
- gestureId: idSchema
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"
2341
- ]);
2342
- new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
2343
- [...RESERVED_PLATFORM_PATH_SEGMENTS];
2344
- function parseUsername(value) {
2345
- if (value === null || value === void 0) return null;
2346
- const normalized = value.trim().toLowerCase();
2347
- return USERNAME_PATTERN.test(normalized) ? normalized : null;
2348
- }
2349
- function parseSpaceSlug(value) {
2350
- if (value === null || value === void 0) return null;
2351
- const normalized = value.trim();
2352
- return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
2353
- }
1297
+ }).strict();
2354
1298
  //#endregion
2355
1299
  //#region ../protocol/dist/ui-command.js
2356
1300
  /**
@@ -2386,14 +1330,14 @@ const parseUiCommandId = (value) => {
2386
1330
  const trimmed = value.trim();
2387
1331
  return UI_COMMAND_ID_RE.test(trimmed) ? trimmed : null;
2388
1332
  };
2389
- const isRecord$2 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
1333
+ const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
2390
1334
  const asTrimmed = (value) => {
2391
1335
  if (typeof value !== "string") return null;
2392
1336
  const trimmed = value.trim();
2393
1337
  return trimmed ? trimmed : null;
2394
1338
  };
2395
1339
  const parseLaunch = (value) => {
2396
- if (!isRecord$2(value)) return void 0;
1340
+ if (!isRecord$1(value)) return void 0;
2397
1341
  const search = asTrimmed(value.search);
2398
1342
  const hash = asTrimmed(value.hash);
2399
1343
  if (!search && !hash) return void 0;
@@ -2411,7 +1355,7 @@ const measureUiCommandPayload = (value) => {
2411
1355
  }
2412
1356
  };
2413
1357
  const parseUiCommand = (input) => {
2414
- if (!isRecord$2(input)) return {
1358
+ if (!isRecord$1(input)) return {
2415
1359
  command: null,
2416
1360
  error: "command must be an object"
2417
1361
  };
@@ -2420,7 +1364,7 @@ const parseUiCommand = (input) => {
2420
1364
  error: "command.type must be one of: preview.show"
2421
1365
  };
2422
1366
  const preview = input.preview;
2423
- if (!isRecord$2(preview)) return {
1367
+ if (!isRecord$1(preview)) return {
2424
1368
  command: null,
2425
1369
  error: "command.preview is required"
2426
1370
  };
@@ -2477,7 +1421,7 @@ const parseUiCommand = (input) => {
2477
1421
  }
2478
1422
  let request;
2479
1423
  if (input.request !== void 0 && input.request !== null) {
2480
- if (!isRecord$2(input.request)) return {
1424
+ if (!isRecord$1(input.request)) return {
2481
1425
  command: null,
2482
1426
  error: "command.request must be an object"
2483
1427
  };
@@ -2529,123 +1473,6 @@ const parseUiCommand = (input) => {
2529
1473
  };
2530
1474
  };
2531
1475
  //#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
1476
  //#region src/realtime.ts
2650
1477
  function ensureRealtimeConnected(websocketClient) {
2651
1478
  if (websocketClient.state === "open" || websocketClient.state === "connecting" || websocketClient.state === "reconnecting") return;
@@ -3664,26 +2491,6 @@ const getFilenameFromContentDisposition = (value) => {
3664
2491
  return value.match(/filename="?([^";]+)"?/i)?.[1] ?? null;
3665
2492
  };
3666
2493
  /**
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
2494
  * Identifier for a client-authored Board entity (connection id, transaction id).
3688
2495
  *
3689
2496
  * `crypto.randomUUID` is used where available and falls back to a random string
@@ -4298,7 +3105,7 @@ var SpaceEventsApi = class {
4298
3105
  handler(event);
4299
3106
  return;
4300
3107
  }
4301
- if (type === "board.transaction.applied" && event.type === "board.transaction.applied") {
3108
+ if (type === "board.changed" && event.type === "board.changed") {
4302
3109
  handler(event);
4303
3110
  return;
4304
3111
  }
@@ -4656,10 +3463,10 @@ var BoardRealtimeClient = class {
4656
3463
  const releaseRoom = this.websocketClient.retainRooms([getRealtimeSpaceRoom(this.spaceId), getRealtimeBoardRoom(this.boardId)]);
4657
3464
  const unsubscribe = this.websocketClient.on("event", (event) => {
4658
3465
  if (event.spaceId !== this.spaceId) return;
4659
- if (event.type === "board.transaction.applied" && event.payload.boardId === this.boardId) {
4660
- const transactionEvent = event;
4661
- handlers.event?.(transactionEvent);
4662
- handlers.transaction?.(transactionEvent);
3466
+ if (event.type === "board.changed" && event.payload.boardId === this.boardId) {
3467
+ const changedEvent = event;
3468
+ handlers.event?.(changedEvent);
3469
+ handlers.changed?.(changedEvent);
4663
3470
  }
4664
3471
  if (event.type === "board.awareness.updated" && event.payload.boardId === this.boardId) {
4665
3472
  const awarenessEvent = event;
@@ -4680,7 +3487,7 @@ var BoardRealtimeClient = class {
4680
3487
  };
4681
3488
  }
4682
3489
  on(type, handler) {
4683
- if (type === "transaction") return this.subscribe({ transaction: handler });
3490
+ if (type === "changed") return this.subscribe({ changed: handler });
4684
3491
  if (type === "awareness") return this.subscribe({ awareness: handler });
4685
3492
  return this.subscribe({ playback: handler });
4686
3493
  }
@@ -4698,67 +3505,20 @@ var BoardClient = class {
4698
3505
  this.boards = new SpaceBoardsApi(transport, spaceId, websocketClient);
4699
3506
  this.realtime = new BoardRealtimeClient(websocketClient, spaceId, id);
4700
3507
  }
4701
- inspect(input = {}, customFetch) {
4702
- return this.boards.inspect(this.id, input, customFetch);
4703
- }
4704
3508
  capabilities(customFetch) {
4705
3509
  return this.boards.capabilities(this.id, customFetch);
4706
3510
  }
4707
3511
  summary(customFetch) {
4708
3512
  return this.boards.summary(this.id, customFetch);
4709
3513
  }
4710
- authoring(customFetch) {
4711
- return this.boards.authoring(this.id, customFetch);
3514
+ authoring(input = {}, customFetch) {
3515
+ return this.boards.authoring(this.id, input, customFetch);
4712
3516
  }
4713
3517
  mutateSemantic(input) {
4714
3518
  return this.boards.mutateSemantic(this.id, {
4715
3519
  ...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
3520
+ mutationId: input.mutationId ?? randomBoardId(),
3521
+ dryRun: input.dryRun ?? false
4762
3522
  });
4763
3523
  }
4764
3524
  updateAwareness(seq, update) {
@@ -4770,91 +3530,6 @@ var BoardClient = class {
4770
3530
  update
4771
3531
  });
4772
3532
  }
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
3533
  play(command) {
4859
3534
  return this.boards.play(this.id, command);
4860
3535
  }
@@ -4871,7 +3546,7 @@ var BoardClient = class {
4871
3546
  return this.realtime.subscribe(handlers);
4872
3547
  }
4873
3548
  on(type, handler) {
4874
- if (type === "transaction") return this.realtime.on("transaction", handler);
3549
+ if (type === "changed") return this.realtime.on("changed", handler);
4875
3550
  if (type === "awareness") return this.realtime.on("awareness", handler);
4876
3551
  return this.realtime.on("playback", handler);
4877
3552
  }
@@ -4888,7 +3563,7 @@ var SpaceBoardsApi = class {
4888
3563
  byId(boardId) {
4889
3564
  return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
4890
3565
  }
4891
- create(input) {
3566
+ async create(input) {
4892
3567
  for (const item of input.items ?? []) BoardAuthoringItemSchema.parse(item);
4893
3568
  return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
4894
3569
  method: "POST",
@@ -4896,15 +3571,19 @@ var SpaceBoardsApi = class {
4896
3571
  body: JSON.stringify(input)
4897
3572
  });
4898
3573
  }
4899
- inspect(boardId, input = {}, customFetch) {
3574
+ authoring(boardId, input = {}, customFetch) {
4900
3575
  const params = new URLSearchParams();
4901
- for (const section of input.include ?? []) params.append("include", section);
3576
+ if (input.include) {
3577
+ for (const section of input.include) params.append("include", section);
3578
+ if (input.include.length === 0) params.set("include", "");
3579
+ }
3580
+ if (input.itemIds?.length) params.set("itemIds", input.itemIds.join(","));
3581
+ if (input.connectionIds?.length) params.set("connectionIds", input.connectionIds.join(","));
3582
+ if (input.effectIds?.length) params.set("effectIds", input.effectIds.join(","));
3583
+ if (input.compositionIds?.length) params.set("compositionIds", input.compositionIds.join(","));
4902
3584
  if (input.viewport) params.set("viewport", JSON.stringify(input.viewport));
4903
3585
  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 });
3586
+ return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/authoring${query ? `?${query}` : ""}`, { fetch: customFetch });
4908
3587
  }
4909
3588
  mutateSemantic(boardId, mutation) {
4910
3589
  return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/mutations`, {
@@ -4919,25 +3598,6 @@ var SpaceBoardsApi = class {
4919
3598
  capabilities(boardId, customFetch) {
4920
3599
  return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/capabilities`, { fetch: customFetch });
4921
3600
  }
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
3601
  playback(boardId, command) {
4942
3602
  return this.transport.request(`/api/spaces/${this.spaceId}/boards/${boardId}/playback`, {
4943
3603
  method: "POST",
@@ -5717,4 +4377,4 @@ var CohubHttpClient = class {
5717
4377
  };
5718
4378
  const createHttpClient = (options) => new CohubHttpClient(options);
5719
4379
  //#endregion
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 };
4380
+ export { SessionAccessApi as $, isTerminalUiCommandStatus as A, BOARD_ANIMATION_CHANNELS as B, UI_COMMAND_DEFAULT_TIMEOUT_MS as C, UI_COMMAND_SETTLEMENT_GRACE_SECONDS as D, UI_COMMAND_PENDING_TTL_SECONDS as E, BoardItemPatchSchema as F, BoardTrackSchema as G, BoardCompositionInputSchema as H, BoardSemanticMutationSchema as I, BOARD_ARROW_STROKE_SIZE as J, parseBoardCompositionInput as K, BoardEffectInputSchema as L, parseUiCommand as M, parseUiCommandId as N, UI_COMMAND_TERMINAL_TTL_SECONDS as O, BoardAuthoringItemSchema as P, DEFAULT_BOARD_RENDER_LIMITS as Q, BoardEffectSchema as R, ensureRealtimeConnected as S, UI_COMMAND_PAYLOAD_MAX_BYTES as T, BoardCompositionSchema as U, BOARD_ANIMATION_CHANNEL_CAPABILITIES as V, BoardProceduralClipSchema as W, BOARD_BUILTIN_CLIP_KINDS as X, BOARD_BUILTIN_CAPABILITIES as Y, BOARD_BUILTIN_EFFECT_KINDS as Z, SessionGenerationStreamClient as _, ReferralsApi as a, ModelsApi as at, SessionPatchReducer as b, UiCommandsApi as c, ChannelsApi as ct, SpaceClient as d, ReferencesApi as et, SpacePublicFilesApi as f, buildSpacePath as g, buildSpaceInvitePath as h, WorksApi as i, PromptsApi as it, isUiSurfaceMethod as j, UI_COMMAND_VERSION as k, TasksApi as l, PublicInviteApi as m, createHttpClient as n, PublicAssetsApi as nt, UsersApi as o, GenerationsApi as ot, SpacesApi as p, BoardConnectionSchema as q, WorkCommerceApi as r, SkillsApi as rt, UserApi as s, CronJobsApi as st, CohubHttpClient as t, SearchApi as tt, BoardClient as u, createSessionGenerationStreamClient as v, UI_COMMAND_MAX_TIMEOUT_MS as w, createSessionPatchReducer as x, parseAssistantMessageCommit as y, parseBoardEffectInput as z };