pmx-canvas 0.9.2 → 0.9.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 +9 -3
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/canvas/global.css +12 -17
- package/dist/canvas/index.js +58 -58
- package/dist/canvas/mcp-app.html +75 -80
- package/dist/types/client/canvas/submit-without-navigation.d.ts +3 -0
- package/dist/types/client/state/intent-bridge.d.ts +0 -3
- package/dist/types/client/state/presence-store.d.ts +3 -3
- package/dist/types/client/types.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +2 -2
- package/dist/types/server/knowledge-lifecycle.d.ts +11 -2
- package/dist/types/server/lifecycle-status.d.ts +3 -1
- package/dist/types/server/tour-control.d.ts +6 -3
- package/docs/cli.md +10 -1
- package/docs/design/Chrome.dc.html +1 -0
- package/docs/design.md +3 -1
- package/docs/mcp.md +5 -1
- package/docs/node-types.md +3 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +68 -9
- package/docs/sdk.md +7 -0
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +9 -3
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/skills/pmx-canvas/SKILL.md +4 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/src/client/canvas/BoardCopyDialog.tsx +2 -1
- package/src/client/canvas/KnowledgeDialog.tsx +3 -0
- package/src/client/canvas/LifecyclePanel.tsx +4 -0
- package/src/client/canvas/RelationPresetsDialog.tsx +2 -0
- package/src/client/canvas/submit-without-navigation.ts +14 -0
- package/src/client/state/canvas-store.ts +12 -24
- package/src/client/state/intent-bridge.ts +0 -6
- package/src/client/state/presence-store.ts +3 -3
- package/src/client/state/sse-bridge.ts +11 -6
- package/src/client/theme/global.css +12 -17
- package/src/server/canvas-state.ts +10 -0
- package/src/server/index.ts +10 -30
- package/src/server/knowledge-lifecycle.ts +66 -0
- package/src/server/lifecycle-status.ts +79 -7
- package/src/server/operations/ops/lifecycle.ts +3 -7
- package/src/server/operations/ops/nodes.ts +2 -5
- package/src/server/operations/ops/viewport.ts +3 -7
- package/src/server/server.ts +13 -10
- package/src/server/tour-control.ts +17 -8
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AgentPresenceSnapshot } from '../../shared/agent-presence.js';
|
|
2
1
|
import type { CanvasNodeState } from '../types';
|
|
3
2
|
/**
|
|
4
3
|
* Per-tab identity: an AGENT driving the workbench UI from its own pane opens
|
|
@@ -156,8 +155,6 @@ export interface AxInteractionResponse {
|
|
|
156
155
|
}
|
|
157
156
|
/** Fetch the compact AX state snapshot pushed into AX-enabled surfaces. */
|
|
158
157
|
export declare function fetchAxSurfaceState(): Promise<unknown>;
|
|
159
|
-
/** Connect-time agent-presence read; live updates arrive as `agent-presence` SSE frames. */
|
|
160
|
-
export declare function fetchAgentPresence(): Promise<Partial<AgentPresenceSnapshot> | null>;
|
|
161
158
|
/** Ask the server to open a node's surface in the system browser. */
|
|
162
159
|
export declare function openNodeInSystemBrowserRequest(nodeId: string, url?: string): Promise<{
|
|
163
160
|
ok: boolean;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { PmxAxIntent } from '../../shared/ax-intent.js';
|
|
2
2
|
import { type AgentActivityEntry, type AgentPresence, type AgentPresenceSnapshot } from '../../shared/agent-presence.js';
|
|
3
3
|
/**
|
|
4
|
-
* Agent presence (rail-chrome-v2 phase 2). Fed by the server's
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Agent presence (rail-chrome-v2 phase 2). Fed by the server's ordered
|
|
5
|
+
* `agent-presence` frames: a connect snapshot and every change, including TTL
|
|
6
|
+
* expiry, over SSE or polling. This store never runs its own expiry ticker.
|
|
7
7
|
*
|
|
8
8
|
* `sessionActive` is the ONE selector every agent surface reads — the session
|
|
9
9
|
* panel, command bar, presence layer, and top-bar chip all mount on it.
|
|
@@ -75,7 +75,7 @@ export type AnnotationTool = 'pen' | 'eraser' | 'text' | null;
|
|
|
75
75
|
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected';
|
|
76
76
|
export declare const TYPE_LABELS: Record<CanvasNodeState['type'], string>;
|
|
77
77
|
/** Node types that support the full-viewport expand/focus overlay. */
|
|
78
|
-
export declare const EXPANDABLE_TYPES: Set<"
|
|
78
|
+
export declare const EXPANDABLE_TYPES: Set<"board" | "image" | "file" | "markdown" | "mcp-app" | "webpage" | "json-render" | "graph" | "prompt" | "response" | "status" | "context" | "ledger" | "trace" | "diff" | "mermaid" | "html" | "group">;
|
|
79
79
|
export declare const EXCALIDRAW_SERVER_NAME = "Excalidraw";
|
|
80
80
|
export declare const EXCALIDRAW_CREATE_VIEW_TOOL = "create_view";
|
|
81
81
|
export declare function isExcalidrawNode(node: CanvasNodeState): boolean;
|
|
@@ -432,6 +432,8 @@ declare class CanvasStateManager {
|
|
|
432
432
|
updateNode(id: string, patch: Partial<CanvasNodeState>, savedWikiLinks?: WikiLink[] | null): void;
|
|
433
433
|
removeNode(id: string): void;
|
|
434
434
|
getNode(id: string): CanvasNodeState | undefined;
|
|
435
|
+
/** Camera targets need geometry, not hydrated documents, frames or image blobs. */
|
|
436
|
+
nodeGeometry(): Array<Pick<CanvasNodeState, 'id' | 'type' | 'position' | 'size'>>;
|
|
435
437
|
/** Who made and last edited each card, and its content revision — no content, no copies. */
|
|
436
438
|
nodeStamps(): Array<Pick<CanvasNodeState, 'id' | 'createdBy' | 'lastEditedBy' | 'contentRevision'>>;
|
|
437
439
|
getNodeForPersistence(id: string): CanvasNodeState | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import {
|
|
2
|
+
import type { Camera, Tour, TourStop } from '../shared/tour.js';
|
|
3
3
|
import { type TourStep, type TourStepResult } from './tour-control.js';
|
|
4
4
|
export type { Tour, TourStop } from '../shared/tour.js';
|
|
5
5
|
export type { TourStep, TourStepResult } from './tour-control.js';
|
|
@@ -22,7 +22,7 @@ import type { HtmlPrimitiveKind } from './html-primitives.js';
|
|
|
22
22
|
import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
|
|
23
23
|
import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
|
|
24
24
|
import type { DiagramPresetOpenInput } from './diagram-presets.js';
|
|
25
|
-
import {
|
|
25
|
+
import type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
|
|
26
26
|
import type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus } from './server.js';
|
|
27
27
|
/**
|
|
28
28
|
* Node object returned by the SDK's create/get methods. It is the fully
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { type MemoryTarget } from '../shared/memory-graph.js';
|
|
3
4
|
import { type ActorAttribution } from './attribution.js';
|
|
4
5
|
import type { LifecycleQualification } from '../shared/lifecycle-status.js';
|
|
5
6
|
import type { CanvasNodeState } from './canvas-state.js';
|
|
@@ -182,7 +183,7 @@ export declare class KnowledgeLifecycle {
|
|
|
182
183
|
cadenceDays?: number | undefined;
|
|
183
184
|
} | null;
|
|
184
185
|
lifecycleSourceFolder: string | null;
|
|
185
|
-
lifecycleSource: "
|
|
186
|
+
lifecycleSource: "card" | "unknown" | "folder";
|
|
186
187
|
};
|
|
187
188
|
resolveCard(folderPath: string | null, node: Pick<CanvasNodeState, 'type' | 'data'>, catalog?: {
|
|
188
189
|
categories: KnowledgeCategory[];
|
|
@@ -208,8 +209,16 @@ export declare class KnowledgeLifecycle {
|
|
|
208
209
|
cadenceDays?: number | undefined;
|
|
209
210
|
} | null;
|
|
210
211
|
lifecycleSourceFolder: string | null;
|
|
211
|
-
lifecycleSource: "
|
|
212
|
+
lifecycleSource: "card" | "unknown" | "folder";
|
|
212
213
|
};
|
|
214
|
+
private indexedDb;
|
|
215
|
+
/** Stored owners of semantic sources; the caller overlays the active board's unsaved edges. */
|
|
216
|
+
incomingSourceBoards(target: MemoryTarget, activeBoardId: string | null): string[];
|
|
217
|
+
/** One indexed latest validation and retirement per card, never its full audit history. */
|
|
218
|
+
reviewSummaries(targets: MemoryTarget[]): Map<string, {
|
|
219
|
+
lastValidation: ReviewRecord | null;
|
|
220
|
+
retired: boolean;
|
|
221
|
+
}>;
|
|
213
222
|
reviews(boardId: string, nodeId: string): {
|
|
214
223
|
records: ReviewRecord[];
|
|
215
224
|
retired: boolean;
|
|
@@ -3,8 +3,10 @@ import type { LifecycleQualification } from '../shared/lifecycle-status.js';
|
|
|
3
3
|
import type { ReviewCardSummary } from './knowledge-lifecycle.js';
|
|
4
4
|
/** A read-only qualification, shared by briefs, search, Home and card review. No clock is renewed. */
|
|
5
5
|
export declare function lifecycleQualifications(now?: number, atUse?: MemoryTarget[]): Record<string, LifecycleQualification>;
|
|
6
|
+
/** A card being inspected is in use, including its transitive sources and portal contents. */
|
|
7
|
+
export declare function lifecycleQualification(target: MemoryTarget, now?: number): LifecycleQualification | undefined;
|
|
6
8
|
/** Compact queue metadata and qualifications come from the same authoritative read. */
|
|
7
|
-
export declare function lifecycleSnapshot(now?: number, atUse?: MemoryTarget[]): {
|
|
9
|
+
export declare function lifecycleSnapshot(now?: number, atUse?: MemoryTarget[], only?: MemoryTarget): {
|
|
8
10
|
cards: Record<string, LifecycleQualification>;
|
|
9
11
|
reviewCards: ReviewCardSummary[];
|
|
10
12
|
};
|
|
@@ -15,9 +15,12 @@ export interface TourStepResult {
|
|
|
15
15
|
total: number;
|
|
16
16
|
stop: TourStop;
|
|
17
17
|
}
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export interface TourState {
|
|
19
|
+
tour: Tour;
|
|
20
|
+
derived: boolean;
|
|
21
|
+
position: number | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function currentTour(): TourState;
|
|
21
24
|
/** Move the shared cursor. `next`/`previous` clamp at the ends; an explicit index must exist. */
|
|
22
25
|
export declare function goToTourStop(step: TourStep): TourStepResult;
|
|
23
26
|
export declare function exitTour(): void;
|
package/docs/cli.md
CHANGED
|
@@ -99,7 +99,8 @@ offset mid-move without changing endpoints. Tours allow at most 1000 stops.
|
|
|
99
99
|
Tours persist with the board and snapshots, participate in undo, and clear with
|
|
100
100
|
the board. A file containing `null` resets the tour: when none is saved, groups
|
|
101
101
|
are sorted by y, then x, then ID. A saved empty `stops` array deliberately has no
|
|
102
|
-
stops. SDK: `canvas.setTour(tourOrNull)` / `canvas.getTour()
|
|
102
|
+
stops. SDK: `canvas.setTour(tourOrNull)` / `canvas.getTour()`, which returns
|
|
103
|
+
the tour directly; read stops from `.stops`.
|
|
103
104
|
|
|
104
105
|
### Driving a tour (agents)
|
|
105
106
|
|
|
@@ -593,6 +594,14 @@ pmx-canvas copilot install-extension --dry-run # Preview target, writes nothin
|
|
|
593
594
|
pmx-canvas copilot install-extension --yes # Install/overwrite into .github/extensions/pmx-canvas/
|
|
594
595
|
```
|
|
595
596
|
|
|
597
|
+
After updating the adapter, start a new Copilot project session to load it.
|
|
598
|
+
Panel instructions, `send_instruction` and canvas steering request the SDK's
|
|
599
|
+
`immediate` mode: during an active turn, Copilot receives them before its next
|
|
600
|
+
model call, without aborting an executing tool. If the turn has already ended,
|
|
601
|
+
the SDK queues the instruction instead. A successful send means the host accepted
|
|
602
|
+
the instruction, not that the model has consumed it. Failed sends are not marked
|
|
603
|
+
delivered; the delivery pump retries them.
|
|
604
|
+
|
|
596
605
|
## Skill sync
|
|
597
606
|
|
|
598
607
|
Refresh the pmx-canvas skill copies **already installed** in this workspace.
|
|
@@ -125,6 +125,7 @@ body{margin:0}
|
|
|
125
125
|
<div style="height: 350px; display: flex; flex-direction: column; border: 1px solid #1b2c44; border-radius: 14px; overflow: hidden; background: #0f1d31">
|
|
126
126
|
<div style="flex-shrink: 0; height: 46px; display: flex; align-items: center; gap: 10px; padding: 0 16px; border-bottom: 1px solid #1b2c44; font-size: 13px; font-weight: 600">Research index <span style="margin-left: auto; font-size: 12px; font-weight: 400">Relations</span><span style="margin-left: 12px">×</span></div>
|
|
127
127
|
<div style="flex: 1; min-height: 0; overflow: auto; padding: 16px; font-size: 13px; color: #b9c6d6">Read and edit the source here. Actual edits finish saving before a reference opens; a refused save keeps this reader and its draft.</div>
|
|
128
|
+
<button style="flex-shrink: 0; align-self: flex-end; margin: 16px; padding: 8px 18px; border: 1px solid #1b2c44; border-radius: 6px; font: 600 13px 'IBM Plex Sans', sans-serif; background: #13243b; color: #8ea3bd"></> Source</button>
|
|
128
129
|
<div role="region" aria-label="Cross-board references" tabindex="0" style="flex-shrink: 0; max-height: min(40%, 132px); overflow: auto; overscroll-behavior: contain">
|
|
129
130
|
<div style="padding: 6px 12px; border-top: 1px solid #1b2c44; font-size: 12px; color: #b9c6d6">↗ references: Research / Interview notes</div>
|
|
130
131
|
<div style="padding: 6px 12px; border-top: 1px solid #1b2c44; font-size: 12px; color: #b9c6d6">↗ references: Research / Findings</div>
|
package/docs/design.md
CHANGED
|
@@ -504,7 +504,9 @@ retain their hit targets. Grab areas yield to every card, including neighboring
|
|
|
504
504
|
but outrank connection labels and lines. Group interiors still select and open their menus,
|
|
505
505
|
below child ports and labels; the frames and labels keep their existing visual stacking.
|
|
506
506
|
Expanded flow documents put Source in a separate row above the reader, clear of the
|
|
507
|
-
bottom dock's action hints
|
|
507
|
+
bottom dock's action hints. Ordinary documents keep Source at the bottom right in a
|
|
508
|
+
reserved row below the scroll viewport, so it cannot cover text at narrow widths
|
|
509
|
+
or intermediate scroll positions (0.9.2 review follow-up, 2026-10-24).
|
|
508
510
|
Hovering a flow action temporarily
|
|
509
511
|
hides another action's keyboard hint without moving focus; leaving restores the focused hint.
|
|
510
512
|
Card checkbox clicks address parsed Markdown tasks, not fenced/indented examples or raw
|
package/docs/mcp.md
CHANGED
|
@@ -57,7 +57,7 @@ on its source board. It appears as a `portal` edge in `canvas://graph`, independ
|
|
|
57
57
|
|
|
58
58
|
## Board tours
|
|
59
59
|
|
|
60
|
-
`canvas_view { action: "get-tour" }` returns `{ tour, derived }`, using group
|
|
60
|
+
`canvas_view { action: "get-tour" }` returns `{ tour, derived, position }`, using group
|
|
61
61
|
reading order when there is no saved tour. `canvas_view { action: "set-tour",
|
|
62
62
|
tour: { stops: [...] } }` persists an ordered tour; `tour: null` resets it.
|
|
63
63
|
`canvas_view { action: "tour-go", stop: 0 }` (or `step: "next" | "previous"`)
|
|
@@ -170,6 +170,10 @@ in the user's browser. Standalone Markdown edits carry captured board/content
|
|
|
170
170
|
revisions; a refused save keeps the current editor draft. Copy that draft before
|
|
171
171
|
closing the editor or switching boards; unsaved drafts do not survive remounts.
|
|
172
172
|
|
|
173
|
+
Knowledge review, categories, relation presets and board-copy forms handle saves
|
|
174
|
+
in JavaScript, including Enter and input validation. They do not require the
|
|
175
|
+
outer host to grant `allow-forms`; the SDK test host keeps `sandbox="allow-scripts"`.
|
|
176
|
+
|
|
173
177
|
**Host constraints still apply.** Native PMX controls work without child frames.
|
|
174
178
|
HTML, Mermaid, JSON/graph and nested app renderers need the host to permit local
|
|
175
179
|
`srcdoc` frames. The portable MCP Apps default with empty `frameDomains` is
|
package/docs/node-types.md
CHANGED
|
@@ -77,6 +77,9 @@ Tick a task directly on its card, including quoted and nested tasks. A card clic
|
|
|
77
77
|
only that task's `[ ]` or `[x]` marker, preserving the rest of the source exactly.
|
|
78
78
|
Task-shaped text in code examples and raw HTML checkboxes are not Markdown task controls.
|
|
79
79
|
|
|
80
|
+
Expand a Markdown card to read and edit the full document. Source stays outside the
|
|
81
|
+
scrolling text, below ordinary documents and above documents with flow actions.
|
|
82
|
+
|
|
80
83
|
## File nodes
|
|
81
84
|
|
|
82
85
|
File nodes display project files with line numbers and language detection.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Plan 019: After 0.9.0
|
|
2
2
|
|
|
3
|
-
**Status (2026-10-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
**Status (2026-10-24):** 0.9.0–0.9.2 are released. The maintainer authorized review,
|
|
4
|
+
testing and release of the patch-only 0.9.3 cleanup after the prerequisite fixes reached
|
|
5
|
+
`origin/main` at `bed57fe9a4b79bd54f8b5ee51cefd65fec3dbe1a`. The SDK tour return shape
|
|
6
|
+
stays unchanged. MCP hardening and SDK tour result parity move to 0.10 on SDK v1.
|
|
7
|
+
Later version numbers are proposals, not publication approval; every release must pass
|
|
8
|
+
the [release gates](../RELEASE.md).
|
|
8
9
|
**Sources:** [product vision](../product-vision-2026-09.md) (moves and the 2026-10-19 sequence),
|
|
9
10
|
[design.md build plan](../design.md#build-plan) (Waves 4, 5, Later),
|
|
10
11
|
[plan 009](plan-009-tech-debt-backlog.md) (open debt),
|
|
@@ -21,7 +22,7 @@ measured; the maintainer carries that risk rather than reinstating an earlier ga
|
|
|
21
22
|
|
|
22
23
|
| Step | Proposed version | Scope | Vision | Size |
|
|
23
24
|
|---|---|---|---|---|
|
|
24
|
-
| 1 | 0.9.
|
|
25
|
+
| 1 | 0.9.3 | Cleanup: tour debt, large-board costs, bounded due evaluator, MCP reconnect, lint | 12, 1 | S–M |
|
|
25
26
|
| 2 | 0.10 | MCP hardening: contracts, annotations, cancellation, resource subscriptions | 6a, 1 | M |
|
|
26
27
|
| 3 | 0.11 | One frame host, portal-layer mount, Wave-0 design pass | 5, 9 | M–L |
|
|
27
28
|
| 4 | 0.12 | Wave 5: agent output as nodes, make/inline board, group drill-in, relations sketch parity | 3, 0 | L |
|
|
@@ -31,10 +32,66 @@ measured; the maintainer carries that risk rather than reinstating an earlier ga
|
|
|
31
32
|
Each step starts after the previous release ships, except that a concrete defect found in
|
|
32
33
|
use can be fixed narrowly in a patch release without pulling later scope forward.
|
|
33
34
|
|
|
34
|
-
## Step 1: 0.9.
|
|
35
|
+
## Step 1: 0.9.3 cleanup
|
|
35
36
|
|
|
36
37
|
Debt deliberately kept out of 0.9.0, plus measured performance leads.
|
|
37
38
|
|
|
39
|
+
**Local checkpoint, 2026-10-24 (not release approval):**
|
|
40
|
+
- Camera moves now persist only the viewport. A headed browser regression failed on the
|
|
41
|
+
baseline's two full-card geometry writes, then passed without them; the complete seven-case
|
|
42
|
+
presentation/recording file passes. Both camera paths use one animation driver while retaining
|
|
43
|
+
their existing interpolation. Tour resolution reads geometry without hydrating card contents.
|
|
44
|
+
- Tour reads share one result builder. HTTP and MCP return `{ tour, derived, position }`;
|
|
45
|
+
the SDK preserves its existing direct tour result (`.stops`). Result-shape parity is
|
|
46
|
+
deferred to 0.10 because changing the SDK to `.tour.stops` breaks callers.
|
|
47
|
+
- `bun run scripts/probe-mcp-reconnect.ts` opened 12 local stdio fixture apps concurrently,
|
|
48
|
+
then restored them through five server restarts and queried every replacement session's live
|
|
49
|
+
tool catalog. All 60 restored sessions passed, with no `-32001` timeout. The native-host/remote
|
|
50
|
+
backend failure remains open; this probe is not evidence that it is fixed.
|
|
51
|
+
- Saved Markdown skips unused reference parsing; saved targets, unresolved links, drafts and
|
|
52
|
+
inline images retain their behavior. The measurements below leave serialization, offscreen
|
|
53
|
+
rendering and SSE unchanged because each needs a separate contract or lifecycle decision.
|
|
54
|
+
- Lifecycle-get, node-get and review qualification use the dependency/portal closure, including
|
|
55
|
+
incoming cross-board sources and unsaved active-board edges. Latest evidence and retirement
|
|
56
|
+
summaries use indexed queries; requested audit history remains complete. Home counts are
|
|
57
|
+
unchanged. **Deferred:** aggregate-only Home reads, per-card storage reads within closure boards,
|
|
58
|
+
and whole-library mutation broadcasts. The current Home client consumes `knowledge.list.cards`.
|
|
59
|
+
- Lint passes with 55 warnings and 12 infos, down from 63 warnings and 12 infos. Eight unused
|
|
60
|
+
import diagnostics were removed in the touched SDK module. Remaining diagnostics are listed below.
|
|
61
|
+
- Combined checkout verification passed: `bun run build`, `bun run typecheck`, `bun run test`
|
|
62
|
+
(1,503 tests), `bun run test:client` (332 tests), and
|
|
63
|
+
`xvfb-run -a bash scripts/run-playwright.sh --headed` (333 tests). The final node-get
|
|
64
|
+
integration assertion also passed its 32-case relation suite, typecheck and lint. The inspected
|
|
65
|
+
tour recording shows both stops and the restored editor after exit. These are local source
|
|
66
|
+
checks, not release smoke, CI, installed-adapter or native-host verification. The patch-only
|
|
67
|
+
decision is settled; release gates are being run against the final 0.9.3 candidate.
|
|
68
|
+
|
|
69
|
+
**Reproducible measurements (local Bun 1.4.4, synthetic data):**
|
|
70
|
+
|
|
71
|
+
| Workload and command | Before | After | Limits |
|
|
72
|
+
|---|---|---|---|
|
|
73
|
+
| 12 Markdown cards, five decodable 400×300 PNGs, 2,459,314 source bytes; `bun run scripts/benchmark-markdown-load.ts --browser --expect-no-reference-lex` | 24 extra reference parses scanning 4,918,628 bytes | Zero extra reference parses; all 12 normalized HTML hashes match | Three warmups and nine HTTP samples; headed Chromium at 1440×900, 2×, one warmup and three reloads. Timing varied on the shared orb, so no board-load speedup claim |
|
|
74
|
+
| Same Markdown board, public state serialization | 4,927,936 bytes | Unchanged | 2,460,294 bytes are duplicated top-level content; removing that alias is only a counterfactual measurement |
|
|
75
|
+
| Same Markdown board, offscreen rendering | Six offscreen POSTs, 508,608 source bytes | Unchanged | All 12 cards render; all five images decode |
|
|
76
|
+
| Same Markdown board, one-character edit | First layout SSE frame: 2,466,713 bytes, 12 nodes | Unchanged | First frame only, not total event traffic |
|
|
77
|
+
| 12 boards × 24 cards × 32 reviews; `bun run scripts/benchmark-lifecycle.ts` | 13 board reads and 9,248 full-history records per request | Two board reads and 32 records per request | One independent inactive-card request; all 32 requested audit records returned. Closure boards still load whole board bodies |
|
|
78
|
+
| Same lifecycle workload, warm medians from three independent runs | 16.016 / 14.938 / 16.648 ms | 0.832 / 1.101 / 0.609 ms | Five warmups and 20 samples per run. First request initializes indexes and was not consistently faster; no cold-start claim |
|
|
79
|
+
|
|
80
|
+
Remove `--expect-no-reference-lex` when running the Markdown script against the baseline.
|
|
81
|
+
Both benchmarks isolate their databases in disposable workspaces. Dependency-heavy lifecycle
|
|
82
|
+
behavior is covered by cycle, provenance, missing-source and cross-board tests, not this timing workload.
|
|
83
|
+
|
|
84
|
+
**Remaining lint diagnostics:** `bunx --no-install biome check . --max-diagnostics=200`
|
|
85
|
+
lists the exact locations. These remain outside the narrow behavioral fixes:
|
|
86
|
+
|
|
87
|
+
| Diagnostics | Count | Reason retained |
|
|
88
|
+
|---|---:|---|
|
|
89
|
+
| Unused imports / variables / parameters / private members | 11 / 8 / 6 / 1 | Spread across unrelated client, server and test modules; remove with their owning cleanup |
|
|
90
|
+
| Optional-chain suggestions | 23 | Existing style suggestions, including untouched regions of changed files |
|
|
91
|
+
| Literal template placeholders | 3 | Tests intentionally exercise literal `${…}` text |
|
|
92
|
+
| Global `isNaN`, confusing `void`, comma operator | 1 each | Existing chart coercion, frame callback and presence expressions need individual semantic review |
|
|
93
|
+
| Infos: template strings / switch cases / index lookup / config deprecation | 6 / 3 / 2 / 1 | Non-gating style/config follow-up, no rule disabled |
|
|
94
|
+
|
|
38
95
|
- **Tour debt** ([vision move 12](../product-vision-2026-09.md#12-tours-the-board-is-the-presentation-m)):
|
|
39
96
|
a tour camera move saves every node's layout (`commitViewport` → `persistLayout` from the SSE
|
|
40
97
|
camera handler) and re-reads the whole layout (`tour-control.ts`); `animateCameraPath`
|
|
@@ -55,8 +112,8 @@ Debt deliberately kept out of 0.9.0, plus measured performance leads.
|
|
|
55
112
|
**Done when**
|
|
56
113
|
- A tour step changes the viewport and writes no node layout: a test asserts the intended
|
|
57
114
|
viewport change and that no layout-persistence write occurs during `tour go`/`next`
|
|
58
|
-
(unchanged positions alone would also pass a needless identical rewrite). SDK
|
|
59
|
-
|
|
115
|
+
(unchanged positions alone would also pass a needless identical rewrite). The SDK keeps its
|
|
116
|
+
existing tour shape; result-shape parity moves to step 2. One camera-animation path remains.
|
|
60
117
|
- Each WI-01 amplifier has a reproducible measurement script and a before/after number on the
|
|
61
118
|
same workload, per the measurement rules in `CLAUDE.md`; unimplicated ones are recorded, not changed.
|
|
62
119
|
- A one-card lifecycle request on a board with populated review histories reads only its
|
|
@@ -77,6 +134,8 @@ and move 1's context delivery, on the current SDK v1.
|
|
|
77
134
|
for long operations where the host supports it; partial outcomes reported honestly.
|
|
78
135
|
5. Resource subscriptions: advertise the capability, track subscribed URIs per connection,
|
|
79
136
|
notify only subscribers. Explicit context reads keep working for hosts without them.
|
|
137
|
+
6. Align SDK tour reads with HTTP/MCP `{ tour, derived, position }`; document the breaking
|
|
138
|
+
change from `.stops` to `.tour.stops` at this minor-version boundary.
|
|
80
139
|
|
|
81
140
|
Candidates, not automatic scope: `canvas_help { op }`, `session.start`, per-action schemas.
|
|
82
141
|
|
package/docs/sdk.md
CHANGED
|
@@ -36,6 +36,13 @@ Missing/stale validating preconditions fail. Reviewed content must persist befor
|
|
|
36
36
|
record. Read, edit, refresh and explicit retirement restore never renew validation; historical
|
|
37
37
|
records retain their reviewed revision, and legacy unversioned evidence remains unknown.
|
|
38
38
|
|
|
39
|
+
## Board tours
|
|
40
|
+
|
|
41
|
+
`canvas.getTour()` returns the tour directly. Read stops from `canvas.getTour().stops`.
|
|
42
|
+
When no tour is saved, stops come from group reading order.
|
|
43
|
+
`canvas.setTour(tourOrNull)` saves the tour or resets it to derived group order.
|
|
44
|
+
See the [tour contract](http-api.md#board-tours) for navigation and camera moves.
|
|
45
|
+
|
|
39
46
|
## Quick example
|
|
40
47
|
|
|
41
48
|
`canvas.addNode` also accepts `destinationBoardId`, `requestId`, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
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",
|
|
@@ -312,7 +312,11 @@ async function startPanelServer(instanceId, ctx, pmx) {
|
|
|
312
312
|
jsonResponse(res, 400, { ok: false, error: "prompt is required" });
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
|
-
|
|
315
|
+
if (!copilotSession) {
|
|
316
|
+
jsonResponse(res, 503, { ok: false, error: "Copilot session is unavailable." });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await copilotSession.send({ prompt: body.prompt, mode: "immediate" });
|
|
316
320
|
// Explicit user instruction from the panel → mirror onto the AX
|
|
317
321
|
// timeline as a steering message (fire-and-forget).
|
|
318
322
|
const target = await verifiedEntryTarget(entry);
|
|
@@ -635,7 +639,8 @@ const pmxCanvas = createCanvas({
|
|
|
635
639
|
handler: async (ctx) => {
|
|
636
640
|
const prompt = typeof ctx.input?.prompt === "string" ? ctx.input.prompt.trim() : "";
|
|
637
641
|
if (!prompt) throw new CanvasError("prompt_required", "prompt is required");
|
|
638
|
-
|
|
642
|
+
if (!copilotSession) throw new CanvasError("copilot_unavailable", "Copilot session is unavailable.");
|
|
643
|
+
await copilotSession.send({ prompt, mode: "immediate" });
|
|
639
644
|
// Mirror the explicit user instruction onto the AX timeline as a
|
|
640
645
|
// steering message (fire-and-forget). This is an explicit action
|
|
641
646
|
// flow, never a sync from the prompt hook.
|
|
@@ -867,7 +872,8 @@ sessionWorkspaceRoot = resolve((await copilotSession.rpc.metadata.snapshot()).wo
|
|
|
867
872
|
steeringDeliveryPump = createSteeringDeliveryPump({
|
|
868
873
|
consumer: "copilot",
|
|
869
874
|
claim: claimCopilotSteering,
|
|
870
|
-
|
|
875
|
+
// The SDK defaults to enqueue; canvas steering must target the active turn.
|
|
876
|
+
send: async (message) => await copilotSession.send({ prompt: message, mode: "immediate" }),
|
|
871
877
|
mark: markCopilotSteering,
|
|
872
878
|
getPresence: getCopilotPresence,
|
|
873
879
|
setPresence: async (patch) => await postPresence(sessionWorkspaceRoot, patch),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "pmx-canvas",
|
|
4
4
|
"description": "PMX Canvas spatial workbench and MCP tools, with a native canvas panel in the GitHub Copilot app.",
|
|
5
|
-
"version": "0.9.
|
|
5
|
+
"version": "0.9.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "pskoett",
|
|
8
8
|
"url": "https://github.com/pskoett"
|
|
@@ -106,7 +106,7 @@ for the authoritative stop, viewport, persistence, reset, group-order, and captu
|
|
|
106
106
|
|
|
107
107
|
## Runtime prerequisites
|
|
108
108
|
|
|
109
|
-
PMX Canvas 0.9.
|
|
109
|
+
PMX Canvas 0.9.3 requires **Bun >=1.4.4**, including when installed through npm.
|
|
110
110
|
Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
|
|
111
111
|
use an absolute executable path if needed. Install/configure only when requested.
|
|
112
112
|
For pinned installation, MCP configuration, managed services, and disposable verification,
|
|
@@ -389,9 +389,9 @@ If the documented receipts or warnings are unexpectedly absent, do not guess aro
|
|
|
389
389
|
payload reads: verify that the CLI/MCP host and daemon use the same 0.7 installation and workspace,
|
|
390
390
|
restart the matching daemon after upgrade, and re-check `/health`.
|
|
391
391
|
|
|
392
|
-
**Token hygiene.** For routine state checks use
|
|
393
|
-
`
|
|
394
|
-
actually consuming
|
|
392
|
+
**Token hygiene.** For routine state checks use
|
|
393
|
+
`canvas_ax_state { action: "get", includeContext: false }` to omit the serialized AX context.
|
|
394
|
+
Omitting `includeContext` defaults to `true`; request full context only when actually consuming it.
|
|
395
395
|
|
|
396
396
|
## Context Pins
|
|
397
397
|
|
|
@@ -4,7 +4,7 @@ Use this reference when PMX Canvas is running inside the GitHub Copilot app as a
|
|
|
4
4
|
extension. The adapter is intentionally thin: PMX Canvas remains the state owner, and the extension
|
|
5
5
|
maps Copilot SDK features onto PMX AX primitives.
|
|
6
6
|
|
|
7
|
-
The [standalone agent plugin](
|
|
7
|
+
The [standalone agent plugin](https://github.com/pskoett/pmx-canvas/blob/main/docs/agent-plugin.md) bundles this adapter and PMX's
|
|
8
8
|
MCP launcher. Its native canvas is Copilot-only; other Agent Plugins-compatible clients use
|
|
9
9
|
the portable MCP tools and browser workbench.
|
|
10
10
|
|
|
@@ -125,7 +125,7 @@ imports the Copilot SDK.
|
|
|
125
125
|
- **Await gates** with `canvas_ax_gate { kind, action: "await", id }` (or surface a native modal
|
|
126
126
|
and await the PMX result) so an approval gate actually blocks the agent until the human resolves it.
|
|
127
127
|
|
|
128
|
-
See [`docs/ax-host-adapter-contract.md`](
|
|
128
|
+
See [`docs/ax-host-adapter-contract.md`](https://github.com/pskoett/pmx-canvas/blob/main/docs/ax-host-adapter-contract.md).
|
|
129
129
|
- Keeps all persistent PMX state in `.pmx-canvas/canvas.db`; the extension does not own canvas
|
|
130
130
|
state.
|
|
131
131
|
|
|
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
|
|
|
4
4
|
|
|
5
5
|
## Prerequisites and version
|
|
6
6
|
|
|
7
|
-
The examples pin **PMX Canvas 0.9.
|
|
7
|
+
The examples pin **PMX Canvas 0.9.3**, which requires **Bun >=1.4.4** even when
|
|
8
8
|
installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
|
|
9
9
|
first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
|
|
10
10
|
with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
|
|
@@ -22,11 +22,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
|
|
|
22
22
|
## Install from npm
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install -g pmx-canvas@0.9.
|
|
25
|
+
npm install -g pmx-canvas@0.9.3
|
|
26
26
|
pmx-canvas --version
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Without a global install, use `bunx pmx-canvas@0.9.
|
|
29
|
+
Without a global install, use `bunx pmx-canvas@0.9.3` in place of `pmx-canvas` in
|
|
30
30
|
the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
|
|
31
31
|
|
|
32
32
|
## Choose the workspace and target
|
|
@@ -96,7 +96,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
|
|
|
96
96
|
"mcpServers": {
|
|
97
97
|
"canvas": {
|
|
98
98
|
"command": "bunx",
|
|
99
|
-
"args": ["pmx-canvas@0.9.
|
|
99
|
+
"args": ["pmx-canvas@0.9.3", "--mcp"],
|
|
100
100
|
"env": {
|
|
101
101
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
102
102
|
"PMX_CANVAS_PORT": "14313",
|
|
@@ -140,7 +140,7 @@ Amp executes, not only on the user's laptop. Merge this into that workspace's
|
|
|
140
140
|
"amp.mcpServers": {
|
|
141
141
|
"canvas": {
|
|
142
142
|
"command": "bunx",
|
|
143
|
-
"args": ["pmx-canvas@0.9.
|
|
143
|
+
"args": ["pmx-canvas@0.9.3", "--mcp"],
|
|
144
144
|
"includeTools": ["canvas_*"],
|
|
145
145
|
"env": {
|
|
146
146
|
"PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
|
|
@@ -174,7 +174,7 @@ The agent must not run this approval itself or move the command to global settin
|
|
|
174
174
|
`--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
|
|
175
175
|
command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
|
|
176
176
|
After approval, reload MCP connections (`reload_mcp` in Amp) and require `connected`.
|
|
177
|
-
Discover `canvas` tools with `tool_search`; for the full unfiltered 0.9.
|
|
177
|
+
Discover `canvas` tools with `tool_search`; for the full unfiltered 0.9.3 server there
|
|
178
178
|
are 24 tools. If still absent, inspect the server status, command/PATH and target instead
|
|
179
179
|
of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
|
|
180
180
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type BoardSummary,
|
|
8
8
|
} from '../state/boards-store';
|
|
9
9
|
import { useFocusTrap } from './use-focus-trap';
|
|
10
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
10
11
|
|
|
11
12
|
export function BoardCopyDialog({ board, onClose }: { board: BoardSummary; onClose: () => void }) {
|
|
12
13
|
const dialogRef = useRef<HTMLDivElement>(null);
|
|
@@ -63,7 +64,7 @@ export function BoardCopyDialog({ board, onClose }: { board: BoardSummary; onClo
|
|
|
63
64
|
aria-labelledby="board-copy-title"
|
|
64
65
|
onMouseDown={(e) => e.stopPropagation()}
|
|
65
66
|
>
|
|
66
|
-
<form onSubmit={submit}>
|
|
67
|
+
<form onClick={submitWithoutNavigation} onSubmit={submit}>
|
|
67
68
|
<h2 id="board-copy-title">Create from {board.name}</h2>
|
|
68
69
|
<label class="board-copy-field">
|
|
69
70
|
Board name
|
|
@@ -8,6 +8,7 @@ import { requestJson } from '../state/intent-bridge';
|
|
|
8
8
|
import { cleanLifecycle, DEFAULT_LIFECYCLE, LifecycleFields } from './LifecycleFields';
|
|
9
9
|
import { useFocusTrap } from './use-focus-trap';
|
|
10
10
|
import { ChoiceMenu } from './ChoiceMenu';
|
|
11
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
11
12
|
|
|
12
13
|
export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose: () => void }) {
|
|
13
14
|
const enabled = knowledgeLifecycleEnabled.value;
|
|
@@ -158,6 +159,7 @@ export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose:
|
|
|
158
159
|
<p>Knowledge settings unavailable. Close and retry.</p>
|
|
159
160
|
) : folder ? (
|
|
160
161
|
<form
|
|
162
|
+
onClick={submitWithoutNavigation}
|
|
161
163
|
onSubmit={async (event) => {
|
|
162
164
|
event.preventDefault();
|
|
163
165
|
if (!knowledgeLifecycleEnabled.value || busy) return;
|
|
@@ -286,6 +288,7 @@ export function KnowledgeDialog({ folder, onClose }: { folder?: string; onClose:
|
|
|
286
288
|
<form
|
|
287
289
|
class="category-inspector"
|
|
288
290
|
noValidate
|
|
291
|
+
onClick={submitWithoutNavigation}
|
|
289
292
|
onSubmit={async (event) => {
|
|
290
293
|
event.preventDefault();
|
|
291
294
|
if (!knowledgeLifecycleEnabled.value || busy) return;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { sendSteering } from '../state/session-store';
|
|
21
21
|
import { hasUnsavedMarkdown } from '../nodes/MarkdownNode';
|
|
22
22
|
import { cleanLifecycle, DEFAULT_LIFECYCLE, LifecycleFields } from './LifecycleFields';
|
|
23
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
23
24
|
|
|
24
25
|
export function LifecyclePanel() {
|
|
25
26
|
const target = lifecycleTarget.value;
|
|
@@ -264,6 +265,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
264
265
|
<form
|
|
265
266
|
class="lifecycle-quick-review"
|
|
266
267
|
noValidate
|
|
268
|
+
onClick={submitWithoutNavigation}
|
|
267
269
|
onSubmit={(event) => {
|
|
268
270
|
event.preventDefault();
|
|
269
271
|
void recordReview('retain', true).then((ok) => {
|
|
@@ -345,6 +347,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
345
347
|
· category belongs to the folder
|
|
346
348
|
</p>
|
|
347
349
|
<form
|
|
350
|
+
onClick={submitWithoutNavigation}
|
|
348
351
|
onSubmit={(event) => {
|
|
349
352
|
event.preventDefault();
|
|
350
353
|
void mutate('/lifecycle', { lifecycleOverride: override ? cleanLifecycle(policy) : null });
|
|
@@ -446,6 +449,7 @@ function LifecycleDialog({ target }: { target: NonNullable<typeof lifecycleTarge
|
|
|
446
449
|
) : (
|
|
447
450
|
<form
|
|
448
451
|
class="lifecycle-review"
|
|
452
|
+
onClick={submitWithoutNavigation}
|
|
449
453
|
onSubmit={(event) => {
|
|
450
454
|
event.preventDefault();
|
|
451
455
|
void recordReview(outcome, applied);
|
|
@@ -4,6 +4,7 @@ import { boardList, updateBoard } from '../state/boards-store';
|
|
|
4
4
|
import { requestJson } from '../state/intent-bridge';
|
|
5
5
|
import { knowledge, refreshKnowledge } from '../state/knowledge-store';
|
|
6
6
|
import { useFocusTrap } from './use-focus-trap';
|
|
7
|
+
import { submitWithoutNavigation } from './submit-without-navigation';
|
|
7
8
|
|
|
8
9
|
/** Shared templates live in Home; applying one never rewrites connection kinds. */
|
|
9
10
|
export function RelationPresetsDialog({ onClose }: { onClose: () => void }) {
|
|
@@ -152,6 +153,7 @@ export function RelationPresetsDialog({ onClose }: { onClose: () => void }) {
|
|
|
152
153
|
{editing ? (
|
|
153
154
|
<form
|
|
154
155
|
class="relation-vocabulary"
|
|
156
|
+
onClick={submitWithoutNavigation}
|
|
155
157
|
onSubmit={async (event) => {
|
|
156
158
|
event.preventDefault();
|
|
157
159
|
if (busy) return;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
|
|
3
|
+
/** A scripts-only host blocks native submission before onSubmit can run. */
|
|
4
|
+
export function submitWithoutNavigation(event: JSX.TargetedMouseEvent<HTMLFormElement>): void {
|
|
5
|
+
if (event.defaultPrevented || !(event.target instanceof Element)) return;
|
|
6
|
+
const button = event.target.closest('button');
|
|
7
|
+
const form = event.currentTarget;
|
|
8
|
+
if (button?.type !== 'submit' || button.form !== form || button.matches(':disabled')) return;
|
|
9
|
+
// Enter in a form input also activates its submit button. Keep native
|
|
10
|
+
// validation, but dispatch only the JS event, never a navigation/submission.
|
|
11
|
+
event.preventDefault();
|
|
12
|
+
if (!form.noValidate && !button.formNoValidate && !form.reportValidity()) return;
|
|
13
|
+
form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true, submitter: button }));
|
|
14
|
+
}
|