agentic-canvas 0.10.0 → 0.11.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.
@@ -1,13 +1,19 @@
1
- import {
2
- isMachineMade,
3
- principalSchema,
4
- storedPrincipalSchema
5
- } from "./chunk-JOBTIAFJ.js";
1
+ // src/core/principal.ts
2
+ import { PRINCIPAL_KINDS, isMachineMade, parsePrincipal } from "agentic-identity";
3
+ import { z } from "zod";
4
+ var principalSchema = z.object({ kind: z.string(), id: z.string() }).strict().transform((value, ctx) => {
5
+ try {
6
+ return parsePrincipal(value);
7
+ } catch (error) {
8
+ ctx.addIssue({ code: "custom", message: error instanceof Error ? error.message : "invalid principal" });
9
+ return z.NEVER;
10
+ }
11
+ });
12
+ var storedPrincipalSchema = z.object({ kind: z.enum(PRINCIPAL_KINDS), id: z.string().min(1).max(200) }).strict();
6
13
 
7
14
  // src/core/board.ts
8
- import { randomUUID } from "crypto";
9
15
  import * as Y from "yjs";
10
- import { z } from "zod";
16
+ import { z as z2 } from "zod";
11
17
  var CanvasError = class extends Error {
12
18
  constructor(code, message) {
13
19
  super(message);
@@ -19,18 +25,18 @@ var CanvasError = class extends Error {
19
25
  var SHAPE_TYPES = ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
20
26
  var MAX_ELEMENTS = 1e4;
21
27
  var MAX_TEXT = 1e4;
22
- var id = z.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
23
- var coord = z.number().finite().min(-1e7).max(1e7);
24
- var size = z.number().finite().gt(0).max(1e7);
25
- var text = z.string().max(MAX_TEXT);
28
+ var id = z2.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
29
+ var coord = z2.number().finite().min(-1e7).max(1e7);
30
+ var size = z2.number().finite().gt(0).max(1e7);
31
+ var text = z2.string().max(MAX_TEXT);
26
32
  var COLOR_PATTERN = /^(#[0-9a-fA-F]{3,8}|[a-z]{3,20})$/;
27
33
  var IMAGE_PATTERN = /^(data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+=*|blob:[A-Za-z0-9._-]{1,200})$/;
28
- var color = z.string().regex(COLOR_PATTERN);
29
- var imageSrc = z.string().max(2e6).regex(IMAGE_PATTERN);
30
- var endpoint = z.union([z.object({ id }).strict(), z.object({ x: coord, y: coord }).strict()]);
31
- var shapeInput = z.object({
34
+ var color = z2.string().regex(COLOR_PATTERN);
35
+ var imageSrc = z2.string().max(2e6).regex(IMAGE_PATTERN);
36
+ var endpoint = z2.union([z2.object({ id }).strict(), z2.object({ x: coord, y: coord }).strict()]);
37
+ var shapeInput = z2.object({
32
38
  id: id.optional(),
33
- type: z.enum(SHAPE_TYPES),
39
+ type: z2.enum(SHAPE_TYPES),
34
40
  x: coord,
35
41
  y: coord,
36
42
  w: size.default(120),
@@ -42,40 +48,40 @@ var shapeInput = z.object({
42
48
  }).strict().refine((s) => s.type === "image" === (s.src !== void 0), {
43
49
  message: "src is required for image shapes and allowed on no other type"
44
50
  });
45
- var connectorInput = z.object({
51
+ var connectorInput = z2.object({
46
52
  id: id.optional(),
47
53
  from: endpoint,
48
54
  to: endpoint,
49
55
  text: text.default(""),
50
- startArrow: z.boolean().default(false),
51
- endArrow: z.boolean().default(true),
56
+ startArrow: z2.boolean().default(false),
57
+ endArrow: z2.boolean().default(true),
52
58
  stroke: color.optional()
53
59
  }).strict();
54
- var shapePatch = z.object({ x: coord, y: coord, w: size, h: size, text, fill: color, stroke: color, src: imageSrc }).partial().strict();
55
- var connectorPatch = z.object({ from: endpoint, to: endpoint, text, startArrow: z.boolean(), endArrow: z.boolean(), stroke: color }).partial().strict();
56
- var delta = z.number().finite().min(-2e7).max(2e7);
60
+ var shapePatch = z2.object({ x: coord, y: coord, w: size, h: size, text, fill: color, stroke: color, src: imageSrc }).partial().strict();
61
+ var connectorPatch = z2.object({ from: endpoint, to: endpoint, text, startArrow: z2.boolean(), endArrow: z2.boolean(), stroke: color }).partial().strict();
62
+ var delta = z2.number().finite().min(-2e7).max(2e7);
57
63
  var MAX_Z = 1e9;
58
64
  var stampFields = {
59
65
  id,
60
- z: z.number().finite().min(-MAX_Z).max(MAX_Z),
66
+ z: z2.number().finite().min(-MAX_Z).max(MAX_Z),
61
67
  groupId: id.optional(),
62
68
  author: storedPrincipalSchema,
63
69
  editedBy: storedPrincipalSchema
64
70
  };
65
- var storedShape = z.object({ ...stampFields, type: z.enum(SHAPE_TYPES), x: coord, y: coord, w: size, h: size, text, fill: color.optional(), stroke: color.optional(), src: imageSrc.optional() }).refine((s) => s.type === "image" === (s.src !== void 0), { message: "src is required for image shapes and allowed on no other type" });
66
- var storedConnector = z.object({
71
+ var storedShape = z2.object({ ...stampFields, type: z2.enum(SHAPE_TYPES), x: coord, y: coord, w: size, h: size, text, fill: color.optional(), stroke: color.optional(), src: imageSrc.optional() }).refine((s) => s.type === "image" === (s.src !== void 0), { message: "src is required for image shapes and allowed on no other type" });
72
+ var storedConnector = z2.object({
67
73
  ...stampFields,
68
- type: z.literal("connector"),
74
+ type: z2.literal("connector"),
69
75
  from: endpoint,
70
76
  to: endpoint,
71
77
  text,
72
- startArrow: z.boolean(),
73
- endArrow: z.boolean(),
78
+ startArrow: z2.boolean(),
79
+ endArrow: z2.boolean(),
74
80
  stroke: color.optional()
75
81
  });
76
- var storedGroup = z.object({ ...stampFields, type: z.literal("group") });
77
- var storedElement = z.union([storedShape, storedConnector, storedGroup]);
78
- var titleSchema = z.string().max(200);
82
+ var storedGroup = z2.object({ ...stampFields, type: z2.literal("group") });
83
+ var storedElement = z2.union([storedShape, storedConnector, storedGroup]);
84
+ var titleSchema = z2.string().max(200);
79
85
  function parse(schema, value) {
80
86
  const result = schema.safeParse(value);
81
87
  if (!result.success) {
@@ -134,7 +140,7 @@ var Board = class _Board {
134
140
  return value.success ? value.data : void 0;
135
141
  }
136
142
  setTitle(title, actor) {
137
- const t = parse(z.string().max(200), title);
143
+ const t = parse(z2.string().max(200), title);
138
144
  const by = parse(principalSchema, actor);
139
145
  this.doc.transact(() => {
140
146
  this.meta.set("title", t);
@@ -325,7 +331,7 @@ var Board = class _Board {
325
331
  /** Shift an element, or a whole group. Returns the ids that moved. Bound connector ends follow their shapes. */
326
332
  move(elementId, dx, dy, actor) {
327
333
  const by = parse(principalSchema, actor);
328
- const [mx, my] = parse(z.tuple([delta, delta]), [dx, dy]);
334
+ const [mx, my] = parse(z2.tuple([delta, delta]), [dx, dy]);
329
335
  this.require(elementId);
330
336
  const free = (e) => hasId(e) ? e : { x: e.x + mx, y: e.y + my };
331
337
  const plan = [];
@@ -354,7 +360,7 @@ var Board = class _Board {
354
360
  }
355
361
  group(ids, actor, groupId) {
356
362
  const by = parse(principalSchema, actor);
357
- const members = parse(z.array(id).min(2).max(MAX_ELEMENTS), ids);
363
+ const members = parse(z2.array(id).min(2).max(MAX_ELEMENTS), ids);
358
364
  const gid = parse(id.optional(), groupId);
359
365
  if (new Set(members).size !== members.length) throw new CanvasError("INVALID", "member ids repeat");
360
366
  const parents = new Set(members.map((m) => this.require(m).groupId));
@@ -424,12 +430,12 @@ var Board = class _Board {
424
430
  }
425
431
  /** The z value just past the top or the bottom. Not clamped: the caller checks the range. */
426
432
  edgeZ(direction) {
427
- let z2 = 0;
433
+ let z3 = 0;
428
434
  for (const m of this.elements.values()) {
429
435
  const value = m instanceof Y.Map ? m.get("z") : void 0;
430
- if (isValidZ(value)) z2 = direction === 1 ? Math.max(z2, value) : Math.min(z2, value);
436
+ if (isValidZ(value)) z3 = direction === 1 ? Math.max(z3, value) : Math.min(z3, value);
431
437
  }
432
- return z2 + direction;
438
+ return z3 + direction;
433
439
  }
434
440
  /** The element and, for a group, everything below it. Cycle-safe. */
435
441
  subtree(elementId) {
@@ -446,7 +452,7 @@ var Board = class _Board {
446
452
  }
447
453
  create(fields, actor) {
448
454
  const by = parse(principalSchema, actor);
449
- const elementId = fields.id ?? randomUUID();
455
+ const elementId = fields.id ?? crypto.randomUUID();
450
456
  if (this.elements.has(elementId)) throw new CanvasError("DUPLICATE_ID", `element "${elementId}" already exists`);
451
457
  if (this.elements.size >= this.maxElements) throw new CanvasError("LIMIT", `a board holds at most ${this.maxElements} elements`);
452
458
  const record = { ...fields, id: elementId, z: clampZ(this.edgeZ(1)), author: by, editedBy: by };
@@ -469,116 +475,6 @@ var Board = class _Board {
469
475
  }
470
476
  };
471
477
 
472
- // src/core/flow.ts
473
- import { randomUUID as randomUUID2 } from "crypto";
474
- import dagre from "@dagrejs/dagre";
475
- import * as Y2 from "yjs";
476
- var MAX_FLOW_NODES = 500;
477
- var MAX_FLOW_EDGES = 2e3;
478
- var MAX_FLOW_CHARS = 2e5;
479
- var NODE = String.raw`([A-Za-z0-9_-]{1,48})(?:\[([^\]]*)\]|\(([^)]*)\)|\{([^}]*)\})?`;
480
- var EDGE = new RegExp(String.raw`^${NODE}\s*->\s*${NODE}\s*(?::(.*))?$`);
481
- var ALONE = new RegExp(String.raw`^${NODE}$`);
482
- var SHAPES = ["rect", "ellipse", "diamond"];
483
- function parseFlow(source) {
484
- if (source.length > MAX_FLOW_CHARS) throw new CanvasError("LIMIT", `flow text is over ${MAX_FLOW_CHARS} characters`);
485
- const graph = { direction: "TB", nodes: [], edges: [] };
486
- const byId = /* @__PURE__ */ new Map();
487
- const fail = (line, message) => {
488
- throw new CanvasError("INVALID", `line ${line}: ${message}`);
489
- };
490
- const node = (line, m, at) => {
491
- const id2 = m[at];
492
- if (/^edge_\d+$/.test(id2)) fail(line, `"${id2}" is reserved for connectors. Pick another node id.`);
493
- const brackets = m.slice(at + 1, at + 4);
494
- const open = brackets.findIndex((b) => b !== void 0);
495
- const shape = SHAPES[Math.max(open, 0)];
496
- const text2 = (open < 0 ? "" : brackets[open].trim()) || id2;
497
- const known = byId.get(id2);
498
- if (!known) {
499
- const created = { id: id2, text: text2, shape, explicit: open >= 0 };
500
- byId.set(id2, created);
501
- graph.nodes.push(created);
502
- } else if (open >= 0) {
503
- if (known.explicit && (known.text !== text2 || known.shape !== shape)) fail(line, `"${id2}" is defined twice with different text`);
504
- Object.assign(known, { text: text2, shape, explicit: true });
505
- }
506
- return id2;
507
- };
508
- source.replace(/^/, "").split(/\r?\n/).forEach((raw, index) => {
509
- const line = index + 1;
510
- const text2 = raw.trim();
511
- if (text2 === "" || text2.startsWith("#") || text2.startsWith("//")) return;
512
- if (/[\u0000-\u0008\u000a-\u001f\u007f]/.test(text2)) fail(line, "control characters are not allowed");
513
- const direction = /^direction\s+(\S+)$/.exec(text2);
514
- if (direction) {
515
- if (direction[1] !== "TB" && direction[1] !== "LR") fail(line, `direction must be TB or LR, got "${direction[1]}"`);
516
- graph.direction = direction[1];
517
- return;
518
- }
519
- const edge = EDGE.exec(text2);
520
- if (edge) {
521
- const from = node(line, edge, 1);
522
- const to = node(line, edge, 5);
523
- graph.edges.push({ from, to, text: (edge[9] ?? "").trim() });
524
- } else {
525
- const alone = ALONE.exec(text2);
526
- if (!alone) fail(line, `cannot read "${text2.slice(0, 40)}". Use "A -> B: label".`);
527
- else node(line, alone, 1);
528
- }
529
- if (graph.nodes.length > MAX_FLOW_NODES) throw new CanvasError("LIMIT", `a flow holds at most ${MAX_FLOW_NODES} nodes`);
530
- if (graph.edges.length > MAX_FLOW_EDGES) throw new CanvasError("LIMIT", `a flow holds at most ${MAX_FLOW_EDGES} edges`);
531
- });
532
- if (graph.nodes.length === 0) throw new CanvasError("INVALID", "the flow has no nodes");
533
- return { ...graph, nodes: graph.nodes.map(({ id: id2, text: text2, shape }) => ({ id: id2, text: text2, shape })) };
534
- }
535
- function sizeOf(n) {
536
- const w = Math.min(360, Math.max(120, 24 + 8 * n.text.length));
537
- return n.shape === "diamond" ? { w: Math.min(360, Math.round(w * 1.5)), h: 90 } : { w, h: 60 };
538
- }
539
- function layoutFlow(graph) {
540
- const g = new dagre.graphlib.Graph({ multigraph: true });
541
- g.setGraph({ rankdir: graph.direction, nodesep: 40, ranksep: 70, marginx: 0, marginy: 0 });
542
- g.setDefaultEdgeLabel(() => ({}));
543
- const index = new Map(graph.nodes.map((n, i) => [n.id, `n${i}`]));
544
- const sizes = graph.nodes.map(sizeOf);
545
- sizes.forEach(({ w, h }, i) => g.setNode(`n${i}`, { width: w, height: h }));
546
- graph.edges.forEach((e, i) => g.setEdge(index.get(e.from), index.get(e.to), {}, String(i)));
547
- dagre.layout(g);
548
- const boxes = graph.nodes.map((n, i) => {
549
- const { w, h } = sizes[i];
550
- const c = g.node(`n${i}`);
551
- return { id: n.id, x: Math.round(c.x - w / 2), y: Math.round(c.y - h / 2), w, h };
552
- });
553
- const minX = Math.min(...boxes.map((b) => b.x));
554
- const minY = Math.min(...boxes.map((b) => b.y));
555
- return boxes.map((b) => ({ ...b, x: b.x - minX, y: b.y - minY }));
556
- }
557
- function drawFlow(board, source, actor, options = {}) {
558
- const prefix = options.idPrefix ?? `${randomUUID2().slice(0, 8)}-`;
559
- if (!/^[A-Za-z0-9_-]{0,16}$/.test(prefix)) throw new CanvasError("INVALID", "idPrefix must be up to 16 of A-Z, a-z, 0-9, _ and -");
560
- const graph = parseFlow(source);
561
- const placed = layoutFlow(graph);
562
- const shapes = board.list().flatMap((e) => e.type === "connector" || e.type === "group" ? [] : [e]);
563
- const origin = options.origin ?? {
564
- x: shapes.length ? Math.min(...shapes.map((s) => s.x)) : 0,
565
- y: shapes.length ? Math.max(...shapes.map((s) => s.y + s.h)) + 80 : 0
566
- };
567
- const work = board.clone();
568
- const text2 = new Map(graph.nodes.map((n) => [n.id, n]));
569
- for (const p of placed) {
570
- const n = text2.get(p.id);
571
- work.addShape({ id: prefix + p.id, type: n.shape, text: n.text, x: p.x + origin.x, y: p.y + origin.y, w: p.w, h: p.h }, actor);
572
- }
573
- const edges = graph.edges.map((e, i) => {
574
- const id2 = `${prefix}edge_${i + 1}`;
575
- work.addConnector({ id: id2, from: { id: prefix + e.from }, to: { id: prefix + e.to }, text: e.text }, actor);
576
- return id2;
577
- });
578
- Y2.applyUpdate(board.doc, Y2.encodeStateAsUpdate(work.doc, Y2.encodeStateVector(board.doc)));
579
- return { nodes: placed.map((p) => prefix + p.id), edges };
580
- }
581
-
582
478
  // src/core/read.ts
583
479
  function readBoard(board) {
584
480
  const data = { title: board.title, nodes: [], edges: [], groups: [], texts: [] };
@@ -599,182 +495,16 @@ function readBoard(board) {
599
495
  return data;
600
496
  }
601
497
 
602
- // src/core/svg.ts
603
- var LINE = 17;
604
- var num = (n) => String(Math.round(n * 100) / 100);
605
- var esc = (value) => (typeof value === "string" ? value : "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f￾￿]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
606
- var paint = (value, fallback) => typeof value === "string" && COLOR_PATTERN.test(value) ? value : fallback;
607
- var str = (value) => typeof value === "string" ? value : "";
608
- var within = (n, low, high) => typeof n === "number" && Number.isFinite(n) && n >= low && n <= high;
609
- var LIMIT_COORD = 1e7;
610
- var TYPES = /* @__PURE__ */ new Set([...SHAPE_TYPES, "connector", "group"]);
611
- var sane = (s) => within(s.x, -LIMIT_COORD, LIMIT_COORD) && within(s.y, -LIMIT_COORD, LIMIT_COORD) && within(s.w, 1e-6, LIMIT_COORD) && within(s.h, 1e-6, LIMIT_COORD);
612
- var isShape = (v) => "w" in v;
613
- var center = (s) => ({ x: s.x + s.w / 2, y: s.y + s.h / 2 });
614
- function edgePoint(s, toward) {
615
- const c = center(s);
616
- const dx = toward.x - c.x;
617
- const dy = toward.y - c.y;
618
- if (dx === 0 && dy === 0) return c;
619
- const a = s.w / 2;
620
- const b = s.h / 2;
621
- const k = s.type === "ellipse" ? 1 / Math.hypot(dx / a, dy / b) : s.type === "diamond" ? 1 / (Math.abs(dx) / a + Math.abs(dy) / b) : Math.min(a / Math.abs(dx), b / Math.abs(dy));
622
- return { x: c.x + dx * k, y: c.y + dy * k };
623
- }
624
- function wrap(text2, width) {
625
- const max = Math.max(4, Math.floor((width - 16) / 7.5));
626
- const out = [];
627
- for (const paragraph of text2.split(/\r?\n/)) {
628
- let line = "";
629
- for (const word of paragraph.split(/\s+/).filter(Boolean)) {
630
- const chars = [...word];
631
- while (chars.length > max) {
632
- if (line) out.push(line);
633
- line = "";
634
- out.push(chars.splice(0, max).join(""));
635
- }
636
- const rest = chars.join("");
637
- if (line && [...line].length + 1 + chars.length > max) {
638
- out.push(line);
639
- line = rest;
640
- } else line = line ? `${line} ${rest}` : rest;
641
- }
642
- out.push(line);
643
- }
644
- return out.slice(0, 40);
645
- }
646
- function label(text2, cx, cy, width, extra = "") {
647
- if (text2 === "") return "";
648
- const lines = wrap(text2, width);
649
- const top = cy - (lines.length - 1) * LINE / 2;
650
- const spans = lines.map((l, i) => `<tspan x="${num(cx)}" y="${num(top + i * LINE)}">${esc(l)}</tspan>`);
651
- return `<text text-anchor="middle" dominant-baseline="central"${extra}>${spans.join("")}</text>`;
652
- }
653
- function shapeBody(s) {
654
- const { x, y, w, h } = s;
655
- const stroke = paint(s.stroke, "#1f2937");
656
- const look = (fill, more = "") => `fill="${paint(s.fill, fill)}" stroke="${stroke}" stroke-width="2"${more}`;
657
- const text2 = label(str(s.text), x + w / 2, y + h / 2, w);
658
- switch (s.type) {
659
- case "ellipse":
660
- return `<ellipse cx="${num(x + w / 2)}" cy="${num(y + h / 2)}" rx="${num(w / 2)}" ry="${num(h / 2)}" ${look("#ffffff")}/>${text2}`;
661
- case "diamond": {
662
- const pts = [[x + w / 2, y], [x + w, y + h / 2], [x + w / 2, y + h], [x, y + h / 2]];
663
- return `<polygon points="${pts.map(([px, py]) => `${num(px)},${num(py)}`).join(" ")}" ${look("#ffffff")}/>${text2}`;
664
- }
665
- case "text":
666
- return text2;
667
- case "frame":
668
- return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" ${look("none", ' stroke-dasharray="8 6"')}/>${label(str(s.text), x + w / 2, y - 12, w)}`;
669
- case "image":
670
- if (typeof s.src === "string" && s.src.startsWith("data:") && IMAGE_PATTERN.test(s.src)) {
671
- return `<image href="${s.src}" x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" preserveAspectRatio="xMidYMid meet"/>`;
672
- }
673
- return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" ${look("#f3f4f6", ' stroke-dasharray="4 4"')}/>${label("image", x + w / 2, y + h / 2, w)}`;
674
- default:
675
- return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" rx="${s.type === "sticky" ? 2 : 6}" ${look(s.type === "sticky" ? "#fde68a" : "#ffffff")}/>${text2}`;
676
- }
677
- }
678
- var endKey = (e) => "id" in e ? e.id : `${e.x},${e.y}`;
679
- var wellFormed = (e) => typeof e === "object" && e !== null && (typeof e.id === "string" || within(e.x, -LIMIT_COORD, LIMIT_COORD) && within(e.y, -LIMIT_COORD, LIMIT_COORD));
680
- function connectorBody(c, shapes, lane) {
681
- const resolve = (e) => "id" in e ? shapes.get(e.id) : e;
682
- const a = resolve(c.from);
683
- const b = resolve(c.to);
684
- if (!a || !b) return "";
685
- let d;
686
- let at;
687
- if (isShape(a) && a === b) {
688
- const x0 = a.x + a.w * 0.35;
689
- const x1 = a.x + a.w * 0.65;
690
- const lift = 45 + 32 * lane.index;
691
- d = `M${num(x0)} ${num(a.y)}C${num(x0)} ${num(a.y - lift)} ${num(x1)} ${num(a.y - lift)} ${num(x1)} ${num(a.y)}`;
692
- at = { x: a.x + a.w / 2, y: a.y - lift * 0.75 - 6 };
693
- } else {
694
- const ca = isShape(a) ? center(a) : a;
695
- const cb = isShape(b) ? center(b) : b;
696
- const flip = endKey(c.from) > endKey(c.to);
697
- const dx = (flip ? ca.x - cb.x : cb.x - ca.x) || 0;
698
- const dy = (flip ? ca.y - cb.y : cb.y - ca.y) || 0;
699
- const len = Math.hypot(dx, dy) || 1;
700
- const offset = (lane.index - (lane.count - 1) / 2) * 44;
701
- const mid = { x: (ca.x + cb.x) / 2, y: (ca.y + cb.y) / 2 };
702
- const ctrl = { x: mid.x - dy / len * 2 * offset, y: mid.y + dx / len * 2 * offset };
703
- const p = isShape(a) ? edgePoint(a, ctrl) : a;
704
- const q = isShape(b) ? edgePoint(b, ctrl) : b;
705
- d = offset === 0 ? `M${num(p.x)} ${num(p.y)}L${num(q.x)} ${num(q.y)}` : `M${num(p.x)} ${num(p.y)}Q${num(ctrl.x)} ${num(ctrl.y)} ${num(q.x)} ${num(q.y)}`;
706
- at = offset === 0 ? { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 } : { x: (p.x + q.x) / 4 + ctrl.x / 2, y: (p.y + q.y) / 4 + ctrl.y / 2 };
707
- }
708
- const marks = `${c.startArrow ? ' marker-start="url(#arrow)"' : ""}${c.endArrow ? ' marker-end="url(#arrow)"' : ""}`;
709
- const halo = ' stroke="#ffffff" stroke-width="4" paint-order="stroke"';
710
- return `<path d="${d}" fill="none" stroke="${paint(c.stroke, "#1f2937")}" stroke-width="2"${marks}/>${label(str(c.text), at.x, at.y, 160, halo)}`;
711
- }
712
- var drawable = (el) => TYPES.has(el.type) && (el.type === "connector" ? wellFormed(el.from) && wellFormed(el.to) : el.type === "group" || sane(el));
713
- function toSvg(board) {
714
- const listed = board.list();
715
- if (listed.length > MAX_ELEMENTS) throw new CanvasError("LIMIT", `a board holds at most ${MAX_ELEMENTS} elements`);
716
- const all = listed.filter((el) => drawable(el));
717
- const shapes = /* @__PURE__ */ new Map();
718
- const box = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
719
- const grow = (x, y) => {
720
- box.minX = Math.min(box.minX, x);
721
- box.minY = Math.min(box.minY, y);
722
- box.maxX = Math.max(box.maxX, x);
723
- box.maxY = Math.max(box.maxY, y);
724
- };
725
- for (const el of all) {
726
- if (el.type === "connector") {
727
- for (const e of [el.from, el.to]) if (!("id" in e)) grow(e.x, e.y);
728
- } else if (el.type !== "group") {
729
- shapes.set(el.id, el);
730
- grow(el.x, el.y);
731
- grow(el.x + el.w, el.y + el.h);
732
- }
733
- }
734
- const pad = 80;
735
- const any = box.minX !== Infinity;
736
- const minX = any ? box.minX - pad : 0;
737
- const minY = any ? box.minY - pad : 0;
738
- const width = any ? Math.ceil(box.maxX + pad - minX) : 240;
739
- const height = any ? Math.ceil(box.maxY + pad - minY) : 120;
740
- const lanes = /* @__PURE__ */ new Map();
741
- const groups = /* @__PURE__ */ new Map();
742
- for (const el of all) {
743
- if (el.type !== "connector") continue;
744
- const key = [endKey(el.from), endKey(el.to)].sort().join("|");
745
- groups.set(key, [...groups.get(key) ?? [], el.id]);
746
- }
747
- for (const ids of groups.values()) ids.forEach((id2, index) => lanes.set(id2, { index, count: ids.length }));
748
- const body = all.map((el) => {
749
- if (el.type === "group") return "";
750
- const inner = el.type === "connector" ? connectorBody(el, shapes, lanes.get(el.id)) : shapeBody(el);
751
- if (inner === "") return "";
752
- const kind = el.author.kind;
753
- const mark = `data-id="${esc(el.id)}" data-type="${el.type}" data-author-kind="${kind}" data-machine-made="${kind !== "human"}"`;
754
- return `<g ${mark}>${inner}</g>`;
755
- });
756
- const title = esc(board.title) || "Whiteboard";
757
- return [
758
- `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${num(minX)} ${num(minY)} ${width} ${height}" font-family="sans-serif" font-size="14" fill="#111827" role="img" aria-label="${title}">`,
759
- `<title>${title}</title>`,
760
- '<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#1f2937"/></marker></defs>',
761
- `<rect x="${num(minX)}" y="${num(minY)}" width="${width}" height="${height}" fill="#ffffff"/>`,
762
- ...body.filter(Boolean),
763
- "</svg>"
764
- ].join("\n");
765
- }
766
-
767
498
  export {
499
+ PRINCIPAL_KINDS,
500
+ isMachineMade,
501
+ principalSchema,
768
502
  CanvasError,
769
503
  SHAPE_TYPES,
770
504
  MAX_ELEMENTS,
771
505
  MAX_TEXT,
506
+ COLOR_PATTERN,
507
+ IMAGE_PATTERN,
772
508
  Board,
773
- MAX_FLOW_NODES,
774
- MAX_FLOW_EDGES,
775
- parseFlow,
776
- layoutFlow,
777
- drawFlow,
778
- readBoard,
779
- toSvg
509
+ readBoard
780
510
  };
@@ -2,15 +2,15 @@ import {
2
2
  FileStoreError
3
3
  } from "./chunk-NWQED42L.js";
4
4
  import {
5
- Board,
6
- CanvasError,
7
5
  drawFlow,
8
- readBoard,
9
6
  toSvg
10
- } from "./chunk-ZSPZMGHG.js";
7
+ } from "./chunk-EDQNA2SU.js";
11
8
  import {
12
- principalSchema
13
- } from "./chunk-JOBTIAFJ.js";
9
+ Board,
10
+ CanvasError,
11
+ principalSchema,
12
+ readBoard
13
+ } from "./chunk-MVWUIBGV.js";
14
14
 
15
15
  // src/service.ts
16
16
  import * as Y from "yjs";
package/dist/cli.js CHANGED
@@ -5,18 +5,18 @@ import {
5
5
  operatorActor,
6
6
  runMcp,
7
7
  svgToPng
8
- } from "./chunk-NLTN7J22.js";
8
+ } from "./chunk-EE725BJ3.js";
9
9
  import {
10
10
  openLocalStore
11
11
  } from "./chunk-NRLF6MGR.js";
12
12
  import {
13
13
  CanvasService
14
- } from "./chunk-M74ENUWR.js";
14
+ } from "./chunk-Z6KLGAFP.js";
15
15
  import "./chunk-NWQED42L.js";
16
+ import "./chunk-EDQNA2SU.js";
16
17
  import {
17
18
  CanvasError
18
- } from "./chunk-ZSPZMGHG.js";
19
- import "./chunk-JOBTIAFJ.js";
19
+ } from "./chunk-MVWUIBGV.js";
20
20
 
21
21
  // src/cli.ts
22
22
  import { readFileSync, writeFileSync } from "fs";
package/dist/embed.d.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  import * as react from 'react';
2
2
  import { C as CanvasClient } from './client-Dyd30StF.js';
3
3
  import { PrincipalKind } from 'agentic-identity';
4
+ import { SyncClient, Status } from 'agentic-realtime/client';
5
+ import * as Y from 'yjs';
4
6
  import './read-UIJlrkwx.js';
5
- import 'yjs';
6
7
  import 'zod';
7
8
 
9
+ /** The presence of a room: who is here. A `SyncClient` of agentic-realtime has these. */
10
+ type RoomPresence = Pick<SyncClient, "peers" | "onPeers">;
11
+ /**
12
+ * A board that the app keeps itself, in its own Yjs room. The UI reads and writes it, and does not call a client, poll, or
13
+ * open a live room. The app keeps the document and its room.
14
+ */
15
+ interface HostBoard {
16
+ /** The Yjs document. The UI opens it as a `Board`: its `elements` and `meta` maps, as this package stores them. */
17
+ doc: Y.Doc;
18
+ /** The presence of the app's room. With it, the UI shows who else is here. */
19
+ presence?: RoomPresence;
20
+ /** The connection state now. With neither `status` nor `onStatus`, the UI shows the board as live. With only `onStatus`, it shows not live until the room reports "connected". */
21
+ status?: () => Status;
22
+ /** A way to hear when the connection state changes. It returns a function that stops the listener. The UI reads it once each time it opens `doc`. */
23
+ onStatus?: (listener: (status: Status) => void) => () => void;
24
+ }
25
+
8
26
  interface RoomOptions {
9
27
  /** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
10
28
  baseUrl: string;
@@ -16,14 +34,21 @@ interface RoomOptions {
16
34
  interface BoardPrincipal {
17
35
  kind: PrincipalKind;
18
36
  name: string;
37
+ /** Stamped on each element the principal writes in host mode. The default is `name`, which must then be a valid id. A client mode board takes the actor from its token. */
38
+ id?: string;
19
39
  /** Only `editor` can change the board. Any other value, or no value, opens it read only. */
20
40
  role?: "editor" | "viewer";
21
41
  }
22
42
  interface BoardEditorOptions {
23
43
  /** The board id. */
24
44
  boardId: string;
25
- /** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. */
26
- client: CanvasClient;
45
+ /** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. Needed unless `host` is given. */
46
+ client?: CanvasClient;
47
+ /**
48
+ * The board in your own Yjs document. The UI then reads and writes that document, and calls no client, does not poll, and
49
+ * opens no live room. `client` or `room` together with `host` throws a `TypeError`.
50
+ */
51
+ host?: HostBoard;
27
52
  principal: BoardPrincipal;
28
53
  /** Open the board read only, even for an editor. */
29
54
  readOnly?: boolean;
@@ -43,4 +68,4 @@ interface MountedBoard {
43
68
  /** Render the board into an element, for an app that does not use React itself. */
44
69
  declare function mount(element: HTMLElement, options: BoardEditorOptions): MountedBoard;
45
70
 
46
- export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type MountedBoard, type RoomOptions, mount };
71
+ export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type HostBoard, type MountedBoard, type RoomOptions, mount };