@neta-art/cohub 8.6.0 → 8.7.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.
@@ -26,12 +26,17 @@ const idSchema = z.string().min(1).max(160);
26
26
  const jsonObjectSchema = z.record(z.string(), z.unknown());
27
27
  const finiteSchema = z.number().finite();
28
28
  const extensionTypeSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
29
- const BoardAuthoringFrameSchema = z.object({
29
+ const BoardAuthoringPositionSchema = z.object({
30
30
  x: finiteSchema,
31
- y: finiteSchema,
31
+ y: finiteSchema
32
+ }).strict();
33
+ const BoardAuthoringSizeSchema = z.object({
32
34
  width: finiteSchema.positive(),
33
- height: finiteSchema.positive(),
34
- rotation: finiteSchema.default(0)
35
+ height: finiteSchema.positive()
36
+ }).strict();
37
+ const BoardAuthoringPositionPatchSchema = z.object({
38
+ x: finiteSchema.optional(),
39
+ y: finiteSchema.optional()
35
40
  }).strict();
36
41
  const pointSchema = z.object({
37
42
  x: finiteSchema,
@@ -62,15 +67,26 @@ const BoardItemSourceSchema = z.object({
62
67
  path: z.string().refine(isSafeBoardSourcePath, "source path must be a safe relative Board file path"),
63
68
  snapshot: sourceSnapshotSchema.optional()
64
69
  }).strict();
65
- const baseFields = {
70
+ const sizedBaseFields = {
66
71
  id: idSchema,
67
- frame: BoardAuthoringFrameSchema,
72
+ position: BoardAuthoringPositionSchema.default({
73
+ x: 0,
74
+ y: 0
75
+ }),
76
+ rotation: finiteSchema.default(0),
68
77
  parentId: idSchema.nullable().optional(),
69
78
  locked: z.boolean().optional(),
70
- metadata: jsonObjectSchema.optional()
79
+ metadata: jsonObjectSchema.optional(),
80
+ size: BoardAuthoringSizeSchema.optional()
71
81
  };
72
82
  const styledBaseFields = {
73
- ...baseFields,
83
+ ...sizedBaseFields,
84
+ style: BoardItemStyleSchema.optional()
85
+ };
86
+ const { position: _positionField, size: _sizeField, ...strokeBaseFields } = sizedBaseFields;
87
+ const strokeStyledBaseFields = {
88
+ ...strokeBaseFields,
89
+ rotation: z.literal(0).default(0),
74
90
  style: BoardItemStyleSchema.optional()
75
91
  };
76
92
  const BoardTextAuthoringItemSchema = z.object({
@@ -90,12 +106,12 @@ const BoardGeoAuthoringItemSchema = z.object({
90
106
  }).strict()
91
107
  }).strict();
92
108
  const BoardDrawAuthoringItemSchema = z.object({
93
- ...styledBaseFields,
109
+ ...strokeStyledBaseFields,
94
110
  type: z.literal("draw"),
95
111
  props: z.object({ points: z.array(pointSchema).min(1) }).strict()
96
112
  }).strict();
97
113
  const BoardArrowAuthoringItemSchema = z.object({
98
- ...styledBaseFields,
114
+ ...strokeStyledBaseFields,
99
115
  type: z.literal("arrow"),
100
116
  props: z.object({
101
117
  start: worldPointSchema,
@@ -112,7 +128,7 @@ const BoardFrameAuthoringItemSchema = z.object({
112
128
  props: z.object({ label: z.string().default("Frame") }).strict()
113
129
  }).strict();
114
130
  const fileBackedFields = {
115
- ...baseFields,
131
+ ...sizedBaseFields,
116
132
  style: z.object({}).strict().optional(),
117
133
  source: BoardItemSourceSchema
118
134
  };
@@ -137,7 +153,7 @@ const BoardFileAuthoringItemSchema = z.object({
137
153
  props: z.object({}).strict()
138
154
  }).strict();
139
155
  const BoardTaskAuthoringItemSchema = z.object({
140
- ...baseFields,
156
+ ...sizedBaseFields,
141
157
  type: z.literal("task"),
142
158
  props: z.object({
143
159
  taskRunId: z.string().min(1),
@@ -159,7 +175,7 @@ const builtinItemSchemas = [
159
175
  ];
160
176
  const BoardBuiltinAuthoringItemSchema = z.discriminatedUnion("type", builtinItemSchemas);
161
177
  const BoardExtensionAuthoringItemSchema = z.object({
162
- ...baseFields,
178
+ ...sizedBaseFields,
163
179
  type: extensionTypeSchema,
164
180
  kindVersion: z.number().int().positive(),
165
181
  props: jsonObjectSchema,
@@ -171,16 +187,11 @@ const BoardExtensionAuthoringItemSchema = z.object({
171
187
  }).strict().optional()
172
188
  }).strict();
173
189
  const BoardAuthoringItemSchema = z.union([BoardBuiltinAuthoringItemSchema, BoardExtensionAuthoringItemSchema]);
174
- const framePatchSchema = z.object({
175
- x: finiteSchema.optional(),
176
- y: finiteSchema.optional(),
177
- width: finiteSchema.positive().optional(),
178
- height: finiteSchema.positive().optional(),
179
- rotation: finiteSchema.optional()
180
- }).strict();
181
190
  /** JSON Merge Patch semantics, constrained to the stable Item envelope. */
182
191
  const BoardItemPatchSchema = z.object({
183
- frame: framePatchSchema.optional(),
192
+ position: BoardAuthoringPositionPatchSchema.optional(),
193
+ size: BoardAuthoringSizeSchema.nullable().optional(),
194
+ rotation: finiteSchema.optional(),
184
195
  parentId: idSchema.nullable().optional(),
185
196
  locked: z.boolean().nullable().optional(),
186
197
  props: jsonObjectSchema.optional(),
@@ -1,4 +1,7 @@
1
1
  import { BoardAuthoringItemSchema, BoardItemPatchSchema } from "./board-authoring.js";
2
+ import { BoardNodeInputSchema } from "./board.js";
3
+ import { boardArrowFrame, boardDrawBounds, boardDrawPointsToLocal } from "./board-geometry.js";
4
+ import { BOARD_NATIVE_NODE_TYPES, validateBoardNodeInput } from "./board-node.js";
2
5
  //#region ../protocol/dist/board-codec.js
3
6
  var BoardItemValidationError = class extends Error {
4
7
  diagnostics;
@@ -16,7 +19,203 @@ function authoringSchemaDiagnostics(error, path) {
16
19
  path: `${path}.${issue.path.map(String).join(".") || "item"}`
17
20
  }));
18
21
  }
22
+ function authoringDiagnostic(diagnostic, path) {
23
+ const internalPath = diagnostic.path;
24
+ const authoringSuffix = internalPath.replace(/^node(?:\.|$)/, "").replace(/^data\.points/, "props.points").replace(/^data\./, "props.").replace(/^style\./, "style.").replace(/^view\./, "source.snapshot.").replace(/^nodeId$/, "id").replace(/^(x|y)$/, "position.$1").replace(/^(width|height)$/, "size.$1");
25
+ const mappedPath = `${path}${authoringSuffix ? `.${authoringSuffix}` : ""}`;
26
+ return {
27
+ ...diagnostic,
28
+ path: mappedPath,
29
+ message: diagnostic.message.replace(internalPath, mappedPath)
30
+ };
31
+ }
19
32
  const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
33
+ function commonData(item) {
34
+ return {
35
+ ...item.locked ? { locked: true } : {},
36
+ ...item.metadata ? { metadata: item.metadata } : {}
37
+ };
38
+ }
39
+ function defaultSize(type) {
40
+ if (type === "frame") return {
41
+ width: 480,
42
+ height: 320
43
+ };
44
+ if (type === "text") return {
45
+ width: 320,
46
+ height: 48
47
+ };
48
+ if (type === "task") return {
49
+ width: 420,
50
+ height: 240
51
+ };
52
+ if (type === "image" || type === "video") return {
53
+ width: 640,
54
+ height: 360
55
+ };
56
+ if (type === "audio") return {
57
+ width: 480,
58
+ height: 96
59
+ };
60
+ if (type === "file") return {
61
+ width: 360,
62
+ height: 220
63
+ };
64
+ return {
65
+ width: 240,
66
+ height: 160
67
+ };
68
+ }
69
+ function authoringFrame(item) {
70
+ if (item.type === "draw") {
71
+ const points = item.props;
72
+ const size = Number(item.style?.strokeWidth ?? 4);
73
+ const bounds = boardDrawBounds(points.points, size);
74
+ return {
75
+ x: bounds.x,
76
+ y: bounds.y,
77
+ width: bounds.width,
78
+ height: bounds.height,
79
+ rotation: item.rotation
80
+ };
81
+ }
82
+ if (item.type === "arrow") {
83
+ const props = item.props;
84
+ return {
85
+ ...boardArrowFrame({
86
+ ...props,
87
+ size: Number(item.style?.strokeWidth ?? 2.5)
88
+ }),
89
+ rotation: item.rotation
90
+ };
91
+ }
92
+ const position = item.position;
93
+ const size = item.size ?? defaultSize(item.type);
94
+ return {
95
+ x: position.x,
96
+ y: position.y,
97
+ width: size.width,
98
+ height: size.height,
99
+ rotation: item.rotation
100
+ };
101
+ }
102
+ function boardAuthoringItemToNode(value, options = {}) {
103
+ const item = BoardAuthoringItemSchema.parse(value);
104
+ const node = {
105
+ nodeId: item.id,
106
+ type: item.type,
107
+ parentId: item.parentId ?? null,
108
+ orderKey: options.orderKey ?? null,
109
+ ...authoringFrame(item),
110
+ refKind: null,
111
+ refPath: null,
112
+ refUrl: null,
113
+ view: {},
114
+ style: {},
115
+ data: commonData(item)
116
+ };
117
+ const itemStyle = "style" in item ? item.style : void 0;
118
+ const color = itemStyle && "color" in itemStyle ? itemStyle.color : void 0;
119
+ const strokeWidth = itemStyle && "strokeWidth" in itemStyle ? itemStyle.strokeWidth : void 0;
120
+ const fillOpacity = itemStyle && "fillOpacity" in itemStyle ? itemStyle.fillOpacity : void 0;
121
+ switch (item.type) {
122
+ case "text":
123
+ node.data = {
124
+ ...node.data,
125
+ ...item.props,
126
+ color: color ?? "neutral"
127
+ };
128
+ break;
129
+ case "geo":
130
+ node.data = {
131
+ ...node.data,
132
+ geo: item.props.shape,
133
+ text: item.props.text,
134
+ color: color ?? "brand",
135
+ fillOpacity: fillOpacity ?? 0
136
+ };
137
+ break;
138
+ case "draw": {
139
+ const points = item.props;
140
+ node.data = {
141
+ ...node.data,
142
+ points: boardDrawPointsToLocal(points.points, node.x, node.y),
143
+ color: color ?? "brand",
144
+ size: strokeWidth ?? 4
145
+ };
146
+ break;
147
+ }
148
+ case "arrow":
149
+ node.data = {
150
+ ...node.data,
151
+ ...item.props,
152
+ color: color ?? "brand",
153
+ size: strokeWidth ?? 2.5
154
+ };
155
+ break;
156
+ case "frame":
157
+ node.data = {
158
+ ...node.data,
159
+ ...item.props,
160
+ color: color ?? "neutral"
161
+ };
162
+ break;
163
+ case "image": {
164
+ const source = item.source;
165
+ node.data = {
166
+ ...node.data,
167
+ ...item.props.crop ? { crop: item.props.crop } : {}
168
+ };
169
+ node.refKind = "space_file";
170
+ node.refPath = source.path;
171
+ node.view = source.snapshot ?? {};
172
+ break;
173
+ }
174
+ case "video":
175
+ case "audio":
176
+ case "file": {
177
+ const source = item.source;
178
+ node.refKind = "space_file";
179
+ node.refPath = source.path;
180
+ node.view = source.snapshot ?? {};
181
+ break;
182
+ }
183
+ case "task":
184
+ node.data = {
185
+ ...node.data,
186
+ taskRunId: item.props.taskRunId
187
+ };
188
+ node.view = item.props.snapshot;
189
+ break;
190
+ default: {
191
+ const extension = item;
192
+ node.data = {
193
+ ...node.data,
194
+ ...extension.props,
195
+ kindVersion: extension.kindVersion
196
+ };
197
+ node.style = extension.style ?? {};
198
+ if (extension.source) {
199
+ node.refKind = extension.source.kind;
200
+ node.refPath = extension.source.ref;
201
+ node.view = extension.source.snapshot ?? {};
202
+ }
203
+ }
204
+ }
205
+ if (BOARD_NATIVE_NODE_TYPES.includes(node.type)) {
206
+ const diagnostics = validateBoardNodeInput(node);
207
+ if (diagnostics.length) throw new BoardItemValidationError(diagnostics.map((diagnostic) => authoringDiagnostic(diagnostic, options.path ?? "item")));
208
+ } else {
209
+ const parsed = BoardNodeInputSchema.safeParse(node);
210
+ if (!parsed.success) throw new BoardItemValidationError(parsed.error.issues.map((issue) => ({
211
+ severity: "error",
212
+ code: "INVALID_BOARD_NODE",
213
+ message: issue.message,
214
+ path: `${options.path ?? "item"}.${issue.path.join(".") || "item"}`
215
+ })));
216
+ }
217
+ return node;
218
+ }
20
219
  function applyBoardItemPatch(current, value, path = "item") {
21
220
  const merged = mergePatch(current, BoardItemPatchSchema.parse(value));
22
221
  merged.id = current.id;
@@ -34,4 +233,4 @@ function mergePatch(current, patch) {
34
233
  return result;
35
234
  }
36
235
  //#endregion
37
- export { BoardItemValidationError, applyBoardItemPatch, authoringSchemaDiagnostics };
236
+ export { BoardItemValidationError, applyBoardItemPatch, authoringSchemaDiagnostics, boardAuthoringItemToNode };
@@ -0,0 +1,15 @@
1
+ import { BoardFrame, BoardPoint, DrawPoint } from "./board-document.js";
2
+ //#region ../protocol/dist/board-geometry.d.ts
3
+ type BoardArrowGeometryInput = {
4
+ start: BoardPoint;
5
+ end: BoardPoint;
6
+ bend: number;
7
+ size: number;
8
+ };
9
+ /** Radius of a draw sample in world units given stroke size and pressure. */
10
+ declare function boardDrawSampleRadius(size: number, pressure: number): number;
11
+ /** Exact local bounds required by the draw node contract. */
12
+ declare function boardDrawBounds(points: readonly DrawPoint[], size: number): Pick<BoardFrame, "x" | "y" | "width" | "height">;
13
+ declare function boardArrowFrame(input: BoardArrowGeometryInput): BoardFrame;
14
+ //#endregion
15
+ export { BoardArrowGeometryInput, boardArrowFrame, boardDrawBounds, boardDrawSampleRadius };
@@ -0,0 +1,137 @@
1
+ //#region ../protocol/dist/board-geometry.js
2
+ /**
3
+ * Renderer-independent Board geometry contracts.
4
+ *
5
+ * Renderers build their own primitives from these results, but validation and
6
+ * item factories use the same coordinate and bounds rules.
7
+ */
8
+ /** Radius of a draw sample in world units given stroke size and pressure. */
9
+ function boardDrawSampleRadius(size, pressure) {
10
+ const clamped = Math.min(1, Math.max(0, pressure));
11
+ return Math.max(.5, size / 2 * (.5 + clamped));
12
+ }
13
+ /** Convert persisted frame-local draw points to world-space authoring points. */
14
+ function boardDrawPointsToWorld(points, x, y) {
15
+ return points.map((point) => ({
16
+ x: point.x + x,
17
+ y: point.y + y,
18
+ p: point.p
19
+ }));
20
+ }
21
+ /** Convert world-space authoring points to persisted frame-local points. */
22
+ function boardDrawPointsToLocal(points, x, y) {
23
+ return points.map((point) => ({
24
+ x: point.x - x,
25
+ y: point.y - y,
26
+ p: point.p
27
+ }));
28
+ }
29
+ /** Exact local bounds required by the draw node contract. */
30
+ function boardDrawBounds(points, size) {
31
+ if (points.length === 0) return {
32
+ x: 0,
33
+ y: 0,
34
+ width: 1,
35
+ height: 1
36
+ };
37
+ let minX = Number.POSITIVE_INFINITY;
38
+ let minY = Number.POSITIVE_INFINITY;
39
+ let maxX = Number.NEGATIVE_INFINITY;
40
+ let maxY = Number.NEGATIVE_INFINITY;
41
+ for (const point of points) {
42
+ const radius = boardDrawSampleRadius(size, point.p);
43
+ minX = Math.min(minX, point.x - radius);
44
+ minY = Math.min(minY, point.y - radius);
45
+ maxX = Math.max(maxX, point.x + radius);
46
+ maxY = Math.max(maxY, point.y + radius);
47
+ }
48
+ return {
49
+ x: minX,
50
+ y: minY,
51
+ width: Math.max(1, maxX - minX),
52
+ height: Math.max(1, maxY - minY)
53
+ };
54
+ }
55
+ /** Resolve the world-space quadratic curve represented by a free arrow. */
56
+ function boardResolveArrow(input) {
57
+ const start = input.start;
58
+ const end = input.end;
59
+ const mid = {
60
+ x: (start.x + end.x) / 2,
61
+ y: (start.y + end.y) / 2
62
+ };
63
+ if (!input.bend) return {
64
+ start,
65
+ end,
66
+ control: mid
67
+ };
68
+ const dx = end.x - start.x;
69
+ const dy = end.y - start.y;
70
+ const length = Math.hypot(dx, dy) || 1;
71
+ const offset = input.bend * length;
72
+ return {
73
+ start,
74
+ end,
75
+ control: {
76
+ x: mid.x + -dy / length * offset,
77
+ y: mid.y + dx / length * offset
78
+ }
79
+ };
80
+ }
81
+ /** Sample an already resolved arrow curve. */
82
+ function boardSampleResolvedArrow(resolved, segments) {
83
+ const { start, control, end } = resolved;
84
+ const count = Math.max(1, Math.floor(segments));
85
+ const points = [];
86
+ for (let index = 0; index <= count; index += 1) {
87
+ const t = index / count;
88
+ const mt = 1 - t;
89
+ points.push({
90
+ x: mt * mt * start.x + 2 * mt * t * control.x + t * t * end.x,
91
+ y: mt * mt * start.y + 2 * mt * t * control.y + t * t * end.y
92
+ });
93
+ }
94
+ return points;
95
+ }
96
+ /** World bounds of the curve, padded for the stroke. */
97
+ function boardArrowBounds(input) {
98
+ const { start, control, end } = boardResolveArrow(input);
99
+ const points = [start, end];
100
+ for (const axis of ["x", "y"]) {
101
+ const denominator = start[axis] - 2 * control[axis] + end[axis];
102
+ if (Math.abs(denominator) < 1e-12) continue;
103
+ const t = (start[axis] - control[axis]) / denominator;
104
+ if (t > 0 && t < 1) {
105
+ const mt = 1 - t;
106
+ points.push({
107
+ x: mt * mt * start.x + 2 * mt * t * control.x + t * t * end.x,
108
+ y: mt * mt * start.y + 2 * mt * t * control.y + t * t * end.y
109
+ });
110
+ }
111
+ }
112
+ let minX = Number.POSITIVE_INFINITY;
113
+ let minY = Number.POSITIVE_INFINITY;
114
+ let maxX = Number.NEGATIVE_INFINITY;
115
+ let maxY = Number.NEGATIVE_INFINITY;
116
+ for (const point of points) {
117
+ minX = Math.min(minX, point.x);
118
+ minY = Math.min(minY, point.y);
119
+ maxX = Math.max(maxX, point.x);
120
+ maxY = Math.max(maxY, point.y);
121
+ }
122
+ const pad = Math.max(8, input.size * 2);
123
+ return {
124
+ x: minX - pad,
125
+ y: minY - pad,
126
+ width: Math.max(1, maxX - minX + pad * 2),
127
+ height: Math.max(1, maxY - minY + pad * 2)
128
+ };
129
+ }
130
+ function boardArrowFrame(input) {
131
+ return {
132
+ ...boardArrowBounds(input),
133
+ rotation: 0
134
+ };
135
+ }
136
+ //#endregion
137
+ export { boardArrowBounds, boardArrowFrame, boardDrawBounds, boardDrawPointsToLocal, boardDrawPointsToWorld, boardDrawSampleRadius, boardResolveArrow, boardSampleResolvedArrow };
@@ -1,5 +1,6 @@
1
1
  import { BOARD_ARROW_STROKE_SIZE } from "./board-constants.js";
2
2
  import { BoardTaskSnapshotSchema } from "./board-document.js";
3
+ import { boardArrowFrame, boardDrawBounds } from "./board-geometry.js";
3
4
  import { z } from "zod";
4
5
  //#region ../protocol/dist/board-node.js
5
6
  const BOARD_COLOR_IDS = [
@@ -155,5 +156,97 @@ function jsonSchema(schema) {
155
156
  return z.toJSONSchema(schema);
156
157
  }
157
158
  jsonSchema(nodeEnvelopeSchema), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]));
159
+ function issueDiagnostic(issue, path) {
160
+ const fullPath = [path, ...issue.path].join(".");
161
+ const values = "values" in issue && Array.isArray(issue.values) ? issue.values.filter((value) => typeof value === "string") : void 0;
162
+ return {
163
+ severity: "error",
164
+ code: "INVALID_BOARD_NODE",
165
+ message: `${fullPath}: ${issue.message}`,
166
+ path: fullPath,
167
+ ...values?.length ? { allowedValues: values } : {}
168
+ };
169
+ }
170
+ function drawGeometryDiagnostic(node, data, path) {
171
+ const { x: minX, y: minY, width, height } = boardDrawBounds(data.points, data.size);
172
+ const tolerance = Math.max(.01, node.width * 1e-6, node.height * 1e-6);
173
+ if (Math.abs(minX) <= tolerance && Math.abs(minY) <= tolerance && Math.abs(width - node.width) <= tolerance && Math.abs(height - node.height) <= tolerance) return null;
174
+ return {
175
+ severity: "error",
176
+ code: "INVALID_BOARD_GEOMETRY",
177
+ message: `${path}.data.points must use frame-local coordinates and match the node frame`,
178
+ path: `${path}.data.points`,
179
+ expected: `frame-local bounds x=0, y=0, width=${width}, height=${height}`,
180
+ received: {
181
+ frame: {
182
+ x: node.x,
183
+ y: node.y,
184
+ width: node.width,
185
+ height: node.height
186
+ },
187
+ pointsBounds: {
188
+ x: minX,
189
+ y: minY,
190
+ width,
191
+ height
192
+ }
193
+ },
194
+ coordinateSpace: "frame-local"
195
+ };
196
+ }
197
+ function validateBoardNodeInput(node, path = "node") {
198
+ const envelopeResult = nodeEnvelopeSchema.safeParse(node);
199
+ if (!envelopeResult.success) return envelopeResult.error.issues.map((issue) => issueDiagnostic(issue, path));
200
+ if (typeof node.type !== "string" || !BOARD_NATIVE_NODE_TYPES.includes(node.type)) return [{
201
+ severity: "error",
202
+ code: "INVALID_BOARD_NODE",
203
+ message: `${path}.type is not supported`,
204
+ path: `${path}.type`,
205
+ expected: "BoardNativeNodeType",
206
+ received: node.type,
207
+ allowedValues: BOARD_NATIVE_NODE_TYPES
208
+ }];
209
+ const type = node.type;
210
+ const dataResult = dataSchemas[type].safeParse(node.data ?? {});
211
+ if (!dataResult.success) return dataResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.data`));
212
+ const viewResult = viewSchemas[type].safeParse(node.view ?? {});
213
+ if (!viewResult.success) return viewResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.view`));
214
+ if (type === "image" || type === "video" || type === "audio" || type === "file") {
215
+ if (node.refKind !== "space_file" || typeof node.refPath !== "string" || !node.refPath) return [{
216
+ severity: "error",
217
+ code: "INVALID_BOARD_NODE",
218
+ message: `${path} requires a space file reference`,
219
+ path: `${path}.refPath`,
220
+ expected: "non-empty refPath with refKind space_file"
221
+ }];
222
+ }
223
+ if (type === "draw") {
224
+ const diagnostic = drawGeometryDiagnostic(node, dataResult.data, path);
225
+ return diagnostic ? [diagnostic] : [];
226
+ }
227
+ if (type === "arrow") {
228
+ const data = dataResult.data;
229
+ const expected = boardArrowFrame(data);
230
+ const tolerance = Math.max(.01, node.width * 1e-6, node.height * 1e-6);
231
+ if (Math.abs(node.x - expected.x) > tolerance || Math.abs(node.y - expected.y) > tolerance || Math.abs(node.width - expected.width) > tolerance || Math.abs(node.height - expected.height) > tolerance) return [{
232
+ severity: "error",
233
+ code: "INVALID_BOARD_GEOMETRY",
234
+ message: `${path}.data endpoints must use world-space coordinates and match the arrow frame`,
235
+ path: `${path}.data`,
236
+ expected: `world-space curve bounds x=${expected.x}, y=${expected.y}, width=${expected.width}, height=${expected.height}`,
237
+ received: {
238
+ frame: {
239
+ x: node.x,
240
+ y: node.y,
241
+ width: node.width,
242
+ height: node.height
243
+ },
244
+ curveBounds: expected
245
+ },
246
+ coordinateSpace: "world"
247
+ }];
248
+ }
249
+ return [];
250
+ }
158
251
  //#endregion
159
- export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BoardColorIdSchema, BoardGeoKindSchema };
252
+ export { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BoardColorIdSchema, BoardGeoKindSchema, validateBoardNodeInput };
@@ -78,7 +78,7 @@ const BoardCameraFocusParamsSchema = z.object({
78
78
  path: ["minZoom"]
79
79
  });
80
80
  });
81
- z.object({
81
+ const BoardNodeInputSchema = z.object({
82
82
  nodeId: idSchema,
83
83
  type: z.string().min(1).max(40),
84
84
  parentId: idSchema.nullable(),
@@ -195,4 +195,4 @@ function isBoardPath(path) {
195
195
  return path.toLowerCase().endsWith(BOARD_EXTENSION);
196
196
  }
197
197
  //#endregion
198
- export { BOARD_AUTHORING_SCHEMAS, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_MANIFEST_KIND, BoardCameraFocusParamsSchema, BoardCameraFocusSchema, BoardCameraStateSchema, BoardCreateInputSchema, BoardManifestSchema, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest };
198
+ export { BOARD_AUTHORING_SCHEMAS, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_MANIFEST_KIND, BoardCameraFocusParamsSchema, BoardCameraFocusSchema, BoardCameraStateSchema, BoardCreateInputSchema, BoardManifestSchema, BoardNodeInputSchema, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest };
@@ -8,8 +8,9 @@ import "./board-capability-registry.js";
8
8
  import { BOARD_COLOR_IDS, BOARD_GEO_KINDS, BoardColorId, BoardGeoKind } from "./board-node.js";
9
9
  import "./board-codec.js";
10
10
  import "./board-content.js";
11
+ import { BoardArrowGeometryInput, boardArrowFrame, boardDrawBounds, boardDrawSampleRadius } from "./board-geometry.js";
11
12
  import "./realtime/board-awareness.js";
12
13
  import "./app.js";
13
14
  import "./realtime/types.js";
14
15
  import "./app-catalog.js";
15
- export { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_COLOR_IDS, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_GEO_KINDS, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BoardAnimationTarget, BoardAnimationTargetSchema, type BoardAssetRef, BoardAuthoringItem, BoardAuthoringItemSchema, BoardAuthoringSnapshot, BoardCameraFocus, BoardCameraFocusParams, BoardCameraFocusParamsSchema, BoardCameraFocusSchema, BoardCameraState, BoardCameraStateSchema, BoardCapability, BoardColorId, BoardComposition, BoardCompositionInput, BoardCompositionInputSchema, BoardCompositionSchema, BoardConnection, BoardConnectionAnchor, BoardConnectionAnchorSchema, BoardConnectionDirection, BoardConnectionEndpoint, BoardConnectionEndpointSchema, BoardConnectionInput, BoardConnectionLine, BoardConnectionPatch, BoardConnectionPatchSchema, BoardConnectionRecord, BoardConnectionRouting, BoardConnectionRoutingConfig, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionSide, BoardConnectionStyle, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardDiagnostic, BoardEasing, BoardEasingSchema, type BoardEffect, BoardGeoKind, BoardManifest, BoardManifestSchema, BoardMutationReceipt, BoardPlaybackSnapshot, BoardPlaybackStatus, BoardProceduralClip, BoardProceduralClipSchema, BoardRelationSchema, BoardRenderCost, BoardSemanticCommand, BoardSemanticCommandSchema, BoardTimelineMarker, BoardTimelineMarkerSchema, BoardTrack, BoardTrackInterpolation, BoardTrackSchema, BoardValidationResult, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, clampBoardStrokeSize, connectionItemIds, connectionOtherItemId, connectionTouchesItem, createBoardConnection, flipBoardConnection, isBoardPath, normalizeBoardConnectionStyle, parseBoardManifest, serializeBoardManifest };
16
+ export { AUTO_BOARD_CONNECTION_ANCHOR, BOARD_COLOR_IDS, BOARD_CONNECTION_DIRECTIONS, BOARD_CONNECTION_LINES, BOARD_CONNECTION_ROUTINGS, BOARD_CONNECTION_SIDES, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BOARD_GEO_KINDS, BOARD_STROKE_MAX_SIZE, BOARD_STROKE_MIN_SIZE, BoardAnimationTarget, BoardAnimationTargetSchema, BoardArrowGeometryInput, type BoardAssetRef, BoardAuthoringItem, BoardAuthoringItemSchema, BoardAuthoringSnapshot, BoardCameraFocus, BoardCameraFocusParams, BoardCameraFocusParamsSchema, BoardCameraFocusSchema, BoardCameraState, BoardCameraStateSchema, BoardCapability, BoardColorId, BoardComposition, BoardCompositionInput, BoardCompositionInputSchema, BoardCompositionSchema, BoardConnection, BoardConnectionAnchor, BoardConnectionAnchorSchema, BoardConnectionDirection, BoardConnectionEndpoint, BoardConnectionEndpointSchema, BoardConnectionInput, BoardConnectionLine, BoardConnectionPatch, BoardConnectionPatchSchema, BoardConnectionRecord, BoardConnectionRouting, BoardConnectionRoutingConfig, BoardConnectionRoutingSchema, BoardConnectionSchema, BoardConnectionSide, BoardConnectionStyle, BoardConnectionStyleSchema, BoardConnectionWaypointSchema, BoardDiagnostic, BoardEasing, BoardEasingSchema, type BoardEffect, BoardGeoKind, BoardManifest, BoardManifestSchema, BoardMutationReceipt, BoardPlaybackSnapshot, BoardPlaybackStatus, BoardProceduralClip, BoardProceduralClipSchema, BoardRelationSchema, BoardRenderCost, BoardSemanticCommand, BoardSemanticCommandSchema, BoardTimelineMarker, BoardTimelineMarkerSchema, BoardTrack, BoardTrackInterpolation, BoardTrackSchema, BoardValidationResult, DEFAULT_BOARD_CONNECTION_ROUTING, DEFAULT_BOARD_CONNECTION_STYLE, DEFAULT_BOARD_RELATION, boardArrowFrame, boardDrawBounds, boardDrawSampleRadius, clampBoardStrokeSize, connectionItemIds, connectionOtherItemId, connectionTouchesItem, createBoardConnection, flipBoardConnection, isBoardPath, normalizeBoardConnectionStyle, parseBoardManifest, serializeBoardManifest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "8.6.0",
3
+ "version": "8.7.0",
4
4
  "description": "Cohub SDK for spaces, sessions, boards, and realtime agent collaboration.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,