pmx-canvas 0.1.36 → 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 (85) hide show
  1. package/CHANGELOG.md +409 -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 +256 -0
  6. package/dist/types/server/ax-state.d.ts +1 -1
  7. package/dist/types/server/canvas-operations.d.ts +1 -12
  8. package/dist/types/server/canvas-state.d.ts +3 -23
  9. package/dist/types/server/index.d.ts +6 -24
  10. package/dist/types/server/operations/composites.d.ts +121 -0
  11. package/dist/types/server/operations/http.d.ts +7 -0
  12. package/dist/types/server/operations/index.d.ts +8 -0
  13. package/dist/types/server/operations/invoker.d.ts +13 -0
  14. package/dist/types/server/operations/mcp.d.ts +15 -0
  15. package/dist/types/server/operations/ops/annotation.d.ts +2 -0
  16. package/dist/types/server/operations/ops/app.d.ts +33 -0
  17. package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
  18. package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
  19. package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
  20. package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
  21. package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
  22. package/dist/types/server/operations/ops/batch.d.ts +19 -0
  23. package/dist/types/server/operations/ops/edges.d.ts +2 -0
  24. package/dist/types/server/operations/ops/groups.d.ts +2 -0
  25. package/dist/types/server/operations/ops/json-render.d.ts +31 -0
  26. package/dist/types/server/operations/ops/nodes.d.ts +62 -0
  27. package/dist/types/server/operations/ops/query.d.ts +2 -0
  28. package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
  29. package/dist/types/server/operations/ops/validate.d.ts +2 -0
  30. package/dist/types/server/operations/ops/viewport.d.ts +2 -0
  31. package/dist/types/server/operations/ops/webview.d.ts +2 -0
  32. package/dist/types/server/operations/registry.d.ts +15 -0
  33. package/dist/types/server/operations/types.d.ts +116 -0
  34. package/dist/types/server/operations/webview-runner.d.ts +69 -0
  35. package/docs/RELEASE.md +5 -0
  36. package/docs/adr-001-bun-only-runtime.md +46 -0
  37. package/docs/api-stability.md +57 -0
  38. package/docs/ax-state-contract.md +72 -0
  39. package/docs/mcp.md +60 -11
  40. package/docs/plans/plan-005-operation-registry.md +84 -0
  41. package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
  42. package/docs/plans/plan-007-ax-domain.md +99 -0
  43. package/docs/plans/plan-008-registry-finish.md +91 -0
  44. package/docs/tech-debt-assessment-2026-06.md +90 -0
  45. package/package.json +3 -3
  46. package/skills/pmx-canvas/SKILL.md +192 -186
  47. package/skills/pmx-canvas/evals/evals.json +3 -3
  48. package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
  49. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +4 -5
  50. package/src/cli/agent.ts +52 -31
  51. package/src/mcp/canvas-access.ts +30 -830
  52. package/src/mcp/server.ts +162 -2014
  53. package/src/server/ax-state-manager.ts +808 -0
  54. package/src/server/ax-state.ts +2 -2
  55. package/src/server/canvas-operations.ts +2 -328
  56. package/src/server/canvas-schema.ts +2 -2
  57. package/src/server/canvas-state.ts +95 -465
  58. package/src/server/index.ts +54 -190
  59. package/src/server/operations/composites.ts +355 -0
  60. package/src/server/operations/http.ts +103 -0
  61. package/src/server/operations/index.ts +65 -0
  62. package/src/server/operations/invoker.ts +87 -0
  63. package/src/server/operations/mcp.ts +221 -0
  64. package/src/server/operations/ops/annotation.ts +60 -0
  65. package/src/server/operations/ops/app.ts +447 -0
  66. package/src/server/operations/ops/ax-await.ts +216 -0
  67. package/src/server/operations/ops/ax-shared.ts +38 -0
  68. package/src/server/operations/ops/ax-state.ts +249 -0
  69. package/src/server/operations/ops/ax-timeline.ts +381 -0
  70. package/src/server/operations/ops/ax-work.ts +635 -0
  71. package/src/server/operations/ops/batch.ts +365 -0
  72. package/src/server/operations/ops/edges.ts +166 -0
  73. package/src/server/operations/ops/groups.ts +176 -0
  74. package/src/server/operations/ops/json-render.ts +691 -0
  75. package/src/server/operations/ops/nodes.ts +1047 -0
  76. package/src/server/operations/ops/query.ts +281 -0
  77. package/src/server/operations/ops/snapshots.ts +366 -0
  78. package/src/server/operations/ops/validate.ts +37 -0
  79. package/src/server/operations/ops/viewport.ts +219 -0
  80. package/src/server/operations/ops/webview.ts +339 -0
  81. package/src/server/operations/registry.ts +79 -0
  82. package/src/server/operations/types.ts +150 -0
  83. package/src/server/operations/webview-runner.ts +77 -0
  84. package/src/server/server.ts +158 -2255
  85. package/src/server/web-artifacts.ts +6 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,414 @@ All notable changes to `pmx-canvas` are documented here. This project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.2.0] - 2026-06-16
9
+
10
+ ### Breaking
11
+
12
+ Per [`docs/api-stability.md`](docs/api-stability.md), v0.2.0 is the first minor
13
+ allowed to change public-surface behavior, and every such change is collected here.
14
+ All are **cross-surface unifications** — a surface that silently diverged from the
15
+ others is brought into line, never a new restriction invented for this release. The
16
+ detailed per-slice rationale is under **Changed**. MCP tool *names* + input *schemas*,
17
+ HTTP routes + documented success shapes, and CLI flags are all unchanged (frozen by
18
+ `tests/unit/mcp-tool-freeze.test.ts`).
19
+
20
+ - **SDK `PmxCanvas.removeNode(id)` now throws `Node "<id>" not found.` on an unknown
21
+ id** instead of silently succeeding — matching the HTTP `DELETE` (already 404) and
22
+ local MCP `canvas_remove_node`. Idempotent-delete callers must guard or catch.
23
+ - **Local (in-process) MCP error surfacing now matches HTTP for missing targets:**
24
+ `canvas_remove_node`, `canvas_remove_edge`, `canvas_create_group` (missing child ids),
25
+ `canvas_focus_node`, and `canvas_ungroup` now return an MCP error result (were silent
26
+ success / a different message).
27
+ - **MCP `*_resolve` / `*_respond` / `update_work_item` / review `update` /
28
+ `invoke_command` now return an `isError` result** (not a success-shaped
29
+ `{ ok:false, <item>:null }`) when the target is missing, already resolved, or an
30
+ unknown command. Success shapes are unchanged.
31
+ - **AX blocking-gate await over a *remote* MCP transport** surfaces a missing gate as an
32
+ `isError` rather than `{ <gate>:null }` (the in-process path is unchanged). The HTTP
33
+ single-gate read keeps its 404 status but its missing-gate body changed to
34
+ `{ ok:false, <gate>:null, pending:false }`, and it no longer aborts the long-poll on
35
+ client disconnect (it runs to its ≤120 s timeout).
36
+ - **`canvas_batch` entries now inherit each operation's validation** (they dispatch
37
+ through the same registry op as the standalone tools): a `node.add` with no `type`
38
+ fails the batch (was a silent markdown node), an empty-name `snapshot.save` fails the
39
+ batch (was `ok:true`), and `node.add type:"html-primitive"` now succeeds (was a
40
+ batch-only rejection). A batched `node.update` now applies the full HTTP superset
41
+ (webpage `refresh`, top-level `html`, group `children`). The
42
+ `{ ok, results, refs, failedIndex?, error? }` envelope itself is unchanged.
43
+
44
+ ### Added
45
+
46
+ - **AX state isolated into `AxStateManager` (plan-007 Slice A).** The canvas-bound
47
+ AX state, its mutators/readers, and the timeline-direct ops moved out of the
48
+ 2,498-line `CanvasStateManager` into `src/server/ax-state-manager.ts`;
49
+ `CanvasStateManager` delegates, so the public surface (SDK/HTTP/MCP) is
50
+ unchanged. The three-partition snapshot-vs-audit contract is now documented
51
+ authoritatively in `docs/ax-state-contract.md`.
52
+
53
+ - **Composite MCP tools (plan-006 MCP tool consolidation, wave 1).** Seven
54
+ action-discriminated tools fold the single-purpose tools behind a clear
55
+ `action` enum: `canvas_node` (add/get/update/remove), `canvas_render`
56
+ (describe-schema/validate/add-json-render/stream-json-render/add-graph),
57
+ `canvas_edge` (add/remove), `canvas_group` (create/add/ungroup),
58
+ `canvas_history` (undo/redo), `canvas_view` (arrange/focus/fit/clear), and
59
+ `canvas_query` (search/layout). Each action dispatches to the same registered
60
+ operation as its standalone tool — reusing that op's input mapping and result
61
+ formatting — so behavior is byte-identical (proven by
62
+ `tests/unit/mcp-composites.test.ts`). Additive: the legacy single-purpose
63
+ tools keep working and are removed in v0.3 per `docs/api-stability.md`. The
64
+ public MCP surface grows from 69 to 76 tools (deliberate; see
65
+ `tests/unit/mcp-tool-freeze.test.ts`). Composites/actions whose targets are
66
+ not yet registry-backed (`canvas_snapshot`, and the `refresh` /
67
+ `add-primitive` / `remove-annotation` / board-validation actions) land in later
68
+ waves as the AX / side-channel registry slices complete.
69
+
70
+ - **AX composite MCP tools (plan-006 MCP tool consolidation, wave 2 / plan-007
71
+ Slice C).** Five action-discriminated tools fold the 22 legacy AX tools behind
72
+ clear enums: `canvas_ax_state` (get/set-focus/set-policy/report-capability),
73
+ `canvas_ax_work` (add/update/annotate), `canvas_ax_timeline`
74
+ (read/record-event/add-evidence/send-steering), `canvas_ax_delivery`
75
+ (claim/mark), and `canvas_ax_gate` — which folds **9 gate tools into 1** via two
76
+ discriminators `{ kind: approval|elicitation|mode, action: request|resolve|await }`
77
+ so the request/resolve/await lifecycle finally reads as one tool. Each action
78
+ dispatches to the same registered AX operation as its standalone tool — reusing
79
+ that op's input mapping and result formatting — so behavior is byte-identical
80
+ (proven by `tests/unit/mcp-composites.test.ts`). `canvas_ax_interaction`,
81
+ `canvas_ingest_activity`, and `canvas_invoke_command` deliberately stay
82
+ standalone (trust-boundary / firehose / execution-intent tools). Additive: the
83
+ legacy AX tools keep working and are removed in v0.3 per `docs/api-stability.md`.
84
+ The public MCP surface grows from 76 to 81 tools (deliberate; see
85
+ `tests/unit/mcp-tool-freeze.test.ts`).
86
+
87
+ - **`canvas_webview` composite + the 5 webview ops migrated to the operation
88
+ registry via runner injection (plan-008 Wave 3).** The five browser-automation
89
+ tools (`canvas_webview_status`, `canvas_webview_start`, `canvas_webview_stop`,
90
+ `canvas_resize`, `canvas_evaluate`) are now defined once in
91
+ `src/server/operations/ops/webview.ts` and shared by HTTP, MCP, and the SDK.
92
+ The Bun.WebView machinery lives in `server.ts` (which `operations/` must not
93
+ import), so the runner is **injected** — `src/server/operations/webview-runner.ts`
94
+ declares a `WebviewRunner` interface + `setWebviewRunner`/`getWebviewRunner`,
95
+ and `server.ts` wires the real automation functions at module load, exactly
96
+ mirroring the `setOperationEventEmitter` SSE-emitter injection. The new
97
+ `canvas_webview` composite folds the five tools behind a `status` / `start` /
98
+ `stop` / `resize` / `evaluate` action enum; each action dispatches to the same
99
+ op, so behavior is byte-identical (proven by `tests/unit/mcp-composites.test.ts`).
100
+ Wire shapes, MCP result shapes, and the `canvas_evaluate` arbitrary-eval trust
101
+ posture are unchanged. `canvas_screenshot` is deliberately **not** folded — it
102
+ returns a binary image payload the registry JSON wire shape does not model — and
103
+ stays a standalone hand-written tool (its `POST /api/workbench/webview/screenshot`
104
+ route also stays hand-written). The public MCP surface grows from 81 to 82 tools
105
+ (deliberate; see `tests/unit/mcp-tool-freeze.test.ts`).
106
+
107
+ - **`canvas_app` composite + the 3 external / built-content tools migrated to the
108
+ operation registry (plan-008 Wave 4).** `canvas_open_mcp_app`,
109
+ `canvas_add_diagram`, and `canvas_build_web_artifact` are now defined once in
110
+ `src/server/operations/ops/app.ts` (ops `mcpapp.open`, `diagram.open`,
111
+ `webartifact.build`) and shared by HTTP, MCP, and the SDK. They were previously
112
+ deferred as "poor fits" (stateful external session + custom SSE; long-running
113
+ build), but they migrate cleanly: `executeOperation` is async (the long-running
114
+ build fits — the "long-running" caveat is about MCP-client timeouts, not registry
115
+ fit), and their runtimes are server-independent **domain modules**
116
+ (`mcp-app-runtime.ts`, `diagram-presets.ts`, `web-artifacts.ts`), not `server.ts`
117
+ — so the op handlers call them directly, no runner injection. To keep
118
+ `web-artifacts.ts` server-independent its one `emitPrimaryWorkbenchEvent` call
119
+ was switched to the already-injected `emitCanvasLayoutUpdate` (canvas-operations'
120
+ `setCanvasLayoutUpdateEmitter`, wired by `server.ts`). The three ops are
121
+ `mutates:false`: `mcpapp.open` / `diagram.open` emit `ext-app-open` +
122
+ `ext-app-result` via `ctx.emit` (byte-identical to the legacy events, fired
123
+ once); `webartifact.build`'s node creation emits its own `canvas-layout-update`
124
+ from inside `web-artifacts.ts`. The new `canvas_app` composite folds the three
125
+ behind an `open-mcp-app` / `diagram` / `build-artifact` action enum; each action
126
+ dispatches to the same op, so behavior is byte-identical (proven by
127
+ `tests/unit/mcp-composites.test.ts`). Wire shapes and MCP result shapes are
128
+ unchanged. The public MCP surface grows from 82 to 83 tools (deliberate; see
129
+ `tests/unit/mcp-tool-freeze.test.ts`).
130
+
131
+ ### Changed
132
+
133
+ - **Board validation and annotation removal migrated to the operation registry
134
+ (plan-008 Wave 1).** `canvas_validate` (board validation, a pure read) and
135
+ `canvas_remove_annotation` (DELETE-by-id) are now defined once in
136
+ `src/server/operations/ops/validate.ts` and
137
+ `src/server/operations/ops/annotation.ts`, shared by HTTP, MCP, and the CLI
138
+ (legacy hand-written handlers + routes + MCP tool blocks + the orphaned
139
+ `CanvasAccess.validate` / `CanvasAccess.removeAnnotation` methods deleted; the
140
+ SDK `PmxCanvas.validate()` / `removeAnnotation()` stay public). Tool names,
141
+ HTTP routes (`GET /api/canvas/validate`, `DELETE /api/canvas/annotation/:id`),
142
+ the 404 denial body, MCP result shapes, and the `canvas-layout-update` SSE
143
+ frame are unchanged. Two new composite actions land additively:
144
+ `canvas_query` gains `validate` (→ `validate.get`) and `canvas_view` gains
145
+ `remove-annotation` (→ `annotation.remove`).
146
+
147
+ - **Batch migrated to the `canvas.batch` registry meta-op (plan-008 Wave 2 —
148
+ the last and highest-risk registry slice).** `canvas_batch` now dispatches
149
+ each entry through the shared `executeOperation` path (defined once in
150
+ `src/server/operations/ops/batch.ts`) instead of a hand-written ~290-line
151
+ switch in `canvas-operations.ts`, which is deleted along with its
152
+ `resolveBatchRefs` / `serializeCreatedNode` helpers, the
153
+ `handleCanvasBatch` HTTP handler + route, and the standalone `canvas_batch`
154
+ MCP tool block. `executeOperation` gained an internal, depth-counted
155
+ emit-suppression (`runWithSuppressedEmits`) so the meta-op runs every entry
156
+ with per-entry SSE frames suppressed and then emits exactly ONE final
157
+ `canvas-layout-update` — preserving the single-final-frame behavior. The
158
+ `canvas_batch` tool name, the `{ operations:[...] }` and bare-`[...]` body
159
+ shapes, per-entry result shapes, and the `{ ok, results, refs, failedIndex?,
160
+ error? }` envelope are unchanged; per-entry ops record their own undo/redo
161
+ history as before. The SDK `PmxCanvas.runBatch` stays public and delegates to
162
+ the meta-op.
163
+
164
+ - **Deleting a node no longer silently re-anchors AX work (plan-007 Slice A).**
165
+ Node deletion already re-normalized canvas-bound AX state (work items / approval
166
+ gates / elicitations / mode requests keep the item but lose the dangling node
167
+ ref; node-anchored review annotations are dropped) — but did so silently. It now
168
+ records exactly one auditable timeline event (`kind:'note'`, `source:'system'`,
169
+ `data.systemEvent:'ax-node-orphan'`) naming what was re-anchored/removed, so the
170
+ human and a resuming agent can see the change. The note is append-only (not
171
+ rolled back on undo, not duplicated on redo). See `docs/ax-state-contract.md`.
172
+
173
+ - **AX state ops migrated to the operation registry (plan-007 Slice B, wave 1).**
174
+ `canvas_get_ax`, `canvas_set_ax_focus`, `canvas_set_ax_policy`, and
175
+ `canvas_report_host_capability` are now defined once in
176
+ `src/server/operations/ops/ax-state.ts` and shared by HTTP, MCP, and the SDK
177
+ (legacy hand-written handlers + tools + CanvasAccess methods deleted). Tool
178
+ names, MCP result shapes, SSE frames (`ax-state-changed`), and `source`
179
+ defaulting are unchanged. One deliberate HTTP expansion: `GET /api/canvas/ax`
180
+ now returns `{ ok, state, host, context }` (previously `{ ok, state }`) — the
181
+ registry serves one wire body that the remote-MCP invoker also consumes, so the
182
+ `canvas_get_ax` aggregate is preserved across local and remote MCP. Additive;
183
+ existing clients that read `state` are unaffected.
184
+
185
+ - **AX work / review / gate ops migrated to the operation registry (plan-007
186
+ Slice B, wave 2).** `canvas_add_work_item`, `canvas_update_work_item`,
187
+ `canvas_add_review_annotation`, `canvas_request_approval`,
188
+ `canvas_resolve_approval`, `canvas_request_elicitation`,
189
+ `canvas_respond_elicitation`, `canvas_request_mode`, `canvas_resolve_mode`
190
+ (plus the HTTP-only review-annotation update) are now defined once in
191
+ `src/server/operations/ops/ax-work.ts` (legacy handlers + tools + CanvasAccess
192
+ methods deleted). Tool names, success wire shapes, denial bodies (and their
193
+ 404/400 status codes), SSE frames, and `source` defaulting are unchanged.
194
+ Cross-surface unification (same class as plan-005 slices 1–4): the
195
+ `update`/`resolve`/`respond` MCP tools previously returned a success-shaped
196
+ `{ ok:false, <item>:null }` on a missing/already-resolved target; they now
197
+ surface the HTTP 404 as an MCP `isError` result with the same message —
198
+ success shapes are unchanged.
199
+
200
+ - **AX timeline / delivery / command ops migrated to the operation registry
201
+ (plan-007 Slice B, wave 3).** `canvas_record_ax_event`, `canvas_add_evidence`,
202
+ `canvas_send_steering`, `canvas_get_ax_timeline`, `canvas_claim_ax_delivery`,
203
+ `canvas_mark_ax_delivery`, and `canvas_invoke_command` are now defined once in
204
+ `src/server/operations/ops/ax-timeline.ts` (legacy handlers + tools +
205
+ CanvasAccess methods deleted). Tool names, success wire shapes (`{ ok, event }`,
206
+ `{ ok, evidence }`, `{ ok, steering }`, the timeline aggregate, `{ ok, delivered }`),
207
+ the timeline SSE frame (`ax-event-created`, distinct from the wave-1/2
208
+ `ax-state-changed`), the `delivery.mark` `{ steeringDelivered }` frame (emitted
209
+ only when a message is actually marked), the loop-safe consumer scoping of
210
+ `canvas_claim_ax_delivery` (steering + `pendingActivity` both filtered), the
211
+ command allowlist (unknown name → 400 `Unknown command "…"`), and `source`
212
+ defaulting are unchanged. Two deliberate unifications: (1) `GET
213
+ /api/canvas/ax/delivery/pending` now returns `{ ok, pending, pendingActivity }`
214
+ (previously `{ ok, pending }`) so the one wire body matches the
215
+ `canvas_claim_ax_delivery` aggregate across local and remote MCP — additive,
216
+ existing clients reading `pending` are unaffected; (2) `canvas_invoke_command`
217
+ on an unknown name previously returned a success-shaped `{ ok:false, event:null }`
218
+ over MCP and now surfaces the HTTP 400 as an MCP `isError` (same class as the
219
+ wave-2 resolve/respond unification). The legacy `GET /api/canvas/ax/command`
220
+ registry list, `canvas_ingest_activity`, and `canvas_ax_interaction` remain on
221
+ the standalone surface (deferred to later waves).
222
+
223
+ - **AX long-poll gate reads migrated to the operation registry (plan-007 Slice
224
+ B, wave 4).** `canvas_await_approval`, `canvas_await_elicitation`, and
225
+ `canvas_await_mode` (the report primitive D "gates that actually gate" reads,
226
+ HTTP `GET /api/canvas/ax/{approval,elicitation,mode}/:id`) are now defined once
227
+ in `src/server/operations/ops/ax-await.ts` (legacy MCP tools, HTTP single-read
228
+ handlers, and the orphaned CanvasAccess `await*` methods deleted; the public
229
+ SDK `PmxCanvas.await*` methods stay). The async handler performs the wait via
230
+ `waitForAxResolution` (`status !== 'pending'` resolution predicate for all
231
+ three, matching the legacy HTTP + SDK). `timeoutMs` is normalized to one field
232
+ the handler reads: HTTP parses `?waitMs` (absent/non-positive ⇒ 0 = immediate
233
+ read), MCP defaults an omitted `timeoutMs` to 30000; both clamp to
234
+ [0, 120000]. Tool names, the `{ <gate>, pending }` success shapes, and the
235
+ HTTP 404 status on a missing gate are unchanged. Two documented behavior
236
+ changes, both the plan's accepted long-poll tradeoff: (1) the HTTP handler no
237
+ longer aborts the wait on client disconnect — the registry handler has no
238
+ access to the `Request`, so the wait runs to its (≤120s, subscription-based,
239
+ cheap) timeout instead of honoring `req.signal`; resolution detection,
240
+ timeout, and the `{ value, pending }` result are otherwise identical. (2) The
241
+ HTTP missing-gate body changed from `{ ok:false, error:"<gate> not found." }`
242
+ to `{ ok:false, <gate>:null, pending:false }` (status stays 404) so one wire
243
+ body serves both surfaces; the in-process (Local) MCP path still round-trips
244
+ the success-shaped `{ <gate>:null }` JSON exactly as before. (Over a remote
245
+ MCP transport — untested for these reads — an await-on-missing now surfaces as
246
+ an `isError` rather than the legacy `{ <gate>:null }`, since the generic
247
+ HTTP invoker throws on 404.)
248
+
249
+ - **Removing a missing node now errors on every surface (plan-005 slice 1).**
250
+ Node CRUD + layout reads (`node.add` / `node.get` / `node.update` /
251
+ `node.remove` / `layout.get`) are defined once in a new operation registry
252
+ (`src/server/operations/`) shared by HTTP, MCP, CLI, and the SDK. As part of
253
+ the unification, `canvas_remove_node` over local (in-process) MCP access and
254
+ `PmxCanvas.removeNode` no longer silently succeed on an unknown node id —
255
+ they now report the same `Node "<id>" not found.` error the HTTP DELETE
256
+ already returned (404). The SDK's `updateNode` also gained the HTTP superset
257
+ patch semantics (webpage `titleSource`/`url`, html top-level `html` /
258
+ `axCapabilities`, group `children`), erasing long-standing cross-surface
259
+ drift. Wire shapes and MCP tool names are unchanged.
260
+
261
+ - **Edges, arrange/focus/fit/clear, and groups migrated to the operation
262
+ registry (plan-005 slice 2).** Same unification class as slice 1:
263
+ `canvas_remove_edge` over local MCP access now errors on a missing edge
264
+ (remote already did); `canvas_create_group` over local access now rejects
265
+ missing child ids (HTTP already did); `canvas_focus_node` on a missing node
266
+ now returns a proper MCP error result; `canvas_ungroup` failure text is now
267
+ the HTTP message `Group not found or empty.`. Group and focus SSE frames now
268
+ carry the standard sessionId/timestamp envelope like every other mutation.
269
+ HTTP paths, wire shapes, and MCP tool names are unchanged.
270
+
271
+ - **Pins, search, history, undo/redo, and snapshots migrated to the operation
272
+ registry (plan-005 slice 3).** Same unification class as earlier slices:
273
+ pin updates are now computed server-side authoritatively; undo/redo over
274
+ local MCP now use the HTTP emit set; restore/delete/diff on a missing
275
+ snapshot now return the same error on every surface; percent-encoded
276
+ snapshot ids now work on DELETE. POST `/api/canvas/context-pins` honors an
277
+ optional `mode` field (`set`/`add`/`remove`, default `set`). Plain-text
278
+ legacy snapshot error bodies became the standard `{ ok:false, error }`
279
+ envelope. HTTP paths, wire shapes, and MCP tool names are unchanged.
280
+
281
+ - **json-render, graph, streaming, schema, and spec-validation migrated to the
282
+ operation registry (plan-005 slice 4 / migration item 6).** `jsonrender.add`,
283
+ `jsonrender.stream`, `graph.add`, `schema.describe`, and `spec.validate` are
284
+ now defined once and shared by HTTP, MCP, CLI, and the SDK. The frame-height
285
+ alias triangle (`heightPx` from the SDK, `nodeHeight` over HTTP/MCP, `height`
286
+ for chart content, plus `size.height`) is absorbed into one schema with a
287
+ legacy-exact resolution order (`nodeHeight ?? heightPx ?? size.height`). The
288
+ CLI `node add --type json-render`/`--type graph` and `graph add` commands now
289
+ route through the registry invoker instead of hand-written `fetch` paths, and
290
+ the SDK's `streamJsonRenderNode` shares the create-or-append core with the op
291
+ handler. `spec.validate`'s graph branch now honors the full graph payload
292
+ surface (previously the HTTP handler silently dropped fields the MCP tool and
293
+ graph-create route honored). The spec-validation error body keeps its exact
294
+ `{ ok, error, type }` shape. HTTP paths, wire shapes, and MCP tool names are
295
+ unchanged.
296
+
297
+ - **Smaller consolidation behavior changes (surfaced by the v0.2.0 release review,
298
+ documented here for completeness).** Beyond the slice entries above, the unification
299
+ also changed a few smaller behaviors: (1) `PmxCanvas.addNode({ type:"html" })` and
300
+ `canvas_add_node { type:"html" }` *without* an explicit size now default to **720×640**
301
+ (was 360×200 on those two surfaces — their size ladder lacked an html case; this
302
+ converges to the documented html default and the dedicated `canvas_add_html_node`).
303
+ (2) The HTTP `POST /api/canvas/mcp-app/open` and `/api/canvas/diagram` success bodies no
304
+ longer echo `serverName` / `toolName` (both are caller-supplied inputs and remain on the
305
+ `ext-app-open` / `ext-app-result` SSE frames; the MCP results were never affected).
306
+ (3) `webview start`'s `dataStoreDir` is now workspace-sandboxed on the HTTP surface too
307
+ (out-of-workspace → 400), matching the MCP tool. (4) The batch unsupported-operation
308
+ error text is now `Unsupported canvas_batch operation "<op>".`. None of these change a
309
+ frozen tool name, input schema, or documented success response shape.
310
+
311
+ ### Deprecated
312
+
313
+ - **`canvas_validate` and `canvas_remove_annotation` superseded by composite
314
+ actions (plan-008 Wave 1).** Both tools now carry a `Deprecated: use canvas_x
315
+ with action "y".` prefix on their tool description (derived automatically from
316
+ the composite definitions): `canvas_validate` → `canvas_query` action
317
+ `validate`; `canvas_remove_annotation` → `canvas_view` action
318
+ `remove-annotation`. They keep working through v0.2 and are removed in v0.3 per
319
+ `docs/api-stability.md`.
320
+
321
+ - **The 5 webview MCP tools superseded by the `canvas_webview` composite
322
+ (plan-008 Wave 3).** `canvas_webview_status`, `canvas_webview_start`,
323
+ `canvas_webview_stop`, `canvas_resize`, and `canvas_evaluate` now carry a
324
+ `Deprecated: use canvas_webview with action "y".` prefix on their tool
325
+ description (derived automatically from the composite definition):
326
+ `canvas_webview_status` → action `status`; `canvas_webview_start` → `start`;
327
+ `canvas_webview_stop` → `stop`; `canvas_resize` → `resize`; `canvas_evaluate`
328
+ → `evaluate`. They keep working through v0.2 (now registry-served) and are
329
+ removed in v0.3 per `docs/api-stability.md`. `canvas_screenshot` is NOT
330
+ deprecated — it stays standalone (binary payload).
331
+
332
+ - **The 3 external / built-content MCP tools superseded by the `canvas_app`
333
+ composite (plan-008 Wave 4).** `canvas_open_mcp_app`, `canvas_add_diagram`, and
334
+ `canvas_build_web_artifact` now carry a `Deprecated: use canvas_app with action
335
+ "y".` prefix on their tool description (derived automatically from the composite
336
+ definition): `canvas_open_mcp_app` → action `open-mcp-app`; `canvas_add_diagram`
337
+ → `diagram`; `canvas_build_web_artifact` → `build-artifact`. They keep working
338
+ through v0.2 (now registry-served) and are removed in v0.3 per
339
+ `docs/api-stability.md`.
340
+
341
+ - **The 3 HTML/webpage MCP tools superseded by `canvas_node` (plan-008 Wave 5 — the
342
+ final fold).** `canvas_add_html_node` → `canvas_node` with `action: "add"` and
343
+ `type: "html"`; `canvas_add_html_primitive` → `canvas_node` with `action: "add"`,
344
+ `type: "html"`, `primitive: "<kind>"` (and `data`); `canvas_refresh_webpage_node`
345
+ → `canvas_node` with `action: "update"` and `refresh: true`. All three now carry a
346
+ `Deprecated: use canvas_node …` prefix on their tool description. **No new action,
347
+ composite mechanism, op, or SDK change was needed** — the earlier plan-008 verdict
348
+ that a fold required a per-action input-injection mechanism was wrong:
349
+ `node.add` already routes `type:"html"` + `primitive|kind` → `createHtmlPrimitiveNode`
350
+ and merges the top-level html fields (`summary`/`agentSummary`/`description`/
351
+ `presentation`/`slideTitles`/`embeddedNodeIds`/`embeddedUrls`) into the node data,
352
+ and `node.update` already routes `refresh:true` → `refreshCanvasWebpageNode`. Two
353
+ small correctness fixes made the fold sound: (a) `axCapabilities` is now **advertised**
354
+ in `node.add`'s schema (it previously only passed through `z.looseObject`, so
355
+ schema-guided agents migrating off `canvas_add_html_node` would silently drop the
356
+ AX-bridge config); (b) `node.update`'s `formatResult` now surfaces a FAILED webpage
357
+ refresh as `isError` + `{ ok:false, error }` (it previously masked it as a false
358
+ `{ ok:true }` over the MCP-default local invoker — a live bug), matching the HTTP
359
+ 400 and the legacy tool. Parity (incl. the refresh failure path) is proven by
360
+ `tests/unit/mcp-composites.test.ts`. All three keep working through v0.2 and are
361
+ removed in v0.3 per `docs/api-stability.md`.
362
+
363
+ - **Single-purpose MCP tools superseded by wave-1 composites.** The standalone
364
+ tools folded by the composites above (`canvas_add_node`, `canvas_get_node`,
365
+ `canvas_update_node`, `canvas_remove_node`, `canvas_add_edge`,
366
+ `canvas_remove_edge`, `canvas_arrange`, `canvas_focus_node`, `canvas_fit_view`,
367
+ `canvas_clear`, `canvas_create_group`, `canvas_group_nodes`, `canvas_ungroup`,
368
+ `canvas_undo`, `canvas_redo`, `canvas_search`, `canvas_get_layout`,
369
+ `canvas_add_json_render_node`, `canvas_stream_json_render_node`,
370
+ `canvas_add_graph_node`, `canvas_describe_schema`, `canvas_validate_spec`) now
371
+ carry a `Deprecated: use canvas_x with action "y".` prefix on their tool
372
+ description (derived automatically from the composite definitions). They keep
373
+ working through v0.2 and are removed in v0.3 per `docs/api-stability.md`.
374
+
375
+ - **Legacy AX MCP tools superseded by the AX composites (wave 2).** The 22
376
+ standalone AX tools folded by the composites above now carry a `Deprecated: use
377
+ canvas_x …` prefix on their tool description (derived automatically from the
378
+ composite definitions): `canvas_get_ax`, `canvas_set_ax_focus`,
379
+ `canvas_set_ax_policy`, `canvas_report_host_capability` → `canvas_ax_state`;
380
+ `canvas_add_work_item`, `canvas_update_work_item`, `canvas_add_review_annotation`
381
+ → `canvas_ax_work`; `canvas_request_approval`, `canvas_resolve_approval`,
382
+ `canvas_await_approval`, `canvas_request_elicitation`,
383
+ `canvas_respond_elicitation`, `canvas_await_elicitation`, `canvas_request_mode`,
384
+ `canvas_resolve_mode`, `canvas_await_mode` → `canvas_ax_gate` (with kind/action);
385
+ `canvas_get_ax_timeline`, `canvas_record_ax_event`, `canvas_add_evidence`,
386
+ `canvas_send_steering` → `canvas_ax_timeline`; `canvas_claim_ax_delivery`,
387
+ `canvas_mark_ax_delivery` → `canvas_ax_delivery`. They keep working through v0.2
388
+ and are removed in v0.3 per `docs/api-stability.md`.
389
+
390
+ ### Fixed
391
+
392
+ - **Webview `evaluate` / `resize` runtime failures return the legacy `400` JSON, not a
393
+ `500` HTML overlay (v0.2.0 release-review fix).** During the registry migration these
394
+ ops let the automation runner's plain `Error` (e.g. "no active session") escape
395
+ `dispatchOperationRoute` (which maps only `OperationError`); with no `Bun.serve`
396
+ request-level boundary, Bun rendered its dev error overlay — HTTP `500 text/html`
397
+ disclosing the absolute server source path. The handlers now convert runner failures to
398
+ the legacy `400 { ok:false, error, webview }` contract, the dispatcher returns a clean
399
+ JSON `500` for any other unexpected throw, and a `Bun.serve` `error()` boundary
400
+ guarantees no route can ever render the overlay. Covered by a new no-session regression
401
+ test.
402
+ - **In-place mcp-app / diagram update preserves the node's existing title (v0.2.0
403
+ release-review fix).** Re-opening an existing `mcp-app` node in place *without* a
404
+ `title` no longer resets the node title to the external tool's name — the
405
+ `ext-app-open` title fallback restores the legacy `targetNode.data.title` step.
406
+
407
+ - **json-render and graph viewer iframes crashed at mount (blank iframes).**
408
+ Bun 1.3.14 ignores the `NODE_ENV` define for JSX dev/prod selection when a
409
+ tsconfig is in scope, so the viewer bundle shipped the dev JSX transform
410
+ (`jsxDEV`) against production React, which exports it as `undefined`. Every
411
+ json-render/graph iframe failed with `TypeError: t is not a function`.
412
+ `scripts/build-json-render.sh` now passes `--production` to `bun build`,
413
+ forcing the production JSX transform. Caught by the restored Playwright e2e
414
+ gate (8 tests had been failing silently on main).
415
+
8
416
  ## [0.1.36] - 2026-06-09
9
417
 
10
418
  ### Added
@@ -1826,6 +2234,7 @@ otherwise have to discover by trial and error.
1826
2234
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
1827
2235
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
1828
2236
 
2237
+ [0.2.0]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.2.0
1829
2238
  [0.1.36]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.36
1830
2239
  [0.1.35]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.35
1831
2240
  [0.1.34]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.34
package/Readme.md CHANGED
@@ -105,7 +105,7 @@ the DB so SQLite WAL data is checkpointed into the file.
105
105
 
106
106
  ### 06 / Any agent
107
107
 
108
- Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (69 tools,
108
+ Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (83 tools,
109
109
  14 resources, change notifications), the [CLI](docs/cli.md), the
110
110
  [HTTP API](docs/http-api.md), or the [Bun SDK](docs/sdk.md). Works with
111
111
  Claude Code, GitHub Copilot CLI, Codex, Cursor, Windsurf, or any agent
@@ -255,7 +255,7 @@ the agent can read `canvas://skills` and pull in companion skills
255
255
  the three-tier visual matrix (json-render → html → web-artifact)
256
256
  - **[CLI reference](docs/cli.md)** — full command surface, daemon mode,
257
257
  watch streams, WebView automation
258
- - **[MCP reference](docs/mcp.md)** — 69 tools, 14 resources, change
258
+ - **[MCP reference](docs/mcp.md)** — 83 tools, 14 resources, change
259
259
  notifications, node-type routing
260
260
  - **[HTTP API](docs/http-api.md)** — REST endpoints, SSE, batch operations
261
261
  - **[AX host-adapter contract](docs/ax-host-adapter-contract.md)** — how native