pmx-canvas 0.1.10 → 0.1.12

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.
@@ -84,6 +84,7 @@ export declare const allComponentDefinitions: {
84
84
  axisKey: z.ZodString;
85
85
  metrics: z.ZodArray<z.ZodString>;
86
86
  height: z.ZodNullable<z.ZodNumber>;
87
+ showLegend: z.ZodOptional<z.ZodBoolean>;
87
88
  }, z.core.$strip>;
88
89
  readonly description: "Radar chart for comparing multiple metrics across categories. Each metric in `metrics` is plotted as its own polygon.";
89
90
  readonly example: {
@@ -104,6 +105,7 @@ export declare const allComponentDefinitions: {
104
105
  readonly axisKey: "skill";
105
106
  readonly metrics: readonly ["alice", "bob"];
106
107
  readonly height: null;
108
+ readonly showLegend: true;
107
109
  };
108
110
  };
109
111
  StackedBarChart: {
@@ -118,6 +120,7 @@ export declare const allComponentDefinitions: {
118
120
  avg: "avg";
119
121
  }>>;
120
122
  height: z.ZodNullable<z.ZodNumber>;
123
+ showLegend: z.ZodOptional<z.ZodBoolean>;
121
124
  }, z.core.$strip>;
122
125
  readonly description: "Stacked bar chart for compositional data. Each entry in `series` is plotted as its own bar segment per x value.";
123
126
  readonly example: {
@@ -142,6 +145,7 @@ export declare const allComponentDefinitions: {
142
145
  readonly series: readonly ["north", "south", "east"];
143
146
  readonly aggregate: null;
144
147
  readonly height: null;
148
+ readonly showLegend: true;
145
149
  };
146
150
  };
147
151
  ComposedChart: {
@@ -154,6 +158,7 @@ export declare const allComponentDefinitions: {
154
158
  barColor: z.ZodNullable<z.ZodString>;
155
159
  lineColor: z.ZodNullable<z.ZodString>;
156
160
  height: z.ZodNullable<z.ZodNumber>;
161
+ showLegend: z.ZodOptional<z.ZodBoolean>;
157
162
  }, z.core.$strip>;
158
163
  readonly description: "Combined bar + line chart for paired metrics (e.g. counts + a derived rate) on the same axis.";
159
164
  readonly example: {
@@ -177,6 +182,7 @@ export declare const allComponentDefinitions: {
177
182
  readonly barColor: null;
178
183
  readonly lineColor: null;
179
184
  readonly height: null;
185
+ readonly showLegend: true;
180
186
  };
181
187
  };
182
188
  LineChart: {
@@ -254,6 +260,8 @@ export declare const allComponentDefinitions: {
254
260
  nameKey: z.ZodString;
255
261
  valueKey: z.ZodString;
256
262
  height: z.ZodNullable<z.ZodNumber>;
263
+ showLegend: z.ZodOptional<z.ZodBoolean>;
264
+ showLabels: z.ZodOptional<z.ZodBoolean>;
257
265
  }, z.core.$strip>;
258
266
  readonly description: "Pie chart for showing proportions. Provide data as an array of objects with nameKey and valueKey fields.";
259
267
  readonly example: {
@@ -271,6 +279,8 @@ export declare const allComponentDefinitions: {
271
279
  readonly nameKey: "name";
272
280
  readonly valueKey: "share";
273
281
  readonly height: null;
282
+ readonly showLegend: true;
283
+ readonly showLabels: true;
274
284
  };
275
285
  };
276
286
  Badge: {
@@ -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;
@@ -0,0 +1,87 @@
1
+ import { type CanvasLayout, type CanvasNodeState, type CanvasSnapshot, type PmxCanvas } from '../server/index.js';
2
+ type AddNodeInput = Parameters<PmxCanvas['addNode']>[0];
3
+ type AddWebpageNodeInput = Parameters<PmxCanvas['addWebpageNode']>[0];
4
+ type RefreshWebpageNodeResult = Awaited<ReturnType<PmxCanvas['refreshWebpageNode']>>;
5
+ type OpenMcpAppInput = Parameters<PmxCanvas['openMcpApp']>[0];
6
+ type OpenMcpAppResult = Awaited<ReturnType<PmxCanvas['openMcpApp']>>;
7
+ type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
8
+ type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
9
+ type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
10
+ type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
11
+ type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
12
+ type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
13
+ type AddEdgeInput = Parameters<PmxCanvas['addEdge']>[0];
14
+ type CreateGroupInput = Parameters<PmxCanvas['createGroup']>[0];
15
+ type GroupNodesOptions = Parameters<PmxCanvas['groupNodes']>[2];
16
+ type ArrangeLayout = Parameters<PmxCanvas['arrange']>[0];
17
+ type FocusNodeResult = ReturnType<PmxCanvas['focusNode']>;
18
+ type FitViewOptions = Parameters<PmxCanvas['fitView']>[0];
19
+ type FitViewResult = ReturnType<PmxCanvas['fitView']>;
20
+ type SearchResult = ReturnType<PmxCanvas['search']>;
21
+ type UndoRedoResult = Awaited<ReturnType<PmxCanvas['undo']>>;
22
+ type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
23
+ type SetContextPinsResult = ReturnType<PmxCanvas['setContextPins']>;
24
+ type RunBatchInput = Parameters<PmxCanvas['runBatch']>[0];
25
+ type RunBatchResult = Awaited<ReturnType<PmxCanvas['runBatch']>>;
26
+ type SnapshotList = ReturnType<PmxCanvas['listSnapshots']>;
27
+ type DeleteSnapshotResult = ReturnType<PmxCanvas['deleteSnapshot']>;
28
+ type DiffSnapshotResult = ReturnType<PmxCanvas['diffSnapshot']>;
29
+ type CodeGraphResult = ReturnType<PmxCanvas['getCodeGraph']>;
30
+ type ValidationResult = ReturnType<PmxCanvas['validate']>;
31
+ type WebArtifactInput = Parameters<PmxCanvas['buildWebArtifact']>[0];
32
+ type WebArtifactResult = Awaited<ReturnType<PmxCanvas['buildWebArtifact']>>;
33
+ type AutomationWebViewOptions = Parameters<PmxCanvas['startAutomationWebView']>[0];
34
+ type AutomationWebViewStatus = Awaited<ReturnType<PmxCanvas['startAutomationWebView']>>;
35
+ type AutomationEvaluateResult = Awaited<ReturnType<PmxCanvas['evaluateAutomationWebView']>>;
36
+ type AutomationScreenshotOptions = Parameters<PmxCanvas['screenshotAutomationWebView']>[0];
37
+ export interface CanvasAccess {
38
+ readonly port: number;
39
+ readonly remoteBaseUrl: string | null;
40
+ getLayout(): Promise<CanvasLayout>;
41
+ getNode(id: string): Promise<CanvasNodeState | undefined>;
42
+ addNode(input: AddNodeInput): Promise<string>;
43
+ addWebpageNode(input: AddWebpageNodeInput): Promise<Awaited<ReturnType<PmxCanvas['addWebpageNode']>>>;
44
+ refreshWebpageNode(id: string, url?: string): Promise<RefreshWebpageNodeResult>;
45
+ openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult>;
46
+ addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
47
+ addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
48
+ addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
49
+ buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
50
+ updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
51
+ removeNode(id: string): Promise<void>;
52
+ addEdge(input: AddEdgeInput): Promise<string>;
53
+ removeEdge(id: string): Promise<void>;
54
+ createGroup(input: CreateGroupInput): Promise<string>;
55
+ groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): Promise<boolean>;
56
+ ungroupNodes(groupId: string): Promise<boolean>;
57
+ arrange(layout?: ArrangeLayout): Promise<void>;
58
+ focusNode(id: string, options?: {
59
+ noPan?: boolean;
60
+ }): Promise<FocusNodeResult>;
61
+ fitView(options?: FitViewOptions): Promise<FitViewResult>;
62
+ clear(): Promise<void>;
63
+ search(query: string): Promise<SearchResult>;
64
+ undo(): Promise<UndoRedoResult>;
65
+ redo(): Promise<UndoRedoResult>;
66
+ getHistory(): Promise<HistoryResult>;
67
+ setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove'): Promise<SetContextPinsResult>;
68
+ getPinnedNodeIds(): Promise<string[]>;
69
+ runBatch(operations: RunBatchInput): Promise<RunBatchResult>;
70
+ listSnapshots(): Promise<SnapshotList>;
71
+ saveSnapshot(name: string): Promise<CanvasSnapshot | null>;
72
+ restoreSnapshot(id: string): Promise<{
73
+ ok: boolean;
74
+ }>;
75
+ deleteSnapshot(id: string): Promise<DeleteSnapshotResult>;
76
+ diffSnapshot(idOrName: string): Promise<DiffSnapshotResult>;
77
+ getCodeGraph(): Promise<CodeGraphResult>;
78
+ validate(): Promise<ValidationResult>;
79
+ getAutomationWebViewStatus(): Promise<AutomationWebViewStatus>;
80
+ startAutomationWebView(options?: AutomationWebViewOptions): Promise<AutomationWebViewStatus>;
81
+ stopAutomationWebView(): Promise<boolean>;
82
+ evaluateAutomationWebView(expression: string): Promise<AutomationEvaluateResult>;
83
+ resizeAutomationWebView(width: number, height: number): Promise<AutomationWebViewStatus>;
84
+ screenshotAutomationWebView(options?: AutomationScreenshotOptions): Promise<Uint8Array>;
85
+ }
86
+ export declare function createCanvasAccess(): Promise<CanvasAccess>;
87
+ export {};
@@ -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;
@@ -96,6 +96,7 @@ export interface CanvasServerOptions {
96
96
  port?: number;
97
97
  workspaceRoot?: string;
98
98
  autoOpenBrowser?: boolean;
99
+ allowPortFallback?: boolean;
99
100
  }
100
101
  export declare function startCanvasServer(options?: CanvasServerOptions): string | null;
101
102
  export declare function stopCanvasServer(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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];
@@ -249,6 +249,10 @@ function optionalBooleanFlag(flags: Record<string, string | true>, name: string,
249
249
  die(`Invalid value for --${name}: ${String(val)}`, hint);
250
250
  }
251
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
+
252
256
  function isRecord(value: unknown): value is Record<string, unknown> {
253
257
  return !!value && typeof value === 'object' && !Array.isArray(value);
254
258
  }
@@ -606,6 +610,7 @@ async function buildJsonRenderRequestBody(
606
610
  width: 'Use a positive number, e.g. --width 840',
607
611
  height: 'Use a positive number, e.g. --height 620',
608
612
  });
613
+ applyStrictSizeFlags(body, flags);
609
614
  return body;
610
615
  }
611
616
 
@@ -665,6 +670,10 @@ async function buildGraphRequestBody(
665
670
  if (color) body.color = color;
666
671
  if (barColor) body.barColor = barColor;
667
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;
668
677
 
669
678
  const chartHeight = optionalPositiveFiniteFlag(flags, 'chart-height', 'Use a positive number, e.g. --chart-height 300');
670
679
  const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
@@ -682,6 +691,7 @@ async function buildGraphRequestBody(
682
691
  if (y !== undefined) body.y = y;
683
692
  if (width !== undefined) body.width = width;
684
693
  if (nodeHeight !== undefined) body.nodeHeight = nodeHeight;
694
+ applyStrictSizeFlags(body, flags);
685
695
  return body;
686
696
  }
687
697
 
@@ -1067,6 +1077,7 @@ cmd('node add', 'Add a node to the canvas', [
1067
1077
  width: 'Use a positive number, e.g. --width 500',
1068
1078
  height: 'Use a positive number, e.g. --height 280',
1069
1079
  });
1080
+ applyStrictSizeFlags(body, flags);
1070
1081
 
1071
1082
  // Support --stdin for piping content
1072
1083
  if (flags.stdin) {
@@ -1081,6 +1092,37 @@ cmd('node add', 'Add a node to the canvas', [
1081
1092
  output(result);
1082
1093
  });
1083
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
+
1084
1126
  cmd('graph add', 'Add a graph node to the canvas', [
1085
1127
  'pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
1086
1128
  'pmx-canvas graph add --graphType composed --data \'[{"day":"Mon","visits":10,"conversion":0.4}]\' --xKey day --barKey visits --lineKey conversion',
@@ -1097,6 +1139,7 @@ cmd('node schema', 'Describe server-supported node create schemas and canonical
1097
1139
  'pmx-canvas node schema',
1098
1140
  'pmx-canvas node schema --type webpage',
1099
1141
  'pmx-canvas node schema --type json-render',
1142
+ 'pmx-canvas json-render --schema --summary',
1100
1143
  'pmx-canvas node schema --type json-render --component Table',
1101
1144
  'pmx-canvas node schema --type webpage --field url',
1102
1145
  'pmx-canvas node schema --summary',
@@ -1263,6 +1306,8 @@ cmd('node update', 'Update a node by ID', [
1263
1306
  ? false
1264
1307
  : undefined;
1265
1308
 
1309
+ applyStrictSizeFlags(body, flags);
1310
+
1266
1311
  if (x !== undefined || y !== undefined || width !== undefined || frameHeight !== undefined || arrangeLocked !== undefined) {
1267
1312
  const existing = await api('GET', `/api/canvas/node/${encodeURIComponent(id)}`) as {
1268
1313
  position: { x: number; y: number };
@@ -1294,7 +1339,7 @@ cmd('node update', 'Update a node by ID', [
1294
1339
  if (Object.keys(body).length === 0) {
1295
1340
  die(
1296
1341
  'No updates specified',
1297
- 'Use --title, --content, --x, --y, --width, --height, --pinned, --lock-arrange, --unlock-arrange, or --stdin',
1342
+ 'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, --lock-arrange, --unlock-arrange, or --stdin',
1298
1343
  );
1299
1344
  }
1300
1345
 
@@ -2057,6 +2102,16 @@ cmd('webview screenshot', 'Capture a screenshot from the active Bun.WebView auto
2057
2102
  });
2058
2103
  });
2059
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
+
2060
2115
  // ── code-graph ───────────────────────────────────────────────
2061
2116
  cmd('code-graph', 'Show auto-detected file dependency graph', [
2062
2117
  'pmx-canvas code-graph',
@@ -2193,11 +2248,21 @@ function showCommandHelp(name: string): void {
2193
2248
  console.log(' pmx-canvas node add --help --type json-render --component Table');
2194
2249
  console.log(' pmx-canvas node add --help --type graph');
2195
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');
2196
2260
  }
2197
2261
  if (name === 'node add' || name === 'graph add' || name === 'validate spec') {
2198
2262
  console.log('\nGraph flags:');
2199
2263
  console.log(' Graph fields accept kebab-case CLI flags and camelCase schema names, e.g. --graph-type/--graphType and --x-key/--xKey');
2200
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.');
2201
2266
  }
2202
2267
  if (name === 'node schema') {
2203
2268
  console.log('\nFilters:');
@@ -2227,6 +2292,13 @@ function showCommandHelp(name: string): void {
2227
2292
  console.log(' --padding <px> World-space padding around fitted nodes (default 60)');
2228
2293
  console.log(' --max-scale <scale> Maximum zoom scale (default 1)');
2229
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
+ }
2230
2302
  if (name === 'external-app add') {
2231
2303
  console.log('\nOptions:');
2232
2304
  console.log(' --kind excalidraw External app kind to create');
@@ -2258,6 +2330,7 @@ Node commands:
2258
2330
  pmx-canvas node get <id> Get a node by ID
2259
2331
  pmx-canvas node update <id> [opts] Update a node
2260
2332
  pmx-canvas node remove <id> Remove a node
2333
+ pmx-canvas json-render Show json-render schema/examples
2261
2334
  pmx-canvas graph add [options] Add a graph node
2262
2335
 
2263
2336
  Edge commands:
@@ -2277,6 +2350,7 @@ Canvas commands:
2277
2350
  pmx-canvas watch [options] Watch semantic canvas changes over SSE
2278
2351
  pmx-canvas focus <id> Pan viewport to node
2279
2352
  pmx-canvas fit [id ...] Fit viewport to canvas or selected nodes
2353
+ pmx-canvas screenshot Save automation screenshot to disk
2280
2354
  pmx-canvas external-app add Add hosted external apps like Excalidraw
2281
2355
  pmx-canvas webview status Show WebView automation status
2282
2356
  pmx-canvas webview start [options] Start or replace automation session
@@ -2325,6 +2399,8 @@ Examples:
2325
2399
  pmx-canvas node add --type markdown --title "API Design" --content "# REST API"
2326
2400
  pmx-canvas node add --type webpage --url "https://example.com/docs"
2327
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
2328
2404
  pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
2329
2405
  pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
2330
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
- 'fit', '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 webview screenshot --output ./canvas.png Save a WebView screenshot
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(' ');
@@ -12,7 +12,7 @@ function hasExplicitStructuredFrame(node: CanvasNodeState): boolean {
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 {