pmx-canvas 0.1.9 → 0.1.11
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 +154 -0
- package/dist/canvas/index.js +44 -44
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +115 -115
- package/dist/types/client/canvas/auto-fit.d.ts +1 -1
- package/dist/types/json-render/catalog.d.ts +326 -310
- package/dist/types/json-render/charts/components.d.ts +18 -0
- package/dist/types/json-render/charts/definitions.d.ts +4 -0
- package/dist/types/json-render/charts/extra-components.d.ts +3 -0
- package/dist/types/json-render/charts/extra-definitions.d.ts +6 -0
- package/dist/types/json-render/server.d.ts +4 -0
- package/dist/types/server/canvas-operations.d.ts +2 -0
- package/dist/types/server/index.d.ts +2 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +9 -0
- package/src/cli/agent.ts +103 -5
- package/src/cli/index.ts +6 -3
- package/src/client/canvas/CanvasNode.tsx +3 -1
- package/src/client/canvas/auto-fit.ts +3 -3
- package/src/json-render/catalog.ts +9 -0
- package/src/json-render/charts/components.tsx +18 -10
- package/src/json-render/charts/definitions.ts +4 -0
- package/src/json-render/charts/extra-components.tsx +23 -16
- package/src/json-render/charts/extra-definitions.ts +6 -0
- package/src/json-render/renderer/index.css +61 -0
- package/src/json-render/renderer/index.tsx +22 -0
- package/src/json-render/server.ts +11 -11
- package/src/mcp/server.ts +10 -0
- package/src/server/canvas-operations.ts +21 -1
- package/src/server/canvas-schema.ts +5 -0
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/diagram-presets.ts +82 -4
- package/src/server/index.ts +7 -1
- package/src/server/server.ts +33 -2
|
@@ -26,6 +26,8 @@ interface PieChartProps {
|
|
|
26
26
|
nameKey: string;
|
|
27
27
|
valueKey: string;
|
|
28
28
|
height?: number | null;
|
|
29
|
+
showLegend?: boolean | null;
|
|
30
|
+
showLabels?: boolean | null;
|
|
29
31
|
}
|
|
30
32
|
export declare const axisStyle: {
|
|
31
33
|
fontSize: number;
|
|
@@ -38,6 +40,22 @@ export declare const tooltipStyle: {
|
|
|
38
40
|
color: string;
|
|
39
41
|
fontSize: number;
|
|
40
42
|
};
|
|
43
|
+
export declare const chartMargin: {
|
|
44
|
+
top: number;
|
|
45
|
+
right: number;
|
|
46
|
+
bottom: number;
|
|
47
|
+
left: number;
|
|
48
|
+
};
|
|
49
|
+
export declare const polarChartMargin: {
|
|
50
|
+
top: number;
|
|
51
|
+
right: number;
|
|
52
|
+
bottom: number;
|
|
53
|
+
left: number;
|
|
54
|
+
};
|
|
55
|
+
export declare const axisTickMargin = 8;
|
|
56
|
+
export declare const legendMargin: {
|
|
57
|
+
top: number;
|
|
58
|
+
};
|
|
41
59
|
/** Shared wrapper for cartesian charts (Line + Bar). */
|
|
42
60
|
export declare function CartesianChart({ props, children, className, }: {
|
|
43
61
|
props: CartesianChartProps;
|
|
@@ -81,6 +81,8 @@ export declare const chartComponentDefinitions: {
|
|
|
81
81
|
nameKey: z.ZodString;
|
|
82
82
|
valueKey: z.ZodString;
|
|
83
83
|
height: z.ZodNullable<z.ZodNumber>;
|
|
84
|
+
showLegend: z.ZodOptional<z.ZodBoolean>;
|
|
85
|
+
showLabels: z.ZodOptional<z.ZodBoolean>;
|
|
84
86
|
}, z.core.$strip>;
|
|
85
87
|
readonly description: "Pie chart for showing proportions. Provide data as an array of objects with nameKey and valueKey fields.";
|
|
86
88
|
readonly example: {
|
|
@@ -98,6 +100,8 @@ export declare const chartComponentDefinitions: {
|
|
|
98
100
|
readonly nameKey: "name";
|
|
99
101
|
readonly valueKey: "share";
|
|
100
102
|
readonly height: null;
|
|
103
|
+
readonly showLegend: true;
|
|
104
|
+
readonly showLabels: true;
|
|
101
105
|
};
|
|
102
106
|
};
|
|
103
107
|
};
|
|
@@ -26,6 +26,7 @@ interface RadarChartProps {
|
|
|
26
26
|
axisKey: string;
|
|
27
27
|
metrics: string[];
|
|
28
28
|
height?: number | null;
|
|
29
|
+
showLegend?: boolean | null;
|
|
29
30
|
}
|
|
30
31
|
declare function ChartRadarChart({ props }: BaseComponentProps<RadarChartProps>): import("react/jsx-runtime").JSX.Element;
|
|
31
32
|
interface StackedBarChartProps {
|
|
@@ -35,6 +36,7 @@ interface StackedBarChartProps {
|
|
|
35
36
|
series: string[];
|
|
36
37
|
aggregate?: 'sum' | 'count' | 'avg' | null;
|
|
37
38
|
height?: number | null;
|
|
39
|
+
showLegend?: boolean | null;
|
|
38
40
|
}
|
|
39
41
|
declare function ChartStackedBarChart({ props }: BaseComponentProps<StackedBarChartProps>): import("react/jsx-runtime").JSX.Element;
|
|
40
42
|
interface ComposedChartProps {
|
|
@@ -46,6 +48,7 @@ interface ComposedChartProps {
|
|
|
46
48
|
barColor?: string | null;
|
|
47
49
|
lineColor?: string | null;
|
|
48
50
|
height?: number | null;
|
|
51
|
+
showLegend?: boolean | null;
|
|
49
52
|
}
|
|
50
53
|
declare function ChartComposedChart({ props }: BaseComponentProps<ComposedChartProps>): import("react/jsx-runtime").JSX.Element;
|
|
51
54
|
export declare const extraChartComponents: {
|
|
@@ -83,6 +83,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
83
83
|
axisKey: z.ZodString;
|
|
84
84
|
metrics: z.ZodArray<z.ZodString>;
|
|
85
85
|
height: z.ZodNullable<z.ZodNumber>;
|
|
86
|
+
showLegend: z.ZodOptional<z.ZodBoolean>;
|
|
86
87
|
}, z.core.$strip>;
|
|
87
88
|
readonly description: "Radar chart for comparing multiple metrics across categories. Each metric in `metrics` is plotted as its own polygon.";
|
|
88
89
|
readonly example: {
|
|
@@ -103,6 +104,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
103
104
|
readonly axisKey: "skill";
|
|
104
105
|
readonly metrics: readonly ["alice", "bob"];
|
|
105
106
|
readonly height: null;
|
|
107
|
+
readonly showLegend: true;
|
|
106
108
|
};
|
|
107
109
|
};
|
|
108
110
|
readonly StackedBarChart: {
|
|
@@ -117,6 +119,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
117
119
|
avg: "avg";
|
|
118
120
|
}>>;
|
|
119
121
|
height: z.ZodNullable<z.ZodNumber>;
|
|
122
|
+
showLegend: z.ZodOptional<z.ZodBoolean>;
|
|
120
123
|
}, z.core.$strip>;
|
|
121
124
|
readonly description: "Stacked bar chart for compositional data. Each entry in `series` is plotted as its own bar segment per x value.";
|
|
122
125
|
readonly example: {
|
|
@@ -141,6 +144,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
141
144
|
readonly series: readonly ["north", "south", "east"];
|
|
142
145
|
readonly aggregate: null;
|
|
143
146
|
readonly height: null;
|
|
147
|
+
readonly showLegend: true;
|
|
144
148
|
};
|
|
145
149
|
};
|
|
146
150
|
readonly ComposedChart: {
|
|
@@ -153,6 +157,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
153
157
|
barColor: z.ZodNullable<z.ZodString>;
|
|
154
158
|
lineColor: z.ZodNullable<z.ZodString>;
|
|
155
159
|
height: z.ZodNullable<z.ZodNumber>;
|
|
160
|
+
showLegend: z.ZodOptional<z.ZodBoolean>;
|
|
156
161
|
}, z.core.$strip>;
|
|
157
162
|
readonly description: "Combined bar + line chart for paired metrics (e.g. counts + a derived rate) on the same axis.";
|
|
158
163
|
readonly example: {
|
|
@@ -176,6 +181,7 @@ export declare const extraChartComponentDefinitions: {
|
|
|
176
181
|
readonly barColor: null;
|
|
177
182
|
readonly lineColor: null;
|
|
178
183
|
readonly height: null;
|
|
184
|
+
readonly showLegend: true;
|
|
179
185
|
};
|
|
180
186
|
};
|
|
181
187
|
};
|
|
@@ -10,6 +10,7 @@ export interface JsonRenderNodeInput {
|
|
|
10
10
|
y?: number;
|
|
11
11
|
width?: number;
|
|
12
12
|
height?: number;
|
|
13
|
+
strictSize?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export interface GraphNodeInput {
|
|
15
16
|
title?: string;
|
|
@@ -30,10 +31,13 @@ export interface GraphNodeInput {
|
|
|
30
31
|
barColor?: string;
|
|
31
32
|
lineColor?: string;
|
|
32
33
|
height?: number;
|
|
34
|
+
showLegend?: boolean;
|
|
35
|
+
showLabels?: boolean;
|
|
33
36
|
x?: number;
|
|
34
37
|
y?: number;
|
|
35
38
|
width?: number;
|
|
36
39
|
heightPx?: number;
|
|
40
|
+
strictSize?: boolean;
|
|
37
41
|
}
|
|
38
42
|
export declare const JSON_RENDER_NODE_SIZE: {
|
|
39
43
|
width: number;
|
|
@@ -32,6 +32,7 @@ export interface CanvasStructuredNodeUpdateInput extends Omit<CanvasGraphNodeUpd
|
|
|
32
32
|
content?: unknown;
|
|
33
33
|
data?: unknown;
|
|
34
34
|
arrangeLocked?: unknown;
|
|
35
|
+
strictSize?: boolean;
|
|
35
36
|
chartHeight?: unknown;
|
|
36
37
|
}
|
|
37
38
|
interface CanvasAddNodeInput {
|
|
@@ -46,6 +47,7 @@ interface CanvasAddNodeInput {
|
|
|
46
47
|
defaultWidth?: number;
|
|
47
48
|
defaultHeight?: number;
|
|
48
49
|
fileMode?: 'path' | 'inline' | 'auto';
|
|
50
|
+
strictSize?: boolean;
|
|
49
51
|
}
|
|
50
52
|
interface CanvasCreateGroupInput {
|
|
51
53
|
title?: string;
|
|
@@ -27,6 +27,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
27
27
|
y?: number;
|
|
28
28
|
width?: number;
|
|
29
29
|
height?: number;
|
|
30
|
+
strictSize?: boolean;
|
|
30
31
|
}): string;
|
|
31
32
|
addWebpageNode(input: {
|
|
32
33
|
title?: string;
|
|
@@ -35,6 +36,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
35
36
|
y?: number;
|
|
36
37
|
width?: number;
|
|
37
38
|
height?: number;
|
|
39
|
+
strictSize?: boolean;
|
|
38
40
|
}): Promise<{
|
|
39
41
|
ok: boolean;
|
|
40
42
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -144,6 +144,11 @@ pmx-canvas open
|
|
|
144
144
|
pmx-canvas arrange --layout flow
|
|
145
145
|
pmx-canvas focus <node-id> --no-pan # Select/raise without moving the user's viewport
|
|
146
146
|
pmx-canvas fit --width 1440 --height 900 # Fit the whole board for screenshots/review
|
|
147
|
+
pmx-canvas screenshot --output ./canvas.png # Shorthand for `webview screenshot`
|
|
148
|
+
pmx-canvas json-render --schema --summary # Inspect json-render component catalog
|
|
149
|
+
pmx-canvas json-render --example --component Table
|
|
150
|
+
pmx-canvas node add --type markdown --title "Long doc" --strict-size # Scroll instead of auto-fit
|
|
151
|
+
pmx-canvas node add --type graph --graphType pie --data-file metrics.json --show-legend false --show-labels false
|
|
147
152
|
pmx-canvas node update <node-id> --spec-file ./dashboard.json
|
|
148
153
|
pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary
|
|
149
154
|
pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx --deps recharts --include-logs
|
|
@@ -170,6 +175,10 @@ pmx-canvas spatial
|
|
|
170
175
|
- `search`, `layout`, `status`, `arrange`, `focus` — inspect and navigate the canvas. Prefer
|
|
171
176
|
`focus --no-pan` when you only need to select/raise a node without hijacking the human's camera.
|
|
172
177
|
- `fit [id ...]` — set the server viewport to fit the whole canvas or selected nodes before screenshots or whole-board review
|
|
178
|
+
- `screenshot --output <path>` — top-level shortcut for `webview screenshot`; supports `--format png|jpeg|webp` and `--quality`
|
|
179
|
+
- `json-render --schema|--examples` — inspect the json-render component catalog with `--component`/`--field` filters; same data as `node schema --type json-render` in a more direct shape
|
|
180
|
+
- `--strict-size` (alias `--scroll-overflow`) on `node add`/`node update` — keep explicit width/height fixed and scroll overflowing content instead of letting the renderer auto-fit. Useful for long markdown, dense webpages, and dashboards that should fit a tile-sized frame.
|
|
181
|
+
- `--show-legend false` / `--show-labels false` on `node add --type graph` and `graph add` — hide chart legends and pie slice labels for compact graph nodes in tile-style boards.
|
|
173
182
|
- `open` — open the current workbench in the browser
|
|
174
183
|
- `pin --list|--clear|<ids...>` — manage context pins
|
|
175
184
|
- `undo`, `redo`, `history` — time travel
|
package/src/cli/agent.ts
CHANGED
|
@@ -155,7 +155,7 @@ function parseFlags(args: string[]): { positional: string[]; flags: Record<strin
|
|
|
155
155
|
const BOOL_FLAGS = new Set([
|
|
156
156
|
'help', 'h', 'ids', 'stdin', 'yes', 'list', 'clear', 'set', 'animated', 'dry-run',
|
|
157
157
|
'no-open-in-canvas', 'lock-arrange', 'unlock-arrange', 'json', 'compact', 'summary',
|
|
158
|
-
'verbose', 'include-logs', 'no-pan',
|
|
158
|
+
'verbose', 'include-logs', 'no-pan', 'schema', 'example', 'examples', 'strict-size', 'scroll-overflow',
|
|
159
159
|
]);
|
|
160
160
|
for (let i = 0; i < args.length; i++) {
|
|
161
161
|
const arg = args[i];
|
|
@@ -241,6 +241,18 @@ function optionalPositiveFiniteFlagWithAliases(
|
|
|
241
241
|
return undefined;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
function optionalBooleanFlag(flags: Record<string, string | true>, name: string, hint: string): boolean | undefined {
|
|
245
|
+
const val = flags[name];
|
|
246
|
+
if (val === undefined) return undefined;
|
|
247
|
+
if (val === true || val === 'true') return true;
|
|
248
|
+
if (val === 'false') return false;
|
|
249
|
+
die(`Invalid value for --${name}: ${String(val)}`, hint);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function applyStrictSizeFlags(body: Record<string, unknown>, flags: Record<string, string | true>): void {
|
|
253
|
+
if (flags['strict-size'] || flags['scroll-overflow']) body.strictSize = true;
|
|
254
|
+
}
|
|
255
|
+
|
|
244
256
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
245
257
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
246
258
|
}
|
|
@@ -598,6 +610,7 @@ async function buildJsonRenderRequestBody(
|
|
|
598
610
|
width: 'Use a positive number, e.g. --width 840',
|
|
599
611
|
height: 'Use a positive number, e.g. --height 620',
|
|
600
612
|
});
|
|
613
|
+
applyStrictSizeFlags(body, flags);
|
|
601
614
|
return body;
|
|
602
615
|
}
|
|
603
616
|
|
|
@@ -657,6 +670,10 @@ async function buildGraphRequestBody(
|
|
|
657
670
|
if (color) body.color = color;
|
|
658
671
|
if (barColor) body.barColor = barColor;
|
|
659
672
|
if (lineColor) body.lineColor = lineColor;
|
|
673
|
+
const showLegend = optionalBooleanFlag(flags, 'show-legend', 'Use --show-legend true or --show-legend false');
|
|
674
|
+
const showLabels = optionalBooleanFlag(flags, 'show-labels', 'Use --show-labels true or --show-labels false');
|
|
675
|
+
if (showLegend !== undefined) body.showLegend = showLegend;
|
|
676
|
+
if (showLabels !== undefined) body.showLabels = showLabels;
|
|
660
677
|
|
|
661
678
|
const chartHeight = optionalPositiveFiniteFlag(flags, 'chart-height', 'Use a positive number, e.g. --chart-height 300');
|
|
662
679
|
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
|
|
@@ -674,6 +691,7 @@ async function buildGraphRequestBody(
|
|
|
674
691
|
if (y !== undefined) body.y = y;
|
|
675
692
|
if (width !== undefined) body.width = width;
|
|
676
693
|
if (nodeHeight !== undefined) body.nodeHeight = nodeHeight;
|
|
694
|
+
applyStrictSizeFlags(body, flags);
|
|
677
695
|
return body;
|
|
678
696
|
}
|
|
679
697
|
|
|
@@ -1059,6 +1077,7 @@ cmd('node add', 'Add a node to the canvas', [
|
|
|
1059
1077
|
width: 'Use a positive number, e.g. --width 500',
|
|
1060
1078
|
height: 'Use a positive number, e.g. --height 280',
|
|
1061
1079
|
});
|
|
1080
|
+
applyStrictSizeFlags(body, flags);
|
|
1062
1081
|
|
|
1063
1082
|
// Support --stdin for piping content
|
|
1064
1083
|
if (flags.stdin) {
|
|
@@ -1073,6 +1092,37 @@ cmd('node add', 'Add a node to the canvas', [
|
|
|
1073
1092
|
output(result);
|
|
1074
1093
|
});
|
|
1075
1094
|
|
|
1095
|
+
cmd('json-render', 'Show json-render schema and canonical examples', [
|
|
1096
|
+
'pmx-canvas json-render --schema --summary',
|
|
1097
|
+
'pmx-canvas json-render --examples',
|
|
1098
|
+
'pmx-canvas json-render --example --component Table',
|
|
1099
|
+
'pmx-canvas json-render --schema --component Badge --field variant',
|
|
1100
|
+
], async (args) => {
|
|
1101
|
+
const { flags } = parseFlags(args);
|
|
1102
|
+
if (flags.help || flags.h) return showCommandHelp('json-render');
|
|
1103
|
+
|
|
1104
|
+
const schema = await loadCanvasSchema();
|
|
1105
|
+
const componentName = getStringFlag(flags, 'component');
|
|
1106
|
+
const fieldName = getStringFlag(flags, 'field');
|
|
1107
|
+
|
|
1108
|
+
if (flags.example || flags.examples) {
|
|
1109
|
+
if (fieldName) die('--field is only supported with --schema.', 'Use: pmx-canvas json-render --schema --component Table --field rows');
|
|
1110
|
+
if (componentName) {
|
|
1111
|
+
const component = schema.jsonRender.components.find((entry) => entry.type === componentName);
|
|
1112
|
+
if (!component) die(`Unknown json-render component: ${componentName}`, 'Run: pmx-canvas json-render --schema --summary');
|
|
1113
|
+
output({ component: component.type, example: component.example });
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
output({
|
|
1117
|
+
rootShape: schema.jsonRender.rootShape,
|
|
1118
|
+
examples: Object.fromEntries(schema.jsonRender.components.map((entry) => [entry.type, entry.example])),
|
|
1119
|
+
});
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
output(filterJsonRenderSchemaView(schema.jsonRender, flags));
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1076
1126
|
cmd('graph add', 'Add a graph node to the canvas', [
|
|
1077
1127
|
'pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1078
1128
|
'pmx-canvas graph add --graphType composed --data \'[{"day":"Mon","visits":10,"conversion":0.4}]\' --xKey day --barKey visits --lineKey conversion',
|
|
@@ -1089,6 +1139,7 @@ cmd('node schema', 'Describe server-supported node create schemas and canonical
|
|
|
1089
1139
|
'pmx-canvas node schema',
|
|
1090
1140
|
'pmx-canvas node schema --type webpage',
|
|
1091
1141
|
'pmx-canvas node schema --type json-render',
|
|
1142
|
+
'pmx-canvas json-render --schema --summary',
|
|
1092
1143
|
'pmx-canvas node schema --type json-render --component Table',
|
|
1093
1144
|
'pmx-canvas node schema --type webpage --field url',
|
|
1094
1145
|
'pmx-canvas node schema --summary',
|
|
@@ -1216,6 +1267,7 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1216
1267
|
'pmx-canvas node update <node-id> --width 840 --height 620',
|
|
1217
1268
|
'pmx-canvas node update <node-id> --spec-file ./dashboard.json',
|
|
1218
1269
|
'pmx-canvas node update <graph-id> --data-file ./metrics.json --chart-height 420',
|
|
1270
|
+
'pmx-canvas node update <node-id> --pinned true',
|
|
1219
1271
|
'pmx-canvas node update <node-id> --lock-arrange',
|
|
1220
1272
|
], async (args) => {
|
|
1221
1273
|
const { positional, flags } = parseFlags(args);
|
|
@@ -1234,6 +1286,17 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1234
1286
|
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 300');
|
|
1235
1287
|
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 840');
|
|
1236
1288
|
const height = optionalPositiveFiniteFlag(flags, 'height', 'Use a positive number, e.g. --height 620');
|
|
1289
|
+
const nodeHeight = optionalPositiveFiniteFlagWithAliases(
|
|
1290
|
+
flags,
|
|
1291
|
+
'Use a positive number, e.g. --node-height 620',
|
|
1292
|
+
'node-height',
|
|
1293
|
+
'nodeHeight',
|
|
1294
|
+
);
|
|
1295
|
+
if (height !== undefined && nodeHeight !== undefined) {
|
|
1296
|
+
die('Use either --height/--node-height, not both.');
|
|
1297
|
+
}
|
|
1298
|
+
const frameHeight = height ?? nodeHeight;
|
|
1299
|
+
const pinned = optionalBooleanFlag(flags, 'pinned', 'Use --pinned true or --pinned false');
|
|
1237
1300
|
if (flags['lock-arrange'] && flags['unlock-arrange']) {
|
|
1238
1301
|
die('Use either --lock-arrange or --unlock-arrange, not both.');
|
|
1239
1302
|
}
|
|
@@ -1243,7 +1306,9 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1243
1306
|
? false
|
|
1244
1307
|
: undefined;
|
|
1245
1308
|
|
|
1246
|
-
|
|
1309
|
+
applyStrictSizeFlags(body, flags);
|
|
1310
|
+
|
|
1311
|
+
if (x !== undefined || y !== undefined || width !== undefined || frameHeight !== undefined || arrangeLocked !== undefined) {
|
|
1247
1312
|
const existing = await api('GET', `/api/canvas/node/${encodeURIComponent(id)}`) as {
|
|
1248
1313
|
position: { x: number; y: number };
|
|
1249
1314
|
size: { width: number; height: number };
|
|
@@ -1257,10 +1322,10 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1257
1322
|
};
|
|
1258
1323
|
}
|
|
1259
1324
|
|
|
1260
|
-
if (width !== undefined ||
|
|
1325
|
+
if (width !== undefined || frameHeight !== undefined) {
|
|
1261
1326
|
body.size = {
|
|
1262
1327
|
width: width ?? existing.size.width,
|
|
1263
|
-
height:
|
|
1328
|
+
height: frameHeight ?? existing.size.height,
|
|
1264
1329
|
};
|
|
1265
1330
|
}
|
|
1266
1331
|
|
|
@@ -1269,10 +1334,12 @@ cmd('node update', 'Update a node by ID', [
|
|
|
1269
1334
|
}
|
|
1270
1335
|
}
|
|
1271
1336
|
|
|
1337
|
+
if (pinned !== undefined) body.pinned = pinned;
|
|
1338
|
+
|
|
1272
1339
|
if (Object.keys(body).length === 0) {
|
|
1273
1340
|
die(
|
|
1274
1341
|
'No updates specified',
|
|
1275
|
-
'Use --title, --content, --x, --y, --width, --height, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1342
|
+
'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1276
1343
|
);
|
|
1277
1344
|
}
|
|
1278
1345
|
|
|
@@ -2035,6 +2102,16 @@ cmd('webview screenshot', 'Capture a screenshot from the active Bun.WebView auto
|
|
|
2035
2102
|
});
|
|
2036
2103
|
});
|
|
2037
2104
|
|
|
2105
|
+
cmd('screenshot', 'Capture a screenshot from the active Bun.WebView automation session', [
|
|
2106
|
+
'pmx-canvas screenshot --output ./canvas.png',
|
|
2107
|
+
'pmx-canvas screenshot --output ./canvas.webp --format webp --quality 80',
|
|
2108
|
+
], async (args) => {
|
|
2109
|
+
if (args.includes('--help') || args.includes('-h')) return showCommandHelp('screenshot');
|
|
2110
|
+
const screenshotCommand = COMMANDS['webview screenshot'];
|
|
2111
|
+
if (!screenshotCommand) die('Internal error: webview screenshot command is unavailable.');
|
|
2112
|
+
await screenshotCommand.run(args);
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2038
2115
|
// ── code-graph ───────────────────────────────────────────────
|
|
2039
2116
|
cmd('code-graph', 'Show auto-detected file dependency graph', [
|
|
2040
2117
|
'pmx-canvas code-graph',
|
|
@@ -2171,11 +2248,21 @@ function showCommandHelp(name: string): void {
|
|
|
2171
2248
|
console.log(' pmx-canvas node add --help --type json-render --component Table');
|
|
2172
2249
|
console.log(' pmx-canvas node add --help --type graph');
|
|
2173
2250
|
console.log(' pmx-canvas node add --help --type webpage --json');
|
|
2251
|
+
console.log(' Use --strict-size to keep explicit width/height fixed and scroll overflowing content.');
|
|
2252
|
+
}
|
|
2253
|
+
if (name === 'json-render') {
|
|
2254
|
+
console.log('\nOptions:');
|
|
2255
|
+
console.log(' --schema Show json-render catalog schema (default)');
|
|
2256
|
+
console.log(' --summary Show compact component summaries');
|
|
2257
|
+
console.log(' --component <name> Focus on one component');
|
|
2258
|
+
console.log(' --field <name> Focus on one component prop');
|
|
2259
|
+
console.log(' --example, --examples Print canonical component examples');
|
|
2174
2260
|
}
|
|
2175
2261
|
if (name === 'node add' || name === 'graph add' || name === 'validate spec') {
|
|
2176
2262
|
console.log('\nGraph flags:');
|
|
2177
2263
|
console.log(' Graph fields accept kebab-case CLI flags and camelCase schema names, e.g. --graph-type/--graphType and --x-key/--xKey');
|
|
2178
2264
|
console.log(' Use --node-height/--nodeHeight for canvas frame height; use --chart-height for chart content height. --height is kept as a frame-height alias for compatibility.');
|
|
2265
|
+
console.log(' Pass --show-legend false to hide legends in compact node layouts.');
|
|
2179
2266
|
}
|
|
2180
2267
|
if (name === 'node schema') {
|
|
2181
2268
|
console.log('\nFilters:');
|
|
@@ -2205,6 +2292,13 @@ function showCommandHelp(name: string): void {
|
|
|
2205
2292
|
console.log(' --padding <px> World-space padding around fitted nodes (default 60)');
|
|
2206
2293
|
console.log(' --max-scale <scale> Maximum zoom scale (default 1)');
|
|
2207
2294
|
}
|
|
2295
|
+
if (name === 'screenshot' || name === 'webview screenshot') {
|
|
2296
|
+
console.log('\nOptions:');
|
|
2297
|
+
console.log(' --output <path> Required output image path');
|
|
2298
|
+
console.log(' --format <type> png, jpeg, or webp');
|
|
2299
|
+
console.log(' --quality <number> Encoder quality for lossy formats');
|
|
2300
|
+
console.log(' Requires an active automation session: pmx-canvas webview start');
|
|
2301
|
+
}
|
|
2208
2302
|
if (name === 'external-app add') {
|
|
2209
2303
|
console.log('\nOptions:');
|
|
2210
2304
|
console.log(' --kind excalidraw External app kind to create');
|
|
@@ -2236,6 +2330,7 @@ Node commands:
|
|
|
2236
2330
|
pmx-canvas node get <id> Get a node by ID
|
|
2237
2331
|
pmx-canvas node update <id> [opts] Update a node
|
|
2238
2332
|
pmx-canvas node remove <id> Remove a node
|
|
2333
|
+
pmx-canvas json-render Show json-render schema/examples
|
|
2239
2334
|
pmx-canvas graph add [options] Add a graph node
|
|
2240
2335
|
|
|
2241
2336
|
Edge commands:
|
|
@@ -2255,6 +2350,7 @@ Canvas commands:
|
|
|
2255
2350
|
pmx-canvas watch [options] Watch semantic canvas changes over SSE
|
|
2256
2351
|
pmx-canvas focus <id> Pan viewport to node
|
|
2257
2352
|
pmx-canvas fit [id ...] Fit viewport to canvas or selected nodes
|
|
2353
|
+
pmx-canvas screenshot Save automation screenshot to disk
|
|
2258
2354
|
pmx-canvas external-app add Add hosted external apps like Excalidraw
|
|
2259
2355
|
pmx-canvas webview status Show WebView automation status
|
|
2260
2356
|
pmx-canvas webview start [options] Start or replace automation session
|
|
@@ -2303,6 +2399,8 @@ Examples:
|
|
|
2303
2399
|
pmx-canvas node add --type markdown --title "API Design" --content "# REST API"
|
|
2304
2400
|
pmx-canvas node add --type webpage --url "https://example.com/docs"
|
|
2305
2401
|
pmx-canvas node add --type json-render --title "Dashboard" --spec-file ./dashboard.json
|
|
2402
|
+
pmx-canvas json-render --schema --summary
|
|
2403
|
+
pmx-canvas json-render --example --component Table
|
|
2306
2404
|
pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
|
|
2307
2405
|
pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
2308
2406
|
pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
package/src/cli/index.ts
CHANGED
|
@@ -29,8 +29,8 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
29
29
|
// ── Agent CLI subcommands ────────────────────────────────────
|
|
30
30
|
// If first arg is a known subcommand (not a --flag), route to the agent CLI.
|
|
31
31
|
const AGENT_COMMANDS = new Set([
|
|
32
|
-
'node', 'edge', 'search', 'layout', 'status', 'arrange', 'focus',
|
|
33
|
-
'pin', 'undo', 'redo', 'history', 'snapshot', 'diff', 'group', 'webview', 'open',
|
|
32
|
+
'node', 'edge', 'json-render', 'search', 'layout', 'status', 'arrange', 'focus',
|
|
33
|
+
'fit', 'screenshot', 'pin', 'undo', 'redo', 'history', 'snapshot', 'diff', 'group', 'webview', 'open',
|
|
34
34
|
'clear', 'code-graph', 'spatial', 'watch', 'web-artifact', 'external-app', 'graph', 'batch', 'validate', 'serve',
|
|
35
35
|
]);
|
|
36
36
|
|
|
@@ -486,8 +486,10 @@ Server options:
|
|
|
486
486
|
|
|
487
487
|
Agent CLI (works against running server):
|
|
488
488
|
node add|list|get|update|remove Manage nodes
|
|
489
|
+
json-render Show json-render schema/examples
|
|
489
490
|
graph add Add graph nodes (alias for node add --type graph)
|
|
490
491
|
edge add|list|remove Manage edges
|
|
492
|
+
screenshot Save a WebView automation screenshot
|
|
491
493
|
webview status|start|evaluate|resize|screenshot|stop
|
|
492
494
|
Manage Bun.WebView automation session
|
|
493
495
|
search <query> Search nodes
|
|
@@ -540,6 +542,7 @@ Examples:
|
|
|
540
542
|
pmx-canvas node add --type markdown --title "Hello World" Add a node
|
|
541
543
|
pmx-canvas node add --type webpage --url "https://example.com" Add a webpage node
|
|
542
544
|
pmx-canvas node add --type json-render --title "Dashboard" --spec-file ./dashboard.json
|
|
545
|
+
pmx-canvas json-render --schema --summary Show json-render schema info
|
|
543
546
|
pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
|
|
544
547
|
pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
545
548
|
pmx-canvas node list List all nodes
|
|
@@ -549,7 +552,7 @@ Examples:
|
|
|
549
552
|
pmx-canvas validate spec --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
550
553
|
pmx-canvas open Open the workbench in a browser
|
|
551
554
|
pmx-canvas webview status Show WebView automation status
|
|
552
|
-
pmx-canvas
|
|
555
|
+
pmx-canvas screenshot --output ./canvas.png Save a WebView screenshot
|
|
553
556
|
pmx-canvas search "auth" Find nodes
|
|
554
557
|
pmx-canvas arrange --layout column Auto-arrange
|
|
555
558
|
pmx-canvas batch --file ./canvas-ops.json Run batch canvas ops
|
|
@@ -179,12 +179,13 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
179
179
|
autoFitPersistTimer.current = null;
|
|
180
180
|
}
|
|
181
181
|
};
|
|
182
|
-
}, [node.id, node.type, node.data.mode, node.collapsed, node.dockPosition, node.size.width, node.size.height]);
|
|
182
|
+
}, [node.id, node.type, node.data.mode, node.data.strictSize, node.collapsed, node.dockPosition, node.size.width, node.size.height]);
|
|
183
183
|
|
|
184
184
|
const isPinned = node.pinned;
|
|
185
185
|
const isTrace = node.type === 'trace';
|
|
186
186
|
const isTraceRunning = isTrace && node.data.status === 'running';
|
|
187
187
|
const isGroup = node.type === 'group';
|
|
188
|
+
const isStrictSize = node.data.strictSize === true;
|
|
188
189
|
const viewportScale = Math.max(viewport.value.scale, 0.01);
|
|
189
190
|
const chromeScale = viewportScale < 1 ? Math.min(2.2, 1 / viewportScale) : 1;
|
|
190
191
|
|
|
@@ -213,6 +214,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
|
|
|
213
214
|
isTrace ? 'trace-node' : '',
|
|
214
215
|
isTraceRunning ? 'trace-running' : '',
|
|
215
216
|
isGroup ? 'group-node' : '',
|
|
217
|
+
isStrictSize ? 'strict-size' : '',
|
|
216
218
|
]
|
|
217
219
|
.filter(Boolean)
|
|
218
220
|
.join(' ');
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { CanvasNodeState } from '../types';
|
|
2
2
|
|
|
3
|
-
export const AUTO_FIT_MAX_HEIGHT = 600;
|
|
4
3
|
export const AUTO_FIT_TITLEBAR_HEIGHT = 37;
|
|
4
|
+
export const AUTO_FIT_MAX_HEIGHT = 600;
|
|
5
5
|
|
|
6
6
|
function isExtAppNode(node: CanvasNodeState): boolean {
|
|
7
7
|
return node.type === 'mcp-app' && node.data.mode === 'ext-app';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function hasExplicitStructuredFrame(node: CanvasNodeState): boolean {
|
|
11
|
-
return
|
|
11
|
+
return node.type === 'graph' || node.type === 'json-render';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function shouldAutoFitNode(node: CanvasNodeState): boolean {
|
|
15
|
-
return !node.collapsed && !node.dockPosition && node.type !== 'group' && !isExtAppNode(node) && !hasExplicitStructuredFrame(node);
|
|
15
|
+
return !node.collapsed && !node.dockPosition && node.data.strictSize !== true && node.type !== 'group' && !isExtAppNode(node) && !hasExplicitStructuredFrame(node);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function computeAutoFitHeight(node: CanvasNodeState, contentHeight: number): number | null {
|
|
@@ -7,13 +7,22 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { defineCatalog } from '@json-render/core';
|
|
10
|
+
import { z } from 'zod';
|
|
10
11
|
import { schema } from './schema.js';
|
|
11
12
|
import { shadcnComponentDefinitions } from '@json-render/shadcn/catalog';
|
|
12
13
|
import { chartComponentDefinitions } from './charts/definitions';
|
|
13
14
|
import { extraChartComponentDefinitions } from './charts/extra-definitions';
|
|
14
15
|
|
|
16
|
+
const badgeDefinition = shadcnComponentDefinitions.Badge;
|
|
17
|
+
|
|
15
18
|
export const allComponentDefinitions = {
|
|
16
19
|
...shadcnComponentDefinitions,
|
|
20
|
+
Badge: {
|
|
21
|
+
...badgeDefinition,
|
|
22
|
+
props: badgeDefinition.props.extend({
|
|
23
|
+
variant: z.enum(['default', 'secondary', 'destructive', 'outline', 'success', 'info', 'warning', 'error', 'danger']).nullable(),
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
17
26
|
...chartComponentDefinitions,
|
|
18
27
|
...extraChartComponentDefinitions,
|
|
19
28
|
};
|
|
@@ -84,6 +84,8 @@ interface PieChartProps {
|
|
|
84
84
|
nameKey: string;
|
|
85
85
|
valueKey: string;
|
|
86
86
|
height?: number | null;
|
|
87
|
+
showLegend?: boolean | null;
|
|
88
|
+
showLabels?: boolean | null;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export const axisStyle = {
|
|
@@ -99,6 +101,11 @@ export const tooltipStyle = {
|
|
|
99
101
|
fontSize: 13,
|
|
100
102
|
};
|
|
101
103
|
|
|
104
|
+
export const chartMargin = { top: 14, right: 28, bottom: 28, left: 10 };
|
|
105
|
+
export const polarChartMargin = { top: 18, right: 40, bottom: 30, left: 40 };
|
|
106
|
+
export const axisTickMargin = 8;
|
|
107
|
+
export const legendMargin = { top: 10 };
|
|
108
|
+
|
|
102
109
|
/** Shared wrapper for cartesian charts (Line + Bar). */
|
|
103
110
|
export function CartesianChart({
|
|
104
111
|
props,
|
|
@@ -127,10 +134,10 @@ function ChartLineChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
|
127
134
|
return (
|
|
128
135
|
<CartesianChart props={props} className="pmx-chart--line">
|
|
129
136
|
{(data) => (
|
|
130
|
-
<RechartsLineChart data={data}>
|
|
137
|
+
<RechartsLineChart data={data} margin={chartMargin}>
|
|
131
138
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
132
|
-
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
133
|
-
<YAxis tick={axisStyle} />
|
|
139
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} />
|
|
140
|
+
<YAxis tick={axisStyle} tickMargin={axisTickMargin} />
|
|
134
141
|
<Tooltip contentStyle={tooltipStyle} />
|
|
135
142
|
<Line
|
|
136
143
|
type="monotone"
|
|
@@ -151,10 +158,10 @@ function ChartBarChart({ props }: BaseComponentProps<CartesianChartProps>) {
|
|
|
151
158
|
return (
|
|
152
159
|
<CartesianChart props={props} className="pmx-chart--bar">
|
|
153
160
|
{(data) => (
|
|
154
|
-
<RechartsBarChart data={data}>
|
|
161
|
+
<RechartsBarChart data={data} margin={chartMargin}>
|
|
155
162
|
<CartesianGrid strokeDasharray="3 3" stroke="var(--border, #e5e5e5)" />
|
|
156
|
-
<XAxis dataKey={props.xKey} tick={axisStyle} />
|
|
157
|
-
<YAxis tick={axisStyle} />
|
|
163
|
+
<XAxis dataKey={props.xKey} tick={axisStyle} tickMargin={axisTickMargin} />
|
|
164
|
+
<YAxis tick={axisStyle} tickMargin={axisTickMargin} />
|
|
158
165
|
<Tooltip contentStyle={tooltipStyle} cursor={false} />
|
|
159
166
|
<Bar dataKey={props.yKey} fill={fill} radius={[4, 4, 0, 0]} />
|
|
160
167
|
</RechartsBarChart>
|
|
@@ -171,17 +178,18 @@ function ChartPieChart({ props }: BaseComponentProps<PieChartProps>) {
|
|
|
171
178
|
<div className="pmx-chart pmx-chart--pie">
|
|
172
179
|
{props.title && <div className="pmx-chart__title">{props.title}</div>}
|
|
173
180
|
<ResponsiveContainer width="100%" height={h}>
|
|
174
|
-
<RechartsPieChart>
|
|
181
|
+
<RechartsPieChart margin={polarChartMargin}>
|
|
175
182
|
<Tooltip contentStyle={tooltipStyle} />
|
|
176
|
-
<Legend />
|
|
183
|
+
{props.showLegend !== false && <Legend wrapperStyle={legendMargin} />}
|
|
177
184
|
<Pie
|
|
178
185
|
data={data}
|
|
179
186
|
dataKey={props.valueKey}
|
|
180
187
|
nameKey={props.nameKey}
|
|
181
188
|
cx="50%"
|
|
182
189
|
cy="50%"
|
|
183
|
-
outerRadius="
|
|
184
|
-
label
|
|
190
|
+
outerRadius="64%"
|
|
191
|
+
label={props.showLabels !== false}
|
|
192
|
+
labelLine={false}
|
|
185
193
|
>
|
|
186
194
|
{data.map((_, i) => (
|
|
187
195
|
<Cell key={i} fill={CHART_COLORS[i % CHART_COLORS.length]} />
|
|
@@ -63,6 +63,8 @@ export const chartComponentDefinitions = {
|
|
|
63
63
|
nameKey: z.string(),
|
|
64
64
|
valueKey: z.string(),
|
|
65
65
|
height: z.number().nullable(),
|
|
66
|
+
showLegend: z.boolean().optional(),
|
|
67
|
+
showLabels: z.boolean().optional(),
|
|
66
68
|
}),
|
|
67
69
|
description:
|
|
68
70
|
'Pie chart for showing proportions. Provide data as an array of objects with nameKey and valueKey fields.',
|
|
@@ -76,6 +78,8 @@ export const chartComponentDefinitions = {
|
|
|
76
78
|
nameKey: 'name',
|
|
77
79
|
valueKey: 'share',
|
|
78
80
|
height: null,
|
|
81
|
+
showLegend: true,
|
|
82
|
+
showLabels: true,
|
|
79
83
|
},
|
|
80
84
|
},
|
|
81
85
|
} as const;
|