pmx-canvas 0.5.0 โ 0.6.1
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 +45 -0
- package/Readme.md +9 -6
- package/dist/canvas/global.css +83 -6
- package/dist/canvas/index.js +177 -116
- package/dist/canvas/mermaid-entry.js +1303 -1303
- package/dist/json-render/index.js +101 -102
- package/dist/types/client/canvas/canvas-area.d.ts +14 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -0
- package/dist/types/server/canvas-operations.d.ts +10 -0
- package/docs/RELEASE.md +14 -0
- package/docs/adr-001-bun-only-runtime.md +1 -1
- package/docs/bun-webview-integration.md +2 -2
- package/docs/product-context-vision-2026-09.md +207 -0
- package/docs/product-review-2026-09.md +174 -0
- package/docs/product-vision-2026-09.md +200 -0
- package/docs/screenshot.png +0 -0
- package/package.json +5 -5
- package/skills/pmx-canvas/SKILL.md +61 -30
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +126 -18
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +12 -1
- package/src/cli/commands/pump.ts +13 -2
- package/src/cli/index.ts +1 -0
- package/src/client/canvas/CanvasViewport.tsx +11 -0
- package/src/client/canvas/TopBar.tsx +85 -45
- package/src/client/canvas/canvas-area.ts +33 -0
- package/src/client/canvas/use-pan-zoom.ts +7 -0
- package/src/client/nodes/ExtAppFrame.tsx +152 -23
- package/src/client/state/canvas-store.ts +29 -11
- package/src/client/theme/global.css +83 -6
- package/src/server/canvas-operations.ts +41 -6
- package/src/server/index.ts +4 -2
- package/src/server/operations/mcp.ts +45 -31
- package/src/server/operations/ops/ax-presence.ts +3 -0
- package/src/server/operations/ops/nodes.ts +12 -0
- package/src/server/operations/ops/snapshots.ts +6 -19
- package/src/server/scope-fence.ts +1 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Product Vision โ September 2026
|
|
2
|
+
|
|
3
|
+
**Status:** Proposed
|
|
4
|
+
**Date:** 2026-09-05
|
|
5
|
+
**Scope:** Where `pmx-canvas` should go, what must be fixed now, what to add, what to delete, and what is architecturally wrong. Written against `main` `e17776f6` (clean tree).
|
|
6
|
+
**Method:** My own position, drafted first, then stress-tested by a 54-agent panel: four fact-finders, six independent visions from different angles (context engineering, systems, product strategy, rendering, developer experience, minimalism), a merge into 14 moves, three adversarial refuters per move (evidence, feasibility, value), and a completeness critic. Where the panel refuted me, this document says so. Companion: [`product-review-2026-09.md`](product-review-2026-09.md) (the audit).
|
|
7
|
+
|
|
8
|
+
## The one-line vision
|
|
9
|
+
|
|
10
|
+
**The board is the agent's working memory, and the human's attention is the compiler.** Explicit steering directs agents; annotations, pins, connections, and grouping inform relevance, while spatial layout and human camera attention can contribute weaker cues. The server compiles one budgeted brief for every agent turn. Everything the agent has to say to the human is a card on the board, never a row in a side table. The board owns its own store and its own history, and reaches remote agents through its own authenticated network mode. One journal records every write by either side, so time on the board can be scrubbed like a video. In the long run the board is hosted, and a team and its agents share it as one working memory.
|
|
11
|
+
|
|
12
|
+
**Purpose clarification, 2026-09-06:** pmx-canvas is a visual workspace for **planning, discovery, analysis, and coordination with agents**. This is its intended purpose, not merely an initial audience experiment. Its promise is to let people shape the work spatially, carry useful understanding into the next task, and see known changes and uncertainty in the context they use. The [companion vision](product-context-vision-2026-09.md#position) develops these promises; personal and shared knowledge support the working surface rather than replace it. Attention does not grant approval or sharing permission.
|
|
13
|
+
|
|
14
|
+
## What the real boards show
|
|
15
|
+
|
|
16
|
+
Two corrections from the maintainer shaped this section. First, boards inside the `pmx-canvas` repository are test boards by design. Second, the real boards are mixed in with test boards on the board the globally installed MCP server writes to. That board lives in the directory the host spawns the MCP server from, plus a worktree copy. Its live state today is a fixture set (`F Graph`, `Excal A` to `E`), but its snapshot history holds every board that came before, and that is where the real work is. Read-only, titles only, snapshot ids decoded to dates:
|
|
17
|
+
|
|
18
|
+
| Date | Real board (from snapshot history) | Content |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| 2026-04-15 | A quarterly OKR and developer-metrics board | 8 objective cards, 8 metric charts, 1 dashboard app |
|
|
21
|
+
| 2026-05-11 | A workshop deck on working with agents | 1 HTML deck, 15 slides |
|
|
22
|
+
| 2026-05-27 | Vibe cluster feedback, agent dev environment | 2 HTML surfaces |
|
|
23
|
+
| 2026-06-22 | A product discovery board | 17 markdown cards in 4 groups, 1 double-diamond HTML |
|
|
24
|
+
| 2026-06-25 to 07-08 | Vibe coding cluster model, agent kanban | 1 Excalidraw app, 2 HTML surfaces |
|
|
25
|
+
| 2026-07-07 | Two training tracks for a workplace rollout | 2 markdown outlines, 2 HTML slide decks, 1 file |
|
|
26
|
+
| 2026-08-03 | A metrics read on delivery cycle times | 1 markdown, 5 charts, 1 file |
|
|
27
|
+
| 2026-08-07 | A department-wide planning overview | about 30 markdown cards, a status card, a six-step flow |
|
|
28
|
+
| 2026-08-10 to 08-28 | Work board | 1 json-render surface, the longest-lived real node |
|
|
29
|
+
|
|
30
|
+
Three things follow, and they change the vision more than anything the panel produced.
|
|
31
|
+
|
|
32
|
+
1. **The product is used for real, by a PM, for thinking and presenting with agents:** OKR boards, metric charts, discovery boards, org overviews, slide decks. Nine sessions over four and a half months, almost all agent-written from the maintainer's prompts.
|
|
33
|
+
2. **Every real board was destroyed by the next session.** One board per workspace means the planning overview, the discovery board and the OKR board exist today only as snapshot rows under test fixtures. The product's own persistence model is what makes real use invisible. It is the first thing to fix, and nobody on the panel saw it because nobody looked in the snapshot tables.
|
|
34
|
+
3. **The rich surfaces are real-use surfaces.** Thirteen real chart nodes, five real HTML decks and surfaces, two real Excalidraw apps, one long-lived json-render board. My earlier cut of `graph` and json-render was wrong on the evidence, and the maintainer's broader point stands: his own usage is not evidence that any type is unused. No node type is cut anywhere in this document; move 5 changes only how they render.
|
|
35
|
+
|
|
36
|
+
What still holds from the test boards: steering is the most exercised human verb (84 to 89 browser-sent steers per board), pins are rare (1 or 2 live), and snapshots accumulate without bound (273 and 337 on boards of 14 and 16 live nodes, 24 MB each), which is now doubly telling, since snapshots are the only place the real work survived.
|
|
37
|
+
|
|
38
|
+
## Part 1: The foundation
|
|
39
|
+
|
|
40
|
+
Move 0 comes first, so real boards stop being overwritten. Then three changes that everything in Part 2 stands on. None of them is speculative; each is worth shipping on its own.
|
|
41
|
+
|
|
42
|
+
1. **One read.** `canvas://context?budget=N` is the only thing an agent reads per turn: pinned nodes first, then what changed since this consumer's last read (a stable seq cursor), then human-authored nodes, then open asks and undelivered steers for this consumer. Nothing done, delivered or system-generated ever enters it. Everything else stays reachable by explicit pull. It folds 10 of 14 resources into the one read and turns spatial analysis (clusters, neighborhoods, reading order) into an input to the ranker rather than a separate resource.
|
|
43
|
+
2. **The board fills itself.** Nobody on the panel proposed this and it is the cheapest radical move available: stop requiring the agent to remember to call `canvas_node`. A Claude Code hook, the Copilot extension and the Codex heartbeat project the session onto the board, meaning files touched, diffs, test output and the plan, into a session lane the human then curates from abundance. Today a node exists only when an agent decides to write one.
|
|
44
|
+
3. **Author on every node.** The `nodes` table has no writer column, and provenance lives only in an in-memory ring that dies on restart. Add `author`, human or agent id. The compiler ranks human-written above agent-written, the board can show who wrote what, and hosted multiplayer needs it later anyway.
|
|
45
|
+
|
|
46
|
+
## Part 2: The shape I believe in
|
|
47
|
+
|
|
48
|
+
Twelve moves, numbered from the one the evidence demands first. Move 3 was rewritten and move 6 split after the maintainer's decisions on 2026-09-06. Each carries the panel's strongest objection and my answer, because several of them changed the move.
|
|
49
|
+
|
|
50
|
+
### 0. Many boards per workspace (S)
|
|
51
|
+
|
|
52
|
+
The move the evidence demands before any other. A workspace holds many named boards, switchable from the top bar and addressable from every transport (`board` on the MCP session, `--board` on the CLI, one journal per board once move 4 lands). A new session starts on a new or chosen board; test runs get their own boards; nothing is overwritten. Recovery for the maintainer today: the nine real boards above can be restored from snapshot history into named boards in an afternoon. Deletes nothing. Objection: none, the panel did not see it.
|
|
53
|
+
|
|
54
|
+
### 1. One read, one brief (M)
|
|
55
|
+
|
|
56
|
+
Above. Objection: ranking by "the human's camera" rests on a signal the product does not have; the server holds one global viewport that agents also write via `view.fit` and `view.focus`, so a camera ranker would rank the agent's own last focus. Accepted. The camera rides the per-tab human-presence heartbeat, viewport writes get an actor like every other write, and the camera is a weak tiebreak, never the primary axis. Primary axes: pins, delta since last read, author, recency.
|
|
57
|
+
|
|
58
|
+
### 2. The session projects itself onto the board (M)
|
|
59
|
+
|
|
60
|
+
Above. Objection: none raised; the panel missed it. Risk: noise. Mitigation: the projection lands in its own lane at low rank; the compiler's tiers keep it out of the brief unless pinned or recent.
|
|
61
|
+
|
|
62
|
+
### 3. The agent's output moves onto the board (M)
|
|
63
|
+
|
|
64
|
+
**Decided with the maintainer, 2026-09-06: the fleet features stay.** They are new, deliberate capability, and the same rule that protects the node types protects them. Scope fences, consumer keys, the `pump`, parent rollups, census chips, addressed versus broadcast steering and per-consumer delivery are all kept as they are. What follows is a relocation, not a cut.
|
|
65
|
+
|
|
66
|
+
The problem this move solves is one surface too many. When the agent opens a work item, raises a gate, asks a question or files evidence, it lands in a side table that the canvas cannot show. The human reads it in a panel while the board, the surface built for exactly this, stays blank about it. Two side effects follow: deleting a node forces the server to re-anchor orphaned items, and the panel and the board have different lifetimes, since canvas-bound state rides snapshots while the timeline does not.
|
|
67
|
+
|
|
68
|
+
So the agent's output becomes nodes. A work item is a `status` node carrying the work state. Approval gates, elicitations and mode requests become one new `ask` node with a typed answer, human-only (move 7). Evidence is a node or a file node. Every gesture that already exists then applies to agent output: drag it beside the file it concerns, group it, pin it, connect it to the diff that raised it. Nothing to re-anchor, because the item is the node. One lifetime, because there is one surface.
|
|
69
|
+
|
|
70
|
+
The evidence is the maintainer's own board. When three agents reviewed the product for him, they wrote markdown cards and patched each other's sections rather than filing work items. The card won because it was visible and spatial. This move makes the winner the design.
|
|
71
|
+
|
|
72
|
+
Deletes: the duplicate storage only. The three timeline tables fold into the journal in move 4, which was already happening.
|
|
73
|
+
|
|
74
|
+
**Three primitives I would still question, as open questions rather than proposals.** The `policy` singleton, `host-capability`, and the command registry (`pmx.plan`, `pmx.review`, and the rest) each carry a schema, a route, an MCP action and a doc section, and I cannot find a use for them on any board or in any field report. I no longer claim the evidence to delete them, and the maintainer's rule is that my not finding a use is not proof. They are worth a deliberate decision, not a default one. Separately, ghost intents: keep the capability, but change the bundled skill so signalling one is opt-in rather than required before every mutation. That is a default, not a feature.
|
|
75
|
+
|
|
76
|
+
Objection (value lens): steer is the one verb the human demonstrably uses, so any move that touched it would remove the thing that works. Accepted, and now moot: steering, delivery and the fleet around it are untouched. Objection (feasibility): the presence cursors are the presence registry, so they cannot be separated. Accepted, and also moot: presence is untouched.
|
|
77
|
+
|
|
78
|
+
### 4. One journal is the truth; the app owns its history and its store (L, then XL)
|
|
79
|
+
|
|
80
|
+
Beside the SQLite store the product already owns, one append-only journal (seq, actor, op, inverse patch) written by the single write path. Snapshots become named bookmarks on the journal; the History drawer becomes a time scrubber over it; SSE becomes journal tailing with `Last-Event-ID` (today there is no cursor, and every reconnect replays the full board); `watch` becomes "entries since seq"; undo survives restart. No external system is involved: the app is its own version control. The maintainer's databases make the case: 4.2 MB of which 3.1 MB is 196 full-copy snapshots of a 25-node board, and 273 to 337 snapshots on the boards where the real work lived.
|
|
81
|
+
|
|
82
|
+
A folder export (one markdown file per note plus a board file in the product's own schema) is worth having so a human or an agent can read a board without the server, but it is an export, not the store. With Obsidian and git both out of scope, the folder loses two of its three reasons to be canonical, and a canonical file that any process can write would make every trust guarantee advisory. The store stays SQLite; the journal is the truth for who did what.
|
|
83
|
+
|
|
84
|
+
Objection (feasibility, refuted as written): "a log written by `executeOperation` and nothing else" does not describe this codebase (487 `canvasState.` references in 40 files; the SDK makes 87 direct calls). Accepted: the XL half (state as a fold of the journal) comes after move 6 collapses the SDK onto the registry.
|
|
85
|
+
|
|
86
|
+
### 5. Keep every node type; unify how they render (M)
|
|
87
|
+
|
|
88
|
+
The maintainer's rule, and it is the right one: his own usage is not evidence that a node type is unused, and a node that renders any MCP app belongs in the product. So no node type is removed, and the generic `mcp-app` node stays generic. What changes is the machinery under the iframe-backed kinds (`html`, `mcp-app`, `json-render`, `graph`, `webpage`, `mermaid`), which today is spread over six components with three copies of the AX bridge and one 1,585-line frame component whose recovery ladder produced nine rounds of black tiles.
|
|
89
|
+
|
|
90
|
+
One frame host for every iframe-backed kind: mounted lazily when the node is in or near the viewport, counted against a live-frame budget, given one paint deadline, and shown with one visible fallback and a retry when it misses, instead of an escalating oracle. Mermaid renders inline and lazily, with no per-node 3.5 MB document. The React chart bundle stays behind `graph` and `json-render` as types, loads only when such a node is on screen, and whether it is later replaced by a lighter renderer is a cost decision, not a usage one. The 21 HTML primitives, the web-artifact builder and every other surface stay as capabilities. One observation, not a cut: the web-artifact builder is the only feature that fetches a toolchain from the network at build time, which sits oddly beside the no-externals rule and deserves a deliberate look.
|
|
91
|
+
|
|
92
|
+
Deletes: the `ExtAppFrame` recovery machinery, two of three AX-bridge copies, five per-type iframe implementations. Capabilities removed: none. Every deletion the earlier version of this move argued from "zero real uses" was withdrawn once the real boards surfaced; what remains is argued from cost and incident count only.
|
|
93
|
+
|
|
94
|
+
### 6a. Make the tool surface cheap (S) โ ready to ship
|
|
95
|
+
|
|
96
|
+
Measured on this tree, `tools/list` is 49,222 bytes across 22 tools, about 12,300 tokens that every agent pays on every session before it does anything. Two tools account for a fifth of it: one advertises 51 optional properties, another 49, because a composite flattens four actions' fields into one schema. Per-action requirements exist only in prose, so a call missing a required field passes validation and fails in the handler.
|
|
97
|
+
|
|
98
|
+
Nothing structural is needed to fix most of that. Ship per-action schemas where hosts support them and a `canvas_help { op }` that returns one operation's schema on demand, so an agent loads what it uses instead of the whole surface. Add `session.start`, returning workspace, brief and inbox in one round trip, so a useful first session is about five calls. Fix the two live descriptions that are wrong today, and add the test that an action summary always equals its own action list. Put a token budget on every agent read, which is Part 1's one read.
|
|
99
|
+
|
|
100
|
+
This is the half the maintainer has already endorsed in direction. It removes no capability, renames nothing, and is worth roughly 10,000 tokens per session per agent.
|
|
101
|
+
|
|
102
|
+
### 6b. Generate every surface from the registry (L, two releases) โ pending the maintainer's decision
|
|
103
|
+
|
|
104
|
+
**Status: undecided as of 2026-09-06.** The maintainer likes the simplification and the token saving, and wants more time on the restructuring. Nothing below should start before that decision.
|
|
105
|
+
|
|
106
|
+
The proposal: `listOperations()` becomes the single published vocabulary. Five MCP tools (`canvas_read`, `canvas_write` taking a list so batch is just a longer list, `canvas_ask`, `canvas_inbox`, `canvas_help`) replace the 22. The CLI becomes one generic dispatcher over operation names plus eight human commands, and the SDK becomes a typed facade over the same invoker. The API document is rendered from the registry, replacing 23,000 words of hand-written prose that has drifted at least seven times.
|
|
107
|
+
|
|
108
|
+
The case for it: four naming conventions for one operation (`set-focus`, `ax focus`, a path, `setAxFocus`) mean an agent cannot transfer knowledge between transports, and about 14 operations exist in three surfaces but not in MCP with no error explaining the gap.
|
|
109
|
+
|
|
110
|
+
The case against, which is why it is worth thinking about: 22 named tools are self-documenting in a way that five generic ones are not, a host that renders tool names to the user shows something meaningful today, and a single write tool keyed on operation names needs a discriminated union to catch wrong-field arguments, which not every host supports yet. Generated CLI help is also usually worse than hand-written help.
|
|
111
|
+
|
|
112
|
+
If the answer is no, 6a still stands on its own, and the one piece worth taking regardless is routing the SDK through the registry, since that is a trust fix rather than a surface change: the SDK makes 87 direct state calls today and 4 registry calls, so an SDK write skips the human edit lock, the scope fence and the activity feed.
|
|
113
|
+
|
|
114
|
+
Objection (feasibility, refuted as bundled): the previous registry campaign took four pull requests plus a month of follow-up slices, so five refactors in six weeks will not land. Accepted: two releases, SDK first. Objection (evidence): "41 operations unreachable from MCP" is a field-count artifact; the real gap is about 14. Accepted.
|
|
115
|
+
|
|
116
|
+
### 7. Trust that is not a label (S, then M)
|
|
117
|
+
|
|
118
|
+
The workbench marker is a plain header any agent with `curl` can set, so "human-only gate resolution keyed on the marker" is still theater. The server mints a secret at boot, serves it only in the workbench HTML, and human-only ops require it. Then: `ask` answers are human-only; file nodes are confined by default to the workspace plus explicit allow-roots (persistence hygiene, since every host already gives the agent `cat`); the webpage fetcher refuses loopback, private, and link-local addresses on every redirect hop; the client bridge checks `res.ok` and shows the toast on 400 and 500. One reservation I am not settled on: the real boards carry file nodes for documents (weekly prioritization reports) that may live outside any repository, and default confinement would have refused them; the default should probably be a per-board allow-root list that includes the folders a PM board actually reads, not the workspace alone.
|
|
119
|
+
|
|
120
|
+
The same mechanism gives the product its own remote story, without relying on anything external. A loopback-only server means Claude Code cloud, Codex cloud and the Copilot coding agent, the agents that run longest unattended, cannot reach the board at all. With per-writer tokens minted by the server (human tokens served only to the workbench, agent tokens issued per session), the server can bind beyond loopback deliberately (`--listen` plus a token), and a remote agent is just another authenticated writer. No relay, no sync service, no version-control detour.
|
|
121
|
+
|
|
122
|
+
Correction to my own review: a human's write cannot hit the 403 fence or the 409 lock, both sit behind `if (!meta.fromWorkbench)`; the silent-failure defect is real but smaller than I wrote.
|
|
123
|
+
|
|
124
|
+
### 8. Ship a binary, one-screen README, net-negative releases (S)
|
|
125
|
+
|
|
126
|
+
`bun build --compile` works today: 0.5 s, 66 MB, `bun:sqlite` unmodified, verified by the panel. Release binaries per platform and an npm package that is an installer. The 227 tracked `dist/` files stop being committed. The README becomes the thesis, one scenario showing both directions, Quick start, `smoke`, and a two-minute GIF. For 60 days a release may ship only if it deletes more lines than it adds, or is one of the trust fixes, or is move 0. Move 3 and move 6a are exempt too, since both add before they remove.
|
|
127
|
+
|
|
128
|
+
### 9. Render for the pane you live in (M)
|
|
129
|
+
|
|
130
|
+
The home is a 500 to 600 px panel inside Claude Code, Codex, and Copilot. Viewport culling, `content-visibility`, lazy iframe mount, and a live-frame budget. Objection: the thumbnail-eviction pipeline I wanted does not exist (no clip support, capture is macOS-only). Accepted: no thumbnails; culling, lazy mount and the frame budget are move 5's frame host doing its job.
|
|
131
|
+
|
|
132
|
+
### 10. CLAUDE.md and AGENTS.md become one contract, the incidents become checks (S)
|
|
133
|
+
|
|
134
|
+
Both files stay, because different agents read different files (Claude Code reads CLAUDE.md; Codex, Copilot and Amp read AGENTS.md), and they stay byte-identical under the CI gate that already enforces it. Each becomes the same 120-line contract; the AX design reference moves to `docs/ax.md` and is linked from both; each "this shipped N times" paragraph becomes a test or a Biome rule where an oracle exists. Objection: the nine-time black-tile class has no executable oracle (Playwright is Chromium-only). True, and move 5 replaces the oracle ladder with a paint deadline and a visible fallback, which a test can assert; a WebKit Playwright project would cover the rest.
|
|
135
|
+
|
|
136
|
+
### The payoff nobody asked for
|
|
137
|
+
|
|
138
|
+
With one journal, a **time scrubber** replaces snapshots, the History drawer, and diff: drag a slider and watch the agent's work unfold on the board. With one brief, an **attention heat** on the board shows the human exactly what the agent will read next, so looking and pinning become visible tuning. Those two are the demo. They are also the two-way surface at its most literal: you see what the agent sees, and you see what it did.
|
|
139
|
+
|
|
140
|
+
## Part 3: Long term, hosted multiplayer
|
|
141
|
+
|
|
142
|
+
**Discussion extension, 2026-09-06:** [Boards, personal wiki, and workspace context](product-context-vision-2026-09.md) reviews this plan and proposes app-owned personal and shared knowledge built from board evidence. It includes oracle feedback, publication and permission boundaries, corrections to the trust/provenance assumptions below, and a small validation experiment. It is a proposal, not a replacement for the maintainer's settled decisions or authorization to start hosted work.
|
|
143
|
+
|
|
144
|
+
The maintainer's long-term direction, and the natural end of the two-way surface: the board is hosted, people log in, and several humans and their agents work on the same canvas. My position is that this is the right destination and that almost everything in Part 2 is its prerequisite, which is the strongest argument for Part 2.
|
|
145
|
+
|
|
146
|
+
**What Part 2 already buys.** The journal (move 4) is the sync model: an ordered log with sequence numbers, per-consumer cursors and inverse patches is exactly what multiple clients need, and node-granular edits with compare-and-set on the node version resolve concurrent human edits without operational transforms or CRDTs. Per-writer tokens (move 7) become per-user credentials; the listen mode becomes the hosted server; author on every node (Part 1) becomes attribution; presence is already multi-writer with identity colors; many boards (move 0) become the team's board list; the ask node with a human-only answer becomes an ask addressed to a person or a role.
|
|
147
|
+
|
|
148
|
+
**What is genuinely new.** Identity and authorization: accounts, per-board roles (read, write, answer asks), and an audit trail, which the journal already is. Hosting: the same Bun binary run with `--listen` behind TLS, one server per team first (single-tenant, one store per team), multi-tenant only if a second team ever needs the same server. Uploads instead of file nodes: a hosted server has no workspace on disk, so a file node becomes an uploaded document with its watcher replaced by re-upload or a connector. Per-person attention on a shared board: the brief an agent reads is compiled from the attention of the people in that agent's session, not from everyone's, so two people can work the same board with different agents without steering each other's context.
|
|
149
|
+
|
|
150
|
+
**The vision it enables.** A team's working memory: a quarterly OKR board with the PM, the team leads and each of their agents on it, every card attributed, every ask answered by the person it was addressed to, and the time scrubber showing how the plan changed and who changed it. That is a product nobody has, and it is the same product as Part 2 with a login screen.
|
|
151
|
+
|
|
152
|
+
**The constraints that hold.** No externals: identity is the app's own (passkeys and email, with federation optional later), and history and sync are the journal, not a third-party service. Trust stops being optional: everything in move 7 is mandatory before a single port is opened to the internet, and MCP-app hosting of third-party apps needs a per-board allowlist once viewers are not all the owner. Scope: this is 2.0 work for a solo maintainer. It should not start before 1.0, and it should not shape 0.6 to 0.8 beyond the choices above that cost nothing now.
|
|
153
|
+
|
|
154
|
+
## What is architecturally wrong today
|
|
155
|
+
|
|
156
|
+
1. **One write lands in four logs and none is durable.** Mutation history (200 closures, in-memory), the SSE ring (500, in-memory), presence activity (50), and three AX tables with independent retention. Undo is empty after every restart. SSE has an `id:` field nobody reads.
|
|
157
|
+
2. **The human marker is a label, not a gate.** Actor attribution, the fence, and the lock all key on an unauthenticated header.
|
|
158
|
+
3. **Three write paths with three policies.** The registry (fence, lock, actor), the SDK (87 direct state calls, none of those), and 21 hand-written routes in a 3,471-line server file.
|
|
159
|
+
4. **Node type is not the renderer.** Five rendering tiers, three node types funnelled through one iframe component, two UI frameworks, two CSS toolchains.
|
|
160
|
+
5. **Single-slot listeners and import-order wiring.** `onMutation` and the work-items listener hold one callback each; module-level timers outlive the server.
|
|
161
|
+
6. **The knowledge that keeps the product working lives in prose.** Rules 3, 7, and 9 in CLAUDE.md and its byte-identical AGENTS.md twin have each grown a paragraph per recurrence.
|
|
162
|
+
|
|
163
|
+
## What must not be touched
|
|
164
|
+
|
|
165
|
+
The operation registry and its dispatcher. State in the server, browser as renderer. The human/agent distinction as a first-class concept (to be made real, not removed). Context pins and `canvas://pinned-context`. Six of six iframes sandboxed without `allow-same-origin`. `smoke`, the e2e gate, the changelog discipline. The velocity: 110 commits, +64,444 and โ13,026 lines in the last six weeks, which is what makes a deletion-heavy plan credible.
|
|
166
|
+
|
|
167
|
+
## Sequence
|
|
168
|
+
|
|
169
|
+
| Release | Weeks | Content |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| 0.6 | 2โ3 | Many boards (move 0), trust (move 7), the cheap tool surface (6a), the frame-host consolidation of move 5, the binary (8). |
|
|
172
|
+
| 0.7 | 2 | The foundation (Part 1): one read, session projection, author on every node. |
|
|
173
|
+
| 0.8 | 4โ6 | The journal and bookmarks (4), the agent's output onto the board (3), the SDK through the registry. |
|
|
174
|
+
| 1.0 | | Generated surfaces (6b, if decided), state as a fold of the journal, time scrubber, attention heat, culling and frame budget (9). |
|
|
175
|
+
| 2.0 | | Hosted multiplayer (Part 3): accounts and per-board roles, listen mode behind TLS, uploads instead of file nodes, per-person attention on a shared board. |
|
|
176
|
+
|
|
177
|
+
## Where I disagree with the panel
|
|
178
|
+
|
|
179
|
+
- The minimalist and the strategist delete AX. Wrong target, and after the maintainer's decision the target is narrower still: the agent's output moves onto the board, and every AX mechanism including the whole fleet layer stays.
|
|
180
|
+
- The minimalist deletes the registry. Refuted three to zero: the browser alone calls 55 API paths.
|
|
181
|
+
- The rendering architect deletes the tool rail and top bar. Design for 600 px first; the rail is the good part of the chrome.
|
|
182
|
+
- Every vision, and my own first draft, proposed cutting node types on the strength of the repo's test board. The real boards refute the chart cut outright, and the maintainer's rule closes the rest: usage on one machine is not evidence of non-use. No node type is cut; consolidation is confined to how nodes render.
|
|
183
|
+
- Four of six visions leaned on Obsidian, and five on git, as the format, the snapshot system, or the remote transport. The maintainer's rule is that this is its own app with no reliance on externals, and I agree: the app owns its history (the journal), its store (SQLite), and its remote reach (tokens and a listen mode). What survives of the folder idea is an export.
|
|
184
|
+
- Several visions invented constants (700 chars, six frames, 14k lines) with the same confidence they mocked the repo's. So did I. Every such number in this document is a starting value to tune in use, not a claim.
|
|
185
|
+
|
|
186
|
+
## Evidence
|
|
187
|
+
|
|
188
|
+
| Claim | Source |
|
|
189
|
+
|---|---|
|
|
190
|
+
| Nine real boards AprโAug 2026 in the snapshot history of the board the global MCP install writes to (13 chart, 5+ HTML, 2 Excalidraw, 1 json-render, about 60 markdown real nodes); live state is fixtures; 84โ89 browser steers; 273โ337 snapshots on 14โ16 live nodes | read-only `sqlite3` over `snapshot_nodes`, titles only, snapshot ids decoded to dates, 2026-09-05 |
|
|
191
|
+
| 3.1 MB of 4.2 MB is snapshots; 196 snapshots, 2,206 rows | `dbstat`, `snapshot_meta`, `snapshot_nodes` counts, 2026-09-05 |
|
|
192
|
+
| Rendering tier 11,298 lines, 10 of 17 deps, 5.6 MB dist | fact-finder `rendering`, `wc -l`, `package.json` |
|
|
193
|
+
| SDK bypass 87 direct calls vs 4 registry calls | `grep -c "canvasState\." src/server/index.ts` |
|
|
194
|
+
| File nodes unconfined at create | `src/server/canvas-operations.ts:760`; confinement exists only in the byte-serving route |
|
|
195
|
+
| Webpage fetcher follows redirects, no host filter | `src/server/webpage-node.ts:216,245-246` |
|
|
196
|
+
| Marker is a plain header | `src/server/operations/http.ts:91`; no auth in `server.ts` |
|
|
197
|
+
| SSE has no resume cursor | fact-finder `logs`; `Last-Event-ID` unused |
|
|
198
|
+
| `bun build --compile` works | panel feasibility refuter: 0.5 s, 66 MB, state route 200 |
|
|
199
|
+
| Velocity | `git log --since=2026-07-24 --shortstat`: 110 commits, +64,444 โ13,026 |
|
|
200
|
+
| Loopback-only server | `server.ts:89` |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
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.6.1",
|
|
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",
|
|
@@ -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
|
|
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",
|
|
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
|
|
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",
|
|
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",
|
|
@@ -117,6 +117,6 @@
|
|
|
117
117
|
},
|
|
118
118
|
"homepage": "https://github.com/pskoett/pmx-canvas#readme",
|
|
119
119
|
"engines": {
|
|
120
|
-
"bun": ">=1.
|
|
120
|
+
"bun": ">=1.4.2"
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -16,6 +16,14 @@ PMX Canvas is a server-authoritative spatial workbench controlled through MCP, H
|
|
|
16
16
|
Humans curate agent context by pinning nodes; agents read that curation through
|
|
17
17
|
`canvas://pinned-context`. State survives browser refresh.
|
|
18
18
|
|
|
19
|
+
## Runtime prerequisites
|
|
20
|
+
|
|
21
|
+
PMX Canvas 0.6.1 requires **Bun >=1.4.2**, including when installed through npm.
|
|
22
|
+
Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
|
|
23
|
+
use an absolute executable path if needed. Install/configure only when requested.
|
|
24
|
+
For pinned installation, MCP configuration, managed services, and disposable verification,
|
|
25
|
+
read [Installing PMX Canvas](references/installing-pmx-canvas.md).
|
|
26
|
+
|
|
19
27
|
## Required Operating Sequence
|
|
20
28
|
|
|
21
29
|
0. **After a `pmx-canvas` upgrade, refresh this skill first.** Run
|
|
@@ -28,9 +36,10 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
28
36
|
any `--mcp` processes; Bun does not hot-reload a running process.
|
|
29
37
|
1. **Open or focus the workbench before mutating.** Reuse one visible canvas surface for the
|
|
30
38
|
session.
|
|
31
|
-
2. **Verify workspace identity.** Read `GET /health`
|
|
32
|
-
`workspace` must equal the intended absolute
|
|
33
|
-
|
|
39
|
+
2. **Verify workspace identity at the actual target.** Read `GET /health` at the URL your
|
|
40
|
+
CLI/MCP/browser is using; its `workspace` must equal the intended canonical absolute root.
|
|
41
|
+
`pmx-canvas serve status --port=<port>` checks the local listener on that port, not an
|
|
42
|
+
arbitrary `PMX_CANVAS_URL`. A healthy listener may belong to another project.
|
|
34
43
|
3. **Read before write.** Search with `canvas_query { action: "search", query }` before creating
|
|
35
44
|
nodes. Read the full layout only when necessary. The MCP parameter is `query` โ passing the
|
|
36
45
|
HTTP API's `q` is silently ignored and returns zero results.
|
|
@@ -68,38 +77,52 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
68
77
|
|
|
69
78
|
## Workspace Safety
|
|
70
79
|
|
|
71
|
-
Before any create, update, remove, clear, restore, or
|
|
80
|
+
Before any create, update, remove, clear, restore, arrange, or smoke test, choose an
|
|
81
|
+
absolute project root and a dedicated free port. For a local target:
|
|
72
82
|
|
|
73
83
|
```bash
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
export PMX_CANVAS_WORKSPACE_ROOT=/absolute/path/to/project
|
|
85
|
+
export PMX_CANVAS_PORT=14313 # Example only: choose a free port for this workspace.
|
|
86
|
+
unset PMX_CANVAS_URL # Remove a stale URL before selecting a different local target.
|
|
87
|
+
curl --fail --silent --show-error "http://localhost:${PMX_CANVAS_PORT}/health"
|
|
88
|
+
pmx-canvas serve status --port="$PMX_CANVAS_PORT"
|
|
76
89
|
```
|
|
77
90
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
Compare the health field **`workspace`** (not `workspaceRoot`) to the intended root,
|
|
92
|
+
resolving symlinks before comparison. Stop on mismatch or failed health; do not write.
|
|
93
|
+
If deliberately using `PMX_CANVAS_URL` or `--server-url`, check `/health` at that exact
|
|
94
|
+
URL instead. Confirm the browser and MCP use the same verified target, especially
|
|
95
|
+
after fallback to another port. Do not print URLs containing credentials.
|
|
96
|
+
|
|
97
|
+
`pmx-canvas smoke` reports health/workspace/version information, checks MCP initialize,
|
|
98
|
+
creates/searches/removes a temporary node, and validates the board. **It mutates state**
|
|
99
|
+
and does not enforce your intended workspace identity; version skew is reported, not
|
|
100
|
+
necessarily a failing check. Use a disposable workspace, verify health first, and inspect
|
|
101
|
+
both the JSON details and exit status (1 when a check fails). It is not a read-only probe
|
|
102
|
+
or a guarantee of unchanged history.
|
|
83
103
|
|
|
84
104
|
- If `responsive: true` but `pidRunning: false`, treat the listener as potentially stale.
|
|
85
|
-
- On mismatch,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
- `PMX_CANVAS_PORT`
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
- On mismatch, leave the other workspace's server alone. Start the intended workspace on
|
|
106
|
+
a different free port, target it explicitly, and re-check `/health` before writing.
|
|
107
|
+
- On an ordinary local machine, `pmx-canvas serve --daemon --no-open --port=<free-port>`
|
|
108
|
+
refuses a different-workspace occupant. In a managed environment, supervise foreground
|
|
109
|
+
`pmx-canvas serve --no-open --port=<free-port>` with the host's service manager instead.
|
|
110
|
+
- `PMX_CANVAS_PORT` selects the CLI/MCP port. CLI target flags override environment values
|
|
111
|
+
(`--server-url` wins over `--port`); otherwise `PMX_CANVAS_URL` wins over `PMX_CANVAS_PORT`.
|
|
112
|
+
For server startup, explicit `--port` wins, then `PMX_WEB_CANVAS_PORT`, then
|
|
113
|
+
`PMX_CANVAS_PORT`; in Amp orbs `PORT` is a later fallback. URL targeting does not select
|
|
114
|
+
the server's bind port.
|
|
115
|
+
- **MCP transport workspace resolution.** Set **`PMX_CANVAS_WORKSPACE_ROOT` plus a dedicated
|
|
116
|
+
`PMX_CANVAS_PORT`** in the host configuration. Without an explicit root, MCP may inherit a
|
|
117
|
+
different workspace from the preferred-port daemon; `PMX_CANVAS_ALLOW_WORKSPACE_SPLIT=1`
|
|
118
|
+
opts out of that heuristic. Current source honors an explicit root, but may bind a fallback
|
|
119
|
+
port if the preferred port is occupied. Root alone therefore does not guarantee CLI, MCP,
|
|
120
|
+
and browser target alignment; older installed runtimes can also differ. Inspect startup
|
|
121
|
+
output and the actual health endpoint, never assume the preferred port was bound.
|
|
122
|
+
Clear stale inherited `PMX_CANVAS_URL` values in the host configuration as well as the shell.
|
|
123
|
+
- CLI query/mutation commands are HTTP clients and do not start a server. `serve` or `--mcp`
|
|
124
|
+
starts or attaches to one. Use the host's authenticated preview mechanism for remote viewing;
|
|
125
|
+
a loopback URL is only reachable inside the machine/orb running the service.
|
|
103
126
|
|
|
104
127
|
## Choose the Smallest Useful Node Type
|
|
105
128
|
|
|
@@ -147,7 +170,7 @@ this table is kept in sync with the server's composite registry; `tools/list` on
|
|
|
147
170
|
| `canvas_query` | `search`, `layout`, `validate` |
|
|
148
171
|
| `canvas_webview` | `status`, `start`, `stop`, `resize`, `evaluate` |
|
|
149
172
|
| `canvas_app` | `open-mcp-app`, `diagram`, `build-artifact` |
|
|
150
|
-
| `canvas_ax_state` | `get`, `set-focus`, `set-policy`, `report-capability` |
|
|
173
|
+
| `canvas_ax_state` | `get`, `set-focus`, `set-policy`, `report-capability`, `presence`, `set-presence` |
|
|
151
174
|
| `canvas_ax_work` | `add`, `update`, `annotate` |
|
|
152
175
|
| `canvas_ax_gate` | `request`, `resolve`, `await` with `approval`, `elicitation`, or `mode` |
|
|
153
176
|
| `canvas_ax_timeline` | `read`, `record-event`, `add-evidence`, `send-steering` |
|
|
@@ -262,6 +285,14 @@ The board has three modes, all gated on one fact โ whether a session is attach
|
|
|
262
285
|
extension) attach for you; the human can also start a session from the board's *Start agent
|
|
263
286
|
session* button โ your writes (transport or `PMX_CANVAS_AGENT_SOURCE` label alike) are
|
|
264
287
|
attributed to it and it takes your name.
|
|
288
|
+
- **No adapter? Use the pump.** Any CLI agent becomes steer-reactive with one command in a
|
|
289
|
+
terminal: `pmx-canvas pump --consumer <your-key> --exec '<command>'`. It long-polls your
|
|
290
|
+
delivery queue, runs the command once per steer (message on stdin plus the
|
|
291
|
+
`PMX_STEER_MESSAGE`/`ID`/`SOURCE`/`TARGET`/`CREATED_AT` env envelope), and marks
|
|
292
|
+
per-consumer only after the command exits 0 โ a failed hand-off stays pending and the pump
|
|
293
|
+
exits non-zero. `{message}` in the template expands to a quoted env reference (never spliced;
|
|
294
|
+
refused on Windows โ read stdin there). `--parent <key>` rolls you up under an orchestrator's
|
|
295
|
+
chip; `--once` for scripts. See `pmx-canvas pump --help`.
|
|
265
296
|
|
|
266
297
|
What the session asks of you:
|
|
267
298
|
|
|
@@ -2,41 +2,101 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas` command is not available yet.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Prerequisites and version
|
|
6
|
+
|
|
7
|
+
The examples pin **PMX Canvas 0.6.1**, which requires **Bun >=1.4.2** even when
|
|
8
|
+
installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
|
|
9
|
+
first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
|
|
10
|
+
with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
|
|
11
|
+
interactive shell's; use an absolute `bun`/`bunx` executable path if needed.
|
|
12
|
+
|
|
13
|
+
Install/configure only when requested. After an intentional runtime upgrade, sync the
|
|
14
|
+
skill from that installed package and restart existing server/MCP processes as described
|
|
15
|
+
in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowingly.
|
|
16
|
+
|
|
17
|
+
## Install from npm
|
|
6
18
|
|
|
7
19
|
```bash
|
|
8
|
-
npm install -g pmx-canvas
|
|
9
|
-
pmx-canvas
|
|
10
|
-
pmx-canvas open
|
|
20
|
+
npm install -g pmx-canvas@0.6.1
|
|
21
|
+
pmx-canvas --version
|
|
11
22
|
```
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
Without a global install, use `bunx pmx-canvas@0.6.1` in place of `pmx-canvas` in
|
|
25
|
+
the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
|
|
26
|
+
|
|
27
|
+
## Choose the workspace and target
|
|
28
|
+
|
|
29
|
+
Select an existing absolute root and a dedicated free port. These shell examples use
|
|
30
|
+
POSIX syntax; supply equivalent environment variables on other platforms.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export PMX_CANVAS_WORKSPACE_ROOT=/absolute/path/to/project
|
|
34
|
+
export PMX_CANVAS_PORT=14313 # Example only: choose a free port for this workspace.
|
|
35
|
+
unset PMX_CANVAS_URL # A stale URL overrides the environment's CLI port target.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use **both root and port**. Root pinning alone does not align the CLI, MCP, and browser
|
|
39
|
+
if a server falls back to another port. Without an explicit root, MCP may attach to a
|
|
40
|
+
different workspace on the preferred port. `PMX_CANVAS_ALLOW_WORKSPACE_SPLIT=1` opts
|
|
41
|
+
out of that heuristic but is not a substitute for checking the actual target.
|
|
42
|
+
|
|
43
|
+
For server startup, precedence is `--port`, then `PMX_WEB_CANVAS_PORT`, then
|
|
44
|
+
`PMX_CANVAS_PORT` (then `PORT` in Amp orbs), then the default 4313. For CLI queries and
|
|
45
|
+
mutations, explicit `--server-url`/`--port` override environment targeting; otherwise
|
|
46
|
+
`PMX_CANVAS_URL` wins over `PMX_CANVAS_PORT`. URL targeting does not set the bind port.
|
|
47
|
+
|
|
48
|
+
## Start the server
|
|
49
|
+
|
|
50
|
+
On an ordinary local machine:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pmx-canvas serve --daemon --no-open --port="$PMX_CANVAS_PORT" --wait-ms=20000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In a managed environment, use the host's supervised-service mechanism with foreground
|
|
57
|
+
`pmx-canvas serve --no-open --port=<chosen-port>`, passing the selected environment.
|
|
58
|
+
Do not daemonize inside a supervisor or rely on `nohup`, background shell jobs, or tmux
|
|
59
|
+
to survive an orb restart. In an Amp orb, prefer declared services with
|
|
60
|
+
`amp orb services ensure`; otherwise use `amp orb service start` with the foreground
|
|
61
|
+
command. Use the host's authenticated portal/preview for remote viewing, not a raw
|
|
62
|
+
loopback URL. Verify health below before opening the workbench or making writes.
|
|
63
|
+
|
|
64
|
+
CLI query/mutation commands do not auto-start a server; `serve` or `--mcp` does.
|
|
65
|
+
|
|
66
|
+
## From a local checkout (development)
|
|
14
67
|
|
|
15
68
|
```bash
|
|
16
69
|
git clone https://github.com/pskoett/pmx-canvas.git
|
|
17
70
|
cd pmx-canvas
|
|
18
71
|
bun install
|
|
19
72
|
bun run build
|
|
20
|
-
bun run src/cli/index.ts
|
|
73
|
+
bun run src/cli/index.ts --version
|
|
21
74
|
```
|
|
22
75
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
76
|
+
This uses the checked-out source, not necessarily the published 0.6.1 package. Follow
|
|
77
|
+
that checkout's `package.json` Bun requirement and bundled skill. Set the same root/port
|
|
78
|
+
environment above; substitute `bun run /absolute/path/to/pmx-canvas/src/cli/index.ts`
|
|
79
|
+
for `pmx-canvas` in startup and verification commands. The target workspace can be
|
|
80
|
+
different from the source checkout. Build the client before browser verification.
|
|
29
81
|
|
|
30
82
|
## MCP Config
|
|
31
83
|
|
|
32
|
-
For agents that support MCP, configure
|
|
84
|
+
For agents that support MCP, configure the pinned package as a stdio server. Replace
|
|
85
|
+
the root and port with your selections; ensure the port is dedicated to this workspace.
|
|
86
|
+
The empty URL overrides a stale inherited value (empty is treated as unset). If the host
|
|
87
|
+
discards empty env values, remove `PMX_CANVAS_URL` from its launch environment instead.
|
|
33
88
|
|
|
34
89
|
```json
|
|
35
90
|
{
|
|
36
91
|
"mcpServers": {
|
|
37
92
|
"canvas": {
|
|
38
93
|
"command": "bunx",
|
|
39
|
-
"args": ["pmx-canvas", "--mcp"]
|
|
94
|
+
"args": ["pmx-canvas@0.6.1", "--mcp"],
|
|
95
|
+
"env": {
|
|
96
|
+
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
97
|
+
"PMX_CANVAS_PORT": "14313",
|
|
98
|
+
"PMX_CANVAS_URL": ""
|
|
99
|
+
}
|
|
40
100
|
}
|
|
41
101
|
}
|
|
42
102
|
}
|
|
@@ -49,18 +109,66 @@ If you are using a local checkout instead of the published package, point the co
|
|
|
49
109
|
"mcpServers": {
|
|
50
110
|
"canvas": {
|
|
51
111
|
"command": "bun",
|
|
52
|
-
"args": ["run", "/path/to/pmx-canvas/src/cli/index.ts", "--mcp"]
|
|
112
|
+
"args": ["run", "/absolute/path/to/pmx-canvas/src/cli/index.ts", "--mcp"],
|
|
113
|
+
"env": {
|
|
114
|
+
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
115
|
+
"PMX_CANVAS_PORT": "14313",
|
|
116
|
+
"PMX_CANVAS_URL": ""
|
|
117
|
+
}
|
|
53
118
|
}
|
|
54
119
|
}
|
|
55
120
|
}
|
|
56
121
|
```
|
|
57
122
|
|
|
58
|
-
|
|
123
|
+
Restart the host's MCP process after changing configuration. Inspect startup output for
|
|
124
|
+
fallback ports; do not assume the configured port is the one the MCP server bound.
|
|
125
|
+
|
|
126
|
+
## Verify the actual target before writes
|
|
127
|
+
|
|
128
|
+
For the local target selected above:
|
|
59
129
|
|
|
60
130
|
```bash
|
|
61
131
|
pmx-canvas --version
|
|
62
|
-
|
|
132
|
+
curl --fail --silent --show-error "http://localhost:${PMX_CANVAS_PORT}/health"
|
|
133
|
+
pmx-canvas serve status --port="$PMX_CANVAS_PORT"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Require healthy `ok`, the expected running `version`, and a **`workspace`** matching the
|
|
137
|
+
intended canonical absolute root (resolve symlinks on both paths). Inspect persistence
|
|
138
|
+
health too. `serve status` checks a local port, not an arbitrary `PMX_CANVAS_URL`.
|
|
139
|
+
When deliberately targeting a URL or a fallback port, fetch `/health` there instead,
|
|
140
|
+
then align CLI, MCP, and browser to it. Never print a credential-bearing URL.
|
|
141
|
+
On mismatch, stop: leave the other server alone, choose a free port, and repeat.
|
|
142
|
+
|
|
143
|
+
Only after confirming the target:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
63
146
|
pmx-canvas layout
|
|
147
|
+
pmx-canvas open
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Inside an orb, open the registered portal rather than asking the user to open localhost.
|
|
151
|
+
|
|
152
|
+
## Disposable smoke verification
|
|
153
|
+
|
|
154
|
+
`smoke` creates/searches/removes a temporary node and can leave history/activity records.
|
|
155
|
+
It reports the workspace but does **not** compare it with your intended root. Do not run
|
|
156
|
+
it on a valuable board as an identity check. Use a new disposable workspace and its own
|
|
157
|
+
free port, start it with the appropriate startup method above, and verify `/health` first:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export PMX_CANVAS_WORKSPACE_ROOT="$(mktemp -d)"
|
|
161
|
+
export PMX_CANVAS_PORT=14314 # Choose another free port.
|
|
162
|
+
unset PMX_CANVAS_URL
|
|
163
|
+
# Start this workspace's server, then verify its actual health workspace/version.
|
|
164
|
+
# Only after that verification succeeds:
|
|
165
|
+
pmx-canvas smoke
|
|
64
166
|
```
|
|
65
167
|
|
|
66
|
-
|
|
168
|
+
Inspect the JSON report and exit status: health, MCP initialize, node lifecycle, and
|
|
169
|
+
validation should pass; failures exit 1. Version skew is reported in details and is not
|
|
170
|
+
necessarily a failing check. Smoke proves neither `tools/list` discovery nor browser
|
|
171
|
+
rendering: verify those separately when testing the complete onboarding experience.
|
|
172
|
+
Stop only the test server (service-manager stop, or `serve stop --port=<test-port>` for
|
|
173
|
+
your local daemon) before removing its disposable directory. Never clear a user's board
|
|
174
|
+
or stop an unrelated listener to make a smoke test pass.
|
|
@@ -161,7 +161,18 @@ rm -rf dist bundle.html
|
|
|
161
161
|
|
|
162
162
|
# Build with Parcel
|
|
163
163
|
echo "๐จ Building with Parcel..."
|
|
164
|
-
run_with_filtered_stderr run_local_binary parcel build index.html --dist-dir dist --no-source-maps --log-level error
|
|
164
|
+
run_with_filtered_stderr run_local_binary parcel build index.html --dist-dir dist --no-source-maps --log-level error || true
|
|
165
|
+
|
|
166
|
+
# Self-heal a wedged cache: these build projects are REUSED across artifact
|
|
167
|
+
# builds, and a parcel killed mid-write (e.g. the canvas server torn down
|
|
168
|
+
# during a build) leaves a corrupted .parcel-cache that makes every later
|
|
169
|
+
# build die before emitting anything โ permanently, until the cache goes.
|
|
170
|
+
# One cold-cache retry converts that wedge into a slow build.
|
|
171
|
+
if [ ! -s "dist/index.html" ]; then
|
|
172
|
+
echo "๐งน Parcel produced no output โ clearing .parcel-cache and retrying cold..."
|
|
173
|
+
rm -rf .parcel-cache dist
|
|
174
|
+
run_with_filtered_stderr run_local_binary parcel build index.html --dist-dir dist --no-source-maps --log-level error || true
|
|
175
|
+
fi
|
|
165
176
|
|
|
166
177
|
if [ ! -s "dist/index.html" ]; then
|
|
167
178
|
echo "โ Error: Parcel did not produce dist/index.html" >&2
|