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,5 +1,7 @@
1
1
  import { PHASE_COLORS } from '../theme/tokens';
2
2
  import type { CanvasNodeState } from '../types';
3
+ import { isWorkCardState } from '../../shared/ax-kinds.js';
4
+ import { WorkCard } from './WorkCard';
3
5
 
4
6
  export function getStatusDisplayPhase(node: CanvasNodeState): string {
5
7
  const phase = typeof node.data.phase === 'string' && node.data.phase.trim().length > 0 ? node.data.phase.trim() : '';
@@ -13,12 +15,23 @@ export function getStatusDisplayPhase(node: CanvasNodeState): string {
13
15
  }
14
16
 
15
17
  export function StatusNode({ node }: { node: CanvasNodeState }) {
18
+ if (typeof node.data.workItemId === 'string' && node.data.liveWorkAvailable === false) {
19
+ return (
20
+ <div class="work-card">
21
+ <div class="work-card-error">⚠ Task unavailable</div>
22
+ <div>The linked task is no longer on this board.</div>
23
+ <div class="work-card-live">Read-only · removing this card does not cancel work.</div>
24
+ </div>
25
+ );
26
+ }
27
+ if (isWorkCardState(node.data.workState)) return <WorkCard node={node} state={node.data.workState} />;
16
28
  const phase = getStatusDisplayPhase(node);
17
29
  const detail = (node.data.detail as string) || '';
18
30
  const message = (node.data.message as string) || '';
19
31
  const level = (node.data.level as string) || 'ok';
20
32
  const activeTool = node.data.activeTool as string | null;
21
33
  const subagent = node.data.subagent as { state: string; name: string } | undefined;
34
+ const progress = typeof node.data.progress === 'number' ? Math.max(0, Math.min(100, node.data.progress)) : null;
22
35
 
23
36
  const phaseColor = PHASE_COLORS[phase] ?? 'var(--c-muted)';
24
37
  const isActive = phase !== 'idle';
@@ -33,7 +46,6 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
33
46
  height: '8px',
34
47
  borderRadius: '50%',
35
48
  background: phaseColor,
36
- boxShadow: isActive ? `0 0 8px ${phaseColor}` : 'none',
37
49
  animation: isActive ? 'pulse 1.5s infinite' : 'none',
38
50
  flexShrink: 0,
39
51
  }}
@@ -42,11 +54,13 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
42
54
  style={{
43
55
  fontWeight: 600,
44
56
  color: phaseColor,
45
- textTransform: 'uppercase',
46
- letterSpacing: '0.05em',
57
+ fontSize: 'var(--fs-meta)',
58
+ padding: '2px 8px',
59
+ borderRadius: 'var(--r-pill)',
60
+ background: `color-mix(in srgb, ${phaseColor} 12%, transparent)`,
47
61
  }}
48
62
  >
49
- {phase}
63
+ {phase.charAt(0).toUpperCase() + phase.slice(1)}
50
64
  </span>
51
65
  {detail && (
52
66
  <span
@@ -62,10 +76,23 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
62
76
  )}
63
77
  </div>
64
78
 
79
+ {progress !== null && (
80
+ <div
81
+ class="status-progress"
82
+ role="progressbar"
83
+ aria-label="Progress"
84
+ aria-valuemin={0}
85
+ aria-valuemax={100}
86
+ aria-valuenow={progress}
87
+ >
88
+ <div style={{ width: `${progress}%`, background: phaseColor }} />
89
+ </div>
90
+ )}
91
+
65
92
  {/* Active tool */}
66
93
  {activeTool && (
67
94
  <div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--c-agent)' }}>
68
- <span style={{ fontSize: '10px' }}>⚙</span>
95
+ <span style={{ fontSize: 'var(--fs-meta)' }}>⚙</span>
69
96
  <span style={{ fontFamily: 'var(--font-code)', fontSize: '11px' }}>{activeTool}</span>
70
97
  </div>
71
98
  )}
@@ -73,9 +100,9 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
73
100
  {/* Sub-agent */}
74
101
  {subagent && subagent.state !== 'completed' && (
75
102
  <div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--c-subagent)' }}>
76
- <span style={{ fontSize: '10px' }}>⠉</span>
103
+ <span style={{ fontSize: 'var(--fs-meta)' }}>⠉</span>
77
104
  <span>{subagent.name}</span>
78
- <span style={{ color: 'var(--c-muted)', fontSize: '10px' }}>({subagent.state})</span>
105
+ <span style={{ color: 'var(--c-muted)', fontSize: 'var(--fs-meta)' }}>({subagent.state})</span>
79
106
  </div>
80
107
  )}
81
108
 
@@ -1,8 +1,19 @@
1
1
  import { PHASE_COLORS } from '../theme/tokens';
2
2
  import type { CanvasNodeState } from '../types';
3
3
  import { getStatusDisplayPhase } from './StatusNode';
4
+ import { isWorkCardState, WORK_CARD_STATES } from '../../shared/ax-kinds.js';
4
5
 
5
6
  export function StatusSummary({ node }: { node: CanvasNodeState }) {
7
+ if (typeof node.data.workItemId === 'string' && node.data.liveWorkAvailable === false)
8
+ return <span class="work-card-error">⚠ Task unavailable</span>;
9
+ if (isWorkCardState(node.data.workState)) {
10
+ const state = WORK_CARD_STATES[node.data.workState];
11
+ return (
12
+ <span class={`work-card-state work-card-${node.data.workState}`}>
13
+ <span aria-hidden="true">{state.glyph}</span> {state.label}
14
+ </span>
15
+ );
16
+ }
6
17
  const phase = getStatusDisplayPhase(node);
7
18
  const activeTool = node.data.activeTool as string | null;
8
19
  const subagent = node.data.subagent as { state: string; name: string } | undefined;
@@ -21,14 +32,16 @@ export function StatusSummary({ node }: { node: CanvasNodeState }) {
21
32
  flexShrink: 0,
22
33
  }}
23
34
  />
24
- <span style={{ color: phaseColor, fontSize: '10px', textTransform: 'uppercase', fontWeight: 600 }}>{phase}</span>
35
+ <span style={{ color: phaseColor, fontSize: 'var(--fs-meta)', fontWeight: 600 }}>
36
+ {phase.charAt(0).toUpperCase() + phase.slice(1)}
37
+ </span>
25
38
  {activeTool && (
26
- <span style={{ color: 'var(--c-agent)', fontSize: '10px', fontFamily: 'var(--font-code)' }}>
39
+ <span style={{ color: 'var(--c-agent)', fontSize: 'var(--fs-meta)', fontFamily: 'var(--font-code)' }}>
27
40
  ⚙ {activeTool}
28
41
  </span>
29
42
  )}
30
43
  {subagent && subagent.state !== 'completed' && (
31
- <span style={{ color: 'var(--c-subagent)', fontSize: '10px' }}>⠉ {subagent.name}</span>
44
+ <span style={{ color: 'var(--c-subagent)', fontSize: 'var(--fs-meta)' }}>⠉ {subagent.name}</span>
32
45
  )}
33
46
  </span>
34
47
  );
@@ -17,7 +17,7 @@ const STATUS_ICONS: Record<string, string> = {
17
17
  };
18
18
 
19
19
  const STATUS_COLORS: Record<string, string> = {
20
- running: 'var(--c-accent)',
20
+ running: 'var(--c-agent)',
21
21
  success: 'var(--c-ok)',
22
22
  failed: 'var(--c-danger)',
23
23
  };
@@ -71,9 +71,9 @@ export function TraceNode({ node }: { node: CanvasNodeState }) {
71
71
  <div
72
72
  style={{
73
73
  fontFamily: 'var(--font-code)',
74
- fontSize: '11px',
75
- fontWeight: 600,
76
- color: catColor,
74
+ fontSize: 'var(--fs-ui)',
75
+ fontWeight: 500,
76
+ color: category === 'subagent' ? catColor : 'var(--c-text)',
77
77
  whiteSpace: 'nowrap',
78
78
  overflow: 'hidden',
79
79
  textOverflow: 'ellipsis',
@@ -84,7 +84,7 @@ export function TraceNode({ node }: { node: CanvasNodeState }) {
84
84
  {summary && (
85
85
  <div
86
86
  style={{
87
- fontSize: '10px',
87
+ fontSize: 'var(--fs-meta)',
88
88
  color: error ? 'var(--c-danger)' : 'var(--c-muted)',
89
89
  whiteSpace: 'nowrap',
90
90
  overflow: 'hidden',
@@ -101,10 +101,10 @@ export function TraceNode({ node }: { node: CanvasNodeState }) {
101
101
  {duration && (
102
102
  <span
103
103
  style={{
104
- fontSize: '9px',
104
+ fontSize: 'var(--fs-meta)',
105
105
  padding: '1px 5px',
106
- borderRadius: '3px',
107
- background: 'rgba(255,255,255,0.06)',
106
+ borderRadius: 'var(--r-control)',
107
+ background: 'var(--c-surface-hover)',
108
108
  color: 'var(--c-muted)',
109
109
  flexShrink: 0,
110
110
  whiteSpace: 'nowrap',
@@ -0,0 +1,273 @@
1
+ import type { JSX, RefObject } from 'preact';
2
+ import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
3
+ import {
4
+ HTML_SURFACE_PUSH_SOURCE,
5
+ readSurfaceNotification,
6
+ surfaceNotification,
7
+ } from '../../shared/ax-surface-protocol.js';
8
+
9
+ export const VIEWER_LOAD_DEADLINE_MS = 10_000;
10
+ export type ViewerSource = { src?: string; srcdoc?: string; error?: boolean; retry?: () => void };
11
+ type LoadDocument = (
12
+ frame: HTMLIFrameElement,
13
+ signal: AbortSignal,
14
+ ) => Promise<boolean | 'cancelled' | undefined> | undefined;
15
+ type Entry = { key: string; source: ViewerSource; attempt: number; load?: LoadDocument };
16
+ type MountFrame = (frame: HTMLIFrameElement, fail: () => void) => () => void;
17
+
18
+ /** The document acknowledges mounting before its frame-gated paint signal. */
19
+ export function waitForViewerPaint(
20
+ frame: HTMLIFrameElement,
21
+ token: string,
22
+ signal: AbortSignal,
23
+ jsonRpc = false,
24
+ ): Promise<boolean> {
25
+ if (signal.aborted) return Promise.resolve(false);
26
+ return new Promise((resolve) => {
27
+ const finish = (ready: boolean) => {
28
+ clearTimeout(timer);
29
+ window.removeEventListener('message', receive);
30
+ signal.removeEventListener('abort', cancel);
31
+ resolve(ready);
32
+ };
33
+ const cancel = () => finish(false);
34
+ const receive = (event: MessageEvent) => {
35
+ const data = (jsonRpc ? readSurfaceNotification(event.data) : event.data) as {
36
+ source?: string;
37
+ token?: string;
38
+ type?: string;
39
+ } | null;
40
+ if (event.source !== frame.contentWindow || data?.source !== HTML_SURFACE_PUSH_SOURCE || data?.token !== token)
41
+ return;
42
+ if (data.type === 'mounted') {
43
+ clearTimeout(timer);
44
+ }
45
+ if (data.type === 'ready') finish(true);
46
+ };
47
+ const timer = setTimeout(cancel, VIEWER_LOAD_DEADLINE_MS);
48
+ window.addEventListener('message', receive);
49
+ signal.addEventListener('abort', cancel, { once: true });
50
+ const message = { source: HTML_SURFACE_PUSH_SOURCE, type: 'check-ready', token };
51
+ frame.contentWindow?.postMessage(jsonRpc ? surfaceNotification(message) : message, '*');
52
+ });
53
+ }
54
+
55
+ /** Bind protocol adapters before document scripts run, once per document. */
56
+ function ViewerDocument({
57
+ mount,
58
+ iframeRef,
59
+ ...attributes
60
+ }: JSX.IntrinsicElements['iframe'] & {
61
+ mount?: (frame: HTMLIFrameElement) => () => void;
62
+ iframeRef?: RefObject<HTMLIFrameElement>;
63
+ }) {
64
+ const frame = useRef<HTMLIFrameElement>(null);
65
+ useLayoutEffect(() => mount?.(frame.current!), []);
66
+ useLayoutEffect(() => {
67
+ if (iframeRef) iframeRef.current = frame.current;
68
+ }, [iframeRef]);
69
+ return <iframe {...attributes} ref={frame} />;
70
+ }
71
+
72
+ /** Preserve the painted document until its replacement restores and paints. */
73
+ export function RefreshingViewerFrame({
74
+ source,
75
+ identity = '',
76
+ iframeRef,
77
+ onLoad = () => {},
78
+ onMount,
79
+ onOpen,
80
+ onDocumentLoad,
81
+ title,
82
+ className = 'mcp-app-frame',
83
+ sandbox = 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox',
84
+ allow = '',
85
+ tabIndex,
86
+ referrerPolicy,
87
+ }: {
88
+ source: ViewerSource;
89
+ identity?: string;
90
+ iframeRef: RefObject<HTMLIFrameElement>;
91
+ onLoad?: () => void;
92
+ onMount?: MountFrame;
93
+ onOpen?: () => void;
94
+ onDocumentLoad?: LoadDocument;
95
+ title: string;
96
+ className?: string;
97
+ sandbox?: string;
98
+ allow?: string;
99
+ tabIndex?: number;
100
+ referrerPolicy?: JSX.IntrinsicElements['iframe']['referrerPolicy'];
101
+ }) {
102
+ const documentSource = source.src ?? source.srcdoc ?? '';
103
+ const sourceKey = documentSource ? `${identity}\0${documentSource}` : '';
104
+ const [painted, setPainted] = useState<Entry>({ key: sourceKey, source, attempt: 0, load: onDocumentLoad });
105
+ const [pending, setPending] = useState<Entry | null>(null);
106
+ const [failure, setFailure] = useState<Entry | null>(null);
107
+ const [ready, setReady] = useState(false);
108
+ const promotion = useRef(0);
109
+ const readiness = useRef(new Map<AbortController, Entry>());
110
+ const currentEntries = useRef({ painted, pending });
111
+ currentEntries.current = { painted, pending };
112
+ const networkTimer = useRef<{ entry: Entry; timer: ReturnType<typeof setTimeout> }>();
113
+ const loadedEntries = useRef(new WeakSet<Entry>());
114
+
115
+ const fail = (entry: Entry) => {
116
+ const current = currentEntries.current;
117
+ if (entry === (current.pending ?? current.painted)) setFailure(entry);
118
+ };
119
+
120
+ useLayoutEffect(() => {
121
+ promotion.current += 1;
122
+ setFailure(null);
123
+ if (!sourceKey || sourceKey === painted.key) {
124
+ setPending(null);
125
+ return;
126
+ }
127
+ if (!painted.key) {
128
+ setPainted({ key: sourceKey, source, attempt: 0, load: onDocumentLoad });
129
+ return;
130
+ }
131
+ setPending({ key: sourceKey, source, attempt: 0, load: onDocumentLoad });
132
+ }, [sourceKey, painted.key]);
133
+
134
+ useLayoutEffect(() => {
135
+ // Retry retires an entry without changing its source key. Keep the retained
136
+ // foreground's first-paint wait, but cancel every removed document's work.
137
+ for (const [controller, entry] of readiness.current) {
138
+ if (entry === painted || entry === pending) continue;
139
+ controller.abort();
140
+ readiness.current.delete(controller);
141
+ }
142
+ }, [painted, pending]);
143
+
144
+ useLayoutEffect(() => {
145
+ const entry = pending ?? painted;
146
+ if (!entry.key || loadedEntries.current.has(entry)) return;
147
+ const timer = setTimeout(() => fail(entry), VIEWER_LOAD_DEADLINE_MS);
148
+ networkTimer.current = { entry, timer };
149
+ return () => clearTimeout(timer);
150
+ }, [pending, painted]);
151
+
152
+ useEffect(
153
+ () => () => {
154
+ promotion.current += 1;
155
+ for (const controller of readiness.current.keys()) controller.abort();
156
+ readiness.current.clear();
157
+ },
158
+ [],
159
+ );
160
+
161
+ const frame = (entry: Entry, isPending: boolean) =>
162
+ entry.key && (
163
+ <ViewerDocument
164
+ key={`${entry.key}:${entry.attempt}`}
165
+ iframeRef={isPending ? undefined : iframeRef}
166
+ mount={onMount ? (frame) => onMount(frame, () => fail(entry)) : undefined}
167
+ src={entry.source.src}
168
+ srcdoc={entry.source.srcdoc}
169
+ class={className}
170
+ sandbox={sandbox}
171
+ allow={allow}
172
+ tabIndex={tabIndex}
173
+ referrerPolicy={referrerPolicy}
174
+ inert={isPending}
175
+ aria-hidden={isPending ? true : undefined}
176
+ onError={() => fail(entry)}
177
+ onLoad={async (event) => {
178
+ loadedEntries.current.add(entry);
179
+ if (networkTimer.current?.entry === entry) clearTimeout(networkTimer.current.timer);
180
+ const token = isPending ? ++promotion.current : promotion.current;
181
+ const loadedFrame = event.currentTarget;
182
+ const controller = new AbortController();
183
+ readiness.current.set(controller, entry);
184
+ const result = entry.load?.(loadedFrame, controller.signal);
185
+ const loaded = result ? await result : undefined;
186
+ readiness.current.delete(controller);
187
+ const current = currentEntries.current;
188
+ if (
189
+ (isPending && promotion.current !== token) ||
190
+ (entry !== current.painted && entry !== current.pending) ||
191
+ loaded === 'cancelled' ||
192
+ controller.signal.aborted
193
+ )
194
+ return;
195
+ if (loaded === false) {
196
+ fail(entry);
197
+ return;
198
+ }
199
+ setFailure((failed) => (failed === entry ? null : failed));
200
+ if (!isPending) {
201
+ setReady(true);
202
+ onLoad();
203
+ return;
204
+ }
205
+ requestAnimationFrame(() =>
206
+ requestAnimationFrame(() => {
207
+ if (promotion.current !== token) return;
208
+ iframeRef.current = loadedFrame;
209
+ setPainted(entry);
210
+ setPending(null);
211
+ setReady(true);
212
+ onLoad();
213
+ }),
214
+ );
215
+ }}
216
+ style={{
217
+ position: 'absolute',
218
+ inset: 0,
219
+ width: '100%',
220
+ height: '100%',
221
+ border: 0,
222
+ visibility: 'visible',
223
+ zIndex: isPending ? 0 : 1,
224
+ background: 'var(--c-panel)',
225
+ }}
226
+ title={title}
227
+ />
228
+ );
229
+
230
+ return (
231
+ <div class="mcp-app-frame-stack">
232
+ {frame(painted, false)}
233
+ {pending && frame(pending, true)}
234
+ {!ready && !failure && !source.error && (
235
+ <div class="viewer-fallback" role="status">
236
+ Loading viewer…
237
+ </div>
238
+ )}
239
+ {(failure || source.error) && (
240
+ <div class={`viewer-fallback${ready ? ' viewer-fallback-retained' : ''}`} role="status">
241
+ <span>Viewer could not load.{ready ? ' Keeping the last view.' : ''}</span>
242
+ <button
243
+ type="button"
244
+ onClick={() => {
245
+ if (source.error) {
246
+ source.retry?.();
247
+ return;
248
+ }
249
+ if (!failure) return;
250
+ const retry = { ...failure, attempt: failure.attempt + 1 };
251
+ setFailure(null);
252
+ if (pending) setPending(retry);
253
+ else setPainted(retry);
254
+ }}
255
+ >
256
+ Retry
257
+ </button>
258
+ </div>
259
+ )}
260
+ {onOpen && !failure && !source.error && (
261
+ <button
262
+ type="button"
263
+ class="ext-app-preview-catcher"
264
+ aria-label="Open full view to edit"
265
+ onClick={(event) => {
266
+ event.stopPropagation();
267
+ onOpen();
268
+ }}
269
+ />
270
+ )}
271
+ </div>
272
+ );
273
+ }
@@ -0,0 +1,41 @@
1
+ import type { ComponentChildren } from 'preact';
2
+ import { useEffect, useRef, useState } from 'preact/hooks';
3
+ import { expandNode } from '../state/canvas-store';
4
+ import type { CanvasNodeState } from '../types';
5
+
6
+ // Mount on first approach at any zoom, then retain the document's arbitrary
7
+ // live state. A lifetime cap starves later regions of a populated board.
8
+ export function ViewerHost({ node, children }: { node: CanvasNodeState; children: ComponentChildren }) {
9
+ const anchor = useRef<HTMLDivElement>(null);
10
+ const [mounted, setMounted] = useState(false);
11
+
12
+ useEffect(() => {
13
+ if (!anchor.current) return;
14
+ const observer = new IntersectionObserver(
15
+ ([entry]) => {
16
+ if (entry!.isIntersecting) {
17
+ setMounted(true);
18
+ observer.disconnect();
19
+ }
20
+ },
21
+ { rootMargin: '200px' },
22
+ );
23
+ observer.observe(anchor.current);
24
+ return () => observer.disconnect();
25
+ }, []);
26
+
27
+ return (
28
+ <div ref={anchor} class="viewer-host" data-viewer-mounted={mounted}>
29
+ {mounted ? (
30
+ children
31
+ ) : (
32
+ <div class="viewer-placeholder">
33
+ <span>{String(node.data.title || node.type)}</span>
34
+ <button type="button" onClick={() => expandNode(node.id)}>
35
+ Open viewer
36
+ </button>
37
+ </div>
38
+ )}
39
+ </div>
40
+ );
41
+ }
@@ -1,6 +1,7 @@
1
- import { useCallback, useEffect, useState } from 'preact/hooks';
1
+ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
2
2
  import { refreshWebpageNodeFromClient } from '../state/intent-bridge';
3
3
  import type { CanvasNodeState } from '../types';
4
+ import { RefreshingViewerFrame } from './ViewerFrame';
4
5
 
5
6
  function formatHost(url: string): string {
6
7
  try {
@@ -35,6 +36,7 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
35
36
  const frameBlockedReason = typeof node.data.frameBlockedReason === 'string' ? node.data.frameBlockedReason : '';
36
37
  const [refreshing, setRefreshing] = useState(false);
37
38
  const [showEmbed, setShowEmbed] = useState(expanded);
39
+ const iframeRef = useRef<HTMLIFrameElement>(null);
38
40
 
39
41
  useEffect(() => {
40
42
  if (expanded) {
@@ -63,9 +65,9 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
63
65
  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: '12px' }}>
64
66
  <div style={{ minWidth: 0, flex: 1 }}>
65
67
  <div style={{ fontSize: '12px', color: 'var(--c-muted)' }}>{formatHost(url)}</div>
66
- <div style={{ fontSize: expanded ? '18px' : '15px', fontWeight: 700, color: 'var(--c-text)' }}>
67
- {pageTitle || (node.data.title as string) || url}
68
- </div>
68
+ {pageTitle && pageTitle !== node.data.title && (
69
+ <div style={{ fontSize: 'var(--fs-title)', fontWeight: 600, color: 'var(--c-text)' }}>{pageTitle}</div>
70
+ )}
69
71
  <a
70
72
  href={url}
71
73
  target="_blank"
@@ -82,7 +84,7 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
82
84
  gap: '6px',
83
85
  padding: '4px 8px',
84
86
  borderRadius: '999px',
85
- background: 'rgba(255,255,255,0.04)',
87
+ background: 'var(--c-panel-soft)',
86
88
  color: statusTone,
87
89
  fontSize: '11px',
88
90
  textTransform: 'uppercase',
@@ -194,7 +196,7 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
194
196
  fontSize: '11px',
195
197
  color: 'var(--c-muted)',
196
198
  borderBottom: '1px solid var(--c-line)',
197
- background: 'rgba(255,255,255,0.03)',
199
+ background: 'var(--c-panel)',
198
200
  }}
199
201
  >
200
202
  Live preview unavailable. This site refuses embedding, so PMX Canvas cannot show it inline.
@@ -216,9 +218,13 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
216
218
  <div
217
219
  style={{
218
220
  border: '1px solid var(--c-line)',
219
- borderRadius: '12px',
221
+ borderRadius: 'var(--r-control)',
220
222
  overflow: 'hidden',
221
- background: 'var(--c-panel-soft)',
223
+ background: 'var(--c-panel)',
224
+ height: '320px',
225
+ flexShrink: 0,
226
+ display: 'flex',
227
+ flexDirection: 'column',
222
228
  }}
223
229
  >
224
230
  <div
@@ -227,26 +233,20 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
227
233
  fontSize: '11px',
228
234
  color: 'var(--c-muted)',
229
235
  borderBottom: '1px solid var(--c-line)',
230
- background: 'rgba(255,255,255,0.03)',
236
+ background: 'var(--c-panel)',
231
237
  }}
232
238
  >
233
239
  Live preview (best effort). If this stays blank, the site likely blocks framing. The cached text snapshot
234
240
  below still works.
235
241
  </div>
236
- <iframe
237
- class="webpage-node-iframe"
242
+ <RefreshingViewerFrame
243
+ iframeRef={iframeRef}
244
+ onLoad={() => {}}
245
+ className="webpage-node-iframe"
238
246
  title={pageTitle || (node.data.title as string) || url}
239
- src={url}
240
- loading="lazy"
247
+ source={{ src: url }}
241
248
  referrerPolicy="no-referrer"
242
249
  sandbox="allow-downloads allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts"
243
- style={{
244
- display: 'block',
245
- width: '100%',
246
- height: expanded ? '320px' : '180px',
247
- border: 'none',
248
- background: '#fff',
249
- }}
250
250
  />
251
251
  </div>
252
252
  )}
@@ -266,10 +266,8 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
266
266
  flex: 1,
267
267
  minHeight: 0,
268
268
  overflow: 'auto',
269
- border: '1px solid var(--c-line)',
270
- borderRadius: '10px',
271
- background: 'var(--c-panel-soft)',
272
- padding: expanded ? '14px' : '10px',
269
+ borderTop: '1px solid var(--c-line)',
270
+ paddingTop: '12px',
273
271
  }}
274
272
  >
275
273
  {excerpt ? (