pmx-canvas 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +3 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +688 -128
  4. package/CHANGELOG.md +69 -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 +251 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1086 -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 +1785 -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 +251 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +111 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +900 -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-cwtsds5d.js +1 -0
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/architectureDiagram-T3A2C74G-b4xc9tpz.js +36 -0
  35. package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-ahp3yxxs.js +132 -0
  36. package/dist/canvas/chunks/c4Diagram-5PPSVZJV-nappnay7.js +10 -0
  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-a428syda.js +1 -0
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-nspsqebj.js +62 -0
  42. package/dist/canvas/chunks/dagre-VZM6K2ZE-8y6177fh.js +4 -0
  43. package/dist/canvas/chunks/diagram-7IWD3JNH-vym028wn.js +30 -0
  44. package/dist/canvas/chunks/diagram-B4RE2ZJO-jddqb8sy.js +3 -0
  45. package/dist/canvas/chunks/diagram-LBJQPF4R-bjwbpkrn.js +24 -0
  46. package/dist/canvas/chunks/diagram-Q27KOJAE-j6ph6hcx.js +24 -0
  47. package/dist/canvas/chunks/diagram-UB23O5K3-p09w8jej.js +41 -0
  48. package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-j975ycq5.js +1 -0
  49. package/dist/canvas/chunks/erDiagram-JOGREHBK-sxp6hjn8.js +85 -0
  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-4tv230y2.js +292 -0
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-mqgmnw9g.js +106 -0
  55. package/dist/canvas/chunks/index-037z5dz2.js +232 -0
  56. package/dist/canvas/chunks/index-081n3har.js +207 -0
  57. package/dist/canvas/chunks/index-5n03dmb9.js +2 -0
  58. package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
  59. package/dist/canvas/chunks/index-60pgzdqe.js +157 -0
  60. package/dist/canvas/chunks/index-6ceya556.js +332 -0
  61. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  62. package/dist/canvas/chunks/index-8rjes9r6.js +2 -0
  63. package/dist/canvas/chunks/index-9022d1n9.js +16 -0
  64. package/dist/canvas/chunks/index-9b02042t.js +2 -0
  65. package/dist/canvas/chunks/index-ap4q4vez.js +2 -0
  66. package/dist/canvas/chunks/index-dyp2nhd3.js +2 -0
  67. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  68. package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
  69. package/dist/canvas/chunks/index-h9ycj8n2.js +2 -0
  70. package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
  71. package/dist/canvas/chunks/index-mergmj97.js +2 -0
  72. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  73. package/dist/canvas/chunks/index-nz8qbd1p.js +2 -0
  74. package/dist/canvas/chunks/index-qhfrazv3.js +2 -0
  75. package/dist/canvas/chunks/index-v119nznk.js +4 -0
  76. package/dist/canvas/chunks/index-vpt37m3d.js +89 -0
  77. package/dist/canvas/chunks/index-y9ma6azy.js +2 -0
  78. package/dist/canvas/chunks/index-zx0j3a2z.js +179 -0
  79. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  80. package/dist/canvas/chunks/infoDiagram-6WML65LV-qv97qq6a.js +2 -0
  81. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  82. package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-sv67zats.js +139 -0
  83. package/dist/canvas/chunks/kanban-definition-27J2QSJJ-k2pfam6p.js +89 -0
  84. package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
  85. package/dist/canvas/chunks/mermaid.core-83r0fdce.js +12 -0
  86. package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-vg2newm1.js +96 -0
  87. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  88. package/dist/canvas/chunks/pegDiagram-VL7TDLO6-k2h7zc5n.js +1 -0
  89. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  90. package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-hg169p1v.js +39 -0
  91. package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-bxtvsq0b.js +7 -0
  92. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  93. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  94. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  95. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  96. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  97. package/dist/canvas/chunks/railroadDiagram-AXF67PYL-njyhcp4p.js +1 -0
  98. package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-2z6v09gm.js +84 -0
  99. package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-15mzam0e.js +40 -0
  100. package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-rbshyd2c.js +162 -0
  101. package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-sg26xwts.js +1 -0
  102. package/dist/canvas/chunks/stateDiagram-OKZ733FA-6eahp98e.js +1 -0
  103. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  104. package/dist/canvas/chunks/swimlanes-SLNWSIFB-ze5db10t.js +1 -0
  105. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  106. package/dist/canvas/chunks/timeline-definition-Z64GVDOM-vnp7jyy8.js +120 -0
  107. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  108. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  109. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  110. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  111. package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-8nxg8wqt.js +78 -0
  112. package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-nj921e7s.js +7 -0
  113. package/dist/canvas/global.css +483 -230
  114. package/dist/canvas/index.js +158 -219
  115. package/dist/canvas/mcp-app.html +4301 -548
  116. package/dist/canvas/mermaid-entry.js +338 -338
  117. package/dist/canvas/surface-theme.css +6 -6
  118. package/dist/json-render/index.css +1 -1
  119. package/dist/json-render/index.js +49 -49
  120. package/dist/types/client/canvas/BarHint.d.ts +5 -3
  121. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  122. package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
  123. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  124. package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
  125. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  126. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  127. package/dist/types/client/canvas/auto-fit.d.ts +4 -5
  128. package/dist/types/client/mermaid-entry.d.ts +1 -2
  129. package/dist/types/client/mermaid-render.d.ts +1 -0
  130. package/dist/types/client/nodes/BoardNode.d.ts +1 -1
  131. package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
  132. package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
  133. package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
  134. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  135. package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
  136. package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
  137. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  138. package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
  139. package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
  140. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  141. package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
  142. package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
  143. package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
  144. package/dist/types/client/state/canvas-store.d.ts +18 -1
  145. package/dist/types/client/state/human-store.d.ts +2 -0
  146. package/dist/types/client/state/intent-bridge.d.ts +5 -2
  147. package/dist/types/client/state/presentation.d.ts +17 -6
  148. package/dist/types/client/state/relation-store.d.ts +11 -0
  149. package/dist/types/client/types.d.ts +1 -1
  150. package/dist/types/json-render/catalog.d.ts +2 -2
  151. package/dist/types/json-render/server.d.ts +2 -1
  152. package/dist/types/server/agent-presence.d.ts +1 -0
  153. package/dist/types/server/ax-interaction.d.ts +1 -0
  154. package/dist/types/server/ax-state-manager.d.ts +10 -2
  155. package/dist/types/server/board-local.d.ts +4 -0
  156. package/dist/types/server/board-move.d.ts +38 -0
  157. package/dist/types/server/canvas-db.d.ts +12 -2
  158. package/dist/types/server/canvas-operations.d.ts +5 -6
  159. package/dist/types/server/canvas-provenance.d.ts +1 -1
  160. package/dist/types/server/canvas-serialization.d.ts +1 -0
  161. package/dist/types/server/canvas-state.d.ts +86 -20
  162. package/dist/types/server/card-relocation.d.ts +6 -0
  163. package/dist/types/server/context-reads.d.ts +3 -1
  164. package/dist/types/server/human-presence.d.ts +1 -1
  165. package/dist/types/server/index.d.ts +26 -10
  166. package/dist/types/server/intent-registry.d.ts +8 -4
  167. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  168. package/dist/types/server/mutation-history.d.ts +7 -5
  169. package/dist/types/server/operations/ops/boards.d.ts +0 -1
  170. package/dist/types/server/operations/registry.d.ts +2 -0
  171. package/dist/types/server/tour-control.d.ts +13 -3
  172. package/dist/types/server/trace-manager.d.ts +1 -0
  173. package/dist/types/shared/ax-kinds.d.ts +6 -0
  174. package/dist/types/shared/ax-surface-protocol.d.ts +8 -9
  175. package/dist/types/shared/human-presence.d.ts +1 -0
  176. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  177. package/dist/types/shared/tour.d.ts +8 -0
  178. package/docs/RELEASE.md +21 -14
  179. package/docs/agent-plugin.md +16 -2
  180. package/docs/amp-plugin.md +271 -0
  181. package/docs/cli.md +42 -3
  182. package/docs/design/Chrome.dc.html +32 -6
  183. package/docs/design/GuestName.dc.html +50 -0
  184. package/docs/design/PluginLogo.svg +7 -0
  185. package/docs/design/Types1.dc.html +3 -2
  186. package/docs/design/Types2.dc.html +1 -1
  187. package/docs/design/Types3.dc.html +1 -1
  188. package/docs/design/WorkCards.dc.html +39 -0
  189. package/docs/design/Zoom.dc.html +37 -60
  190. package/docs/design/canvas.json +17 -1
  191. package/docs/design/plugin-logo.png +0 -0
  192. package/docs/design.md +280 -12
  193. package/docs/evals/agents-md-sections.md +38 -0
  194. package/docs/http-api.md +179 -11
  195. package/docs/mcp.md +69 -9
  196. package/docs/node-types.md +109 -4
  197. package/docs/plans/plan-009-tech-debt-backlog.md +3 -3
  198. package/docs/plans/plan-019-post-0.9-roadmap.md +327 -31
  199. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  200. package/docs/product-vision-2026-09.md +42 -17
  201. package/docs/sdk.md +18 -0
  202. package/package.json +7 -4
  203. package/plugins/pmx-canvas/assets/preview.png +0 -0
  204. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +3 -1
  205. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  206. package/plugins/pmx-canvas/plugin.json +2 -2
  207. package/plugins/pmx-canvas/runtime.json +1 -1
  208. package/plugins/pmx-canvas/tools.json +688 -128
  209. package/skills/data-analysis/SKILL.md +13 -26
  210. package/skills/frontend-design/LICENSE.txt +177 -0
  211. package/skills/frontend-design/SKILL.md +3 -0
  212. package/skills/pmx-canvas/SKILL.md +232 -849
  213. package/skills/pmx-canvas/evals/evals.json +26 -3
  214. package/skills/pmx-canvas/references/full-reference.md +89 -24
  215. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  216. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  217. package/skills/pmx-canvas/references/tours-and-recording.md +29 -2
  218. package/skills/pmx-canvas/references/workbench-workflows.md +900 -0
  219. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  220. package/skills/published-consumer-e2e/SKILL.md +25 -8
  221. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -77
  222. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  223. package/skills/web-artifacts-builder/SKILL.md +6 -0
  224. package/skills/web-artifacts-builder/scripts/init-artifact.sh +2 -2
  225. package/src/cli/commands/apps.ts +1 -0
  226. package/src/cli/commands/boards.ts +30 -0
  227. package/src/client/App.tsx +20 -4
  228. package/src/client/canvas/AttentionHistory.tsx +10 -2
  229. package/src/client/canvas/BarHint.tsx +6 -3
  230. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  231. package/src/client/canvas/CanvasNode.tsx +79 -49
  232. package/src/client/canvas/CanvasViewport.tsx +42 -14
  233. package/src/client/canvas/ContextMenu.tsx +97 -15
  234. package/src/client/canvas/EdgeEditor.tsx +149 -0
  235. package/src/client/canvas/EdgeLayer.tsx +441 -174
  236. package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -2
  237. package/src/client/canvas/GroupNavigation.tsx +85 -0
  238. package/src/client/canvas/Presentation.tsx +53 -3
  239. package/src/client/canvas/RelationsPanel.tsx +9 -0
  240. package/src/client/canvas/SelectionBar.tsx +21 -0
  241. package/src/client/canvas/SessionPanel.tsx +35 -1
  242. package/src/client/canvas/TextPrompt.tsx +18 -1
  243. package/src/client/canvas/TopBar.tsx +54 -9
  244. package/src/client/canvas/auto-fit.ts +9 -11
  245. package/src/client/canvas/canvas-area.ts +2 -2
  246. package/src/client/canvas/use-focus-trap.ts +6 -1
  247. package/src/client/mermaid-entry.ts +6 -20
  248. package/src/client/mermaid-render.ts +39 -0
  249. package/src/client/nodes/AxStepControls.tsx +45 -69
  250. package/src/client/nodes/BoardNode.tsx +65 -9
  251. package/src/client/nodes/ContextNode.tsx +88 -396
  252. package/src/client/nodes/ExtAppFrame.tsx +302 -1465
  253. package/src/client/nodes/FileNode.tsx +92 -81
  254. package/src/client/nodes/HtmlNode.tsx +8 -36
  255. package/src/client/nodes/LedgerNode.tsx +31 -17
  256. package/src/client/nodes/MarkdownNode.tsx +14 -24
  257. package/src/client/nodes/McpAppNode.tsx +48 -154
  258. package/src/client/nodes/MermaidNode.tsx +80 -69
  259. package/src/client/nodes/PromptNode.tsx +44 -17
  260. package/src/client/nodes/ResponseNode.tsx +8 -6
  261. package/src/client/nodes/StatusNode.tsx +34 -7
  262. package/src/client/nodes/StatusSummary.tsx +16 -3
  263. package/src/client/nodes/TraceNode.tsx +8 -8
  264. package/src/client/nodes/ViewerFrame.tsx +273 -0
  265. package/src/client/nodes/ViewerHost.tsx +41 -0
  266. package/src/client/nodes/WebpageNode.tsx +22 -24
  267. package/src/client/nodes/WorkCard.tsx +175 -0
  268. package/src/client/nodes/iframe-document-url.ts +20 -27
  269. package/src/client/nodes/primitive-copy.ts +97 -0
  270. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  271. package/src/client/nodes/use-iframe-content-height.ts +34 -10
  272. package/src/client/nodes/use-surface-frame.ts +22 -11
  273. package/src/client/nodes/viewer-runtime-state.ts +41 -35
  274. package/src/client/state/attention-bridge.ts +5 -0
  275. package/src/client/state/attention-store.ts +1 -0
  276. package/src/client/state/canvas-store.ts +202 -26
  277. package/src/client/state/human-store.ts +22 -2
  278. package/src/client/state/intent-bridge.ts +20 -5
  279. package/src/client/state/presentation.ts +135 -15
  280. package/src/client/state/relation-store.ts +127 -1
  281. package/src/client/state/sse-bridge.ts +18 -19
  282. package/src/client/theme/global.css +483 -230
  283. package/src/client/theme/surface-theme.css +6 -6
  284. package/src/client/types.ts +1 -0
  285. package/src/json-render/charts/tufte-components.tsx +44 -2
  286. package/src/json-render/renderer/index.css +62 -2
  287. package/src/json-render/renderer/index.tsx +31 -7
  288. package/src/json-render/server.ts +3 -2
  289. package/src/mcp/server.ts +13 -3
  290. package/src/server/agent-presence.ts +43 -23
  291. package/src/server/ax-flow-loop.ts +6 -0
  292. package/src/server/ax-gate-ttl.ts +8 -1
  293. package/src/server/ax-interaction.ts +16 -10
  294. package/src/server/ax-state-manager.ts +17 -4
  295. package/src/server/ax-wait.ts +20 -6
  296. package/src/server/board-export.ts +54 -30
  297. package/src/server/board-local.ts +20 -0
  298. package/src/server/board-move.ts +297 -0
  299. package/src/server/canvas-db.ts +249 -158
  300. package/src/server/canvas-geometry.ts +1 -0
  301. package/src/server/canvas-operations.ts +139 -109
  302. package/src/server/canvas-schema.ts +21 -0
  303. package/src/server/canvas-serialization.ts +2 -0
  304. package/src/server/canvas-state.ts +797 -159
  305. package/src/server/card-relocation.ts +53 -0
  306. package/src/server/code-graph.ts +4 -3
  307. package/src/server/context-reads.ts +83 -55
  308. package/src/server/demo-state.json +140 -114
  309. package/src/server/file-watcher.ts +30 -10
  310. package/src/server/html-primitives.ts +3 -9
  311. package/src/server/html-surface.ts +8 -0
  312. package/src/server/human-presence.ts +18 -5
  313. package/src/server/index.ts +54 -24
  314. package/src/server/intent-registry.ts +43 -5
  315. package/src/server/knowledge-lifecycle.ts +32 -9
  316. package/src/server/lifecycle-status.ts +29 -1
  317. package/src/server/mcp-app-runtime.ts +26 -7
  318. package/src/server/mutation-history.ts +37 -8
  319. package/src/server/operations/composites.ts +6 -3
  320. package/src/server/operations/mcp-contracts.ts +32 -2
  321. package/src/server/operations/mcp.ts +36 -9
  322. package/src/server/operations/ops/annotation.ts +22 -1
  323. package/src/server/operations/ops/ax-await.ts +1 -0
  324. package/src/server/operations/ops/ax-timeline.ts +28 -2
  325. package/src/server/operations/ops/batch.ts +25 -11
  326. package/src/server/operations/ops/boards.ts +45 -6
  327. package/src/server/operations/ops/nodes.ts +70 -0
  328. package/src/server/operations/ops/query.ts +32 -3
  329. package/src/server/operations/ops/snapshots.ts +1 -0
  330. package/src/server/operations/ops/viewport.ts +11 -9
  331. package/src/server/operations/registry.ts +49 -4
  332. package/src/server/server.ts +69 -75
  333. package/src/server/tour-control.ts +97 -12
  334. package/src/server/trace-manager.ts +4 -0
  335. package/src/server/web-artifacts/scripts/init-artifact.sh +2 -2
  336. package/src/shared/agent-context.ts +7 -1
  337. package/src/shared/auto-arrange.ts +5 -4
  338. package/src/shared/ax-kinds.ts +13 -0
  339. package/src/shared/ax-surface-protocol.ts +20 -9
  340. package/src/shared/content-height-reporter.ts +4 -3
  341. package/src/shared/human-presence.ts +1 -0
  342. package/src/shared/lifecycle-status.ts +2 -0
  343. package/src/shared/tour.ts +8 -0
@@ -137,9 +137,18 @@ function logAxStateWarning(action: string, error: unknown, details?: Record<stri
137
137
 
138
138
  export class AxStateManager {
139
139
  private _axState: PmxAxState = createEmptyAxState();
140
- private _axHostCapability: PmxAxHostCapability | null = null;
141
140
 
142
- constructor(private readonly deps: AxStateManagerDeps) {}
141
+ constructor(
142
+ private readonly deps: AxStateManagerDeps,
143
+ private readonly host: { capability: PmxAxHostCapability | null } = { capability: null },
144
+ ) {}
145
+
146
+ private get _axHostCapability() {
147
+ return this.host.capability;
148
+ }
149
+ private set _axHostCapability(value: PmxAxHostCapability | null) {
150
+ this.host.capability = value;
151
+ }
143
152
 
144
153
  // ── Normalization (against the owner's current node set) ──────────
145
154
  private normalizeForCurrentNodes(state: unknown): PmxAxState {
@@ -1088,7 +1097,9 @@ export class AxStateManager {
1088
1097
  * whose source equals the consumer to prevent delivery loops (e.g. Copilot
1089
1098
  * should not be handed back steering it originated).
1090
1099
  */
1091
- getPendingSteering(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
1100
+ getPendingSteering(
1101
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
1102
+ ): PmxAxSteeringMessage[] {
1092
1103
  const db = this.deps.getDb();
1093
1104
  return db ? loadPendingAxSteeringFromDB(db, options) : [];
1094
1105
  }
@@ -1098,7 +1109,9 @@ export class AxStateManager {
1098
1109
  * #57) — so a fresh steer is visible even behind a long backlog. Loop-safe like
1099
1110
  * getPendingSteering, but ordered DESC instead of the FIFO ASC delivery queue.
1100
1111
  */
1101
- getPendingSteeringForContext(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
1112
+ getPendingSteeringForContext(
1113
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
1114
+ ): PmxAxSteeringMessage[] {
1102
1115
  const db = this.deps.getDb();
1103
1116
  return db ? loadNewestPendingAxSteeringFromDB(db, options) : [];
1104
1117
  }
@@ -24,7 +24,8 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
24
24
  signal?: AbortSignal;
25
25
  }): Promise<AxWaitResult<T>> {
26
26
  opts.signal?.throwIfAborted();
27
- const { read, isResolved } = opts;
27
+ const read = canvasState.bindBoard(opts.read);
28
+ const { isResolved } = opts;
28
29
  const timeoutMs = Math.max(0, Math.min(opts.timeoutMs, AX_WAIT_MAX_MS));
29
30
  const pendingOf = (v: T | null): boolean => (v ? !isResolved(v) : false);
30
31
 
@@ -40,13 +41,18 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
40
41
  done = true;
41
42
  clearTimeout(timer);
42
43
  dispose();
44
+ disposeOwner();
43
45
  opts.signal?.removeEventListener('abort', onAbort);
44
46
  if (opts.signal?.aborted) {
45
47
  reject(opts.signal.reason);
46
48
  return;
47
49
  }
48
- const v = read();
49
- resolve({ value: v, pending: pendingOf(v) });
50
+ try {
51
+ const v = read();
52
+ resolve({ value: v, pending: pendingOf(v) });
53
+ } catch (error) {
54
+ reject(error);
55
+ }
50
56
  };
51
57
  const onAbort = finish;
52
58
  const check = (type: string): void => {
@@ -56,6 +62,9 @@ export async function waitForAxResolution<T extends { status: string }>(opts: {
56
62
  };
57
63
  const timer = setTimeout(finish, timeoutMs);
58
64
  const dispose = canvasState.onChange(check);
65
+ const disposeOwner = canvasState.onBoardDispose(() => {
66
+ finish();
67
+ });
59
68
  opts.signal?.addEventListener('abort', onAbort, { once: true });
60
69
  if (opts.signal?.aborted) finish();
61
70
  });
@@ -77,7 +86,8 @@ export async function waitForAxCondition(opts: {
77
86
  }): Promise<void> {
78
87
  opts.signal?.throwIfAborted();
79
88
  const timeoutMs = Math.max(0, Math.min(opts.timeoutMs, AX_WAIT_MAX_MS));
80
- if (timeoutMs === 0 || opts.ready()) return;
89
+ const ready = canvasState.bindBoard(opts.ready);
90
+ if (timeoutMs === 0 || ready()) return;
81
91
  const channel = opts.channel ?? 'ax-timeline';
82
92
  await new Promise<void>((resolve, reject) => {
83
93
  let done = false;
@@ -86,6 +96,7 @@ export async function waitForAxCondition(opts: {
86
96
  done = true;
87
97
  clearTimeout(timer);
88
98
  dispose();
99
+ disposeOwner();
89
100
  opts.signal?.removeEventListener('abort', finish);
90
101
  if (opts.signal?.aborted) {
91
102
  reject(opts.signal.reason);
@@ -94,10 +105,13 @@ export async function waitForAxCondition(opts: {
94
105
  resolve();
95
106
  };
96
107
  const check = (type: string): void => {
97
- if (type === channel && opts.ready()) finish();
108
+ if (type === channel && ready()) finish();
98
109
  };
99
110
  const timer = setTimeout(finish, timeoutMs);
100
- const dispose = canvasState.onChange(check);
111
+ // Steering is workspace-wide and can be sent from another board. Keep
112
+ // ready() bound to its owner but listen outside the board-specific filter.
113
+ const dispose = canvasState.withVisibleBoard(() => canvasState.onChange(check));
114
+ const disposeOwner = canvasState.onBoardDispose(finish);
101
115
  opts.signal?.addEventListener('abort', finish, { once: true });
102
116
  if (opts.signal?.aborted) finish();
103
117
  });
@@ -12,7 +12,8 @@
12
12
  import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
14
14
  import { Marked, Renderer, type RendererThis, type Tokens } from 'marked';
15
- import { canvasThemeScheme } from '../shared/themes.js';
15
+ import { isWorkCardState, WORK_CARD_STATES } from '../shared/ax-kinds.js';
16
+ import { canvasThemeScheme, normalizeCanvasThemeName, type CanvasThemeName } from '../shared/themes.js';
16
17
  import { delimiterForPath, parseDelimitedText } from '../shared/delimited-text.js';
17
18
  import { buildJsonRenderViewerHtml, escapeInlineScriptSource, readJsonRenderBundle } from '../json-render/server.js';
18
19
  import type { JsonRenderSpec } from '../json-render/server.js';
@@ -58,6 +59,7 @@ interface ExportCard {
58
59
  w: number;
59
60
  h: number;
60
61
  group?: true;
62
+ heading?: true;
61
63
  html?: string;
62
64
  frame?: string;
63
65
  }
@@ -66,7 +68,7 @@ interface Collected {
66
68
  manifest: ExportManifest;
67
69
  cards: ExportCard[];
68
70
  edges: CanvasEdge[];
69
- scheme: 'dark' | 'light';
71
+ theme: CanvasThemeName;
70
72
  needsJsonRender: boolean;
71
73
  needsMermaid: boolean;
72
74
  }
@@ -207,9 +209,7 @@ async function collect(
207
209
  const read = canvasState.readBoard(boardId);
208
210
  if (!read) return null;
209
211
  const { board, state } = read;
210
- const theme = canvasThemeScheme(state.theme ?? canvasState.theme);
211
- // Exports collapse to a scheme; surface documents take the theme that draws it.
212
- const surfaceTheme = theme === 'light' ? 'daylight' : 'harbor';
212
+ const theme = normalizeCanvasThemeName(state.theme ?? canvasState.theme);
213
213
  const remoteImages = new Set<string>();
214
214
  const links = new Set<string>();
215
215
  const frameNetworkDestinations = new Set<string>();
@@ -278,15 +278,28 @@ async function collect(
278
278
  card.group = true;
279
279
  break;
280
280
  case 'markdown':
281
+ if (data.display === 'heading') card.heading = true;
281
282
  card.html = `<div class="md">${await markdown.parse(text(data.content))}</div>`;
282
283
  break;
283
284
  case 'status':
284
- card.html = rows([
285
- ['Phase', text(data.phase).trim() || text(data.content).trim() || text(data.status).trim() || 'idle'],
286
- ['Message', data.message],
287
- ['Detail', data.detail],
288
- ['Active tool', data.activeTool],
289
- ]);
285
+ if (typeof data.workItemId === 'string' && data.liveWorkAvailable === false) {
286
+ placeholder('Task unavailable. The linked task is no longer on this board.');
287
+ break;
288
+ }
289
+ card.html = isWorkCardState(data.workState)
290
+ ? rows([
291
+ ['Task view', typeof data.workItemId === 'string' ? 'Read-only snapshot of linked task' : undefined],
292
+ ['Task source', typeof data.workItemId === 'string' ? data.liveWorkSource : undefined],
293
+ ['Work state', WORK_CARD_STATES[data.workState].label],
294
+ ['Detail', data.detail],
295
+ ['Progress', typeof data.progress === 'number' ? `${data.progress}%` : undefined],
296
+ ])
297
+ : rows([
298
+ ['Phase', text(data.phase).trim() || text(data.content).trim() || text(data.status).trim() || 'idle'],
299
+ ['Message', data.message],
300
+ ['Detail', data.detail],
301
+ ['Active tool', data.activeTool],
302
+ ]);
290
303
  break;
291
304
  case 'context': {
292
305
  const items = Array.isArray(data.cards) ? (data.cards as Array<Record<string, unknown>>) : [];
@@ -385,8 +398,7 @@ async function collect(
385
398
  const html = text(data.html) || text(data.content);
386
399
  collectFrameNetworkDestinations(html, frameNetworkDestinations);
387
400
  manifest.frames += 1;
388
- if (withFrames)
389
- card.frame = buildHtmlSurfaceDocument(html, { theme: surfaceTheme, title, inlineThemeCss: themeCss });
401
+ if (withFrames) card.frame = buildHtmlSurfaceDocument(html, { theme, title, inlineThemeCss: themeCss });
390
402
  break;
391
403
  }
392
404
  case 'mermaid':
@@ -398,7 +410,7 @@ async function collect(
398
410
  '<script src="/canvas/mermaid-entry.js"></script>',
399
411
  `<script src="${MERMAID_SRC}"></script>`,
400
412
  ),
401
- { theme: surfaceTheme, title, inlineThemeCss: themeCss },
413
+ { theme, title, inlineThemeCss: themeCss },
402
414
  );
403
415
  }
404
416
  break;
@@ -415,7 +427,8 @@ async function collect(
415
427
  title,
416
428
  spec: data.spec as JsonRenderSpec,
417
429
  theme,
418
- display: 'site',
430
+ // The saved card owns the viewport, just as on the live canvas.
431
+ // Omitting display selects embedded layout and its duplicate-title policy.
419
432
  });
420
433
  }
421
434
  break;
@@ -444,7 +457,7 @@ async function collect(
444
457
  manifest.links = [...links];
445
458
  manifest.frameNetworkDestinations = [...frameNetworkDestinations];
446
459
  manifest.embeddedCodeCanAccessNetwork = manifest.frames > 0;
447
- return { manifest, cards, edges: state.edges, scheme: theme, needsJsonRender, needsMermaid };
460
+ return { manifest, cards, edges: state.edges, theme, needsJsonRender, needsMermaid };
448
461
  }
449
462
 
450
463
  /** What an export of `boardId` would put in the file, without building it. */
@@ -467,7 +480,7 @@ export async function buildBoardExport(
467
480
  ): Promise<{ html: string; manifest: ExportManifest } | null> {
468
481
  const collected = await collect(boardId, includeFiles, true, includeDerivedText);
469
482
  if (!collected) return null;
470
- const { manifest, cards, edges: snapshotEdges, scheme, needsJsonRender, needsMermaid } = collected;
483
+ const { manifest, cards, edges: snapshotEdges, theme, needsJsonRender, needsMermaid } = collected;
471
484
 
472
485
  // Store each viewer bundle once; frames reference it by placeholder.
473
486
  const assets: Record<string, string> = {};
@@ -496,7 +509,7 @@ export async function buildBoardExport(
496
509
  const exportedAt = new Date().toISOString();
497
510
  const html = exportPage({
498
511
  title: manifest.boardName,
499
- scheme,
512
+ theme,
500
513
  exportedAt,
501
514
  board: { cards, edges },
502
515
  assets,
@@ -536,7 +549,7 @@ export function exportsFolder(): string | null {
536
549
 
537
550
  function exportPage(input: {
538
551
  title: string;
539
- scheme: 'dark' | 'light';
552
+ theme: CanvasThemeName;
540
553
  exportedAt: string;
541
554
  board: {
542
555
  cards: ExportCard[];
@@ -545,14 +558,22 @@ function exportPage(input: {
545
558
  assets: Record<string, string>;
546
559
  }): string {
547
560
  const date = new Date(input.exportedAt).toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' });
561
+ // Use the workbench's shipped faces and shared palette without offline requests.
562
+ const fonts = (readAsset('global.css').match(/@font-face\s*\{[^}]*\}/g) ?? [])
563
+ .join('\n')
564
+ .replace(
565
+ /url\("fonts\/([\w.-]+\.woff2)"\)/g,
566
+ (_, file: string) =>
567
+ `url("data:font/woff2;base64,${readFileSync(join(canvasBundleDir(), 'fonts', file)).toString('base64')}")`,
568
+ );
548
569
  return `<!doctype html>
549
- <html lang="en" data-scheme="${input.scheme}">
570
+ <html lang="en" data-scheme="${canvasThemeScheme(input.theme)}" data-theme="${input.theme}">
550
571
  <head>
551
572
  <meta charset="utf-8">
552
573
  <meta name="viewport" content="width=device-width, initial-scale=1">
553
574
  <meta name="generator" content="PMX Canvas">
554
575
  <title>${escapeHtml(input.title)}</title>
555
- <style>${EXPORT_CSS}</style>
576
+ <style>${fonts}\n${readAsset('surface-theme.css')}\n${EXPORT_CSS}</style>
556
577
  </head>
557
578
  <body>
558
579
  <header class="bar">
@@ -574,10 +595,9 @@ function exportPage(input: {
574
595
  }
575
596
 
576
597
  const EXPORT_CSS = `
577
- :root{--bg:#0b1016;--panel:#121a23;--line:#263241;--text:#e8edf2;--soft:#9aa7b4;--accent:#4bbcff;--add:#2ea66f;--del:#e25d6e}
578
- [data-scheme=light]{--bg:#f4f6f8;--panel:#ffffff;--line:#d5dbe1;--text:#17202a;--soft:#5b6875;--accent:#0a6fc2;--add:#1c7a4f;--del:#b3263a}
598
+ :root{--bg:var(--c-bg);--panel:var(--c-panel);--line:var(--c-line);--text:var(--c-text);--soft:var(--c-muted);--accent:var(--c-accent);--add:var(--c-ok);--del:var(--c-danger)}
579
599
  *{box-sizing:border-box}
580
- html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:15px/1.65 system-ui,-apple-system,Segoe UI,sans-serif}
600
+ html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:13px/1.6 var(--font-ui)}
581
601
  .bar{position:fixed;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 16px;background:var(--panel);border-bottom:1px solid var(--line);z-index:5}
582
602
  .bar .name{display:flex;flex-direction:column;min-width:0}
583
603
  .bar .name strong,.bar .name span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -592,12 +612,16 @@ button:focus-visible,a:focus-visible,[tabindex]:focus-visible{outline:2px solid
592
612
  #edges{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
593
613
  #edges line{stroke:var(--soft);stroke-width:1.5}
594
614
  #edges text{fill:var(--soft);font-size:12px;text-anchor:middle}
595
- .card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;box-shadow:0 6px 24px rgba(0,0,0,.18)}
615
+ .card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:var(--r-node);overflow:hidden;box-shadow:var(--e-1)}
596
616
  .card.group{background:color-mix(in srgb,var(--accent) 5%,transparent);border-style:dashed;box-shadow:none}
597
- .card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:12px 42px 12px 18px;font-weight:600;font-size:13px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:color-mix(in srgb,var(--soft) 5%,var(--panel))}
617
+ .card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:7px 38px 7px 12px;font-weight:600;font-size:12px;line-height:22px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:transparent}
598
618
  button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--soft);font-size:16px}
599
619
  .card.group .card-title{border-bottom:none;cursor:default;color:var(--soft)}
600
- .card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:20px;overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
620
+ .card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:10px 14px;font-size:var(--fs-ui);color:var(--c-text-soft);overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
621
+ .card.heading{background:transparent;border-color:transparent;box-shadow:none}
622
+ .card.heading .card-title{padding:8px 38px 0 16px;border:0;font-size:var(--fs-heading);line-height:28px;letter-spacing:-.01em}
623
+ .card.heading .card-body{padding:0 16px 8px;color:var(--soft)}
624
+ .card.heading:not(:hover):not(:focus-within) .card-title:after{visibility:hidden}
601
625
  .card-body>:first-child,.md>:first-child{margin-top:0}.card-body>:last-child,.md>:last-child{margin-bottom:0}
602
626
  .card iframe,#overlay-body iframe{flex:1;min-height:0;width:100%;height:100%;border:0;background:var(--bg)}
603
627
  .md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:1.4em 0 .6em;line-height:1.3;font-weight:650;letter-spacing:-.02em}
@@ -607,7 +631,7 @@ button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--s
607
631
  .md input[type=checkbox]{accent-color:var(--accent);margin-right:.5em}
608
632
  .md blockquote{margin:1em 0;padding:10px 16px;border-left:3px solid var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));color:var(--soft)}
609
633
  .md blockquote>:last-child{margin-bottom:0}.md hr{border:0;border-top:1px solid var(--line);margin:24px 0}
610
- .md code,pre{font:13px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace;tab-size:2}
634
+ .md code,pre{font:13px/1.65 var(--font-code);tab-size:2}
611
635
  .md :not(pre)>code{padding:2px 5px;border:1px solid var(--line);border-radius:4px;background:color-mix(in srgb,var(--soft) 8%,var(--panel))}
612
636
  pre{margin:0 0 16px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--soft) 5%,var(--panel));white-space:pre;overflow:auto;overflow-wrap:normal;overscroll-behavior:contain}
613
637
  .prose{white-space:pre-wrap}.table-scroll{width:100%;max-width:100%;overflow:auto;margin:16px 0;border:1px solid var(--line);border-radius:8px;overscroll-behavior:contain}
@@ -629,7 +653,7 @@ details{margin-top:16px}summary{cursor:pointer;color:var(--soft);margin-bottom:1
629
653
  #overlay .sheet{width:min(1100px,100%);height:min(90vh,100%);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
630
654
  #overlay .sheet>header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line)}
631
655
  #overlay-body{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}
632
- #overlay-body .card-body{padding:28px 32px}#overlay-body .md{max-width:960px;margin:0 auto}
656
+ #overlay-body .card-body{padding:28px 32px;font-size:15px}#overlay-body .md{max-width:960px;margin:0 auto}
633
657
  @media(max-width:600px){#overlay{padding:8px}#overlay .sheet{height:100%}#overlay-body .card-body{padding:18px}.bar{padding:0 10px}.bar .name span{display:none}}
634
658
  `;
635
659
 
@@ -660,7 +684,7 @@ const body = (card) => {
660
684
  const ordered = [...board.cards].sort((a, b) => (b.group ? 1 : 0) - (a.group ? 1 : 0));
661
685
  for (const card of ordered) {
662
686
  const el = document.createElement('section');
663
- el.className = 'card' + (card.group ? ' group' : '');
687
+ el.className = 'card' + (card.group ? ' group' : '') + (card.heading ? ' heading' : '');
664
688
  el.dataset.nodeId = card.id;
665
689
  el.dataset.nodeType = card.kind;
666
690
  Object.assign(el.style, { left: card.x + 'px', top: card.y + 'px', width: card.w + 'px', height: card.h + 'px' });
@@ -0,0 +1,20 @@
1
+ import { canvasState } from './canvas-state.js';
2
+
3
+ /** Board-owned runtime state, discarded with its owner rather than on navigation. */
4
+ export function createBoardLocal<T>(create: () => T, dispose?: (value: T) => void) {
5
+ // Hoisted for canvas-state's mutation-history dependency; access the owner only on read.
6
+ const values = new WeakMap<object, T>();
7
+ return {
8
+ get current(): T {
9
+ const owner = canvasState.boardIdentity;
10
+ let value = values.get(owner);
11
+ if (value === undefined) {
12
+ value = create();
13
+ values.set(owner, value);
14
+ const created = value;
15
+ canvasState.onBoardDispose(() => dispose?.(created));
16
+ }
17
+ return value;
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,297 @@
1
+ import type { ActorAttribution } from './attribution.js';
2
+ import { createEmptyAxState } from './ax-state.js';
3
+ import type { CanvasBoard, PersistedCanvasState } from './canvas-db.js';
4
+ import type { CanvasNodeState } from './canvas-state.js';
5
+ import type { DocumentImport } from './document-import.js';
6
+ import { OperationError } from './operations/types.js';
7
+
8
+ export interface BoardMoveInput {
9
+ sourceBoardId: string;
10
+ nodeIds: string[];
11
+ name: string;
12
+ expectedRevision: number;
13
+ requestId: string;
14
+ preview?: boolean;
15
+ }
16
+
17
+ export interface BoardMovePreview {
18
+ sourceBoardId: string;
19
+ nodeIds: string[];
20
+ affectedBoardIds: string[];
21
+ readmeNodeId: string | null;
22
+ pinnedNodeIds: string[];
23
+ liveTaskNodeIds: string[];
24
+ attachmentIds: string[];
25
+ blockers: string[];
26
+ notices: string[];
27
+ }
28
+
29
+ export interface BoardMoveResult {
30
+ sourceBoardId: string;
31
+ destinationBoardId: string;
32
+ portalId: string;
33
+ nodeIds: string[];
34
+ affectedBoardIds: string[];
35
+ }
36
+
37
+ export interface MoveBoard {
38
+ board: CanvasBoard;
39
+ state: PersistedCanvasState;
40
+ }
41
+
42
+ function record(value: unknown): Record<string, unknown> | undefined {
43
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
44
+ ? (value as Record<string, unknown>)
45
+ : undefined;
46
+ }
47
+
48
+ function strings(value: unknown): string[] {
49
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
50
+ }
51
+
52
+ function attachmentId(node: CanvasNodeState): string | undefined {
53
+ const value = node.data.attachmentId ?? record(node.data.source)?.attachmentId;
54
+ return typeof value === 'string' ? value : undefined;
55
+ }
56
+
57
+ /** Pure relocation plan. Preview and commit run the same checks on a consistent library view. */
58
+ export function prepareBoardMove(
59
+ input: BoardMoveInput,
60
+ library: Map<string, MoveBoard>,
61
+ destinationId: string,
62
+ portalId: string,
63
+ imports: DocumentImport[],
64
+ runtimeBlockers: ReadonlyMap<string, string>,
65
+ actor: ActorAttribution,
66
+ at: string,
67
+ ): { preview: BoardMovePreview; states: Map<string, PersistedCanvasState> } {
68
+ const source = library.get(input.sourceBoardId);
69
+ if (!source) throw new OperationError('Source board not found.', 404);
70
+ if (input.expectedRevision !== (source.state.revisionState?.revision ?? 0))
71
+ throw new OperationError('The source board changed. Preview the move again.', 409);
72
+ if (!input.nodeIds.length) throw new OperationError('Select at least one card to move.');
73
+ const nodes = new Map(source.state.nodes.map((node) => [node.id, node]));
74
+ const moving = new Set<string>();
75
+ const include = (id: string) => {
76
+ if (moving.has(id)) return;
77
+ const node = nodes.get(id);
78
+ if (!node) throw new OperationError(`Selected card "${id}" no longer exists.`, 409);
79
+ moving.add(id);
80
+ if (node.type === 'group') {
81
+ for (const child of strings(node.data.children)) include(child);
82
+ for (const child of nodes.values()) if (child.data.parentGroup === id) include(child.id);
83
+ }
84
+ };
85
+ for (const id of input.nodeIds) include(id);
86
+ const blockers = new Set<string>();
87
+ const movedAttachments = new Set<string>();
88
+ const liveTasks: string[] = [];
89
+ const blocks = (id: string, reason: string) => blockers.add(`${id}: ${reason}`);
90
+ for (const node of source.state.nodes) {
91
+ const moves = moving.has(node.id);
92
+ const data = node.data;
93
+ if (moves) {
94
+ const runtime = runtimeBlockers.get(node.id);
95
+ if (runtime) blocks(node.id, runtime);
96
+ if (
97
+ data.streamStatus === 'open' ||
98
+ data._activeResponseId ||
99
+ (['prompt', 'response'].includes(node.type) &&
100
+ ['pending', 'sending', 'streaming'].includes(String(data.threadStatus ?? data.status)))
101
+ )
102
+ blocks(node.id, 'finish the active producer before moving this card');
103
+ if (
104
+ (node.type === 'context' && node.id === 'context-main') ||
105
+ (node.type === 'status' && node.id === 'status-main')
106
+ )
107
+ blocks(node.id, 'this presentation is owned by the host adapter');
108
+ if (data.appSessionId || data.transportConfig || data.sessionStatus === 'reconnecting')
109
+ blocks(node.id, 'the external app binding cannot be relocated without reconnecting');
110
+ if (data.workboard || data.axStep || data.axFlow || data.axFlowId)
111
+ blocks(node.id, 'materialized task/flow controls retain authority on this board');
112
+ if (node.type === 'status' && typeof data.workItemId === 'string') liveTasks.push(node.id);
113
+ const attachment = attachmentId(node);
114
+ if (attachment) movedAttachments.add(attachment);
115
+ if (
116
+ ['html', 'json-render', 'graph', 'mcp-app'].includes(node.type) &&
117
+ /ax[.:](work|approval|flow|elicitation|mode)|data-ax-|\/api\/canvas\/ax/.test(JSON.stringify(data))
118
+ )
119
+ blocks(node.id, 'embedded action targets cannot retain their board authority after this move');
120
+ }
121
+ // Declared local bindings are not opaque strings to rewrite. Both ends must move together.
122
+ const local = [
123
+ ...strings(data.embeddedNodeIds),
124
+ ...strings(data.contextNodeIds),
125
+ data.embeddedNodeId,
126
+ data.embeddedGraphId,
127
+ data.sourceNodeId,
128
+ data.promptNodeId,
129
+ data.responseNodeId,
130
+ data.parentNodeId,
131
+ ].filter((id): id is string => typeof id === 'string');
132
+ for (const text of [data.url, data.html, data.content, ...strings(data.embeddedUrls)]) {
133
+ if (typeof text !== 'string') continue;
134
+ for (const match of text.matchAll(/\/api\/canvas\/[^\s"'<>]+/g)) {
135
+ const url = new URL(match[0].replaceAll('&amp;', '&'), 'http://canvas.local');
136
+ let target =
137
+ url.pathname === '/api/canvas/json-render/view' || url.pathname === '/api/canvas/file-bytes'
138
+ ? url.searchParams.get('nodeId')
139
+ : url.pathname.match(/^\/api\/canvas\/image\/(.+)$/)?.[1];
140
+ if (url.pathname.startsWith('/api/canvas/surface/')) {
141
+ try {
142
+ target = decodeURIComponent(url.pathname.slice('/api/canvas/surface/'.length));
143
+ } catch {
144
+ // Malformed examples in authored text cannot resolve to a surface.
145
+ continue;
146
+ }
147
+ }
148
+ if (target) local.push(target);
149
+ }
150
+ }
151
+ for (const id of local)
152
+ if (moving.has(id) !== moves)
153
+ blocks(node.id, `include both ends of the local viewer/content reference to "${id}"`);
154
+ }
155
+ for (const node of source.state.nodes) {
156
+ const attachment = attachmentId(node);
157
+ if (attachment && movedAttachments.has(attachment) && !moving.has(node.id))
158
+ blocks(node.id, 'include every card sharing this attachment/import');
159
+ }
160
+ for (const job of imports) {
161
+ if (!movedAttachments.has(job.attachmentId) && !job.committedNodeIds.some((id) => moving.has(id))) continue;
162
+ if (job.status === 'requested' || job.status === 'drafted')
163
+ blocks(job.id, 'finish or cancel the document import first');
164
+ if (!movedAttachments.has(job.attachmentId) || job.committedNodeIds.some((id) => !moving.has(id)))
165
+ blocks(job.id, 'include the original attachment and every imported section');
166
+ }
167
+ for (const stop of source.state.tour?.stops ?? []) {
168
+ if ('nodeId' in stop.target && moving.has(stop.target.nodeId))
169
+ blocks(stop.target.nodeId, 'a source-board tour targets this card; remove that stop before moving');
170
+ }
171
+ const ax = source.state.ax ?? createEmptyAxState();
172
+ for (const item of [...ax.workItems, ...ax.approvalGates, ...ax.elicitations, ...ax.modeRequests]) {
173
+ for (const id of item.nodeIds)
174
+ if (moving.has(id)) blocks(id, `board-owned work/approval/input record "${item.id}" anchors this card`);
175
+ }
176
+ for (const review of ax.reviewAnnotations)
177
+ if (review.nodeId && moving.has(review.nodeId))
178
+ blocks(review.nodeId, `board-local review annotation "${review.id}" anchors this card`);
179
+
180
+ const states = new Map<string, PersistedCanvasState>();
181
+ const destination: PersistedCanvasState = {
182
+ version: 1,
183
+ theme: source.state.theme,
184
+ viewport: { x: 0, y: 0, scale: 1 },
185
+ nodes: [],
186
+ edges: [],
187
+ contextPins: [],
188
+ contextPinMeta: {},
189
+ annotations: [],
190
+ ax: createEmptyAxState(),
191
+ };
192
+ const locate = (boardId: string, cardId: string) =>
193
+ boardId === input.sourceBoardId && moving.has(cardId) ? destinationId : boardId;
194
+ for (const [boardId, entry] of library) {
195
+ const state = structuredClone(entry.state);
196
+ const prior = JSON.stringify(state);
197
+ state.edges = state.edges.flatMap((edge) => {
198
+ const owner = locate(boardId, edge.from);
199
+ const target = locate(edge.toBoardId ?? boardId, edge.to);
200
+ const next = { ...edge };
201
+ if (target === owner) delete next.toBoardId;
202
+ else next.toBoardId = target;
203
+ if (owner === destinationId) {
204
+ destination.edges.push(next);
205
+ return [];
206
+ }
207
+ return [next];
208
+ });
209
+ state.nodes = state.nodes.flatMap((node) => {
210
+ const data = node.data;
211
+ for (const link of Array.isArray(data.wikiLinks) ? data.wikiLinks : []) {
212
+ const target = record(record(link)?.target);
213
+ if (target?.boardId === input.sourceBoardId && typeof target.cardId === 'string' && moving.has(target.cardId))
214
+ target.boardId = destinationId;
215
+ }
216
+ const provenance = record(data.provenance);
217
+ for (const target of Array.isArray(provenance?.sources) ? provenance.sources : []) {
218
+ const ref = record(target);
219
+ if (ref?.boardId === input.sourceBoardId && typeof ref.cardId === 'string' && moving.has(ref.cardId))
220
+ ref.boardId = destinationId;
221
+ }
222
+ if (boardId !== input.sourceBoardId) return [node];
223
+ if (!moving.has(node.id)) {
224
+ if (node.type === 'group') data.children = strings(data.children).filter((id) => !moving.has(id));
225
+ return [node];
226
+ }
227
+ if (typeof data.parentGroup === 'string' && !moving.has(data.parentGroup)) delete data.parentGroup;
228
+ if (node.type === 'status' && typeof data.workItemId === 'string') data.workItemBoardId ??= input.sourceBoardId;
229
+ destination.nodes.push(node);
230
+ if (state.contextPins.includes(node.id)) {
231
+ destination.contextPins.push(node.id);
232
+ const meta = state.contextPinMeta?.[node.id];
233
+ if (meta) destination.contextPinMeta![node.id] = meta;
234
+ }
235
+ return [];
236
+ });
237
+ if (boardId === input.sourceBoardId) {
238
+ state.contextPins = state.contextPins.filter((id) => !moving.has(id));
239
+ for (const id of moving) if (state.contextPinMeta) delete state.contextPinMeta[id];
240
+ if (state.ax) {
241
+ state.ax.focus.nodeIds = state.ax.focus.nodeIds.filter((id) => !moving.has(id));
242
+ if (state.ax.focus.primaryNodeId && moving.has(state.ax.focus.primaryNodeId))
243
+ state.ax.focus.primaryNodeId = null;
244
+ }
245
+ }
246
+ if (prior !== JSON.stringify(state) || boardId === input.sourceBoardId) states.set(boardId, state);
247
+ }
248
+ const revision =
249
+ Math.max(
250
+ source.state.revisionState?.revision ?? 0,
251
+ ...destination.nodes.flatMap((node) => [node.contentRevision ?? 0, node.linksRevision ?? 0]),
252
+ ...destination.edges.map((edge) => edge.revision ?? 0),
253
+ ) + 1;
254
+ const selected = [...moving].map((id) => nodes.get(id)!);
255
+ states.get(input.sourceBoardId)!.nodes.push({
256
+ id: portalId,
257
+ type: 'board',
258
+ data: { boardId: destinationId, title: input.name },
259
+ position: {
260
+ x: Math.min(...selected.map((node) => node.position.x)),
261
+ y: Math.min(...selected.map((node) => node.position.y)),
262
+ },
263
+ size: { width: 360, height: 200 },
264
+ zIndex: Math.max(...selected.map((node) => node.zIndex)),
265
+ collapsed: false,
266
+ pinned: false,
267
+ createdBy: actor,
268
+ lastEditedBy: actor,
269
+ contentRevision: revision,
270
+ contentChangedAt: at,
271
+ freshnessStartedAt: at,
272
+ });
273
+ states.set(destinationId, destination);
274
+ for (const state of states.values()) {
275
+ const floor = Math.max(state.revisionState?.revision ?? 0, revision) + 1;
276
+ state.revisionState = { revision: floor, floor, tombstones: [] };
277
+ }
278
+ return {
279
+ states,
280
+ preview: {
281
+ sourceBoardId: input.sourceBoardId,
282
+ nodeIds: [...moving].sort(),
283
+ affectedBoardIds: [...states.keys()].filter((id) => id !== destinationId).sort(),
284
+ readmeNodeId:
285
+ source.board.readmeNodeId && moving.has(source.board.readmeNodeId) ? source.board.readmeNodeId : null,
286
+ pinnedNodeIds: destination.contextPins,
287
+ liveTaskNodeIds: liveTasks,
288
+ attachmentIds: [...movedAttachments],
289
+ blockers: [...blockers],
290
+ notices: [
291
+ 'Moves the original cards and leaves a portal. This is not a copy.',
292
+ 'Pre-move Undo/Redo is cleared on affected boards. Older snapshots remain inspectable but cannot be restored across the move.',
293
+ 'Board-space annotations stay on the source. Live tasks keep their original owner.',
294
+ ],
295
+ },
296
+ };
297
+ }