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
@@ -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,11 +10,7 @@ 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';
13
+ import { HttpOperationInvoker, LocalOperationInvoker, type OperationInvoker } from '../server/operations/index.js';
18
14
 
19
15
  // openMcpApp / addDiagram / buildWebArtifact / refreshWebpageNode / addHtmlNode /
20
16
  // addHtmlPrimitive CanvasAccess methods + their type aliases removed with the
@@ -66,7 +62,10 @@ export interface CanvasAccess {
66
62
  listWorkItems(): Promise<ListWorkItemsResult>;
67
63
  listApprovalGates(): Promise<ListApprovalGatesResult>;
68
64
  listReviewAnnotations(): Promise<ListReviewAnnotationsResult>;
69
- submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult>;
65
+ submitAxInteraction(
66
+ input: SubmitAxInteractionInput,
67
+ options?: { source?: PmxAxSource },
68
+ ): Promise<SubmitAxInteractionResult>;
70
69
  getPendingSteering(options?: { consumer?: string; limit?: number }): Promise<GetPendingSteeringResult>;
71
70
  listElicitations(): Promise<ListElicitationsResult>;
72
71
  listModeRequests(): Promise<ListModeRequestsResult>;
@@ -120,7 +119,10 @@ class LocalCanvasAccess implements CanvasAccess {
120
119
  return this.canvas.getAxTimeline(query);
121
120
  }
122
121
 
123
- async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
122
+ async submitAxInteraction(
123
+ input: SubmitAxInteractionInput,
124
+ options?: { source?: PmxAxSource },
125
+ ): Promise<SubmitAxInteractionResult> {
124
126
  return this.canvas.submitAxInteraction(input, { source: options?.source ?? 'mcp' });
125
127
  }
126
128
 
@@ -213,9 +215,10 @@ class RemoteCanvasAccess implements CanvasAccess {
213
215
  }
214
216
  }
215
217
  if (!response.ok) {
216
- const error = parsed && typeof parsed === 'object' && 'error' in parsed
217
- ? String((parsed as { error?: unknown }).error)
218
- : `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}`;
219
222
  if (path === '/api/canvas/batch' && parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
220
223
  return parsed as T;
221
224
  }
@@ -232,7 +235,7 @@ class RemoteCanvasAccess implements CanvasAccess {
232
235
  const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?includeBlobs=true`);
233
236
  if (response.status === 404) return undefined;
234
237
  const text = await response.text();
235
- let parsed: unknown = undefined;
238
+ let parsed: unknown;
236
239
  if (text.length > 0) {
237
240
  try {
238
241
  parsed = JSON.parse(text) as unknown;
@@ -241,9 +244,10 @@ class RemoteCanvasAccess implements CanvasAccess {
241
244
  }
242
245
  }
243
246
  if (!response.ok) {
244
- const error = parsed && typeof parsed === 'object' && 'error' in parsed
245
- ? String((parsed as { error?: unknown }).error)
246
- : `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}`;
247
251
  throw new Error(error);
248
252
  }
249
253
  return parsed as CanvasNodeState;
@@ -269,7 +273,10 @@ class RemoteCanvasAccess implements CanvasAccess {
269
273
  return await this.requestJson<GetAxTimelineResult>('GET', `/api/canvas/ax/timeline${qs}`);
270
274
  }
271
275
 
272
- async submitAxInteraction(input: SubmitAxInteractionInput, options?: { source?: PmxAxSource }): Promise<SubmitAxInteractionResult> {
276
+ async submitAxInteraction(
277
+ input: SubmitAxInteractionInput,
278
+ options?: { source?: PmxAxSource },
279
+ ): Promise<SubmitAxInteractionResult> {
273
280
  // The interaction endpoint returns its structured outcome (ok/code/error) in
274
281
  // the body for both accepted and rejected interactions, so read the body
275
282
  // regardless of HTTP status rather than throwing on a denial.
@@ -324,12 +331,18 @@ class RemoteCanvasAccess implements CanvasAccess {
324
331
  }
325
332
 
326
333
  async listApprovalGates(): Promise<ListApprovalGatesResult> {
327
- 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
+ );
328
338
  return response.approvalGates ?? [];
329
339
  }
330
340
 
331
341
  async listReviewAnnotations(): Promise<ListReviewAnnotationsResult> {
332
- 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
+ );
333
346
  return response.reviewAnnotations ?? [];
334
347
  }
335
348
 
@@ -400,7 +413,7 @@ async function readHealth(baseUrl: string): Promise<HealthResponse | null> {
400
413
  try {
401
414
  const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(400) });
402
415
  if (!response.ok) return null;
403
- return await response.json() as HealthResponse;
416
+ return (await response.json()) as HealthResponse;
404
417
  } catch {
405
418
  return null;
406
419
  }
@@ -444,7 +457,9 @@ export function shouldAttachToExistingDaemon(
444
457
  occupant: { ok?: boolean; workspace?: unknown } | null,
445
458
  allowSplit: boolean,
446
459
  ): boolean {
447
- 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
+ );
448
463
  }
449
464
 
450
465
  /**
@@ -550,8 +565,7 @@ export async function createCanvasAccess(): Promise<CanvasAccess> {
550
565
  const boundPort = canvas.port;
551
566
  if (boundPort !== port) {
552
567
  const occupant = await readHealth(occupantBaseUrl);
553
- const occupantWorkspace =
554
- typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
568
+ const occupantWorkspace = typeof occupant?.workspace === 'string' ? ` (serving ${occupant.workspace})` : '';
555
569
  process.stderr.write(
556
570
  `[pmx-canvas] preferred port ${port} was in use${occupantWorkspace}; ` +
557
571
  `started this canvas on port ${boundPort} instead. To share one canvas, run the daemon ` +