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,74 @@
1
+ import type { CanvasNodeState } from './canvas-state.js';
2
+
3
+ const ORDINALS = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth'];
4
+ /** "See change" shows this much of each side, centred on the first difference. */
5
+ export const CHANGE_EXCERPT = 1200;
6
+
7
+ export function cardText(node: CanvasNodeState): string {
8
+ const { content, text } = node.data;
9
+ return typeof content === 'string' ? content : typeof text === 'string' ? text : '';
10
+ }
11
+
12
+ function cardTitle(node: CanvasNodeState): string {
13
+ return typeof node.data.title === 'string' ? node.data.title : '';
14
+ }
15
+
16
+ const paragraphs = (text: string) =>
17
+ text
18
+ .split(/\n\s*\n/)
19
+ .map((paragraph) => paragraph.trim().replace(/\s+/g, ' '))
20
+ .filter(Boolean);
21
+
22
+ /**
23
+ * What an agent's edit did to a card, in a few words, from the card before
24
+ * the session and now (AgentContext.dc.html: "rewrote the second paragraph").
25
+ * Paragraphs are aligned on their shared start and end, so a paragraph added
26
+ * at the top reads as added, not as everything rewritten.
27
+ */
28
+ export function describeEdit(before: CanvasNodeState, after: CanvasNodeState): string {
29
+ const parts: string[] = [];
30
+ if (cardTitle(before) !== cardTitle(after)) parts.push(`renamed it “${cardTitle(after)}”`);
31
+ const was = cardText(before);
32
+ const now = cardText(after);
33
+ if (was !== now) {
34
+ const a = paragraphs(was);
35
+ const b = paragraphs(now);
36
+ let head = 0;
37
+ while (head < a.length && head < b.length && a[head] === b[head]) head += 1;
38
+ let tail = 0;
39
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail])
40
+ tail += 1;
41
+ const removed = a.length - head - tail;
42
+ const added = b.length - head - tail;
43
+ const count = (n: number) => `${n} paragraph${n === 1 ? '' : 's'}`;
44
+ if (removed === 0 && added === 0) parts.push('reformatted its text');
45
+ else if (a.length === 0) parts.push('wrote its text');
46
+ else if (b.length === 0) parts.push('cleared its text');
47
+ else if (removed === 0) parts.push(`added ${count(added)}`);
48
+ else if (added === 0) parts.push(`removed ${count(removed)}`);
49
+ else if (removed === 1 && added === 1) parts.push(`rewrote the ${ORDINALS[head] ?? `#${head + 1}`} paragraph`);
50
+ else if (Math.max(removed, added) > Math.max(a.length, b.length) / 2) parts.push('rewrote most of it');
51
+ else parts.push(`changed ${count(Math.max(removed, added))} of ${Math.max(a.length, b.length)}`);
52
+ }
53
+ return parts.join(' and ') || 'changed its settings';
54
+ }
55
+
56
+ /**
57
+ * Before and after for "See change": each side cut to CHANGE_EXCERPT around
58
+ * the first character that differs, with "…" where text was cut, so a change
59
+ * deep in a long card is what shows.
60
+ */
61
+ export function changeExcerpts(was: string, now: string): { before: string; after: string } {
62
+ let at = 0;
63
+ while (at < was.length && at < now.length && was[at] === now[at]) at += 1;
64
+ const from = Math.max(0, at - Math.floor(CHANGE_EXCERPT / 4));
65
+ // Never cut between the two halves of a surrogate pair (an emoji).
66
+ const low = (text: string, index: number) => /[\uDC00-\uDFFF]/.test(text[index] ?? '');
67
+ const cut = (text: string) => {
68
+ const start = low(text, from) ? from - 1 : from;
69
+ let end = start + CHANGE_EXCERPT;
70
+ if (low(text, end)) end -= 1;
71
+ return `${start > 0 ? '…' : ''}${text.slice(start, end)}${end < text.length ? '…' : ''}`;
72
+ };
73
+ return { before: cut(was), after: cut(now) };
74
+ }
@@ -60,7 +60,14 @@ function buildThemeBridge(themeToken: string): string {
60
60
  const PMX_CANVAS_THEME_TOKEN = ${token};
61
61
  window.addEventListener('message', (event) => {
62
62
  const message = event.data;
63
- if (!message || message.source !== '${HTML_SURFACE_PUSH_SOURCE}' || message.type !== 'theme-update' || message.token !== PMX_CANVAS_THEME_TOKEN) return;
63
+ if (event.source !== window.parent || !message || message.source !== '${HTML_SURFACE_PUSH_SOURCE}' || message.token !== PMX_CANVAS_THEME_TOKEN) return;
64
+ if (message.type === 'check-ready') {
65
+ const reply = (type) => window.parent.postMessage({ source: '${HTML_SURFACE_PUSH_SOURCE}', type, token: PMX_CANVAS_THEME_TOKEN }, '*');
66
+ reply('mounted');
67
+ requestAnimationFrame(() => requestAnimationFrame(() => reply('ready')));
68
+ return;
69
+ }
70
+ if (message.type !== 'theme-update') return;
64
71
  if (typeof message.theme !== 'string') return;
65
72
  document.documentElement.setAttribute('data-pmx-canvas-theme', message.theme);
66
73
  document.documentElement.setAttribute('data-theme', message.theme);
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { derivedTour, type Tour } from '../shared/tour.js';
2
+ import { derivedTour, type Camera, type Tour, type TourStop } from '../shared/tour.js';
3
+ import { exitTour, goToTourStop, moveCamera, type TourStep, type TourStepResult } from './tour-control.js';
3
4
  export type { Tour, TourStop } from '../shared/tour.js';
5
+ export type { TourStep, TourStepResult } from './tour-control.js';
4
6
  import { agentPresence } from './agent-presence.js';
5
7
  import { checkFenceTarget, type FenceTarget } from './scope-fence.js';
6
8
  import type { AgentPhase, AgentPresence, AgentPresenceSnapshot } from '../shared/agent-presence.js';
@@ -38,6 +40,7 @@ import type {
38
40
  PmxAxWorkItemStatus,
39
41
  } from './ax-state.js';
40
42
  import type { AxTimelineQuery, CanvasBoard } from './canvas-db.js';
43
+ import type { MemoryGraph } from '../shared/memory-graph.js';
41
44
  import type { Attachment, DocumentImport, ImportSection } from './document-import.js';
42
45
  import { workbenchToken } from './workbench-auth.js';
43
46
  import type { BackupFile, BackupStatus } from './backup.js';
@@ -45,7 +48,8 @@ import type { ExportManifest } from './board-export.js';
45
48
  import { onFileNodeChanged } from './file-watcher.js';
46
49
  import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
47
50
  import { overlapsAny } from '../shared/placement.js';
48
- import { searchNodes, buildSpatialContext } from './spatial-analysis.js';
51
+ import { buildSpatialContext } from './spatial-analysis.js';
52
+ import { searchCanvasNodes } from './operations/ops/query.js';
49
53
  import { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
50
54
  import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
51
55
  import {
@@ -75,10 +79,12 @@ import {
75
79
  import {
76
80
  buildNodePatch,
77
81
  createBasicCanvasNode,
82
+ createDestinationNode,
78
83
  removeNodeCore,
79
84
  setGroupChildrenFromApi,
80
85
  } from './operations/ops/nodes.js';
81
86
  import { streamJsonRenderCore } from './operations/ops/json-render.js';
87
+ import { boardsPayload } from './operations/ops/boards.js';
82
88
  import { executeOperation, runCanvasBatchOperation, type OpenMcpAppCoreResult } from './operations/index.js';
83
89
  import { validateCanvasLayout } from './canvas-validation.js';
84
90
  import { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
@@ -289,10 +295,14 @@ export class PmxCanvas extends EventEmitter {
289
295
  * or keep the whole node — both work. (Previously returned a bare id string.)
290
296
  */
291
297
  addNode(input: {
298
+ destinationBoardId?: string;
299
+ requestId?: string;
300
+ expectedBoardId?: string | null;
292
301
  intentId?: string;
293
302
  type: CanvasNodeState['type'];
294
303
  title?: string;
295
304
  content?: string;
305
+ data?: Record<string, unknown>;
296
306
  children?: string[];
297
307
  childIds?: string[];
298
308
  childLayout?: 'grid' | 'column' | 'flow';
@@ -309,6 +319,19 @@ export class PmxCanvas extends EventEmitter {
309
319
  height?: number;
310
320
  strictSize?: boolean;
311
321
  }): SdkCanvasNode {
322
+ if (input.expectedBoardId !== undefined && input.expectedBoardId !== canvasState.activeBoardId)
323
+ throw new Error('The active board changed. Refresh before retrying this action.');
324
+ if (input.destinationBoardId !== undefined || input.requestId !== undefined) {
325
+ const revision = canvasState.getContentRevision().revision;
326
+ const node = createDestinationNode(input);
327
+ if (
328
+ (input.destinationBoardId ?? canvasState.activeBoardId) === canvasState.activeBoardId ||
329
+ revision !== canvasState.getContentRevision().revision
330
+ )
331
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
332
+ emitPrimaryWorkbenchEvent('boards-changed', boardsPayload());
333
+ return toSdkNode(node);
334
+ }
312
335
  assertInsideFence(
313
336
  'node.add',
314
337
  createTarget(input, input.type === 'group' ? (input.childIds ?? input.children ?? []) : []),
@@ -442,17 +465,41 @@ export class PmxCanvas extends EventEmitter {
442
465
  }
443
466
 
444
467
  addEdge(input: {
468
+ destinationBoardId?: string;
469
+ requestId?: string;
470
+ expectedBoardId?: string | null;
445
471
  intentId?: string;
446
472
  from?: string;
447
473
  to?: string;
474
+ toBoardId?: string;
448
475
  fromSearch?: string;
449
476
  toSearch?: string;
450
477
  type: CanvasEdge['type'];
478
+ kind?: string;
479
+ reason?: string;
451
480
  label?: string;
452
481
  style?: CanvasEdge['style'];
453
482
  animated?: boolean;
454
483
  }): string {
455
- assertInsideFence('edge.add', { nodeIds: [input.from ?? '', input.to ?? ''] });
484
+ if (input.expectedBoardId !== undefined && input.expectedBoardId !== canvasState.activeBoardId)
485
+ throw new Error('The active board changed. Refresh before retrying this action.');
486
+ if (input.destinationBoardId !== undefined || input.requestId !== undefined) {
487
+ const revision = canvasState.getContentRevision().revision;
488
+ const edge = addCanvasEdge(input);
489
+ if (
490
+ (input.destinationBoardId ?? canvasState.activeBoardId) === canvasState.activeBoardId ||
491
+ revision !== canvasState.getContentRevision().revision
492
+ )
493
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
494
+ emitPrimaryWorkbenchEvent('boards-changed', boardsPayload());
495
+ return edge.id;
496
+ }
497
+ assertInsideFence('edge.add', {
498
+ nodeIds:
499
+ input.toBoardId && input.toBoardId !== canvasState.activeBoardId
500
+ ? [input.from ?? '']
501
+ : [input.from ?? '', input.to ?? ''],
502
+ });
456
503
  return this.runIntentCommit(
457
504
  input.intentId,
458
505
  ['connect'],
@@ -465,6 +512,13 @@ export class PmxCanvas extends EventEmitter {
465
512
  );
466
513
  }
467
514
 
515
+ async updateEdge(
516
+ id: string,
517
+ patch: Partial<Pick<CanvasEdge, 'type' | 'kind' | 'reason' | 'label' | 'style' | 'animated'>>,
518
+ ): Promise<CanvasEdge> {
519
+ return (await executeOperation('edge.update', { id, ...patch }, { source: 'sdk' })) as CanvasEdge;
520
+ }
521
+
468
522
  addAnnotation(input: Omit<CanvasAnnotation, 'id' | 'createdAt'> & { id?: string; createdAt?: string }): string {
469
523
  const id = input.id ?? `ann-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
470
524
  canvasState.addAnnotation({
@@ -484,7 +538,8 @@ export class PmxCanvas extends EventEmitter {
484
538
 
485
539
  removeEdge(id: string): void {
486
540
  const edge = canvasState.getEdges().find((entry) => entry.id === id);
487
- if (edge) assertInsideFence('edge.remove', { nodeIds: [edge.from, edge.to] });
541
+ if (edge)
542
+ assertInsideFence('edge.remove', { nodeIds: edge.toBoardId !== undefined ? [edge.from] : [edge.from, edge.to] });
488
543
  removeCanvasEdge(id);
489
544
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
490
545
  }
@@ -494,6 +549,9 @@ export class PmxCanvas extends EventEmitter {
494
549
  * If childIds are provided, the group auto-sizes to contain them with padding.
495
550
  */
496
551
  createGroup(input: {
552
+ destinationBoardId?: string;
553
+ requestId?: string;
554
+ expectedBoardId?: string | null;
497
555
  intentId?: string;
498
556
  title?: string;
499
557
  childIds?: string[];
@@ -504,6 +562,19 @@ export class PmxCanvas extends EventEmitter {
504
562
  color?: string;
505
563
  childLayout?: 'grid' | 'column' | 'flow';
506
564
  }): string {
565
+ if (input.expectedBoardId !== undefined && input.expectedBoardId !== canvasState.activeBoardId)
566
+ throw new Error('The active board changed. Refresh before retrying this action.');
567
+ if (input.destinationBoardId !== undefined || input.requestId !== undefined) {
568
+ const revision = canvasState.getContentRevision().revision;
569
+ const { id } = createCanvasGroup(input);
570
+ if (
571
+ (input.destinationBoardId ?? canvasState.activeBoardId) === canvasState.activeBoardId ||
572
+ revision !== canvasState.getContentRevision().revision
573
+ )
574
+ emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
575
+ emitPrimaryWorkbenchEvent('boards-changed', boardsPayload());
576
+ return id;
577
+ }
507
578
  assertInsideFence('group.create', { nodeIds: input.childIds ?? [] });
508
579
  return this.runIntentCommit(
509
580
  input.intentId,
@@ -571,6 +642,26 @@ export class PmxCanvas extends EventEmitter {
571
642
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
572
643
  }
573
644
 
645
+ /** Present a tour stop (index, `next` or `previous`) in the workbench. Not recorded in undo history. */
646
+ goToTourStop(step: TourStep, options?: { present?: boolean }): TourStepResult {
647
+ const result = goToTourStop(step);
648
+ emitPrimaryWorkbenchEvent('canvas-tour-step', { ...result, present: options?.present !== false });
649
+ return result;
650
+ }
651
+
652
+ /** Leave presentation in every viewer. */
653
+ exitTour(): void {
654
+ exitTour();
655
+ emitPrimaryWorkbenchEvent('canvas-tour-exit', {});
656
+ }
657
+
658
+ /** Ease the camera to a node, world rect or viewport. Not recorded in undo history. */
659
+ moveCamera(stop: TourStop): { viewport: Camera } {
660
+ const result = moveCamera(stop);
661
+ emitPrimaryWorkbenchEvent('canvas-camera-move', result);
662
+ return { viewport: result.viewport };
663
+ }
664
+
574
665
  clear(): void {
575
666
  assertInsideFence('canvas.clear', { boardWide: true });
576
667
  for (const node of canvasState.getLayout().nodes) {
@@ -805,10 +896,10 @@ export class PmxCanvas extends EventEmitter {
805
896
  )) as { import: DocumentImport; nodeIds: string[] };
806
897
  }
807
898
 
808
- async createBoard(name: string, category?: string): Promise<CanvasBoard> {
899
+ async createBoard(name: string, folder?: string): Promise<CanvasBoard> {
809
900
  const result = (await executeOperation(
810
901
  'board.create',
811
- { name, ...(category ? { category } : {}) },
902
+ { name, ...(folder ? { folder } : {}) },
812
903
  { source: 'sdk' },
813
904
  )) as {
814
905
  board: CanvasBoard;
@@ -816,8 +907,27 @@ export class PmxCanvas extends EventEmitter {
816
907
  return result.board;
817
908
  }
818
909
 
819
- /** Rename a board and/or file it under a category (`category: null` removes it). */
820
- async updateBoard(id: string, patch: { name?: string; category?: string | null }): Promise<void> {
910
+ /** Workspace category and folder actions do not open a board. */
911
+ async knowledge<T extends import('./knowledge-lifecycle.js').KnowledgeInput>(
912
+ input: T,
913
+ ): Promise<import('./knowledge-lifecycle.js').KnowledgeResults[T['action']]> {
914
+ const { action, ...payload } = input;
915
+ return (await executeOperation(`knowledge.${action}`, payload, {
916
+ source: 'sdk',
917
+ })) as import('./knowledge-lifecycle.js').KnowledgeResults[T['action']];
918
+ }
919
+
920
+ async lifecycle<T extends import('./knowledge-lifecycle.js').LifecycleInput>(
921
+ input: T,
922
+ ): Promise<import('./knowledge-lifecycle.js').LifecycleResults[T['action']]> {
923
+ const { action, ...payload } = input;
924
+ return (await executeOperation(action === 'review' ? 'node.review' : `node.lifecycle-${action}`, payload, {
925
+ source: 'sdk',
926
+ })) as import('./knowledge-lifecycle.js').LifecycleResults[T['action']];
927
+ }
928
+
929
+ /** Rename a board and/or file it under a folder (`folder: null` unfiles it). */
930
+ async updateBoard(id: string, patch: Parameters<typeof canvasState.updateBoard>[1]): Promise<void> {
821
931
  await executeOperation('board.update', { id, ...patch }, { source: 'sdk' });
822
932
  }
823
933
 
@@ -825,10 +935,19 @@ export class PmxCanvas extends EventEmitter {
825
935
  await executeOperation('board.update', { id, readmeNodeId }, { source: 'sdk' });
826
936
  }
827
937
 
938
+ /** Pin a board into the agent's working set (its README and pinned cards reach every brief). */
939
+ async pinBoard(id: string, reason?: string): Promise<void> {
940
+ await executeOperation('board.pin', { id, ...(reason ? { reason } : {}) }, { source: 'sdk' });
941
+ }
942
+
943
+ async unpinBoard(id: string): Promise<void> {
944
+ await executeOperation('board.unpin', { id }, { source: 'sdk' });
945
+ }
946
+
828
947
  async createBoardFrom(input: {
829
948
  sourceBoardId: string;
830
949
  name: string;
831
- category?: string;
950
+ folder?: string;
832
951
  nodeIds?: string[];
833
952
  includeReadme?: boolean;
834
953
  includeStructure?: boolean;
@@ -844,6 +963,13 @@ export class PmxCanvas extends EventEmitter {
844
963
  >;
845
964
  }
846
965
 
966
+ /** Same projection as Home Graph. Discovery summaries never count as full-content reads. */
967
+ async getMemoryGraph(
968
+ options: { focus?: string; depth?: number; projection?: 'full' | 'boards' } = {},
969
+ ): Promise<MemoryGraph> {
970
+ return (await executeOperation('memory-graph.get', options, { source: 'sdk' })) as MemoryGraph;
971
+ }
972
+
847
973
  /** Budgeted cross-board brief. Budget is measured in UTF-16 characters. */
848
974
  async getContextBrief(
849
975
  options: { consumer?: string; since?: number; budget?: number } = {},
@@ -912,6 +1038,11 @@ export class PmxCanvas extends EventEmitter {
912
1038
  return canvasState.getNodeReadStatus(board ?? canvasState.activeBoardId);
913
1039
  }
914
1040
 
1041
+ /** A board's latest agent read of any kind (a pinned board's map counts), or null. */
1042
+ getBoardLastRead(board?: string): ReturnType<typeof canvasState.getBoardLastRead> {
1043
+ return canvasState.getBoardLastRead(board ?? canvasState.activeBoardId);
1044
+ }
1045
+
915
1046
  listWorkItems(): PmxAxWorkItem[] {
916
1047
  return canvasState.getWorkItems();
917
1048
  }
@@ -1279,8 +1410,11 @@ export class PmxCanvas extends EventEmitter {
1279
1410
  return result;
1280
1411
  }
1281
1412
 
1282
- search(query: string): ReturnType<typeof searchNodes> {
1283
- return searchNodes(canvasState.getLayout().nodes, query);
1413
+ search(
1414
+ query: string,
1415
+ options: { scope?: 'active' | 'library'; boardId?: string; folder?: string; limit?: number } = {},
1416
+ ) {
1417
+ return searchCanvasNodes(query, options).results;
1284
1418
  }
1285
1419
 
1286
1420
  getSpatialContext() {
@@ -1293,6 +1427,10 @@ export class PmxCanvas extends EventEmitter {
1293
1427
  if (!entry) return { ok: false, description: 'Nothing to undo' };
1294
1428
  await syncCanvasRuntimeBackends();
1295
1429
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1430
+ emitPrimaryWorkbenchEvent('context-pins-changed', {
1431
+ count: canvasState.contextPinnedNodeIds.size,
1432
+ nodeIds: Array.from(canvasState.contextPinnedNodeIds),
1433
+ });
1296
1434
  // Undo can reverse an AX mutation (work item, focus, …); nudge AX surfaces to
1297
1435
  // re-fetch so a live board reflects the reversal (debounced client-side).
1298
1436
  emitPrimaryWorkbenchEvent('ax-state-changed', {});
@@ -1304,6 +1442,10 @@ export class PmxCanvas extends EventEmitter {
1304
1442
  if (!entry) return { ok: false, description: 'Nothing to redo' };
1305
1443
  await syncCanvasRuntimeBackends();
1306
1444
  emitPrimaryWorkbenchEvent('canvas-layout-update', { layout: canvasState.getLayout() });
1445
+ emitPrimaryWorkbenchEvent('context-pins-changed', {
1446
+ count: canvasState.contextPinnedNodeIds.size,
1447
+ nodeIds: Array.from(canvasState.contextPinnedNodeIds),
1448
+ });
1307
1449
  emitPrimaryWorkbenchEvent('ax-state-changed', {});
1308
1450
  return { ok: true, description: `Redid: ${entry.description}` };
1309
1451
  }
@@ -1359,6 +1501,24 @@ export class PmxCanvas extends EventEmitter {
1359
1501
  return result;
1360
1502
  }
1361
1503
 
1504
+ /** Put one card back to its content in a snapshot (the receipt's Undo on an edit). */
1505
+ async restoreSnapshotNode(
1506
+ snapshotId: string,
1507
+ nodeId: string,
1508
+ options: { preserveHumanEdits?: boolean } = {},
1509
+ ): Promise<{ ok: boolean; id: string; restored: boolean; skipped?: 'human-edit' }> {
1510
+ return (await executeOperation(
1511
+ 'snapshot.restore-node',
1512
+ { id: snapshotId, nodeId, ...options },
1513
+ { source: 'sdk' },
1514
+ )) as {
1515
+ ok: boolean;
1516
+ id: string;
1517
+ restored: boolean;
1518
+ skipped?: 'human-edit';
1519
+ };
1520
+ }
1521
+
1362
1522
  deleteSnapshot(id: string): { ok: boolean } {
1363
1523
  return deleteCanvasSnapshot(id);
1364
1524
  }
@@ -1779,3 +1939,13 @@ export type {
1779
1939
  PmxAxWorkItemStatus,
1780
1940
  } from './ax-state.js';
1781
1941
  export type { AxTimelineQuery } from './canvas-db.js';
1942
+ export type {
1943
+ Lifecycle,
1944
+ KnowledgeCategory,
1945
+ FolderProperties,
1946
+ ReviewRecord,
1947
+ KnowledgeInput,
1948
+ KnowledgeResults,
1949
+ LifecycleInput,
1950
+ LifecycleResults,
1951
+ } from './knowledge-lifecycle.js';