pmx-canvas 0.1.35 → 0.2.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 (100) hide show
  1. package/CHANGELOG.md +461 -0
  2. package/Readme.md +14 -2
  3. package/dist/canvas/index.js +82 -41
  4. package/dist/json-render/index.js +89 -334
  5. package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -0
  6. package/dist/types/mcp/canvas-access.d.ts +12 -159
  7. package/dist/types/server/ax-context.d.ts +1 -1
  8. package/dist/types/server/ax-state-manager.d.ts +256 -0
  9. package/dist/types/server/ax-state.d.ts +29 -1
  10. package/dist/types/server/ax-wait.d.ts +23 -0
  11. package/dist/types/server/canvas-operations.d.ts +1 -12
  12. package/dist/types/server/canvas-state.d.ts +46 -14
  13. package/dist/types/server/html-surface.d.ts +7 -0
  14. package/dist/types/server/index.d.ts +66 -26
  15. package/dist/types/server/operations/composites.d.ts +121 -0
  16. package/dist/types/server/operations/http.d.ts +7 -0
  17. package/dist/types/server/operations/index.d.ts +8 -0
  18. package/dist/types/server/operations/invoker.d.ts +13 -0
  19. package/dist/types/server/operations/mcp.d.ts +15 -0
  20. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  21. package/dist/types/server/operations/ops/app.d.ts +33 -0
  22. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  23. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  24. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  25. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  26. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  27. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  28. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  29. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  30. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  31. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  32. package/dist/types/server/operations/ops/query.d.ts +2 -0
  33. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  34. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  35. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  36. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  37. package/dist/types/server/operations/registry.d.ts +15 -0
  38. package/dist/types/server/operations/types.d.ts +116 -0
  39. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  40. package/docs/RELEASE.md +5 -0
  41. package/docs/adr-001-bun-only-runtime.md +46 -0
  42. package/docs/api-stability.md +57 -0
  43. package/docs/ax-host-adapter-contract.md +65 -0
  44. package/docs/ax-state-contract.md +72 -0
  45. package/docs/http-api.md +34 -2
  46. package/docs/mcp.md +64 -11
  47. package/docs/plans/plan-005-operation-registry.md +84 -0
  48. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  49. package/docs/plans/plan-007-ax-domain.md +99 -0
  50. package/docs/plans/plan-008-registry-finish.md +91 -0
  51. package/docs/screenshot.png +0 -0
  52. package/docs/tech-debt-assessment-2026-06.md +90 -0
  53. package/package.json +3 -3
  54. package/skills/pmx-canvas/SKILL.md +233 -185
  55. package/skills/pmx-canvas/evals/evals.json +3 -3
  56. package/skills/pmx-canvas/references/codex-app-adapter.md +24 -11
  57. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +31 -1
  58. package/src/cli/agent.ts +52 -31
  59. package/src/client/nodes/ExtAppFrame.tsx +73 -5
  60. package/src/client/nodes/HtmlNode.tsx +12 -3
  61. package/src/client/nodes/McpAppNode.tsx +12 -3
  62. package/src/json-render/renderer/index.tsx +3 -0
  63. package/src/mcp/canvas-access.ts +43 -774
  64. package/src/mcp/server.ts +190 -2001
  65. package/src/server/ax-context.ts +7 -1
  66. package/src/server/ax-state-manager.ts +808 -0
  67. package/src/server/ax-state.ts +89 -2
  68. package/src/server/ax-wait.ts +56 -0
  69. package/src/server/canvas-operations.ts +2 -328
  70. package/src/server/canvas-schema.ts +2 -2
  71. package/src/server/canvas-state.ts +140 -382
  72. package/src/server/html-surface.ts +49 -11
  73. package/src/server/index.ts +136 -192
  74. package/src/server/operations/composites.ts +355 -0
  75. package/src/server/operations/http.ts +103 -0
  76. package/src/server/operations/index.ts +65 -0
  77. package/src/server/operations/invoker.ts +87 -0
  78. package/src/server/operations/mcp.ts +221 -0
  79. package/src/server/operations/ops/annotation.ts +60 -0
  80. package/src/server/operations/ops/app.ts +447 -0
  81. package/src/server/operations/ops/ax-await.ts +216 -0
  82. package/src/server/operations/ops/ax-shared.ts +38 -0
  83. package/src/server/operations/ops/ax-state.ts +249 -0
  84. package/src/server/operations/ops/ax-timeline.ts +381 -0
  85. package/src/server/operations/ops/ax-work.ts +635 -0
  86. package/src/server/operations/ops/batch.ts +365 -0
  87. package/src/server/operations/ops/edges.ts +166 -0
  88. package/src/server/operations/ops/groups.ts +176 -0
  89. package/src/server/operations/ops/json-render.ts +691 -0
  90. package/src/server/operations/ops/nodes.ts +1047 -0
  91. package/src/server/operations/ops/query.ts +281 -0
  92. package/src/server/operations/ops/snapshots.ts +366 -0
  93. package/src/server/operations/ops/validate.ts +37 -0
  94. package/src/server/operations/ops/viewport.ts +219 -0
  95. package/src/server/operations/ops/webview.ts +339 -0
  96. package/src/server/operations/registry.ts +79 -0
  97. package/src/server/operations/types.ts +150 -0
  98. package/src/server/operations/webview-runner.ts +77 -0
  99. package/src/server/server.ts +253 -2170
  100. 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
 
@@ -843,6 +845,12 @@ Eligible nodes can emit one normalized, validated AX interaction that maps onto
843
845
  AX operation — work item, evidence, approval, review, focus, steering, event,
844
846
  elicitation, or mode request. One envelope, many transports:
845
847
 
848
+ This is the **agent-native nodes** model: existing canvas node types become
849
+ interactive agent controls when their AX capabilities allow it. Do not describe
850
+ this as a separate node type; it is a capability layer on top of markdown,
851
+ status, HTML, json-render, graph, web-artifact, MCP app, and other supported
852
+ nodes.
853
+
846
854
  - **Endpoint:** `POST /api/canvas/ax/interaction` with
847
855
  `{ type, sourceNodeId, payload }` (MCP: `canvas_ax_interaction`; CLI:
848
856
  `pmx-canvas ax interaction`). Returns `{ ok, primitive }` or
@@ -857,39 +865,65 @@ elicitation, or mode request. One envelope, many transports:
857
865
  before submitting: `html` / `html-primitive` nodes (when opted in) call
858
866
  `window.PMX_AX.emit(type, payload)`; **json-render / graph** viewers forward a
859
867
  spec action named after an AX type (e.g. `on.press → { action:
860
- "ax.work.create", params }`, `sourceSurface: 'json-render'`); opted-in ext-app
861
- **`mcp-app`** nodes get the same `window.PMX_AX.emit` injected
862
- (`sourceSurface: 'mcp-app'`). The server re-validates capabilities regardless
863
- of transport bridges are convenience, not a trust boundary.
868
+ "ax.work.create", params }`, `sourceSurface: 'json-render'`); web-artifact
869
+ **`mcp-app`** nodes use the same parent bridge; external MCP app frames
870
+ (`mode: "ext-app"`) can emit through an injected `window.PMX_AX.emit` with
871
+ Promise acknowledgements, but do not get the read-state bridge. The server
872
+ re-validates capabilities regardless of transport — bridges are convenience,
873
+ not a trust boundary.
864
874
  - **Delivery (adapterless):** `canvas://ax-pending-steering` /
865
- `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
866
876
  receives items it originated):
867
877
  - `pending` — undelivered **steering** (directives). Act, then acknowledge with
868
- `canvas_mark_ax_delivery`.
878
+ `canvas_ax_delivery { action: "mark" }`.
869
879
  - `pendingActivity` — open canvas-bound items **awaiting the agent** (open work
870
880
  items, pending approval gates / elicitations / mode requests), usually created
871
881
  by the human in the browser. These are **state, not steering**: don't
872
- `canvas_mark_ax_delivery` them — resolve each via its own tool
873
- (`canvas_resolve_approval` / `canvas_respond_elicitation` /
874
- `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" }`).
875
887
  - **Contract:** every AX mutation fires `ax-state-changed`, so MCP clients that
876
888
  **subscribe** to resources are pushed `canvas://ax-work` / `canvas://ax-context`
877
- live. Clients that **poll** instead should poll `canvas_claim_ax_delivery` —
889
+ live. Clients that **poll** instead should poll `canvas_ax_delivery { action: "claim" }` —
878
890
  `pendingActivity` is how non-steering browser changes reach them. Only steering
879
891
  flows through the claim/ack queue.
892
+ - **Steering is gated, not pushed.** A surface button that emits `ax.steer`
893
+ enqueues a steer — it does NOT wake the agent. With a prompt-injecting host
894
+ adapter (e.g. Copilot), it reaches the next turn only when (1) the **pin/focus
895
+ gate is open** (something pinned or focused — so keep a steering board pinned, or
896
+ have its button also emit `ax.focus.set` on itself), (2) a **human message** fires
897
+ the turn, and (3) the agent **acts then acks** (`canvas_ax_delivery { action: "mark" }`),
898
+ or the steer re-injects every gated turn. `GET /api/canvas/ax/context?consumer=<id>` adds
899
+ a compact, loop-safe `delivery: { pendingSteering, pendingActivity }` lead block an
900
+ adapter can inject un-truncated, so steering survives the full-context char clip.
901
+ - **Activity ingestion (bidirectional board):** a host adapter forwards the agent's
902
+ tool/session events with `canvas_ingest_activity` (standalone; HTTP `POST /api/canvas/ax/activity`)
903
+ and the board auto-reacts — `failure`/`error` (or `outcome:"failure"`) → a blocked
904
+ work item + a review finding + `logs` evidence; `tool-result` + `outcome:"success"` →
905
+ `tool-result` evidence; everything else records a timeline event only. Override or
906
+ suppress per call via `reactions` (`{ workItem: false }`, `{ review: { severity } }`, …).
907
+ - **Blocking gates (gates that actually gate):** `canvas_ax_gate` is the request →
908
+ await → resolve machine. After `{ action: "request" }`, call
909
+ `canvas_ax_gate { kind, action: "await", id, timeoutMs }` (HTTP
910
+ `GET /api/canvas/ax/<kind>/<id>?waitMs=`) to BLOCK until the human resolves it in the
911
+ browser or the timeout elapses (`timeoutMs` 0 = immediate read; ≤120000). Use this to
912
+ pause real work on a human decision instead of polling.
880
913
  - **Elicitation / mode:** request structured human input
881
- (`canvas_request_elicitation` `canvas_respond_elicitation`) or a workflow
882
- mode transition (`canvas_request_mode` `canvas_resolve_mode`); both are
883
- canvas-bound and snapshotted.
914
+ (`canvas_ax_gate { kind: "elicitation", action: "request" }` →
915
+ `canvas_ax_gate { kind: "elicitation", action: "resolve" }`) or a workflow
916
+ mode transition (`canvas_ax_gate { kind: "mode", action: "request" }` →
917
+ `canvas_ax_gate { kind: "mode", action: "resolve" }`); both are canvas-bound and snapshotted.
884
918
  - **Commands:** invoke a registry command — `pmx.plan`, `pmx.execute`,
885
919
  `pmx.promote-context`, `pmx.summarize`, `pmx.review` — via
886
- `canvas_invoke_command` (HTTP `POST /api/canvas/ax/command`; CLI
920
+ `canvas_invoke_command` (standalone; HTTP `POST /api/canvas/ax/command`; CLI
887
921
  `pmx-canvas ax command invoke`; envelope `ax.command.invoke`). Unknown names
888
922
  are rejected; an invocation records an `agent-event` of kind `command`.
889
923
  - **Policy:** a canvas-bound, snapshotted tool/prompt policy
890
924
  (`tools.allowed|excluded|approvalRequired`, `prompt.systemAppend|mode`) read
891
- into `canvas://ax-context`. Patch it with `canvas_set_ax_policy` (HTTP
892
- `GET|POST /api/canvas/ax/policy`; CLI `pmx-canvas ax policy get|set`); patches
925
+ into `canvas://ax-context`. Patch it with `canvas_ax_state { action: "set-policy" }`
926
+ (HTTP `GET|POST /api/canvas/ax/policy`; CLI `pmx-canvas ax policy get|set`); patches
893
927
  merge and are normalized server-side.
894
928
 
895
929
  Interactions request PMX-AX primitives only — never arbitrary shell, tool, MCP,
@@ -916,8 +950,10 @@ AX interactions are gated per node type. The lists below are each type's **ceili
916
950
  | `webpage` | `ax.evidence.add`, `ax.review.add`, `ax.focus.set`, `ax.event.record` |
917
951
  | `group` | `ax.focus.set`, `ax.work.create`, `ax.command.invoke`, `ax.event.record` |
918
952
 
919
- **Opt-in** — set `data.axCapabilities.enabled = true` (MCP: pass `axCapabilities` to
920
- `canvas_add_html_node` or `canvas_update_node`; HTTP: nest under `data`):
953
+ **Opt-in** — set `axCapabilities.enabled = true` (MCP: pass `axCapabilities` to
954
+ `canvas_add_html_node` / `canvas_node { action: "update" }`. HTTP: `axCapabilities` **and** the
955
+ `html` body are accepted **top-level on both `POST /api/canvas/node` and
956
+ `PATCH /api/canvas/node/<id>`**, or nested under `data` — both work, top-level wins):
921
957
 
922
958
  | Node type | Allowed AX interaction types |
923
959
  |-----------|------------------------------|
@@ -928,7 +964,9 @@ AX interactions are gated per node type. The lists below are each type's **ceili
928
964
  only, no human-facing emit.
929
965
 
930
966
  The 11 interaction types and what they create: `ax.work.create` / `ax.work.update`
931
- (work-queue items, status todoin-progressblocked→done→cancelled), `ax.evidence.add`
967
+ (work-queue items; status is exactly one of `todo`, `in-progress`, `blocked`, `done`,
968
+ `cancelled` — **hyphens, not underscores**; `POST`/`PATCH /api/canvas/ax/work` reject an
969
+ unknown token like `in_progress` with `400`), `ax.evidence.add`
932
970
  (timeline evidence), `ax.review.add` (review annotation), `ax.focus.set` (agent focus
933
971
  pointer), `ax.steer` (a steering message delivered to the agent), `ax.approval.request`
934
972
  (approval gate), `ax.elicitation.request` (structured human input), `ax.mode.request`
@@ -944,11 +982,17 @@ state. The read side mirrors the write side:
944
982
  - **Opt in** (html/mcp-app are off by default): create with
945
983
  `canvas_add_html_node({ html, axCapabilities: { enabled: true, allowed: ["ax.work.create","ax.work.update"] } })`,
946
984
  or flip an existing node on with
947
- `canvas_update_node({ id, axCapabilities: { enabled: true, allowed: [...] } })`.
985
+ `canvas_node({ action: "update", id, axCapabilities: { enabled: true, allowed: [...] } })`.
948
986
  json-render / graph nodes are enabled by default.
949
987
  - **Emit (write):** in `html`, call `window.PMX_AX.emit("ax.work.create", { title })`;
950
988
  in `json-render`, bind a control action named after the AX type
951
989
  (`on: { press: { action: "ax.work.create", params: { title } } }`).
990
+ - **Confirm (#55):** for `html` / `html-primitive` and PMX_AX-enabled `mcp-app`
991
+ surfaces, `emit` returns a Promise that resolves with the result once the
992
+ canvas acks it, so a button can self-confirm: `const r = await
993
+ window.PMX_AX.emit(...); if (r.ok) showQueued();`. You can also
994
+ `window.PMX_AX.on('ack', cb)` or listen for the `pmx-ax-ack` event. (Falls back
995
+ to an `ax-ack-timeout` result after 10s, so `await` never hangs.)
952
996
  - **Reflect (read):** the canvas seeds the surface with a compact AX snapshot at
953
997
  load (the same shape as `GET /api/canvas/ax/surface-snapshot`) and live-updates it
954
998
  as AX state changes. Works on all three authored surface types:
@@ -966,11 +1010,15 @@ state. The read side mirrors the write side:
966
1010
  Minimal html work board (drop-in via `canvas_add_html_node`, `axCapabilities.enabled: true`):
967
1011
 
968
1012
  ```html
969
- <button onclick="window.PMX_AX.emit('ax.work.create',{title:'New task'})">+ Task</button>
1013
+ <button id="add">+ Task</button> <span id="ok"></span>
970
1014
  <ul id="q"></ul>
971
1015
  <script>
972
1016
  function render(s){ document.getElementById('q').innerHTML =
973
1017
  ((s&&s.workItems)||[]).map(w => '<li>['+w.status+'] '+w.title+'</li>').join(''); }
1018
+ document.getElementById('add').onclick = async () => {
1019
+ const r = await window.PMX_AX.emit('ax.work.create',{title:'New task'});
1020
+ document.getElementById('ok').textContent = r && r.ok ? 'queued ✓' : 'failed'; // #55 self-confirm
1021
+ };
974
1022
  render(window.PMX_AX && window.PMX_AX.state);
975
1023
  window.addEventListener('pmx-ax-update', e => render(e.detail));
976
1024
  </script>
@@ -1112,10 +1160,10 @@ Show system components and how they interact:
1112
1160
 
1113
1161
  1. Create `markdown` nodes for each service/component (include port, tech stack in content)
1114
1162
  2. Use `flow` edges for data flow, `depends-on` for dependencies — always label edges
1115
- 3. Group related services with `canvas_create_group` (e.g., "Application Services", "Data Layer")
1163
+ 3. Group related services with `canvas_group { action: "create" }` (e.g., "Application Services", "Data Layer")
1116
1164
  4. Use colors: green for healthy, yellow for degraded, red for down
1117
1165
  5. Arrange with `grid` layout initially
1118
- 6. For tiered architectures, fine-tune with explicit `x`/`y` via `canvas_update_node` to show
1166
+ 6. For tiered architectures, fine-tune with explicit `x`/`y` via `canvas_node { action: "update" }` to show
1119
1167
  layers (e.g., gateway at top, services in middle, data stores at bottom)
1120
1168
  7. Connect pipeline stages with `flow` edges where applicable
1121
1169
 
@@ -1127,7 +1175,7 @@ Track work items and their relationships:
1127
1175
  2. Color-code: green=done, yellow=in-progress, red=blocked, gray=queued, blue=ready/available
1128
1176
  3. Connect with `depends-on` edges — use `dashed` style for blocked dependencies, `solid` for
1129
1177
  satisfied ones
1130
- 4. Update status nodes as work progresses using `canvas_update_node`
1178
+ 4. Update status nodes as work progresses using `canvas_node { action: "update" }`
1131
1179
  5. Arrange with `flow` layout to show the dependency chain left-to-right
1132
1180
  6. Group related tasks if the plan has distinct phases
1133
1181
 
@@ -1140,7 +1188,7 @@ Understand a codebase by visualizing file relationships:
1140
1188
  don't need to manually add import-based edges. You can still add manual edges for
1141
1189
  conceptual relationships beyond imports (e.g., "middleware validates using jwt")
1142
1190
  3. Read `canvas://code-graph` for dependency analysis: central files, isolated files
1143
- 4. Group related files with `canvas_create_group` (e.g., "Auth Module", "API Routes")
1191
+ 4. Group related files with `canvas_group { action: "create" }` (e.g., "Auth Module", "API Routes")
1144
1192
  5. Pin important files so the human sees them highlighted
1145
1193
  6. Arrange with `grid` layout after adding files
1146
1194
 
@@ -1162,8 +1210,8 @@ Monitor ongoing processes:
1162
1210
  1. Create `status` nodes for each metric/process
1163
1211
  2. Use semantic colors: green=passing, yellow=running, red=failing, gray=queued
1164
1212
  3. Connect sequential pipeline stages with `flow` edges (label: "then", "triggers")
1165
- 4. Update nodes in-place as state changes using `canvas_update_node` — never delete and recreate,
1166
- as that loses position and edges
1213
+ 4. Update nodes in-place as state changes using `canvas_node { action: "update" }` — never delete
1214
+ and recreate, as that loses position and edges
1167
1215
  5. Arrange with `grid` layout
1168
1216
  6. The human sees real-time updates via SSE
1169
1217
 
@@ -1181,7 +1229,7 @@ Show two states side by side for the human to compare:
1181
1229
  When the human wants to explore a different approach without losing current work:
1182
1230
 
1183
1231
  1. **First**, save the current state: `canvas_snapshot` with a descriptive name
1184
- 2. **Then** clear: `canvas_clear` (never clear without snapshotting first)
1232
+ 2. **Then** clear: `canvas_view { action: "clear" }` (never clear without snapshotting first)
1185
1233
  3. Set up the new workspace with initial nodes
1186
1234
  4. Tell the human the snapshot name and that `canvas_restore` can bring everything back
1187
1235
 
@@ -1195,17 +1243,17 @@ When the human wants to explore a different approach without losing current work
1195
1243
  3. **Label every edge.** Unlabeled edges lose meaning. "depends on", "calls", "blocks"
1196
1244
  are all more useful than a bare arrow.
1197
1245
 
1198
- 4. **Auto-arrange after batch adds.** When adding multiple nodes, call `canvas_arrange`
1199
- once at the end, not after each node.
1246
+ 4. **Auto-arrange after batch adds.** When adding multiple nodes, call
1247
+ `canvas_view { action: "arrange" }` once at the end, not after each node.
1200
1248
 
1201
- 5. **Update in place.** Use `canvas_update_node` to change status, content, or color.
1202
- Don't delete and recreate — that loses position and edges.
1249
+ 5. **Update in place.** Use `canvas_node { action: "update" }` to change status, content, or
1250
+ color. Don't delete and recreate — that loses position and edges.
1203
1251
 
1204
1252
  6. **Clean up.** Remove nodes that are no longer relevant. A cluttered canvas is worse
1205
1253
  than no canvas.
1206
1254
 
1207
- 7. **Read before writing.** Check `canvas://layout` or `canvas_get_layout` before adding
1208
- nodes to avoid duplicates and understand the current state.
1255
+ 7. **Read before writing.** Check `canvas://layout` or `canvas_query { action: "layout" }` before
1256
+ adding nodes to avoid duplicates and understand the current state.
1209
1257
 
1210
1258
  8. **Use pinning.** When you want the human to focus on specific nodes, pin them.
1211
1259
  When the human pins nodes, read `canvas://pinned-context` to see what they care about.