pmx-canvas 0.1.25 → 0.1.26

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 CHANGED
@@ -3,6 +3,47 @@
3
3
  All notable changes to `pmx-canvas` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.1.26] - 2026-06-03
7
+
8
+ Small follow-up to 0.1.25. `canvas_add_node` can now create populated
9
+ groups directly, the snapshot diff is available over HTTP, and the
10
+ packaged skill documents host-aware browser-panel etiquette.
11
+
12
+ ### Added
13
+
14
+ - **`canvas_add_node({ type: 'group' })` creates a populated group.**
15
+ The generic add path (MCP and SDK) now routes `type: 'group'` to
16
+ `createGroup`, accepting `children` / `childIds` (node IDs to
17
+ enclose), an optional `childLayout` (`grid` / `column` / `flow`),
18
+ and a frame `color`. `canvas_create_group` remains the dedicated
19
+ entry point; this just removes the dead-end where `canvas_add_node`
20
+ produced an empty group node. Child-ID validation is inherited from
21
+ `createGroup` (missing / self / nested-group children rejected).
22
+ - **`GET /api/canvas/snapshots/diff` over HTTP.** The snapshot-vs-
23
+ current-layout diff that was previously MCP-only (`canvas_diff`) is
24
+ now reachable over HTTP at
25
+ `/api/canvas/snapshots/diff?name=<name|id>`, returning both the
26
+ structured `diff` and a `text` rendering. Missing name → 400,
27
+ unknown snapshot → 404.
28
+
29
+ ### Changed
30
+
31
+ - **Packaged skill documents host-aware browser-panel etiquette.**
32
+ `skills/pmx-canvas/SKILL.md` now tells agents to reuse an existing
33
+ native canvas panel (e.g. the GitHub Copilot `pmx-canvas` extension
34
+ or Codex's in-app Browser on `/workbench`) instead of opening a
35
+ second browser panel to the same workbench, and to open the browser
36
+ workbench only when no native adapter is present. It also restates
37
+ that only same-origin `/api/canvas/frame-documents/<id>` URLs are
38
+ auto-trusted — external `mcp-app` URLs show the unverified-domain
39
+ interstitial by design.
40
+
41
+ ### Internal
42
+
43
+ - Regression coverage for: `canvas_add_node` group creation via both
44
+ `children` and `childIds` (MCP), and the HTTP snapshot-diff endpoint
45
+ returning the snapshot name in the structured diff.
46
+
6
47
  ## [0.1.25] - 2026-06-03
7
48
 
8
49
  Adapter-regression cleanup on top of 0.1.24. Fixes several issues the
@@ -1235,6 +1276,7 @@ otherwise have to discover by trial and error.
1235
1276
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
1236
1277
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
1237
1278
 
1279
+ [0.1.26]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.26
1238
1280
  [0.1.25]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.25
1239
1281
  [0.1.24]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.24
1240
1282
  [0.1.23]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.23
@@ -25,6 +25,10 @@ export declare class PmxCanvas extends EventEmitter {
25
25
  type: CanvasNodeState['type'];
26
26
  title?: string;
27
27
  content?: string;
28
+ children?: string[];
29
+ childIds?: string[];
30
+ childLayout?: 'grid' | 'column' | 'flow';
31
+ color?: string;
28
32
  toolName?: string;
29
33
  category?: string;
30
34
  status?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
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",
@@ -75,6 +75,19 @@ reference before using adapter-native features:
75
75
  - `references/codex-app-adapter.md` — Codex app native Browser + MCP adapter, AX context reading,
76
76
  focus labeling, and live-test checklist.
77
77
 
78
+ Host-aware visibility rule:
79
+ - If a native PMX Canvas adapter/panel is available and already represents the workbench (for example
80
+ the GitHub Copilot app `pmx-canvas` canvas extension, or Codex's in-app Browser opened to
81
+ `/workbench`), use that panel. Do **not** also open a separate browser panel to the same workbench;
82
+ it wastes space and confuses which surface is authoritative.
83
+ - If no native adapter/panel is available (generic MCP client, shell-only agent, raw CLI harness,
84
+ or another agent harness without canvas support), open the normal PMX Canvas browser workbench first
85
+ so the human can see mutations as they happen. Use the server URL's `/workbench` route.
86
+ - External URLs in `mcp-app` nodes show the "Unverified domain" interstitial by design. Only
87
+ same-origin `/api/canvas/frame-documents/<id>` URLs are auto-trusted. For external tools, use a
88
+ bundled `web-artifact`, same-origin frame document, or set `data.trustedDomain: true` only when the
89
+ user accepts the risk.
90
+
78
91
  The canvas auto-starts on first MCP tool call when running in MCP mode (`pmx-canvas --mcp`).
79
92
  For manual start:
80
93
 
package/src/mcp/server.ts CHANGED
@@ -363,6 +363,10 @@ export async function startMcpServer(): Promise<void> {
363
363
  width: z.number().optional().describe('Width in pixels (default: 720)'),
364
364
  height: z.number().optional().describe('Height in pixels (default: 600)'),
365
365
  strictSize: z.boolean().optional().describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
366
+ children: z.array(z.string()).optional().describe('Group-only alias for childIds. Node IDs to include in a generic group node.'),
367
+ childIds: z.array(z.string()).optional().describe('Group-only field. Node IDs to include in a generic group node. Prefer canvas_create_group for groups.'),
368
+ childLayout: z.enum(['grid', 'column', 'flow']).optional().describe('Group-only optional layout for grouped children.'),
369
+ color: z.string().optional().describe('Group-only frame accent color.'),
366
370
  toolName: z.string().optional().describe('Trace node tool or operation label'),
367
371
  category: z.string().optional().describe('Trace node category: mcp, file, subagent, or other'),
368
372
  status: z.string().optional().describe('Trace node status: running, success, or failed'),
@@ -167,6 +167,10 @@ export class PmxCanvas extends EventEmitter {
167
167
  type: CanvasNodeState['type'];
168
168
  title?: string;
169
169
  content?: string;
170
+ children?: string[];
171
+ childIds?: string[];
172
+ childLayout?: 'grid' | 'column' | 'flow';
173
+ color?: string;
170
174
  toolName?: string;
171
175
  category?: string;
172
176
  status?: string;
@@ -182,6 +186,18 @@ export class PmxCanvas extends EventEmitter {
182
186
  if (input.type === 'webpage') {
183
187
  throw new Error('Use addWebpageNode for webpage nodes so page content is fetched and cached on the server.');
184
188
  }
189
+ if (input.type === 'group') {
190
+ return this.createGroup({
191
+ ...(typeof input.title === 'string' ? { title: input.title } : {}),
192
+ childIds: input.childIds ?? input.children ?? [],
193
+ ...(typeof input.x === 'number' ? { x: input.x } : {}),
194
+ ...(typeof input.y === 'number' ? { y: input.y } : {}),
195
+ ...(typeof input.width === 'number' ? { width: input.width } : {}),
196
+ ...(typeof input.height === 'number' ? { height: input.height } : {}),
197
+ ...(typeof input.color === 'string' ? { color: input.color } : {}),
198
+ ...(input.childLayout ? { childLayout: input.childLayout } : {}),
199
+ });
200
+ }
185
201
  const { id, needsCodeGraphRecompute } = addCanvasNode({
186
202
  ...input,
187
203
  defaultWidth: input.type === 'markdown'
@@ -4584,6 +4584,15 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
4584
4584
  return handleSnapshotGc(req);
4585
4585
  }
4586
4586
 
4587
+ if (url.pathname === '/api/canvas/snapshots/diff' && req.method === 'GET') {
4588
+ const name = url.searchParams.get('name') ?? url.searchParams.get('id') ?? '';
4589
+ if (!name.trim()) return responseJson({ ok: false, error: 'Missing snapshot name or id.' }, 400);
4590
+ const snapshot = canvasState.getSnapshotData(name);
4591
+ if (!snapshot) return responseJson({ ok: false, error: `Snapshot "${name}" not found.` }, 404);
4592
+ const diff = diffLayouts(snapshot.name, snapshot, canvasState.getLayout());
4593
+ return responseJson({ ok: true, text: formatDiff(diff), diff });
4594
+ }
4595
+
4587
4596
  if (url.pathname.startsWith('/api/canvas/snapshots/') && url.pathname.endsWith('/diff') && req.method === 'GET') {
4588
4597
  const id = decodeURIComponent(url.pathname.slice('/api/canvas/snapshots/'.length, -'/diff'.length));
4589
4598
  const snapshot = canvasState.getSnapshotData(id);