pmx-canvas 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +4 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +440 -18
  4. package/CHANGELOG.md +51 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +267 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1168 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1839 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +301 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +117 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/{abnfDiagram-N423BO3Z-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
  35. package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
  36. package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/{cose-bilkent-JH36ORCC-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
  42. package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
  43. package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
  44. package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
  45. package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
  46. package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
  47. package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
  48. package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
  49. package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/{ganttDiagram-PKOTCBZU-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
  55. package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
  56. package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
  57. package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
  58. package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
  59. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  60. package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
  61. package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
  62. package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
  63. package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
  64. package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
  65. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  66. package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
  67. package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
  68. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  69. package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
  70. package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
  71. package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
  72. package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
  73. package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
  74. package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
  75. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  76. package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
  77. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  78. package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
  79. package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
  80. package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
  81. package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
  82. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  83. package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
  84. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  85. package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
  86. package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
  87. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  88. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  89. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  90. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  91. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  92. package/dist/canvas/chunks/{railroadDiagram-AXF67PYL-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
  93. package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
  94. package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
  95. package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
  96. package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
  97. package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
  98. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  99. package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
  100. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  101. package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
  102. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  103. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  104. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  105. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  106. package/dist/canvas/chunks/{wardleyDiagram-T6FBY63Y-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
  107. package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
  108. package/dist/canvas/global.css +94 -1
  109. package/dist/canvas/index.js +154 -145
  110. package/dist/canvas/mcp-app.html +600 -498
  111. package/dist/json-render/index.css +1 -1
  112. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  113. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  114. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  115. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  116. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  117. package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
  118. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  119. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  120. package/dist/types/client/state/canvas-store.d.ts +15 -0
  121. package/dist/types/client/state/human-store.d.ts +2 -0
  122. package/dist/types/client/state/intent-bridge.d.ts +4 -2
  123. package/dist/types/client/state/relation-store.d.ts +11 -0
  124. package/dist/types/client/types.d.ts +1 -1
  125. package/dist/types/json-render/catalog.d.ts +2 -2
  126. package/dist/types/server/agent-presence.d.ts +1 -0
  127. package/dist/types/server/ax-interaction.d.ts +1 -0
  128. package/dist/types/server/ax-state-manager.d.ts +10 -2
  129. package/dist/types/server/board-local.d.ts +4 -0
  130. package/dist/types/server/board-move.d.ts +38 -0
  131. package/dist/types/server/canvas-db.d.ts +12 -2
  132. package/dist/types/server/canvas-operations.d.ts +5 -6
  133. package/dist/types/server/canvas-provenance.d.ts +1 -1
  134. package/dist/types/server/canvas-serialization.d.ts +1 -0
  135. package/dist/types/server/canvas-state.d.ts +84 -20
  136. package/dist/types/server/card-relocation.d.ts +6 -0
  137. package/dist/types/server/context-reads.d.ts +3 -1
  138. package/dist/types/server/human-presence.d.ts +1 -1
  139. package/dist/types/server/index.d.ts +16 -5
  140. package/dist/types/server/intent-registry.d.ts +8 -4
  141. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  142. package/dist/types/server/mutation-history.d.ts +7 -5
  143. package/dist/types/server/operations/registry.d.ts +2 -0
  144. package/dist/types/server/trace-manager.d.ts +1 -0
  145. package/dist/types/shared/ax-kinds.d.ts +6 -0
  146. package/dist/types/shared/ax-surface-protocol.d.ts +8 -0
  147. package/dist/types/shared/human-presence.d.ts +1 -0
  148. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  149. package/docs/RELEASE.md +21 -14
  150. package/docs/agent-plugin.md +22 -2
  151. package/docs/amp-plugin.md +270 -0
  152. package/docs/cli.md +42 -3
  153. package/docs/design/GuestName.dc.html +50 -0
  154. package/docs/design/PluginLogo.svg +7 -0
  155. package/docs/design/WorkCards.dc.html +39 -0
  156. package/docs/design/canvas.json +16 -0
  157. package/docs/design/plugin-logo.png +0 -0
  158. package/docs/design.md +111 -8
  159. package/docs/evals/agents-md-sections.md +38 -0
  160. package/docs/http-api.md +128 -10
  161. package/docs/mcp.md +61 -9
  162. package/docs/node-types.md +71 -1
  163. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  164. package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
  165. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  166. package/docs/product-vision-2026-09.md +33 -17
  167. package/docs/sdk.md +18 -0
  168. package/package.json +6 -3
  169. package/plugins/pmx-canvas/assets/preview.png +0 -0
  170. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +4 -1
  171. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  172. package/plugins/pmx-canvas/plugin.json +2 -2
  173. package/plugins/pmx-canvas/runtime.json +1 -1
  174. package/plugins/pmx-canvas/tools.json +440 -18
  175. package/skills/data-analysis/SKILL.md +13 -26
  176. package/skills/frontend-design/LICENSE.txt +177 -0
  177. package/skills/frontend-design/SKILL.md +3 -0
  178. package/skills/pmx-canvas/SKILL.md +248 -842
  179. package/skills/pmx-canvas/evals/evals.json +110 -5
  180. package/skills/pmx-canvas/references/full-reference.md +142 -28
  181. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  182. package/skills/pmx-canvas/references/installing-pmx-canvas.md +61 -11
  183. package/skills/pmx-canvas/references/tours-and-recording.md +39 -6
  184. package/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  185. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  186. package/skills/published-consumer-e2e/SKILL.md +25 -8
  187. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -75
  188. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  189. package/skills/web-artifacts-builder/SKILL.md +6 -0
  190. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +2 -2
  191. package/skills/web-artifacts-builder/scripts/init-artifact.sh +8 -8
  192. package/src/cli/commands/apps.ts +1 -0
  193. package/src/cli/commands/boards.ts +30 -0
  194. package/src/client/App.tsx +20 -4
  195. package/src/client/canvas/AttentionHistory.tsx +10 -2
  196. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  197. package/src/client/canvas/CanvasNode.tsx +45 -2
  198. package/src/client/canvas/CanvasViewport.tsx +38 -9
  199. package/src/client/canvas/ContextMenu.tsx +34 -12
  200. package/src/client/canvas/EdgeEditor.tsx +149 -0
  201. package/src/client/canvas/EdgeLayer.tsx +87 -16
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
  203. package/src/client/canvas/GroupNavigation.tsx +85 -0
  204. package/src/client/canvas/RelationsPanel.tsx +9 -0
  205. package/src/client/canvas/SelectionBar.tsx +21 -0
  206. package/src/client/canvas/SessionPanel.tsx +35 -1
  207. package/src/client/canvas/TextPrompt.tsx +18 -1
  208. package/src/client/canvas/TopBar.tsx +30 -0
  209. package/src/client/canvas/canvas-area.ts +1 -1
  210. package/src/client/nodes/ExtAppFrame.tsx +26 -10
  211. package/src/client/nodes/MarkdownNode.tsx +6 -7
  212. package/src/client/nodes/PromptNode.tsx +32 -3
  213. package/src/client/nodes/StatusNode.tsx +12 -0
  214. package/src/client/nodes/StatusSummary.tsx +11 -0
  215. package/src/client/nodes/ViewerFrame.tsx +21 -10
  216. package/src/client/nodes/WorkCard.tsx +175 -0
  217. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  218. package/src/client/state/attention-bridge.ts +5 -0
  219. package/src/client/state/attention-store.ts +1 -0
  220. package/src/client/state/canvas-store.ts +185 -15
  221. package/src/client/state/human-store.ts +22 -2
  222. package/src/client/state/intent-bridge.ts +19 -5
  223. package/src/client/state/relation-store.ts +127 -1
  224. package/src/client/theme/global.css +94 -1
  225. package/src/client/types.ts +1 -0
  226. package/src/mcp/server.ts +13 -3
  227. package/src/server/agent-presence.ts +43 -23
  228. package/src/server/ax-flow-loop.ts +6 -0
  229. package/src/server/ax-gate-ttl.ts +8 -1
  230. package/src/server/ax-interaction.ts +16 -10
  231. package/src/server/ax-state-manager.ts +17 -4
  232. package/src/server/ax-wait.ts +20 -6
  233. package/src/server/board-export.ts +19 -6
  234. package/src/server/board-local.ts +20 -0
  235. package/src/server/board-move.ts +297 -0
  236. package/src/server/canvas-db.ts +262 -160
  237. package/src/server/canvas-operations.ts +139 -109
  238. package/src/server/canvas-schema.ts +21 -0
  239. package/src/server/canvas-serialization.ts +2 -0
  240. package/src/server/canvas-state.ts +792 -159
  241. package/src/server/card-relocation.ts +53 -0
  242. package/src/server/code-graph.ts +4 -3
  243. package/src/server/context-reads.ts +83 -55
  244. package/src/server/demo-state.json +35 -15
  245. package/src/server/file-watcher.ts +30 -10
  246. package/src/server/human-presence.ts +18 -5
  247. package/src/server/index.ts +35 -15
  248. package/src/server/intent-registry.ts +43 -5
  249. package/src/server/knowledge-lifecycle.ts +32 -9
  250. package/src/server/lifecycle-status.ts +29 -1
  251. package/src/server/mcp-app-runtime.ts +26 -7
  252. package/src/server/mutation-history.ts +37 -8
  253. package/src/server/operations/composites.ts +6 -3
  254. package/src/server/operations/mcp-contracts.ts +15 -0
  255. package/src/server/operations/mcp.ts +36 -9
  256. package/src/server/operations/ops/annotation.ts +22 -1
  257. package/src/server/operations/ops/ax-await.ts +1 -0
  258. package/src/server/operations/ops/ax-timeline.ts +28 -2
  259. package/src/server/operations/ops/batch.ts +25 -11
  260. package/src/server/operations/ops/boards.ts +44 -3
  261. package/src/server/operations/ops/nodes.ts +70 -0
  262. package/src/server/operations/ops/query.ts +32 -3
  263. package/src/server/operations/ops/snapshots.ts +1 -0
  264. package/src/server/operations/registry.ts +49 -4
  265. package/src/server/server.ts +62 -16
  266. package/src/server/tour-control.ts +37 -12
  267. package/src/server/trace-manager.ts +4 -0
  268. package/src/server/web-artifacts/scripts/bundle-artifact.sh +2 -2
  269. package/src/server/web-artifacts/scripts/init-artifact.sh +8 -8
  270. package/src/shared/agent-context.ts +7 -1
  271. package/src/shared/auto-arrange.ts +5 -4
  272. package/src/shared/ax-kinds.ts +13 -0
  273. package/src/shared/ax-surface-protocol.ts +22 -0
  274. package/src/shared/human-presence.ts +1 -0
  275. package/src/shared/lifecycle-status.ts +2 -0
  276. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
  277. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
  278. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
  279. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
  280. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
  281. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
  282. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
  283. package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
  284. package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
  285. package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
  286. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
  287. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
  288. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
  289. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
  290. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
  291. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
  292. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
  293. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
  294. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
  295. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
  296. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
  297. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
  298. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
  299. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
  300. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
@@ -0,0 +1,53 @@
1
+ import type { Database } from 'bun:sqlite';
2
+
3
+ /** JSON target list parameter; event ranges prevent a reused old ID inheriting the moved card's history. */
4
+ export const CARD_HISTORY_CTE = `WITH RECURSIVE
5
+ targets AS (SELECT json_extract(value, '$.boardId') AS current_board,
6
+ json_extract(value, '$.cardId') AS node_id FROM json_each(?)),
7
+ history(current_board, node_id, board_id, review_after, review_through, read_after, read_through, move_before) AS (
8
+ SELECT current_board, node_id, current_board,
9
+ COALESCE((SELECT MAX(review_through) FROM card_relocations r WHERE (r.from_board = t.current_board OR r.to_board = t.current_board) AND r.node_id = t.node_id), 0),
10
+ 9223372036854775807,
11
+ COALESCE((SELECT MAX(read_through) FROM card_relocations r WHERE (r.from_board = t.current_board OR r.to_board = t.current_board) AND r.node_id = t.node_id), 0),
12
+ 9223372036854775807, 9223372036854775807 FROM targets t
13
+ UNION ALL
14
+ SELECT h.current_board, h.node_id, r.from_board, r.review_after, r.review_through,
15
+ r.read_after, r.read_through, r.sequence
16
+ FROM history h JOIN card_relocations r ON r.to_board = h.board_id AND r.node_id = h.node_id
17
+ AND r.sequence = (SELECT MAX(sequence) FROM card_relocations prior
18
+ WHERE (prior.to_board = h.board_id OR prior.from_board = h.board_id)
19
+ AND prior.node_id = h.node_id AND prior.sequence < h.move_before)
20
+ )`;
21
+
22
+ /** Record locations, not new reads or validations. Called inside the move transaction. */
23
+ export function recordCardRelocation(
24
+ db: Database,
25
+ requestId: string,
26
+ from: string,
27
+ to: string,
28
+ nodeIds: string[],
29
+ ): void {
30
+ const reviewThrough = db
31
+ .query<{ value: number }, []>('SELECT COALESCE(MAX(sequence), 0) AS value FROM review_records')
32
+ .get()!.value;
33
+ const readThrough = db
34
+ .query<{ value: number }, []>('SELECT COALESCE(MAX(seq), 0) AS value FROM context_reads')
35
+ .get()!.value;
36
+ for (const id of nodeIds)
37
+ db.run(
38
+ `
39
+ INSERT INTO card_relocations(request_id, node_id, from_board, to_board, review_after, review_through, read_after, read_through)
40
+ VALUES (?, ?, ?, ?,
41
+ COALESCE((SELECT MAX(review_through) FROM card_relocations WHERE (from_board = ? OR to_board = ?) AND node_id = ?), 0), ?,
42
+ COALESCE((SELECT MAX(read_through) FROM card_relocations WHERE (from_board = ? OR to_board = ?) AND node_id = ?), 0), ?)`,
43
+ [requestId, id, from, to, from, from, id, reviewThrough, from, from, id, readThrough],
44
+ );
45
+ }
46
+
47
+ export function boardMoveBoundary(db: Database, boardId: string): string | null {
48
+ return (
49
+ db
50
+ .query<{ request_id: string }, [string]>('SELECT request_id FROM board_move_boundaries WHERE board_id = ?')
51
+ .get(boardId)?.request_id ?? null
52
+ );
53
+ }
@@ -18,6 +18,7 @@ import { resolve, dirname, basename, extname, relative } from 'node:path';
18
18
  import { existsSync } from 'node:fs';
19
19
  import { canvasState } from './canvas-state.js';
20
20
  import type { CanvasNodeState, CanvasEdge } from './canvas-state.js';
21
+ import { createBoardLocal } from './board-local.js';
21
22
 
22
23
  // ── Types ────────────────────────────────────────────────────────────
23
24
 
@@ -176,7 +177,7 @@ function tryResolveWithExtensions(basePath: string): string | null {
176
177
  const AUTO_EDGE_PREFIX = 'codegraph-';
177
178
 
178
179
  /** Cache of last recomputed edges for buildCodeGraphSummary() to reuse. */
179
- let _lastDiscoveredEdges: CodeGraphEdge[] = [];
180
+ const discovered = createBoardLocal(() => ({ edges: [] as CodeGraphEdge[] }));
180
181
 
181
182
  /**
182
183
  * Get all file nodes currently on the canvas.
@@ -263,7 +264,7 @@ export function recomputeCodeGraph(): CodeGraphEdge[] {
263
264
  }
264
265
  });
265
266
 
266
- _lastDiscoveredEdges = discoveredEdges;
267
+ discovered.current.edges = discoveredEdges;
267
268
  return discoveredEdges;
268
269
  }
269
270
 
@@ -295,7 +296,7 @@ export function buildCodeGraphSummary(): CodeGraphSummary {
295
296
  for (const node of fileNodes) idToPath.set(node.id, node.data.path as string);
296
297
 
297
298
  // Group import specifiers by source node from cached edges
298
- for (const edge of _lastDiscoveredEdges) {
299
+ for (const edge of discovered.current.edges) {
299
300
  const specs = importSpecs.get(edge.fromNodeId);
300
301
  if (specs && !specs.includes(edge.importSpecifier)) {
301
302
  specs.push(edge.importSpecifier);
@@ -6,9 +6,11 @@
6
6
  * recording never notifies (a read must not trigger more reads).
7
7
  */
8
8
  import type { Database } from 'bun:sqlite';
9
- import { randomUUID } from 'node:crypto';
9
+ import { createHash, randomUUID } from 'node:crypto';
10
+ import { CARD_HISTORY_CTE } from './card-relocation.js';
10
11
  import type { MemoryGraph } from '../shared/memory-graph.js';
11
12
  import type { CompiledContextEntry } from './context-brief.js';
13
+ import { OperationError } from './operations/types.js';
12
14
 
13
15
  export const CONTEXT_READ_RETENTION = 5000;
14
16
  const CONTEXT_READ_DEFAULT_LIMIT = 50;
@@ -51,6 +53,8 @@ export interface ContextRead {
51
53
  }
52
54
 
53
55
  export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId' | 'readNodes' | 'seenLinks'> & {
56
+ /** Stable adapter receipt identity. Retries record this delivery once per consumer and board. */
57
+ deliveryId?: string;
54
58
  /** Explicit read target, captured before asynchronous formatting/proxy work. */
55
59
  boardId?: string | null;
56
60
  /** Ids of every serialized node in what the reader received; the server keeps those on the board. */
@@ -264,6 +268,7 @@ export function graphReads(
264
268
  }
265
269
 
266
270
  export const CONTEXT_READS_SCHEMA_SQL = `
271
+ CREATE TABLE IF NOT EXISTS context_read_receipts (id TEXT PRIMARY KEY);
267
272
  CREATE TABLE IF NOT EXISTS context_reads (
268
273
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
269
274
  id TEXT NOT NULL UNIQUE,
@@ -289,30 +294,47 @@ export function appendContextReadToDB(
289
294
  readNodes: Record<string, number> = {},
290
295
  seenLinks: Record<string, number> = {},
291
296
  ): ContextRead {
292
- const id = `read-${randomUUID()}`;
293
- const at = new Date().toISOString();
294
- db.run(
295
- 'INSERT INTO context_reads (id, at, channel, resource, source, consumer, agent_id, pinned_node_ids, delivered_node_ids, bytes, board_id, read_nodes, seen_links) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
296
- [
297
- id,
298
- at,
299
- input.channel,
300
- input.resource,
301
- input.source,
302
- input.consumer,
303
- input.agentId,
304
- JSON.stringify(input.pinnedNodeIds),
305
- JSON.stringify(input.deliveredNodeIds),
306
- input.bytes,
307
- boardId,
308
- JSON.stringify(readNodes),
309
- JSON.stringify(seenLinks),
310
- ],
311
- );
312
- const seq = Number(db.query<{ seq: number }, []>('SELECT last_insert_rowid() AS seq').get()?.seq ?? 0);
313
- db.run('DELETE FROM context_reads WHERE seq <= ?', [seq - CONTEXT_READ_RETENTION]);
314
- const { readNodeIds: _readNodeIds, seenNodeIds: _seenNodeIds, ...record } = input;
315
- return { ...record, seq, id, at, boardId, readNodes, seenLinks };
297
+ return db.transaction(() => {
298
+ const id = input.deliveryId
299
+ ? `read-${createHash('sha256')
300
+ .update(JSON.stringify([input.source, input.consumer, boardId, input.deliveryId]))
301
+ .digest('hex')}`
302
+ : `read-${randomUUID()}`;
303
+ if (input.deliveryId) {
304
+ const existing = db.query<ContextReadRow, [string]>('SELECT * FROM context_reads WHERE id = ?').get(id);
305
+ if (existing) {
306
+ db.run('INSERT OR IGNORE INTO context_read_receipts VALUES (?)', [id]);
307
+ return rowToContextRead(existing);
308
+ }
309
+ if (db.query('SELECT id FROM context_read_receipts WHERE id = ?').get(id))
310
+ throw new OperationError('Context receipt expired. Obtain fresh context instead of retrying this token.', 409, {
311
+ code: 'context_receipt_expired',
312
+ });
313
+ }
314
+ const at = new Date().toISOString();
315
+ db.run(
316
+ 'INSERT INTO context_reads (id, at, channel, resource, source, consumer, agent_id, pinned_node_ids, delivered_node_ids, bytes, board_id, read_nodes, seen_links) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING',
317
+ [
318
+ id,
319
+ at,
320
+ input.channel,
321
+ input.resource,
322
+ input.source,
323
+ input.consumer,
324
+ input.agentId,
325
+ JSON.stringify(input.pinnedNodeIds),
326
+ JSON.stringify(input.deliveredNodeIds),
327
+ input.bytes,
328
+ boardId,
329
+ JSON.stringify(readNodes),
330
+ JSON.stringify(seenLinks),
331
+ ],
332
+ );
333
+ const row = db.query<ContextReadRow, [string]>('SELECT * FROM context_reads WHERE id = ?').get(id)!;
334
+ if (input.deliveryId) db.run('INSERT INTO context_read_receipts VALUES (?)', [id]);
335
+ db.run('DELETE FROM context_reads WHERE seq <= ?', [row.seq - CONTEXT_READ_RETENTION]);
336
+ return rowToContextRead(row);
337
+ })();
316
338
  }
317
339
 
318
340
  interface ContextReadRow {
@@ -351,27 +373,43 @@ function rowToContextRead(row: ContextReadRow): ContextRead {
351
373
  };
352
374
  }
353
375
 
376
+ /** Read only the matching card's observation, not a copy of the whole receipt for every card. */
377
+ function loadCardReads(db: Database, boardId: string, field: 'read_nodes' | 'seen_links') {
378
+ const ids = db
379
+ .query<{ id: string }, [string, string, string]>(`
380
+ SELECT id FROM nodes WHERE board_id = ?
381
+ UNION SELECT j.key AS id FROM context_reads r, json_each(r.read_nodes) j WHERE r.board_id = ?
382
+ UNION SELECT j.key AS id FROM context_reads r, json_each(r.seen_links) j WHERE r.board_id = ?
383
+ `)
384
+ .all(boardId, boardId, boardId);
385
+ return db
386
+ .query<{ nodeId: string; revision: number; at: string; who: string }, [string]>(`
387
+ ${CARD_HISTORY_CTE} SELECT h.node_id AS nodeId, j.value AS revision, r.at,
388
+ COALESCE(r.consumer, r.agent_id, r.source) AS who FROM history h JOIN context_reads r
389
+ ON r.board_id = h.board_id AND r.seq > h.read_after AND r.seq <= h.read_through
390
+ JOIN json_each(r.${field}) j ON j.key = h.node_id
391
+ ORDER BY r.seq DESC
392
+ `)
393
+ .all(JSON.stringify(ids.map(({ id }) => ({ boardId, cardId: id }))));
394
+ }
395
+
354
396
  /** Per node on a board, the latest agent read that delivered its content (newest row wins). */
355
397
  export function loadNodeReadStatusFromDB(db: Database, boardId: string): NodeReadStatus[] {
356
- const rows = db
357
- .query<ContextReadRow, [string]>('SELECT * FROM context_reads WHERE board_id = ? ORDER BY seq DESC')
358
- .all(boardId);
359
398
  const status = new Map<string, NodeReadStatus>();
360
- for (const read of rows.map(rowToContextRead)) {
361
- for (const [nodeId, readRevision] of Object.entries(read.readNodes)) {
362
- const known = status.get(nodeId);
363
- if (known) {
364
- known.readCount += 1;
365
- continue;
366
- }
367
- status.set(nodeId, {
368
- nodeId,
369
- lastReadAt: read.at,
370
- lastReadBy: read.consumer ?? read.agentId ?? read.source,
371
- readRevision,
372
- readCount: 1,
373
- });
399
+ for (const read of loadCardReads(db, boardId, 'read_nodes')) {
400
+ const nodeId = read.nodeId;
401
+ const known = status.get(nodeId);
402
+ if (known) {
403
+ known.readCount += 1;
404
+ continue;
374
405
  }
406
+ status.set(nodeId, {
407
+ nodeId,
408
+ lastReadAt: read.at,
409
+ lastReadBy: read.who,
410
+ readRevision: read.revision,
411
+ readCount: 1,
412
+ });
375
413
  }
376
414
  return [...status.values()];
377
415
  }
@@ -391,27 +429,17 @@ export function loadBoardLastReadFromDB(
391
429
 
392
430
  /** Per node on a board, the newest links revision any agent read has carried (connection marks). */
393
431
  export function loadSeenLinksFromDB(db: Database, boardId: string): Record<string, number> {
394
- const rows = db
395
- .query<{ seen_links: string }, [string]>('SELECT seen_links FROM context_reads WHERE board_id = ?')
396
- .all(boardId);
397
432
  const seen: Record<string, number> = {};
398
- for (const row of rows) {
399
- for (const [nodeId, revision] of Object.entries(JSON.parse(row.seen_links) as Record<string, number>)) {
400
- if (revision > (seen[nodeId] ?? -1)) seen[nodeId] = revision;
401
- }
433
+ for (const { nodeId, revision } of loadCardReads(db, boardId, 'seen_links')) {
434
+ if (revision > (seen[nodeId] ?? -1)) seen[nodeId] = revision;
402
435
  }
403
436
  return seen;
404
437
  }
405
438
 
406
439
  /** Every node on a board whose content an agent read at or after `since` (ISO). */
407
440
  export function loadReadNodeIdsSince(db: Database, boardId: string, since: string): string[] {
408
- const rows = db
409
- .query<{ read_nodes: string }, [string, string]>(
410
- 'SELECT read_nodes FROM context_reads WHERE board_id = ? AND at >= ? ORDER BY seq',
411
- )
412
- .all(boardId, since);
413
441
  const ids = new Set<string>();
414
- for (const row of rows) for (const id of Object.keys(JSON.parse(row.read_nodes) as object)) ids.add(id);
442
+ for (const row of loadCardReads(db, boardId, 'read_nodes')) if (row.at >= since) ids.add(row.nodeId);
415
443
  return [...ids];
416
444
  }
417
445
 
@@ -204,7 +204,7 @@
204
204
  "source": "api"
205
205
  },
206
206
  "contentRevision": 5,
207
- "linksRevision": 93,
207
+ "linksRevision": 94,
208
208
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
209
209
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
210
210
  "kind": "trace",
@@ -247,7 +247,7 @@
247
247
  "source": "api"
248
248
  },
249
249
  "contentRevision": 6,
250
- "linksRevision": 93,
250
+ "linksRevision": 94,
251
251
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
252
252
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
253
253
  "kind": "trace",
@@ -319,7 +319,7 @@
319
319
  "source": "api"
320
320
  },
321
321
  "contentRevision": 7,
322
- "linksRevision": 94,
322
+ "linksRevision": 95,
323
323
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
324
324
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
325
325
  "kind": "context",
@@ -362,7 +362,7 @@
362
362
  "source": "api"
363
363
  },
364
364
  "contentRevision": 8,
365
- "linksRevision": 94,
365
+ "linksRevision": 95,
366
366
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
367
367
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
368
368
  "kind": "ledger",
@@ -444,7 +444,7 @@
444
444
  "source": "api"
445
445
  },
446
446
  "contentRevision": 10,
447
- "linksRevision": 95,
447
+ "linksRevision": 96,
448
448
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
449
449
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
450
450
  "kind": "file",
@@ -483,7 +483,7 @@
483
483
  "source": "api"
484
484
  },
485
485
  "contentRevision": 11,
486
- "linksRevision": 95,
486
+ "linksRevision": 96,
487
487
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
488
488
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
489
489
  "kind": "diff",
@@ -778,7 +778,7 @@
778
778
  "source": "api"
779
779
  },
780
780
  "contentRevision": 19,
781
- "linksRevision": 96,
781
+ "linksRevision": 97,
782
782
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
783
783
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
784
784
  "kind": "mermaid",
@@ -817,7 +817,7 @@
817
817
  "source": "api"
818
818
  },
819
819
  "contentRevision": 20,
820
- "linksRevision": 96,
820
+ "linksRevision": 97,
821
821
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
822
822
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
823
823
  "kind": "mermaid",
@@ -886,7 +886,7 @@
886
886
  "pinned": false,
887
887
  "data": {
888
888
  "mode": "ext-app",
889
- "html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n body { margin:0; font:14px/1.5 var(--font-ui); background:var(--c-panel); color:var(--c-text); }\n main { padding:16px; display:grid; gap:12px; }\n .row { display:grid; grid-template-columns:1fr auto; align-items:center; gap:12px; padding:11px 13px; border:1px solid var(--c-line); border-radius:6px; background:var(--c-panel-soft); }\n .row b { font-weight:600; font-size:13px; } .row small { display:block; color:var(--c-muted); font-size:12px; }\n .pill { font:11px var(--font-mono); padding:3px 9px; border-radius:99px; background:var(--c-panel); color:var(--c-text-soft); border:1px solid var(--c-line); }\n footer { padding:12px 16px; border-top:1px solid var(--c-line); color:var(--c-muted); font-size:12px; }\n</style></head><body>\n <main>\n <div class=\"row\"><span><b>Nodes on board</b><small>across 8 spatial bands</small></span><span class=\"pill\">69</span></div>\n <div class=\"row\"><span><b>Edges</b><small>flow · depends-on · relation · references</small></span><span class=\"pill\">6</span></div>\n <div class=\"row\"><span><b>Context pins</b><small>read via canvas://pinned-context</small></span><span class=\"pill\">3</span></div>\n <div class=\"row\"><span><b>Groups</b><small>one example frame</small></span><span class=\"pill\">1</span></div>\n </main>\n <footer>An MCP App surface rendered through the app bridge from inline HTML — no live server session required.</footer>\n</body></html>",
889
+ "html": "<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><style>\n body { margin:0; font:14px/1.5 var(--font-ui); background:var(--c-panel); color:var(--c-text); }\n main { padding:16px; display:grid; gap:12px; }\n .row { display:grid; grid-template-columns:1fr auto; align-items:center; gap:12px; padding:11px 13px; border:1px solid var(--c-line); border-radius:6px; background:var(--c-panel-soft); }\n .row b { font-weight:600; font-size:13px; } .row small { display:block; color:var(--c-muted); font-size:12px; }\n .pill { font:11px var(--font-mono); padding:3px 9px; border-radius:99px; background:var(--c-panel); color:var(--c-text-soft); border:1px solid var(--c-line); }\n footer { padding:12px 16px; border-top:1px solid var(--c-line); color:var(--c-muted); font-size:12px; }\n</style></head><body>\n <main>\n <div class=\"row\"><span><b>Nodes on board</b><small>across 8 spatial bands</small></span><span class=\"pill\">69</span></div>\n <div class=\"row\"><span><b>Edges</b><small>flow · depends-on · relation · references</small></span><span class=\"pill\">7</span></div>\n <div class=\"row\"><span><b>Context pins</b><small>read via canvas://pinned-context</small></span><span class=\"pill\">3</span></div>\n <div class=\"row\"><span><b>Groups</b><small>one example frame</small></span><span class=\"pill\">1</span></div>\n </main>\n <footer>An MCP App surface rendered through the app bridge from inline HTML — no live server session required.</footer>\n</body></html>",
890
890
  "serverName": "pmx-demo",
891
891
  "toolName": "inspect_board",
892
892
  "title": "Board inspector",
@@ -3264,7 +3264,7 @@
3264
3264
  "source": "api"
3265
3265
  },
3266
3266
  "contentRevision": 46,
3267
- "linksRevision": 97,
3267
+ "linksRevision": 98,
3268
3268
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
3269
3269
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
3270
3270
  "kind": "markdown",
@@ -3308,7 +3308,7 @@
3308
3308
  "source": "api"
3309
3309
  },
3310
3310
  "contentRevision": 47,
3311
- "linksRevision": 97,
3311
+ "linksRevision": 98,
3312
3312
  "contentChangedAt": "2026-08-01T09:00:00.000Z",
3313
3313
  "freshnessStartedAt": "2026-08-01T09:00:00.000Z",
3314
3314
  "kind": "status",
@@ -5966,6 +5966,26 @@
5966
5966
  },
5967
5967
  "createdAt": "2026-08-01T09:00:00.000Z"
5968
5968
  },
5969
+ {
5970
+ "id": "edge-review-loop",
5971
+ "from": "trace-apply",
5972
+ "to": "trace-plan",
5973
+ "type": "flow",
5974
+ "kind": "loop",
5975
+ "reason": "An illustrated review loop, not an execution trigger.",
5976
+ "label": "review again",
5977
+ "revision": 94,
5978
+ "changedBy": {
5979
+ "actor": "agent",
5980
+ "source": "api"
5981
+ },
5982
+ "changedAt": "2026-08-01T09:00:00.000Z",
5983
+ "createdBy": {
5984
+ "actor": "agent",
5985
+ "source": "api"
5986
+ },
5987
+ "createdAt": "2026-08-01T09:00:00.000Z"
5988
+ },
5969
5989
  {
5970
5990
  "id": "edge-decisions",
5971
5991
  "from": "context",
@@ -5974,7 +5994,7 @@
5974
5994
  "kind": "supports",
5975
5995
  "reason": "The working context explains the recorded decisions.",
5976
5996
  "label": "decisions",
5977
- "revision": 94,
5997
+ "revision": 95,
5978
5998
  "changedBy": {
5979
5999
  "actor": "agent",
5980
6000
  "source": "api"
@@ -5993,7 +6013,7 @@
5993
6013
  "type": "depends-on",
5994
6014
  "label": "changed in",
5995
6015
  "sourceRevision": 11,
5996
- "revision": 95,
6016
+ "revision": 96,
5997
6017
  "changedBy": {
5998
6018
  "actor": "agent",
5999
6019
  "source": "api"
@@ -6012,7 +6032,7 @@
6012
6032
  "type": "references",
6013
6033
  "label": "approval gate",
6014
6034
  "style": "dashed",
6015
- "revision": 96,
6035
+ "revision": 97,
6016
6036
  "changedBy": {
6017
6037
  "actor": "agent",
6018
6038
  "source": "api"
@@ -6030,7 +6050,7 @@
6030
6050
  "to": "group-status",
6031
6051
  "type": "relation",
6032
6052
  "style": "dashed",
6033
- "revision": 97,
6053
+ "revision": 98,
6034
6054
  "changedBy": {
6035
6055
  "actor": "agent",
6036
6056
  "source": "api"
@@ -10,6 +10,7 @@ import { watch, existsSync, statSync } from 'node:fs';
10
10
  import type { FSWatcher } from 'node:fs';
11
11
  import { canvasState } from './canvas-state.js';
12
12
  import { applyFileContentToNodeData, readFileNodeContent } from './file-content.js';
13
+ import { createBoardLocal } from './board-local.js';
13
14
 
14
15
  function logFileWatcherWarning(action: string, error: unknown, details?: Record<string, unknown>): void {
15
16
  console.warn(`[file-watcher] ${action}`, { error, ...(details ?? {}) });
@@ -22,7 +23,22 @@ interface WatchedFile {
22
23
  lastMtime: number;
23
24
  }
24
25
 
25
- const watched = new Map<string, WatchedFile>();
26
+ const watcherSets = new Set<Map<string, WatchedFile>>();
27
+ function stopWatching(watched: Map<string, WatchedFile>): void {
28
+ for (const entry of watched.values()) entry.watcher.close();
29
+ watched.clear();
30
+ }
31
+ const boardWatchers = createBoardLocal(
32
+ () => {
33
+ const watched = new Map<string, WatchedFile>();
34
+ watcherSets.add(watched);
35
+ return watched;
36
+ },
37
+ (watched) => {
38
+ stopWatching(watched);
39
+ watcherSets.delete(watched);
40
+ },
41
+ );
26
42
  let onFileChanged: ((nodeId: string) => void) | null = null;
27
43
 
28
44
  /** Register a callback for when a watched file changes. */
@@ -34,6 +50,7 @@ export function onFileNodeChanged(cb: (nodeId: string) => void): void {
34
50
  export function watchFileForNode(nodeId: string, filePath: string): void {
35
51
  if (!filePath || !existsSync(filePath)) return;
36
52
 
53
+ const watched = boardWatchers.current;
37
54
  const existing = watched.get(filePath);
38
55
  if (existing) {
39
56
  existing.nodeIds.add(nodeId);
@@ -42,10 +59,14 @@ export function watchFileForNode(nodeId: string, filePath: string): void {
42
59
 
43
60
  try {
44
61
  const stat = statSync(filePath);
45
- const watcher = watch(filePath, { persistent: false }, (eventType) => {
46
- if (eventType !== 'change') return;
47
- handleFileChange(filePath);
48
- });
62
+ const watcher = watch(
63
+ filePath,
64
+ { persistent: false },
65
+ canvasState.bindBoard((eventType) => {
66
+ if (eventType !== 'change') return;
67
+ handleFileChange(filePath);
68
+ }),
69
+ );
49
70
 
50
71
  watched.set(filePath, {
51
72
  path: filePath,
@@ -60,6 +81,7 @@ export function watchFileForNode(nodeId: string, filePath: string): void {
60
81
 
61
82
  /** Stop watching a file for a given node. */
62
83
  export function unwatchFileForNode(nodeId: string, filePath?: string): void {
84
+ const watched = boardWatchers.current;
63
85
  for (const [path, entry] of watched) {
64
86
  if (filePath && path !== filePath) continue;
65
87
  entry.nodeIds.delete(nodeId);
@@ -72,14 +94,11 @@ export function unwatchFileForNode(nodeId: string, filePath?: string): void {
72
94
 
73
95
  /** Stop all watchers. */
74
96
  export function unwatchAll(): void {
75
- for (const entry of watched.values()) {
76
- entry.watcher.close();
77
- }
78
- watched.clear();
97
+ for (const watched of watcherSets) stopWatching(watched);
79
98
  }
80
99
 
81
100
  export function rewatchAllFileNodes(): void {
82
- unwatchAll();
101
+ stopWatching(boardWatchers.current);
83
102
  for (const node of canvasState.getLayout().nodes) {
84
103
  if (node.type === 'file' && typeof node.data.path === 'string') {
85
104
  watchFileForNode(node.id, node.data.path);
@@ -88,6 +107,7 @@ export function rewatchAllFileNodes(): void {
88
107
  }
89
108
 
90
109
  function handleFileChange(filePath: string): void {
110
+ const watched = boardWatchers.current;
91
111
  const entry = watched.get(filePath);
92
112
  if (!entry) return;
93
113
 
@@ -1,12 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import {
3
3
  HUMAN_GRAB_TTL_MS,
4
+ HUMAN_NAME_MAX_LENGTH,
4
5
  HUMAN_PRESENCE_TTL_MS,
5
6
  type HumanPresence,
6
7
  type HumanPresenceSnapshot,
7
8
  MAX_HUMANS,
8
9
  } from '../shared/human-presence.js';
9
10
  import { OperationError } from './operations/types.js';
11
+ import { canvasState } from './canvas-state.js';
10
12
 
11
13
  /**
12
14
  * Registry of open workbench tabs (rail-chrome-v2 phase 8). Fed by the
@@ -17,7 +19,7 @@ import { OperationError } from './operations/types.js';
17
19
  */
18
20
  export const HUMAN_PRESENCE_SHAPE = {
19
21
  clientId: z.string().min(1).max(64),
20
- name: z.string().min(1).max(40).optional(),
22
+ name: z.string().min(1).max(HUMAN_NAME_MAX_LENGTH).optional(),
21
23
  cursor: z.object({ x: z.number().finite(), y: z.number().finite() }).nullable().optional(),
22
24
  grabbingNodeId: z.string().max(200).nullable().optional(),
23
25
  /** Explicit leave (tab closing): drop the presence now. */
@@ -26,6 +28,7 @@ export const HUMAN_PRESENCE_SHAPE = {
26
28
  const presenceSchema = z.object(HUMAN_PRESENCE_SHAPE);
27
29
 
28
30
  interface Stored extends HumanPresence {
31
+ boardId: string | null;
29
32
  lastSeenMs: number;
30
33
  grabSinceMs: number | null;
31
34
  }
@@ -57,6 +60,7 @@ export class HumanPresenceRegistry {
57
60
  }
58
61
  const existing = this.humans.get(input.clientId);
59
62
  const stored: Stored = existing ?? {
63
+ boardId: canvasState.activeBoardId,
60
64
  clientId: input.clientId,
61
65
  name: input.name ?? 'Human',
62
66
  cursor: null,
@@ -65,6 +69,11 @@ export class HumanPresenceRegistry {
65
69
  lastSeenMs: now,
66
70
  grabSinceMs: null,
67
71
  };
72
+ if (stored.boardId !== canvasState.activeBoardId) {
73
+ stored.boardId = canvasState.activeBoardId;
74
+ stored.grabbingNodeId = null;
75
+ stored.grabSinceMs = null;
76
+ }
68
77
  stored.lastSeenMs = now;
69
78
  stored.lastSeenAt = new Date(now).toISOString();
70
79
  if (input.name) stored.name = input.name;
@@ -91,9 +100,10 @@ export class HumanPresenceRegistry {
91
100
  }
92
101
 
93
102
  /** Nodes a human is holding right now → that human's name. The edit lock. */
94
- lockedNodes(now = Date.now()): Map<string, string> {
103
+ lockedNodes(now = Date.now(), boardId = canvasState.activeBoardId): Map<string, string> {
95
104
  const locked = new Map<string, string>();
96
105
  for (const human of this.humans.values()) {
106
+ if (human.boardId !== boardId) continue;
97
107
  if (human.grabbingNodeId && human.grabSinceMs !== null && now - human.grabSinceMs <= HUMAN_GRAB_TTL_MS) {
98
108
  locked.set(human.grabbingNodeId, human.name);
99
109
  }
@@ -111,13 +121,16 @@ export class HumanPresenceRegistry {
111
121
  }
112
122
 
113
123
  private publicView(stored: Stored): HumanPresence {
114
- const { lastSeenMs: _lastSeenMs, grabSinceMs, ...human } = stored;
124
+ const { lastSeenMs: _lastSeenMs, grabSinceMs, boardId, ...human } = stored;
115
125
  // An expired grab reads as released even before the sweep drops it.
116
126
  const grabbing =
117
- human.grabbingNodeId && grabSinceMs !== null && Date.now() - grabSinceMs <= HUMAN_GRAB_TTL_MS
127
+ boardId === canvasState.activeBoardId &&
128
+ human.grabbingNodeId &&
129
+ grabSinceMs !== null &&
130
+ Date.now() - grabSinceMs <= HUMAN_GRAB_TTL_MS
118
131
  ? human.grabbingNodeId
119
132
  : null;
120
- return { ...human, grabbingNodeId: grabbing };
133
+ return { ...human, cursor: boardId === canvasState.activeBoardId ? human.cursor : null, grabbingNodeId: grabbing };
121
134
  }
122
135
 
123
136
  private sweep(now = Date.now(), options: { emit?: boolean } = {}): void {