pmx-canvas 0.6.4 → 0.7.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 (204) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +67 -3
  2. package/CHANGELOG.md +73 -0
  3. package/Readme.md +35 -5
  4. package/dist/canvas/global.css +498 -28
  5. package/dist/canvas/index.js +110 -95
  6. package/dist/canvas/mermaid-entry.js +258 -258
  7. package/dist/types/cli/agent.d.ts +4 -0
  8. package/dist/types/cli/commands/backup.d.ts +1 -0
  9. package/dist/types/cli/commands/boards.d.ts +1 -0
  10. package/dist/types/cli/commands/export.d.ts +1 -0
  11. package/dist/types/cli/commands/record.d.ts +12 -0
  12. package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
  13. package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
  14. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
  15. package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
  16. package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
  17. package/dist/types/client/canvas/HomeView.d.ts +9 -0
  18. package/dist/types/client/canvas/Presentation.d.ts +13 -0
  19. package/dist/types/client/canvas/use-node-drag.d.ts +2 -1
  20. package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
  21. package/dist/types/client/nodes/BoardNode.d.ts +5 -0
  22. package/dist/types/client/nodes/McpAppNode.d.ts +24 -1
  23. package/dist/types/client/state/boards-store.d.ts +79 -0
  24. package/dist/types/client/state/canvas-store.d.ts +2 -0
  25. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  26. package/dist/types/client/state/presentation.d.ts +3 -0
  27. package/dist/types/client/state/session-store.d.ts +7 -0
  28. package/dist/types/client/types.d.ts +2 -2
  29. package/dist/types/json-render/catalog.d.ts +3 -3
  30. package/dist/types/json-render/charts/definitions.d.ts +1 -1
  31. package/dist/types/json-render/server.d.ts +8 -0
  32. package/dist/types/mcp/canvas-access.d.ts +10 -3
  33. package/dist/types/mcp/context-uri-template.d.ts +6 -0
  34. package/dist/types/server/agent-presence.d.ts +1 -1
  35. package/dist/types/server/attribution.d.ts +11 -0
  36. package/dist/types/server/ax-context.d.ts +3 -1
  37. package/dist/types/server/ax-interaction.d.ts +2 -2
  38. package/dist/types/server/ax-state.d.ts +13 -3
  39. package/dist/types/server/backup.d.ts +36 -0
  40. package/dist/types/server/board-export.d.ts +45 -0
  41. package/dist/types/server/canvas-db.d.ts +65 -9
  42. package/dist/types/server/canvas-operations.d.ts +45 -0
  43. package/dist/types/server/canvas-provenance.d.ts +1 -1
  44. package/dist/types/server/canvas-schema.d.ts +1 -0
  45. package/dist/types/server/canvas-state.d.ts +146 -3
  46. package/dist/types/server/canvas-validation.d.ts +3 -0
  47. package/dist/types/server/context-brief.d.ts +111 -0
  48. package/dist/types/server/context-reads.d.ts +55 -0
  49. package/dist/types/server/document-import.d.ts +66 -0
  50. package/dist/types/server/html-surface.d.ts +1 -1
  51. package/dist/types/server/index.d.ts +107 -4
  52. package/dist/types/server/mutation-history.d.ts +1 -0
  53. package/dist/types/server/operations/invoker.d.ts +2 -0
  54. package/dist/types/server/operations/mcp.d.ts +7 -1
  55. package/dist/types/server/operations/ops/app.d.ts +2 -0
  56. package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
  57. package/dist/types/server/operations/ops/backup.d.ts +2 -0
  58. package/dist/types/server/operations/ops/boards.d.ts +10 -0
  59. package/dist/types/server/operations/ops/export.d.ts +2 -0
  60. package/dist/types/server/operations/ops/imports.d.ts +2 -0
  61. package/dist/types/server/operations/ops/json-render.d.ts +2 -0
  62. package/dist/types/server/operations/ops/nodes.d.ts +4 -2
  63. package/dist/types/server/operations/ops/query.d.ts +2 -0
  64. package/dist/types/server/operations/registry.d.ts +9 -1
  65. package/dist/types/server/operations/types.d.ts +1 -1
  66. package/dist/types/server/server.d.ts +1 -0
  67. package/dist/types/server/trace-manager.d.ts +7 -0
  68. package/dist/types/server/web-artifacts.d.ts +3 -0
  69. package/dist/types/server/webpage-node.d.ts +22 -1
  70. package/dist/types/server/workbench-auth.d.ts +3 -0
  71. package/dist/types/shared/boards.d.ts +6 -0
  72. package/dist/types/shared/tour.d.ts +72 -0
  73. package/docs/RELEASE.md +5 -0
  74. package/docs/cli.md +157 -0
  75. package/docs/http-api.md +277 -6
  76. package/docs/mcp.md +96 -7
  77. package/docs/node-types.md +106 -0
  78. package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
  79. package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
  80. package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
  81. package/docs/plans/plan-013-static-board-export.md +47 -0
  82. package/docs/plans/plan-014-connected-board-memory.md +140 -0
  83. package/docs/product-vision-2026-09.md +118 -18
  84. package/docs/sdk.md +50 -0
  85. package/package.json +1 -1
  86. package/skills/pmx-canvas/SKILL.md +62 -5
  87. package/skills/pmx-canvas/references/full-reference.md +44 -8
  88. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
  89. package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
  90. package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
  91. package/skills/pmx-canvas-testing/SKILL.md +4 -0
  92. package/src/cli/agent.ts +9 -0
  93. package/src/cli/commands/ax.ts +13 -0
  94. package/src/cli/commands/backup.ts +66 -0
  95. package/src/cli/commands/boards.ts +71 -0
  96. package/src/cli/commands/export.ts +30 -0
  97. package/src/cli/commands/nodes.ts +14 -1
  98. package/src/cli/commands/pins.ts +6 -2
  99. package/src/cli/commands/pump.ts +3 -0
  100. package/src/cli/commands/query.ts +33 -7
  101. package/src/cli/commands/record.ts +218 -0
  102. package/src/cli/commands/view.ts +7 -5
  103. package/src/cli/index.ts +21 -9
  104. package/src/client/App.tsx +28 -4
  105. package/src/client/canvas/AgentPresenceLayer.tsx +1 -1
  106. package/src/client/canvas/BoardCopyDialog.tsx +153 -0
  107. package/src/client/canvas/BoardFolderDialog.tsx +120 -0
  108. package/src/client/canvas/BoardLinkDialog.tsx +93 -0
  109. package/src/client/canvas/BoardSwitcher.tsx +149 -0
  110. package/src/client/canvas/CanvasNode.tsx +8 -0
  111. package/src/client/canvas/CanvasViewport.tsx +3 -0
  112. package/src/client/canvas/CommandPalette.tsx +107 -4
  113. package/src/client/canvas/ContextMenu.tsx +4 -0
  114. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
  115. package/src/client/canvas/ExportDialog.tsx +197 -0
  116. package/src/client/canvas/HomeView.tsx +277 -0
  117. package/src/client/canvas/IntentLayer.tsx +9 -2
  118. package/src/client/canvas/Presentation.tsx +108 -0
  119. package/src/client/canvas/SessionPanel.tsx +28 -1
  120. package/src/client/canvas/ToolRail.tsx +38 -3
  121. package/src/client/canvas/TopBar.tsx +49 -7
  122. package/src/client/canvas/import-files.ts +19 -1
  123. package/src/client/canvas/kind-colors.ts +1 -0
  124. package/src/client/canvas/use-node-drag.ts +8 -3
  125. package/src/client/icons.tsx +2 -0
  126. package/src/client/mermaid-entry.ts +39 -0
  127. package/src/client/nodes/AttachmentNode.tsx +148 -0
  128. package/src/client/nodes/BoardNode.tsx +35 -0
  129. package/src/client/nodes/FileNode.tsx +6 -0
  130. package/src/client/nodes/HtmlNode.tsx +12 -13
  131. package/src/client/nodes/MarkdownNode.tsx +47 -19
  132. package/src/client/nodes/McpAppNode.tsx +105 -13
  133. package/src/client/nodes/MermaidNode.tsx +3 -2
  134. package/src/client/nodes/ax-node-actions.ts +2 -5
  135. package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
  136. package/src/client/state/boards-store.ts +167 -0
  137. package/src/client/state/canvas-store.ts +22 -0
  138. package/src/client/state/intent-bridge.ts +30 -2
  139. package/src/client/state/intent-store.ts +5 -1
  140. package/src/client/state/node-factory.ts +1 -0
  141. package/src/client/state/presentation.ts +7 -0
  142. package/src/client/state/session-store.ts +8 -0
  143. package/src/client/state/sse-bridge.ts +52 -5
  144. package/src/client/theme/global.css +498 -28
  145. package/src/client/types.ts +2 -0
  146. package/src/json-render/server.ts +45 -9
  147. package/src/mcp/canvas-access.ts +59 -22
  148. package/src/mcp/context-uri-template.ts +28 -0
  149. package/src/mcp/server.ts +122 -15
  150. package/src/server/agent-context.ts +116 -29
  151. package/src/server/agent-presence.ts +15 -9
  152. package/src/server/attribution.ts +29 -0
  153. package/src/server/ax-context.ts +4 -2
  154. package/src/server/ax-interaction.ts +1 -0
  155. package/src/server/ax-state-manager.ts +72 -3
  156. package/src/server/ax-state.ts +41 -0
  157. package/src/server/backup.ts +178 -0
  158. package/src/server/board-export.ts +686 -0
  159. package/src/server/canvas-db.ts +612 -105
  160. package/src/server/canvas-operations.ts +183 -31
  161. package/src/server/canvas-provenance.ts +1 -0
  162. package/src/server/canvas-schema.ts +22 -0
  163. package/src/server/canvas-state.ts +812 -48
  164. package/src/server/canvas-validation.ts +9 -1
  165. package/src/server/context-brief.ts +372 -0
  166. package/src/server/context-reads.ts +238 -0
  167. package/src/server/demo-state.json +670 -9
  168. package/src/server/document-import.ts +223 -0
  169. package/src/server/html-primitives.ts +199 -91
  170. package/src/server/html-surface.ts +8 -3
  171. package/src/server/index.ts +243 -5
  172. package/src/server/mutation-history.ts +10 -13
  173. package/src/server/operations/composites.ts +29 -5
  174. package/src/server/operations/http.ts +9 -3
  175. package/src/server/operations/index.ts +8 -0
  176. package/src/server/operations/invoker.ts +14 -3
  177. package/src/server/operations/mcp.ts +50 -11
  178. package/src/server/operations/ops/app.ts +15 -0
  179. package/src/server/operations/ops/ax-read.ts +10 -6
  180. package/src/server/operations/ops/ax-timeline.ts +75 -0
  181. package/src/server/operations/ops/backup.ts +134 -0
  182. package/src/server/operations/ops/batch.ts +20 -4
  183. package/src/server/operations/ops/boards.ts +351 -0
  184. package/src/server/operations/ops/export.ts +74 -0
  185. package/src/server/operations/ops/ext-app.ts +4 -0
  186. package/src/server/operations/ops/groups.ts +14 -7
  187. package/src/server/operations/ops/imports.ts +220 -0
  188. package/src/server/operations/ops/json-render.ts +37 -5
  189. package/src/server/operations/ops/nodes.ts +99 -21
  190. package/src/server/operations/ops/query.ts +206 -9
  191. package/src/server/operations/ops/viewport.ts +32 -0
  192. package/src/server/operations/registry.ts +129 -31
  193. package/src/server/operations/types.ts +1 -1
  194. package/src/server/scope-fence.ts +1 -1
  195. package/src/server/server.ts +155 -8
  196. package/src/server/spatial-analysis.ts +9 -17
  197. package/src/server/trace-manager.ts +19 -0
  198. package/src/server/web-artifacts.ts +11 -3
  199. package/src/server/webpage-node.ts +173 -13
  200. package/src/server/workbench-auth.ts +11 -0
  201. package/src/server/workboard.ts +3 -3
  202. package/src/shared/boards.ts +18 -0
  203. package/src/shared/node-sizes.ts +1 -0
  204. package/src/shared/tour.ts +87 -0
package/docs/cli.md CHANGED
@@ -8,11 +8,109 @@ wins over `--port`, and both win over the environment variables). An invalid
8
8
  value for either flag is a hard error — the CLI never falls back silently to
9
9
  the default port.
10
10
 
11
+ Read commands accept `--board <board-id>` without switching the active board:
12
+ `layout` (including `--summary`), `status`, `spatial`, `pin --list`, and
13
+ `node get`. Writes and presence still target the board open in the workbench;
14
+ unknown board IDs fail rather than falling back to it.
15
+
16
+ An in-flight asynchronous write stops if the board changes; subsequent calls use
17
+ the newly open board. Batches stop rather than continuing on another board, and
18
+ already-completed entries remain applied.
19
+
20
+ On SIGINT or SIGTERM, the server saves before exiting. If saving fails, it reports
21
+ the error and stays running so the in-memory edits can be recovered. Fix the
22
+ storage failure and retry shutdown; a forced process kill can still lose edits.
23
+
11
24
  ```bash
12
25
  pmx-canvas node list --port 4750 # target a non-default daemon
13
26
  pmx-canvas --server-url http://127.0.0.1:4750 status # same, by URL
14
27
  ```
15
28
 
29
+ ## Tour and presentation
30
+
31
+ This is the small tour slice of the [vision's “board is the presentation”](product-vision-2026-09.md#12-tours-the-board-is-the-presentation-m):
32
+ the existing cards remain the content, rather than being rebuilt as a separate deck.
33
+ The same stops and camera interpolation drive interactive presentation and optional capture.
34
+ A visual stop editor and carrying tours into static exports/share links are not implemented here.
35
+
36
+ Click **Present** in the top bar, or open `/workbench?present=1` for a chrome-free
37
+ embedded/capture view. Right/Down/Space advances, Left/Up goes back, Esc exits.
38
+ The camera is local to the viewer; board content continues updating over SSE.
39
+ Stops do not auto-advance. Missing targets show an error and can be skipped.
40
+
41
+ Save this as `tour.json` (replace the IDs with IDs from `pmx-canvas node list`):
42
+
43
+ ```json
44
+ {
45
+ "stops": [
46
+ { "target": { "nodeId": "overview-group" }, "duration": 1.5, "padding": 60 },
47
+ { "target": { "nodeId": "detail-note" }, "duration": 2, "easing": "ease-in-out", "pullback": 0.7 },
48
+ { "target": { "viewport": { "x": 80, "y": 100, "scale": 0.5 } }, "duration": 1 }
49
+ ]
50
+ }
51
+ ```
52
+
53
+ ```bash
54
+ pmx-canvas tour set --file tour.json
55
+ pmx-canvas tour get
56
+ ```
57
+
58
+ A stop targets either a node ID (including a group ID) or an explicit viewport.
59
+ Viewport translation is screen-space: `screen = world * scale + offset`.
60
+ `duration` is transition time in seconds (default 1, zero jumps); `padding` is
61
+ screen pixels around a target (default 40). Easing is `linear`, `ease-in-out`
62
+ (default), or `ease-out`. Zoom interpolates logarithmically about the moving
63
+ world-space centre. `pullback` (0–4, default 0) subtracts a sinusoidal log-zoom
64
+ offset mid-move without changing endpoints. Tours allow at most 1000 stops.
65
+
66
+ Tours persist with the board and snapshots, participate in undo, and clear with
67
+ the board. A file containing `null` resets the tour: when none is saved, groups
68
+ are sorted by y, then x, then ID. A saved empty `stops` array deliberately has no
69
+ stops. SDK: `canvas.setTour(tourOrNull)` / `canvas.getTour()`.
70
+
71
+ ### Optional capture of the same tour
72
+
73
+ `record` is an optional local utility, not a second presentation model. It reads
74
+ the saved/derived tour, or the identical tour JSON supplied with `--tour-file`.
75
+ Start the server first. Capture launches its own Bun.WebView Chrome session;
76
+ it does not replace a server automation session or move other viewers' cameras.
77
+
78
+ ```bash
79
+ # Capture the saved/derived board tour
80
+ pmx-canvas record --mode deterministic --present --fps 30 --resolution 1920x1080 --output tour.mp4
81
+
82
+ # Try the same tour model from a file without changing the saved tour
83
+ pmx-canvas record --mode deterministic --tour-file tour.json --present --output frames
84
+
85
+ # Alternatively, sample the live presentation while agents update the board
86
+ pmx-canvas record --duration 10 --present --theme light --output activity.mp4
87
+
88
+ # Keep recording until SIGINT (Ctrl+C) or SIGTERM, then encode
89
+ pmx-canvas record --stop-on-signal --output live.mp4
90
+ ```
91
+
92
+ Options: `--mode realtime|deterministic` (default realtime), `--resolution WIDTHxHEIGHT`
93
+ (default 1280x720), `--fps` (1–120, default 30), `--theme`, `--present`,
94
+ `--duration SECONDS` or `--stop-on-signal` for realtime, `--tour-file FILE`
95
+ for deterministic mode, `--chrome-path PATH`, and required `--output PATH`.
96
+ Global `--port` / `--server-url` work here too. Existing output paths are refused.
97
+
98
+ An `.mp4` output uses ffmpeg/H.264; any other path is a PNG sequence directory.
99
+ Frames and `recording.json` are retained at `<output>.frames` even after video
100
+ encoding. If ffmpeg is missing, the command clearly reports the PNG fallback
101
+ and returns that directory. Encoding failures retain frames and exit nonzero.
102
+
103
+ Deterministic mode resolves target geometry once and writes
104
+ `max(1, ceil(duration × fps))` frames per stop, including each endpoint. It sets
105
+ the camera, waits for two animation frames, checks the applied camera, then
106
+ captures. Duplicate a target to hold it. This makes **camera timing** deterministic,
107
+ not animated iframe content, fonts loaded later, network assets, or concurrent
108
+ board edits. Realtime mode samples the live board; slow captures repeat frames
109
+ to preserve wall-clock speed, with the repeated-frame count in `recording.json`.
110
+ Capture has no audio. Chrome and a Bun runtime with WebView support are required.
111
+ Recording is a local CLI/file operation, not a remote HTTP/MCP file-writing API;
112
+ HTTP/MCP manage tours and retain their existing screenshot/automation surfaces.
113
+
16
114
  ## pump — reactive steering for CLI agents
17
115
 
18
116
  `pmx-canvas pump` turns any CLI agent (Codex, Amp, a script) into a steerable
@@ -99,6 +197,10 @@ pmx-canvas node update <node-id> --width 840 --height 620
99
197
  pmx-canvas node update <node-id> --title "Renamed" --full
100
198
  ```
101
199
 
200
+ Mermaid `node add` and `node update` accept `--fit contain|none`, stored as
201
+ `data.fit`. Contain is the default and shrinks only overflowing diagrams;
202
+ none keeps 100% with scrolling. [Sizing and readability](node-types.md#mermaid-nodes).
203
+
102
204
  `--from-search` / `--to-search` must each resolve to exactly one node — broad
103
205
  queries fail rather than guess. Use the full visible title.
104
206
 
@@ -173,6 +275,48 @@ pmx-canvas fit <id-a> <id-b> # Fit to just these nodes
173
275
  `--width` / `--height`. After creating output a human should see, focus a single
174
276
  node or fit the new ids — auto-placement is board-relative, not camera-relative.
175
277
 
278
+ ## Boards
279
+
280
+ ```bash
281
+ pmx-canvas board list # most recently opened first; activeBoardId null = Home
282
+ pmx-canvas board create "Q4 planning" # created, not opened
283
+ pmx-canvas board rename <board-id> "New name"
284
+ pmx-canvas board create "Q4 planning" --category Planning
285
+ pmx-canvas board category <board-id> "Planning/Quarterly" # nested folders; --clear unfiles it
286
+ pmx-canvas board from <source-id> "Follow-up" --preview
287
+ pmx-canvas board from <source-id> "Follow-up" --nodes id1,id2 --readme --structure
288
+ ```
289
+
290
+ Commands write to the board open in the workbench. Opening, switching and
291
+ deleting boards happens in the workbench (the human's call). `board from`
292
+ excludes `prompt`, `response`, `trace`, and `mcp-app` cards; selected reusable
293
+ cards get fresh IDs. `--structure` includes group frames; edges between copied
294
+ cards are retained automatically. `--readme` carries the designated README. The new board remains inactive and
295
+ does not inherit pins, AX state, or mutation history.
296
+
297
+ ## Backup and restore
298
+
299
+ Boards save automatically. Backups are separate recovery copies of the whole library,
300
+ configured through the CLI or HTTP API rather than the Home screen. Scheduled backups
301
+ run while the server is running; scheduling is off until configured.
302
+
303
+ ```bash
304
+ pmx-canvas backup # every board, now; into .pmx-canvas/backups
305
+ pmx-canvas backup --to ~/Backups/pmx --keep 14
306
+ pmx-canvas backup schedule --every 24h --keep 14 --to ~/Backups/pmx # run by the server
307
+ pmx-canvas backup schedule --off
308
+ pmx-canvas backup status
309
+ pmx-canvas restore ~/Backups/pmx/canvas-20260927T090000000Z.db # keeps canvas.db.before-restore
310
+ ```
311
+
312
+ ## Export
313
+
314
+ ```bash
315
+ pmx-canvas export # the open board → .pmx-canvas/exports/<board>-<time>.html
316
+ pmx-canvas export --board <board-id> --out ~/Desktop/okrs.html
317
+ pmx-canvas export --include-files # include local file contents (left out by default)
318
+ ```
319
+
176
320
  ## Snapshots
177
321
 
178
322
  ```bash
@@ -186,6 +330,18 @@ pmx-canvas diff <snapshot-id> # Compare the canvas against a snapshot
186
330
 
187
331
  ## AX context
188
332
 
333
+ The cross-board brief has its own compact command. Its budget is measured in
334
+ UTF-16 code units (default 16000, maximum 100000), not model tokens. `--consumer`
335
+ selects a durable cursor scoped to the open board; `--since` performs an
336
+ explicit pull without changing that cursor. With neither flag, every call starts
337
+ from the beginning. Use a stable, unique consumer ID per agent/adapter; changing
338
+ or sharing it loses or conflates delivery progress.
339
+
340
+ ```bash
341
+ pmx-canvas context --consumer codex --budget 16000
342
+ pmx-canvas context --since 42 --budget 8000
343
+ ```
344
+
189
345
  AX commands expose the host-agnostic context contract used by adapters. Pins
190
346
  remain the explicit human-curated context set; AX focus is the current
191
347
  attention target and persists with canvas state and snapshots.
@@ -209,6 +365,7 @@ pmx-canvas ax event add --kind tool-start --summary "ran tests"
209
365
  pmx-canvas ax steer "focus on the failing test first"
210
366
  pmx-canvas ax evidence add --kind test-output --title "unit pass"
211
367
  pmx-canvas ax timeline --limit 50
368
+ pmx-canvas ax reads --limit 50 # context read log: which context each agent read, pinned delivery
212
369
 
213
370
  # Work items (canvas-bound)
214
371
  pmx-canvas ax work add --title "Wire up auth" --status in-progress node-1
package/docs/http-api.md CHANGED
@@ -9,14 +9,207 @@ bodies are treated as an empty request. As of 0.4.0 every error response is a
9
9
  JSON envelope of that same shape (`{ ok: false, error }`, status unchanged) —
10
10
  no plain-text errors remain.
11
11
 
12
+ Node-create responses include `sizeAdjustment` when a frame dimension was
13
+ defaulted or raised to its node type's readability floor. It contains the
14
+ `requested` dimensions (`null` when omitted), the final `applied` dimensions,
15
+ and a `reason` (`defaulted`, `clamped-to-minimum`, or
16
+ `defaulted-and-clamped-to-minimum`; groups also use `fit-to-children`). `strictSize: true`
17
+ still bypasses the readability clamp, but omitted dimensions are reported as
18
+ defaulted. MCP create actions return the same size report.
19
+ Updates preserve explicit dimensions without applying creation floors; their
20
+ returned node `size` is the applied size. Omitted update dimensions retain the
21
+ existing size and are not reported as defaulted adjustments.
22
+
23
+ Both group-create routes report defaults or final child-derived dimensions, including
24
+ when children override supplied width/height because x/y were omitted. Explicit manual
25
+ frames retain their existing semantics. Group child replacement reports `fit-to-children`
26
+ when it changes the frame; `group.add` reports the previous frame as `requested` and
27
+ the final frame as `applied`. Reports survive compact/full MCP and batch responses.
28
+ External MCP app creation and web-artifact builds that open a node report their default
29
+ frames too; reusing an existing app node or building without opening does not report a
30
+ creation default. These reports describe the synchronous operation, not later browser
31
+ content auto-height or indirect parent-group reflow after editing a child.
32
+
33
+ ## Board tours
34
+
35
+ - `GET /api/canvas/tour` → `{ tour: { stops: [...] }, derived: boolean }`.
36
+ - `POST /api/canvas/tour` with `{ "tour": { "stops": [...] } }` persists the tour;
37
+ `{ "tour": null }` resets to derived group reading order. Returns `{ "ok": true }`.
38
+ - `GET /api/canvas/state` includes optional `tour`. Tour writes broadcast a layout
39
+ update and are included in snapshots/undo. They are board-wide writes under a scope fence.
40
+
41
+ The [tour model and recording walkthrough](cli.md#tour-and-presentation) describes
42
+ targets, duration, easing, padding, and pullback. Open `/workbench?present=1` to hide
43
+ chrome; camera movement is local to that viewer. Recording writes local files via
44
+ the CLI, not an HTTP job endpoint.
45
+
46
+ ## Boards
47
+
48
+ A workspace holds many named boards; one is open at a time. Agent writes go to
49
+ the open board. While no board is open (Home), the first write opens the most
50
+ recently opened board, or creates one when the workspace has none;
51
+ `canvas.clear` on Home opens nothing. Only the trusted workbench opens or
52
+ deletes a board — HTTP calls require both `x-pmx-workbench: 1` and the
53
+ per-boot `x-pmx-workbench-token` embedded in the served `/workbench` HTML;
54
+ the marker alone is not authority. Public HTTP integrations must not open or
55
+ delete boards. In-process SDK embedding hosts retain authority to do so.
56
+ Every change emits a
57
+ `boards-changed` SSE frame with `{ activeBoardId, boards }`.
58
+
59
+ `category` is a folder path, for example `Engineering/Canvas/Decisions`.
60
+ Home renders nested, collapsible folders and searches board names and paths.
61
+ Paths support up to eight levels, each with 1–60 characters; surrounding spaces
62
+ are trimmed. Empty segments and `.`/`..` are rejected. `null` or `""` unfiles a
63
+ board. Folders are derived from boards, so empty folders do not persist. Moving
64
+ a board changes its path, not its ID or content.
65
+
66
+ ```bash
67
+ # List boards (most recently opened first) and which is open (null = Home)
68
+ curl http://localhost:4313/api/canvas/boards
69
+
70
+ # Read one board
71
+ curl http://localhost:4313/api/canvas/boards/<board-id>
72
+
73
+ # Create a board (optionally in a category) — it is NOT opened
74
+ curl -X POST http://localhost:4313/api/canvas/boards \
75
+ -H "Content-Type: application/json" -d '{"name":"Q4 planning","category":"Planning"}'
76
+
77
+ # Preview/create an inactive copy from reusable cards (preview false creates)
78
+ curl -X POST http://localhost:4313/api/canvas/boards/from \
79
+ -H "Content-Type: application/json" \
80
+ -d '{"sourceBoardId":"board-source","name":"Follow-up","nodeIds":["node-1"],"includeReadme":true,"includeStructure":true,"preview":true}'
81
+
82
+ # Rename and/or file under a category on Home ("category": "" removes it)
83
+ curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
84
+ -H "Content-Type: application/json" -d '{"name":"Q4 planning (final)","category":"Planning"}'
85
+
86
+ # Designate a Markdown card on this board as its README (null clears it)
87
+ curl -X PATCH http://localhost:4313/api/canvas/boards/<board-id> \
88
+ -H "Content-Type: application/json" -d '{"readmeNodeId":"<markdown-node-id>"}'
89
+
90
+ # Open a board, or Home with {"id":null} — workbench only
91
+ curl -X POST http://localhost:4313/api/canvas/boards/open \
92
+ -H "Content-Type: application/json" -H "x-pmx-workbench: 1" \
93
+ -H "x-pmx-workbench-token: <token-from-served-workbench>" -d '{"id":"<board-id>"}'
94
+
95
+ # Delete a board and its snapshots — workbench only; deleting the open board returns to Home
96
+ curl -X DELETE http://localhost:4313/api/canvas/boards/<board-id> \
97
+ -H "x-pmx-workbench: 1" -H "x-pmx-workbench-token: <token-from-served-workbench>"
98
+ ```
99
+
100
+ ## Backup and restore
101
+
102
+ A backup is one SQLite file holding every board, its snapshots and blobs,
103
+ written with `VACUUM INTO` (safe while the server runs) to
104
+ `canvas-<timestamp>.db` in the backup folder — by default `backups/` beside the
105
+ database (`.pmx-canvas/backups`). Keeping the newest `keep` (default 14)
106
+ removes older backups in that folder. The schedule is stored in the database
107
+ and runs inside the server. Not exposed over MCP.
108
+
109
+ ```bash
110
+ # Folder, schedule, last backup and the backups on disk
111
+ curl http://localhost:4313/api/canvas/backup
112
+
113
+ # Back up now (optional folder and keep)
114
+ curl -X POST http://localhost:4313/api/canvas/backup \
115
+ -H "Content-Type: application/json" -d '{"to":"/Users/me/Backups/pmx","keep":14}'
116
+
117
+ # Back up every 24h ("30m", "7d"; at least once a minute; "off" stops it)
118
+ curl -X POST http://localhost:4313/api/canvas/backup/schedule \
119
+ -H "Content-Type: application/json" -d '{"every":"24h","keep":14,"to":"/Users/me/Backups/pmx"}'
120
+
121
+ # Replace every board with a backup; the current file is kept as canvas.db.before-restore.
122
+ # Refuses files that are not PMX Canvas databases, and the live database itself.
123
+ curl -X POST http://localhost:4313/api/canvas/backup/restore \
124
+ -H "Content-Type: application/json" -d '{"file":"/Users/me/Backups/pmx/canvas-20260927T090000000Z.db"}'
125
+ ```
126
+
127
+ ## Static board export
128
+
129
+ One self-contained, read-only HTML file of one board (pan, zoom, expand) that
130
+ opens with no server or install. Cards render as escaped text; html cards,
131
+ charts, diagrams and built apps run in sandboxed iframes, with viewer bundles
132
+ stored once per file. Live MCP apps become placeholders; local file contents
133
+ are left out unless `includeFiles` is true. Files are written to `exports/`
134
+ beside the database. Built-app files are only embedded when they are regular,
135
+ bounded-size files inside the board's owned `.pmx-canvas/artifacts/` directory.
136
+ The preview manifest lists card links, remote images, and statically visible
137
+ network destinations in embedded HTML. Imported source-linked Markdown is also
138
+ excluded unless `includeDerivedText=true`, independently of `includeFiles`;
139
+ editing it does not remove that classification. Original attachment bytes are
140
+ never embedded in v1. Embedded scripts can make additional
141
+ network requests that cannot be inventoried in advance. Not exposed over MCP —
142
+ sharing is the human's act.
143
+
144
+ ```bash
145
+ # What the file would contain (defaults to the open board; ?board=<id> for another)
146
+ curl "http://localhost:4313/api/canvas/export/preview?includeFiles=false&includeDerivedText=false"
147
+
148
+ # Write it → { path, url, bytes, manifest }
149
+ curl -X POST http://localhost:4313/api/canvas/export \
150
+ -H "Content-Type: application/json" \
151
+ -d '{"board":"<board-id>","includeFiles":false,"includeDerivedText":false}'
152
+
153
+ # Serve a written export (opaque-origin sandbox; ?download=1 as an attachment)
154
+ curl "http://localhost:4313/api/canvas/exports/<file>.html?download=1" -o board.html
155
+ ```
156
+
157
+ ## Agent-assisted document imports
158
+
159
+ PDF and Office uploads are raw bytes, not JSON. Upload requires the trusted
160
+ workbench token, an active captured board, a non-empty body of at most 20 MiB,
161
+ and creates both an original file card and an `attached` import. The original
162
+ remains board-owned until board deletion; snapshot restore does not remove it,
163
+ and whole-database backups include it.
164
+
165
+ ```bash
166
+ curl -X POST \
167
+ "http://localhost:4313/api/canvas/attachments?boardId=<board-id>&name=report.pdf&mime=application/pdf&x=40&y=40" \
168
+ -H "x-pmx-workbench-token: <token>" --data-binary @report.pdf
169
+
170
+ # Human-only: explicit consent asks the connected agent; captured board must be open
171
+ curl -X POST http://localhost:4313/api/canvas/imports/<import-id>/request \
172
+ -H "Content-Type: application/json" -H "x-pmx-workbench: 1" \
173
+ -H "x-pmx-workbench-token: <token>" \
174
+ -d '{"consent":true}'
175
+
176
+ # Agent reads metadata and a controlled byte path; inline=true adds base64 only <=2 MiB
177
+ curl "http://localhost:4313/api/canvas/imports/<import-id>?inline=true"
178
+ curl http://localhost:4313/api/canvas/attachments/<attachment-id>/bytes -o source.bin
179
+
180
+ # Agent submits a reviewable draft (allowed even if another board is active)
181
+ curl -X POST http://localhost:4313/api/canvas/imports/<import-id>/submit \
182
+ -H "Content-Type: application/json" \
183
+ -d '{"sections":[{"title":"Summary","markdown":"...","reference":"page 1"}],"warnings":[],"agentDescription":"Extracted with the connected host PDF reader"}'
184
+
185
+ # Human-only review/commit; the original captured board must be open
186
+ curl -X POST http://localhost:4313/api/canvas/imports/<import-id>/commit \
187
+ -H "x-pmx-workbench: 1" -H "x-pmx-workbench-token: <token>"
188
+ ```
189
+
190
+ `GET /api/canvas/imports?boardId=...` lists jobs. An agent that cannot access or
191
+ interpret the source posts `{ "reason": "..." }` to
192
+ `/api/canvas/imports/:id/unavailable`; a human may later request a new attempt.
193
+ Humans can cancel requested or drafted jobs at `/cancel`. There is no bundled
194
+ converter, OCR, automatic agent launch, or guarantee for charts, scans, and
195
+ host-specific formats. A remote agent may need the human/host to transfer the
196
+ bytes before it can submit a draft.
197
+
12
198
  ## Canvas state
13
199
 
200
+ Read endpoints accept an optional `board=<board-id>` query parameter without
201
+ opening that board or changing where writes go. This is supported by
202
+ `/api/canvas/state`, `/api/canvas/node/:id`, `/api/canvas/summary`,
203
+ `/api/canvas/spatial-context`, and `/api/canvas/pinned-context`. Unknown boards
204
+ return 404. Omit `board` to read the active board. Live AX session/delivery reads
205
+ and fixed MCP resources continue to describe the open board.
206
+
14
207
  ```bash
15
208
  # Get canvas state
16
209
  curl http://localhost:4313/api/canvas/state
17
210
 
18
- # Search nodes (optional limit= caps the result count)
19
- curl "http://localhost:4313/api/canvas/search?q=auth&limit=10"
211
+ # Search the library (omit scope for the active board)
212
+ curl "http://localhost:4313/api/canvas/search?q=auth&limit=10&scope=library"
20
213
 
21
214
  # Validate the current layout
22
215
  curl http://localhost:4313/api/canvas/validate
@@ -35,6 +228,10 @@ curl -X POST http://localhost:4313/api/canvas/json-render \
35
228
  -H "Content-Type: application/json" \
36
229
  -d '{"title":"Panel","nodeHeight":280,"spec":{"root":"card","elements":{"card":{"type":"Card","props":{"title":"Live"},"children":[]}}}}'
37
230
 
231
+ # For graph nodes, `height` is the chart's plot height, not the node frame.
232
+ # Set the frame with `nodeHeight` (`heightPx` in the SDK). A graph request that
233
+ # supplies only `height` gets the default frame height and a response warning.
234
+
38
235
  # Validate an HTML primitive without creating a node
39
236
  curl -X POST http://localhost:4313/api/canvas/schema/validate \
40
237
  -H "Content-Type: application/json" \
@@ -70,6 +267,16 @@ A node creation request must resolve a `type` — pass it in the body (`{ "type"
70
267
  ... }`) or as a `?type=` query param. An empty / type-less body returns `400`
71
268
  rather than silently creating a markdown node.
72
269
 
270
+ An optional `boardId` on node creation is a captured-board precondition, not a
271
+ switch command: if it no longer matches the active board, the request returns
272
+ `409` without creating a node or opening a board from Home. The file-drop UI
273
+ uses this to prevent delayed uploads from landing on a different board.
274
+
275
+ Mermaid nodes accept `data: { "fit": "contain" | "none" }` on both
276
+ `POST /api/canvas/node` and `PATCH /api/canvas/node/:id`. The default, `contain`,
277
+ shrinks overflowing diagrams without changing aspect ratio; `none` keeps 100%
278
+ with scrolling. [Mermaid sizing and readability](node-types.md#mermaid-nodes).
279
+
73
280
  ### File node content and bytes
74
281
 
75
282
  A `file` node reads its file server-side. Text is stored on the node as
@@ -145,6 +352,31 @@ curl -X DELETE http://localhost:4313/api/canvas/annotation/ann-123
145
352
  Agent-readable context reports annotation IDs, targets, and bounds. Use WebView
146
353
  inspection or screenshots when the drawn shape matters.
147
354
 
355
+ ## Viewport
356
+
357
+ ```bash
358
+ # Set the camera transform (screen = world * scale + {x,y})
359
+ curl -X POST http://localhost:4313/api/canvas/viewport \
360
+ -H "Content-Type: application/json" \
361
+ -d '{"x":64,"y":96,"scale":0.8}'
362
+
363
+ # Fit selected nodes (omit nodeIds to fit the whole canvas)
364
+ curl -X POST http://localhost:4313/api/canvas/fit \
365
+ -H "Content-Type: application/json" \
366
+ -d '{"nodeIds":["node-1"],"width":1200,"height":700}'
367
+
368
+ # Focus without moving the camera
369
+ curl -X POST http://localhost:4313/api/canvas/focus \
370
+ -H "Content-Type: application/json" \
371
+ -d '{"id":"node-1","noPan":true}'
372
+ ```
373
+
374
+ Viewport `x`/`y`, fit `width`/`height`, and fit padding are interpreted against
375
+ the **canvas area**, excluding the tool rail, top bar, and other surrounding
376
+ page chrome—not the full browser window. Camera, fit, and focus-only requests
377
+ do not create or refresh agent writer presence. Use the explicit presence API
378
+ when an agent should publish a cursor or `focusNodeId`.
379
+
148
380
  ## Pins
149
381
 
150
382
  ```bash
@@ -155,14 +387,30 @@ curl -X POST http://localhost:4313/api/canvas/context-pins \
155
387
 
156
388
  # Get pinned context
157
389
  curl http://localhost:4313/api/canvas/pinned-context
390
+
391
+ # Cross-board brief. budget is a UTF-16 character ceiling (default 16000, max 100000).
392
+ # Without since, the board/consumer cursor advances through only fully delivered revisions.
393
+ curl 'http://localhost:4313/api/canvas/context?consumer=codex&budget=16000'
158
394
  ```
159
395
 
396
+ `consumer` is optional. Supply a stable unique value only for a durable,
397
+ board-scoped cursor; without `consumer` or `since`, each request starts from the
398
+ beginning. An explicit `since` never advances a cursor. The brief prioritizes
399
+ pins, then relevant current-board changes and asks, then compact linked or
400
+ same-folder board context. Every item carries source board/card IDs. If card text
401
+ is clipped, use those IDs with the board/node read route rather than guessing
402
+ from the excerpt. Reading inactive boards does not open them.
403
+
160
404
  ## AX context and focus
161
405
 
162
406
  AX context is the host-agnostic agent-experience layer. It combines existing
163
407
  context pins with a persisted focus node set that adapters can inject into
164
408
  their native prompt/context hooks.
165
409
 
410
+ The context response includes `boardId` (`null` on Home), captured with its content
411
+ and pins. Adapters should send that ID when recording a proxied context read, rather
412
+ than looking up whichever board is active after delivery.
413
+
166
414
  ```bash
167
415
  # Get persisted AX state
168
416
  curl http://localhost:4313/api/canvas/ax
@@ -208,6 +456,22 @@ curl -X POST http://localhost:4313/api/canvas/ax/evidence \
208
456
  # Timeline — read the bounded timeline (default limit 50, max 200)
209
457
  curl "http://localhost:4313/api/canvas/ax/timeline?limit=50"
210
458
 
459
+ # Context reads — which canvas context each agent read, and whether its pinned
460
+ # nodes were in what came back (newest first, default 50, max 500; the per-consumer
461
+ # summary covers the whole retained log of 5,000 reads). Agent reads of pinned
462
+ # context, AX context, AX state, summary, spatial context and layout are recorded
463
+ # automatically; the workbench's own reads are not.
464
+ curl "http://localhost:4313/api/canvas/ax/context-reads?limit=50"
465
+
466
+ # Context reads — a proxy (an MCP server attached to this daemon, a host adapter)
467
+ # records the read its agent actually made. Its own fetches send
468
+ # `x-pmx-proxied-read: 1` so they are not recorded twice.
469
+ # Pass boardId captured at read time (null for Home); omitting it uses the
470
+ # currently open board. MCP bytes measure the final formatted text response.
471
+ curl -X POST http://localhost:4313/api/canvas/ax/context-reads \
472
+ -H "Content-Type: application/json" \
473
+ -d '{"channel":"adapter","resource":"copilot:prompt-context","consumer":"copilot","pinnedNodeIds":["node-1"],"deliveredNodeIds":["node-1"],"bytes":2048}'
474
+
211
475
  # Canvas-bound — add / update a work item
212
476
  curl -X POST http://localhost:4313/api/canvas/ax/work \
213
477
  -H "Content-Type: application/json" \
@@ -341,13 +605,20 @@ When that session ends (`attached: false`, `session-end`, or the idle expiry)
341
605
  the stream carries one `agent-session-ended` frame:
342
606
 
343
607
  ```json
344
- { "label": "Copilot", "endedAt": "…", "counts": { "items": 4, "done": 3, "vetoed": 1 },
608
+ { "label": "Copilot", "parentAgentId": null, "endedAt": "…", "endedBy": "agent",
609
+ "unchanged": false, "counts": { "items": 4, "done": 3, "cancelled": 1, "rejected": 0, "held": 0 },
345
610
  "snapshot": { "id": "…", "name": "Before session · Copilot · 14:00" } }
346
611
  ```
347
612
 
348
- `items`/`done` count the work items on the board; `vetoed` counts cancelled
349
- items plus rejected or held gates. `snapshot` is null when the board was
350
- empty at attach. The browser renders this as the session receipt; its *View
613
+ `items`/`done` count the work items on the board; cancelled items, rejected
614
+ gates, and held gates have separate counts. `snapshot` is null when the board was
615
+ empty at attach or unchanged at end (the redundant snapshot is removed).
616
+ The browser shows a receipt only for a changed top-level session: `unchanged: true`
617
+ and worker endings (`parentAgentId` set) never open or replace a popup. An empty
618
+ board that stays empty is unchanged too. This only filters the popup: session-end
619
+ events, timeline records, and changed-session snapshots remain available.
620
+ There is at most one receipt, replaced by the next qualifying ending and kept until
621
+ dismissed so restore/diff actions do not disappear on a timer. Its *View
351
622
  diff* is `GET /api/canvas/snapshots/<id>/diff` against that snapshot, and
352
623
  restoring the snapshot undoes the session. Adapters should end their session
353
624
  explicitly so the human gets the receipt promptly rather than after the idle