pmx-canvas 0.1.36 → 0.2.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 (91) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/Readme.md +2 -2
  3. package/dist/json-render/index.js +89 -334
  4. package/dist/types/mcp/canvas-access.d.ts +5 -171
  5. package/dist/types/server/ax-state-manager.d.ts +267 -0
  6. package/dist/types/server/ax-state.d.ts +3 -1
  7. package/dist/types/server/canvas-db.d.ts +13 -0
  8. package/dist/types/server/canvas-operations.d.ts +1 -12
  9. package/dist/types/server/canvas-state.d.ts +8 -23
  10. package/dist/types/server/index.d.ts +6 -24
  11. package/dist/types/server/operations/composites.d.ts +121 -0
  12. package/dist/types/server/operations/http.d.ts +7 -0
  13. package/dist/types/server/operations/index.d.ts +8 -0
  14. package/dist/types/server/operations/invoker.d.ts +13 -0
  15. package/dist/types/server/operations/mcp.d.ts +15 -0
  16. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  17. package/dist/types/server/operations/ops/app.d.ts +33 -0
  18. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  19. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  20. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  21. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  23. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  24. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  25. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  26. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  27. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  28. package/dist/types/server/operations/ops/query.d.ts +2 -0
  29. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  30. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  31. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  32. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  33. package/dist/types/server/operations/registry.d.ts +15 -0
  34. package/dist/types/server/operations/types.d.ts +116 -0
  35. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  36. package/docs/RELEASE.md +5 -0
  37. package/docs/adr-001-bun-only-runtime.md +46 -0
  38. package/docs/api-stability.md +57 -0
  39. package/docs/ax-host-adapter-contract.md +19 -1
  40. package/docs/ax-state-contract.md +72 -0
  41. package/docs/http-api.md +4 -0
  42. package/docs/mcp.md +61 -12
  43. package/docs/plans/plan-005-operation-registry.md +84 -0
  44. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  45. package/docs/plans/plan-007-ax-domain.md +99 -0
  46. package/docs/plans/plan-008-registry-finish.md +91 -0
  47. package/docs/tech-debt-assessment-2026-06.md +90 -0
  48. package/package.json +3 -3
  49. package/skills/pmx-canvas/SKILL.md +221 -193
  50. package/skills/pmx-canvas/evals/evals.json +3 -3
  51. package/skills/pmx-canvas/references/ax-html-control-surface.md +93 -0
  52. package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
  53. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +26 -11
  54. package/src/cli/agent.ts +52 -31
  55. package/src/mcp/canvas-access.ts +30 -830
  56. package/src/mcp/server.ts +162 -2014
  57. package/src/server/ax-context.ts +8 -1
  58. package/src/server/ax-state-manager.ts +826 -0
  59. package/src/server/ax-state.ts +10 -2
  60. package/src/server/canvas-db.ts +35 -0
  61. package/src/server/canvas-operations.ts +2 -328
  62. package/src/server/canvas-schema.ts +2 -2
  63. package/src/server/canvas-state.ts +103 -465
  64. package/src/server/index.ts +54 -190
  65. package/src/server/operations/composites.ts +355 -0
  66. package/src/server/operations/http.ts +103 -0
  67. package/src/server/operations/index.ts +65 -0
  68. package/src/server/operations/invoker.ts +87 -0
  69. package/src/server/operations/mcp.ts +221 -0
  70. package/src/server/operations/ops/annotation.ts +60 -0
  71. package/src/server/operations/ops/app.ts +447 -0
  72. package/src/server/operations/ops/ax-await.ts +216 -0
  73. package/src/server/operations/ops/ax-shared.ts +38 -0
  74. package/src/server/operations/ops/ax-state.ts +249 -0
  75. package/src/server/operations/ops/ax-timeline.ts +381 -0
  76. package/src/server/operations/ops/ax-work.ts +635 -0
  77. package/src/server/operations/ops/batch.ts +365 -0
  78. package/src/server/operations/ops/edges.ts +166 -0
  79. package/src/server/operations/ops/groups.ts +176 -0
  80. package/src/server/operations/ops/json-render.ts +691 -0
  81. package/src/server/operations/ops/nodes.ts +1047 -0
  82. package/src/server/operations/ops/query.ts +281 -0
  83. package/src/server/operations/ops/snapshots.ts +366 -0
  84. package/src/server/operations/ops/validate.ts +37 -0
  85. package/src/server/operations/ops/viewport.ts +219 -0
  86. package/src/server/operations/ops/webview.ts +339 -0
  87. package/src/server/operations/registry.ts +79 -0
  88. package/src/server/operations/types.ts +150 -0
  89. package/src/server/operations/webview-runner.ts +77 -0
  90. package/src/server/server.ts +158 -2255
  91. package/src/server/web-artifacts.ts +6 -2
@@ -276,7 +276,7 @@ The CLI targets `http://localhost:4313` by default. Override with `PMX_CANVAS_UR
276
276
  | `graph` | Native chart panel | Line, bar, pie, area, scatter, radar, stacked-bar, composed, plus Tufte primitives (sparkline, dot-plot, bullet, slopegraph) rendered inside the canvas |
277
277
  | `html` | Sandboxed HTML+JS document | Self-contained HTML with optional inline `<script>` and CDN imports rendered in a sandbox-restricted iframe; canvas theme tokens are auto-injected |
278
278
  | `group` | Spatial container/frame | Visually group related nodes together |
279
- | `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations. **Internal type — surfaces in `canvas://layout` for thread rendering but is not created via the public `canvas_add_node` API. Don't try to add one directly.** |
279
+ | `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations. **Internal type — surfaces in `canvas://layout` for thread rendering but is not created via the public `canvas_node { action: "add" }` API. Don't try to add one directly.** |
280
280
  | `response` | Prompt reply / streamed answer | Agent responses linked to prompt threads. **Same internal-only restriction as `prompt`.** |
281
281
 
282
282
  ### Edge Types
@@ -328,56 +328,100 @@ Use color consistently to convey meaning:
328
328
 
329
329
  ## MCP Tools Reference
330
330
 
331
+ PMX Canvas exposes **~21 composable tools**: 12 action-discriminated **composites** (the
332
+ recommended surface) plus a set of first-class standalones. The composites fold the older
333
+ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) discriminator —
334
+ **field names are unchanged**; only the tool name + the `action`/`kind` selector differ.
335
+
336
+ > **Legacy single-purpose tools are Deprecated.** The old names (`canvas_add_node`,
337
+ > `canvas_update_node`, `canvas_request_approval`, `canvas_add_work_item`, …) still work but are
338
+ > marked `Deprecated:` and are **removed in v0.3**. Prefer the composites. The authoritative
339
+ > legacy→composite mapping table lives in [`docs/mcp.md`](../../docs/mcp.md) — this skill does not
340
+ > re-enumerate the deprecated names.
341
+
342
+ ### The 12 composites
343
+
344
+ | Composite | `action` values | What it does |
345
+ |-----------|-----------------|--------------|
346
+ | `canvas_node` | `add` · `get` · `update` · `remove` | Create / read / mutate / delete a node |
347
+ | `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` | Schema introspection, dry-run validation, and native json-render / graph node creation |
348
+ | `canvas_edge` | `add` · `remove` | Connect / disconnect nodes |
349
+ | `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
350
+ | `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
351
+ | `canvas_view` | `arrange` · `focus` · `fit` · `clear` | Auto-arrange, pan-to-node, fit viewport, clear the board |
352
+ | `canvas_query` | `search` · `layout` | Find nodes by keyword, or read full canvas state |
353
+ | `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` | Read AX state; set AX focus; patch tool/prompt policy; report host capability |
354
+ | `canvas_ax_work` | `add` · `update` · `annotate` | Canvas-bound work items + review annotations |
355
+ | `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | The human-decision gate machine (request → await → resolve) |
356
+ | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` | The bounded AX diagnostics timeline |
357
+ | `canvas_ax_delivery` | `claim` · `mark` | Adapterless steering delivery (claim → act → mark) |
358
+
359
+ Call shape examples: `canvas_node { action: "add", type, title }`,
360
+ `canvas_view { action: "focus", id }`, `canvas_group { action: "create", childIds }`,
361
+ `canvas_render { action: "add-graph", graphType, data }`,
362
+ `canvas_query { action: "search", query }`,
363
+ `canvas_ax_work { action: "update", id, status }`.
364
+
365
+ `canvas_ax_gate` takes **two** discriminators, `{ kind, action }` — e.g.
366
+ `{ kind: "approval", action: "request", title }`,
367
+ `{ kind: "elicitation", action: "resolve", id, response }`,
368
+ `{ kind: "mode", action: "await", id, timeoutMs }`. (The approval machine-readable action
369
+ identifier is passed as `approvalAction`, since `action` is the lifecycle discriminator.)
370
+
371
+ ### Standalones (first-class — not deprecated)
372
+
373
+ These stay separate by design (trust-boundary, firehose, execution-intent, or not-yet-consolidated
374
+ surfaces): `canvas_batch`, `canvas_pin_nodes`, `canvas_screenshot`, `canvas_build_web_artifact`,
375
+ `canvas_open_mcp_app`, `canvas_add_diagram`, `canvas_add_html_node`, `canvas_add_html_primitive`,
376
+ `canvas_refresh_webpage_node`, `canvas_remove_annotation`, `canvas_ax_interaction`,
377
+ `canvas_ingest_activity`, `canvas_invoke_command`, the WebView tools
378
+ (`canvas_webview_start` / `canvas_webview_status` / `canvas_webview_stop`, `canvas_resize`,
379
+ `canvas_evaluate`), and the snapshot tools (`canvas_snapshot`, `canvas_list_snapshots`,
380
+ `canvas_restore`, `canvas_delete_snapshot`, `canvas_gc_snapshots`, `canvas_diff` — a
381
+ `canvas_snapshot` composite is deferred to v0.3).
382
+
331
383
  ### Node Operations
332
384
 
333
- MCP node-type routing:
385
+ MCP node-type routing — which tool creates which node category:
334
386
 
335
- | Node category | MCP creation tool |
387
+ | Node category | MCP creation call |
336
388
  |---------------|-------------------|
337
- | Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_add_node` |
338
- | `json-render` | `canvas_add_json_render_node` |
339
- | `graph` | `canvas_add_graph_node` |
340
- | `html-primitive` | `canvas_add_html_primitive` |
341
- | `html` | `canvas_add_html_node` |
342
- | `web-artifact` | `canvas_build_web_artifact` |
343
- | `external-app` / tool-backed `mcp-app` | `canvas_open_mcp_app` |
344
- | `group` | `canvas_create_group` |
345
-
346
- If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` and read
347
- `mcp.nodeTypeRouting`; do not keep retrying the generic tool.
348
-
349
- **`canvas_add_node`** — Add a node to the canvas
389
+ | Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_node { action: "add" }` |
390
+ | `json-render` | `canvas_render { action: "add-json-render" }` |
391
+ | `graph` | `canvas_render { action: "add-graph" }` |
392
+ | `html-primitive` | `canvas_add_html_primitive` (standalone) |
393
+ | `html` | `canvas_add_html_node` (standalone) |
394
+ | `web-artifact` | `canvas_build_web_artifact` (standalone) |
395
+ | `external-app` / tool-backed `mcp-app` | `canvas_open_mcp_app` (standalone) |
396
+ | `group` | `canvas_group { action: "create" }` |
397
+
398
+ If a node type is rejected by `canvas_node { action: "add" }`, call
399
+ `canvas_render { action: "describe-schema" }` and read `mcp.nodeTypeRouting`; do not keep
400
+ retrying the generic add.
401
+
402
+ **`canvas_node { action: "add", … }`** — Add a node to the canvas
350
403
  - `type` (required): basic node type only; structured/app/group nodes use the routing table above
351
404
  - `title`: short, scannable title
352
- - `content`: for most types, this is markdown text. For `file` type, pass the **file path**
353
- (e.g., `"src/auth/login.ts"`) — the server auto-loads the file content and watches for changes.
354
- For `image` type, pass a file path, URL, or data URI.
405
+ - `content`: markdown text for most types. For `file`, pass the **file path** (e.g. `"src/auth/login.ts"`) —
406
+ the server auto-loads + watches it. For `image`, pass a file path, URL, or data URI.
355
407
  - `path`: compatibility alias for image paths only; prefer `content` for new image calls
356
- - `x`, `y`: position (auto-placed if omitted — prefer omitting for auto-layout)
357
- - `width`, `height`: dimensions (sensible defaults provided)
358
- - `color`: semantic color
359
- - `metadata`: arbitrary JSON
408
+ - `x`, `y`: position (prefer omitting for auto-layout); `width`, `height`: dimensions (sensible defaults); `color`: semantic color; `metadata`: arbitrary JSON
360
409
  - Returns: `{ id: "<node-id>" }` — capture this ID for edges and groups
361
410
 
362
- **`canvas_update_node`** — Update an existing node
363
- - `id` (required): node to update
364
- - Any of: `title`, `content`, `x`, `y`, `width`, `height`, `collapsed`, `arrangeLocked`, `data`
365
- - For `json-render`, pass `spec` to update the rendered spec in place while preserving node ID, edges, pins, and position
366
- - For `graph`, pass graph fields such as `graphType`, `data`, `xKey`, `yKey`, `color`, and `chartHeight` to rebuild the chart in place; use `height`/`nodeHeight` for frame geometry and `chartHeight` for chart content in CLI flows
367
- - Use to update status nodes as work progresses
411
+ **`canvas_node { action: "update", id, … }`** — Update an existing node in place (preferred over
412
+ delete+recreate; preserves edges, pins, position)
413
+ - `id` (required), plus any of: `title`, `content`, `x`, `y`, `width`, `height`, `collapsed`, `arrangeLocked`, `data`
414
+ - For `json-render`, pass `spec` to update the rendered spec in place
415
+ - For `graph`, pass graph fields (`graphType`, `data`, `xKey`, `yKey`, `color`, `chartHeight`) to rebuild the chart; `height`/`nodeHeight` set frame geometry, `chartHeight` the chart content
368
416
 
369
- **`canvas_remove_node`** — Remove a node and all its connected edges
370
- - `id` (required): node to remove
371
- - Clean up nodes that are no longer relevant
417
+ **`canvas_node { action: "remove", id }`** — Remove a node and all its connected edges. Clean up nodes that are no longer relevant.
372
418
 
373
- **`canvas_remove_annotation`** — Remove a human-drawn annotation
374
- - `id` (required): annotation to remove
375
- - Use when context gives you the annotation ID; use WebView first if you need to identify a mark by shape or location.
419
+ **`canvas_node { action: "get", id }`** — Get a single node's full data by `id`.
376
420
 
377
- **`canvas_get_node`** — Get a single node's full data
378
- - `id` (required): node to retrieve
421
+ **`canvas_remove_annotation`** (standalone) Remove a human-drawn annotation by `id`. Use when
422
+ context gives you the annotation ID; use WebView first if you need to identify a mark by shape or location.
379
423
 
380
- **`canvas_refresh_webpage_node`** — Re-fetch the URL stored on a `webpage` node
424
+ **`canvas_refresh_webpage_node`** (standalone) — Re-fetch the URL stored on a `webpage` node
381
425
  - `id` (required): webpage node to refresh
382
426
  - Optional `url`: replace the stored URL before refreshing (use when the human moved the page)
383
427
  - Returns the refreshed node with updated `pageTitle` and cached extracted text
@@ -386,7 +430,7 @@ If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` a
386
430
 
387
431
  ```typescript
388
432
  // Add the page once
389
- canvas_add_node({ type: 'webpage', url: 'https://example.com/docs' })
433
+ canvas_node({ action: 'add', type: 'webpage', url: 'https://example.com/docs' })
390
434
  // → returns { id: 'node-abc' }
391
435
 
392
436
  // …later, after the human reopens the canvas…
@@ -394,7 +438,7 @@ If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` a
394
438
  // → re-fetches the URL, updates pageTitle + extracted text, keeps the node ID and position
395
439
  ```
396
440
 
397
- **`canvas_add_json_render_node`** — Add a native json-render node
441
+ **`canvas_render { action: "add-json-render", … }`** — Add a native json-render node
398
442
  - Required: `spec`; `title` is optional and inferred from the root element when omitted
399
443
  - Prefer a complete json-render object with `root`, `elements`, and optional `state`
400
444
  - Legacy bare component specs like `{ type: "Badge", props: {...} }` are accepted and wrapped into a one-element document for compatibility
@@ -403,18 +447,16 @@ If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` a
403
447
  `outline`. Legacy `props.label` and status variants (`success`, `info`, `warning`, `error`,
404
448
  `danger`) are normalized for saved-spec compatibility.
405
449
 
406
- **`canvas_stream_json_render_node`** — Build a json-render node progressively (live)
407
- - Omit `nodeId` on the first call to create a new streaming node it returns the node `id`
408
- - Pass that same `nodeId` on later calls to append more `patches`; set `done: true` on the final call
409
- - `patches` are SpecStream JSON-Patch ops applied server-side (the canvas accumulates the spec):
410
- `{ "op": "add", "path": "/elements/card", "value": { "type": "Card", "props": { "title": "Live" }, "children": [] } }`,
411
- `{ "op": "replace", "path": "/root", "value": "card" }`,
412
- `{ "op": "add", "path": "/elements/card/children/-", "value": "row1" }`
413
- - Build incrementally: set `/root`, add container elements, then append child element ids and elements
414
- - Each call re-renders the live node; partial specs render what they can. Use for dashboards/reports
415
- that should fill in as you generate them rather than appearing all at once.
416
-
417
- **`canvas_add_graph_node`** — Add a native graph/chart node
450
+ **`canvas_render { action: "stream-json-render", … }`** — Build a json-render node progressively (live)
451
+ - Omit `nodeId` on the first call to create a streaming node (returns its `id`); reuse that `nodeId`
452
+ on later calls to append `patches`; set `done: true` on the final call.
453
+ - `patches` are SpecStream JSON-Patch ops applied server-side (the canvas accumulates the spec), e.g.
454
+ `{ "op": "add", "path": "/elements/card", "value": { … } }`, `{ "op": "replace", "path": "/root", "value": "card" }`.
455
+ - Build incrementally: set `/root`, add container elements, then append child element ids/elements.
456
+ Each call re-renders; partial specs render what they can. Use for dashboards/reports that fill in
457
+ as you generate them rather than appearing all at once.
458
+
459
+ **`canvas_render { action: "add-graph", }`** Add a native graph/chart node
418
460
  - Required: `graphType`, `data`
419
461
  - Supports `line`, `bar`, `pie`, `area`, `scatter`, `radar`, `stacked-bar`, `composed`,
420
462
  and the Tufte primitives `sparkline`, `dot-plot`, `bullet`, `slopegraph` (aliases accepted)
@@ -444,7 +486,7 @@ the `tufte-viz` skill (`skills/tufte-viz/SKILL.md`). Key rules:
444
486
  - For more than ~4 overlapping series, build small multiples (several small graph nodes on a shared
445
487
  scale, arranged in a grid/group) instead of one multi-color chart.
446
488
 
447
- **`canvas_build_web_artifact`** — Build and optionally open a bundled web artifact
489
+ **`canvas_build_web_artifact`** (standalone) — Build and optionally open a bundled web artifact
448
490
  - Required: `title`, `appTsx` (source string contents, not a file path)
449
491
  - CLI `--app-file` reads a file before calling the same build path; MCP callers must pass the source contents
450
492
  - Cold builds commonly take 45-60 seconds; use a long client timeout such as 300000 ms or more
@@ -454,88 +496,60 @@ ID extraction for mixed tool responses:
454
496
  - Most add-style tools return a flat `id`; web artifacts return `id` plus `nodeId`; snapshots return `id` plus nested `snapshot.id`.
455
497
  - Defensive extractor: `const getId = (r) => r.id ?? r.nodeId ?? r.snapshot?.id;`
456
498
 
457
- **`canvas_open_mcp_app`** — Open a tool-backed external MCP app node
499
+ **`canvas_open_mcp_app`** (standalone) — Open a tool-backed external MCP app node
458
500
  - Required: `toolName`, `transport`
459
501
  - `transport` is either `{ type: "stdio", command, args?, cwd?, env? }` or `{ type: "http", url, headers? }`
460
502
  - This is lower-level than `pmx-canvas external-app add --kind excalidraw`; use `canvas_add_diagram` for the built-in Excalidraw preset
461
503
 
462
- **`canvas_pin_nodes`** — Set, add, or remove pinned context nodes
463
- - Use `{ nodeIds: [...] }`; the field is `nodeIds`, not `ids`
504
+ **`canvas_pin_nodes`** (standalone) — Set, add, or remove pinned context nodes. Use `{ nodeIds: [...] }` — the field is `nodeIds`, not `ids`.
464
505
 
465
- **`canvas_diff`** — Compare current canvas state with a saved snapshot
466
- - Requires `{ snapshot: "<snapshot-id-or-name>" }`; there is no implicit previous-snapshot default
506
+ **`canvas_diff`** (standalone) — Compare current canvas state with a saved snapshot. Requires `{ snapshot: "<snapshot-id-or-name>" }`; there is no implicit previous-snapshot default.
467
507
 
468
- **`canvas_describe_schema`** — Inspect the running server's create schemas and canonical examples
469
- - Use this before generating structured payloads when you need the authoritative current shape
470
- - Read `mcp.nodeTypeRouting` to choose the right MCP creation tool for each node category
508
+ **`canvas_render { action: "describe-schema" }`** — Inspect the running server's create schemas and
509
+ canonical examples. Use before generating structured payloads when you need the authoritative current
510
+ shape; read `mcp.nodeTypeRouting` to choose the right creation call for each node category.
471
511
 
472
- **`canvas_validate_spec`** — Validate a json-render spec or graph payload without creating a node
473
- - Returns the normalized json-render spec the server would accept
474
- - Use this when you want a dry run before creating a `json-render` or `graph` node
512
+ **`canvas_render { action: "validate", … }`** — Dry-run a json-render spec or graph payload without
513
+ creating a node. Returns the normalized json-render spec the server would accept.
475
514
 
476
- **`canvas_fit_view`** — Fit viewport to all nodes or selected nodes
477
- - Optional: `width`, `height`, `padding`, `maxScale`, `nodeIds`
478
- - Use before screenshot workflows or whole-board review so the server viewport matches the intended camera
515
+ **`canvas_view { action: "fit", … }`** — Fit viewport to all nodes or selected nodes; optional
516
+ `width`, `height`, `padding`, `maxScale`, `nodeIds`. Use before screenshot/whole-board review so the
517
+ server viewport matches the intended camera.
479
518
 
480
- **Batch graph creation**
481
- - Use `graph.add` inside `canvas_batch` / `pmx-canvas batch` when you need a graph node as part of
482
- a larger one-shot build.
483
- - It accepts the same shape as `canvas_add_graph_node`: `graphType`, `data`, optional `title`,
484
- `xKey`, `yKey`, `zKey`, `nameKey`, `valueKey`, `axisKey`, `metrics`, `series`, `barKey`,
485
- `lineKey`, `aggregate`, `color`, `barColor`, `lineColor`, `height`, `x`, `y`, `width`, and
486
- `nodeHeight`.
487
- - In batch/MCP/HTTP payloads, `height` is chart content height and `nodeHeight` is the canvas frame height.
519
+ **Batch graph creation** — Use the `graph.add` op inside `canvas_batch` / `pmx-canvas batch` for a
520
+ graph node in a larger one-shot build. It takes the same shape as `canvas_render { action: "add-graph" }`.
521
+ In batch/MCP/HTTP payloads, `height` is chart content height and `nodeHeight` is the canvas frame height.
488
522
 
489
523
  ### Edge Operations
490
524
 
491
- **`canvas_add_edge`** — Connect two nodes
525
+ **`canvas_edge { action: "add", … }`** — Connect two nodes
492
526
  - `from`, `to` (required): source and target node IDs
493
527
  - `fromSearch`, `toSearch`: optional search-based selectors when you do not have IDs. Each search
494
528
  query must resolve to exactly one node or the edge creation fails with an ambiguity error.
495
529
  - `type`: `flow`, `depends-on`, `relation`, or `references` (default: `relation`)
496
- - `label`: descriptive relationship label
497
- - `style`: `solid`, `dashed`, or `dotted`
498
- - `animated`: boolean for visual emphasis
499
-
500
- **`canvas_remove_edge`** — Remove a connection
501
- - `id` (required): edge to remove
530
+ - `label`: descriptive relationship label; `style`: `solid`/`dashed`/`dotted`; `animated`: visual emphasis
531
+ - `canvas_edge { action: "remove", id }` removes a connection by edge `id`.
502
532
 
503
533
  ### Layout & Navigation
504
534
 
505
- **`canvas_get_layout`** — Get full canvas state (all nodes, edges, viewport)
506
- - Use to understand current canvas state before making changes
507
-
508
- **`canvas_arrange`** Auto-arrange all nodes
509
- - `layout`: `grid` (default), `column`, or `flow`
510
- - Use `grid` for dashboards and architecture overviews, `column` for vertical lists, `flow`
511
- for horizontal sequences and dependency chains
512
- - Call after adding multiple nodes
513
- - For tiered/layered layouts (e.g., gateway → services → data stores), use `canvas_update_node`
514
- with explicit `x`/`y` coordinates after auto-arrange to fine-tune the topology
515
-
516
- **`canvas_focus_node`** — Pan viewport to center on a specific node
517
- - `id` (required): node to focus
518
- - Good for drawing the human's attention
519
- - Avoid focusing every node in a batch. Focus only the final result or use CLI `focus --no-pan`
520
- when the goal is selection/raising without camera movement.
535
+ - **`canvas_query { action: "layout" }`** — full canvas state (nodes, edges, viewport). Read before mutating.
536
+ - **`canvas_view { action: "arrange", layout }`** auto-arrange all nodes; `layout` is `grid` (default,
537
+ dashboards/overviews), `column` (vertical lists), or `flow` (horizontal sequences / dependency chains).
538
+ Call once after a batch of adds. For tiered/layered layouts, fine-tune with explicit `x`/`y` via
539
+ `canvas_node { action: "update" }` after arranging.
540
+ - **`canvas_view { action: "focus", id }`** pan the viewport to a node. Don't focus every node in a
541
+ batch focus only the final result, or use CLI `focus --no-pan` to select/raise without moving the camera.
521
542
 
522
543
  ### Groups
523
544
 
524
- **`canvas_create_group`** — Create a visual container
525
- - `title`: group label
526
- - `childIds`: array of node IDs to include
527
- - `color`: group border/background color
528
- - Auto-sizes to fit children
529
-
530
- **`canvas_group_nodes`** — Add nodes to an existing group
531
- - `groupId`, `childIds` (required)
532
-
533
- **`canvas_ungroup`** — Release children from a group
534
- - `groupId` (required): group to dissolve
545
+ - **`canvas_group { action: "create", … }`** — visual container; `title`, `childIds` (node IDs), `color`. Auto-sizes to fit children.
546
+ - **`canvas_group { action: "add", groupId, childIds }`** — add nodes to an existing group.
547
+ - **`canvas_group { action: "ungroup", groupId }`** — release all children from a group.
535
548
 
536
549
  ### Group Layout Guidance
537
550
 
538
- Use groups as spacious semantic regions, not as tight containers.
551
+ Use groups as spacious semantic regions, not as tight containers. (Group calls below use
552
+ `canvas_group { action: "create" | "add" | "ungroup" }`.)
539
553
 
540
554
  - Size the child nodes first, especially `graph`, `json-render`, `mcp-app`, image, and webpage
541
555
  nodes whose rendered content may need more height than their visible title suggests.
@@ -548,7 +562,7 @@ Use groups as spacious semantic regions, not as tight containers.
548
562
  tight groups make the canvas harder to read than no groups.
549
563
  - Keep edges local to a group where possible. Long cross-board edges can look like they come from
550
564
  nowhere; use a nearby bridge/context node or split the relationship into shorter labeled edges.
551
- - After grouping, verify the result in `canvas_get_layout` or the browser: child nodes should be
565
+ - After grouping, verify the result in `canvas_query { action: "layout" }` or the browser: child nodes should be
552
566
  fully inside the group with padding, visible nodes should not overlap, and group headers should
553
567
  not cover content.
554
568
  - If a group makes important content less visible, enlarge the group, split it into clearer
@@ -558,8 +572,8 @@ Use groups as spacious semantic regions, not as tight containers.
558
572
 
559
573
  Use groups as named comparison areas, not just visual boxes.
560
574
 
561
- - Create the comparison frame first with `canvas_create_group` or `pmx-canvas group create`, then
562
- add charts, artifacts, and diagrams into that area deliberately.
575
+ - Create the comparison frame first with `canvas_group { action: "create" }` or
576
+ `pmx-canvas group create`, then add charts, artifacts, and diagrams into that area deliberately.
563
577
  - Prefer graph nodes for fast capability demos and side-by-side comparisons. They are lightweight,
564
578
  validate quickly, and are easier to regenerate.
565
579
  - Prefer web artifacts when the board needs a richer narrative UI, custom interaction, or a more
@@ -588,42 +602,31 @@ Current product caveats for grouped comparison boards:
588
602
 
589
603
  ### Search & Discovery
590
604
 
591
- **`canvas_search`** — Find nodes by title or content keywords
592
- - `query` (required): search text
593
- - Returns matches with relevance ranking and content snippets
594
- - Use instead of parsing full layout when looking for specific nodes
605
+ **`canvas_query { action: "search", query }`** — Find nodes by title or content keywords. Returns
606
+ ranked matches with content snippets. Use instead of parsing the full layout to locate specific nodes.
595
607
 
596
608
  ### Context Pinning
597
609
 
598
- **`canvas_pin_nodes`** — Manage pinned context
599
- - `nodeIds` (required): array of node IDs
600
- - `mode`: `set` (replace all pins), `add` (add to pins), `remove` (remove from pins)
601
- - Pinned nodes are the primary human-to-agent communication channel
602
- - When a human pins nodes in the browser, they're telling you "pay attention to these"
603
- - Best default pin set for agent collaboration: one intent-setting markdown node plus 1-3 concrete
604
- output nodes
605
- - Graph, file, and markdown pins usually carry richer usable context than `mcp-app` pins
606
- - Artifact and Excalidraw pins still matter as intent signals, but pair them with a markdown or
607
- graph pin when you want the agent to understand what is inside the app, not just that it matters
610
+ **`canvas_pin_nodes`** (standalone) — Manage pinned context: `nodeIds` (required) plus `mode`
611
+ (`set` replaces all pins, `add`, `remove`).
612
+ - Pinned nodes are the primary human-to-agent communication channel when a human pins in the
613
+ browser, they're saying "pay attention to these."
614
+ - Best default pin set: one intent-setting markdown node plus 1-3 concrete output nodes.
615
+ - Graph, file, and markdown pins carry richer usable context than `mcp-app` pins. Artifact and
616
+ Excalidraw pins still matter as intent signals, but pair them with a markdown or graph pin so the
617
+ agent understands what is inside the app, not just that it matters.
608
618
 
609
619
  ### History & Snapshots
610
620
 
611
- **`canvas_undo`** — Undo the last canvas mutation
612
- **`canvas_redo`** — Redo the last undone mutation
613
- **`canvas_snapshot`** — Save a named snapshot to disk
614
- - `name` (required): descriptive snapshot name (e.g., "before-refactor")
615
- - Returns `{ ok, id, snapshot }`; the flat `id` is an alias for `snapshot.id`
616
- **`canvas_restore`** — Restore canvas from a saved snapshot
617
- - `id`: snapshot to restore
618
- **`canvas_diff`** — Compare current canvas against a saved snapshot
619
- - Shows added, removed, and modified nodes/edges
620
- - Useful for reviewing what changed during a work session
621
+ - **`canvas_history { action: "undo" }`** / **`canvas_history { action: "redo" }`** step the mutation ring buffer.
622
+ - **`canvas_snapshot`** (standalone) save a named snapshot; `name` required. Returns `{ ok, id, snapshot }` (flat `id` aliases `snapshot.id`).
623
+ - **`canvas_restore`** (standalone) restore from a snapshot `id`.
624
+ - **`canvas_diff`** (standalone) compare current canvas against a saved snapshot (added/removed/modified nodes & edges).
621
625
 
622
626
  ### Canvas Management
623
627
 
624
- **`canvas_clear`** — Remove all nodes and edges
625
- - **Always call `canvas_snapshot` first** to save a backup before clearing
626
- - This is irreversible without a prior snapshot
628
+ **`canvas_view { action: "clear" }`** — Remove all nodes and edges. **Always `canvas_snapshot` first** —
629
+ this is irreversible without a prior snapshot.
627
630
 
628
631
  ### Browser Automation (WebView)
629
632
 
@@ -670,7 +673,7 @@ Useful workbench selectors:
670
673
  the eraser toolbar button; agents can remove a known annotation ID with
671
674
  `canvas_remove_annotation`.
672
675
  - Canvas chrome: `.hud-layer`, `.canvas-toolbar`, `.connection-dot`, `.canvas-bootstrap-card`
673
- - Nodes do not expose stable `data-node-id` attributes. Use `canvas_get_layout`, `canvas_search`, or MCP resource data for exact node IDs.
676
+ - Nodes do not expose stable `data-node-id` attributes. Use `canvas_query` (`layout` / `search`) or MCP resource data for exact node IDs.
674
677
 
675
678
  Async script example:
676
679
 
@@ -748,7 +751,7 @@ server's `ui://` resource as an iframe node on the canvas
748
751
 
749
752
  ### HTML Nodes (Sandboxed iframe)
750
753
 
751
- **`canvas_add_html_node`** — Add a normal self-contained HTML document rendered in a sandboxed iframe
754
+ **`canvas_add_html_node`** (standalone) — Add a normal self-contained HTML document rendered in a sandboxed iframe
752
755
  - Required: `html` (full document or fragment; inline `<script>` and CDN `<script src="...">` are allowed). If `html` is a bare path to an existing local `.html`/`.htm` file, the server reads that file's contents; otherwise it is treated as raw HTML.
753
756
  - Optional: `title`, `summary`, `agentSummary`, `presentation`, `slideTitles`, `embeddedNodeIds`, `embeddedUrls`, `x`, `y`, `width` (default 720), `height` (default 640), `strictSize`
754
757
  - Iframe sandbox is `allow-scripts` only — no same-origin access, no top-navigation, no forms
@@ -758,12 +761,11 @@ server's `ui://` resource as an iframe node on the canvas
758
761
  - Only presentation-marked HTML nodes expose a browser `Present` button. Use it when the HTML is a deck, briefing, or fullscreen review surface; the PMX shell owns the fullscreen overlay and exits via `Esc` or `Exit presentation`.
759
762
  - PMX stores a semantic sidecar (`agentSummary`, `contentSummary`, embedded references) so HTML nodes remain understandable in search, pinned context, and spatial context
760
763
 
761
- **`canvas_add_html_primitive`** — Generate a reusable HTML communication primitive as a sandboxed `html` node
762
- - Required: `kind`; run `canvas_describe_schema` and read `htmlPrimitives` for the current catalog
764
+ **`canvas_add_html_primitive`** (standalone) — Generate a reusable HTML communication primitive as a sandboxed `html` node
765
+ - Required: `kind`; run `canvas_render { action: "describe-schema" }` and read `htmlPrimitives` for the current catalog
763
766
  - Optional: `title`, `data`, `x`, `y`, `width`, `height`, `strictSize`
764
767
  - Use when markdown would be too dense and a structured visual artifact is clearer: tradeoff grids, implementation plans, PR reviews, module maps, design sheets, explainers, reports, and lightweight human-editable boards/editors
765
768
  - When the human asks for a PowerPoint-like output, pitch deck, briefing, or presentation, use `kind: "presentation"` unless a bespoke raw HTML deck is required. Include `slides` with short titles, one idea per slide, optional `metrics`, `note` fields for speaker notes, and optional `theme: "canvas" | "midnight" | "paper" | "aurora"` or a custom theme object.
766
- - Read `htmlPrimitives` from `canvas_describe_schema` for the data shape and examples before constructing a payload
767
769
  - For payload patterns, export loops, and the primitive catalog, read `references/html-primitives.md` before creating dense or editable artifacts
768
770
 
769
771
  ### Open as Site (standalone surfaces)
@@ -782,9 +784,9 @@ the host's embedded browser (e.g. Codex) opens `_blank` tabs in-place.
782
784
  in-canvas iframe loads the **exact same URL**, so there is one render path and no
783
785
  separate "preview" version — what you see in the canvas is what opens. The URL reflects
784
786
  current node state and survives a refresh.
785
- - Agents can read this URL from any node payload (`canvas_get_node` / `canvas_get_layout`)
786
- as `surfaceUrl` — a reliable way to tell a human "open the artifact" without disturbing
787
- the canvas.
787
+ - Agents can read this URL from any node payload (`canvas_node { action: "get" }` /
788
+ `canvas_query { action: "layout" }`) as `surfaceUrl` — a reliable way to tell a human
789
+ "open the artifact" without disturbing the canvas.
788
790
  - Served HTML stays sandboxed (opaque origin via a `Content-Security-Policy: sandbox`
789
791
  response header), so opening author code top-level cannot reach the canvas origin.
790
792
  - ext-app `mcp-app` nodes open their UI, but interactive tool-calls only work inside the
@@ -798,7 +800,7 @@ When the output is more than markdown, pick the lightest tier that fits:
798
800
 
799
801
  | Tier | Tool | Build cost | When to pick it |
800
802
  |------|------|------------|-----------------|
801
- | Declarative UI | `canvas_add_json_render_node` / `canvas_add_graph_node` | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via `canvas_get_node` |
803
+ | Declarative UI | `canvas_render` (`add-json-render` / `add-graph`) | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via `canvas_node { action: "get" }` |
802
804
  | Generated HTML primitive | `canvas_add_html_primitive` | None | Reusable communication artifacts such as choices, plans, reviews, maps, reports, presentations/decks, and lightweight editors |
803
805
  | Sandboxed HTML+JS | `canvas_add_html_node` | None | Self-contained HTML with inline JS or CDN scripts; one-off visualizations or report views |
804
806
  | Hosted MCP app | `canvas_open_mcp_app` / `canvas_add_diagram` | None | Interactive editors backed by an external MCP server (e.g. Excalidraw) |
@@ -808,8 +810,8 @@ When the output is more than markdown, pick the lightest tier that fits:
808
810
 
809
811
  Use native `json-render` and `graph` nodes when the output should stay fully inside PMX Canvas:
810
812
 
811
- 1. Use `canvas_add_json_render_node` for dashboards, forms, summaries, and interactive UI panels
812
- 2. Use `canvas_add_graph_node` for charts and trend visualizations
813
+ 1. Use `canvas_render { action: "add-json-render" }` for dashboards, forms, summaries, and interactive UI panels
814
+ 2. Use `canvas_render { action: "add-graph" }` for charts and trend visualizations
813
815
  3. Use the repo-local `json-render-*` skills when you need help authoring or refining the spec itself
814
816
  4. Use `canvas_build_web_artifact` instead when the result needs a full custom React app rather than a schema-driven UI
815
817
 
@@ -870,54 +872,66 @@ nodes.
870
872
  re-validates capabilities regardless of transport — bridges are convenience,
871
873
  not a trust boundary.
872
874
  - **Delivery (adapterless):** `canvas://ax-pending-steering` /
873
- `canvas_claim_ax_delivery` return two things, both loop-safe (a consumer never
875
+ `canvas_ax_delivery { action: "claim" }` return two things, both loop-safe (a consumer never
874
876
  receives items it originated):
875
877
  - `pending` — undelivered **steering** (directives). Act, then acknowledge with
876
- `canvas_mark_ax_delivery`.
878
+ `canvas_ax_delivery { action: "mark" }`.
877
879
  - `pendingActivity` — open canvas-bound items **awaiting the agent** (open work
878
880
  items, pending approval gates / elicitations / mode requests), usually created
879
881
  by the human in the browser. These are **state, not steering**: don't
880
- `canvas_mark_ax_delivery` them — resolve each via its own tool
881
- (`canvas_resolve_approval` / `canvas_respond_elicitation` /
882
- `canvas_resolve_mode` / `canvas_update_work_item`).
882
+ `canvas_ax_delivery { action: "mark" }` them — resolve each via its gate/work call
883
+ (`canvas_ax_gate { kind: "approval", action: "resolve" }` /
884
+ `canvas_ax_gate { kind: "elicitation", action: "resolve" }` /
885
+ `canvas_ax_gate { kind: "mode", action: "resolve" }` /
886
+ `canvas_ax_work { action: "update" }`).
883
887
  - **Contract:** every AX mutation fires `ax-state-changed`, so MCP clients that
884
888
  **subscribe** to resources are pushed `canvas://ax-work` / `canvas://ax-context`
885
- live. Clients that **poll** instead should poll `canvas_claim_ax_delivery` —
889
+ live. Clients that **poll** instead should poll `canvas_ax_delivery { action: "claim" }` —
886
890
  `pendingActivity` is how non-steering browser changes reach them. Only steering
887
891
  flows through the claim/ack queue.
888
- - **Steering is gated, not pushed.** A surface button that emits `ax.steer`
889
- enqueues a steer it does NOT wake the agent. With a prompt-injecting host
890
- adapter (e.g. Copilot), it reaches the next turn only when (1) the **pin/focus
891
- gate is open** (something pinned or focused so keep a steering board pinned, or
892
- have its button also emit `ax.focus.set` on itself), (2) a **human message** fires
893
- the turn, and (3) the agent **acts then acks** (`canvas_mark_ax_delivery`), or the
894
- steer re-injects every gated turn. `GET /api/canvas/ax/context?consumer=<id>` adds
895
- a compact, loop-safe `delivery: { pendingSteering, pendingActivity }` lead block an
896
- adapter can inject un-truncated, so steering survives the full-context char clip.
892
+ - **Steering is gated, not pushed (and does NOT wake the agent).** A surface button
893
+ that emits `ax.steer` *records/queues* a steer (the `ok:true` emit ack means
894
+ "recorded", not "the agent woke") it does NOT interrupt or notify the active
895
+ session. With a prompt-injecting host adapter (e.g. Copilot), it reaches the next
896
+ turn only when (1) the **pin/focus gate is open** (something pinned or focused — so
897
+ keep a steering board pinned, or have its button also emit `ax.focus.set` on
898
+ itself), (2) a **human message** fires the turn, and (3) the agent **acts then acks**
899
+ (`canvas_ax_delivery { action: "mark" }`), or the steer re-injects every gated turn.
900
+ Immediate wake (turning a queued steer into a visible turn) is **host-adapter-owned**
901
+ — the adapter must drain `canvas_ax_delivery { action: "claim" }` and call its native
902
+ send. So a steering button should label itself honestly ("queued for the agent's next
903
+ turn"), never imply it steers the agent *now*. `GET /api/canvas/ax/context?consumer=<id>`
904
+ adds a compact, loop-safe `delivery: { pendingSteering, totalPending, omittedPending,
905
+ pendingActivity }` lead block an adapter injects un-truncated; `pendingSteering` is
906
+ **newest-first** (most recent first), capped at 10, so a fresh steer is visible even
907
+ behind a backlog, and the counts say how many more to drain from the FIFO
908
+ `canvas_ax_delivery { action: "claim" }` queue (which stays oldest-first).
897
909
  - **Activity ingestion (bidirectional board):** a host adapter forwards the agent's
898
- tool/session events with `canvas_ingest_activity` (HTTP `POST /api/canvas/ax/activity`)
910
+ tool/session events with `canvas_ingest_activity` (standalone; HTTP `POST /api/canvas/ax/activity`)
899
911
  and the board auto-reacts — `failure`/`error` (or `outcome:"failure"`) → a blocked
900
912
  work item + a review finding + `logs` evidence; `tool-result` + `outcome:"success"` →
901
913
  `tool-result` evidence; everything else records a timeline event only. Override or
902
914
  suppress per call via `reactions` (`{ workItem: false }`, `{ review: { severity } }`, …).
903
- - **Blocking gates (gates that actually gate):** after requesting an approval /
904
- elicitation / mode, `canvas_await_approval` / `canvas_await_elicitation` /
905
- `canvas_await_mode` (HTTP `GET /api/canvas/ax/<kind>/<id>?waitMs=`) BLOCK until the
906
- human resolves it in the browser or the timeout elapses (`timeoutMs` 0 = immediate
907
- read; ≤120000). Use this to pause real work on a human decision instead of polling.
915
+ - **Blocking gates (gates that actually gate):** `canvas_ax_gate` is the request
916
+ await resolve machine. After `{ action: "request" }`, call
917
+ `canvas_ax_gate { kind, action: "await", id, timeoutMs }` (HTTP
918
+ `GET /api/canvas/ax/<kind>/<id>?waitMs=`) to BLOCK until the human resolves it in the
919
+ browser or the timeout elapses (`timeoutMs` 0 = immediate read; ≤120000). Use this to
920
+ pause real work on a human decision instead of polling.
908
921
  - **Elicitation / mode:** request structured human input
909
- (`canvas_request_elicitation` `canvas_respond_elicitation`) or a workflow
910
- mode transition (`canvas_request_mode` `canvas_resolve_mode`); both are
911
- canvas-bound and snapshotted.
922
+ (`canvas_ax_gate { kind: "elicitation", action: "request" }` →
923
+ `canvas_ax_gate { kind: "elicitation", action: "resolve" }`) or a workflow
924
+ mode transition (`canvas_ax_gate { kind: "mode", action: "request" }` →
925
+ `canvas_ax_gate { kind: "mode", action: "resolve" }`); both are canvas-bound and snapshotted.
912
926
  - **Commands:** invoke a registry command — `pmx.plan`, `pmx.execute`,
913
927
  `pmx.promote-context`, `pmx.summarize`, `pmx.review` — via
914
- `canvas_invoke_command` (HTTP `POST /api/canvas/ax/command`; CLI
928
+ `canvas_invoke_command` (standalone; HTTP `POST /api/canvas/ax/command`; CLI
915
929
  `pmx-canvas ax command invoke`; envelope `ax.command.invoke`). Unknown names
916
930
  are rejected; an invocation records an `agent-event` of kind `command`.
917
931
  - **Policy:** a canvas-bound, snapshotted tool/prompt policy
918
932
  (`tools.allowed|excluded|approvalRequired`, `prompt.systemAppend|mode`) read
919
- into `canvas://ax-context`. Patch it with `canvas_set_ax_policy` (HTTP
920
- `GET|POST /api/canvas/ax/policy`; CLI `pmx-canvas ax policy get|set`); patches
933
+ into `canvas://ax-context`. Patch it with `canvas_ax_state { action: "set-policy" }`
934
+ (HTTP `GET|POST /api/canvas/ax/policy`; CLI `pmx-canvas ax policy get|set`); patches
921
935
  merge and are normalized server-side.
922
936
 
923
937
  Interactions request PMX-AX primitives only — never arbitrary shell, tool, MCP,
@@ -945,7 +959,7 @@ AX interactions are gated per node type. The lists below are each type's **ceili
945
959
  | `group` | `ax.focus.set`, `ax.work.create`, `ax.command.invoke`, `ax.event.record` |
946
960
 
947
961
  **Opt-in** — set `axCapabilities.enabled = true` (MCP: pass `axCapabilities` to
948
- `canvas_add_html_node` / `canvas_update_node`. HTTP: `axCapabilities` **and** the
962
+ `canvas_add_html_node` / `canvas_node { action: "update" }`. HTTP: `axCapabilities` **and** the
949
963
  `html` body are accepted **top-level on both `POST /api/canvas/node` and
950
964
  `PATCH /api/canvas/node/<id>`**, or nested under `data` — both work, top-level wins):
951
965
 
@@ -976,7 +990,7 @@ state. The read side mirrors the write side:
976
990
  - **Opt in** (html/mcp-app are off by default): create with
977
991
  `canvas_add_html_node({ html, axCapabilities: { enabled: true, allowed: ["ax.work.create","ax.work.update"] } })`,
978
992
  or flip an existing node on with
979
- `canvas_update_node({ id, axCapabilities: { enabled: true, allowed: [...] } })`.
993
+ `canvas_node({ action: "update", id, axCapabilities: { enabled: true, allowed: [...] } })`.
980
994
  json-render / graph nodes are enabled by default.
981
995
  - **Emit (write):** in `html`, call `window.PMX_AX.emit("ax.work.create", { title })`;
982
996
  in `json-render`, bind a control action named after the AX type
@@ -1022,6 +1036,20 @@ This is the right home for a deliberate, interactive AX experience — not the
1022
1036
  native node buttons. Any agent (via MCP/SDK) can also create/update the same work
1023
1037
  items, and the board reflects them live.
1024
1038
 
1039
+ > **Authoring an AX HTML node? Use the blessed, copy-paste-safe recipe in
1040
+ > [`references/ax-html-control-surface.md`](references/ax-html-control-surface.md)**
1041
+ > and avoid the common footguns:
1042
+ > - **The iframe is sandboxed opaque-origin** (no `allow-same-origin`): `localStorage`,
1043
+ > `sessionStorage`, and cookies **throw** and will halt your startup script, making the
1044
+ > node look inert. Keep state in plain JS variables / `window.PMX_AX.state`, or wrap any
1045
+ > storage access in `try/catch`.
1046
+ > - **`window.PMX_AX.emit` is async — `await` it** (or use `.then`/`window.PMX_AX.on('ack')`);
1047
+ > don't read a result synchronously. It's injected only when the node is opted in
1048
+ > (`axCapabilities.enabled = true`).
1049
+ > - **`ax.steer` is recorded, not delivered** — a successful emit means the steer is
1050
+ > *queued for the agent's next turn*, not that the agent woke (see "Steering is gated").
1051
+ > Label steering buttons accordingly.
1052
+
1025
1053
  > Security note: an AX-enabled surface can READ the whole canvas AX board (all
1026
1054
  > work items, focus, approval gates, etc. — human review comment text is redacted),
1027
1055
  > while its EMITS are clamped to its own node. Under the single-workspace
@@ -1154,10 +1182,10 @@ Show system components and how they interact:
1154
1182
 
1155
1183
  1. Create `markdown` nodes for each service/component (include port, tech stack in content)
1156
1184
  2. Use `flow` edges for data flow, `depends-on` for dependencies — always label edges
1157
- 3. Group related services with `canvas_create_group` (e.g., "Application Services", "Data Layer")
1185
+ 3. Group related services with `canvas_group { action: "create" }` (e.g., "Application Services", "Data Layer")
1158
1186
  4. Use colors: green for healthy, yellow for degraded, red for down
1159
1187
  5. Arrange with `grid` layout initially
1160
- 6. For tiered architectures, fine-tune with explicit `x`/`y` via `canvas_update_node` to show
1188
+ 6. For tiered architectures, fine-tune with explicit `x`/`y` via `canvas_node { action: "update" }` to show
1161
1189
  layers (e.g., gateway at top, services in middle, data stores at bottom)
1162
1190
  7. Connect pipeline stages with `flow` edges where applicable
1163
1191
 
@@ -1169,7 +1197,7 @@ Track work items and their relationships:
1169
1197
  2. Color-code: green=done, yellow=in-progress, red=blocked, gray=queued, blue=ready/available
1170
1198
  3. Connect with `depends-on` edges — use `dashed` style for blocked dependencies, `solid` for
1171
1199
  satisfied ones
1172
- 4. Update status nodes as work progresses using `canvas_update_node`
1200
+ 4. Update status nodes as work progresses using `canvas_node { action: "update" }`
1173
1201
  5. Arrange with `flow` layout to show the dependency chain left-to-right
1174
1202
  6. Group related tasks if the plan has distinct phases
1175
1203
 
@@ -1182,7 +1210,7 @@ Understand a codebase by visualizing file relationships:
1182
1210
  don't need to manually add import-based edges. You can still add manual edges for
1183
1211
  conceptual relationships beyond imports (e.g., "middleware validates using jwt")
1184
1212
  3. Read `canvas://code-graph` for dependency analysis: central files, isolated files
1185
- 4. Group related files with `canvas_create_group` (e.g., "Auth Module", "API Routes")
1213
+ 4. Group related files with `canvas_group { action: "create" }` (e.g., "Auth Module", "API Routes")
1186
1214
  5. Pin important files so the human sees them highlighted
1187
1215
  6. Arrange with `grid` layout after adding files
1188
1216
 
@@ -1204,8 +1232,8 @@ Monitor ongoing processes:
1204
1232
  1. Create `status` nodes for each metric/process
1205
1233
  2. Use semantic colors: green=passing, yellow=running, red=failing, gray=queued
1206
1234
  3. Connect sequential pipeline stages with `flow` edges (label: "then", "triggers")
1207
- 4. Update nodes in-place as state changes using `canvas_update_node` — never delete and recreate,
1208
- as that loses position and edges
1235
+ 4. Update nodes in-place as state changes using `canvas_node { action: "update" }` — never delete
1236
+ and recreate, as that loses position and edges
1209
1237
  5. Arrange with `grid` layout
1210
1238
  6. The human sees real-time updates via SSE
1211
1239
 
@@ -1223,7 +1251,7 @@ Show two states side by side for the human to compare:
1223
1251
  When the human wants to explore a different approach without losing current work:
1224
1252
 
1225
1253
  1. **First**, save the current state: `canvas_snapshot` with a descriptive name
1226
- 2. **Then** clear: `canvas_clear` (never clear without snapshotting first)
1254
+ 2. **Then** clear: `canvas_view { action: "clear" }` (never clear without snapshotting first)
1227
1255
  3. Set up the new workspace with initial nodes
1228
1256
  4. Tell the human the snapshot name and that `canvas_restore` can bring everything back
1229
1257
 
@@ -1237,17 +1265,17 @@ When the human wants to explore a different approach without losing current work
1237
1265
  3. **Label every edge.** Unlabeled edges lose meaning. "depends on", "calls", "blocks"
1238
1266
  are all more useful than a bare arrow.
1239
1267
 
1240
- 4. **Auto-arrange after batch adds.** When adding multiple nodes, call `canvas_arrange`
1241
- once at the end, not after each node.
1268
+ 4. **Auto-arrange after batch adds.** When adding multiple nodes, call
1269
+ `canvas_view { action: "arrange" }` once at the end, not after each node.
1242
1270
 
1243
- 5. **Update in place.** Use `canvas_update_node` to change status, content, or color.
1244
- Don't delete and recreate — that loses position and edges.
1271
+ 5. **Update in place.** Use `canvas_node { action: "update" }` to change status, content, or
1272
+ color. Don't delete and recreate — that loses position and edges.
1245
1273
 
1246
1274
  6. **Clean up.** Remove nodes that are no longer relevant. A cluttered canvas is worse
1247
1275
  than no canvas.
1248
1276
 
1249
- 7. **Read before writing.** Check `canvas://layout` or `canvas_get_layout` before adding
1250
- nodes to avoid duplicates and understand the current state.
1277
+ 7. **Read before writing.** Check `canvas://layout` or `canvas_query { action: "layout" }` before
1278
+ adding nodes to avoid duplicates and understand the current state.
1251
1279
 
1252
1280
  8. **Use pinning.** When you want the human to focus on specific nodes, pin them.
1253
1281
  When the human pins nodes, read `canvas://pinned-context` to see what they care about.