pmx-canvas 0.8.0 → 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 (341) 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 +66 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2759 -510
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2936 -632
  8. package/dist/json-render/index.css +81 -1
  9. package/dist/json-render/index.js +20 -20
  10. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  11. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  12. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  13. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  15. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  17. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  18. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  19. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  20. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  21. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  22. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  23. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  24. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  25. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  26. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  27. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  28. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  29. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  30. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  31. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  32. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  33. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  34. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  35. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  36. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  37. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  38. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  39. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  40. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  41. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  42. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  43. package/dist/types/client/icons.d.ts +0 -10
  44. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  45. package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
  46. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  47. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  48. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  49. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  50. package/dist/types/client/state/boards-store.d.ts +81 -9
  51. package/dist/types/client/state/canvas-store.d.ts +14 -1
  52. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  53. package/dist/types/client/state/context-status-store.d.ts +35 -0
  54. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  55. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  56. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  57. package/dist/types/client/state/presence-store.d.ts +4 -4
  58. package/dist/types/client/state/presentation.d.ts +28 -0
  59. package/dist/types/client/state/relation-store.d.ts +10 -0
  60. package/dist/types/client/state/session-store.d.ts +41 -2
  61. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  62. package/dist/types/client/types.d.ts +13 -1
  63. package/dist/types/client/utils/keyboard.d.ts +2 -0
  64. package/dist/types/client/utils/time-ago.d.ts +4 -0
  65. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  66. package/dist/types/server/agent-presence.d.ts +10 -3
  67. package/dist/types/server/ax-context.d.ts +1 -1
  68. package/dist/types/server/canvas-db.d.ts +30 -7
  69. package/dist/types/server/canvas-operations.d.ts +24 -2
  70. package/dist/types/server/canvas-provenance.d.ts +22 -2
  71. package/dist/types/server/canvas-serialization.d.ts +2 -0
  72. package/dist/types/server/canvas-state.d.ts +93 -15
  73. package/dist/types/server/canvas-validation.d.ts +1 -1
  74. package/dist/types/server/context-brief.d.ts +94 -6
  75. package/dist/types/server/context-reads.d.ts +33 -7
  76. package/dist/types/server/edit-summary.d.ts +20 -0
  77. package/dist/types/server/index.d.ts +71 -10
  78. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  79. package/dist/types/server/lifecycle-status.d.ts +10 -0
  80. package/dist/types/server/memory-graph.d.ts +9 -0
  81. package/dist/types/server/mutation-history.d.ts +2 -0
  82. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  83. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  84. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  85. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  86. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  87. package/dist/types/server/operations/ops/query.d.ts +23 -0
  88. package/dist/types/server/operations/registry.d.ts +1 -0
  89. package/dist/types/server/placement.d.ts +7 -0
  90. package/dist/types/server/scope-fence.d.ts +2 -1
  91. package/dist/types/server/spatial-analysis.d.ts +6 -3
  92. package/dist/types/server/tour-control.d.ts +32 -0
  93. package/dist/types/server/wiki-links.d.ts +14 -0
  94. package/dist/types/server/workbench-auth.d.ts +2 -0
  95. package/dist/types/shared/agent-context.d.ts +2 -0
  96. package/dist/types/shared/agent-presence.d.ts +15 -12
  97. package/dist/types/shared/auto-arrange.d.ts +1 -0
  98. package/dist/types/shared/boards.d.ts +4 -4
  99. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  100. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  101. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  102. package/dist/types/shared/memory-graph.d.ts +53 -0
  103. package/dist/types/shared/relations.d.ts +68 -0
  104. package/dist/types/shared/semantic-attention.d.ts +2 -0
  105. package/dist/types/shared/tour.d.ts +38 -0
  106. package/dist/types/shared/workspace-settings.d.ts +11 -0
  107. package/docs/RELEASE.md +21 -3
  108. package/docs/adr-001-bun-only-runtime.md +7 -1
  109. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  110. package/docs/api-reference.md +109 -0
  111. package/docs/ax-host-adapter-contract.md +1 -1
  112. package/docs/ax-state-contract.md +5 -4
  113. package/docs/cli.md +153 -15
  114. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  115. package/docs/design/AgentContext.dc.html +3 -3
  116. package/docs/design/BoardPins.dc.html +37 -22
  117. package/docs/design/Categories.dc.html +208 -0
  118. package/docs/design/CategoryEditor.dc.html +33 -0
  119. package/docs/design/Chrome.dc.html +55 -9
  120. package/docs/design/ContextChip.dc.html +224 -0
  121. package/docs/design/ContextChip600.dc.html +136 -0
  122. package/docs/design/EmptyState.dc.html +45 -0
  123. package/docs/design/Flows.dc.html +23 -1
  124. package/docs/design/HeaderMarks.dc.html +183 -0
  125. package/docs/design/Home.dc.html +19 -1
  126. package/docs/design/HomeGraph.dc.html +86 -202
  127. package/docs/design/HomeMap.dc.html +23 -1
  128. package/docs/design/HomeTeam.dc.html +32 -14
  129. package/docs/design/Lifecycle.dc.html +22 -0
  130. package/docs/design/Lifecycle600.dc.html +18 -0
  131. package/docs/design/LifecycleVariants.dc.html +157 -0
  132. package/docs/design/LinkAuthorship.dc.html +139 -0
  133. package/docs/design/LinksChanged.dc.html +203 -0
  134. package/docs/design/LinksOptions.dc.html +137 -0
  135. package/docs/design/LinksOptions600.dc.html +134 -0
  136. package/docs/design/NearPin.dc.html +9 -10
  137. package/docs/design/Pane600.dc.html +4 -3
  138. package/docs/design/Relations.dc.html +3 -2
  139. package/docs/design/Restore.dc.html +152 -0
  140. package/docs/design/SeeChange.dc.html +267 -0
  141. package/docs/design/Uses.dc.html +12 -6
  142. package/docs/design/canvas.json +146 -19
  143. package/docs/design.md +454 -18
  144. package/docs/environment.md +0 -1
  145. package/docs/evals/curation-effect.md +128 -0
  146. package/docs/http-api.md +465 -38
  147. package/docs/mcp.md +150 -19
  148. package/docs/node-types.md +58 -3
  149. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  150. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  151. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  152. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  153. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  154. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  155. package/docs/product-context-vision-2026-09.md +17 -1
  156. package/docs/product-vision-2026-09.md +1082 -43
  157. package/docs/product-vision-review-2026-09.md +13 -1
  158. package/docs/screenshots/workbench-live-dark.png +0 -0
  159. package/docs/screenshots/workbench-live-light.png +0 -0
  160. package/docs/sdk.md +62 -2
  161. package/package.json +1 -1
  162. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/plugin.json +1 -1
  164. package/plugins/pmx-canvas/runtime.json +1 -1
  165. package/plugins/pmx-canvas/tools.json +601 -22
  166. package/skills/pmx-canvas/SKILL.md +234 -43
  167. package/skills/pmx-canvas/evals/evals.json +62 -0
  168. package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
  169. package/skills/pmx-canvas/references/full-reference.md +120 -21
  170. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  171. package/skills/pmx-canvas/references/importing-context.md +132 -0
  172. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  173. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  174. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  175. package/src/cli/agent.ts +2 -0
  176. package/src/cli/commands/ax.ts +12 -0
  177. package/src/cli/commands/boards.ts +87 -11
  178. package/src/cli/commands/edges.ts +28 -0
  179. package/src/cli/commands/groups.ts +7 -0
  180. package/src/cli/commands/nodes.ts +4 -2
  181. package/src/cli/commands/query.ts +34 -2
  182. package/src/cli/commands/record.ts +199 -48
  183. package/src/cli/index.ts +7 -1
  184. package/src/client/App.tsx +177 -70
  185. package/src/client/canvas/AttentionHistory.tsx +0 -2
  186. package/src/client/canvas/BarHint.tsx +40 -2
  187. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  188. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  189. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  190. package/src/client/canvas/BoardPinButton.tsx +33 -0
  191. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  192. package/src/client/canvas/CanvasNode.tsx +360 -237
  193. package/src/client/canvas/CanvasViewport.tsx +45 -7
  194. package/src/client/canvas/CommandBar.tsx +29 -23
  195. package/src/client/canvas/CommandPalette.tsx +5 -12
  196. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  197. package/src/client/canvas/ContextChip.tsx +296 -0
  198. package/src/client/canvas/ContextMenu.tsx +63 -5
  199. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  200. package/src/client/canvas/EdgeLayer.tsx +339 -76
  201. package/src/client/canvas/EmptyState.tsx +33 -18
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  203. package/src/client/canvas/ExternalWriters.tsx +57 -42
  204. package/src/client/canvas/HomeGraphView.tsx +942 -0
  205. package/src/client/canvas/HomeMapView.tsx +641 -0
  206. package/src/client/canvas/HomeReviewView.tsx +167 -0
  207. package/src/client/canvas/HomeView.tsx +687 -160
  208. package/src/client/canvas/IntentLayer.tsx +19 -12
  209. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  210. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  211. package/src/client/canvas/LifecycleFields.tsx +91 -0
  212. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  213. package/src/client/canvas/MemoryInspector.tsx +104 -0
  214. package/src/client/canvas/Minimap.tsx +3 -3
  215. package/src/client/canvas/NearPinLayer.tsx +187 -0
  216. package/src/client/canvas/NodeContextMark.tsx +175 -97
  217. package/src/client/canvas/Presentation.tsx +23 -57
  218. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  219. package/src/client/canvas/RelationsPanel.tsx +82 -0
  220. package/src/client/canvas/SelectionBar.tsx +3 -6
  221. package/src/client/canvas/SessionPanel.tsx +90 -50
  222. package/src/client/canvas/SessionReceipt.tsx +311 -39
  223. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  224. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  225. package/src/client/canvas/ToolRail.tsx +367 -192
  226. package/src/client/canvas/TopBar.tsx +354 -124
  227. package/src/client/canvas/canvas-area.ts +10 -3
  228. package/src/client/canvas/graph-geometry.ts +195 -0
  229. package/src/client/canvas/graph-model.ts +254 -0
  230. package/src/client/canvas/memory-layout.ts +106 -0
  231. package/src/client/canvas/use-chrome-height.ts +20 -0
  232. package/src/client/canvas/use-focus-trap.ts +31 -5
  233. package/src/client/canvas/use-memory-graph.ts +58 -0
  234. package/src/client/canvas/use-node-drag.ts +2 -1
  235. package/src/client/canvas/use-pan-zoom.ts +64 -7
  236. package/src/client/icons.tsx +0 -63
  237. package/src/client/mcp-app/style.css +0 -6
  238. package/src/client/nodes/AxStepControls.tsx +74 -50
  239. package/src/client/nodes/BoardNode.tsx +5 -1
  240. package/src/client/nodes/ContextNode.tsx +18 -11
  241. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  242. package/src/client/nodes/FileNode.tsx +46 -42
  243. package/src/client/nodes/HtmlNode.tsx +44 -2
  244. package/src/client/nodes/ImageNode.tsx +1 -1
  245. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  246. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  247. package/src/client/nodes/MarkdownNode.tsx +377 -109
  248. package/src/client/nodes/McpAppNode.tsx +50 -20
  249. package/src/client/nodes/MdFormatBar.tsx +21 -18
  250. package/src/client/nodes/PromptNode.tsx +1 -1
  251. package/src/client/nodes/ResponseNode.tsx +1 -1
  252. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  253. package/src/client/state/attention-bridge.ts +64 -15
  254. package/src/client/state/boards-store.ts +193 -39
  255. package/src/client/state/canvas-store.ts +99 -12
  256. package/src/client/state/context-chip-store.ts +70 -0
  257. package/src/client/state/context-status-store.ts +53 -0
  258. package/src/client/state/intent-bridge.ts +51 -30
  259. package/src/client/state/knowledge-store.ts +43 -0
  260. package/src/client/state/near-pin-store.ts +115 -6
  261. package/src/client/state/presence-store.ts +7 -10
  262. package/src/client/state/presentation.ts +91 -0
  263. package/src/client/state/relation-store.ts +81 -0
  264. package/src/client/state/session-store.ts +144 -18
  265. package/src/client/state/sse-bridge.ts +206 -29
  266. package/src/client/state/workspace-settings.ts +11 -0
  267. package/src/client/theme/global.css +2759 -510
  268. package/src/client/types.ts +12 -0
  269. package/src/client/utils/keyboard.ts +10 -0
  270. package/src/client/utils/time-ago.ts +14 -0
  271. package/src/json-render/renderer/index.css +6 -1
  272. package/src/json-render/renderer/index.tsx +31 -1
  273. package/src/mcp/canvas-app.ts +12 -2
  274. package/src/mcp/context-uri-template.ts +7 -4
  275. package/src/mcp/server.ts +63 -41
  276. package/src/mcp/workbench-app.ts +39 -6
  277. package/src/server/agent-presence.ts +128 -28
  278. package/src/server/ax-context.ts +27 -13
  279. package/src/server/board-export.ts +12 -1
  280. package/src/server/canvas-db.ts +331 -42
  281. package/src/server/canvas-operations.ts +196 -84
  282. package/src/server/canvas-provenance.ts +53 -22
  283. package/src/server/canvas-serialization.ts +2 -0
  284. package/src/server/canvas-state.ts +950 -122
  285. package/src/server/canvas-validation.ts +9 -2
  286. package/src/server/context-brief.ts +345 -38
  287. package/src/server/context-reads.ts +179 -30
  288. package/src/server/demo-state.json +355 -24
  289. package/src/server/edit-summary.ts +74 -0
  290. package/src/server/html-surface.ts +8 -1
  291. package/src/server/index.ts +203 -13
  292. package/src/server/knowledge-lifecycle.ts +304 -0
  293. package/src/server/lifecycle-status.ts +232 -0
  294. package/src/server/memory-graph.ts +166 -0
  295. package/src/server/mutation-history.ts +18 -6
  296. package/src/server/operations/composites.ts +32 -11
  297. package/src/server/operations/http.ts +7 -2
  298. package/src/server/operations/index.ts +4 -0
  299. package/src/server/operations/mcp.ts +32 -1
  300. package/src/server/operations/ops/ax-presence.ts +1 -1
  301. package/src/server/operations/ops/ax-read.ts +57 -6
  302. package/src/server/operations/ops/ax-timeline.ts +28 -2
  303. package/src/server/operations/ops/backup.ts +5 -3
  304. package/src/server/operations/ops/batch.ts +35 -4
  305. package/src/server/operations/ops/boards.ts +188 -47
  306. package/src/server/operations/ops/canvas-wire.ts +31 -1
  307. package/src/server/operations/ops/edges.ts +36 -2
  308. package/src/server/operations/ops/groups.ts +6 -1
  309. package/src/server/operations/ops/json-render.ts +5 -1
  310. package/src/server/operations/ops/lifecycle.ts +262 -0
  311. package/src/server/operations/ops/memory-graph.ts +45 -0
  312. package/src/server/operations/ops/nodes.ts +107 -24
  313. package/src/server/operations/ops/query.ts +281 -68
  314. package/src/server/operations/ops/snapshots.ts +73 -1
  315. package/src/server/operations/ops/validate.ts +5 -1
  316. package/src/server/operations/ops/viewport.ts +97 -3
  317. package/src/server/operations/registry.ts +77 -26
  318. package/src/server/placement.ts +29 -0
  319. package/src/server/scope-fence.ts +78 -6
  320. package/src/server/server.ts +115 -16
  321. package/src/server/spatial-analysis.ts +48 -23
  322. package/src/server/tour-control.ts +82 -0
  323. package/src/server/wiki-links.ts +137 -0
  324. package/src/server/workbench-auth.ts +11 -0
  325. package/src/shared/agent-context.ts +3 -0
  326. package/src/shared/agent-presence.ts +15 -15
  327. package/src/shared/auto-arrange.ts +2 -0
  328. package/src/shared/boards.ts +4 -4
  329. package/src/shared/knowledge-lifecycle.ts +22 -0
  330. package/src/shared/knowledge-presets.ts +80 -0
  331. package/src/shared/lifecycle-status.ts +26 -0
  332. package/src/shared/memory-graph.ts +74 -0
  333. package/src/shared/relations.ts +98 -0
  334. package/src/shared/semantic-attention.ts +15 -1
  335. package/src/shared/tour.ts +23 -5
  336. package/src/shared/workspace-settings.ts +13 -0
  337. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  338. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  339. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  340. package/dist/types/server/agent-context.d.ts +0 -22
  341. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -12,6 +12,14 @@ export function shouldShowPresentationControls(node: CanvasNodeState): boolean {
12
12
  return node.type === 'html' && node.data.presentation === true;
13
13
  }
14
14
 
15
+ export function htmlAxCapabilitySignature(value: unknown): string {
16
+ const capabilities = value as { enabled?: unknown; allowed?: unknown } | undefined;
17
+ if (capabilities?.enabled !== true) return 'disabled';
18
+ if (!Array.isArray(capabilities.allowed)) return 'enabled:default';
19
+ const allowed = [...new Set(capabilities.allowed.filter((item): item is string => typeof item === 'string'))].sort();
20
+ return allowed.length > 0 ? `enabled:${allowed.join(',')}` : 'disabled';
21
+ }
22
+
15
23
  export function HtmlNode({
16
24
  node,
17
25
  expanded = false,
@@ -27,10 +35,12 @@ export function HtmlNode({
27
35
  }) {
28
36
  const iframeRef = useRef<HTMLIFrameElement>(null);
29
37
  const theme = canvasTheme.value;
38
+ const axCapabilitySignature = htmlAxCapabilitySignature(node.data.axCapabilities);
30
39
  // Stable per-mount nonce that authorizes parent → iframe theme-update messages.
31
40
  const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
32
- // Per-mount nonce authorizing iframe → parent AX emits (Phase 3 HTML bridge).
33
- const axToken = useMemo(() => `ax-${crypto.randomUUID()}`, []);
41
+ // Rotate the nonce when capabilities change: both the URL and injected srcdoc
42
+ // change, rebootstrapping the bridge and rejecting emits from the old document.
43
+ const axToken = useMemo(() => `ax-${crypto.randomUUID()}`, [axCapabilitySignature]);
34
44
  // Per-mount nonce for the content-height reporter (node grows to fit content).
35
45
  const frameToken = useMemo(() => `frame-${crypto.randomUUID()}`, []);
36
46
  const html =
@@ -111,6 +121,37 @@ export function HtmlNode({
111
121
  return () => window.clearTimeout(id);
112
122
  }, [autoFocus, surfaceSrc]);
113
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
+
114
155
  const handleFrameLoad = () => {
115
156
  iframeRef.current?.contentWindow?.postMessage(
116
157
  {
@@ -164,6 +205,7 @@ export function HtmlNode({
164
205
  allow=""
165
206
  tabIndex={autoFocus ? 0 : undefined}
166
207
  onLoad={handleFrameLoad}
208
+ onDocumentLoad={handleDocumentLoad}
167
209
  />
168
210
  );
169
211
  }
@@ -142,7 +142,7 @@ export function ImageNode({ node, expanded = false }: { node: CanvasNodeState; e
142
142
  <span class="image-node-meta">
143
143
  {sizeLabel && <span>{sizeLabel}</span>}
144
144
  {zoom !== 1 && (
145
- <button type="button" class="image-node-zoom-reset" onClick={resetView} title="Reset zoom">
145
+ <button type="button" class="image-node-zoom-reset" onClick={resetView} aria-label="Reset zoom">
146
146
  {zoomPct}% <IconRefresh size={14} />
147
147
  </button>
148
148
  )}
@@ -1,4 +1,5 @@
1
1
  import type { RefObject } from 'preact';
2
+ import { BarHint } from '../canvas/BarHint';
2
3
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
3
4
  import { modChord } from '../utils/platform';
4
5
  import { promptAndInsertLink, wrapSelectionInCode } from './inline-editor-commands';
@@ -136,15 +137,11 @@ export function InlineFormatBar({ hostRef, onChange }: { hostRef: RefObject<HTML
136
137
  >
137
138
  {ACTIONS.flatMap((a, i) => {
138
139
  const btn = (
139
- <button
140
- key={`btn-${i}`}
141
- type="button"
142
- class="md-inline-format-btn"
143
- title={a.title}
144
- onClick={() => handleClick(a)}
145
- >
146
- {a.icon}
147
- </button>
140
+ <BarHint key={`btn-${i}`} label={a.title}>
141
+ <button type="button" class="md-inline-format-btn" aria-label={a.title} onClick={() => handleClick(a)}>
142
+ {a.icon}
143
+ </button>
144
+ </BarHint>
148
145
  );
149
146
  return a.dividerBefore ? [<span key={`div-${i}`} class="md-inline-format-divider" />, btn] : [btn];
150
147
  })}
@@ -1,6 +1,6 @@
1
1
  import { gfm } from '@joplin/turndown-plugin-gfm';
2
2
  import TurndownService from 'turndown';
3
- import { useCallback, useEffect, useRef } from 'preact/hooks';
3
+ import { useCallback, useLayoutEffect, useRef } from 'preact/hooks';
4
4
  import { InlineFormatBar } from './InlineFormatBar';
5
5
  import { promptAndInsertLink } from './inline-editor-commands';
6
6
  import { sanitizeMarkdownHtml } from './markdown-sanitize';
@@ -15,6 +15,15 @@ function getTurndown(): TurndownService {
15
15
  bulletListMarker: '-',
16
16
  });
17
17
  td.use(gfm);
18
+ td.addRule('wikiLink', {
19
+ filter: (node) => node.nodeName === 'A' && node.hasAttribute('data-wiki-markdown'),
20
+ replacement: (_content, node) => {
21
+ const label = node.textContent ?? '';
22
+ return label === node.getAttribute('data-wiki-label')
23
+ ? node.getAttribute('data-wiki-markdown')!
24
+ : `[[${node.getAttribute('data-wiki-ref')}|${label}]]`;
25
+ },
26
+ });
18
27
  _turndown = td;
19
28
  return td;
20
29
  }
@@ -23,36 +32,61 @@ function getTurndown(): TurndownService {
23
32
  *
24
33
  * Two persistence signals:
25
34
  * - `onChange(md)` — fires on every input. Caller typically debounces.
26
- * - `onSave(md)` — fires on ⌘S and blur. Caller should persist immediately
35
+ * - `onSave(md)` — fires on ⌘S and blur. Caller should persist dirty drafts immediately
27
36
  * and cancel any pending debounced save, since `md` is the authoritative
28
- * latest content. Both receive freshly-serialized markdown so the caller
29
- * never reads a stale state snapshot. */
37
+ * latest content: the exact caller draft until input, serialized DOM after it. */
30
38
  export function InlineMarkdownEditor({
39
+ markdown,
31
40
  initialHtml,
32
41
  className,
33
42
  onChange,
34
43
  onSave,
35
44
  }: {
45
+ markdown: string;
36
46
  initialHtml: string;
37
47
  className?: string;
38
48
  onChange: (markdown: string) => void;
39
49
  onSave?: (markdown: string) => void;
40
50
  }) {
41
51
  const rootRef = useRef<HTMLDivElement>(null);
42
- // Capture mount-time HTML — decouples us from prop identity churn under
43
- // strict-mode double-invoke. Re-mount via `key` to swap documents.
44
- const initialHtmlRef = useRef(initialHtml);
52
+ const initialized = useRef(false);
53
+ const edited = useRef(false);
54
+ const displayedMarkdown = useRef(markdown);
55
+ const previousMarkdown = useRef(markdown);
45
56
 
46
- useEffect(() => {
57
+ useLayoutEffect(() => {
47
58
  const el = rootRef.current;
48
59
  if (!el) return;
49
- el.innerHTML = sanitizeMarkdownHtml(initialHtmlRef.current);
50
- // GFM task checkboxes are live in the editor too: marked emits them
51
- // `disabled`, which also swallows clicks in the expanded view.
52
- for (const box of el.querySelectorAll('input[type="checkbox"][disabled]')) {
53
- box.removeAttribute('disabled');
60
+ const html = sanitizeMarkdownHtml(initialHtml);
61
+ const sourceChanged = previousMarkdown.current !== markdown;
62
+ previousMarkdown.current = markdown;
63
+ if (!initialized.current || (sourceChanged && displayedMarkdown.current !== markdown)) {
64
+ initialized.current = true;
65
+ displayedMarkdown.current = markdown;
66
+ edited.current = false;
67
+ el.innerHTML = html;
68
+ // Marked emits disabled GFM boxes; they are editable here.
69
+ for (const box of el.querySelectorAll('input[type="checkbox"][disabled]')) box.removeAttribute('disabled');
70
+ return;
54
71
  }
55
- }, []);
72
+ // Local input already owns this Markdown. Only a different accepted source
73
+ // replaces it; metadata-only refreshes must not move the selection.
74
+ const fresh = document.createElement('div');
75
+ fresh.innerHTML = html;
76
+ const links = new Map(
77
+ [...fresh.querySelectorAll('a[data-wiki-ref]')].map((link) => [link.getAttribute('data-wiki-ref'), link]),
78
+ );
79
+ for (const link of el.querySelectorAll('a[data-wiki-ref]')) {
80
+ const replacement = links.get(link.getAttribute('data-wiki-ref'));
81
+ if (!replacement) continue;
82
+ for (const attribute of ['data-wiki-board', 'data-wiki-card', 'data-wiki-missing']) {
83
+ const value = replacement.getAttribute(attribute);
84
+ if (value === null) link.removeAttribute(attribute);
85
+ else link.setAttribute(attribute, value);
86
+ }
87
+ link.classList.toggle('is-unresolved', replacement.classList.contains('is-unresolved'));
88
+ }
89
+ }, [initialHtml, markdown]);
56
90
 
57
91
  const serialize = useCallback((): string => {
58
92
  const el = rootRef.current;
@@ -61,7 +95,10 @@ export function InlineMarkdownEditor({
61
95
  }, []);
62
96
 
63
97
  const handleInput = useCallback(() => {
64
- onChange(serialize());
98
+ edited.current = true;
99
+ const md = serialize();
100
+ displayedMarkdown.current = md;
101
+ onChange(md);
65
102
  }, [onChange, serialize]);
66
103
 
67
104
  const handleSave = useCallback(() => {
@@ -69,8 +106,10 @@ export function InlineMarkdownEditor({
69
106
  // focused) reads back an empty tree — saving that would WIPE the document.
70
107
  const el = rootRef.current;
71
108
  if (!el || !el.isConnected || el.innerHTML === '') return;
72
- onSave?.(serialize());
73
- }, [onSave, serialize]);
109
+ // Turndown cannot round-trip source spacing. Until this editor receives
110
+ // input, flush the caller's exact draft (including edits from Source mode).
111
+ onSave?.(edited.current ? serialize() : markdown);
112
+ }, [markdown, onSave, serialize]);
74
113
 
75
114
  const handleKeyDown = useCallback(
76
115
  (e: KeyboardEvent) => {