pmx-canvas 0.3.0 → 0.3.2
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 +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +194 -80
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +41 -15
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +122 -1028
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -10,4 +10,12 @@
|
|
|
10
10
|
* - Idempotent operations where possible
|
|
11
11
|
* - --yes for destructive actions, --dry-run for preview
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Extract the global `--port <n>` / `--server-url <url>` flags (any position,
|
|
15
|
+
* `=` or space-separated value) and set the invocation's target override.
|
|
16
|
+
* Returns the remaining args for command dispatch. Invalid values are a loud
|
|
17
|
+
* `die` — never a silent fallback to the default port. `--server-url` wins
|
|
18
|
+
* over `--port` when both are given.
|
|
19
|
+
*/
|
|
20
|
+
export declare function extractGlobalTargetFlags(args: string[]): string[];
|
|
21
|
+
export declare function runAgentCli(rawArgs: string[]): Promise<void>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare function outputJson(data: unknown): void;
|
|
2
|
+
export declare function readPidFile(path: string): number | null;
|
|
3
|
+
export declare function isProcessRunning(pid: number): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether pid's command line contains `needle`. Returns null when `ps` cannot
|
|
6
|
+
* answer (no such process, unsupported platform) — callers fall back to the
|
|
7
|
+
* plain liveness signal.
|
|
8
|
+
*/
|
|
9
|
+
export declare function processCommandMatches(pid: number, needle: string): boolean | null;
|
|
10
|
+
/**
|
|
11
|
+
* Liveness with a PID-recycling guard: the pid must be alive AND its command
|
|
12
|
+
* line must still look like our daemon (unless `ps` is unavailable, in which
|
|
13
|
+
* case the plain liveness signal wins).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isOwnDaemonProcess(pid: number, entryNeedle: string): boolean;
|
|
16
|
+
export declare function removePidFile(path: string): void;
|
|
17
|
+
export interface HealthStatus {
|
|
18
|
+
responsive: boolean;
|
|
19
|
+
workspace: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function readHealthStatus(url: string): Promise<HealthStatus>;
|
|
22
|
+
export declare function isHealthy(url: string): Promise<boolean>;
|
|
23
|
+
export declare function readLogTail(path: string, maxLines?: number): string | null;
|
|
24
|
+
export declare function waitForHealth(healthUrl: string, timeoutMs: number, getExitMessage: () => string | null): Promise<{
|
|
25
|
+
ok: true;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: string;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function waitForShutdown(healthUrl: string, timeoutMs: number, isAlive: () => boolean): Promise<boolean>;
|
|
31
|
+
/** Workspace the daemon child will bind — mirrors startCanvasServer's resolution. */
|
|
32
|
+
export declare function resolveExpectedWorkspace(): string;
|
|
33
|
+
export type PrecheckVerdict = 'not-running' | 'already-running' | 'foreign-port-owner';
|
|
34
|
+
/** Pure decision for the pre-spawn health probe. */
|
|
35
|
+
export declare function classifyPrecheck(health: HealthStatus, expectedWorkspace: string): PrecheckVerdict;
|
|
36
|
+
export type LockResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
holderPid: number | null;
|
|
41
|
+
reason: 'running' | 'starting';
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Exclusive pid-file creation IS the spawn lock. A held file with a live
|
|
45
|
+
* daemon pid means 'running'; a fresh empty file means another starter is
|
|
46
|
+
* mid-spawn ('starting'); anything stale is reclaimed once.
|
|
47
|
+
*/
|
|
48
|
+
export declare function acquireDaemonLock(pidFile: string, entryNeedle: string): LockResult;
|
|
49
|
+
/**
|
|
50
|
+
* True when the pid file is a concurrent starter's spawn lock: an EMPTY file
|
|
51
|
+
* (acquireDaemonLock creates it before writing the child pid) younger than
|
|
52
|
+
* LOCK_FRESH_MS. `serve status` must NOT delete such a file — clearing it
|
|
53
|
+
* mid-spawn would defeat the spawn lock and let a racing starter double-spawn.
|
|
54
|
+
* Mirrors the fresh-lock protection acquireDaemonLock applies. A stat failure
|
|
55
|
+
* biases toward preserving the lock.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isFreshEmptyLock(pidFile: string): boolean;
|
|
58
|
+
export interface DaemonPaths {
|
|
59
|
+
port: number;
|
|
60
|
+
logFile: string;
|
|
61
|
+
pidFile: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function startDaemonMode(options: DaemonPaths & {
|
|
64
|
+
baseArgs: string[];
|
|
65
|
+
waitMs: number;
|
|
66
|
+
entry: string;
|
|
67
|
+
}): Promise<void>;
|
|
68
|
+
export declare function showServeStatus(options: DaemonPaths & {
|
|
69
|
+
entry: string;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
export declare function stopServeDaemon(options: DaemonPaths & {
|
|
72
|
+
waitMs: number;
|
|
73
|
+
entry: string;
|
|
74
|
+
}): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer, type SemanticWatchEvent, type SemanticWatchEventType, type SseMessage } from '../shared/semantic-attention.js';
|
|
2
|
-
export { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer
|
|
3
|
-
export type { SemanticWatchEvent, SemanticWatchEventType, SseMessage
|
|
2
|
+
export { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer };
|
|
3
|
+
export type { SemanticWatchEvent, SemanticWatchEventType, SseMessage };
|
|
4
4
|
export declare function parseSemanticEventFilter(raw: string | undefined): Set<SemanticWatchEventType>;
|
|
5
5
|
export declare function parseSseStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<SseMessage>;
|
|
@@ -7,5 +7,5 @@ interface CanvasViewportProps {
|
|
|
7
7
|
annotationTool?: AnnotationTool;
|
|
8
8
|
}
|
|
9
9
|
export declare function getRenderableWorldNodes(allNodes: Iterable<CanvasNodeState>, focusedNodeId: string | null): CanvasNodeState[];
|
|
10
|
-
export declare function CanvasViewport({ onNodeContextMenu, onCanvasContextMenu, annotationMode, annotationTool }: CanvasViewportProps): import("preact/src").JSX.Element;
|
|
10
|
+
export declare function CanvasViewport({ onNodeContextMenu, onCanvasContextMenu, annotationMode, annotationTool, }: CanvasViewportProps): import("preact/src").JSX.Element;
|
|
11
11
|
export {};
|
|
@@ -19,5 +19,5 @@ interface MinimapProps {
|
|
|
19
19
|
containerWidth: number;
|
|
20
20
|
containerHeight: number;
|
|
21
21
|
}
|
|
22
|
-
export declare function Minimap({ viewport, nodes, edges, onNavigate, containerWidth, containerHeight
|
|
22
|
+
export declare function Minimap({ viewport, nodes, edges, onNavigate, containerWidth, containerHeight }: MinimapProps): import("preact/src").JSX.Element;
|
|
23
23
|
export {};
|
|
@@ -19,7 +19,15 @@ interface ExtAppHostDimensionsTarget {
|
|
|
19
19
|
* everywhere we can test (Chrome / Codex / Playwright).
|
|
20
20
|
*/
|
|
21
21
|
export declare function isWebKitOnlyHost(userAgent: string): boolean;
|
|
22
|
-
export declare
|
|
22
|
+
export declare const WEBKIT_REMOUNT_SETTLE_MS = 1000;
|
|
23
|
+
export interface WebkitRemountTask {
|
|
24
|
+
/** Perform the remount. Return false if the node no longer needs it (skips the boot wait). */
|
|
25
|
+
remount: () => boolean;
|
|
26
|
+
/** Resolves when the remounted app genuinely boots AND finishes its bootstrap replay, or after a bounded timeout. */
|
|
27
|
+
awaitBoot: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function extAppRecoveryLog(nodeId: string, event: string): void;
|
|
30
|
+
export declare function enqueueWebkitRemount(task: WebkitRemountTask): void;
|
|
23
31
|
export declare function shouldScheduleWebKitRepaint(status: ExtAppFrameStatus, hasReplayToolResult: boolean): boolean;
|
|
24
32
|
export declare function getExtAppBridgeInitKey(node: CanvasNodeState, retryKey: number): string;
|
|
25
33
|
export declare function resolveExtAppDisplayModeRequest(requestedMode: DisplayMode, isExpanded: boolean): {
|
|
@@ -4,7 +4,7 @@ import type { CanvasNodeState } from '../types';
|
|
|
4
4
|
* Supports: file paths (served via /api/canvas/image/:nodeId), data URIs, and URLs.
|
|
5
5
|
* Features: fit-to-container, zoom in/out within node, pan when zoomed.
|
|
6
6
|
*/
|
|
7
|
-
export declare function ImageNode({ node, expanded
|
|
7
|
+
export declare function ImageNode({ node, expanded }: {
|
|
8
8
|
node: CanvasNodeState;
|
|
9
9
|
expanded?: boolean;
|
|
10
10
|
}): import("preact/src").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RefObject } from 'preact';
|
|
2
2
|
/** Floating selection toolbar for a contentEditable host. Mounts once; while
|
|
3
3
|
* visible, positions itself above the current selection's viewport rect. */
|
|
4
|
-
export declare function InlineFormatBar({ hostRef, onChange
|
|
4
|
+
export declare function InlineFormatBar({ hostRef, onChange }: {
|
|
5
5
|
hostRef: RefObject<HTMLElement>;
|
|
6
6
|
onChange: () => void;
|
|
7
7
|
}): import("preact").JSX.Element | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from 'preact';
|
|
2
2
|
import type { CanvasNodeState } from '../types';
|
|
3
|
-
export declare function MarkdownNode({ node, expanded
|
|
3
|
+
export declare function MarkdownNode({ node, expanded }: {
|
|
4
4
|
node: CanvasNodeState;
|
|
5
5
|
expanded?: boolean;
|
|
6
6
|
}): JSX.Element;
|
|
@@ -34,7 +34,7 @@ export interface StructuredValidationResult {
|
|
|
34
34
|
summary: Record<string, unknown>;
|
|
35
35
|
}
|
|
36
36
|
declare const CANONICAL_GRAPH_TYPES: readonly ["line", "bar", "pie", "area", "scatter", "radar", "stacked-bar", "composed", "sparkline", "dot-plot", "bullet", "slopegraph"];
|
|
37
|
-
type CanvasGraphType = typeof CANONICAL_GRAPH_TYPES[number];
|
|
37
|
+
type CanvasGraphType = (typeof CANONICAL_GRAPH_TYPES)[number];
|
|
38
38
|
export declare function describeCanvasSchema(): {
|
|
39
39
|
ok: true;
|
|
40
40
|
source: 'running-server';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const HTML_PRIMITIVE_KINDS: readonly ["choice-grid", "plan-timeline", "review-sheet", "pr-writeup", "system-map", "code-walkthrough", "design-sheet", "component-gallery", "interaction-prototype", "flowchart", "deck", "presentation", "illustration-set", "explainer", "status-report", "incident-report", "triage-board", "config-editor", "prompt-tuner"];
|
|
2
|
-
export type HtmlPrimitiveKind = typeof HTML_PRIMITIVE_KINDS[number];
|
|
2
|
+
export type HtmlPrimitiveKind = (typeof HTML_PRIMITIVE_KINDS)[number];
|
|
3
3
|
export interface HtmlPrimitiveDescriptor {
|
|
4
4
|
kind: HtmlPrimitiveKind;
|
|
5
5
|
title: string;
|
|
@@ -13,7 +13,7 @@ import { type SerializedCanvasNode } from './canvas-serialization.js';
|
|
|
13
13
|
import type { HtmlPrimitiveKind } from './html-primitives.js';
|
|
14
14
|
import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
|
|
15
15
|
import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
|
|
16
|
-
import {
|
|
16
|
+
import type { DiagramPresetOpenInput } from './diagram-presets.js';
|
|
17
17
|
import { type GraphNodeInput, type JsonRenderNodeInput, type JsonRenderSpec } from '../json-render/server.js';
|
|
18
18
|
import type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus } from './server.js';
|
|
19
19
|
/**
|
|
@@ -557,21 +557,21 @@ export type { CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from '.
|
|
|
557
557
|
export type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus, PrimaryWorkbenchCanvasPromptRequest, PrimaryWorkbenchIntent, } from './server.js';
|
|
558
558
|
export { emitPrimaryWorkbenchEvent, consumePrimaryWorkbenchIntents, setPrimaryWorkbenchAutoOpenEnabled, setPrimaryWorkbenchCanvasPromptHandler, startCanvasServer, stopCanvasServer, getCanvasServerPort, openUrlInExternalBrowser, getCanvasAutomationWebViewStatus, startCanvasAutomationWebView, stopCanvasAutomationWebView, evaluateCanvasAutomationWebView, resizeCanvasAutomationWebView, screenshotCanvasAutomationWebView, } from './server.js';
|
|
559
559
|
export { canvasState } from './canvas-state.js';
|
|
560
|
-
export type { CanvasAnnotation, CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions } from './canvas-state.js';
|
|
560
|
+
export type { CanvasAnnotation, CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions, } from './canvas-state.js';
|
|
561
561
|
export { findOpenCanvasPosition } from './placement.js';
|
|
562
562
|
export { searchNodes, buildSpatialContext, detectClusters, findNeighborhoods } from './spatial-analysis.js';
|
|
563
563
|
export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo } from './spatial-analysis.js';
|
|
564
564
|
export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
|
|
565
565
|
export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
|
|
566
566
|
export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
567
|
-
export { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
|
|
567
|
+
export { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors, } from './html-primitives.js';
|
|
568
568
|
export { buildWebArtifactOnCanvas, executeWebArtifactBuild, openWebArtifactInCanvas, resolveWebArtifactScriptPath, resolveWorkspacePath, } from './web-artifacts.js';
|
|
569
569
|
export { buildGraphSpec, buildJsonRenderViewerHtml, createJsonRenderNodeData, GRAPH_NODE_SIZE, JSON_RENDER_NODE_SIZE, normalizeAndValidateJsonRenderSpec, } from '../json-render/server.js';
|
|
570
570
|
export type { CodeGraphSummary, CodeGraphEdge } from './code-graph.js';
|
|
571
571
|
export type { MutationEntry, MutationSummary, SnapshotDiffResult } from './mutation-history.js';
|
|
572
572
|
export type { WebArtifactBuildInput, WebArtifactBuildOutput, WebArtifactCanvasBuildResult, WebArtifactCanvasOpenResult, } from './web-artifacts.js';
|
|
573
573
|
export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
|
|
574
|
-
export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
|
|
574
|
+
export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult, } from './html-primitives.js';
|
|
575
575
|
export { traceManager } from './trace-manager.js';
|
|
576
576
|
export type { PmxAxApprovalGate, PmxAxApprovalStatus, PmxAxCommandDescriptor, PmxAxContext, PmxAxEvent, PmxAxElicitation, PmxAxElicitationStatus, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxModeRequestStatus, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxTimelineSummary, PmxAxWorkItem, PmxAxWorkItemStatus, } from './ax-state.js';
|
|
577
577
|
export type { AxTimelineQuery } from './canvas-db.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { executeOperation, getOperation, listOperations, registerOperation, setOperationEventEmitter, } from './registry.js';
|
|
2
2
|
export { dispatchOperationRoute } from './http.js';
|
|
3
3
|
export { runCanvasBatchOperation, type BatchEnvelope } from './ops/batch.js';
|
|
4
|
-
export {
|
|
4
|
+
export type { OpenMcpAppCoreResult } from './ops/app.js';
|
|
5
5
|
export { LocalOperationInvoker, HttpOperationInvoker, type OperationInvoker } from './invoker.js';
|
|
6
6
|
export { registerOperationTools, registerCompositeTools, type OperationToolHost } from './mcp.js';
|
|
7
7
|
export { compositeToolDefinitions, type CompositeToolDefinition } from './composites.js';
|
|
@@ -81,6 +81,14 @@ export declare function setPrimaryWorkbenchAutoOpenEnabled(enabled: boolean): vo
|
|
|
81
81
|
export declare function isPrimaryWorkbenchAutoOpenEnabled(): boolean;
|
|
82
82
|
export declare function hasWorkbenchSubscribers(): boolean;
|
|
83
83
|
export declare function setPrimaryWorkbenchCanvasPromptHandler(handler: PrimaryWorkbenchCanvasPromptHandler | null): void;
|
|
84
|
+
/**
|
|
85
|
+
* Containment check for bundle-asset serving. Separator-agnostic: on Windows,
|
|
86
|
+
* `resolve()` returns backslash paths, so comparing against a `${dir}/` template
|
|
87
|
+
* rejected every asset — /canvas/index.js 404'd and the SPA never booted (the
|
|
88
|
+
* 0.3.1 Windows report). Normalizing both sides keeps the check exact on POSIX
|
|
89
|
+
* and correct on win32, and testable with win32-shaped fixtures on any host.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isCanvasBundlePath(distPath: string, bundleDir: string): boolean;
|
|
84
92
|
export declare function buildMacBrowserOpenScript(appName: string, url: string): string;
|
|
85
93
|
export declare function openUrlInExternalBrowser(url: string): boolean;
|
|
86
94
|
/**
|
package/docs/cli.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
The CLI is the shell-native way to run and control PMX Canvas. It targets
|
|
4
4
|
`http://localhost:4313` by default — override with `PMX_CANVAS_URL` or
|
|
5
|
-
`PMX_CANVAS_PORT` when the server runs elsewhere
|
|
5
|
+
`PMX_CANVAS_PORT` when the server runs elsewhere, or per invocation with the
|
|
6
|
+
global `--port <n>` / `--server-url <url>` flags (any position; `--server-url`
|
|
7
|
+
wins over `--port`, and both win over the environment variables). An invalid
|
|
8
|
+
value for either flag is a hard error — the CLI never falls back silently to
|
|
9
|
+
the default port.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pmx-canvas node list --port 4750 # target a non-default daemon
|
|
13
|
+
pmx-canvas --server-url http://127.0.0.1:4750 status # same, by URL
|
|
14
|
+
```
|
|
6
15
|
|
|
7
16
|
## Server lifecycle
|
|
8
17
|
|
package/docs/http-api.md
CHANGED
|
@@ -276,6 +276,34 @@ and `/ax/mode/:id/resolve` return 404 for unknown IDs; `/ax/activity` requires a
|
|
|
276
276
|
valid `kind` + `title` (400 otherwise); the single-item gate GETs return 404 for
|
|
277
277
|
unknown IDs and clamp `?waitMs` to ≤120000.
|
|
278
278
|
|
|
279
|
+
## Ghost Cursor intents (ephemeral)
|
|
280
|
+
|
|
281
|
+
Intents are verb-routed over HTTP — there is **no `action` field**. That
|
|
282
|
+
discriminator exists only on the MCP `canvas_intent` composite; `POST` always
|
|
283
|
+
signals a *new* intent, so a body like `{"action":"clear"}` is rejected by the
|
|
284
|
+
registry's kind validation. Pick the operation by method + path:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Signal a ghost — returns { ok, intent } with the id. Auto-expires after ttlMs
|
|
288
|
+
# (default 8000, max 60000), so cleanup is optional.
|
|
289
|
+
curl -X POST http://localhost:4313/api/canvas/ax/intent \
|
|
290
|
+
-H "Content-Type: application/json" \
|
|
291
|
+
-d '{"kind":"create","position":{"x":400,"y":300},"nodeType":"markdown","label":"Add evidence","reason":"collecting run logs"}'
|
|
292
|
+
|
|
293
|
+
# Update the ghost (position/label/reason/confidence/ttlMs; vetoed:true dissolves
|
|
294
|
+
# it AND poisons the id so a later linked settle is rejected)
|
|
295
|
+
curl -X PATCH http://localhost:4313/api/canvas/ax/intent/<id> \
|
|
296
|
+
-H "Content-Type: application/json" \
|
|
297
|
+
-d '{"position":{"x":520,"y":300},"reason":"moved next to the trace"}'
|
|
298
|
+
|
|
299
|
+
# Clear the ghost: plain DELETE dissolves it; settle it into the real node it
|
|
300
|
+
# became via settledNodeId, or veto it. Fields are accepted as JSON body or
|
|
301
|
+
# query params (?vetoed=true/false is coerced; other values are rejected 400).
|
|
302
|
+
curl -X DELETE http://localhost:4313/api/canvas/ax/intent/<id>
|
|
303
|
+
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?settledNodeId=node-42"
|
|
304
|
+
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?vetoed=true"
|
|
305
|
+
```
|
|
306
|
+
|
|
279
307
|
## Diagrams (Excalidraw preset)
|
|
280
308
|
|
|
281
309
|
```bash
|
|
@@ -10,9 +10,9 @@ Each item is tagged by shape: **project** (multi-day, own plan/PR), **decision**
|
|
|
10
10
|
|
|
11
11
|
| Item | Source | What's open | Shape |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| Complete the registry migration | C1 | Fold the
|
|
13
|
+
| Complete the registry migration | C1 | Fold the remaining hand-written routes in `server.ts` into operations; collapse the 76-method `PmxCanvas` onto `executeOperation`; migrate the CLI's 60 raw `fetch` calls onto the operation invoker. **Slice 1 done post-0.3.0:** the 7 `/api/ext-app/*` routes moved to `ops/ext-app.ts` (~290 lines out of `server.ts`, the 5 clone handlers — old M5 — collapsed to one parametrized definition; wire envelopes byte-identical, no new MCP tools). **Slice 2 done:** the 15 ax wire routes (8 GET lists + `ax/context` + `ax/surface-snapshot` + `pinned-context` + `code-graph` + `ax/activity` + `ax/interaction` + legacy `PATCH /api/canvas/ax`) moved to `ops/ax-read.ts` (~370 more lines out of `server.ts`); `open-external` stays hand-written (coupled to the live server port + system browser opener). **Slice 3 done:** theme GET/POST, canvas update, viewport, annotation POST, and node `:id/refresh` moved to the registry (`ops/canvas-wire.ts`, plus additions to `ops/viewport.ts`, `ops/annotation.ts`, `ops/nodes.ts`); the two dispatch gates unified into one `/api/` gate; `server.ts` is at ~2,980 lines (from 3,873 at the audit). **Remaining routes (~20)** are the deliberate poor fits (SSE, binary, HTML/static, file bodies) plus four server-coupled handlers: `prompt` + workbench `state`/`open`/`intent` (module-local `primaryWorkbench*` session state), `render` (Excalidraw URL-normalizer chain), `open-external` (live port + system browser). Those need state injection like the webview runner — a deliberate follow-up, not mechanical. After routes, the bigger halves remain: `PmxCanvas` (76 methods) onto `executeOperation`, and the CLI's raw fetches onto the invoker. | project (in progress) |
|
|
14
14
|
| One HTTP envelope | M4 residual | Registry ops return bare bodies, legacy ext-app handlers `{ok:true,result}`, 404s plaintext. Unify (naturally falls out of C1). Also: surface persistence save failures via a `/health` degraded flag — `canvas-state.ts` has 63 catch→warn→continue sites. | project (with C1) |
|
|
15
|
-
| `dist/` commit policy | C3 residual |
|
|
15
|
+
| `dist/` commit policy | C3 residual | **Decided + done post-0.3.0:** `dist/` stays committed; `test.yml` now has a `dist/types` staleness gate (rebuild + fail on drift). The `dist/canvas`/`dist/json-render` bundles are deliberately not byte-checked — bundler output varies across bun versions; their staleness story folds into M1. | done |
|
|
16
16
|
| Skill-mirror validator | C2 residual | `validate:agent-skills` exits 0 when the (local-only, gitignored) trees are absent. Docs are honest about this now; decide whether to repoint, gate, or delete the check. | decision |
|
|
17
17
|
| Generate CLAUDE.md/AGENTS.md from one source | M9 residual | The twins are byte-identical again but still hand-maintained; generate or symlink to prevent the next drift. | small |
|
|
18
18
|
|
|
@@ -20,10 +20,10 @@ Each item is tagged by shape: **project** (multi-day, own plan/PR), **decision**
|
|
|
20
20
|
|
|
21
21
|
| Item | Source | What's open | Shape |
|
|
22
22
|
|---|---|---|---|
|
|
23
|
-
| Lint/format tooling | H6 |
|
|
23
|
+
| Lint/format tooling | H6 | **Done post-0.3.0:** Biome 2.5 adopted — `bun run lint` / `lint:fix`, wired into `release:check` (making RELEASE.md's "lints" claim true) and CI. First-run format sweep landed as its own mechanical commit; 7 lint errors hand-fixed (incl. a real conditional-hooks bug in `SelectionBar.tsx`). Deliberately disabled rules to revisit: a11y group, `useExhaustiveDependencies` (misfires on Preact signals), `noNonNullAssertion` (143 deliberate uses), `noArrayIndexKey` (13 sites). ~77 warnings (unused imports/vars, optional-chain) left as a burn-down list — warnings don't fail the gate. | done |
|
|
24
24
|
| Client + server unit coverage | H7 | Zero component render tests (no DOM test lib in repo); untested big modules: `ax-state-manager.ts` (826 lines), `mcp-app-host.ts` (814), `canvas-schema.ts` (644), `shared/semantic-attention.ts` (600, in zero test files). Coverage is collected but gates nothing; tests aren't typechecked. Add `happy-dom` + `@testing-library/preact`, a tests tsconfig, and a coverage threshold. | project |
|
|
25
|
-
| Daemon port/lock lifecycle | H4 |
|
|
26
|
-
| Test-suite structure | M8 | Integration monoliths under `tests/unit/` (`server-api` 5,213 lines, `cli-node` 2,903); `getAvailablePort()` copy-pasted in 5 files; `waitForPersistence(650ms)` fixed sleeps in 9 files + ~19 magic `Bun.sleep`s; Playwright lacks `retries`/`forbidOnly`; `test:e2e-cli` (517-line script) runs in no CI workflow. | project |
|
|
25
|
+
| Daemon port/lock lifecycle | H4 | **Fixed post-0.3.0** (`src/cli/daemon.ts` + `tests/unit/cli-daemon.test.ts`). Note the original finding was partly wrong: the daemon child was already strict-port (`PmxCanvas.start` pins `allowPortFallback: false`), so fallback-blind polling could not occur. The real defects — fixed — were: the pre-check accepted any responsive `/health` as "already running" (even a foreign workspace's daemon), a TOCTOU double-spawn window, the pid recorded only after health passed (slow start → unkillable orphan), and no PID-recycling guard. The flaky `pmx-canvas-sdk.test.ts` port collision is NOT this — it's M8's copy-pasted `getAvailablePort()` racing. | done |
|
|
26
|
+
| Test-suite structure | M8 | Integration monoliths under `tests/unit/` (`server-api` 5,213 lines, `cli-node` 2,903); `getAvailablePort()` copy-pasted in 5 files; `waitForPersistence(650ms)` fixed sleeps in 9 files + ~19 magic `Bun.sleep`s; Playwright lacks `retries`/`forbidOnly`; `test:e2e-cli` (517-line script) runs in no CI workflow. *Post-0.3.0:* `operation-parity` now binds port 0 and derives the port from the base URL (removes one reserve-then-rebind race); the shared-helper dedup and the rest remain. Also noted: `ax-parity.test.ts` is a string-matching source-analysis test whose needles broke when Biome re-wrapped `cmd(` calls — its CLI needles are now layout-independent, but any future needle should avoid depending on line layout. **Also noted (2026-07-07):** the `server-api` Excalidraw diagram tests do real network round-trips to the hosted `mcp.excalidraw.com` MCP server and intermittently time out (30s) under full parallel runs — they should be mocked or moved to a network-tagged tier. | project |
|
|
27
27
|
| `noUncheckedIndexedAccess` | L7 residual | Enable in tsconfig; will surface many index-access errors — treat as its own sweep. | small (noisy) |
|
|
28
28
|
|
|
29
29
|
## Phase 3 — client consolidation
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
|
@@ -67,8 +67,8 @@ The 3 fully parallel implementations (`canvas_add_html_node`, `canvas_add_html_p
|
|
|
67
67
|
#### H3. `src/cli/agent.ts` is a 3,337-line monolith with 83 hand-registered commands
|
|
68
68
|
83 `cmd(...)` registrations in one file, routed by a manual three-then-two-then-one word lookup (`agent.ts:3287-3301`); 81 copy-pasted `if (flags.help || flags.h) return showCommandHelp(...)` preambles; a hand-rolled flag parser with a ~40-entry `BOOL_FLAGS` allowlist (`agent.ts:184-193`) that must be updated for every new boolean flag — while `src/cli/index.ts:43-87` independently implements a *second* parser with different rules. **Remediation:** per-domain command modules + a `defineCommand` wrapper + one shared arg parser (and see C1 for the transport).
|
|
69
69
|
|
|
70
|
-
#### H4. Daemon lifecycle
|
|
71
|
-
|
|
70
|
+
#### H4. Daemon lifecycle: races, orphans, and a dishonest pre-check — FIXED post-0.3.0 (finding partly corrected)
|
|
71
|
+
*Correction (2026-07-07):* the original claim that a fallen-back child leaves the parent polling the wrong port was wrong — the daemon child was already strict-port (`PmxCanvas.start` pins `allowPortFallback: false`, `index.ts:176`; only the MCP auto-start path opts into fallback). The real defects were: the pre-check treated **any** responsive `/health` on the port as "already running" (including a different workspace's daemon — the same wrong-workspace class as v0.2.7 Finding I); a TOCTOU window between health check and spawn; the pid file written only after health passed, so a slow-starting daemon outlived its `--wait-ms` budget as an unkillable orphan; and `isProcessRunning` treating `EPERM` as alive with no PID-recycling guard. *Fix (post-0.3.0):* daemon lifecycle extracted to `src/cli/daemon.ts` — the pid file is created exclusively as the spawn lock and records the pid immediately, failed startups kill the child, the pre-check is workspace-aware, and liveness is cross-checked against the process command line (`tests/unit/cli-daemon.test.ts`, 19 tests, plus a live start/double-start/foreign-workspace/status/stop smoke). The intermittent `pmx-canvas-sdk.test.ts` port flake is unrelated — it is M8's copy-pasted `getAvailablePort()` racing.
|
|
72
72
|
|
|
73
73
|
#### H5. sse-bridge maintains a second, divergent copy of node construction and thread state
|
|
74
74
|
`src/client/state/sse-bridge.ts` (1,066 lines, 34 handlers) has its own `DEFAULT_POSITIONS`/`makeNode` node factory (`:75-114`) fully separate from `canvas-store`'s `addNode`/`parseCanvasNode` — two factories with divergent defaults. Thread/turn reconciliation hand-mutates a `turns[]` array plus a module-level `responseToThreadMap` whose own comment admits it is *"Not cleaned on SSE reconnect"* (`:43`), with heuristic dedup (`:650-654`). Server-side, `syncEventToCanvasState` (`server.ts:3046`, ~330 lines) mirrors this as a giant if/else chain, with node geometry defaults duplicated against `ensureDefaultDockedNodes` under a comment saying *"keep geometry/dock defaults in sync if you change them"* (`:3006`). This is the exact drift class that Canvas Architecture Rule 2 exists to prevent. **Remediation:** one shared node factory; treat `canvas-layout-update` as the single source of truth for thread nodes; table-drive the event sync.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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",
|
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
"build": "bun run build:client && bun run build:json-render && bun run build:types",
|
|
39
39
|
"start": "bun run src/cli/index.ts --no-open",
|
|
40
40
|
"pack:dry-run": "bun pm pack --dry-run",
|
|
41
|
-
"release:check": "bun run build && bun run typecheck && bun run test:all",
|
|
41
|
+
"release:check": "bun run build && bun run typecheck && bun run lint && bun run test:all",
|
|
42
42
|
"release:smoke": "bash scripts/release-smoke.sh",
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
|
+
"lint": "biome check .",
|
|
45
|
+
"lint:fix": "biome check --write .",
|
|
44
46
|
"test": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
45
47
|
"test:unit": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
46
48
|
"test:coverage": "bun test tests/unit --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir coverage",
|
|
@@ -76,6 +78,7 @@
|
|
|
76
78
|
"zod": "^4.3.6"
|
|
77
79
|
},
|
|
78
80
|
"devDependencies": {
|
|
81
|
+
"@biomejs/biome": "^2.5.2",
|
|
79
82
|
"@playwright/test": "^1.58.2",
|
|
80
83
|
"@tailwindcss/cli": "^4.1.18",
|
|
81
84
|
"@types/bun": "latest",
|
|
@@ -24,7 +24,8 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
24
24
|
`workspace` must equal the intended absolute workspace root. A healthy listener on port 4313
|
|
25
25
|
may belong to another project.
|
|
26
26
|
3. **Read before write.** Search with `canvas_query { action: "search", query }` before creating
|
|
27
|
-
nodes. Read the full layout only when necessary.
|
|
27
|
+
nodes. Read the full layout only when necessary. The MCP parameter is `query` — passing the
|
|
28
|
+
HTTP API's `q` is silently ignored and returns zero results.
|
|
28
29
|
4. **Snapshot before destructive changes.** Use `canvas_snapshot` (deprecated standalone; folds into
|
|
29
30
|
the `canvas_snapshot` composite's `save` action in v0.4) before clear, restore, or a major
|
|
30
31
|
reorganization.
|
|
@@ -33,7 +34,8 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
33
34
|
or edit, then pass the returned `intent.id` as `intentId` on the mutation so the ghost settles
|
|
34
35
|
into the result. Use it as much as possible to make your next move and your work visible: the
|
|
35
36
|
human watches intent form and can veto mid-thought. Skip it only for trivial in-place tweaks or
|
|
36
|
-
high-frequency batch churn.
|
|
37
|
+
high-frequency batch churn. The default TTL (~8s) expires between agent turns: signal with
|
|
38
|
+
`ttlMs: 30000` and settle by passing `intentId` on the mutation in the same or next call.
|
|
37
39
|
6. **Mutate through current composites.** Prefer the 15 composite MCP tools below.
|
|
38
40
|
7. **Arrange and validate.** After batch changes, use `canvas_view { action: "arrange" }` when
|
|
39
41
|
appropriate and always finish with `canvas_query { action: "validate" }`.
|
|
@@ -55,7 +57,9 @@ Both surfaces report `workspace`. It must match the intended workspace root.
|
|
|
55
57
|
|
|
56
58
|
- If `responsive: true` but `pidRunning: false`, treat the listener as potentially stale.
|
|
57
59
|
- On mismatch, do not mutate. Start the intended workspace on an explicit free port:
|
|
58
|
-
`pmx-canvas serve --daemon --no-open --port=<free-port>`.
|
|
60
|
+
`pmx-canvas serve --daemon --no-open --port=<free-port>`. (`serve --daemon` enforces this
|
|
61
|
+
itself: pointed at a port owned by another workspace, it refuses with the owner named instead
|
|
62
|
+
of reporting "already running".)
|
|
59
63
|
- Target that port and re-check `/health`.
|
|
60
64
|
- `PMX_CANVAS_PORT` is the agent CLI target; the server's startup port is controlled by `--port`
|
|
61
65
|
or `PMX_WEB_CANVAS_PORT`.
|
|
@@ -213,13 +217,14 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
213
217
|
loads (e.g. the GitHub Copilot app's embedded WKWebView) can render as a black tile — a host
|
|
214
218
|
compositor paint race on the nested iframe, **not** a broken node (the session is healthy and
|
|
215
219
|
`sessionStatus` is `ready`; it renders fine in Chrome, the Codex browser, and for nodes created
|
|
216
|
-
live after the panel hydrates).
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
live after the panel hydrates). Under WebKit the canvas auto-recovers: after boot, each
|
|
221
|
+
present-at-load ext-app is remounted through a **serialized, boot-aware queue** (one app at a
|
|
222
|
+
time, each waiting for the previous app's handshake + scene replay), with a watchdog retry for
|
|
223
|
+
iframes that never boot — so multi-app boards converge to painted tiles within a few seconds of
|
|
224
|
+
panel load. In rare cases a tile can still end up black (compositing success is not observable
|
|
225
|
+
from page JS, so retries are bounded); recovery is deterministic: **expand-then-close** the
|
|
226
|
+
black tile (forces a fresh mount in the fullscreen overlay, which always paints), or open the
|
|
227
|
+
workbench in a normal browser (Chrome). Do not diagnose a healthy app session as a broken node.
|
|
223
228
|
- Graph and json-render standalone surfaces use `display=site` and fill the browser viewport, and
|
|
224
229
|
reflow on a live window resize in a normal browser. Some single-tab host browsers (e.g. the
|
|
225
230
|
Codex in-app browser) don't deliver live-resize events, so a resized standalone chart can look
|
|
@@ -151,8 +151,13 @@ pmx-canvas serve stop # Stop the daemon for thi
|
|
|
151
151
|
|
|
152
152
|
`serve --daemon` writes a pid file (`.pmx-canvas/daemon-<port>.pid`) and a log file
|
|
153
153
|
(`.pmx-canvas/daemon-<port>.log`); the wait flag blocks until `/health` returns OK so a script
|
|
154
|
-
can rely on the server being responsive when the command returns.
|
|
155
|
-
|
|
154
|
+
can rely on the server being responsive when the command returns. The pid file doubles as the
|
|
155
|
+
spawn lock and records the pid immediately, so a starting-but-not-yet-healthy daemon is already
|
|
156
|
+
addressable by `serve stop`, and a failed startup kills the child — no orphans. If the requested
|
|
157
|
+
port already serves a *different* workspace (or a non-canvas app), `serve --daemon` refuses with
|
|
158
|
+
the owner named and a hint to pick another port; it never reports a foreign daemon as "already
|
|
159
|
+
running". `serve stop` reads the pid file, verifies the pid still looks like a canvas daemon
|
|
160
|
+
(recycled pids read as stale), sends SIGTERM, and cleans up on exit.
|
|
156
161
|
|
|
157
162
|
### Verify workspace identity BEFORE mutating (required)
|
|
158
163
|
|
|
@@ -170,7 +175,9 @@ file, sends SIGTERM, and cleans up on exit.
|
|
|
170
175
|
3. **On mismatch, isolate**: start the intended workspace on an explicit free port —
|
|
171
176
|
`pmx-canvas serve --daemon --no-open --port=<free-port>` — and target that port. (`PMX_CANVAS_PORT`
|
|
172
177
|
alone may still attach to an existing `4313` listener; prefer an explicit `--port`.) Then
|
|
173
|
-
**re-read `/health`** to confirm the workspace now matches.
|
|
178
|
+
**re-read `/health`** to confirm the workspace now matches. `serve --daemon` also enforces
|
|
179
|
+
this itself: pointed at a port owned by another workspace, it exits with an error naming the
|
|
180
|
+
owner instead of attaching to it.
|
|
174
181
|
4. **MCP transport exception:** `pmx-canvas --mcp` launched from an incidental host dir may attach to
|
|
175
182
|
the healthy daemon already on the preferred port when no explicit workspace root is set, so writes
|
|
176
183
|
land in the visible workbench instead of a hidden fallback workspace. Host adapters should set
|
|
@@ -503,6 +510,13 @@ surfaces): `canvas_batch`, `canvas_pin_nodes`, `canvas_screenshot`, `canvas_ax_i
|
|
|
503
510
|
`canvas_diff` — deprecated pending a `canvas_snapshot` composite in v0.4; the name collides with
|
|
504
511
|
the current save-snapshot tool, so the composite cannot land additively).
|
|
505
512
|
|
|
513
|
+
`canvas_batch` supports exactly these ops: `node.add`, `node.update`, `node.remove`, `graph.add`,
|
|
514
|
+
`edge.add`, `edge.remove`, `group.create`, `group.add`, `group.remove`, `pin.set`/`pin.add`/
|
|
515
|
+
`pin.remove`, `snapshot.save`, and `arrange`. Anything else fails with "Unsupported canvas_batch
|
|
516
|
+
operation" — batch is non-atomic, so earlier ops stay applied and the response carries
|
|
517
|
+
`failedIndex`. `canvas_screenshot` requires an active automation WebView: call
|
|
518
|
+
`canvas_webview { action: "start" }` first.
|
|
519
|
+
|
|
506
520
|
> **Removed in v0.3.0 → use the composite instead**: `canvas_open_mcp_app` / `canvas_add_diagram` /
|
|
507
521
|
> `canvas_build_web_artifact` → **`canvas_app`**; `canvas_webview_start` / `canvas_webview_status` /
|
|
508
522
|
> `canvas_webview_stop` / `canvas_resize` / `canvas_evaluate` → **`canvas_webview`**;
|