pmx-canvas 0.7.1 → 0.7.3
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/.github/extensions/pmx-canvas/extension.mjs +79 -61
- package/CHANGELOG.md +29 -1
- package/Readme.md +48 -9
- package/dist/canvas/global.css +144 -45
- package/dist/canvas/index.js +90 -90
- package/dist/json-render/index.js +55 -55
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
- package/dist/types/client/icons.d.ts +15 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +0 -12
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -1
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/dist/types/shared/delimited-text.d.ts +14 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/docs/cli.md +9 -2
- package/docs/http-api.md +12 -8
- package/docs/mcp.md +8 -1
- package/docs/sdk.md +2 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +41 -16
- package/skills/pmx-canvas/evals/evals.json +112 -2
- package/skills/pmx-canvas/references/full-reference.md +9 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +49 -0
- package/src/cli/commands/backup.ts +15 -4
- package/src/cli/commands/boards.ts +16 -3
- package/src/client/canvas/AttentionHistory.tsx +2 -1
- package/src/client/canvas/BoardSwitcher.tsx +3 -3
- package/src/client/canvas/CanvasNode.tsx +211 -197
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +27 -25
- package/src/client/canvas/ContextPinBar.tsx +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/canvas/ExternalWriters.tsx +4 -3
- package/src/client/canvas/FeedbackDialog.tsx +12 -9
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/SelectionBar.tsx +2 -2
- package/src/client/canvas/SessionPanel.tsx +6 -4
- package/src/client/canvas/SessionReceipt.tsx +2 -1
- package/src/client/canvas/SnapshotPanel.tsx +3 -2
- package/src/client/canvas/ToolRail.tsx +48 -100
- package/src/client/canvas/TopBar.tsx +2 -2
- package/src/client/icons.tsx +118 -1
- package/src/client/nodes/BoardNode.tsx +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +3 -1
- package/src/client/nodes/FileNode.tsx +4 -78
- package/src/client/nodes/ImageNode.tsx +2 -1
- package/src/client/nodes/MarkdownNode.tsx +2 -1
- package/src/client/nodes/McpAppNode.tsx +16 -0
- package/src/client/nodes/PromptNode.tsx +3 -1
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/canvas-store.ts +3 -0
- package/src/client/state/intent-bridge.ts +14 -2
- package/src/client/theme/global.css +144 -45
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +8 -1
- package/src/mcp/server.ts +2 -2
- package/src/server/agent-presence.ts +17 -11
- package/src/server/board-export.ts +97 -32
- package/src/server/index.ts +9 -7
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-presence.ts +7 -3
- package/src/server/operations/ops/boards.ts +12 -5
- package/src/server/operations/registry.ts +4 -13
- package/src/server/server.ts +13 -4
- package/src/shared/ax-surface-protocol.ts +3 -0
- package/src/shared/delimited-text.ts +75 -0
|
@@ -251,7 +251,19 @@ async function resolvePmxServer(ctxOrInput, options = {}) {
|
|
|
251
251
|
|
|
252
252
|
async function resolvePanelTarget(ctx, options = {}) {
|
|
253
253
|
const selected = panelTargets.get(ctx?.instanceId);
|
|
254
|
-
if (!selected)
|
|
254
|
+
if (!selected) {
|
|
255
|
+
const panel = panelServers.get(ctx?.instanceId);
|
|
256
|
+
if (!panel) return await resolvePmxServer(ctx, options);
|
|
257
|
+
// A fallback panel still owns its original target. Action inputs may
|
|
258
|
+
// contain neither that URL nor its workspace, so never rediscover from them.
|
|
259
|
+
const resolved = await resolvePmxServer({
|
|
260
|
+
input: { ...panel.entry.input, ...(options.autoStart === true ? { autoStart: true } : {}) },
|
|
261
|
+
session: { workingDirectory: panel.entry.workspaceRoot },
|
|
262
|
+
}, options);
|
|
263
|
+
panel.entry.pmx = resolved;
|
|
264
|
+
rememberPanelTarget(ctx, resolved);
|
|
265
|
+
return resolved;
|
|
266
|
+
}
|
|
255
267
|
|
|
256
268
|
const probe = await probeServer(selected.baseUrl, selected.workspaceRoot, {
|
|
257
269
|
timeoutMs: options.timeoutMs ?? 2_000,
|
|
@@ -280,15 +292,9 @@ function escapeHtml(value) {
|
|
|
280
292
|
.replaceAll('"', """);
|
|
281
293
|
}
|
|
282
294
|
|
|
283
|
-
function renderShell(
|
|
284
|
-
const status = entry.pmx?.ok ? "Connected" : "Not connected";
|
|
285
|
-
// theme=light is a client-local session default matching the Copilot app's
|
|
286
|
-
// light chrome — it never changes the server-global theme other clients
|
|
287
|
-
// see, and the in-panel theme picker still overrides it.
|
|
288
|
-
const frameSrc = entry.pmx?.baseUrl ? `${entry.pmx.baseUrl}/workbench?theme=light` : "about:blank";
|
|
289
|
-
const error = entry.pmx?.error ? `<p class="error">${escapeHtml(entry.pmx.error)}</p>` : "";
|
|
295
|
+
function renderShell(entry) {
|
|
290
296
|
return `<!doctype html>
|
|
291
|
-
<html>
|
|
297
|
+
<html lang="en">
|
|
292
298
|
<head>
|
|
293
299
|
<meta charset="utf-8" />
|
|
294
300
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -301,70 +307,83 @@ function renderShell(instanceId, entry) {
|
|
|
301
307
|
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
|
|
302
308
|
}
|
|
303
309
|
.shell {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
310
|
+
max-width: 560px;
|
|
311
|
+
margin: 12vh auto;
|
|
312
|
+
padding: 24px;
|
|
307
313
|
}
|
|
308
|
-
|
|
314
|
+
.actions {
|
|
309
315
|
display: flex;
|
|
310
|
-
|
|
316
|
+
flex-wrap: wrap;
|
|
311
317
|
gap: 12px;
|
|
312
|
-
|
|
313
|
-
border-bottom: 1px solid var(--border-color-default, #30363d);
|
|
314
|
-
background: var(--background-color-muted, rgba(22, 27, 34, 0.95));
|
|
318
|
+
margin-top: 24px;
|
|
315
319
|
}
|
|
316
|
-
|
|
317
|
-
.
|
|
320
|
+
p { line-height: 1.6; overflow-wrap: anywhere; }
|
|
321
|
+
.workspace {
|
|
318
322
|
color: var(--text-color-muted, #8b949e);
|
|
319
|
-
font-size:
|
|
323
|
+
font-size: 13px;
|
|
320
324
|
}
|
|
321
325
|
.error {
|
|
322
|
-
margin: 0;
|
|
323
326
|
color: var(--true-color-red, #ff7b72);
|
|
324
|
-
|
|
327
|
+
min-height: 24px;
|
|
325
328
|
}
|
|
326
329
|
button {
|
|
327
|
-
border: 1px solid var(--border-color-default, #
|
|
330
|
+
border: 1px solid var(--border-color-default, #6e7681);
|
|
328
331
|
border-radius: 8px;
|
|
329
|
-
background: var(--background-color-
|
|
332
|
+
background: var(--background-color-muted, #21262d);
|
|
330
333
|
color: inherit;
|
|
334
|
+
font: inherit;
|
|
331
335
|
cursor: pointer;
|
|
332
|
-
|
|
336
|
+
min-height: 40px;
|
|
337
|
+
padding: 8px 16px;
|
|
333
338
|
}
|
|
334
|
-
button:hover {
|
|
339
|
+
button:hover, button:focus-visible {
|
|
335
340
|
border-color: var(--color-focus-outline, #2f81f7);
|
|
336
341
|
}
|
|
337
|
-
|
|
338
|
-
width: 100%;
|
|
339
|
-
height: 100%;
|
|
340
|
-
border: 0;
|
|
341
|
-
background: #0d1117;
|
|
342
|
-
}
|
|
343
|
-
.spacer { flex: 1; }
|
|
342
|
+
button:disabled { opacity: 0.6; cursor: wait; }
|
|
344
343
|
</style>
|
|
345
344
|
</head>
|
|
346
345
|
<body>
|
|
347
|
-
<
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
<button type="button" onclick="
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
346
|
+
<main class="shell">
|
|
347
|
+
<h1>Connect to PMX Canvas</h1>
|
|
348
|
+
<p>The workbench is not connected yet. Home and recent boards will appear when the server is available.</p>
|
|
349
|
+
<p class="workspace">Workspace: ${escapeHtml(entry.workspaceRoot)}</p>
|
|
350
|
+
<p class="workspace">Server: ${escapeHtml(candidateBaseUrls(entry.input)[0].baseUrl)}</p>
|
|
351
|
+
<p id="connection-status" class="error" role="status">${escapeHtml(entry.pmx?.error ?? "Checking connection…")}</p>
|
|
352
|
+
<div class="actions">
|
|
353
|
+
<button type="button" onclick="connect(false)">Check connection</button>
|
|
354
|
+
<button type="button" onclick="connect(true)">Start server</button>
|
|
355
|
+
</div>
|
|
356
|
+
<p class="workspace">This page checks for the server automatically. Start server launches a local server for this workspace.</p>
|
|
357
|
+
</main>
|
|
358
358
|
<script>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
359
|
+
let busy = false;
|
|
360
|
+
let timer;
|
|
361
|
+
async function connect(start) {
|
|
362
|
+
if (busy) return;
|
|
363
|
+
busy = true;
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
const buttons = document.querySelectorAll('button');
|
|
366
|
+
buttons.forEach(button => button.disabled = true);
|
|
367
|
+
const status = document.getElementById('connection-status');
|
|
368
|
+
status.textContent = start ? 'Starting server…' : 'Checking connection…';
|
|
369
|
+
try {
|
|
370
|
+
const response = await fetch(start ? '/start' : '/status', { method: start ? 'POST' : 'GET', cache: 'no-store' });
|
|
371
|
+
const target = await response.json();
|
|
372
|
+
if (response.ok && target.ok && target.baseUrl) {
|
|
373
|
+
// Load the real workbench at its own origin, including its token,
|
|
374
|
+
// Home, boards, and relative API paths. The shell is not a proxy.
|
|
375
|
+
window.location.replace(target.baseUrl + '/workbench?theme=light');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
status.textContent = target.error || 'No matching PMX Canvas server is running.';
|
|
379
|
+
} catch (error) {
|
|
380
|
+
status.textContent = 'Connection check failed. Reopen the canvas if the adapter has stopped.';
|
|
381
|
+
}
|
|
382
|
+
busy = false;
|
|
383
|
+
buttons.forEach(button => button.disabled = false);
|
|
384
|
+
timer = setTimeout(() => connect(false), 2000);
|
|
367
385
|
}
|
|
386
|
+
connect(false);
|
|
368
387
|
</script>
|
|
369
388
|
</body>
|
|
370
389
|
</html>`;
|
|
@@ -378,7 +397,7 @@ async function readRequestJson(req) {
|
|
|
378
397
|
}
|
|
379
398
|
|
|
380
399
|
async function verifiedEntryTarget(entry) {
|
|
381
|
-
if (!entry.pmx?.baseUrl) return { ok: false, baseUrl: null, error: "PMX Canvas server is unavailable." };
|
|
400
|
+
if (!entry.pmx?.ok || !entry.pmx.baseUrl) return { ok: false, baseUrl: null, error: "PMX Canvas server is unavailable." };
|
|
382
401
|
const probe = await probeServer(entry.pmx.baseUrl, entry.pmx.health.workspace, {
|
|
383
402
|
timeoutMs: 2_000,
|
|
384
403
|
});
|
|
@@ -401,18 +420,17 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
401
420
|
try {
|
|
402
421
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
403
422
|
if (req.method === "GET" && url.pathname === "/") {
|
|
404
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
405
|
-
res.end(renderShell(
|
|
423
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
424
|
+
res.end(renderShell(entry));
|
|
406
425
|
return;
|
|
407
426
|
}
|
|
408
427
|
if (req.method === "POST" && url.pathname === "/start") {
|
|
409
|
-
|
|
410
|
-
jsonResponse(res, 200,
|
|
428
|
+
const latest = await resolvePanelTarget({ instanceId }, { autoStart: true });
|
|
429
|
+
jsonResponse(res, 200, latest);
|
|
411
430
|
return;
|
|
412
431
|
}
|
|
413
432
|
if (req.method === "GET" && url.pathname === "/status") {
|
|
414
|
-
const latest = await
|
|
415
|
-
entry.pmx = latest.ok ? latest : entry.pmx;
|
|
433
|
+
const latest = await resolvePanelTarget({ instanceId }, { autoStart: false });
|
|
416
434
|
jsonResponse(res, 200, latest);
|
|
417
435
|
return;
|
|
418
436
|
}
|
|
@@ -545,7 +563,7 @@ async function recordInjectedContext(baseUrl, context, injected) {
|
|
|
545
563
|
async function getAxStatus(ctx) {
|
|
546
564
|
const resolved = await resolvePanelTarget(ctx, { autoStart: false });
|
|
547
565
|
if (!resolved.ok || !resolved.baseUrl) return { ok: false, server: resolved };
|
|
548
|
-
const state = await fetchJson(resolved.baseUrl, "/api/canvas/ax", { timeoutMs: 2_000 });
|
|
566
|
+
const state = await fetchJson(resolved.baseUrl, "/api/canvas/ax?includeContext=false", { timeoutMs: 2_000 });
|
|
549
567
|
return { ok: true, server: resolved, ax: state };
|
|
550
568
|
}
|
|
551
569
|
|
|
@@ -718,7 +736,7 @@ const pmxCanvas = createCanvas({
|
|
|
718
736
|
actions: [
|
|
719
737
|
{
|
|
720
738
|
name: "status",
|
|
721
|
-
description: "Return PMX Canvas server and AX state
|
|
739
|
+
description: "Return PMX Canvas server and AX state without serialized node context. Use get_ax_context for full pinned/focused context.",
|
|
722
740
|
handler: async (ctx) => await getAxStatus(ctx),
|
|
723
741
|
},
|
|
724
742
|
{
|
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,35 @@
|
|
|
3
3
|
All notable changes to `pmx-canvas` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.7.3] - 2026-10-01
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- JSON form values and dynamic lists survive expanding and closing focus mode without silently saving drafts to the server; authored spec updates still reset runtime state.
|
|
11
|
+
- Interactive surfaces register their action bridge before first paint.
|
|
12
|
+
- Bundled skill quick references match live MCP discovery, including board opening and workboards, and distinguish local JSON runtime state from durable data.
|
|
13
|
+
- Amp setup guidance covers workspace trust approval, portal targeting and native steering verification; testing guidance preserves first-failure evidence and distinguishes host coverage gaps.
|
|
14
|
+
- The main toolbar keeps Markdown, Attach files, Webpage and Group; one file picker replaces separate File, Image and HTML buttons, with supported formats explained in its tooltip.
|
|
15
|
+
- Draw, Text note and Eraser are separate toolbar buttons for quick board feedback.
|
|
16
|
+
- Connection handles are easier to grab at any zoom, and saved connections appear without waiting for the next background update.
|
|
17
|
+
- Feedback and agent selectors use themed menus, and Cancel actions have clearer button styling.
|
|
18
|
+
|
|
19
|
+
## [0.7.2] - 2026-09-30
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Agents can explicitly open a board before writing through MCP, CLI, HTTP, and SDK; board deletion remains human-only.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Main-agent transport attaches reuse the attached host session, avoiding duplicate Copilot agents while keeping identified subagents separate.
|
|
28
|
+
- HTML exports have clearer Markdown typography and tables, readable CSV/TSV previews, complete status and context text, and scrollable cards with keyboard-accessible reading views.
|
|
29
|
+
- Original document attachments now avoid existing cards and each other without moving existing content.
|
|
30
|
+
- Backup and restore commands reject unknown flags instead of silently ignoring them.
|
|
31
|
+
- Amp setup instructions distinguish native MCP configuration from the browser portal.
|
|
32
|
+
- Copilot's fallback panel reconnects to the full workbench and keeps adapter actions on the recovered server.
|
|
33
|
+
- Copilot status omits serialized node context; full context remains available through `get_ax_context`.
|
|
34
|
+
- Attachment, board-menu, card, and panel controls use consistently sized SVG icons instead of small font symbols.
|
|
7
35
|
|
|
8
36
|
## [0.7.1] - 2026-09-30
|
|
9
37
|
|
package/Readme.md
CHANGED
|
@@ -44,9 +44,11 @@ communication — proximity means relatedness, pinning means *focus here*.
|
|
|
44
44
|
The database holds many named boards. **Home is the library view, not a board**:
|
|
45
45
|
it lists recent boards and nested category paths (up to eight 60-character
|
|
46
46
|
segments). Empty folders are not independent objects; they exist only while a
|
|
47
|
-
board is filed beneath them.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
board is filed beneath them. Agents can explicitly open/switch boards; deletion
|
|
48
|
+
remains human-only. Creating a board or a copy from reusable cards leaves it
|
|
49
|
+
inactive. Agents open its returned ID before writing; this also switches the
|
|
50
|
+
shared workbench the human sees. Agents may list, read, rename, categorize, and
|
|
51
|
+
search boards without switching. Home
|
|
50
52
|
provides a copy-preview dialog; an open board's command palette offers a
|
|
51
53
|
board-link picker. Neither depends on native browser prompts.
|
|
52
54
|
|
|
@@ -387,9 +389,45 @@ the full workflow and live-test checklist.
|
|
|
387
389
|
|
|
388
390
|
### Use inside AMPCode orbs and the Amp portal
|
|
389
391
|
|
|
390
|
-
|
|
392
|
+
The portal and the agent's MCP connection are separate. For native Canvas tools,
|
|
393
|
+
merge this into the workspace's `.amp/settings.json` (not `.mcp.json`):
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"amp.mcpServers": {
|
|
398
|
+
"canvas": {
|
|
399
|
+
"command": "bunx",
|
|
400
|
+
"args": ["pmx-canvas@0.7.3", "--mcp"],
|
|
401
|
+
"includeTools": ["canvas_*"],
|
|
402
|
+
"env": {
|
|
403
|
+
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
404
|
+
"PMX_CANVAS_PORT": "14313",
|
|
405
|
+
"PMX_CANVAS_URL": "",
|
|
406
|
+
"PMX_CANVAS_AGENT_SOURCE": "amp"
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Replace the root and example port with the portal service's actual workspace and local
|
|
414
|
+
listening port, including an orb-assigned port. Configure this inside the orb/runner;
|
|
415
|
+
settings on your laptop are not automatically available there.
|
|
416
|
+
In the PMX product checkout, use `"command": "bun"` and
|
|
417
|
+
`"args": ["run", "src/mcp/server.ts"]` to test the checked-out source instead.
|
|
418
|
+
Run `amp mcp doctor` in that workspace. If it reports `awaiting approval`, the user
|
|
419
|
+
must review the command and run `amp mcp approve canvas` there; the agent must not
|
|
420
|
+
self-approve or bypass workspace trust. Then reload MCP connections and confirm
|
|
421
|
+
`connected`, Canvas tool discovery, and matching `/health.workspace`, version and
|
|
422
|
+
active board before writes. An open portal or standalone MCP handshake does not prove
|
|
423
|
+
native discovery. PMX ships no dedicated Amp lifecycle adapter: an active agent can
|
|
424
|
+
poll steering, but MCP setup alone does not wake an idle thread or activate skills.
|
|
425
|
+
See the bundled [Amp setup and verification guide](skills/pmx-canvas/references/installing-pmx-canvas.md#amp-connect-the-agent-separately-from-the-portal)
|
|
426
|
+
and [Amp MCP configuration](https://ampcode.com/docs/customize/mcp).
|
|
427
|
+
|
|
428
|
+
For the visible workbench, run `pmx-canvas` as an orb service (the orb sets
|
|
391
429
|
`AMP_ORB` in the environment automatically) and open the workbench through the
|
|
392
|
-
thread's portal URL.
|
|
430
|
+
thread's portal URL. A consumer-workspace service recipe:
|
|
393
431
|
|
|
394
432
|
```yaml
|
|
395
433
|
# .amp/services.yaml — supervised portal service
|
|
@@ -403,7 +441,7 @@ services:
|
|
|
403
441
|
```bash
|
|
404
442
|
# .agents/setup — install the CLI (pin the exact version: a fresh orb running
|
|
405
443
|
# @latest can silently pick up a newer release than the one you validated)
|
|
406
|
-
npm install -g pmx-canvas@0.7.
|
|
444
|
+
npm install -g pmx-canvas@0.7.3
|
|
407
445
|
```
|
|
408
446
|
|
|
409
447
|
The server binds the portal-assigned `$PORT` automatically (gated on the
|
|
@@ -419,9 +457,10 @@ be inlined and stay blocked by the portal embed: cross-origin hosted apps (e.g.
|
|
|
419
457
|
the hosted Excalidraw MCP app), and PDF file nodes — a PDF node offers an
|
|
420
458
|
"Open PDF" link there instead of a preview.
|
|
421
459
|
Debug overrides: `/workbench?transport=poll|sse` and
|
|
422
|
-
`?iframe-mode=srcdoc|src`.
|
|
423
|
-
|
|
424
|
-
|
|
460
|
+
`?iframe-mode=srcdoc|src`. After verifying the workspace, `pmx-canvas smoke --port=<actual-port>`
|
|
461
|
+
checks health, versions, a standalone MCP handshake, node lifecycle and validation.
|
|
462
|
+
Use a disposable board: it performs temporary writes and leaves history. This does
|
|
463
|
+
not verify native Amp discovery or browser rendering.
|
|
425
464
|
|
|
426
465
|
### Install the agent skill (recommended)
|
|
427
466
|
|
package/dist/canvas/global.css
CHANGED
|
@@ -572,9 +572,23 @@ body,
|
|
|
572
572
|
box-shadow: 0 4px 24px var(--c-shadow);
|
|
573
573
|
display: flex;
|
|
574
574
|
flex-direction: column;
|
|
575
|
-
overflow:
|
|
575
|
+
overflow: visible;
|
|
576
576
|
transition: box-shadow 0.15s ease;
|
|
577
|
-
contain: layout style
|
|
577
|
+
contain: layout style;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/* Clip card content, not the connection handles centered on its border. */
|
|
581
|
+
.node-content {
|
|
582
|
+
display: flex;
|
|
583
|
+
flex-direction: column;
|
|
584
|
+
flex: 1;
|
|
585
|
+
min-height: 0;
|
|
586
|
+
border-radius: inherit;
|
|
587
|
+
overflow: hidden;
|
|
588
|
+
contain: paint;
|
|
589
|
+
}
|
|
590
|
+
.group-node > .node-content {
|
|
591
|
+
display: contents;
|
|
578
592
|
}
|
|
579
593
|
|
|
580
594
|
.canvas-node:hover {
|
|
@@ -610,34 +624,42 @@ body,
|
|
|
610
624
|
/* ── Connection port handles (drag-to-connect) ───────────── */
|
|
611
625
|
.node-port {
|
|
612
626
|
position: absolute;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
627
|
+
/* The visible dot stays compact; its grab area stays usable at every zoom. */
|
|
628
|
+
width: calc(32px * var(--node-port-scale, 1));
|
|
629
|
+
height: calc(32px * var(--node-port-scale, 1));
|
|
630
|
+
display: grid;
|
|
631
|
+
place-items: center;
|
|
618
632
|
opacity: 0;
|
|
619
633
|
cursor: crosshair;
|
|
620
634
|
z-index: 10;
|
|
621
|
-
|
|
622
|
-
|
|
635
|
+
touch-action: none;
|
|
636
|
+
transition: opacity 0.15s;
|
|
623
637
|
pointer-events: auto;
|
|
624
638
|
}
|
|
625
|
-
.
|
|
639
|
+
.node-port::after {
|
|
640
|
+
content: '';
|
|
641
|
+
width: calc(6px * var(--node-port-scale, 1));
|
|
642
|
+
height: calc(6px * var(--node-port-scale, 1));
|
|
643
|
+
border-radius: 50%;
|
|
644
|
+
background: var(--c-accent);
|
|
645
|
+
border: calc(2px * var(--node-port-scale, 1)) solid var(--c-panel);
|
|
646
|
+
box-shadow: 0 0 0 calc(1px * var(--node-port-scale, 1)) var(--c-accent);
|
|
647
|
+
}
|
|
648
|
+
.canvas-node:hover .node-port,
|
|
649
|
+
.canvas-node.selected .node-port,
|
|
650
|
+
.canvas-node:focus-within .node-port {
|
|
626
651
|
opacity: 1;
|
|
627
|
-
transform: translate(-50%, -50%) scale(1);
|
|
628
652
|
}
|
|
629
|
-
.node-port:hover {
|
|
653
|
+
.node-port:hover::after {
|
|
630
654
|
background: var(--c-accent-hover);
|
|
631
|
-
transform: translate(-50%, -50%) scale(1.3);
|
|
632
655
|
}
|
|
633
|
-
|
|
634
|
-
.node-port-
|
|
635
|
-
.node-port-
|
|
636
|
-
.node-port-
|
|
637
|
-
.
|
|
638
|
-
.
|
|
639
|
-
.node-port-
|
|
640
|
-
.node-port-right:hover { transform: translate(50%, -50%) scale(1.3); }
|
|
656
|
+
/* Bias vertical grab areas outward so they do not steal title/footer clicks. */
|
|
657
|
+
.node-port-top { top: 0; left: 50%; transform: translate(-50%, -75%); }
|
|
658
|
+
.node-port-bottom { bottom: 0; left: 50%; transform: translate(-50%, 75%); }
|
|
659
|
+
.node-port-top::after { transform: translateY(calc(8px * var(--node-port-scale, 1))); }
|
|
660
|
+
.node-port-bottom::after { transform: translateY(calc(-8px * var(--node-port-scale, 1))); }
|
|
661
|
+
.node-port-left { left: 0; top: 50%; transform: translate(-50%, -50%); }
|
|
662
|
+
.node-port-right { right: 0; top: 50%; transform: translate(50%, -50%); }
|
|
641
663
|
|
|
642
664
|
.canvas-node .node-titlebar {
|
|
643
665
|
display: flex;
|
|
@@ -760,6 +782,11 @@ body,
|
|
|
760
782
|
border-radius: calc(3px * var(--node-chrome-scale, 1));
|
|
761
783
|
}
|
|
762
784
|
|
|
785
|
+
.canvas-node .node-controls button svg {
|
|
786
|
+
width: 1em;
|
|
787
|
+
height: 1em;
|
|
788
|
+
}
|
|
789
|
+
|
|
763
790
|
.canvas-node .node-controls button:hover {
|
|
764
791
|
background: var(--c-surface-hover);
|
|
765
792
|
color: var(--c-text);
|
|
@@ -1466,6 +1493,10 @@ body,
|
|
|
1466
1493
|
animation: presence-pulse 1.4s ease-in-out infinite;
|
|
1467
1494
|
}
|
|
1468
1495
|
.session-collapse {
|
|
1496
|
+
display: inline-flex;
|
|
1497
|
+
align-items: center;
|
|
1498
|
+
justify-content: center;
|
|
1499
|
+
padding: 0;
|
|
1469
1500
|
width: 26px;
|
|
1470
1501
|
height: 26px;
|
|
1471
1502
|
border: 1px solid transparent;
|
|
@@ -1682,8 +1713,9 @@ body,
|
|
|
1682
1713
|
}
|
|
1683
1714
|
}
|
|
1684
1715
|
.session-section-chevron {
|
|
1716
|
+
display: inline-flex;
|
|
1717
|
+
flex-shrink: 0;
|
|
1685
1718
|
color: var(--c-muted);
|
|
1686
|
-
font-size: 13px;
|
|
1687
1719
|
transition: transform 0.15s ease;
|
|
1688
1720
|
}
|
|
1689
1721
|
.session-section-chevron.is-open {
|
|
@@ -1966,8 +1998,13 @@ body,
|
|
|
1966
1998
|
white-space: nowrap;
|
|
1967
1999
|
}
|
|
1968
2000
|
.command-bar-chip-unpin {
|
|
1969
|
-
|
|
1970
|
-
|
|
2001
|
+
display: inline-flex;
|
|
2002
|
+
align-items: center;
|
|
2003
|
+
justify-content: center;
|
|
2004
|
+
flex-shrink: 0;
|
|
2005
|
+
padding: 0;
|
|
2006
|
+
width: 20px;
|
|
2007
|
+
height: 20px;
|
|
1971
2008
|
border: none;
|
|
1972
2009
|
border-radius: 50%;
|
|
1973
2010
|
background: none;
|
|
@@ -1986,6 +2023,7 @@ body,
|
|
|
1986
2023
|
color: var(--c-dim);
|
|
1987
2024
|
}
|
|
1988
2025
|
.command-bar-composer {
|
|
2026
|
+
position: relative;
|
|
1989
2027
|
display: flex;
|
|
1990
2028
|
align-items: center;
|
|
1991
2029
|
gap: 10px;
|
|
@@ -1997,24 +2035,72 @@ body,
|
|
|
1997
2035
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
|
1998
2036
|
}
|
|
1999
2037
|
.command-bar-target {
|
|
2000
|
-
appearance: none;
|
|
2001
|
-
-webkit-appearance: none;
|
|
2002
2038
|
max-width: 148px;
|
|
2003
|
-
padding: 4px 18px 4px 8px;
|
|
2004
|
-
border: 1px solid var(--c-line);
|
|
2005
|
-
border-radius: 7px;
|
|
2006
|
-
background: color-mix(in srgb, var(--c-panel) 80%, transparent)
|
|
2007
|
-
url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M1 1l3 3 3-3' fill='none' stroke='%237a8699' stroke-width='1.4'/%3E%3C/svg%3E")
|
|
2008
|
-
no-repeat right 6px center;
|
|
2009
|
-
color: var(--c-text-soft);
|
|
2010
|
-
font: 12px/1.3 var(--mono);
|
|
2011
|
-
cursor: pointer;
|
|
2012
2039
|
flex-shrink: 0;
|
|
2013
2040
|
}
|
|
2014
|
-
.
|
|
2015
|
-
|
|
2016
|
-
|
|
2041
|
+
.choice-menu {
|
|
2042
|
+
position: relative;
|
|
2043
|
+
min-width: 0;
|
|
2044
|
+
}
|
|
2045
|
+
.choice-menu-trigger {
|
|
2046
|
+
display: flex;
|
|
2047
|
+
align-items: center;
|
|
2048
|
+
justify-content: space-between;
|
|
2049
|
+
gap: 10px;
|
|
2050
|
+
width: 100%;
|
|
2051
|
+
min-height: 36px;
|
|
2052
|
+
padding: 8px 10px;
|
|
2053
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 65%, var(--c-muted));
|
|
2054
|
+
border-radius: 8px;
|
|
2055
|
+
background: var(--c-panel-overlay);
|
|
2017
2056
|
color: var(--c-text);
|
|
2057
|
+
font: 12px/1.35 var(--font);
|
|
2058
|
+
cursor: pointer;
|
|
2059
|
+
}
|
|
2060
|
+
.choice-menu-trigger > span {
|
|
2061
|
+
overflow: hidden;
|
|
2062
|
+
text-overflow: ellipsis;
|
|
2063
|
+
white-space: nowrap;
|
|
2064
|
+
}
|
|
2065
|
+
.choice-menu-trigger svg { flex-shrink: 0; }
|
|
2066
|
+
.choice-menu-trigger:hover,
|
|
2067
|
+
.choice-menu-trigger[aria-expanded='true'] { border-color: var(--c-accent); }
|
|
2068
|
+
.choice-menu-trigger:focus-visible,
|
|
2069
|
+
.choice-menu-options button:focus-visible {
|
|
2070
|
+
outline: 2px solid var(--c-accent);
|
|
2071
|
+
outline-offset: 2px;
|
|
2072
|
+
}
|
|
2073
|
+
.toolbar-menu.choice-menu-options {
|
|
2074
|
+
top: calc(100% + 6px);
|
|
2075
|
+
left: 0;
|
|
2076
|
+
right: auto;
|
|
2077
|
+
min-width: 100%;
|
|
2078
|
+
width: max-content;
|
|
2079
|
+
max-width: min(320px, calc(100vw - 64px));
|
|
2080
|
+
max-height: min(280px, 40vh);
|
|
2081
|
+
overflow-y: auto;
|
|
2082
|
+
z-index: 20;
|
|
2083
|
+
}
|
|
2084
|
+
.toolbar-menu.choice-menu-options.opens-above {
|
|
2085
|
+
top: auto;
|
|
2086
|
+
bottom: calc(100% + 6px);
|
|
2087
|
+
}
|
|
2088
|
+
.command-bar-target.choice-menu {
|
|
2089
|
+
position: static;
|
|
2090
|
+
}
|
|
2091
|
+
.command-bar-target .choice-menu-options {
|
|
2092
|
+
/* Anchor to the composer, not its inset trigger, to clear the session rail. */
|
|
2093
|
+
min-width: min(200px, 100%);
|
|
2094
|
+
max-width: 100%;
|
|
2095
|
+
}
|
|
2096
|
+
.choice-menu-options .toolbar-menu-item {
|
|
2097
|
+
min-height: 36px;
|
|
2098
|
+
white-space: normal;
|
|
2099
|
+
overflow-wrap: anywhere;
|
|
2100
|
+
}
|
|
2101
|
+
.choice-menu-options button:disabled {
|
|
2102
|
+
opacity: 0.5;
|
|
2103
|
+
cursor: not-allowed;
|
|
2018
2104
|
}
|
|
2019
2105
|
|
|
2020
2106
|
.command-bar-icon {
|
|
@@ -4483,13 +4569,24 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4483
4569
|
}
|
|
4484
4570
|
.text-prompt-cancel,
|
|
4485
4571
|
.text-prompt-confirm {
|
|
4486
|
-
|
|
4572
|
+
min-height: 36px;
|
|
4573
|
+
padding: 8px 14px;
|
|
4487
4574
|
border-radius: 7px;
|
|
4488
|
-
border: 1px solid var(--c-line);
|
|
4489
|
-
background:
|
|
4490
|
-
color: var(--c-text
|
|
4575
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 65%, var(--c-muted));
|
|
4576
|
+
background: var(--c-panel-overlay);
|
|
4577
|
+
color: var(--c-text);
|
|
4491
4578
|
cursor: pointer;
|
|
4492
4579
|
font-size: 12px;
|
|
4580
|
+
font-weight: 600;
|
|
4581
|
+
}
|
|
4582
|
+
.text-prompt-cancel:hover {
|
|
4583
|
+
background: var(--c-accent-10);
|
|
4584
|
+
border-color: var(--c-muted);
|
|
4585
|
+
}
|
|
4586
|
+
.text-prompt-cancel:focus-visible,
|
|
4587
|
+
.text-prompt-confirm:focus-visible {
|
|
4588
|
+
outline: 2px solid var(--c-accent);
|
|
4589
|
+
outline-offset: 2px;
|
|
4493
4590
|
}
|
|
4494
4591
|
.text-prompt-confirm {
|
|
4495
4592
|
background: var(--c-accent);
|
|
@@ -4509,7 +4606,8 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4509
4606
|
overflow-y: auto;
|
|
4510
4607
|
color: var(--c-text);
|
|
4511
4608
|
}
|
|
4512
|
-
.feedback-dialog label
|
|
4609
|
+
.feedback-dialog label,
|
|
4610
|
+
.feedback-field {
|
|
4513
4611
|
display: flex;
|
|
4514
4612
|
flex-direction: column;
|
|
4515
4613
|
gap: 6px;
|
|
@@ -6896,7 +6994,7 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6896
6994
|
align-items: center;
|
|
6897
6995
|
gap: 6px;
|
|
6898
6996
|
max-width: 280px;
|
|
6899
|
-
padding:
|
|
6997
|
+
padding: 6px 8px;
|
|
6900
6998
|
margin-left: -8px;
|
|
6901
6999
|
background: none;
|
|
6902
7000
|
border: 1px solid transparent;
|
|
@@ -6914,7 +7012,8 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6914
7012
|
white-space: nowrap;
|
|
6915
7013
|
}
|
|
6916
7014
|
.board-switcher-caret {
|
|
6917
|
-
|
|
7015
|
+
display: inline-flex;
|
|
7016
|
+
flex-shrink: 0;
|
|
6918
7017
|
color: var(--c-text-soft);
|
|
6919
7018
|
}
|
|
6920
7019
|
.board-switcher-menu {
|