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.
Files changed (161) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/canvas/index.js +65 -65
  3. package/dist/types/cli/agent.d.ts +9 -1
  4. package/dist/types/cli/daemon.d.ts +74 -0
  5. package/dist/types/cli/watch.d.ts +2 -2
  6. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  7. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  8. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  9. package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
  10. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  11. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  12. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  13. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  14. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  15. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  16. package/dist/types/server/canvas-schema.d.ts +1 -1
  17. package/dist/types/server/html-primitives.d.ts +1 -1
  18. package/dist/types/server/index.d.ts +4 -4
  19. package/dist/types/server/operations/index.d.ts +1 -1
  20. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  21. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  22. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  23. package/dist/types/server/server.d.ts +8 -0
  24. package/docs/cli.md +10 -1
  25. package/docs/http-api.md +28 -0
  26. package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
  27. package/docs/screenshot.png +0 -0
  28. package/docs/tech-debt-assessment-2026-07.md +2 -2
  29. package/package.json +5 -2
  30. package/skills/pmx-canvas/SKILL.md +15 -10
  31. package/skills/pmx-canvas/references/full-reference.md +17 -3
  32. package/src/cli/agent.ts +1951 -1571
  33. package/src/cli/daemon.ts +460 -0
  34. package/src/cli/index.ts +80 -323
  35. package/src/cli/watch.ts +2 -10
  36. package/src/client/App.tsx +48 -46
  37. package/src/client/canvas/AttentionHistory.tsx +11 -1
  38. package/src/client/canvas/CanvasNode.tsx +41 -29
  39. package/src/client/canvas/CanvasViewport.tsx +101 -66
  40. package/src/client/canvas/CommandPalette.tsx +61 -27
  41. package/src/client/canvas/ContextMenu.tsx +13 -20
  42. package/src/client/canvas/ContextPinBar.tsx +1 -5
  43. package/src/client/canvas/ContextPinHud.tsx +1 -6
  44. package/src/client/canvas/DockedNode.tsx +4 -4
  45. package/src/client/canvas/EdgeLayer.tsx +37 -36
  46. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  47. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  48. package/src/client/canvas/IntentLayer.tsx +31 -14
  49. package/src/client/canvas/Minimap.tsx +11 -16
  50. package/src/client/canvas/SelectionBar.tsx +4 -11
  51. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  52. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  53. package/src/client/canvas/auto-fit.ts +15 -14
  54. package/src/client/canvas/snap-guides.ts +12 -12
  55. package/src/client/canvas/use-node-resize.ts +1 -5
  56. package/src/client/canvas/use-pan-zoom.ts +25 -26
  57. package/src/client/ext-app/bridge.ts +3 -12
  58. package/src/client/icons.tsx +63 -20
  59. package/src/client/nodes/ContextNode.tsx +14 -25
  60. package/src/client/nodes/ExtAppFrame.tsx +194 -80
  61. package/src/client/nodes/FileNode.tsx +74 -62
  62. package/src/client/nodes/GroupNode.tsx +4 -6
  63. package/src/client/nodes/HtmlNode.tsx +76 -46
  64. package/src/client/nodes/ImageNode.tsx +18 -27
  65. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  66. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  67. package/src/client/nodes/LedgerNode.tsx +10 -4
  68. package/src/client/nodes/MarkdownNode.tsx +3 -10
  69. package/src/client/nodes/McpAppNode.tsx +26 -22
  70. package/src/client/nodes/MdFormatBar.tsx +10 -7
  71. package/src/client/nodes/PromptNode.tsx +23 -51
  72. package/src/client/nodes/ResponseNode.tsx +3 -13
  73. package/src/client/nodes/StatusNode.tsx +5 -9
  74. package/src/client/nodes/StatusSummary.tsx +2 -8
  75. package/src/client/nodes/WebpageNode.tsx +20 -14
  76. package/src/client/nodes/iframe-document-url.ts +25 -16
  77. package/src/client/nodes/image-warnings.ts +1 -7
  78. package/src/client/nodes/md-format.ts +20 -5
  79. package/src/client/state/attention-bridge.ts +4 -9
  80. package/src/client/state/attention-store.ts +1 -7
  81. package/src/client/state/canvas-store.ts +52 -36
  82. package/src/client/state/intent-bridge.ts +176 -112
  83. package/src/client/state/intent-store.ts +4 -1
  84. package/src/client/state/sse-bridge.ts +53 -70
  85. package/src/json-render/catalog.ts +12 -16
  86. package/src/json-render/charts/components.tsx +16 -20
  87. package/src/json-render/charts/extra-components.tsx +8 -16
  88. package/src/json-render/charts/extra-definitions.ts +1 -2
  89. package/src/json-render/charts/tufte-components.tsx +37 -20
  90. package/src/json-render/charts/tufte-definitions.ts +8 -2
  91. package/src/json-render/renderer/index.tsx +42 -22
  92. package/src/json-render/schema.ts +6 -3
  93. package/src/json-render/server.ts +33 -39
  94. package/src/mcp/canvas-access.ts +35 -21
  95. package/src/mcp/server.ts +132 -70
  96. package/src/server/agent-context.ts +63 -36
  97. package/src/server/ax-context.ts +7 -5
  98. package/src/server/ax-interaction.ts +176 -43
  99. package/src/server/ax-state-manager.ts +182 -39
  100. package/src/server/ax-state.ts +142 -47
  101. package/src/server/canvas-db.ts +213 -95
  102. package/src/server/canvas-operations.ts +180 -123
  103. package/src/server/canvas-provenance.ts +1 -4
  104. package/src/server/canvas-schema.ts +454 -73
  105. package/src/server/canvas-serialization.ts +27 -35
  106. package/src/server/canvas-state.ts +150 -58
  107. package/src/server/chart-template.ts +4 -5
  108. package/src/server/code-graph.ts +19 -6
  109. package/src/server/diagram-presets.ts +28 -29
  110. package/src/server/ext-app-lookup.ts +3 -12
  111. package/src/server/html-node-summary.ts +19 -10
  112. package/src/server/html-primitives.ts +326 -97
  113. package/src/server/html-surface.ts +6 -9
  114. package/src/server/image-source.ts +6 -4
  115. package/src/server/index.ts +320 -217
  116. package/src/server/intent-registry.ts +2 -5
  117. package/src/server/mcp-app-candidate.ts +5 -10
  118. package/src/server/mcp-app-host.ts +14 -38
  119. package/src/server/mcp-app-runtime.ts +12 -20
  120. package/src/server/mutation-history.ts +15 -5
  121. package/src/server/operations/composites.ts +1 -3
  122. package/src/server/operations/http.ts +2 -3
  123. package/src/server/operations/index.ts +7 -1
  124. package/src/server/operations/invoker.ts +4 -3
  125. package/src/server/operations/mcp.ts +22 -30
  126. package/src/server/operations/ops/annotation.ts +122 -10
  127. package/src/server/operations/ops/app.ts +98 -73
  128. package/src/server/operations/ops/ax-await.ts +17 -10
  129. package/src/server/operations/ops/ax-read.ts +347 -0
  130. package/src/server/operations/ops/ax-shared.ts +2 -7
  131. package/src/server/operations/ops/ax-state.ts +32 -14
  132. package/src/server/operations/ops/ax-timeline.ts +32 -19
  133. package/src/server/operations/ops/ax-work.ts +54 -37
  134. package/src/server/operations/ops/batch.ts +41 -15
  135. package/src/server/operations/ops/canvas-wire.ts +91 -0
  136. package/src/server/operations/ops/edges.ts +37 -25
  137. package/src/server/operations/ops/ext-app.ts +346 -0
  138. package/src/server/operations/ops/groups.ts +49 -20
  139. package/src/server/operations/ops/intent.ts +18 -12
  140. package/src/server/operations/ops/json-render.ts +239 -98
  141. package/src/server/operations/ops/nodes.ts +298 -109
  142. package/src/server/operations/ops/query.ts +46 -28
  143. package/src/server/operations/ops/snapshots.ts +35 -26
  144. package/src/server/operations/ops/validate.ts +2 -1
  145. package/src/server/operations/ops/viewport.ts +60 -16
  146. package/src/server/operations/ops/webview.ts +44 -18
  147. package/src/server/operations/registry.ts +2 -3
  148. package/src/server/operations/types.ts +7 -5
  149. package/src/server/operations/webview-runner.ts +1 -3
  150. package/src/server/placement.ts +8 -18
  151. package/src/server/server.ts +122 -1028
  152. package/src/server/spatial-analysis.ts +39 -25
  153. package/src/server/trace-manager.ts +3 -8
  154. package/src/server/web-artifacts.ts +23 -27
  155. package/src/server/webpage-node.ts +5 -13
  156. package/src/shared/auto-arrange.ts +12 -5
  157. package/src/shared/content-height-reporter.ts +8 -6
  158. package/src/shared/ext-app-tool-result.ts +2 -6
  159. package/src/shared/placement.ts +1 -4
  160. package/src/shared/semantic-attention.ts +39 -37
  161. package/src/shared/surface.ts +8 -4
@@ -1,10 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { canvasState } from './canvas-state.js';
3
3
  import type { CanvasAnnotation, CanvasLayout, CanvasNodeState, ViewportState } from './canvas-state.js';
4
- import {
5
- normalizeCanvasNodeData,
6
- type CanvasNodeProvenance,
7
- } from './canvas-provenance.js';
4
+ import { normalizeCanvasNodeData, type CanvasNodeProvenance } from './canvas-provenance.js';
8
5
  import { getCanvasNodeKind as getSharedCanvasNodeKind } from '../shared/canvas-node-kind.js';
9
6
  import { canOpenNodeAsSurface } from '../shared/surface.js';
10
7
 
@@ -79,34 +76,32 @@ export function getCanvasNodeKind(node: CanvasNodeState, data: Record<string, un
79
76
  }
80
77
 
81
78
  export function getCanvasNodeTitle(node: CanvasNodeState): string | null {
82
- return pickString(node.data.title)
83
- ?? (node.type === 'webpage' ? pickString(node.data.pageTitle) : null)
84
- ?? null;
79
+ return pickString(node.data.title) ?? (node.type === 'webpage' ? pickString(node.data.pageTitle) : null) ?? null;
85
80
  }
86
81
 
87
82
  export function getCanvasNodeContent(node: CanvasNodeState): string | null {
88
83
  if (node.type === 'html') {
89
84
  const primitive = typeof node.data.htmlPrimitive === 'string' ? node.data.htmlPrimitive : null;
90
85
  const description = pickString(node.data.description);
91
- return pickString(node.data.agentSummary)
92
- ?? pickString(node.data.contentSummary)
93
- ?? (primitive
94
- ? (description ? `${primitive}: ${description}` : primitive)
95
- : null);
86
+ return (
87
+ pickString(node.data.agentSummary) ??
88
+ pickString(node.data.contentSummary) ??
89
+ (primitive ? (description ? `${primitive}: ${description}` : primitive) : null)
90
+ );
96
91
  }
97
- return pickString(node.data.content)
98
- ?? pickString(node.data.fileContent)
99
- ?? pickString(node.data.text)
100
- ?? (node.type === 'file' ? pickString(node.data.path) : null)
101
- ?? (node.type === 'image' ? pickString(node.data.src) : null)
102
- ?? (node.type === 'webpage' ? pickString(node.data.url) : null)
103
- ?? null;
92
+ return (
93
+ pickString(node.data.content) ??
94
+ pickString(node.data.fileContent) ??
95
+ pickString(node.data.text) ??
96
+ (node.type === 'file' ? pickString(node.data.path) : null) ??
97
+ (node.type === 'image' ? pickString(node.data.src) : null) ??
98
+ (node.type === 'webpage' ? pickString(node.data.url) : null) ??
99
+ null
100
+ );
104
101
  }
105
102
 
106
103
  export function getCanvasNodeSurfaceUrl(node: CanvasNodeState, data: Record<string, unknown>): string | null {
107
- return canOpenNodeAsSurface(node.type, data)
108
- ? `/api/canvas/surface/${encodeURIComponent(node.id)}`
109
- : null;
104
+ return canOpenNodeAsSurface(node.type, data) ? `/api/canvas/surface/${encodeURIComponent(node.id)}` : null;
110
105
  }
111
106
 
112
107
  export function serializeCanvasNode(node: CanvasNodeState): SerializedCanvasNode {
@@ -236,22 +231,21 @@ function rectsOverlap(
236
231
  a: { x: number; y: number; width: number; height: number },
237
232
  b: { x: number; y: number; width: number; height: number },
238
233
  ): boolean {
239
- return a.x <= b.x + b.width &&
240
- a.x + a.width >= b.x &&
241
- a.y <= b.y + b.height &&
242
- a.y + a.height >= b.y;
234
+ return a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
243
235
  }
244
236
 
245
237
  export function summarizeCanvasAnnotationForContext(
246
238
  annotation: CanvasAnnotation,
247
239
  nodes: CanvasNodeState[],
248
240
  ): CanvasAnnotationContextSummary {
249
- const targetNodes = nodes.filter((node) => rectsOverlap(annotation.bounds, {
250
- x: node.position.x,
251
- y: node.position.y,
252
- width: node.size.width,
253
- height: node.size.height,
254
- }));
241
+ const targetNodes = nodes.filter((node) =>
242
+ rectsOverlap(annotation.bounds, {
243
+ x: node.position.x,
244
+ y: node.position.y,
245
+ width: node.size.width,
246
+ height: node.size.height,
247
+ }),
248
+ );
255
249
  const targetNodeTitles = targetNodes.map((node) => getCanvasNodeTitle(node) ?? node.id);
256
250
  return {
257
251
  id: annotation.id,
@@ -284,9 +278,7 @@ export function buildCanvasSummary(): CanvasSummary {
284
278
  typeCounts[kind] = (typeCounts[kind] ?? 0) + 1;
285
279
  }
286
280
 
287
- const pinnedTitles = layout.nodes
288
- .filter((n) => pinnedIds.has(n.id))
289
- .map((n) => getCanvasNodeTitle(n) ?? n.id);
281
+ const pinnedTitles = layout.nodes.filter((n) => pinnedIds.has(n.id)).map((n) => getCanvasNodeTitle(n) ?? n.id);
290
282
 
291
283
  return {
292
284
  totalNodes: layout.nodes.length,
@@ -11,7 +11,16 @@
11
11
  */
12
12
 
13
13
  import { createHash } from 'node:crypto';
14
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, renameSync, rmSync, unlinkSync } from 'node:fs';
14
+ import {
15
+ existsSync,
16
+ readFileSync,
17
+ writeFileSync,
18
+ mkdirSync,
19
+ readdirSync,
20
+ renameSync,
21
+ rmSync,
22
+ unlinkSync,
23
+ } from 'node:fs';
15
24
  import { isAbsolute, join, dirname, relative } from 'node:path';
16
25
  import { gzipSync, gunzipSync } from 'node:zlib';
17
26
  import { normalizeCanvasNodeData } from './canvas-provenance.js';
@@ -123,9 +132,15 @@ interface LoadFromDiskOptions {
123
132
  }
124
133
 
125
134
  export const IMAGE_MIME_MAP: Record<string, string> = {
126
- png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
127
- gif: 'image/gif', svg: 'image/svg+xml', webp: 'image/webp',
128
- bmp: 'image/bmp', ico: 'image/x-icon', avif: 'image/avif',
135
+ png: 'image/png',
136
+ jpg: 'image/jpeg',
137
+ jpeg: 'image/jpeg',
138
+ gif: 'image/gif',
139
+ svg: 'image/svg+xml',
140
+ webp: 'image/webp',
141
+ bmp: 'image/bmp',
142
+ ico: 'image/x-icon',
143
+ avif: 'image/avif',
129
144
  };
130
145
 
131
146
  export interface CanvasSnapshot {
@@ -235,7 +250,34 @@ export interface CanvasNodeUpdate {
235
250
  export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
236
251
 
237
252
  export interface MutationRecordInfo {
238
- operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
253
+ operationType:
254
+ | 'addNode'
255
+ | 'updateNode'
256
+ | 'removeNode'
257
+ | 'addEdge'
258
+ | 'removeEdge'
259
+ | 'addAnnotation'
260
+ | 'removeAnnotation'
261
+ | 'clear'
262
+ | 'restoreSnapshot'
263
+ | 'setPins'
264
+ | 'setAxFocus'
265
+ | 'addWorkItem'
266
+ | 'updateWorkItem'
267
+ | 'requestApproval'
268
+ | 'resolveApproval'
269
+ | 'addReviewAnnotation'
270
+ | 'updateReviewAnnotation'
271
+ | 'requestElicitation'
272
+ | 'respondElicitation'
273
+ | 'requestMode'
274
+ | 'resolveModeRequest'
275
+ | 'setPolicy'
276
+ | 'arrange'
277
+ | 'batch'
278
+ | 'groupNodes'
279
+ | 'ungroupNodes'
280
+ | 'viewport';
239
281
  description: string;
240
282
  forward: () => void;
241
283
  inverse: () => void;
@@ -287,13 +329,15 @@ function replaceById<T extends { id: string }>(list: T[], item: T): T[] {
287
329
  }
288
330
 
289
331
  function isPersistedBlobRef(value: unknown): value is PersistedBlobRef {
290
- return isRecord(value) &&
332
+ return (
333
+ isRecord(value) &&
291
334
  value.__pmxCanvasBlob === 'v1' &&
292
335
  typeof value.path === 'string' &&
293
336
  typeof value.sha256 === 'string' &&
294
337
  value.encoding === 'json+gzip' &&
295
338
  typeof value.bytes === 'number' &&
296
- typeof value.jsonBytes === 'number';
339
+ typeof value.jsonBytes === 'number'
340
+ );
297
341
  }
298
342
 
299
343
  class CanvasStateManager {
@@ -360,7 +404,11 @@ class CanvasStateManager {
360
404
  /** Run a function with mutation recording suppressed (for undo/redo replay and computed edges). */
361
405
  withSuppressedRecording(fn: () => void): void {
362
406
  this._suppressRecordingDepth++;
363
- try { fn(); } finally { this._suppressRecordingDepth--; }
407
+ try {
408
+ fn();
409
+ } finally {
410
+ this._suppressRecordingDepth--;
411
+ }
364
412
  }
365
413
 
366
414
  /** Create a closure that runs with recording suppressed. */
@@ -388,9 +436,8 @@ class CanvasStateManager {
388
436
  bounds: { x: number; y: number; width: number; height: number },
389
437
  existingGroups?: CanvasPlacementRect[],
390
438
  ): void {
391
- const otherGroups = existingGroups ?? Array.from(this.nodes.values()).filter(
392
- (node) => node.id !== groupId && node.type === 'group',
393
- );
439
+ const otherGroups =
440
+ existingGroups ?? Array.from(this.nodes.values()).filter((node) => node.id !== groupId && node.type === 'group');
394
441
  const resolved = resolveGroupCollision(bounds, otherGroups);
395
442
  const deltaX = resolved.x - bounds.x;
396
443
  const deltaY = resolved.y - bounds.y;
@@ -472,7 +519,12 @@ class CanvasStateManager {
472
519
  }
473
520
  }
474
521
 
475
- private translateGroupChildren(groupId: string, deltaX: number, deltaY: number, skipIds: ReadonlySet<string> = new Set()): void {
522
+ private translateGroupChildren(
523
+ groupId: string,
524
+ deltaX: number,
525
+ deltaY: number,
526
+ skipIds: ReadonlySet<string> = new Set(),
527
+ ): void {
476
528
  if (deltaX === 0 && deltaY === 0) return;
477
529
  const snapshot = this.getGroupSnapshot(groupId);
478
530
  if (!snapshot) return;
@@ -509,9 +561,7 @@ class CanvasStateManager {
509
561
  const snapshot = this.getGroupSnapshot(groupId);
510
562
  if (!snapshot || snapshot.children.length === 0) return;
511
563
  if (snapshot.group.data.frameMode === 'manual') {
512
- const sorted = [...snapshot.children].sort(
513
- (a, b) => a.position.y - b.position.y || a.position.x - b.position.x,
514
- );
564
+ const sorted = [...snapshot.children].sort((a, b) => a.position.y - b.position.y || a.position.x - b.position.x);
515
565
  const left = snapshot.group.position.x + GROUP_PAD;
516
566
  const top = snapshot.group.position.y + GROUP_TITLEBAR_HEIGHT + GROUP_PAD;
517
567
  const right = snapshot.group.position.x + snapshot.group.size.width - GROUP_PAD;
@@ -595,9 +645,10 @@ class CanvasStateManager {
595
645
  }
596
646
 
597
647
  // Keep legacy _stateFilePath for JSON migration detection
598
- this._stateFilePath = stateFileOverride && !stateFileOverride.endsWith('.db')
599
- ? stateFileOverride
600
- : join(workspaceRoot, PMX_CANVAS_DIR, STATE_FILENAME);
648
+ this._stateFilePath =
649
+ stateFileOverride && !stateFileOverride.endsWith('.db')
650
+ ? stateFileOverride
651
+ : join(workspaceRoot, PMX_CANVAS_DIR, STATE_FILENAME);
601
652
 
602
653
  try {
603
654
  this._db = openCanvasDb(dbPath);
@@ -749,9 +800,7 @@ class CanvasStateManager {
749
800
  private externalizePersistedStateBlobs<T extends PersistedCanvasState>(state: T): T {
750
801
  return {
751
802
  ...state,
752
- nodes: Array.isArray(state.nodes)
753
- ? state.nodes.map((node) => this.externalizeNodeDataBlobs(node))
754
- : [],
803
+ nodes: Array.isArray(state.nodes) ? state.nodes.map((node) => this.externalizeNodeDataBlobs(node)) : [],
755
804
  };
756
805
  }
757
806
 
@@ -1160,7 +1209,9 @@ class CanvasStateManager {
1160
1209
  if (!dir || !existsSync(dir)) return [];
1161
1210
 
1162
1211
  try {
1163
- const files = readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
1212
+ const files = readdirSync(dir)
1213
+ .filter((f) => f.endsWith('.json'))
1214
+ .sort();
1164
1215
  const snapshots: CanvasSnapshot[] = [];
1165
1216
  for (const file of files) {
1166
1217
  try {
@@ -1232,7 +1283,9 @@ class CanvasStateManager {
1232
1283
  viewport: structuredClone(resolved.state.viewport),
1233
1284
  nodes: Array.isArray(resolved.state.nodes) ? resolved.state.nodes.map((node) => structuredClone(node)) : [],
1234
1285
  edges: Array.isArray(resolved.state.edges) ? resolved.state.edges.map((edge) => structuredClone(edge)) : [],
1235
- annotations: Array.isArray(resolved.state.annotations) ? resolved.state.annotations.map((annotation) => structuredClone(annotation)) : [],
1286
+ annotations: Array.isArray(resolved.state.annotations)
1287
+ ? resolved.state.annotations.map((annotation) => structuredClone(annotation))
1288
+ : [],
1236
1289
  contextPins: Array.isArray(resolved.state.contextPins) ? [...resolved.state.contextPins] : [],
1237
1290
  ax: resolved.state.ax ? structuredClone(resolved.state.ax) : createEmptyAxState(),
1238
1291
  };
@@ -1273,7 +1326,9 @@ class CanvasStateManager {
1273
1326
  }
1274
1327
 
1275
1328
  /** Read a snapshot's data without restoring it (for diff). Resolves by ID or name. */
1276
- getSnapshotData(idOrName: string): { name: string; nodes: CanvasNodeState[]; edges: CanvasEdge[]; annotations: CanvasAnnotation[] } | null {
1329
+ getSnapshotData(
1330
+ idOrName: string,
1331
+ ): { name: string; nodes: CanvasNodeState[]; edges: CanvasEdge[]; annotations: CanvasAnnotation[] } | null {
1277
1332
  const resolved = this.readResolvedSnapshot(idOrName);
1278
1333
  if (!resolved) return null;
1279
1334
  const state = {
@@ -1286,7 +1341,9 @@ class CanvasStateManager {
1286
1341
  name: resolved.snapshot.name,
1287
1342
  nodes: Array.isArray(state.nodes) ? state.nodes.map((node) => structuredClone(node)) : [],
1288
1343
  edges: Array.isArray(state.edges) ? state.edges.map((edge) => structuredClone(edge)) : [],
1289
- annotations: Array.isArray(state.annotations) ? state.annotations.map((annotation) => structuredClone(annotation)) : [],
1344
+ annotations: Array.isArray(state.annotations)
1345
+ ? state.annotations.map((annotation) => structuredClone(annotation))
1346
+ : [],
1290
1347
  };
1291
1348
  }
1292
1349
 
@@ -1344,9 +1401,10 @@ class CanvasStateManager {
1344
1401
  // (including undock → dockPosition null) are respected (see normalizeNode).
1345
1402
  // Skip during suppressed replay (undo/redo re-add) so a deliberately-undocked
1346
1403
  // context node is restored verbatim instead of being snapped back to the dock.
1347
- const seeded = node.type === 'context' && node.dockPosition == null && this._suppressRecordingDepth === 0
1348
- ? { ...node, dockPosition: 'right' as const, collapsed: true }
1349
- : node;
1404
+ const seeded =
1405
+ node.type === 'context' && node.dockPosition == null && this._suppressRecordingDepth === 0
1406
+ ? { ...node, dockPosition: 'right' as const, collapsed: true }
1407
+ : node;
1350
1408
  const cloned = structuredClone(this.normalizeNode(seeded));
1351
1409
  this.nodes.set(node.id, cloned);
1352
1410
  this.scheduleSave();
@@ -1372,11 +1430,7 @@ class CanvasStateManager {
1372
1430
  if (!existing) return;
1373
1431
  const oldSnapshot = structuredClone(existing);
1374
1432
  if (existing.type === 'group' && patch.position) {
1375
- this.translateGroupChildren(
1376
- id,
1377
- patch.position.x - existing.position.x,
1378
- patch.position.y - existing.position.y,
1379
- );
1433
+ this.translateGroupChildren(id, patch.position.x - existing.position.x, patch.position.y - existing.position.y);
1380
1434
  }
1381
1435
  const nextNode = this.normalizeNode({ ...existing, ...patch });
1382
1436
  this.nodes.set(id, nextNode);
@@ -1395,7 +1449,11 @@ class CanvasStateManager {
1395
1449
  operationType: 'updateNode',
1396
1450
  description: `Updated node "${(existing.data.title as string) ?? id}"`,
1397
1451
  forward: this.suppressed(() => this.updateNode(id, structuredClone(patch))),
1398
- inverse: this.suppressed(() => { this.nodes.set(id, structuredClone(oldSnapshot)); this.scheduleSave(); this.notifyChange('nodes'); }),
1452
+ inverse: this.suppressed(() => {
1453
+ this.nodes.set(id, structuredClone(oldSnapshot));
1454
+ this.scheduleSave();
1455
+ this.notifyChange('nodes');
1456
+ }),
1399
1457
  });
1400
1458
  }
1401
1459
 
@@ -1448,7 +1506,8 @@ class CanvasStateManager {
1448
1506
  // original deletion already recorded the note, so replaying must NOT append a
1449
1507
  // duplicate (the timeline is append-only). `revalidateAfterNodeRemoval` above
1450
1508
  // still runs unconditionally — only the timeline note is gated.
1451
- const affected = orphaned.reanchoredIds.length > 0 || orphaned.removedReviewIds.length > 0 || orphaned.reanchoredFocus;
1509
+ const affected =
1510
+ orphaned.reanchoredIds.length > 0 || orphaned.removedReviewIds.length > 0 || orphaned.reanchoredFocus;
1452
1511
  if (existing && this._suppressRecordingDepth === 0 && affected) {
1453
1512
  const title = (existing.data.title as string) ?? id;
1454
1513
  const focusNote = orphaned.reanchoredFocus ? ' (focus anchor cleared)' : '';
@@ -1611,9 +1670,7 @@ class CanvasStateManager {
1611
1670
  const oldSnapshots = new Map<string, CanvasNodeState>();
1612
1671
  const appliedUpdates: CanvasNodeUpdate[] = [];
1613
1672
  const explicitPositionUpdateIds = new Set(
1614
- updates
1615
- .filter((update) => update.position !== undefined)
1616
- .map((update) => update.id),
1673
+ updates.filter((update) => update.position !== undefined).map((update) => update.id),
1617
1674
  );
1618
1675
 
1619
1676
  for (const update of updates) {
@@ -1623,16 +1680,10 @@ class CanvasStateManager {
1623
1680
  continue;
1624
1681
  }
1625
1682
  const nextPatch: Partial<CanvasNodeState> = {};
1626
- if (
1627
- update.position &&
1628
- (update.position.x !== existing.position.x || update.position.y !== existing.position.y)
1629
- ) {
1683
+ if (update.position && (update.position.x !== existing.position.x || update.position.y !== existing.position.y)) {
1630
1684
  nextPatch.position = update.position;
1631
1685
  }
1632
- if (
1633
- update.size &&
1634
- (update.size.width !== existing.size.width || update.size.height !== existing.size.height)
1635
- ) {
1686
+ if (update.size && (update.size.width !== existing.size.width || update.size.height !== existing.size.height)) {
1636
1687
  nextPatch.size = update.size;
1637
1688
  }
1638
1689
  if (update.collapsed !== undefined && update.collapsed !== existing.collapsed) {
@@ -1655,10 +1706,13 @@ class CanvasStateManager {
1655
1706
  explicitPositionUpdateIds,
1656
1707
  );
1657
1708
  }
1658
- this.nodes.set(update.id, this.normalizeNode({
1659
- ...existing,
1660
- ...nextPatch,
1661
- }));
1709
+ this.nodes.set(
1710
+ update.id,
1711
+ this.normalizeNode({
1712
+ ...existing,
1713
+ ...nextPatch,
1714
+ }),
1715
+ );
1662
1716
  const parentGroupId = existing.data.parentGroup as string | undefined;
1663
1717
  if (parentGroupId) {
1664
1718
  touchedParentGroups.add(parentGroupId);
@@ -1683,7 +1737,10 @@ class CanvasStateManager {
1683
1737
  operationType: 'batch',
1684
1738
  description: formatBatchUpdateDescription(appliedUpdates),
1685
1739
  forward: this.suppressed(() => {
1686
- this.applyUpdates(appliedUpdates.map((update) => structuredClone(update)), options);
1740
+ this.applyUpdates(
1741
+ appliedUpdates.map((update) => structuredClone(update)),
1742
+ options,
1743
+ );
1687
1744
  }),
1688
1745
  inverse: this.suppressed(() => {
1689
1746
  for (const snapshot of inverseSnapshots) {
@@ -1883,7 +1940,11 @@ class CanvasStateManager {
1883
1940
  }
1884
1941
 
1885
1942
  /** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
1886
- invokeCommand(name: string, args: Record<string, unknown> | null = null, options: { source?: PmxAxSource } = {}): PmxAxEvent | null {
1943
+ invokeCommand(
1944
+ name: string,
1945
+ args: Record<string, unknown> | null = null,
1946
+ options: { source?: PmxAxSource } = {},
1947
+ ): PmxAxEvent | null {
1887
1948
  return this.ax.invokeCommand(name, args, options);
1888
1949
  }
1889
1950
 
@@ -1905,14 +1966,27 @@ class CanvasStateManager {
1905
1966
 
1906
1967
  // ── Timeline (DB-direct; NOT in _axState; NOT history-recorded) ───
1907
1968
  recordAxEvent(
1908
- input: { kind: PmxAxEventKind; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
1969
+ input: {
1970
+ kind: PmxAxEventKind;
1971
+ summary: string;
1972
+ detail?: string | null;
1973
+ nodeIds?: string[];
1974
+ data?: Record<string, unknown> | null;
1975
+ },
1909
1976
  options: { source?: PmxAxSource } = {},
1910
1977
  ): PmxAxEvent {
1911
1978
  return this.ax.recordAxEvent(input, options);
1912
1979
  }
1913
1980
 
1914
1981
  addEvidence(
1915
- input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
1982
+ input: {
1983
+ kind: PmxAxEvidenceKind;
1984
+ title: string;
1985
+ body?: string | null;
1986
+ ref?: string | null;
1987
+ nodeIds?: string[];
1988
+ data?: Record<string, unknown> | null;
1989
+ },
1916
1990
  options: { source?: PmxAxSource } = {},
1917
1991
  ): PmxAxEvidence {
1918
1992
  return this.ax.addEvidence(input, options);
@@ -1938,11 +2012,23 @@ class CanvasStateManager {
1938
2012
  reactions?: {
1939
2013
  workItem?: false | { status?: PmxAxWorkItemStatus; detail?: string | null };
1940
2014
  evidence?: false | { kind?: PmxAxEvidenceKind; body?: string | null };
1941
- review?: false | { severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind; anchorType?: PmxAxReviewAnchorType; nodeId?: string | null };
2015
+ review?:
2016
+ | false
2017
+ | {
2018
+ severity?: PmxAxReviewSeverity;
2019
+ kind?: PmxAxReviewKind;
2020
+ anchorType?: PmxAxReviewAnchorType;
2021
+ nodeId?: string | null;
2022
+ };
1942
2023
  };
1943
2024
  },
1944
2025
  options: { source?: PmxAxSource } = {},
1945
- ): { event: PmxAxEvent; workItem: PmxAxWorkItem | null; evidence: PmxAxEvidence | null; review: PmxAxReviewAnnotation | null } {
2026
+ ): {
2027
+ event: PmxAxEvent;
2028
+ workItem: PmxAxWorkItem | null;
2029
+ evidence: PmxAxEvidence | null;
2030
+ review: PmxAxReviewAnnotation | null;
2031
+ } {
1946
2032
  return this.ax.ingestActivity(input, options);
1947
2033
  }
1948
2034
 
@@ -1974,7 +2060,12 @@ class CanvasStateManager {
1974
2060
  return this.ax.getAxTimelineSummary();
1975
2061
  }
1976
2062
 
1977
- getAxTimeline(q: AxTimelineQuery = {}): { events: PmxAxEvent[]; evidence: PmxAxEvidence[]; steering: PmxAxSteeringMessage[]; summary: PmxAxTimelineSummary } {
2063
+ getAxTimeline(q: AxTimelineQuery = {}): {
2064
+ events: PmxAxEvent[];
2065
+ evidence: PmxAxEvidence[];
2066
+ steering: PmxAxSteeringMessage[];
2067
+ summary: PmxAxTimelineSummary;
2068
+ } {
1978
2069
  return this.ax.getAxTimeline(q);
1979
2070
  }
1980
2071
 
@@ -2056,7 +2147,8 @@ class CanvasStateManager {
2056
2147
  const c = this.nodes.get(id);
2057
2148
  if (!c) continue;
2058
2149
  const d = { ...c.data };
2059
- if (oldParent) d.parentGroup = oldParent; else delete d.parentGroup;
2150
+ if (oldParent) d.parentGroup = oldParent;
2151
+ else delete d.parentGroup;
2060
2152
  this.nodes.set(id, { ...c, data: d });
2061
2153
  }
2062
2154
  this.scheduleSave();
@@ -15,15 +15,14 @@ import { createRequire } from 'node:module';
15
15
 
16
16
  const require = createRequire(import.meta.url);
17
17
  const extAppsPackageDir = dirname(require.resolve('@modelcontextprotocol/ext-apps/package.json'));
18
- const extAppsRuntimeSource = readFileSync(
19
- join(extAppsPackageDir, 'dist', 'src', 'app-with-deps.js'),
20
- 'utf-8',
21
- );
18
+ const extAppsRuntimeSource = readFileSync(join(extAppsPackageDir, 'dist', 'src', 'app-with-deps.js'), 'utf-8');
22
19
  const appBindingMatch = extAppsRuntimeSource.match(/([A-Za-z_$][\w$]*) as App/);
23
20
  const transportBindingMatch = extAppsRuntimeSource.match(/([A-Za-z_$][\w$]*) as PostMessageTransport/);
24
21
 
25
22
  if (!appBindingMatch || !transportBindingMatch) {
26
- throw new Error('Failed to locate App or PostMessageTransport export bindings in @modelcontextprotocol/ext-apps runtime');
23
+ throw new Error(
24
+ 'Failed to locate App or PostMessageTransport export bindings in @modelcontextprotocol/ext-apps runtime',
25
+ );
27
26
  }
28
27
 
29
28
  const extAppsBootstrapSource = `${extAppsRuntimeSource}
@@ -123,9 +123,20 @@ function resolveImportPath(specifier: string, fromFilePath: string): string | nu
123
123
  }
124
124
 
125
125
  const RESOLVE_EXTENSIONS = [
126
- '', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.mts',
127
- '/index.ts', '/index.tsx', '/index.js', '/index.jsx',
128
- '.py', '.go', '.rs',
126
+ '',
127
+ '.ts',
128
+ '.tsx',
129
+ '.js',
130
+ '.jsx',
131
+ '.mjs',
132
+ '.mts',
133
+ '/index.ts',
134
+ '/index.tsx',
135
+ '/index.js',
136
+ '/index.jsx',
137
+ '.py',
138
+ '.go',
139
+ '.rs',
129
140
  ];
130
141
 
131
142
  function tryResolveWithExtensions(basePath: string): string | null {
@@ -179,7 +190,7 @@ function getFileNodes(): CanvasNodeState[] {
179
190
  */
180
191
  function buildPathIndex(fileNodes?: CanvasNodeState[]): Map<string, string> {
181
192
  const index = new Map<string, string>();
182
- for (const node of (fileNodes ?? getFileNodes())) {
193
+ for (const node of fileNodes ?? getFileNodes()) {
183
194
  index.set(node.data.path as string, node.id);
184
195
  }
185
196
  return index;
@@ -299,7 +310,7 @@ export function buildCodeGraphSummary(): CodeGraphSummary {
299
310
  path: relative(process.cwd(), node.data.path as string) || (node.data.path as string),
300
311
  title: (node.data.title as string) ?? null,
301
312
  imports: importSpecs.get(node.id) ?? [],
302
- importedBy: [...(inc)].map((id) => {
313
+ importedBy: [...inc].map((id) => {
303
314
  const path = idToPath.get(id);
304
315
  return path ? relative(process.cwd(), path) : id;
305
316
  }),
@@ -350,7 +361,9 @@ export function formatCodeGraph(summary: CodeGraphSummary): string {
350
361
  }
351
362
 
352
363
  if (summary.isolatedFiles.length > 0) {
353
- lines.push(`Isolated files (${summary.isolatedFiles.length}): ${summary.isolatedFiles.map((f) => f.title ?? f.path).join(', ')}`);
364
+ lines.push(
365
+ `Isolated files (${summary.isolatedFiles.length}): ${summary.isolatedFiles.map((f) => f.title ?? f.path).join(', ')}`,
366
+ );
354
367
  lines.push('');
355
368
  }
356
369