@neta-art/cohub 5.3.2 → 5.4.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 (44) hide show
  1. package/dist/board/codec.d.ts +2 -0
  2. package/dist/board/codec.js +18 -13
  3. package/dist/board/core/arrow-geometry.d.ts +23 -0
  4. package/dist/board/core/arrow-geometry.js +109 -0
  5. package/dist/board/core/connections.d.ts +83 -0
  6. package/dist/board/core/connections.js +399 -0
  7. package/dist/board/core/export-plan.d.ts +15 -4
  8. package/dist/board/core/export-plan.js +49 -16
  9. package/dist/board/core/shape-definition.js +1 -1
  10. package/dist/board/core/shape-types.d.ts +8 -2
  11. package/dist/board/core/shape-types.js +1 -1
  12. package/dist/board/core/tool-styles.d.ts +9 -3
  13. package/dist/board/core/tool-styles.js +10 -6
  14. package/dist/board/export/index.js +1 -0
  15. package/dist/board/export/scene.d.ts +3 -0
  16. package/dist/board/export/scene.js +18 -1
  17. package/dist/board/index.d.ts +8 -5
  18. package/dist/board/index.js +8 -5
  19. package/dist/board/render/connection-layer.d.ts +48 -0
  20. package/dist/board/render/connection-layer.js +223 -0
  21. package/dist/board/render/index.d.ts +2 -1
  22. package/dist/board/render/index.js +3 -2
  23. package/dist/board/render/renderers/arrow-card-renderer.js +35 -48
  24. package/dist/chunks/http.d.ts +53 -1
  25. package/dist/chunks/http.js +287 -1
  26. package/dist/chunks/websocket.d.ts +296 -57
  27. package/dist/http.d.ts +1 -1
  28. package/dist/index.d.ts +9 -3
  29. package/dist/index.js +22 -58
  30. package/dist/protocol/dist/board-connection.d.ts +310 -0
  31. package/dist/protocol/dist/board-connection.js +215 -0
  32. package/dist/protocol/dist/board-constants.d.ts +11 -1
  33. package/dist/protocol/dist/board-constants.js +15 -1
  34. package/dist/protocol/dist/board-document.d.ts +106 -60
  35. package/dist/protocol/dist/board-document.js +57 -17
  36. package/dist/protocol/dist/board.d.ts +1 -0
  37. package/dist/protocol/dist/board.js +3 -0
  38. package/dist/protocol/dist/index.d.ts +2 -1
  39. package/dist/protocol/dist/index.js +2 -1
  40. package/dist/protocol/dist/realtime/board-awareness.js +15 -15
  41. package/docs/work-runtime-guide.md +1 -1
  42. package/package.json +1 -1
  43. package/dist/board/core/bindings.d.ts +0 -45
  44. package/dist/board/core/bindings.js +0 -162
@@ -0,0 +1,215 @@
1
+ import { BOARD_CONNECTION_STROKE_SIZE, clampBoardStrokeSize } from "./board-constants.js";
2
+ import { z } from "zod";
3
+ //#region ../protocol/dist/board-connection.js
4
+ /**
5
+ * Board connections — the first-class relation between two Board nodes.
6
+ *
7
+ * A connection is *not* a shape. It has no frame of its own: its geometry is
8
+ * derived entirely from the two nodes it joins, which is exactly why it belongs
9
+ * in its own entity rather than in the item list. Storing it as a node would
10
+ * force a bounding box to be persisted and kept in sync with both endpoints —
11
+ * a second source of truth that is stale the moment either node moves.
12
+ *
13
+ * The stored form is therefore purely semantic: which nodes, in which direction,
14
+ * anchored where, routed how. Every world coordinate is resolved at read time
15
+ * from the live node frames (see the SDK's connection geometry), so a connection
16
+ * can never drift from the nodes it describes.
17
+ *
18
+ * Agents read this model directly: `source`, `target`, `relation` and `label`
19
+ * carry the meaning, while `routing` and `style` carry only presentation. A
20
+ * reader that wants the graph never has to interpret pixels.
21
+ */
22
+ /** Sides of a node frame a connection can attach to. */
23
+ const BOARD_CONNECTION_SIDES = [
24
+ "top",
25
+ "right",
26
+ "bottom",
27
+ "left"
28
+ ];
29
+ /**
30
+ * Where a connection meets its node.
31
+ *
32
+ * - `auto` — the side is chosen from the live geometry of both endpoints, so the
33
+ * connection stays sensible through any move or resize. This is the default and
34
+ * what almost every connection should use.
35
+ * - `side` — the user pinned a side; `offset` (0..1) positions it along that edge.
36
+ * - `fixed` — the user pinned an exact normalized point on the frame.
37
+ *
38
+ * `auto` is a *declaration of intent*, not a computed value: the resolved side is
39
+ * never written back, so the connection keeps adapting instead of freezing the
40
+ * first layout it happened to have.
41
+ */
42
+ const BoardConnectionAnchorSchema = z.union([
43
+ z.object({ kind: z.literal("auto") }),
44
+ z.object({
45
+ kind: z.literal("side"),
46
+ side: z.enum(BOARD_CONNECTION_SIDES),
47
+ offset: z.number().finite().min(0).max(1).default(.5)
48
+ }),
49
+ z.object({
50
+ kind: z.literal("fixed"),
51
+ nx: z.number().finite().min(0).max(1),
52
+ ny: z.number().finite().min(0).max(1)
53
+ })
54
+ ]);
55
+ const AUTO_BOARD_CONNECTION_ANCHOR = { kind: "auto" };
56
+ const BoardConnectionEndpointSchema = z.object({
57
+ nodeId: z.string().min(1).max(160),
58
+ anchor: BoardConnectionAnchorSchema.default(AUTO_BOARD_CONNECTION_ANCHOR)
59
+ });
60
+ /**
61
+ * Which ends carry an arrowhead.
62
+ *
63
+ * This is the *semantic* direction, not a style flag: `forward` means the
64
+ * relation reads source → target, `backward` means target → source, and `none`
65
+ * means the relation is symmetric. Renderers derive arrowheads from it, so the
66
+ * drawing can never disagree with the meaning.
67
+ */
68
+ const BOARD_CONNECTION_DIRECTIONS = [
69
+ "none",
70
+ "forward",
71
+ "backward",
72
+ "both"
73
+ ];
74
+ /** How the line travels between its two resolved endpoints. */
75
+ const BOARD_CONNECTION_ROUTINGS = [
76
+ "straight",
77
+ "curve",
78
+ "orthogonal"
79
+ ];
80
+ const BOARD_CONNECTION_LINES = ["solid", "dashed"];
81
+ /**
82
+ * The default relation kind.
83
+ *
84
+ * "related" is deliberately unopinionated: drawing a line between two nodes
85
+ * states that they are connected, not *how*. A stronger claim (depends-on,
86
+ * blocks, ...) is something the user or an agent asserts explicitly.
87
+ */
88
+ const DEFAULT_BOARD_RELATION = "related";
89
+ /**
90
+ * Relation kind — a free-form slug, not an enum.
91
+ *
92
+ * Boards are used for domains we do not control, so a closed vocabulary would
93
+ * force unrelated meanings into the wrong bucket. The format is constrained
94
+ * (lowercase, dash/dot separated) so relations stay comparable and queryable
95
+ * across clients instead of accumulating near-duplicate spellings.
96
+ */
97
+ const BoardRelationSchema = z.string().min(1).max(64).regex(/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/, "relation must be a lowercase slug").default(DEFAULT_BOARD_RELATION);
98
+ /**
99
+ * Waypoints a user dragged the line through, in world space.
100
+ *
101
+ * Stored because they are *input*, not output: the resolved path is recomputed
102
+ * from them on every read, but the intent behind a hand-routed line cannot be
103
+ * recovered once discarded.
104
+ */
105
+ const BoardConnectionWaypointSchema = z.object({
106
+ x: z.number().finite(),
107
+ y: z.number().finite()
108
+ });
109
+ const BoardConnectionRoutingSchema = z.object({
110
+ kind: z.enum(BOARD_CONNECTION_ROUTINGS).default("curve"),
111
+ /** Curve bow as a fraction of endpoint distance (-0.85..0.85). */
112
+ bend: z.number().finite().min(-.85).max(.85).default(0),
113
+ waypoints: z.array(BoardConnectionWaypointSchema).max(64).default([])
114
+ });
115
+ const BoardConnectionStyleSchema = z.object({
116
+ /** Palette color id, resolved to a theme token at render time. */
117
+ color: z.string().min(1).max(40).default("brand"),
118
+ size: z.number().finite().positive().default(BOARD_CONNECTION_STROKE_SIZE),
119
+ line: z.enum(BOARD_CONNECTION_LINES).default("solid")
120
+ });
121
+ const DEFAULT_BOARD_CONNECTION_ROUTING = {
122
+ kind: "curve",
123
+ bend: 0,
124
+ waypoints: []
125
+ };
126
+ const DEFAULT_BOARD_CONNECTION_STYLE = {
127
+ color: "brand",
128
+ size: BOARD_CONNECTION_STROKE_SIZE,
129
+ line: "solid"
130
+ };
131
+ const BoardConnectionSchema = z.object({
132
+ id: z.string().min(1).max(160),
133
+ source: BoardConnectionEndpointSchema,
134
+ target: BoardConnectionEndpointSchema,
135
+ relation: BoardRelationSchema,
136
+ direction: z.enum(BOARD_CONNECTION_DIRECTIONS).default("forward"),
137
+ label: z.string().max(280).default(""),
138
+ routing: BoardConnectionRoutingSchema.default(DEFAULT_BOARD_CONNECTION_ROUTING),
139
+ style: BoardConnectionStyleSchema.default(DEFAULT_BOARD_CONNECTION_STYLE),
140
+ metadata: z.record(z.string(), z.unknown()).default({})
141
+ });
142
+ /**
143
+ * A patch to an existing connection.
144
+ *
145
+ * `id` is excluded: a connection's identity never changes, and re-pointing both
146
+ * endpoints is an edit of the same relation, not a new one.
147
+ */
148
+ const BoardConnectionPatchSchema = BoardConnectionSchema.omit({ id: true }).partial();
149
+ /** Both node ids a connection touches, deduped for a self-loop. */
150
+ function connectionNodeIds(connection) {
151
+ return connection.source.nodeId === connection.target.nodeId ? [connection.source.nodeId] : [connection.source.nodeId, connection.target.nodeId];
152
+ }
153
+ /** Whether a connection touches the given node. */
154
+ function connectionTouchesNode(connection, nodeId) {
155
+ return connection.source.nodeId === nodeId || connection.target.nodeId === nodeId;
156
+ }
157
+ /** The node at the far end of a connection from `nodeId`, or null. */
158
+ function connectionOtherNodeId(connection, nodeId) {
159
+ if (connection.source.nodeId === nodeId) return connection.target.nodeId;
160
+ if (connection.target.nodeId === nodeId) return connection.source.nodeId;
161
+ return null;
162
+ }
163
+ function normalizeBoardConnectionStyle(style) {
164
+ return {
165
+ color: style?.color?.trim() || DEFAULT_BOARD_CONNECTION_STYLE.color,
166
+ size: clampBoardStrokeSize(typeof style?.size === "number" && Number.isFinite(style.size) ? style.size : DEFAULT_BOARD_CONNECTION_STYLE.size),
167
+ line: style?.line ?? DEFAULT_BOARD_CONNECTION_STYLE.line
168
+ };
169
+ }
170
+ /**
171
+ * Build a connection with every default filled in.
172
+ *
173
+ * Callers only state what they mean (which nodes, and optionally the relation),
174
+ * so a connection created by the editor, an agent or the CLI is byte-identical
175
+ * for the same intent.
176
+ */
177
+ function createBoardConnection(input) {
178
+ return {
179
+ id: input.id,
180
+ source: {
181
+ nodeId: input.sourceNodeId,
182
+ anchor: input.sourceAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
183
+ },
184
+ target: {
185
+ nodeId: input.targetNodeId,
186
+ anchor: input.targetAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
187
+ },
188
+ relation: input.relation ?? "related",
189
+ direction: input.direction ?? "forward",
190
+ label: input.label ?? "",
191
+ routing: {
192
+ ...DEFAULT_BOARD_CONNECTION_ROUTING,
193
+ ...input.routing
194
+ },
195
+ style: normalizeBoardConnectionStyle(input.style),
196
+ metadata: input.metadata ?? {}
197
+ };
198
+ }
199
+ /** Reverse a connection's endpoints, preserving the relation's reading order. */
200
+ function flipBoardConnection(connection) {
201
+ const direction = connection.direction === "forward" ? "backward" : connection.direction === "backward" ? "forward" : connection.direction;
202
+ return {
203
+ ...connection,
204
+ source: connection.target,
205
+ target: connection.source,
206
+ direction,
207
+ routing: {
208
+ ...connection.routing,
209
+ bend: -connection.routing.bend,
210
+ waypoints: [...connection.routing.waypoints].reverse()
211
+ }
212
+ };
213
+ }
214
+ //#endregion
215
+ export { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BoardConnectionAnchorSchema, BoardConnectionEndpointSchema, BoardConnectionPatchSchema, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardRelationSchema, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, connectionNodeIds, connectionOtherNodeId, connectionTouchesNode, createBoardConnection, flipBoardConnection, normalizeBoardConnectionStyle };
@@ -19,5 +19,15 @@ type BoardCapability = {
19
19
  fallbackId?: string;
20
20
  schema?: Record<string, unknown>;
21
21
  };
22
+ /**
23
+ * Stroke width bounds, shared by every stroked board entity.
24
+ *
25
+ * Defined here (not beside the editor) because the persisted schemas clamp with
26
+ * them: a single range is what keeps a width authored by the editor, an agent or
27
+ * the CLI from being silently re-clamped to something else on read.
28
+ */
29
+ declare const BOARD_STROKE_MIN_SIZE = 1;
30
+ declare const BOARD_STROKE_MAX_SIZE = 64;
31
+ declare function clampBoardStrokeSize(size: number): number;
22
32
  //#endregion
23
- export { BoardCapability, BoardRenderCost };
33
+ export { BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BoardCapability, BoardRenderCost, clampBoardStrokeSize };
@@ -35,6 +35,20 @@ const BOARD_BUILTIN_EFFECT_KINDS = ["effects.pulse", "effects.float"];
35
35
  */
36
36
  const BOARD_TEXT_FONT_FAMILY = "Geist";
37
37
  const BOARD_ARROW_STROKE_SIZE = 2.5;
38
+ const BOARD_CONNECTION_STROKE_SIZE = 2.5;
39
+ /**
40
+ * Stroke width bounds, shared by every stroked board entity.
41
+ *
42
+ * Defined here (not beside the editor) because the persisted schemas clamp with
43
+ * them: a single range is what keeps a width authored by the editor, an agent or
44
+ * the CLI from being silently re-clamped to something else on read.
45
+ */
46
+ const BOARD_STROKE_MIN_SIZE = 1;
47
+ const BOARD_STROKE_MAX_SIZE = 64;
48
+ function clampBoardStrokeSize(size) {
49
+ if (!Number.isFinite(size)) return BOARD_CONNECTION_STROKE_SIZE;
50
+ return Math.min(64, Math.max(1, size));
51
+ }
38
52
  /**
39
53
  * Font stacks used by every board renderer.
40
54
  *
@@ -58,4 +72,4 @@ const BOARD_BUILTIN_CAPABILITIES = [...BOARD_BUILTIN_CLIP_KINDS.map((id) => ({
58
72
  renderers: ["webgpu", "webgl"]
59
73
  }))];
60
74
  //#endregion
61
- export { BOARD_ARROW_STROKE_SIZE, BOARD_BUILTIN_CAPABILITIES, BOARD_BUILTIN_CLIP_KINDS, BOARD_BUILTIN_EFFECT_KINDS, BOARD_FONT_STACK, BOARD_MONO_FONT_STACK, BOARD_TEXT_FONT_FAMILY, DEFAULT_BOARD_RENDER_LIMITS };
75
+ export { BOARD_ARROW_STROKE_SIZE, BOARD_BUILTIN_CAPABILITIES, BOARD_BUILTIN_CLIP_KINDS, BOARD_BUILTIN_EFFECT_KINDS, BOARD_CONNECTION_STROKE_SIZE, BOARD_FONT_STACK, BOARD_MONO_FONT_STACK, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BOARD_TEXT_FONT_FAMILY, DEFAULT_BOARD_RENDER_LIMITS, clampBoardStrokeSize };
@@ -150,6 +150,11 @@ declare const DrawPointSchema: z.ZodObject<{
150
150
  y: z.ZodNumber;
151
151
  p: z.ZodDefault<z.ZodNumber>;
152
152
  }, z.core.$strip>;
153
+ /** A world-space point. */
154
+ declare const BoardPointSchema: z.ZodObject<{
155
+ x: z.ZodNumber;
156
+ y: z.ZodNumber;
157
+ }, z.core.$strip>;
153
158
  declare const BoardDrawItemSchema: z.ZodObject<{
154
159
  id: z.ZodString;
155
160
  frame: z.ZodObject<{
@@ -187,18 +192,15 @@ declare const BoardDrawItemSchema: z.ZodObject<{
187
192
  color: z.ZodDefault<z.ZodString>;
188
193
  size: z.ZodDefault<z.ZodNumber>;
189
194
  }, z.core.$strip>;
190
- /** An arrow endpoint: a free point or a binding to another shape. */
191
- declare const ArrowEndpointSchema: z.ZodUnion<readonly [z.ZodObject<{
192
- kind: z.ZodLiteral<"point">;
193
- x: z.ZodNumber;
194
- y: z.ZodNumber;
195
- }, z.core.$strip>, z.ZodObject<{
196
- kind: z.ZodLiteral<"binding">;
197
- target: z.ZodString;
198
- nx: z.ZodDefault<z.ZodNumber>;
199
- ny: z.ZodDefault<z.ZodNumber>;
200
- precise: z.ZodDefault<z.ZodBoolean>;
201
- }, z.core.$strip>]>;
195
+ /**
196
+ * A free arrow — a standalone annotation stroke between two world points.
197
+ *
198
+ * Arrows do not relate nodes. A relation between two nodes is a
199
+ * `BoardConnection`, which is stored separately and resolves its geometry from
200
+ * the live node frames. Keeping the two apart is what lets an arrow be a plain
201
+ * shape (its own frame, freely movable) while a connection stays purely
202
+ * semantic — neither has to pretend to be the other.
203
+ */
202
204
  declare const BoardArrowItemSchema: z.ZodObject<{
203
205
  id: z.ZodString;
204
206
  frame: z.ZodObject<{
@@ -228,28 +230,14 @@ declare const BoardArrowItemSchema: z.ZodObject<{
228
230
  }, z.core.$strip>>;
229
231
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
230
232
  type: z.ZodLiteral<"arrow">;
231
- start: z.ZodUnion<readonly [z.ZodObject<{
232
- kind: z.ZodLiteral<"point">;
233
+ start: z.ZodObject<{
233
234
  x: z.ZodNumber;
234
235
  y: z.ZodNumber;
235
- }, z.core.$strip>, z.ZodObject<{
236
- kind: z.ZodLiteral<"binding">;
237
- target: z.ZodString;
238
- nx: z.ZodDefault<z.ZodNumber>;
239
- ny: z.ZodDefault<z.ZodNumber>;
240
- precise: z.ZodDefault<z.ZodBoolean>;
241
- }, z.core.$strip>]>;
242
- end: z.ZodUnion<readonly [z.ZodObject<{
243
- kind: z.ZodLiteral<"point">;
236
+ }, z.core.$strip>;
237
+ end: z.ZodObject<{
244
238
  x: z.ZodNumber;
245
239
  y: z.ZodNumber;
246
- }, z.core.$strip>, z.ZodObject<{
247
- kind: z.ZodLiteral<"binding">;
248
- target: z.ZodString;
249
- nx: z.ZodDefault<z.ZodNumber>;
250
- ny: z.ZodDefault<z.ZodNumber>;
251
- precise: z.ZodDefault<z.ZodBoolean>;
252
- }, z.core.$strip>]>;
240
+ }, z.core.$strip>;
253
241
  bend: z.ZodDefault<z.ZodNumber>;
254
242
  color: z.ZodDefault<z.ZodString>;
255
243
  size: z.ZodDefault<z.ZodNumber>;
@@ -677,26 +665,12 @@ declare const BoardItemSchema: z.ZodPipe<z.ZodAny, z.ZodTransform<BoardUnknownIt
677
665
  metadata?: Record<string, unknown> | undefined;
678
666
  type: "arrow";
679
667
  start: {
680
- kind: "point";
681
668
  x: number;
682
669
  y: number;
683
- } | {
684
- kind: "binding";
685
- target: string;
686
- nx: number;
687
- ny: number;
688
- precise: boolean;
689
670
  };
690
671
  end: {
691
- kind: "point";
692
672
  x: number;
693
673
  y: number;
694
- } | {
695
- kind: "binding";
696
- target: string;
697
- nx: number;
698
- ny: number;
699
- precise: boolean;
700
674
  };
701
675
  bend: number;
702
676
  color: string;
@@ -966,26 +940,12 @@ declare const BoardDocumentSchema: z.ZodObject<{
966
940
  metadata?: Record<string, unknown> | undefined;
967
941
  type: "arrow";
968
942
  start: {
969
- kind: "point";
970
943
  x: number;
971
944
  y: number;
972
- } | {
973
- kind: "binding";
974
- target: string;
975
- nx: number;
976
- ny: number;
977
- precise: boolean;
978
945
  };
979
946
  end: {
980
- kind: "point";
981
947
  x: number;
982
948
  y: number;
983
- } | {
984
- kind: "binding";
985
- target: string;
986
- nx: number;
987
- ny: number;
988
- precise: boolean;
989
949
  };
990
950
  bend: number;
991
951
  color: string;
@@ -1016,6 +976,76 @@ declare const BoardDocumentSchema: z.ZodObject<{
1016
976
  label: string;
1017
977
  color: string;
1018
978
  }, any>>>;
979
+ connections: z.ZodDefault<z.ZodArray<z.ZodObject<{
980
+ id: z.ZodString;
981
+ source: z.ZodObject<{
982
+ nodeId: z.ZodString;
983
+ anchor: z.ZodDefault<z.ZodUnion<readonly [z.ZodObject<{
984
+ kind: z.ZodLiteral<"auto">;
985
+ }, z.core.$strip>, z.ZodObject<{
986
+ kind: z.ZodLiteral<"side">;
987
+ side: z.ZodEnum<{
988
+ bottom: "bottom";
989
+ left: "left";
990
+ right: "right";
991
+ top: "top";
992
+ }>;
993
+ offset: z.ZodDefault<z.ZodNumber>;
994
+ }, z.core.$strip>, z.ZodObject<{
995
+ kind: z.ZodLiteral<"fixed">;
996
+ nx: z.ZodNumber;
997
+ ny: z.ZodNumber;
998
+ }, z.core.$strip>]>>;
999
+ }, z.core.$strip>;
1000
+ target: z.ZodObject<{
1001
+ nodeId: z.ZodString;
1002
+ anchor: z.ZodDefault<z.ZodUnion<readonly [z.ZodObject<{
1003
+ kind: z.ZodLiteral<"auto">;
1004
+ }, z.core.$strip>, z.ZodObject<{
1005
+ kind: z.ZodLiteral<"side">;
1006
+ side: z.ZodEnum<{
1007
+ bottom: "bottom";
1008
+ left: "left";
1009
+ right: "right";
1010
+ top: "top";
1011
+ }>;
1012
+ offset: z.ZodDefault<z.ZodNumber>;
1013
+ }, z.core.$strip>, z.ZodObject<{
1014
+ kind: z.ZodLiteral<"fixed">;
1015
+ nx: z.ZodNumber;
1016
+ ny: z.ZodNumber;
1017
+ }, z.core.$strip>]>>;
1018
+ }, z.core.$strip>;
1019
+ relation: z.ZodDefault<z.ZodString>;
1020
+ direction: z.ZodDefault<z.ZodEnum<{
1021
+ backward: "backward";
1022
+ both: "both";
1023
+ forward: "forward";
1024
+ none: "none";
1025
+ }>>;
1026
+ label: z.ZodDefault<z.ZodString>;
1027
+ routing: z.ZodDefault<z.ZodObject<{
1028
+ kind: z.ZodDefault<z.ZodEnum<{
1029
+ curve: "curve";
1030
+ orthogonal: "orthogonal";
1031
+ straight: "straight";
1032
+ }>>;
1033
+ bend: z.ZodDefault<z.ZodNumber>;
1034
+ waypoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
1035
+ x: z.ZodNumber;
1036
+ y: z.ZodNumber;
1037
+ }, z.core.$strip>>>;
1038
+ }, z.core.$strip>>;
1039
+ style: z.ZodDefault<z.ZodObject<{
1040
+ color: z.ZodDefault<z.ZodString>;
1041
+ size: z.ZodDefault<z.ZodNumber>;
1042
+ line: z.ZodDefault<z.ZodEnum<{
1043
+ dashed: "dashed";
1044
+ solid: "solid";
1045
+ }>>;
1046
+ }, z.core.$strip>>;
1047
+ metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1048
+ }, z.core.$strip>>>;
1019
1049
  }, z.core.$strip>;
1020
1050
  type BoardFrame = z.infer<typeof BoardFrameSchema>;
1021
1051
  type BoardViewport = z.infer<typeof BoardViewportSchema>;
@@ -1026,8 +1056,8 @@ type BoardMediaSnapshot = z.infer<typeof BoardMediaSnapshotSchema>;
1026
1056
  type BoardTextItem = z.infer<typeof BoardTextItemSchema>;
1027
1057
  type BoardGeoItem = z.infer<typeof BoardGeoItemSchema>;
1028
1058
  type DrawPoint = z.infer<typeof DrawPointSchema>;
1059
+ type BoardPoint = z.infer<typeof BoardPointSchema>;
1029
1060
  type BoardDrawItem = z.infer<typeof BoardDrawItemSchema>;
1030
- type ArrowEndpoint = z.infer<typeof ArrowEndpointSchema>;
1031
1061
  type BoardArrowItem = z.infer<typeof BoardArrowItemSchema>;
1032
1062
  type BoardFrameItem = z.infer<typeof BoardFrameItemSchema>;
1033
1063
  type BoardImageItem = z.infer<typeof BoardImageItemSchema>;
@@ -1039,9 +1069,25 @@ type BoardKnownItem = BoardImageItem | BoardVideoItem | BoardFileItem | BoardTex
1039
1069
  /** Any item, including forward-compatible unknown types. */
1040
1070
  type BoardItem = BoardKnownItem | BoardUnknownItem;
1041
1071
  type BoardDocument = z.infer<typeof BoardDocumentSchema>;
1072
+ /**
1073
+ * Parse a board document and drop connections whose endpoints are missing.
1074
+ *
1075
+ * The schema alone cannot express "every endpoint must name an existing item",
1076
+ * so referential integrity is enforced here, at the single place a document
1077
+ * enters the system. A dangling connection is dropped rather than repaired:
1078
+ * there is no correct node to invent, and keeping it would render nothing while
1079
+ * still counting as data.
1080
+ *
1081
+ * Item parsing stays lenient (see parseBoardItemLoose) — nodes carry content and
1082
+ * are never discarded — while connections are pure references and are only
1083
+ * meaningful with both ends present.
1084
+ */
1085
+ declare function parseBoardDocument(input: unknown): BoardDocument;
1086
+ /** Keep only the connections whose endpoints both exist in `items`. */
1087
+ declare function withResolvedConnections(document: BoardDocument): BoardDocument;
1042
1088
  declare function isUnknownItem(item: BoardItem): item is BoardUnknownItem;
1043
1089
  declare function isMediaItem(item: BoardItem): item is BoardImageItem | BoardVideoItem;
1044
1090
  /** Whether an item references a workspace file (image, video or file card). */
1045
1091
  declare function isFileBackedItem(item: BoardItem): item is BoardImageItem | BoardVideoItem | BoardFileItem;
1046
1092
  //#endregion
1047
- export { ArrowEndpoint, ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
1093
+ export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardPoint, BoardPointSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
@@ -1,4 +1,5 @@
1
1
  import { BOARD_ARROW_STROKE_SIZE } from "./board-constants.js";
2
+ import { BoardConnectionSchema } from "./board-connection.js";
2
3
  import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION } from "./board.js";
3
4
  import { z } from "zod";
4
5
  //#region ../protocol/dist/board-document.js
@@ -110,29 +111,31 @@ const DrawPointSchema = z.object({
110
111
  y: z.number().finite(),
111
112
  p: z.number().finite().min(0).max(1).default(.5)
112
113
  });
114
+ /** A world-space point. */
115
+ const BoardPointSchema = z.object({
116
+ x: z.number().finite(),
117
+ y: z.number().finite()
118
+ });
113
119
  const BoardDrawItemSchema = BoardItemBaseSchema.extend({
114
120
  type: z.literal("draw"),
115
121
  points: z.array(DrawPointSchema).default([]),
116
122
  color: z.string().min(1).default("brand"),
117
123
  size: z.number().finite().positive().default(4)
118
124
  });
119
- /** An arrow endpoint: a free point or a binding to another shape. */
120
- const ArrowEndpointSchema = z.union([z.object({
121
- kind: z.literal("point"),
122
- x: z.number().finite(),
123
- y: z.number().finite()
124
- }), z.object({
125
- kind: z.literal("binding"),
126
- target: z.string().min(1),
127
- nx: z.number().finite().default(.5),
128
- ny: z.number().finite().default(.5),
129
- precise: z.boolean().default(true)
130
- })]);
125
+ /**
126
+ * A free arrow — a standalone annotation stroke between two world points.
127
+ *
128
+ * Arrows do not relate nodes. A relation between two nodes is a
129
+ * `BoardConnection`, which is stored separately and resolves its geometry from
130
+ * the live node frames. Keeping the two apart is what lets an arrow be a plain
131
+ * shape (its own frame, freely movable) while a connection stays purely
132
+ * semantic — neither has to pretend to be the other.
133
+ */
131
134
  const BoardArrowItemSchema = BoardItemBaseSchema.extend({
132
135
  type: z.literal("arrow"),
133
- start: ArrowEndpointSchema,
134
- end: ArrowEndpointSchema,
135
- bend: z.number().finite().default(0),
136
+ start: BoardPointSchema,
137
+ end: BoardPointSchema,
138
+ bend: z.number().finite().min(-.85).max(.85).default(0),
136
139
  color: z.string().min(1).default("brand"),
137
140
  size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
138
141
  arrowStart: z.boolean().default(false),
@@ -305,8 +308,45 @@ const BoardDocumentSchema = z.object({
305
308
  mood: "clean"
306
309
  }),
307
310
  viewport: BoardViewportSchema,
308
- items: z.array(BoardItemSchema)
311
+ items: z.array(BoardItemSchema),
312
+ /**
313
+ * Node relations. Separate from `items` because a connection has no frame of
314
+ * its own — its geometry is derived from the nodes it joins, so it is a
315
+ * relation over the item set rather than a member of it.
316
+ *
317
+ * Connections referencing a missing node are dropped on parse: a relation to
318
+ * nothing is not a relation, and keeping one would let an invisible dangling
319
+ * edge accumulate silently. Callers that need to know write through the
320
+ * transaction API, which reports the reference error instead.
321
+ */
322
+ connections: z.array(BoardConnectionSchema).default([])
309
323
  });
324
+ /**
325
+ * Parse a board document and drop connections whose endpoints are missing.
326
+ *
327
+ * The schema alone cannot express "every endpoint must name an existing item",
328
+ * so referential integrity is enforced here, at the single place a document
329
+ * enters the system. A dangling connection is dropped rather than repaired:
330
+ * there is no correct node to invent, and keeping it would render nothing while
331
+ * still counting as data.
332
+ *
333
+ * Item parsing stays lenient (see parseBoardItemLoose) — nodes carry content and
334
+ * are never discarded — while connections are pure references and are only
335
+ * meaningful with both ends present.
336
+ */
337
+ function parseBoardDocument(input) {
338
+ return withResolvedConnections(BoardDocumentSchema.parse(input));
339
+ }
340
+ /** Keep only the connections whose endpoints both exist in `items`. */
341
+ function withResolvedConnections(document) {
342
+ if (document.connections.length === 0) return document;
343
+ const ids = new Set(document.items.map((item) => item.id));
344
+ const connections = document.connections.filter((connection) => ids.has(connection.source.nodeId) && ids.has(connection.target.nodeId));
345
+ return connections.length === document.connections.length ? document : {
346
+ ...document,
347
+ connections
348
+ };
349
+ }
310
350
  function isUnknownItem(item) {
311
351
  return item.type === UNKNOWN_BOARD_ITEM_TYPE;
312
352
  }
@@ -318,4 +358,4 @@ function isFileBackedItem(item) {
318
358
  return item.type === "image" || item.type === "video" || item.type === "file";
319
359
  }
320
360
  //#endregion
321
- export { ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearanceSchema, BoardArrowItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
361
+ export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearanceSchema, BoardArrowItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardPointSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardDocument, parseBoardItemLoose, unknownRealType, withResolvedConnections };
@@ -1,4 +1,5 @@
1
1
  import { BoardCapability, BoardRenderCost } from "./board-constants.js";
2
+ import "./board-connection.js";
2
3
  import { z } from "zod";
3
4
  //#region ../protocol/dist/board.d.ts
4
5
  declare const BOARD_EXTENSION: ".board";
@@ -1,4 +1,5 @@
1
1
  import "./board-constants.js";
2
+ import { BoardConnectionSchema } from "./board-connection.js";
2
3
  import { z } from "zod";
3
4
  //#region ../protocol/dist/board.js
4
5
  const BOARD_EXTENSION = ".board";
@@ -149,6 +150,7 @@ z.object({
149
150
  title: z.string().min(1).max(255).optional(),
150
151
  metadata: jsonObjectSchema.optional(),
151
152
  nodes: z.array(BoardNodeInputSchema).max(5e4).optional(),
153
+ connections: z.array(BoardConnectionSchema).max(5e4).optional(),
152
154
  effects: z.array(BoardEffectSchema.omit({
153
155
  boardId: true,
154
156
  revision: true
@@ -164,6 +166,7 @@ z.object({
164
166
  z.object({
165
167
  include: z.array(z.enum([
166
168
  "nodes",
169
+ "connections",
167
170
  "effects",
168
171
  "sequences",
169
172
  "clips",
@@ -1,6 +1,7 @@
1
1
  import { BoardCapability, BoardRenderCost } from "./board-constants.js";
2
+ import { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BoardConnection, BoardConnectionAnchor, BoardConnectionAnchorSchema, BoardConnectionDirection, BoardConnectionEndpoint, BoardConnectionEndpointSchema, BoardConnectionInput, BoardConnectionLine, BoardConnectionPatch, BoardConnectionPatchSchema, BoardConnectionRecord, BoardConnectionRouting, BoardConnectionRoutingConfig, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionSide, BoardConnectionStyle, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardRelationSchema, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, connectionNodeIds, connectionOtherNodeId, connectionTouchesNode, createBoardConnection, flipBoardConnection, normalizeBoardConnectionStyle } from "./board-connection.js";
2
3
  import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAssetRef, BoardAssetRefSchema, BoardClip, BoardClipSchema, BoardDiagnostic, BoardEffect, BoardEffectSchema, BoardManifest, BoardManifestSchema, BoardNodeInput, BoardNodeRecord, BoardRecord, BoardSequence, BoardSequenceSchema, BoardTarget, BoardTargetSchema, BoardValidationResult, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "./board.js";
3
4
  import "./realtime/board-awareness.js";
4
5
  import "./work.js";
5
6
  import "./realtime/types.js";
6
- export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAssetRef, BoardAssetRefSchema, BoardClip, BoardClipSchema, BoardDiagnostic, BoardEffect, BoardEffectSchema, BoardManifest, BoardManifestSchema, BoardNodeInput, BoardNodeRecord, BoardRecord, BoardSequence, BoardSequenceSchema, BoardTarget, BoardTargetSchema, BoardValidationResult, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest };
7
+ export { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAssetRef, BoardAssetRefSchema, BoardClip, BoardClipSchema, BoardConnection, BoardConnectionAnchor, BoardConnectionAnchorSchema, BoardConnectionDirection, BoardConnectionEndpoint, BoardConnectionEndpointSchema, BoardConnectionInput, BoardConnectionLine, BoardConnectionPatch, BoardConnectionPatchSchema, BoardConnectionRecord, BoardConnectionRouting, BoardConnectionRoutingConfig, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionSide, BoardConnectionStyle, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardDiagnostic, BoardEffect, BoardEffectSchema, BoardManifest, BoardManifestSchema, BoardNodeInput, BoardNodeRecord, BoardRecord, BoardRelationSchema, BoardSequence, BoardSequenceSchema, BoardTarget, BoardTargetSchema, BoardValidationResult, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, InvalidBoardFileError, connectionNodeIds, connectionOtherNodeId, connectionTouchesNode, createBoardConnection, flipBoardConnection, isBoardPath, normalizeBoardConnectionStyle, parseBoardManifest, serializeBoardManifest };
@@ -1,7 +1,8 @@
1
1
  import "./board-constants.js";
2
+ import { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BoardConnectionAnchorSchema, BoardConnectionEndpointSchema, BoardConnectionPatchSchema, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardRelationSchema, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, connectionNodeIds, connectionOtherNodeId, connectionTouchesNode, createBoardConnection, flipBoardConnection, normalizeBoardConnectionStyle } from "./board-connection.js";
2
3
  import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_MANIFEST_KIND, BoardAssetRefSchema, BoardClipSchema, BoardEffectSchema, BoardKeyframeSchema, BoardManifestSchema, BoardNodeInputSchema, BoardSequenceSchema, BoardTargetSchema, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "./board.js";
3
4
  import { BoardAwarenessDrawPointSchema, BoardAwarenessFrameSchema, BoardAwarenessGestureSchema, BoardAwarenessNodePreviewSchema, BoardAwarenessPointSchema, BoardAwarenessStateUpdateSchema, BoardAwarenessUpdateSchema } from "./realtime/board-awareness.js";
4
5
  import { COHUB_SOURCE_HEADER } from "./provenance.js";
5
6
  import { RESERVED_PLATFORM_PATH_SEGMENTS } from "./public-identifiers.js";
6
7
  import "./work-view-stats.js";
7
- export { BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_MANIFEST_KIND, BoardAssetRefSchema, BoardAwarenessDrawPointSchema, BoardAwarenessFrameSchema, BoardAwarenessGestureSchema, BoardAwarenessNodePreviewSchema, BoardAwarenessPointSchema, BoardAwarenessStateUpdateSchema, BoardAwarenessUpdateSchema, BoardClipSchema, BoardEffectSchema, BoardKeyframeSchema, BoardManifestSchema, BoardNodeInputSchema, BoardSequenceSchema, BoardTargetSchema, COHUB_SOURCE_HEADER, InvalidBoardFileError, RESERVED_PLATFORM_PATH_SEGMENTS, isBoardPath, parseBoardManifest, serializeBoardManifest };
8
+ export { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_MANIFEST_KIND, BoardAssetRefSchema, BoardAwarenessDrawPointSchema, BoardAwarenessFrameSchema, BoardAwarenessGestureSchema, BoardAwarenessNodePreviewSchema, BoardAwarenessPointSchema, BoardAwarenessStateUpdateSchema, BoardAwarenessUpdateSchema, BoardClipSchema, BoardConnectionAnchorSchema, BoardConnectionEndpointSchema, BoardConnectionPatchSchema, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardEffectSchema, BoardKeyframeSchema, BoardManifestSchema, BoardNodeInputSchema, BoardRelationSchema, BoardSequenceSchema, BoardTargetSchema, COHUB_SOURCE_HEADER, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, InvalidBoardFileError, RESERVED_PLATFORM_PATH_SEGMENTS, connectionNodeIds, connectionOtherNodeId, connectionTouchesNode, createBoardConnection, flipBoardConnection, isBoardPath, normalizeBoardConnectionStyle, parseBoardManifest, serializeBoardManifest };