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
@@ -33,6 +33,8 @@ export interface CanvasNodeState {
33
33
  collapsed: boolean;
34
34
  pinned: boolean;
35
35
  contentRevision?: number;
36
+ /** Revision of this node's latest link change, kept apart from its text. */
37
+ linksRevision?: number;
36
38
  /** Who created / last edited the node's content (server attribution). */
37
39
  createdBy?: NodeActor;
38
40
  lastEditedBy?: NodeActor;
@@ -49,10 +51,20 @@ export interface CanvasEdge {
49
51
  id: string;
50
52
  from: string;
51
53
  to: string;
54
+ toBoardId?: string;
52
55
  type: 'relation' | 'depends-on' | 'flow' | 'references';
56
+ kind?: string;
57
+ reason?: string;
53
58
  label?: string;
54
59
  style?: 'solid' | 'dashed' | 'dotted';
55
60
  animated?: boolean;
61
+ /** Board revision of this link's latest add, retype or relabel. */
62
+ revision?: number;
63
+ changedBy?: NodeActor;
64
+ changedAt?: string;
65
+ /** Who first drew the link, and when (LinkAuthorship.dc.html). */
66
+ createdBy?: NodeActor;
67
+ createdAt?: string;
56
68
  }
57
69
 
58
70
  export interface CanvasAnnotationPoint {
@@ -0,0 +1,10 @@
1
+ /** Overlays and text editors get Escape before the underlying view selection. */
2
+ export function isSelectionEscape(event: KeyboardEvent): boolean {
3
+ const target = event.target instanceof HTMLElement ? event.target : null;
4
+ return (
5
+ event.key === 'Escape' &&
6
+ !event.defaultPrevented &&
7
+ !target?.closest('input, textarea, [contenteditable="true"]') &&
8
+ !document.querySelector('[role="dialog"], [role="menu"], .context-menu, .shortcut-overlay-backdrop')
9
+ );
10
+ }
@@ -0,0 +1,14 @@
1
+ /** How long ago, compact: "just now", "5m", "3h", "2d". */
2
+ export function age(iso: string): string {
3
+ const mins = Math.max(0, Math.floor((Date.now() - new Date(iso).getTime()) / 60000));
4
+ if (mins < 1) return 'just now';
5
+ if (mins < 60) return `${mins}m`;
6
+ const hours = Math.floor(mins / 60);
7
+ return hours < 24 ? `${hours}h` : `${Math.floor(hours / 24)}d`;
8
+ }
9
+
10
+ /** "just now", "5m ago", "3h ago", "2d ago". */
11
+ export function timeAgo(iso: string): string {
12
+ const value = age(iso);
13
+ return value === 'just now' ? value : `${value} ago`;
14
+ }
@@ -5,6 +5,11 @@
5
5
 
6
6
  @custom-variant dark (&:is([data-theme="dark"] *));
7
7
 
8
+ @theme {
9
+ --font-sans: "IBM Plex Sans", system-ui, sans-serif;
10
+ --font-mono: "IBM Plex Mono", monospace;
11
+ }
12
+
8
13
  :root {
9
14
  --radius: 0.5rem;
10
15
  --background: oklch(1 0 0);
@@ -135,7 +140,7 @@
135
140
  body {
136
141
  @apply bg-background text-foreground antialiased;
137
142
  margin: 0;
138
- font-family: system-ui, sans-serif;
143
+ font-family: var(--font-sans);
139
144
  }
140
145
  }
141
146
 
@@ -14,6 +14,7 @@ import {
14
14
  JSON_VIEWER_STATE_SOURCE,
15
15
  } from '../../shared/ax-surface-protocol.js';
16
16
  import { createRoot } from 'react-dom/client';
17
+ import { useLayoutEffect } from 'react';
17
18
  import { defineRegistry, JSONUIProvider, Renderer } from '@json-render/react';
18
19
  import { shadcnComponents } from '@json-render/shadcn';
19
20
  import { catalog } from '../catalog';
@@ -173,6 +174,36 @@ function applyTheme(theme: unknown): void {
173
174
  }
174
175
 
175
176
  function App({ store }: { store: StateStore }) {
177
+ useLayoutEffect(() => {
178
+ const token = window.__PMX_CANVAS_UI_STATE_TOKEN__;
179
+ if (!token) return;
180
+ // Mount is runtime health, not paint. Offscreen frames may not run rAF;
181
+ // acknowledge the commit now so the parent never times out a healthy viewer.
182
+ window.parent.postMessage(
183
+ {
184
+ source: JSON_VIEWER_STATE_SOURCE,
185
+ type: 'mounted',
186
+ token,
187
+ nodeId: window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,
188
+ },
189
+ '*',
190
+ );
191
+ // Let this document's rendering cycle advance after React's commit;
192
+ // parent animation frames alone do not advance a child frame's paint.
193
+ requestAnimationFrame(() =>
194
+ requestAnimationFrame(() => {
195
+ window.parent.postMessage(
196
+ {
197
+ source: JSON_VIEWER_STATE_SOURCE,
198
+ type: 'ready',
199
+ token,
200
+ nodeId: window.__PMX_CANVAS_JSON_RENDER_NODE_ID__,
201
+ },
202
+ '*',
203
+ );
204
+ }),
205
+ );
206
+ }, []);
176
207
  const spec = window.__PMX_CANVAS_JSON_RENDER_SPEC__;
177
208
 
178
209
  if (!spec) {
@@ -184,7 +215,6 @@ function App({ store }: { store: StateStore }) {
184
215
  alignItems: 'center',
185
216
  justifyContent: 'center',
186
217
  color: 'var(--muted-foreground, #666)',
187
- fontFamily: 'system-ui',
188
218
  }}
189
219
  >
190
220
  Waiting for UI spec...
@@ -187,8 +187,18 @@ export function registerCanvasApp(server: McpServer, ensureCanvas: () => Promise
187
187
  position: node.position,
188
188
  size: node.size,
189
189
  edges: layout.edges
190
- .filter((edge) => edge.from === node.id || edge.to === node.id)
191
- .map(({ id, from, to, type, label }) => ({ id, from, to, type, label: label ?? '' })),
190
+ .filter(
191
+ (edge) =>
192
+ edge.from === node.id || ((!edge.toBoardId || edge.toBoardId === input.boardId) && edge.to === node.id),
193
+ )
194
+ .map(({ id, from, to, toBoardId, type, label }) => ({
195
+ id,
196
+ from,
197
+ to,
198
+ ...(toBoardId !== undefined ? { toBoardId } : {}),
199
+ type,
200
+ label: label ?? '',
201
+ })),
192
202
  };
193
203
  return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result };
194
204
  }),
@@ -2,8 +2,11 @@ import { UriTemplate } from '@modelcontextprotocol/sdk/shared/uriTemplate.js';
2
2
 
3
3
  /** SDK v1 matches {?a,b} as two required, ordered parameters, unlike its expansion. */
4
4
  export class ContextUriTemplate extends UriTemplate {
5
- constructor() {
6
- super('canvas://context{?budget,consumer,since}');
5
+ constructor(
6
+ private readonly host = 'context',
7
+ private readonly parameters = ['budget', 'consumer', 'since'],
8
+ ) {
9
+ super(`canvas://${host}{?${parameters.join(',')}}`);
7
10
  }
8
11
 
9
12
  override match(uri: string): Record<string, string> | null {
@@ -11,7 +14,7 @@ export class ContextUriTemplate extends UriTemplate {
11
14
  const url = new URL(uri);
12
15
  if (
13
16
  url.protocol !== 'canvas:' ||
14
- url.host !== 'context' ||
17
+ url.host !== this.host ||
15
18
  url.pathname ||
16
19
  url.hash ||
17
20
  url.username ||
@@ -20,7 +23,7 @@ export class ContextUriTemplate extends UriTemplate {
20
23
  return null;
21
24
  const variables: Record<string, string> = {};
22
25
  for (const [key, value] of url.searchParams) {
23
- if (!['budget', 'consumer', 'since'].includes(key) || key in variables) return null;
26
+ if (!this.parameters.includes(key) || key in variables) return null;
24
27
  variables[key] = value;
25
28
  }
26
29
  return variables;
package/src/mcp/server.ts CHANGED
@@ -32,10 +32,10 @@ import { AX_INTERACTION_TYPES } from '../server/ax-interaction.js';
32
32
  import { buildPendingAxActivity } from '../server/ax-state.js';
33
33
  import { registerOperationTools, registerCompositeTools } from '../server/operations/index.js';
34
34
  import { agentSourceLabel } from '../server/operations/invoker.js';
35
- import { contextReadFromPayload } from '../server/context-reads.js';
35
+ import { contextReadFromPayload, pinnedBoardReads, graphReads } from '../server/context-reads.js';
36
+ import type { MemoryGraph } from '../shared/memory-graph.js';
36
37
  import { createCanvasAccess, refreshCanvasAccess, type CanvasAccess } from './canvas-access.js';
37
- import { serializeNodeForAgentContext } from '../shared/agent-context.js';
38
- import { buildSpatialContext, findNeighborhoods } from '../server/spatial-analysis.js';
38
+ import { buildSpatialContext } from '../server/spatial-analysis.js';
39
39
  import {
40
40
  getCanvasNodeTitle,
41
41
  serializeCanvasLayoutForAgent,
@@ -89,7 +89,7 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-tim
89
89
  const server = resourceNotificationServer;
90
90
  if (!server) return;
91
91
  try {
92
- if (type === 'pins') {
92
+ if (type === 'pins' || type === 'nodes') {
93
93
  server.server.sendResourceUpdated({ uri: 'canvas://pinned-context' });
94
94
  }
95
95
  if (type === 'pins' || type === 'ax') {
@@ -108,6 +108,7 @@ function sendCanvasResourceNotifications(type: 'nodes' | 'pins' | 'ax' | 'ax-tim
108
108
  server.server.sendResourceUpdated({ uri: 'canvas://boards' });
109
109
  server.server.sendResourceUpdated({ uri: 'canvas://summary' });
110
110
  server.server.sendResourceUpdated({ uri: 'canvas://context' });
111
+ server.server.sendResourceUpdated({ uri: 'canvas://graph' });
111
112
  server.server.sendResourceUpdated({ uri: 'canvas://spatial-context' });
112
113
  server.server.sendResourceUpdated({ uri: 'canvas://history' });
113
114
  server.server.sendResourceUpdated({ uri: 'canvas://code-graph' });
@@ -121,7 +122,8 @@ function handleRemoteSseFrame(frame: string): void {
121
122
  const event = eventLine?.slice('event: '.length).trim() ?? '';
122
123
  if (!event || event === 'connected' || event === 'ping') return;
123
124
  sendCanvasResourceNotifications(
124
- event === 'context-pins-changed'
125
+ // boards-changed covers board pins and switches, both of which change pinned context.
126
+ event === 'context-pins-changed' || event === 'boards-changed'
125
127
  ? 'pins'
126
128
  : event === 'ax-state-changed'
127
129
  ? 'ax'
@@ -508,45 +510,18 @@ export async function startMcpServer(): Promise<void> {
508
510
  description:
509
511
  'Content of all pinned nodes on the canvas. When the human pins nodes, ' +
510
512
  'they are telling the agent "this is what matters right now." Read this ' +
511
- 'resource to get structured context from the canvas.',
513
+ 'resource to get structured context from the canvas. pinnedBoards lists the ' +
514
+ 'boards in the working set (README summary and pinned card titles); their ' +
515
+ 'content arrives through canvas://context.',
512
516
  mimeType: 'application/json',
513
517
  },
514
518
  async () => {
515
519
  const c = await ensureCanvas();
516
520
  const readTarget = await c.prepareContextRead();
517
521
  const board = readTarget.boardId ?? undefined;
518
- const pinnedIds = new Set(readTarget.pinnedNodeIds);
519
- const layout = await c.getLayout(board);
520
-
521
- const pinnedNodes = layout.nodes.filter((n) => pinnedIds.has(n.id));
522
- const pinnedEdges = layout.edges.filter((e) => pinnedIds.has(e.from) && pinnedIds.has(e.to));
523
-
524
- // Compute neighborhoods: for each pinned node, find nearby unpinned nodes
525
- const neighborhoods = findNeighborhoods(layout.nodes, pinnedIds);
526
-
527
- const context = {
528
- pinnedCount: pinnedNodes.length,
529
- nodes: pinnedNodes.map((n) =>
530
- serializeNodeForAgentContext(n, {
531
- defaultTextLength: 700,
532
- webpageTextLength: 1600,
533
- includePosition: true,
534
- }),
535
- ),
536
- edges: pinnedEdges.map((e) => ({
537
- id: e.id,
538
- from: e.from,
539
- to: e.to,
540
- type: e.type,
541
- label: e.label ?? null,
542
- })),
543
- neighborhoods: neighborhoods.map((nh) => ({
544
- pinnedNodeId: nh.pinnedNodeId,
545
- pinnedNodeTitle: nh.pinnedNodeTitle,
546
- nearbyNodes: nh.neighbors,
547
- })),
548
- };
549
-
522
+ // Content and lifecycle are captured by one synchronous server operation,
523
+ // never joined across separate HTTP reads with different retirement state.
524
+ const context = await c.invoker().invoke('pinned-context.get', { board });
550
525
  const text = JSON.stringify(context, null, 2);
551
526
  await recordContextRead(c, 'mcp-resource', 'canvas://pinned-context', text, readTarget);
552
527
  return {
@@ -575,19 +550,66 @@ export async function startMcpServer(): Promise<void> {
575
550
  if (uri.searchParams.has('consumer')) input.consumer = uri.searchParams.get('consumer');
576
551
  if (uri.searchParams.has('budget')) input.budget = uri.searchParams.get('budget');
577
552
  if (uri.searchParams.has('since')) input.since = uri.searchParams.get('since');
578
- const document = await c.invoker().invoke('context.get', input);
553
+ // MCP invokers skip registry recording (proxied reads), so record here like
554
+ // the other context resources: the open board, then each pinned board
555
+ // delivered in full.
556
+ const readTarget = await c.prepareContextRead();
557
+ const document = await c.invoker().invoke('context.get', { ...input, expectedBoardId: readTarget.boardId });
558
+ const text = JSON.stringify(document);
559
+ await recordContextRead(c, 'mcp-resource', 'canvas://context', text, readTarget);
560
+ try {
561
+ const base = {
562
+ channel: 'mcp-resource' as const,
563
+ resource: 'canvas://context',
564
+ source: agentSourceLabel('mcp'),
565
+ consumer: server.server.getClientVersion()?.name ?? null,
566
+ agentId: null,
567
+ };
568
+ for (const read of pinnedBoardReads(base, text)) await c.recordContextRead(read);
569
+ } catch (error) {
570
+ console.error('[pmx-canvas mcp] recording a pinned-board read failed:', error);
571
+ }
579
572
  return {
580
573
  contents: [
581
574
  {
582
575
  uri: uri.href,
583
576
  mimeType: 'application/json',
584
- text: JSON.stringify(document),
577
+ text,
585
578
  },
586
579
  ],
587
580
  };
588
581
  },
589
582
  );
590
583
 
584
+ server.resource(
585
+ 'memory-graph',
586
+ new ResourceTemplate(new ContextUriTemplate('graph', ['focus', 'depth']), { list: undefined }),
587
+ {
588
+ description:
589
+ 'The same connected-memory projection Home draws. Focus on a board ID, qualified card key, or pins; depth 0–4. Summaries are seen, not full reads.',
590
+ mimeType: 'application/json',
591
+ },
592
+ async (uri) => {
593
+ const c = await ensureCanvas();
594
+ const input: Record<string, unknown> = {};
595
+ if (uri.searchParams.has('focus')) input.focus = uri.searchParams.get('focus');
596
+ if (uri.searchParams.has('depth')) input.depth = uri.searchParams.get('depth');
597
+ const graph = (await c.invoker().invoke('memory-graph.get', input)) as MemoryGraph;
598
+ for (const read of graphReads(
599
+ {
600
+ channel: 'mcp-resource',
601
+ resource: 'canvas://graph',
602
+ source: agentSourceLabel('mcp'),
603
+ consumer: server.server.getClientVersion()?.name ?? null,
604
+ agentId: null,
605
+ },
606
+ graph,
607
+ ))
608
+ await c.recordContextRead(read);
609
+ return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(graph) }] };
610
+ },
611
+ );
612
+
591
613
  server.resource(
592
614
  'ax-state',
593
615
  'canvas://ax',
@@ -797,7 +819,7 @@ export async function startMcpServer(): Promise<void> {
797
819
  'canvas://boards',
798
820
  {
799
821
  description:
800
- 'The boards in this workspace with their categories, most recently opened first, and which one is ' +
822
+ 'The boards in this workspace with their folders, most recently opened first, and which one is ' +
801
823
  'open (activeBoardId; null = Home). Writes go to the open board; use canvas_board action "open" to switch the shared workbench.',
802
824
  mimeType: 'application/json',
803
825
  },
@@ -10,6 +10,8 @@ const writes = new Set([
10
10
  'board.create',
11
11
  'board.open',
12
12
  'board.update',
13
+ 'board.pin',
14
+ 'board.unpin',
13
15
  'board.create-from',
14
16
  'board.delete',
15
17
  'node.add',
@@ -18,6 +20,14 @@ const writes = new Set([
18
20
  'node.update',
19
21
  'node.remove',
20
22
  'node.refresh',
23
+ 'node.lifecycle-set',
24
+ 'node.review',
25
+ 'node.lifecycle-restore',
26
+ 'knowledge.category-set',
27
+ 'knowledge.category-remove',
28
+ 'knowledge.folder-set',
29
+ 'knowledge.relation-preset-set',
30
+ 'knowledge.relation-preset-remove',
21
31
  'edge.add',
22
32
  'edge.update',
23
33
  'edge.remove',
@@ -28,6 +38,7 @@ const writes = new Set([
28
38
  'annotation.add',
29
39
  'annotation.remove',
30
40
  'theme.set',
41
+ 'settings.set',
31
42
  'viewport.set',
32
43
  'canvas.apply-updates',
33
44
  'canvas.clear',
@@ -38,6 +49,8 @@ const writes = new Set([
38
49
  'snapshot.restore',
39
50
  'snapshot.delete',
40
51
  'tour.set',
52
+ 'tour.go',
53
+ 'tour.exit',
41
54
  'ax.focus.set',
42
55
  'ax.policy.set',
43
56
  'ax.work.create',
@@ -157,6 +170,23 @@ export function registerWorkbenchApp(server: McpServer, ensureCanvas: () => Prom
157
170
  const node = await canvas.getNode(String(args.id));
158
171
  if (node?.data.path) throw new OperationError('Edit file-linked content in the local PMX workbench.', 403);
159
172
  }
173
+ if (op.name === 'settings.set') {
174
+ // Only this app-only UI preference request carries the workbench
175
+ // marker. Canvas edits still use the agent invoker below; never
176
+ // fetch or forward the daemon's human-author capability.
177
+ const response = await fetch(new URL('/api/canvas/settings', base), {
178
+ method: 'POST',
179
+ headers: {
180
+ 'Content-Type': 'application/json',
181
+ 'X-PMX-Workbench': '1',
182
+ 'X-PMX-Source': 'mcp',
183
+ },
184
+ body: JSON.stringify(args),
185
+ redirect: 'error',
186
+ signal: AbortSignal.timeout(15_000),
187
+ });
188
+ return reply(response.status, await response.text());
189
+ }
160
190
  const result = await canvas.invoker().invoke(op.name, args);
161
191
  return reply(route.status ? route.status(result) : 200, JSON.stringify(result));
162
192
  }
@@ -191,12 +221,15 @@ export function registerWorkbenchApp(server: McpServer, ensureCanvas: () => Prom
191
221
  throw new OperationError('This asset is too large for the MCP connection.');
192
222
  const contentType = response.headers.get('content-type') ?? 'application/octet-stream';
193
223
  const text = /^(text\/|application\/(json|javascript))/.test(contentType);
194
- return reply(
195
- response.status,
196
- text ? new TextDecoder().decode(bytes) : Buffer.from(bytes).toString('base64'),
197
- contentType,
198
- text ? 'text' : 'base64',
199
- );
224
+ let body = text ? new TextDecoder().decode(bytes) : Buffer.from(bytes).toString('base64');
225
+ if (target.pathname === '/api/workbench/poll') {
226
+ // Hosted frames act as agents; the direct workbench's human capability
227
+ // must not escape in private tool hydration metadata either.
228
+ const poll = JSON.parse(body) as { events: Array<{ payload: Record<string, unknown> }> };
229
+ for (const event of poll.events) delete event.payload.workbenchToken;
230
+ body = JSON.stringify(poll);
231
+ }
232
+ return reply(response.status, body, contentType, text ? 'text' : 'base64');
200
233
  } catch (error) {
201
234
  if (error instanceof OperationError)
202
235
  return reply(error.status, JSON.stringify({ ok: false, error: error.message }));