pmx-canvas 0.8.2 → 0.9.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 (335) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +601 -22
  3. package/CHANGELOG.md +49 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2724 -476
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2899 -596
  8. package/dist/json-render/index.js +20 -20
  9. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  10. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  11. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  12. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  13. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  14. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  15. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  16. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  17. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  18. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  19. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  20. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  21. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  22. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  23. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  24. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  25. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  26. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  27. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  28. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  29. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  30. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  31. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  32. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  33. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  34. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  35. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  36. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  37. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  38. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  39. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  40. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  41. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  42. package/dist/types/client/icons.d.ts +0 -10
  43. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  44. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  45. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  46. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  47. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  48. package/dist/types/client/state/boards-store.d.ts +81 -9
  49. package/dist/types/client/state/canvas-store.d.ts +14 -1
  50. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  51. package/dist/types/client/state/context-status-store.d.ts +35 -0
  52. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  53. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  54. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  55. package/dist/types/client/state/presence-store.d.ts +4 -4
  56. package/dist/types/client/state/presentation.d.ts +28 -0
  57. package/dist/types/client/state/relation-store.d.ts +10 -0
  58. package/dist/types/client/state/session-store.d.ts +41 -2
  59. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  60. package/dist/types/client/types.d.ts +13 -1
  61. package/dist/types/client/utils/keyboard.d.ts +2 -0
  62. package/dist/types/client/utils/time-ago.d.ts +4 -0
  63. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  64. package/dist/types/server/agent-presence.d.ts +10 -3
  65. package/dist/types/server/ax-context.d.ts +1 -1
  66. package/dist/types/server/canvas-db.d.ts +30 -7
  67. package/dist/types/server/canvas-operations.d.ts +24 -2
  68. package/dist/types/server/canvas-provenance.d.ts +22 -2
  69. package/dist/types/server/canvas-serialization.d.ts +2 -0
  70. package/dist/types/server/canvas-state.d.ts +93 -15
  71. package/dist/types/server/canvas-validation.d.ts +1 -1
  72. package/dist/types/server/context-brief.d.ts +94 -6
  73. package/dist/types/server/context-reads.d.ts +33 -7
  74. package/dist/types/server/edit-summary.d.ts +20 -0
  75. package/dist/types/server/index.d.ts +71 -10
  76. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  77. package/dist/types/server/lifecycle-status.d.ts +10 -0
  78. package/dist/types/server/memory-graph.d.ts +9 -0
  79. package/dist/types/server/mutation-history.d.ts +2 -0
  80. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  81. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  82. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  83. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  84. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  85. package/dist/types/server/operations/ops/query.d.ts +23 -0
  86. package/dist/types/server/operations/registry.d.ts +1 -0
  87. package/dist/types/server/placement.d.ts +7 -0
  88. package/dist/types/server/scope-fence.d.ts +2 -1
  89. package/dist/types/server/spatial-analysis.d.ts +6 -3
  90. package/dist/types/server/tour-control.d.ts +32 -0
  91. package/dist/types/server/wiki-links.d.ts +14 -0
  92. package/dist/types/server/workbench-auth.d.ts +2 -0
  93. package/dist/types/shared/agent-context.d.ts +2 -0
  94. package/dist/types/shared/agent-presence.d.ts +15 -12
  95. package/dist/types/shared/auto-arrange.d.ts +1 -0
  96. package/dist/types/shared/boards.d.ts +4 -4
  97. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  98. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  99. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  100. package/dist/types/shared/memory-graph.d.ts +53 -0
  101. package/dist/types/shared/relations.d.ts +68 -0
  102. package/dist/types/shared/semantic-attention.d.ts +2 -0
  103. package/dist/types/shared/tour.d.ts +38 -0
  104. package/dist/types/shared/workspace-settings.d.ts +11 -0
  105. package/docs/RELEASE.md +21 -3
  106. package/docs/adr-001-bun-only-runtime.md +7 -1
  107. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  108. package/docs/api-reference.md +109 -0
  109. package/docs/ax-host-adapter-contract.md +1 -1
  110. package/docs/ax-state-contract.md +5 -4
  111. package/docs/cli.md +153 -15
  112. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  113. package/docs/design/AgentContext.dc.html +3 -3
  114. package/docs/design/BoardPins.dc.html +37 -22
  115. package/docs/design/Categories.dc.html +208 -0
  116. package/docs/design/CategoryEditor.dc.html +33 -0
  117. package/docs/design/Chrome.dc.html +52 -9
  118. package/docs/design/ContextChip.dc.html +224 -0
  119. package/docs/design/ContextChip600.dc.html +136 -0
  120. package/docs/design/EmptyState.dc.html +45 -0
  121. package/docs/design/Flows.dc.html +23 -1
  122. package/docs/design/HeaderMarks.dc.html +183 -0
  123. package/docs/design/Home.dc.html +19 -1
  124. package/docs/design/HomeGraph.dc.html +86 -202
  125. package/docs/design/HomeMap.dc.html +23 -1
  126. package/docs/design/HomeTeam.dc.html +32 -14
  127. package/docs/design/Lifecycle.dc.html +22 -0
  128. package/docs/design/Lifecycle600.dc.html +18 -0
  129. package/docs/design/LifecycleVariants.dc.html +157 -0
  130. package/docs/design/LinkAuthorship.dc.html +139 -0
  131. package/docs/design/LinksChanged.dc.html +203 -0
  132. package/docs/design/LinksOptions.dc.html +137 -0
  133. package/docs/design/LinksOptions600.dc.html +134 -0
  134. package/docs/design/NearPin.dc.html +9 -10
  135. package/docs/design/Pane600.dc.html +4 -3
  136. package/docs/design/Relations.dc.html +3 -2
  137. package/docs/design/Restore.dc.html +152 -0
  138. package/docs/design/SeeChange.dc.html +267 -0
  139. package/docs/design/Uses.dc.html +12 -6
  140. package/docs/design/canvas.json +146 -19
  141. package/docs/design.md +449 -16
  142. package/docs/environment.md +0 -1
  143. package/docs/evals/curation-effect.md +128 -0
  144. package/docs/http-api.md +459 -37
  145. package/docs/mcp.md +146 -18
  146. package/docs/node-types.md +58 -3
  147. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  148. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  149. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  150. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  151. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  152. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  153. package/docs/product-context-vision-2026-09.md +17 -1
  154. package/docs/product-vision-2026-09.md +1082 -43
  155. package/docs/product-vision-review-2026-09.md +13 -1
  156. package/docs/screenshots/workbench-live-dark.png +0 -0
  157. package/docs/screenshots/workbench-live-light.png +0 -0
  158. package/docs/sdk.md +62 -2
  159. package/package.json +1 -1
  160. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  161. package/plugins/pmx-canvas/plugin.json +1 -1
  162. package/plugins/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/tools.json +601 -22
  164. package/skills/pmx-canvas/SKILL.md +216 -42
  165. package/skills/pmx-canvas/references/full-reference.md +120 -21
  166. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  167. package/skills/pmx-canvas/references/importing-context.md +132 -0
  168. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  169. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  170. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  171. package/src/cli/agent.ts +2 -0
  172. package/src/cli/commands/ax.ts +12 -0
  173. package/src/cli/commands/boards.ts +87 -11
  174. package/src/cli/commands/edges.ts +28 -0
  175. package/src/cli/commands/groups.ts +7 -0
  176. package/src/cli/commands/nodes.ts +1 -0
  177. package/src/cli/commands/query.ts +34 -2
  178. package/src/cli/commands/record.ts +199 -48
  179. package/src/cli/index.ts +6 -1
  180. package/src/client/App.tsx +177 -70
  181. package/src/client/canvas/AttentionHistory.tsx +0 -2
  182. package/src/client/canvas/BarHint.tsx +40 -2
  183. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  184. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  185. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  186. package/src/client/canvas/BoardPinButton.tsx +33 -0
  187. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  188. package/src/client/canvas/CanvasNode.tsx +360 -237
  189. package/src/client/canvas/CanvasViewport.tsx +45 -7
  190. package/src/client/canvas/CommandBar.tsx +29 -23
  191. package/src/client/canvas/CommandPalette.tsx +5 -12
  192. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  193. package/src/client/canvas/ContextChip.tsx +296 -0
  194. package/src/client/canvas/ContextMenu.tsx +63 -5
  195. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  196. package/src/client/canvas/EdgeLayer.tsx +339 -76
  197. package/src/client/canvas/EmptyState.tsx +33 -18
  198. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  199. package/src/client/canvas/ExternalWriters.tsx +57 -42
  200. package/src/client/canvas/HomeGraphView.tsx +942 -0
  201. package/src/client/canvas/HomeMapView.tsx +641 -0
  202. package/src/client/canvas/HomeReviewView.tsx +167 -0
  203. package/src/client/canvas/HomeView.tsx +687 -160
  204. package/src/client/canvas/IntentLayer.tsx +19 -12
  205. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  206. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  207. package/src/client/canvas/LifecycleFields.tsx +91 -0
  208. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  209. package/src/client/canvas/MemoryInspector.tsx +104 -0
  210. package/src/client/canvas/Minimap.tsx +3 -3
  211. package/src/client/canvas/NearPinLayer.tsx +187 -0
  212. package/src/client/canvas/NodeContextMark.tsx +175 -97
  213. package/src/client/canvas/Presentation.tsx +23 -57
  214. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  215. package/src/client/canvas/RelationsPanel.tsx +82 -0
  216. package/src/client/canvas/SelectionBar.tsx +3 -6
  217. package/src/client/canvas/SessionPanel.tsx +90 -50
  218. package/src/client/canvas/SessionReceipt.tsx +311 -39
  219. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  220. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  221. package/src/client/canvas/ToolRail.tsx +367 -192
  222. package/src/client/canvas/TopBar.tsx +354 -124
  223. package/src/client/canvas/canvas-area.ts +10 -3
  224. package/src/client/canvas/graph-geometry.ts +195 -0
  225. package/src/client/canvas/graph-model.ts +254 -0
  226. package/src/client/canvas/memory-layout.ts +106 -0
  227. package/src/client/canvas/use-chrome-height.ts +20 -0
  228. package/src/client/canvas/use-focus-trap.ts +31 -5
  229. package/src/client/canvas/use-memory-graph.ts +58 -0
  230. package/src/client/canvas/use-node-drag.ts +2 -1
  231. package/src/client/canvas/use-pan-zoom.ts +64 -7
  232. package/src/client/icons.tsx +0 -63
  233. package/src/client/mcp-app/style.css +0 -6
  234. package/src/client/nodes/AxStepControls.tsx +74 -50
  235. package/src/client/nodes/BoardNode.tsx +5 -1
  236. package/src/client/nodes/ContextNode.tsx +18 -11
  237. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  238. package/src/client/nodes/FileNode.tsx +46 -42
  239. package/src/client/nodes/HtmlNode.tsx +32 -0
  240. package/src/client/nodes/ImageNode.tsx +1 -1
  241. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  242. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  243. package/src/client/nodes/MarkdownNode.tsx +377 -109
  244. package/src/client/nodes/McpAppNode.tsx +50 -20
  245. package/src/client/nodes/MdFormatBar.tsx +21 -18
  246. package/src/client/nodes/PromptNode.tsx +1 -1
  247. package/src/client/nodes/ResponseNode.tsx +1 -1
  248. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  249. package/src/client/state/attention-bridge.ts +64 -15
  250. package/src/client/state/boards-store.ts +193 -39
  251. package/src/client/state/canvas-store.ts +99 -12
  252. package/src/client/state/context-chip-store.ts +70 -0
  253. package/src/client/state/context-status-store.ts +53 -0
  254. package/src/client/state/intent-bridge.ts +51 -30
  255. package/src/client/state/knowledge-store.ts +43 -0
  256. package/src/client/state/near-pin-store.ts +115 -6
  257. package/src/client/state/presence-store.ts +7 -10
  258. package/src/client/state/presentation.ts +91 -0
  259. package/src/client/state/relation-store.ts +81 -0
  260. package/src/client/state/session-store.ts +144 -18
  261. package/src/client/state/sse-bridge.ts +206 -29
  262. package/src/client/state/workspace-settings.ts +11 -0
  263. package/src/client/theme/global.css +2724 -476
  264. package/src/client/types.ts +12 -0
  265. package/src/client/utils/keyboard.ts +10 -0
  266. package/src/client/utils/time-ago.ts +14 -0
  267. package/src/json-render/renderer/index.tsx +31 -0
  268. package/src/mcp/canvas-app.ts +12 -2
  269. package/src/mcp/context-uri-template.ts +7 -4
  270. package/src/mcp/server.ts +63 -41
  271. package/src/mcp/workbench-app.ts +39 -6
  272. package/src/server/agent-presence.ts +128 -28
  273. package/src/server/ax-context.ts +27 -13
  274. package/src/server/board-export.ts +12 -1
  275. package/src/server/canvas-db.ts +331 -42
  276. package/src/server/canvas-operations.ts +195 -83
  277. package/src/server/canvas-provenance.ts +53 -22
  278. package/src/server/canvas-serialization.ts +2 -0
  279. package/src/server/canvas-state.ts +950 -122
  280. package/src/server/canvas-validation.ts +9 -2
  281. package/src/server/context-brief.ts +345 -38
  282. package/src/server/context-reads.ts +178 -30
  283. package/src/server/demo-state.json +355 -24
  284. package/src/server/edit-summary.ts +74 -0
  285. package/src/server/html-surface.ts +8 -1
  286. package/src/server/index.ts +181 -11
  287. package/src/server/knowledge-lifecycle.ts +304 -0
  288. package/src/server/lifecycle-status.ts +232 -0
  289. package/src/server/memory-graph.ts +166 -0
  290. package/src/server/mutation-history.ts +18 -6
  291. package/src/server/operations/composites.ts +32 -11
  292. package/src/server/operations/http.ts +7 -2
  293. package/src/server/operations/index.ts +4 -0
  294. package/src/server/operations/mcp.ts +26 -1
  295. package/src/server/operations/ops/ax-presence.ts +1 -1
  296. package/src/server/operations/ops/ax-read.ts +57 -6
  297. package/src/server/operations/ops/ax-timeline.ts +28 -2
  298. package/src/server/operations/ops/backup.ts +5 -3
  299. package/src/server/operations/ops/batch.ts +35 -4
  300. package/src/server/operations/ops/boards.ts +188 -47
  301. package/src/server/operations/ops/canvas-wire.ts +31 -1
  302. package/src/server/operations/ops/edges.ts +36 -2
  303. package/src/server/operations/ops/groups.ts +6 -1
  304. package/src/server/operations/ops/lifecycle.ts +262 -0
  305. package/src/server/operations/ops/memory-graph.ts +45 -0
  306. package/src/server/operations/ops/nodes.ts +107 -24
  307. package/src/server/operations/ops/query.ts +281 -68
  308. package/src/server/operations/ops/snapshots.ts +73 -1
  309. package/src/server/operations/ops/validate.ts +5 -1
  310. package/src/server/operations/ops/viewport.ts +97 -3
  311. package/src/server/operations/registry.ts +74 -25
  312. package/src/server/placement.ts +29 -0
  313. package/src/server/scope-fence.ts +78 -6
  314. package/src/server/server.ts +115 -16
  315. package/src/server/spatial-analysis.ts +48 -23
  316. package/src/server/tour-control.ts +82 -0
  317. package/src/server/wiki-links.ts +137 -0
  318. package/src/server/workbench-auth.ts +11 -0
  319. package/src/shared/agent-context.ts +3 -0
  320. package/src/shared/agent-presence.ts +15 -15
  321. package/src/shared/auto-arrange.ts +2 -0
  322. package/src/shared/boards.ts +4 -4
  323. package/src/shared/knowledge-lifecycle.ts +22 -0
  324. package/src/shared/knowledge-presets.ts +80 -0
  325. package/src/shared/lifecycle-status.ts +26 -0
  326. package/src/shared/memory-graph.ts +74 -0
  327. package/src/shared/relations.ts +98 -0
  328. package/src/shared/semantic-attention.ts +15 -1
  329. package/src/shared/tour.ts +23 -5
  330. package/src/shared/workspace-settings.ts +13 -0
  331. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  332. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  333. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  334. package/dist/types/server/agent-context.d.ts +0 -22
  335. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -0,0 +1,58 @@
1
+ import { useEffect, useRef, useState } from 'preact/hooks';
2
+ import type { MemoryGraph } from '../../shared/memory-graph.js';
3
+ import { boardList, catalogEpoch, memoryGraphRevision } from '../state/boards-store';
4
+ import { requestJson } from '../state/intent-bridge';
5
+
6
+ /** Coalesce live discovery and reject responses from an older library revision. */
7
+ export function useMemoryGraph(projection: 'full' | 'boards') {
8
+ const boards = boardList.value;
9
+ const liveRevision = memoryGraphRevision.value;
10
+ const [graph, setGraph] = useState<MemoryGraph | null>(null);
11
+ const [loading, setLoading] = useState(true);
12
+ const [error, setError] = useState(false);
13
+ const refresh = useRef(() => {});
14
+ useEffect(() => {
15
+ let cancelled = false;
16
+ let running = false;
17
+ let dirty = false;
18
+ let timer: number | undefined;
19
+ const schedule = () => {
20
+ dirty = true;
21
+ if (!running && timer === undefined) timer = window.setTimeout(load, 120);
22
+ };
23
+ const load = async () => {
24
+ timer = undefined;
25
+ dirty = false;
26
+ running = true;
27
+ setLoading(true);
28
+ const epoch = catalogEpoch;
29
+ const revision = memoryGraphRevision.peek();
30
+ const catalog = boardList.peek();
31
+ const result = await requestJson<MemoryGraph | null>(
32
+ 'memoryGraph',
33
+ `/api/canvas/memory-graph?projection=${projection}`,
34
+ null,
35
+ );
36
+ if (cancelled) return;
37
+ running = false;
38
+ if (epoch !== catalogEpoch || revision !== memoryGraphRevision.peek() || catalog !== boardList.peek()) {
39
+ schedule();
40
+ return;
41
+ }
42
+ if (result && Array.isArray(result.nodes) && Array.isArray(result.edges)) {
43
+ setGraph(result);
44
+ setError(false);
45
+ } else setError(true);
46
+ setLoading(false);
47
+ if (dirty) schedule();
48
+ };
49
+ refresh.current = schedule;
50
+ schedule();
51
+ return () => {
52
+ cancelled = true;
53
+ window.clearTimeout(timer);
54
+ };
55
+ }, [projection]);
56
+ useEffect(() => refresh.current(), [liveRevision, boards]);
57
+ return { graph, loading, error, retry: () => refresh.current() };
58
+ }
@@ -47,6 +47,7 @@ export function useNodeDrag({ nodeId, viewport, onMove, onDragEnd, onClick }: No
47
47
  const onPointerMove = (ev: PointerEvent) => {
48
48
  if (!isDragging.current) return;
49
49
  if (!moved && Math.hypot(ev.clientX - e.clientX, ev.clientY - e.clientY) < 4) return;
50
+ if (!moved) document.documentElement.classList.add('is-node-moving');
50
51
  moved = true;
51
52
  pendingPointer = { x: ev.clientX, y: ev.clientY };
52
53
  if (frameId !== null) return;
@@ -59,7 +60,7 @@ export function useNodeDrag({ nodeId, viewport, onMove, onDragEnd, onClick }: No
59
60
  flushMove();
60
61
  }
61
62
  isDragging.current = false;
62
- document.documentElement.classList.remove('is-node-dragging');
63
+ document.documentElement.classList.remove('is-node-dragging', 'is-node-moving');
63
64
  document.removeEventListener('pointermove', onPointerMove);
64
65
  document.removeEventListener('pointerup', finishDrag);
65
66
  document.removeEventListener('pointercancel', finishDrag);
@@ -6,6 +6,7 @@ import type { ViewportState } from '../types';
6
6
  const MIN_SCALE = 0.1;
7
7
  const MAX_SCALE = 4;
8
8
  const PAN_SPEED = 1;
9
+ const RIGHT_DRAG_THRESHOLD = 5;
9
10
 
10
11
  function clampScale(scale: number): number {
11
12
  return Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale));
@@ -15,6 +16,8 @@ interface PanZoomOptions {
15
16
  viewport: Signal<ViewportState>;
16
17
  onViewportChange: (v: ViewportState) => void;
17
18
  onViewportCommit: (v: ViewportState) => void;
19
+ onBackgroundContextMenu?: (e: MouseEvent, worldX: number, worldY: number) => void;
20
+ onEdgeContextMenu?: (e: MouseEvent, edgeId: string) => void;
18
21
  disabled?: boolean;
19
22
  }
20
23
 
@@ -23,13 +26,28 @@ interface PanZoomOptions {
23
26
  * - Wheel + Ctrl/Cmd: zoom centered on pointer
24
27
  * - Wheel without modifier: pan
25
28
  * - Right or middle pointer drag: pan from anywhere
29
+ * - Right-click on background: creation menu on release, unless dragged
26
30
  * - Pointer drag on background in pan mode: pan
27
31
  * - Pinch (touch): zoom
28
32
  */
29
- export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disabled = false }: PanZoomOptions) {
33
+ export function usePanZoom({
34
+ viewport,
35
+ onViewportChange,
36
+ onViewportCommit,
37
+ onBackgroundContextMenu,
38
+ onEdgeContextMenu,
39
+ disabled = false,
40
+ }: PanZoomOptions) {
30
41
  const containerRef = useRef<HTMLDivElement>(null);
31
42
  const isPanning = useRef(false);
32
43
  const lastPointer = useRef({ x: 0, y: 0 });
44
+ const rightPress = useRef<{
45
+ x: number;
46
+ y: number;
47
+ background: boolean;
48
+ edgeId: string | null;
49
+ dragged: boolean;
50
+ } | null>(null);
33
51
  const lastPinchDist = useRef(0);
34
52
  const wheelCommitTimer = useRef<number | null>(null);
35
53
 
@@ -123,6 +141,16 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
123
141
  // Select tool: background drag belongs to the lasso (CanvasViewport).
124
142
  return;
125
143
  }
144
+ rightPress.current =
145
+ e.button === 2
146
+ ? {
147
+ x: e.clientX,
148
+ y: e.clientY,
149
+ background: e.target === container,
150
+ edgeId: e.target instanceof Element ? e.target.getAttribute('data-edge-id') : null,
151
+ dragged: false,
152
+ }
153
+ : null;
126
154
  isPanning.current = true;
127
155
  document.documentElement.classList.add('is-canvas-panning');
128
156
  lastPointer.current = { x: e.clientX, y: e.clientY };
@@ -135,6 +163,11 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
135
163
  (e: PointerEvent) => {
136
164
  if (!isPanning.current) return;
137
165
  if (disabled) return;
166
+ const press = rightPress.current;
167
+ if (press && !press.dragged) {
168
+ if (Math.hypot(e.clientX - press.x, e.clientY - press.y) <= RIGHT_DRAG_THRESHOLD) return;
169
+ press.dragged = true;
170
+ }
138
171
  const dx = e.clientX - lastPointer.current.x;
139
172
  const dy = e.clientY - lastPointer.current.y;
140
173
  lastPointer.current = { x: e.clientX, y: e.clientY };
@@ -145,13 +178,37 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
145
178
  [disabled, viewport, onViewportChange],
146
179
  );
147
180
 
148
- const handlePointerUp = useCallback(() => {
149
- if (isPanning.current) {
181
+ const handlePointerUp = useCallback(
182
+ (e: PointerEvent) => {
183
+ if (!isPanning.current) return;
184
+ // Completing a middle pan must not paste the Linux primary selection.
185
+ e.preventDefault();
186
+ const press = rightPress.current;
187
+ rightPress.current = null;
188
+ isPanning.current = false;
189
+ document.documentElement.classList.remove('is-canvas-panning');
190
+ if (press && !press.dragged) {
191
+ // Native contextmenu may fire on press or release depending on the OS.
192
+ // Decide here instead, and never open on cancellation or lost capture.
193
+ if (!disabled && e.type === 'pointerup') {
194
+ if (press.edgeId) onEdgeContextMenu?.(e, press.edgeId);
195
+ else if (press.background) {
196
+ const rect = containerRef.current?.getBoundingClientRect();
197
+ if (!rect) return;
198
+ const v = viewport.value;
199
+ onBackgroundContextMenu?.(
200
+ e,
201
+ (e.clientX - rect.left - v.x) / v.scale,
202
+ (e.clientY - rect.top - v.y) / v.scale,
203
+ );
204
+ }
205
+ }
206
+ return;
207
+ }
150
208
  onViewportCommit(viewport.value);
151
- }
152
- isPanning.current = false;
153
- document.documentElement.classList.remove('is-canvas-panning');
154
- }, [onViewportCommit, viewport]);
209
+ },
210
+ [disabled, onBackgroundContextMenu, onEdgeContextMenu, onViewportCommit, viewport],
211
+ );
155
212
 
156
213
  // Touch pinch
157
214
  const handleTouchMove = useCallback(
@@ -192,23 +192,6 @@ export function IconMinimap(p: IconProps): JSX.Element {
192
192
  );
193
193
  }
194
194
 
195
- /** Sun with rays */
196
- export function IconSun(p: IconProps): JSX.Element {
197
- return (
198
- <Icon {...p}>
199
- <circle cx="8" cy="8" r="3" />
200
- <line x1="8" y1="1" x2="8" y2="3" />
201
- <line x1="8" y1="13" x2="8" y2="15" />
202
- <line x1="1" y1="8" x2="3" y2="8" />
203
- <line x1="13" y1="8" x2="15" y2="8" />
204
- <line x1="3.05" y1="3.05" x2="4.46" y2="4.46" />
205
- <line x1="11.54" y1="11.54" x2="12.95" y2="12.95" />
206
- <line x1="3.05" y1="12.95" x2="4.46" y2="11.54" />
207
- <line x1="11.54" y1="4.46" x2="12.95" y2="3.05" />
208
- </Icon>
209
- );
210
- }
211
-
212
195
  /** Crescent moon */
213
196
  export function IconMoon(p: IconProps): JSX.Element {
214
197
  return (
@@ -253,39 +236,6 @@ export function IconTextAnnotation(p: IconProps): JSX.Element {
253
236
  );
254
237
  }
255
238
 
256
- /** Camera — snapshots */
257
- export function IconSnapshot(p: IconProps): JSX.Element {
258
- return (
259
- <Icon {...p}>
260
- <rect x="1" y="4" width="14" height="10" rx="1.5" />
261
- <path d="M5.5 4 L6.5 2.5 H9.5 L10.5 4" />
262
- <circle cx="8" cy="9" r="2.3" />
263
- </Icon>
264
- );
265
- }
266
-
267
- /** Bullseye — trace toggle */
268
- export function IconTrace(p: IconProps): JSX.Element {
269
- return (
270
- <Icon {...p}>
271
- <circle cx="8" cy="8" r="6" />
272
- <circle cx="8" cy="8" r="3" />
273
- <circle cx="8" cy="8" r="0.8" fill="currentColor" stroke="none" />
274
- </Icon>
275
- );
276
- }
277
-
278
- /** X in circle — clear trace */
279
- export function IconClearTrace(p: IconProps): JSX.Element {
280
- return (
281
- <Icon {...p}>
282
- <circle cx="8" cy="8" r="6" />
283
- <line x1="5.5" y1="5.5" x2="10.5" y2="10.5" />
284
- <line x1="10.5" y1="5.5" x2="5.5" y2="10.5" />
285
- </Icon>
286
- );
287
- }
288
-
289
239
  /** Magnifying glass — search */
290
240
  export function IconSearch(p: IconProps): JSX.Element {
291
241
  return (
@@ -296,19 +246,6 @@ export function IconSearch(p: IconProps): JSX.Element {
296
246
  );
297
247
  }
298
248
 
299
- /** Keyboard — shortcuts */
300
- export function IconShortcuts(p: IconProps): JSX.Element {
301
- return (
302
- <Icon {...p}>
303
- <rect x="1" y="3" width="14" height="10" rx="1.5" />
304
- <line x1="4" y1="6" x2="5" y2="6" />
305
- <line x1="7.5" y1="6" x2="8.5" y2="6" />
306
- <line x1="11" y1="6" x2="12" y2="6" />
307
- <line x1="4" y1="10" x2="12" y2="10" />
308
- </Icon>
309
- );
310
- }
311
-
312
249
  /** Speech bubble — steer the agent (command bar composer). */
313
250
  export function IconSteer(p: IconProps): JSX.Element {
314
251
  return (
@@ -1,10 +1,4 @@
1
1
  .pmx-host-app { height: 100%; display: flex; flex-direction: column; }
2
2
  .pmx-host-app > .app-shell { flex: 1; min-height: 0; height: auto; }
3
- /* Keep context controls clear of the host's floating chat composer. */
4
- .pmx-host-app .context-pin-bar {
5
- bottom: 16px;
6
- left: 16px;
7
- transform: none;
8
- }
9
3
  /* Guard refusals must remain readable while the full source editor is open. */
10
4
  .pmx-host-app .attention-toast.attention-tone-remove { z-index: 100100; }
@@ -1,4 +1,5 @@
1
1
  import { useState } from 'preact/hooks';
2
+ import { BarHint } from '../canvas/BarHint';
2
3
  import { readAxFlow, readAxStep, type AxFlowStamp } from '../../shared/ax-flow.js';
3
4
  import { nodes, updateNodeData } from '../state/canvas-store';
4
5
  import { updateNodeFromClient } from '../state/intent-bridge';
@@ -121,31 +122,43 @@ function FlowLoopControls({ node, flow }: { node: CanvasNodeState; flow: AxFlowS
121
122
  {running ? `Loop running · run ${run + 1}/${maxRuns}` : `Loop idle · ${run}/${maxRuns} runs`}
122
123
  </span>
123
124
  {running ? (
124
- <button
125
- type="button"
126
- class="ax-node-action"
127
- style={activeButtonStyle}
128
- title="Stop the loop now. The stop is persisted on this node, so it survives a refresh and no further step is opened."
129
- onClick={(e) => {
130
- e.stopPropagation();
131
- void persistLoop(node, flow, { ...flow.loop, running: false });
132
- }}
125
+ <BarHint
126
+ label="Stop the loop now"
127
+ body="The stop is persisted on this node, so it survives a refresh and no further step is opened."
128
+ side="up"
129
+ fitWithin=".node-content, .expanded-overlay-panel"
133
130
  >
134
- Stop
135
- </button>
131
+ <button
132
+ type="button"
133
+ class="ax-node-action"
134
+ style={activeButtonStyle}
135
+ onClick={(e) => {
136
+ e.stopPropagation();
137
+ void persistLoop(node, flow, { ...flow.loop, running: false });
138
+ }}
139
+ >
140
+ Stop
141
+ </button>
142
+ </BarHint>
136
143
  ) : (
137
- <button
138
- type="button"
139
- class="ax-node-action"
140
- style={axNodeActionButtonStyle}
141
- title="Run this flow as a loop: each completed step opens the next one, and the last step starts the next run — until the run cap is reached or you press Stop."
142
- onClick={(e) => {
143
- e.stopPropagation();
144
- void handleRun();
145
- }}
144
+ <BarHint
145
+ label="Run this flow as a loop"
146
+ body="Each completed step opens the next one, and the last step starts the next run — until the run cap is reached or you press Stop."
147
+ side="up"
148
+ fitWithin=".node-content, .expanded-overlay-panel"
146
149
  >
147
- Run loop
148
- </button>
150
+ <button
151
+ type="button"
152
+ class="ax-node-action"
153
+ style={axNodeActionButtonStyle}
154
+ onClick={(e) => {
155
+ e.stopPropagation();
156
+ void handleRun();
157
+ }}
158
+ >
159
+ Run loop
160
+ </button>
161
+ </BarHint>
149
162
  )}
150
163
  <input
151
164
  class="ax-flow-steer-input"
@@ -170,18 +183,24 @@ function FlowLoopControls({ node, flow }: { node: CanvasNodeState; flow: AxFlowS
170
183
  color: 'var(--c-text)',
171
184
  }}
172
185
  />
173
- <button
174
- type="button"
175
- class="ax-node-action"
176
- style={axNodeActionButtonStyle}
177
- title="Send a steering message to the agent working this flow."
178
- onClick={(e) => {
179
- e.stopPropagation();
180
- void handleSteer();
181
- }}
186
+ <BarHint
187
+ label="Send a steering message to the agent working this flow"
188
+ side="up"
189
+ align="end"
190
+ fitWithin=".node-content, .expanded-overlay-panel"
182
191
  >
183
- Steer
184
- </button>
192
+ <button
193
+ type="button"
194
+ class="ax-node-action"
195
+ style={axNodeActionButtonStyle}
196
+ onClick={(e) => {
197
+ e.stopPropagation();
198
+ void handleSteer();
199
+ }}
200
+ >
201
+ Steer
202
+ </button>
203
+ </BarHint>
185
204
  </div>
186
205
  );
187
206
  }
@@ -199,25 +218,30 @@ export function AxStepControls({ node }: { node: CanvasNodeState }) {
199
218
  Step {step.index}/{step.total}
200
219
  </span>
201
220
  {STEP_ACTIONS.map((action) => (
202
- <button
221
+ <BarHint
203
222
  key={action.status}
204
- type="button"
205
- class="ax-node-action"
206
- aria-pressed={status === action.status}
207
- style={status === action.status ? activeButtonStyle : axNodeActionButtonStyle}
208
- title={`Set this step's work item to "${action.status}".`}
209
- onClick={(e) => {
210
- e.stopPropagation();
211
- void runNodeAxInteraction(
212
- node,
213
- 'ax.work.update',
214
- { id: step.workItemId, status: action.status },
215
- action.toast,
216
- );
217
- }}
223
+ label={`Set this step's work item to "${action.status}"`}
224
+ side="up"
225
+ fitWithin=".node-content, .expanded-overlay-panel"
218
226
  >
219
- {action.label}
220
- </button>
227
+ <button
228
+ type="button"
229
+ class="ax-node-action"
230
+ aria-pressed={status === action.status}
231
+ style={status === action.status ? activeButtonStyle : axNodeActionButtonStyle}
232
+ onClick={(e) => {
233
+ e.stopPropagation();
234
+ void runNodeAxInteraction(
235
+ node,
236
+ 'ax.work.update',
237
+ { id: step.workItemId, status: action.status },
238
+ action.toast,
239
+ );
240
+ }}
241
+ >
242
+ {action.label}
243
+ </button>
244
+ </BarHint>
221
245
  ))}
222
246
  </div>
223
247
  {flow && <FlowLoopControls node={node} flow={flow} />}
@@ -11,7 +11,11 @@ export function BoardNode({ node }: { node: CanvasNodeState }) {
11
11
  <div class="board-node-content">
12
12
  <div class="board-node-label">Linked board</div>
13
13
  <strong class="board-node-title">{target?.name ?? 'Missing board'}</strong>
14
- <div class="board-node-meta">{target ? `${target.nodeCount} nodes` : boardId || 'No target id'}</div>
14
+ <div class="board-node-meta">
15
+ {target
16
+ ? `${target.nodeCount} nodes${target.pin ? ' · pinned, so it travels with you' : ''}`
17
+ : boardId || 'No target id'}
18
+ </div>
15
19
  {target?.summary && <p class="board-node-summary">{target.summary}</p>}
16
20
  {!!target?.pinnedTitles?.length && (
17
21
  <p class="board-node-meta">Pinned: {target.pinnedTitles.map((pin) => pin.title).join(', ')}</p>
@@ -1,4 +1,5 @@
1
1
  import { openWorkbenchFile } from '../state/intent-bridge';
2
+ import { BarHint } from '../canvas/BarHint';
2
3
  import { TYPE_LABELS, type CanvasNodeState } from '../types';
3
4
  import { axNodeActionButtonStyle, runNodeAxInteraction } from './ax-node-actions';
4
5
 
@@ -172,18 +173,24 @@ export function ContextNode({
172
173
  >
173
174
  {/* AX: focus the agent on this context node */}
174
175
  <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
175
- <button
176
- type="button"
177
- class="ax-node-action"
178
- title="Point the agent at this node — sets it as the agent's current AX focus so the agent pulls it into context to work on next (a one-click alternative to manually pinning)."
179
- style={axNodeActionButtonStyle}
180
- onClick={(e) => {
181
- e.stopPropagation();
182
- void runNodeAxInteraction(node, 'ax.focus.set', undefined, 'Focus set');
183
- }}
176
+ <BarHint
177
+ label="Point the agent at this node"
178
+ body="Sets it as the agent's current AX focus, so the agent pulls it into context to work on next — a one-click alternative to pinning."
179
+ align="end"
180
+ fitWithin=".node-content"
184
181
  >
185
- Set focus
186
- </button>
182
+ <button
183
+ type="button"
184
+ class="ax-node-action"
185
+ style={axNodeActionButtonStyle}
186
+ onClick={(e) => {
187
+ e.stopPropagation();
188
+ void runNodeAxInteraction(node, 'ax.focus.set', undefined, 'Focus set');
189
+ }}
190
+ >
191
+ Set focus
192
+ </button>
193
+ </BarHint>
187
194
  </div>
188
195
 
189
196
  {tokenLimit !== null && tokenLimit > 0 && (
@@ -1576,7 +1576,6 @@ export function ExtAppFrame({ node, expanded = false }: { node: CanvasNodeState;
1576
1576
  expandNode(nodeId);
1577
1577
  }}
1578
1578
  class="ext-app-preview-catcher"
1579
- title="Click to open"
1580
1579
  style={{
1581
1580
  position: 'absolute',
1582
1581
  top: 0,
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
2
+ import { BarHint } from '../canvas/BarHint';
2
3
  import { IconPlusCircle, IconRefresh } from '../icons';
3
4
  import { delimiterForPath, parseDelimitedText } from '../../shared/delimited-text';
4
5
  import { updateNodeData } from '../state/canvas-store';
@@ -243,7 +244,6 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
243
244
  whiteSpace: 'nowrap',
244
245
  flex: 1,
245
246
  }}
246
- title={filePath}
247
247
  >
248
248
  {filePath}
249
249
  </span>
@@ -255,47 +255,51 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
255
255
  {new Date(updatedAt).toLocaleTimeString()}
256
256
  </span>
257
257
  )}
258
- <button
259
- type="button"
260
- class="ax-node-action"
261
- title="Mark this file as AX evidence"
262
- onClick={(e) => {
263
- e.stopPropagation();
264
- void runNodeAxInteraction(
265
- node,
266
- 'ax.evidence.add',
267
- { kind: 'file', title: filePath.split('/').pop() || filePath, ref: filePath },
268
- 'Marked as evidence',
269
- );
270
- }}
271
- style={{
272
- background: 'none',
273
- border: 'none',
274
- color: 'var(--c-muted)',
275
- cursor: 'pointer',
276
- padding: '2px 4px',
277
- fontSize: '12px',
278
- flexShrink: 0,
279
- }}
280
- >
281
- <IconPlusCircle size={14} />
282
- </button>
283
- <button
284
- type="button"
285
- onClick={handleReload}
286
- title="Reload file"
287
- style={{
288
- background: 'none',
289
- border: 'none',
290
- color: 'var(--c-muted)',
291
- cursor: 'pointer',
292
- padding: '2px 4px',
293
- fontSize: '12px',
294
- flexShrink: 0,
295
- }}
296
- >
297
- <IconRefresh size={14} />
298
- </button>
258
+ <BarHint label="Mark this file as AX evidence" align="end" fitWithin=".node-content">
259
+ <button
260
+ type="button"
261
+ class="ax-node-action"
262
+ aria-label="Mark this file as AX evidence"
263
+ onClick={(e) => {
264
+ e.stopPropagation();
265
+ void runNodeAxInteraction(
266
+ node,
267
+ 'ax.evidence.add',
268
+ { kind: 'file', title: filePath.split('/').pop() || filePath, ref: filePath },
269
+ 'Marked as evidence',
270
+ );
271
+ }}
272
+ style={{
273
+ background: 'none',
274
+ border: 'none',
275
+ color: 'var(--c-muted)',
276
+ cursor: 'pointer',
277
+ padding: '2px 4px',
278
+ fontSize: '12px',
279
+ flexShrink: 0,
280
+ }}
281
+ >
282
+ <IconPlusCircle size={14} />
283
+ </button>
284
+ </BarHint>
285
+ <BarHint label="Reload file" align="end" fitWithin=".node-content">
286
+ <button
287
+ type="button"
288
+ onClick={handleReload}
289
+ aria-label="Reload file"
290
+ style={{
291
+ background: 'none',
292
+ border: 'none',
293
+ color: 'var(--c-muted)',
294
+ cursor: 'pointer',
295
+ padding: '2px 4px',
296
+ fontSize: '12px',
297
+ flexShrink: 0,
298
+ }}
299
+ >
300
+ <IconRefresh size={14} />
301
+ </button>
302
+ </BarHint>
299
303
  </div>
300
304
 
301
305
  {/* Content area */}
@@ -121,6 +121,37 @@ export function HtmlNode({
121
121
  return () => window.clearTimeout(id);
122
122
  }, [autoFocus, surfaceSrc]);
123
123
 
124
+ const handleDocumentLoad = (frame: HTMLIFrameElement, signal: AbortSignal): Promise<boolean> => {
125
+ if (signal.aborted) return Promise.resolve(false);
126
+ return new Promise((resolve) => {
127
+ const finish = (ready: boolean) => {
128
+ clearTimeout(timer);
129
+ window.removeEventListener('message', receive);
130
+ signal.removeEventListener('abort', cancel);
131
+ resolve(ready);
132
+ };
133
+ const cancel = () => finish(false);
134
+ const receive = (event: MessageEvent) => {
135
+ if (
136
+ event.source !== frame.contentWindow ||
137
+ event.data?.source !== HTML_SURFACE_PUSH_SOURCE ||
138
+ event.data?.token !== themeToken
139
+ )
140
+ return;
141
+ // A healthy offscreen frame can wait for paint without a deadline.
142
+ if (event.data.type === 'mounted') clearTimeout(timer);
143
+ if (event.data.type === 'ready') finish(true);
144
+ };
145
+ const timer = setTimeout(cancel, 10_000);
146
+ window.addEventListener('message', receive);
147
+ signal.addEventListener('abort', cancel, { once: true });
148
+ frame.contentWindow?.postMessage(
149
+ { source: HTML_SURFACE_PUSH_SOURCE, type: 'check-ready', token: themeToken },
150
+ '*',
151
+ );
152
+ });
153
+ };
154
+
124
155
  const handleFrameLoad = () => {
125
156
  iframeRef.current?.contentWindow?.postMessage(
126
157
  {
@@ -174,6 +205,7 @@ export function HtmlNode({
174
205
  allow=""
175
206
  tabIndex={autoFocus ? 0 : undefined}
176
207
  onLoad={handleFrameLoad}
208
+ onDocumentLoad={handleDocumentLoad}
177
209
  />
178
210
  );
179
211
  }