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
@@ -1,11 +1,9 @@
1
- import type { RefObject } from 'preact';
2
- import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
1
+ import { useEffect, useMemo, useRef } from 'preact/hooks';
3
2
  import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
4
3
  import { canvasThemeScheme } from '../../shared/themes.js';
5
4
  import type { CanvasNodeState } from '../types';
6
5
  import { axSurfaceState, canvasTheme } from '../state/canvas-store';
7
- import { showToast } from '../state/attention-bridge';
8
- import { iframeMode } from '../state/iframe-mode';
6
+ import { RefreshingViewerFrame } from './ViewerFrame';
9
7
  import { shouldContentFitIframeNode } from '../canvas/auto-fit';
10
8
  import { ExtAppFrame } from './ExtAppFrame';
11
9
  import { useAxSurfaceBridge } from './use-ax-surface-bridge';
@@ -16,6 +14,7 @@ import { isHostedWorkbench } from '../state/workbench-transport';
16
14
 
17
15
  function withViewerParams(
18
16
  url: string,
17
+ theme: string,
19
18
  expanded: boolean,
20
19
  specVersion?: number,
21
20
  axToken?: string,
@@ -28,9 +27,7 @@ function withViewerParams(
28
27
  try {
29
28
  const hosted = isHostedWorkbench() && url.startsWith('/');
30
29
  const resolved = new URL(url, hosted ? 'https://pmx.invalid' : window.location.origin);
31
- // The json-render viewer + artifacts understand dark/light — collapse named
32
- // themes (sepia → light, midnight/arctic/ember/forest → dark) to a scheme.
33
- resolved.searchParams.set('theme', canvasThemeScheme(canvasTheme.value));
30
+ resolved.searchParams.set('theme', theme);
34
31
  if (expanded) resolved.searchParams.set('display', 'expanded');
35
32
  // Streaming json-render nodes bump specVersion as patches accumulate; including
36
33
  // it in the src reloads the iframe so it re-reads the latest accumulated spec.
@@ -61,139 +58,6 @@ export function isSameOriginFrameDocumentUrl(url: string, origin = window.locati
61
58
  }
62
59
  }
63
60
 
64
- type ViewerFrameSource = { src?: string; srcdoc?: string };
65
-
66
- /**
67
- * Keep the last painted viewer in front while its replacement navigates. Viewer
68
- * specs are immutable documents, so updates normally change `?v=` and reload the
69
- * iframe; putting the new document behind the old one conceals Chromium's white
70
- * navigation paint without suppressing rendering. Wait for restore and render commit,
71
- * then two animation frames before the old viewer is removed.
72
- */
73
- export function RefreshingViewerFrame({
74
- source,
75
- iframeRef,
76
- onLoad,
77
- onDocumentLoad,
78
- title,
79
- className = 'mcp-app-frame',
80
- sandbox = 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox',
81
- allow = 'clipboard-read; clipboard-write',
82
- tabIndex,
83
- }: {
84
- source: ViewerFrameSource;
85
- iframeRef: RefObject<HTMLIFrameElement>;
86
- onLoad: () => void;
87
- onDocumentLoad?: (frame: HTMLIFrameElement, signal: AbortSignal) => void | Promise<boolean | 'cancelled' | void>;
88
- title: string;
89
- className?: string;
90
- sandbox?: string;
91
- allow?: string;
92
- tabIndex?: number;
93
- }) {
94
- const sourceKey = source.src ?? source.srcdoc ?? '';
95
- const [painted, setPainted] = useState({ key: sourceKey, source, attempt: 0 });
96
- const [pending, setPending] = useState<{ key: string; source: ViewerFrameSource; attempt: number } | null>(null);
97
- const promotionRef = useRef(0);
98
- const readiness = useRef(new Set<AbortController>());
99
-
100
- useLayoutEffect(() => {
101
- promotionRef.current += 1;
102
- for (const controller of readiness.current) controller.abort();
103
- readiness.current.clear();
104
- if (!sourceKey || sourceKey === painted.key) {
105
- setPending(null);
106
- return;
107
- }
108
- if (!painted.key) {
109
- setPainted({ key: sourceKey, source, attempt: 0 });
110
- return;
111
- }
112
- setPending({ key: sourceKey, source, attempt: 0 });
113
- }, [sourceKey, painted.key]);
114
-
115
- useEffect(
116
- () => () => {
117
- promotionRef.current += 1;
118
- for (const controller of readiness.current) controller.abort();
119
- readiness.current.clear();
120
- },
121
- [],
122
- );
123
-
124
- const frame = (entry: { key: string; source: ViewerFrameSource; attempt: number }, isPending: boolean) => (
125
- <iframe
126
- key={`${entry.key}:${entry.attempt}`}
127
- ref={isPending ? undefined : iframeRef}
128
- {...entry.source}
129
- class={className}
130
- sandbox={sandbox}
131
- allow={allow}
132
- tabIndex={tabIndex}
133
- inert={isPending}
134
- aria-hidden={isPending ? true : undefined}
135
- loading={iframeMode.value === 'srcdoc' ? undefined : 'lazy'}
136
- onLoad={async (event) => {
137
- // Seed runtime state while a replacement is still behind the painted
138
- // frame, and wait until that document has committed its rendered UI.
139
- const token = isPending ? ++promotionRef.current : promotionRef.current;
140
- const loadedFrame = event.currentTarget;
141
- const controller = new AbortController();
142
- readiness.current.add(controller);
143
- const ready = onDocumentLoad?.(loadedFrame, controller.signal);
144
- const loaded = ready ? await ready : undefined;
145
- readiness.current.delete(controller);
146
- if (promotionRef.current !== token || loaded === 'cancelled') return;
147
- if (loaded === false) {
148
- if (entry.attempt === 0) {
149
- const retry = { ...entry, attempt: 1 };
150
- if (isPending) setPending(retry);
151
- else setPainted(retry);
152
- } else {
153
- if (isPending) setPending(null);
154
- showToast(
155
- 'remove',
156
- 'Viewer could not load',
157
- `${isPending ? 'Keeping the last view. ' : ''}Reload to retry.`,
158
- );
159
- }
160
- return;
161
- }
162
- if (!isPending) {
163
- onLoad();
164
- return;
165
- }
166
- requestAnimationFrame(() =>
167
- requestAnimationFrame(() => {
168
- if (promotionRef.current !== token) return;
169
- iframeRef.current = loadedFrame;
170
- setPainted(entry);
171
- setPending(null);
172
- onLoad();
173
- }),
174
- );
175
- }}
176
- style={{
177
- position: 'absolute',
178
- inset: 0,
179
- width: '100%',
180
- height: '100%',
181
- visibility: 'visible',
182
- zIndex: isPending ? 0 : 1,
183
- background: 'var(--c-panel-soft)',
184
- }}
185
- title={title}
186
- />
187
- );
188
-
189
- return (
190
- <div class="mcp-app-frame-stack">
191
- {frame(painted, false)}
192
- {pending && frame(pending, true)}
193
- </div>
194
- );
195
- }
196
-
197
61
  export function McpAppNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
198
62
  if (node.data.mode === 'ext-app') {
199
63
  return <ExtAppFrame node={node} expanded={expanded} />;
@@ -223,7 +87,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
223
87
  // overlay frame (fill-down), not sit at its intrinsic in-canvas height.
224
88
  const contentFit = shouldContentFitIframeNode(node) && !expanded;
225
89
  const frameToken = useMemo(() => (contentFit ? `frame-${crypto.randomUUID()}` : ''), [contentFit]);
226
- useIframeContentHeight(node, iframeRef, frameToken);
90
+ const measureContent = useIframeContentHeight(node, iframeRef, frameToken);
227
91
 
228
92
  // AX emits forwarded by the json-render viewer — the shared sandboxed-surface
229
93
  // trust boundary (M2).
@@ -257,15 +121,38 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
257
121
  () => (isJsonViewer ? `ui-${crypto.randomUUID()}` : ''),
258
122
  [isJsonViewer, specVersion, node.data.url],
259
123
  );
260
- const url = withViewerParams(
261
- (node.data.url as string) || '',
262
- expanded,
263
- specVersion,
264
- axToken || undefined,
265
- isAxViewer ? node.id : undefined,
266
- frameToken || undefined,
267
- contentFit,
268
- stateToken || undefined,
124
+ const theme = canvasTheme.value;
125
+ const urlTheme = isJsonViewer ? null : canvasThemeScheme(theme);
126
+ const pushTheme = () => {
127
+ if (!isJsonViewer) return;
128
+ iframeRef.current?.contentWindow?.postMessage(
129
+ {
130
+ source: HTML_SURFACE_PUSH_SOURCE,
131
+ type: 'theme-update',
132
+ token: stateToken,
133
+ theme: node.data.theme ?? theme,
134
+ title: node.data.title,
135
+ },
136
+ '*',
137
+ );
138
+ };
139
+ useEffect(pushTheme, [theme, stateToken, node.data.theme, node.data.title]);
140
+ // Only JSON/graph understand theme messages and retain drafts across themes.
141
+ // Generic viewers consume a dark/light query and navigate when it changes.
142
+ const url = useMemo(
143
+ () =>
144
+ withViewerParams(
145
+ (node.data.url as string) || '',
146
+ urlTheme ?? theme,
147
+ expanded,
148
+ specVersion,
149
+ axToken || undefined,
150
+ isAxViewer ? node.id : undefined,
151
+ frameToken || undefined,
152
+ contentFit,
153
+ stateToken || undefined,
154
+ ),
155
+ [node.data.url, urlTheme, expanded, specVersion, axToken, node.id, frameToken, contentFit, stateToken],
269
156
  );
270
157
  // src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
271
158
  // External viewer URLs always stay src — the hook passes them through.
@@ -295,7 +182,9 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
295
182
  >
296
183
  {url}
297
184
  </a>
298
- {sourceServer && <div style={{ color: 'var(--c-dim)', fontSize: '10px' }}>Source: {sourceServer}</div>}
185
+ {sourceServer && (
186
+ <div style={{ color: 'var(--c-dim)', fontSize: 'var(--fs-meta)' }}>Source: {sourceServer}</div>
187
+ )}
299
188
  </div>
300
189
  );
301
190
  }
@@ -313,7 +202,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
313
202
  <div
314
203
  style={{
315
204
  padding: '4px 8px',
316
- fontSize: '10px',
205
+ fontSize: 'var(--fs-meta)',
317
206
  background: 'color-mix(in srgb, var(--c-warn) 10%, transparent)',
318
207
  color: 'var(--c-warn)',
319
208
  borderBottom: '1px solid color-mix(in srgb, var(--c-warn) 15%, transparent)',
@@ -328,11 +217,16 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
328
217
  <RefreshingViewerFrame
329
218
  source={surfaceFrame}
330
219
  iframeRef={iframeRef}
220
+ allow="clipboard-read; clipboard-write"
331
221
  onDocumentLoad={(frame, signal) => {
332
222
  if (stateToken && frame.contentWindow)
333
- return restoreViewerRuntimeState(node.id, stateToken, frame.contentWindow, signal);
223
+ return restoreViewerRuntimeState(node, stateToken, frame.contentWindow, signal);
224
+ }}
225
+ onLoad={() => {
226
+ measureContent();
227
+ pushAxState();
228
+ pushTheme();
334
229
  }}
335
- onLoad={pushAxState}
336
230
  title={`MCP App: ${sourceServer}`}
337
231
  />
338
232
  </div>
@@ -1,82 +1,93 @@
1
- import { useEffect, useMemo, useRef } from 'preact/hooks';
2
- import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
3
- import { canvasTheme } from '../state/canvas-store';
1
+ import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
2
+ import { renderMermaid } from '../mermaid-render';
3
+ import { canvasTheme, nodes, updateNode } from '../state/canvas-store';
4
+ import { pushCanvasUpdate } from '../state/intent-bridge';
5
+ import { computeContentGrowHeight, contentFitPosition } from '../canvas/auto-fit';
4
6
  import type { CanvasNodeState } from '../types';
5
- import { nodeSurfaceUrl, surfaceContentHash } from './surface-url';
6
- import { useIframeContentHeight } from './use-iframe-content-height';
7
- import { useSurfaceFrame } from './use-surface-frame';
8
7
 
9
- /**
10
- * Mermaid diagram node — a display-only iframe surface. The server renders the
11
- * surface document (escaped diagram source + /canvas/mermaid-entry.js) at
12
- * /api/canvas/surface/:id; the iframe and "Open as site" share that one render
13
- * path, exactly like html nodes. Minimal subset of HtmlNode: theme push and
14
- * content-height growth, no AX bridge, no presentation mode.
15
- */
16
8
  export function MermaidNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
17
- const iframeRef = useRef<HTMLIFrameElement>(null);
18
- const theme = canvasTheme.value;
19
- // Stable per-mount nonce that authorizes parent → iframe theme-update messages.
20
- const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
21
- // Per-mount nonce for the content-height reporter (node grows to fit content).
22
- const frameToken = useMemo(() => `frame-${crypto.randomUUID()}`, []);
9
+ const container = useRef<HTMLDivElement>(null);
10
+ const [error, setError] = useState('');
11
+ const [attempt, setAttempt] = useState(0);
23
12
  const source = typeof node.data.content === 'string' ? node.data.content : '';
24
- const fit = node.data.fit === 'none' ? 'none' : 'contain';
25
- const v = useMemo(() => surfaceContentHash(`${fit}:${source}`), [source, fit]);
26
-
27
- // `theme` is intentionally excluded from the deps: live theme changes are
28
- // pushed via postMessage below (no reload), while `v` reloads the frame when
29
- // the diagram source changes.
30
- const surfaceSrc = useMemo(
31
- () => (source ? nodeSurfaceUrl(node.id, { theme, themeToken, v, frameToken }) : ''),
32
- [source, themeToken, v, node.id, frameToken],
33
- );
34
-
35
- // src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
36
- const surfaceFrame = useSurfaceFrame(surfaceSrc);
13
+ const theme = canvasTheme.value;
14
+ const fit = node.data.fit !== 'none';
15
+ // Fit is layout, not content: it resizes the painted SVG in place. Re-rendering
16
+ // on a fit change replaced the element and left a detached SVG mid-update.
17
+ const fitRef = useRef(fit);
18
+ fitRef.current = fit;
37
19
 
38
- // Grow the node to fit the surface's reported content height (grow-only, gated).
39
- // Never in the expanded overlay — there the surface fills the large overlay frame.
40
- useIframeContentHeight(node, iframeRef, expanded ? '' : frameToken);
20
+ const fitDiagram = () => {
21
+ const element = container.current;
22
+ const svg = element?.querySelector('svg');
23
+ const box = svg?.viewBox.baseVal;
24
+ if (!element || !svg || !box?.width || !box.height) return;
25
+ const scale = fitRef.current ? Math.min(1, element.clientWidth / box.width, element.clientHeight / box.height) : 1;
26
+ svg.style.maxWidth = 'none';
27
+ svg.style.width = `${box.width * scale}px`;
28
+ svg.style.height = `${box.height * scale}px`;
29
+ svg.style.margin = fitRef.current ? '0 auto' : '0';
30
+ };
41
31
 
42
- const pushTheme = () => {
43
- iframeRef.current?.contentWindow?.postMessage(
44
- {
45
- source: HTML_SURFACE_PUSH_SOURCE,
46
- type: 'theme-update',
47
- token: themeToken,
48
- theme,
49
- },
50
- '*',
51
- );
32
+ const growToDiagram = () => {
33
+ const element = container.current;
34
+ const box = element?.querySelector('svg')?.viewBox.baseVal;
35
+ const current = nodes.value.get(node.id);
36
+ if (expanded || !element || !box?.width || !current) return;
37
+ const naturalHeight = box.height * (fitRef.current ? Math.min(1, element.clientWidth / box.width) : 1);
38
+ const card = element.closest<HTMLElement>('.canvas-node')!;
39
+ const height = computeContentGrowHeight(current, naturalHeight, card.offsetHeight - element.clientHeight);
40
+ if (height === null) return;
41
+ const position = contentFitPosition(current, height, Array.from(nodes.value.values()));
42
+ const patch = { size: { width: current.size.width, height }, position };
43
+ updateNode(node.id, patch);
44
+ void pushCanvasUpdate([{ id: node.id, ...patch }], { recordHistory: false });
52
45
  };
53
46
 
54
- useEffect(pushTheme, [theme, themeToken]);
47
+ useEffect(() => {
48
+ let cancelled = false;
49
+ const element = container.current;
50
+ if (!element || !source) return;
51
+ const observer = new ResizeObserver(fitDiagram);
52
+ observer.observe(element);
53
+ const render = async () => {
54
+ try {
55
+ const svg = await renderMermaid(source);
56
+ if (cancelled) return;
57
+ element.innerHTML = svg;
58
+ setError('');
59
+ element.querySelector('svg')?.setAttribute('preserveAspectRatio', 'xMidYMid meet');
60
+ fitDiagram();
61
+ growToDiagram();
62
+ } catch (failure) {
63
+ if (!cancelled) setError(failure instanceof Error ? failure.message : String(failure));
64
+ }
65
+ };
66
+ void render();
67
+ return () => {
68
+ cancelled = true;
69
+ observer.disconnect();
70
+ };
71
+ }, [source, theme, expanded, attempt]);
55
72
 
56
- if (!source) {
57
- return <div style={{ color: 'var(--c-dim)', fontStyle: 'italic', padding: '12px' }}>No diagram source set</div>;
58
- }
73
+ // Same commit as data-fit, so the attribute and the painted size never disagree.
74
+ useLayoutEffect(() => {
75
+ fitDiagram();
76
+ growToDiagram();
77
+ }, [fit]);
59
78
 
60
- // SECURITY: sandbox stays `allow-scripts` ONLY — same tier as html surfaces
61
- // (see HtmlNode for the full rationale); the surface route sends a matching
62
- // CSP sandbox header.
79
+ if (!source) return <div class="viewer-placeholder">No diagram source set</div>;
63
80
  return (
64
- <iframe
65
- ref={iframeRef}
66
- class="html-node-frame"
67
- title={typeof node.data.title === 'string' ? node.data.title : 'Mermaid diagram'}
68
- sandbox="allow-scripts"
69
- {...surfaceFrame}
70
- onLoad={pushTheme}
71
- style={{
72
- width: '100%',
73
- height: '100%',
74
- minHeight: expanded ? '70vh' : '0',
75
- border: 'none',
76
- background: 'var(--c-bg)',
77
- borderRadius: '6px',
78
- display: 'block',
79
- }}
80
- />
81
+ <div class="mermaid-viewer" data-fit={fit ? 'contain' : 'none'}>
82
+ <div ref={container} class="mermaid-diagram" />
83
+ {error && (
84
+ <div class="viewer-fallback" role="status">
85
+ <span>{error}</span>
86
+ <button type="button" onClick={() => setAttempt(attempt + 1)}>
87
+ Retry
88
+ </button>
89
+ </div>
90
+ )}
91
+ </div>
81
92
  );
82
93
  }
@@ -1,10 +1,17 @@
1
- import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
2
2
  import { IconClose } from '../icons';
3
+ import { activeBoardId } from '../state/boards-store';
3
4
  import { updateNodeData } from '../state/canvas-store';
4
5
  import { fetchSlashCommands, renderMarkdown, submitCanvasPrompt, submitThreadReply } from '../state/intent-bridge';
5
6
  import type { CanvasNodeState } from '../types';
6
7
  import { sanitizeMarkdownHtml } from './markdown-sanitize';
7
8
 
9
+ // Multiple inline/focus renderers may own inputs for the same qualified card.
10
+ const unsentInputs = new Set<{ boardId: string | null; nodeId: string; draft: string; reply: string }>();
11
+ export function hasUnsavedPrompt(boardId: string, nodeId: string): boolean {
12
+ return [...unsentInputs].some((input) => input.boardId === boardId && input.nodeId === nodeId);
13
+ }
14
+
8
15
  // Cached slash commands — fetched once on first use.
9
16
  let cachedCommands: Array<{ name: string; description: string }> | null = null;
10
17
  async function getCommands() {
@@ -57,7 +64,7 @@ function ContextBadge({ count }: { count: number }) {
57
64
  padding: '4px 8px',
58
65
  fontSize: '11px',
59
66
  color: 'var(--c-accent)',
60
- background: 'rgba(70,182,255,0.08)',
67
+ background: 'color-mix(in srgb, var(--c-pin) 8%, transparent)',
61
68
  borderRadius: 'var(--radius-sm)',
62
69
  flexShrink: 0,
63
70
  }}
@@ -75,7 +82,7 @@ function ErrorBanner({ message, onDismiss }: { message: string | null; onDismiss
75
82
  padding: '4px 8px',
76
83
  fontSize: '11px',
77
84
  color: 'var(--c-danger)',
78
- background: 'rgba(255,80,80,0.08)',
85
+ background: 'color-mix(in srgb, var(--c-danger) 8%, transparent)',
79
86
  borderRadius: 'var(--radius-sm)',
80
87
  display: 'flex',
81
88
  alignItems: 'center',
@@ -117,8 +124,30 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
117
124
  const isStreaming = threadStatus === 'streaming';
118
125
  const isAnswered = threadStatus === 'answered';
119
126
 
120
- const [draft, setDraft] = useState('');
121
- const [replyDraft, setReplyDraft] = useState('');
127
+ const [draft, updateDraft] = useState('');
128
+ const [replyDraft, updateReplyDraft] = useState('');
129
+ const boardId = activeBoardId.value;
130
+ const input = useMemo(() => ({ boardId, nodeId: node.id, draft: '', reply: '' }), [boardId, node.id]);
131
+ const remember = () => {
132
+ if (input.draft || input.reply) unsentInputs.add(input);
133
+ else unsentInputs.delete(input);
134
+ };
135
+ const setDraft = (text: string) => {
136
+ input.draft = text;
137
+ remember();
138
+ updateDraft(text);
139
+ };
140
+ const setReplyDraft = (text: string) => {
141
+ input.reply = text;
142
+ remember();
143
+ updateReplyDraft(text);
144
+ };
145
+ useEffect(
146
+ () => () => {
147
+ unsentInputs.delete(input);
148
+ },
149
+ [input],
150
+ );
122
151
  const [error, setError] = useState<string | null>(null);
123
152
  const [renderedTurns, setRenderedTurns] = useState<Map<number, string>>(new Map());
124
153
  const textareaRef = useRef<HTMLTextAreaElement>(null);
@@ -312,7 +341,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
312
341
  style={{
313
342
  flex: 1,
314
343
  resize: 'none',
315
- background: 'rgba(10,14,30,0.4)',
344
+ background: 'var(--c-input-bg)',
316
345
  border: '1px solid var(--c-line)',
317
346
  borderRadius: 'var(--radius-sm)',
318
347
  color: 'var(--c-text)',
@@ -324,7 +353,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
324
353
  }}
325
354
  />
326
355
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
327
- <span style={{ fontSize: '10px', color: 'var(--c-muted)' }}>{'\u2318'}+Enter to send</span>
356
+ <kbd class="prompt-key-hint">{'\u2318'}+Enter to send</kbd>
328
357
  <button
329
358
  type="button"
330
359
  onClick={handleSubmit}
@@ -377,10 +406,9 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
377
406
  >
378
407
  <span
379
408
  style={{
380
- fontSize: '10px',
381
- textTransform: 'uppercase',
409
+ fontSize: 'var(--fs-meta)',
382
410
  fontWeight: 600,
383
- color: isPending ? 'var(--c-warn)' : isAnswered ? 'var(--c-ok)' : 'var(--c-muted)',
411
+ color: isPending ? 'var(--c-agent)' : isAnswered ? 'var(--c-ok)' : 'var(--c-muted)',
384
412
  }}
385
413
  >
386
414
  {isPending ? 'Sending…' : isAnswered ? 'Answered' : legacyStatus}
@@ -481,13 +509,12 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
481
509
  >
482
510
  <span
483
511
  style={{
484
- fontSize: '10px',
485
- textTransform: 'uppercase',
512
+ fontSize: 'var(--fs-meta)',
486
513
  fontWeight: 600,
487
514
  color: isStreaming
488
- ? 'var(--c-accent)'
515
+ ? 'var(--c-agent)'
489
516
  : isPending
490
- ? 'var(--c-warn)'
517
+ ? 'var(--c-agent)'
491
518
  : isAnswered
492
519
  ? 'var(--c-ok)'
493
520
  : 'var(--c-muted)',
@@ -495,7 +522,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
495
522
  >
496
523
  {isStreaming ? 'Streaming…' : isPending ? 'Sending…' : isAnswered ? 'Answered' : threadStatus}
497
524
  </span>
498
- <span style={{ fontSize: '10px', color: 'var(--c-muted)' }}>
525
+ <span style={{ fontSize: 'var(--fs-meta)', color: 'var(--c-muted)' }}>
499
526
  {turns.length} turn{turns.length !== 1 ? 's' : ''}
500
527
  </span>
501
528
  </div>
@@ -513,7 +540,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
513
540
  style={{
514
541
  width: '100%',
515
542
  resize: 'none',
516
- background: 'rgba(10,14,30,0.4)',
543
+ background: 'var(--c-input-bg)',
517
544
  border: '1px solid var(--c-line)',
518
545
  borderRadius: 'var(--radius-sm)',
519
546
  color: 'var(--c-text)',
@@ -532,7 +559,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
532
559
  marginTop: '4px',
533
560
  }}
534
561
  >
535
- <span style={{ fontSize: '10px', color: 'var(--c-muted)' }}>{'\u2318'}+Enter</span>
562
+ <kbd class="prompt-key-hint">{'\u2318'}+Enter</kbd>
536
563
  <button
537
564
  type="button"
538
565
  onClick={handleReplySubmit}
@@ -49,13 +49,16 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
49
49
 
50
50
  return (
51
51
  <div class="response-node-inner" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
52
+ <div class="thread-turn-role" style={{ color: 'var(--c-agent)' }}>
53
+ ✦ Agent
54
+ </div>
52
55
  {/* Streaming indicator */}
53
56
  {isStreaming && (
54
57
  <div
55
58
  class="response-streaming-bar"
56
59
  style={{
57
60
  height: '2px',
58
- background: 'linear-gradient(90deg, transparent, var(--c-accent), transparent)',
61
+ background: 'linear-gradient(90deg, transparent, var(--c-agent), transparent)',
59
62
  animation: 'response-stream-pulse 1.5s ease-in-out infinite',
60
63
  borderRadius: '1px',
61
64
  marginBottom: '4px',
@@ -100,10 +103,9 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
100
103
  >
101
104
  <span
102
105
  style={{
103
- fontSize: '10px',
104
- textTransform: 'uppercase',
106
+ fontSize: 'var(--fs-meta)',
105
107
  fontWeight: 600,
106
- color: isStreaming ? 'var(--c-accent)' : isComplete ? 'var(--c-ok)' : 'var(--c-muted)',
108
+ color: isStreaming ? 'var(--c-agent)' : isComplete ? 'var(--c-ok)' : 'var(--c-muted)',
107
109
  }}
108
110
  >
109
111
  {isStreaming ? 'Streaming…' : isComplete ? 'Complete' : status}
@@ -115,8 +117,8 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
115
117
  style={{
116
118
  padding: '3px 10px',
117
119
  fontSize: '11px',
118
- background: 'rgba(70,182,255,0.1)',
119
- border: '1px solid rgba(70,182,255,0.3)',
120
+ background: 'var(--c-panel-soft)',
121
+ border: '1px solid var(--c-line)',
120
122
  borderRadius: 'var(--radius-sm)',
121
123
  color: 'var(--c-accent)',
122
124
  cursor: 'pointer',