pmx-canvas 0.4.5 → 0.4.7

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 (109) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/Readme.md +76 -36
  3. package/dist/canvas/global.css +169 -3
  4. package/dist/canvas/index.js +112 -87
  5. package/dist/canvas/mermaid-entry.js +3563 -0
  6. package/dist/json-render/index.css +1 -1
  7. package/dist/types/cli/agent.d.ts +1 -0
  8. package/dist/types/cli/commands/smoke.d.ts +1 -0
  9. package/dist/types/cli/shared.d.ts +13 -1
  10. package/dist/types/client/canvas/CanvasNode.d.ts +11 -0
  11. package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
  12. package/dist/types/client/mermaid-entry.d.ts +11 -0
  13. package/dist/types/client/nodes/AxStepControls.d.ts +4 -0
  14. package/dist/types/client/nodes/DiffNode.d.ts +20 -0
  15. package/dist/types/client/nodes/ExtAppFrame.d.ts +0 -2
  16. package/dist/types/client/nodes/FileNode.d.ts +14 -0
  17. package/dist/types/client/nodes/MermaidNode.d.ts +12 -0
  18. package/dist/types/client/state/canvas-store.d.ts +10 -0
  19. package/dist/types/client/state/intent-bridge.d.ts +9 -0
  20. package/dist/types/client/types.d.ts +2 -2
  21. package/dist/types/server/ax-flow-loop.d.ts +5 -0
  22. package/dist/types/server/ax-interaction.d.ts +21 -2
  23. package/dist/types/server/canvas-operations.d.ts +5 -0
  24. package/dist/types/server/canvas-provenance.d.ts +1 -1
  25. package/dist/types/server/canvas-state.d.ts +18 -1
  26. package/dist/types/server/canvas-validation.d.ts +28 -1
  27. package/dist/types/server/file-content.d.ts +46 -0
  28. package/dist/types/server/html-primitives.d.ts +13 -1
  29. package/dist/types/server/html-surface.d.ts +8 -0
  30. package/dist/types/server/index.d.ts +5 -0
  31. package/dist/types/server/operations/ops/ax-work.d.ts +1 -0
  32. package/dist/types/server/operations/ops/nodes.d.ts +1 -1
  33. package/dist/types/server/server.d.ts +1 -0
  34. package/dist/types/server/web-artifacts.d.ts +14 -0
  35. package/dist/types/server/workboard.d.ts +46 -0
  36. package/dist/types/shared/ax-flow.d.ts +51 -0
  37. package/dist/types/shared/ax-surface-protocol.d.ts +7 -0
  38. package/dist/types/shared/node-sizes.d.ts +33 -0
  39. package/docs/cli.md +31 -2
  40. package/docs/environment.md +1 -0
  41. package/docs/http-api.md +29 -0
  42. package/docs/mcp.md +13 -1
  43. package/docs/node-types.md +47 -1
  44. package/docs/screenshot.png +0 -0
  45. package/package.json +5 -4
  46. package/skills/pmx-canvas/SKILL.md +123 -25
  47. package/skills/pmx-canvas/evals/evals.json +54 -3
  48. package/skills/pmx-canvas/references/full-reference.md +11 -6
  49. package/skills/pmx-canvas-testing/SKILL.md +3 -0
  50. package/src/cli/agent.ts +2 -0
  51. package/src/cli/commands/ax.ts +28 -3
  52. package/src/cli/commands/history.ts +1 -1
  53. package/src/cli/commands/nodes.ts +2 -2
  54. package/src/cli/commands/query.ts +1 -1
  55. package/src/cli/commands/smoke.ts +212 -0
  56. package/src/cli/commands/view.ts +1 -1
  57. package/src/cli/index.ts +7 -0
  58. package/src/cli/shared.ts +35 -5
  59. package/src/client/App.tsx +28 -15
  60. package/src/client/canvas/CanvasNode.tsx +54 -5
  61. package/src/client/canvas/CanvasViewport.tsx +6 -0
  62. package/src/client/canvas/EdgeLayer.tsx +44 -24
  63. package/src/client/canvas/ExpandedNodeOverlay.tsx +21 -0
  64. package/src/client/canvas/Minimap.tsx +2 -0
  65. package/src/client/canvas/auto-fit.ts +12 -1
  66. package/src/client/canvas/use-pan-zoom.ts +16 -1
  67. package/src/client/icons.tsx +4 -0
  68. package/src/client/mermaid-entry.ts +76 -0
  69. package/src/client/nodes/AxStepControls.tsx +226 -0
  70. package/src/client/nodes/DiffNode.tsx +114 -0
  71. package/src/client/nodes/ExtAppFrame.tsx +259 -84
  72. package/src/client/nodes/FileNode.tsx +249 -51
  73. package/src/client/nodes/MermaidNode.tsx +81 -0
  74. package/src/client/state/canvas-store.ts +19 -0
  75. package/src/client/state/intent-bridge.ts +32 -0
  76. package/src/client/state/node-factory.ts +2 -0
  77. package/src/client/state/sse-bridge.ts +32 -1
  78. package/src/client/theme/global.css +169 -3
  79. package/src/client/types.ts +6 -0
  80. package/src/mcp/server.ts +19 -1
  81. package/src/server/ax-flow-loop.ts +114 -0
  82. package/src/server/ax-interaction.ts +305 -4
  83. package/src/server/canvas-operations.ts +83 -26
  84. package/src/server/canvas-provenance.ts +2 -0
  85. package/src/server/canvas-schema.ts +33 -0
  86. package/src/server/canvas-state.ts +46 -10
  87. package/src/server/canvas-validation.ts +88 -0
  88. package/src/server/demo-state.json +4605 -523
  89. package/src/server/demo.ts +24 -0
  90. package/src/server/file-content.ts +152 -0
  91. package/src/server/file-watcher.ts +7 -10
  92. package/src/server/html-primitives.ts +636 -0
  93. package/src/server/html-surface.ts +11 -0
  94. package/src/server/index.ts +25 -6
  95. package/src/server/operations/composites.ts +3 -2
  96. package/src/server/operations/ops/ax-work.ts +5 -0
  97. package/src/server/operations/ops/batch.ts +8 -0
  98. package/src/server/operations/ops/json-render.ts +44 -0
  99. package/src/server/operations/ops/nodes.ts +24 -2
  100. package/src/server/operations/ops/viewport.ts +15 -2
  101. package/src/server/server.ts +154 -6
  102. package/src/server/web-artifacts.ts +109 -0
  103. package/src/server/workboard.ts +159 -0
  104. package/src/shared/ax-flow.ts +102 -0
  105. package/src/shared/ax-surface-protocol.ts +8 -0
  106. package/src/shared/node-sizes.ts +52 -0
  107. package/src/shared/surface.ts +2 -0
  108. package/dist/types/server/chart-template.d.ts +0 -29
  109. package/src/server/chart-template.ts +0 -448
package/CHANGELOG.md CHANGED
@@ -3,6 +3,122 @@
3
3
  All notable changes to `pmx-canvas` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.4.7] - 2026-08-09
7
+
8
+ ### Added
9
+
10
+ - The flow works without the panel too: materialized step nodes get native
11
+ Start / Done / Blocked controls, and the first step adds Run loop / Stop and
12
+ a steer box. That loop runs server-side, so it survives a browser reload and
13
+ keeps advancing while the tab is closed.
14
+ - Two live AX control surfaces you can put on the board: **`ax-board`** (give
15
+ the agent tasks, watch their status change as it works, steer it, and run a
16
+ bounded task loop) and **`ax-flow`** (the same controls drawn as a task flow
17
+ with a loop-back rail, plus a **Materialize to board** button that lays the
18
+ flow out as real nodes joined by edges, each tracking its own status).
19
+ - The demo board is a proper showcase now: every node type, every chart type,
20
+ every HTML primitive, a grouped cluster, labelled edges and context pins —
21
+ laid out in labelled bands instead of the old three-group OKR dashboard.
22
+ - `GET /api/canvas/file-bytes?nodeId=…` serves a file node's bytes from the
23
+ workspace (used by the inline PDF viewer); paths outside the workspace, or
24
+ reached through a symlink out of it, are refused.
25
+ - File nodes now handle more than source code: `.csv` and `.tsv` files render
26
+ as real tables, PDFs render in an embedded viewer, and other binary files
27
+ show a name-and-size placeholder instead of decoded gibberish. Text files
28
+ larger than 2 MB render truncated with a notice rather than being read into
29
+ memory whole.
30
+
31
+ ### Changed
32
+
33
+ - The mouse wheel now zooms the canvas (trackpad two-finger scrolling still
34
+ pans, and the toolbar +/- buttons and their keyboard shortcuts both zoom
35
+ around the viewport centre).
36
+ - The browser honors the server's readability floor, so content-fit never
37
+ shrinks a node back below it.
38
+ - `validate` now reports edges whose endpoint node is docked (and therefore
39
+ cannot be drawn) as an advisory `hiddenEdgeEndpoints` list. Like
40
+ `sizeWarnings` it does not fail `ok`, so boards that passed before still
41
+ pass.
42
+
43
+ ### Fixed
44
+
45
+ - `strictSize` is no longer dropped on file, image and webpage nodes — it used
46
+ to be honored when sizing the node but never stored, so the browser resized
47
+ the node anyway.
48
+ - Node chrome no longer crowds out short nodes when you zoom out: the title bar
49
+ used to grow until it took most of a small node and clipped its text away.
50
+ - Context nodes no longer dock themselves invisibly: a node you create lands on
51
+ the canvas where you put it, and docking to the HUD column is something you
52
+ ask for. Previously an agent-created context node existed in the API and
53
+ passed validation but never rendered, so edges to it trailed off into empty
54
+ space.
55
+ - `fit` now sizes the board to the connected browser window instead of assuming
56
+ 1440x900, so fitting no longer leaves nodes clipped on both sides.
57
+ - Edges stay legible when you zoom out: strokes, dashes, arrowheads, and labels
58
+ now hold their on-screen size instead of shrinking to invisible hairlines,
59
+ and `references` edges use a higher-contrast colour.
60
+ - CLI flag values that begin with a dash now work, so a unified diff starting
61
+ with `--- a/file` can be passed straight to `--content` (and a bare `--` ends
62
+ flag parsing).
63
+ - Web-artifact builds now fail fast with a clear message naming the Node.js
64
+ version they need, instead of an opaque package-manager error.
65
+
66
+ ## [0.4.6] - 2026-08-08
67
+
68
+ ### Added
69
+
70
+ - A `pmx-canvas smoke` command: one-command check that a running canvas works
71
+ in the current environment — server health + workspace, CLI/server version
72
+ skew, MCP initialize handshake, a temporary node create/search/remove
73
+ round-trip, and board validation. JSON report, exit 1 on failure.
74
+ - The `/health` endpoint now reports the server's package `version`, so tools
75
+ can detect version skew between a CLI and the daemon it targets.
76
+ - The CLI now accepts `--agent-id` on `ax work add/update` and `ax event add`,
77
+ and `--agent-id` / `--target` on `ax steer`; the SDK's work, event, and
78
+ steering methods accept the same identity fields.
79
+ - The skill now teaches in-view placement and content sizing: focus or fit
80
+ newly created user-facing nodes, use per-type default sizes, and keep AX
81
+ state reads lean.
82
+ - A new `diff` node type: unified diffs render with per-file headers, hunk
83
+ rows, and colored added/removed lines instead of a plain code fence.
84
+ - A new `mermaid` node type: flowcharts, sequence and state diagrams render
85
+ client-side from mermaid text — no hosted app needed; Excalidraw remains
86
+ for interactive drawing.
87
+ - A live work-item board: `canvas_render { action: "workboard" }` (HTTP
88
+ `POST /api/canvas/workboard`) materializes one board node showing all AX
89
+ work items by status, and it refreshes automatically whenever work items
90
+ change.
91
+
92
+
93
+ ### Changed
94
+
95
+ - **Nodes created with unreadably small explicit sizes are now clamped up to a
96
+ per-type minimum.** A request for e.g. 200x100 now yields a readable
97
+ default-ish size instead; pass `strictSize: true` to keep a genuinely small
98
+ fixed frame. `validate` reports any undersized nodes as advisory size
99
+ warnings. Agents doing their own layout arithmetic should read back the
100
+ created size (or set `strictSize`).
101
+ - The README's MCP and Amp orb setup guides now cover portable configs: pin
102
+ the package version in orb setup scripts and avoid hard-coding an absolute
103
+ `PMX_CANVAS_WORKSPACE_ROOT` in committed MCP configs.
104
+ - Focus margins are now screen-space and zoom-aware, so a focused node clears
105
+ the floating toolbar at any scale.
106
+
107
+ ### Fixed
108
+
109
+ - A browser panel left open across a `pmx-canvas` upgrade now reloads itself
110
+ once on reconnect instead of silently running the old client bundle (which
111
+ hid new client behavior such as the auto-ghost minimum dwell).
112
+ - A batch that pins nodes now updates the browser's pin state immediately —
113
+ the context menu no longer shows a stale "Pin as context" until reload.
114
+ - The MCP stdio server now exits when its client closes the connection instead
115
+ of remaining as an orphaned process.
116
+ - Black app tiles in WebKit host panels (e.g. the GitHub Copilot app): hosted
117
+ app frames now boot one at a time, confirm they actually painted via a paint
118
+ probe, and recover automatically when they did not — soft resize, then
119
+ remount, then an explicit Retry button instead of a silent black tile. The
120
+ loading overlay stays up until the paint is confirmed.
121
+
6
122
  ## [0.4.5] - 2026-08-07
7
123
 
8
124
  ### Added
@@ -2989,6 +3105,8 @@ otherwise have to discover by trial and error.
2989
3105
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
2990
3106
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
2991
3107
 
3108
+ [0.4.7]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.7
3109
+ [0.4.6]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.6
2992
3110
  [0.4.5]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.5
2993
3111
  [0.4.4]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.4
2994
3112
  [0.4.3]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.4.3
package/Readme.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pmx-canvas
2
2
 
3
3
  **A moldable canvas for agent-assisted thinking.** An infinite 2D surface
4
- where files, plans, status, charts, fetched web pages, annotations, and
5
- hand-drawn diagrams live side by side. Every node carries its own renderer; agents
4
+ where files, plans, status, charts, fetched web pages, annotations, code
5
+ diffs, and diagrams live side by side. Every node carries its own renderer; agents
6
6
  (and you) build new views in the middle of a session — even streaming a
7
7
  structured panel into place as they generate it — not as a separate tooling
8
8
  project. Pin what matters and the agent reads your spatial curation as
@@ -35,14 +35,21 @@ neighborhoods) and uses your layout to ground its next action.
35
35
 
36
36
  ### 02 / Mix any data source
37
37
 
38
- Files, web pages, screenshots, structured panels, charts, hand-drawn
39
- diagrams, embedded MCP Apps, and bundled web artifacts all live on the same
40
- surface. Any rich surface — an HTML node or a web artifact — can be **opened as
41
- a site**, full-page in its own browser tab with one click; the canvas and the
42
- tab render the same document. The reach of the canvas is the union of its
43
- [built-in node types](docs/node-types.md) and **whatever your agent's harness
44
- already has access to** — MCP servers, CLIs, file reads, web fetch, anything
45
- on its toolbelt.
38
+ Files, web pages, screenshots, structured panels, charts, diagrams, embedded
39
+ MCP Apps, and bundled web artifacts all live on the same surface. A file node
40
+ follows the file on disk as the agent edits it — source with line numbers, a
41
+ CSV or TSV as a real table, a PDF inline. Anything else binary shows its name
42
+ and size rather than decoded gibberish, and a very large text file renders
43
+ truncated with a note saying so. Code review has its own node: a unified
44
+ diff with per-file headers and colored added and removed lines. Diagrams can
45
+ be drawn by hand in Excalidraw or written as plain mermaid text and rendered
46
+ on the spot. Context cards, execution ledgers, and agent trace pills round out
47
+ the set. Any rich surface — HTML, mermaid, json-render, graph, webpage, or a
48
+ web artifact — can be **opened as a site**, full-page in its own browser tab
49
+ with one click; the canvas and the tab render the same document. The reach of
50
+ the canvas is the union of its [built-in node types](docs/node-types.md) and
51
+ **whatever your agent's harness already has access to** — MCP servers, CLIs,
52
+ file reads, web fetch, anything on its toolbelt.
46
53
 
47
54
  ### 03 / Annotate
48
55
 
@@ -61,13 +68,17 @@ immediately — an explicit, low-noise control over what the agent sees next.
61
68
  On top of pins, a host-agnostic **AX (agent-experience) layer** turns the
62
69
  canvas into a shared workspace between you and the agent:
63
70
 
64
- - **Agent-native nodes** — create markdown, status, HTML, json-render, graph,
65
- web-artifact, or MCP app nodes that can act as interactive controls for the
66
- agent. A node can focus context, create or update work, add evidence, request
67
- input or approval, or send steering without leaving the board.
71
+ - **Agent-native nodes** — most node types can act as interactive controls for
72
+ the agent out of the box: a node can focus context, create or update work,
73
+ add evidence, request input or approval, or send steering without leaving the
74
+ board. The sandboxed surfaces — HTML nodes and MCP apps — stay off until a
75
+ node opts in, since their content is author-supplied.
68
76
  - **Focus** — promote nodes into the agent's active context without moving the viewport.
69
77
  - **Work items & approval gates** — track visible tasks tied to nodes, and gate
70
- high-impact actions behind a human `pending → approved/rejected` decision.
78
+ high-impact actions behind a human `pending → approved/rejected` decision. A
79
+ linked node shows its work item's status on the title bar, and a live work
80
+ board lays the whole queue out by status and keeps itself current — useful
81
+ when several agents are working at once.
71
82
  - **Steering messages & agent-event timeline** — send instructions to the
72
83
  active session, and read a normalized, bounded timeline of prompts, tool
73
84
  runs, evidence (logs/diffs/screenshots/test-output), and failures.
@@ -109,8 +120,9 @@ Harness-agnostic. Drive the canvas from [MCP](docs/mcp.md) (22 tools,
109
120
  14 resources, change notifications), the [CLI](docs/cli.md), the
110
121
  [HTTP API](docs/http-api.md), or the [Bun SDK](docs/sdk.md) — all
111
122
  [environment variables documented here](docs/environment.md). Works with
112
- Claude Code, GitHub Copilot CLI, Codex, Cursor, Windsurf, or any agent
113
- that can spawn an MCP stdio server, call a CLI, or hit an HTTP endpoint.
123
+ Claude Code, the GitHub Copilot app and CLI, Codex, Amp, Cursor, Windsurf, or
124
+ any agent that can spawn an MCP stdio server, call a CLI, or hit an HTTP
125
+ endpoint.
114
126
 
115
127
  ### 07 / Native app adapters
116
128
 
@@ -203,15 +215,20 @@ To work on the canvas itself, clone the repo — see [Development](#development)
203
215
 
204
216
  ```bash
205
217
  bunx pmx-canvas # Start canvas, open browser
206
- bunx pmx-canvas --demo # Start with the saved dashboard demo board
218
+ bunx pmx-canvas --demo # Start with the showcase demo board
207
219
  bunx pmx-canvas --no-open # Headless (good for daemons / CI)
220
+ bunx pmx-canvas --theme=volt # Pick a theme (nine ship; PMX_CANVAS_THEME works too)
208
221
  bunx pmx-canvas --mcp # Run as MCP server (stdio)
209
222
  bunx pmx-canvas --help # All commands
210
223
  ```
211
224
 
225
+ Themes are a toolbar picker away too, and any one panel can override the
226
+ shared theme for itself with `?theme=<name|auto>`.
227
+
212
228
  The canvas opens at `http://localhost:4313`. Try `--demo` first — it seeds a
213
- saved dashboard with grouped OKR notes, graph panels, context pins, and labeled
214
- edges.
229
+ showcase board with every node type on it: notes and status, files, a diff, a
230
+ CSV table, charts of every kind, mermaid diagrams, the HTML primitives,
231
+ structured panels, a grouped cluster, labeled edges, and context pins.
215
232
 
216
233
  ### Connect your agent (MCP)
217
234
 
@@ -228,7 +245,21 @@ Add to your agent's MCP config:
228
245
  }
229
246
  ```
230
247
 
231
- The canvas auto-starts on first tool call.
248
+ The canvas auto-starts on first tool call. Keep the config portable: commit it
249
+ to the repo and let the workspace default to the launch directory — hard-coding
250
+ an absolute `PMX_CANVAS_WORKSPACE_ROOT` breaks other checkouts and machines.
251
+ Set that env var only when your host spawns the MCP server from an incidental
252
+ directory (e.g. `~/.copilot`) instead of the project root.
253
+
254
+ Verify any environment in one command against a running server:
255
+
256
+ ```bash
257
+ bunx pmx-canvas smoke
258
+ ```
259
+
260
+ It checks server health + workspace, CLI/server version skew, the MCP stdio
261
+ initialize handshake, a temp-node create/search/remove round-trip, and board
262
+ validation — JSON report, exit 1 on failure.
232
263
 
233
264
  ### Use inside the GitHub Copilot app
234
265
 
@@ -274,8 +305,9 @@ services:
274
305
  ```
275
306
 
276
307
  ```bash
277
- # .agents/setup — install the CLI (pin the version for reproducible starts)
278
- npm install -g pmx-canvas@latest
308
+ # .agents/setup — install the CLI (pin the exact version: a fresh orb running
309
+ # @latest can silently pick up a newer release than the one you validated)
310
+ npm install -g pmx-canvas@0.4.7
279
311
  ```
280
312
 
281
313
  The server binds the portal-assigned `$PORT` automatically (gated on the
@@ -286,12 +318,15 @@ caveat: WebView automation (`pmx-canvas screenshot`) needs Bun >= 1.3.12 in
286
318
  the orb image; the canvas itself runs fine without it and says so in the
287
319
  error. The canvas detects the orb and the portal's nested-iframe
288
320
  embed on its own: live updates arrive over the proxy-safe polling transport,
289
- and iframe-backed nodes (HTML, graph, json-render, web artifacts) render
290
- inline via `srcdoc` with their theme styling included. Cross-origin hosted
291
- apps (e.g. the hosted Excalidraw MCP app) cannot be inlined and may stay
292
- blocked by the portal embed — everything served from the canvas itself works.
321
+ and iframe-backed nodes (HTML, mermaid, graph, json-render, web artifacts)
322
+ render inline via `srcdoc` with their theme styling included. Two things can't
323
+ be inlined and stay blocked by the portal embed: cross-origin hosted apps (e.g.
324
+ the hosted Excalidraw MCP app), and PDF file nodes — a PDF node offers an
325
+ "Open PDF" link there instead of a preview.
293
326
  Debug overrides: `/workbench?transport=poll|sse` and
294
- `?iframe-mode=srcdoc|src`.
327
+ `?iframe-mode=srcdoc|src`. Once the service is up, `pmx-canvas smoke` verifies
328
+ the whole stack (health, versions, MCP handshake, node lifecycle, validation)
329
+ in one command.
295
330
 
296
331
  ### Install the agent skill (recommended)
297
332
 
@@ -315,7 +350,8 @@ Common harness skill directories: `.claude/skills/` (Claude Code),
315
350
  `.github/skills/` or `.copilot/skills/` (Copilot CLI),
316
351
  `.agents/skills/` (cross-harness convention). Once the canvas is running,
317
352
  the agent can read `canvas://skills` and pull in companion skills
318
- (`control-session-orchestrator`, `web-artifacts-builder`, `json-render-*`,
353
+ (`pmx-canvas-orchestration` for running several agents on one board,
354
+ `control-session-orchestrator`, `web-artifacts-builder`, `json-render-*`,
319
355
  `pmx-canvas-testing`, `playwright-cli`, etc.) as the work demands.
320
356
 
321
357
  ## Documentation
@@ -341,16 +377,20 @@ the agent can read `canvas://skills` and pull in companion skills
341
377
  `.pmx-canvas/canvas.db`.
342
378
  - **What leaves your machine.** The core canvas runs entirely on
343
379
  `localhost`. Network egress only happens for explicit, opt-in flows:
344
- `webpage` nodes fetch the URL you give them; `mcp-app` /
345
- `canvas_app { action: "diagram" }` calls go to whatever MCP server URL you
346
- configure (the Excalidraw preset uses `https://mcp.excalidraw.com/mcp`);
347
- `bunx` itself reads the npm registry on first install. Nothing else phones
348
- home.
380
+ `webpage` nodes and remote-URL `image` nodes fetch the address you give
381
+ them; `mcp-app` / `canvas_app { action: "diagram" }` calls go to whatever
382
+ MCP server URL you configure (the Excalidraw preset uses
383
+ `https://mcp.excalidraw.com/mcp`); building a web artifact runs a package
384
+ manager, which fetches a React/Tailwind toolchain plus that artifact's declared dependencies (and, if neither pnpm nor bun is present, installs pnpm globally) from the npm
385
+ registry; and `bunx` itself reads the registry on first install. Nothing
386
+ else phones home.
349
387
 
350
388
  ## Tech stack
351
389
 
352
390
  - **Runtime:** [Bun](https://bun.sh)
353
- - **UI:** [Preact](https://preactjs.com) + [@preact/signals](https://github.com/preactjs/signals)
391
+ - **UI:** [Preact](https://preactjs.com) + [@preact/signals](https://github.com/preactjs/signals);
392
+ the json-render/graph viewer bundle is React + [recharts](https://recharts.org),
393
+ and mermaid nodes render with [mermaid](https://mermaid.js.org)
354
394
  - **Styling:** CSS custom properties + Tailwind (json-render bundle only)
355
395
  - **Server:** Bun.serve (HTTP + SSE)
356
396
  - **MCP:** [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) (stdio)
@@ -363,7 +403,7 @@ cd pmx-canvas
363
403
  bun install
364
404
  bun run build
365
405
  bun run dev # Start + open browser
366
- bun run dev:demo # Start with the demo board
406
+ bun run dev:demo # Start with the showcase demo board
367
407
  bun run test # Unit tests
368
408
  bun run test:e2e # Playwright end-to-end tests
369
409
  bun run test:all # Unit tests + browser smoke
@@ -643,6 +643,10 @@ body,
643
643
  overflow: hidden;
644
644
  text-overflow: ellipsis;
645
645
  flex: 1;
646
+ /* The name is the one thing that must survive a cramped title bar. Everything
647
+ else in the bar is flex-shrink: 0, so without a floor the title absorbed all
648
+ the pressure and collapsed to an ellipsis on narrow nodes. */
649
+ min-width: 9ch;
646
650
  min-width: 0;
647
651
  }
648
652
 
@@ -670,6 +674,13 @@ body,
670
674
  }
671
675
 
672
676
  .canvas-node .node-ax-status {
677
+ /* A long status ("in-progress") must not push the node's NAME out of the bar;
678
+ the pill is a glance-level cue, the name is the identity. */
679
+ max-width: 11ch;
680
+ overflow: hidden;
681
+ text-overflow: ellipsis;
682
+ white-space: nowrap;
683
+ flex-shrink: 1;
673
684
  font-size: calc(9px * var(--node-chrome-scale, 1));
674
685
  padding: calc(1px * var(--node-chrome-scale, 1)) calc(6px * var(--node-chrome-scale, 1));
675
686
  border-radius: calc(4px * var(--node-chrome-scale, 1));
@@ -1846,10 +1857,11 @@ html.is-node-resizing .ext-app-preview-catcher {
1846
1857
  border-radius: 999px;
1847
1858
  }
1848
1859
 
1849
- /* Edge labels */
1860
+ /* Edge labels — --edge-chrome-scale is set on the edge <svg> so labels keep a
1861
+ constant screen size while zoomed out (see edgeChromeScale in EdgeLayer). */
1850
1862
  .edge-label {
1851
1863
  font-family: var(--font);
1852
- font-size: 11px;
1864
+ font-size: calc(11px * var(--edge-chrome-scale, 1));
1853
1865
  pointer-events: none;
1854
1866
  user-select: none;
1855
1867
  }
@@ -1857,7 +1869,7 @@ html.is-node-resizing .ext-app-preview-catcher {
1857
1869
  .edge-label-bg {
1858
1870
  fill: var(--c-panel-glass);
1859
1871
  stroke: var(--c-line);
1860
- stroke-width: 0.5;
1872
+ stroke-width: calc(0.5 * var(--edge-chrome-scale, 1));
1861
1873
  }
1862
1874
 
1863
1875
  /* Trace nodes — compact pills for agent tool/subagent visualization */
@@ -1895,6 +1907,160 @@ html.is-node-resizing .ext-app-preview-catcher {
1895
1907
  }
1896
1908
  }
1897
1909
 
1910
+ /* Diff nodes — unified diff viewer with per-file sections */
1911
+ .diff-node {
1912
+ display: flex;
1913
+ flex-direction: column;
1914
+ gap: 10px;
1915
+ height: 100%;
1916
+ overflow: auto;
1917
+ font-family: var(--mono);
1918
+ font-size: 12px;
1919
+ color: var(--c-text);
1920
+ }
1921
+
1922
+ .diff-node.diff-node-empty {
1923
+ align-items: center;
1924
+ justify-content: center;
1925
+ color: var(--c-muted);
1926
+ }
1927
+
1928
+ .diff-node .diff-file {
1929
+ border: 1px solid var(--c-line);
1930
+ border-radius: 6px;
1931
+ overflow: hidden;
1932
+ flex-shrink: 0;
1933
+ }
1934
+
1935
+ .diff-node .diff-file-header {
1936
+ padding: 4px 8px;
1937
+ background: var(--c-panel-overlay);
1938
+ border-bottom: 1px solid var(--c-line);
1939
+ color: var(--c-text-soft);
1940
+ font-weight: 600;
1941
+ white-space: nowrap;
1942
+ overflow: hidden;
1943
+ text-overflow: ellipsis;
1944
+ }
1945
+
1946
+ .diff-node .diff-lines {
1947
+ margin: 0;
1948
+ padding: 4px 0;
1949
+ overflow-x: auto;
1950
+ font-family: inherit;
1951
+ font-size: inherit;
1952
+ line-height: 1.5;
1953
+ }
1954
+
1955
+ .diff-node .diff-lines > div {
1956
+ padding: 0 8px;
1957
+ white-space: pre;
1958
+ }
1959
+
1960
+ .diff-node .diff-hunk {
1961
+ color: var(--c-muted);
1962
+ }
1963
+
1964
+ .diff-node .diff-line-add {
1965
+ color: var(--c-ok);
1966
+ background: var(--c-ok-10);
1967
+ }
1968
+
1969
+ .diff-node .diff-line-remove {
1970
+ color: var(--c-danger);
1971
+ background: var(--c-danger-12);
1972
+ }
1973
+
1974
+ .diff-node .diff-line-context {
1975
+ color: var(--c-text-soft);
1976
+ }
1977
+
1978
+ /* File nodes — delimited-data tables and non-text placeholders */
1979
+ .file-table-shell {
1980
+ display: flex;
1981
+ flex-direction: column;
1982
+ height: 100%;
1983
+ min-height: 0;
1984
+ }
1985
+
1986
+ .file-node-notice {
1987
+ flex-shrink: 0;
1988
+ padding: 4px 10px;
1989
+ color: var(--c-muted);
1990
+ font-size: 10px;
1991
+ }
1992
+
1993
+ /* Own scroll port so a wide CSV scrolls inside the node instead of blowing out
1994
+ the layout — and so the sticky header row has something to stick to. */
1995
+ .file-table-wrap {
1996
+ flex: 1;
1997
+ min-height: 0;
1998
+ overflow: auto;
1999
+ }
2000
+
2001
+ /* The shell/wrap prefix is load-bearing: these rules must out-specify the markdown
2002
+ table styles (`.canvas-node .node-body table` / `th` / `td`), whose 0.5em
2003
+ margin and translucent header background break the sticky header. Borders are
2004
+ separate, not collapsed — collapsed borders belong to the table element, so
2005
+ the header leaks the rows scrolling beneath it. */
2006
+ .file-table-shell .file-table-wrap .file-table {
2007
+ margin: 0;
2008
+ border-collapse: separate;
2009
+ border-spacing: 0;
2010
+ width: max-content;
2011
+ min-width: 100%;
2012
+ font-family: var(--mono);
2013
+ color: var(--c-text);
2014
+ }
2015
+
2016
+ .file-table-shell .file-table-wrap .file-table th,
2017
+ .file-table-shell .file-table-wrap .file-table td {
2018
+ max-width: 320px;
2019
+ padding: 3px 8px;
2020
+ border: none;
2021
+ border-right: 1px solid var(--c-line);
2022
+ border-bottom: 1px solid var(--c-line);
2023
+ text-align: left;
2024
+ white-space: nowrap;
2025
+ overflow: hidden;
2026
+ text-overflow: ellipsis;
2027
+ }
2028
+
2029
+ .file-table-shell .file-table-wrap .file-table th {
2030
+ position: sticky;
2031
+ top: 0;
2032
+ z-index: 1;
2033
+ /* Opaque in every theme — rows must not bleed through while scrolling. */
2034
+ background: var(--c-panel);
2035
+ color: var(--c-text-soft);
2036
+ font-weight: 600;
2037
+ }
2038
+
2039
+ .file-table-shell .file-table-wrap .file-table tbody tr:nth-child(even) {
2040
+ background: var(--c-surface-subtle);
2041
+ }
2042
+
2043
+ .file-node-placeholder {
2044
+ display: flex;
2045
+ flex-direction: column;
2046
+ align-items: center;
2047
+ justify-content: center;
2048
+ gap: 4px;
2049
+ height: 100%;
2050
+ padding: 16px;
2051
+ color: var(--c-muted);
2052
+ text-align: center;
2053
+ }
2054
+
2055
+ .file-node-placeholder-name {
2056
+ color: var(--c-text-soft);
2057
+ word-break: break-all;
2058
+ }
2059
+
2060
+ .file-node-link {
2061
+ color: var(--c-accent);
2062
+ }
2063
+
1898
2064
  /* Docked node — compact toolbar-flanking panel */
1899
2065
  .docked-node {
1900
2066
  min-width: 200px;