pmx-canvas 0.6.2 → 0.6.4

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 (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/Readme.md +30 -3
  3. package/dist/canvas/global.css +32 -0
  4. package/dist/canvas/index.js +68 -66
  5. package/dist/json-render/index.js +57 -57
  6. package/dist/types/client/canvas/FeedbackDialog.d.ts +4 -0
  7. package/dist/types/client/canvas/auto-fit.d.ts +3 -0
  8. package/dist/types/client/nodes/use-iframe-content-height.d.ts +3 -3
  9. package/dist/types/client/state/human-store.d.ts +1 -0
  10. package/dist/types/json-render/catalog.d.ts +20 -20
  11. package/dist/types/json-render/schema.d.ts +40 -0
  12. package/dist/types/mcp/skills.d.ts +13 -0
  13. package/dist/types/server/bundled-skills.d.ts +32 -4
  14. package/docs/cli.md +7 -2
  15. package/docs/http-api.md +5 -1
  16. package/docs/mcp.md +35 -1
  17. package/docs/node-types.md +43 -1
  18. package/docs/product-context-vision-2026-09.md +17 -6
  19. package/docs/product-vision-2026-09.md +136 -41
  20. package/docs/product-vision-review-2026-09.md +277 -0
  21. package/package.json +11 -11
  22. package/skills/json-render-mcp/SKILL.md +118 -2
  23. package/skills/pmx-canvas/SKILL.md +43 -13
  24. package/skills/pmx-canvas/evals/evals.json +46 -0
  25. package/skills/pmx-canvas/references/installing-pmx-canvas.md +5 -5
  26. package/src/client/canvas/FeedbackDialog.tsx +86 -0
  27. package/src/client/canvas/ToolRail.tsx +225 -189
  28. package/src/client/canvas/auto-fit.ts +20 -1
  29. package/src/client/nodes/use-iframe-content-height.ts +44 -24
  30. package/src/client/state/human-store.ts +6 -6
  31. package/src/client/theme/global.css +32 -0
  32. package/src/json-render/catalog.ts +6 -0
  33. package/src/json-render/renderer/index.tsx +17 -0
  34. package/src/json-render/schema.ts +4 -0
  35. package/src/mcp/server.ts +9 -4
  36. package/src/mcp/skills.ts +47 -0
  37. package/src/server/bundled-skills.ts +92 -53
  38. package/src/server/operations/ops/nodes.ts +43 -11
  39. package/src/shared/content-height-reporter.ts +4 -2
@@ -0,0 +1,277 @@
1
+ # Vision Review — September 2026
2
+
3
+ **Status:** Accepted and folded into [`product-vision-2026-09.md`](product-vision-2026-09.md) on 2026-09-23; this document keeps the reasoning and the full design sketch.
4
+ **Date:** 2026-09-23
5
+ **Scope:** An independent review of the long-term direction set by [`product-vision-2026-09.md`](product-vision-2026-09.md) and [`product-context-vision-2026-09.md`](product-context-vision-2026-09.md), checked against what has shipped since and against the real board in use today, plus a design sketch for boards as the wiki. Written against `main` `561e6ec5` (v0.6.3, clean tree, equal to `origin/main`).
6
+ **Method:** Read all three September documents (review, vision, context vision) and the 0.6.0–0.6.3 changelog; re-checked the review's trust findings in current source; read the live board the globally installed MCP server writes to, read-only, titles and counts only. No code was run and no board was modified.
7
+
8
+ ## Where things stand
9
+
10
+ - **Three vision documents in September; none of their 0.6 plan has shipped.** The vision's 0.6 was many boards, trust, the cheap tool surface, frame-host consolidation and a binary, in 2–3 weeks. Seventeen days and three releases later, 0.6.0–0.6.3 carried the Bun 1.4.2 upgrade, field-report fixes, the MCP Skills extension and json-render updates.
11
+ - **The afternoon-sized trust defects are all still present.** `ax.approval.resolve` never checks the caller, so an agent can approve its own gate. `requestJson` never reads `res.ok`, so a human's failed write is silent. The webpage fetcher uses `redirect: 'follow'` with no host filter. There is no boards table.
12
+ - **The black-tile problem is on round ten.** 0.6.2 ships it as a known limitation for Copilot/WebKit Excalidraw tiles, and `ExtAppFrame.tsx` grew to 1,591 lines.
13
+ - **The real board today is plain.** `C4 2026 OKR planning`: 26 markdown cards, 4 groups, 29 edges, 8 pins, no annotations, no iframe-backed nodes, written from Copilot sessions. 2 of 152 recorded steers were sent in September. 108 snapshots put `canvas.db` at 29.6 MB for a 30-node board.
14
+
15
+ ## Thesis
16
+
17
+ **pmx-canvas is the medium where human and agent thinking lands and stays.** It is not primarily a context compiler, and it is not a knowledge-management system. On every real board the agent writes the board, the human shapes it, and the board itself is the product: OKRs, planning overviews, discovery maps, decks. Agent context is a by-product of that.
18
+
19
+ The 2026-09-06 correction to "the human's extended memory" was right, and the evidence now supports it. Pins went from 1–2 on test boards to 8 on the first board that is plainly the maintainer's own work. Curation shows up when the human owns the artifact.
20
+
21
+ ## Keep
22
+
23
+ These moves from the vision are sound and should go ahead as written.
24
+
25
+ - **Many boards (move 0).** Real work is still being overwritten into snapshot rows.
26
+ - **One journal as the truth (move 4).** The highest-leverage architectural bet: undo that survives restart, SSE resume, the time scrubber, an audit trail, the sync model for sharing, and the end of full-copy snapshot bloat.
27
+ - **Agent output as cards (move 3).** Asks, work items and evidence belong on the surface.
28
+ - **Author on every node and a budgeted brief (Part 1).**
29
+ - **Trust that is not a label (move 7).**
30
+
31
+ ## Disagreements
32
+
33
+ ### 1. The vision is growing faster than the product
34
+
35
+ In eighteen days the scope went from "agent working memory" to a memory graph, a maintained wiki, working and confirmed revisions, personal and shared audiences, publication governance, derived-content permissions and hosted multiplayer. That is four products: a canvas, an agent control plane, a knowledge-management system and a team collaboration service, for one maintainer.
36
+
37
+ The biggest risk to the long-term vision is not its direction. It is that each document adds scope while host compatibility work takes the throughput. No new vision document should be written until move 0 and the trust fixes ship.
38
+
39
+ ### 2. Boards are the wiki
40
+
41
+ The context vision designs a second data model (entities, immutable revisions, confirmation, publication, audience intersection) to bring pmx-context-frame into the canvas. Most of what it wants follows from making boards first-class instead:
42
+
43
+ - named boards;
44
+ - a board library with a title, summary, last-touched date and search;
45
+ - cross-board links, where a node references a node on another board;
46
+ - a brief that can retrieve from past boards.
47
+
48
+ That library is the memory graph, and it needs no second filing system. Confirmation and publication should wait until a second person needs something from these boards; until then they are governance for an audience that does not exist. The [design sketch](#design-sketch-boards-are-the-wiki) below works this out.
49
+
50
+ ### 3. The destination is "share this board", not hosted SaaS
51
+
52
+ **Decided with the maintainer, 2026-09-23: this is the destination.** It replaces hosted multi-tenant multiplayer (vision Part 3) as the long-term end state.
53
+
54
+ Hosted multi-tenant multiplayer competes head-on with established whiteboard products that have teams, funding and their own MCP integrations. What they cannot easily be is local-first, host-agnostic, and inside the agent's pane in Claude Code, Codex, Copilot and Amp. That is where this product can win.
55
+
56
+ An OKR board is something a PM presents, so the natural steps are:
57
+
58
+ 1. a read-only share link (listen mode plus a token);
59
+ 2. comments;
60
+ 3. a second human writer;
61
+ 4. accounts and per-board roles, single-tenant.
62
+
63
+ Each step is justified only by someone using the one before it.
64
+
65
+ ### 4. The core thesis is still unmeasured
66
+
67
+ Nobody has shown that a pin changes what an agent does. This is cheap to settle:
68
+
69
+ - record which resources each agent session reads;
70
+ - record whether pinned nodes were in the context it read;
71
+ - add one fixed eval under `docs/evals/`: the same task on a curated and an uncurated board.
72
+
73
+ Until that exists, "attention is the compiler" is a belief.
74
+
75
+ ### 5. The host tax sets the roadmap
76
+
77
+ Four hosts, sandboxed iframes and WebKit took most of the 0.5 and 0.6 work. Move 5's single frame host is necessary but not sufficient. Name one reference surface where everything must work (the standalone browser, or the compiled binary in its own window), and treat embedded panes as best-effort with an honest fallback. Otherwise field reports keep setting the roadmap.
78
+
79
+ **Decided with the maintainer, 2026-09-24:** Chromium at 600 px, for now; WebKit joins the gate on a trigger. See [vision move 5](product-vision-2026-09.md#5-keep-every-node-type-unify-how-they-render-m).
80
+
81
+ ### 6. The host's chat is the steering channel
82
+
83
+ Canvas steering went nearly silent once real work moved to Copilot, whose own chat sits beside the board. The evidence is one board, so this is a signal rather than a finding. Steering and the fleet layer stay, per the 2026-09-06 decision, but they are unlikely to drive growth. The canvas wins on what host chat cannot do: a persistent, spatial, shareable result.
84
+
85
+ ## Design sketch: boards are the wiki
86
+
87
+ ### Principle
88
+
89
+ A wiki is pages, links between them, and a way to find your way around. The canvas already has a better page than a wiki does: a board. So instead of adding a knowledge model beside the boards, make boards linkable, nestable and mappable. Nothing new is filed; the structure grows out of the work.
90
+
91
+ ### Concepts
92
+
93
+ | Concept | What it is | New or existing |
94
+ |---|---|---|
95
+ | Board | A named canvas: nodes, edges, groups, pins, annotations, its own snapshots | New as a stored entity; today a workspace has exactly one |
96
+ | Home | The board a workspace opens on when nothing else is chosen; level 0 | New, an ordinary board |
97
+ | Portal | A `board` node: a card that stands for another board | New node type |
98
+ | Level | A board's shortest distance from Home through portals | Derived, never stored |
99
+ | Link | A reference from a card on one board to another board or card | New: portals plus `[[wiki links]]` in markdown |
100
+ | Board map | A generated board that shows every board and link | New, built like the code graph |
101
+ | README card | The one card on a board marked as its summary | New flag on an existing markdown node |
102
+
103
+ ### Levels grow from placement
104
+
105
+ A portal on a board makes the target one level below it. Levels are computed from Home by shortest path each time the library changes; nothing stores a level, so moving a portal re-levels a board without a migration.
106
+
107
+ ```
108
+ Home level 0
109
+ ├── Tech Enabling level 1 (area board)
110
+ │ ├── C4 2026 OKR planning level 2
111
+ │ ├── Delivery metrics level 2
112
+ │ └── Product discovery level 2
113
+ └── Workshops level 1
114
+ └── Working with agents (deck) level 2
115
+ ```
116
+
117
+ - **No fixed tiers.** Org, team, initiative and working board are names a user may give boards, not a taxonomy the product enforces. A mandatory taxonomy is the part of a wiki people stop maintaining.
118
+ - **A graph, not a tree.** A board may have portals on several parents; its level is the shortest one. `Product discovery` can sit under both `Tech Enabling` and a quarterly planning board.
119
+ - **Cycles are allowed.** Two boards may portal to each other; shortest-path levelling cannot loop.
120
+ - **Orphans are visible.** A board no portal reaches has no level and appears in an "Unplaced" lane on the board map, which is the wiki's missing-link report for free.
121
+
122
+ ### Links
123
+
124
+ - **Portal.** Dropping a board onto another board, or choosing "Link board" from the context menu, creates a portal node with `data.boardId`. Double-click opens the target board. A portal renders text only: title, README summary, last-touched time, and the titles of its pinned cards. It never embeds the live board, so nesting cannot multiply iframes.
125
+ - **Wiki link.** `[[Board name]]` and `[[Board name#Card title]]` in any markdown card. On save the server resolves the name to a board id (and card id) and stores the ids beside the text, so renaming a board does not break links. The markdown renderer shows a resolved link as a chip that opens the target; an unresolved link renders as a dashed chip that offers "Create board", which is how a wiki grows a new page.
126
+ - **Backlinks.** Every board's library entry lists the boards that link to it. The portal card and the board's top bar both show the count.
127
+
128
+ ### The board map is a board
129
+
130
+ The graph view is not a separate screen. It is a generated board with one node per board and one edge per link, rebuilt the way `code-graph.ts` builds file dependencies: auto-edges carry their own prefix (`boardmap-`), stay out of mutation history, and recompute on a debounce when boards or links change.
131
+
132
+ - **Arrangement is the human's.** Nodes are generated, but positions the human drags are kept, so the map can be laid out by meaning rather than force. New boards appear near their first parent.
133
+ - **Encoding.** Portal edges solid, wiki-link edges dotted. Node size from card count, fade from time since last touched, one colour per level.
134
+ - **Pinning a board on the map pulls it into the agent's brief.** Today a pinned neighborhood means nearby cards on one board; on the map it means linked boards. It is the same gesture one level up, and it is the retrieval control for the whole wiki.
135
+ - **Everything else works.** Groups, annotations, search and focus behave as on any board, because it is one. The name is "board map" to avoid confusion with the existing `graph` chart node type.
136
+
137
+ ### How the wiki grows
138
+
139
+ - **Make board.** On a group's context menu: its cards move to a new board named after the group, and a portal takes the group's place. A cluster that outgrew its board becomes a page of its own.
140
+ - **Inline board.** On a portal's context menu: the reverse. The target board's cards come back as a group, and the board is archived.
141
+ - **New session, new board or chosen board.** A session starts on a board the human picks, or a new one linked from the board it was started from. This replaces today's "Before session" snapshot (`server.ts:138`) as the way work is kept apart; snapshots go back to being bookmarks.
142
+
143
+ Group, board and board of boards are one continuous scale, and a board moves along it by a gesture, not by filing.
144
+
145
+ ### What the agent gets
146
+
147
+ - **`canvas://boards`.** The library: id, name, README summary, parents, level, links, backlinks, card count, last touched. Bounded like every other read: summaries are previews, not bodies.
148
+ - **A board target everywhere.** Every operation accepts `board`; an MCP session binds to one board at start, so an agent never writes to a board it did not choose. A write to another board is an explicit target, never an accident of which board the human is looking at.
149
+ - **A cross-board brief.** The brief carries the current board first, then boards pinned on the map, then boards one link away, each as README summary plus pinned card titles, with full content by explicit pull. This is the budgeted brief of vision Part 1 with the board library as its second tier.
150
+ - **One composite tool.** A `canvas_board` composite (`list`, `create`, `open`, `rename`, `archive`, `link`, `make-board`, `inline`) plus the `board` field on existing tools. That moves the tool count from 22 to 23 and needs the tool-surface freeze test and the four transports updated together.
151
+
152
+ ### Data model
153
+
154
+ - A `boards` table: `id`, `name`, `created_at`, `updated_at`, `archived_at`.
155
+ - A `board_id` column on `nodes`, `edges`, `annotations`, `context_pins` and `snapshots`. Node ids are already unique across the database, so pins stay keyed by node.
156
+ - A `board_links` table (`from_board`, `from_node`, `to_board`, `to_node`, `kind`) rebuilt from portals and parsed wiki links; it is a projection, not a source of truth.
157
+ - The README flag lives in the node's `data`.
158
+
159
+ Migration: the current board becomes Home. The nine real boards in snapshot history (vision, "What the real boards show") restore as boards under Home, which is the first test of the whole design.
160
+
161
+ ### State architecture: the honest cost
162
+
163
+ `CanvasStateManager` is one singleton holding one board, and 400+ call sites assume it. Two stages keep the cost down:
164
+
165
+ 1. **One active board per server.** Opening a board saves and loads, the same as a restore. Reads of other boards (library, portals, brief, map) query SQLite directly and never load a second board into memory. Agent writes to a board other than the active one are refused with a clear error. This is enough for one person with one or two agents and is the first stage of move 0 (resized from S to M on 2026-09-24: `board_id` on five tables, per-board snapshots, a `board` target on every transport).
166
+ 2. **Concurrent boards.** A registry of state managers keyed by board id, loaded on demand, so an agent can work on one board while the human looks at another. This is an L-sized change and should wait until stage 1 is used and the journal (move 4) exists, since each board then gets its own journal.
167
+
168
+ ### What this replaces in the context vision
169
+
170
+ - The separate entity and immutable-revision model: boards and cards are the entities; the journal is the revision history.
171
+ - The personal wiki: a workspace's board library is the personal wiki.
172
+ - Working versus confirmed: postponed. When needed it is a label on a card or board, not a second store.
173
+ - Personal versus shared: postponed until sharing exists. Sharing is per board; a portal to a board the viewer cannot see renders as a locked card with no title. Sharing a board never shares what it links to.
174
+
175
+ ### Open questions
176
+
177
+ - **Scope of Home.** The store is per workspace (`.pmx-canvas/canvas.db`), and real PM work already lives in one workspace. Keep the wiki per workspace; revisit a user-level library only if work spreads across several.
178
+ - **Board-level pins versus map pins.** Both feed the brief; the map should show which boards the current brief includes, which is the attention heat of the vision applied to boards.
179
+ - **Archive semantics.** Archived boards leave the map and the brief but keep their links resolvable, and a link to one renders greyed.
180
+
181
+ ### Success criteria
182
+
183
+ - The nine real boards are restored under Home and reachable in two clicks from the map.
184
+ - `C4 2026 OKR planning` links to the April OKR board, and an agent starting a new session on a fresh board answers "what changed since the April OKRs?" from the brief without being told which board to read.
185
+ - A group turned into a board and inlined back leaves the original board byte-identical apart from ids and timestamps.
186
+ - A board with 20 portals renders no more iframes than one without them.
187
+
188
+ ### Sequence
189
+
190
+ | Step | Size | Delivers |
191
+ |---|---|---|
192
+ | Many boards, one active (stage 1) | S | Boards, Home, open and switch, restore of the nine boards |
193
+ | Portals, README card, `canvas://boards` | M | Levels, library, backlinks |
194
+ | Wiki links and the board map | M | Links in cards, the generated map, orphan lane |
195
+ | Cross-board brief and map pins | M | Retrieval across the wiki |
196
+ | Make board and inline board | S | Organic growth |
197
+ | Concurrent boards (stage 2) | L | After the journal; agents on other boards |
198
+
199
+ ## Additional moves
200
+
201
+ Four moves that follow from what the real boards contain. Two make the sharing destination real before any network work; two keep a board wiki useful over time instead of letting it pile up.
202
+
203
+ ### Static export: the first step to sharing (S)
204
+
205
+ Sharing through a network mode has to wait for the trust fixes, since the vision makes move 7 mandatory before any port is opened. A self-contained HTML file of one board does not: the renderer and the board's data in one file, read-only, with pan, zoom and card expand. It can be attached to an email or a chat message, or posted to a wiki page.
206
+
207
+ - No server, no authentication, no attack surface, and the app produces the file itself, so the no-externals rule holds.
208
+ - The sharing path becomes **export → link → comments → second writer**. The first step puts a board in front of a colleague, which is the second-user milestone (resized from S to M on 2026-09-24 and shipped on its own as 0.7.x).
209
+ - The live `C4 2026 OKR planning` board has a "Workshop flow" card; exporting that board for the workshop's participants is the first real test.
210
+ - Iframe-backed nodes export their stored markup inside sandboxed iframes, as they render today. A node whose content cannot be inlined (a live MCP app) exports as its last screenshot and says so.
211
+
212
+ ### Tours: the board is the presentation (M)
213
+
214
+ Five of the real boards include agent-generated HTML slide decks, one with 15 slides. The agent rebuilt as slides content that already existed as cards. A tour is an ordered path through a board's cards, seeded from the reading order `spatial-analysis.ts` already computes and editable by the human. Arrow keys move the camera card to card, and a card fills the screen when it is the current step.
215
+
216
+ - A deck stops being a separate artifact that drifts from its board.
217
+ - Tours carry into exports and share links, so presenting a board and sharing it are one feature.
218
+ - HTML decks stay as a capability; they stop being the only way to present.
219
+
220
+ ### Recipe cards: boards that can be refreshed (M)
221
+
222
+ The real boards are full of numbers from elsewhere: 13 metric charts, developer-experience metrics, and an "Atlassian OKR-board mirror" card on the live board. They go stale silently, and in a board wiki staleness is the rot that kills wikis.
223
+
224
+ Node provenance already records a source and a refresh strategy (`canvas-provenance.ts`), but only for files, URLs, images, apps and artifacts. Add one source kind, the **recipe**: the tool call and prompt that produced the card, recorded when an agent writes it.
225
+
226
+ - The canvas never fetches the external data itself; it stores the recipe and marks the card stale after a set age.
227
+ - "Refresh this board" hands the stale recipes to whichever agent is attached, which rewrites the cards through the normal write path.
228
+ - The board map shows staleness beside last-touched time, so it is visible which parts of the wiki are out of date.
229
+ - A recipe is data, never instructions: refreshing runs only when a human asks, and the agent treats the recipe as a request it can decline.
230
+
231
+ ### New board from this board: continuity across cycles (S)
232
+
233
+ The same OKR board appears in April and again for C4. **New board from this board** copies the structure (groups, the README card, recipe cards) without the content, and links the new board to the old one as its previous board.
234
+
235
+ - Recurring work forms chains on the board map without anyone filing it.
236
+ - The design sketch's success test, "what changed since the April OKRs?", becomes answerable because the link exists.
237
+ - It is templates for free: any board can be the pattern for the next one.
238
+
239
+ | Move | Size | Needs first |
240
+ |---|---|---|
241
+ | Static export | S | nothing |
242
+ | New board from this board | S | many boards (stage 1) |
243
+ | Tours | M | nothing; better with export |
244
+ | Recipe cards | M | nothing; better with the board map |
245
+
246
+ ## Proposed vision
247
+
248
+ > **Think with your agents on boards that are still there next week.** Every session lands on a named board, the boards link into a wiki you can see as a map, the agent reads what you curated, and you can share or present a board with anyone.
249
+
250
+ | Horizon | Content |
251
+ |---|---|
252
+ | Now (weeks) | Human-only answers with requester withdraw (decided 2026-09-24), backed by a real secret, toasts on failed writes, a redirect host filter, many boards with one active (restore the nine lost boards), instrumentation of what agents read, and static export. |
253
+ | Foundation (1–2 months) | Portals, the board library, wiki links, the board map and the cross-board brief; new board from this board, tours and recipe cards; the journal and author on every node, with snapshots as bookmarks; agent output as cards; the SDK routed through the registry. |
254
+ | Reach (1.0 and after) | Read-only share links and the time scrubber in 1.0, then comments, then a second writer. |
255
+ | Only if pulled by use | Personal and shared knowledge governance; multi-tenant hosting. |
256
+
257
+ No capability or node type is cut. The fleet layer, every node type and the MCP-app host stay; this changes only what comes first.
258
+
259
+ ## The one thing that matters most
260
+
261
+ There is still no second user. The repository has 18 stars, every pull request is the maintainer's, and npm downloads are plausibly CI and release smoke. Every long-term claim about wikis, teams or hosting is speculation until someone else depends on a board. The next milestone should be "one colleague opens a shared board", not an architecture move.
262
+
263
+ ## Evidence
264
+
265
+ | Claim | Source |
266
+ |---|---|
267
+ | 0.6.0–0.6.3 contents | `CHANGELOG.md` at `561e6ec5` |
268
+ | Gate self-approval still possible | `src/server/operations/ops/ax-work.ts`, `ax.approval.resolve` handler has no caller check |
269
+ | Silent human write failures | `src/client/state/intent-bridge.ts`, no `res.ok` check in `requestJson` |
270
+ | Fetcher follows redirects | `src/server/webpage-node.ts:246` |
271
+ | No boards table | `CREATE TABLE` list in `src/server/canvas-db.ts` |
272
+ | Black tile known limitation | `CHANGELOG.md` 0.6.2, Known limitations |
273
+ | `ExtAppFrame.tsx` 1,591 lines | `wc -l` at `561e6ec5` |
274
+ | Live board contents (30 nodes, 29 edges, 8 pins, 0 annotations) | read-only `sqlite3` over `nodes`, `edges`, `context_pins`, `annotations`, 2026-09-23 |
275
+ | Steering volume (152 total, 89 browser, 2 in September) | read-only `sqlite3` over `ax_steering`, grouped by source and date |
276
+ | 108 snapshots, 29.6 MB database | `snapshots` count and file size, 2026-09-23 |
277
+ | Session-driven snapshots from Copilot | `snapshots.name` ("Before session · GitHub Copilot"), September rows |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.6.2",
4
- "description": "Spatial canvas workbench for coding agents \u2014 infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
3
+ "version": "0.6.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",
7
7
  "module": "./src/server/index.ts",
@@ -43,9 +43,9 @@
43
43
  "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
44
44
  "lint": "biome check .",
45
45
  "lint:fix": "biome check --write .",
46
- "test": "if [ -d tests/unit ]; then PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit; else echo 'tests/ is not shipped in the npm package \u2014 clone the repo to run the suite'; fi",
46
+ "test": "if [ -d tests/unit ]; then PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit; else echo 'tests/ is not shipped in the npm package — clone the repo to run the suite'; fi",
47
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": "if [ -d tests/client ]; then bun test --preload ./tests/client/setup.ts tests/client; else echo 'tests/ is not shipped in the npm package \u2014 clone the repo to run the suite'; fi",
48
+ "test:client": "if [ -d tests/client ]; then bun test --preload ./tests/client/setup.ts tests/client; else echo 'tests/ is not shipped in the npm package — clone the repo to run the suite'; fi",
49
49
  "test:coverage": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir coverage && bun scripts/check-coverage.ts",
50
50
  "test:web-canvas": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun run build && PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bash scripts/run-playwright.sh",
51
51
  "test:e2e": "bun run test:web-canvas",
@@ -59,11 +59,11 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@joplin/turndown-plugin-gfm": "^1.0.64",
62
- "@json-render/core": "0.19.0",
63
- "@json-render/directives": "0.19.0",
64
- "@json-render/mcp": "0.19.0",
65
- "@json-render/react": "0.19.0",
66
- "@json-render/shadcn": "0.19.0",
62
+ "@json-render/core": "0.21.0",
63
+ "@json-render/directives": "0.21.0",
64
+ "@json-render/mcp": "0.21.0",
65
+ "@json-render/react": "0.21.0",
66
+ "@json-render/shadcn": "0.21.0",
67
67
  "@modelcontextprotocol/ext-apps": "^1.3.1",
68
68
  "@modelcontextprotocol/sdk": "^1.0.0",
69
69
  "@preact/signals": "^2.0.0",
@@ -79,8 +79,8 @@
79
79
  "devDependencies": {
80
80
  "@biomejs/biome": "^2.5.2",
81
81
  "@happy-dom/global-registrator": "^20.10.6",
82
- "@json-render/devtools": "0.19.0",
83
- "@json-render/devtools-react": "0.19.0",
82
+ "@json-render/devtools": "0.21.0",
83
+ "@json-render/devtools-react": "0.21.0",
84
84
  "@playwright/test": "^1.58.2",
85
85
  "@tailwindcss/cli": "^4.1.18",
86
86
  "@testing-library/preact": "^3.2.4",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: json-render-mcp
3
- description: MCP Apps integration for json-render. Use when building MCP servers that render interactive UIs in Claude, ChatGPT, Cursor, or VS Code, or when integrating json-render with the Model Context Protocol.
3
+ description: Builds interactive json-render panels with PMX Canvas native tools or standalone MCP Apps. Use for stateful forms, repeated content, and AX actions in PMX Canvas, or MCP-hosted UIs in Claude, ChatGPT, Cursor, and VS Code.
4
4
  ---
5
5
 
6
6
  # @json-render/mcp
@@ -12,7 +12,123 @@ In `pmx-canvas`, prefer the native `canvas_render { action: "add-json-render" }`
12
12
  directly in canvas node state and render them through the local `pmx-canvas` viewer route without
13
13
  needing a separate sidecar server.
14
14
 
15
- ## Quick Start
15
+ ## PMX Canvas workflow (json-render 0.21)
16
+
17
+ 1. Read `canvas://schema` (or use `canvas_render { action: "describe-schema" }`) for
18
+ the installed component catalog. PMX includes shadcn components and its own charts;
19
+ do not assume every component or action from an upstream example is registered.
20
+ 2. Build a complete `{ root, elements, state }` spec. Validate with
21
+ `canvas_render { action: "validate", type: "json-render", spec }`.
22
+ 3. Create it with `canvas_render { action: "add-json-render", title, spec }`.
23
+ For incremental generation, use `action: "stream-json-render"`, reuse the returned
24
+ `nodeId` for subsequent patches, and set `done: true` on the final call.
25
+ 4. Inspect the panel and exercise its inputs. Schema acceptance does not prove that
26
+ an action reached the server or that a form edit was persisted.
27
+
28
+ ### State, forms, and repeated content
29
+
30
+ - Put initial values in spec-level `state`, alongside `root` and `elements`. PMX seeds
31
+ its viewer from this object; do not put `initialState` on a component.
32
+ - Use JSON Pointer paths: `value: { "$bindState": "/form/title" }` for `Input`,
33
+ `Textarea`, or `Select`; `checked: { "$bindState": "/form/urgent" }` for `Checkbox`
34
+ or `Switch`. Supply the catalog's required `label` and `name` props. `$state` reads
35
+ a value only; `$bindState` also writes edits. `props.statePath` is not input binding.
36
+ - `repeat`, `watch`, `visible`, `on`, and `slots` are element fields, not props.
37
+ `repeat: { "statePath": "/projects", "key": "id" }` repeats the container's children.
38
+ Within those children, a nested container can use
39
+ `repeat: { "statePath": { "$item": "tasks" }, "key": "id" }` to iterate the current
40
+ project's tasks. Do not use `"/tasks"` (a root path) for that nested array.
41
+ `$item` and `$index` refer to the innermost repeat; use
42
+ `checked: { "$bindItem": "done" }` to edit that task's `done` field.
43
+ - Card body content uses `children: ["bodyId"]`. Named regions use
44
+ `slots: { "header": ["headingId"], "footer": ["actionsId"] }`. Every referenced ID
45
+ must exist in `elements`; do not put body content in `slots.default`. A header slot
46
+ replaces the Card's title and description props; without it, those props work as before.
47
+ - Watchers skip mount: `watch: { "/form/title": { "action": "setState", "params":
48
+ { "statePath": "/dirty", "value": true } } }` runs after the value changes, not to
49
+ initialize `/dirty`. Seed initial and derived defaults explicitly. Avoid watchers
50
+ that write back to their own watched paths and create loops.
51
+
52
+ ### Actions and persistence boundaries
53
+
54
+ - Bind buttons with `on.press`, for example an action named `ax.work.create` or
55
+ `ax.steer`, with its AX payload in `params`. PMX forwards only registered handlers;
56
+ invented action names cannot execute arbitrary tools, shell commands, or requests.
57
+ The server still validates the payload and the node's AX capabilities.
58
+ - AX handlers are **fire-and-forget** postMessages. Their `onSuccess` callbacks mean
59
+ local dispatch completed, **not server acknowledgment**. Do not clear a draft or
60
+ show “Saved” on that basis. Confirm via reflected AX state under reserved `/ax`
61
+ (where applicable), or query the server's AX state/timeline. Do not use `/ax` for drafts.
62
+ - Built-in `validateForm` writes `{ valid, errors }` to `/formValidation` (or
63
+ `params.statePath`) and returns without running `onSuccess`. Do not chain submission
64
+ through that callback, or assume a following action in an array is validation-gated.
65
+ Use an explicit validation step and state-based gating; server-side validation is
66
+ still required for any submitted data.
67
+ - Form edits, `setState`, and `$bindItem` mutate **viewer-local state**, not the stored
68
+ spec. Iframe reloads, including spec/stream updates, reset it from the latest spec's
69
+ initial state. Finish streaming before collecting input; persist important changes
70
+ through an explicit supported server interaction. Stored canvas nodes survive
71
+ reloads, but unsent local drafts do not.
72
+
73
+ ### Example: editable task draft
74
+
75
+ Pass this object to `canvas_render`. Editing either field sets a local dirty flag;
76
+ the button requests a real AX work item without pretending the request was saved.
77
+
78
+ ```json
79
+ {
80
+ "action": "add-json-render",
81
+ "title": "Task draft",
82
+ "spec": {
83
+ "root": "card",
84
+ "state": { "form": { "title": "Review release notes", "detail": "Check the upgrade caveats." }, "dirty": false },
85
+ "elements": {
86
+ "card": {
87
+ "type": "Card",
88
+ "props": { "title": "Task draft" },
89
+ "children": ["fields"],
90
+ "slots": { "footer": ["request"] }
91
+ },
92
+ "fields": {
93
+ "type": "Stack",
94
+ "props": { "direction": "vertical", "gap": "md" },
95
+ "children": ["title", "detail"],
96
+ "watch": {
97
+ "/form/title": { "action": "setState", "params": { "statePath": "/dirty", "value": true } },
98
+ "/form/detail": { "action": "setState", "params": { "statePath": "/dirty", "value": true } }
99
+ }
100
+ },
101
+ "title": {
102
+ "type": "Input",
103
+ "props": { "label": "Title", "name": "title", "value": { "$bindState": "/form/title" } }
104
+ },
105
+ "detail": {
106
+ "type": "Textarea",
107
+ "props": { "label": "Details", "name": "detail", "value": { "$bindState": "/form/detail" } }
108
+ },
109
+ "request": {
110
+ "type": "Button",
111
+ "props": { "label": "Request task", "variant": "primary" },
112
+ "on": {
113
+ "press": {
114
+ "action": "ax.work.create",
115
+ "params": { "title": { "$state": "/form/title" }, "detail": { "$state": "/form/detail" } }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ The draft remains visible after dispatch. Verify that the work item appears in AX
125
+ state before reporting success. Repeated clicks can create duplicate work items.
126
+
127
+ ## Standalone MCP App quick start
128
+
129
+ Use this path only when building a separate MCP Apps server rather than a native
130
+ PMX panel. PMX's `canvas_render` path needs none of the server, bundler, or client
131
+ configuration below.
16
132
 
17
133
  ### Server (Node.js)
18
134
 
@@ -18,12 +18,24 @@ Humans curate agent context by pinning nodes; agents read that curation through
18
18
 
19
19
  ## Runtime prerequisites
20
20
 
21
- PMX Canvas 0.6.1 requires **Bun >=1.4.2**, including when installed through npm.
21
+ PMX Canvas 0.6.4 requires **Bun >=1.4.2**, including when installed through npm.
22
22
  Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
23
23
  use an absolute executable path if needed. Install/configure only when requested.
24
24
  For pinned installation, MCP configuration, managed services, and disposable verification,
25
25
  read [Installing PMX Canvas](references/installing-pmx-canvas.md).
26
26
 
27
+ ## Reading bundled skills over MCP
28
+
29
+ When the server advertises `io.modelcontextprotocol/skills`, supporting hosts can discover
30
+ bundled skills with `skills/list` and retrieve a manifest with `skills/get`. These are MCP
31
+ protocol methods, not canvas tools. Read `skill://pmx-canvas/SKILL.md` and relative supporting
32
+ files (for example `skill://pmx-canvas/references/installing-pmx-canvas.md`) through
33
+ `resources/read`, fetching only what is needed. Hosts without extension support can still read
34
+ ordinary resources or use installed filesystem skills; `canvas://skills` remains available.
35
+ Resource reads do not activate skills or authorize executing their scripts. The host must
36
+ preserve MCP origin and apply integrity/approval checks. Restart MCP after upgrading the package;
37
+ the served catalog is a startup snapshot. Local mirrors still use `skills sync` below.
38
+
27
39
  ## Required Operating Sequence
28
40
 
29
41
  0. **After a `pmx-canvas` upgrade, refresh this skill first.** Run
@@ -237,8 +249,18 @@ graph 760×520, mcp-app 960×600, web-artifact 960×720. A *hosted* app opened w
237
249
  `canvas_app { action: "open-mcp-app" | "diagram" }` — including the Excalidraw diagram preset — is
238
250
  the exception: request `width: 720, height: 500` for its standard landscape preset instead of
239
251
  relying on the 960×600 default for a directly created `mcp-app` node. After the app mounts, read
240
- the node back with `canvas_query { action: "layout" }` and use its persisted width and height for
241
- subsequent placement or layout; the mounted app may have changed the requested dimensions.
252
+ only its geometry, e.g. `pmx-canvas node get <id> --fields id,type,position,size`, and use the
253
+ persisted size for subsequent placement. With MCP tool execution that supports output projection,
254
+ project the layout to node id/type/position/size before returning it to context; otherwise prefer
255
+ a targeted `canvas_node { action: "get", id }`. Read the full layout only when its other state
256
+ is needed. Mounted apps may change the requested dimensions.
257
+ Authored iframe surfaces grow in height as content changes, up to 1400px. A growing ungrouped,
258
+ unpinned card moves down when needed to leave a 24px gap without moving its neighbors; grouped
259
+ cards retain their authored placement. Width stays explicit, and `strictSize` or manually resized
260
+ cards do not auto-grow. Fitting waits while a human holds the card, including in another tab;
261
+ automatic relocations are undoable. This is not a shrink-to-fit action or `view.fit` (which only changes zoom).
262
+ After content settles, inspect the rendered result and arrange the intended node IDs if more
263
+ spacing is needed; do not compensate with arbitrarily huge fixed frames.
242
264
  Since 0.4.6 the server clamps explicit creation sizes UP to per-type
243
265
  readability floors (e.g. markdown 360×180, graph/json-render/html 420×280, mcp-app 480×320) —
244
266
  a tiny probe size silently becomes the floor. `strictSize: true` is the only opt-out (a fixed
@@ -291,8 +313,15 @@ The board has three modes, all gated on one fact — whether a session is attach
291
313
  terminal: `pmx-canvas pump --consumer <your-key> --exec '<command>'`. It long-polls your
292
314
  delivery queue, runs the command once per steer (message on stdin plus the
293
315
  `PMX_STEER_MESSAGE`/`ID`/`SOURCE`/`TARGET`/`CREATED_AT` env envelope), and marks
294
- per-consumer only after the command exits 0 — a failed hand-off stays pending and the pump
295
- exits non-zero. `{message}` in the template expands to a quoted env reference (never spliced;
316
+ executed steers per-consumer after the command exits 0 — a failed hand-off stays pending
317
+ after retries and the pump exits non-zero. **Startup exception:** by default the pump
318
+ silently marks pending backlog delivered for this consumer **without executing it**.
319
+ Start the pump before accepting new tasks, or review queued instructions and deliberately
320
+ pass `--backlog=deliver` to execute them. A startup acknowledgement is not proof of work;
321
+ `--once` can remain waiting for a fresh steer after skipping history. With reviewed backlog,
322
+ `pmx-canvas pump --consumer <your-key> --exec '<command>' --backlog=deliver --once`
323
+ executes one queued steer. This does not automatically wake an existing desktop task.
324
+ `{message}` in the template expands to a quoted env reference (never spliced;
296
325
  refused on Windows — read stdin there). `--parent <key>` rolls you up under an orchestrator's
297
326
  chip; `--once` for scripts. See `pmx-canvas pump --help`.
298
327
 
@@ -467,20 +496,21 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
467
496
  iframe's per-mount nonce, so AX buttons only work inside the in-canvas node (0.4.4 Codex note).
468
497
  Do not tell a user a standalone tab's controls will steer the agent.
469
498
  - A hosted ext-app (Excalidraw) node in a **WebKit** host panel (e.g. the GitHub Copilot app's
470
- embedded WKWebView) historically could render as a black tile — a host compositor paint race
471
- on the nested iframe, **not** a broken node (the session is healthy, `sessionStatus` is
472
- `ready`, and it renders fine in Chrome/Codex). Since 0.4.6 the canvas runs a **paint oracle +
499
+ embedded WKWebView) can remain blank even when its API session is healthy and `sessionStatus`
500
+ is `ready`. This host-specific rendering issue remains open; successful Chrome/Codex rendering
501
+ does not establish native-host health. Since 0.4.6 the canvas runs a **paint oracle +
473
502
  recovery ladder** under WebKit: present-at-load ext-apps mount strictly one at a time (the
474
503
  cold burst was the trigger), each frame answers a double-rAF **paint probe** after settle, and
475
504
  on silence the ladder escalates — soft-expand cycle (the automatic analogue of the proven
476
505
  enlarge+close) → serialized remount → an explicit "App surface failed to paint / Retry"
477
- affordance. The connecting overlay stays up until paint is confirmed, so a black layer is
478
- never presented as ready. The recovery trail
506
+ affordance. Native Copilot/WebKit surfaces can still remain blank despite a successful
507
+ probe, including when visibility is reported as visible. The recovery trail
479
508
  (`GET /api/canvas/debug/ext-app-recovery` / `window.__PMX_EXTAPP_LOG`) now records
480
509
  `mount-slot`, `paint-ok`, `paint-fail`, `soft-expand-cycle`, `recovery-exhausted`, and
481
- `assume-visible-rearm` — when diagnosing, trust `paint-ok`/`paint-fail`, and never assert
482
- health from `settled` alone. Exception: in a host that reports the document hidden (the
483
- GitHub Copilot panel does so continuously), `paint-ok` is recorded as
510
+ `assume-visible-rearm`. Treat `paint-ok`/`paint-fail` as diagnostic evidence, not compositor
511
+ proof. Require a rendered screenshot in the affected host before declaring its surface
512
+ healthy; a Chromium pass does not close a native Copilot/WebKit failure. In a host that
513
+ reports the document hidden, `paint-ok` is recorded as
484
514
  `paint-ok (unverified: host hidden)` — the app answered the paint probe, but nothing is
485
515
  proven composited, so do not treat it as a verified paint. If a
486
516
  tile still shows the Retry affordance, click Retry (fresh recovery budget) or expand-then-close;
@@ -486,6 +486,52 @@
486
486
  "type": "output_check"
487
487
  }
488
488
  ]
489
+ },
490
+ {
491
+ "id": 20,
492
+ "name": "pump-startup-skip-history",
493
+ "prompt": "In an isolated workspace I queued an instruction for testbot, then started pmx-canvas pump --consumer testbot --exec cat --once. The instruction disappeared from pending, but cat printed nothing and the pump says parked. Did it perform the work? Explain what to do for future tasks without replaying old instructions.",
494
+ "expected_output": "Explains that default startup acknowledges pending history for this consumer without executing it. Does not report the work as performed or infer execution from delivery marks. Explains that --once waits for one successful command execution and can remain parked after skipping history. Keeps the safety default and starts the pump before accepting new tasks; a fresh addressed test message should reach cat and allow --once to exit. Does not promise automatic wakeup of an existing desktop task.",
495
+ "assertions": [
496
+ {
497
+ "name": "acknowledgement-is-not-execution",
498
+ "description": "Identifies default startup skipping and marking backlog, and explicitly says the old instruction was not executed",
499
+ "type": "output_check"
500
+ },
501
+ {
502
+ "name": "once-waits-for-execution",
503
+ "description": "Explains why --once remains parked after skipping history and proposes a fresh addressed message after pump startup",
504
+ "type": "output_check"
505
+ },
506
+ {
507
+ "name": "preserves-safe-default",
508
+ "description": "Does not replay old instructions, change runtime defaults, or claim automatic desktop-task wakeup",
509
+ "type": "output_check"
510
+ }
511
+ ]
512
+ },
513
+ {
514
+ "id": 21,
515
+ "name": "pump-startup-deliver-reviewed-backlog",
516
+ "prompt": "In a disposable POSIX workspace, one harmless message is already queued for testbot. I reviewed it and want the pump to print it using cat, acknowledge it after success, and exit. Give the command and explain how you would verify it, including a failing-command control.",
517
+ "expected_output": "Uses pmx-canvas pump --consumer testbot --exec cat --backlog=deliver --once against the verified isolated target. Explains that omitting --backlog=deliver would skip and acknowledge the old message without executing cat. Verifies the queued message appears in command output, the pump exits successfully, and it is no longer pending for testbot. Uses a separate disposable message/consumer for a command exiting nonzero and confirms it remains pending after retries. Does not treat queue acknowledgement alone as proof of execution.",
518
+ "assertions": [
519
+ {
520
+ "name": "explicit-reviewed-backlog-mode",
521
+ "description": "Chooses --backlog=deliver with --once, the correct consumer and harmless cat command, rather than silently relying on default startup",
522
+ "type": "output_check"
523
+ },
524
+ {
525
+ "name": "verifies-output-and-delivery",
526
+ "description": "Requires actual command output and successful exit as well as a per-consumer pending check; contrasts the default skip behavior",
527
+ "type": "output_check"
528
+ },
529
+ {
530
+ "name": "failure-remains-pending",
531
+ "description": "Keeps the failing-command control isolated and expects nonzero exit after retries with the instruction still pending",
532
+ "type": "output_check"
533
+ }
534
+ ]
489
535
  }
490
536
  ]
491
537
  }