pmx-canvas 0.3.0 → 0.3.1

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 (156) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/canvas/index.js +2 -2
  3. package/dist/types/cli/daemon.d.ts +74 -0
  4. package/dist/types/cli/watch.d.ts +2 -2
  5. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  6. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  7. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  8. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  9. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  10. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  11. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  12. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  13. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  14. package/dist/types/server/canvas-schema.d.ts +1 -1
  15. package/dist/types/server/html-primitives.d.ts +1 -1
  16. package/dist/types/server/index.d.ts +4 -4
  17. package/dist/types/server/operations/index.d.ts +1 -1
  18. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  19. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  20. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  21. package/docs/http-api.md +28 -0
  22. package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
  23. package/docs/tech-debt-assessment-2026-07.md +2 -2
  24. package/package.json +5 -2
  25. package/skills/pmx-canvas/SKILL.md +3 -1
  26. package/skills/pmx-canvas/references/full-reference.md +10 -3
  27. package/src/cli/agent.ts +1861 -1548
  28. package/src/cli/daemon.ts +460 -0
  29. package/src/cli/index.ts +63 -326
  30. package/src/cli/watch.ts +2 -10
  31. package/src/client/App.tsx +48 -46
  32. package/src/client/canvas/AttentionHistory.tsx +11 -1
  33. package/src/client/canvas/CanvasNode.tsx +41 -29
  34. package/src/client/canvas/CanvasViewport.tsx +101 -66
  35. package/src/client/canvas/CommandPalette.tsx +61 -27
  36. package/src/client/canvas/ContextMenu.tsx +13 -20
  37. package/src/client/canvas/ContextPinBar.tsx +1 -5
  38. package/src/client/canvas/ContextPinHud.tsx +1 -6
  39. package/src/client/canvas/DockedNode.tsx +4 -4
  40. package/src/client/canvas/EdgeLayer.tsx +37 -36
  41. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  42. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  43. package/src/client/canvas/IntentLayer.tsx +31 -14
  44. package/src/client/canvas/Minimap.tsx +11 -16
  45. package/src/client/canvas/SelectionBar.tsx +4 -11
  46. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  47. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  48. package/src/client/canvas/auto-fit.ts +15 -14
  49. package/src/client/canvas/snap-guides.ts +12 -12
  50. package/src/client/canvas/use-node-resize.ts +1 -5
  51. package/src/client/canvas/use-pan-zoom.ts +25 -26
  52. package/src/client/ext-app/bridge.ts +3 -12
  53. package/src/client/icons.tsx +63 -20
  54. package/src/client/nodes/ContextNode.tsx +14 -25
  55. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  56. package/src/client/nodes/FileNode.tsx +74 -62
  57. package/src/client/nodes/GroupNode.tsx +4 -6
  58. package/src/client/nodes/HtmlNode.tsx +76 -46
  59. package/src/client/nodes/ImageNode.tsx +18 -27
  60. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  61. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  62. package/src/client/nodes/LedgerNode.tsx +10 -4
  63. package/src/client/nodes/MarkdownNode.tsx +3 -10
  64. package/src/client/nodes/McpAppNode.tsx +26 -22
  65. package/src/client/nodes/MdFormatBar.tsx +10 -7
  66. package/src/client/nodes/PromptNode.tsx +23 -51
  67. package/src/client/nodes/ResponseNode.tsx +3 -13
  68. package/src/client/nodes/StatusNode.tsx +5 -9
  69. package/src/client/nodes/StatusSummary.tsx +2 -8
  70. package/src/client/nodes/WebpageNode.tsx +20 -14
  71. package/src/client/nodes/iframe-document-url.ts +25 -16
  72. package/src/client/nodes/image-warnings.ts +1 -7
  73. package/src/client/nodes/md-format.ts +20 -5
  74. package/src/client/state/attention-bridge.ts +4 -9
  75. package/src/client/state/attention-store.ts +1 -7
  76. package/src/client/state/canvas-store.ts +52 -36
  77. package/src/client/state/intent-bridge.ts +176 -112
  78. package/src/client/state/intent-store.ts +4 -1
  79. package/src/client/state/sse-bridge.ts +53 -70
  80. package/src/json-render/catalog.ts +12 -16
  81. package/src/json-render/charts/components.tsx +16 -20
  82. package/src/json-render/charts/extra-components.tsx +8 -16
  83. package/src/json-render/charts/extra-definitions.ts +1 -2
  84. package/src/json-render/charts/tufte-components.tsx +37 -20
  85. package/src/json-render/charts/tufte-definitions.ts +8 -2
  86. package/src/json-render/renderer/index.tsx +42 -22
  87. package/src/json-render/schema.ts +6 -3
  88. package/src/json-render/server.ts +33 -39
  89. package/src/mcp/canvas-access.ts +35 -21
  90. package/src/mcp/server.ts +132 -70
  91. package/src/server/agent-context.ts +63 -36
  92. package/src/server/ax-context.ts +7 -5
  93. package/src/server/ax-interaction.ts +176 -43
  94. package/src/server/ax-state-manager.ts +182 -39
  95. package/src/server/ax-state.ts +142 -47
  96. package/src/server/canvas-db.ts +213 -95
  97. package/src/server/canvas-operations.ts +177 -120
  98. package/src/server/canvas-provenance.ts +1 -4
  99. package/src/server/canvas-schema.ts +454 -73
  100. package/src/server/canvas-serialization.ts +27 -35
  101. package/src/server/canvas-state.ts +150 -58
  102. package/src/server/chart-template.ts +4 -5
  103. package/src/server/code-graph.ts +19 -6
  104. package/src/server/diagram-presets.ts +28 -29
  105. package/src/server/ext-app-lookup.ts +3 -12
  106. package/src/server/html-node-summary.ts +19 -10
  107. package/src/server/html-primitives.ts +326 -97
  108. package/src/server/html-surface.ts +6 -9
  109. package/src/server/image-source.ts +6 -4
  110. package/src/server/index.ts +320 -217
  111. package/src/server/intent-registry.ts +2 -5
  112. package/src/server/mcp-app-candidate.ts +5 -10
  113. package/src/server/mcp-app-host.ts +14 -38
  114. package/src/server/mcp-app-runtime.ts +12 -20
  115. package/src/server/mutation-history.ts +15 -5
  116. package/src/server/operations/composites.ts +1 -3
  117. package/src/server/operations/http.ts +2 -3
  118. package/src/server/operations/index.ts +7 -1
  119. package/src/server/operations/invoker.ts +4 -3
  120. package/src/server/operations/mcp.ts +22 -30
  121. package/src/server/operations/ops/annotation.ts +122 -10
  122. package/src/server/operations/ops/app.ts +98 -73
  123. package/src/server/operations/ops/ax-await.ts +17 -10
  124. package/src/server/operations/ops/ax-read.ts +347 -0
  125. package/src/server/operations/ops/ax-shared.ts +2 -7
  126. package/src/server/operations/ops/ax-state.ts +32 -14
  127. package/src/server/operations/ops/ax-timeline.ts +32 -19
  128. package/src/server/operations/ops/ax-work.ts +54 -37
  129. package/src/server/operations/ops/batch.ts +39 -14
  130. package/src/server/operations/ops/canvas-wire.ts +91 -0
  131. package/src/server/operations/ops/edges.ts +37 -25
  132. package/src/server/operations/ops/ext-app.ts +346 -0
  133. package/src/server/operations/ops/groups.ts +49 -20
  134. package/src/server/operations/ops/intent.ts +18 -12
  135. package/src/server/operations/ops/json-render.ts +239 -98
  136. package/src/server/operations/ops/nodes.ts +298 -109
  137. package/src/server/operations/ops/query.ts +46 -28
  138. package/src/server/operations/ops/snapshots.ts +35 -26
  139. package/src/server/operations/ops/validate.ts +2 -1
  140. package/src/server/operations/ops/viewport.ts +60 -16
  141. package/src/server/operations/ops/webview.ts +44 -18
  142. package/src/server/operations/registry.ts +2 -3
  143. package/src/server/operations/types.ts +7 -5
  144. package/src/server/operations/webview-runner.ts +1 -3
  145. package/src/server/placement.ts +8 -18
  146. package/src/server/server.ts +108 -1027
  147. package/src/server/spatial-analysis.ts +39 -25
  148. package/src/server/trace-manager.ts +3 -8
  149. package/src/server/web-artifacts.ts +23 -27
  150. package/src/server/webpage-node.ts +5 -13
  151. package/src/shared/auto-arrange.ts +12 -5
  152. package/src/shared/content-height-reporter.ts +8 -6
  153. package/src/shared/ext-app-tool-result.ts +2 -6
  154. package/src/shared/placement.ts +1 -4
  155. package/src/shared/semantic-attention.ts +39 -37
  156. package/src/shared/surface.ts +8 -4
@@ -152,7 +152,8 @@ function boundTextRefId(value: unknown): string | null {
152
152
  function hasRenderableExcalidrawElement(elements: Array<Record<string, unknown>>): boolean {
153
153
  return elements.some((element) => {
154
154
  if (element.isDeleted === true) return false;
155
- if (element.type === 'cameraUpdate' || element.type === 'restoreCheckpoint' || element.type === 'delete') return false;
155
+ if (element.type === 'cameraUpdate' || element.type === 'restoreCheckpoint' || element.type === 'delete')
156
+ return false;
156
157
  if (typeof element.type !== 'string' || element.type.length === 0) return false;
157
158
  if (element.type === 'text') {
158
159
  return typeof element.text === 'string' && element.text.trim().length > 0;
@@ -173,7 +174,12 @@ function normalizeExcalidrawBoundText(elements: Array<Record<string, unknown>>):
173
174
  const collapsedTextIds = new Set<string>();
174
175
 
175
176
  for (const container of elements) {
176
- if (!isTextBindableContainer(container) || typeof container.id !== 'string' || !Array.isArray(container.boundElements)) continue;
177
+ if (
178
+ !isTextBindableContainer(container) ||
179
+ typeof container.id !== 'string' ||
180
+ !Array.isArray(container.boundElements)
181
+ )
182
+ continue;
177
183
  for (const rawBoundElement of container.boundElements) {
178
184
  const textId = boundTextRefId(rawBoundElement);
179
185
  if (!textId) continue;
@@ -187,9 +193,8 @@ function normalizeExcalidrawBoundText(elements: Array<Record<string, unknown>>):
187
193
 
188
194
  for (const element of elements) {
189
195
  if (element.type !== 'text' || typeof element.id !== 'string') continue;
190
- const containerId = typeof element.containerId === 'string'
191
- ? element.containerId
192
- : containerIdByTextId.get(element.id);
196
+ const containerId =
197
+ typeof element.containerId === 'string' ? element.containerId : containerIdByTextId.get(element.id);
193
198
  if (!containerId) continue;
194
199
  const container = elementsById.get(containerId);
195
200
  if (!container || !isTextBindableContainer(container)) continue;
@@ -227,9 +232,7 @@ function normalizeExcalidrawBoundText(elements: Array<Record<string, unknown>>):
227
232
  function resolveExcalidrawCameraSize(width: number, height: number): { width: number; height: number } {
228
233
  const requiredWidth = Math.max(EXCALIDRAW_MIN_CAMERA_WIDTH, width);
229
234
  const requiredHeight = Math.max(EXCALIDRAW_MIN_CAMERA_HEIGHT, height);
230
- const standard = EXCALIDRAW_CAMERA_SIZES.find(
231
- (size) => size.width >= requiredWidth && size.height >= requiredHeight,
232
- );
235
+ const standard = EXCALIDRAW_CAMERA_SIZES.find((size) => size.width >= requiredWidth && size.height >= requiredHeight);
233
236
  if (standard) return standard;
234
237
 
235
238
  const heightFromWidth = requiredWidth / EXCALIDRAW_CAMERA_ASPECT_RATIO;
@@ -241,9 +244,7 @@ function resolveExcalidrawCameraSize(width: number, height: number): { width: nu
241
244
  };
242
245
  }
243
246
 
244
- export function inferExcalidrawCameraUpdate(
245
- elements: Array<Record<string, unknown>>,
246
- ): Record<string, unknown> | null {
247
+ export function inferExcalidrawCameraUpdate(elements: Array<Record<string, unknown>>): Record<string, unknown> | null {
247
248
  let minX = Number.POSITIVE_INFINITY;
248
249
  let minY = Number.POSITIVE_INFINITY;
249
250
  let maxX = Number.NEGATIVE_INFINITY;
@@ -257,7 +258,12 @@ export function inferExcalidrawCameraUpdate(
257
258
  };
258
259
 
259
260
  for (const element of elements) {
260
- if (element.isDeleted === true || element.type === 'cameraUpdate' || element.type === 'restoreCheckpoint' || element.type === 'delete') {
261
+ if (
262
+ element.isDeleted === true ||
263
+ element.type === 'cameraUpdate' ||
264
+ element.type === 'restoreCheckpoint' ||
265
+ element.type === 'delete'
266
+ ) {
261
267
  continue;
262
268
  }
263
269
 
@@ -287,10 +293,7 @@ export function inferExcalidrawCameraUpdate(
287
293
 
288
294
  const contentWidth = Math.max(1, maxX - minX);
289
295
  const contentHeight = Math.max(1, maxY - minY);
290
- const padding = Math.max(
291
- EXCALIDRAW_CAMERA_PADDING,
292
- Math.round(Math.max(contentWidth, contentHeight) * 0.18),
293
- );
296
+ const padding = Math.max(EXCALIDRAW_CAMERA_PADDING, Math.round(Math.max(contentWidth, contentHeight) * 0.18));
294
297
  const camera = resolveExcalidrawCameraSize(contentWidth + padding * 2, contentHeight + padding * 2);
295
298
  const centerX = minX + contentWidth / 2;
296
299
  const centerY = minY + contentHeight / 2;
@@ -304,9 +307,7 @@ export function inferExcalidrawCameraUpdate(
304
307
  };
305
308
  }
306
309
 
307
- function withInferredCameraUpdate(
308
- elements: Array<Record<string, unknown>>,
309
- ): Array<Record<string, unknown>> {
310
+ function withInferredCameraUpdate(elements: Array<Record<string, unknown>>): Array<Record<string, unknown>> {
310
311
  if (elementHasCameraUpdate(elements)) return elements;
311
312
  const camera = inferExcalidrawCameraUpdate(elements);
312
313
  return camera ? [camera, ...elements] : elements;
@@ -332,10 +333,7 @@ export function normalizeExcalidrawCheckpointDataForToolInput(data: unknown): st
332
333
  export function buildExcalidrawRestoreCheckpointToolInput(checkpointId: string, data?: unknown): string {
333
334
  const elements = parseExcalidrawCheckpointElements(data);
334
335
  const camera = elements ? inferExcalidrawCameraUpdate(elements) : null;
335
- return JSON.stringify([
336
- { type: 'restoreCheckpoint', id: checkpointId },
337
- ...(camera ? [camera] : []),
338
- ]);
336
+ return JSON.stringify([{ type: 'restoreCheckpoint', id: checkpointId }, ...(camera ? [camera] : [])]);
339
337
  }
340
338
 
341
339
  export function isExcalidrawCreateView(serverName: unknown, toolName: unknown): boolean {
@@ -343,7 +341,10 @@ export function isExcalidrawCreateView(serverName: unknown, toolName: unknown):
343
341
  }
344
342
 
345
343
  export function buildExcalidrawCheckpointId(seed: string): string {
346
- const safe = seed.replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96);
344
+ const safe = seed
345
+ .replace(/[^A-Za-z0-9_-]+/g, '-')
346
+ .replace(/^-+|-+$/g, '')
347
+ .slice(0, 96);
347
348
  return `pmx-${safe || 'checkpoint'}`;
348
349
  }
349
350
 
@@ -353,10 +354,7 @@ export function getExcalidrawCheckpointIdFromToolResult(result: unknown): string
353
354
  return typeof checkpointId === 'string' && checkpointId.trim().length > 0 ? checkpointId.trim() : null;
354
355
  }
355
356
 
356
- export function withExcalidrawCheckpointId(
357
- result: CallToolResult,
358
- checkpointId: string,
359
- ): CallToolResult {
357
+ export function withExcalidrawCheckpointId(result: CallToolResult, checkpointId: string): CallToolResult {
360
358
  const structuredContent = isRecord(result.structuredContent) ? result.structuredContent : {};
361
359
  return {
362
360
  ...result,
@@ -392,6 +390,7 @@ export function buildExcalidrawOpenMcpAppInput(input: DiagramPresetOpenInput): E
392
390
  if (typeof input.y === 'number' && Number.isFinite(input.y)) out.y = input.y;
393
391
  if (typeof input.width === 'number' && Number.isFinite(input.width)) out.width = input.width;
394
392
  if (typeof input.height === 'number' && Number.isFinite(input.height)) out.height = input.height;
395
- if (typeof input.timeoutMs === 'number' && Number.isFinite(input.timeoutMs) && input.timeoutMs > 0) out.timeoutMs = input.timeoutMs;
393
+ if (typeof input.timeoutMs === 'number' && Number.isFinite(input.timeoutMs) && input.timeoutMs > 0)
394
+ out.timeoutMs = input.timeoutMs;
396
395
  return out;
397
396
  }
@@ -19,21 +19,12 @@ export interface ExtAppLookupSource {
19
19
  listNodes(): readonly CanvasNodeState[];
20
20
  }
21
21
 
22
- export function findCanvasExtAppNodeId(
23
- toolCallId: string,
24
- source: ExtAppLookupSource,
25
- ): string | null {
26
- const directId = toolCallId.startsWith('ext-app-')
27
- ? toolCallId
28
- : `ext-app-${toolCallId}`;
22
+ export function findCanvasExtAppNodeId(toolCallId: string, source: ExtAppLookupSource): string | null {
23
+ const directId = toolCallId.startsWith('ext-app-') ? toolCallId : `ext-app-${toolCallId}`;
29
24
  if (source.getNode(directId)) return directId;
30
25
 
31
26
  for (const node of source.listNodes()) {
32
- if (
33
- node.type === 'mcp-app' &&
34
- node.data.mode === 'ext-app' &&
35
- node.data.toolCallId === toolCallId
36
- ) {
27
+ if (node.type === 'mcp-app' && node.data.mode === 'ext-app' && node.data.toolCallId === toolCallId) {
37
28
  return node.id;
38
29
  }
39
30
  }
@@ -34,11 +34,15 @@ function decodeHtmlEntities(value: string): string {
34
34
  const lower = entity.toLowerCase();
35
35
  if (lower.startsWith('#x')) {
36
36
  const codePoint = Number.parseInt(lower.slice(2), 16);
37
- return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
37
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
38
+ ? String.fromCodePoint(codePoint)
39
+ : match;
38
40
  }
39
41
  if (lower.startsWith('#')) {
40
42
  const codePoint = Number.parseInt(lower.slice(1), 10);
41
- return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : match;
43
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
44
+ ? String.fromCodePoint(codePoint)
45
+ : match;
42
46
  }
43
47
  return named[lower] ?? match;
44
48
  });
@@ -51,7 +55,10 @@ export function summarizeHtmlText(html: string): string | null {
51
55
  .replace(/<style\b[\s\S]*?<\/style>/gi, ' ')
52
56
  .replace(/<noscript\b[\s\S]*?<\/noscript>/gi, ' ');
53
57
  const text = withoutNoise
54
- .replace(/<(?:h[1-6]|p|li|br|div|section|article|header|footer|main|aside|summary|figcaption|blockquote|tr|td|th)\b[^>]*>/gi, '\n')
58
+ .replace(
59
+ /<(?:h[1-6]|p|li|br|div|section|article|header|footer|main|aside|summary|figcaption|blockquote|tr|td|th)\b[^>]*>/gi,
60
+ '\n',
61
+ )
55
62
  .replace(/<[^>]+>/g, ' ');
56
63
  const normalized = normalizeWhitespace(decodeHtmlEntities(text));
57
64
  return normalized.length > 0 ? truncateText(normalized, HTML_CONTENT_SUMMARY_MAX_LENGTH) : null;
@@ -123,13 +130,15 @@ export function normalizeHtmlNodeSemanticData<T extends Record<string, unknown>>
123
130
  ].filter((value): value is string => value !== null);
124
131
  const embeddedNodeIds = uniqueLimited([...explicitNodeIds, ...(html ? extractHtmlNodeIds(html) : [])]);
125
132
  const embeddedUrls = uniqueLimited([...strings(data.embeddedUrls), ...(html ? extractHtmlUrls(html) : [])]);
126
- const agentSummary = pickString(data.agentSummary) ?? joinSummaryParts([
127
- primitive ? `HTML primitive: ${primitive}` : '',
128
- explicitSummary ?? '',
129
- contentSummary ?? '',
130
- embeddedNodeIds.length > 0 ? `Embedded canvas nodes: ${embeddedNodeIds.join(', ')}` : '',
131
- embeddedUrls.length > 0 ? `Embedded URLs: ${embeddedUrls.join(', ')}` : '',
132
- ]);
133
+ const agentSummary =
134
+ pickString(data.agentSummary) ??
135
+ joinSummaryParts([
136
+ primitive ? `HTML primitive: ${primitive}` : '',
137
+ explicitSummary ?? '',
138
+ contentSummary ?? '',
139
+ embeddedNodeIds.length > 0 ? `Embedded canvas nodes: ${embeddedNodeIds.join(', ')}` : '',
140
+ embeddedUrls.length > 0 ? `Embedded URLs: ${embeddedUrls.join(', ')}` : '',
141
+ ]);
133
142
 
134
143
  return {
135
144
  ...base,