agentic-canvas 0.10.0 → 0.12.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.
package/dist/embed.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import {
2
- PRINCIPAL_KINDS
3
- } from "./chunk-JOBTIAFJ.js";
2
+ Board,
3
+ PRINCIPAL_KINDS,
4
+ principalSchema,
5
+ readBoard
6
+ } from "./chunk-MVWUIBGV.js";
4
7
 
5
8
  // ui/embed.tsx
6
9
  import { ToastProvider, TooltipProvider } from "agentic-design/react";
@@ -9,7 +12,7 @@ import { createRoot } from "react-dom/client";
9
12
 
10
13
  // ui/BoardView.tsx
11
14
  import { Button as Button2, EmptyState, Input, PresenceChip, PresenceStack, Select, Textarea, Toolbar, ToolbarButton, ToolbarSeparator, useToast } from "agentic-design/react";
12
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
15
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
13
16
 
14
17
  // ui/errors.ts
15
18
  var isApiError = (error) => typeof error === "object" && error !== null && error.name === "CanvasApiError" && typeof error.code === "string" && typeof error.status === "number";
@@ -226,6 +229,29 @@ function Failure({ message, retry }) {
226
229
  ] });
227
230
  }
228
231
 
232
+ // ui/host.ts
233
+ import * as Y from "yjs";
234
+ function checkHost(host) {
235
+ if (!host || !(host.doc instanceof Y.Doc)) throw new TypeError("a host board needs a Yjs document in `doc`");
236
+ const presence = host.presence;
237
+ if (presence !== void 0 && (!Array.isArray(presence?.peers) || typeof presence.onPeers !== "function")) {
238
+ throw new TypeError("a host presence needs peers and onPeers, as a SyncClient has");
239
+ }
240
+ if (host.status !== void 0 && typeof host.status !== "function" || host.onStatus !== void 0 && typeof host.onStatus !== "function") {
241
+ throw new TypeError("a host status and onStatus must be functions");
242
+ }
243
+ }
244
+ function boardCalls(board, who) {
245
+ return {
246
+ readBoard: async (id) => ({ id, ...readBoard(board) }),
247
+ addShape: async (_id, shape) => board.addShape(shape, who()),
248
+ addConnector: async (_id, connector) => board.addConnector(connector, who()),
249
+ update: async (_id, element, patch) => board.update(element, patch, who()),
250
+ move: async (_id, element, dx, dy) => ({ moved: board.move(element, dx, dy, who()) }),
251
+ remove: async (_id, element) => ({ removed: board.remove(element) })
252
+ };
253
+ }
254
+
229
255
  // ui/presence.ts
230
256
  import { SyncClient } from "agentic-realtime/client";
231
257
  var WS_PROTOCOL = "agentic-canvas";
@@ -288,7 +314,7 @@ var TOOLS = [
288
314
  var POLL_MS = 5e3;
289
315
  var DEFAULT_SIZE = { rect: [140, 70], ellipse: [140, 70], diamond: [140, 90], text: [140, 40], sticky: [150, 120] };
290
316
  var DEFAULT_TEXT = { rect: "Rectangle", ellipse: "Ellipse", diamond: "Decision", text: "Text", sticky: "Note" };
291
- function BoardView({ id, client, room: roomOptions, me, backHref }) {
317
+ function BoardView({ id, client: remote, host, room: roomOptions, me, backHref }) {
292
318
  const roomUrl2 = roomOptions?.baseUrl;
293
319
  const roomToken = roomOptions?.token;
294
320
  const toast = useToast();
@@ -302,6 +328,17 @@ function BoardView({ id, client, room: roomOptions, me, backHref }) {
302
328
  const queue = useRef2(Promise.resolve());
303
329
  const canvasBox = useRef2(null);
304
330
  const editable = me.role === "editor";
331
+ const hostDoc = host?.doc;
332
+ const hostPresence = host?.presence;
333
+ const hostRef = useRef2(host);
334
+ hostRef.current = host;
335
+ const who = useRef2({ kind: me.kind, id: me.id ?? me.actor });
336
+ who.current = { kind: me.kind, id: me.id ?? me.actor };
337
+ const client = useMemo(() => {
338
+ if (hostDoc) return boardCalls(new Board(hostDoc), () => who.current);
339
+ if (!remote) throw new TypeError("the board needs a client or a host board");
340
+ return remote;
341
+ }, [remote, hostDoc]);
305
342
  const [saving, setSaving] = useState2(0);
306
343
  const pending = useRef2(0);
307
344
  const generation = useRef2(0);
@@ -319,7 +356,7 @@ function BoardView({ id, client, room: roomOptions, me, backHref }) {
319
356
  }, [client, id]);
320
357
  const load = useCallback(async () => {
321
358
  if (pending.current > 0) return;
322
- const wait = lastRead.current + 1e3 - Date.now();
359
+ const wait = hostDoc ? 0 : lastRead.current + 1e3 - Date.now();
323
360
  if (wait > 0) {
324
361
  clearTimeout(timer.current);
325
362
  timer.current = setTimeout(() => void load(), wait);
@@ -327,7 +364,7 @@ function BoardView({ id, client, room: roomOptions, me, backHref }) {
327
364
  }
328
365
  lastRead.current = Date.now();
329
366
  await read();
330
- }, [read]);
367
+ }, [read, hostDoc]);
331
368
  useEffect2(() => {
332
369
  setBoard({ state: "loading" });
333
370
  lastRead.current = 0;
@@ -341,15 +378,45 @@ function BoardView({ id, client, room: roomOptions, me, backHref }) {
341
378
  return () => window.removeEventListener("beforeunload", guard);
342
379
  }, [saving]);
343
380
  useEffect2(() => {
344
- if (roomUrl2 === void 0 || roomToken === void 0) return;
345
- const joined = joinRoom({ board: id, baseUrl: roomUrl2, token: roomToken, name: me.actor, onPeers: setPeers, onChange: () => void load(), onStatus: setLive });
381
+ if (!hostDoc) return;
382
+ let queued = false;
383
+ const changed = () => {
384
+ if (queued) return;
385
+ queued = true;
386
+ queueMicrotask(() => (queued = false, void load()));
387
+ };
388
+ const elements = hostDoc.getMap("elements");
389
+ const meta = hostDoc.getMap("meta");
390
+ elements.observeDeep(changed);
391
+ meta.observe(changed);
392
+ return () => (elements.unobserveDeep(changed), meta.unobserve(changed));
393
+ }, [hostDoc, load]);
394
+ useEffect2(() => {
395
+ if (!hostPresence) return;
396
+ setPeers(hostPresence.peers.map(toEntry));
397
+ const off = hostPresence.onPeers((list) => setPeers(list.map(toEntry)));
398
+ return () => (off(), setPeers([]));
399
+ }, [hostPresence]);
400
+ useEffect2(() => {
401
+ if (!hostDoc) return;
402
+ const reset = () => setLive(false);
403
+ const { status: ask, onStatus: listen } = hostRef.current ?? {};
404
+ setLive(ask ? ask() === "connected" : !listen);
405
+ const stop = listen?.((next) => setLive(next === "connected"));
406
+ return () => (stop?.(), reset());
407
+ }, [hostDoc]);
408
+ useEffect2(() => {
409
+ if (hostDoc || roomUrl2 === void 0 || roomToken === void 0) return;
410
+ let left = false;
411
+ const joined = joinRoom({ board: id, baseUrl: roomUrl2, token: roomToken, name: me.actor, onPeers: (list) => !left && setPeers(list), onChange: () => !left && void load(), onStatus: (up) => !left && setLive(up) });
346
412
  room.current = joined;
347
- return () => (joined.leave(), room.current = void 0);
348
- }, [id, roomUrl2, roomToken, me.actor, load]);
413
+ return () => (left = true, joined.leave(), setPeers([]), room.current = void 0);
414
+ }, [id, hostDoc, roomUrl2, roomToken, me.actor, load]);
349
415
  useEffect2(() => {
416
+ if (hostDoc) return;
350
417
  const timer2 = setInterval(() => void load(), POLL_MS);
351
418
  return () => clearInterval(timer2);
352
- }, [load]);
419
+ }, [load, hostDoc]);
353
420
  const write = useCallback(
354
421
  (change, failure, select) => {
355
422
  pending.current++;
@@ -410,25 +477,29 @@ function BoardView({ id, client, room: roomOptions, me, backHref }) {
410
477
  const empty = d.nodes.length === 0 && d.edges.length === 0;
411
478
  return /* @__PURE__ */ jsxs3("section", { className: "board", "aria-labelledby": "board-h", children: [
412
479
  /* @__PURE__ */ jsxs3("div", { className: "row", children: [
413
- /* @__PURE__ */ jsx3("h1", { id: "board-h", children: d.title || d.id }),
480
+ /* @__PURE__ */ jsxs3("div", { className: "heading", children: [
481
+ /* @__PURE__ */ jsx3("h1", { id: "board-h", children: d.title || d.id }),
482
+ /* @__PURE__ */ jsx3("p", { className: "note", role: "status", children: editable ? status : "" })
483
+ ] }),
414
484
  /* @__PURE__ */ jsx3(PresenceStack, { entries: [{ id: "me", name: me.actor, kind: me.kind === "human" ? "person" : "agent" }, ...peers], label: live ? "In this board" : "In this board (not live)" })
415
485
  ] }),
416
- /* @__PURE__ */ jsxs3(Toolbar, { label: "Board tools", children: [
417
- TOOLS.map((t) => /* @__PURE__ */ jsx3(ToolbarButton, { label: t.text, icon: t.icon, disabled: !editable, onClick: () => add(t.type) }, t.type)),
418
- /* @__PURE__ */ jsx3(ToolbarSeparator, {}),
419
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Zoom out", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 10h12" })), onClick: () => zoom(1 / 1.25) }),
420
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Zoom in", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 10h12M10 4v12" })), onClick: () => zoom(1.25) }),
421
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan left", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M12 4l-6 6 6 6" })), onClick: () => setView((v) => ({ ...v, x: v.x + PAN_STEP })) }),
422
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan right", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M8 4l6 6-6 6" })), onClick: () => setView((v) => ({ ...v, x: v.x - PAN_STEP })) }),
423
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan up", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 12l6-6 6 6" })), onClick: () => setView((v) => ({ ...v, y: v.y + PAN_STEP })) }),
424
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan down", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 8l6 6 6-6" })), onClick: () => setView((v) => ({ ...v, y: v.y - PAN_STEP })) }),
425
- /* @__PURE__ */ jsx3(ToolbarButton, { label: "Reset view", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 4h5M4 4v5M16 16h-5M16 16v-5" })), onClick: () => setView(HOME) })
426
- ] }),
427
- /* @__PURE__ */ jsx3("p", { className: "note", role: "status", children: editable ? status : "" }),
428
- !editable && /* @__PURE__ */ jsx3("p", { className: "note", children: "This token can read the board but not change it." }),
429
- /* @__PURE__ */ jsxs3("div", { className: "stage", ref: canvasBox, children: [
430
- /* @__PURE__ */ jsx3(Canvas, { data: d, selected, editable, view, onView: setView, onSelect: setSelected, onMove: move, onResize: resize, onDelete: remove }),
431
- empty && /* @__PURE__ */ jsx3("div", { className: "empty-overlay", children: /* @__PURE__ */ jsx3(EmptyState, { title: "This board is empty", description: editable ? "Add a shape or a note with the tools above." : "Nothing has been drawn yet." }) }),
486
+ !editable && /* @__PURE__ */ jsx3("p", { className: "note", children: host ? "This board is read only." : "This token can read the board but not change it." }),
487
+ /* @__PURE__ */ jsxs3("div", { className: "stage", children: [
488
+ /* @__PURE__ */ jsxs3("div", { className: "surface", ref: canvasBox, children: [
489
+ /* @__PURE__ */ jsx3("div", { className: "palette", children: /* @__PURE__ */ jsx3(Toolbar, { label: "Shape palette", children: TOOLS.map((t) => /* @__PURE__ */ jsx3(ToolbarButton, { label: t.text, icon: t.icon, disabled: !editable, onClick: () => add(t.type) }, t.type)) }) }),
490
+ /* @__PURE__ */ jsx3("div", { className: "zoom", children: /* @__PURE__ */ jsxs3(Toolbar, { label: "Zoom and pan", children: [
491
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Zoom out", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 10h12" })), onClick: () => zoom(1 / 1.25) }),
492
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Zoom in", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 10h12M10 4v12" })), onClick: () => zoom(1.25) }),
493
+ /* @__PURE__ */ jsx3(ToolbarSeparator, {}),
494
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan left", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M12 4l-6 6 6 6" })), onClick: () => setView((v) => ({ ...v, x: v.x + PAN_STEP })) }),
495
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan right", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M8 4l6 6-6 6" })), onClick: () => setView((v) => ({ ...v, x: v.x - PAN_STEP })) }),
496
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan up", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 12l6-6 6 6" })), onClick: () => setView((v) => ({ ...v, y: v.y + PAN_STEP })) }),
497
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Pan down", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 8l6 6 6-6" })), onClick: () => setView((v) => ({ ...v, y: v.y - PAN_STEP })) }),
498
+ /* @__PURE__ */ jsx3(ToolbarButton, { label: "Reset view", icon: icon(/* @__PURE__ */ jsx3("path", { d: "M4 4h5M4 4v5M16 16h-5M16 16v-5" })), onClick: () => setView(HOME) })
499
+ ] }) }),
500
+ /* @__PURE__ */ jsx3(Canvas, { data: d, selected, editable, view, onView: setView, onSelect: setSelected, onMove: move, onResize: resize, onDelete: remove }),
501
+ empty && /* @__PURE__ */ jsx3("div", { className: "empty-overlay", children: /* @__PURE__ */ jsx3(EmptyState, { title: "This board is empty", description: editable ? "Pick a shape from the palette to start." : "Nothing has been drawn yet." }) })
502
+ ] }),
432
503
  /* @__PURE__ */ jsx3(Inspector, { item, nodes: d.nodes, editable, onText: (text) => item && write(() => client.update(id, item.id, { text }), "Could not save the text"), onPlace: place, onDelete: remove, onConnect: (to) => item && write(() => client.addConnector(id, { from: { id: item.id }, to: { id: to } }), "Could not connect") }, item?.id ?? "none")
433
504
  ] })
434
505
  ] });
@@ -487,10 +558,18 @@ function Inspector({ item, nodes, editable, onText, onPlace, onDelete, onConnect
487
558
 
488
559
  // ui/embed.tsx
489
560
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
490
- function checkPrincipal(principal) {
561
+ function checkPrincipal(principal, options) {
491
562
  if (!principal || !PRINCIPAL_KINDS.includes(principal.kind) || typeof principal.name !== "string" || principal.name.trim() === "") {
492
563
  throw new TypeError(`the board needs a principal with a kind (${PRINCIPAL_KINDS.join(", ")}) and a name`);
493
564
  }
565
+ if (!options) return;
566
+ if (options.host) {
567
+ checkHost(options.host);
568
+ if (options.client) throw new TypeError("a host board writes to its own document, not through a `client`: give one of them");
569
+ const stamp = principalSchema.safeParse({ kind: options.principal.kind, id: options.principal.id ?? options.principal.name });
570
+ if (!stamp.success) throw new TypeError(`the principal needs an \`id\` of letters, digits, ".", "_" and "-" to stamp its writes: ${stamp.error.issues[0]?.message}`);
571
+ if (options.room) throw new TypeError("a host board has no live room of its own: give its presence in `host.presence`, not `room`");
572
+ } else if (!options.client) throw new TypeError("the board needs a `client`, or a `host` board");
494
573
  }
495
574
  var Boundary = class extends Component {
496
575
  state = { failed: false };
@@ -504,16 +583,16 @@ var Boundary = class extends Component {
504
583
  ] }) }) : this.props.children;
505
584
  }
506
585
  };
507
- function Board({ boardId, client, principal, readOnly, room, backHref }) {
508
- checkPrincipal(principal);
586
+ function Board2({ boardId, client, host, principal, readOnly, room, backHref }) {
587
+ checkPrincipal(principal, { boardId, client, host, principal, room });
509
588
  const role = principal.role === "editor" && !readOnly ? "editor" : "viewer";
510
- return /* @__PURE__ */ jsx4(BoardView, { id: boardId, client, room, backHref, me: { actor: principal.name, kind: principal.kind, role } }, boardId);
589
+ return /* @__PURE__ */ jsx4(BoardView, { id: boardId, client, host, room, backHref, me: { actor: principal.name, id: principal.id, kind: principal.kind, role } }, boardId);
511
590
  }
512
591
  function BoardEditor(options) {
513
- return /* @__PURE__ */ jsx4("div", { className: "cv-root", children: /* @__PURE__ */ jsx4(Boundary, { children: /* @__PURE__ */ jsx4(Board, { ...options }) }) });
592
+ return /* @__PURE__ */ jsx4("div", { className: "cv-root", children: /* @__PURE__ */ jsx4(Boundary, { children: /* @__PURE__ */ jsx4(Board2, { ...options }) }) });
514
593
  }
515
594
  function mount(element, options) {
516
- checkPrincipal(options.principal);
595
+ checkPrincipal(options.principal, options);
517
596
  const root = createRoot(element);
518
597
  const render = (next) => root.render(
519
598
  /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsx4(ToastProvider, { children: /* @__PURE__ */ jsx4(BoardEditor, { ...next }) }) })
@@ -521,7 +600,7 @@ function mount(element, options) {
521
600
  render(options);
522
601
  let open = true;
523
602
  return {
524
- update: (next) => (checkPrincipal(next.principal), open && render(next)),
603
+ update: (next) => (checkPrincipal(next.principal, next), open && render(next)),
525
604
  unmount: () => {
526
605
  if (!open) return;
527
606
  open = false;
package/dist/index.js CHANGED
@@ -6,34 +6,34 @@ import {
6
6
  operatorActor,
7
7
  runMcp,
8
8
  svgToPng
9
- } from "./chunk-NLTN7J22.js";
9
+ } from "./chunk-A4YK2SWM.js";
10
10
  import {
11
11
  createFileStore,
12
12
  openLocalStore
13
13
  } from "./chunk-NRLF6MGR.js";
14
14
  import {
15
15
  CanvasService
16
- } from "./chunk-M74ENUWR.js";
16
+ } from "./chunk-Z6KLGAFP.js";
17
17
  import "./chunk-NWQED42L.js";
18
18
  import {
19
- Board,
20
- CanvasError,
21
- MAX_ELEMENTS,
22
19
  MAX_FLOW_EDGES,
23
20
  MAX_FLOW_NODES,
24
- MAX_TEXT,
25
- SHAPE_TYPES,
26
21
  drawFlow,
27
22
  layoutFlow,
28
23
  parseFlow,
29
- readBoard,
30
24
  toSvg
31
- } from "./chunk-ZSPZMGHG.js";
25
+ } from "./chunk-EDQNA2SU.js";
32
26
  import {
27
+ Board,
28
+ CanvasError,
29
+ MAX_ELEMENTS,
30
+ MAX_TEXT,
33
31
  PRINCIPAL_KINDS,
32
+ SHAPE_TYPES,
34
33
  isMachineMade,
35
- principalSchema
36
- } from "./chunk-JOBTIAFJ.js";
34
+ principalSchema,
35
+ readBoard
36
+ } from "./chunk-MVWUIBGV.js";
37
37
  export {
38
38
  ActorConfigError,
39
39
  BRAND,
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  createApp
3
- } from "./chunk-JQCUVXXD.js";
4
- import "./chunk-M74ENUWR.js";
3
+ } from "./chunk-5II2WZIL.js";
4
+ import "./chunk-Z6KLGAFP.js";
5
5
  import "./chunk-NWQED42L.js";
6
+ import "./chunk-EDQNA2SU.js";
6
7
  import {
7
8
  CanvasError
8
- } from "./chunk-ZSPZMGHG.js";
9
- import "./chunk-JOBTIAFJ.js";
9
+ } from "./chunk-MVWUIBGV.js";
10
10
 
11
11
  // src/live-worker.ts
12
12
  import { parseRegion } from "agentic-identity";
package/dist/live.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
- Board,
3
- CanvasError,
4
2
  drawFlow,
5
- readBoard,
6
3
  toSvg
7
- } from "./chunk-ZSPZMGHG.js";
8
- import "./chunk-JOBTIAFJ.js";
4
+ } from "./chunk-EDQNA2SU.js";
5
+ import {
6
+ Board,
7
+ CanvasError,
8
+ readBoard
9
+ } from "./chunk-MVWUIBGV.js";
9
10
 
10
11
  // src/live.ts
11
12
  import { parsePrincipal } from "agentic-identity";
package/dist/ui.css CHANGED
@@ -5,32 +5,40 @@
5
5
  .note { color: var(--agd-color-text-muted); }
6
6
  .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
7
7
 
8
- .board { display: grid; gap: var(--agd-space-3); }
9
- .stage { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 16rem; gap: var(--agd-space-3); height: 65vh; min-height: 22rem; }
8
+ .board { display: grid; gap: var(--agd-space-4); }
9
+ .heading { display: grid; gap: var(--agd-space-1); }
10
+ .heading h1, .heading .note { margin: 0; }
11
+ .heading .note { font-size: var(--agd-font-size-sm); min-height: 1.25em; }
12
+ .stage { display: grid; grid-template-columns: minmax(0, 1fr) 15rem; gap: var(--agd-space-4); height: 68vh; min-height: 24rem; }
13
+ /* The palette and the zoom controls float on the canvas, so the page keeps one calm surface. They come before the canvas, so Tab reaches them first. */
14
+ .surface { position: relative; min-height: 18rem; }
15
+ .palette, .zoom { position: absolute; z-index: 1; left: var(--agd-space-3); max-width: calc(100% - 2 * var(--agd-space-3)); }
16
+ .palette { top: var(--agd-space-3); }
17
+ .zoom { bottom: var(--agd-space-3); }
18
+ .palette .agd-toolbar, .zoom .agd-toolbar { flex-wrap: wrap; box-shadow: var(--agd-shadow-raised); }
19
+ .canvas { height: 100%; box-sizing: border-box; background-color: var(--agd-color-surface); background-image: radial-gradient(var(--agd-color-border) 1px, transparent 1px); background-size: 24px 24px; border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-lg); overflow: hidden; touch-action: none; min-height: 18rem; }
10
20
  @media (max-width: 48rem) { .stage { grid-template-columns: 1fr; height: auto; } .canvas { height: 55vh; } }
11
- .canvas { background: var(--agd-color-surface-sunken); border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-md); overflow: hidden; touch-action: none; min-height: 18rem; }
12
21
  .canvas:focus-visible { outline: 3px solid var(--agd-color-focus); outline-offset: 2px; }
13
22
  .canvas svg { display: block; cursor: grab; font-family: var(--agd-font-family-sans); font-size: 14px; }
14
23
  .item { outline: none; cursor: pointer; }
15
- .shape { fill: var(--agd-color-surface-raised); stroke: var(--agd-color-border-strong); stroke-width: 1.5; }
24
+ .shape { fill: var(--agd-color-surface-raised); stroke: var(--agd-color-border-strong); stroke-width: 1; }
16
25
  .shape.text { fill: transparent; stroke: none; }
17
26
  .shape.frame, .shape.image { fill: none; stroke-dasharray: 6 4; pointer-events: visiblePainted; }
18
27
  .shape.sticky { fill: var(--agd-color-warning-subtle); stroke: var(--agd-color-warning); }
19
28
  .label { fill: var(--agd-color-text); pointer-events: none; }
20
29
  .sticky-label { fill: var(--agd-color-warning-text); }
21
- .edge { stroke: var(--agd-color-text-muted); stroke-width: 2; fill: none; pointer-events: none; }
30
+ .edge { stroke: var(--agd-color-text-muted); stroke-width: 1.5; fill: none; pointer-events: none; }
22
31
  .hit { stroke: transparent; stroke-width: 14; }
23
32
  .arrowhead { fill: var(--agd-color-text-muted); }
24
- .edge-text { fill: var(--agd-color-text); paint-order: stroke; stroke: var(--agd-color-surface-sunken); stroke-width: 4px; pointer-events: none; }
33
+ .edge-text { fill: var(--agd-color-text); paint-order: stroke; stroke: var(--agd-color-surface); stroke-width: 4px; pointer-events: none; }
25
34
  .ring, .ring-line { fill: none; stroke: none; stroke-width: 3; pointer-events: none; }
26
35
  .item.selected .ring, .item.selected .ring-line, .item:focus-visible .ring, .item:focus-visible .ring-line { stroke: var(--agd-color-focus); }
27
36
  .item:focus-visible .ring, .item:focus-visible .ring-line { stroke-width: 4; }
28
37
  .handle { fill: var(--agd-color-focus); stroke: var(--agd-color-bg); stroke-width: 2; cursor: nwse-resize; }
29
38
  .agent-badge rect { fill: var(--agd-color-agent-subtle); stroke: var(--agd-color-agent); }
30
39
  .agent-badge text { fill: var(--agd-color-agent-text); font-size: 11px; font-weight: 600; pointer-events: none; }
31
- .empty-overlay { position: absolute; inset: 0 16rem 0 0; display: grid; place-items: center; pointer-events: none; }
32
- @media (max-width: 48rem) { .empty-overlay { inset: 0; bottom: auto; height: 55vh; } }
33
- .inspector { border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-md); padding: var(--agd-space-3); background: var(--agd-color-surface); display: grid; gap: var(--agd-space-3); align-content: start; overflow: auto; }
40
+ .empty-overlay { position: absolute; inset: 0; display: grid; place-items: center; pointer-events: none; }
41
+ .inspector { border: 1px solid var(--agd-color-border); border-radius: var(--agd-radius-lg); padding: var(--agd-space-4); background: var(--agd-color-surface); display: grid; gap: var(--agd-space-3); align-content: start; overflow: auto; }
34
42
  .inspector h2 { margin: 0; font-size: var(--agd-font-size-lg); }
35
43
  .inspector form { display: grid; gap: var(--agd-space-3); }
36
44
  .inspector .by { margin: 0; color: var(--agd-color-text-muted); font-size: var(--agd-font-size-sm); }
package/dist/worker.js CHANGED
@@ -5,11 +5,11 @@ import {
5
5
  identify,
6
6
  sniffImage,
7
7
  worker_default
8
- } from "./chunk-JQCUVXXD.js";
9
- import "./chunk-M74ENUWR.js";
8
+ } from "./chunk-5II2WZIL.js";
9
+ import "./chunk-Z6KLGAFP.js";
10
10
  import "./chunk-NWQED42L.js";
11
- import "./chunk-ZSPZMGHG.js";
12
- import "./chunk-JOBTIAFJ.js";
11
+ import "./chunk-EDQNA2SU.js";
12
+ import "./chunk-MVWUIBGV.js";
13
13
  export {
14
14
  WS_PROTOCOL,
15
15
  authenticate,
package/llms.txt CHANGED
@@ -11,7 +11,7 @@ Keywords: whiteboard,canvas,diagrams,drawing,collaboration,agents,mcp,cli.
11
11
  - Live: set CANVAS_SERVER and CANVAS_TOKEN, and the CLI and `agentic-canvas mcp` edit the board in its live room.
12
12
  - SDK: import { LiveBoard, LiveService } from "agentic-canvas/live".
13
13
  - SDK: import { createClient, CanvasApiError } from "agentic-canvas/client". It calls the Worker API with fetch. Methods: me, listBoards, createBoard, readBoard, deleteBoard, exportSvg, addShape, addConnector, drawFlow, update, move, remove, access, grant, revoke, invite, uploadImage, getImage, state, checkpoint. A failure throws CanvasApiError with `code` and `status`. No retries.
14
- - UI: import { mount } from "agentic-canvas/ui"; import "agentic-canvas/ui.css" (with agentic-design/styles.css). mount(element, { boardId, client, principal: { kind, name, role }, readOnly?, room?: { baseUrl, token } }) renders the board and returns { update(options), unmount() }. BoardEditor is the same as a React component. React 19 is a peer dependency.
14
+ - UI: import { mount } from "agentic-canvas/ui"; import "agentic-canvas/ui.css" (with agentic-design/styles.css). mount(element, { boardId, client, principal: { kind, name, role, id? }, readOnly?, room?: { baseUrl, token } }) (or host: { doc, presence?, status?, onStatus? } instead of client and room; in host mode id (default name) must be letters, digits, . _ -, to edit the app's own Yjs document with no HTTP call) renders the board and returns { update(options), unmount() }. BoardEditor is the same as a React component. React 19 is a peer dependency.
15
15
 
16
16
  ## Docs
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-canvas",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "A shared whiteboard for diagrams, drawn by people and agents.",
5
5
  "keywords": [
6
6
  "whiteboard",
@@ -74,6 +74,8 @@
74
74
  "typecheck": "tsc --noEmit && tsc --noEmit -p ui",
75
75
  "test": "vitest run",
76
76
  "clean": "rm -rf dist",
77
+ "dev": "npm run build && WRANGLER_SEND_METRICS=false wrangler dev --port 8793 --var CANVAS_REGION:unknown",
78
+ "dev:smoke": "npm run build && node scripts/dev-smoke.mjs 8793 /health --var CANVAS_REGION:unknown",
77
79
  "ci:local": "npm run build && npm run typecheck && npm test && npm run test:e2e",
78
80
  "deploy:demo": "wrangler deploy -c wrangler.demo.jsonc",
79
81
  "test:e2e": "playwright test -c e2e/playwright.config.ts"
@@ -81,7 +83,7 @@
81
83
  "dependencies": {
82
84
  "@dagrejs/dagre": "^3.1.3",
83
85
  "@modelcontextprotocol/sdk": "^1.12.0",
84
- "agentic-design": "^0.4.0",
86
+ "agentic-design": "^0.5.0",
85
87
  "agentic-identity": "^0.8.0",
86
88
  "agentic-realtime": "^0.4.2",
87
89
  "yjs": "^13.6.0",
@@ -1,19 +0,0 @@
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();
13
-
14
- export {
15
- PRINCIPAL_KINDS,
16
- isMachineMade,
17
- principalSchema,
18
- storedPrincipalSchema
19
- };