pmx-canvas 0.9.1 → 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.
Files changed (54) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +9 -3
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/start-mcp.mjs +29 -29
  4. package/CHANGELOG.md +19 -0
  5. package/Readme.md +1 -1
  6. package/dist/canvas/global.css +12 -17
  7. package/dist/canvas/index.js +58 -58
  8. package/dist/canvas/mcp-app.html +75 -80
  9. package/dist/json-render/index.js +50 -50
  10. package/dist/types/client/canvas/submit-without-navigation.d.ts +3 -0
  11. package/dist/types/client/state/intent-bridge.d.ts +0 -3
  12. package/dist/types/client/state/presence-store.d.ts +3 -3
  13. package/dist/types/client/types.d.ts +1 -1
  14. package/dist/types/server/canvas-state.d.ts +2 -0
  15. package/dist/types/server/index.d.ts +2 -2
  16. package/dist/types/server/knowledge-lifecycle.d.ts +11 -2
  17. package/dist/types/server/lifecycle-status.d.ts +3 -1
  18. package/dist/types/server/tour-control.d.ts +6 -3
  19. package/docs/bun-webview-integration.md +2 -2
  20. package/docs/cli.md +10 -1
  21. package/docs/design/Chrome.dc.html +1 -0
  22. package/docs/design.md +3 -1
  23. package/docs/mcp.md +5 -1
  24. package/docs/node-types.md +3 -0
  25. package/docs/plans/plan-019-post-0.9-roadmap.md +68 -9
  26. package/docs/sdk.md +7 -0
  27. package/package.json +2 -2
  28. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +9 -3
  29. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  30. package/plugins/pmx-canvas/plugin.json +1 -1
  31. package/plugins/pmx-canvas/runtime.json +1 -1
  32. package/plugins/pmx-canvas/start-mcp.mjs +29 -29
  33. package/skills/pmx-canvas/SKILL.md +4 -4
  34. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  35. package/skills/pmx-canvas/references/installing-pmx-canvas.md +11 -6
  36. package/src/client/canvas/BoardCopyDialog.tsx +2 -1
  37. package/src/client/canvas/KnowledgeDialog.tsx +3 -0
  38. package/src/client/canvas/LifecyclePanel.tsx +4 -0
  39. package/src/client/canvas/RelationPresetsDialog.tsx +2 -0
  40. package/src/client/canvas/submit-without-navigation.ts +14 -0
  41. package/src/client/state/canvas-store.ts +12 -24
  42. package/src/client/state/intent-bridge.ts +0 -6
  43. package/src/client/state/presence-store.ts +3 -3
  44. package/src/client/state/sse-bridge.ts +11 -6
  45. package/src/client/theme/global.css +12 -17
  46. package/src/server/canvas-state.ts +10 -0
  47. package/src/server/index.ts +10 -30
  48. package/src/server/knowledge-lifecycle.ts +66 -0
  49. package/src/server/lifecycle-status.ts +79 -7
  50. package/src/server/operations/ops/lifecycle.ts +3 -7
  51. package/src/server/operations/ops/nodes.ts +2 -5
  52. package/src/server/operations/ops/viewport.ts +3 -7
  53. package/src/server/server.ts +13 -10
  54. package/src/server/tour-control.ts +17 -8
@@ -0,0 +1,3 @@
1
+ import type { JSX } from 'preact';
2
+ /** A scripts-only host blocks native submission before onSubmit can run. */
3
+ export declare function submitWithoutNavigation(event: JSX.TargetedMouseEvent<HTMLFormElement>): void;
@@ -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 `agent-presence`
5
- * SSE snapshot (every change, including TTL expiry) and the connect-time read,
6
- * so this store never runs its own expiry ticker.
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<"image" | "board" | "file" | "markdown" | "mcp-app" | "webpage" | "json-render" | "graph" | "prompt" | "response" | "status" | "context" | "ledger" | "trace" | "diff" | "mermaid" | "html" | "group">;
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 { type Camera, type Tour, type TourStop } from '../shared/tour.js';
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 { type GraphNodeInput, type JsonRenderNodeInput, type JsonRenderSpec } from '../json-render/server.js';
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: "unknown" | "card" | "folder";
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: "unknown" | "card" | "folder";
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 declare function currentTour(): Tour;
19
- /** The stop the tour is on for the active board, or null when not touring. */
20
- export declare function tourPosition(tour?: Tour): number | null;
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;
@@ -12,7 +12,7 @@ This document tracks:
12
12
 
13
13
  **Implemented** for Phase 1.
14
14
 
15
- The repo now targets Bun `>=1.4.2`, and PMX Canvas ships an opt-in, server-owned WebView automation session that can:
15
+ The repo now targets Bun `>=1.4.4`, and PMX Canvas ships an opt-in, server-owned WebView automation session that can:
16
16
 
17
17
  - start a headless browser session for `/workbench`
18
18
  - report WebView runtime status
@@ -192,7 +192,7 @@ If `chromePath` or `chromeArgv` is provided, PMX Canvas forces a Chrome-backed s
192
192
 
193
193
  The repo now requires:
194
194
 
195
- - Bun `>=1.4.2`
195
+ - Bun `>=1.4.4`
196
196
 
197
197
  That change is reflected in:
198
198
 
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">&lt;/&gt; 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; ordinary documents keep their bottom-right Source button.
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
@@ -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-19):** proposed sequence, agreed in direction with the maintainer. Nothing
4
- here is started. 0.9.0 itself (the CHANGELOG's Unreleased section) still needs the owner's
5
- acceptance and release approval; this plan begins once it ships. Version numbers below are
6
- proposals, not assignments; each release still needs explicit approval and the
7
- [release gates](../RELEASE.md).
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.1 | Cleanup: tour debt, large-board costs, bounded due evaluator, MCP reconnect, lint | 12, 1 | S–M |
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.1 cleanup
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, HTTP and MCP
59
- return the same tour shape, including position. One camera-animation path remains.
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.1",
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",
@@ -123,6 +123,6 @@
123
123
  },
124
124
  "homepage": "https://github.com/pskoett/pmx-canvas#readme",
125
125
  "engines": {
126
- "bun": ">=1.4.2"
126
+ "bun": ">=1.4.4"
127
127
  }
128
128
  }
@@ -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
- await copilotSession?.send({ prompt: body.prompt });
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
- await copilotSession?.send({ prompt });
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
- send: async (message) => await copilotSession.send({ prompt: message }),
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),
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.9.1"
2
+ "version": "0.9.3"
3
3
  }
@@ -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.1",
5
+ "version": "0.9.3",
6
6
  "author": {
7
7
  "name": "pskoett",
8
8
  "url": "https://github.com/pskoett"
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.9.1"
2
+ "version": "0.9.3"
3
3
  }