pmx-canvas 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/Readme.md +19 -6
  3. package/dist/canvas/global.css +35 -2
  4. package/dist/canvas/index.js +70 -69
  5. package/dist/json-render/index.js +109 -109
  6. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  7. package/dist/types/client/icons.d.ts +2 -0
  8. package/dist/types/client/state/canvas-store.d.ts +2 -0
  9. package/dist/types/client/types.d.ts +2 -1
  10. package/dist/types/json-render/charts/components.d.ts +5 -1
  11. package/dist/types/json-render/renderer/index.d.ts +1 -0
  12. package/dist/types/json-render/server.d.ts +1 -0
  13. package/dist/types/mcp/canvas-access.d.ts +3 -0
  14. package/dist/types/server/canvas-operations.d.ts +4 -0
  15. package/dist/types/server/canvas-schema.d.ts +19 -3
  16. package/dist/types/server/canvas-serialization.d.ts +1 -0
  17. package/dist/types/server/canvas-state.d.ts +6 -2
  18. package/dist/types/server/html-primitives.d.ts +34 -0
  19. package/dist/types/server/index.d.ts +19 -0
  20. package/docs/cli.md +4 -1
  21. package/docs/http-api.md +10 -0
  22. package/docs/mcp.md +6 -4
  23. package/docs/node-types.md +30 -2
  24. package/docs/screenshot.png +0 -0
  25. package/docs/sdk.md +11 -0
  26. package/package.json +1 -1
  27. package/skills/pmx-canvas/SKILL.md +8 -0
  28. package/src/cli/agent.ts +150 -5
  29. package/src/client/App.tsx +20 -1
  30. package/src/client/canvas/AnnotationLayer.tsx +33 -12
  31. package/src/client/canvas/CanvasViewport.tsx +88 -7
  32. package/src/client/canvas/CommandPalette.tsx +1 -1
  33. package/src/client/canvas/ContextMenu.tsx +2 -2
  34. package/src/client/canvas/ExpandedNodeOverlay.tsx +7 -1
  35. package/src/client/icons.tsx +13 -0
  36. package/src/client/nodes/McpAppNode.tsx +12 -4
  37. package/src/client/state/canvas-store.ts +15 -5
  38. package/src/client/state/sse-bridge.ts +4 -3
  39. package/src/client/theme/global.css +35 -2
  40. package/src/client/types.ts +2 -1
  41. package/src/json-render/charts/components.tsx +41 -7
  42. package/src/json-render/charts/extra-components.tsx +13 -12
  43. package/src/json-render/renderer/index.tsx +1 -0
  44. package/src/json-render/server.ts +3 -1
  45. package/src/mcp/canvas-access.ts +23 -0
  46. package/src/mcp/server.ts +83 -27
  47. package/src/server/agent-context.ts +17 -0
  48. package/src/server/canvas-operations.ts +91 -38
  49. package/src/server/canvas-schema.ts +83 -3
  50. package/src/server/canvas-serialization.ts +9 -2
  51. package/src/server/canvas-state.ts +9 -4
  52. package/src/server/demo-state.json +1143 -0
  53. package/src/server/demo.ts +25 -777
  54. package/src/server/html-primitives.ts +990 -0
  55. package/src/server/index.ts +43 -2
  56. package/src/server/server.ts +138 -14
  57. package/src/server/spatial-analysis.ts +3 -3
@@ -1,5 +1,5 @@
1
1
  import type { CanvasNodeState } from '../types';
2
- type AnnotationTool = 'pen' | 'eraser' | null;
2
+ type AnnotationTool = 'pen' | 'eraser' | 'text' | null;
3
3
  interface CanvasViewportProps {
4
4
  onNodeContextMenu?: (e: MouseEvent, nodeId: string) => void;
5
5
  onCanvasContextMenu?: (e: MouseEvent, canvasX: number, canvasY: number) => void;
@@ -23,6 +23,8 @@ export declare function IconMoon(p: IconProps): JSX.Element;
23
23
  export declare function IconPen(p: IconProps): JSX.Element;
24
24
  /** Eraser — remove canvas annotations */
25
25
  export declare function IconEraser(p: IconProps): JSX.Element;
26
+ /** Text cursor — canvas text annotation mode */
27
+ export declare function IconTextAnnotation(p: IconProps): JSX.Element;
26
28
  /** Camera — snapshots */
27
29
  export declare function IconSnapshot(p: IconProps): JSX.Element;
28
30
  /** Bullseye — trace toggle */
@@ -45,9 +45,11 @@ export declare function removeEdgesForNode(nodeId: string): void;
45
45
  export declare function addAnnotation(annotation: CanvasAnnotation): void;
46
46
  export declare function removeAnnotation(id: string): void;
47
47
  export declare function createAnnotationFromClient(input: {
48
+ type?: CanvasAnnotation['type'];
48
49
  points: CanvasAnnotation['points'];
49
50
  color: string;
50
51
  width: number;
52
+ text?: string;
51
53
  label?: string;
52
54
  }): Promise<{
53
55
  ok: boolean;
@@ -35,7 +35,7 @@ export interface CanvasAnnotationPoint {
35
35
  }
36
36
  export interface CanvasAnnotation {
37
37
  id: string;
38
- type: 'freehand';
38
+ type: 'freehand' | 'text';
39
39
  points: CanvasAnnotationPoint[];
40
40
  bounds: {
41
41
  x: number;
@@ -45,6 +45,7 @@ export interface CanvasAnnotation {
45
45
  };
46
46
  color: string;
47
47
  width: number;
48
+ text?: string;
48
49
  label?: string;
49
50
  createdAt: string;
50
51
  }
@@ -6,7 +6,7 @@
6
6
  * chat example. Each component receives BaseComponentProps<T> and renders
7
7
  * a responsive chart inside a styled container.
8
8
  */
9
- import type { ReactNode } from 'react';
9
+ import { type ReactNode } from 'react';
10
10
  import type { BaseComponentProps } from '@json-render/react';
11
11
  export declare const CHART_COLORS: string[];
12
12
  export type AggregateMode = 'sum' | 'count' | 'avg';
@@ -56,6 +56,10 @@ export declare const axisTickMargin = 8;
56
56
  export declare const legendMargin: {
57
57
  top: number;
58
58
  };
59
+ export declare function useChartFrameHeight(explicitHeight: number | null | undefined, fallbackHeight?: number): {
60
+ frameRef: import("react").RefObject<HTMLDivElement | null>;
61
+ height: number;
62
+ };
59
63
  /** Shared wrapper for cartesian charts (Line + Bar). */
60
64
  export declare function CartesianChart({ props, children, className, }: {
61
65
  props: CartesianChartProps;
@@ -12,5 +12,6 @@ declare global {
12
12
  state?: Record<string, unknown>;
13
13
  };
14
14
  __PMX_CANVAS_JSON_RENDER_THEME__?: string;
15
+ __PMX_CANVAS_JSON_RENDER_DISPLAY__?: string;
15
16
  }
16
17
  }
@@ -58,4 +58,5 @@ export declare function buildJsonRenderViewerHtml(options: {
58
58
  title: string;
59
59
  spec: JsonRenderSpec;
60
60
  theme?: 'dark' | 'light' | 'high-contrast';
61
+ display?: 'expanded';
61
62
  }): Promise<string>;
@@ -8,6 +8,8 @@ type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
8
8
  type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
9
9
  type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
10
10
  type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
11
+ type AddHtmlPrimitiveInput = Parameters<PmxCanvas['addHtmlPrimitive']>[0];
12
+ type AddHtmlPrimitiveResult = ReturnType<PmxCanvas['addHtmlPrimitive']>;
11
13
  type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
12
14
  type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
13
15
  type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
@@ -50,6 +52,7 @@ export interface CanvasAccess {
50
52
  addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
51
53
  addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
52
54
  addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
55
+ addHtmlPrimitive(input: AddHtmlPrimitiveInput): Promise<AddHtmlPrimitiveResult>;
53
56
  addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
54
57
  buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
55
58
  updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
@@ -56,6 +56,10 @@ interface CanvasAddNodeInput {
56
56
  fileMode?: 'path' | 'inline' | 'auto';
57
57
  strictSize?: boolean;
58
58
  }
59
+ export declare const MARKDOWN_NODE_DEFAULT_SIZE: {
60
+ width: number;
61
+ height: number;
62
+ };
59
63
  interface CanvasCreateGroupInput {
60
64
  title?: string;
61
65
  childIds?: string[];
@@ -1,5 +1,6 @@
1
1
  import { type JsonRenderComponentDescriptor } from '../json-render/catalog.js';
2
2
  import { type GraphNodeInput, type JsonRenderSpec } from '../json-render/server.js';
3
+ import { type HtmlPrimitiveDescriptor } from './html-primitives.js';
3
4
  export interface CanvasCreateField {
4
5
  name: string;
5
6
  type: string;
@@ -19,8 +20,17 @@ export interface CanvasCreateTypeSchema {
19
20
  }
20
21
  export interface StructuredValidationResult {
21
22
  ok: true;
22
- type: 'json-render' | 'graph';
23
- normalizedSpec: JsonRenderSpec;
23
+ type: 'json-render' | 'graph' | 'html-primitive';
24
+ normalizedSpec?: JsonRenderSpec;
25
+ normalizedPrimitive?: {
26
+ kind: string;
27
+ title: string;
28
+ htmlBytes: number;
29
+ defaultSize: {
30
+ width: number;
31
+ height: number;
32
+ };
33
+ };
24
34
  summary: Record<string, unknown>;
25
35
  }
26
36
  declare const CANONICAL_GRAPH_TYPES: readonly ["line", "bar", "pie", "area", "scatter", "radar", "stacked-bar", "composed"];
@@ -37,6 +47,7 @@ export declare function describeCanvasSchema(): {
37
47
  graph: {
38
48
  graphTypes: CanvasGraphType[];
39
49
  };
50
+ htmlPrimitives: HtmlPrimitiveDescriptor[];
40
51
  mcp: {
41
52
  tools: string[];
42
53
  resources: string[];
@@ -44,8 +55,13 @@ export declare function describeCanvasSchema(): {
44
55
  };
45
56
  };
46
57
  export declare function validateStructuredCanvasPayload(input: {
47
- type: 'json-render' | 'graph';
58
+ type: 'json-render' | 'graph' | 'html-primitive';
48
59
  spec?: unknown;
49
60
  graph?: GraphNodeInput;
61
+ primitive?: {
62
+ kind: string;
63
+ title?: string;
64
+ data?: Record<string, unknown>;
65
+ };
50
66
  }): StructuredValidationResult;
51
67
  export {};
@@ -18,6 +18,7 @@ export interface CanvasAnnotationSummary {
18
18
  color: string;
19
19
  width: number;
20
20
  pointCount: number;
21
+ text: string | null;
21
22
  label: string | null;
22
23
  createdAt: string;
23
24
  }
@@ -90,7 +90,7 @@ export interface CanvasAnnotationPoint {
90
90
  }
91
91
  export interface CanvasAnnotation {
92
92
  id: string;
93
- type: 'freehand';
93
+ type: 'freehand' | 'text';
94
94
  points: CanvasAnnotationPoint[];
95
95
  bounds: {
96
96
  x: number;
@@ -100,6 +100,7 @@ export interface CanvasAnnotation {
100
100
  };
101
101
  color: string;
102
102
  width: number;
103
+ text?: string;
103
104
  label?: string;
104
105
  createdAt: string;
105
106
  }
@@ -134,6 +135,9 @@ interface GroupNodesOptions {
134
135
  layout?: 'grid' | 'column' | 'flow';
135
136
  keepGroupFrame?: boolean;
136
137
  }
138
+ interface ApplyUpdatesOptions {
139
+ skipGroupChildTranslation?: boolean;
140
+ }
137
141
  declare class CanvasStateManager {
138
142
  private nodes;
139
143
  private edges;
@@ -231,7 +235,7 @@ declare class CanvasStateManager {
231
235
  private removeEdgesForNode;
232
236
  getLayout(): CanvasLayout;
233
237
  getLayoutForPersistence(): CanvasLayout;
234
- applyUpdates(updates: CanvasNodeUpdate[]): {
238
+ applyUpdates(updates: CanvasNodeUpdate[], options?: ApplyUpdatesOptions): {
235
239
  applied: number;
236
240
  skipped: number;
237
241
  };
@@ -0,0 +1,34 @@
1
+ export declare const HTML_PRIMITIVE_KINDS: readonly ["choice-grid", "plan-timeline", "review-sheet", "pr-writeup", "system-map", "code-walkthrough", "design-sheet", "component-gallery", "interaction-prototype", "flowchart", "deck", "illustration-set", "explainer", "status-report", "incident-report", "triage-board", "config-editor", "prompt-tuner"];
2
+ export type HtmlPrimitiveKind = typeof HTML_PRIMITIVE_KINDS[number];
3
+ export interface HtmlPrimitiveDescriptor {
4
+ kind: HtmlPrimitiveKind;
5
+ title: string;
6
+ description: string;
7
+ useWhen: string;
8
+ defaultSize: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ dataShape: string;
13
+ example: Record<string, unknown>;
14
+ }
15
+ export interface HtmlPrimitiveInput {
16
+ kind: HtmlPrimitiveKind;
17
+ title?: string;
18
+ data?: Record<string, unknown>;
19
+ }
20
+ export interface HtmlPrimitiveBuildResult {
21
+ kind: HtmlPrimitiveKind;
22
+ title: string;
23
+ html: string;
24
+ summary: string;
25
+ defaultSize: {
26
+ width: number;
27
+ height: number;
28
+ };
29
+ data: Record<string, unknown>;
30
+ }
31
+ export declare function isHtmlPrimitiveKind(value: string): value is HtmlPrimitiveKind;
32
+ export declare function getHtmlPrimitiveDescriptor(kind: HtmlPrimitiveKind): HtmlPrimitiveDescriptor;
33
+ export declare function listHtmlPrimitiveDescriptors(): HtmlPrimitiveDescriptor[];
34
+ export declare function buildHtmlPrimitive(input: HtmlPrimitiveInput): HtmlPrimitiveBuildResult;
@@ -3,6 +3,7 @@ import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from
3
3
  import { searchNodes } from './spatial-analysis.js';
4
4
  import { diffLayouts } from './mutation-history.js';
5
5
  import { fitCanvasView, gcCanvasSnapshots, listCanvasSnapshots } from './canvas-operations.js';
6
+ import type { HtmlPrimitiveKind } from './html-primitives.js';
6
7
  import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
7
8
  import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
8
9
  import { type DiagramPresetOpenInput } from './diagram-presets.js';
@@ -181,6 +182,7 @@ export declare class PmxCanvas extends EventEmitter {
181
182
  graph: {
182
183
  graphTypes: ("line" | "bar" | "pie" | "area" | "scatter" | "radar" | "composed" | "stacked-bar")[];
183
184
  };
185
+ htmlPrimitives: import("./html-primitives.js").HtmlPrimitiveDescriptor[];
184
186
  mcp: {
185
187
  tools: string[];
186
188
  resources: string[];
@@ -248,6 +250,21 @@ export declare class PmxCanvas extends EventEmitter {
248
250
  height?: number;
249
251
  strictSize?: boolean;
250
252
  }): string;
253
+ addHtmlPrimitive(input: {
254
+ kind: HtmlPrimitiveKind;
255
+ title?: string;
256
+ data?: Record<string, unknown>;
257
+ x?: number;
258
+ y?: number;
259
+ width?: number;
260
+ height?: number;
261
+ strictSize?: boolean;
262
+ }): {
263
+ id: string;
264
+ kind: HtmlPrimitiveKind;
265
+ title: string;
266
+ htmlBytes: number;
267
+ };
251
268
  addGraphNode(input: GraphNodeInput): {
252
269
  id: string;
253
270
  url: string;
@@ -275,10 +292,12 @@ export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo }
275
292
  export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
276
293
  export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
277
294
  export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
295
+ export { buildHtmlPrimitive, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
278
296
  export { buildWebArtifactOnCanvas, executeWebArtifactBuild, openWebArtifactInCanvas, resolveWebArtifactScriptPath, resolveWorkspacePath, } from './web-artifacts.js';
279
297
  export { buildGraphSpec, buildJsonRenderViewerHtml, createJsonRenderNodeData, GRAPH_NODE_SIZE, JSON_RENDER_NODE_SIZE, normalizeAndValidateJsonRenderSpec, } from '../json-render/server.js';
280
298
  export type { CodeGraphSummary, CodeGraphEdge } from './code-graph.js';
281
299
  export type { MutationEntry, MutationSummary, SnapshotDiffResult } from './mutation-history.js';
282
300
  export type { WebArtifactBuildInput, WebArtifactBuildOutput, WebArtifactCanvasBuildResult, WebArtifactCanvasOpenResult, } from './web-artifacts.js';
283
301
  export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
302
+ export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
284
303
  export { traceManager } from './trace-manager.js';
package/docs/cli.md CHANGED
@@ -8,7 +8,7 @@ The CLI is the shell-native way to run and control PMX Canvas. It targets
8
8
 
9
9
  ```bash
10
10
  pmx-canvas # Start canvas, open browser
11
- pmx-canvas --demo # Start with the project-tour demo board
11
+ pmx-canvas --demo # Start with the saved dashboard demo board
12
12
  pmx-canvas --port=8080 # Custom port
13
13
  pmx-canvas --no-open # Headless (for agents/CI)
14
14
  pmx-canvas --theme=light # dark | light | high-contrast
@@ -35,6 +35,8 @@ pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
35
35
  pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
36
36
  pmx-canvas node add --type graph --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
37
37
  pmx-canvas graph add --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y # Alias
38
+ pmx-canvas html primitive add --kind choice-grid --data-file ./options.json --title "Options"
39
+ pmx-canvas html primitive schema --summary
38
40
  pmx-canvas node add --help --type webpage --json # Schema for one type
39
41
 
40
42
  pmx-canvas external-app add --kind excalidraw --title "Diagram"
@@ -66,6 +68,7 @@ content.
66
68
  pmx-canvas node schema --type json-render --component Table --summary
67
69
  pmx-canvas validate # Layout validation
68
70
  pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary
71
+ pmx-canvas validate spec --type html-primitive --kind choice-grid --data-json '{"items":[{"title":"A"}]}' --summary
69
72
  ```
70
73
 
71
74
  The schema commands surface the running server's data, which is strictly
package/docs/http-api.md CHANGED
@@ -22,6 +22,11 @@ curl http://localhost:4313/api/canvas/schema
22
22
  curl -X POST http://localhost:4313/api/canvas/schema/validate \
23
23
  -H "Content-Type: application/json" \
24
24
  -d '{"type":"json-render","spec":{"root":"card","elements":{"card":{"type":"Card","props":{"title":"Preview"},"children":[]}}}}'
25
+
26
+ # Validate an HTML primitive without creating a node
27
+ curl -X POST http://localhost:4313/api/canvas/schema/validate \
28
+ -H "Content-Type: application/json" \
29
+ -d '{"type":"html-primitive","kind":"choice-grid","data":{"items":[{"title":"A"}]}}'
25
30
  ```
26
31
 
27
32
  ## Nodes
@@ -36,6 +41,11 @@ curl -X POST http://localhost:4313/api/canvas/node \
36
41
  curl -X POST http://localhost:4313/api/canvas/node \
37
42
  -H "Content-Type: application/json" \
38
43
  -d '{"type":"html","title":"Chart","html":"<canvas id=\"c\"></canvas><script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script><script>/* ... */</script>"}'
44
+
45
+ # Add a generated HTML primitive as a sandboxed html node
46
+ curl -X POST http://localhost:4313/api/canvas/node \
47
+ -H "Content-Type: application/json" \
48
+ -d '{"type":"html-primitive","kind":"choice-grid","title":"Options","data":{"items":[{"title":"Small patch","summary":"Least disruption."}]}}'
39
49
  ```
40
50
 
41
51
  ## Edges
package/docs/mcp.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP reference
2
2
 
3
- PMX Canvas ships an MCP stdio server with **41 tools** + **8 core resources**,
3
+ PMX Canvas ships an MCP stdio server with **42 tools** + **8 core resources**,
4
4
  plus per-skill resources at `canvas://skills/<name>`. The server emits
5
5
  `notifications/resources/updated` when canvas state changes — humans pin
6
6
  nodes in the browser, agents are notified immediately.
@@ -28,10 +28,11 @@ The canvas auto-starts on first tool call.
28
28
  |------|-------------|
29
29
  | `canvas_add_node` | Add a node (markdown, status, context, file, webpage, html, etc.) |
30
30
  | `canvas_add_html_node` | Create an `html` node from a self-contained HTML/JS document (sandboxed iframe) |
31
+ | `canvas_add_html_primitive` | Create a reusable generated HTML communication primitive as a sandboxed `html` node |
31
32
  | `canvas_add_diagram` | Hand-drawn diagram via the hosted Excalidraw MCP App (preset alias for `canvas_open_mcp_app`) |
32
33
  | `canvas_open_mcp_app` | Open any [MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) server's `ui://` resource as an iframe node |
33
- | `canvas_describe_schema` | Describe the running server's create schemas, examples, and json-render catalog |
34
- | `canvas_validate_spec` | Validate a json-render spec or graph payload without creating a node |
34
+ | `canvas_describe_schema` | Describe the running server's create schemas, examples, json-render catalog, and HTML primitive catalog |
35
+ | `canvas_validate_spec` | Validate a json-render spec, graph payload, or HTML primitive payload without creating a node |
35
36
  | `canvas_refresh_webpage_node` | Re-fetch and update a webpage node from its stored URL |
36
37
  | `canvas_add_json_render_node` | Create a native json-render node from a validated spec |
37
38
  | `canvas_add_graph_node` | Create a native graph node (line, bar, pie, area, scatter, radar, stacked-bar, composed) |
@@ -118,6 +119,7 @@ in doubt:
118
119
 
119
120
  - `json-render` → `canvas_add_json_render_node`
120
121
  - `graph` → `canvas_add_graph_node`
122
+ - `html-primitive` → `canvas_add_html_primitive`
121
123
  - `html` → `canvas_add_html_node`
122
124
  - `web-artifact` → `canvas_build_web_artifact`
123
125
  - `mcp-app` → `canvas_open_mcp_app`
@@ -128,7 +130,7 @@ in doubt:
128
130
  ## CLI/MCP alignment
129
131
 
130
132
  CLI and MCP are kept aligned for the main canvas operations: node and edge
131
- creation, graph/json-render/html nodes, web artifacts, external apps, groups,
133
+ creation, graph/json-render/html/html-primitive nodes, web artifacts, external apps, groups,
132
134
  batch builds, layout validation, snapshots, search, focus, pins, undo/redo,
133
135
  semantic watch streams, WebView automation, and daemon/server control where
134
136
  it applies. A few agent-native capabilities — resource subscriptions and
@@ -164,6 +164,33 @@ canvas_add_html_node({
164
164
  A fragment without `<html>`/`<head>` is wrapped in a full document
165
165
  automatically. Default size is 720×640.
166
166
 
167
+ ### HTML primitives
168
+
169
+ `html-primitive` is a virtual schema type that creates a normal sandboxed
170
+ `html` node from a reusable communication template. Use it when a long markdown
171
+ answer would be easier to review as an option grid, implementation timeline,
172
+ review sheet, PR writeup, code walkthrough, system map, design sheet,
173
+ component gallery, interaction prototype, flowchart, SVG illustration set,
174
+ explainer, status report, incident report, triage board, config editor, or
175
+ prompt tuner.
176
+
177
+ ```ts
178
+ canvas_add_html_primitive({
179
+ kind: 'choice-grid',
180
+ title: 'Implementation options',
181
+ data: {
182
+ items: [
183
+ { title: 'Small patch', summary: 'Least disruption.', pros: ['Fast'], cons: ['Less flexible'] },
184
+ ],
185
+ },
186
+ });
187
+ ```
188
+
189
+ HTTP callers may post either `{ "type": "html-primitive", "kind": "choice-grid", "data": ... }`
190
+ or `{ "type": "html", "primitive": "choice-grid", "data": ... }`. The stored
191
+ node remains `type: "html"` with `data.htmlPrimitive`, `data.primitiveData`, and
192
+ the generated `data.html` payload.
193
+
167
194
  ## Web artifacts
168
195
 
169
196
  A **web artifact** is a single-file, fully bundled HTML app (React + Tailwind
@@ -222,9 +249,9 @@ Agents don't have to guess node shapes. The running server exposes its create
222
249
  schemas, json-render component catalog, and node-type examples:
223
250
 
224
251
  - `canvas_describe_schema` / `GET /api/canvas/schema` — list all node-create
225
- schemas, required fields, json-render components, and sample payloads
252
+ schemas, required fields, json-render components, HTML primitives, and sample payloads
226
253
  - `canvas_validate_spec` / `POST /api/canvas/schema/validate` — validate a
227
- json-render spec or graph payload **without** creating a node
254
+ json-render spec, graph payload, or HTML primitive payload **without** creating a node
228
255
  - `canvas_validate` / `GET /api/canvas/validate` — validate the current
229
256
  layout for collisions, containment, and missing edge endpoints
230
257
  - `canvas://schema` — the same data as an MCP resource
@@ -236,6 +263,7 @@ MCP node creation uses dedicated tools for structured node families. Read
236
263
  `mcp.nodeTypeRouting` from `canvas_describe_schema` when in doubt:
237
264
  `json-render` → `canvas_add_json_render_node`,
238
265
  `graph` → `canvas_add_graph_node`,
266
+ `html-primitive` → `canvas_add_html_primitive`,
239
267
  `html` → `canvas_add_html_node`,
240
268
  `web-artifact` → `canvas_build_web_artifact`,
241
269
  `mcp-app` → `canvas_open_mcp_app`,
Binary file
package/docs/sdk.md CHANGED
@@ -32,6 +32,17 @@ canvas.addHtmlNode({
32
32
  html: '<canvas id="c"></canvas><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><script>/* ... */</script>',
33
33
  });
34
34
 
35
+ // Generated HTML communication primitive, stored as a sandboxed html node
36
+ canvas.addHtmlPrimitive({
37
+ kind: 'choice-grid',
38
+ title: 'Implementation options',
39
+ data: {
40
+ items: [
41
+ { title: 'Small patch', summary: 'Least disruption.', pros: ['Fast'], cons: ['Less flexible'] },
42
+ ],
43
+ },
44
+ });
45
+
35
46
  // Hand-drawn diagram via the Excalidraw MCP-app preset
36
47
  await canvas.addDiagram({
37
48
  elements: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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",
@@ -270,6 +270,7 @@ MCP node-type routing:
270
270
  | Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_add_node` |
271
271
  | `json-render` | `canvas_add_json_render_node` |
272
272
  | `graph` | `canvas_add_graph_node` |
273
+ | `html-primitive` | `canvas_add_html_primitive` |
273
274
  | `html` | `canvas_add_html_node` |
274
275
  | `web-artifact` | `canvas_build_web_artifact` |
275
276
  | `external-app` / tool-backed `mcp-app` | `canvas_open_mcp_app` |
@@ -659,6 +660,12 @@ server's `ui://` resource as an iframe node on the canvas
659
660
  - Canvas theme tokens are auto-injected as CSS custom properties (both `--c-*` and common `--color-*` aliases such as `--color-text-primary`, `--color-bg`, `--color-accent`) so authored HTML inherits the active theme
660
661
  - Use for moderate-complexity visualizations and interactive widgets that need real JS but do not warrant a full React build (Chart.js demos, D3 sketches, custom HTML report views)
661
662
 
663
+ **`canvas_add_html_primitive`** — Generate a reusable HTML communication primitive as a sandboxed `html` node
664
+ - Required: `kind`; run `canvas_describe_schema` and read `htmlPrimitives` for the current catalog
665
+ - Optional: `title`, `data`, `x`, `y`, `width`, `height`, `strictSize`
666
+ - Use when markdown would be too dense and a structured visual artifact is clearer: tradeoff grids, implementation plans, PR reviews, module maps, design sheets, explainers, reports, and lightweight human-editable boards/editors
667
+ - Read `htmlPrimitives` from `canvas_describe_schema` for the data shape and examples before constructing a payload
668
+
662
669
  ### Choosing the Right Visual Tier
663
670
 
664
671
  When the output is more than markdown, pick the lightest tier that fits:
@@ -666,6 +673,7 @@ When the output is more than markdown, pick the lightest tier that fits:
666
673
  | Tier | Tool | Build cost | When to pick it |
667
674
  |------|------|------------|-----------------|
668
675
  | Declarative UI | `canvas_add_json_render_node` / `canvas_add_graph_node` | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via `canvas_get_node` |
676
+ | Generated HTML primitive | `canvas_add_html_primitive` | None | Reusable communication artifacts such as choices, plans, reviews, maps, reports, decks, and lightweight editors |
669
677
  | Sandboxed HTML+JS | `canvas_add_html_node` | None | Self-contained HTML with inline JS or CDN scripts; one-off visualizations or report views |
670
678
  | Hosted MCP app | `canvas_open_mcp_app` / `canvas_add_diagram` | None | Interactive editors backed by an external MCP server (e.g. Excalidraw) |
671
679
  | Bundled React app | `canvas_build_web_artifact` | Heavy (npm install + bundle) | Multi-component UIs needing React state, routing, shadcn/ui, or Tailwind class composition |