pmx-canvas 0.2.7 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +110 -1
  2. package/Readme.md +13 -11
  3. package/dist/json-render/index.css +1 -1
  4. package/dist/types/mcp/canvas-access.d.ts +0 -7
  5. package/dist/types/server/ax-state-manager.d.ts +3 -2
  6. package/dist/types/server/bundled-skills.d.ts +3 -3
  7. package/dist/types/server/canvas-state.d.ts +2 -0
  8. package/dist/types/server/ext-app-lookup.d.ts +1 -3
  9. package/dist/types/server/operations/composites.d.ts +29 -23
  10. package/dist/types/server/operations/http.d.ts +2 -1
  11. package/dist/types/shared/ax-intent.d.ts +1 -1
  12. package/docs/RELEASE.md +23 -8
  13. package/docs/ax-host-adapter-contract.md +7 -7
  14. package/docs/ax-state-contract.md +7 -6
  15. package/docs/bun-webview-integration.md +5 -5
  16. package/docs/cli.md +1 -1
  17. package/docs/http-api.md +7 -3
  18. package/docs/mcp.md +121 -100
  19. package/docs/node-types.md +45 -35
  20. package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
  21. package/docs/plans/plan-008-registry-finish.md +2 -0
  22. package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
  23. package/docs/screenshot.png +0 -0
  24. package/docs/tech-debt-assessment-2026-06.md +1 -1
  25. package/docs/tech-debt-assessment-2026-07.md +135 -0
  26. package/package.json +1 -1
  27. package/skills/data-analysis/SKILL.md +3 -3
  28. package/skills/frontend-design/SKILL.md +3 -2
  29. package/skills/json-render-mcp/SKILL.md +4 -3
  30. package/skills/playwright-cli/SKILL.md +1 -1
  31. package/skills/pmx-canvas/SKILL.md +9 -6
  32. package/skills/pmx-canvas/evals/evals.json +2 -2
  33. package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
  34. package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
  35. package/skills/pmx-canvas/references/full-reference.md +48 -53
  36. package/skills/pmx-canvas/references/html-primitives.md +3 -3
  37. package/skills/tufte-viz/SKILL.md +5 -4
  38. package/skills/web-artifacts-builder/SKILL.md +4 -4
  39. package/src/mcp/canvas-access.ts +4 -99
  40. package/src/mcp/server.ts +74 -201
  41. package/src/server/ax-state-manager.ts +3 -2
  42. package/src/server/bundled-skills.ts +3 -3
  43. package/src/server/canvas-schema.ts +46 -29
  44. package/src/server/canvas-state.ts +5 -0
  45. package/src/server/code-graph.ts +1 -1
  46. package/src/server/ext-app-lookup.ts +1 -8
  47. package/src/server/index.ts +1 -1
  48. package/src/server/intent-registry.ts +1 -1
  49. package/src/server/operations/composites.ts +39 -39
  50. package/src/server/operations/http.ts +3 -2
  51. package/src/server/operations/mcp.ts +7 -6
  52. package/src/server/operations/ops/app.ts +4 -4
  53. package/src/server/operations/ops/nodes.ts +5 -5
  54. package/src/server/operations/ops/query.ts +19 -8
  55. package/src/server/operations/ops/snapshots.ts +6 -6
  56. package/src/server/server.ts +30 -2
  57. package/src/shared/ax-intent.ts +1 -1
@@ -45,7 +45,7 @@ When an agent edits a file through its normal tools, the canvas node updates
45
45
  automatically via `fs.watch()`.
46
46
 
47
47
  ```ts
48
- canvas_add_node({ type: 'file', content: 'src/server/index.ts' })
48
+ canvas_node({ action: 'add', type: 'file', content: 'src/server/index.ts' })
49
49
  ```
50
50
 
51
51
  ## Image nodes
@@ -55,7 +55,8 @@ HTTP(S)-backed images preserve provenance so agents can tell where evidence
55
55
  came from. Nodes can carry validation status or warnings.
56
56
 
57
57
  ```ts
58
- canvas_add_node({
58
+ canvas_node({
59
+ action: 'add',
59
60
  type: 'image',
60
61
  content: 'artifacts/dashboard.png',
61
62
  data: {
@@ -73,8 +74,8 @@ keep enough information for an agent to refresh the node from the original
73
74
  URL later.
74
75
 
75
76
  ```ts
76
- canvas_add_node({ type: 'webpage', url: 'https://example.com/docs' })
77
- canvas_refresh_webpage_node({ id: 'node-abc123' })
77
+ canvas_node({ action: 'add', type: 'webpage', url: 'https://example.com/docs' })
78
+ canvas_node({ action: 'update', id: 'node-abc123', refresh: true })
78
79
  ```
79
80
 
80
81
  ## MCP App nodes
@@ -82,11 +83,12 @@ canvas_refresh_webpage_node({ id: 'node-abc123' })
82
83
  `mcp-app` nodes embed other MCP servers' UI resources (`ui://...`) directly
83
84
  on the canvas as sandboxed iframes. Any server implementing the
84
85
  [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps)
85
- can be opened with `canvas_open_mcp_app`.
86
+ can be opened with `canvas_app { action: "open-mcp-app" }`.
86
87
 
87
88
  Generic `pmx-canvas node add --type mcp-app` is intentionally rejected —
88
- these nodes need tool/session metadata. Use `canvas_open_mcp_app` (or the
89
- `canvas_add_diagram` Excalidraw preset) instead.
89
+ these nodes need tool/session metadata. Use `canvas_app { action:
90
+ "open-mcp-app" }` (or the `canvas_app { action: "diagram" }` Excalidraw
91
+ preset) instead.
90
92
 
91
93
  ### Excalidraw preset (hand-drawn diagrams)
92
94
 
@@ -95,7 +97,8 @@ server at `https://mcp.excalidraw.com/mcp`. PMX Canvas exposes a one-call
95
97
  preset:
96
98
 
97
99
  ```ts
98
- canvas_add_diagram({
100
+ canvas_app({
101
+ action: 'diagram',
99
102
  elements: [
100
103
  { type: 'rectangle', id: 'a', x: 80, y: 120, width: 180, height: 80,
101
104
  roundness: { type: 3 }, backgroundColor: '#a5d8ff', fillStyle: 'solid',
@@ -111,8 +114,8 @@ canvas_add_diagram({
111
114
  });
112
115
  ```
113
116
 
114
- For any other MCP App, call `canvas_open_mcp_app` directly with the server's
115
- transport, tool name, and arguments.
117
+ For any other MCP App, call `canvas_app { action: "open-mcp-app" }` directly
118
+ with the server's transport, tool name, and arguments.
116
119
 
117
120
  ## json-render nodes
118
121
 
@@ -122,7 +125,8 @@ transport, tool name, and arguments.
122
125
  and component catalog (core + react + shadcn).
123
126
 
124
127
  ```ts
125
- canvas_add_json_render_node({
128
+ canvas_render({
129
+ action: 'add-json-render',
126
130
  title: 'Deploy status',
127
131
  spec: {
128
132
  root: 'card',
@@ -144,7 +148,8 @@ host-provided handlers. PMX wires AX handlers named after interaction types, so
144
148
  spec action named `ax.*` becomes a capability-gated AX interaction:
145
149
 
146
150
  ```ts
147
- canvas_add_json_render_node({
151
+ canvas_render({
152
+ action: 'add-json-render',
148
153
  title: 'Approve plan',
149
154
  spec: {
150
155
  root: 'btn',
@@ -164,8 +169,8 @@ source + per-viewer nonce + node id) and submits it server-side; `json-render` /
164
169
  `graph` viewers are sandboxed surfaces, so caller-supplied `nodeIds` are clamped
165
170
  to the node's own id. See the [MCP reference](mcp.md#node-interactions-capability-gated).
166
171
 
167
- Use `canvas_describe_schema` / `canvas_validate_spec` to introspect the
168
- component catalog before building a spec.
172
+ Use `canvas_render { action: "describe-schema" }` / `canvas_render { action:
173
+ "validate" }` to introspect the component catalog before building a spec.
169
174
 
170
175
  ## HTML nodes
171
176
 
@@ -182,7 +187,9 @@ match the active theme. Theme updates are posted into sandboxed HTML iframes,
182
187
  so theme-aware HTML can follow dark/light switches without reopening the node.
183
188
 
184
189
  ```ts
185
- canvas_add_html_node({
190
+ canvas_node({
191
+ action: 'add',
192
+ type: 'html',
186
193
  title: 'Cost projection',
187
194
  html: '<canvas id="c"></canvas><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><script>...</script>',
188
195
  })
@@ -213,8 +220,10 @@ presentation, explainer, status report, incident report, triage board, config
213
220
  editor, or prompt tuner.
214
221
 
215
222
  ```ts
216
- canvas_add_html_primitive({
217
- kind: 'choice-grid',
223
+ canvas_node({
224
+ action: 'add',
225
+ type: 'html',
226
+ primitive: 'choice-grid',
218
227
  title: 'Implementation options',
219
228
  data: {
220
229
  items: [
@@ -246,9 +255,9 @@ A **web artifact** is a single-file, fully bundled HTML app (React + Tailwind
246
255
  real interactive app — charts, forms, mini-dashboards — beyond what a static
247
256
  node or `html` snippet can express.
248
257
 
249
- `canvas_build_web_artifact` takes source strings (`App.tsx`, optional
250
- `index.css`, `main.tsx`, `index.html`, plus extra files), runs the bundled
251
- web-artifacts-builder scripts, writes the self-contained HTML to
258
+ `canvas_app { action: "build-artifact" }` takes source strings (`App.tsx`,
259
+ optional `index.css`, `main.tsx`, `index.html`, plus extra files), runs the
260
+ bundled web-artifacts-builder scripts, writes the self-contained HTML to
252
261
  `.pmx-canvas/artifacts/<slug>.html`, and (by default) opens it in the canvas.
253
262
 
254
263
  ```bash
@@ -277,7 +286,7 @@ as dashed-border frames with a title bar and optional accent color.
277
286
  lay children out inside it
278
287
 
279
288
  ```ts
280
- canvas_create_group({ title: 'Auth Module', childIds: ['node-1', 'node-2'], color: '#4a9eff' })
289
+ canvas_group({ action: 'create', title: 'Auth Module', childIds: ['node-1', 'node-2'], color: '#4a9eff' })
281
290
  ```
282
291
 
283
292
  ## Edge types
@@ -296,25 +305,26 @@ All edges support labels, styles (solid/dashed/dotted), and animation.
296
305
  Agents don't have to guess node shapes. The running server exposes its create
297
306
  schemas, json-render component catalog, and node-type examples:
298
307
 
299
- - `canvas_describe_schema` / `GET /api/canvas/schema` — list all node-create
300
- schemas, required fields, json-render components, HTML primitives, and sample payloads
301
- - `canvas_validate_spec` / `POST /api/canvas/schema/validate` — validate a
308
+ - `canvas_render { action: "describe-schema" }` / `GET /api/canvas/schema` — list all
309
+ node-create schemas, required fields, json-render components, HTML primitives, and sample payloads
310
+ - `canvas_render { action: "validate" }` / `POST /api/canvas/schema/validate` — validate a
302
311
  json-render spec, graph payload, or HTML primitive payload **without** creating a node
303
- - `canvas_validate` / `GET /api/canvas/validate` — validate the current
312
+ - `canvas_query { action: "validate" }` / `GET /api/canvas/validate` — validate the current
304
313
  layout for collisions, containment, and missing edge endpoints
305
314
  - `canvas://schema` — the same data as an MCP resource
306
315
 
307
316
  The CLI's `node schema` / `validate spec` subcommands surface the same data
308
317
  from the terminal.
309
318
 
310
- MCP node creation uses dedicated tools for structured node families. Read
311
- `mcp.nodeTypeRouting` from `canvas_describe_schema` when in doubt:
312
- `json-render` `canvas_add_json_render_node`,
313
- `graph` → `canvas_add_graph_node`,
314
- `html-primitive` → `canvas_add_html_primitive`,
315
- `html` → `canvas_add_html_node`,
316
- `web-artifact` → `canvas_build_web_artifact`,
317
- `mcp-app` → `canvas_open_mcp_app`,
318
- `group` → `canvas_create_group`.
319
+ MCP node creation uses dedicated composite actions for structured node
320
+ families. Read `mcp.nodeTypeRouting` from `canvas_render { action:
321
+ "describe-schema" }` when in doubt:
322
+ `json-render` → `canvas_render { action: "add-json-render" }`,
323
+ `graph` → `canvas_render { action: "add-graph" }`,
324
+ `html-primitive` → `canvas_node { action: "add", type: "html", primitive: "<kind>" }`,
325
+ `html` → `canvas_node { action: "add", type: "html" }`,
326
+ `web-artifact` → `canvas_app { action: "build-artifact" }`,
327
+ `mcp-app` → `canvas_app { action: "open-mcp-app" }`,
328
+ `group` → `canvas_group { action: "create" }`.
319
329
  Basic nodes (`markdown`, `status`, `file`, `image`, `webpage`) use
320
- `canvas_add_node`.
330
+ `canvas_node { action: "add" }`.
@@ -1,6 +1,8 @@
1
1
  # Plan 006: MCP tool consolidation (69 tools to 21)
2
2
 
3
- **Status:** In progress — wave 1 landed (7 canvas composites) and the AX wave landed (5 composites: `canvas_ax_state`, `canvas_ax_work`, `canvas_ax_gate`, `canvas_ax_timeline`, `canvas_ax_delivery`). Remaining: `canvas_snapshot` (name held by the legacy save tool until v0.3), `canvas_app`/`canvas_webview` (need plan-005 item 8), and the deferred actions `refresh` / `add-primitive` / `remove-annotation` / board validation.
3
+ **Status: completed in v0.3.0 (canvas_snapshot composite moved to v0.4).**
4
+
5
+ **Status (historical):** In progress — wave 1 landed (7 canvas composites) and the AX wave landed (5 composites: `canvas_ax_state`, `canvas_ax_work`, `canvas_ax_gate`, `canvas_ax_timeline`, `canvas_ax_delivery`). Remaining (closed by plan-008 + the v0.3.0 removal): `canvas_snapshot` (name held by the legacy save tool until v0.4), `canvas_app`/`canvas_webview` (landed via plan-005 item 8), and the deferred actions `refresh` / `add-primitive` / `remove-annotation` / board validation (landed via plan-008 waves 1 and 5).
4
6
  **Date:** 2026-06-12
5
7
  **Depends on:** plan-005 (operation registry). Slices 1-4 are migrated; consolidation lands per-domain as the corresponding registry slices complete.
6
8
  **Motivation:** docs/tech-debt-assessment-2026-06.md item 2. Governed by docs/api-stability.md (deprecation: marked in one minor, removed in the next).
@@ -1,5 +1,7 @@
1
1
  # Plan 008 — Finish the operation-registry refactor (plan-005 items 8–9 + plan-006 completion)
2
2
 
3
+ **Status: completed in v0.3.0 (canvas_snapshot composite moved to v0.4).**
4
+
3
5
  **Status:** Complete
4
6
  **Date:** 2026-06-15
5
7
  **Depends on:** plan-005 (registry — slices 1–7 merged), plan-006 (consolidation — waves 1–2 merged), plan-007 (AX domain — merged).
@@ -0,0 +1,51 @@
1
+ # Plan 009 — Tech debt backlog (open findings from the July 2026 assessment)
2
+
3
+ **Status:** Open
4
+ **Date:** 2026-07-06
5
+ **Motivation:** [`docs/tech-debt-assessment-2026-07.md`](../tech-debt-assessment-2026-07.md) (revised 2026-07-06). The v0.3.0 tree plus the same-day fix pass closed H1, H2, M12, and the small residuals of C2/C3/M4/M7/M9/L7. This plan records everything still open so it survives the release. Finding IDs below refer to the assessment, which carries the full `file:line` evidence.
6
+
7
+ Each item is tagged by shape: **project** (multi-day, own plan/PR), **decision** (needs a maintainer call before code), or **small** (safe standalone change).
8
+
9
+ ## Phase 1 — finish what was started
10
+
11
+ | Item | Source | What's open | Shape |
12
+ |---|---|---|---|
13
+ | Complete the registry migration | C1 | Fold the ~50 remaining hand-written routes (44 tombstone comments) in `server.ts` into operations; collapse the 76-method `PmxCanvas` onto `executeOperation`; migrate the CLI's 60 raw `fetch` calls onto the operation invoker. Deletes the M5 handler clones and most M4 envelope inconsistency as side effects. | project |
14
+ | One HTTP envelope | M4 residual | Registry ops return bare bodies, legacy ext-app handlers `{ok:true,result}`, 404s plaintext. Unify (naturally falls out of C1). Also: surface persistence save failures via a `/health` degraded flag — `canvas-state.ts` has 63 catch→warn→continue sites. | project (with C1) |
15
+ | `dist/` commit policy | C3 residual | Decide whether `dist/canvas` + `dist/types` stay committed; if yes, add a CI staleness check (every source change currently hand-carries `.d.ts` diffs). | decision |
16
+ | Skill-mirror validator | C2 residual | `validate:agent-skills` exits 0 when the (local-only, gitignored) trees are absent. Docs are honest about this now; decide whether to repoint, gate, or delete the check. | decision |
17
+ | Generate CLAUDE.md/AGENTS.md from one source | M9 residual | The twins are byte-identical again but still hand-maintained; generate or symlink to prevent the next drift. | small |
18
+
19
+ ## Phase 2 — make the safety net real
20
+
21
+ | Item | Source | What's open | Shape |
22
+ |---|---|---|---|
23
+ | Lint/format tooling | H6 | No ESLint/Biome/Prettier anywhere; `docs/RELEASE.md` claims `release:check` lints (it doesn't). Adopt Biome, wire into CI and `release:check`. First run will be a large mechanical diff — land it standalone. | decision + project |
24
+ | Client + server unit coverage | H7 | Zero component render tests (no DOM test lib in repo); untested big modules: `ax-state-manager.ts` (826 lines), `mcp-app-host.ts` (814), `canvas-schema.ts` (644), `shared/semantic-attention.ts` (600, in zero test files). Coverage is collected but gates nothing; tests aren't typechecked. Add `happy-dom` + `@testing-library/preact`, a tests tsconfig, and a coverage threshold. | project |
25
+ | Daemon port/lock lifecycle | H4 | The server does port fallback but `cli/index.ts:202,268,302` polls the *requested* port: a fallen-back daemon reads as dead and `serve stop` can't kill it. TOCTOU double-spawn between health check and spawn; `isProcessRunning` treats `EPERM` as alive with no PID-recycling guard. Fix: child reports its actual bound port; parent locks before spawn and records the pid immediately. Root cause of the intermittently flaky `pmx-canvas-sdk.test.ts` port collision. | project (small-ish, high value) |
26
+ | Test-suite structure | M8 | Integration monoliths under `tests/unit/` (`server-api` 5,213 lines, `cli-node` 2,903); `getAvailablePort()` copy-pasted in 5 files; `waitForPersistence(650ms)` fixed sleeps in 9 files + ~19 magic `Bun.sleep`s; Playwright lacks `retries`/`forbidOnly`; `test:e2e-cli` (517-line script) runs in no CI workflow. | project |
27
+ | `noUncheckedIndexedAccess` | L7 residual | Enable in tsconfig; will surface many index-access errors — treat as its own sweep. | small (noisy) |
28
+
29
+ ## Phase 3 — client consolidation
30
+
31
+ | Item | Source | What's open | Shape |
32
+ |---|---|---|---|
33
+ | One node factory | H5 | `sse-bridge.ts` (1,066 lines) keeps its own `DEFAULT_POSITIONS`/`makeNode` divergent from `canvas-store`; `responseToThreadMap` is not cleaned on SSE reconnect; server-side `syncEventToCanvasState` (~330 lines) mirrors it as an if/else chain with geometry defaults duplicated against `ensureDefaultDockedNodes`. Share one factory; treat `canvas-layout-update` as the single source of truth; table-drive the event sync. | project |
34
+ | Shared AX iframe bridge | M2 | The ~33-line `onAxMessage` listener is copy-pasted in `HtmlNode.tsx`, `McpAppNode.tsx`, `ExtAppFrame.tsx`; `pmx-canvas-ax` protocol string spread across 9 sites. This is the sandboxed-surface trust boundary (prior near-miss: LRN-20260607-005). Extract `useAxSurfaceBridge` + a protocol-constants module. | project (security-adjacent) |
35
+ | `ExtAppFrame.tsx` timing hacks | M3 | 915 lines, 12 refs, 7 `setTimeout`s, 5 rAFs, WebKit remount slot counter, 1200 ms fallback bootstrap, two near-duplicate `buildHostContext` blocks. Extract `useExtAppBridge`; single `buildHostContext`. | project |
36
+ | json-render stack decision | M1 | Preact + React 19 + recharts all runtime deps; 7 exact-pinned `@json-render/*` incl. 3 devtools packages shipped; `dist/json-render/index.js` 1.37 MB and `dist/canvas/index.js` 599 KB both unminified; build script shells to python3 with a stale-bundle fallback. Minimum: minify, move devtools to devDependencies, lazy-load the viewer, replace the python mtime gate. Bigger call: separate optional package if the React stack stays. | decision + project |
37
+ | Client dead code / duplication | L1, L2, L3 | `ContextPinHud.tsx` unreferenced; three pure re-export shims; two parallel markdown-format stacks; `global.css` 3,746 lines with 11 `!important` vs 227 inline styles and unscaled z-indexes. | small (batchable) |
38
+
39
+ ## Smaller items (no phase, pick up opportunistically)
40
+
41
+ | Item | Source | What's open |
42
+ |---|---|---|
43
+ | Per-action composite schemas | M6 | Composite tools advertise every field optional; per-action required fields fail only at runtime. Decide: `oneOf` schemas vs per-action requirement docs. (Now the only schema surface agents see.) |
44
+ | Packaging | M10 | `files` ships both `src/` and `dist/` (~double tarball, two import paths); `bin` points at raw `.ts` (undocumented Bun-only constraint); `@types/turndown` in runtime deps. |
45
+ | Env var hygiene | M11 residual | `PMX_CANVAS_PORT` vs `PMX_WEB_CANVAS_PORT` applied inconsistently across entry points; `PMX_DATA` (15 uses) and the `PMX_CANVAS_JSON_RENDER_*` / `PMX_MCP_APP_HOST_*` families undocumented. |
46
+ | `publish.yml` e2e gap | M11 residual | Publishing skips e2e with no verification that the tagged SHA passed `test.yml`. |
47
+ | MCP notification fan-out | M7 residual | Five `canvas://` resource URIs notify on every mutation regardless of type (`mcp/server.ts:95-99`). |
48
+ | Legacy migration code on every boot | L4 | JSON→SQLite migration runs unconditionally (`canvas-state.ts:758,785`); gate behind a one-shot marker and retire. |
49
+ | Suppression/mutation plumbing | L5 | Two parallel suppression depth-counters; `onMutation` is a single-slot setter wired twice (last-write-wins). |
50
+ | Module-global lifecycle leaks | L6 | ~20 module-level `let`s in `server.ts`; code-graph debounce timer survives `stopCanvasServer`; remote SSE watcher loop has no cancellation. |
51
+ | June doc citations | L7 residual | `tech-debt-assessment-2026-06.md` cites gitignored `.learnings/` entries future maintainers can't read. |
Binary file
@@ -1,6 +1,6 @@
1
1
  # Tech Debt Assessment & Direction Proposal — June 2026
2
2
 
3
- **Status:** Proposed
3
+ **Status:** Superseded by [tech-debt-assessment-2026-07.md](tech-debt-assessment-2026-07.md)
4
4
  **Date:** 2026-06-10
5
5
  **Scope:** Full-repo audit at v0.1.36 (~70k lines TS; src/server 23.2k, src/client 13.9k, src/mcp 4.2k, src/cli 4.0k)
6
6
 
@@ -0,0 +1,135 @@
1
+ # Tech Debt Assessment — July 2026
2
+
3
+ **Status:** Proposed
4
+ **Date:** 2026-07-05, revised 2026-07-06
5
+ **Scope:** Full-repo audit at v0.2.7 (`289b7d6`, ~53.4k lines TS in `src/`, ~23.8k in `tests/`) **plus the uncommitted v0.3.0 working tree** (51 files, +1,085/−1,120 — the 57-legacy-MCP-tool removal, plan-006/plan-008 closure, and the matching docs/skills/test updates)
6
+ **Supersedes:** [`tech-debt-assessment-2026-06.md`](tech-debt-assessment-2026-06.md) (v0.1.36)
7
+ **Method:** Six parallel subsystem audits (server core, client, MCP layer, CLI/SDK/json-render, tests/tooling, docs/config), then a revision pass re-verifying every affected anchor against the dev machine's working tree. Verification on the v0.3.0 tree: `tsc --noEmit` clean; `bun test tests/unit` **567 pass / 0 fail** (the port-collision flake from the first run passed here; its root cause is H4 and still stands).
8
+
9
+ > **Revision note (2026-07-06):** the first revision of this document audited committed v0.2.7 only and missed the uncommitted v0.3.0 change already sitting in the working tree. That change resolves H1, most of H2, and parts of M11/L7 as originally written. This revision corrects those findings, adds M12, and confirms the delta touches nothing in C1–C3, H3–H7, M1–M5, M7–M10, or L1–L6 — those anchors were re-verified and stand.
10
+ >
11
+ > **Fix pass (2026-07-06, same day):** a follow-up pass slated for the v0.3.0 release then closed the small/safe findings: the H2 residual, M7, M12, the malformed-JSON half of M4, most of C3, the C2 doc drift, and the M9/L7 residuals. Each affected finding below carries a "Fix pass" status line. Regression tests live in `tests/unit/http-request-hygiene.test.ts`.
12
+
13
+ ## Verdict
14
+
15
+ June's central diagnosis — one systemic disease of n-way duplication with manual sync — was correct, and the cure is further along than first reported. The operation registry (`src/server/operations/`) exists and is genuinely well-factored: one `defineOperation` record drives HTTP routing, MCP tools, and composites from a single zod schema. E2E Playwright is a hard CI gate. And June's #2 — MCP tool consolidation — is **fully implemented in the uncommitted v0.3.0 working tree**: 84 tools → 27, every one of the 57 removals mapped to its composite replacement in the changelog, the surface pinned by a freeze test, docs and skills updated in the same change. It is disciplined work — and it currently exists on one machine, in zero commits. The cheapest critical action in this report is committing and releasing it.
16
+
17
+ The debt that remains concentrates in two places. First, the **unfinished registry migration**: `server.ts` still interleaves ~50 hand-written route branches with 44 "migrated to the operation registry" tombstone comments, `PmxCanvas` remains a fourth hand-maintained surface of 76 delegating methods, and the CLI bypasses everything with 60 raw `fetch` calls beside 26 registry calls. Second, **governance drift**: CLAUDE.md/AGENTS.md mandate skill-mirror infrastructure that is gitignored and absent from a clean checkout, guarded by a validator that exits green having checked nothing — while 72 generated runtime files and an orphaned 435 KB bundle sit committed.
18
+
19
+ Code hygiene remains excellent for the velocity: zero `as any`, zero dynamic imports, strict TypeScript, clean typecheck, disciplined changelog. The problem is structural, not stylistic — and it is concentrated in exactly the files with the highest churn (`server.ts` was touched in 27 of the last 50 commits).
20
+
21
+ ## Scorecard: June → July
22
+
23
+ | June finding | Status at v0.2.7 + v0.3.0 working tree |
24
+ |---|---|
25
+ | #1 4-layer copy machine → build operation registry | **~60% done.** Registry exists (88 ops); `server.ts` shrank 5,934 → 3,873 lines; `src/mcp/server.ts` 2,861 → 868. Remaining: ~50 legacy routes, `PmxCanvas` (76 methods), CLI (60 raw calls). |
26
+ | #2 69 MCP tools → consolidate to ~20 | **Done in the uncommitted v0.3.0 tree.** 15 composites + 12 standalones = 27 tools; the 57 legacy tools are removed with a per-tool changelog migration map; `mcp-tool-freeze.test.ts` pins the exact 27-name list. Residual: 6 deprecated snapshot standalones fold into a `canvas_snapshot` composite in v0.4. **Not yet committed or released.** |
27
+ | #3 CanvasStateManager mixes concerns | **Partially improved.** AX state largely moved to `ax-state.ts`/`ax-state-manager.ts`; `canvas-state.ts` still 2,136 lines / 87 methods with CRUD + undo + persistence + snapshots. |
28
+ | #4 E2E not a CI gate | **Fixed.** `test.yml` runs Playwright as a hard gate. `publish.yml` still skips e2e (relies on the tag being green on main). |
29
+ | #5 Triple-mirrored skill trees + CLAUDE/AGENTS duplication | **Unresolved** — the mirror trees are gitignored, so docs + CI check reference infrastructure absent from the repo. Re-verified 2026-07-06: CLAUDE.md/AGENTS.md are still 357-line near-identical twins, still differing by the same single demo-board line. |
30
+ | #6 Dual rendering stack | **Unresolved.** Preact + React 19 + recharts + 7 exact-pinned `@json-render` packages all in runtime deps; json-render bundle is 1.37 MB unminified. |
31
+ | #7 No API versioning | **Improved and now exercised.** `docs/api-stability.md` documented the 0.3.0 removal intent; the v0.3.0 working tree consumes that mechanism exactly as designed. |
32
+ | Smaller: `readJson` swallows malformed input | **Unresolved** (`server.ts:923`; registry reader `operations/http.ts:43` inherits the same behavior). |
33
+ | Smaller: giant integration tests posing as unit tests | **Grew.** `server-api.test.ts` 4,950 → 5,213 lines; `cli-node.test.ts` 2,877 → 2,903. |
34
+ | Smaller: client covered by e2e only | **Unresolved.** Still zero component render tests; only pure-helper imports are unit-tested. |
35
+
36
+ ## Findings, ranked
37
+
38
+ ### Critical
39
+
40
+ #### C1. The registry migration is stalled at the halfway point
41
+ The target architecture exists and works; the old one hasn't been deleted. Evidence:
42
+
43
+ - `server.ts` (3,873 lines) interleaves `dispatchOperationRoute` with ~50 hand-written `if (url.pathname === …)` branches and 44 tombstone comments of the form `// POST /api/canvas/ax/mode … migrated to the operation registry (plan-007 Slice B wave 2)` (e.g. `server.ts:3700-3778`).
44
+ - `PmxCanvas` (`src/server/index.ts`, 76 public methods) bypasses the registry and calls `canvas-operations.ts` directly — a fourth hand-maintained surface. `node.add` alone exists as `canvasState.addNode`, `addCanvasNode`, the `node.add` operation, the `canvas_node` MCP action, and `PmxCanvas.addNode`.
45
+ - The CLI (`src/cli/agent.ts`) is a fifth surface: 60 raw `api('POST', '/api/canvas/…')` calls with literal path strings coexist with 26 `invokeOperation(...)` calls that use the registry (`agent.ts:117` vs `:167`) — two dispatch mechanisms with independently implemented error semantics inside one binary.
46
+
47
+ Half-migrated is the most expensive state: every change currently needs to check *both* systems. **Remediation:** finish it — move remaining routes into operations, make `PmxCanvas` a thin wrapper over `executeOperation`, migrate the CLI's raw calls onto the operation invoker. This deletes code in four files at once.
48
+
49
+ #### C2. Governance docs and a CI gate describe infrastructure that doesn't exist in the repo
50
+ CLAUDE.md/AGENTS.md (lines ~317-332) mandate: *"Agent-facing pipeline skills live in `.agents/skills/` and must be mirrored identically in `.claude/skills/` and `.opencode/skills/` … byte-for-byte identical; verify with `bun run validate:agent-skills`."* All three directories are gitignored/absent from a clean checkout. `scripts/validate-agent-skill-mirrors.sh` detects this and exits 0: *"No skills roots present in this checkout; nothing to validate (skipping)."* The 17 skill names it guards have zero overlap with the committed `skills/` tree. Every agent onboarded via CLAUDE.md is instructed to maintain phantom infrastructure, and the check wired into `package.json` guarantees nothing. Re-verified 2026-07-06 — untouched by the v0.3.0 delta. *Fix pass 2026-07-06:* both files now state that the mirror trees are local-only, gitignored dev tooling absent from clean checkouts and that the validator skips (exits 0) when they are absent — the docs no longer misrepresent the repo. Whether to repoint or delete the validator remains a maintainer decision.
51
+
52
+ #### C3. Generated artifacts are committed; runtime output pollutes the tree
53
+ - **72 tracked files under `.pmx-canvas/`** — a full generated web-artifact scaffold (`artifacts/.web-artifacts/sdlc-control-room/` with `pnpm-lock.yaml`, shadcn `ui/*.tsx`, `bundle.html`). The `.gitignore` patterns `artifacts/.web-artifacts/` are root-anchored and don't match the `.pmx-canvas/` copies (`git check-ignore` confirms).
54
+ - **160 tracked files under `dist/`**, including `dist/client/index.js` (435 KB) — an orphaned bundle untouched since the repo's first commit (current builds output to `dist/canvas/`) — and 154 generated `.d.ts` files under `dist/types/`. The v0.3.0 delta illustrates the tax directly: 10 `dist/types/*.d.ts` diffs are hand-carried inside an otherwise source-level change.
55
+ - Running `bun test tests/unit` can drop an untracked `.pmx-canvas/canvas.db` outside the workspace being tested: at least one code path resolves the workspace to `process.cwd()`. Related bug: `code-graph.ts:116` resolves workspace-relative imports against `process.cwd()` instead of the configured `workspaceRoot`, so the code graph silently mis-resolves whenever the server is started for another directory.
56
+
57
+ *Fix pass 2026-07-06:* `.gitignore` now covers `.pmx-canvas/artifacts/.web-artifacts/` and daemon `*.log`/`*.pid` files; the 72 tracked scaffold files are untracked and the orphaned `dist/client` bundle is deleted; `code-graph.ts` resolves against `canvasState.workspaceRoot`. **Remaining:** the deliberate decision whether `dist/canvas`/`dist/types` stay committed (if yes, add a staleness CI check).
58
+
59
+ ### High
60
+
61
+ #### H1. ~~MCP legacy-tool overlap window is overdue~~ — RESOLVED in the v0.3.0 working tree (pending commit/release)
62
+ The 57 legacy tools are removed; 27 remain: 15 composites + `canvas_batch`, `canvas_pin_nodes`, `canvas_invoke_command`, `canvas_ax_interaction`, `canvas_ingest_activity`, `canvas_screenshot`, and the 6 snapshot standalones (deprecated, folding into a `canvas_snapshot` composite in v0.4 — a dated, pinned residual rather than an open-ended window). The surface is now governed instead of accreted: `registerOperationTools` skips composite-folded ops (`operations/mcp.ts:26-31`) and `mcp-tool-freeze.test.ts:157-176` asserts the exact 27-name list plus 14 resources, so a new op can't silently ship a standalone tool. The remaining risk is that all of this is uncommitted, single-machine state — see Direction item 1.
63
+
64
+ #### H2. ~~Six hand-written MCP tools duplicate the registry~~ — mostly RESOLVED in the v0.3.0 working tree
65
+ The 3 fully parallel implementations (`canvas_add_html_node`, `canvas_add_html_primitive`, `canvas_refresh_webpage_node`) and their drift-prone Local/Remote `CanvasAccess` method pairs are deleted (`canvas-access.ts:19` documents the removal; `src/mcp/server.ts` 1,009 → 868 lines, `canvas-access.ts` → 562). Residual (low): `canvas_ax_interaction`, `canvas_ingest_activity`, `canvas_screenshot` remain hand-written — legitimately MCP-specific (image payloads, interaction envelopes). *Fix pass 2026-07-06:* the first two now carry the same try/catch → `{isError:true}` handling as `canvas_screenshot` and the registry tools; nothing of H2 remains open.
66
+
67
+ #### H3. `src/cli/agent.ts` is a 3,337-line monolith with 83 hand-registered commands
68
+ 83 `cmd(...)` registrations in one file, routed by a manual three-then-two-then-one word lookup (`agent.ts:3287-3301`); 81 copy-pasted `if (flags.help || flags.h) return showCommandHelp(...)` preambles; a hand-rolled flag parser with a ~40-entry `BOOL_FLAGS` allowlist (`agent.ts:184-193`) that must be updated for every new boolean flag — while `src/cli/index.ts:43-87` independently implements a *second* parser with different rules. **Remediation:** per-domain command modules + a `defineCommand` wrapper + one shared arg parser (and see C1 for the transport).
69
+
70
+ #### H4. Daemon lifecycle management contradicts the server's own port behavior
71
+ The server does port fallback (`buildPortCandidates()`, `server.ts:2773`, bind loop at `:3533`), but the CLI daemon manager hardcodes `http://localhost:${options.port}/health` (`cli/index.ts:202,268,302`). If the child falls back, the parent polls the wrong port and reports a dead daemon that is actually running — orphaned and unkillable via `serve stop` since the pid file is only written after health succeeds (`:249`). Additional fragility: TOCTOU between the health check (`:206`) and spawn (`:225`) lets two concurrent `serve` calls double-spawn, and `isProcessRunning` treats `EPERM` as alive with no PID-recycling guard (`:105-115`). This port-collision class caused the one flaky unit-test failure in the first verification run (`pmx-canvas-sdk.test.ts`, `EADDRINUSE` on candidate 4799; passed in the 2026-07-06 re-run — the flake is intermittent, the root cause is unfixed). **Remediation:** child reports its actual bound port (pidfile/stdout); parent locks before spawning and records pid immediately.
72
+
73
+ #### H5. sse-bridge maintains a second, divergent copy of node construction and thread state
74
+ `src/client/state/sse-bridge.ts` (1,066 lines, 34 handlers) has its own `DEFAULT_POSITIONS`/`makeNode` node factory (`:75-114`) fully separate from `canvas-store`'s `addNode`/`parseCanvasNode` — two factories with divergent defaults. Thread/turn reconciliation hand-mutates a `turns[]` array plus a module-level `responseToThreadMap` whose own comment admits it is *"Not cleaned on SSE reconnect"* (`:43`), with heuristic dedup (`:650-654`). Server-side, `syncEventToCanvasState` (`server.ts:3046`, ~330 lines) mirrors this as a giant if/else chain, with node geometry defaults duplicated against `ensureDefaultDockedNodes` under a comment saying *"keep geometry/dock defaults in sync if you change them"* (`:3006`). This is the exact drift class that Canvas Architecture Rule 2 exists to prevent. **Remediation:** one shared node factory; treat `canvas-layout-update` as the single source of truth for thread nodes; table-drive the event sync.
75
+
76
+ #### H6. No lint or format tooling exists — and RELEASE.md claims it does
77
+ No ESLint/Biome/Prettier config anywhere; no lint script. `docs/RELEASE.md:35` states `release:check` "bundles + lints" — it runs build + typecheck + tests only. 53k lines across two UI frameworks with zero automated consistency enforcement. **Remediation:** add Biome, wire into CI and `release:check`.
78
+
79
+ #### H7. Client rendering has zero unit-level coverage; big server modules untested; coverage is decorative
80
+ The 12 `client-*.test.ts` files import only pure exported helpers; no DOM testing library exists in the repo, so every component (`CanvasNode`, `PromptNode`, `ContextNode`, `ContextMenu`, …) plus `sse-bridge.ts` and `canvas-store.ts` are e2e-only. Server modules with no dedicated tests include `ax-state-manager.ts` (826 lines), `mcp-app-host.ts` (814), `canvas-schema.ts` (644), and `shared/semantic-attention.ts` (600 — appears in zero test files). CI collects coverage but nothing fails on a drop. Tests are also not typechecked (`tsconfig.json` includes only `src/**`). **Remediation:** add `happy-dom` + `@testing-library/preact`; unit-test the two pure-logic modules first; set a coverage threshold; add a tests tsconfig.
81
+
82
+ ### Medium
83
+
84
+ - **M1. Dual rendering stack, unminified, dev tooling in prod.** Preact app + React 19 + react-dom + recharts as runtime deps; 7 `@json-render/*` packages exact-pinned at 0.19.0, three of them devtools (`@json-render/devtools*` imported at `renderer/index.tsx:20`, gated behind a window flag but shipped). Bundles: `dist/json-render/index.js` 1.37 MB, `dist/canvas/index.js` 599 KB — both readable/unminified (the `--minify` in `build:client` isn't reflected in the committed bundle). `scripts/build-json-render.sh` shells to `python3` for an mtime cache and has a timeout-then-use-stale-bundle fallback, making builds non-deterministic. **Remediation:** minify; move devtools to devDependencies; lazy-load the viewer; replace the Python mtime gate.
85
+ - **M2. Copy-pasted iframe AX bridge across three renderers.** An identical ~33-line `onAxMessage` listener appears in `HtmlNode.tsx:55-89`, `McpAppNode.tsx:89-121`, `ExtAppFrame.tsx:272-303`; the `pmx-canvas-ax` protocol string is spread across 9 sites; the `ax-update` push is likewise duplicated. This is the trust boundary for sandboxed surfaces — the one place copy-paste drift has already caused a security near-miss (June's LRN-20260607-005). **Remediation:** one `useAxSurfaceBridge` hook + a protocol-constants module.
86
+ - **M3. `ExtAppFrame.tsx` (915 lines) is a timing-hack concentration.** 12 refs, 7 `setTimeout`s, 5 rAFs, a WebKit black-tile remount driven by a global serialized slot counter with a 3000 ms reset (`:69-78`), a 1200 ms fallback bootstrap timer (`:619-640`), and two near-duplicate `buildHostContext` blocks (`:426-442` vs `:737-747`). Highest-churn client file (11 of last 50 commits). **Remediation:** extract a `useExtAppBridge` hook; single `buildHostContext`.
87
+ - **M4. Inconsistent HTTP envelopes and swallowed errors.** Registry ops return bare bodies, legacy ext-app handlers return `{ok:true,result}`, 404s are plaintext — three shapes on one API. `canvas-state.ts` has 63 catch→warn→continue sites, so a full-disk/corrupt-DB save failure is invisible to clients. *Fix pass 2026-07-06:* malformed non-empty JSON bodies now return 400 on every route (`readJson` returns null → guarded at all 23 call sites; the registry reader throws `OperationError`) — regression-tested. **Remaining:** one envelope; surface persistence failure via a `/health` degraded flag.
88
+ - **M5. Five byte-identical ext-app list handlers** (`server.ts:1894-1949`) differing only in the runtime function called; the ~30 `readJson → validate → responseJson` skeletons across legacy handlers are the same pattern the registry already solves declaratively. Folds into C1.
89
+ - **M6. Composite MCP schemas advertise every field optional — and are now the only schema surface.** `buildCompositeShape` (`operations/mcp.ts:88`) flattens per-action requirements away (`shape[publicKey] = schema.optional()`), so `canvas_node {action:"add"}` without `type` passes schema validation and fails only in the handler. With the strict-schema legacy tools removed in v0.3.0, this flattened shape is the *only* contract agents see. `resolveCompositeOp` rejects unknown actions loudly (`operations/mcp.ts:141-158`) — good — but per-action required fields still fail only at runtime. **Remediation:** per-action `oneOf` schemas or explicit per-action requirement docs.
90
+ - **M7. Confirmed cross-transport drift (small but real).** *Fix pass 2026-07-06:* the HTTP search route now honors `?limit=` (regression-tested) and the SDK's `setContextPins` emits the same `context-pins-changed` event as HTTP/MCP. **Remaining:** resource notifications over-fan-out — five `canvas://` URIs fire on *every* mutation regardless of type (`mcp/server.ts:95-99`).
91
+ - **M8. Test-suite structural debt.** The two integration monoliths (5,213 + 2,903 lines) under `tests/unit/`; `getAvailablePort()` copy-pasted verbatim in 5 files; `waitForPersistence(ms = 650)` fixed-sleep used across 9 files plus ~19 magic `Bun.sleep` literals; Playwright config lacks `retries` and `forbidOnly`; two hard `waitForTimeout` sleeps (`canvas.pw.ts:1929`, `showcase.pw.ts:985`). `test:e2e-cli` (517-line script) runs in no workflow — it's a manual-only gate. **Remediation:** `tests/integration/` tier, shared port/MCP-client helpers, poll instead of sleep, add retries/forbidOnly, wire `e2e-cli` into CI.
92
+ - **M9. CLAUDE.md/AGENTS.md are 357-line hand-maintained twins.** *Fix pass 2026-07-06:* the demo-board drift line is resolved (the fixture is a saved OKR/DX dashboard, so AGENTS.md was right), the files are byte-identical again, and the executable bit is cleared. **Remaining:** they are still two hand-maintained copies — generate one from the other (or symlink) to prevent the next drift.
93
+ - **M10. Packaging inconsistencies.** `bin` points at a raw `.ts` (Bun-only — consistent with ADR-001 but undocumented in the README install path); `files` ships both `src/` and `dist/`, roughly doubling the tarball with two import paths; `@types/turndown` sits in runtime `dependencies`. **Remediation:** pick one distribution; move `@types/*` to dev.
94
+ - **M11. Doc gaps (node-types staleness fixed in the delta).** ~~`docs/node-types.md` omits ext-app/webview~~ — fixed: webpage nodes get a dedicated section and `mcp-app` is documented. Still open: two live port env vars (`PMX_CANVAS_PORT` vs `PMX_WEB_CANVAS_PORT`) applied inconsistently across entry points (`canvas-access.ts:464` reads both, `cli/index.ts` only one, `cli/agent.ts` only the other); `PMX_DATA` (used 15×) and the `PMX_CANVAS_JSON_RENDER_*` / `PMX_MCP_APP_HOST_*` families undocumented (re-verified 2026-07-06); `publish.yml` intentionally skips e2e with no verification that the tagged SHA passed `test.yml`.
95
+ - **M12 (new). The v0.3.0 removal left stale references to deleted tools inside live registry metadata.** The `node.add` op description (`ops/nodes.ts:711`) still routes agents to six removed tools (*"json-render -> canvas_add_json_render_node, graph -> canvas_add_graph_node, web-artifact -> canvas_build_web_artifact, external apps -> canvas_open_mcp_app, html (preferred) -> canvas_add_html_node, groups -> canvas_create_group"*) and ends *"Call canvas_describe_schema…"* — also removed. Field descriptions at `ops/nodes.ts:657,668,714,716` still say "prefer canvas_create_group". Because `node.add` is composite-folded, none of this text registers anywhere today (`registerOperationTools` skips folded ops), and the agent-facing routing table was correctly updated to composite syntax (`canvas-schema.ts:83-336`) — so the impact is maintainer misdirection plus the risk of stale strings resurfacing if an op is ever unfolded. The now registration-dead `toolName:` fields on folded ops (`ops/app.ts:247,377`, `ops/groups.ts:51`, `ops/json-render.ts:117,224,353,472,543`, …) likewise read as live wiring. *Fix pass 2026-07-06:* the `node.add` description and field hints now route to composite syntax — **fixed**. The `toolName:`/`description` metadata on folded ops stays deliberately: it documents the historical mapping and the folded-op filter plus freeze test keep it inert.
96
+
97
+ ### Low
98
+
99
+ - **L1.** Dead client code: `ContextPinHud.tsx` has zero references. Three pure re-export shims (`client/utils/placement.ts`, `client/utils/ext-app-tool-result.ts`, `server/ext-app-tool-result.ts`) add indirection without value.
100
+ - **L2.** Two parallel markdown-formatting stacks (`MdFormatBar`/`md-format.ts` vs `InlineFormatBar`/`inline-editor-commands.ts`) implementing overlapping bold/italic/code/link commands.
101
+ - **L3.** CSS: `global.css` is 3,746 lines/528 rule blocks with 11 `!important`, competing with 227 inline `style={{}}` objects (38 in `ContextNode.tsx` alone) and hard-coded `zIndex: 9998/10000/10001` outside any token scale.
102
+ - **L4.** Legacy JSON→SQLite migration code runs unconditionally on every boot (`canvas-state.ts:758,785`) with perpetual fallback branches; gate behind a one-shot marker and retire.
103
+ - **L5.** Two parallel suppression depth-counters (`_suppressRecordingDepth` in canvas-state, `suppressEmitDepth` in the registry — the latter's comment admits it "mirrors" the former); `onMutation` is a single-slot setter masquerading as a subscribe API and is wired twice (`server.ts:3500` and `index.ts:184`), currently harmless only because last-write-wins.
104
+ - **L6.** ~20 module-level mutable `let` bindings in `server.ts` plus module-global timers (the code-graph debounce timer survives `stopCanvasServer`, leaking across start/stop cycles); the remote SSE watcher in `mcp/server.ts:120-145` is a `while(true)` loop with no cancellation when the target changes.
105
+ - **L7.** ~~`mcp-tool-freeze.test.ts` title says "83-tool list" while asserting 84~~ — fixed in the v0.3.0 delta (asserts the frozen 27-name list with an accurate history comment). *Fix pass 2026-07-06:* `seed-and-screenshot.sh` now sets `-euo pipefail`. Still: `tsconfig` lacks `noUncheckedIndexedAccess`; the June assessment doc cites gitignored `.learnings/` entries unreadable by future maintainers.
106
+
107
+ ## What is actually fine
108
+
109
+ - **The operation registry is good architecture, correctly executed** — single zod schema driving HTTP + MCP + composites with uniform validation and error mapping. The debt is that migration onto it stopped, not the design.
110
+ - **The v0.3.0 removal was executed with discipline**: every one of the 57 removals has a changelog mapping to its composite replacement; plans 006/008 closed with status lines; skills, `docs/mcp.md`, CLAUDE.md/AGENTS.md, and the schema routing table updated in the same change; and the folded-op filter + freeze test convert the tool surface from something that accretes into something that is governed.
111
+ - Zero `as any`, zero dynamic imports, `strict: true`, clean `tsc --noEmit` at 53k lines — including on the v0.3.0 tree.
112
+ - Node chrome (titlebar/pin/resize/ports) is properly centralized in `CanvasNode.tsx` — renderers do not duplicate it.
113
+ - `src/shared/` is genuinely shared (9 client + 10 server importers), not a dumping ground; `server/placement.ts` extends rather than copies it.
114
+ - `examples/` compile against the current SDK; `skills/pmx-canvas/SKILL.md` and its references are current at the v0.3.0 surface; tool/resource counts in docs match the code exactly (27/14).
115
+ - CI runs typecheck + build + unit + coverage + a real e2e gate; e2e predominantly uses `expect.poll`; 5 of 6 scripts are disciplined bash.
116
+ - CHANGELOG discipline is genuinely good — the v0.3.0 Breaking section is a model migration map.
117
+
118
+ ## Direction
119
+
120
+ Everything still open below is tracked as an actionable backlog in [`docs/plans/plan-009-tech-debt-backlog.md`](plans/plan-009-tech-debt-backlog.md).
121
+
122
+ ### Phase 1 — finish what was started (highest leverage, mostly deletion)
123
+ 1. **Commit and release v0.3.0.** The consolidation the first revision of this report asked for already exists in the working tree — until it lands it is one `git checkout .` away from vanishing, and every downstream doc/test already assumes it. (M12's stale metadata is already swept in the same tree.)
124
+ 2. **Complete the registry migration** (C1): fold the ~50 legacy routes into operations, collapse `PmxCanvas` onto `executeOperation`, migrate the CLI's 60 raw calls. Deletes the M5 handler clones and most envelope inconsistency (M4) as side effects.
125
+ 3. **Fix the governance/CI lie** (C2) and **untrack generated artifacts** (C3) — a day of work, permanent credibility win.
126
+
127
+ ### Phase 2 — make the safety net real
128
+ 4. Add Biome (H6); typecheck tests; coverage threshold; Playwright retries + `forbidOnly`; wire `e2e-cli` into CI; split the integration monoliths (M8, H7).
129
+ 5. Fix the daemon port/lock lifecycle (H4) — it is the root of both user-facing "dead daemon" confusion and the suite's one intermittent test.
130
+
131
+ ### Phase 3 — client consolidation
132
+ 6. One node factory shared by sse-bridge and canvas-store; single AX-bridge hook; `useExtAppBridge` (H5, M2, M3).
133
+ 7. Decide the json-render stack's future deliberately (M1): minify + lazy-load at minimum; a separate optional package if the React stack stays.
134
+
135
+ The pattern from June holds: agent velocity faithfully replicates whatever structure exists. The registry proved that fixing the structure works — `src/mcp/server.ts` is down 70% from June (2,861 → 868 lines) while *adding* capability, and the v0.3.0 tree shows the same discipline scaling to a breaking release. The remaining risk is stopping halfway: two routing systems, two node factories, two arg parsers, and two governance stories are each more expensive than either one alone.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
5
5
  "type": "module",
6
6
  "main": "./src/server/index.ts",
@@ -31,9 +31,9 @@ outputs:
31
31
 
32
32
  Perform analytics synthesis across engineering and product data sources. Answer questions about trends, correlations, comparisons, and distributions by querying MCP servers, analyzing the data, and presenting findings with visualizations.
33
33
 
34
- In `pmx-canvas`, prefer `canvas_add_graph_node` for charts and trend lines and
35
- `canvas_add_json_render_node` when the analysis should land as a richer dashboard or table inside
36
- the canvas.
34
+ In `pmx-canvas`, prefer `canvas_render { action: "add-graph" }` for charts and trend lines and
35
+ `canvas_render { action: "add-json-render" }` when the analysis should land as a richer dashboard
36
+ or table inside the canvas.
37
37
 
38
38
  For chart design and color choices, apply the `tufte-viz` skill (`skills/tufte-viz/SKILL.md`): color
39
39
  must encode data, not decorate. Single-series bar charts default to one accent with the key bar
@@ -6,8 +6,9 @@ license: Complete terms in LICENSE.txt
6
6
 
7
7
  This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
8
8
 
9
- In `pmx-canvas`, use `canvas_build_web_artifact` when the result should be a full custom React app,
10
- and use `canvas_add_json_render_node` when a schema-driven UI panel is enough.
9
+ In `pmx-canvas`, use `canvas_app { action: "build-artifact" }` when the result should be a full
10
+ custom React app, and use `canvas_render { action: "add-json-render" }` when a schema-driven UI
11
+ panel is enough.
11
12
 
12
13
  The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
13
14
 
@@ -7,9 +7,10 @@ description: MCP Apps integration for json-render. Use when building MCP servers
7
7
 
8
8
  MCP Apps integration that serves json-render UIs as interactive MCP Apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients.
9
9
 
10
- In `pmx-canvas`, prefer the native `canvas_add_json_render_node` and `canvas_add_graph_node`
11
- tools first. They store validated specs directly in canvas node state and render them through the
12
- local `pmx-canvas` viewer route without needing a separate sidecar server.
10
+ In `pmx-canvas`, prefer the native `canvas_render { action: "add-json-render" }` and
11
+ `canvas_render { action: "add-graph" }` composite actions first. They store validated specs
12
+ directly in canvas node state and render them through the local `pmx-canvas` viewer route without
13
+ needing a separate sidecar server.
13
14
 
14
15
  ## Quick Start
15
16
 
@@ -56,7 +56,7 @@ playwright-cli -s=canvas close
56
56
  ## PMX Canvas Guidance
57
57
 
58
58
  - Start the canvas first: `bun run src/cli/index.ts --no-open`
59
- - Use this skill after `canvas_build_web_artifact` when you need browser validation
59
+ - Use this skill after `canvas_app { action: "build-artifact" }` when you need browser validation
60
60
  - Prefer visible browser automation for UI investigation so the human can follow along
61
61
  - Store screenshots and snapshots on disk, then only read back the specific files you need
62
62
 
@@ -25,7 +25,8 @@ Humans curate agent context by pinning nodes; agents read that curation through
25
25
  may belong to another project.
26
26
  3. **Read before write.** Search with `canvas_query { action: "search", query }` before creating
27
27
  nodes. Read the full layout only when necessary.
28
- 4. **Snapshot before destructive changes.** Use `canvas_snapshot` before clear, restore, or a major
28
+ 4. **Snapshot before destructive changes.** Use `canvas_snapshot` (deprecated standalone; folds into
29
+ the `canvas_snapshot` composite's `save` action in v0.4) before clear, restore, or a major
29
30
  reorganization.
30
31
  5. **Show intent with the Ghost Cursor — by default.** Signal with
31
32
  `canvas_intent { action: "signal", ... }` before every meaningful create, move, connect, remove,
@@ -123,10 +124,12 @@ Important routing:
123
124
  - Excalidraw: `canvas_app { action: "diagram", ... }`
124
125
  - Web artifact: `canvas_app { action: "build-artifact", ... }`
125
126
 
126
- Legacy single-purpose tools remain during the v0.2 compatibility window but are deprecated and
127
- scheduled for removal in v0.3. Standalones that intentionally remain include `canvas_batch`,
128
- `canvas_pin_nodes`, `canvas_screenshot`, snapshot tools, `canvas_ax_interaction`,
129
- `canvas_ingest_activity`, and `canvas_invoke_command`.
127
+ As of v0.3.0, the 57 legacy single-purpose tools from the v0.2 compatibility window are removed.
128
+ The composites above plus the retained standalones are now the whole MCP surface: `canvas_batch`,
129
+ `canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`, `canvas_ingest_activity`, and
130
+ `canvas_invoke_command`. The 6 snapshot tools (`canvas_snapshot`, `canvas_list_snapshots`,
131
+ `canvas_restore`, `canvas_delete_snapshot`, `canvas_gc_snapshots`, `canvas_diff`) are also retained
132
+ but deprecated, pending a `canvas_snapshot` composite in v0.4.
130
133
 
131
134
  ## Spatial Rules
132
135
 
@@ -235,7 +238,7 @@ annotations, pins, snapshots, AX canvas state, and large-node blobs.
235
238
  - Stop the server or close/flush the SDK before committing `canvas.db`.
236
239
  - History is session-scoped and is not persisted.
237
240
  - Timeline AX data persists independently from canvas snapshots.
238
- - `canvas_clear` clears canvas-bound state but not host/session diagnostics.
241
+ - `canvas_view { action: "clear" }` clears canvas-bound state but not host/session diagnostics.
239
242
 
240
243
  ## Detailed References
241
244
 
@@ -218,7 +218,7 @@
218
218
  "id": 9,
219
219
  "name": "current-composite-routing",
220
220
  "prompt": "Create a markdown note, a bar chart, and an Excalidraw diagram on the canvas.",
221
- "expected_output": "Uses current composites: canvas_node {action:add} for markdown, canvas_render {action:add-graph} for the chart, canvas_app {action:diagram} for Excalidraw. Avoids deprecated single-purpose tools (canvas_add_node/canvas_add_diagram/canvas_build_web_artifact/canvas_open_mcp_app).",
221
+ "expected_output": "Uses current composites: canvas_node {action:add} for markdown, canvas_render {action:add-graph} for the chart, canvas_app {action:diagram} for Excalidraw. Avoids removed single-purpose tools (canvas_add_node/canvas_add_diagram/canvas_build_web_artifact/canvas_open_mcp_app).",
222
222
  "assertions": [
223
223
  {
224
224
  "name": "uses-canvas-node",
@@ -237,7 +237,7 @@
237
237
  },
238
238
  {
239
239
  "name": "avoids-deprecated",
240
- "description": "Does not call deprecated standalones (canvas_add_node/canvas_add_diagram/etc.)",
240
+ "description": "Does not call removed standalones (canvas_add_node/canvas_add_diagram/etc.)",
241
241
  "type": "output_check"
242
242
  }
243
243
  ]
@@ -10,7 +10,9 @@ messages. Just opt the node in and use `window.PMX_AX`.
10
10
 
11
11
  ```js
12
12
  // MCP / SDK
13
- canvas_add_html_node({
13
+ canvas_node({
14
+ action: "add",
15
+ type: "html",
14
16
  title: "AX Control Room",
15
17
  html: "<!-- see below -->",
16
18
  axCapabilities: { enabled: true, allowed: ["ax.work.create", "ax.steer"] },
@@ -1,6 +1,6 @@
1
1
  # Excalidraw Diagram Authoring
2
2
 
3
- Use this guide when creating diagrams through PMX Canvas with `canvas_add_diagram` or
3
+ Use this guide when creating diagrams through PMX Canvas with `canvas_app { action: "diagram" }` or
4
4
  `pmx-canvas external-app add --kind excalidraw`.
5
5
 
6
6
  ## Why Text Can Still Drift
@@ -142,4 +142,4 @@ If a title, footer, or rightmost label is clipped, the camera is wrong even if t
142
142
  - Camera has at least `80px` padding around all visible content.
143
143
  - Titles and footers are not near the camera edge.
144
144
  - Arrows have explicit `points` and enough space for labels.
145
- - Calls go through PMX (`canvas_add_diagram` or `external-app add --kind excalidraw`) unless you manually apply these rules to raw Excalidraw MCP input.
145
+ - Calls go through PMX (`canvas_app { action: "diagram" }` or `external-app add --kind excalidraw`) unless you manually apply these rules to raw Excalidraw MCP input.