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
@@ -21,12 +21,12 @@
21
21
  /* ── Core palette (dark · ink + cyan) ────────────────────── */
22
22
  --c-bg: #081524;
23
23
  --c-panel: #0f1d31;
24
- --c-panel-soft: #0a1729;
24
+ --c-panel-soft: #13243b;
25
25
  --c-line: #1b2c44;
26
26
  --c-text: #e6eef7;
27
- --c-text-soft: #c7d3ea;
27
+ --c-text-soft: #b9c6d6;
28
28
  --c-muted: #8ea3bd;
29
- --c-dim: #5c6b80;
29
+ --c-dim: #6b7d94;
30
30
  --c-accent: #4BBCFF;
31
31
  --c-ok: #2fd07f;
32
32
  --c-warn: #f4c542;
@@ -46,13 +46,13 @@
46
46
  /* Common aliases authored HTML might use. */
47
47
  --color-bg: #081524;
48
48
  --color-panel: #0f1d31;
49
- --color-surface: #0a1729;
49
+ --color-surface: #13243b;
50
50
  --color-border: #1b2c44;
51
51
  --color-text: #e6eef7;
52
52
  --color-text-primary: #e6eef7;
53
- --color-text-secondary: #c7d3ea;
53
+ --color-text-secondary: #b9c6d6;
54
54
  --color-text-muted: #8ea3bd;
55
- --color-text-dim: #5c6b80;
55
+ --color-text-dim: #6b7d94;
56
56
  --color-accent: #4BBCFF;
57
57
  --color-success: #2fd07f;
58
58
  --color-warning: #f4c542;
@@ -115,6 +115,7 @@ export const TYPE_LABELS: Record<CanvasNodeState['type'], string> = {
115
115
  /** Node types that support the full-viewport expand/focus overlay. */
116
116
  export const EXPANDABLE_TYPES = new Set<CanvasNodeState['type']>([
117
117
  'markdown',
118
+ 'status',
118
119
  'mcp-app',
119
120
  'webpage',
120
121
  'json-render',
@@ -336,6 +336,18 @@ interface SlopegraphProps {
336
336
  height?: number | null;
337
337
  }
338
338
 
339
+ /** Separate close labels without moving the data points they describe. */
340
+ function slopeLabelPositions(values: number[], top: number, bottom: number): number[] {
341
+ const order = values.map((y, index) => ({ y, index })).sort((a, b) => a.y - b.y);
342
+ const gap = Math.min(14, (bottom - top) / Math.max(1, order.length - 1));
343
+ for (let i = 1; i < order.length; i++) order[i].y = Math.max(order[i].y, order[i - 1].y + gap);
344
+ if (order.length) order[order.length - 1].y = Math.min(order[order.length - 1].y, bottom);
345
+ for (let i = order.length - 2; i >= 0; i--) order[i].y = Math.min(order[i].y, order[i + 1].y - gap);
346
+ const result = [...values];
347
+ for (const { y, index } of order) result[index] = y;
348
+ return result;
349
+ }
350
+
339
351
  function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
340
352
  const stroke = props.color ?? ACCENT;
341
353
  const rows = (props.data ?? []).map((row) => ({
@@ -359,6 +371,16 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
359
371
  // fills without pushing a scrollbar onto the iframe document.
360
372
  const plotH = chartPlotHeight(height, Boolean(props.title));
361
373
  const scaleY = (v: number) => topPad + (1 - (v - min) / (max - min)) * (plotH - topPad - botPad);
374
+ const beforeLabels = slopeLabelPositions(
375
+ rows.map((row) => scaleY(row.before)),
376
+ topPad,
377
+ plotH - botPad,
378
+ );
379
+ const afterLabels = slopeLabelPositions(
380
+ rows.map((row) => scaleY(row.after)),
381
+ topPad,
382
+ plotH - botPad,
383
+ );
362
384
 
363
385
  return (
364
386
  <div ref={frameRef} className="pmx-chart pmx-chart--slopegraph" style={{ height }}>
@@ -390,10 +412,30 @@ function ChartSlopegraph({ props }: BaseComponentProps<SlopegraphProps>) {
390
412
  <line x1={leftX} y1={y1} x2={rightX} y2={y2} stroke={lineColor} strokeWidth={1.5} />
391
413
  <circle cx={leftX} cy={y1} r={2.5} fill={lineColor} />
392
414
  <circle cx={rightX} cy={y2} r={2.5} fill={lineColor} />
393
- <text x={leftX - 8} y={y1} textAnchor="end" dominantBaseline="central" fontSize={11} fill={INK}>
415
+ {beforeLabels[i] !== y1 && (
416
+ <line x1={leftX} y1={y1} x2={leftX - 5} y2={beforeLabels[i]} stroke={lineColor} />
417
+ )}
418
+ {afterLabels[i] !== y2 && (
419
+ <line x1={rightX} y1={y2} x2={rightX + 5} y2={afterLabels[i]} stroke={lineColor} />
420
+ )}
421
+ <text
422
+ x={leftX - 8}
423
+ y={beforeLabels[i]}
424
+ textAnchor="end"
425
+ dominantBaseline="central"
426
+ fontSize={11}
427
+ fill={INK}
428
+ >
394
429
  {`${row.label} ${row.before}`}
395
430
  </text>
396
- <text x={rightX + 8} y={y2} textAnchor="start" dominantBaseline="central" fontSize={11} fill={INK}>
431
+ <text
432
+ x={rightX + 8}
433
+ y={afterLabels[i]}
434
+ textAnchor="start"
435
+ dominantBaseline="central"
436
+ fontSize={11}
437
+ fill={INK}
438
+ >
397
439
  {`${row.after} ${row.label}`}
398
440
  </text>
399
441
  </g>
@@ -3,7 +3,7 @@
3
3
 
4
4
  @source "../../../../node_modules/@json-render/shadcn/dist/index.js";
5
5
 
6
- @custom-variant dark (&:is([data-theme="dark"] *));
6
+ @custom-variant dark (&:is(.dark *));
7
7
 
8
8
  @theme {
9
9
  --font-sans: "IBM Plex Sans", system-ui, sans-serif;
@@ -99,6 +99,66 @@
99
99
  --chart-6: #ffffff;
100
100
  }
101
101
 
102
+ /* Named themes share the canvas surface, including nested cards and charts.
103
+ Explicit dark/light remain available for authored design experiments. */
104
+ :root[data-canvas-theme] {
105
+ --radius: 6px;
106
+ --text-xs: 11px;
107
+ --text-sm: 12px;
108
+ --text-base: 13px;
109
+ --text-lg: 15px;
110
+ --text-xl: 20px;
111
+ --background: var(--c-panel);
112
+ --foreground: var(--c-text);
113
+ --card: var(--c-panel-soft);
114
+ --card-foreground: var(--c-text);
115
+ --popover: var(--c-panel);
116
+ --popover-foreground: var(--c-text);
117
+ --primary: var(--c-accent);
118
+ --primary-foreground: var(--c-on-accent, var(--c-on-pin));
119
+ --secondary: var(--c-panel-soft);
120
+ --secondary-foreground: var(--c-text);
121
+ --muted: var(--c-panel-soft);
122
+ --muted-foreground: var(--c-muted);
123
+ --accent: var(--c-panel-soft);
124
+ --accent-foreground: var(--c-text);
125
+ --destructive: var(--c-danger);
126
+ --border: var(--c-line);
127
+ --input: var(--c-line);
128
+ --ring: var(--c-accent);
129
+ --chart-1: var(--c-accent);
130
+ --chart-2: var(--c-rel-supports);
131
+ --chart-3: var(--c-rel-contradicts);
132
+ --chart-4: var(--c-rel-cites);
133
+ --chart-5: var(--c-rel-informs);
134
+ --chart-6: var(--c-muted);
135
+ font-size: 16px;
136
+ }
137
+
138
+ /* surface-theme's unlayered body rule otherwise wins over Tailwind's base layer. */
139
+ body {
140
+ background: var(--background);
141
+ color: var(--foreground);
142
+ padding: 0;
143
+ font-family: var(--font-sans);
144
+ font-size: 13px;
145
+ }
146
+
147
+ .pmx-viewer-root { width: 100%; min-width: 0; }
148
+ [data-embedded] .pmx-viewer-root:not(:has(> [data-slot="card"])) { padding: 12px 14px; }
149
+ [data-embedded] .pmx-viewer-root > [data-slot="card"] {
150
+ width: 100%;
151
+ max-width: none;
152
+ border: 0;
153
+ border-radius: 0;
154
+ box-shadow: none;
155
+ background: transparent;
156
+ padding: 12px 14px;
157
+ }
158
+ [data-embedded][data-duplicate-title] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-header"] > [data-slot="card-title"] { display: none; }
159
+ [data-embedded][data-duplicate-title] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-header"]:not(:has([data-slot="card-description"])) { display: none; }
160
+ [data-embedded] .pmx-viewer-root > [data-slot="card"] > [data-slot="card-content"] { padding: 0; }
161
+
102
162
  @theme inline {
103
163
  --radius-sm: calc(var(--radius) - 4px);
104
164
  --radius-md: calc(var(--radius) - 2px);
@@ -159,7 +219,7 @@ button {
159
219
  border: 1px solid transparent;
160
220
  border-radius: 9999px;
161
221
  padding: 0.125rem 0.5rem;
162
- font-size: 0.75rem;
222
+ font-size: max(11px, 0.75rem);
163
223
  font-weight: 500;
164
224
  line-height: 1.25rem;
165
225
  }
@@ -23,6 +23,7 @@ import { extraChartComponents } from '../charts/extra-components';
23
23
  import { tufteChartComponents } from '../charts/tufte-components';
24
24
  import { pmxCanvasDirectives } from '../directives';
25
25
  import { JsonRenderDevtools } from '@json-render/devtools-react';
26
+ import { canvasThemeScheme, isCanvasTheme } from '../../shared/themes.js';
26
27
 
27
28
  type BadgeVariant =
28
29
  | 'default'
@@ -167,10 +168,12 @@ function syncPreferredTheme(): void {
167
168
  }
168
169
 
169
170
  function applyTheme(theme: unknown): void {
170
- if (theme !== 'dark' && theme !== 'light' && theme !== 'high-contrast') return;
171
+ if (theme !== 'dark' && theme !== 'light' && !isCanvasTheme(theme)) return;
171
172
  document.documentElement.setAttribute('data-theme', theme);
172
- document.documentElement.classList.toggle('dark', theme === 'dark' || theme === 'high-contrast');
173
- document.documentElement.style.colorScheme = theme === 'light' ? 'light' : 'dark';
173
+ document.documentElement.toggleAttribute('data-canvas-theme', isCanvasTheme(theme));
174
+ const scheme = theme === 'light' ? 'light' : canvasThemeScheme(theme);
175
+ document.documentElement.classList.toggle('dark', scheme === 'dark');
176
+ document.documentElement.style.colorScheme = scheme;
174
177
  }
175
178
 
176
179
  function App({ store }: { store: StateStore }) {
@@ -235,11 +238,15 @@ function App({ store }: { store: StateStore }) {
235
238
  padding: 0,
236
239
  boxSizing: 'border-box' as const,
237
240
  }
238
- : { minHeight: '100vh', padding: 16, boxSizing: 'border-box' as const };
241
+ : { minHeight: '100vh', padding: 0, boxSizing: 'border-box' as const };
239
242
  return (
240
- <div style={containerStyle}>
243
+ <div
244
+ style={containerStyle}
245
+ data-embedded={!isSite ? '' : undefined}
246
+ data-duplicate-title={spec.elements[spec.root]?.props?.title === document.title ? '' : undefined}
247
+ >
241
248
  <JSONUIProvider registry={registry} store={store} directives={pmxCanvasDirectives} handlers={buildAxHandlers()}>
242
- <div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
249
+ <div className="pmx-viewer-root" style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
243
250
  <Renderer spec={spec} registry={registry} loading={false} />
244
251
  </div>
245
252
  {window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? <JsonRenderDevtools position="right" /> : null}
@@ -282,8 +289,25 @@ if (root) {
282
289
  // Standalone viewers have no state token and render immediately.
283
290
  window.addEventListener('message', (event: MessageEvent) => {
284
291
  if (event.source !== window.parent) return;
285
- const m = event.data as { source?: string; type?: string; token?: string; state?: unknown } | null;
292
+ const m = event.data as {
293
+ source?: string;
294
+ type?: string;
295
+ token?: string;
296
+ state?: unknown;
297
+ theme?: unknown;
298
+ title?: unknown;
299
+ } | null;
286
300
  if (!m) return;
301
+ if (m.source === HTML_SURFACE_PUSH_SOURCE && m.type === 'theme-update' && stateToken && m.token === stateToken) {
302
+ applyTheme(m.theme);
303
+ if (typeof m.title === 'string') {
304
+ document.title = m.title;
305
+ const spec = window.__PMX_CANVAS_JSON_RENDER_SPEC__;
306
+ document
307
+ .querySelector('[data-embedded]')
308
+ ?.toggleAttribute('data-duplicate-title', spec?.elements[spec.root]?.props?.title === m.title);
309
+ }
310
+ }
287
311
  if (m.source === JSON_VIEWER_STATE_SOURCE && m.type === 'restore' && stateToken && m.token === stateToken) {
288
312
  if (m.state === null || (typeof m.state === 'object' && !Array.isArray(m.state)))
289
313
  mount(m.state as Record<string, unknown> | null);
@@ -6,6 +6,7 @@ import { applySpecPatch, parseSpecStreamLine, type Spec, type SpecStreamLine } f
6
6
  import { allComponentDefinitions, catalog, validateShadcnElementProps, type JsonRenderIssue } from './catalog.js';
7
7
  import { findUnknownDirectiveKey, isDynamicPropValue } from './directives.js';
8
8
  import { contentHeightReporterTag } from '../shared/content-height-reporter.js';
9
+ import type { CanvasThemeName } from '../shared/themes.js';
9
10
 
10
11
  export interface JsonRenderSpec {
11
12
  root: string;
@@ -1030,7 +1031,7 @@ export async function readJsonRenderBundle(): Promise<{ js: string; css: string
1030
1031
  export async function buildJsonRenderViewerHtml(options: {
1031
1032
  title: string;
1032
1033
  spec: JsonRenderSpec;
1033
- theme?: 'dark' | 'light' | 'high-contrast';
1034
+ theme?: CanvasThemeName | 'dark' | 'light';
1034
1035
  display?: 'expanded' | 'site';
1035
1036
  devtools?: boolean;
1036
1037
  nodeId?: string;
@@ -1076,7 +1077,7 @@ export async function buildJsonRenderViewerHtml(options: {
1076
1077
  const heightReporter = options.frameToken ? contentHeightReporterTag(options.frameToken) : '';
1077
1078
  return buildAppHtml({
1078
1079
  title: options.title,
1079
- css: cssBundle,
1080
+ css: `${readFileSync(join(import.meta.dir, '../client/theme/surface-theme.css'), 'utf-8')}\n${cssBundle}`,
1080
1081
  js: escapeInlineScriptSource(boot),
1081
1082
  head: `<meta name="color-scheme" content="light dark" />${heightReporter}`,
1082
1083
  });
package/src/mcp/server.ts CHANGED
@@ -200,6 +200,8 @@ export function createMcpServer(): McpServer {
200
200
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
201
201
  outputSchema: interactionResult,
202
202
  inputSchema: {
203
+ targetBoardId: z.string().optional(),
204
+ agentId: z.string().optional(),
203
205
  type: z
204
206
  .enum(AX_INTERACTION_TYPES)
205
207
  .describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
@@ -213,13 +215,15 @@ export function createMcpServer(): McpServer {
213
215
  source: z.enum(AX_SOURCES).optional().describe('Optional host/source label. Defaults to mcp.'),
214
216
  },
215
217
  },
216
- async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source }, extra) => {
218
+ async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source, targetBoardId, agentId }, extra) => {
217
219
  try {
218
220
  const c = await ensureCanvas();
219
221
  extra.signal.throwIfAborted();
220
222
  const result = await c.invoker().invoke(
221
223
  'ax.interaction.submit',
222
224
  {
225
+ targetBoardId,
226
+ agentId,
223
227
  type,
224
228
  sourceNodeId,
225
229
  ...(payload ? { payload } : {}),
@@ -269,6 +273,8 @@ export function createMcpServer(): McpServer {
269
273
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
270
274
  outputSchema: activityResult,
271
275
  inputSchema: {
276
+ targetBoardId: z.string().optional(),
277
+ agentId: z.string().optional(),
272
278
  kind: z.enum([
273
279
  'tool-start',
274
280
  'tool-result',
@@ -327,13 +333,15 @@ export function createMcpServer(): McpServer {
327
333
  source: z.enum(AX_SOURCES).optional(),
328
334
  },
329
335
  },
330
- async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }, extra) => {
336
+ async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source, targetBoardId, agentId }, extra) => {
331
337
  try {
332
338
  const c = await ensureCanvas();
333
339
  extra.signal.throwIfAborted();
334
340
  const result = await c.invoker().invoke(
335
341
  'ax.activity.ingest',
336
342
  {
343
+ targetBoardId,
344
+ agentId,
337
345
  kind,
338
346
  title,
339
347
  ...(summary !== undefined ? { summary } : {}),
@@ -389,6 +397,7 @@ export function createMcpServer(): McpServer {
389
397
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
390
398
  outputSchema: screenshotResult,
391
399
  inputSchema: {
400
+ targetBoardId: z.string().optional().describe('Not supported: screenshots inspect the shared WebView.'),
392
401
  format: z
393
402
  .enum(['png', 'jpeg', 'webp'])
394
403
  .optional()
@@ -396,8 +405,9 @@ export function createMcpServer(): McpServer {
396
405
  quality: z.number().optional().describe('Optional quality for lossy formats'),
397
406
  },
398
407
  },
399
- async ({ format, quality }, extra) => {
408
+ async ({ format, quality, targetBoardId }, extra) => {
400
409
  try {
410
+ if (targetBoardId !== undefined) throw new Error('Screenshots inspect the shared WebView, not a scoped board.');
401
411
  const c = await ensureCanvas();
402
412
  extra.signal.throwIfAborted();
403
413
  const bytes = await c.screenshotAutomationWebView(
@@ -203,6 +203,8 @@ export const PRESENCE_SET_SHAPE = {
203
203
  const presenceSetSchema = z.object(PRESENCE_SET_SHAPE);
204
204
 
205
205
  interface StoredPresence extends AgentPresence {
206
+ spatialBoardId: string | null;
207
+ sessionBoardId: string | null;
206
208
  lastSeenMs: number;
207
209
  /** Last explicit phase report; cursor/focus heartbeats must not keep a stale phase alive. */
208
210
  phaseSetMs: number;
@@ -296,7 +298,7 @@ type SessionEndListener = (
296
298
  export class AgentPresenceRegistry {
297
299
  private readonly presences = new Map<string, StoredPresence>();
298
300
  /** Newest first; bounded. Survives a writer fading — the feed is history, the writer list is presence. */
299
- private activity: AgentActivityEntry[] = [];
301
+ private activity: Array<AgentActivityEntry & { boardId: string | null }> = [];
300
302
  private activitySeq = 0;
301
303
  private emit: PresenceEmitter = () => {};
302
304
  /** Single slots (like the emitter): server.ts owns the pre-session snapshot + receipt. */
@@ -435,6 +437,8 @@ export class AgentPresenceRegistry {
435
437
  }
436
438
  const existing = this.presences.get(key);
437
439
  const stored: StoredPresence = existing ?? {
440
+ spatialBoardId: canvasState.activeBoardId,
441
+ sessionBoardId: canvasState.activeBoardId,
438
442
  sessionId: key,
439
443
  source: input.source,
440
444
  agentId: input.agentId?.trim() || null,
@@ -458,6 +462,9 @@ export class AgentPresenceRegistry {
458
462
  touchesCache: null,
459
463
  };
460
464
  const wasAttached = stored.attached;
465
+ if (input.op || input.cursor !== undefined || input.focusNodeId !== undefined)
466
+ stored.spatialBoardId = canvasState.activeBoardId;
467
+ if (stored.attached && stored.sessionBoardId === null) stored.sessionBoardId = canvasState.activeBoardId;
461
468
  stored.lastSeenMs = now;
462
469
  stored.lastSeenAt = new Date(now).toISOString();
463
470
  if (input.label) stored.label = input.label;
@@ -466,6 +473,7 @@ export class AgentPresenceRegistry {
466
473
  if (input.activity?.summary) {
467
474
  this.activitySeq += 1;
468
475
  this.activity.unshift({
476
+ boardId: canvasState.activeBoardId,
469
477
  id: `act-${this.activitySeq}`,
470
478
  at: stored.lastSeenAt,
471
479
  sessionId: key,
@@ -480,6 +488,7 @@ export class AgentPresenceRegistry {
480
488
  }
481
489
  if (input.attached !== undefined) stored.attached = input.attached;
482
490
  if (!wasAttached && input.attached === true) {
491
+ stored.sessionBoardId = canvasState.activeBoardId;
483
492
  // *Start agent session* with one agent-less writer already on the board
484
493
  // adopts that writer — the human is claiming the agent they can see.
485
494
  if (stored.source === 'browser') {
@@ -539,13 +548,7 @@ export class AgentPresenceRegistry {
539
548
  captureBaseline(stored.startBoards, stored.startedAt);
540
549
  }
541
550
  if (input.attached === false) {
542
- if (wasAttached)
543
- this.onSessionEnd(
544
- this.publicView(stored, now),
545
- stored.startSnapshotId,
546
- input.endedBy ?? 'agent',
547
- stored.startedAt,
548
- );
551
+ if (wasAttached) this.endSession(stored, input.endedBy ?? 'agent', now);
549
552
  // An explicit detach always removes the presence. In particular, a
550
553
  // worker that reports attached:false must not be refreshed into another
551
554
  // full activity TTL as an external writer.
@@ -690,13 +693,21 @@ export class AgentPresenceRegistry {
690
693
  }
691
694
 
692
695
  /** Remove a writer (session-end). */
696
+ private endSession(stored: StoredPresence, endedBy: 'human' | 'agent' | 'idle-timeout', now: number): void {
697
+ const id = stored.sessionBoardId;
698
+ if (id && !canvasState.listBoards().some((board) => board.id === id)) return;
699
+ const finish = () =>
700
+ this.onSessionEnd(this.publicView(stored, now), stored.startSnapshotId, endedBy, stored.startedAt);
701
+ if (id) canvasState.withBoard(id, finish);
702
+ else canvasState.withVisibleBoard(finish);
703
+ }
704
+
693
705
  detach(sessionId: string): boolean {
694
706
  const stored = this.presences.get(sessionId);
695
707
  const removed = this.presences.delete(sessionId);
696
708
  if (removed) {
697
709
  this.dropAliasesTo(sessionId);
698
- if (stored?.attached)
699
- this.onSessionEnd(this.publicView(stored, Date.now()), stored.startSnapshotId, 'agent', stored.startedAt);
710
+ if (stored?.attached) this.endSession(stored, 'agent', Date.now());
700
711
  this.scheduleEmit();
701
712
  }
702
713
  this.maybeStopSweeper();
@@ -709,7 +720,10 @@ export class AgentPresenceRegistry {
709
720
  return {
710
721
  presences,
711
722
  sessionActive: isSessionActive(presences),
712
- activity: this.activity.map((entry) => ({ ...entry })),
723
+ activity: this.activity.map(({ boardId, ...entry }) => ({
724
+ ...entry,
725
+ nodeId: boardId === canvasState.activeBoardId ? entry.nodeId : null,
726
+ })),
713
727
  };
714
728
  }
715
729
 
@@ -744,6 +758,8 @@ export class AgentPresenceRegistry {
744
758
  startBoards: _startBoards,
745
759
  touchesCache: _touchesCache,
746
760
  lastActivityNodeId,
761
+ spatialBoardId,
762
+ sessionBoardId: _sessionBoardId,
747
763
  ...presence
748
764
  } = stored;
749
765
  if (withTouches) {
@@ -764,11 +780,15 @@ export class AgentPresenceRegistry {
764
780
  // Park-at fallback (joint-gaps #3): an attached-but-focusless writer is
765
781
  // invisible everywhere — fall back to the last node its activity touched.
766
782
  if (!presence.focusNodeId && lastActivityNodeId) presence.focusNodeId = lastActivityNodeId;
783
+ if (spatialBoardId !== canvasState.activeBoardId) {
784
+ presence.focusNodeId = null;
785
+ presence.cursor = null;
786
+ }
767
787
  // An attached session blocked on a human decision reads as waiting — the
768
788
  // gate itself lives in the AX state, this is only the presence view of it.
769
789
  // It overrides tooling too: the gate REQUEST is itself a write, and the
770
790
  // human's decision is the state that matters while it is pending.
771
- if (presence.attached && this.hasPendingGate()) {
791
+ if (presence.attached && spatialBoardId === canvasState.activeBoardId && this.hasPendingGate()) {
772
792
  phase = 'waiting-approval';
773
793
  detail = null;
774
794
  }
@@ -824,13 +844,7 @@ export class AgentPresenceRegistry {
824
844
  if (presence.lastSeenMs + ttl <= now) {
825
845
  this.presences.delete(key);
826
846
  this.dropAliasesTo(key);
827
- if (presence.attached)
828
- this.onSessionEnd(
829
- this.publicView(presence, now),
830
- presence.startSnapshotId,
831
- 'idle-timeout',
832
- presence.startedAt,
833
- );
847
+ if (presence.attached) this.endSession(presence, 'idle-timeout', now);
834
848
  changed = true;
835
849
  continue;
836
850
  }
@@ -866,14 +880,16 @@ export class AgentPresenceRegistry {
866
880
  if (this.emitTimer) return;
867
881
  this.emitTimer = setTimeout(() => {
868
882
  this.emitTimer = null;
869
- this.emit('agent-presence', this.snapshot() as unknown as Record<string, unknown>);
883
+ canvasState.withVisibleBoard(() =>
884
+ this.emit('agent-presence', this.snapshot() as unknown as Record<string, unknown>),
885
+ );
870
886
  }, 50);
871
887
  (this.emitTimer as { unref?: () => void }).unref?.();
872
888
  }
873
889
 
874
890
  private ensureSweeper(): void {
875
891
  if (this.sweepTimer || this.presences.size === 0) return;
876
- this.sweepTimer = setInterval(() => this.sweep(), 1000);
892
+ this.sweepTimer = setInterval(() => canvasState.withVisibleBoard(() => this.sweep()), 1000);
877
893
  (this.sweepTimer as { unref?: () => void }).unref?.();
878
894
  }
879
895
 
@@ -894,10 +910,14 @@ export const agentPresence = new AgentPresenceRegistry();
894
910
  // the operation registry are covered too.
895
911
  // The live lens also follows card content (a person's edit takes a card out of
896
912
  // the agent's session) and the open board — but not moves, so a drag stays quiet.
897
- let lastBoardId = canvasState.activeBoardId;
913
+ let lastBoardId = canvasState.visibleBoardId;
898
914
  let lastRevision = canvasState.getContentRevision().revision;
899
915
  canvasState.onChange((type) => {
900
- const boardId = canvasState.activeBoardId;
916
+ if (canvasState.isBackgroundBoard) {
917
+ agentPresence.refresh();
918
+ return;
919
+ }
920
+ const boardId = canvasState.visibleBoardId;
901
921
  const revision = canvasState.getContentRevision().revision;
902
922
  if (boardId !== lastBoardId) agentPresence.boardOpened();
903
923
  else if (type === 'ax' || type === 'pins' || revision !== lastRevision) agentPresence.refresh();
@@ -111,4 +111,10 @@ export function advanceAxFlowLoops(): void {
111
111
  canvasState.setWorkItemsChangedListener(() => {
112
112
  refreshWorkboardNodes();
113
113
  advanceAxFlowLoops();
114
+ // Task title/detail changes also repaint explicitly presented live cards.
115
+ if (
116
+ canvasState.getLayout().nodes.some((node) => node.type === 'status' && typeof node.data.workItemId === 'string')
117
+ ) {
118
+ emitCanvasLayoutUpdate();
119
+ }
114
120
  });
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { formatCountdown } from '../shared/approval-gates.js';
13
13
  import { canvasState } from './canvas-state.js';
14
+ import { withCurrentActor } from './attribution.js';
14
15
 
15
16
  type GateEmitter = (event: string, payload: Record<string, unknown>) => void;
16
17
 
@@ -50,7 +51,13 @@ export function sweepExpiredGates(now = Date.now()): string[] {
50
51
  export function startGateTtlSweeper(emitter: GateEmitter): void {
51
52
  emit = emitter;
52
53
  if (sweepTimer) return;
53
- sweepTimer = setInterval(() => sweepExpiredGates(), 1000);
54
+ sweepTimer = setInterval(
55
+ () =>
56
+ withCurrentActor({ actor: 'system', source: 'system' }, () => {
57
+ canvasState.forEachLiveBoard(() => sweepExpiredGates());
58
+ }),
59
+ 1000,
60
+ );
54
61
  (sweepTimer as { unref?: () => void }).unref?.();
55
62
  }
56
63
 
@@ -379,6 +379,7 @@ const PAYLOAD_SCHEMAS: Record<string, z.ZodType> = {
379
379
  * Injected so this module stays free of a runtime canvas-state import.
380
380
  */
381
381
  export interface AxInteractionManager {
382
+ readonly boardIdentity: object;
382
383
  getNode(id: string): CanvasNodeState | undefined;
383
384
  /** Canvas read for `ax.flow.materialize` (placement obstacles + stale flow sweep). */
384
385
  getLayout(): CanvasLayout;
@@ -722,22 +723,27 @@ function materializeFlow(
722
723
  */
723
724
  // ── ax.work.create idempotency window ─────────────
724
725
  const WORK_CREATE_DEDUPE_MS = 2_500;
725
- const recentWorkCreates = new Map<string, { item: PmxAxWorkItem; at: number }>();
726
+ const recentWorkCreates = new WeakMap<object, Map<string, { item: PmxAxWorkItem; at: number }>>();
726
727
 
727
- function recentWorkCreate(sourceNodeId: string, title: string): PmxAxWorkItem | null {
728
+ function recentWorkCreate(owner: object, sourceNodeId: string, title: string): PmxAxWorkItem | null {
728
729
  const key = `${sourceNodeId}\u0000${title}`;
729
- const hit = recentWorkCreates.get(key);
730
+ const hit = recentWorkCreates.get(owner)?.get(key);
730
731
  if (hit && Date.now() - hit.at <= WORK_CREATE_DEDUPE_MS) return hit.item;
731
732
  return null;
732
733
  }
733
734
 
734
- function rememberWorkCreate(sourceNodeId: string, title: string, item: PmxAxWorkItem): void {
735
+ function rememberWorkCreate(owner: object, sourceNodeId: string, title: string, item: PmxAxWorkItem): void {
735
736
  const key = `${sourceNodeId}\u0000${title}`;
736
- recentWorkCreates.set(key, { item, at: Date.now() });
737
- if (recentWorkCreates.size > 64) {
737
+ let entries = recentWorkCreates.get(owner);
738
+ if (!entries) {
739
+ entries = new Map();
740
+ recentWorkCreates.set(owner, entries);
741
+ }
742
+ entries.set(key, { item, at: Date.now() });
743
+ if (entries.size > 64) {
738
744
  const now = Date.now();
739
- for (const [k, v] of recentWorkCreates) {
740
- if (now - v.at > WORK_CREATE_DEDUPE_MS) recentWorkCreates.delete(k);
745
+ for (const [k, v] of entries) {
746
+ if (now - v.at > WORK_CREATE_DEDUPE_MS) entries.delete(k);
741
747
  }
742
748
  }
743
749
  }
@@ -851,7 +857,7 @@ export function applyAxInteraction(
851
857
  // booked two identical work items ~1s apart): the SAME node creating a
852
858
  // work item with the SAME title inside the window gets the first item
853
859
  // back instead of a duplicate.
854
- const dupe = recentWorkCreate(sourceNodeId, p.title);
860
+ const dupe = recentWorkCreate(manager.boardIdentity, sourceNodeId, p.title);
855
861
  if (dupe) return accept(type, sourceNodeId, dupe, 'ax-state-changed', { workItem: dupe });
856
862
  const workItem = manager.addWorkItem(
857
863
  {
@@ -862,7 +868,7 @@ export function applyAxInteraction(
862
868
  },
863
869
  opts,
864
870
  );
865
- rememberWorkCreate(sourceNodeId, p.title, workItem);
871
+ rememberWorkCreate(manager.boardIdentity, sourceNodeId, p.title, workItem);
866
872
  return withLayoutBroadcast(accept(type, sourceNodeId, workItem, 'ax-state-changed', { workItem }), manager, [
867
873
  workItem,
868
874
  ]);