pmx-canvas 0.2.7 → 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 (189) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/Readme.md +13 -11
  3. package/dist/canvas/index.js +2 -2
  4. package/dist/json-render/index.css +1 -1
  5. package/dist/types/cli/daemon.d.ts +74 -0
  6. package/dist/types/cli/watch.d.ts +2 -2
  7. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  8. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  9. package/dist/types/client/canvas/Minimap.d.ts +1 -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/mcp/canvas-access.d.ts +0 -7
  17. package/dist/types/server/ax-state-manager.d.ts +3 -2
  18. package/dist/types/server/bundled-skills.d.ts +3 -3
  19. package/dist/types/server/canvas-schema.d.ts +1 -1
  20. package/dist/types/server/canvas-state.d.ts +2 -0
  21. package/dist/types/server/ext-app-lookup.d.ts +1 -3
  22. package/dist/types/server/html-primitives.d.ts +1 -1
  23. package/dist/types/server/index.d.ts +4 -4
  24. package/dist/types/server/operations/composites.d.ts +29 -23
  25. package/dist/types/server/operations/http.d.ts +2 -1
  26. package/dist/types/server/operations/index.d.ts +1 -1
  27. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  28. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  29. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  30. package/dist/types/shared/ax-intent.d.ts +1 -1
  31. package/docs/RELEASE.md +23 -8
  32. package/docs/ax-host-adapter-contract.md +7 -7
  33. package/docs/ax-state-contract.md +7 -6
  34. package/docs/bun-webview-integration.md +5 -5
  35. package/docs/cli.md +1 -1
  36. package/docs/http-api.md +35 -3
  37. package/docs/mcp.md +121 -100
  38. package/docs/node-types.md +45 -35
  39. package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
  40. package/docs/plans/plan-008-registry-finish.md +2 -0
  41. package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
  42. package/docs/screenshot.png +0 -0
  43. package/docs/tech-debt-assessment-2026-06.md +1 -1
  44. package/docs/tech-debt-assessment-2026-07.md +135 -0
  45. package/package.json +5 -2
  46. package/skills/data-analysis/SKILL.md +3 -3
  47. package/skills/frontend-design/SKILL.md +3 -2
  48. package/skills/json-render-mcp/SKILL.md +4 -3
  49. package/skills/playwright-cli/SKILL.md +1 -1
  50. package/skills/pmx-canvas/SKILL.md +12 -7
  51. package/skills/pmx-canvas/evals/evals.json +2 -2
  52. package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
  53. package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
  54. package/skills/pmx-canvas/references/full-reference.md +58 -56
  55. package/skills/pmx-canvas/references/html-primitives.md +3 -3
  56. package/skills/tufte-viz/SKILL.md +5 -4
  57. package/skills/web-artifacts-builder/SKILL.md +4 -4
  58. package/src/cli/agent.ts +1861 -1548
  59. package/src/cli/daemon.ts +460 -0
  60. package/src/cli/index.ts +63 -326
  61. package/src/cli/watch.ts +2 -10
  62. package/src/client/App.tsx +48 -46
  63. package/src/client/canvas/AttentionHistory.tsx +11 -1
  64. package/src/client/canvas/CanvasNode.tsx +41 -29
  65. package/src/client/canvas/CanvasViewport.tsx +101 -66
  66. package/src/client/canvas/CommandPalette.tsx +61 -27
  67. package/src/client/canvas/ContextMenu.tsx +13 -20
  68. package/src/client/canvas/ContextPinBar.tsx +1 -5
  69. package/src/client/canvas/ContextPinHud.tsx +1 -6
  70. package/src/client/canvas/DockedNode.tsx +4 -4
  71. package/src/client/canvas/EdgeLayer.tsx +37 -36
  72. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  73. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  74. package/src/client/canvas/IntentLayer.tsx +31 -14
  75. package/src/client/canvas/Minimap.tsx +11 -16
  76. package/src/client/canvas/SelectionBar.tsx +4 -11
  77. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  78. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  79. package/src/client/canvas/auto-fit.ts +15 -14
  80. package/src/client/canvas/snap-guides.ts +12 -12
  81. package/src/client/canvas/use-node-resize.ts +1 -5
  82. package/src/client/canvas/use-pan-zoom.ts +25 -26
  83. package/src/client/ext-app/bridge.ts +3 -12
  84. package/src/client/icons.tsx +63 -20
  85. package/src/client/nodes/ContextNode.tsx +14 -25
  86. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  87. package/src/client/nodes/FileNode.tsx +74 -62
  88. package/src/client/nodes/GroupNode.tsx +4 -6
  89. package/src/client/nodes/HtmlNode.tsx +76 -46
  90. package/src/client/nodes/ImageNode.tsx +18 -27
  91. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  92. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  93. package/src/client/nodes/LedgerNode.tsx +10 -4
  94. package/src/client/nodes/MarkdownNode.tsx +3 -10
  95. package/src/client/nodes/McpAppNode.tsx +26 -22
  96. package/src/client/nodes/MdFormatBar.tsx +10 -7
  97. package/src/client/nodes/PromptNode.tsx +23 -51
  98. package/src/client/nodes/ResponseNode.tsx +3 -13
  99. package/src/client/nodes/StatusNode.tsx +5 -9
  100. package/src/client/nodes/StatusSummary.tsx +2 -8
  101. package/src/client/nodes/WebpageNode.tsx +20 -14
  102. package/src/client/nodes/iframe-document-url.ts +25 -16
  103. package/src/client/nodes/image-warnings.ts +1 -7
  104. package/src/client/nodes/md-format.ts +20 -5
  105. package/src/client/state/attention-bridge.ts +4 -9
  106. package/src/client/state/attention-store.ts +1 -7
  107. package/src/client/state/canvas-store.ts +52 -36
  108. package/src/client/state/intent-bridge.ts +176 -112
  109. package/src/client/state/intent-store.ts +4 -1
  110. package/src/client/state/sse-bridge.ts +53 -70
  111. package/src/json-render/catalog.ts +12 -16
  112. package/src/json-render/charts/components.tsx +16 -20
  113. package/src/json-render/charts/extra-components.tsx +8 -16
  114. package/src/json-render/charts/extra-definitions.ts +1 -2
  115. package/src/json-render/charts/tufte-components.tsx +37 -20
  116. package/src/json-render/charts/tufte-definitions.ts +8 -2
  117. package/src/json-render/renderer/index.tsx +42 -22
  118. package/src/json-render/schema.ts +6 -3
  119. package/src/json-render/server.ts +33 -39
  120. package/src/mcp/canvas-access.ts +40 -121
  121. package/src/mcp/server.ts +206 -271
  122. package/src/server/agent-context.ts +63 -36
  123. package/src/server/ax-context.ts +7 -5
  124. package/src/server/ax-interaction.ts +176 -43
  125. package/src/server/ax-state-manager.ts +185 -41
  126. package/src/server/ax-state.ts +142 -47
  127. package/src/server/bundled-skills.ts +3 -3
  128. package/src/server/canvas-db.ts +213 -95
  129. package/src/server/canvas-operations.ts +177 -120
  130. package/src/server/canvas-provenance.ts +1 -4
  131. package/src/server/canvas-schema.ts +500 -102
  132. package/src/server/canvas-serialization.ts +27 -35
  133. package/src/server/canvas-state.ts +155 -58
  134. package/src/server/chart-template.ts +4 -5
  135. package/src/server/code-graph.ts +20 -7
  136. package/src/server/diagram-presets.ts +28 -29
  137. package/src/server/ext-app-lookup.ts +4 -20
  138. package/src/server/html-node-summary.ts +19 -10
  139. package/src/server/html-primitives.ts +326 -97
  140. package/src/server/html-surface.ts +6 -9
  141. package/src/server/image-source.ts +6 -4
  142. package/src/server/index.ts +321 -218
  143. package/src/server/intent-registry.ts +3 -6
  144. package/src/server/mcp-app-candidate.ts +5 -10
  145. package/src/server/mcp-app-host.ts +14 -38
  146. package/src/server/mcp-app-runtime.ts +12 -20
  147. package/src/server/mutation-history.ts +15 -5
  148. package/src/server/operations/composites.ts +38 -40
  149. package/src/server/operations/http.ts +5 -5
  150. package/src/server/operations/index.ts +7 -1
  151. package/src/server/operations/invoker.ts +4 -3
  152. package/src/server/operations/mcp.ts +29 -36
  153. package/src/server/operations/ops/annotation.ts +122 -10
  154. package/src/server/operations/ops/app.ts +102 -77
  155. package/src/server/operations/ops/ax-await.ts +17 -10
  156. package/src/server/operations/ops/ax-read.ts +347 -0
  157. package/src/server/operations/ops/ax-shared.ts +2 -7
  158. package/src/server/operations/ops/ax-state.ts +32 -14
  159. package/src/server/operations/ops/ax-timeline.ts +32 -19
  160. package/src/server/operations/ops/ax-work.ts +54 -37
  161. package/src/server/operations/ops/batch.ts +39 -14
  162. package/src/server/operations/ops/canvas-wire.ts +91 -0
  163. package/src/server/operations/ops/edges.ts +37 -25
  164. package/src/server/operations/ops/ext-app.ts +346 -0
  165. package/src/server/operations/ops/groups.ts +49 -20
  166. package/src/server/operations/ops/intent.ts +18 -12
  167. package/src/server/operations/ops/json-render.ts +239 -98
  168. package/src/server/operations/ops/nodes.ts +300 -111
  169. package/src/server/operations/ops/query.ts +62 -33
  170. package/src/server/operations/ops/snapshots.ts +35 -26
  171. package/src/server/operations/ops/validate.ts +2 -1
  172. package/src/server/operations/ops/viewport.ts +60 -16
  173. package/src/server/operations/ops/webview.ts +44 -18
  174. package/src/server/operations/registry.ts +2 -3
  175. package/src/server/operations/types.ts +7 -5
  176. package/src/server/operations/webview-runner.ts +1 -3
  177. package/src/server/placement.ts +8 -18
  178. package/src/server/server.ts +121 -1012
  179. package/src/server/spatial-analysis.ts +39 -25
  180. package/src/server/trace-manager.ts +3 -8
  181. package/src/server/web-artifacts.ts +23 -27
  182. package/src/server/webpage-node.ts +5 -13
  183. package/src/shared/auto-arrange.ts +12 -5
  184. package/src/shared/ax-intent.ts +1 -1
  185. package/src/shared/content-height-reporter.ts +8 -6
  186. package/src/shared/ext-app-tool-result.ts +2 -6
  187. package/src/shared/placement.ts +1 -4
  188. package/src/shared/semantic-attention.ts +39 -37
  189. package/src/shared/surface.ts +8 -4
@@ -19,7 +19,16 @@ import { tufteChartComponents } from '../charts/tufte-components';
19
19
  import { pmxCanvasDirectives } from '../directives';
20
20
  import { JsonRenderDevtools } from '@json-render/devtools-react';
21
21
 
22
- type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'info' | 'warning' | 'error' | 'danger';
22
+ type BadgeVariant =
23
+ | 'default'
24
+ | 'secondary'
25
+ | 'destructive'
26
+ | 'outline'
27
+ | 'success'
28
+ | 'info'
29
+ | 'warning'
30
+ | 'error'
31
+ | 'danger';
23
32
  type BadgeProps = {
24
33
  text: string;
25
34
  variant?: BadgeVariant | null;
@@ -30,11 +39,7 @@ function Badge({ props }: { props: BadgeProps }) {
30
39
  const variant = props.variant;
31
40
  const resolvedVariant = variant ?? 'default';
32
41
  return (
33
- <span
34
- data-slot="badge"
35
- data-variant={resolvedVariant}
36
- className={`pmx-badge pmx-badge--${resolvedVariant}`}
37
- >
42
+ <span data-slot="badge" data-variant={resolvedVariant} className={`pmx-badge pmx-badge--${resolvedVariant}`}>
38
43
  {props.text}
39
44
  </span>
40
45
  );
@@ -111,9 +116,17 @@ function AxStateSync() {
111
116
  // validates + submits through the capability-gated endpoint). Convention-based
112
117
  // opt-in: spec authors name the action handler after the AX interaction type.
113
118
  const AX_INTERACTION_HANDLER_NAMES = [
114
- 'ax.event.record', 'ax.steer', 'ax.work.create', 'ax.work.update',
115
- 'ax.evidence.add', 'ax.approval.request', 'ax.review.add', 'ax.focus.set',
116
- 'ax.elicitation.request', 'ax.mode.request', 'ax.command.invoke',
119
+ 'ax.event.record',
120
+ 'ax.steer',
121
+ 'ax.work.create',
122
+ 'ax.work.update',
123
+ 'ax.evidence.add',
124
+ 'ax.approval.request',
125
+ 'ax.review.add',
126
+ 'ax.focus.set',
127
+ 'ax.elicitation.request',
128
+ 'ax.mode.request',
129
+ 'ax.command.invoke',
117
130
  ] as const;
118
131
 
119
132
  function buildAxHandlers(): Record<string, (params: Record<string, unknown>) => void> {
@@ -126,12 +139,15 @@ function buildAxHandlers(): Record<string, (params: Record<string, unknown>) =>
126
139
  // is no JS surface for a Promise-style ack here, so we don't stamp a correlationId.
127
140
  for (const type of AX_INTERACTION_HANDLER_NAMES) {
128
141
  handlers[type] = (params: Record<string, unknown>) => {
129
- window.parent.postMessage({
130
- source: 'pmx-canvas-ax',
131
- token,
132
- nodeId,
133
- interaction: { type, payload: params && typeof params === 'object' ? params : {} },
134
- }, '*');
142
+ window.parent.postMessage(
143
+ {
144
+ source: 'pmx-canvas-ax',
145
+ token,
146
+ nodeId,
147
+ interaction: { type, payload: params && typeof params === 'object' ? params : {} },
148
+ },
149
+ '*',
150
+ );
135
151
  };
136
152
  }
137
153
  return handlers;
@@ -176,16 +192,22 @@ function App() {
176
192
 
177
193
  // Seed AX state under a reserved `/ax` key so specs can bind { "$state": "/ax/workItems" }.
178
194
  const axState = window.__PMX_CANVAS_AX_STATE__;
179
- const initialState = axState !== undefined && axState !== null
180
- ? { ...(spec.state ?? {}), ax: axState }
181
- : spec.state ?? undefined;
195
+ const initialState =
196
+ axState !== undefined && axState !== null ? { ...(spec.state ?? {}), ax: axState } : (spec.state ?? undefined);
182
197
 
183
198
  // Standalone "Open as site" tab (#65): fill the browser viewport instead of the
184
199
  // in-canvas card height. The chart child flex-grows; useChartFrameHeight measures
185
200
  // the full viewport in this mode. Embedded/expanded keep the padded min-height box.
186
201
  const isSite = window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ === 'site';
187
202
  const containerStyle = isSite
188
- ? { display: 'flex', flexDirection: 'column' as const, height: '100dvh', minHeight: '100dvh', padding: 0, boxSizing: 'border-box' as const }
203
+ ? {
204
+ display: 'flex',
205
+ flexDirection: 'column' as const,
206
+ height: '100dvh',
207
+ minHeight: '100dvh',
208
+ padding: 0,
209
+ boxSizing: 'border-box' as const,
210
+ }
189
211
  : { minHeight: '100vh', padding: 16, boxSizing: 'border-box' as const };
190
212
  return (
191
213
  <div style={containerStyle}>
@@ -199,9 +221,7 @@ function App() {
199
221
  <div style={isSite ? { flex: 1, minHeight: 0 } : undefined}>
200
222
  <Renderer spec={spec} registry={registry} loading={false} />
201
223
  </div>
202
- {window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? (
203
- <JsonRenderDevtools position="right" />
204
- ) : null}
224
+ {window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ ? <JsonRenderDevtools position="right" /> : null}
205
225
  </JSONUIProvider>
206
226
  </div>
207
227
  );
@@ -34,11 +34,13 @@ export const schema = defineSchema(
34
34
  builtInActions: [
35
35
  {
36
36
  name: 'setState',
37
- description: 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
37
+ description:
38
+ 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
38
39
  },
39
40
  {
40
41
  name: 'pushState',
41
- description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
42
+ description:
43
+ 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
42
44
  },
43
45
  {
44
46
  name: 'removeState',
@@ -46,7 +48,8 @@ export const schema = defineSchema(
46
48
  },
47
49
  {
48
50
  name: 'validateForm',
49
- description: 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
51
+ description:
52
+ 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
50
53
  },
51
54
  ],
52
55
  defaultRules: [
@@ -212,9 +212,7 @@ async function ensureJsonRenderBundle(): Promise<void> {
212
212
  // Avoid live source-vs-dist rebuild checks here because Bun's bundler can stall on
213
213
  // the @json-render/shadcn dependency graph during request-time viewer generation.
214
214
  const needsBuild =
215
- !existsSync(jsPath) ||
216
- !existsSync(cssPath) ||
217
- process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
215
+ !existsSync(jsPath) || !existsSync(cssPath) || process.env.PMX_CANVAS_FORCE_JSON_RENDER_REBUILD === '1';
218
216
 
219
217
  if (needsBuild) {
220
218
  await rebuildJsonRenderBundle();
@@ -273,7 +271,7 @@ function normalizeItemArray(value: unknown): unknown {
273
271
  if (hasString(item)) return { label: item, value: item };
274
272
  const record = asRecord(item);
275
273
  const label = hasString(record?.label) ? record.label : hasString(record?.text) ? record.text : null;
276
- const resolvedValue = hasString(record?.value) ? record.value : label ?? `option-${index + 1}`;
274
+ const resolvedValue = hasString(record?.value) ? record.value : (label ?? `option-${index + 1}`);
277
275
  return {
278
276
  label: label ?? resolvedValue,
279
277
  value: resolvedValue,
@@ -283,11 +281,7 @@ function normalizeItemArray(value: unknown): unknown {
283
281
 
284
282
  function normalizeStringMatrix(value: unknown): unknown {
285
283
  if (!Array.isArray(value)) return value;
286
- return value.map((row) => (
287
- Array.isArray(row)
288
- ? row.map((cell) => String(cell ?? ''))
289
- : [String(row ?? '')]
290
- ));
284
+ return value.map((row) => (Array.isArray(row) ? row.map((cell) => String(cell ?? '')) : [String(row ?? '')]));
291
285
  }
292
286
 
293
287
  function normalizeButtonVariant(value: unknown): unknown {
@@ -347,12 +341,7 @@ function normalizeElementProps(
347
341
  const props = (stripNullishDeep(rawProps) as Record<string, unknown> | undefined) ?? {};
348
342
 
349
343
  for (const key of COERCIBLE_STRING_PROPS) {
350
- if (
351
- key in props &&
352
- typeof props[key] !== 'string' &&
353
- props[key] !== undefined &&
354
- !isDynamicPropValue(props[key])
355
- ) {
344
+ if (key in props && typeof props[key] !== 'string' && props[key] !== undefined && !isDynamicPropValue(props[key])) {
356
345
  props[key] = String(props[key]);
357
346
  }
358
347
  }
@@ -516,7 +505,9 @@ export function inferJsonRenderNodeTitle(spec: JsonRenderSpec, fallback = 'json-
516
505
  export function normalizeAndValidateJsonRenderSpec(spec: unknown): JsonRenderSpec {
517
506
  const specRecord = asRecord(normalizeJsonRenderInput(spec));
518
507
  if (!specRecord || typeof specRecord.root !== 'string' || !asRecord(specRecord.elements)) {
519
- throw new Error('Missing root and elements in spec. Pass a complete {root, elements} document, or a single bare component object with a type field.');
508
+ throw new Error(
509
+ 'Missing root and elements in spec. Pass a complete {root, elements} document, or a single bare component object with a type field.',
510
+ );
520
511
  }
521
512
 
522
513
  // Reject an unrecognized $-keyed expression object in any element prop BEFORE
@@ -576,10 +567,7 @@ function collectDataKeys(data: Array<Record<string, unknown>>): Set<string> {
576
567
  * as "value") without an explicit valueKey, instead of failing the data-key
577
568
  * check on a reasonable guess.
578
569
  */
579
- function firstPresentDataKey(
580
- data: Array<Record<string, unknown>>,
581
- candidates: string[],
582
- ): string | undefined {
570
+ function firstPresentDataKey(data: Array<Record<string, unknown>>, candidates: string[]): string | undefined {
583
571
  const keys = collectDataKeys(data);
584
572
  return candidates.find((key) => keys.has(key));
585
573
  }
@@ -653,7 +641,9 @@ function assertGraphDataKeys(
653
641
  const missing = [...required].filter((key) => !available.has(key));
654
642
  if (missing.length === 0) return;
655
643
  const availableList = [...available].sort().join(', ') || '(none)';
656
- throw new Error(`Graph data key mismatch for ${chartType}: missing ${missing.join(', ')}. Available keys: ${availableList}.`);
644
+ throw new Error(
645
+ `Graph data key mismatch for ${chartType}: missing ${missing.join(', ')}. Available keys: ${availableList}.`,
646
+ );
657
647
  }
658
648
 
659
649
  export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
@@ -685,11 +675,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
685
675
  }
686
676
  case 'RadarChart': {
687
677
  const axisKey = input.axisKey ?? input.xKey ?? 'axis';
688
- const metrics = input.metrics?.length
689
- ? input.metrics
690
- : inferKeysFromData(input.data, [axisKey]);
678
+ const metrics = input.metrics?.length ? input.metrics : inferKeysFromData(input.data, [axisKey]);
691
679
  if (metrics.length === 0) {
692
- throw new Error('RadarChart requires at least one metric key (provide `metrics` or include numeric columns in `data`).');
680
+ throw new Error(
681
+ 'RadarChart requires at least one metric key (provide `metrics` or include numeric columns in `data`).',
682
+ );
693
683
  }
694
684
  chartProps.axisKey = axisKey;
695
685
  chartProps.metrics = metrics;
@@ -698,11 +688,11 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
698
688
  }
699
689
  case 'StackedBarChart': {
700
690
  const xKey = input.xKey ?? 'label';
701
- const series = input.series?.length
702
- ? input.series
703
- : inferKeysFromData(input.data, [xKey]);
691
+ const series = input.series?.length ? input.series : inferKeysFromData(input.data, [xKey]);
704
692
  if (series.length === 0) {
705
- throw new Error('StackedBarChart requires at least one series key (provide `series` or include numeric columns in `data`).');
693
+ throw new Error(
694
+ 'StackedBarChart requires at least one series key (provide `series` or include numeric columns in `data`).',
695
+ );
706
696
  }
707
697
  chartProps.xKey = xKey;
708
698
  chartProps.series = series;
@@ -713,9 +703,10 @@ export function buildGraphSpec(input: GraphNodeInput): JsonRenderSpec {
713
703
  case 'ComposedChart': {
714
704
  chartProps.xKey = input.xKey ?? 'label';
715
705
  chartProps.barKey = input.barKey ?? input.yKey ?? 'value';
716
- chartProps.lineKey = input.lineKey
717
- ?? inferKeysFromData(input.data, [chartProps.xKey as string, chartProps.barKey as string])[0]
718
- ?? 'rate';
706
+ chartProps.lineKey =
707
+ input.lineKey ??
708
+ inferKeysFromData(input.data, [chartProps.xKey as string, chartProps.barKey as string])[0] ??
709
+ 'rate';
719
710
  chartProps.barColor = input.barColor ?? null;
720
711
  chartProps.lineColor = input.lineColor ?? null;
721
712
  chartProps.showLegend = input.showLegend !== false;
@@ -952,7 +943,8 @@ export async function buildJsonRenderViewerHtml(options: {
952
943
  * node grows to it. Off for strictSize / user-resized nodes (they fill-down). */
953
944
  fitContent?: boolean;
954
945
  }): Promise<string> {
955
- const sanitizeAxValue = (v?: string): string => (typeof v === 'string' ? v.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) : '');
946
+ const sanitizeAxValue = (v?: string): string =>
947
+ typeof v === 'string' ? v.replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80) : '';
956
948
  try {
957
949
  await ensureJsonRenderBundle();
958
950
  const dir = bundleDir();
@@ -967,12 +959,14 @@ export async function buildJsonRenderViewerHtml(options: {
967
959
  ...(options.theme ? [`window.__PMX_CANVAS_JSON_RENDER_THEME__ = ${JSON.stringify(options.theme)};`] : []),
968
960
  ...(options.display ? [`window.__PMX_CANVAS_JSON_RENDER_DISPLAY__ = ${JSON.stringify(options.display)};`] : []),
969
961
  ...(options.devtools ? ['window.__PMX_CANVAS_JSON_RENDER_DEVTOOLS__ = true;'] : []),
970
- ...(options.nodeId && options.axToken ? [
971
- `window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`,
972
- `window.__PMX_CANVAS_AX_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.axToken))};`,
973
- // Read-side AX state: seed for initial render + bound under /ax for specs.
974
- `window.__PMX_CANVAS_AX_STATE__ = ${JSON.stringify(options.axState ?? null).replace(/</g, '\\u003c')};`,
975
- ] : []),
962
+ ...(options.nodeId && options.axToken
963
+ ? [
964
+ `window.__PMX_CANVAS_JSON_RENDER_NODE_ID__ = ${JSON.stringify(sanitizeAxValue(options.nodeId))};`,
965
+ `window.__PMX_CANVAS_AX_TOKEN__ = ${JSON.stringify(sanitizeAxValue(options.axToken))};`,
966
+ // Read-side AX state: seed for initial render + bound under /ax for specs.
967
+ `window.__PMX_CANVAS_AX_STATE__ = ${JSON.stringify(options.axState ?? null).replace(/</g, '\\u003c')};`,
968
+ ]
969
+ : []),
976
970
  ...(options.fitContent ? ['window.__PMX_CANVAS_FIT_CONTENT__ = true;'] : []),
977
971
  jsBundle,
978
972
  ].join('\n');
@@ -10,21 +10,14 @@ import {
10
10
  type PmxCanvas,
11
11
  } from '../server/index.js';
12
12
  import type { PmxAxSource } from '../server/ax-state.js';
13
- import {
14
- HttpOperationInvoker,
15
- LocalOperationInvoker,
16
- type OperationInvoker,
17
- } from '../server/operations/index.js';
18
-
19
- type RefreshWebpageNodeResult = Awaited<ReturnType<PmxCanvas['refreshWebpageNode']>>;
20
- // openMcpApp / addDiagram / buildWebArtifact CanvasAccess methods + their type
21
- // aliases removed with the standalone MCP tools (plan-008 Wave 4): those tools
22
- // migrated to the operation registry (mcpapp.open / diagram.open /
13
+ import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } from '../server/operations/index.js';
14
+
15
+ // openMcpApp / addDiagram / buildWebArtifact / refreshWebpageNode / addHtmlNode /
16
+ // addHtmlPrimitive CanvasAccess methods + their type aliases removed with the
17
+ // standalone MCP tools (plan-008 Wave 4; refresh/html tools removed v0.3.0):
18
+ // those tools migrated to the operation registry (mcpapp.open / diagram.open /
23
19
  // webartifact.build) and the composite/registry tools dispatch via the invoker,
24
20
  // not CanvasAccess. The public SDK PmxCanvas methods are unchanged.
25
- type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
26
- type AddHtmlPrimitiveInput = Parameters<PmxCanvas['addHtmlPrimitive']>[0];
27
- type AddHtmlPrimitiveResult = ReturnType<PmxCanvas['addHtmlPrimitive']>;
28
21
  type AxStateResult = ReturnType<PmxCanvas['getAxState']>;
29
22
  type AxContextResult = ReturnType<PmxCanvas['getAxContext']>;
30
23
  type SubmitAxInteractionInput = Parameters<PmxCanvas['submitAxInteraction']>[0];
@@ -56,11 +49,6 @@ interface HealthResponse {
56
49
  workspace?: string;
57
50
  }
58
51
 
59
- interface NodeResponse {
60
- id?: string;
61
- node?: { id?: string };
62
- }
63
-
64
52
  export interface CanvasAccess {
65
53
  readonly port: number;
66
54
  readonly remoteBaseUrl: string | null;
@@ -68,16 +56,16 @@ export interface CanvasAccess {
68
56
  invoker(): OperationInvoker;
69
57
  getLayout(): Promise<CanvasLayout>;
70
58
  getNode(id: string): Promise<CanvasNodeState | undefined>;
71
- refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult>;
72
- addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
73
- addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult>;
74
59
  getAxState(): Promise<AxStateResult>;
75
60
  getAxContext(options?: { consumer?: string }): Promise<AxContextResult>;
76
61
  getAxTimeline(query?: GetAxTimelineQuery): Promise<GetAxTimelineResult>;
77
62
  listWorkItems(): Promise<ListWorkItemsResult>;
78
63
  listApprovalGates(): Promise<ListApprovalGatesResult>;
79
64
  listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
80
- submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult>;
65
+ submitAxInteraction(
66
+ input: SubmitAxInteractionInput,
67
+ options?: { source?: PmxAxSource },
68
+ ): Promise<SubmitAxInteractionResult>;
81
69
  getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult>;
82
70
  listElicitations(): Promise<ListElicitationsResult>;
83
71
  listModeRequests(): Promise<ListModeRequestsResult>;
@@ -119,20 +107,6 @@ class LocalCanvasAccess implements CanvasAccess {
119
107
  return this.canvas.getNode(id);
120
108
  }
121
109
 
122
- async refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult> {
123
- return await this.canvas.refreshWebpageNode(id, url);
124
- }
125
-
126
- async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
127
- // PmxCanvas.addHtmlNode returns the created node; the CanvasAccess contract
128
- // is a bare id string, so extract it (mirrors addNode above).
129
- return this.canvas.addHtmlNode(input).id;
130
- }
131
-
132
- async addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult> {
133
- return this.canvas.addHtmlPrimitive(input);
134
- }
135
-
136
110
  async getAxState(): Promise<AxStateResult> {
137
111
  return this.canvas.getAxState();
138
112
  }
@@ -145,7 +119,10 @@ class LocalCanvasAccess implements CanvasAccess {
145
119
  return this.canvas.getAxTimeline(query);
146
120
  }
147
121
 
148
- async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
122
+ async submitAxInteraction(
123
+ input: SubmitAxInteractionInput,
124
+ options?: { source?: PmxAxSource },
125
+ ): Promise<SubmitAxInteractionResult> {
149
126
  return this.canvas.submitAxInteraction(input, { source: options?.source ?? 'mcp' });
150
127
  }
151
128
 
@@ -238,9 +215,10 @@ class RemoteCanvasAccess implements CanvasAccess {
238
215
  }
239
216
  }
240
217
  if (!response.ok) {
241
- const error = parsed && typeof parsed === 'object' && 'error' in parsed
242
- ? String((parsed as { error?: unknown }).error)
243
- : `HTTP ${response.status}`;
218
+ const error =
219
+ parsed && typeof parsed === 'object' && 'error' in parsed
220
+ ? String((parsed as { error?: unknown }).error)
221
+ : `HTTP ${response.status}`;
244
222
  if (path === '/api/canvas/batch' && parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
245
223
  return parsed as T;
246
224
  }
@@ -249,17 +227,6 @@ class RemoteCanvasAccess implements CanvasAccess {
249
227
  return parsed as T;
250
228
  }
251
229
 
252
- private async requestNodeId(method: string, path: string, body?: unknown): Promise<string> {
253
- const response = await this.requestJson<NodeResponse>(method, path, body);
254
- const id = typeof response.id === 'string'
255
- ? response.id
256
- : typeof response.node?.id === 'string'
257
- ? response.node.id
258
- : '';
259
- if (!id) throw new Error('Canvas response did not include a node id.');
260
- return id;
261
- }
262
-
263
230
  async getLayout(): Promise<CanvasLayout> {
264
231
  return await this.requestJson<CanvasLayout>('GET', '/api/canvas/state?includeBlobs=true');
265
232
  }
@@ -268,7 +235,7 @@ class RemoteCanvasAccess implements CanvasAccess {
268
235
  const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?includeBlobs=true`);
269
236
  if (response.status === 404) return undefined;
270
237
  const text = await response.text();
271
- let parsed: unknown = undefined;
238
+ let parsed: unknown;
272
239
  if (text.length > 0) {
273
240
  try {
274
241
  parsed = JSON.parse(text) as unknown;
@@ -277,73 +244,15 @@ class RemoteCanvasAccess implements CanvasAccess {
277
244
  }
278
245
  }
279
246
  if (!response.ok) {
280
- const error = parsed && typeof parsed === 'object' && 'error' in parsed
281
- ? String((parsed as { error?: unknown }).error)
282
- : `HTTP ${response.status}`;
247
+ const error =
248
+ parsed && typeof parsed === 'object' && 'error' in parsed
249
+ ? String((parsed as { error?: unknown }).error)
250
+ : `HTTP ${response.status}`;
283
251
  throw new Error(error);
284
252
  }
285
253
  return parsed as CanvasNodeState;
286
254
  }
287
255
 
288
- async refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult> {
289
- return await this.requestJson<RefreshWebpageNodeResult>('POST', `/api/canvas/node/${encodeURIComponent(id)}/refresh`, {
290
- ...(url ? { url } : {}),
291
- });
292
- }
293
-
294
- async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
295
- const {
296
- summary,
297
- agentSummary,
298
- description,
299
- presentation,
300
- slideTitles,
301
- embeddedNodeIds,
302
- embeddedUrls,
303
- axCapabilities,
304
- ...rest
305
- } = input as AddHtmlNodeInput & {
306
- summary?: string;
307
- agentSummary?: string;
308
- description?: string;
309
- presentation?: boolean;
310
- slideTitles?: string[];
311
- embeddedNodeIds?: string[];
312
- embeddedUrls?: string[];
313
- axCapabilities?: { enabled?: boolean; allowed?: string[] };
314
- };
315
- return await this.requestNodeId('POST', '/api/canvas/node', {
316
- type: 'html',
317
- ...rest,
318
- data: {
319
- ...(typeof summary === 'string' ? { summary } : {}),
320
- ...(typeof agentSummary === 'string' ? { agentSummary } : {}),
321
- ...(typeof description === 'string' ? { description } : {}),
322
- ...(presentation === true ? { presentation: true } : {}),
323
- ...(Array.isArray(slideTitles) ? { slideTitles } : {}),
324
- ...(Array.isArray(embeddedNodeIds) ? { embeddedNodeIds } : {}),
325
- ...(Array.isArray(embeddedUrls) ? { embeddedUrls } : {}),
326
- ...(axCapabilities ? { axCapabilities } : {}),
327
- },
328
- });
329
- }
330
-
331
- async addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult> {
332
- const response = await this.requestJson<{
333
- id?: string;
334
- node?: { id?: string };
335
- primitive?: { kind?: string; title?: string; htmlBytes?: number };
336
- }>('POST', '/api/canvas/node', { type: 'html', ...input, primitive: input.kind });
337
- const id = typeof response.id === 'string' ? response.id : response.node?.id;
338
- if (!id) throw new Error('html primitive response did not include a node id.');
339
- return {
340
- id,
341
- kind: input.kind,
342
- title: response.primitive?.title ?? input.title ?? input.kind,
343
- htmlBytes: response.primitive?.htmlBytes ?? 0,
344
- };
345
- }
346
-
347
256
  async getHistory(): Promise<HistoryResult> {
348
257
  return await this.requestJson<HistoryResult>('GET', '/api/canvas/history');
349
258
  }
@@ -364,7 +273,10 @@ class RemoteCanvasAccess implements CanvasAccess {
364
273
  return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
365
274
  }
366
275
 
367
- async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
276
+ async submitAxInteraction(
277
+ input: SubmitAxInteractionInput,
278
+ options?: { source?: PmxAxSource },
279
+ ): Promise<SubmitAxInteractionResult> {
368
280
  // The interaction endpoint returns its structured outcome (ok/code/error) in
369
281
  // the body for both accepted and rejected interactions, so read the body
370
282
  // regardless of HTTP status rather than throwing on a denial.
@@ -419,12 +331,18 @@ class RemoteCanvasAccess implements CanvasAccess {
419
331
  }
420
332
 
421
333
  async listApprovalGates(): Promise<ListApprovalGatesResult> {
422
- const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>('GET', '/api/canvas/ax/approval');
334
+ const response = await this.requestJson<{ approvalGates?: ListApprovalGatesResult }>(
335
+ 'GET',
336
+ '/api/canvas/ax/approval',
337
+ );
423
338
  return response.approvalGates ?? [];
424
339
  }
425
340
 
426
341
  async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
427
- const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>('GET', '/api/canvas/ax/review');
342
+ const response = await this.requestJson<{ reviewAnnotations?: ListReviewAnnotationsResult }>(
343
+ 'GET',
344
+ '/api/canvas/ax/review',
345
+ );
428
346
  return response.reviewAnnotations ?? [];
429
347
  }
430
348
 
@@ -495,7 +413,7 @@ async function readHealth(baseUrl: string): Promise<HealthResponse | null> {
495
413
  try {
496
414
  const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(400) });
497
415
  if (!response.ok) return null;
498
- return await response.json() as HealthResponse;
416
+ return (await response.json()) as HealthResponse;
499
417
  } catch {
500
418
  return null;
501
419
  }
@@ -539,7 +457,9 @@ export function shouldAttachToExistingDaemon(
539
457
  occupant: { ok?: boolean; workspace?: unknown } | null,
540
458
  allowSplit: boolean,
541
459
  ): boolean {
542
- return !allowSplit && occupant?.ok === true && typeof occupant.workspace === 'string' && occupant.workspace.length > 0;
460
+ return (
461
+ !allowSplit && occupant?.ok === true && typeof occupant.workspace === 'string' && occupant.workspace.length > 0
462
+ );
543
463
  }
544
464
 
545
465
  /**
@@ -645,8 +565,7 @@ export async function createCanvasAccess(): Promise<CanvasAccess> {
645
565
  const boundPort = canvas.port;
646
566
  if (boundPort !== port) {
647
567
  const occupant = await readHealth(occupantBaseUrl);
648
- const occupantWorkspace =
649
- typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
568
+ const occupantWorkspace = typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
650
569
  process.stderr.write(
651
570
  `[pmx-canvas] preferred port ${port} was in use${occupantWorkspace}; ` +
652
571
  `started this canvas on port ${boundPort} instead. To share one canvas, run the daemon ` +