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.
- package/CHANGELOG.md +38 -0
- package/dist/canvas/index.js +2 -2
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +108 -1027
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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:
|
|
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
|
|
198
|
-
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
293
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
327
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
389
|
-
|
|
390
|
-
|
|
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' ||
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
507
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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: [
|
|
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 =
|
|
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' ||
|
|
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:
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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 } : {}),
|