pmx-canvas 0.8.2 → 0.9.1

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 (338) 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 +59 -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/human-store.d.ts +2 -1
  53. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  54. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  55. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  56. package/dist/types/client/state/presence-store.d.ts +4 -4
  57. package/dist/types/client/state/presentation.d.ts +28 -0
  58. package/dist/types/client/state/relation-store.d.ts +10 -0
  59. package/dist/types/client/state/session-store.d.ts +41 -2
  60. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  61. package/dist/types/client/types.d.ts +13 -1
  62. package/dist/types/client/utils/keyboard.d.ts +2 -0
  63. package/dist/types/client/utils/time-ago.d.ts +4 -0
  64. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  65. package/dist/types/server/agent-presence.d.ts +10 -3
  66. package/dist/types/server/ax-context.d.ts +1 -1
  67. package/dist/types/server/canvas-db.d.ts +30 -7
  68. package/dist/types/server/canvas-operations.d.ts +24 -2
  69. package/dist/types/server/canvas-provenance.d.ts +22 -2
  70. package/dist/types/server/canvas-serialization.d.ts +2 -0
  71. package/dist/types/server/canvas-state.d.ts +93 -15
  72. package/dist/types/server/canvas-validation.d.ts +1 -1
  73. package/dist/types/server/context-brief.d.ts +94 -6
  74. package/dist/types/server/context-reads.d.ts +33 -7
  75. package/dist/types/server/edit-summary.d.ts +20 -0
  76. package/dist/types/server/index.d.ts +71 -10
  77. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  78. package/dist/types/server/lifecycle-status.d.ts +10 -0
  79. package/dist/types/server/memory-graph.d.ts +9 -0
  80. package/dist/types/server/mutation-history.d.ts +2 -0
  81. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  82. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  83. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  84. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  85. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  86. package/dist/types/server/operations/ops/query.d.ts +23 -0
  87. package/dist/types/server/operations/registry.d.ts +1 -0
  88. package/dist/types/server/placement.d.ts +7 -0
  89. package/dist/types/server/scope-fence.d.ts +2 -1
  90. package/dist/types/server/spatial-analysis.d.ts +6 -3
  91. package/dist/types/server/tour-control.d.ts +32 -0
  92. package/dist/types/server/wiki-links.d.ts +14 -0
  93. package/dist/types/server/workbench-auth.d.ts +2 -0
  94. package/dist/types/shared/agent-context.d.ts +2 -0
  95. package/dist/types/shared/agent-presence.d.ts +15 -12
  96. package/dist/types/shared/auto-arrange.d.ts +1 -0
  97. package/dist/types/shared/boards.d.ts +4 -4
  98. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  99. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  100. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  101. package/dist/types/shared/memory-graph.d.ts +53 -0
  102. package/dist/types/shared/relations.d.ts +68 -0
  103. package/dist/types/shared/semantic-attention.d.ts +2 -0
  104. package/dist/types/shared/tour.d.ts +38 -0
  105. package/dist/types/shared/workspace-settings.d.ts +11 -0
  106. package/docs/RELEASE.md +21 -3
  107. package/docs/adr-001-bun-only-runtime.md +7 -1
  108. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  109. package/docs/api-reference.md +109 -0
  110. package/docs/ax-host-adapter-contract.md +1 -1
  111. package/docs/ax-state-contract.md +5 -4
  112. package/docs/cli.md +153 -15
  113. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  114. package/docs/design/AgentContext.dc.html +3 -3
  115. package/docs/design/BoardPins.dc.html +37 -22
  116. package/docs/design/Categories.dc.html +208 -0
  117. package/docs/design/CategoryEditor.dc.html +33 -0
  118. package/docs/design/Chrome.dc.html +52 -9
  119. package/docs/design/ContextChip.dc.html +224 -0
  120. package/docs/design/ContextChip600.dc.html +136 -0
  121. package/docs/design/EmptyState.dc.html +45 -0
  122. package/docs/design/Flows.dc.html +23 -1
  123. package/docs/design/HeaderMarks.dc.html +183 -0
  124. package/docs/design/Home.dc.html +19 -1
  125. package/docs/design/HomeGraph.dc.html +86 -202
  126. package/docs/design/HomeMap.dc.html +23 -1
  127. package/docs/design/HomeTeam.dc.html +32 -14
  128. package/docs/design/Lifecycle.dc.html +22 -0
  129. package/docs/design/Lifecycle600.dc.html +18 -0
  130. package/docs/design/LifecycleVariants.dc.html +157 -0
  131. package/docs/design/LinkAuthorship.dc.html +139 -0
  132. package/docs/design/LinksChanged.dc.html +203 -0
  133. package/docs/design/LinksOptions.dc.html +137 -0
  134. package/docs/design/LinksOptions600.dc.html +134 -0
  135. package/docs/design/NearPin.dc.html +9 -10
  136. package/docs/design/Pane600.dc.html +4 -3
  137. package/docs/design/Relations.dc.html +3 -2
  138. package/docs/design/Restore.dc.html +152 -0
  139. package/docs/design/SeeChange.dc.html +267 -0
  140. package/docs/design/Uses.dc.html +12 -6
  141. package/docs/design/canvas.json +146 -19
  142. package/docs/design.md +449 -16
  143. package/docs/environment.md +0 -1
  144. package/docs/evals/curation-effect.md +128 -0
  145. package/docs/http-api.md +459 -37
  146. package/docs/mcp.md +146 -18
  147. package/docs/node-types.md +58 -3
  148. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  149. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  150. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  151. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  152. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  153. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  154. package/docs/product-context-vision-2026-09.md +17 -1
  155. package/docs/product-vision-2026-09.md +1082 -43
  156. package/docs/product-vision-review-2026-09.md +13 -1
  157. package/docs/screenshots/workbench-live-dark.png +0 -0
  158. package/docs/screenshots/workbench-live-light.png +0 -0
  159. package/docs/sdk.md +62 -2
  160. package/package.json +1 -1
  161. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  162. package/plugins/pmx-canvas/plugin.json +1 -1
  163. package/plugins/pmx-canvas/runtime.json +1 -1
  164. package/plugins/pmx-canvas/tools.json +601 -22
  165. package/skills/pmx-canvas/SKILL.md +216 -42
  166. package/skills/pmx-canvas/evals/evals.json +224 -0
  167. package/skills/pmx-canvas/references/full-reference.md +120 -21
  168. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  169. package/skills/pmx-canvas/references/importing-context.md +132 -0
  170. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  171. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  172. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  173. package/src/cli/agent.ts +2 -0
  174. package/src/cli/commands/ax.ts +12 -0
  175. package/src/cli/commands/boards.ts +88 -11
  176. package/src/cli/commands/edges.ts +28 -0
  177. package/src/cli/commands/groups.ts +7 -0
  178. package/src/cli/commands/nodes.ts +1 -0
  179. package/src/cli/commands/query.ts +34 -2
  180. package/src/cli/commands/record.ts +199 -48
  181. package/src/cli/index.ts +6 -1
  182. package/src/client/App.tsx +177 -70
  183. package/src/client/canvas/AttentionHistory.tsx +0 -2
  184. package/src/client/canvas/BarHint.tsx +40 -2
  185. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  186. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  187. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  188. package/src/client/canvas/BoardPinButton.tsx +33 -0
  189. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  190. package/src/client/canvas/CanvasNode.tsx +360 -237
  191. package/src/client/canvas/CanvasViewport.tsx +45 -7
  192. package/src/client/canvas/CommandBar.tsx +29 -23
  193. package/src/client/canvas/CommandPalette.tsx +5 -12
  194. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  195. package/src/client/canvas/ContextChip.tsx +296 -0
  196. package/src/client/canvas/ContextMenu.tsx +63 -5
  197. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  198. package/src/client/canvas/EdgeLayer.tsx +339 -76
  199. package/src/client/canvas/EmptyState.tsx +33 -18
  200. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  201. package/src/client/canvas/ExternalWriters.tsx +57 -42
  202. package/src/client/canvas/HomeGraphView.tsx +942 -0
  203. package/src/client/canvas/HomeMapView.tsx +641 -0
  204. package/src/client/canvas/HomeReviewView.tsx +167 -0
  205. package/src/client/canvas/HomeView.tsx +687 -160
  206. package/src/client/canvas/IntentLayer.tsx +19 -12
  207. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  208. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  209. package/src/client/canvas/LifecycleFields.tsx +91 -0
  210. package/src/client/canvas/LifecyclePanel.tsx +543 -0
  211. package/src/client/canvas/MemoryInspector.tsx +104 -0
  212. package/src/client/canvas/Minimap.tsx +3 -3
  213. package/src/client/canvas/NearPinLayer.tsx +187 -0
  214. package/src/client/canvas/NodeContextMark.tsx +175 -97
  215. package/src/client/canvas/Presentation.tsx +23 -57
  216. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  217. package/src/client/canvas/RelationsPanel.tsx +82 -0
  218. package/src/client/canvas/SelectionBar.tsx +3 -6
  219. package/src/client/canvas/SessionPanel.tsx +90 -50
  220. package/src/client/canvas/SessionReceipt.tsx +311 -39
  221. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  222. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  223. package/src/client/canvas/ToolRail.tsx +367 -192
  224. package/src/client/canvas/TopBar.tsx +354 -124
  225. package/src/client/canvas/canvas-area.ts +10 -3
  226. package/src/client/canvas/graph-geometry.ts +195 -0
  227. package/src/client/canvas/graph-model.ts +254 -0
  228. package/src/client/canvas/memory-layout.ts +106 -0
  229. package/src/client/canvas/use-chrome-height.ts +20 -0
  230. package/src/client/canvas/use-focus-trap.ts +31 -5
  231. package/src/client/canvas/use-memory-graph.ts +58 -0
  232. package/src/client/canvas/use-node-drag.ts +2 -1
  233. package/src/client/canvas/use-pan-zoom.ts +64 -7
  234. package/src/client/icons.tsx +0 -63
  235. package/src/client/mcp-app/style.css +0 -6
  236. package/src/client/nodes/AxStepControls.tsx +74 -50
  237. package/src/client/nodes/BoardNode.tsx +5 -1
  238. package/src/client/nodes/ContextNode.tsx +18 -11
  239. package/src/client/nodes/ExtAppFrame.tsx +6 -3
  240. package/src/client/nodes/FileNode.tsx +46 -42
  241. package/src/client/nodes/HtmlNode.tsx +32 -0
  242. package/src/client/nodes/ImageNode.tsx +1 -1
  243. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  244. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  245. package/src/client/nodes/MarkdownNode.tsx +377 -109
  246. package/src/client/nodes/McpAppNode.tsx +50 -20
  247. package/src/client/nodes/MdFormatBar.tsx +21 -18
  248. package/src/client/nodes/PromptNode.tsx +1 -1
  249. package/src/client/nodes/ResponseNode.tsx +1 -1
  250. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  251. package/src/client/state/attention-bridge.ts +64 -15
  252. package/src/client/state/boards-store.ts +193 -39
  253. package/src/client/state/canvas-store.ts +99 -12
  254. package/src/client/state/context-chip-store.ts +70 -0
  255. package/src/client/state/context-status-store.ts +53 -0
  256. package/src/client/state/human-store.ts +13 -7
  257. package/src/client/state/intent-bridge.ts +51 -30
  258. package/src/client/state/knowledge-store.ts +43 -0
  259. package/src/client/state/near-pin-store.ts +115 -6
  260. package/src/client/state/presence-store.ts +7 -10
  261. package/src/client/state/presentation.ts +91 -0
  262. package/src/client/state/relation-store.ts +81 -0
  263. package/src/client/state/session-store.ts +144 -18
  264. package/src/client/state/sse-bridge.ts +235 -32
  265. package/src/client/state/workspace-settings.ts +11 -0
  266. package/src/client/theme/global.css +2724 -476
  267. package/src/client/types.ts +12 -0
  268. package/src/client/utils/keyboard.ts +10 -0
  269. package/src/client/utils/time-ago.ts +14 -0
  270. package/src/json-render/renderer/index.tsx +31 -0
  271. package/src/mcp/canvas-app.ts +12 -2
  272. package/src/mcp/context-uri-template.ts +7 -4
  273. package/src/mcp/server.ts +63 -41
  274. package/src/mcp/workbench-app.ts +39 -6
  275. package/src/server/agent-presence.ts +128 -28
  276. package/src/server/ax-context.ts +27 -13
  277. package/src/server/board-export.ts +12 -1
  278. package/src/server/canvas-db.ts +331 -42
  279. package/src/server/canvas-operations.ts +195 -83
  280. package/src/server/canvas-provenance.ts +53 -22
  281. package/src/server/canvas-serialization.ts +2 -0
  282. package/src/server/canvas-state.ts +950 -122
  283. package/src/server/canvas-validation.ts +9 -2
  284. package/src/server/context-brief.ts +345 -38
  285. package/src/server/context-reads.ts +178 -30
  286. package/src/server/demo-state.json +355 -24
  287. package/src/server/edit-summary.ts +74 -0
  288. package/src/server/html-surface.ts +8 -1
  289. package/src/server/index.ts +181 -11
  290. package/src/server/knowledge-lifecycle.ts +304 -0
  291. package/src/server/lifecycle-status.ts +232 -0
  292. package/src/server/memory-graph.ts +166 -0
  293. package/src/server/mutation-history.ts +18 -6
  294. package/src/server/operations/composites.ts +32 -11
  295. package/src/server/operations/http.ts +7 -2
  296. package/src/server/operations/index.ts +4 -0
  297. package/src/server/operations/mcp.ts +26 -1
  298. package/src/server/operations/ops/ax-presence.ts +1 -1
  299. package/src/server/operations/ops/ax-read.ts +57 -6
  300. package/src/server/operations/ops/ax-timeline.ts +28 -2
  301. package/src/server/operations/ops/backup.ts +5 -3
  302. package/src/server/operations/ops/batch.ts +35 -4
  303. package/src/server/operations/ops/boards.ts +188 -47
  304. package/src/server/operations/ops/canvas-wire.ts +31 -1
  305. package/src/server/operations/ops/edges.ts +36 -2
  306. package/src/server/operations/ops/groups.ts +6 -1
  307. package/src/server/operations/ops/lifecycle.ts +262 -0
  308. package/src/server/operations/ops/memory-graph.ts +45 -0
  309. package/src/server/operations/ops/nodes.ts +107 -24
  310. package/src/server/operations/ops/query.ts +281 -68
  311. package/src/server/operations/ops/snapshots.ts +73 -1
  312. package/src/server/operations/ops/validate.ts +5 -1
  313. package/src/server/operations/ops/viewport.ts +97 -3
  314. package/src/server/operations/registry.ts +74 -25
  315. package/src/server/placement.ts +29 -0
  316. package/src/server/scope-fence.ts +78 -6
  317. package/src/server/server.ts +115 -16
  318. package/src/server/spatial-analysis.ts +48 -23
  319. package/src/server/tour-control.ts +82 -0
  320. package/src/server/wiki-links.ts +137 -0
  321. package/src/server/workbench-auth.ts +11 -0
  322. package/src/shared/agent-context.ts +3 -0
  323. package/src/shared/agent-presence.ts +15 -15
  324. package/src/shared/auto-arrange.ts +2 -0
  325. package/src/shared/boards.ts +4 -4
  326. package/src/shared/knowledge-lifecycle.ts +22 -0
  327. package/src/shared/knowledge-presets.ts +80 -0
  328. package/src/shared/lifecycle-status.ts +26 -0
  329. package/src/shared/memory-graph.ts +74 -0
  330. package/src/shared/relations.ts +98 -0
  331. package/src/shared/semantic-attention.ts +15 -1
  332. package/src/shared/tour.ts +23 -5
  333. package/src/shared/workspace-settings.ts +13 -0
  334. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  335. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  336. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  337. package/dist/types/server/agent-context.d.ts +0 -22
  338. package/src/client/canvas/ContextPinBar.tsx +0 -25
package/docs/http-api.md CHANGED
@@ -3,6 +3,145 @@
3
3
  REST endpoints for all canvas operations + an SSE event stream. Works from
4
4
  any language. Default base URL: `http://localhost:4313`.
5
5
 
6
+ ## Knowledge lifecycle data (plan 017 slice 1)
7
+
8
+ The slice-1 data contract now also drives the workbench, due engine and qualified agent context.
9
+ Categories remain explicit; nothing classifies cards automatically.
10
+ `Lifecycle` is `{ decayType: "reality"|"decision"|"dependency"|"relevance",
11
+ rate: "fast"|"medium"|"slow"|"durable", triggers: string[], revalidationMethod: string, cadenceDays?: number }`.
12
+ Strings in triggers and the method must be nonempty after trimming. `cadenceDays` is an integer,
13
+ 1–36500, required for Medium and optional for other rates. Category, folder and card policy writes
14
+ refuse Medium without a cadence rather than saving a policy with no expiry. Fast checks at use in a
15
+ new session, slow on resumed work, durable has no passive expiry. The latest attached session
16
+ is the boundary, falling back to the active board's last-open time, then process start.
17
+ A category is the same fields plus stable `id`, editable `name` and `glyph` (nonempty strings).
18
+
19
+ Newly authored cards start fresh without a review record. Medium's first deadline is
20
+ creation plus `cadenceDays`; Fast/Slow first require review in a later use/resumed
21
+ session. Evidence validation stays null until an actual successful review. Concrete
22
+ source changes and contradictions can still require review immediately. Edits, reads,
23
+ undo/redo and snapshot restore never restart the initial freshness clock.
24
+ Creation accepts `data.contentOrigin: "authored"|"imported"` (immutable afterward).
25
+ Use `"imported"` for historical text pasted or extracted into a new Markdown card;
26
+ attachments and externally sourced provenance are also treated as imports. Imports
27
+ and pre-existing undated content do not acquire a synthetic freshness baseline.
28
+ Groups and board portals skip inherited factual decay unless given an explicit
29
+ card lifecycle override; explicit dependency/source warnings remain visible.
30
+
31
+ | Route | Input / result |
32
+ |---|---|
33
+ | `GET /api/canvas/knowledge` | `{ok, categories, folders, relationPresets, cards, reviewCards, resolvedFolders}`; `cards` uses JSON `[boardId,cardId]` keys |
34
+ | `POST /api/canvas/knowledge/category` | `{category}` creates or replaces by id; returns `{ok, category}` |
35
+ | `DELETE /api/canvas/knowledge/category/:id` | Deletes an unused category; returns `{ok:true, id}`. Unknown id: 404; blank id: invalid input (400); omitted path id: no matching route (404). Explicit folder assignments, including empty folders, refuse deletion (409) and name the folders to reassign or clear first. Never clears folders or inherited policies. |
36
+ | `POST /api/canvas/knowledge/folder` | `{path, categoryId: string|null, lifecycleOverride: Lifecycle|null}` replaces folder properties; returns `{ok, folder}` |
37
+ | `POST /api/canvas/knowledge/relation-preset` | `{preset: {id, name, vocabulary: [{kind, ink, directed}]}}` creates or replaces a shared template; returns `{ok, preset}`. Duplicate names: 409. |
38
+ | `DELETE /api/canvas/knowledge/relation-preset/:id` | Deletes the shared template; returns `{ok:true, id}`. Unknown id: 404. Existing board vocabularies and connections remain unchanged. |
39
+ | `GET /api/canvas/node/:id/lifecycle?board=<board-id>` | Effective category/lifecycle and history, without opening that board |
40
+ | `POST /api/canvas/node/:id/lifecycle` | `{lifecycleOverride: Lifecycle|null, expectedBoardId?}`; returns `{ok:true}` |
41
+ | `POST /api/canvas/node/:id/review` | `{outcome, evidence: string, applied: boolean, expectedBoardId?, expectedContentRevision?, expectedSources?}`; returns `{ok, record}` |
42
+ | `POST /api/canvas/node/:id/lifecycle/restore` | `{evidence: string, expectedBoardId?}`; returns `{ok, record}` |
43
+
44
+ **Additive local contract, 2026-10-14 (validation ongoing):** `knowledge.list.reviewCards`
45
+ is an array of `{boardId: string, cardId: string, boardTitle: string, folder: string|null,
46
+ title: string}`. Titles and folder placement come from the authoritative board/card observation,
47
+ not cached Home previews. Each nonretired card with `due === true` or nonempty `sources` appears
48
+ once, including a card with both. Join to `cards[JSON.stringify([boardId, cardId])]` for reasons,
49
+ source warnings and `lastValidatedAt`; missing validation is not a synthetic last-checked date.
50
+ Unknown policy alone does not qualify, but explicit content or source warnings still do.
51
+ The read covers the library without opening boards or writing validation. Consumers may filter
52
+ to a canonical folder and its descendants; null folder is unfiled and included in whole-library
53
+ scope. Opening a result is navigation, not a review record. The same additive result is returned
54
+ by SDK `knowledge({action:"list"})`.
55
+
56
+ Folder paths use the board-folder canonicalization (trimmed segments, at most eight levels).
57
+ Null category inherits; null override clears that local override. An explicit child category
58
+ starts its own defaults, discarding ancestor lifecycle overrides. A card's full override changes
59
+ only lifecycle, never category. Unfiled/unclassified stays unknown. No defaults are inferred.
60
+ The effective read returns `boardId`, `nodeId`, `contentRevision`, `category`, `categorySourceFolder`, `lifecycle`,
61
+ `lifecycleSourceFolder`, `lifecycleSource` (`card`/`folder`/`unknown`), `records`, `retired`,
62
+ `lastValidation` (record or null), and `qualification`. Source folders are canonical paths; null means no folder source.
63
+ Qualification carries `retired`, `unknown`, `due`, `reasons`, `changedSinceRead`, `lastReadAt`,
64
+ `contentChangedAt`, `lastValidatedAt`, `contentRevision`, `relevance`, source warnings in `sources`,
65
+ and inspected source revision tokens in `observations`. Direct node reads and search results also
66
+ carry qualification. Reads on an inactive board do not open it.
67
+
68
+ Outcomes are `retain`, `revise`, `externalize`, `retire`, `unresolved`. Only an **applied**
69
+ retain/revise/externalize with non-whitespace evidence renews validation. Reviews do not perform
70
+ content edits or externalization for you: `applied` records whether the disposition was actually
71
+ carried out. Validating outcomes require `expectedContentRevision` from the actual content checked;
72
+ missing is 400, a stale version is 409. Never copy a newer metadata revision onto evidence for an older
73
+ body. For cards with dependencies, also pass `expectedSources` from the checked lifecycle's
74
+ `qualification.observations`. Changed, added, removed or unavailable sources refuse validation
75
+ with 409; a later read is not permission to apply older evidence. The reviewed content is saved successfully before its record is appended; save failure adds
76
+ no review. Applied retire persists until explicit restore, even after later successful reviews.
77
+ Restore is audited with outcome `restore`, applied true, and never validates. Rows retain server
78
+ id, board+node identity, evidence, application, actor `who`, server timestamp `when`, and reviewed
79
+ `contentRevision` (null for legacy unversioned records, never backfilled as a current validation);
80
+ append order resolves retirement even when timestamps coincide. Reload and board snapshots
81
+ never erase this history. Restoring content is not restoring retirement.
82
+
83
+ Workspace writes work on Home without opening/switching boards; agent workspace writes are
84
+ refused under a scope fence. Moving or unfiling a board changes inherited lifecycle for all its
85
+ cards, so agent folder changes are also refused under a fence; other board metadata is unaffected.
86
+ This checks both the active board's applicable fence and the target board's stored global fence
87
+ or the writer's assigned territory, even when another board or Home is open. It never opens the target.
88
+ Category and folder-policy edits also check stored fences on every board whose effective
89
+ inheritance would change. Unrelated folders and policies hidden by a child category remain editable.
90
+ Card writes target the active board and use the existing expected
91
+ board, scope and human edit-lock guards. Writes emit live knowledge/lifecycle notifications;
92
+ metadata-only edits do not advance content revision or lastEditedBy. Read/delivery clocks,
93
+ content revisions, evidence validation and task relevance remain separate; reading, editing,
94
+ refreshing and restoration never validate. No synthetic content-change date is added to legacy cards.
95
+ Generic node PATCH/SDK `updateNode` rejects `data.lifecycleOverride`; use the dedicated lifecycle
96
+ setter (including to clear an override) so metadata cannot replay the body or resolve wiki links.
97
+
98
+ Shared relation presets live in Home alongside Categories, not in per-board settings.
99
+ Research, Dependencies, Decision and Flow are initial editable templates. Applying a saved
100
+ preset explicitly copies its vocabulary through the existing board update contract; editing
101
+ or deleting a template never silently rewrites boards. The canvas dropdown filters only the
102
+ relation kinds actually present on that board, plus All relations, without a Custom vocabulary state.
103
+
104
+ SDK (Bun): `canvas.knowledge({action:"list"|"category-set"|"category-remove"|"folder-set"|"relation-preset-set"|"relation-preset-remove", ...payload})`
105
+ (`category-remove` takes `id`)
106
+ and `canvas.lifecycle({action:"get"|"set"|"review"|"restore", id, ...payload})` use these
107
+ same inputs/results. `get` accepts `board`; writes accept `expectedBoardId`, and validating
108
+ reviews require `expectedContentRevision` and the matching `expectedSources` when dependencies exist.
109
+
110
+ ### Relations, source observations and active context
111
+
112
+ Edge add/update accept open `kind` (1–80 characters) and `reason` (up to 4000), independently
113
+ of the existing `type`, label, style and animation. Update with null clears kind/reason.
114
+ Kinds/reasons survive undo, snapshots and restart and advance the links clock, not content.
115
+ Board update accepts `relationVocabulary: [{kind, ink, directed}]` (up to 64 unique kinds,
116
+ including an explicit empty list). `ink` is supports/contradicts/cites/derived/informs/related;
117
+ pin, agent and warning colours are not relation inks. Unknown kinds remain legal.
118
+
119
+ For lifecycle, `derived-from`, `contradicts` and `supersedes` point **source → dependent**;
120
+ `depends-on` points **dependent → prerequisite**. `cites`, `supports`, `related`, wiki links
121
+ and portals are navigational unless an explicit dependency or provenance source is also recorded.
122
+ The server captures `sourceRevision` when a dependency is authored or retyped. A reason or
123
+ style edit never resets that baseline. Source changes propagate through dependencies, including
124
+ cycles, but not unrelated references. A new contradiction or superseding decision needs review.
125
+ Folder defaults stop at links. Portal source warnings stay separate from the portal's own lifecycle.
126
+
127
+ `data.provenance` accepts `sources: [{boardId, cardId, contentRevision?, observedAt?,
128
+ availability?: "live"|"cached"|"unavailable"}]` and an inert `recipe: {tool, input?, prompt}`.
129
+ Explicit provenance requires `sourceKind`, nonempty `sourceUri` and `refreshStrategy`;
130
+ invalid or incomplete input returns 400 with field errors rather than being discarded.
131
+ Omit provenance to use the normal inference for file, image and other sourced nodes.
132
+ Keep `observedAt`/source revision distinct from retrieval `syncedAt`/`fetchedAt`; retrieval-only
133
+ updates do not advance content or validation. Cached/unavailable warnings never claim a fresh check.
134
+ The human's **Ask agent to refresh this board** sends steering; Canvas executes no recipe tool.
135
+
136
+ Retired cards remain in raw layout, direct reads and search, marked retired. They leave briefs,
137
+ pinned context, nearby active context and README summaries. Restore is audited, never validation.
138
+ Brief entries carry qualification and board overviews carry `readmeQualification` separately.
139
+ Every brief also carries a lifecycle snapshot independent of its content cursor. With
140
+ `lifecycle.complete: true`, its retired/unknown/review lists replace cached qualification; keys
141
+ are JSON `[boardId,cardId]`. If the snapshot exceeds a quarter of the budget, `complete: false`
142
+ explicitly requires rechecking cached cards via lifecycle-get before reuse. Empty lists in an
143
+ incomplete snapshot do not mean healthy or active. Delivered entries still carry qualification.
144
+
6
145
  `GET /health` includes `contracts: ["pmx-embedded-workbench-v4"]` when the daemon
7
146
  enforces `expectedBoardId` and `expectedContentRevision` preconditions, bulk-write
8
147
  fences/locks using validated geometry and affected automatic parent frames only,
@@ -39,11 +178,25 @@ content auto-height or indirect parent-group reflow after editing a child.
39
178
 
40
179
  ## Board tours
41
180
 
42
- - `GET /api/canvas/tour` → `{ tour: { stops: [...] }, derived: boolean }`.
181
+ - `GET /api/canvas/tour` → `{ tour: { stops: [...] }, derived: boolean, position: number | null }`
182
+ (`position` is the stop being presented, or `null`).
43
183
  - `POST /api/canvas/tour` with `{ "tour": { "stops": [...] } }` persists the tour;
44
184
  `{ "tour": null }` resets to derived group reading order. Returns `{ "ok": true }`.
45
185
  - `GET /api/canvas/state` includes optional `tour`. Tour writes broadcast a layout
46
186
  update and are included in snapshots/undo. They are board-wide writes under a scope fence.
187
+ - `POST /api/canvas/tour/go` with `{ "stop": 2 }` or `{ "step": "next" | "previous" }`
188
+ (plus optional `"present": false`) moves the shared tour cursor →
189
+ `{ ok, index, total, stop }`; 400 when the stop does not exist or the board has no stops.
190
+ Broadcasts `canvas-tour-step`; idle workbenches start presenting unless `present` is false.
191
+ - `POST /api/canvas/tour/exit` leaves presentation in every viewer (`canvas-tour-exit`).
192
+ - `POST /api/canvas/camera/move` with exactly one of `nodeId`, `rect: { x, y, width, height }`
193
+ or `viewport: { x, y, scale }`, plus optional `duration`, `easing`, `padding`, `pullback`
194
+ → `{ ok, stop, viewport }`. Broadcasts `canvas-camera-move`; browsers ease to the target in
195
+ their real canvas area. `viewport` is the server's estimate from the last reported window
196
+ size and is stored as the board viewport. 404 for an unknown node.
197
+
198
+ Tour steps and camera moves are navigation: no undo entry, no agent-presence write, no
199
+ scope-fence check.
47
200
 
48
201
  The [tour model and recording walkthrough](cli.md#tour-and-presentation) describes
49
202
  targets, duration, easing, padding, and pullback. Open `/workbench?present=1` to hide
@@ -62,16 +215,71 @@ trusted workbench deletes a board — deletion requires both `x-pmx-workbench: 1
62
215
  per-boot `x-pmx-workbench-token` embedded in the served `/workbench` HTML;
63
216
  the marker alone is not authority. Public HTTP integrations must not delete
64
217
  boards. In-process SDK embedding hosts retain deletion authority.
65
- Every change emits a
66
- `boards-changed` SSE frame with `{ activeBoardId, boards }`.
67
-
68
- `category` is a folder path, for example `Engineering/Canvas/Decisions`.
218
+ Every change emits a `boards-changed` SSE frame with
219
+ `{ activeBoardId, boards, catalogEpoch, catalogRevision, reloadRevision, boardGeneration }`.
220
+ Board-list and board-mutation responses carry the same catalog fields. Within a server
221
+ process, `catalogRevision` increases for every catalog snapshot; reject older or duplicate
222
+ snapshots before changing board identity or camera. Only a current `connected` handshake
223
+ authorizes a replacement `catalogEpoch`. `reloadRevision` is a sticky library-restore marker,
224
+ retained by subsequent GETs so an overtaking response cannot hide a restore.
225
+ `boardGeneration` identifies the loaded board incarnation, including revisits and restores.
226
+
227
+ `folder` is a folder path, for example `Engineering/Canvas/Decisions`.
69
228
  Home renders nested, collapsible folders and searches board names and paths.
70
229
  Paths support up to eight levels, each with 1–60 characters; surrounding spaces
71
230
  are trimmed. Empty segments and `.`/`..` are rejected. `null` or `""` unfiles a
72
231
  board. Folders are derived from boards, so empty folders do not persist. Moving
73
232
  a board changes its path, not its ID or content.
74
233
 
234
+ Home offers Folders, Map, and Graph. Folders are neutral organization, independent
235
+ of links. `PATCH /api/canvas/boards/<board-id>` accepts
236
+ `mapPosition: { x, y }` (finite numbers) to persist a dragged Map position in
237
+ SQLite, or `mapPosition: null` to return to the generated folder layout.
238
+ Creating a copy records `previousBoardId` as its source board ID; it does not
239
+ open either board.
240
+ Board-list `links` expose qualified `boardId`/`cardId` targets; native card
241
+ connections include `cardTitle` (null if missing), so footers need not display raw IDs.
242
+
243
+ ### Markdown previews and saved links
244
+
245
+ `POST /api/render` accepts `{markdown, nodeId?, expectedBoardId?}` and returns `{html}`.
246
+ For a card-aware preview, send its captured board ID with `nodeId`; a different active
247
+ board returns 409 without HTML, so a delayed request cannot borrow another card's bindings.
248
+ Workbench previews discard responses that no longer belong to the displayed board or draft.
249
+ Plain Markdown without `nodeId` renders independently of a card.
250
+
251
+ Saved text replays its durable wiki bindings, including unresolved targets. Changed drafts
252
+ retain existing bindings and may resolve newly authored references by title; previews do not
253
+ persist those bindings. An explicit save is required to bind a saved unresolved reference.
254
+
255
+ ### Connected memory projection
256
+
257
+ `GET /api/canvas/memory-graph?focus=<focus>&depth=2&projection=full` returns the
258
+ connected-memory projection used by Home and discovery in the context brief. Omit
259
+ `focus` for the library; otherwise use a board ID, a JSON qualified node key
260
+ `["board-id","card-id"]` (or `["board-id",null]`), or `pins`. URL-encode focus.
261
+ `depth` is an integer from 0–4, default 2: undirected hops including board/card
262
+ membership. Explicit 0 is valid; empty depth values are rejected. An unknown focus returns 404.
263
+ `projection` is `full` (default) or `boards` (compact board-only summaries).
264
+ Home Map uses `boards` and loads depth-2 details when you select a board.
265
+ Map/Graph coalesce live catalog, layout, pin and reconnect updates. Failed reads preserve
266
+ the last graph with **Retry**. Neither a refresh nor an agent board switch resets the
267
+ human's library camera or selection; explicit Open board/card leaves the library.
268
+
269
+ The response has `nodes`, `edges`, `unresolvedLinks: [{ source, ref }]`, `focus`,
270
+ and `depth`. Nodes have board-qualified `key`, `boardId`, optional `cardId`,
271
+ `kind` (`board` or `card`), `title`, `summary` (at most 280 characters), `folder`,
272
+ `inContext`, and `summaryOnly: true`. Edges have qualified `from`/`to`,
273
+ `source`/`target`, `missing`, and kind `member`, `portal`, `wiki`, `card`, or
274
+ `previous`. These projection kinds are separate from a card edge's rich `relationKind` and `reason`.
275
+ Graph reads mark delivered links seen on each owning board without switching
276
+ boards; they do not count as full content reads. Card nodes carry `linksRevision`
277
+ captured with the projection, so a later write is never acknowledged by an earlier read. In focused views,
278
+ `linksComplete: false` means some incident relations fall outside the requested
279
+ depth; that card's links are not acknowledged as seen. SDK: `getMemoryGraph({ focus,
280
+ depth, projection })`; MCP: `canvas_query { action: "graph", focus, depth, projection }`.
281
+ Cross-board briefs give incoming and outgoing connections readable board/card titles.
282
+
75
283
  ```bash
76
284
  # List boards (most recently opened first) and which is open (null = Home)
77
285
  curl http://localhost:4313/api/canvas/boards
@@ -79,23 +287,31 @@ curl http://localhost:4313/api/canvas/boards
79
287
  # Read one board
80
288
  curl http://localhost:4313/api/canvas/boards/<board-id>
81
289
 
82
- # Create a board (optionally in a category) — it is NOT opened
290
+ # Create a board (optionally in a folder) — it is NOT opened
83
291
  curl -X POST http://localhost:4313/api/canvas/boards \
84
- -H "Content-Type: application/json" -d '{"name":"Q4 planning","category":"Planning"}'
292
+ -H "Content-Type: application/json" -d '{"name":"Q4 planning","folder":"Planning"}'
85
293
 
86
294
  # Preview/create an inactive copy from reusable cards (preview false creates)
87
295
  curl -X POST http://localhost:4313/api/canvas/boards/from \
88
296
  -H "Content-Type: application/json" \
89
297
  -d '{"sourceBoardId":"board-source","name":"Follow-up","nodeIds":["node-1"],"includeReadme":true,"includeStructure":true,"preview":true}'
90
298
 
91
- # Rename and/or file under a category on Home ("category": "" removes it)
299
+ # Rename and/or file under a folder on Home ("folder": "" removes it)
92
300
  curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
93
- -H "Content-Type: application/json" -d '{"name":"Q4 planning (final)","category":"Planning"}'
301
+ -H "Content-Type: application/json" -d '{"name":"Q4 planning (final)","folder":"Planning"}'
94
302
 
95
303
  # Designate a Markdown card on this board as its README (null clears it)
96
304
  curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
97
305
  -H "Content-Type: application/json" -d '{"readmeNodeId":"<markdown-node-id>"}'
98
306
 
307
+ # Pin a board into the agent's working set (its README and pinned cards reach every
308
+ # brief, whichever board is open; read-only context, never a write target), or unpin it.
309
+ # A repeat pin keeps its first attribution. Every board carries `pin` ({pinnedBy,
310
+ # pinnedAt, reason} or null).
311
+ curl -X POST http://localhost:4313/api/canvas/boards/<board-id>/pin \
312
+ -H "Content-Type: application/json" -d '{"reason":"Q4 rests on it"}'
313
+ curl -X POST http://localhost:4313/api/canvas/boards/<board-id>/unpin
314
+
99
315
  # Open a board, or Home with {"id":null} — also available to agents
100
316
  curl -X POST http://localhost:4313/api/canvas/boards/open \
101
317
  -H "Content-Type: application/json" -d '{"id":"<board-id>"}'
@@ -222,6 +438,9 @@ curl http://localhost:4313/api/canvas/state
222
438
  # Search the library (omit scope for the active board)
223
439
  curl "http://localhost:4313/api/canvas/search?q=auth&limit=10&scope=library"
224
440
 
441
+ # Restrict to a board inside a folder subtree without opening it
442
+ curl "http://localhost:4313/api/canvas/search?q=auth&boardId=<id>&folder=Research"
443
+
225
444
  # Validate the current layout. Reports collisions, groupFrameOverlaps (a group
226
445
  # frame covering a node that is not one of its members, nested members
227
446
  # included — e.g. after a group auto-fits around enlarged children),
@@ -254,6 +473,52 @@ curl -X POST http://localhost:4313/api/canvas/schema/validate \
254
473
 
255
474
  ## Nodes
256
475
 
476
+ Search accepts intersecting `boardId` (stable ID) and `folder` filters. A folder
477
+ matches itself and slash-boundary descendants; `folder=` selects unfiled boards.
478
+ Filters default an omitted scope to `library`. Explicit `scope=active` stays
479
+ constrained to the active board, even when another board is selected. Unknown or
480
+ nonmatching selections return no results. Filtering precedes ranking and limits;
481
+ term-OR discovery and whole-term ranking remain unchanged. Search never navigates.
482
+
483
+ `POST /api/canvas/node` accepts `destinationBoardId` to create on a stored board
484
+ without opening it or changing the human's active board. `expectedBoardId` remains
485
+ an **active-board** precondition, not a destination selector; stale values return 409.
486
+ Unknown destinations return 404. This is creation-only, not background CRUD.
487
+
488
+ Optional `requestId` identifies a logical creation within the workspace database.
489
+ Use the same id, destination and creation input when retrying; a new intentional
490
+ creation needs a new id even for identical content. Node and receipt commit in one
491
+ transaction. Receipts survive restart, deletion, undo and snapshot restore; replay
492
+ returns the original creation payload without recreating or updating anything.
493
+ Reusing an id with different input/destination returns 409. Object key order and
494
+ transport-only `full`, `verbose`, `agentId`, `expectedBoardId` do not affect identity.
495
+ Receipts survive individual node removal but are deleted with their owning board.
496
+
497
+ Either field opts into static creation only: `markdown`, `status`, `context`,
498
+ `ledger`, `trace`, `diff`, `mermaid`, and plain `html`. Runtime-backed types, generated
499
+ HTML primitives, ghost intents and membership mutations are explicitly refused (400),
500
+ never redirected to the active board. Persistent workspace storage is required.
501
+ New creations are conservatively refused (403) if either the active or destination
502
+ board has a scope fence or per-agent territory. Inactive creation creates no ghost or
503
+ undo entry on the active board and starts no runtime watcher. Only genuine changes
504
+ to active cards' incoming link clocks can emit an active layout; the destination's
505
+ layout is never substituted. Library metadata refreshes through `boards-changed`.
506
+ Auto-placement uses the destination's nodes. Batch entries accept the same fields;
507
+ batches still prefix-commit and assigned references work on receipts as on new nodes.
508
+
509
+ `POST /api/canvas/group` and `POST /api/canvas/edge` also accept
510
+ `destinationBoardId`, `requestId` and `expectedBoardId` under the same commit,
511
+ retry, scope-fence and no-navigation contract. Groups resolve all `childIds` on
512
+ the destination, preserve their positions by default, and support explicit frames
513
+ and `childLayout`. Membership changes respect human edit locks, including moved
514
+ descendants and affected parent frames. Edges resolve `from`/`fromSearch` and
515
+ `to`/`toSearch` on the destination; `toBoardId` independently selects a foreign
516
+ target board, not the owner of the edge. Both endpoints' relation clocks update
517
+ without changing their content/freshness. A retry returns its original group or
518
+ edge receipt, preserving later edits and not resurrecting removals. A request ID
519
+ cannot be reused for a different creation operation. Inactive group membership
520
+ updates/removals and other background CRUD remain unsupported.
521
+
257
522
  ```bash
258
523
  # Add a node
259
524
  curl -X POST http://localhost:4313/api/canvas/node \
@@ -294,6 +559,26 @@ against the node's top-level `contentRevision`; stale content returns `409`
294
559
  without changing the node. Conditional edits cannot request webpage refresh.
295
560
  The private MCP App uses these guards to reject stale board actions and note drafts.
296
561
 
562
+ Native Markdown Save captures the currently observed revision for each attempt. Newer content
563
+ arriving during rendering or network delivery refuses that attempt and keeps the draft; a later
564
+ deliberate human Save may replace already-observed work. Queued saves advance their precondition
565
+ only through successful acknowledgements of their own preceding saves, never through an external
566
+ revision or an automatic retry after conflict. Acknowledgements from before a restart, restore,
567
+ or board replacement cannot update canonical content, clear an unsaved draft or advance queued
568
+ preconditions, even if the replacement reused the same card ID with a lower revision.
569
+ Board switches detach the old cards until the
570
+ replacement layout arrives, keeping unsaved drafts under their original board/card. The replacement
571
+ `GET /api/canvas/state` supplies the captured `expectedBoardId` query (`null` for Home), so another
572
+ tab switching boards before the read returns causes `409`, not a layout attributed to the wrong board.
573
+
574
+ For file-backed Markdown, `PATCH /api/canvas/node/:id` accepts `persistFile: true` with top-level
575
+ `content`, `expectedBoardId`, and `expectedContentRevision`. It checks both preconditions and
576
+ validates the patch before synchronously writing the existing workspace `.md` file and card,
577
+ normalizing line endings to LF. In this mode, `data` accepts only the `rendered` preview;
578
+ change other metadata separately, never alongside the disk save.
579
+ A stale/refused save changes neither disk nor card. Ordinary PATCH remains data-only, and the
580
+ hosted MCP App still refuses file-linked edits. This is not a crash-recovery disk/SQLite transaction.
581
+
297
582
  Replacing a JSON-render node's `spec` preserves its current title; include `title`
298
583
  to rename it explicitly. A new authored spec still resets the viewer's local drafts.
299
584
 
@@ -336,6 +621,25 @@ workspace root returns `403`, and an unknown node id returns `404`.
336
621
 
337
622
  ## Edges
338
623
 
624
+ Edge creation accepts optional `toBoardId` to target a card on another board;
625
+ `from` stays on the open source board. For example:
626
+ `{ "from": "source-card", "to": "remote-card", "toBoardId": "target-board", "type": "references" }`.
627
+ The source footer and human More-menu card picker expose remote connections.
628
+ Auto-arrange stays local; static export omits remote lines but preserves explicit
629
+ external connection evidence.
630
+
631
+ Markdown saves bind `[[Board]]`, `[[Board#Card]]`, and `|alias` variants to durable
632
+ IDs in server-owned `data.wikiLinks`. IDs take precedence; names/titles must be
633
+ unique. Existing bindings survive renames, while ambiguous or missing targets
634
+ stay explicit and never guess or silently retarget. Rendering uses saved bindings:
635
+ an unresolved link stays unresolved until an explicit save; Undo and restore preserve bindings.
636
+ Rich-text editing preserves wiki references and aliases; editing a visible label changes
637
+ the alias, not the bound target. Code spans/fences are ignored.
638
+ The human Create dialog for an unresolved board-only link creates exactly its
639
+ authored name and re-saves the source using its captured board/revision; a stale
640
+ source is refused. Rich relationship kinds/reasons and source-driven lifecycle qualification
641
+ use the [shared contract above](#relations-source-observations-and-active-context).
642
+
339
643
  ```bash
340
644
  # Add an edge
341
645
  curl -X POST http://localhost:4313/api/canvas/edge \
@@ -379,6 +683,11 @@ inspection or screenshots when the drawn shape matters.
379
683
 
380
684
  ## Viewport
381
685
 
686
+ Workbench camera writes include an optional `clientId` (1–128 characters), echoed
687
+ in `canvas-viewport-update`. The originating tab ignores that echo so an older
688
+ Fit or zoom cannot cancel a newer local gesture. Other tabs and agent camera
689
+ commands still update the view. The identifier is echo metadata, not authorization.
690
+
382
691
  ```bash
383
692
  # Set the camera transform (screen = world * scale + {x,y})
384
693
  curl -X POST http://localhost:4313/api/canvas/viewport \
@@ -437,16 +746,30 @@ curl http://localhost:4313/api/canvas/pinned-context
437
746
  curl 'http://localhost:4313/api/canvas/context?consumer=codex&budget=16000'
438
747
  ```
439
748
 
749
+ At most 20 cards can be pinned at once. `remove` processes every supplied ID, even when
750
+ a merged receipt has recorded more than 20 IDs across sessions; unrelated pins stay.
751
+
440
752
  `consumer` is optional. Supply a stable unique value only for a durable,
441
753
  board-scoped cursor; without `consumer` or `since`, each request starts from the
442
- beginning. An explicit `since` never advances a cursor. The brief prioritizes
443
- pins, then each pin's neighbours (reason `near`: up to five unpinned cards within
444
- 600 px, nearest first, as title + a short summary with `near: { pinNodeId,
445
- pinTitle }`; pin a card to send its full content), then relevant current-board
446
- changes and asks, then compact linked or
447
- same-folder board context. Every item carries source board/card IDs. If card text
448
- is clipped, use those IDs with the board/node read route rather than guessing
449
- from the excerpt. Reading inactive boards does not open them.
754
+ beginning. An explicit `since` never advances a cursor. The brief is a map,
755
+ not a dump: an `overview` entry for the open board first, then pins, then each
756
+ pin's neighbours (reason `near`: up to five unpinned cards within a 600 canvas-pixel
757
+ edge-to-edge gap, nearest first, with `near: { pinNodeId, pinTitle }`), then relevant
758
+ current-board changes and asks, then pinned boards (reason `pinned-board`: the board and its pinned
759
+ cards), then compact linked or same-folder board context. Cards arrive as title,
760
+ a short summary (`summaryOnly: true`) and their relations; a pin puts a card
761
+ first, it does not send its full text. On a later read a changed card lists what changed in
762
+ `changes` (`text`, `links` or both) and marks relations drawn or changed since the cursor,
763
+ e.g. `(new, by a person)`. Every item carries source board/card IDs:
764
+ use them with the board/node read route to pull a card in full when you need it,
765
+ rather than guessing from the summary. Reading inactive boards does not open them.
766
+ Entries with capped or omitted relations declare `linksComplete: false` and do
767
+ not acknowledge that card's complete links revision as seen. Near-pin, human,
768
+ linked README and pinned-board card summaries carry no card relations. Complete
769
+ relation entries carry their captured `linksRevision`; delivery is qualified by
770
+ `sourceBoardId`, never by a bare card ID across boards. Single-card and layout
771
+ reads record delivered content but do not acknowledge incident relations. Read
772
+ a complete graph or brief relation entry for relations; pull cards separately for full content.
450
773
 
451
774
  ## AX context and focus
452
775
 
@@ -454,6 +777,10 @@ AX context is the host-agnostic agent-experience layer. It combines existing
454
777
  context pins with a persisted focus node set that adapters can inject into
455
778
  their native prompt/context hooks.
456
779
 
780
+ AX node excerpts declare `linksComplete: false`. AX reads, including annotation
781
+ anchors, never acknowledge incident relations as seen; use a complete graph or
782
+ brief relation entry for that acknowledgement.
783
+
457
784
  The context response includes `boardId` (`null` on Home), captured with its content
458
785
  and pins. Adapters should send that ID when recording a proxied context read, rather
459
786
  than looking up whichever board is active after delivery.
@@ -516,7 +843,15 @@ curl "http://localhost:4313/api/canvas/ax/context-reads?limit=50"
516
843
  # read that delivered its content: when, who, and the content revision it had.
517
844
  # A node whose current contentRevision is newer was changed since it was read.
518
845
  # pins maps each pinned node with a known pinner to { pinnedBy, pinnedAt, reason }.
846
+ # board is the latest agent read on that board of any kind ({ lastReadAt, lastReadBy } or
847
+ # null): a pinned board whose map reached an agent reads as read even before any card is pulled.
848
+ # A brief's card summaries mark nothing read; only a full pull of a card does.
849
+ # links maps each node to the newest linksRevision any read carried (summaries count, since a
850
+ # brief summary carries the card's relations). An edge whose revision is newer than both
851
+ # ends' links value has not been seen by an agent; the workbench marks it on the link's pill.
519
852
  curl "http://localhost:4313/api/canvas/ax/context-status?board=<board-id>"
853
+ # boards=<id>,<id> also returns boards: { <id>: { lastReadAt, lastReadBy } | null } for each
854
+ # listed board, so the context chip reads every pinned board's status in one request.
520
855
 
521
856
  # Context reads — a proxy (an MCP server attached to this daemon, a host adapter)
522
857
  # records the read its agent actually made. Its own fetches send
@@ -525,7 +860,11 @@ curl "http://localhost:4313/api/canvas/ax/context-status?board=<board-id>"
525
860
  # currently open board. MCP bytes measure the final formatted text response.
526
861
  # readNodeIds lists every serialized node in what the agent received (pinned or
527
862
  # not); the server keeps those on the board and stamps their revisions. Omitted,
528
- # it defaults to deliveredNodeIds.
863
+ # it defaults to deliveredNodeIds. seenNodeIds adds the nodes that arrived only as
864
+ # summaries, which marks their links seen; omitted, it defaults to readNodeIds.
865
+ # For a captured graph/brief, also pass seenLinkRevisions: {"node-1": 12} from
866
+ # the delivered linksRevision values, not revisions fetched after delivery.
867
+ # Only seenNodeIds with captured entries are acknowledged when this map is supplied.
529
868
  curl -X POST http://localhost:4313/api/canvas/ax/context-reads \
530
869
  -H "Content-Type: application/json" \
531
870
  -d '{"channel":"adapter","resource":"copilot:prompt-context","consumer":"copilot","pinnedNodeIds":["node-1"],"deliveredNodeIds":["node-1"],"bytes":2048}'
@@ -592,7 +931,7 @@ without a `session-end`.
592
931
  curl http://localhost:4313/api/canvas/ax/presence
593
932
  # → { ok, presences: [{ sessionId, source, agentId, label, phase, detail,
594
933
  # focusNodeId, cursor, attached, opCount, contextUsage, lastSeenAt }],
595
- # budget: { used, total }, sessionActive,
934
+ # sessionActive,
596
935
  # activity: [{ id, at, sessionId, label, op, summary, nodeId }] } # newest first, last 50
597
936
 
598
937
  # Attach a session and report a phase (idle | thinking | tooling | waiting-approval)
@@ -619,13 +958,13 @@ a transport writer folds into it), bounded to the last 50 and kept after a
619
958
  writer fades. Detaching (`attached: false`, `session-end`) removes the
620
959
  presence outright — an ended session never lingers as an external writer.
621
960
 
622
- **The context meter is an estimate unless the host says otherwise.** `budget`
623
- is a token estimate (chars ÷ 4) of the `pinned-context` payload against
624
- `PMX_CANVAS_CONTEXT_BUDGET_TOKENS` (default 32000) — what the human's pins
625
- would cost the agent, not the agent's live window; the top bar labels it
626
- **Pins**. A host that knows the agent's real usage reports it on the presence
627
- (`contextUsage: { used, total }` on `POST /api/canvas/ax/presence` /
628
- `set-presence`); the top bar then shows **Context** with those numbers. The
961
+ **The context summary shows a cost, not a budget.** The top bar's context
962
+ summary shows what the brief costs, as ≈ tokens (about four characters per
963
+ token). There is no share, meter or clipped state: a brief longer than one page
964
+ continues on the next read. A host that knows how many tokens its agent holds
965
+ reports it on the presence (`contextUsage: { used, total }` on
966
+ `POST /api/canvas/ax/presence` / `set-presence`); the context panel then also
967
+ names that count ("Copilot’s whole context holds 42.8k tokens"). The
629
968
  bundled Copilot extension reports it from the SDK's `session.usage_info`
630
969
  event (root agent, coalesced to one report per 500 ms); the legacy
631
970
  `context-usage` workbench event feeds the single attached session the same
@@ -663,21 +1002,27 @@ When that session ends (`attached: false`, `session-end`, or the idle expiry)
663
1002
  the stream carries one `agent-session-ended` frame:
664
1003
 
665
1004
  ```json
666
- { "label": "Copilot", "parentAgentId": null, "endedAt": "…", "endedBy": "agent",
1005
+ { "label": "Copilot", "writer": "copilot", "parentAgentId": null, "startedAt": "…", "endedAt": "…", "endedBy": "agent",
667
1006
  "unchanged": false, "counts": { "items": 4, "done": 3, "cancelled": 1, "rejected": 0, "held": 0 },
668
- "snapshot": { "id": "…", "name": "Before session · Copilot · 14:00" },
669
- "context": { "read": [{ "id": "…", "title": "Brief" }], "pinned": [{ "id": "…", "title": "Chart", "reason": "…" }],
670
- "created": [], "edited": [], "changedSinceRead": [{ "id": "…", "title": "Brief" }] } }
1007
+ "snapshot": { "id": "…", "name": "Before session · Copilot · 14:00", "boardId": "…" },
1008
+ "context": { "read": [{ "id": "…", "boardId": "…", "title": "Brief" }], "pinned": [{ "id": "…", "boardId": "…", "title": "Chart", "reason": "…" }],
1009
+ "created": [], "edited": [{ "id": "…", "boardId": "…", "title": "SMB", "change": "rewrote the second paragraph", "before": "…", "after": "…" }],
1010
+ "changedSinceRead": [{ "id": "…", "boardId": "…", "title": "Brief" }] } }
671
1011
  ```
672
1012
 
673
1013
  `context` is what the session did with the board's context: nodes whose content an
674
1014
  agent read since the session attached, pins the agent made (with their reason), nodes
675
- it created and edited (against the pre-session snapshot), and pinned nodes changed
1015
+ it created and edited (against the pre-session snapshot; each edit says what it did in
1016
+ `change` and keeps bounded `before`/`after` excerpts around the first difference), and pinned nodes changed
676
1017
  after their latest read. A session that only read or pinned is not `unchanged`.
1018
+ Every touch carries its `boardId` (null with no active board); receipt identities are
1019
+ board + node ID, so equal IDs on different boards are not merged.
677
1020
 
678
1021
  `items`/`done` count the work items on the board; cancelled items, rejected
679
1022
  gates, and held gates have separate counts. `snapshot` is null when the board was
680
1023
  empty at attach or unchanged at end (the redundant snapshot is removed).
1024
+ Its `boardId` identifies the owning board: a retained receipt offers to reopen it before
1025
+ comparing, including sessions that only added or removed cards. Snapshot access remains board-scoped.
681
1026
  The browser shows a receipt only for a changed top-level session: `unchanged: true`
682
1027
  and worker endings (`parentAgentId` set) never open, replace, or extend a popup —
683
1028
  the orchestrator's receipt covers the board since it attached. An empty
@@ -687,8 +1032,26 @@ There is at most one receipt. Qualifying endings that arrive while it is up merg
687
1032
  into it ("3 sessions ended", their labels, the union of their context); a merged
688
1033
  receipt has no *View diff* and points to History, which keeps each session's
689
1034
  snapshot. The receipt is kept until dismissed so restore/diff actions do not
690
- disappear on a timer. A single session's *View diff* is `GET /api/canvas/snapshots/<id>/diff` against that
691
- snapshot, and restoring the snapshot undoes the session. Adapters should end their session
1035
+ disappear on a timer. It also stays across direct board switches, with a button to
1036
+ reopen an edited card's board; Home dismisses it. Undo and Unpin are unavailable
1037
+ on another board, and the lens and edit marks only apply to the active board's touches.
1038
+ The browser shows one stacked Before/After at a time, marking removed and added words;
1039
+ its Undo restores the whole card, not just the excerpt, and leaves cards whose latest edit is human alone.
1040
+ The caption distinguishes clipped excerpts ("undoes the whole card"), an already restored card
1041
+ ("the card shows Before again"), and a protected human edit ("undo would lose your edit").
1042
+ A single session's *View diff* is `GET /api/canvas/snapshots/<id>/diff` against that
1043
+ snapshot, and restoring the snapshot undoes the session. *Undo* on one edited card is
1044
+ `POST /api/canvas/snapshots/<id>/restore-node` with `{ "nodeId": "…", "preserveHumanEdits": true }`:
1045
+ the server checks the latest editor at the write boundary, so an unseen human edit is safe even
1046
+ before polling delivers it. A skipped restore returns `{ "ok": true, "id": "…", "restored": false,
1047
+ "skipped": "human-edit" }`; a successful restore returns `restored: true`. Without
1048
+ `preserveHumanEdits`, explicit restore can replace human content. The same option is available on
1049
+ `canvas_snapshot { action: "restore-node", id, nodeId, preserveHumanEdits: true }` and
1050
+ `canvas.restoreSnapshotNode(id, nodeId, { preserveHumanEdits: true })`. That card's content goes
1051
+ back to the snapshot's, as one undoable edit; position, group, links and pins stay (file cards
1052
+ refuse, they follow their file; a card whose type changed since answers 409).
1053
+ The receipt's Unpin removes only its recorded pins and is safe to repeat; pins added later stay.
1054
+ Adapters should end their session
692
1055
  explicitly so the human gets the receipt promptly rather than after the idle
693
1056
  expiry. The browser's *Start agent session* button is this same endpoint
694
1057
  (`source: "browser"`, `attached: true`) — subsequent agent-less writes are
@@ -898,6 +1261,25 @@ curl -N http://localhost:4313/api/workbench/events
898
1261
  The browser, the CLI `watch` command, and the MCP resource notifications
899
1262
  all consume this stream. Auto-reconnect with exponential backoff.
900
1263
 
1264
+ The direct workbench's `connected` snapshot renews its per-process
1265
+ `workbenchToken` (also embedded in boot HTML) over both SSE and polling.
1266
+ A same-bundle daemon restart does not reload the page or discard an open draft.
1267
+ Registered writes and file saves marked as workbench calls with a supplied stale capability
1268
+ return 409 until reconnect renews it; they are never silently recorded as agent
1269
+ edits. A refused Markdown save retains its draft and enabled Save control for
1270
+ retry after reconnect. Drafts and complete save queues are qualified by board + card in the
1271
+ current tab, surviving close/reopen and board switches (not a page reload); superseded drafts
1272
+ cannot resume an older render and overwrite newer text. Do not log or forward this human capability: hosted MCP
1273
+ workbench polling strips it from tool results, including hydration metadata.
1274
+
1275
+ `canvas-layout-update` carries its owning `boardId` (null for Home), `catalogEpoch`
1276
+ and `boardGeneration`; accept its baseline only for the activation in the accepted catalog.
1277
+ `context-pins-changed` carries the owning `boardId`. A board transition is a new baseline,
1278
+ not a deletion or unpin. Same-board reconnects install a fresh baseline without closing
1279
+ the editor or discarding its draft and selection, whichever snapshot arrives first.
1280
+ Remote-edge endpoint changes also publish `boards-changed` so card titles and
1281
+ missing-target states update; ordinary geometry changes do not refresh the catalog.
1282
+
901
1283
  `agent-presence` frames carry the full presence snapshot (the same body as
902
1284
  `GET /api/canvas/ax/presence`) on every change, including TTL expiry — a
903
1285
  client never needs its own expiry timer. `agent-session-ended` carries the
@@ -922,12 +1304,24 @@ through any proxy:
922
1304
  # current cursor. With `since=<seq>`: only events after that cursor.
923
1305
  curl "http://localhost:4313/api/workbench/poll"
924
1306
  curl "http://localhost:4313/api/workbench/poll?since=42"
1307
+ # Optional long poll: return when an event arrives, or after at most 1000 ms.
1308
+ curl "http://localhost:4313/api/workbench/poll?since=42&waitMs=1000"
925
1309
  ```
926
1310
 
927
- Responses are `{ ok, transport: "poll", snapshot, seq, events }` — poll again
928
- with `since=<seq>` every couple of seconds. A `since` from a previous server
929
- run or one that fell off the bounded event ring recovers with a fresh
930
- snapshot. The browser client uses this automatically: if the SSE stream
1311
+ Responses are `{ ok, transport: "poll", snapshot, seq, sessionId, events }`.
1312
+ Poll again with `since=<seq>`. Without `waitMs`, the response is immediate;
1313
+ with it, a caught-up cursor waits for the next event. The wait is clamped to
1314
+ 0–1000 ms and ends on request cancellation or server shutdown. A cursor with
1315
+ pending events or needing a full snapshot never waits. The direct HTTP browser
1316
+ client requests a 1000 ms wait and pauses 80 ms between completed requests:
1317
+ cursor movement arrives promptly without fast empty polling while idle.
1318
+ Hosted MCP-app tool polling retains its two-second cadence; SSE is unchanged.
1319
+
1320
+ A cursor that fell off the bounded event ring recovers with a fresh snapshot.
1321
+ After a server restart, an out-of-range cursor receives a snapshot; if the old
1322
+ cursor happens to fit the new ring, the browser detects the changed `sessionId`
1323
+ and requests a new snapshot without `since`. The browser client uses polling
1324
+ automatically: if the SSE stream
931
1325
  produces no event within 3 seconds of connecting, it switches to polling (and
932
1326
  tells the boot screen to wait for the fallback instead of alarming). Pages
933
1327
  served by an Amp orb (`AMP_ORB` set — the same stamp that drives srcdoc mode)
@@ -935,6 +1329,34 @@ skip SSE entirely and default straight to polling. Force a transport with
935
1329
  `/workbench?transport=poll` (or `transport=sse` to disable the fallback /
936
1330
  override the orb default).
937
1331
 
1332
+ ## Workspace UI beta settings
1333
+
1334
+ `GET /api/canvas/settings` returns `{ ok: true, workspaceSettings: {
1335
+ agentSessions: false, knowledgeLifecycle: false } }`. Both switches default to
1336
+ false, including existing libraries with no saved settings.
1337
+
1338
+ `POST /api/canvas/settings` accepts a partial object such as
1339
+ `{ "agentSessions": true }`. Values must be booleans (not strings, numbers,
1340
+ or null); omitted switches are preserved. Success returns the full canonical
1341
+ `workspaceSettings` only after SQLite persistence; writes without a workspace
1342
+ database fail. Settings are workspace-wide SQLite metadata, independent of
1343
+ boards and snapshots, and included in whole-library backups/restores.
1344
+
1345
+ The HTTP-only operations `settings.get` / `settings.set` are UI preferences,
1346
+ not agent MCP tools or restrictions on session/knowledge APIs. Writes never
1347
+ create a board or attribute agent activity. POST requires the workbench's
1348
+ `X-PMX-Workbench: 1` UI marker; unmarked API writes receive 403. The hosted
1349
+ app-only bridge supplies that marker for this preferences operation alone,
1350
+ without forwarding the daemon's human-author token or exempting subsequent
1351
+ canvas edits from agent protections. The marker is a UI-policy convention,
1352
+ not authentication against a caller deliberately forging HTTP headers; hosted
1353
+ UI isolation relies on the host honoring app-only tool visibility.
1354
+
1355
+ `settings-changed` carries `{ workspaceSettings: { agentSessions,
1356
+ knowledgeLifecycle } }`, including after a full-library restore. The `connected`
1357
+ snapshot includes `workspaceSettings` for SSE reconnect and polling recovery.
1358
+ Clients use these ordered events/snapshots as canonical, not late POST replies.
1359
+
938
1360
  ## Session theme override (host-default theming)
939
1361
 
940
1362
  `/workbench?theme=<name>` gives that browser session its own theme without