pmx-canvas 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +4 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +440 -18
  4. package/CHANGELOG.md +51 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +267 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1168 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1839 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +301 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +117 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/{abnfDiagram-N423BO3Z-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
  35. package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
  36. package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/{cose-bilkent-JH36ORCC-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
  42. package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
  43. package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
  44. package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
  45. package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
  46. package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
  47. package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
  48. package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
  49. package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/{ganttDiagram-PKOTCBZU-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
  55. package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
  56. package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
  57. package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
  58. package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
  59. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  60. package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
  61. package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
  62. package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
  63. package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
  64. package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
  65. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  66. package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
  67. package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
  68. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  69. package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
  70. package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
  71. package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
  72. package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
  73. package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
  74. package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
  75. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  76. package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
  77. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  78. package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
  79. package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
  80. package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
  81. package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
  82. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  83. package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
  84. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  85. package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
  86. package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
  87. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  88. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  89. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  90. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  91. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  92. package/dist/canvas/chunks/{railroadDiagram-AXF67PYL-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
  93. package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
  94. package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
  95. package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
  96. package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
  97. package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
  98. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  99. package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
  100. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  101. package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
  102. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  103. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  104. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  105. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  106. package/dist/canvas/chunks/{wardleyDiagram-T6FBY63Y-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
  107. package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
  108. package/dist/canvas/global.css +94 -1
  109. package/dist/canvas/index.js +154 -145
  110. package/dist/canvas/mcp-app.html +600 -498
  111. package/dist/json-render/index.css +1 -1
  112. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  113. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  114. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  115. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  116. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  117. package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
  118. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  119. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  120. package/dist/types/client/state/canvas-store.d.ts +15 -0
  121. package/dist/types/client/state/human-store.d.ts +2 -0
  122. package/dist/types/client/state/intent-bridge.d.ts +4 -2
  123. package/dist/types/client/state/relation-store.d.ts +11 -0
  124. package/dist/types/client/types.d.ts +1 -1
  125. package/dist/types/json-render/catalog.d.ts +2 -2
  126. package/dist/types/server/agent-presence.d.ts +1 -0
  127. package/dist/types/server/ax-interaction.d.ts +1 -0
  128. package/dist/types/server/ax-state-manager.d.ts +10 -2
  129. package/dist/types/server/board-local.d.ts +4 -0
  130. package/dist/types/server/board-move.d.ts +38 -0
  131. package/dist/types/server/canvas-db.d.ts +12 -2
  132. package/dist/types/server/canvas-operations.d.ts +5 -6
  133. package/dist/types/server/canvas-provenance.d.ts +1 -1
  134. package/dist/types/server/canvas-serialization.d.ts +1 -0
  135. package/dist/types/server/canvas-state.d.ts +84 -20
  136. package/dist/types/server/card-relocation.d.ts +6 -0
  137. package/dist/types/server/context-reads.d.ts +3 -1
  138. package/dist/types/server/human-presence.d.ts +1 -1
  139. package/dist/types/server/index.d.ts +16 -5
  140. package/dist/types/server/intent-registry.d.ts +8 -4
  141. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  142. package/dist/types/server/mutation-history.d.ts +7 -5
  143. package/dist/types/server/operations/registry.d.ts +2 -0
  144. package/dist/types/server/trace-manager.d.ts +1 -0
  145. package/dist/types/shared/ax-kinds.d.ts +6 -0
  146. package/dist/types/shared/ax-surface-protocol.d.ts +8 -0
  147. package/dist/types/shared/human-presence.d.ts +1 -0
  148. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  149. package/docs/RELEASE.md +21 -14
  150. package/docs/agent-plugin.md +22 -2
  151. package/docs/amp-plugin.md +270 -0
  152. package/docs/cli.md +42 -3
  153. package/docs/design/GuestName.dc.html +50 -0
  154. package/docs/design/PluginLogo.svg +7 -0
  155. package/docs/design/WorkCards.dc.html +39 -0
  156. package/docs/design/canvas.json +16 -0
  157. package/docs/design/plugin-logo.png +0 -0
  158. package/docs/design.md +111 -8
  159. package/docs/evals/agents-md-sections.md +38 -0
  160. package/docs/http-api.md +128 -10
  161. package/docs/mcp.md +61 -9
  162. package/docs/node-types.md +71 -1
  163. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  164. package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
  165. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  166. package/docs/product-vision-2026-09.md +33 -17
  167. package/docs/sdk.md +18 -0
  168. package/package.json +6 -3
  169. package/plugins/pmx-canvas/assets/preview.png +0 -0
  170. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +4 -1
  171. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  172. package/plugins/pmx-canvas/plugin.json +2 -2
  173. package/plugins/pmx-canvas/runtime.json +1 -1
  174. package/plugins/pmx-canvas/tools.json +440 -18
  175. package/skills/data-analysis/SKILL.md +13 -26
  176. package/skills/frontend-design/LICENSE.txt +177 -0
  177. package/skills/frontend-design/SKILL.md +3 -0
  178. package/skills/pmx-canvas/SKILL.md +248 -842
  179. package/skills/pmx-canvas/evals/evals.json +110 -5
  180. package/skills/pmx-canvas/references/full-reference.md +142 -28
  181. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  182. package/skills/pmx-canvas/references/installing-pmx-canvas.md +61 -11
  183. package/skills/pmx-canvas/references/tours-and-recording.md +39 -6
  184. package/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  185. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  186. package/skills/published-consumer-e2e/SKILL.md +25 -8
  187. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -75
  188. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  189. package/skills/web-artifacts-builder/SKILL.md +6 -0
  190. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +2 -2
  191. package/skills/web-artifacts-builder/scripts/init-artifact.sh +8 -8
  192. package/src/cli/commands/apps.ts +1 -0
  193. package/src/cli/commands/boards.ts +30 -0
  194. package/src/client/App.tsx +20 -4
  195. package/src/client/canvas/AttentionHistory.tsx +10 -2
  196. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  197. package/src/client/canvas/CanvasNode.tsx +45 -2
  198. package/src/client/canvas/CanvasViewport.tsx +38 -9
  199. package/src/client/canvas/ContextMenu.tsx +34 -12
  200. package/src/client/canvas/EdgeEditor.tsx +149 -0
  201. package/src/client/canvas/EdgeLayer.tsx +87 -16
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
  203. package/src/client/canvas/GroupNavigation.tsx +85 -0
  204. package/src/client/canvas/RelationsPanel.tsx +9 -0
  205. package/src/client/canvas/SelectionBar.tsx +21 -0
  206. package/src/client/canvas/SessionPanel.tsx +35 -1
  207. package/src/client/canvas/TextPrompt.tsx +18 -1
  208. package/src/client/canvas/TopBar.tsx +30 -0
  209. package/src/client/canvas/canvas-area.ts +1 -1
  210. package/src/client/nodes/ExtAppFrame.tsx +26 -10
  211. package/src/client/nodes/MarkdownNode.tsx +6 -7
  212. package/src/client/nodes/PromptNode.tsx +32 -3
  213. package/src/client/nodes/StatusNode.tsx +12 -0
  214. package/src/client/nodes/StatusSummary.tsx +11 -0
  215. package/src/client/nodes/ViewerFrame.tsx +21 -10
  216. package/src/client/nodes/WorkCard.tsx +175 -0
  217. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  218. package/src/client/state/attention-bridge.ts +5 -0
  219. package/src/client/state/attention-store.ts +1 -0
  220. package/src/client/state/canvas-store.ts +185 -15
  221. package/src/client/state/human-store.ts +22 -2
  222. package/src/client/state/intent-bridge.ts +19 -5
  223. package/src/client/state/relation-store.ts +127 -1
  224. package/src/client/theme/global.css +94 -1
  225. package/src/client/types.ts +1 -0
  226. package/src/mcp/server.ts +13 -3
  227. package/src/server/agent-presence.ts +43 -23
  228. package/src/server/ax-flow-loop.ts +6 -0
  229. package/src/server/ax-gate-ttl.ts +8 -1
  230. package/src/server/ax-interaction.ts +16 -10
  231. package/src/server/ax-state-manager.ts +17 -4
  232. package/src/server/ax-wait.ts +20 -6
  233. package/src/server/board-export.ts +19 -6
  234. package/src/server/board-local.ts +20 -0
  235. package/src/server/board-move.ts +297 -0
  236. package/src/server/canvas-db.ts +262 -160
  237. package/src/server/canvas-operations.ts +139 -109
  238. package/src/server/canvas-schema.ts +21 -0
  239. package/src/server/canvas-serialization.ts +2 -0
  240. package/src/server/canvas-state.ts +792 -159
  241. package/src/server/card-relocation.ts +53 -0
  242. package/src/server/code-graph.ts +4 -3
  243. package/src/server/context-reads.ts +83 -55
  244. package/src/server/demo-state.json +35 -15
  245. package/src/server/file-watcher.ts +30 -10
  246. package/src/server/human-presence.ts +18 -5
  247. package/src/server/index.ts +35 -15
  248. package/src/server/intent-registry.ts +43 -5
  249. package/src/server/knowledge-lifecycle.ts +32 -9
  250. package/src/server/lifecycle-status.ts +29 -1
  251. package/src/server/mcp-app-runtime.ts +26 -7
  252. package/src/server/mutation-history.ts +37 -8
  253. package/src/server/operations/composites.ts +6 -3
  254. package/src/server/operations/mcp-contracts.ts +15 -0
  255. package/src/server/operations/mcp.ts +36 -9
  256. package/src/server/operations/ops/annotation.ts +22 -1
  257. package/src/server/operations/ops/ax-await.ts +1 -0
  258. package/src/server/operations/ops/ax-timeline.ts +28 -2
  259. package/src/server/operations/ops/batch.ts +25 -11
  260. package/src/server/operations/ops/boards.ts +44 -3
  261. package/src/server/operations/ops/nodes.ts +70 -0
  262. package/src/server/operations/ops/query.ts +32 -3
  263. package/src/server/operations/ops/snapshots.ts +1 -0
  264. package/src/server/operations/registry.ts +49 -4
  265. package/src/server/server.ts +62 -16
  266. package/src/server/tour-control.ts +37 -12
  267. package/src/server/trace-manager.ts +4 -0
  268. package/src/server/web-artifacts/scripts/bundle-artifact.sh +2 -2
  269. package/src/server/web-artifacts/scripts/init-artifact.sh +8 -8
  270. package/src/shared/agent-context.ts +7 -1
  271. package/src/shared/auto-arrange.ts +5 -4
  272. package/src/shared/ax-kinds.ts +13 -0
  273. package/src/shared/ax-surface-protocol.ts +22 -0
  274. package/src/shared/human-presence.ts +1 -0
  275. package/src/shared/lifecycle-status.ts +2 -0
  276. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
  277. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
  278. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
  279. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
  280. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
  281. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
  282. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
  283. package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
  284. package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
  285. package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
  286. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
  287. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
  288. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
  289. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
  290. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
  291. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
  292. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
  293. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
  294. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
  295. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
  296. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
  297. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
  298. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
  299. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
  300. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
@@ -0,0 +1,1839 @@
1
+ ---
2
+ name: pmx-canvas
3
+ description: >
4
+ Spatial canvas workbench for visual thinking — nodes, edges, groups on an infinite 2D canvas
5
+ with pan/zoom, minimap, and real-time sync. Use this skill whenever you need to lay out
6
+ information spatially: investigation boards, architecture diagrams, dependency maps, task plans,
7
+ status dashboards, file relationship views, or any scenario where a flat list or text wall
8
+ isn't enough. Also use when the user mentions "canvas", "board", "diagram", "spatial layout",
9
+ "visual map", "node graph", or wants to see how things connect. The canvas is your extended
10
+ working memory — pin nodes to curate context, read spatial arrangement to understand intent.
11
+ ---
12
+
13
+ # PMX Canvas — Full Reference
14
+
15
+ PMX Canvas is a spatial canvas workbench you control through MCP tools or HTTP API. It renders an
16
+ infinite 2D canvas in the browser with nodes, edges, groups, pan/zoom, and a minimap. State lives
17
+ on the server and survives browser refresh.
18
+
19
+ The canvas is your extended working memory. Humans pin nodes to curate context; you read that
20
+ curation through MCP resources. Spatial arrangement is communication — proximity means
21
+ relatedness, clusters imply grouping, reading order (top-left to bottom-right) implies sequence.
22
+
23
+ ## When to Use
24
+
25
+ The canvas is **agnostic about what you do with it**. Reach for it whenever a
26
+ flat conversation, a list, or a single document hides the relationships
27
+ between pieces of information. The total reach of any session is the union
28
+ of pmx-canvas's own node types and whatever your harness already has access
29
+ to — MCP servers, MCP apps, shell commands and CLIs, files in the working
30
+ directory, web fetch, anything else in your toolbelt. The canvas does not
31
+ care where the data came from; it cares about getting it on the surface as
32
+ the right node type.
33
+
34
+ The README has a non-exhaustive list of example use cases (idea generation,
35
+ validation, research, analysis, mind mapping, investigation boards,
36
+ architecture diagrams, status dashboards, comparison views, plus whatever
37
+ your toolbelt unlocks). This skill stays focused on the operational
38
+ mechanics. If a flat list or text wall is not enough to hold the
39
+ relationships you're working with, the canvas is the right tool — the rest
40
+ of this document is how to drive it.
41
+
42
+ ### When the connected tool is unfamiliar
43
+
44
+ When the human asks you to use a data source or tool you have not used
45
+ before — an MCP server, an MCP app, a CLI, a script, an arbitrary file
46
+ tree:
47
+
48
+ 1. List the tools / commands available; sample one or two outputs to see
49
+ the actual shape.
50
+ 2. Decide which canvas node type best matches each output:
51
+ - Long-form / narrative results → `markdown`
52
+ - Structured records, tables, dashboards → `json-render` or `graph`
53
+ - Rich reusable communication artifacts → `html-primitive`
54
+ - Interactive tool surfaces with their own UI → `mcp-app` (open with
55
+ `canvas_app { action: "open-mcp-app" }`)
56
+ - Local source files → `file` (live-watched)
57
+ - URLs that need cached fetches → `webpage`
58
+ - Stream of state events → `status` or `ledger`
59
+ 3. Propose the mapping to the human before bulk-creating nodes; let them
60
+ confirm or adjust before you commit a layout.
61
+
62
+ ## Quick Operating Path
63
+
64
+ The fast, current, safe sequence for a typical session. Each step links to the detailed
65
+ section below.
66
+
67
+ 1. **Open/focus the workbench** — make the canvas visible *before* mutating (see "Open the
68
+ canvas first"). Reuse one surface for the session.
69
+ 2. **Verify workspace identity** — read `GET /health` (or `pmx-canvas serve status`) and
70
+ confirm the returned `workspace` matches your intended workspace root. A healthy listener on port
71
+ 4313 can belong to another project — never mutate the wrong board (see "Verify workspace
72
+ identity BEFORE mutating").
73
+ 3. **Read before write** — `canvas_query { action: "search", query }` (or `pmx-canvas search`)
74
+ to find existing nodes; avoid duplicates. Use `canvas_query { action: "layout" }` only when
75
+ you need the full board.
76
+ 4. **Snapshot before destructive work** — `canvas_snapshot { action: "save", name }` before clear/major
77
+ reorg; restore if needed.
78
+ 5. **Signal then mutate (default behavior)** — signal with `canvas_intent { action: "signal", … }`
79
+ to telegraph the move before nearly every mutation, then create with the right composite: `canvas_node` (markdown/status/file/webpage/html
80
+ incl. primitives), `canvas_render` (json-render/graph), `canvas_app` (mcp-app/diagram/
81
+ web-artifact). Use composites — the legacy single-purpose tools were removed in v0.3.0.
82
+ 6. **Arrange + validate** — `canvas_view { action: "arrange" }` after batch adds, then
83
+ `canvas_query { action: "validate" }` to catch collisions / dangling edges before finishing.
84
+ 7. **Pin through a verified path** — `canvas_pin_nodes` (or the browser "Pin as context" /
85
+ double-click menu's "Pin as context"), then read `canvas://pinned-context` to confirm.
86
+ 8. **Clean up** — remove retry/test fixtures (`canvas_node { action: "remove" }` — works for
87
+ every type, including `status`), and restore the baseline snapshot.
88
+
89
+ ## Starting the Canvas
90
+
91
+ If this skill is installed before the `pmx-canvas` command exists, install the project first. See
92
+ `installing-pmx-canvas.md` for local development, npm/global install, and MCP config
93
+ options.
94
+
95
+ ## Adapter References
96
+
97
+ PMX Canvas core is host-agnostic. When a host-specific adapter is available, read the matching
98
+ reference before using adapter-native features:
99
+
100
+ - `github-copilot-app-adapter.md` — GitHub Copilot app project extension, native canvas
101
+ panel, AX context injection, and live-test checklist.
102
+ - `codex-app-adapter.md` — Codex app native Browser + MCP adapter, AX context reading,
103
+ focus labeling, and live-test checklist.
104
+
105
+ Open the canvas first — always:
106
+ The canvas is the shared human↔agent surface. **Before you create or mutate any nodes, make the
107
+ workbench visible.** Do **not** assume the host opened it for you — some hosts (e.g. the Codex app)
108
+ do not open it on their own. Take the action to open it yourself, whatever the host:
109
+ - **Native adapter/panel available** (the GitHub Copilot app `pmx-canvas` canvas extension, or the
110
+ Codex in-app Browser): open/focus that panel to the server's `/workbench` route.
111
+ - **Any other browser** (Chrome, Safari, Arc, Edge, …) **or a generic/CLI agent** with no native
112
+ panel: open the server's `/workbench` URL in a browser.
113
+
114
+ Then reuse that **single** surface for the rest of the session — do **not** open a second panel to
115
+ the same workbench (it wastes space and confuses which surface is authoritative). If you genuinely
116
+ cannot open any browser (headless/CI), say so and proceed, but still print the `/workbench` URL so a
117
+ human can open it.
118
+ - External URLs in `mcp-app` nodes show the "Unverified domain" interstitial by design. Only
119
+ same-origin `/api/canvas/frame-documents/<id>` URLs are auto-trusted. For external tools, use a
120
+ bundled `web-artifact`, same-origin frame document, or set `data.trustedDomain: true` only when the
121
+ user accepts the risk.
122
+
123
+ The canvas auto-starts on first MCP tool call when running in MCP mode (`pmx-canvas --mcp`).
124
+ For manual start:
125
+
126
+ ```bash
127
+ pmx-canvas # Start and open browser (port 4313)
128
+ pmx-canvas --no-open # Start without opening browser (for agents)
129
+ pmx-canvas --port=8080 # Custom port
130
+ pmx-canvas --demo # Start with sample content
131
+ pmx-canvas --theme=daylight # Daylight theme
132
+ pmx-canvas --version # Print installed version and exit
133
+ ```
134
+
135
+ `--theme` accepts `harbor` (default), `daylight`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the rail's theme picker lists the same nine). Same value can be set via
136
+ the `PMX_CANVAS_THEME` environment variable, or picked live from the left tool rail.
137
+
138
+ Start the canvas once per session, then reuse it. Use `--no-open` when running as an agent — the
139
+ human can open the browser URL themselves.
140
+
141
+ ### Daemon mode (long-running background server)
142
+
143
+ When you need the canvas to outlive the current shell or agent session — e.g. so a follow-up
144
+ agent run can attach to the same state — start it as a daemon:
145
+
146
+ ```bash
147
+ pmx-canvas serve --daemon --no-open --wait-ms=20000 # Detach, wait for /health
148
+ pmx-canvas serve status # Print daemon health + pid state
149
+ pmx-canvas serve stop # Stop the daemon for this port
150
+ ```
151
+
152
+ `serve --daemon` writes a pid file (`.pmx-canvas/daemon-<port>.pid`) and a log file
153
+ (`.pmx-canvas/daemon-<port>.log`); the wait flag blocks until `/health` returns OK so a script
154
+ can rely on the server being responsive when the command returns. The pid file doubles as the
155
+ spawn lock and records the pid immediately, so a starting-but-not-yet-healthy daemon is already
156
+ addressable by `serve stop`, and a failed startup kills the child — no orphans. If the requested
157
+ port already serves a *different* workspace (or a non-canvas app), `serve --daemon` refuses with
158
+ the owner named and a hint to pick another port; it never reports a foreign daemon as "already
159
+ running". `serve stop` reads the pid file, verifies the pid still looks like a canvas daemon
160
+ (recycled pids read as stale), sends SIGTERM, and cleans up on exit.
161
+
162
+ ### Verify workspace identity BEFORE mutating (required)
163
+
164
+ > **"Start once, reuse always" has one hard exception for direct CLI/HTTP work: never mutate a
165
+ > listener that belongs to another workspace.** A healthy, responsive server on the default port
166
+ > `4313` can be serving a *different project's* canvas (a leftover daemon, or another repo's
167
+ > session). Mutating it directly would corrupt the wrong board. Always preflight:
168
+
169
+ 1. **Read `GET /health`** (or `pmx-canvas serve status`). Both return a top-level
170
+ `workspace` field. **`workspace` MUST equal your intended workspace root**
171
+ before any create/update/remove. If it doesn't match, do not mutate.
172
+ 2. **Treat `responsive: true` + `pidRunning: false` as a stale-listener warning** — the pid file
173
+ is stale and the live listener may be a different process/workspace. Confirm the real owner with
174
+ `lsof -nP -iTCP:<port> -sTCP:LISTEN`.
175
+ 3. **On mismatch, isolate**: start the intended workspace on an explicit free port —
176
+ `pmx-canvas serve --daemon --no-open --port=<free-port>` — and target that port. (`PMX_CANVAS_PORT`
177
+ alone may still attach to an existing `4313` listener; prefer an explicit `--port`.) Then
178
+ **re-read `/health`** to confirm the workspace now matches. `serve --daemon` also enforces
179
+ this itself: pointed at a port owned by another workspace, it exits with an error naming the
180
+ owner instead of attaching to it.
181
+ 4. **MCP transport exception:** `pmx-canvas --mcp` launched from an incidental host dir may attach to
182
+ the healthy daemon already on the preferred port when no explicit workspace root is set, so writes
183
+ land in the visible workbench instead of a hidden fallback workspace. Host adapters should set
184
+ `PMX_CANVAS_WORKSPACE_ROOT=<abs project root>` for deterministic targeting; set
185
+ `PMX_CANVAS_ALLOW_WORKSPACE_SPLIT=1` or a distinct `PMX_CANVAS_PORT` only when a separate canvas is
186
+ intentional.
187
+ 5. **After any version upgrade**, run a behavior canary (e.g. a batch `node.add` with no `type`
188
+ must return `400`) to confirm the listener is the version you expect, not a stale old daemon.
189
+
190
+ ## Browser Workflows
191
+
192
+ The browser is not just a passive view. Human interactions on the canvas persist back to the
193
+ server and become part of the authoritative canvas state.
194
+
195
+ - Double-left-click empty canvas — open the node-creation menu at that position
196
+ - Right-button hold and drag — pan the board, including over node content
197
+ - Shift+drag on empty canvas — lasso-select multiple nodes
198
+ - Selection bar actions — when nodes are selected, the browser exposes `Pin as context`,
199
+ `Group`, `Connect`, and `Clear`
200
+ - Double-left-click a node — open the node context menu. **`Pin as context`** adds the node to the
201
+ human-curated agent context set (updates the context count + the node's context-pin indicator);
202
+ `Lock position` is the separate arrange-lock. Plus focus, collapse, connect, refresh/open, close,
203
+ and type-specific actions. (Every node type, **including `status`, has a remove `×`** in its title
204
+ bar and a `Close` menu item.)
205
+ - Double-left-click a group node — recolor the group using preset swatches or a custom color picker,
206
+ or ungroup it (dissolves the frame; children stay)
207
+ - Drag-and-drop files or URLs — add file, image, markdown, or webpage nodes directly. PDF/Office
208
+ drops retain an original attachment up to 20 MiB and wait for explicit human consent before
209
+ agent-assisted import; they do not launch an agent or bundled converter.
210
+ - Paste URLs — create webpage nodes from the clipboard
211
+
212
+ Use browser interactions when the human is actively curating spatial layout. Use MCP or the CLI
213
+ when you need deterministic scripted changes or you are acting without a visible browser.
214
+
215
+ ### Known limitations & host differences
216
+
217
+ - **Hosted MCP-app / Excalidraw nodes are not "open as site" targets.** A hosted ext-app is a
218
+ *live* MCP-app shell that only renders with the in-canvas host bridge — there is no standalone
219
+ page for it (the "Open as site" control is hidden for these nodes). View/edit them in the canvas,
220
+ or open them externally through their own app. `html`, `json-render`, `graph`, and bundled
221
+ `web-artifact` nodes DO open as a standalone site (and graph/json-render fill the full browser
222
+ viewport).
223
+ - **Sandboxed-iframe automation (host-dependent).** Some embedded hosts (notably the Codex in-app
224
+ Browser) cannot script *inside* PMX's sandboxed workbench iframes (`Frame target is not
225
+ available`). The surface renders and the server-side interaction path works; this is a host
226
+ automation limitation, not a PMX rendering bug. Verify embedded-iframe behavior in system Chrome
227
+ or via the server-side AX state, not by automating the iframe in such hosts.
228
+ - **`pmx-canvas screenshot` needs an active WebView** and defaults to the workbench camera; start
229
+ `canvas_webview { action:"start" }` first, and use `canvas_webview { action:"evaluate" }` (not
230
+ `eval`) to drive it.
231
+
232
+ ## Agent CLI
233
+
234
+ PMX Canvas also ships an agent-native CLI that talks to the running HTTP server and returns JSON.
235
+ Use it when MCP is not available but you still want structured, scriptable canvas operations.
236
+
237
+ ```bash
238
+ pmx-canvas --help # Top-level help
239
+ pmx-canvas serve --daemon --no-open # Detached daemon with health output
240
+ pmx-canvas serve status # Daemon health + pid status
241
+ pmx-canvas serve stop # Stop the daemon for this port/pid file
242
+ pmx-canvas layout # Full canvas state
243
+ pmx-canvas status # Quick summary
244
+ pmx-canvas node add --type markdown --title "Plan"
245
+ pmx-canvas node add --type webpage --url https://example.com/docs
246
+ pmx-canvas node add --type graph --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
247
+ pmx-canvas node add --type graph --graphType bar --data '[{"x":"a","y":1}]' --xKey x --yKey y
248
+ pmx-canvas graph add --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
249
+ pmx-canvas external-app add --kind excalidraw --title "Diagram"
250
+ pmx-canvas node add --help --type webpage --json
251
+ pmx-canvas node schema --type json-render --component Table --summary
252
+ pmx-canvas node list --type file --ids
253
+ pmx-canvas edge add --from node-a --to node-b --type depends-on
254
+ pmx-canvas search "auth"
255
+ pmx-canvas open
256
+ pmx-canvas arrange --layout flow
257
+ pmx-canvas focus <node-id> --no-pan # Select/raise without moving the user's viewport
258
+ pmx-canvas fit --width 1440 --height 900 # Fit the whole board for screenshots/review
259
+ pmx-canvas screenshot --output ./canvas.png # Shorthand for `webview screenshot`
260
+ pmx-canvas json-render --schema --summary # Inspect json-render component catalog
261
+ pmx-canvas json-render --example --component Table
262
+ pmx-canvas node add --type markdown --title "Long doc" --strict-size # Scroll instead of auto-fit
263
+ pmx-canvas node add --type graph --graphType pie --data-file metrics.json --show-legend false --show-labels false
264
+ pmx-canvas node update <node-id> --spec-file ./dashboard.json
265
+ pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary
266
+ pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx --deps recharts --include-logs
267
+ pmx-canvas node list --type web-artifact --summary
268
+ pmx-canvas node list --type external-app --summary
269
+ pmx-canvas pin --list
270
+ pmx-canvas ax context
271
+ pmx-canvas ax focus <node-id>
272
+ pmx-canvas ax work add --title "Wire up auth" --status in-progress <node-id>
273
+ pmx-canvas ax approval request --title "Deploy to prod"
274
+ pmx-canvas ax steer "focus on the failing test first"
275
+ pmx-canvas ax timeline --limit 50
276
+ pmx-canvas snapshot save --name "before-refactor"
277
+ pmx-canvas code-graph
278
+ pmx-canvas spatial
279
+ ```
280
+
281
+ ### CLI command groups
282
+
283
+ - `node add|list|get|update|remove` — manage nodes
284
+ - `node schema` — inspect running-server create schemas and canonical examples, with `--summary`, `--field`, and `--component` filters
285
+ - `graph add` — convenience alias for graph nodes; `node add --type graph` remains the canonical form
286
+ - Graph CLI fields accept both kebab-case flags and camelCase schema names, e.g. `--graph-type`/`--graphType`, `--x-key`/`--xKey`, and `--bar-color`/`--barColor`.
287
+ - Graph CLI height flags are split: use `--node-height`/`--nodeHeight` for the
288
+ canvas frame and `--chart-height` for the chart content. CLI `--height`
289
+ remains a frame-height compatibility alias.
290
+ - `edge add|list|remove` — manage edges
291
+ - Search-based edge selectors must be specific enough to resolve exactly one node. Queries like
292
+ `"DVT O3"` can be ambiguous; prefer the full visible title such as `"DVT O3 — GitOps"`.
293
+ - `search`, `layout`, `status`, `arrange`, `focus` — inspect and navigate the canvas. Prefer
294
+ `focus --no-pan` when you only need to select/raise a node without hijacking the human's camera.
295
+ - `ax status|context|focus` — inspect the host-agnostic AX layer; `ax context`
296
+ combines pinned context and AX focus for adapter prompt injection.
297
+ - `ax event add`, `ax steer`, `ax evidence add`, `ax timeline` — the AX timeline
298
+ (agent-events, steering messages, evidence). Persisted for diagnostics,
299
+ retention-bounded, and excluded from snapshots.
300
+ - `ax work add|update|list`, `ax approval request|resolve|list`,
301
+ `ax review add|list` — canvas-bound AX state (work items, approval gates,
302
+ review annotations) that rides snapshots and restore and is cleared by `clear`.
303
+ - `ax host report|status` — report/read the host/session capability (own partition).
304
+ - `ax command list|invoke`, `ax policy get|set` — list/invoke registry commands
305
+ (`pmx.plan`, `pmx.execute`, `pmx.promote-context`, `pmx.summarize`, `pmx.review`)
306
+ and read/patch the canvas-bound tool/prompt policy.
307
+ - `copilot install-extension [--dry-run] [--yes]` — install the bundled GitHub
308
+ Copilot adapter into a repo; the core stays host-agnostic.
309
+ - `fit [id ...]` — set the server viewport to fit the whole canvas or selected nodes before screenshots or whole-board review
310
+ - `screenshot --output <path>` — top-level shortcut for `webview screenshot`; supports `--format png|jpeg|webp` and `--quality`
311
+ - `json-render --schema|--examples` — inspect the json-render component catalog with `--component`/`--field` filters; same data as `node schema --type json-render` in a more direct shape
312
+ - `--strict-size` (alias `--scroll-overflow`) on `node add`/`node update` — keep explicit width/height fixed and scroll overflowing content instead of letting the renderer auto-fit. Useful for long markdown, dense webpages, and dashboards that should fit a tile-sized frame.
313
+ - `--show-legend false` / `--show-labels false` on `node add --type graph` and `graph add` — hide chart legends and pie slice labels for compact graph nodes in tile-style boards.
314
+ - `open` — open the current workbench in the browser
315
+ - `pin --list|--clear|<ids...>` — manage context pins
316
+ - `undo`, `redo`, `history` — time travel
317
+ - `snapshot save|list|restore|delete` — manage snapshots
318
+ - `group create|add|remove` — manage groups
319
+ - `clear --yes` — destructive clear with explicit confirmation
320
+ - `validate spec` — validate json-render specs and graph payloads without creating nodes
321
+ - `node update` — returns a compact `{ ok, id, nodeId, position, size, updatedFields }` envelope by default; pass `--full` for the complete node payload. Full ext-app/mcp-app payloads embed the bundled app HTML and tool results (hundreds of KB — a no-op ext-app resize once returned ~940 KB), so in agent turns keep the default envelope and read content via `node get --summary`
322
+ - `web-artifact build` — build bundled React/Tailwind HTML artifacts; use `--deps` for extra packages and `--include-logs` only when raw logs are useful. The build pipeline runs bash scripts (POSIX-only) — it is not supported on native Windows; already-built artifacts render everywhere
323
+ - `external-app add --kind excalidraw` — create the hosted Excalidraw preset; response includes `id` and `nodeId` aliases for the same canvas node
324
+ - `serve status|stop` — inspect and stop daemonized servers started with `serve --daemon`
325
+ - `code-graph`, `spatial` — analysis commands
326
+
327
+ Current caveat:
328
+ - `mcp-app` grouping is not fully uniform yet. Web artifact app nodes have grouped reliably, but
329
+ Excalidraw app nodes have shown inconsistent `group add` behavior and weaker rediscoverability
330
+ through search later in the session. When you plan to curate an app-heavy comparison area,
331
+ capture node IDs immediately after creation and verify membership with `node get --summary`,
332
+ `layout --summary`, or the browser selection state instead of relying on search alone.
333
+ - App-like nodes persist as `type: "mcp-app"` internally but serialized results include `kind`:
334
+ `web-artifact`, `external-app`, or `mcp-app`. Prefer `node list --type web-artifact` or
335
+ `node list --type external-app` when you need the operational subtype.
336
+ - Generic `pmx-canvas node add --type mcp-app` is intentionally not supported because app nodes
337
+ need app/session metadata. Use `pmx-canvas web-artifact build` for bundled React artifacts or
338
+ `pmx-canvas external-app add --kind excalidraw` for the Excalidraw preset.
339
+ - For local `image` nodes on macOS, iCloud/OneDrive cloud-only placeholder files are rejected with
340
+ a download-first hint. Download the image locally before adding it to the canvas.
341
+
342
+ The CLI targets `http://localhost:4313` by default. Override with `PMX_CANVAS_URL` or
343
+ `PMX_CANVAS_PORT` when the canvas is running elsewhere.
344
+
345
+ ## Core Concepts
346
+
347
+ ### Node Types
348
+
349
+ | Type | Purpose | When to use |
350
+ |------|---------|-------------|
351
+ | `markdown` | Rich formatted content | Explanations, documentation, notes, findings |
352
+ | `status` | Compact status or deliberately authored work card | Work plans with `data.workState`; phase/tool status without it |
353
+ | `file` | Live file viewer (auto-watches) | Show source code with live updates on file change |
354
+ | `image` | Image display | Screenshots, diagrams, charts |
355
+ | `context` | Context card | Key context the human should see |
356
+ | `ledger` | Log/ledger viewer | Structured log data, audit trails |
357
+ | `trace` | Trace/timeline viewer | Execution traces, timelines |
358
+ | `mcp-app` | Hosted app/embed frame | Tool-backed MCP apps or external app content; not generic CLI-created notes |
359
+ | `json-render` | Native structured UI panel | Dashboards, forms, tables, interactive layouts from json-render specs |
360
+ | `graph` | Native chart panel | Line, bar, pie, area, scatter, radar, stacked-bar, composed, plus Tufte primitives (sparkline, dot-plot, bullet, slopegraph) rendered inside the canvas |
361
+ | `html` | Sandboxed HTML+JS document | Self-contained HTML with optional inline `<script>` and CDN imports rendered in a sandbox-restricted iframe; canvas theme tokens are auto-injected |
362
+ | `group` | Spatial container/frame | Visually group related nodes together |
363
+ | `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations. **Internal type — surfaces in `canvas://layout` for thread rendering but is not created via the public `canvas_node { action: "add" }` API. Don't try to add one directly.** |
364
+ | `response` | Prompt reply / streamed answer | Agent responses linked to prompt threads. **Same internal-only restriction as `prompt`.** |
365
+
366
+ ### Edge Types
367
+
368
+ | Type | Purpose | Example label |
369
+ |------|---------|---------------|
370
+ | `flow` | Sequential/directional | "then", "calls", "triggers" |
371
+ | `depends-on` | Dependency | "requires", "blocks" |
372
+ | `relation` | General relationship | "related to", "similar to" |
373
+ | `references` | Cross-reference | "see also", "documented in" |
374
+
375
+ Edges support `style` (solid/dashed/dotted) and `animated` flag. Always use descriptive labels.
376
+
377
+ **Edge direction convention:** `from` is the source/dependent, `to` is the target/dependency.
378
+ So `from: A, to: B, type: "depends-on"` means "A depends on B." For `flow` edges, the arrow
379
+ points from `from` to `to`, indicating sequence or data flow direction.
380
+
381
+ **Style conventions:** Use `solid` for active/satisfied relationships, `dashed` for blocked or
382
+ pending dependencies, and `dotted` for weak/optional relationships. Use `animated: true` to
383
+ draw visual attention to critical paths.
384
+
385
+ ### Layout: spacing and groups
386
+
387
+ Use the smallest clear gap that serves the content and its relationships. Both crowded labels
388
+ and oversized empty channels make a board harder to use.
389
+
390
+ - **Unconnected notes and walkthroughs:** start compact, often a 40–80px clear gap. For a
391
+ 280×180 node with a 40px gap, origins are 320px apart horizontally and 220px vertically.
392
+ This is an unconnected-card example, not a minimum for connected cards. Do not reserve
393
+ half a card's width or a large uniform channel where there is no edge.
394
+ - **Connected cards:** provide enough room for the rendered edge line, arrowhead, label and
395
+ authorship marks. Short flow labels may need less room than long explanations. After content settles,
396
+ inspect the rendered relations at the intended viewing zoom, including after auto-arrange.
397
+ `validate` checks node non-overlap, not label/arrowhead clearance or relation readability;
398
+ widen or reroute crowded relations rather than relying on a fixed pixel gap.
399
+ - **After rendering:** use settled card sizes, not just the requested creation heights. Close
400
+ gaps left by automatic shrinking, check the numbered reading order, and frame a readable
401
+ cluster or tour stop. A whole-board fit is orientation, not proof that body text is readable.
402
+ - **Inside a group, apply the same distinction** between compact notes and clear relation
403
+ channels. Groups no longer auto-pack children, so the spacing you set is what the human sees.
404
+ - **Size a group frame larger than its children's bounding box** so the group header — including the
405
+ node-count badge — stays visible and isn't hidden under the top-left child. For an explicit
406
+ (manual) group frame, add margin on every side (≈56px) plus room at the top for the header rather
407
+ than hugging the children. Auto-fit groups (created without an explicit width/height) already
408
+ reserve this margin.
409
+
410
+ ### Colors (Semantic)
411
+
412
+ A `color` parameter is honored as a **renderer** color only for **group** nodes (frame accent) and
413
+ **graph** nodes (series/accent color). It is **not** a renderer parameter for `markdown` / `status` /
414
+ `context` nodes: a top-level `color` on those is dropped on both HTTP and the CLI, and while an
415
+ arbitrary `data.color` you POST under `data` persists like any other `data.*` metadata, it is **not**
416
+ read as a render color for basic node types (report Finding H — renderer-semantic, not raw-storage,
417
+ contract). Their meaning comes from the node **type** and value instead: a `status` node colors its indicator from its
418
+ **phase** (`idle`/`running`/`planning`/`thinking`/`drafting`/`tooling`/`review`/`waiting-approval`/
419
+ `waiting` — e.g. `review` → green, `running` → accent; an unrecognized phase renders gray), and a
420
+ `trace` node from its `status` field (`success` → green, `failed` → red, `running` → accent). To
421
+ color an arbitrary region, group the nodes and set the group's `color`.
422
+ Authored status cards with `data.workState` instead use the work-state glyph and label:
423
+ blocked uses the warning token, done the success token, and cancelled the muted token.
424
+ Do not set arbitrary colors to encode work state.
425
+
426
+ When you do set a color (group/graph), use this palette consistently to convey meaning:
427
+ - **Green** (`#22c55e`) — success, done, healthy
428
+ - **Yellow** (`#eab308`) — in progress, warning, attention needed
429
+ - **Red** (`#ef4444`) — error, blocked, failing
430
+ - **Blue** (`#3b82f6`) — informational, neutral highlight
431
+ - **Gray** (`#6b7280`) — queued, pending, inactive, not yet started
432
+ - **Purple** (`#a855f7`) — special, notable, review needed
433
+
434
+ ## MCP Tools Reference
435
+
436
+ PMX Canvas exposes **18 action-discriminated composites** (the whole recommended surface) plus
437
+ **7 first-class standalones** — 25 tools total. The composites fold the older
438
+ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
439
+ **field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
440
+
441
+ `canvas_help {}` lists MCP registry operation names. `canvas_help { op: "node.add" }`
442
+ returns that operation's public tool, description and JSON argument schema, including
443
+ routing selectors and required fields. It does not start or read a canvas. Handlers still
444
+ enforce conditional requirements. Non-registry tools use `tools/list` instead.
445
+
446
+ > **Legacy single-purpose tools were removed in v0.3.0.** The old names (`canvas_add_node`,
447
+ > `canvas_update_node`, `canvas_request_approval`, `canvas_add_work_item`, …) are no longer
448
+ > registered — use the composites below and fresh MCP `tools/list` for the installed runtime's
449
+ > authoritative action schemas. This skill does not re-enumerate the removed names.
450
+
451
+ ### The 18 composites
452
+
453
+ | Composite | `action` values | What it does |
454
+ |-----------|-----------------|--------------|
455
+ | `canvas_import` | `list` · `read` · `submit` · `unavailable` | Process human-requested imports: inspect metadata/controlled bytes, submit reviewable Markdown with references/warnings/agent description, or report unavailable. Request/cancel/commit are human-only |
456
+ | `canvas_node` | `add` · `get` · `update` · `remove` · `lifecycle-get` · `lifecycle-set` · `review` · `lifecycle-restore` | Node CRUD, lifecycle qualification, evidence-backed review and reversible retirement; retired cards leave active context |
457
+ | `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` · `workboard` | Schema introspection, spec dry-run validation, native json-render / graph node creation, and a live AX work-item board |
458
+ | `canvas_edge` | `add` · `update` · `remove` | Connect / edit / disconnect nodes |
459
+ | `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
460
+ | `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
461
+ | `canvas_board` | `list` · `get` · `create` · `create-from` · `move-selection` · `open` · `update` · `pin` · `unpin` · `knowledge-list` · `category-set` · `category-remove` · `folder-set` · `relation-preset-set` · `relation-preset-remove` | Boards and workspace categories/folder properties/shared relation templates; knowledge writes never open a board. Template edits/deletion never rewrite boards or connections. `category-remove` takes `id` and refuses explicit folder assignments, including empty folders; reassign or clear them first. |
462
+ | `canvas_snapshot` | `save` · `list` · `restore` · `restore-node` · `delete` · `gc` · `diff` | Named snapshots: save/list/restore/delete, garbage-collect old ones, diff current canvas vs a snapshot (`diff` takes `snapshot`, not `id`); `restore-node` puts one card (`nodeId`) back to its content in snapshot `id` |
463
+ | `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `add-annotation` · `remove-annotation` · `get-tour` · `set-tour` · `tour-go` · `tour-exit` · `move` | Auto-arrange, pan-to-node, fit viewport, clear the board, draw/write/erase annotations, read/persist ordered presentation stops (`tour: null` derives group order), present a stop / exit, ease the camera to a node, rect or viewport (never an undo step) |
464
+ | `canvas_query` | `context` · `graph` · `search` · `layout` · `validate` | Read the budgeted brief or summary-only connected memory graph, find nodes, read full layout, or **`validate`** collisions / containment / dangling edges |
465
+ | `canvas_app` | `open-mcp-app` · `diagram` · `build-artifact` | Hosted MCP apps, the Excalidraw diagram preset, and bundled web artifacts (folds `canvas_open_mcp_app` / `canvas_add_diagram` / `canvas_build_web_artifact`) |
466
+ | `canvas_webview` | `status` · `start` · `stop` · `resize` · `evaluate` | Headless Bun.WebView automation for the workbench (folds the `canvas_webview_*` / `canvas_resize` / `canvas_evaluate` tools) |
467
+ | `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` · `presence` · `set-presence` | Read AX state; set AX focus; patch tool/prompt policy (the scope fence is human-set, read-only here); report host capability; read / set agent presence (phase, cursor, focus, attached session) |
468
+ | `canvas_ax_work` | `add` · `update` · `annotate` | Canvas-bound work items + review annotations |
469
+ | `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | Attributed gate lifecycle (request → await → resolve); attribution does not itself grant authority |
470
+ | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` · `reads` · `read-status` | The bounded AX diagnostics timeline, and the context read log (`reads`) |
471
+ | `canvas_ax_delivery` | `claim` · `mark` | Adapterless steering delivery (claim → act → mark) |
472
+ | `canvas_intent` | `signal` · `update` · `clear` | Ghost Cursor of Intent — announce a move before making it |
473
+
474
+ Call shape examples: `canvas_node { action: "add", type, title }`,
475
+ `canvas_view { action: "focus", id }`, `canvas_group { action: "create", childIds }`,
476
+ `canvas_render { action: "add-graph", graphType, data }`,
477
+ `canvas_query { action: "search", query }`,
478
+ `canvas_ax_work { action: "update", id, status }`.
479
+
480
+ Start board work with `canvas_query { action: "context", consumer:
481
+ "<stable-unique-agent-id>", budget: 16000 }`. The budget is UTF-16 code units,
482
+ not tokens. A durable cursor exists only with an explicit consumer and is scoped
483
+ to the active board; no consumer means a fresh read, while explicit `since`
484
+ does not advance a cursor. Pins come first. Follow a clipped entry's source
485
+ board/card IDs with `canvas_board get` and `canvas_node get`.
486
+
487
+ README designation is part of normal agent board authoring: read `canvas_board
488
+ { action: "get" }`, and when `readmeNodeId` is null, choose or create a concise
489
+ markdown introduction and call `canvas_board { action: "update", id:
490
+ "<board-id>", readmeNodeId: "<markdown-node-id>" }`. Explain the board's purpose,
491
+ key context, and where to start. Do not pick an arbitrary first card. Preserve
492
+ an existing README unless the human asks to change it, respect requests for no
493
+ README, and do not mutate boards during read-only tasks. The browser's README
494
+ control is a manual override, not a required setup step.
495
+
496
+ For library work, `canvas_query { action: "search", query, scope: "library" }`
497
+ reads inactive boards without opening them. Board links are native text-only
498
+ `board` nodes with `data.boardId`. Preview `canvas_board create-from` before
499
+ creation: `prompt`, `response`, `trace`, and `mcp-app` are excluded; README and
500
+ group structure are optional; fresh IDs are allocated; pins, AX state, and
501
+ history are not copied; the source remains unchanged and the new board inactive.
502
+ The new board records `previousBoardId` pointing to its source.
503
+
504
+ For an identity-preserving move, use `canvas_board` action `move-selection`, not
505
+ copy/delete. Read the active source board's `contentRevision` with `get`. Preview
506
+ with `sourceBoardId`, `nodeIds`, `name`, `expectedRevision: contentRevision`,
507
+ `requestId` and `preview: true`; inspect blockers and notices before committing
508
+ with the same inputs without `preview`. Retry an uncertain response with the same
509
+ request ID and inputs. Group descendants move together; unsafe bindings refuse
510
+ the entire move. Cards retain identities, pins, relations and review history,
511
+ without new validation or read evidence. The source stays open with a portal to
512
+ the new board. Pre-move Undo/Redo is cleared on affected boards; older snapshots
513
+ remain inspectable but cannot be restored across the move. Open the returned
514
+ `destinationBoardId` only when navigation is wanted.
515
+
516
+ ### Connected memory (wave 2)
517
+
518
+ Home Folders, Map and Graph, wiki links/backlinks, and brief discovery share one
519
+ board/card projection. Folders are neutral and independent of links. Map uses
520
+ deterministic folder regions and saved draggable SQLite `mapPosition`; board
521
+ update accepts `{ x, y }` or `null` to reset. Graph uses neutral circular clusters,
522
+ not force simulation; labels are in selection details, filters in the shared
523
+ toolbar, with no synthetic Home board, levels, or simulation sliders.
524
+
525
+ Read it with `canvas_query { action: "graph", focus: "pins", depth: 2 }`,
526
+ `canvas://graph{?focus,depth}`, `GET /api/canvas/memory-graph`, SDK
527
+ `getMemoryGraph({ focus, depth })`, or CLI `pmx-canvas memory-graph`. Focus is
528
+ a board ID, a JSON qualified key `["board-id","card-id"]` (or
529
+ `["board-id",null]`), or `pins`; omitted means the library. URL-encode resource
530
+ focus. Depth is an integer 0–4, default 2, traversing undirected links including
531
+ membership. Explicit 0 is valid; empty depth and bare CLI `--depth` are rejected.
532
+ HTTP, SDK and the MCP tool accept `projection: "full" | "boards"` (default `full`);
533
+ CLI uses `--projection full|boards`. The graph resource uses the default full projection.
534
+ Home Map uses compact `boards` summaries, loading depth-2 details on selection.
535
+ Metadata edits and dragging do not refetch relationships; **Refresh** reloads them.
536
+ Cross-board briefs give incoming and outgoing connections readable board/card titles.
537
+ The result has `nodes`, `edges`, `unresolvedLinks: [{ source, ref }]`,
538
+ `focus`, and `depth`. Nodes include qualified `key`, `boardId`, optional `cardId`,
539
+ `kind`, `title`, `summary`, `folder`, `inContext`, and `summaryOnly: true`;
540
+ summaries are capped at 280 characters. Edge projection kinds are `member`,
541
+ `portal`, `wiki`, `card`, and `previous`, with qualified `from`/`to`,
542
+ `source`/`target`, and explicit `missing`. Reads count links seen only when a node's
543
+ incident relations are complete; focused nodes with `linksComplete: false` do
544
+ not acknowledge omitted links. Brief entries with capped or omitted relations also
545
+ carry `linksComplete: false`; near-pin, human, linked README and pinned-board
546
+ summaries do not acknowledge unseen relations. Graph cards and complete brief
547
+ relations carry their captured `linksRevision`, so later writes remain unseen;
548
+ brief delivery is qualified by source board, never by a bare cross-board card ID.
549
+ AX excerpts and annotation anchors, including `canvas://ax-context` and
550
+ `pmx-current-context`, never mark incident relations seen; read a complete graph
551
+ or brief relation entry instead.
552
+ Single-card and layout reads record content delivery, not incident relations.
553
+ Reads never switch boards or record full content reads. Pull cards in full
554
+ when needed. Counts are 25 tools, 15 static canvas resources; there are now two
555
+ canvas templates, context and graph, besides the skill file template.
556
+
557
+ Markdown save resolves `[[Board]]`, `[[Board#Card]]`, and `|alias` forms into
558
+ server-owned `data.wikiLinks` ID bindings. IDs win; name/title matches must be
559
+ unique. Existing bindings survive renames. Ambiguous/missing links stay explicit
560
+ and never guess or silently retarget. Rendering follows saved bindings: unresolved
561
+ links stay unresolved until an explicit save, and Undo/restore preserves bindings.
562
+ Legacy Markdown without binding metadata gets a one-time persisted backfill;
563
+ saved empty or unresolved records are not reinterpreted on reads.
564
+ Code spans/fences are ignored. A human
565
+ Create dialog for an unresolved board-only link creates exactly the authored
566
+ name and re-saves its source using the captured board/revision, refusing stale
567
+ source writes.
568
+
569
+ Once the board list has loaded, all workbench context-pin writes send the captured
570
+ `expectedBoardId`; a server-side board switch returns 409 instead of changing
571
+ another board's pins. Receipt pin actions require reopening the owning board.
572
+
573
+ Embedded previews mount automatically near the viewport at every zoom and remain
574
+ mounted to preserve arbitrary app state. There is no lifetime preview cap. Mermaid renders
575
+ inline, without an iframe. JSON/graph viewers inherit all nine named canvas themes
576
+ and keep form drafts when the theme changes. During replacement, the pending
577
+ document temporarily coexists with its painted predecessor.
578
+
579
+ A network load or missing JSON runtime times out after 10 seconds and offers a
580
+ visible **Retry**, retaining the last painted document during replacement.
581
+ An already mounted offscreen runtime may wait for paint without being timed out.
582
+
583
+ Native edge add accepts optional `toBoardId`: `from` is on the open source board,
584
+ `to` is a card on the target board. CLI: `pmx-canvas edge add --from <source> --to <target>
585
+ --to-board <board-id> --type references`. The human More menu offers a card
586
+ picker and the source footer shows remote connections. Auto-arrange stays local;
587
+ static export omits remote lines but shows explicit external connection evidence.
588
+ Edges accept open `kind`/`reason`; update null clears them. Boards accept
589
+ `relationVocabulary: [{kind, ink, directed}]`, with six fixed relation inks, never pin/agent/warning
590
+ colours. `derived-from`, `contradicts`, `supersedes` point source → dependent; `depends-on` points
591
+ dependent → prerequisite. Ordinary references/wiki links/portals do not propagate review due.
592
+ Lifecycle reads return qualification, including source observations. Validating dependent cards
593
+ requires `expectedSources` from the evidence checked, plus `expectedContentRevision`; stale or
594
+ unavailable sources refuse. Retired cards leave briefs/pinned context but remain marked in search
595
+ and direct reads. Brief `lifecycle.complete: false` requires rechecking cached cards before reuse;
596
+ empty incomplete lists are not a health claim. Recipe metadata is inert and refresh never validates.
597
+ For human review, Home's Review needed count filters to affected boards, and each Review button
598
+ opens an affected card. More → Review offers one short note and Checked, still correct; Not now
599
+ writes nothing. Settings and history discloses clocks, policies and advanced outcomes. The folder
600
+ tree is navigation, and No review policy is informational rather than an outstanding task.
601
+
602
+ Use `canvas_board { action: "open", id: "<board-id>" }` to open a created or
603
+ existing board before authoring. Verify the returned `activeBoardId`; opening
604
+ switches the shared visible workbench and all subsequent write targets, not
605
+ just this agent's context. `id: null` opens Home. Deletion stays human-only.
606
+
607
+ `canvas_ax_gate` takes **two** discriminators, `{ kind, action }` — e.g.
608
+ `{ kind: "approval", action: "request", title }`,
609
+ `{ kind: "elicitation", action: "resolve", id, response }`,
610
+ `{ kind: "mode", action: "await", id, timeoutMs }`. (The approval machine-readable action
611
+ identifier is passed as `approvalAction`, since `action` is the lifecycle discriminator.)
612
+
613
+ #### Narrate your next move with `canvas_intent` (Ghost Cursor of Intent)
614
+
615
+ **Use the Ghost Cursor as much as possible** — it is the primary way to make your intent and your
616
+ work visible on a shared board. Default to signaling before you act; skip it only for trivial
617
+ in-place tweaks or high-frequency batch churn.
618
+
619
+ Before you create/move/connect/edit/remove on the canvas, **signal the move** so a
620
+ faint placeholder forms where you're about to act — the human sees the next move
621
+ coming and can veto it mid-thought. Intents are ephemeral presence: never
622
+ persisted, auto-expiring (~8s), never in `canvas_query layout`.
623
+
624
+ Narrate → linked mutation → automatic settle:
625
+ 1. `canvas_intent { action: "signal", kind: "create", nodeType: "markdown", label: "Add evidence", reason: "capturing the failing test", confidence: 0.8 }` → returns `intent.id` and the `position` the canvas chose.
626
+ 2. Make the real move and pass that id: `canvas_node { action: "add", intentId: <intent.id>, ... }`.
627
+ 3. A human-vetoed intent rejects its linked mutation. A quietly expired ghost does not:
628
+ the write proceeds unlinked, subject to the ordinary permissions and revision checks.
629
+ A successful write linked to a still-live intent settles the ghost automatically.
630
+ Expiry is neither approval nor a human veto. Never retry a vetoed plan under a new ID
631
+ just to bypass the human's decision.
632
+
633
+ Use `canvas_intent { action: "clear", id }` only when abandoning a plan without
634
+ performing the linked mutation.
635
+
636
+ **Linked settle is scoped to node, edge, and group mutations** (`canvas_node`,
637
+ `canvas_edge`, `canvas_group` and their ops). `canvas_app` opens (diagram /
638
+ mcp-app) and `canvas_webview` do **not** accept an `intentId` and reject it with a
639
+ 400 — to telegraph one of those, signal a ghost, then `clear` it (or let it
640
+ expire) and run the open *without* an `intentId`.
641
+
642
+ Per kind, pass the anchor it renders against: `position` for `move` (optional for
643
+ `create`), `nodeId` for `move`/`edit`/`remove`, `edge: { from, to, type }` for
644
+ `connect`. **Omit `position` on a create unless you know where the node goes:** the
645
+ canvas then forms the ghost where it will auto-place the node, keeps staged ghosts
646
+ from stacking, and lands the linked create on its ghost. Never pass a placeholder
647
+ such as `{ x: 0, y: 0 }`. The payoff is **legibility** — `reason` is shown beneath
648
+ the ghost.
649
+
650
+ **When to use vs skip.** Signal for adds, removes, and moves of visible nodes;
651
+ connecting nodes; creating groups; layout reorganizations; meaningful title/content
652
+ edits; destructive actions (clear/restore/remove); and creating
653
+ artifact/report/dashboard nodes. Skip for tiny metadata fixes, API-only pin/unpin
654
+ verification, deterministic report-node refreshes the human just asked for,
655
+ post-restore cleanup, and bulk fixture churn. **For batch work, signal one ghost
656
+ per human-meaningful move, not one per low-level op.** Put its `intentId` inside the
657
+ supported leaf operation's `args`, never on the `canvas_batch` envelope. Each live
658
+ intent settles once; it is not a veto gate for every sibling in a batch. For example,
659
+ after signalling a create intent, use its actual returned ID in this leaf:
660
+
661
+ ```json
662
+ {"operations":[{"op":"node.add","args":{"type":"markdown","title":"Evidence","content":"Observed failure","intentId":"<returned-intent-id>"}}]}
663
+ ```
664
+
665
+ Do not rely on a top-level batch `intentId`: standalone MCP can refuse it, while the
666
+ attached HTTP batch reader does not forward it. Unsupported operations cannot be made
667
+ veto-linked by wrapping them in a batch. Use `seq` to order separate staged previews.
668
+
669
+ ### Standalones (first-class — not deprecated)
670
+
671
+ These stay separate by design (trust-boundary, firehose, execution-intent, or not-yet-consolidated
672
+ surfaces): `canvas_batch`, `canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`,
673
+ `canvas_ingest_activity`, `canvas_invoke_command`. Snapshots moved into the `canvas_snapshot`
674
+ composite in v0.4.0 (actions `save | list | restore | delete | gc | diff`); the 6 legacy snapshot
675
+ standalones were removed after their deprecated 0.3.x window.
676
+
677
+ `canvas_batch` supports exactly these ops: `node.add`, `node.update`, `node.remove`, `graph.add`,
678
+ `edge.add`, `edge.remove`, `group.create`, `group.add`, `group.remove`, `annotation.add`,
679
+ `annotation.remove`, `pin.set`, `pin.add`, `pin.remove`, `snapshot.save`, and `arrange`.
680
+ `edge.update` is not supported. Anything else fails with "Unsupported canvas_batch
681
+ operation" — batch is non-atomic, so earlier ops stay applied and the response carries
682
+ `failedIndex`, `results` and `refs`. Inspect that committed prefix before retrying; an
683
+ error does not roll it back. `canvas_screenshot` requires an active automation WebView: call
684
+ `canvas_webview { action: "start" }` first.
685
+
686
+ > **Removed in v0.3.0 → use the composite instead**: `canvas_open_mcp_app` / `canvas_add_diagram` /
687
+ > `canvas_build_web_artifact` → **`canvas_app`**; `canvas_webview_start` / `canvas_webview_status` /
688
+ > `canvas_webview_stop` / `canvas_resize` / `canvas_evaluate` → **`canvas_webview`**;
689
+ > `canvas_add_html_node` / `canvas_add_html_primitive` → **`canvas_node { action:"add", type:"html" }`**
690
+ > (pass `primitive` for a primitive); `canvas_add_node` / `canvas_update_node` / `canvas_remove_node`
691
+ > → **`canvas_node`**; `canvas_remove_annotation` → **`canvas_view { action:"remove-annotation" }`**;
692
+ > `canvas_refresh_webpage_node` → **`canvas_node { action:"update", id, refresh: true }`**;
693
+ > `canvas_validate` (board collisions / containment / dangling edges) → **`canvas_query` `validate`**;
694
+ > `canvas_validate_spec` (json-render / graph spec dry-run) → **`canvas_render` `validate`**.
695
+
696
+ ### Node Operations
697
+
698
+ MCP node-type routing — which tool creates which node category:
699
+
700
+ | Node category | MCP creation call |
701
+ |---------------|-------------------|
702
+ | Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_node { action: "add" }` |
703
+ | `json-render` | `canvas_render { action: "add-json-render" }` |
704
+ | `graph` | `canvas_render { action: "add-graph" }` |
705
+ | `html` | `canvas_node { action: "add", type: "html", html }` |
706
+ | `html-primitive` | `canvas_node { action: "add", type: "html", primitive: "<kind>", data }` |
707
+ | `web-artifact` | `canvas_app { action: "build-artifact" }` |
708
+ | `external-app` / tool-backed `mcp-app` | `canvas_app { action: "open-mcp-app" }` (Excalidraw preset: `canvas_app { action: "diagram" }`) |
709
+ | `group` | `canvas_group { action: "create" }` |
710
+
711
+ If a node type is rejected by `canvas_node { action: "add" }`, call
712
+ `canvas_render { action: "describe-schema" }` and read `mcp.nodeTypeRouting`; do not keep
713
+ retrying the generic add.
714
+
715
+ **`canvas_node { action: "add", … }`** — Add a node to the canvas
716
+ - `type` (required): basic node type only; structured/app/group nodes use the routing table above
717
+ - `title`: short, scannable title
718
+ - `content`: markdown text for most types. For `file`, pass the **file path** (e.g. `"src/auth/login.ts"`) —
719
+ the server auto-loads + watches it. For `image`, pass a file path, URL, or data URI.
720
+ - `path`: compatibility alias for image paths only; prefer `content` for new image calls
721
+ - `x`, `y`: position (prefer omitting for auto-layout); `width`, `height`: dimensions (sensible defaults); `metadata`: arbitrary JSON. (`color` applies to **group**/**graph** nodes only — it is ignored on basic node types; see Colors (Semantic).)
722
+ - Returns: `{ id: "<node-id>" }` — capture this ID for edges and groups
723
+
724
+ **`canvas_node { action: "update", id, … }`** — Update an existing node in place (preferred over
725
+ delete+recreate; preserves edges, pins, position)
726
+ - `id` (required), plus any of: `title`, `content`, `x`, `y`, `width`, `height`, `collapsed`, `arrangeLocked`, `data`
727
+ - For `json-render`, pass `spec` to update the rendered spec in place
728
+ - For `graph`, pass graph fields (`graphType`, `data`, `xKey`, `yKey`, `color`, `chartHeight`) to rebuild the chart; `height`/`nodeHeight` set frame geometry, `chartHeight` the chart content
729
+
730
+ **`canvas_node { action: "remove", id }`** — Remove a node and all its connected edges. Clean up nodes that are no longer relevant.
731
+
732
+ **`canvas_node { action: "get", id }`** — Get a single node's full data by `id`.
733
+
734
+ **`canvas_view { action: "add-annotation", points, … }`** — Draw freehand ink with at least two
735
+ world-coordinate points, or set `type: "text"`, `text` and one point to write a note.
736
+ `width` is stroke width (default 4, clamped to 1–24) or font size (default 24,
737
+ clamped to 1–96). `color` defaults to `currentColor`, the theme's annotation ink;
738
+ explicit colors must be `#rrggbb`. Text is trimmed to 240 characters, labels to 160.
739
+ Bounds and creation time are server-derived.
740
+
741
+ Freehand points render as straight segments with round caps/joins, without automatic
742
+ smoothing. Sample curves densely for smooth-looking outlines and loops. Cursor updates
743
+ through `canvas_ax_state { action: "set-presence", cursor: { x, y } }` are separate,
744
+ ephemeral presence, not drawing gestures; reuse your writer identity without attaching
745
+ a session. An annotation appears complete on creation, not as a streaming stroke.
746
+ See [Shared ink and questions](workbench-workflows.md#shared-ink-and-questions).
747
+
748
+ Use an explicit `id` for retries: equal normalized content returns the existing mark
749
+ without another Undo entry, while different content conflicts. After erasing or Undo,
750
+ adding the ID creates a mark again; this is not a durable creation receipt.
751
+
752
+ **`canvas_view { action: "remove-annotation", id }`** — Erase a whole human or agent mark,
753
+ not the underlying card. An absent mark returns not-found. Context gives you IDs;
754
+ inspect the canvas first when identifying a mark by shape or location.
755
+ CLI and MCP batches accept `annotation.add` / `annotation.remove` with the same arguments.
756
+
757
+ Humans and agents share this layer. Use marks when helpful, not for every task.
758
+ The human may answer in the external agent host's composer; no PMX session,
759
+ PMX composer or reply card is required. Marks are not approval gates, and drawing
760
+ or erasing does not answer a question or execute a loop. Existing scope fences apply.
761
+
762
+ **`canvas_node { action: "update", id, refresh: true }`** — Re-fetch the URL stored on a `webpage` node
763
+ - `id` (required): webpage node to refresh
764
+ - Optional `url`: replace the stored URL before refreshing (use when the human moved the page)
765
+ - Returns the refreshed node with updated `pageTitle` and cached extracted text
766
+ - Use this when a saved canvas is reopened and the agent needs fresh page content without
767
+ losing the node's identity, position, or pins. Example flow:
768
+
769
+ ```typescript
770
+ // Add the page once
771
+ canvas_node({ action: 'add', type: 'webpage', url: 'https://example.com/docs' })
772
+ // → returns { id: 'node-abc' }
773
+
774
+ // …later, after the human reopens the canvas…
775
+ canvas_node({ action: 'update', id: 'node-abc', refresh: true })
776
+ // → re-fetches the URL, updates pageTitle + extracted text, keeps the node ID and position
777
+ ```
778
+
779
+ **`canvas_render { action: "add-json-render", … }`** — Add a native json-render node
780
+ - Required: `spec`; `title` is optional and inferred from the root element when omitted
781
+ - Prefer a complete json-render object with `root`, `elements`, and optional `state`
782
+ - Legacy bare component specs like `{ type: "Badge", props: {...} }` are accepted and wrapped into a one-element document for compatibility
783
+ - Use this when you want a structured UI panel rendered directly inside PMX Canvas
784
+ - For shadcn `Badge`, prefer `props.text` with variants `default`, `secondary`, `destructive`, or
785
+ `outline`. Legacy `props.label` and status variants (`success`, `info`, `warning`, `error`,
786
+ `danger`) are normalized for saved-spec compatibility.
787
+
788
+ **`canvas_render { action: "stream-json-render", … }`** — Build a json-render node progressively (live)
789
+ - Omit `nodeId` on the first call to create a streaming node (returns its `id`); reuse that `nodeId`
790
+ on later calls to append `patches`; set `done: true` on the final call.
791
+ - `patches` are SpecStream JSON-Patch ops applied server-side (the canvas accumulates the spec), e.g.
792
+ `{ "op": "add", "path": "/elements/card", "value": { … } }`, `{ "op": "replace", "path": "/root", "value": "card" }`.
793
+ - Build incrementally: set `/root`, add container elements, then append child element ids/elements.
794
+ Each call re-renders; partial specs render what they can. Use for dashboards/reports that fill in
795
+ as you generate them rather than appearing all at once.
796
+
797
+ **`canvas_render { action: "add-graph", … }`** — Add a native graph/chart node
798
+ - Required: `graphType`, `data`
799
+ - Supports `line`, `bar`, `pie`, `area`, `scatter`, `radar`, `stacked-bar`, `composed`,
800
+ and the Tufte primitives `sparkline`, `dot-plot`, `bullet`, `slopegraph` (aliases accepted)
801
+ - Use `xKey`/`yKey` for line, bar, area, and scatter graphs
802
+ - Use `zKey` for scatter bubble size
803
+ - Use `nameKey`/`valueKey` for pie graphs
804
+ - Use `axisKey` plus `metrics` for radar graphs
805
+ - Use `series` for stacked-bar graphs
806
+ - Use `barKey`/`lineKey` plus optional `barColor`/`lineColor` for composed graphs
807
+ - Bar charts: `colorBy` (`series` default = one accent + a highlighted bar, `category`, `value`, `none`) and `highlight` (`max`/`min`/index)
808
+ - Use `valueKey` for `sparkline` (plus `fill`/`showEndDot`/`showMinMax`/`showValue`)
809
+ - Use `labelKey`/`valueKey` (plus `sort`) for `dot-plot`
810
+ - Use `labelKey`/`valueKey`/`targetKey`/`rangesKey` for `bullet`
811
+ - Use `labelKey`/`beforeKey`/`afterKey` (plus `beforeLabel`/`afterLabel`/`colorByDirection`) for `slopegraph`
812
+ - Use `nodeHeight` for the canvas frame height and `height` for chart content height
813
+ - Uses the native json-render chart catalog under the hood
814
+
815
+ **Tufte-aware charting** — color must encode data, not decorate. For chart design and critique, use
816
+ the `tufte-viz` skill (`skills/tufte-viz/SKILL.md`). Key rules:
817
+ - Single-series `bar` charts use `colorBy`: default `series` (one accent + one highlighted bar),
818
+ `category` (opt-in palette), `value` (sequential shade by magnitude), or `none` (flat). Do not
819
+ rainbow categorical bars by default.
820
+ - Prefer the Tufte primitives where they fit: `sparkline` (inline trend), `dot-plot` (ranked single
821
+ metric vs. a bar forest), `bullet` (measure vs. target, replaces a gauge), `slopegraph`
822
+ (before/after across many categories).
823
+ - Direct-label data (`showLegend: false`) instead of a legend when one or two series are identifiable.
824
+ - For more than ~4 overlapping series, build small multiples (several small graph nodes on a shared
825
+ scale, arranged in a grid/group) instead of one multi-color chart.
826
+
827
+ **`canvas_app { action: "build-artifact" }`** — Build and optionally open a bundled web artifact
828
+ - Required: `title`, `appTsx` (source string contents, not a file path)
829
+ - CLI `--app-file` reads a file before calling the same build path; MCP callers must pass the source contents
830
+ - Cold builds commonly take 45-60 seconds; use a long client timeout such as 300000 ms or more
831
+ - Returns both `id` and `nodeId` for the created artifact node when `openInCanvas` is true
832
+
833
+ ID extraction for mixed tool responses:
834
+ - Most add-style tools return a flat `id`; web artifacts return `id` plus `nodeId`; snapshots return `id` plus nested `snapshot.id`.
835
+ - Defensive extractor: `const getId = (r) => r.id ?? r.nodeId ?? r.snapshot?.id;`
836
+
837
+ **`canvas_app { action: "open-mcp-app" }`** — Open a tool-backed external MCP app node
838
+ - Required: `toolName`, `transport`
839
+ - `transport` is either `{ type: "stdio", command, args?, cwd?, env? }` or `{ type: "http", url, headers? }`
840
+ - This is lower-level than `pmx-canvas external-app add --kind excalidraw`; use `canvas_app { action: "diagram" }` for the built-in Excalidraw preset
841
+
842
+ **`canvas_pin_nodes`** (standalone) — Set, add, or remove pinned context nodes. Use `{ nodeIds: [...] }` — the field is `nodeIds`, not `ids`.
843
+
844
+ **`canvas_snapshot { action: "diff" }`** — Compare current canvas state with a saved snapshot. Requires `{ snapshot: "<snapshot-id-or-name>" }`; there is no implicit previous-snapshot default.
845
+
846
+ **`canvas_render { action: "describe-schema" }`** — Inspect the running server's create schemas and
847
+ canonical examples. Use before generating structured payloads when you need the authoritative current
848
+ shape; read `mcp.nodeTypeRouting` to choose the right creation call for each node category.
849
+
850
+ **`canvas_render { action: "validate", … }`** — Dry-run a json-render spec or graph payload without
851
+ creating a node. Returns the normalized json-render spec the server would accept.
852
+
853
+ **`canvas_view { action: "fit", … }`** — Fit viewport to all nodes or selected nodes; optional
854
+ `width`, `height`, `padding`, `maxScale`, `nodeIds`. Use before screenshot/whole-board review so the
855
+ server viewport matches the intended camera.
856
+
857
+ **Batch graph creation** — Use the `graph.add` op inside `canvas_batch` / `pmx-canvas batch` for a
858
+ graph node in a larger one-shot build. It takes the same shape as `canvas_render { action: "add-graph" }`.
859
+ In batch/MCP/HTTP payloads, `height` is chart content height and `nodeHeight` is the canvas frame height.
860
+
861
+ ### Edge Operations
862
+
863
+ **`canvas_edge { action: "add", … }`** — Connect two nodes
864
+ - `from`, `to` (required): source and target node IDs
865
+ - `fromSearch`, `toSearch`: optional search-based selectors when you do not have IDs. Each search
866
+ query must resolve to exactly one node or the edge creation fails with an ambiguity error.
867
+ - `type`: `flow`, `depends-on`, `relation`, or `references` (default: `relation`)
868
+ - `label`: descriptive relationship label; `style`: `solid`/`dashed`/`dotted`; `animated`: visual emphasis
869
+ - `canvas_edge { action: "remove", id }` removes a connection by edge `id`.
870
+
871
+ ### Layout & Navigation
872
+
873
+ - **`canvas_query { action: "layout" }`** — full canvas state (nodes, edges, viewport). Read before mutating.
874
+ - **`canvas_view { action: "arrange", layout }`** — auto-arrange all nodes; `layout` is `grid` (default,
875
+ dashboards/overviews), `column` (vertical lists), or `flow` (horizontal sequences / dependency chains).
876
+ Call once after a batch of adds. For tiered/layered layouts, fine-tune with explicit `x`/`y` via
877
+ `canvas_node { action: "update" }` after arranging.
878
+ - **`canvas_view { action: "focus", id }`** — pan the viewport to a node. Don't focus every node in a
879
+ batch — focus only the final result, or use CLI `focus --no-pan` to select/raise without moving the camera.
880
+
881
+ ### Groups
882
+
883
+ - **`canvas_group { action: "create", … }`** — visual container; `title`, `childIds` (node IDs), `color`. Auto-sizes to fit children.
884
+ - **`canvas_group { action: "add", groupId, childIds }`** — add nodes to an existing group.
885
+ - **`canvas_group { action: "ungroup", groupId }`** — dissolve the group: its children become
886
+ independent nodes (members of the enclosing group when nested) and the frame is removed, in one
887
+ undo step. Exactly what the human's Ungroup does.
888
+
889
+ ### Group Layout Guidance
890
+
891
+ Use groups as spacious semantic regions, not as tight containers. (Group calls below use
892
+ `canvas_group { action: "create" | "add" | "ungroup" }`.)
893
+
894
+ - Size the child nodes first, especially `graph`, `json-render`, `mcp-app`, image, and webpage
895
+ nodes whose rendered content may need more height than their visible title suggests.
896
+ - Give every group generous interior padding. Reserve extra top padding for the group header, then
897
+ keep children clear of the frame edges so headers, glow, resize handles, and node chrome do not
898
+ visually collide.
899
+ - If creating a group manually, compute its frame from the final child bounds plus padding. If the
900
+ group exists first, expand it before adding large children rather than shrinking children to fit.
901
+ - Use groups to label major regions of a board. Avoid wrapping every small relationship; too many
902
+ tight groups make the canvas harder to read than no groups.
903
+ - Keep edges local to a group where possible. Long cross-board edges can look like they come from
904
+ nowhere; use a nearby bridge/context node or split the relationship into shorter labeled edges.
905
+ - After grouping, verify the result in `canvas_query { action: "layout" }` or the browser: child nodes should be
906
+ fully inside the group with padding, visible nodes should not overlap, and group headers should
907
+ not cover content.
908
+ - If a group makes important content less visible, enlarge the group, split it into clearer
909
+ regions, or remove the group. Visibility is more important than preserving a frame.
910
+
911
+ ### Grouped Comparison Boards
912
+
913
+ Use groups as named comparison areas, not just visual boxes.
914
+
915
+ - Create the comparison frame first with `canvas_group { action: "create" }` or
916
+ `pmx-canvas group create`, then add charts, artifacts, and diagrams into that area deliberately.
917
+ - Prefer graph nodes for fast capability demos and side-by-side comparisons. They are lightweight,
918
+ validate quickly, and are easier to regenerate.
919
+ - Prefer web artifacts when the board needs a richer narrative UI, custom interaction, or a more
920
+ polished presentation layer than a graph or json-render node can provide.
921
+ - Use Excalidraw for sketching and flow diagrams, but treat it as less reliable than web-artifact
922
+ app nodes for grouping and rediscovery until `mcp-app` grouping parity is fixed.
923
+ - Native node types are still the most agent-friendly. Graph nodes are the strongest comparison
924
+ primitive today, web artifacts are good but heavier, and Excalidraw / other `mcp-app` nodes are
925
+ useful but still the weakest operationally for create, rediscover, group, and reconnect flows.
926
+ - Leave larger spacing between major regions than you think you need. The spatial analyzer still
927
+ tends to read dense boards as one giant cluster unless groups and gaps are both clear.
928
+ - If you are expanding a board incrementally, verify each add-to-group step instead of assuming
929
+ the node joined the area. Comparison workflows depend on reliable “add this thing to the region
930
+ I’m already building.”
931
+
932
+ Current product caveats for grouped comparison boards:
933
+ - `mcp-app` grouping parity is inconsistent. Web artifacts have grouped cleanly; Excalidraw has
934
+ not always behaved the same way.
935
+ - Search/discoverability for external app nodes can degrade over time in-session, so node IDs are
936
+ safer than title-based rediscovery for follow-up grouping or focus operations.
937
+ - `mcp-app` nodes are less inspectable than native nodes. For graph nodes you can reason from
938
+ structured config, but app nodes often only tell you that an app exists unless you also inspect
939
+ nearby markdown, file, or graph context.
940
+ - Long-running web artifact builds can exceed a short command timeout. When using them in an
941
+ agent workflow, prefer progress-aware handling and avoid assuming a timeout means failure.
942
+
943
+ ### Search & Discovery
944
+
945
+ **`canvas_query { action: "search", query }`** — Find nodes by title or content keywords. Returns
946
+ ranked matches with content snippets. Use instead of parsing the full layout to locate specific nodes.
947
+
948
+ ### Context Pinning
949
+
950
+ **`canvas_pin_nodes`** (standalone) — Manage pinned context: `nodeIds` (required) plus `mode`
951
+ (`set` replaces all pins, `add`, `remove`).
952
+ - Pinned nodes are the primary human-to-agent communication channel — when a human pins in the
953
+ browser, they're saying "pay attention to these."
954
+ - Best default pin set: one intent-setting markdown node plus 1-3 concrete output nodes.
955
+ - Graph, file, and markdown pins carry richer usable context than `mcp-app` pins. Artifact and
956
+ Excalidraw pins still matter as intent signals, but pair them with a markdown or graph pin so the
957
+ agent understands what is inside the app, not just that it matters.
958
+
959
+ ### History & Snapshots
960
+
961
+ - **`canvas_history { action: "undo" }`** / **`canvas_history { action: "redo" }`** — step the mutation ring buffer.
962
+ - **`canvas_snapshot { action: "save", name }`** — save a named snapshot. Returns `{ ok, id, snapshot }` (flat `id` aliases `snapshot.id`).
963
+ - **`canvas_snapshot { action: "list" }`** / **`{ action: "gc", keep }`** / **`{ action: "delete", id }`** — manage saved snapshots.
964
+ - **`canvas_snapshot { action: "restore", id }`** — restore from a snapshot.
965
+ - **`canvas_snapshot { action: "diff", snapshot }`** — compare current canvas against a saved snapshot (added/removed/modified nodes & edges). Note the field is `snapshot` (id or name), not `id`.
966
+
967
+ ### Canvas Management
968
+
969
+ **`canvas_view { action: "clear" }`** — Remove all nodes and edges. **Always `canvas_snapshot { action: "save" }` first** —
970
+ this is irreversible without a prior snapshot.
971
+
972
+ ### Browser Automation (WebView)
973
+
974
+ The canvas exposes a headless browser session over MCP for self-inspection and
975
+ automated screenshotting. Use this when you want to (a) verify what the live
976
+ canvas actually looks like after a sequence of mutations, (b) capture an image
977
+ of a freshly-built artifact for the human to review, or (c) drive arbitrary
978
+ JavaScript inside the workbench page.
979
+
980
+ The WebView automation runs on Bun's WebKit-based WebView (macOS) or a headless
981
+ Chromium fallback (Linux). It does **not** open a visible window; it's an
982
+ additional headless renderer attached to the same canvas server, so every action
983
+ below operates on the live canvas state through the `canvas_webview` composite.
984
+
985
+ **`canvas_webview { action: "status" }`** — Inspect the current automation session
986
+ - Returns `{ supported, active, headlessOnly, url, backend, width, height, dataStoreDir, startedAt, lastError }`
987
+ (the viewport size is `width` / `height`, not `viewportWidth` / `viewportHeight`)
988
+ - Call before `start` to check whether a session is already alive
989
+
990
+ **`canvas_webview { action: "start" }`** — Start (or replace) the automation session
991
+ - Optional: `backend` (`webkit` macOS-only, or `chrome`), `width`, `height`
992
+ - The session opens `/workbench` at the canvas URL, waits for the SPA to
993
+ hydrate, and reports back via `canvas_webview { action: "status" }`
994
+ - **Backend preflight**: prefer `webkit` on macOS — it is the reliable
995
+ automation backend across tested hosts. The `chrome` backend can trigger a
996
+ native "Allow remote debugging?" dialog on first start in some hosts; if the
997
+ prompt is not approved promptly the start times out with `ok:false`. When
998
+ using `chrome`, approve the dialog immediately and/or raise
999
+ `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` (e.g. `30000`). A single chrome timeout is
1000
+ worth one immediate retry before classifying it as a failure.
1001
+
1002
+ **`canvas_webview { action: "stop" }`** — Tear down the automation session
1003
+
1004
+ **`canvas_webview { action: "evaluate" }`** — Run JavaScript inside the workbench page and return the result
1005
+ - Required: exactly one of `expression` (single JS expression) or `script` (multi-statement body)
1006
+ - `script` is wrapped in an async IIFE, so top-level `await` works inside script bodies
1007
+ - Useful for asserting DOM state after a sequence of canvas mutations
1008
+ - Do not use `fetch()` inside `canvas_webview { action: "evaluate" }` to call PMX HTTP APIs; WebView
1009
+ security/CORS restrictions can block those requests. Use the matching MCP tools instead.
1010
+ - Example: read the count of rendered `.canvas-node` elements:
1011
+
1012
+ ```typescript
1013
+ canvas_webview({ action: 'evaluate', expression: 'document.querySelectorAll(".canvas-node").length' })
1014
+ ```
1015
+
1016
+ Useful workbench selectors:
1017
+ - Nodes: `.canvas-node`, `.canvas-node.active`, `.canvas-node.context-pinned`, `.canvas-node.group-node`
1018
+ - Node internals: `.node-title`, `.node-titlebar`, `.node-body`, `.node-type-badge`, `.node-controls`
1019
+ - Annotations: `.annotation-layer path` renders shared freehand ink; `.annotation-layer text`
1020
+ renders text notes. Use WebView to inspect or screenshot shapes; MCP/context resources
1021
+ expose IDs, text, labels, targets and bounds, not raw stroke geometry. Humans use the
1022
+ rail's Draw / Text note / Eraser tools; agents use `canvas_view` actions
1023
+ `add-annotation` / `remove-annotation` on the same layer.
1024
+ - Canvas chrome (rail-chrome-v2): `.tool-rail`, `.top-bar` (`.connection-dot`, `.agent-chip`,
1025
+ `.gate-badge`, `.context-chip`, `.external-indicator`, `.start-session-btn`),
1026
+ `.connection-banner`, `.canvas-region`, `.session-panel`, `.command-bar`, `.selection-bar`,
1027
+ `.activity-feed`, `.writers-sheet`, `.session-receipt`, `.snapshot-panel` (the History
1028
+ drawer), `.minimap`, `.empty-state`, `.command-palette`, `.expanded-overlay-panel`.
1029
+ - Presence: `.agent-cursor` (attached or external agent), `.human-cursor` (other tabs), `.scope-fence`,
1030
+ `.canvas-node.agent-mutating` (shimmer), `.node-yield-pill`.
1031
+ - Groups: `.canvas-node.group-node` with `.group-name-pill`, `.group-actions`, `.group-chip`
1032
+ (collapsed); `.drop-pill` while a dragged node would join/leave a group; `.edge-hint-pill`
1033
+ during an edge drag.
1034
+ - Every `.canvas-node` carries `data-node-id` and `data-node-type` (rail-chrome redesign); `canvas_query`
1035
+ (`layout` / `search`) remains the reliable way to get ids without a browser.
1036
+
1037
+ Async script example:
1038
+
1039
+ ```typescript
1040
+ canvas_webview({
1041
+ action: 'evaluate',
1042
+ script: 'const title = await Promise.resolve(document.title); return title;',
1043
+ })
1044
+ ```
1045
+
1046
+ **`canvas_webview { action: "resize" }`** — Change the WebView viewport
1047
+ - Required: `width`, `height`
1048
+ - Use before `canvas_screenshot` when the human needs a specific aspect ratio
1049
+
1050
+ **`canvas_screenshot`** — Capture a PNG of the current workbench
1051
+ - Optional: `format` (`png` default), `fullPage` (boolean)
1052
+ - Returns both an MCP image payload (renderable inline by capable agents) and
1053
+ a path under `.pmx-canvas/screenshots/` so the human can view the file
1054
+ - Pair with `canvas_webview { action: "resize" }` to control the framing
1055
+
1056
+ Typical flow when you want to show a result:
1057
+
1058
+ ```typescript
1059
+ canvas_webview({ action: 'start', width: 1440, height: 900 });
1060
+ // …mutations…
1061
+ canvas_screenshot({ fullPage: true });
1062
+ canvas_webview({ action: 'stop' });
1063
+ ```
1064
+
1065
+ ### Diagrams (Excalidraw MCP app preset)
1066
+
1067
+ **`canvas_app { action: "diagram" }`** — Draw a hand-drawn diagram on the canvas via the hosted
1068
+ [Excalidraw MCP app](https://github.com/excalidraw/excalidraw-mcp)
1069
+ - Required: `elements` — an array of Excalidraw elements (rectangles, ellipses, diamonds, arrows,
1070
+ text). Can also be a JSON-array string.
1071
+ - `elements` must be Excalidraw element objects, not Mermaid/DOT/source-text diagrams. Convert source diagrams to Excalidraw elements first or use a markdown/web-artifact node.
1072
+ - Optional: `title`, `x`, `y`, `width`, `height`
1073
+ - The diagram opens inside an `mcp-app` node with fullscreen editing and draw-on animations
1074
+ - CLI equivalent: `pmx-canvas external-app add --kind excalidraw --title "Diagram"`
1075
+ - Edits made in expanded/fullscreen mode are persisted back into the node model context and replayed
1076
+ when the app iframe remounts.
1077
+ - Use this when the human needs a quick sketch, architecture diagram, or flowchart and a
1078
+ geometric `graph` node would feel too rigid
1079
+ - Prefer labeled shapes (`"label": { "text": "..." }` on rectangle/ellipse/diamond) over
1080
+ separate text elements — fewer tokens and auto-centered
1081
+ - Do not use separate `text` elements with `containerId`/`boundElements` to place centered text
1082
+ inside shapes. The hosted SVG preview does not auto-position those; PMX normalizes imported
1083
+ canonical bound text back into shape labels for hosted app calls.
1084
+ - For detailed sizing, camera, and label-fit rules, read `excalidraw-diagram-authoring.md`
1085
+ before creating dense diagrams.
1086
+ - Prefer the pastel fill palette in the Excalidraw `read_me` (light blue/green/orange/...) for
1087
+ a consistent look across diagrams
1088
+
1089
+ ### External MCP apps (bring your own)
1090
+
1091
+ **`canvas_app { action: "open-mcp-app" }`** — Open any external [MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps)
1092
+ server's `ui://` resource as an iframe node on the canvas
1093
+ - Required: `toolName`, `transport` (`http` URL or `stdio` command)
1094
+ - Optional: `serverName`, `toolArguments`, `title`, `x`, `y`, `width`, `height`
1095
+ - Use when no dedicated preset exists yet. The Excalidraw preset (`canvas_app { action: "diagram" }`) is the
1096
+ only one today
1097
+
1098
+ ### Web Artifacts
1099
+
1100
+ **`canvas_app { action: "build-artifact" }`** — Build a single-file HTML artifact from React/Tailwind source
1101
+ - Required: `title`, `appTsx`
1102
+ - Optional: `indexCss`, `mainTsx`, `indexHtml`, extra `files`, `projectPath`, `outputPath`, `deps`, `includeLogs`
1103
+ - By default it opens the result on the canvas as an embedded app node
1104
+ - By default it returns compact log summaries; set `includeLogs: true` when you need raw stdout/stderr
1105
+ - `recharts` is available in the scaffold. For additional libraries, pass CLI `--deps name,name2`
1106
+ or MCP/API `deps: ["name"]` before bundling.
1107
+ - Failed or empty CLI bundles print `ok: false`, exit non-zero, and do not create a canvas node.
1108
+ - Use this when the output should be a richer interactive UI than a simple markdown/file/image node
1109
+ - Prefer the dedicated `web-artifacts-builder` skill when you need the full React + shadcn workflow
1110
+ - Use the `playwright-cli` skill when you need to validate the built artifact in a live browser
1111
+
1112
+ ### HTML Nodes (Sandboxed iframe)
1113
+
1114
+ **`canvas_node { action: "add", type: "html", html }`** — Add a normal self-contained HTML document rendered in a sandboxed iframe
1115
+ - Required: `html` (full document or fragment; inline `<script>` and CDN `<script src="...">` are allowed). If `html` is a bare path to an existing local `.html`/`.htm` file, the server reads that file's contents; otherwise it is treated as raw HTML.
1116
+ - Optional: `title`, `summary`, `agentSummary`, `presentation`, `slideTitles`, `embeddedNodeIds`, `embeddedUrls`, `x`, `y`, `width` (default 720), `height` (default 640), `strictSize`
1117
+ - Iframe sandbox is `allow-scripts` only — no same-origin access, no top-navigation, no forms
1118
+ - Canvas theme tokens are auto-injected as CSS custom properties (both `--c-*` and common `--color-*` aliases such as `--color-text-primary`, `--color-bg`, `--color-accent`) and updated live when the canvas theme changes
1119
+ - Use for moderate-complexity visualizations and interactive widgets that need real JS but do not warrant a full React build (Chart.js demos, D3 sketches, custom HTML report views)
1120
+ - Normal HTML is the default. Only pass `presentation: true` when the user explicitly asks for a deck/fullscreen presentation; otherwise do not mark raw HTML as presentable.
1121
+ - Only presentation-marked HTML nodes expose a browser `Present` button. Use it when the HTML is a deck, briefing, or fullscreen review surface; the PMX shell owns the fullscreen overlay and exits via `Esc` or `Exit presentation`.
1122
+ - PMX stores a semantic sidecar (`agentSummary`, `contentSummary`, embedded references) so HTML nodes remain understandable in search, pinned context, and spatial context
1123
+
1124
+ **`canvas_node { action: "add", type: "html", primitive, data }`** — Generate a reusable HTML communication primitive as a sandboxed `html` node
1125
+ - Required: `kind`; run `canvas_render { action: "describe-schema" }` and read `htmlPrimitives` for the current catalog
1126
+ - Optional: `title`, `data`, `x`, `y`, `width`, `height`, `strictSize`
1127
+ - Use when markdown would be too dense and a structured visual artifact is clearer: tradeoff grids, implementation plans, PR reviews, module maps, design sheets, explainers, reports, and lightweight human-editable boards/editors
1128
+ - When the human asks for a PowerPoint-like output, pitch deck, briefing, or presentation, use `kind: "presentation"` unless a bespoke raw HTML deck is required. Include `slides` with short titles, one idea per slide, optional `metrics`, `note` fields for speaker notes, and optional `theme: "canvas" | "midnight" | "paper" | "aurora"` or a custom theme object.
1129
+ - For payload patterns, export loops, and the primitive catalog, read `html-primitives.md` before creating dense or editable artifacts
1130
+
1131
+ ### Open as Site (standalone surfaces)
1132
+
1133
+ Any renderable surface node can be opened full-page in its own browser tab — the same
1134
+ document it shows in the canvas, just without the node chrome. In the workbench, use the
1135
+ ↗ **Open as site** button (new tab) or the ⤤ **Open in system browser** button in the
1136
+ node title bar (or the expanded overlay). "Open in system browser" launches the real OS
1137
+ browser via `POST /api/canvas/open-external` `{ nodeId }` (it opens only this server's own
1138
+ surface URL; falls back to a normal new tab when the server can't launch) — use it when
1139
+ the host's embedded browser (e.g. Codex) opens `_blank` tabs in-place.
1140
+
1141
+ - Works for `html` / `html-primitive`, bundled `web-artifact`, `json-render` / `graph`,
1142
+ and `webpage` nodes. **Hosted ext-app `mcp-app` nodes (e.g. Excalidraw) are NOT
1143
+ open-as-site targets** (the control is hidden; see the ext-app bullet below).
1144
+ - The tab loads the node's stable surface URL, `/api/canvas/surface/<nodeId>`. The
1145
+ in-canvas iframe loads the **exact same URL**, so there is one render path and no
1146
+ separate "preview" version — what you see in the canvas is what opens. The URL reflects
1147
+ current node state and survives a refresh.
1148
+ - Agents can read this URL from any node payload (`canvas_node { action: "get" }` /
1149
+ `canvas_query { action: "layout" }`) as `surfaceUrl` — a reliable way to tell a human
1150
+ "open the artifact" without disturbing the canvas.
1151
+ - Served HTML stays sandboxed (opaque origin via a `Content-Security-Policy: sandbox`
1152
+ response header), so opening author code top-level cannot reach the canvas origin.
1153
+ - Hosted ext-app `mcp-app` nodes (e.g. Excalidraw) are a live MCP-app shell that only
1154
+ renders with the in-canvas host bridge — a bare tab has no peer, so the surface route
1155
+ returns `404` ("This MCP app renders in the canvas and cannot be opened as a standalone
1156
+ site"); view/edit them in the canvas, or open them externally through their own app
1157
+ (report #61). `POST /api/canvas/open-external` refuses those nodes with the same message
1158
+ instead of launching a browser at a URL that 404s. Only bundled `web-artifact` apps
1159
+ (redirect to `/artifact`) and URL-backed `mcp-app` / `webpage` viewers redirect to their
1160
+ external site.
1161
+ - `graph` / `json-render` nodes redirect to the full-viewport `display=site` viewer; the chart
1162
+ fills the window and reflows on a live resize. Current Codex builds deliver live-resize events —
1163
+ the 0.4.6 pass watched a standalone graph reflow live (SVG 1550×783 → 850×483 on a
1164
+ 1600×900 → 900×600 window resize) with no reload — so do not present a system browser as a
1165
+ workaround for stale resizing (report #67).
1166
+ - This is additive — opening a site never evicts or replaces canvas nodes.
1167
+
1168
+ ### Choosing the Right Visual Tier
1169
+
1170
+ When the output is more than markdown, pick the lightest tier that fits:
1171
+
1172
+ | Tier | Tool | Build cost | When to pick it |
1173
+ |------|------|------------|-----------------|
1174
+ | Declarative UI | `canvas_render` (`add-json-render` / `add-graph`) | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via `canvas_node { action: "get" }` |
1175
+ | Generated HTML primitive | `canvas_node { action:"add", type:"html", primitive }` | None | Reusable communication artifacts such as choices, plans, reviews, maps, reports, presentations/decks, and lightweight editors |
1176
+ | Sandboxed HTML+JS | `canvas_node { action:"add", type:"html", html }` | None | Self-contained HTML with inline JS or CDN scripts; one-off visualizations or report views |
1177
+ | Hosted MCP app | `canvas_app { action:"open-mcp-app" }` / `canvas_app { action:"diagram" }` | None | Interactive editors backed by an external MCP server (e.g. Excalidraw) |
1178
+ | Bundled React app | `canvas_app { action:"build-artifact" }` | Heavy (npm install + bundle) | Multi-component UIs needing React state, routing, shadcn/ui, or Tailwind class composition |
1179
+
1180
+ ### Native Structured UI
1181
+
1182
+ Use native `json-render` and `graph` nodes when the output should stay fully inside PMX Canvas:
1183
+
1184
+ 1. Use `canvas_render { action: "add-json-render" }` for dashboards, forms, summaries, and interactive UI panels
1185
+ 2. Use `canvas_render { action: "add-graph" }` for charts and trend visualizations
1186
+ 3. Use the repo-local `json-render-*` skills when you need help authoring or refining the spec itself
1187
+ 4. Use `canvas_app { action: "build-artifact" }` instead when the result needs a full custom React app rather than a schema-driven UI
1188
+
1189
+ 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.
1190
+
1191
+ ## MCP results and cancellation (0.10)
1192
+
1193
+ The 25-tool catalog remains on MCP SDK v1. Each tool advertises `outputSchema`
1194
+ and effect annotations. Help returns `structuredContent.result` with operation
1195
+ metadata (or the name list); it records no content reads. Registry-dispatched tools expose
1196
+ `structuredContent.result = { operation, outcome, data }`, where `outcome` is
1197
+ `success`, `error` or `partial`; `data` matches the existing compact text JSON,
1198
+ or `{message}` for prose. Exceptions use `operation: null`; they do not prove no
1199
+ writes occurred. Interaction, activity-ingestion and screenshot tools instead
1200
+ use direct structured objects matching their schemas. Check `isError` and domain
1201
+ outcomes. A composite's annotations cover all its actions, not just reads.
1202
+
1203
+ Paired example: `canvas_node { action: "add", type: "markdown", content: "Evidence" }`
1204
+ returns registry data under `structuredContent.result.data` (excerpt; the ID varies):
1205
+
1206
+ ```json
1207
+ {"result":{"operation":"node.add","outcome":"success","data":{"ok":true,"id":"example-node"}}}
1208
+ ```
1209
+
1210
+ By contrast, `canvas_help {}` returns `structuredContent.result.operations`, not
1211
+ `result.data`. `canvas_help { op: "node.add" }` returns metadata with `operation`,
1212
+ `tool`, `description` and `inputSchema` directly under `result`.
1213
+ For direct tools (`canvas_ax_interaction`, `canvas_ingest_activity`, `canvas_screenshot`),
1214
+ read fields directly from `structuredContent` according to their `outputSchema`.
1215
+ These are different contracts, not optional wrappers to strip recursively. Text blocks
1216
+ are a separate representation; an SDK/host may already return `structuredContent`.
1217
+ On a failed call, inspect its error response and partial results before retrying writes.
1218
+
1219
+ Cancellation releases supported AX waits, aborts webpage fetches before late
1220
+ content writes, and stops subsequent batch operations. Completed writes remain;
1221
+ batch partial results retain `results` and `refs`. A created webpage can remain
1222
+ with `fetch.cancelled: true`. External app builds/runtime calls and local WebView
1223
+ captures are not interruptible mid-call. After a disconnect, inspect state before
1224
+ retrying because the response may not arrive. `_meta.progressToken` enables batch
1225
+ and supported AX wait progress notifications, including through the daemon connection. Without
1226
+ a token, calls return their normal result without progress notifications.
1227
+
1228
+ ## MCP Resources
1229
+
1230
+ These resources give you read access to canvas intelligence. Read them to understand
1231
+ what the human has set up and what they're focusing on.
1232
+
1233
+ As of 0.10, live resource updates require `resources/subscribe` on each connection.
1234
+ Subscribe to the exact URI you read, including context/graph query parameters;
1235
+ `resources/unsubscribe` stops it, and reconnecting requires a new subscription.
1236
+ Notifications invalidate cached data but do not deliver content, record a read, or
1237
+ advance a consumer cursor. Explicit `resources/read` remains available without
1238
+ subscriptions. Static schema and bundled skills do not support subscriptions.
1239
+
1240
+ | Resource | What it provides |
1241
+ |----------|-----------------|
1242
+ | `canvas://context{?budget,consumer,since}` | Budgeted cross-board map brief with optional durable consumer cursor |
1243
+ | `canvas://graph{?focus,depth}` | Summary-only connected memory projection, library or focused undirected membership-inclusive exploration |
1244
+ | `canvas://boards` | Board library with README summaries, links/backlinks and previews |
1245
+ | `canvas://pinned-context` | Content of pinned nodes + nearby unpinned neighbors |
1246
+ | `canvas://schema` | Running-server create schemas and json-render catalog metadata |
1247
+ | `canvas://layout` | Full canvas state (viewport, nodes, edges) |
1248
+ | `canvas://summary` | Compact overview: node counts by type, pinned titles |
1249
+ | `canvas://spatial-context` | Proximity clusters, reading order, pinned neighborhoods |
1250
+ | `canvas://history` | Human-readable mutation timeline |
1251
+ | `canvas://code-graph` | Auto-detected file import dependencies (JS/TS, Python, Go, Rust) |
1252
+ | `canvas://ax` | Host-agnostic AX state: focus, work items, approval gates, review annotations, host capability |
1253
+ | `canvas://ax-context` | Agent-ready AX context: pinned context + current focus |
1254
+ | `canvas://ax-work` | Canvas-bound AX work: work items, approval gates, review annotations, elicitations, mode requests, and tool/prompt policy |
1255
+ | `canvas://ax-timeline` | Bounded AX timeline: recent agent events, evidence, and steering messages |
1256
+ | `canvas://ax-pending-steering` | Adapterless delivery: `pending` steering to claim + mark delivered, and `pendingActivity` (open work items / pending approvals / elicitations / mode requests awaiting the agent) |
1257
+ | `canvas://ax-delivery` | Steering delivery state (delivered flag) for diagnostics |
1258
+ | `canvas://skills` | Index of bundled agent skills shipped with the install. Each skill is also addressable as `canvas://skills/<name>` (e.g. `canvas://skills/web-artifacts-builder`) and returns the full SKILL.md. Read this resource first to discover companion workflows the canvas is built to support. |
1259
+
1260
+ ### Node AX Interactions (capability-gated)
1261
+
1262
+ Eligible nodes can emit one normalized, validated AX interaction that maps onto an
1263
+ AX operation — work item, evidence, approval, review, focus, steering, event,
1264
+ elicitation, or mode request. One envelope, many transports:
1265
+
1266
+ This is the **agent-native nodes** model: existing canvas node types become
1267
+ interactive agent controls when their AX capabilities allow it. Do not describe
1268
+ this as a separate node type; it is a capability layer on top of markdown,
1269
+ status, HTML, json-render, graph, web-artifact, MCP app, and other supported
1270
+ nodes.
1271
+
1272
+ - **Endpoint:** `POST /api/canvas/ax/interaction` with
1273
+ `{ type, sourceNodeId, payload }` (MCP: `canvas_ax_interaction`; CLI:
1274
+ `pmx-canvas ax interaction`). Returns `{ ok, primitive }` or
1275
+ `{ ok: false, code }` if the node type/metadata disallows the type.
1276
+ - **Capabilities:** each node type has a default capability set (a ceiling). A
1277
+ node may opt in or narrow via `data.axCapabilities` (`{ enabled, allowed }`),
1278
+ clamped to the ceiling — a node can never escalate beyond its type's ceiling.
1279
+ `html` / `html-primitive`, `mcp-app`, and internal `prompt` / `response` nodes
1280
+ are **disabled by default** (opt-in).
1281
+ - **Transports:** native node controls call the endpoint directly. Sandboxed
1282
+ surfaces emit via a nonce-tagged `postMessage` the parent canvas validates
1283
+ before submitting: `html` / `html-primitive` nodes (when opted in) call
1284
+ `window.PMX_AX.emit(type, payload)`; **json-render / graph** viewers forward a
1285
+ spec action named after an AX type (e.g. `on.press → { action:
1286
+ "ax.work.create", params }`, `sourceSurface: 'json-render'`); web-artifact
1287
+ **`mcp-app`** nodes use the same parent bridge; external MCP app frames
1288
+ (`mode: "ext-app"`) can emit through an injected `window.PMX_AX.emit` with
1289
+ Promise acknowledgements, but do not get the read-state bridge. The server
1290
+ re-validates capabilities regardless of transport — bridges are convenience,
1291
+ not a trust boundary.
1292
+ - **Delivery (adapterless):** `canvas://ax-pending-steering` /
1293
+ `canvas_ax_delivery { action: "claim" }` return two things, both loop-safe (a consumer never
1294
+ receives items it originated):
1295
+ - `pending` — undelivered **steering** (directives). Act, then acknowledge with
1296
+ `canvas_ax_delivery { action: "mark" }`.
1297
+ - `pendingActivity` — open canvas-bound items **awaiting the agent** (open work
1298
+ items, pending approval gates / elicitations / mode requests), usually created
1299
+ by the human in the browser. These are **state, not steering**: don't
1300
+ `canvas_ax_delivery { action: "mark" }` them — resolve each via its gate/work call
1301
+ (`canvas_ax_gate { kind: "approval", action: "resolve" }` /
1302
+ `canvas_ax_gate { kind: "elicitation", action: "resolve" }` /
1303
+ `canvas_ax_gate { kind: "mode", action: "resolve" }` /
1304
+ `canvas_ax_work { action: "update" }`).
1305
+ - **Contract:** every AX mutation fires `ax-state-changed`, so MCP clients that
1306
+ **subscribe** to resources are pushed `canvas://ax-work` / `canvas://ax-context`
1307
+ live. Clients that **poll** instead should poll `canvas_ax_delivery { action: "claim" }` —
1308
+ `pendingActivity` is how non-steering browser changes reach them. Only steering
1309
+ flows through the claim/ack queue.
1310
+ - **Steering is gated, not pushed (and does NOT wake the agent).** A surface button
1311
+ that emits `ax.steer` *records/queues* a steer (the `ok:true` emit ack means
1312
+ "recorded", not "the agent woke") — it does NOT interrupt or notify the active
1313
+ session. With a prompt-injecting host adapter (e.g. Copilot), it reaches the next
1314
+ turn only when (1) the **pin/focus gate is open** (something pinned or focused — so
1315
+ keep a steering board pinned, or have its button also emit `ax.focus.set` on
1316
+ itself), (2) a **human message** fires the turn, and (3) the agent **acts then acks**
1317
+ (`canvas_ax_delivery { action: "mark" }`), or the steer re-injects every gated turn.
1318
+ Immediate wake (turning a queued steer into a visible turn) is **host-adapter-owned**
1319
+ — the adapter must drain `canvas_ax_delivery { action: "claim" }` and call its native
1320
+ send. So a steering button should label itself honestly ("queued for the agent's next
1321
+ turn"), never imply it steers the agent *now*. `GET /api/canvas/ax/context?consumer=<id>`
1322
+ adds a compact, loop-safe `delivery: { pendingSteering, totalPending, omittedPending,
1323
+ pendingActivity }` lead block an adapter injects un-truncated; `pendingSteering` is
1324
+ **newest-first** (most recent first), capped at 10, so a fresh steer is visible even
1325
+ behind a backlog, and the counts say how many more to drain from the FIFO
1326
+ `canvas_ax_delivery { action: "claim" }` queue (which stays oldest-first).
1327
+ - **Activity ingestion (bidirectional board):** a host adapter forwards the agent's
1328
+ tool/session events with `canvas_ingest_activity` (standalone; HTTP `POST /api/canvas/ax/activity`)
1329
+ and the board auto-reacts — `failure`/`error` (or `outcome:"failure"`) → a blocked
1330
+ work item + a review finding + `logs` evidence; `tool-result` + `outcome:"success"` →
1331
+ `tool-result` evidence; everything else records a timeline event only. Override or
1332
+ suppress per call via `reactions` (`{ workItem: false }`, `{ review: { severity } }`, …).
1333
+ - **Blocking gates (gates that actually gate):** `canvas_ax_gate` is the request →
1334
+ await → resolve machine. After `{ action: "request" }`, call
1335
+ `canvas_ax_gate { kind, action: "await", id, timeoutMs }` (HTTP
1336
+ `GET /api/canvas/ax/<kind>/<id>?waitMs=`) to BLOCK until the human resolves it in the
1337
+ browser or the timeout elapses (`timeoutMs` 0 = immediate read; ≤120000). Use this to
1338
+ pause real work on a human decision instead of polling.
1339
+ - **Elicitation / mode:** request structured human input
1340
+ (`canvas_ax_gate { kind: "elicitation", action: "request" }` →
1341
+ `canvas_ax_gate { kind: "elicitation", action: "resolve" }`) or a workflow
1342
+ mode transition (`canvas_ax_gate { kind: "mode", action: "request" }` →
1343
+ `canvas_ax_gate { kind: "mode", action: "resolve" }`); both are canvas-bound and snapshotted.
1344
+ - **Commands:** invoke a registry command — `pmx.plan`, `pmx.execute`,
1345
+ `pmx.promote-context`, `pmx.summarize`, `pmx.review` — via
1346
+ `canvas_invoke_command` (standalone; HTTP `POST /api/canvas/ax/command`; CLI
1347
+ `pmx-canvas ax command invoke`; envelope `ax.command.invoke`). Unknown names
1348
+ are rejected; an invocation records an `agent-event` of kind `command`.
1349
+ - **Policy:** a canvas-bound, snapshotted tool/prompt policy
1350
+ (`tools.allowed|excluded|approvalRequired`, `prompt.systemAppend|mode`, and the
1351
+ **scope fence** `scope: { nodeIds, padding }`) read into `canvas://ax-context`. Patch it
1352
+ with `canvas_ax_state { action: "set-policy" }` (HTTP `GET|POST /api/canvas/ax/policy`;
1353
+ CLI `pmx-canvas ax policy get|set`); patches merge and are normalized server-side.
1354
+ While a fence is set, your canvas WRITES outside it (nodes not in the set, new nodes
1355
+ outside the fenced box, arrange/clear/restore) are refused with HTTP 403 naming the
1356
+ reason; reads are never fenced. The fence is the human's: `set-policy` does not take
1357
+ `scope`, and an HTTP policy call that includes it is refused (403). Read `policy.scope`
1358
+ before writing and ask the human to widen it rather than retrying.
1359
+ - **External steering (no session):** a writer with no attached session is shown passively —
1360
+ a top-bar indicator (avatars, "N writers · M ops"), a click-to-open activity feed (one row
1361
+ per write: "Created markdown “…”" under the writer's label, per-writer filters, inline Veto
1362
+ on your pending explicit intents) and a connected-writers sheet. Pure visibility: pmx-canvas
1363
+ is local-first and veto/ghosting is the safety model, not permissions. The feed is
1364
+ `activity` on `GET /api/canvas/ax/presence` (`canvas_ax_state { action: "presence" }`).
1365
+ - **Session receipt:** attaching a session (`set-presence { attached: true }` or a
1366
+ `session-start` activity) over a non-empty board saves a `Before session · <label> · HH:MM`
1367
+ snapshot; ending it (`attached: false`, `session-end`, or idle expiry) emits one
1368
+ `agent-session-ended` SSE frame with `label`, `endedAt`, `snapshot`, `context` and counts
1369
+ (`items`, `done`, `cancelled`, `rejected`, `held`). Changed top-level endings show a receipt;
1370
+ worker and unchanged endings stay in History, and qualifying endings merge into an open receipt.
1371
+ **See change** opens one stacked Before/After; whole-card Undo preserves geometry and links.
1372
+ Receipt Undo sends `preserveHumanEdits: true` to `restore-node`, so the server skips latest-human
1373
+ edits even when the browser is stale. Agent callers can pass that option to `canvas_snapshot`
1374
+ too: `restored: false, skipped: "human-edit"` is not an undone card. Receipt Unpin only removes
1375
+ its recorded pins, never re-pins them. Reopen a receipt's board before Undo or comparison;
1376
+ Home dismisses it. End your session explicitly so the receipt is prompt. The human's
1377
+ *Start agent session* button is the same attach with `source: "browser"`.
1378
+ - **Human collaborators / user wins:** every open workbench tab is a presence (`GET
1379
+ /api/canvas/human-presence`); a node a human is dragging is locked — an agent write to it is
1380
+ refused with 409 until release (requeue and retry), and a pending explicit intent on it is
1381
+ vetoed with a `yield` timeline event.
1382
+ - **Unattended approvals:** a pending approval gate carries a TTL (`ttlMs` on the request,
1383
+ default 5 min). If the human does not answer in time it resolves to `held` — a
1384
+ non-approval: do not proceed — and a `policy` agent-event explains why. The human can
1385
+ reopen it from the session panel; an `await` on the gate returns when it leaves
1386
+ `pending`, so treat any status other than `approved` as "do not proceed".
1387
+
1388
+ Interactions request PMX-AX primitives only — never arbitrary shell, tool, MCP,
1389
+ or host execution.
1390
+
1391
+ #### Where AX can be used — node capability matrix
1392
+
1393
+ AX interactions are gated per node type. The lists below are each type's **ceiling**
1394
+ — `data.axCapabilities.allowed` can NARROW it, never escalate beyond it.
1395
+
1396
+ **Enabled by default** (no opt-in needed — an agent/native control can emit straight away):
1397
+
1398
+ | Node type | Allowed AX interaction types |
1399
+ |-----------|------------------------------|
1400
+ | `markdown` | `ax.steer`, `ax.work.create`, `ax.work.update`, `ax.evidence.add`, `ax.command.invoke`, `ax.event.record` |
1401
+ | `context` | `ax.focus.set`, `ax.steer`, `ax.evidence.add`, `ax.command.invoke`, `ax.event.record` |
1402
+ | `status` | `ax.work.create`, `ax.work.update`, `ax.approval.request`, `ax.mode.request`, `ax.event.record` |
1403
+ | `file` | `ax.evidence.add`, `ax.review.add`, `ax.focus.set`, `ax.event.record` |
1404
+ | `json-render` | `ax.work.create`, `ax.work.update`, `ax.evidence.add`, `ax.elicitation.request`, `ax.event.record` |
1405
+ | `graph` | `ax.evidence.add`, `ax.focus.set`, `ax.event.record` |
1406
+ | `ledger` | `ax.evidence.add`, `ax.event.record` |
1407
+ | `trace` | `ax.evidence.add`, `ax.event.record` |
1408
+ | `image` | `ax.evidence.add`, `ax.review.add` |
1409
+ | `diff` | `ax.evidence.add`, `ax.review.add`, `ax.focus.set`, `ax.event.record` |
1410
+ | `mermaid` | `ax.steer`, `ax.work.create`, `ax.evidence.add`, `ax.command.invoke`, `ax.event.record` |
1411
+ | `webpage` | `ax.evidence.add`, `ax.review.add`, `ax.focus.set`, `ax.event.record` |
1412
+ | `group` | `ax.focus.set`, `ax.work.create`, `ax.command.invoke`, `ax.event.record` |
1413
+
1414
+ **Opt-in** — set `axCapabilities.enabled = true` (MCP: pass `axCapabilities` to
1415
+ `canvas_node { action: "add", type: "html" }` / `canvas_node { action: "update" }`. HTTP:
1416
+ `axCapabilities` **and** the `html` body are accepted **top-level on both `POST /api/canvas/node`
1417
+ and `PATCH /api/canvas/node/<id>`**, or nested under `data` — both work, top-level wins):
1418
+
1419
+ | Node type | Allowed AX interaction types |
1420
+ |-----------|------------------------------|
1421
+ | `html` / `html-primitive` | the full set: `ax.work.create`, `ax.work.update`, `ax.steer`, `ax.approval.request`, `ax.review.add`, `ax.evidence.add`, `ax.focus.set`, `ax.elicitation.request`, `ax.mode.request`, `ax.command.invoke`, `ax.event.record` |
1422
+ | `mcp-app` (incl. **web-artifact**) | `ax.event.record`, `ax.evidence.add`, `ax.work.create`, `ax.work.update`, `ax.focus.set`, `ax.elicitation.request` |
1423
+
1424
+ **Never (anchor-only):** internal `prompt` / `response` thread nodes — `ax.event.record`
1425
+ only, no human-facing emit.
1426
+
1427
+ The 11 interaction types and what they create: `ax.work.create` / `ax.work.update`
1428
+ (work-queue items; status is exactly one of `todo`, `in-progress`, `blocked`, `done`,
1429
+ `cancelled` — **hyphens, not underscores**; `POST`/`PATCH /api/canvas/ax/work` reject an
1430
+ unknown token like `in_progress` with `400`), `ax.evidence.add`
1431
+ (timeline evidence), `ax.review.add` (review annotation), `ax.focus.set` (agent focus
1432
+ pointer), `ax.steer` (a steering message delivered to the agent), `ax.approval.request`
1433
+ (approval gate), `ax.elicitation.request` (structured human input), `ax.mode.request`
1434
+ (plan/execute/autonomous transition), `ax.command.invoke` (registry command), and
1435
+ `ax.event.record` (diagnostic agent-event).
1436
+
1437
+ #### Building an AX surface in the canvas (emit + reflect)
1438
+
1439
+ AX surfaces are **composable** — you can build a live work board, review board, or
1440
+ inbox as a canvas node that BOTH emits AX interactions AND renders the current AX
1441
+ state. The read side mirrors the write side:
1442
+
1443
+ - **Opt in** (html/mcp-app are off by default): create with
1444
+ `canvas_node({ action: "add", type: "html", html, axCapabilities: { enabled: true, allowed: ["ax.work.create","ax.work.update"] } })`,
1445
+ or flip an existing node on with
1446
+ `canvas_node({ action: "update", id, axCapabilities: { enabled: true, allowed: [...] } })`.
1447
+ json-render / graph nodes are enabled by default.
1448
+ - **Emit (write):** in `html`, call `window.PMX_AX.emit("ax.work.create", { title })`;
1449
+ in `json-render`, bind a control action named after the AX type
1450
+ (`on: { press: { action: "ax.work.create", params: { title } } }`).
1451
+ - **Confirm (#55):** for `html` / `html-primitive` and PMX_AX-enabled `mcp-app`
1452
+ surfaces, `emit` returns a Promise that resolves with the result once the
1453
+ canvas acks it, so a button can self-confirm: `const r = await
1454
+ window.PMX_AX.emit(...); if (r.ok) showQueued();`. You can also
1455
+ `window.PMX_AX.on('ack', cb)` or listen for the `pmx-ax-ack` event. (Falls back
1456
+ to an `ax-ack-timeout` result after 10s, so `await` never hangs.)
1457
+ - **Reflect (read):** the canvas seeds the surface with a compact AX snapshot at
1458
+ load (the same shape as `GET /api/canvas/ax/surface-snapshot`) and live-updates it
1459
+ as AX state changes. Works on all three authored surface types:
1460
+ - `html` / `html-primitive`: read `window.PMX_AX.state` (`{ focus, workItems,
1461
+ approvalGates, reviewAnnotations, elicitations, modeRequests, policy }`) and
1462
+ subscribe to the `pmx-ax-update` event:
1463
+ `window.addEventListener("pmx-ax-update", e => render(e.detail))`.
1464
+ - `json-render` / `graph`: the snapshot is bound under `/ax`, so a spec reads
1465
+ `{ "$state": "/ax/workItems" }` and it stays live as work items change.
1466
+ - `web-artifact` (mcp-app): the same `window.PMX_AX.state` + `pmx-ax-update` bridge
1467
+ is injected at the `/artifact` route once the node opts in — author the React app
1468
+ against `window.PMX_AX`, not direct `fetch()` (the artifact iframe is sandboxed
1469
+ opaque-origin, so it can't call the API directly).
1470
+
1471
+ Minimal html work board (drop-in via `canvas_node { action: "add", type: "html" }`, `axCapabilities.enabled: true`):
1472
+
1473
+ ```html
1474
+ <button id="add">+ Task</button> <span id="ok"></span>
1475
+ <ul id="q"></ul>
1476
+ <script>
1477
+ function render(s){ document.getElementById('q').innerHTML =
1478
+ ((s&&s.workItems)||[]).map(w => '<li>['+w.status+'] '+w.title+'</li>').join(''); }
1479
+ document.getElementById('add').onclick = async () => {
1480
+ const r = await window.PMX_AX.emit('ax.work.create',{title:'New task'});
1481
+ document.getElementById('ok').textContent = r && r.ok ? 'queued ✓' : 'failed'; // #55 self-confirm
1482
+ };
1483
+ render(window.PMX_AX && window.PMX_AX.state);
1484
+ window.addEventListener('pmx-ax-update', e => render(e.detail));
1485
+ </script>
1486
+ ```
1487
+
1488
+ This is the right home for a deliberate, interactive AX experience — not the
1489
+ native node buttons. Any agent (via MCP/SDK) can also create/update the same work
1490
+ items, and the board reflects them live.
1491
+
1492
+ > **Authoring an AX HTML node? Use the blessed, copy-paste-safe recipe in
1493
+ > [`ax-html-control-surface.md`](ax-html-control-surface.md)**
1494
+ > and avoid the common footguns:
1495
+ > - **The iframe is sandboxed opaque-origin** (no `allow-same-origin`): `localStorage`,
1496
+ > `sessionStorage`, and cookies **throw** and will halt your startup script, making the
1497
+ > node look inert. Keep state in plain JS variables / `window.PMX_AX.state`, or wrap any
1498
+ > storage access in `try/catch`.
1499
+ > - **`window.PMX_AX.emit` is async — `await` it** (or use `.then`/`window.PMX_AX.on('ack')`);
1500
+ > don't read a result synchronously. It's injected only when the node is opted in
1501
+ > (`axCapabilities.enabled = true`).
1502
+ > - **`ax.steer` is recorded, not delivered** — a successful emit means the steer is
1503
+ > *queued for the agent's next turn*, not that the agent woke (see "Steering is gated").
1504
+ > Label steering buttons accordingly.
1505
+
1506
+ > Security note: an AX-enabled surface can READ the whole canvas AX board (all
1507
+ > work items, focus, approval gates, etc. — human review comment text is redacted),
1508
+ > while its EMITS are clamped to its own node. Under the single-workspace
1509
+ > local-trust model this is fine, but don't embed untrusted third-party scripts in
1510
+ > an AX-enabled surface.
1511
+
1512
+ ### Reading Spatial Intent
1513
+
1514
+ The `canvas://spatial-context` resource reveals how the human has organized information:
1515
+
1516
+ - **Proximity clusters** — Nodes placed near each other form implicit groups. If the human
1517
+ placed three files next to each other, those files are related in their mental model.
1518
+ - **Reading order** — Nodes sorted top-left to bottom-right, following natural reading flow.
1519
+ This implies sequence or priority.
1520
+ - **Pinned neighborhoods** — For each pinned node, nearby unpinned nodes are listed. These
1521
+ are the human's implicit context — things they consider related to what they pinned.
1522
+ - **Annotations** — Shared markup is summarized by ID, label, text, target and bounds, e.g. an
1523
+ annotation over a node or empty canvas region. Use WebView (`canvas_webview { action: "start" }` +
1524
+ `canvas_webview { action: "evaluate" }`/`canvas_screenshot`) when you need to see whether the mark is an
1525
+ arrow, line, circle, or other drawn shape. Remove known annotations with
1526
+ `canvas_view { action: "remove-annotation" }`; otherwise use WebView to identify the mark first.
1527
+ - **Board density matters** — On a dense board, spatial context can still read like one large
1528
+ gallery unless groups and spacing separate the major regions clearly.
1529
+
1530
+ Use this spatial intelligence to understand what the human is thinking without them having to
1531
+ explain it explicitly.
1532
+
1533
+ ## HTTP API Reference
1534
+
1535
+ All POST/PATCH endpoints accept `Content-Type: application/json`. Default base URL: `http://localhost:4313`
1536
+
1537
+ | Method | Endpoint | Purpose |
1538
+ |--------|----------|---------|
1539
+ | GET | `/api/canvas/state` | Full canvas state |
1540
+ | POST | `/api/canvas/node` | Add node |
1541
+ | PATCH | `/api/canvas/node/<id>` | Update node |
1542
+ | DELETE | `/api/canvas/node/<id>` | Remove node |
1543
+ | POST | `/api/canvas/edge` | Add edge |
1544
+ | DELETE | `/api/canvas/edge` | Remove edge (`{ "edge_id": "..." }`) |
1545
+ | GET | `/api/canvas/snapshots` | List snapshots |
1546
+ | POST | `/api/canvas/snapshots` | Save snapshot |
1547
+ | POST | `/api/canvas/snapshots/<id>` | Restore snapshot |
1548
+ | DELETE | `/api/canvas/snapshots/<id>` | Delete snapshot |
1549
+ | POST | `/api/canvas/context-pins` | Set, add or remove pinned nodes (20 live pins; removal IDs are not capped) |
1550
+ | GET | `/api/canvas/pinned-context` | Get current pins with neighborhood context |
1551
+ | GET | `/api/canvas/search?q=...` | Search nodes |
1552
+ | POST | `/api/canvas/json-render` | Create a native json-render node |
1553
+ | POST | `/api/canvas/json-render/stream` | Create/append a streaming json-render node (SpecStream patches) |
1554
+ | POST | `/api/canvas/graph` | Create a native graph node |
1555
+ | GET | `/api/canvas/schema` | Get running-server create schemas, examples, and json-render catalog metadata |
1556
+ | POST | `/api/canvas/schema/validate` | Validate a json-render spec or graph payload without creating a node |
1557
+ | GET | `/api/canvas/json-render/view?nodeId=...` | View a native json-render or graph node |
1558
+ | POST | `/api/canvas/diagram` | Create an Excalidraw external app node |
1559
+ | POST | `/api/canvas/mcp-app/open` | Open a tool-backed MCP app node |
1560
+ | POST | `/api/canvas/web-artifact` | Build a bundled web artifact and optionally open it on canvas |
1561
+ | POST | `/api/canvas/group` | Create group |
1562
+ | POST | `/api/canvas/group/add` | Add nodes to group |
1563
+ | POST | `/api/canvas/group/ungroup` | Ungroup (dissolve: children released, frame removed) |
1564
+ | POST | `/api/canvas/arrange` | Auto-arrange |
1565
+ | POST | `/api/canvas/focus` | Center viewport on node |
1566
+ | POST | `/api/canvas/fit` | Fit viewport to canvas bounds or selected nodes |
1567
+ | POST | `/api/canvas/clear` | Clear canvas |
1568
+ | POST | `/api/canvas/update` | Batch update positions |
1569
+ | GET | `/api/canvas/spatial-context` | Spatial clusters and reading order |
1570
+ | POST | `/api/canvas/undo` | Undo |
1571
+ | POST | `/api/canvas/redo` | Redo |
1572
+ | GET | `/api/canvas/history` | Mutation history |
1573
+ | GET | `/api/canvas/code-graph` | File dependency graph |
1574
+ | GET | `/api/canvas/memory-graph` | Summary-only board/card projection; optional focus and depth |
1575
+ | GET | `/api/workbench/events` | SSE event stream |
1576
+
1577
+ ## Workflow Patterns
1578
+
1579
+ These are **operational recipes** — how to sequence canvas calls for a few
1580
+ recurring shapes of work. They are not the project's use cases (those live
1581
+ in the README and are intentionally non-exhaustive). The patterns here exist
1582
+ to make the agent's tool-call sequencing concrete: which MCP tool fires
1583
+ when, what to pin, when to read `canvas://pinned-context`, when to snapshot.
1584
+
1585
+ ### Responding to Pinned Context
1586
+
1587
+ When the human pins nodes, they're telling you what matters. This is the most important
1588
+ collaboration pattern:
1589
+
1590
+ 1. Read `canvas://pinned-context` — get the content of pinned nodes and their neighborhoods
1591
+ 2. Read `canvas://spatial-context` — understand how the whole canvas is organized
1592
+ 3. Optionally read `canvas://summary` — see pinned nodes in the context of the full canvas
1593
+ 4. Interpret what you find:
1594
+ - What types are the pinned nodes? (files = code focus, status = progress, markdown = concepts)
1595
+ - Are they clustered together (single focus) or spread across the canvas (multi-topic)?
1596
+ - What unpinned nodes are nearby? These are the human's implicit context
1597
+ - What's the reading order? Top-left to bottom-right suggests sequence or priority
1598
+ - If an `mcp-app` node is pinned, treat it as “important but partially opaque” and use nearby
1599
+ graph/file/markdown nodes to recover the missing semantic detail
1600
+ 5. Respond by summarizing what you see, what you think the human is focusing on, and ask
1601
+ if they'd like you to act on it (add related nodes, investigate further, etc.)
1602
+
1603
+ **When to use `pinned-context` vs `spatial-context`:**
1604
+ - `canvas://pinned-context` — "what did the human explicitly pin, and what's near those pins?"
1605
+ - `canvas://spatial-context` — "how is the entire canvas organized spatially?"
1606
+ - Read both when you need the full picture; read just `pinned-context` for quick pin checks.
1607
+
1608
+ ### Investigation Board
1609
+
1610
+ When debugging, lay out evidence spatially to see connections:
1611
+
1612
+ 1. Create a root node describing the bug/issue
1613
+ 2. Add evidence nodes: logs, stack traces, relevant code files (use `file` nodes for source)
1614
+ 3. Connect evidence to root with `references` edges
1615
+ 4. Add a hypothesis node, connect with `flow` edge
1616
+ 5. As you investigate, add findings and update connections
1617
+ 6. Use `status` nodes to track what you've checked
1618
+ 7. Group evidence nodes together, and investigation tasks together
1619
+ 8. Arrange with `flow` layout, then fine-tune positions if needed
1620
+
1621
+ ```
1622
+ Bug Report ──references──> Error Logs
1623
+ │ │
1624
+ │ references
1625
+ │ ▼
1626
+ └──flow──> Hypothesis ──flow──> Fix
1627
+ │
1628
+ flow
1629
+ ▼
1630
+ Verification
1631
+ ```
1632
+
1633
+ ### Architecture Diagram
1634
+
1635
+ Show system components and how they interact:
1636
+
1637
+ 1. Create `markdown` nodes for each service/component (include port, tech stack in content)
1638
+ 2. Use `flow` edges for data flow, `depends-on` for dependencies — always label edges
1639
+ 3. Group related services with `canvas_group { action: "create" }` (e.g., "Application Services", "Data Layer")
1640
+ 4. Use colors: green for healthy, yellow for degraded, red for down
1641
+ 5. Arrange with `grid` layout initially
1642
+ 6. For tiered architectures, fine-tune with explicit `x`/`y` via `canvas_node { action: "update" }` to show
1643
+ layers (e.g., gateway at top, services in middle, data stores at bottom)
1644
+ 7. Connect pipeline stages with `flow` edges where applicable
1645
+
1646
+ ### Task Plan with Dependencies
1647
+
1648
+ Show useful work deliberately, not a card per task or event. Ordinary work cards are authored
1649
+ node data, not AX work items. They require no session and create no approval or execution.
1650
+
1651
+ 1. Create a `status` node with `data.workState`: `todo`, `in-progress`, `blocked`, `done` or
1652
+ `cancelled`. Use string `data.detail` for context and optional `data.progress` from 0 to 100.
1653
+ Progress is independent of state: Done does not imply 100%; `null` clears progress.
1654
+ 2. Update the same card through `canvas_node { action: "update", id, data: { ... } }`.
1655
+ Include `expectedBoardId` and `expectedContentRevision` when an edit depends on a prior read.
1656
+ 3. Connect steps, dependencies, alternatives and intentional loops with existing relations.
1657
+ A drawn loop does not execute. Group distinct phases when useful, then inspect readability.
1658
+ 4. Humans use **New work card** from the background menu and **Edit/Save/Cancel** inside the card.
1659
+ Drafts and pending/refused saves survive focus-mode open/close, but not a page reload.
1660
+ The editor's temporary growth does not resize the saved card.
1661
+
1662
+ MCP creation:
1663
+
1664
+ ```json
1665
+ {"action":"add","type":"status","title":"Verify","data":{"workState":"todo","detail":"Run browser checks"}}
1666
+ ```
1667
+
1668
+ CLI creation uses structured batch data, not `node add --data` (that flag is for graph series):
1669
+
1670
+ ```bash
1671
+ pmx-canvas batch --json '[{"op":"node.add","args":{"type":"status","title":"Verify","data":{"workState":"todo","detail":"Run browser checks"}}}]'
1672
+ ```
1673
+
1674
+ Without `data.workState`, status cards retain their phase/tool display. Session-task linking
1675
+ is not provided by these authored cards. Questions can be drawn or written beside the plan;
1676
+ the human can answer in the external agent host's composer, without a PMX reply card.
1677
+
1678
+ ### Live task cards
1679
+
1680
+ A live card presents an existing AX work item; it is not another task or an authored
1681
+ work plan. Read the task first with `canvas_ax_work { action: "list" }` on its owning
1682
+ board, then create a `status` presentation using that task's actual ID:
1683
+
1684
+ ```json
1685
+ {"action":"add","type":"status","data":{"workItemId":"<existing-task-id>"}}
1686
+ ```
1687
+
1688
+ The owner defaults to the current board. To present a task from another board, supply
1689
+ its explicit `data.workItemBoardId` too. Both task ID and owner are fixed at creation;
1690
+ moving the card does not transfer the task. Create through the current board owner
1691
+ (or the Amp plugin's bound board), not `destinationBoardId` background creation.
1692
+
1693
+ Omit `title`, `content`, `workState`, `detail`, `progress`, `axWorkStatus`,
1694
+ `liveWorkAvailable` and `liveWorkSource` from creation and updates, including nested
1695
+ data fields. The server derives title, detail, status and source from the task; these
1696
+ are read-only display fields. Update the underlying work item on its owning board with
1697
+ `canvas_ax_work { action: "update", id: "<existing-task-id>", status: "in-progress" }`.
1698
+ Position and size still belong to the card. Deleting the presentation does not cancel
1699
+ or delete the task. If the task is unavailable, the card displays that fact rather than
1700
+ becoming an editable authored card. To change the binding, create a new presentation.
1701
+
1702
+ ### Code Exploration
1703
+
1704
+ Understand a codebase by visualizing file relationships:
1705
+
1706
+ 1. Add `file` nodes for key source files (content auto-loads and live-updates)
1707
+ 2. The code graph auto-detects imports and creates `depends-on` edges automatically — you
1708
+ don't need to manually add import-based edges. You can still add manual edges for
1709
+ conceptual relationships beyond imports (e.g., "middleware validates using jwt")
1710
+ 3. Read `canvas://code-graph` for dependency analysis: central files, isolated files
1711
+ 4. Group related files with `canvas_group { action: "create" }` (e.g., "Auth Module", "API Routes")
1712
+ 5. Pin important files so the human sees them highlighted
1713
+ 6. Arrange with `grid` layout after adding files
1714
+
1715
+ ### Interactive Artifact Builds
1716
+
1717
+ When the user wants a real browser app instead of static notes:
1718
+
1719
+ 1. Use the `web-artifacts-builder` skill if the UI needs React state, routing, or shadcn-style components
1720
+ 2. Build with `canvas_app { action: "build-artifact" }`
1721
+ 3. Keep `openInCanvas` enabled unless the user explicitly wants only the output file
1722
+ 4. Use the returned `projectPath` as the reusable source workspace for iterations
1723
+ 5. Use the returned `path` for sharing or for opening the generated artifact outside the canvas
1724
+ 6. Use the `playwright-cli` skill if you need to verify the artifact route or embedded app behavior in a browser
1725
+
1726
+ ### Status Dashboard
1727
+
1728
+ Monitor ongoing processes:
1729
+
1730
+ 1. Create `status` nodes for each metric/process
1731
+ 2. Use semantic colors: green=passing, yellow=running, red=failing, gray=queued
1732
+ 3. Connect sequential pipeline stages with `flow` edges (label: "then", "triggers")
1733
+ 4. Update nodes in-place as state changes using `canvas_node { action: "update" }` — never delete
1734
+ and recreate, as that loses position and edges
1735
+ 5. Arrange with `grid` layout
1736
+ 6. The human sees real-time updates via SSE
1737
+
1738
+ ### Before/After Comparison
1739
+
1740
+ Show two states side by side for the human to compare:
1741
+
1742
+ 1. Take a snapshot before changes: `canvas_snapshot { action: "save" }` with name "before-X"
1743
+ 2. Make changes to the canvas
1744
+ 3. Use `canvas_snapshot { action: "diff", snapshot }` to show what changed
1745
+ 4. Or: create two groups ("Before" and "After") with corresponding nodes
1746
+
1747
+ ### Save and Start Fresh
1748
+
1749
+ When the human wants to explore a different approach without losing current work:
1750
+
1751
+ 1. **First**, save the current state: `canvas_snapshot { action: "save" }` with a descriptive name
1752
+ 2. **Then** clear: `canvas_view { action: "clear" }` (never clear without snapshotting first)
1753
+ 3. Set up the new workspace with initial nodes
1754
+ 4. Tell the human the snapshot name and that `canvas_snapshot { action: "restore" }` can bring everything back
1755
+
1756
+ ## Best Practices
1757
+
1758
+ 1. **Start once, reuse always.** Don't restart the canvas for each task. Build on the
1759
+ existing canvas state.
1760
+
1761
+ 2. **Titles are scannable.** Keep titles short (3-6 words). Put details in content.
1762
+
1763
+ 3. **Label every edge.** Unlabeled edges lose meaning. "depends on", "calls", "blocks"
1764
+ are all more useful than a bare arrow.
1765
+
1766
+ 4. **Auto-arrange after batch adds.** When adding multiple nodes, call
1767
+ `canvas_view { action: "arrange" }` once at the end, not after each node.
1768
+
1769
+ 5. **Update in place.** Use `canvas_node { action: "update" }` to change status, content, or
1770
+ color. Don't delete and recreate — that loses position and edges.
1771
+
1772
+ 6. **Clean up.** Remove nodes that are no longer relevant. A cluttered canvas is worse
1773
+ than no canvas.
1774
+
1775
+ 7. **Read before writing.** Check `canvas://layout` or `canvas_query { action: "layout" }` before
1776
+ adding nodes to avoid duplicates and understand the current state.
1777
+
1778
+ 8. **Use pinning.** When you want the human to focus on specific nodes, pin them.
1779
+ When the human pins nodes, read `canvas://pinned-context` to see what they care about.
1780
+ Prefer one intent-setting markdown pin plus a small set of concrete output pins over pinning a
1781
+ whole gallery.
1782
+
1783
+ 9. **Snapshot before destructive changes.** Before clearing or major reorganization,
1784
+ save a snapshot so you can restore if needed.
1785
+
1786
+ 10. **Prefer MCP tools over HTTP.** When running as an MCP server, use the canvas tools
1787
+ directly rather than shelling out to curl. The tools handle all the details.
1788
+
1789
+ 11. **Use groups for visual organization.** When 3+ nodes are related, wrap them in a
1790
+ group to make the relationship visible at a glance.
1791
+
1792
+ 12. **Use file nodes for source code.** File nodes auto-watch for changes and update
1793
+ live. This is better than pasting code into markdown nodes.
1794
+
1795
+ 13. **Comparison boards need structure, not just content.** For galleries and evaluations, use a
1796
+ named group, give the area breathing room, and keep related charts/artifacts inside that
1797
+ region instead of letting them drift into the main cluster.
1798
+
1799
+ 14. **Capture external app IDs immediately.** For Excalidraw and other `mcp-app` nodes, store the
1800
+ returned node ID or pin the node right away. Search/title rediscovery is less reliable there
1801
+ than for markdown, graph, or file nodes.
1802
+
1803
+ 15. **Pair app nodes with explainers.** If you create or pin a web artifact or Excalidraw node,
1804
+ add a nearby markdown, graph, or file node that explains what the app is for. This makes
1805
+ pinned context far more useful to later agents.
1806
+
1807
+ ## Persistence
1808
+
1809
+ Canvas state auto-saves to `.pmx-canvas/canvas.db` on every mutation (debounced 500ms). State
1810
+ loads automatically on server start. The SQLite DB is git-committable — spatial knowledge
1811
+ persists across sessions.
1812
+
1813
+ Snapshots, context pins, and large node blobs are stored in the same DB. Web artifacts land in
1814
+ `.pmx-canvas/artifacts/`. Legacy JSON state, snapshot, and blob files are auto-imported into
1815
+ SQLite and renamed to `.bak` on first boot.
1816
+
1817
+ Stop the server or flush/close the SDK before committing `canvas.db`; shutdown checkpoints SQLite
1818
+ WAL data into the DB file.
1819
+
1820
+ Board-owned original attachments are stored in that database until board deletion, survive
1821
+ snapshot restore, and therefore travel with database backups. Agent import reads return a
1822
+ controlled HTTP path and can include inline base64 only for sources at or below 2 MiB. A remote
1823
+ host may need manual transfer. Canvas bundles no converter or OCR and launches no agent
1824
+ automatically; a human explicitly requests processing, reviews draft sections/references/warnings,
1825
+ and commits only while the captured source board is active. Static export v1 never contains
1826
+ original bytes; `includeDerivedText` controls source-linked Markdown separately from
1827
+ `includeFiles`, including after that Markdown is edited.
1828
+
1829
+ ## Real-Time Collaboration
1830
+
1831
+ The canvas supports real-time human-agent collaboration:
1832
+
1833
+ - **Human pins nodes in browser** → agent reads `canvas://pinned-context`
1834
+ - **Agent adds/updates nodes** → human sees changes instantly via SSE
1835
+ - **Human moves/groups nodes** → spatial arrangement communicates intent
1836
+ - **Agent reads spatial context** → understands implicit relationships
1837
+
1838
+ This bidirectional flow means the canvas is a shared workspace, not just an output display.
1839
+ Pay attention to what the human is doing on the canvas — their spatial choices are meaningful.