pmx-canvas 0.3.3 → 0.4.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.
- package/CHANGELOG.md +99 -0
- package/Readme.md +13 -4
- package/dist/canvas/index.js +71 -71
- package/dist/json-render/index.js +47 -47
- package/dist/types/cli/agent.d.ts +13 -8
- package/dist/types/cli/commands/apps.d.ts +1 -0
- package/dist/types/cli/commands/ax.d.ts +1 -0
- package/dist/types/cli/commands/copilot.d.ts +1 -0
- package/dist/types/cli/commands/edges.d.ts +1 -0
- package/dist/types/cli/commands/groups.d.ts +1 -0
- package/dist/types/cli/commands/history.d.ts +1 -0
- package/dist/types/cli/commands/nodes.d.ts +1 -0
- package/dist/types/cli/commands/pins.d.ts +1 -0
- package/dist/types/cli/commands/query.d.ts +1 -0
- package/dist/types/cli/commands/view.d.ts +1 -0
- package/dist/types/cli/commands/webview.d.ts +1 -0
- package/dist/types/cli/shared.d.ts +73 -0
- package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +10 -0
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -1
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +20 -0
- package/dist/types/client/state/node-factory.d.ts +29 -0
- package/dist/types/client/uuid-polyfill.d.ts +0 -0
- package/dist/types/server/canvas-db.d.ts +0 -1
- package/dist/types/server/canvas-operations.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +14 -15
- package/dist/types/server/diagram-presets.d.ts +8 -0
- package/dist/types/server/operations/composites.d.ts +6 -30
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +18 -0
- package/docs/bun-webview-integration.md +5 -0
- package/docs/cli.md +5 -0
- package/docs/environment.md +48 -0
- package/docs/http-api.md +25 -1
- package/docs/mcp.md +21 -30
- package/docs/node-types.md +5 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +34 -15
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +5 -5
- package/package.json +13 -10
- package/skills/pmx-canvas/SKILL.md +16 -6
- package/skills/pmx-canvas/evals/evals.json +7 -7
- package/skills/pmx-canvas/references/full-reference.md +25 -16
- package/src/cli/agent.ts +22 -3512
- package/src/cli/commands/apps.ts +165 -0
- package/src/cli/commands/ax.ts +604 -0
- package/src/cli/commands/copilot.ts +43 -0
- package/src/cli/commands/edges.ts +73 -0
- package/src/cli/commands/groups.ts +81 -0
- package/src/cli/commands/history.ts +220 -0
- package/src/cli/commands/nodes.ts +984 -0
- package/src/cli/commands/pins.ts +42 -0
- package/src/cli/commands/query.ts +223 -0
- package/src/cli/commands/view.ts +186 -0
- package/src/cli/commands/webview.ts +227 -0
- package/src/cli/index.ts +8 -2
- package/src/cli/shared.ts +861 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -2
- package/src/client/index.tsx +2 -0
- package/src/client/nodes/ExtAppFrame.tsx +70 -51
- package/src/client/nodes/HtmlNode.tsx +8 -51
- package/src/client/nodes/McpAppNode.tsx +12 -47
- package/src/client/nodes/use-ax-surface-bridge.ts +71 -0
- package/src/client/state/node-factory.ts +72 -0
- package/src/client/state/sse-bridge.ts +209 -86
- package/src/client/uuid-polyfill.ts +14 -0
- package/src/json-render/renderer/index.tsx +3 -2
- package/src/mcp/server.ts +0 -47
- package/src/server/canvas-db.ts +0 -8
- package/src/server/canvas-operations.ts +8 -0
- package/src/server/canvas-schema.ts +2 -8
- package/src/server/canvas-state.ts +51 -151
- package/src/server/diagram-presets.ts +15 -1
- package/src/server/html-surface.ts +11 -6
- package/src/server/index.ts +3 -9
- package/src/server/operations/composites.ts +25 -34
- package/src/server/operations/invoker.ts +10 -3
- package/src/server/operations/mcp.ts +51 -1
- package/src/server/operations/ops/app.ts +5 -0
- package/src/server/operations/ops/snapshots.ts +6 -7
- package/src/server/operations/registry.ts +4 -1
- package/src/server/server.ts +172 -74
- package/src/shared/ax-surface-protocol.ts +22 -0
- package/dist/types/client/canvas/ContextPinHud.d.ts +0 -1
- package/src/client/canvas/ContextPinHud.tsx +0 -17
package/docs/node-types.md
CHANGED
|
@@ -268,6 +268,11 @@ The scaffold includes `recharts`. Pass `--deps name,name2` for additional
|
|
|
268
268
|
package dependencies. Failed or empty CLI bundles print `ok: false`, exit
|
|
269
269
|
non-zero, and do not create a canvas node.
|
|
270
270
|
|
|
271
|
+
Platform note: the artifact build pipeline runs through bundled bash scripts
|
|
272
|
+
and requires a POSIX shell — it is not supported on native Windows yet.
|
|
273
|
+
Already-built artifacts render fine on every platform; only the build step is
|
|
274
|
+
POSIX-only.
|
|
275
|
+
|
|
271
276
|
The matching agent skill is at
|
|
272
277
|
[`skills/web-artifacts-builder/SKILL.md`](../skills/web-artifacts-builder/SKILL.md).
|
|
273
278
|
|
|
@@ -6,46 +6,65 @@
|
|
|
6
6
|
|
|
7
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
8
|
|
|
9
|
+
## Decision analysis (2026-07-08, maintainer-approved)
|
|
10
|
+
|
|
11
|
+
The remaining items were re-analyzed with pros/cons and tiered; the maintainer approved this ordering:
|
|
12
|
+
|
|
13
|
+
- **Tier 1 (do):** CLI raw fetches → operation invoker **together with** the H3 parser/module split (same file, one project); H7 test coverage **as a prerequisite** for any client refactor; M2 AX-bridge dedup (first client task after H7); the M8 flake subset (mock the Excalidraw network tests, share `getAvailablePort`, Playwright `retries`/`forbidOnly`, `e2e-cli` into CI). *C1b done (2026-07-08):* all 60 raw `api()` calls migrated onto the invoker (9-agent verified mapping; `webartifact.build` gained `errorBodyAsResult`; batch failure prints the envelope + exits 1; the `api()` helper is deleted; two deliberate raw `fetch` sites remain — binary screenshot, SSE watch). *H3 module split done (2026-07-09):* `agent.ts` is a 227-line entry over `src/cli/shared.ts` (cycle-free core: registry, parser, transport) + 11 `src/cli/commands/*` domain modules, moved byte-identically; ax-parity reads the whole cli tree. **Parser sub-item reshaped:** `parseFlags`/`BOOL_FLAGS` is now single-sited in shared.ts; `index.ts` keeps its intentionally tiny fixed-flag launcher reader (different domain — server launch, not agent commands). The spec-driven per-command flag parser (`defineCommand`) remains the ideal but is a redesign of all 83 command surfaces — deferred to a future CLI UX change, not worth manufactured churn now.
|
|
14
|
+
- **Tier 2 — async-SDK question DECIDED NO (2026-08-03, v0.4.0):** the sync `PmxCanvas` facade is the contract. Post-registry the 57 sync methods are thin presentation over the shared operation cores (single logic site — the tier's own analysis), so forcing them async would break every SDK consumer to remove zero duplication. The already-async/void methods dispatch through `executeOperation`; the rest stay sync by design. No further collapse is planned.
|
|
15
|
+
- **Tier 2 (original analysis):** the PmxCanvas→executeOperation collapse is **not** mechanical — `executeOperation` is async while 57/76 SDK methods are sync with typed returns, so a full collapse is a breaking SDK change. Verdict: migrate only the already-async/void methods now; decide the full async-SDK question as a **v0.4 breaking item** with the `canvas_snapshot` composite. Post-registry the sync methods are thin presentation over the shared cores (single logic site) — not logic duplication. H5 (sse-bridge factory) proceeds **after H7**. M6 gets the middle path: runtime per-action required-field errors, no `oneOf` schema break (many MCP hosts handle `oneOf` poorly). M1 gets the minimum four (minify, devtools→devDeps, lazy-load, replace the python mtime gate); the React-stack decision stays open.
|
|
16
|
+
- **Tier 3 (cheap batch):** CI check that CLAUDE.md/AGENTS.md stay identical (instead of doc generation); document `PMX_DATA` + the `PMX_CANVAS_JSON_RENDER_*`/`PMX_MCP_APP_HOST_*` env families; publish.yml verifies the tag SHA passed test.yml; `@types/turndown` → devDependencies; L1 dead `ContextPinHud`; L4 one-shot migration marker; L6 timer-leak fixes.
|
|
17
|
+
- **Tier 4 (closed as by-design / parked):** the six server-coupled routes (nothing to dedup — injection machinery for single-copy handlers isn't worth it); M3 ExtAppFrame refactor (the timing hacks are load-bearing WebKit fixes — document invariants, enter only when a feature forces it); M7 notification fan-out, L2, L3, `noUncheckedIndexedAccess` (parked); M10 packaging closed after the `@types` one-liner (`bin`→`src` under Bun and shipped `dist/` are both load-bearing by design).
|
|
18
|
+
|
|
9
19
|
## Phase 1 — finish what was started
|
|
10
20
|
|
|
11
21
|
| Item | Source | What's open | Shape |
|
|
12
22
|
|---|---|---|---|
|
|
13
|
-
| Complete the registry migration | C1 | Fold the remaining hand-written routes in `server.ts` into operations; collapse the 76-method `PmxCanvas` onto `executeOperation`; migrate the CLI's 60 raw `fetch` calls onto the operation invoker. **Slice 1 done post-0.3.0:** the 7 `/api/ext-app/*` routes moved to `ops/ext-app.ts` (~290 lines out of `server.ts`, the 5 clone handlers — old M5 — collapsed to one parametrized definition; wire envelopes byte-identical, no new MCP tools). **Slice 2 done:** the 15 ax wire routes (8 GET lists + `ax/context` + `ax/surface-snapshot` + `pinned-context` + `code-graph` + `ax/activity` + `ax/interaction` + legacy `PATCH /api/canvas/ax`) moved to `ops/ax-read.ts` (~370 more lines out of `server.ts`); `open-external` stays hand-written (coupled to the live server port + system browser opener). **Slice 3 done:** theme GET/POST, canvas update, viewport, annotation POST, and node `:id/refresh` moved to the registry (`ops/canvas-wire.ts`, plus additions to `ops/viewport.ts`, `ops/annotation.ts`, `ops/nodes.ts`); the two dispatch gates unified into one `/api/` gate; `server.ts` is at ~2,980 lines (from 3,873 at the audit). **Remaining routes (~20)** are the deliberate poor fits (SSE, binary, HTML/static, file bodies) plus four server-coupled handlers: `prompt` + workbench `state`/`open`/`intent` (module-local `primaryWorkbench*` session state), `render` (Excalidraw URL-normalizer chain), `open-external` (live port + system browser). Those need state injection like the webview runner — a deliberate follow-up, not mechanical.
|
|
14
|
-
| One HTTP envelope | M4 residual |
|
|
23
|
+
| Complete the registry migration | C1 | Fold the remaining hand-written routes in `server.ts` into operations; collapse the 76-method `PmxCanvas` onto `executeOperation`; migrate the CLI's 60 raw `fetch` calls onto the operation invoker. **Slice 1 done post-0.3.0:** the 7 `/api/ext-app/*` routes moved to `ops/ext-app.ts` (~290 lines out of `server.ts`, the 5 clone handlers — old M5 — collapsed to one parametrized definition; wire envelopes byte-identical, no new MCP tools). **Slice 2 done:** the 15 ax wire routes (8 GET lists + `ax/context` + `ax/surface-snapshot` + `pinned-context` + `code-graph` + `ax/activity` + `ax/interaction` + legacy `PATCH /api/canvas/ax`) moved to `ops/ax-read.ts` (~370 more lines out of `server.ts`); `open-external` stays hand-written (coupled to the live server port + system browser opener). **Slice 3 done:** theme GET/POST, canvas update, viewport, annotation POST, and node `:id/refresh` moved to the registry (`ops/canvas-wire.ts`, plus additions to `ops/viewport.ts`, `ops/annotation.ts`, `ops/nodes.ts`); the two dispatch gates unified into one `/api/` gate; `server.ts` is at ~2,980 lines (from 3,873 at the audit). **Remaining routes (~20)** are the deliberate poor fits (SSE, binary, HTML/static, file bodies) plus four server-coupled handlers: `prompt` + workbench `state`/`open`/`intent` (module-local `primaryWorkbench*` session state), `render` (Excalidraw URL-normalizer chain), `open-external` (live port + system browser). Those need state injection like the webview runner — a deliberate follow-up, not mechanical. **Status (2026-08-03):** the CLI fetches are done (C1b) and the `PmxCanvas` collapse is closed by the async-SDK decision (sync facade stays). The 4 server-coupled routes (`prompt`, workbench `state`/`open`/`intent`, `render`, `open-external`) remain the only open C1 work — internal state-injection refactor, zero wire impact, deliberately post-0.4. | tail open (post-0.4) |
|
|
24
|
+
| One HTTP envelope | M4 residual | **Done (2026-08-03, v0.4.0):** every hand-written error response is now a JSON `{ ok:false, error }` envelope with unchanged status codes (matching the registry dispatcher); `/health` gained a `persistence` block that reports degraded durability after a failed save (ok again on the next success). The ext-app `{ok:true,result}` wrapper is KEPT deliberately — it is the app-bridge proxy contract our own panel parses; changing it bought no consumer value. | done |
|
|
15
25
|
| `dist/` commit policy | C3 residual | **Decided + done post-0.3.0:** `dist/` stays committed; `test.yml` now has a `dist/types` staleness gate (rebuild + fail on drift). The `dist/canvas`/`dist/json-render` bundles are deliberately not byte-checked — bundler output varies across bun versions; their staleness story folds into M1. | done |
|
|
16
|
-
| Skill-mirror validator | C2 residual |
|
|
17
|
-
| Generate CLAUDE.md/AGENTS.md from one source | M9 residual |
|
|
26
|
+
| Skill-mirror validator | C2 residual | **Decided + done (2026-08-03):** the script prints a labeled SKIPPED notice when trees are absent (local behavior unchanged), and the permanently-no-op CI step was removed with a comment. | done |
|
|
27
|
+
| Generate CLAUDE.md/AGENTS.md from one source | M9 residual | **Done (tier-3 batch):** CI fails on any CLAUDE.md/AGENTS.md byte drift (`test.yml` identity gate) — the cheap alternative to generation the decision analysis chose. Generation/symlinking stays unnecessary while the gate holds. | done |
|
|
18
28
|
|
|
19
29
|
## Phase 2 — make the safety net real
|
|
20
30
|
|
|
21
31
|
| Item | Source | What's open | Shape |
|
|
22
32
|
|---|---|---|---|
|
|
23
33
|
| Lint/format tooling | H6 | **Done post-0.3.0:** Biome 2.5 adopted — `bun run lint` / `lint:fix`, wired into `release:check` (making RELEASE.md's "lints" claim true) and CI. First-run format sweep landed as its own mechanical commit; 7 lint errors hand-fixed (incl. a real conditional-hooks bug in `SelectionBar.tsx`). Deliberately disabled rules to revisit: a11y group, `useExhaustiveDependencies` (misfires on Preact signals), `noNonNullAssertion` (143 deliberate uses), `noArrayIndexKey` (13 sites). ~77 warnings (unused imports/vars, optional-chain) left as a burn-down list — warnings don't fail the gate. | done |
|
|
24
|
-
| Client + server unit coverage | H7 |
|
|
34
|
+
| Client + server unit coverage | H7 | **Done post-0.3.3 (first tranche):** happy-dom + @testing-library/preact harness under `tests/client/` (own preload so DOM globals never leak into server suites); 20 client render tests incl. the SelectionBar conditional-hooks regression; `semantic-attention` (25 tests) and `canvas-schema` (21 tests incl. an M12 stale-tool-drift invariant) covered; tests are TYPECHECKED (`tsconfig.tests.json`, 270 latent errors fixed, wired into `typecheck` + CI); coverage now GATES via `scripts/check-coverage.ts` total floors (bun's bunfig threshold is per-file and its table form is silently ignored — documented). **Done (2026-08-03):** dedicated suites for `ax-state-manager.ts` (39 tests, injected-deps harness + in-memory SQLite) and `mcp-app-host.ts` (15 tests; stateful paths in hermetic subprocesses). Unit-test scripts now pin `PMX_MCP_APP_HOST_STATE_FILE` to a scratch path so suites never touch the real home-dir state. Client suite grows alongside H5. | done |
|
|
25
35
|
| Daemon port/lock lifecycle | H4 | **Fixed post-0.3.0** (`src/cli/daemon.ts` + `tests/unit/cli-daemon.test.ts`). Note the original finding was partly wrong: the daemon child was already strict-port (`PmxCanvas.start` pins `allowPortFallback: false`), so fallback-blind polling could not occur. The real defects — fixed — were: the pre-check accepted any responsive `/health` as "already running" (even a foreign workspace's daemon), a TOCTOU double-spawn window, the pid recorded only after health passed (slow start → unkillable orphan), and no PID-recycling guard. The flaky `pmx-canvas-sdk.test.ts` port collision is NOT this — it's M8's copy-pasted `getAvailablePort()` racing. | done |
|
|
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. *Post-0.3.0:* `operation-parity` now binds port 0 and derives the port from the base URL (removes one reserve-then-rebind race); the shared-helper dedup and the rest remain. Also noted: `ax-parity.test.ts` is a string-matching source-analysis test whose needles broke when Biome re-wrapped `cmd(` calls — its CLI needles are now layout-independent, but any future needle should avoid depending on line layout. **Also noted (2026-07-07):** the `server-api` Excalidraw diagram tests do real network round-trips to the hosted `mcp.excalidraw.com` MCP server and intermittently time out (30s) under full parallel runs
|
|
36
|
+
| 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. *Post-0.3.0:* `operation-parity` now binds port 0 and derives the port from the base URL (removes one reserve-then-rebind race); the shared-helper dedup and the rest remain. Also noted: `ax-parity.test.ts` is a string-matching source-analysis test whose needles broke when Biome re-wrapped `cmd(` calls — its CLI needles are now layout-independent, but any future needle should avoid depending on line layout. **Also noted (2026-07-07):** the `server-api` Excalidraw diagram tests do real network round-trips to the hosted `mcp.excalidraw.com` MCP server and intermittently time out (30s) under full parallel runs. **M8 subset done (2026-07-08):** those tests now run against the local stdio fixture via `PMX_CANVAS_EXCALIDRAW_MCP_COMMAND`; `getAvailablePort` is one shared helper; Playwright has `retries`/`forbidOnly` on CI; `e2e-cli` is a CI job. **Sleep sweep done (2026-08-03):** fixed persistence sleeps replaced by condition polling via a shared helper. **Remaining (post-0.4, test organization only):** the `tests/unit/` monolith split. | sleep sweep done |
|
|
27
37
|
| `noUncheckedIndexedAccess` | L7 residual | Enable in tsconfig; will surface many index-access errors — treat as its own sweep. | small (noisy) |
|
|
28
38
|
|
|
29
39
|
## Phase 3 — client consolidation
|
|
30
40
|
|
|
31
41
|
| Item | Source | What's open | Shape |
|
|
32
42
|
|---|---|---|---|
|
|
33
|
-
| One node factory | H5 |
|
|
34
|
-
| Shared AX iframe bridge | M2 |
|
|
43
|
+
| One node factory | H5 | **Client half done (2026-08-03):** the `responseToThreadMap` reconnect leak is fixed (cleared in the shared per-connection reset path; mutation-verified test) and all four sse-bridge construction sites route through a shared `node-factory.ts`. **Premise correction:** `canvas-store` holds no node-creation defaults — the real divergences are client-vs-SERVER (server `defaultNodeSize` vs the client table; `ensureDefaultDockedNodes` seeding collapsed/docked vs the bridge's expanded/undocked; a same-class `serverResponseToThreadMap` leak at server.ts:2469) — all catalogued in the factory for the deferred server half (table-drive `syncEventToCanvasState`, single source of truth). | client half done |
|
|
44
|
+
| Shared AX iframe bridge | M2 | **Done (2026-08-03):** `useAxSurfaceBridge` (`src/client/nodes/use-ax-surface-bridge.ts`) single-sites the sandboxed-surface trust boundary previously copy-pasted in `HtmlNode.tsx`, `McpAppNode.tsx`, `ExtAppFrame.tsx`; `src/shared/ax-surface-protocol.ts` owns the wire tags (`pmx-canvas-ax`, `-ack`, `pmx-canvas-html-node`, the ext-app boot beacon) — client parsers import them, generated surface scripts (html-surface.ts, ext-app builders, json-render viewer) interpolate them byte-identically. Client render tests pin the validation gates + submit→ack round-trip (`tests/client/use-ax-surface-bridge.test.tsx`). | done |
|
|
35
45
|
| `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 |
|
|
46
|
+
| json-render stack decision | M1 | **Minimum-four done (2026-08-03):** both bundles verified minified (the audit's "unminified" was stale — React+recharts dominate the 1.31 MiB minified size); the 2 `@json-render/devtools*` packages moved to devDependencies; the viewer is already a separate page bundle (nothing to lazy-load — verified, no churn manufactured); the python mtime gate is now Bun with parity-tested exit codes. **Still open (decision):** the React-stack question — a separate optional package remains the only real size lever. | decision |
|
|
37
47
|
| 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
48
|
|
|
39
49
|
## Smaller items (no phase, pick up opportunistically)
|
|
40
50
|
|
|
41
51
|
| Item | Source | What's open |
|
|
42
52
|
|---|---|---|
|
|
43
|
-
| Per-action composite schemas | M6 |
|
|
53
|
+
| Per-action composite schemas | M6 | **Done (2026-08-03, middle path per the decision analysis):** composite dispatch re-enforces each member op's required MCP fields at runtime and names the missing field (`requiredMcpFields` in `operations/mcp.ts`) — no `oneOf` schema break (many MCP hosts handle `oneOf` poorly). Advertised schemas stay all-optional by design. |
|
|
44
54
|
| 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 | `
|
|
46
|
-
| `publish.yml` e2e gap | M11 residual |
|
|
55
|
+
| Env var hygiene | M11 residual | **Done (2026-08-03):** server startup now falls back `--port` → `PMX_WEB_CANVAS_PORT` → `PMX_CANVAS_PORT` → 4313, so setting only the client var no longer splits client and server ports; documented in `docs/environment.md`. |
|
|
56
|
+
| `publish.yml` e2e gap | M11 residual | **Done (tier-3 batch):** publish.yml verifies the tagged SHA has a successful `test.yml` run before publishing. |
|
|
47
57
|
| 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 |
|
|
49
|
-
| Suppression/mutation plumbing | L5 |
|
|
58
|
+
| Legacy migration code on every boot | L4 | **Done (2026-08-03, v0.4.0 breaking window):** the pre-0.2 import path is deleted (~150 lines); fresh-boot bootstrapping untouched; `PMX_CANVAS_STATE_FILE` survives only as a `.db`-path alias with a boot warning otherwise. Verified with a legacy-artifacts smoke (files untouched, no `.bak`). |
|
|
59
|
+
| Suppression/mutation plumbing | L5 | **Done (2026-08-03):** the "two counters" are semantically distinct by design (emit-suppression for batch vs recording-suppression for undo/redo — merging breaks one or the other; documented at both declarations). The redundant second `onMutation` wire in `PmxCanvas.start` was deleted; the slot contract is documented. |
|
|
50
60
|
| 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 |
|
|
61
|
+
| June doc citations | L7 residual | **Done (2026-08-03):** each cited entry's substance is inlined so the doc stands alone; one miscitation corrected. |
|
|
62
|
+
|
|
63
|
+
## Added post-0.3.3 (2026-08-03 sweep)
|
|
64
|
+
|
|
65
|
+
| Item | What's open |
|
|
66
|
+
|---|---|
|
|
67
|
+
| Windows web-artifacts | The artifact build pipeline is bash-script-backed (POSIX-only); `tests/unit/web-artifacts.test.ts` self-skips on win32 and docs now state the boundary. Decide: port the build scripts to a cross-platform runner, or keep the documented "build on POSIX, render anywhere" stance. |
|
|
68
|
+
| Composite parity no longer machine-checked | Since v0.3.0 removed the legacy standalones, the byte-identical composite-vs-legacy guarantee has no head-to-head test — regressions in `buildInput`/`formatResult` reuse surface only via output-shape assertions. Accepted at review; revisit if a composite drifts. |
|
|
69
|
+
| Portal-layer ext-app mount (contingency) | If the maintainer sets the WebKit UX bar at "never any visible gap" (0.3.3 report leaves it open), inline ext-app iframes must render outside the world transform — an architectural change that reopens the parked M3. Only scope on a failing report cycle. |
|
|
70
|
+
| Registry-migration dead-code sweep | **Done (2026-08-03):** removed the orphaned `describeOp`/`describeGateOp` (composites.ts, dead since the v0.3.0 registration filter) and the `compactNodePayload`/`compactLayoutPayload`/`wantsFullPayload` duplicates in `mcp/server.ts` (live copies are in `ops/nodes.ts`). |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
|
@@ -14,11 +14,11 @@ Code quality is better than the release velocity would suggest: zero `as any` ac
|
|
|
14
14
|
|
|
15
15
|
Every operation is hand-written four times — `CanvasStateManager` → `PmxCanvas` → HTTP handler in `server.ts` → MCP tool in `src/mcp/server.ts` — each with its own validation and error shapes. The CLI (`src/cli/agent.ts`, 3,300 lines) duplicates it a fifth time with raw fetch calls instead of using the SDK.
|
|
16
16
|
|
|
17
|
-
Evidence from `.learnings/` that this is actively producing bugs:
|
|
17
|
+
Evidence from `.learnings/` that this is actively producing bugs (the `.learnings/` tree is gitignored, so the substance of each cited entry is inlined here):
|
|
18
18
|
|
|
19
|
-
- [LRN-20260606-006] Fix #32 applied to only one of two duplicated mutation paths (`updateNode()` vs `applyUpdates()`).
|
|
20
|
-
- [LRN-20260607-005] New `json-render` sourceSurface enum member silently took the permissive default because the `scoped` guard in `applyAxInteraction` was not updated. Security near-miss.
|
|
21
|
-
- [LRN-20260608-002] `readJson` hardening silently killed the documented bare-array shape of `POST /api/canvas/batch` (#49).
|
|
19
|
+
- [LRN-20260606-006] Fix #32 applied to only one of two duplicated mutation paths (`updateNode()` vs `applyUpdates()`). Both paths carried an identical copy of the group repack-on-resize logic, so fixing only the batch path left single-node `PATCH` updates still repacking siblings. The same entry records the parallel serializer pair drifting: the `nodeId` response alias was added to the MCP `createdNodePayload` but not the HTTP `buildNodeResponse`, so CLI/HTTP `node add --json` lacked it (#31).
|
|
20
|
+
- [LRN-20260607-005] New `json-render` sourceSurface enum member silently took the permissive default because the `scoped` guard in `applyAxInteraction` was not updated. Security near-miss: the sandboxed viewer forwards author-controlled spec action params verbatim, so a prompt-injected spec could have anchored AX state onto arbitrary nodes via caller-supplied `nodeIds` (`on.press → { action: 'ax.work.create', params: { nodeIds: ['victim'] } }`). The fix added `json-render` to the clamp; the durable rule is that any guard switching on an enum-like string is a mandatory co-change site for every new member, or the new member takes the permissive default.
|
|
21
|
+
- [LRN-20260608-002] `readJson` hardening silently killed the documented bare-array shape of `POST /api/canvas/batch` (#49). The #47 hardening normalized every non-object body — including top-level arrays — to `{}`, which turned the handler's `Array.isArray(body)` branch into dead code: bare-array batches returned `{ ok: true, results: [] }` and created nothing. Fixed with an array-preserving reader (`readJsonObjectOrArray`) used only by the batch endpoint.
|
|
22
22
|
|
|
23
23
|
These are not three bugs. They are one architecture failing three times.
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ A project whose thesis is agent experience ships a tool surface that consumes a
|
|
|
36
36
|
|
|
37
37
|
### 4. E2E is not a CI gate (high)
|
|
38
38
|
|
|
39
|
-
The Playwright/Bun ESM loader blocker ([ERR-20260508-001]) has been open for weeks. E2E was removed from the publish workflow after the apt-mirror hang ([LRN-20260603-
|
|
39
|
+
The Playwright/Bun ESM loader blocker ([ERR-20260508-001]: `bun x playwright test` dies before test discovery because Playwright's transform layer tries to import a nonexistent `playwright.config.ts.esm.preflight` module under Bun; browser tests only run when Playwright is launched through Node with Bun on `PATH`) has been open for weeks. E2E was removed from the publish workflow after the apt-mirror hang ([LRN-20260603-001]: the `playwright install --with-deps chromium` step began reliably hanging ~15 minutes on GitHub runners with zero code change — a runner-image/apt regression — so publish dropped its redundant browser steps and kept build + typecheck + unit + smoke) and does not gate PRs. The bugs that matter (iframe blank flicker, literal `\n` in ledger, SVG calc()) were all caught only by browser tests. Green CI can currently ship a broken canvas.
|
|
40
40
|
|
|
41
41
|
**Fix:** run Playwright via Node's runner in CI permanently, and make headless e2e a hard PR gate.
|
|
42
42
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Spatial canvas workbench for coding agents
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Spatial canvas workbench for coding agents \u2014 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",
|
|
7
7
|
"module": "./src/server/index.ts",
|
|
@@ -40,18 +40,19 @@
|
|
|
40
40
|
"pack:dry-run": "bun pm pack --dry-run",
|
|
41
41
|
"release:check": "bun run build && bun run typecheck && bun run lint && bun run test:all",
|
|
42
42
|
"release:smoke": "bash scripts/release-smoke.sh",
|
|
43
|
-
"typecheck": "tsc --noEmit",
|
|
43
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
44
44
|
"lint": "biome check .",
|
|
45
45
|
"lint:fix": "biome check --write .",
|
|
46
|
-
"test": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
47
|
-
"test:unit": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
48
|
-
"test:
|
|
46
|
+
"test": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
47
|
+
"test:unit": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
48
|
+
"test:client": "bun test --preload ./tests/client/setup.ts tests/client",
|
|
49
|
+
"test:coverage": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json bun test tests/unit --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir coverage && bun scripts/check-coverage.ts",
|
|
49
50
|
"test:web-canvas": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun run build && PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bash scripts/run-playwright.sh",
|
|
50
51
|
"test:e2e": "bun run test:web-canvas",
|
|
51
52
|
"test:e2e-cli": "bash scripts/e2e-cli-coverage.sh",
|
|
52
53
|
"test:web-canvas:headed": "bun run build && bash scripts/run-playwright.sh --headed",
|
|
53
54
|
"test:e2e:headed": "bun run test:web-canvas:headed",
|
|
54
|
-
"test:all": "bun run test && bun run test:web-canvas",
|
|
55
|
+
"test:all": "bun run test && bun run test:client && bun run test:web-canvas",
|
|
55
56
|
"test:install-browsers": "bun x playwright install chromium",
|
|
56
57
|
"validate:agent-skills": "bash scripts/validate-agent-skill-mirrors.sh",
|
|
57
58
|
"prepublishOnly": "bun run build && bun run typecheck"
|
|
@@ -59,8 +60,6 @@
|
|
|
59
60
|
"dependencies": {
|
|
60
61
|
"@joplin/turndown-plugin-gfm": "^1.0.64",
|
|
61
62
|
"@json-render/core": "0.19.0",
|
|
62
|
-
"@json-render/devtools": "0.19.0",
|
|
63
|
-
"@json-render/devtools-react": "0.19.0",
|
|
64
63
|
"@json-render/directives": "0.19.0",
|
|
65
64
|
"@json-render/mcp": "0.19.0",
|
|
66
65
|
"@json-render/react": "0.19.0",
|
|
@@ -68,7 +67,6 @@
|
|
|
68
67
|
"@modelcontextprotocol/ext-apps": "^1.3.1",
|
|
69
68
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
70
69
|
"@preact/signals": "^2.0.0",
|
|
71
|
-
"@types/turndown": "^5.0.6",
|
|
72
70
|
"marked": "^15.0.0",
|
|
73
71
|
"preact": "^10.25.0",
|
|
74
72
|
"react": "^19.2.3",
|
|
@@ -79,11 +77,16 @@
|
|
|
79
77
|
},
|
|
80
78
|
"devDependencies": {
|
|
81
79
|
"@biomejs/biome": "^2.5.2",
|
|
80
|
+
"@happy-dom/global-registrator": "^20.10.6",
|
|
81
|
+
"@json-render/devtools": "0.19.0",
|
|
82
|
+
"@json-render/devtools-react": "0.19.0",
|
|
82
83
|
"@playwright/test": "^1.58.2",
|
|
83
84
|
"@tailwindcss/cli": "^4.1.18",
|
|
85
|
+
"@testing-library/preact": "^3.2.4",
|
|
84
86
|
"@types/bun": "latest",
|
|
85
87
|
"@types/react": "^19.2.14",
|
|
86
88
|
"@types/react-dom": "^19.2.3",
|
|
89
|
+
"@types/turndown": "^5.0.6",
|
|
87
90
|
"tailwindcss": "^4.1.18",
|
|
88
91
|
"tw-animate-css": "^1.4.0",
|
|
89
92
|
"typescript": "^5.7.0"
|
|
@@ -26,8 +26,8 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
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. The MCP parameter is `query` — passing the
|
|
28
28
|
HTTP API's `q` is silently ignored and returns zero results.
|
|
29
|
-
4. **Snapshot before destructive changes.** Use `canvas_snapshot
|
|
30
|
-
|
|
29
|
+
4. **Snapshot before destructive changes.** Use `canvas_snapshot { action: "save", name }` before
|
|
30
|
+
clear, restore, or a major
|
|
31
31
|
reorganization.
|
|
32
32
|
5. **Show intent with the Ghost Cursor — by default.** Signal with
|
|
33
33
|
`canvas_intent { action: "signal", ... }` before every meaningful create, move, connect, remove,
|
|
@@ -36,7 +36,7 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
36
36
|
human watches intent form and can veto mid-thought. Skip it only for trivial in-place tweaks or
|
|
37
37
|
high-frequency batch churn. The default TTL (~8s) expires between agent turns: signal with
|
|
38
38
|
`ttlMs: 30000` and settle by passing `intentId` on the mutation in the same or next call.
|
|
39
|
-
6. **Mutate through current composites.** Prefer the
|
|
39
|
+
6. **Mutate through current composites.** Prefer the 16 composite MCP tools below.
|
|
40
40
|
7. **Arrange and validate.** After batch changes, use `canvas_view { action: "arrange" }` when
|
|
41
41
|
appropriate and always finish with `canvas_query { action: "validate" }`.
|
|
42
42
|
8. **Verify context pins.** Pin with `canvas_pin_nodes` or the browser's **Pin as context**, then
|
|
@@ -115,6 +115,7 @@ json-render, a graph, or an HTML primitive is sufficient.
|
|
|
115
115
|
| `canvas_ax_gate` | `request`, `resolve`, `await` with `approval`, `elicitation`, or `mode` |
|
|
116
116
|
| `canvas_ax_timeline` | `read`, `record-event`, `add-evidence`, `send-steering` |
|
|
117
117
|
| `canvas_ax_delivery` | `claim`, `mark` |
|
|
118
|
+
| `canvas_snapshot` | `save`, `list`, `restore`, `delete`, `gc`, `diff` |
|
|
118
119
|
| `canvas_intent` | `signal`, `update`, `clear` |
|
|
119
120
|
|
|
120
121
|
Important routing:
|
|
@@ -131,9 +132,9 @@ Important routing:
|
|
|
131
132
|
As of v0.3.0, the 57 legacy single-purpose tools from the v0.2 compatibility window are removed.
|
|
132
133
|
The composites above plus the retained standalones are now the whole MCP surface: `canvas_batch`,
|
|
133
134
|
`canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`, `canvas_ingest_activity`, and
|
|
134
|
-
`canvas_invoke_command`.
|
|
135
|
-
`
|
|
136
|
-
|
|
135
|
+
`canvas_invoke_command`. Snapshots are the `canvas_snapshot` composite (actions
|
|
136
|
+
`save | list | restore | delete | gc | diff`); the 6 legacy snapshot standalones were removed in
|
|
137
|
+
v0.4.0 after their deprecated 0.3.x window.
|
|
137
138
|
|
|
138
139
|
## Spatial Rules
|
|
139
140
|
|
|
@@ -228,6 +229,15 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
228
229
|
(Chrome). Do not diagnose a healthy app session as a broken node. When reporting a black tile,
|
|
229
230
|
fetch `GET /api/canvas/debug/ext-app-recovery` from the daemon and attach the trace — it shows
|
|
230
231
|
what the recovery queue actually did in that panel.
|
|
232
|
+
- A hosted ext-app (Excalidraw) resized NARROW/TALL (e.g. 360x529) can show its diagram in the
|
|
233
|
+
upper region with the app's own dark fill below it — in every engine (0.3.4 report Finding Q).
|
|
234
|
+
This letterboxing lives inside the hosted app bundle's root container, not in PMX (a body-level
|
|
235
|
+
background override ships but cannot reach the app's inner root). Keep ext-app tiles near the
|
|
236
|
+
app's aspect (the 960x738 default, or the same ratio), or expand the node — the fullscreen
|
|
237
|
+
overlay renders full-bleed. The durable fix is upstream in the excalidraw-mcp app.
|
|
238
|
+
- Behind proxies that buffer streaming responses (e.g. portal hosts), the workbench auto-falls
|
|
239
|
+
back from SSE to a polling transport within ~3s, so the board still boots and stays live.
|
|
240
|
+
Force a mode with `/workbench?transport=poll` (or `transport=sse`) when diagnosing.
|
|
231
241
|
- Graph and json-render standalone surfaces use `display=site` and fill the browser viewport, and
|
|
232
242
|
reflow on a live window resize in a normal browser. Some single-tab host browsers (e.g. the
|
|
233
243
|
Codex in-app browser) don't deliver live-resize events, so a resized standalone chart can look
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
{
|
|
5
5
|
"id": 1,
|
|
6
6
|
"name": "investigation-board",
|
|
7
|
-
"prompt": "I'm debugging a memory leak in our Node.js API. The /api/users endpoint is leaking memory on every request. I found a suspicious closure in src/handlers/users.ts that captures the entire request object, and the heap snapshot shows growing EventEmitter listeners. Can you set up an investigation board on the canvas so I can see the full picture?",
|
|
7
|
+
"prompt": "I'm debugging a memory leak in our Node.js API. The /api/users endpoint is leaking memory on every request. I found a suspicious closure in evals/fixtures/investigation-board/src/handlers/users.ts that captures the entire request object, and the heap snapshot shows growing EventEmitter listeners. Can you set up an investigation board on the canvas so I can see the full picture?",
|
|
8
8
|
"expected_output": "Creates multiple nodes (bug description, code file, heap findings, hypothesis) connected with edges, arranged in a tree layout. Uses appropriate node types (markdown for findings, file for source, status for investigation progress).",
|
|
9
9
|
"files": [
|
|
10
10
|
"evals/fixtures/investigation-board/src/handlers/users.ts"
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"assertions": [
|
|
97
97
|
{
|
|
98
98
|
"name": "reads-pinned-context",
|
|
99
|
-
"description": "Reads the curated pin set via canvas://pinned-context
|
|
99
|
+
"description": "Reads the curated pin set via canvas://pinned-context \u2014 or the host-equivalent (canvas_ax_state / the HTTP pinned-context endpoint) where a direct resource tool is unavailable \u2014 not just canvas_query action:layout",
|
|
100
100
|
"type": "output_check"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
{
|
|
115
115
|
"id": 5,
|
|
116
116
|
"name": "code-exploration-files",
|
|
117
|
-
"prompt": "I'm trying to understand how the authentication flow works in this project. Can you put the relevant auth files on the canvas so I can see how they connect? The main files are src/auth/login.ts, src/auth/middleware.ts, src/auth/jwt.ts, and src/routes/auth.ts.",
|
|
117
|
+
"prompt": "I'm trying to understand how the authentication flow works in this project. Can you put the relevant auth files on the canvas so I can see how they connect? The main files are evals/fixtures/code-exploration/src/auth/login.ts, evals/fixtures/code-exploration/src/auth/middleware.ts, evals/fixtures/code-exploration/src/auth/jwt.ts, and evals/fixtures/code-exploration/src/routes/auth.ts.",
|
|
118
118
|
"expected_output": "Creates file nodes for each mentioned file (content auto-loads), relies on code graph to auto-detect import dependencies, groups auth files together, and reads canvas://code-graph for dependency analysis.",
|
|
119
119
|
"files": [
|
|
120
120
|
"evals/fixtures/code-exploration/src/auth/jwt.ts",
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
"id": 7,
|
|
173
173
|
"name": "snapshot-before-changes",
|
|
174
174
|
"prompt": "I want to reorganize the canvas to explore a different approach, but I don't want to lose what's there now. Can you save the current state, clear it, and set up a fresh workspace for the new approach? If it doesn't work out I want to go back.",
|
|
175
|
-
"expected_output": "Takes a named snapshot of current state, then clears the canvas. Mentions that
|
|
175
|
+
"expected_output": "Takes a named snapshot of current state, then clears the canvas. Mentions that canvas_snapshot { action: \"restore\" } can bring back the old state. Does NOT just clear without snapshotting.",
|
|
176
176
|
"assertions": [
|
|
177
177
|
{
|
|
178
178
|
"name": "takes-snapshot",
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"name": "mentions-restore",
|
|
189
|
-
"description": "Tells the user how to restore (
|
|
189
|
+
"description": "Tells the user how to restore (canvas_snapshot action restore) if they want to go back",
|
|
190
190
|
"type": "output_check"
|
|
191
191
|
}
|
|
192
192
|
]
|
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
"assertions": [
|
|
279
279
|
{
|
|
280
280
|
"name": "pins-via-verified-path",
|
|
281
|
-
"description": "Pins via canvas_pin_nodes / CLI pin / 'Pin as context'
|
|
281
|
+
"description": "Pins via canvas_pin_nodes / CLI pin / 'Pin as context' \u2014 not an unverified action",
|
|
282
282
|
"type": "output_check"
|
|
283
283
|
},
|
|
284
284
|
{
|
|
@@ -417,7 +417,7 @@
|
|
|
417
417
|
"id": 17,
|
|
418
418
|
"name": "standalone-surface-open-as-site",
|
|
419
419
|
"prompt": "Open the bar-chart graph node as a standalone full-page site for a screenshot, and also try to open the Excalidraw diagram node the same way. Tell me the correct surface URL for each.",
|
|
420
|
-
"expected_output": "For the graph/json-render node, uses the stable surface URL /api/canvas/surface/<id> which redirects to the full-viewport display=site viewer (reads surfaceUrl from the node, does not fabricate a path). Recognizes the hosted Excalidraw/ext-app node is NOT an open-as-site target (its surface route returns a clean 404
|
|
420
|
+
"expected_output": "For the graph/json-render node, uses the stable surface URL /api/canvas/surface/<id> which redirects to the full-viewport display=site viewer (reads surfaceUrl from the node, does not fabricate a path). Recognizes the hosted Excalidraw/ext-app node is NOT an open-as-site target (its surface route returns a clean 404 \u2014 it renders only with the in-canvas host bridge) and does not claim a standalone tab will work. Notes that some host browsers (e.g. the Codex in-app single-tab browser) don't deliver live-resize events, so a system browser is recommended for separate full-page viewing.",
|
|
421
421
|
"assertions": [
|
|
422
422
|
{
|
|
423
423
|
"name": "graph-site-surface-url",
|
|
@@ -73,7 +73,7 @@ section below.
|
|
|
73
73
|
3. **Read before write** — `canvas_query { action: "search", query }` (or `pmx-canvas search`)
|
|
74
74
|
to find existing nodes; avoid duplicates. Use `canvas_query { action: "layout" }` only when
|
|
75
75
|
you need the full board.
|
|
76
|
-
4. **Snapshot before destructive work** — `canvas_snapshot { name }` before clear/major
|
|
76
|
+
4. **Snapshot before destructive work** — `canvas_snapshot { action: "save", name }` before clear/major
|
|
77
77
|
reorg; restore if needed.
|
|
78
78
|
5. **Signal then mutate (default behavior)** — signal with `canvas_intent { action: "signal", … }`
|
|
79
79
|
to telegraph the move before nearly every mutation, then create with the right composite: `canvas_node` (markdown/status/file/webpage/html
|
|
@@ -315,7 +315,8 @@ pmx-canvas spatial
|
|
|
315
315
|
- `group create|add|remove` — manage groups
|
|
316
316
|
- `clear --yes` — destructive clear with explicit confirmation
|
|
317
317
|
- `validate spec` — validate json-render specs and graph payloads without creating nodes
|
|
318
|
-
- `
|
|
318
|
+
- `node update` — returns a compact `{ ok, id, nodeId, position, size, updatedFields }` envelope by default; pass `--full` for the complete node payload. Full ext-app/mcp-app payloads embed the bundled app HTML and tool results (hundreds of KB — a no-op ext-app resize once returned ~940 KB), so in agent turns keep the default envelope and read content via `node get --summary`
|
|
319
|
+
- `web-artifact build` — build bundled React/Tailwind HTML artifacts; use `--deps` for extra packages and `--include-logs` only when raw logs are useful. The build pipeline runs bash scripts (POSIX-only) — it is not supported on native Windows; already-built artifacts render everywhere
|
|
319
320
|
- `external-app add --kind excalidraw` — create the hosted Excalidraw preset; response includes `id` and `nodeId` aliases for the same canvas node
|
|
320
321
|
- `serve status|stop` — inspect and stop daemonized servers started with `serve --daemon`
|
|
321
322
|
- `code-graph`, `spatial` — analysis commands
|
|
@@ -429,7 +430,7 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
|
|
|
429
430
|
> registered — use the composites instead. The authoritative legacy→composite mapping table lives
|
|
430
431
|
> in [`docs/mcp.md`](../../../docs/mcp.md) — this skill does not re-enumerate the removed names.
|
|
431
432
|
|
|
432
|
-
### The
|
|
433
|
+
### The 16 composites
|
|
433
434
|
|
|
434
435
|
| Composite | `action` values | What it does |
|
|
435
436
|
|-----------|-----------------|--------------|
|
|
@@ -438,6 +439,7 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
|
|
|
438
439
|
| `canvas_edge` | `add` · `remove` | Connect / disconnect nodes |
|
|
439
440
|
| `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
|
|
440
441
|
| `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
|
|
442
|
+
| `canvas_snapshot` | `save` · `list` · `restore` · `delete` · `gc` · `diff` | Named snapshots: save/list/restore/delete, garbage-collect old ones, diff current canvas vs a snapshot (`diff` takes `snapshot`, not `id`) |
|
|
441
443
|
| `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `remove-annotation` | Auto-arrange, pan-to-node, fit viewport, clear the board, delete a human-drawn annotation by id |
|
|
442
444
|
| `canvas_query` | `search` · `layout` · `validate` | Find nodes by keyword, read full layout, or **`validate`** the board for node collisions / group-containment / dangling edges |
|
|
443
445
|
| `canvas_app` | `open-mcp-app` · `diagram` · `build-artifact` | Hosted MCP apps, the Excalidraw diagram preset, and bundled web artifacts (folds `canvas_open_mcp_app` / `canvas_add_diagram` / `canvas_build_web_artifact`) |
|
|
@@ -505,10 +507,9 @@ investigation board" intent, then run the batch with that linked `intentId` (use
|
|
|
505
507
|
|
|
506
508
|
These stay separate by design (trust-boundary, firehose, execution-intent, or not-yet-consolidated
|
|
507
509
|
surfaces): `canvas_batch`, `canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_interaction`,
|
|
508
|
-
`canvas_ingest_activity`, `canvas_invoke_command
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
the current save-snapshot tool, so the composite cannot land additively).
|
|
510
|
+
`canvas_ingest_activity`, `canvas_invoke_command`. Snapshots moved into the `canvas_snapshot`
|
|
511
|
+
composite in v0.4.0 (actions `save | list | restore | delete | gc | diff`); the 6 legacy snapshot
|
|
512
|
+
standalones were removed after their deprecated 0.3.x window.
|
|
512
513
|
|
|
513
514
|
`canvas_batch` supports exactly these ops: `node.add`, `node.update`, `node.remove`, `graph.add`,
|
|
514
515
|
`edge.add`, `edge.remove`, `group.create`, `group.add`, `group.remove`, `pin.set`/`pin.add`/
|
|
@@ -650,7 +651,7 @@ ID extraction for mixed tool responses:
|
|
|
650
651
|
|
|
651
652
|
**`canvas_pin_nodes`** (standalone) — Set, add, or remove pinned context nodes. Use `{ nodeIds: [...] }` — the field is `nodeIds`, not `ids`.
|
|
652
653
|
|
|
653
|
-
**`
|
|
654
|
+
**`canvas_snapshot { action: "diff" }`** — Compare current canvas state with a saved snapshot. Requires `{ snapshot: "<snapshot-id-or-name>" }`; there is no implicit previous-snapshot default.
|
|
654
655
|
|
|
655
656
|
**`canvas_render { action: "describe-schema" }`** — Inspect the running server's create schemas and
|
|
656
657
|
canonical examples. Use before generating structured payloads when you need the authoritative current
|
|
@@ -766,13 +767,14 @@ ranked matches with content snippets. Use instead of parsing the full layout to
|
|
|
766
767
|
### History & Snapshots
|
|
767
768
|
|
|
768
769
|
- **`canvas_history { action: "undo" }`** / **`canvas_history { action: "redo" }`** — step the mutation ring buffer.
|
|
769
|
-
- **`canvas_snapshot`**
|
|
770
|
-
- **`
|
|
771
|
-
- **`
|
|
770
|
+
- **`canvas_snapshot { action: "save", name }`** — save a named snapshot. Returns `{ ok, id, snapshot }` (flat `id` aliases `snapshot.id`).
|
|
771
|
+
- **`canvas_snapshot { action: "list" }`** / **`{ action: "gc", keep }`** / **`{ action: "delete", id }`** — manage saved snapshots.
|
|
772
|
+
- **`canvas_snapshot { action: "restore", id }`** — restore from a snapshot.
|
|
773
|
+
- **`canvas_snapshot { action: "diff", snapshot }`** — compare current canvas against a saved snapshot (added/removed/modified nodes & edges). Note the field is `snapshot` (id or name), not `id`.
|
|
772
774
|
|
|
773
775
|
### Canvas Management
|
|
774
776
|
|
|
775
|
-
**`canvas_view { action: "clear" }`** — Remove all nodes and edges. **Always `canvas_snapshot` first** —
|
|
777
|
+
**`canvas_view { action: "clear" }`** — Remove all nodes and edges. **Always `canvas_snapshot { action: "save" }` first** —
|
|
776
778
|
this is irreversible without a prior snapshot.
|
|
777
779
|
|
|
778
780
|
### Browser Automation (WebView)
|
|
@@ -797,6 +799,13 @@ below operates on the live canvas state through the `canvas_webview` composite.
|
|
|
797
799
|
- Optional: `backend` (`webkit` macOS-only, or `chrome`), `width`, `height`
|
|
798
800
|
- The session opens `/workbench` at the canvas URL, waits for the SPA to
|
|
799
801
|
hydrate, and reports back via `canvas_webview { action: "status" }`
|
|
802
|
+
- **Backend preflight**: prefer `webkit` on macOS — it is the reliable
|
|
803
|
+
automation backend across tested hosts. The `chrome` backend can trigger a
|
|
804
|
+
native "Allow remote debugging?" dialog on first start in some hosts; if the
|
|
805
|
+
prompt is not approved promptly the start times out with `ok:false`. When
|
|
806
|
+
using `chrome`, approve the dialog immediately and/or raise
|
|
807
|
+
`PMX_CANVAS_WEBVIEW_TIMEOUT_MS` (e.g. `30000`). A single chrome timeout is
|
|
808
|
+
worth one immediate retry before classifying it as a failure.
|
|
800
809
|
|
|
801
810
|
**`canvas_webview { action: "stop" }`** — Tear down the automation session
|
|
802
811
|
|
|
@@ -1398,19 +1407,19 @@ Monitor ongoing processes:
|
|
|
1398
1407
|
|
|
1399
1408
|
Show two states side by side for the human to compare:
|
|
1400
1409
|
|
|
1401
|
-
1. Take a snapshot before changes: `canvas_snapshot` with name "before-X"
|
|
1410
|
+
1. Take a snapshot before changes: `canvas_snapshot { action: "save" }` with name "before-X"
|
|
1402
1411
|
2. Make changes to the canvas
|
|
1403
|
-
3. Use `
|
|
1412
|
+
3. Use `canvas_snapshot { action: "diff", snapshot }` to show what changed
|
|
1404
1413
|
4. Or: create two groups ("Before" and "After") with corresponding nodes
|
|
1405
1414
|
|
|
1406
1415
|
### Save and Start Fresh
|
|
1407
1416
|
|
|
1408
1417
|
When the human wants to explore a different approach without losing current work:
|
|
1409
1418
|
|
|
1410
|
-
1. **First**, save the current state: `canvas_snapshot` with a descriptive name
|
|
1419
|
+
1. **First**, save the current state: `canvas_snapshot { action: "save" }` with a descriptive name
|
|
1411
1420
|
2. **Then** clear: `canvas_view { action: "clear" }` (never clear without snapshotting first)
|
|
1412
1421
|
3. Set up the new workspace with initial nodes
|
|
1413
|
-
4. Tell the human the snapshot name and that `
|
|
1422
|
+
4. Tell the human the snapshot name and that `canvas_snapshot { action: "restore" }` can bring everything back
|
|
1414
1423
|
|
|
1415
1424
|
## Best Practices
|
|
1416
1425
|
|