pmx-canvas 0.9.3 → 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.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +9418 -2016
- package/CHANGELOG.md +54 -1
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
- package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
- package/dist/canvas/chunks/index-194m3hc3.js +2 -0
- package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
- package/dist/canvas/chunks/index-353m7b0p.js +2 -0
- package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
- package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
- package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
- package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-nprm96xp.js +16 -0
- package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
- package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
- package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
- package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
- package/dist/canvas/chunks/index-s4j678gj.js +2 -0
- package/dist/canvas/chunks/index-tb1wf525.js +179 -0
- package/dist/canvas/chunks/index-vv56h019.js +2 -0
- package/dist/canvas/chunks/index-w2mra17t.js +207 -0
- package/dist/canvas/chunks/index-wgv7en21.js +232 -0
- package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
- package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
- package/dist/canvas/global.css +389 -229
- package/dist/canvas/index.js +141 -211
- package/dist/canvas/mcp-app.html +4205 -554
- package/dist/canvas/mermaid-entry.js +338 -338
- package/dist/canvas/surface-theme.css +6 -6
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +49 -49
- package/dist/types/client/canvas/BarHint.d.ts +5 -3
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/auto-fit.d.ts +4 -5
- package/dist/types/client/mermaid-entry.d.ts +1 -2
- package/dist/types/client/mermaid-render.d.ts +1 -0
- package/dist/types/client/nodes/BoardNode.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
- package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
- package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
- package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
- package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
- package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +3 -1
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/mcp/canvas-access.d.ts +1 -12
- package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
- package/dist/types/mcp/server.d.ts +2 -0
- package/dist/types/server/ax-wait.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +11 -6
- package/dist/types/server/operations/http.d.ts +10 -0
- package/dist/types/server/operations/index.d.ts +2 -2
- package/dist/types/server/operations/invoker.d.ts +4 -3
- package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
- package/dist/types/server/operations/mcp.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +1 -0
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -2
- package/dist/types/server/operations/types.d.ts +12 -2
- package/dist/types/server/server.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/webpage-node.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/api-reference.md +2 -2
- package/docs/cli.md +1 -1
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/Types1.dc.html +3 -2
- package/docs/design/Types2.dc.html +1 -1
- package/docs/design/Types3.dc.html +1 -1
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +1 -1
- package/docs/design.md +169 -4
- package/docs/http-api.md +82 -1
- package/docs/mcp.md +101 -21
- package/docs/node-types.md +38 -3
- package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
- package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
- package/docs/product-vision-2026-09.md +9 -0
- package/docs/sdk.md +5 -2
- package/package.json +2 -2
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +9418 -2016
- package/skills/pmx-canvas/SKILL.md +19 -21
- package/skills/pmx-canvas/references/full-reference.md +44 -6
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/CanvasNode.tsx +34 -47
- package/src/client/canvas/CanvasViewport.tsx +6 -7
- package/src/client/canvas/ContextMenu.tsx +63 -3
- package/src/client/canvas/EdgeLayer.tsx +365 -169
- package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/ToolRail.tsx +2 -0
- package/src/client/canvas/TopBar.tsx +24 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +6 -1
- package/src/client/mermaid-entry.ts +6 -20
- package/src/client/mermaid-render.ts +39 -0
- package/src/client/nodes/AxStepControls.tsx +45 -69
- package/src/client/nodes/BoardNode.tsx +65 -9
- package/src/client/nodes/ContextNode.tsx +88 -396
- package/src/client/nodes/ExtAppFrame.tsx +283 -1462
- package/src/client/nodes/FileNode.tsx +92 -81
- package/src/client/nodes/HtmlNode.tsx +8 -36
- package/src/client/nodes/LedgerNode.tsx +31 -17
- package/src/client/nodes/MarkdownNode.tsx +8 -17
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +12 -14
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +22 -7
- package/src/client/nodes/StatusSummary.tsx +5 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +262 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-iframe-content-height.ts +34 -10
- package/src/client/nodes/use-surface-frame.ts +22 -11
- package/src/client/nodes/viewer-runtime-state.ts +41 -35
- package/src/client/state/canvas-store.ts +18 -12
- package/src/client/state/intent-bridge.ts +1 -0
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +389 -229
- package/src/client/theme/surface-theme.css +6 -6
- package/src/json-render/charts/tufte-components.tsx +44 -2
- package/src/json-render/renderer/index.css +62 -2
- package/src/json-render/renderer/index.tsx +31 -7
- package/src/json-render/server.ts +3 -2
- package/src/mcp/canvas-access.ts +6 -47
- package/src/mcp/resource-subscriptions.ts +181 -0
- package/src/mcp/server.ts +185 -219
- package/src/server/ax-wait.ts +21 -6
- package/src/server/board-export.ts +35 -24
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +6 -2
- package/src/server/canvas-state.ts +5 -0
- package/src/server/demo-state.json +106 -100
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/index.ts +30 -12
- package/src/server/operations/http.ts +69 -21
- package/src/server/operations/index.ts +8 -1
- package/src/server/operations/invoker.ts +53 -5
- package/src/server/operations/mcp-contracts.ts +354 -0
- package/src/server/operations/mcp.ts +153 -18
- package/src/server/operations/ops/ax-await.ts +18 -10
- package/src/server/operations/ops/ax-read.ts +2 -0
- package/src/server/operations/ops/ax-timeline.ts +2 -2
- package/src/server/operations/ops/batch.ts +27 -8
- package/src/server/operations/ops/boards.ts +1 -3
- package/src/server/operations/ops/nodes.ts +18 -3
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +24 -4
- package/src/server/operations/types.ts +16 -2
- package/src/server/server.ts +17 -59
- package/src/server/tour-control.ts +66 -6
- package/src/server/webpage-node.ts +9 -4
- package/src/shared/ax-surface-protocol.ts +0 -11
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/tour.ts +8 -0
|
@@ -106,7 +106,7 @@ for the authoritative stop, viewport, persistence, reset, group-order, and captu
|
|
|
106
106
|
|
|
107
107
|
## Runtime prerequisites
|
|
108
108
|
|
|
109
|
-
PMX Canvas 0.
|
|
109
|
+
PMX Canvas 0.11.0 requires **Bun >=1.4.4**, including when installed through npm.
|
|
110
110
|
Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
|
|
111
111
|
use an absolute executable path if needed. Install/configure only when requested.
|
|
112
112
|
For pinned installation, MCP configuration, managed services, and disposable verification,
|
|
@@ -264,10 +264,15 @@ json-render, a graph, or an HTML primitive is sufficient.
|
|
|
264
264
|
|
|
265
265
|
## Current MCP Composites
|
|
266
266
|
|
|
267
|
-
The live MCP surface is **
|
|
267
|
+
The live MCP surface is **25 tools**: the 18 composites below plus 7 standalones. This table is
|
|
268
268
|
kept in sync with the server's composite registry; `tools/list` on a fresh `pmx-canvas --mcp` is
|
|
269
269
|
always authoritative, independent of the installed release version.
|
|
270
270
|
|
|
271
|
+
Use `canvas_help {}` to list MCP registry operation names, then `canvas_help { op: "node.add" }`
|
|
272
|
+
for that operation's public tool, argument schema and guidance. Help does not start or read a
|
|
273
|
+
canvas. The schema includes routing selectors and required fields; handlers still enforce
|
|
274
|
+
conditional requirements. Use `tools/list` for non-registry tools such as `canvas_screenshot`.
|
|
275
|
+
|
|
271
276
|
| Composite | Actions |
|
|
272
277
|
|-----------|---------|
|
|
273
278
|
| `canvas_import` | `list`, `read`, `submit`, `unavailable` |
|
|
@@ -303,7 +308,7 @@ Important routing:
|
|
|
303
308
|
As of v0.3.0, the 57 legacy single-purpose tools from the v0.2 compatibility window are removed.
|
|
304
309
|
The composites above plus the retained standalones are now the whole MCP surface: `canvas_batch`,
|
|
305
310
|
`canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`, `canvas_ingest_activity`, and
|
|
306
|
-
`canvas_invoke_command`. Snapshots are the `canvas_snapshot` composite (actions
|
|
311
|
+
`canvas_invoke_command`, plus `canvas_help`. Snapshots are the `canvas_snapshot` composite (actions
|
|
307
312
|
`save | list | restore | restore-node | delete | gc | diff`); the 6 legacy snapshot standalones were removed in
|
|
308
313
|
v0.4.0 after their deprecated 0.3.x window.
|
|
309
314
|
|
|
@@ -520,6 +525,10 @@ Use the visible workbench when the human is actively curating layout:
|
|
|
520
525
|
- Drop files or URLs to create matching nodes; an empty board shows starter actions (new note,
|
|
521
526
|
pick files, paste a link, start an agent session).
|
|
522
527
|
- Use a markdown node's Edit button to edit its content.
|
|
528
|
+
- For a frameless section label, set Markdown `data.display: "heading"` (or choose
|
|
529
|
+
**Display as heading** in its menu). `"card"` restores the ordinary frame.
|
|
530
|
+
- Zoom keeps the same spatial board and scales card content proportionally.
|
|
531
|
+
Viewers load automatically near the viewport, including at fit-all zoom.
|
|
523
532
|
- **Groups** are frames with the name pill and an action cluster (auto-arrange children, collapse,
|
|
524
533
|
⋯ rename / ungroup / pin all) on the top edge. Membership changes only on release while the
|
|
525
534
|
"release to add to <group>" pill shows (Esc keeps it out); dragging a child fully out offers
|
|
@@ -683,7 +692,7 @@ Single-card and layout reads record content delivery, not incident relations.
|
|
|
683
692
|
Graph cards and complete brief relations carry a captured `linksRevision`: reads
|
|
684
693
|
acknowledge that version, not later changes. Brief delivery is qualified by source board.
|
|
685
694
|
Reads never switch boards or mark full content read; pull needed cards in full separately. MCP remains
|
|
686
|
-
|
|
695
|
+
25 tools and 15 static canvas resources, with two canvas templates (context/graph).
|
|
687
696
|
|
|
688
697
|
Markdown saves bind `[[Board]]`, `[[Board#Card]]`, and `|alias` forms to durable
|
|
689
698
|
IDs; code spans/fences are ignored. Ambiguous/missing targets never guess or
|
|
@@ -782,23 +791,12 @@ changes do not advance content revisions, while semantic edits and undo do.
|
|
|
782
791
|
- A hosted ext-app (Excalidraw) node in a **WebKit** host panel (e.g. the GitHub Copilot app's
|
|
783
792
|
embedded WKWebView) can remain blank even when its API session is healthy and `sessionStatus`
|
|
784
793
|
is `ready`. This host-specific rendering issue remains open; successful Chrome/Codex rendering
|
|
785
|
-
does not establish native-host health.
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
probe, including when visibility is reported as visible. The recovery trail
|
|
792
|
-
(`GET /api/canvas/debug/ext-app-recovery` / `window.__PMX_EXTAPP_LOG`) now records
|
|
793
|
-
`mount-slot`, `paint-ok`, `paint-fail`, `soft-expand-cycle`, `recovery-exhausted`, and
|
|
794
|
-
`assume-visible-rearm`. Treat `paint-ok`/`paint-fail` as diagnostic evidence, not compositor
|
|
795
|
-
proof. Require a rendered screenshot in the affected host before declaring its surface
|
|
796
|
-
healthy; a Chromium pass does not close a native Copilot/WebKit failure. In a host that
|
|
797
|
-
reports the document hidden, `paint-ok` is recorded as
|
|
798
|
-
`paint-ok (unverified: host hidden)` — the app answered the paint probe, but nothing is
|
|
799
|
-
proven composited, so do not treat it as a verified paint. If a
|
|
800
|
-
tile still shows the Retry affordance, click Retry (fresh recovery budget) or expand-then-close;
|
|
801
|
-
attach the recovery trail when reporting.
|
|
794
|
+
does not establish native-host health. Viewers now share one loading/paint handoff and
|
|
795
|
+
explicit **Retry**. The old WebKit recovery ladder and its
|
|
796
|
+
diagnostic log endpoint are removed. A double-rAF acknowledgement establishes document
|
|
797
|
+
scheduling, not native compositor health. Require a rendered screenshot in the affected
|
|
798
|
+
host before declaring its surface healthy. A failed replacement keeps the last painted
|
|
799
|
+
document; use its Retry control rather than expecting automatic remount cycles.
|
|
802
800
|
- Ext-app frame documents live in server memory. Through 0.4.0, killing/restarting the daemon
|
|
803
801
|
while a panel stays open leaves ext-app tiles on dead frame URLs (`Frame document not found`,
|
|
804
802
|
0.4.0 report Finding S) until a full workbench reload. Since 0.4.1 the browser revalidates its
|
|
@@ -431,10 +431,15 @@ When you do set a color (group/graph), use this palette consistently to convey m
|
|
|
431
431
|
## MCP Tools Reference
|
|
432
432
|
|
|
433
433
|
PMX Canvas exposes **18 action-discriminated composites** (the whole recommended surface) plus
|
|
434
|
-
**
|
|
434
|
+
**7 first-class standalones** — 25 tools total. The composites fold the older
|
|
435
435
|
single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
|
|
436
436
|
**field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
|
|
437
437
|
|
|
438
|
+
`canvas_help {}` lists MCP registry operation names. `canvas_help { op: "node.add" }`
|
|
439
|
+
returns that operation's public tool, description and JSON argument schema, including
|
|
440
|
+
routing selectors and required fields. It does not start or read a canvas. Handlers still
|
|
441
|
+
enforce conditional requirements. Non-registry tools use `tools/list` instead.
|
|
442
|
+
|
|
438
443
|
> **Legacy single-purpose tools were removed in v0.3.0.** The old names (`canvas_add_node`,
|
|
439
444
|
> `canvas_update_node`, `canvas_request_approval`, `canvas_add_work_item`, …) are no longer
|
|
440
445
|
> registered — use the composites below and fresh MCP `tools/list` for the installed runtime's
|
|
@@ -531,7 +536,7 @@ AX excerpts and annotation anchors, including `canvas://ax-context` and
|
|
|
531
536
|
or brief relation entry instead.
|
|
532
537
|
Single-card and layout reads record content delivery, not incident relations.
|
|
533
538
|
Reads never switch boards or record full content reads. Pull cards in full
|
|
534
|
-
when needed. Counts
|
|
539
|
+
when needed. Counts are 25 tools, 15 static canvas resources; there are now two
|
|
535
540
|
canvas templates, context and graph, besides the skill file template.
|
|
536
541
|
|
|
537
542
|
Markdown save resolves `[[Board]]`, `[[Board#Card]]`, and `|alias` forms into
|
|
@@ -550,10 +555,15 @@ Once the board list has loaded, all workbench context-pin writes send the captur
|
|
|
550
555
|
`expectedBoardId`; a server-side board switch returns 409 instead of changing
|
|
551
556
|
another board's pins. Receipt pin actions require reopening the owning board.
|
|
552
557
|
|
|
553
|
-
Embedded
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
558
|
+
Embedded previews mount automatically near the viewport at every zoom and remain
|
|
559
|
+
mounted to preserve arbitrary app state. There is no lifetime preview cap. Mermaid renders
|
|
560
|
+
inline, without an iframe. JSON/graph viewers inherit all nine named canvas themes
|
|
561
|
+
and keep form drafts when the theme changes. During replacement, the pending
|
|
562
|
+
document temporarily coexists with its painted predecessor.
|
|
563
|
+
|
|
564
|
+
A network load or missing JSON runtime times out after 10 seconds and offers a
|
|
565
|
+
visible **Retry**, retaining the last painted document during replacement.
|
|
566
|
+
An already mounted offscreen runtime may wait for paint without being timed out.
|
|
557
567
|
|
|
558
568
|
Native edge add accepts optional `toBoardId`: `from` is on the open source board,
|
|
559
569
|
`to` is a card on the target board. CLI: `pmx-canvas edge add --from <source> --to <target>
|
|
@@ -1124,11 +1134,39 @@ Use native `json-render` and `graph` nodes when the output should stay fully ins
|
|
|
1124
1134
|
|
|
1125
1135
|
Spec elements support an `on` map (`on.press`, `on.change`, …) binding events to actions (`{ action, params }`) — built-in actions (`setState`, `pushState`, …) or, when named after an AX interaction type, a capability-gated AX emit. e.g. a Button with `on: { press: { action: 'ax.work.create', params: { title: '…' } } }` lets a human turn a panel control into a tracked work item; the viewer forwards it to the canvas, which validates and submits it server-side (clamped to the node's own id). See **Node AX Interactions** above.
|
|
1126
1136
|
|
|
1137
|
+
## MCP results and cancellation (0.10)
|
|
1138
|
+
|
|
1139
|
+
The 25-tool catalog remains on MCP SDK v1. Each tool advertises `outputSchema`
|
|
1140
|
+
and effect annotations. Help returns `structuredContent.result` with operation
|
|
1141
|
+
metadata (or the name list); it records no content reads. Registry-dispatched tools expose
|
|
1142
|
+
`structuredContent.result = { operation, outcome, data }`, where `outcome` is
|
|
1143
|
+
`success`, `error` or `partial`; `data` matches the existing compact text JSON,
|
|
1144
|
+
or `{message}` for prose. Exceptions use `operation: null`; they do not prove no
|
|
1145
|
+
writes occurred. Interaction, activity-ingestion and screenshot tools instead
|
|
1146
|
+
use direct structured objects matching their schemas. Check `isError` and domain
|
|
1147
|
+
outcomes. A composite's annotations cover all its actions, not just reads.
|
|
1148
|
+
|
|
1149
|
+
Cancellation releases supported AX waits, aborts webpage fetches before late
|
|
1150
|
+
content writes, and stops subsequent batch operations. Completed writes remain;
|
|
1151
|
+
batch partial results retain `results` and `refs`. A created webpage can remain
|
|
1152
|
+
with `fetch.cancelled: true`. External app builds/runtime calls and local WebView
|
|
1153
|
+
captures are not interruptible mid-call. After a disconnect, inspect state before
|
|
1154
|
+
retrying because the response may not arrive. `_meta.progressToken` enables batch
|
|
1155
|
+
and supported AX wait progress notifications, including through the daemon connection. Without
|
|
1156
|
+
a token, calls return their normal result without progress notifications.
|
|
1157
|
+
|
|
1127
1158
|
## MCP Resources
|
|
1128
1159
|
|
|
1129
1160
|
These resources give you read access to canvas intelligence. Read them to understand
|
|
1130
1161
|
what the human has set up and what they're focusing on.
|
|
1131
1162
|
|
|
1163
|
+
As of 0.10, live resource updates require `resources/subscribe` on each connection.
|
|
1164
|
+
Subscribe to the exact URI you read, including context/graph query parameters;
|
|
1165
|
+
`resources/unsubscribe` stops it, and reconnecting requires a new subscription.
|
|
1166
|
+
Notifications invalidate cached data but do not deliver content, record a read, or
|
|
1167
|
+
advance a consumer cursor. Explicit `resources/read` remains available without
|
|
1168
|
+
subscriptions. Static schema and bundled skills do not support subscriptions.
|
|
1169
|
+
|
|
1132
1170
|
| Resource | What it provides |
|
|
1133
1171
|
|----------|-----------------|
|
|
1134
1172
|
| `canvas://context{?budget,consumer,since}` | Budgeted cross-board map brief with optional durable consumer cursor |
|
|
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites and version
|
|
6
6
|
|
|
7
|
-
The examples pin **PMX Canvas 0.
|
|
7
|
+
The examples pin **PMX Canvas 0.11.0**, which requires **Bun >=1.4.4** even when
|
|
8
8
|
installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
|
|
9
9
|
first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
|
|
10
10
|
with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
|
|
@@ -22,11 +22,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
|
|
|
22
22
|
## Install from npm
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install -g pmx-canvas@0.
|
|
25
|
+
npm install -g pmx-canvas@0.11.0
|
|
26
26
|
pmx-canvas --version
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Without a global install, use `bunx pmx-canvas@0.
|
|
29
|
+
Without a global install, use `bunx pmx-canvas@0.11.0` in place of `pmx-canvas` in
|
|
30
30
|
the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
|
|
31
31
|
|
|
32
32
|
## Choose the workspace and target
|
|
@@ -96,7 +96,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
|
|
|
96
96
|
"mcpServers": {
|
|
97
97
|
"canvas": {
|
|
98
98
|
"command": "bunx",
|
|
99
|
-
"args": ["pmx-canvas@0.
|
|
99
|
+
"args": ["pmx-canvas@0.11.0", "--mcp"],
|
|
100
100
|
"env": {
|
|
101
101
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
102
102
|
"PMX_CANVAS_PORT": "14313",
|
|
@@ -140,7 +140,7 @@ Amp executes, not only on the user's laptop. Merge this into that workspace's
|
|
|
140
140
|
"amp.mcpServers": {
|
|
141
141
|
"canvas": {
|
|
142
142
|
"command": "bunx",
|
|
143
|
-
"args": ["pmx-canvas@0.
|
|
143
|
+
"args": ["pmx-canvas@0.11.0", "--mcp"],
|
|
144
144
|
"includeTools": ["canvas_*"],
|
|
145
145
|
"env": {
|
|
146
146
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
@@ -174,8 +174,8 @@ The agent must not run this approval itself or move the command to global settin
|
|
|
174
174
|
`--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
|
|
175
175
|
command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
|
|
176
176
|
After approval, reload MCP connections (`reload_mcp` in Amp) and require `connected`.
|
|
177
|
-
Discover `canvas` tools with `tool_search`;
|
|
178
|
-
|
|
177
|
+
Discover `canvas` tools with `tool_search`; the full unfiltered 0.11.0 server has 25 tools,
|
|
178
|
+
including `canvas_help`. If still absent, inspect the server status, command/PATH and target instead
|
|
179
179
|
of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
|
|
180
180
|
|
|
181
181
|
### Verify native tools, then steering
|
|
@@ -75,11 +75,9 @@ if [[ -f "${REPO_ROOT}/LICENSE" ]]; then
|
|
|
75
75
|
cp "${REPO_ROOT}/LICENSE" "${PACKAGE_STAGING}/package/LICENSE"
|
|
76
76
|
fi
|
|
77
77
|
cp -R "${REPO_ROOT}/src" "${PACKAGE_STAGING}/package/src"
|
|
78
|
-
mkdir -p "${PACKAGE_STAGING}/package/dist
|
|
79
|
-
cp "${REPO_ROOT}/dist/canvas
|
|
80
|
-
cp "${REPO_ROOT}/dist/
|
|
81
|
-
cp "${REPO_ROOT}/dist/json-render/index.js" "${PACKAGE_STAGING}/package/dist/json-render/index.js"
|
|
82
|
-
cp "${REPO_ROOT}/dist/json-render/index.css" "${PACKAGE_STAGING}/package/dist/json-render/index.css"
|
|
78
|
+
mkdir -p "${PACKAGE_STAGING}/package/dist"
|
|
79
|
+
cp -R "${REPO_ROOT}/dist/canvas" "${PACKAGE_STAGING}/package/dist/canvas"
|
|
80
|
+
cp -R "${REPO_ROOT}/dist/json-render" "${PACKAGE_STAGING}/package/dist/json-render"
|
|
83
81
|
TARBALL="pmx-canvas-published-consumer.tgz"
|
|
84
82
|
tar -czf "${PACK_DIR}/${TARBALL}" -C "${PACKAGE_STAGING}" package
|
|
85
83
|
|
|
@@ -20,6 +20,7 @@ export function BarPortal({ children }: { children: VNode }) {
|
|
|
20
20
|
* `title` hints, which are delay-gated and never render in embedded panes.
|
|
21
21
|
*/
|
|
22
22
|
export function BarHint({
|
|
23
|
+
id,
|
|
23
24
|
label,
|
|
24
25
|
shortcut,
|
|
25
26
|
body,
|
|
@@ -29,10 +30,12 @@ export function BarHint({
|
|
|
29
30
|
fitWithin,
|
|
30
31
|
children,
|
|
31
32
|
}: {
|
|
33
|
+
/** Optional description target for the control's aria-describedby. */
|
|
34
|
+
id?: string;
|
|
32
35
|
label: string;
|
|
33
36
|
shortcut?: string;
|
|
34
|
-
/**
|
|
35
|
-
body?:
|
|
37
|
+
/** Explanatory text, optionally split into readable paragraphs. */
|
|
38
|
+
body?: ComponentChildren;
|
|
36
39
|
align?: 'start' | 'center' | 'end';
|
|
37
40
|
/** Which way the tooltip opens. Bars at the bottom of the region open 'up'. */
|
|
38
41
|
side?: 'down' | 'up';
|
|
@@ -73,7 +76,7 @@ export function BarHint({
|
|
|
73
76
|
onFocusIn={fitWithin ? fit : undefined}
|
|
74
77
|
>
|
|
75
78
|
{children}
|
|
76
|
-
<span class="toolbar-tooltip" role="tooltip">
|
|
79
|
+
<span id={id} class="toolbar-tooltip" role="tooltip">
|
|
77
80
|
<span class="toolbar-tooltip-label">{label}</span>
|
|
78
81
|
{body && <span class="toolbar-tooltip-body">{body}</span>}
|
|
79
82
|
{shortcut && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { boardList } from '../state/boards-store';
|
|
1
|
+
import { activeBoard, boardList } from '../state/boards-store';
|
|
2
2
|
import { cardQualification, effectiveLifecycleFilter as lifecycleFilter } from '../state/knowledge-store';
|
|
3
3
|
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
4
4
|
import { relationHighlight } from '../state/relation-store';
|
|
@@ -50,7 +50,7 @@ import { AxStepControls } from '../nodes/AxStepControls';
|
|
|
50
50
|
import { getNodeIcon, IconChevronRight, IconClose, IconMore, IconExpand, IconPin } from '../icons';
|
|
51
51
|
import { EXPANDABLE_TYPES, TYPE_LABELS } from '../types';
|
|
52
52
|
import type { CanvasNodeState } from '../types';
|
|
53
|
-
import {
|
|
53
|
+
import { computeAutoFitHeight, shouldAutoFitNode } from './auto-fit';
|
|
54
54
|
import { activeGuides, buildSnapCache, clearSnapCache, snapToGuides } from './snap-guides';
|
|
55
55
|
import { useNodeDrag } from './use-node-drag';
|
|
56
56
|
import { useNodeResize } from './use-node-resize';
|
|
@@ -89,44 +89,6 @@ export function nearestNodeInDirection(
|
|
|
89
89
|
return best;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/** Fraction of a node's height the title bar may occupy before it starts
|
|
93
|
-
* crowding out the content it is labelling. */
|
|
94
|
-
const MAX_TITLEBAR_HEIGHT_RATIO = 0.4;
|
|
95
|
-
/** Fraction of a node's width the fixed title-bar chrome may occupy before it
|
|
96
|
-
* starts eating the node's NAME. */
|
|
97
|
-
const MAX_TITLEBAR_CHROME_WIDTH_RATIO = 0.55;
|
|
98
|
-
/** Unscaled width of everything in the bar that is not the title: type icon,
|
|
99
|
-
* status/type pill, the control cluster, and the gaps between them. */
|
|
100
|
-
const TITLEBAR_CHROME_BASE_WIDTH = 175;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* How much to enlarge node chrome (title bar, badges, icons) when zoomed out so
|
|
104
|
-
* it stays legible. Full inverse compensation is capped at 2.2x — but the cap
|
|
105
|
-
* alone ignored the node it is drawn on, so on SHORT nodes the growing bar ate
|
|
106
|
-
* the body: a 116px-tall section label at 46% zoom gave the title bar 72% of the
|
|
107
|
-
* node and left 14px for text that needed 41, so the markdown was simply cut off.
|
|
108
|
-
* The scale is therefore also bounded by the node's own height, keeping the bar
|
|
109
|
-
* under MAX_TITLEBAR_HEIGHT_RATIO of it. Tall nodes are unaffected (their height
|
|
110
|
-
* never binds); pass height 0 for collapsed/auto-height nodes to skip the bound.
|
|
111
|
-
*/
|
|
112
|
-
export function nodeChromeScale(viewportScale: number, nodeHeight: number, nodeWidth = 0): number {
|
|
113
|
-
if (!Number.isFinite(viewportScale) || viewportScale >= 1) return 1;
|
|
114
|
-
const zoomScale = Math.min(2.2, 1 / Math.max(viewportScale, 0.01));
|
|
115
|
-
let bounded = zoomScale;
|
|
116
|
-
if (Number.isFinite(nodeHeight) && nodeHeight > 0) {
|
|
117
|
-
bounded = Math.min(bounded, (nodeHeight * MAX_TITLEBAR_HEIGHT_RATIO) / AUTO_FIT_TITLEBAR_HEIGHT);
|
|
118
|
-
}
|
|
119
|
-
// Width matters as much as height: the icon, pill and control cluster are all
|
|
120
|
-
// flex-shrink: 0, so the NAME is the only thing that gives way. Unbounded, a
|
|
121
|
-
// 360px step node at 52% zoom spent 100 of 187 bar pixels on controls alone and
|
|
122
|
-
// the title collapsed to nothing. Bound the scale so the fixed chrome can never
|
|
123
|
-
// take more than MAX_TITLEBAR_CHROME_WIDTH_RATIO of the node.
|
|
124
|
-
if (Number.isFinite(nodeWidth) && nodeWidth > 0) {
|
|
125
|
-
bounded = Math.min(bounded, (nodeWidth * MAX_TITLEBAR_CHROME_WIDTH_RATIO) / TITLEBAR_CHROME_BASE_WIDTH);
|
|
126
|
-
}
|
|
127
|
-
return Math.max(1, bounded);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
92
|
/** A header title keeps at least this much room, or all of it when shorter (HeaderMarks.dc.html). */
|
|
131
93
|
const TITLE_MIN_WIDTH = 140;
|
|
132
94
|
|
|
@@ -217,7 +179,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
217
179
|
document.removeEventListener('keydown', escListener.current);
|
|
218
180
|
escListener.current = null;
|
|
219
181
|
}
|
|
220
|
-
persistLayout();
|
|
182
|
+
persistLayout({ raisedNodeId: node.id });
|
|
221
183
|
// Membership changes ONLY here, and only while the pill was showing.
|
|
222
184
|
const target = dragDropTarget.value;
|
|
223
185
|
endDropTracking();
|
|
@@ -308,7 +270,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
308
270
|
toggleSelected(node.id);
|
|
309
271
|
return;
|
|
310
272
|
}
|
|
311
|
-
bringToFront(node.id);
|
|
273
|
+
bringToFront(node.id, false);
|
|
312
274
|
// User wins (phase 8): hold the node for the drag; an agent mid-edit yields.
|
|
313
275
|
reportHumanGrab(node.id);
|
|
314
276
|
noteNearAtGrab(node.id);
|
|
@@ -359,6 +321,15 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
359
321
|
(e: KeyboardEvent) => {
|
|
360
322
|
const target = e.target as HTMLElement | null;
|
|
361
323
|
if (target && target !== e.currentTarget && target.closest('input, textarea, [contenteditable="true"]')) return;
|
|
324
|
+
if (e.key === 'ContextMenu' || (e.shiftKey && e.key === 'F10')) {
|
|
325
|
+
const more = shellRef.current?.querySelector<HTMLButtonElement>('.node-hover-control[aria-haspopup="menu"]');
|
|
326
|
+
if (more) {
|
|
327
|
+
e.preventDefault();
|
|
328
|
+
e.stopPropagation();
|
|
329
|
+
more.click();
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
362
333
|
const dir = ARROW_DIRS[e.key];
|
|
363
334
|
if (dir) {
|
|
364
335
|
e.preventDefault();
|
|
@@ -445,10 +416,14 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
445
416
|
const isTrace = node.type === 'trace';
|
|
446
417
|
const isTraceRunning = isTrace && node.data.status === 'running';
|
|
447
418
|
const isGroup = node.type === 'group';
|
|
419
|
+
const isHeading = node.type === 'markdown' && node.data.display === 'heading';
|
|
420
|
+
const graphType = (node.data.graphConfig as { graphType?: string } | undefined)?.graphType;
|
|
448
421
|
const isStrictSize = node.data.strictSize === true;
|
|
449
422
|
const viewportScale = Math.max(viewport.value.scale, 0.01);
|
|
450
|
-
|
|
451
|
-
|
|
423
|
+
// Text alone may grow within the fixed header line. Enlarging the whole bar
|
|
424
|
+
// resizes embedded viewports and reflows their content while zooming.
|
|
425
|
+
const titleScale = Math.min(1.2, Math.max(1, 1 / viewportScale));
|
|
426
|
+
const chipFold = useHeaderFold(titleRef, headerChip(node, isContextPinned), title, node.size.width, titleScale);
|
|
452
427
|
|
|
453
428
|
const groupColor = isGroup && node.data.color ? (node.data.color as string) : undefined;
|
|
454
429
|
const nodeStyle: Record<string, string | number> = {
|
|
@@ -457,7 +432,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
457
432
|
width: `${node.size.width}px`,
|
|
458
433
|
height: node.collapsed ? 'auto' : `${node.size.height}px`,
|
|
459
434
|
zIndex: node.zIndex,
|
|
460
|
-
'--node-
|
|
435
|
+
'--node-title-scale': titleScale.toFixed(3),
|
|
461
436
|
'--node-port-scale': (1 / viewportScale).toFixed(3),
|
|
462
437
|
...(groupColor ? { '--group-color': groupColor } : {}),
|
|
463
438
|
};
|
|
@@ -477,6 +452,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
477
452
|
isTrace ? 'trace-node' : '',
|
|
478
453
|
isTraceRunning ? 'trace-running' : '',
|
|
479
454
|
isGroup ? 'group-node' : '',
|
|
455
|
+
isHeading ? 'heading-node' : '',
|
|
480
456
|
isStrictSize ? 'strict-size' : '',
|
|
481
457
|
isAgentMutating ? 'agent-mutating' : '',
|
|
482
458
|
sessionChromeActive.value && sessionEditedIds.value.has(node.id) ? 'session-edited' : '',
|
|
@@ -623,7 +599,9 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
623
599
|
) : (
|
|
624
600
|
<span class="group-name">{title}</span>
|
|
625
601
|
)}
|
|
626
|
-
<span class="group-count">
|
|
602
|
+
<span class="group-count">
|
|
603
|
+
{groupChildren.length} {groupChildren.length === 1 ? 'node' : 'nodes'}
|
|
604
|
+
</span>
|
|
627
605
|
</span>
|
|
628
606
|
<span class="group-edge-spacer" />
|
|
629
607
|
<span class="group-actions" role="toolbar" aria-label={`${title} actions`}>
|
|
@@ -740,7 +718,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
740
718
|
<NodeTypeIcon node={node} pinned={isContextPinned} title={title} fold={chipFold}>
|
|
741
719
|
{(() => {
|
|
742
720
|
const NodeIcon = getNodeIcon(node.type);
|
|
743
|
-
return <NodeIcon size={
|
|
721
|
+
return <NodeIcon size={14} />;
|
|
744
722
|
})()}
|
|
745
723
|
</NodeTypeIcon>
|
|
746
724
|
{/* The kind-colored icon says what type this is (rail-chrome-v2 card
|
|
@@ -766,6 +744,14 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
766
744
|
{title}
|
|
767
745
|
</span>
|
|
768
746
|
)}
|
|
747
|
+
{activeBoard()?.readmeNodeId === node.id && <span class="node-readme-mark">README</span>}
|
|
748
|
+
{node.type === 'graph' && graphType && <span class="node-header-meta">{graphType}</span>}
|
|
749
|
+
{node.type === 'file' && <div class="node-file-toolbar" data-file-toolbar={node.id} />}
|
|
750
|
+
{node.type === 'status' && typeof node.data.updatedAt === 'string' && (
|
|
751
|
+
<time class="node-header-meta" dateTime={node.data.updatedAt}>
|
|
752
|
+
{new Date(node.data.updatedAt).toLocaleTimeString()}
|
|
753
|
+
</time>
|
|
754
|
+
)}
|
|
769
755
|
<NearPinMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
770
756
|
<NodeContextMark node={node} pinned={isContextPinned} fold={chipFold} />
|
|
771
757
|
<div class="node-controls">
|
|
@@ -827,6 +813,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
827
813
|
onContextMenu?.(e, node.id);
|
|
828
814
|
}}
|
|
829
815
|
aria-label="More actions"
|
|
816
|
+
aria-haspopup="menu"
|
|
830
817
|
>
|
|
831
818
|
<IconMore size={14} />
|
|
832
819
|
</button>
|
|
@@ -17,6 +17,7 @@ import { HtmlNode } from '../nodes/HtmlNode';
|
|
|
17
17
|
import { PromptNode } from '../nodes/PromptNode';
|
|
18
18
|
import { ResponseNode } from '../nodes/ResponseNode';
|
|
19
19
|
import { TraceNode } from '../nodes/TraceNode';
|
|
20
|
+
import { ViewerHost } from '../nodes/ViewerHost';
|
|
20
21
|
import {
|
|
21
22
|
activeNodeId,
|
|
22
23
|
cancelViewportAnimation,
|
|
@@ -111,9 +112,10 @@ class WorldNode extends Component<{
|
|
|
111
112
|
|
|
112
113
|
render() {
|
|
113
114
|
const { node, onContextMenu } = this.props;
|
|
115
|
+
const viewer = ['html', 'mcp-app', 'json-render', 'graph', 'webpage', 'mermaid'].includes(node.type);
|
|
114
116
|
return (
|
|
115
117
|
<CanvasNode node={node} onContextMenu={onContextMenu}>
|
|
116
|
-
{renderNodeContent(node)}
|
|
118
|
+
{viewer ? <ViewerHost node={node}>{renderNodeContent(node)}</ViewerHost> : renderNodeContent(node)}
|
|
117
119
|
</CanvasNode>
|
|
118
120
|
);
|
|
119
121
|
}
|
|
@@ -873,9 +875,8 @@ export function CanvasViewport({
|
|
|
873
875
|
: 'default',
|
|
874
876
|
}}
|
|
875
877
|
>
|
|
876
|
-
{/*
|
|
877
|
-
|
|
878
|
-
the scale pivot is the top-left corner of the world layer. */}
|
|
878
|
+
{/* Scale the complete spatial world proportionally, including iframe
|
|
879
|
+
contents. CSS zoom changes embedded viewport semantics in WebKit. */}
|
|
879
880
|
<div
|
|
880
881
|
ref={worldRef}
|
|
881
882
|
class="canvas-world"
|
|
@@ -894,9 +895,7 @@ export function CanvasViewport({
|
|
|
894
895
|
<IntentLayer />
|
|
895
896
|
<AgentPresenceLayer />
|
|
896
897
|
<HumanPresenceLayer />
|
|
897
|
-
<EdgeLayer nodes={nodes} edges={edges}
|
|
898
|
-
<EdgeLayer nodes={nodes} edges={edges} part="pills" />
|
|
899
|
-
<EdgeLayer nodes={nodes} edges={edges} part="pill-hits" />
|
|
898
|
+
<EdgeLayer nodes={nodes} edges={edges} />
|
|
900
899
|
{draggingEdge.value && (
|
|
901
900
|
<div
|
|
902
901
|
class="edge-hint-pill"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComponentChildren } from 'preact';
|
|
2
2
|
import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
|
|
3
|
-
import { useCallback, useLayoutEffect, useState } from 'preact/hooks';
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
4
4
|
import {
|
|
5
5
|
contextPinnedNodeIds,
|
|
6
6
|
edges,
|
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
updateNodeFromClient,
|
|
29
29
|
} from '../state/intent-bridge';
|
|
30
30
|
import { canOpenAsSite, openNodeAsSite } from '../nodes/surface-url';
|
|
31
|
+
import { canCopyPrimitiveJson, copyPrimitiveJson } from '../nodes/primitive-copy';
|
|
31
32
|
import { askText } from './TextPrompt';
|
|
32
|
-
import { activeBoard, boardIncarnation } from '../state/boards-store';
|
|
33
|
+
import { activeBoard, boardIncarnation, setBoardReadme } from '../state/boards-store';
|
|
33
34
|
import { openLifecycle } from '../state/knowledge-store';
|
|
34
35
|
import { RELATION_KINDS, relationKind } from '../../shared/relations.js';
|
|
35
36
|
import { EXPANDABLE_TYPES } from '../types';
|
|
@@ -62,7 +63,9 @@ export function useContextMenu() {
|
|
|
62
63
|
const openNodeMenu = useCallback((e: MouseEvent, nodeId: string) => {
|
|
63
64
|
e.preventDefault();
|
|
64
65
|
e.stopPropagation();
|
|
65
|
-
|
|
66
|
+
const trigger = e.detail === 0 && e.target instanceof Element ? e.target.closest('button') : null;
|
|
67
|
+
const bounds = trigger?.getBoundingClientRect();
|
|
68
|
+
setMenu({ kind: 'node', x: bounds?.left ?? e.clientX, y: bounds?.bottom ?? e.clientY, nodeId });
|
|
66
69
|
}, []);
|
|
67
70
|
|
|
68
71
|
const openCanvasMenu = useCallback((e: MouseEvent, canvasX: number, canvasY: number) => {
|
|
@@ -113,6 +116,21 @@ const GROUP_COLOR_PRESETS = [
|
|
|
113
116
|
] as const;
|
|
114
117
|
|
|
115
118
|
export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
119
|
+
const root = useRef<HTMLDivElement>(null);
|
|
120
|
+
useLayoutEffect(() => {
|
|
121
|
+
const element = root.current;
|
|
122
|
+
const trigger = document.activeElement;
|
|
123
|
+
element?.querySelector<HTMLButtonElement>('button:not(:disabled)')?.focus();
|
|
124
|
+
return () => {
|
|
125
|
+
if (element?.contains(document.activeElement) && trigger instanceof HTMLElement && trigger.isConnected) {
|
|
126
|
+
// Card controls hide when the menu takes focus. Reveal the opener
|
|
127
|
+
// through its card before restoring focus to the button itself.
|
|
128
|
+
trigger.closest<HTMLElement>('.canvas-node')?.focus({ preventScroll: true });
|
|
129
|
+
trigger.focus({ preventScroll: true });
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}, [menu]);
|
|
133
|
+
|
|
116
134
|
let items: MenuItem[];
|
|
117
135
|
if (menu.kind === 'node') {
|
|
118
136
|
const node = nodes.value.get(menu.nodeId);
|
|
@@ -215,6 +233,9 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
215
233
|
|
|
216
234
|
return (
|
|
217
235
|
<div
|
|
236
|
+
ref={root}
|
|
237
|
+
role="menu"
|
|
238
|
+
aria-label={menu.kind === 'node' ? 'Node actions' : menu.kind === 'edge' ? 'Edge actions' : 'Canvas actions'}
|
|
218
239
|
class="context-menu"
|
|
219
240
|
style={{
|
|
220
241
|
position: 'fixed',
|
|
@@ -224,6 +245,27 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
224
245
|
}}
|
|
225
246
|
onPointerDown={(e) => e.stopPropagation()}
|
|
226
247
|
onClick={(e) => e.stopPropagation()}
|
|
248
|
+
onBlur={(e) => {
|
|
249
|
+
if (e.relatedTarget instanceof Node && !e.currentTarget.contains(e.relatedTarget)) onClose();
|
|
250
|
+
}}
|
|
251
|
+
onKeyDown={(e) => {
|
|
252
|
+
e.stopPropagation();
|
|
253
|
+
if (e.key === 'Escape') {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
onClose();
|
|
256
|
+
} else if (e.target instanceof HTMLButtonElement && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
const controls = [...e.currentTarget.querySelectorAll<HTMLElement>('button:not(:disabled), input, select')];
|
|
259
|
+
const index = controls.indexOf(e.target);
|
|
260
|
+
const next =
|
|
261
|
+
e.key === 'Home'
|
|
262
|
+
? 0
|
|
263
|
+
: e.key === 'End'
|
|
264
|
+
? controls.length - 1
|
|
265
|
+
: (index + (e.key === 'ArrowUp' ? -1 : 1) + controls.length) % controls.length;
|
|
266
|
+
controls[next]?.focus();
|
|
267
|
+
}
|
|
268
|
+
}}
|
|
227
269
|
>
|
|
228
270
|
{items.map((item) => {
|
|
229
271
|
const baseKey = item.separator
|
|
@@ -251,6 +293,8 @@ export function ContextMenu({ menu, onClose }: ContextMenuProps) {
|
|
|
251
293
|
<button
|
|
252
294
|
key={itemKey}
|
|
253
295
|
type="button"
|
|
296
|
+
role="menuitem"
|
|
297
|
+
disabled={!item.action}
|
|
254
298
|
class={`context-menu-item${item.danger ? ' is-danger' : ''}`}
|
|
255
299
|
onClick={() => {
|
|
256
300
|
item.action?.();
|
|
@@ -622,6 +666,22 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
|
|
|
622
666
|
items.push({ separator: true });
|
|
623
667
|
|
|
624
668
|
// Type-specific
|
|
669
|
+
if (canCopyPrimitiveJson(node)) {
|
|
670
|
+
items.push({ label: 'Copy JSON', action: () => void copyPrimitiveJson(node.id) });
|
|
671
|
+
}
|
|
672
|
+
if (node.type === 'markdown') {
|
|
673
|
+
items.push({
|
|
674
|
+
label: node.data.display === 'heading' ? 'Display as card' : 'Display as heading',
|
|
675
|
+
action: () =>
|
|
676
|
+
void updateNodeFromClient(node.id, { data: { display: node.data.display === 'heading' ? 'card' : 'heading' } }),
|
|
677
|
+
});
|
|
678
|
+
const board = activeBoard();
|
|
679
|
+
if (board)
|
|
680
|
+
items.push({
|
|
681
|
+
label: board.readmeNodeId === node.id ? 'Remove as README' : 'Set as README',
|
|
682
|
+
action: () => void setBoardReadme(board.id, board.readmeNodeId === node.id ? null : node.id),
|
|
683
|
+
});
|
|
684
|
+
}
|
|
625
685
|
if ((node.type === 'markdown' || node.type === 'file' || node.type === 'image') && localPath) {
|
|
626
686
|
items.push({
|
|
627
687
|
label: 'Open in browser',
|