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
@@ -24,7 +24,10 @@ const positionShape = z.object({ x: z.number(), y: z.number() });
24
24
  // ── intent.signal (canvas_intent action "signal") ─────────────
25
25
 
26
26
  const intentSignalShape = {
27
- kind: z.enum(INTENT_KINDS).optional().describe('create | move | connect | remove | edit — the move about to be made.'),
27
+ kind: z
28
+ .enum(INTENT_KINDS)
29
+ .optional()
30
+ .describe('create | move | connect | remove | edit — the move about to be made.'),
28
31
  position: positionShape.optional().describe('World coords: where a create forms, or the destination of a move.'),
29
32
  nodeId: z.string().optional().describe('The existing node a move/edit/remove targets.'),
30
33
  edge: z
@@ -66,7 +69,12 @@ const intentUpdateShape = {
66
69
  confidence: z.number().optional().describe('0..1 → ghost opacity/solidity.'),
67
70
  seq: z.number().optional().describe('New ordering hint.'),
68
71
  ttlMs: z.number().optional().describe('Reset the TTL to this many ms from now.'),
69
- vetoed: z.boolean().optional().describe('Veto the intent: dissolves the ghost AND poisons the id so a later linked settle is rejected (same as clear { vetoed:true }).'),
72
+ vetoed: z
73
+ .boolean()
74
+ .optional()
75
+ .describe(
76
+ 'Veto the intent: dissolves the ghost AND poisons the id so a later linked settle is rejected (same as clear { vetoed:true }).',
77
+ ),
70
78
  };
71
79
 
72
80
  const intentUpdateSchema = z.looseObject(intentUpdateShape);
@@ -104,14 +112,16 @@ const intentClearOperation = defineOperation<z.infer<typeof intentClearSchema>,
104
112
  method: 'DELETE',
105
113
  path: '/api/canvas/ax/intent/:id',
106
114
  readInput: async (req, params, url) => {
107
- const query: Record<string, string> = {};
115
+ const query: Record<string, unknown> = {};
108
116
  url.searchParams.forEach((value, key) => {
109
- query[key] = value;
117
+ // `vetoed` is a boolean in the op schema; query params arrive as strings.
118
+ // Coerce only the literal "true"/"false" forms — anything else passes
119
+ // through and fails validation loudly instead of silently becoming false.
120
+ query[key] = key === 'vetoed' && (value === 'true' || value === 'false') ? value === 'true' : value;
110
121
  });
111
122
  const body = await readJsonValue(req);
112
- const record = body !== null && typeof body === 'object' && !Array.isArray(body)
113
- ? body as Record<string, unknown>
114
- : {};
123
+ const record =
124
+ body !== null && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : {};
115
125
  return { ...query, ...record, ...params };
116
126
  },
117
127
  },
@@ -126,8 +136,4 @@ const intentClearOperation = defineOperation<z.infer<typeof intentClearSchema>,
126
136
  },
127
137
  });
128
138
 
129
- export const intentOperations: Operation[] = [
130
- intentSignalOperation,
131
- intentUpdateOperation,
132
- intentClearOperation,
133
- ];
139
+ export const intentOperations: Operation[] = [intentSignalOperation, intentUpdateOperation, intentClearOperation];
@@ -60,16 +60,20 @@ export function parseGraphPayloadData(value: unknown): Array<Record<string, unkn
60
60
 
61
61
  /** MCP json-render spec schema (moved from mcp/server.ts): full document or bare component. */
62
62
  const jsonRenderSpecSchema = z.union([
63
- z.object({
64
- root: z.string(),
65
- elements: z.record(z.string(), z.unknown()),
66
- state: z.record(z.string(), z.unknown()).optional(),
67
- }).passthrough(),
68
- z.object({
69
- type: z.string(),
70
- props: z.record(z.string(), z.unknown()).optional(),
71
- children: z.array(z.string()).optional(),
72
- }).passthrough(),
63
+ z
64
+ .object({
65
+ root: z.string(),
66
+ elements: z.record(z.string(), z.unknown()),
67
+ state: z.record(z.string(), z.unknown()).optional(),
68
+ })
69
+ .passthrough(),
70
+ z
71
+ .object({
72
+ type: z.string(),
73
+ props: z.record(z.string(), z.unknown()).optional(),
74
+ children: z.array(z.string()).optional(),
75
+ })
76
+ .passthrough(),
73
77
  ]);
74
78
 
75
79
  const htmlPrimitiveKindSchema = z.string().refine(isHtmlPrimitiveKind, 'Unknown HTML primitive kind');
@@ -89,14 +93,30 @@ function structuredNodeToolResult(result: unknown): { content: Array<{ type: 'te
89
93
  // ── jsonrender.add ────────────────────────────────────────────
90
94
 
91
95
  const jsonRenderAddShape = {
92
- intentId: z.string().optional().catch(undefined).describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
93
- title: z.string().optional().catch(undefined).describe('Optional node title. If omitted, PMX Canvas infers one from the root element.'),
94
- spec: z.unknown().describe('json-render spec. Prefer a complete {root, elements, state?} document; a single bare component object is accepted for legacy callers.'),
96
+ intentId: z
97
+ .string()
98
+ .optional()
99
+ .catch(undefined)
100
+ .describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
101
+ title: z
102
+ .string()
103
+ .optional()
104
+ .catch(undefined)
105
+ .describe('Optional node title. If omitted, PMX Canvas infers one from the root element.'),
106
+ spec: z
107
+ .unknown()
108
+ .describe(
109
+ 'json-render spec. Prefer a complete {root, elements, state?} document; a single bare component object is accepted for legacy callers.',
110
+ ),
95
111
  x: z.number().optional().catch(undefined).describe('Optional X position'),
96
112
  y: z.number().optional().catch(undefined).describe('Optional Y position'),
97
113
  width: z.number().optional().catch(undefined).describe('Optional node width'),
98
114
  height: z.number().optional().catch(undefined).describe('Optional node height'),
99
- strictSize: z.boolean().optional().catch(undefined).describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
115
+ strictSize: z
116
+ .boolean()
117
+ .optional()
118
+ .catch(undefined)
119
+ .describe('Keep explicit width/height fixed and scroll overflowing content instead of browser auto-fitting'),
100
120
  };
101
121
 
102
122
  const jsonRenderAddSchema = z.looseObject(jsonRenderAddShape);
@@ -115,7 +135,8 @@ const jsonRenderAddOperation = defineOperation<
115
135
  },
116
136
  mcp: {
117
137
  toolName: 'canvas_add_json_render_node',
118
- description: 'Create a native json-render canvas node from a complete spec. Use this for structured dashboards, forms, tables, and other interactive UI panels that should render directly inside PMX Canvas.',
138
+ description:
139
+ 'Create a native json-render canvas node from a complete spec. Use this for structured dashboards, forms, tables, and other interactive UI panels that should render directly inside PMX Canvas.',
119
140
  formatResult: (result) => structuredNodeToolResult(result),
120
141
  },
121
142
  handler: (input) => {
@@ -123,8 +144,7 @@ const jsonRenderAddOperation = defineOperation<
123
144
  const title = typeof body.title === 'string' ? body.title.trim() : '';
124
145
  // Legacy fallback: a body without an object `spec` is treated as the spec
125
146
  // itself (bare-component compatibility path).
126
- const rawSpec =
127
- body.spec && typeof body.spec === 'object' && !Array.isArray(body.spec) ? body.spec : body;
147
+ const rawSpec = body.spec && typeof body.spec === 'object' && !Array.isArray(body.spec) ? body.spec : body;
128
148
  const geometry = resolveCreateGeometry(body);
129
149
  try {
130
150
  return createCanvasJsonRenderNode({
@@ -194,24 +214,36 @@ export function streamJsonRenderCore(input: StreamJsonRenderInput): StreamJsonRe
194
214
  }
195
215
 
196
216
  const jsonRenderStreamShape = {
197
- intentId: z.string().optional().catch(undefined).describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
198
- nodeId: z.string().optional().catch(undefined).describe('Existing streaming node id to append to; omit to create a new streaming node'),
217
+ intentId: z
218
+ .string()
219
+ .optional()
220
+ .catch(undefined)
221
+ .describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
222
+ nodeId: z
223
+ .string()
224
+ .optional()
225
+ .catch(undefined)
226
+ .describe('Existing streaming node id to append to; omit to create a new streaming node'),
199
227
  title: z.string().optional().catch(undefined).describe('Title when creating a new streaming node'),
200
- patches: z.unknown().optional().describe('SpecStream patches to apply this call: JSON-Patch objects ({op,path,value}) or raw JSONL patch lines'),
228
+ patches: z
229
+ .unknown()
230
+ .optional()
231
+ .describe('SpecStream patches to apply this call: JSON-Patch objects ({op,path,value}) or raw JSONL patch lines'),
201
232
  done: z.boolean().optional().catch(undefined).describe('Set true on the final call to mark the stream complete'),
202
233
  x: z.number().optional().catch(undefined).describe('Optional X position (new node)'),
203
234
  y: z.number().optional().catch(undefined).describe('Optional Y position (new node)'),
204
235
  width: z.number().optional().catch(undefined).describe('Optional node width (new node)'),
205
236
  nodeHeight: z.number().optional().catch(undefined).describe('Optional node height (new node)'),
206
- strictSize: z.boolean().optional().catch(undefined).describe('Keep explicit node size fixed and scroll overflowing content (new node)'),
237
+ strictSize: z
238
+ .boolean()
239
+ .optional()
240
+ .catch(undefined)
241
+ .describe('Keep explicit node size fixed and scroll overflowing content (new node)'),
207
242
  };
208
243
 
209
244
  const jsonRenderStreamSchema = z.looseObject(jsonRenderStreamShape);
210
245
 
211
- const jsonRenderStreamOperation = defineOperation<
212
- z.infer<typeof jsonRenderStreamSchema>,
213
- StreamJsonRenderResult
214
- >({
246
+ const jsonRenderStreamOperation = defineOperation<z.infer<typeof jsonRenderStreamSchema>, StreamJsonRenderResult>({
215
247
  name: 'jsonrender.stream',
216
248
  mutates: true,
217
249
  input: jsonRenderStreamSchema,
@@ -222,7 +254,8 @@ const jsonRenderStreamOperation = defineOperation<
222
254
  },
223
255
  mcp: {
224
256
  toolName: 'canvas_stream_json_render_node',
225
- description: 'Progressively build a json-render node by streaming SpecStream patches, so a panel fills in live as you generate it. Omit nodeId on the first call to create a new streaming node (returns its id); pass that same nodeId on later calls to append more patches; set done=true on the final call. Each call updates the live node. Patches are JSON-Patch operations, e.g. {"op":"add","path":"/elements/card","value":{"type":"Card","props":{"title":"Live"},"children":[]}}, {"op":"replace","path":"/root","value":"card"}, {"op":"add","path":"/elements/card/children/-","value":"row1"}. Build the spec incrementally: set /root, add container elements, then append children. The server accumulates the spec (it is the source of truth); partial specs render what they can.',
257
+ description:
258
+ 'Progressively build a json-render node by streaming SpecStream patches, so a panel fills in live as you generate it. Omit nodeId on the first call to create a new streaming node (returns its id); pass that same nodeId on later calls to append more patches; set done=true on the final call. Each call updates the live node. Patches are JSON-Patch operations, e.g. {"op":"add","path":"/elements/card","value":{"type":"Card","props":{"title":"Live"},"children":[]}}, {"op":"replace","path":"/root","value":"card"}, {"op":"add","path":"/elements/card/children/-","value":"row1"}. Build the spec incrementally: set /root, add container elements, then append children. The server accumulates the spec (it is the source of truth); partial specs render what they can.',
226
259
  extraShape: {
227
260
  // Strict patch typing for the MCP surface only; the operation schema
228
261
  // stays loose so the HTTP route keeps tolerating malformed patch lists
@@ -230,7 +263,9 @@ const jsonRenderStreamOperation = defineOperation<
230
263
  patches: z
231
264
  .array(z.union([z.string(), z.record(z.string(), z.unknown())]))
232
265
  .optional()
233
- .describe('SpecStream patches to apply this call: JSON-Patch objects ({op,path,value}) or raw JSONL patch lines'),
266
+ .describe(
267
+ 'SpecStream patches to apply this call: JSON-Patch objects ({op,path,value}) or raw JSONL patch lines',
268
+ ),
234
269
  },
235
270
  buildInput: (input) => {
236
271
  // MCP names the frame height `nodeHeight`; the HTTP body uses `height`.
@@ -244,7 +279,7 @@ const jsonRenderStreamOperation = defineOperation<
244
279
  // node degrades to the bare { ok, id, nodeId } payload.
245
280
  let created: Record<string, unknown> = { ok: true, id, nodeId: id };
246
281
  try {
247
- const node = await host.invoker().invoke('node.get', { id, includeBlobs: true }) as CanvasNodeState;
282
+ const node = (await host.invoker().invoke('node.get', { id, includeBlobs: true })) as CanvasNodeState;
248
283
  created = { ok: true, node: compactNodePayload(node), id, nodeId: id };
249
284
  } catch {
250
285
  // keep the bare payload (legacy c.getNode → undefined path)
@@ -279,52 +314,114 @@ const jsonRenderStreamOperation = defineOperation<
279
314
  // ── graph.add ─────────────────────────────────────────────────
280
315
 
281
316
  const graphAddShape = {
282
- intentId: z.string().optional().catch(undefined).describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
317
+ intentId: z
318
+ .string()
319
+ .optional()
320
+ .catch(undefined)
321
+ .describe('Ghost intent id returned by canvas_intent signal. A vetoed or expired intent blocks this mutation.'),
283
322
  title: z.string().optional().catch(undefined).describe('Optional node title'),
284
- graphType: z.string().optional().catch(undefined).describe('Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo"), sparkline, dot-plot (or "dot"), bullet, slopegraph (or "slope")'),
323
+ graphType: z
324
+ .string()
325
+ .optional()
326
+ .catch(undefined)
327
+ .describe(
328
+ 'Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo"), sparkline, dot-plot (or "dot"), bullet, slopegraph (or "slope")',
329
+ ),
285
330
  data: z.unknown().optional().describe('Array of chart data objects'),
286
331
  xKey: z.string().optional().catch(undefined).describe('X-axis key (line/bar/area/scatter/stacked/composed)'),
287
- yKey: z.string().optional().catch(undefined).describe('Y-axis key (line/bar/area/scatter); falls back to barKey for composed'),
332
+ yKey: z
333
+ .string()
334
+ .optional()
335
+ .catch(undefined)
336
+ .describe('Y-axis key (line/bar/area/scatter); falls back to barKey for composed'),
288
337
  zKey: z.string().optional().catch(undefined).describe('Optional bubble-size key for scatter charts'),
289
338
  nameKey: z.string().optional().catch(undefined).describe('Name key for pie graphs'),
290
- valueKey: z.string().optional().catch(undefined).describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
339
+ valueKey: z
340
+ .string()
341
+ .optional()
342
+ .catch(undefined)
343
+ .describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
291
344
  axisKey: z.string().optional().catch(undefined).describe('Category key for radar charts'),
292
- metrics: z.array(z.string()).optional().catch(undefined).describe('Series keys to plot as radar polygons (defaults to non-axis numeric columns)'),
293
- series: z.array(z.string()).optional().catch(undefined).describe('Series keys for stacked-bar segments (defaults to non-x numeric columns)'),
345
+ metrics: z
346
+ .array(z.string())
347
+ .optional()
348
+ .catch(undefined)
349
+ .describe('Series keys to plot as radar polygons (defaults to non-axis numeric columns)'),
350
+ series: z
351
+ .array(z.string())
352
+ .optional()
353
+ .catch(undefined)
354
+ .describe('Series keys for stacked-bar segments (defaults to non-x numeric columns)'),
294
355
  barKey: z.string().optional().catch(undefined).describe('Bar series key for composed charts'),
295
356
  lineKey: z.string().optional().catch(undefined).describe('Line series key for composed charts'),
296
- aggregate: z.enum(['sum', 'count', 'avg']).optional().catch(undefined).describe('Optional aggregation for repeated x-axis values (line/bar/area/stacked)'),
357
+ aggregate: z
358
+ .enum(['sum', 'count', 'avg'])
359
+ .optional()
360
+ .catch(undefined)
361
+ .describe('Optional aggregation for repeated x-axis values (line/bar/area/stacked)'),
297
362
  color: z.string().optional().catch(undefined).describe('Optional series color (line/bar/area/scatter)'),
298
363
  colorBy: z
299
364
  .enum(['series', 'category', 'value', 'none'])
300
365
  .optional()
301
366
  .catch(undefined)
302
- .describe("Bar charts only: how bars are colored. 'series' (default) = single accent with one highlighted bar; 'category' = rotate palette per bar; 'value' = shade by magnitude; 'none' = flat. Prefer 'series' — color should encode data, not decorate."),
367
+ .describe(
368
+ "Bar charts only: how bars are colored. 'series' (default) = single accent with one highlighted bar; 'category' = rotate palette per bar; 'value' = shade by magnitude; 'none' = flat. Prefer 'series' — color should encode data, not decorate.",
369
+ ),
303
370
  highlight: z
304
371
  .union([z.number(), z.enum(['max', 'min'])])
305
372
  .nullable()
306
373
  .optional()
307
374
  .catch(undefined)
308
- .describe("Bar charts only, for colorBy='series': which bar gets the accent — 'max' (default), 'min', a 0-based index, or null for no emphasis."),
375
+ .describe(
376
+ "Bar charts only, for colorBy='series': which bar gets the accent — 'max' (default), 'min', a 0-based index, or null for no emphasis.",
377
+ ),
309
378
  barColor: z.string().optional().catch(undefined).describe('Optional bar color for composed charts'),
310
379
  lineColor: z.string().optional().catch(undefined).describe('Optional line color for composed charts'),
311
- labelKey: z.string().optional().catch(undefined).describe('Category label key for dot-plot / bullet / slopegraph rows'),
380
+ labelKey: z
381
+ .string()
382
+ .optional()
383
+ .catch(undefined)
384
+ .describe('Category label key for dot-plot / bullet / slopegraph rows'),
312
385
  targetKey: z.string().optional().catch(undefined).describe('Per-row target value key for bullet charts'),
313
- rangesKey: z.string().optional().catch(undefined).describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
386
+ rangesKey: z
387
+ .string()
388
+ .optional()
389
+ .catch(undefined)
390
+ .describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
314
391
  beforeKey: z.string().optional().catch(undefined).describe('Left-column value key for slopegraph'),
315
392
  afterKey: z.string().optional().catch(undefined).describe('Right-column value key for slopegraph'),
316
393
  beforeLabel: z.string().optional().catch(undefined).describe('Header label for the slopegraph left column'),
317
394
  afterLabel: z.string().optional().catch(undefined).describe('Header label for the slopegraph right column'),
318
- sort: z.enum(['asc', 'desc', 'none']).optional().catch(undefined).describe('Row sort order for dot-plot (defaults to desc)'),
395
+ sort: z
396
+ .enum(['asc', 'desc', 'none'])
397
+ .optional()
398
+ .catch(undefined)
399
+ .describe('Row sort order for dot-plot (defaults to desc)'),
319
400
  fill: z.boolean().optional().catch(undefined).describe('Sparkline: draw a light area fill under the line'),
320
- showEndDot: z.boolean().optional().catch(undefined).describe('Sparkline: draw a dot at the last point (default true)'),
401
+ showEndDot: z
402
+ .boolean()
403
+ .optional()
404
+ .catch(undefined)
405
+ .describe('Sparkline: draw a dot at the last point (default true)'),
321
406
  showMinMax: z.boolean().optional().catch(undefined).describe('Sparkline: mark the min and max points'),
322
407
  showValue: z.boolean().optional().catch(undefined).describe('Sparkline: print the last value inline'),
323
- colorByDirection: z.boolean().optional().catch(undefined).describe('Slopegraph: accent rising lines and mute falling ones (default off — lines use one neutral ink)'),
408
+ colorByDirection: z
409
+ .boolean()
410
+ .optional()
411
+ .catch(undefined)
412
+ .describe('Slopegraph: accent rising lines and mute falling ones (default off — lines use one neutral ink)'),
324
413
  // CHART CONTENT height — see the alias-triangle note at the top of this file.
325
414
  height: z.number().optional().catch(undefined).describe('Optional chart content height'),
326
- showLegend: z.boolean().optional().catch(undefined).describe('Show chart legend when supported; pass false for compact node layouts'),
327
- showLabels: z.boolean().optional().catch(undefined).describe('Show direct labels when supported, such as pie slice labels (defaults to true)'),
415
+ showLegend: z
416
+ .boolean()
417
+ .optional()
418
+ .catch(undefined)
419
+ .describe('Show chart legend when supported; pass false for compact node layouts'),
420
+ showLabels: z
421
+ .boolean()
422
+ .optional()
423
+ .catch(undefined)
424
+ .describe('Show direct labels when supported, such as pie slice labels (defaults to true)'),
328
425
  x: z.number().optional().catch(undefined).describe('Optional X position'),
329
426
  y: z.number().optional().catch(undefined).describe('Optional Y position'),
330
427
  width: z.number().optional().catch(undefined).describe('Optional node width'),
@@ -332,15 +429,16 @@ const graphAddShape = {
332
429
  nodeHeight: z.number().optional().catch(undefined).describe('Optional node height'),
333
430
  // Node FRAME height (SDK GraphNodeInput field name) — see the alias-triangle note.
334
431
  heightPx: z.number().optional().catch(undefined).describe('SDK alias for nodeHeight (node frame height)'),
335
- strictSize: z.boolean().optional().catch(undefined).describe('Keep explicit node size fixed and scroll overflowing content instead of browser auto-fitting'),
432
+ strictSize: z
433
+ .boolean()
434
+ .optional()
435
+ .catch(undefined)
436
+ .describe('Keep explicit node size fixed and scroll overflowing content instead of browser auto-fitting'),
336
437
  };
337
438
 
338
439
  const graphAddSchema = z.looseObject(graphAddShape);
339
440
 
340
- const graphAddOperation = defineOperation<
341
- z.infer<typeof graphAddSchema>,
342
- ReturnType<typeof createCanvasGraphNode>
343
- >({
441
+ const graphAddOperation = defineOperation<z.infer<typeof graphAddSchema>, ReturnType<typeof createCanvasGraphNode>>({
344
442
  name: 'graph.add',
345
443
  mutates: true,
346
444
  input: graphAddSchema,
@@ -351,9 +449,14 @@ const graphAddOperation = defineOperation<
351
449
  },
352
450
  mcp: {
353
451
  toolName: 'canvas_add_graph_node',
354
- description: 'Create a native graph node backed by the json-render chart catalog. Supports line, bar, pie, area, scatter, radar, stacked-bar, composed (bar+line), sparkline, dot-plot (Cleveland), bullet (Few KPI vs target), and slopegraph (paired before/after) graphs rendered directly inside PMX Canvas.',
452
+ description:
453
+ 'Create a native graph node backed by the json-render chart catalog. Supports line, bar, pie, area, scatter, radar, stacked-bar, composed (bar+line), sparkline, dot-plot (Cleveland), bullet (Few KPI vs target), and slopegraph (paired before/after) graphs rendered directly inside PMX Canvas.',
355
454
  extraShape: {
356
- graphType: z.string().describe('Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo"), sparkline, dot-plot (or "dot"), bullet, slopegraph (or "slope")'),
455
+ graphType: z
456
+ .string()
457
+ .describe(
458
+ 'Graph type: line, bar, pie, area, scatter, radar, stacked-bar (or "stack"), composed (or "combo"), sparkline, dot-plot (or "dot"), bullet, slopegraph (or "slope")',
459
+ ),
357
460
  data: z.array(z.record(z.string(), z.unknown())).describe('Array of chart data objects'),
358
461
  },
359
462
  formatResult: (result) => structuredNodeToolResult(result),
@@ -362,16 +465,15 @@ const graphAddOperation = defineOperation<
362
465
  const body: Record<string, unknown> = input;
363
466
  const title = typeof body.title === 'string' && body.title.trim() ? body.title.trim() : 'Graph';
364
467
  // `type` is the legacy HTTP-only graphType alias (not advertised over MCP).
365
- const graphType = typeof body.graphType === 'string' ? body.graphType : typeof body.type === 'string' ? body.type : 'line';
468
+ const graphType =
469
+ typeof body.graphType === 'string' ? body.graphType : typeof body.type === 'string' ? body.type : 'line';
366
470
  const data = parseGraphPayloadData(body.data);
367
471
  if (!data) {
368
472
  throw new OperationError('Missing required field: data.');
369
473
  }
370
474
  try {
371
475
  const aggregate =
372
- body.aggregate === 'sum' || body.aggregate === 'count' || body.aggregate === 'avg'
373
- ? body.aggregate
374
- : undefined;
476
+ body.aggregate === 'sum' || body.aggregate === 'count' || body.aggregate === 'avg' ? body.aggregate : undefined;
375
477
  const metrics = Array.isArray(body.metrics)
376
478
  ? body.metrics.filter((m: unknown): m is string => typeof m === 'string')
377
479
  : null;
@@ -385,9 +487,10 @@ const graphAddOperation = defineOperation<
385
487
  const width = pickPositiveNumber(body, 'width') ?? (size ? pickPositiveNumber(size, 'width') : undefined);
386
488
  // Node FRAME height. `body.height` is the CHART plot height (passed
387
489
  // through as `height` below) — see the alias-triangle note at the top.
388
- const nodeHeight = pickPositiveNumber(body, 'nodeHeight')
389
- ?? pickPositiveNumber(body, 'heightPx')
390
- ?? (size ? pickPositiveNumber(size, 'height') : undefined);
490
+ const nodeHeight =
491
+ pickPositiveNumber(body, 'nodeHeight') ??
492
+ pickPositiveNumber(body, 'heightPx') ??
493
+ (size ? pickPositiveNumber(size, 'height') : undefined);
391
494
  const showLegend = typeof body.showLegend === 'boolean' ? body.showLegend : undefined;
392
495
  const showLabels = typeof body.showLabels === 'boolean' ? body.showLabels : undefined;
393
496
  const colorBy =
@@ -395,11 +498,13 @@ const graphAddOperation = defineOperation<
395
498
  ? body.colorBy
396
499
  : undefined;
397
500
  const highlight =
398
- typeof body.highlight === 'number' || body.highlight === 'max' || body.highlight === 'min' || body.highlight === null
501
+ typeof body.highlight === 'number' ||
502
+ body.highlight === 'max' ||
503
+ body.highlight === 'min' ||
504
+ body.highlight === null
399
505
  ? body.highlight
400
506
  : undefined;
401
- const sort =
402
- body.sort === 'asc' || body.sort === 'desc' || body.sort === 'none' ? body.sort : undefined;
507
+ const sort = body.sort === 'asc' || body.sort === 'desc' || body.sort === 'none' ? body.sort : undefined;
403
508
  return createCanvasGraphNode({
404
509
  title,
405
510
  graphType,
@@ -470,7 +575,8 @@ const schemaDescribeOperation = defineOperation<
470
575
  },
471
576
  mcp: {
472
577
  toolName: 'canvas_describe_schema',
473
- description: 'Describe the current server-supported canvas create schemas, json-render component catalog, canonical examples, and related MCP entry points. Includes mcp.nodeTypeRouting, the authoritative map from node type to MCP creation tool.',
578
+ description:
579
+ 'Describe the current server-supported canvas create schemas, json-render component catalog, canonical examples, and related MCP entry points. Includes mcp.nodeTypeRouting, the authoritative map from node type to MCP creation tool.',
474
580
  formatResult: (result) => ({
475
581
  content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
476
582
  }),
@@ -495,40 +601,74 @@ const specValidateShape = {
495
601
  yKey: z.string().optional().catch(undefined).describe('Y-axis key for line/bar graphs'),
496
602
  zKey: z.string().optional().catch(undefined).describe('Optional bubble-size key for scatter charts'),
497
603
  nameKey: z.string().optional().catch(undefined).describe('Slice name key for pie graphs'),
498
- valueKey: z.string().optional().catch(undefined).describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
604
+ valueKey: z
605
+ .string()
606
+ .optional()
607
+ .catch(undefined)
608
+ .describe('Value key for pie slices, sparkline, dot-plot, and the bullet measure'),
499
609
  axisKey: z.string().optional().catch(undefined).describe('Category key for radar charts'),
500
610
  metrics: z.array(z.string()).optional().catch(undefined).describe('Series keys to plot as radar polygons'),
501
611
  series: z.array(z.string()).optional().catch(undefined).describe('Series keys for stacked-bar segments'),
502
612
  barKey: z.string().optional().catch(undefined).describe('Bar series key for composed charts'),
503
613
  lineKey: z.string().optional().catch(undefined).describe('Line series key for composed charts'),
504
- aggregate: z.enum(['sum', 'count', 'avg']).optional().catch(undefined).describe('Optional aggregation for repeated keys'),
614
+ aggregate: z
615
+ .enum(['sum', 'count', 'avg'])
616
+ .optional()
617
+ .catch(undefined)
618
+ .describe('Optional aggregation for repeated keys'),
505
619
  color: z.string().optional().catch(undefined).describe('Optional graph color'),
506
- colorBy: z.enum(['series', 'category', 'value', 'none']).optional().catch(undefined).describe("Bar charts only: how bars are colored (default 'series')"),
507
- highlight: z.union([z.number(), z.enum(['max', 'min'])]).nullable().optional().catch(undefined).describe("Bar charts only, colorBy='series': which bar gets the accent"),
620
+ colorBy: z
621
+ .enum(['series', 'category', 'value', 'none'])
622
+ .optional()
623
+ .catch(undefined)
624
+ .describe("Bar charts only: how bars are colored (default 'series')"),
625
+ highlight: z
626
+ .union([z.number(), z.enum(['max', 'min'])])
627
+ .nullable()
628
+ .optional()
629
+ .catch(undefined)
630
+ .describe("Bar charts only, colorBy='series': which bar gets the accent"),
508
631
  barColor: z.string().optional().catch(undefined).describe('Optional bar color for composed charts'),
509
632
  lineColor: z.string().optional().catch(undefined).describe('Optional line color for composed charts'),
510
- labelKey: z.string().optional().catch(undefined).describe('Category label key for dot-plot / bullet / slopegraph rows'),
633
+ labelKey: z
634
+ .string()
635
+ .optional()
636
+ .catch(undefined)
637
+ .describe('Category label key for dot-plot / bullet / slopegraph rows'),
511
638
  targetKey: z.string().optional().catch(undefined).describe('Per-row target value key for bullet charts'),
512
- rangesKey: z.string().optional().catch(undefined).describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
639
+ rangesKey: z
640
+ .string()
641
+ .optional()
642
+ .catch(undefined)
643
+ .describe('Per-row qualitative band thresholds key (number[]) for bullet charts'),
513
644
  beforeKey: z.string().optional().catch(undefined).describe('Left-column value key for slopegraph'),
514
645
  afterKey: z.string().optional().catch(undefined).describe('Right-column value key for slopegraph'),
515
646
  beforeLabel: z.string().optional().catch(undefined).describe('Header label for the slopegraph left column'),
516
647
  afterLabel: z.string().optional().catch(undefined).describe('Header label for the slopegraph right column'),
517
- sort: z.enum(['asc', 'desc', 'none']).optional().catch(undefined).describe('Row sort order for dot-plot (defaults to desc)'),
648
+ sort: z
649
+ .enum(['asc', 'desc', 'none'])
650
+ .optional()
651
+ .catch(undefined)
652
+ .describe('Row sort order for dot-plot (defaults to desc)'),
518
653
  fill: z.boolean().optional().catch(undefined).describe('Sparkline: draw a light area fill under the line'),
519
- showEndDot: z.boolean().optional().catch(undefined).describe('Sparkline: draw a dot at the last point (default true)'),
654
+ showEndDot: z
655
+ .boolean()
656
+ .optional()
657
+ .catch(undefined)
658
+ .describe('Sparkline: draw a dot at the last point (default true)'),
520
659
  showMinMax: z.boolean().optional().catch(undefined).describe('Sparkline: mark the min and max points'),
521
660
  showValue: z.boolean().optional().catch(undefined).describe('Sparkline: print the last value inline'),
522
- colorByDirection: z.boolean().optional().catch(undefined).describe('Slopegraph: accent rising lines and mute falling ones (default off)'),
661
+ colorByDirection: z
662
+ .boolean()
663
+ .optional()
664
+ .catch(undefined)
665
+ .describe('Slopegraph: accent rising lines and mute falling ones (default off)'),
523
666
  height: z.number().optional().catch(undefined).describe('Optional graph content height'),
524
667
  };
525
668
 
526
669
  const specValidateSchema = z.looseObject(specValidateShape);
527
670
 
528
- const specValidateOperation = defineOperation<
529
- z.infer<typeof specValidateSchema>,
530
- Record<string, unknown>
531
- >({
671
+ const specValidateOperation = defineOperation<z.infer<typeof specValidateSchema>, Record<string, unknown>>({
532
672
  name: 'spec.validate',
533
673
  mutates: false,
534
674
  input: specValidateSchema,
@@ -541,13 +681,17 @@ const specValidateOperation = defineOperation<
541
681
  },
542
682
  mcp: {
543
683
  toolName: 'canvas_validate_spec',
544
- description: 'Validate a json-render spec, graph payload, or HTML primitive payload without creating a node. Returns normalized metadata the server would accept.',
684
+ description:
685
+ 'Validate a json-render spec, graph payload, or HTML primitive payload without creating a node. Returns normalized metadata the server would accept.',
545
686
  extraShape: {
546
687
  type: z.enum(['json-render', 'graph', 'html-primitive']).describe('Structured payload type to validate'),
547
688
  spec: jsonRenderSpecSchema.optional().describe('json-render spec to validate when type="json-render"'),
548
689
  kind: htmlPrimitiveKindSchema.optional().describe('HTML primitive kind when type="html-primitive"'),
549
690
  primitive: htmlPrimitiveKindSchema.optional().describe('Alias for kind when type="html-primitive"'),
550
- primitiveData: z.record(z.string(), z.unknown()).optional().describe('HTML primitive data payload when type="html-primitive"'),
691
+ primitiveData: z
692
+ .record(z.string(), z.unknown())
693
+ .optional()
694
+ .describe('HTML primitive data payload when type="html-primitive"'),
551
695
  data: z.array(z.record(z.string(), z.unknown())).optional().describe('Graph dataset when type="graph"'),
552
696
  },
553
697
  buildInput: (input) => {
@@ -566,7 +710,9 @@ const specValidateOperation = defineOperation<
566
710
  if (body.ok === false) {
567
711
  // Legacy MCP surfaced the bare validation message with isError.
568
712
  return {
569
- content: [{ type: 'text' as const, text: typeof body.error === 'string' ? body.error : 'Validation failed.' }],
713
+ content: [
714
+ { type: 'text' as const, text: typeof body.error === 'string' ? body.error : 'Validation failed.' },
715
+ ],
570
716
  isError: true,
571
717
  };
572
718
  }
@@ -582,10 +728,7 @@ const specValidateOperation = defineOperation<
582
728
 
583
729
  try {
584
730
  if (rawType === 'json-render') {
585
- const rawSpec =
586
- body.spec && typeof body.spec === 'object' && !Array.isArray(body.spec)
587
- ? body.spec
588
- : body;
731
+ const rawSpec = body.spec && typeof body.spec === 'object' && !Array.isArray(body.spec) ? body.spec : body;
589
732
  return validateStructuredCanvasPayload({
590
733
  type: 'json-render',
591
734
  spec: rawSpec,
@@ -593,11 +736,8 @@ const specValidateOperation = defineOperation<
593
736
  }
594
737
 
595
738
  if (rawType === 'html-primitive') {
596
- const kind = typeof body.kind === 'string'
597
- ? body.kind
598
- : typeof body.primitive === 'string'
599
- ? body.primitive
600
- : '';
739
+ const kind =
740
+ typeof body.kind === 'string' ? body.kind : typeof body.primitive === 'string' ? body.primitive : '';
601
741
  const data = isRecord(body.data) ? body.data : {};
602
742
  return validateStructuredCanvasPayload({
603
743
  type: 'html-primitive',
@@ -615,30 +755,31 @@ const specValidateOperation = defineOperation<
615
755
  }
616
756
 
617
757
  const aggregate =
618
- body.aggregate === 'sum' || body.aggregate === 'count' || body.aggregate === 'avg'
619
- ? body.aggregate
620
- : undefined;
758
+ body.aggregate === 'sum' || body.aggregate === 'count' || body.aggregate === 'avg' ? body.aggregate : undefined;
621
759
  const colorBy =
622
760
  body.colorBy === 'series' || body.colorBy === 'category' || body.colorBy === 'value' || body.colorBy === 'none'
623
761
  ? body.colorBy
624
762
  : undefined;
625
763
  const highlight =
626
- typeof body.highlight === 'number' || body.highlight === 'max' || body.highlight === 'min' || body.highlight === null
764
+ typeof body.highlight === 'number' ||
765
+ body.highlight === 'max' ||
766
+ body.highlight === 'min' ||
767
+ body.highlight === null
627
768
  ? body.highlight
628
769
  : undefined;
629
- const sort =
630
- body.sort === 'asc' || body.sort === 'desc' || body.sort === 'none' ? body.sort : undefined;
770
+ const sort = body.sort === 'asc' || body.sort === 'desc' || body.sort === 'none' ? body.sort : undefined;
631
771
 
632
772
  return validateStructuredCanvasPayload({
633
773
  type: 'graph',
634
774
  graph: {
635
775
  title: typeof body.title === 'string' && body.title.trim() ? body.title.trim() : 'Graph',
636
776
  // `typeName` is the legacy HTTP-only graphType alias on this route.
637
- graphType: typeof body.graphType === 'string'
638
- ? body.graphType
639
- : typeof body.typeName === 'string'
640
- ? body.typeName
641
- : 'line',
777
+ graphType:
778
+ typeof body.graphType === 'string'
779
+ ? body.graphType
780
+ : typeof body.typeName === 'string'
781
+ ? body.typeName
782
+ : 'line',
642
783
  data,
643
784
  ...(typeof body.xKey === 'string' ? { xKey: body.xKey } : {}),
644
785
  ...(typeof body.yKey === 'string' ? { yKey: body.yKey } : {}),