pmx-canvas 0.6.1 → 0.6.3
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 +41 -0
- package/Readme.md +14 -2
- package/dist/canvas/global.css +22 -3
- package/dist/canvas/index.js +64 -64
- package/dist/json-render/index.js +57 -57
- package/dist/types/client/canvas/auto-fit.d.ts +3 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +2 -1
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +3 -3
- package/dist/types/client/state/human-store.d.ts +1 -0
- package/dist/types/json-render/catalog.d.ts +20 -20
- package/dist/types/json-render/schema.d.ts +40 -0
- package/dist/types/mcp/skills.d.ts +13 -0
- package/dist/types/server/bundled-skills.d.ts +32 -4
- package/docs/http-api.md +5 -1
- package/docs/mcp.md +35 -1
- package/docs/node-types.md +1 -1
- package/docs/product-context-vision-2026-09.md +16 -5
- package/docs/product-vision-2026-09.md +4 -2
- package/package.json +11 -11
- package/skills/json-render-mcp/SKILL.md +118 -2
- package/skills/pmx-canvas/SKILL.md +42 -16
- package/skills/pmx-canvas/evals/evals.json +1 -1
- package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +5 -0
- package/skills/pmx-canvas/references/full-reference.md +5 -4
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +5 -5
- package/skills/pmx-canvas-testing/SKILL.md +13 -4
- package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +11 -12
- package/src/client/canvas/CanvasNode.tsx +9 -12
- package/src/client/canvas/CanvasViewport.tsx +10 -34
- package/src/client/canvas/ContextMenu.tsx +13 -0
- package/src/client/canvas/EdgeLayer.tsx +2 -2
- package/src/client/canvas/SessionPanel.tsx +23 -4
- package/src/client/canvas/ShortcutOverlay.tsx +2 -2
- package/src/client/canvas/auto-fit.ts +20 -1
- package/src/client/canvas/use-pan-zoom.ts +16 -3
- package/src/client/nodes/ExtAppFrame.tsx +7 -1
- package/src/client/nodes/use-iframe-content-height.ts +44 -24
- package/src/client/state/human-store.ts +6 -6
- package/src/client/state/sse-bridge.ts +3 -1
- package/src/client/theme/global.css +22 -3
- package/src/json-render/catalog.ts +6 -0
- package/src/json-render/renderer/index.tsx +17 -0
- package/src/json-render/schema.ts +4 -0
- package/src/mcp/server.ts +9 -4
- package/src/mcp/skills.ts +47 -0
- package/src/server/bundled-skills.ts +92 -53
- package/src/server/canvas-operations.ts +2 -2
- package/src/server/operations/ops/nodes.ts +43 -11
- package/src/server/operations/ops/viewport.ts +6 -1
- package/src/server/server.ts +9 -9
- package/src/server/web-artifacts/scripts/bundle-artifact.sh +29 -8
- package/src/shared/content-height-reporter.ts +4 -2
|
@@ -17,3 +17,6 @@ export declare function shouldContentFitIframeNode(node: CanvasNodeState): boole
|
|
|
17
17
|
* capped at the iframe ceiling.
|
|
18
18
|
*/
|
|
19
19
|
export declare function computeContentGrowHeight(node: CanvasNodeState, contentHeight: number): number | null;
|
|
20
|
+
/** Keep an automatically growing card clear without moving its neighbors or
|
|
21
|
+
* changing its column. Groups and screen-pinned cards retain authored placement. */
|
|
22
|
+
export declare function contentFitPosition(node: CanvasNodeState, height: number, allNodes: CanvasNodeState[]): CanvasNodeState['position'];
|
|
@@ -10,7 +10,8 @@ interface PanZoomOptions {
|
|
|
10
10
|
* Hook that wires up pan/zoom interactions on a container element.
|
|
11
11
|
* - Wheel + Ctrl/Cmd: zoom centered on pointer
|
|
12
12
|
* - Wheel without modifier: pan
|
|
13
|
-
* -
|
|
13
|
+
* - Right or middle pointer drag: pan from anywhere
|
|
14
|
+
* - Pointer drag on background in pan mode: pan
|
|
14
15
|
* - Pinch (touch): zoom
|
|
15
16
|
*/
|
|
16
17
|
export declare function usePanZoom({ viewport, onViewportChange, onViewportCommit, disabled }: PanZoomOptions): import("preact/src").RefObject<HTMLDivElement>;
|
|
@@ -7,9 +7,9 @@ import type { CanvasNodeState } from '../types';
|
|
|
7
7
|
* because growth is monotonic with a dead-band — cannot oscillate. This is the
|
|
8
8
|
* fix for iframe nodes whose body scrollHeight the parent can't measure.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Debounce before changing geometry, retaining the latest measurement during a
|
|
11
|
+
* human grab. Read current geometry at application time; persist only this node.
|
|
12
|
+
* Relocations are undoable, height-only adjustments do not fill the undo stack.
|
|
13
13
|
*/
|
|
14
14
|
export declare function useIframeContentHeight(node: CanvasNodeState, iframeRef: {
|
|
15
15
|
current: HTMLIFrameElement | null;
|
|
@@ -6,6 +6,7 @@ export declare const otherHumans: import("@preact/signals-core").ReadonlySignal<
|
|
|
6
6
|
/** Nodes a human took over mid-edit → that human's name, shown as the yield pill for a moment. */
|
|
7
7
|
export declare const yieldedNodes: import("@preact/signals-core").Signal<Map<string, string>>;
|
|
8
8
|
export declare function applyHumanSnapshot(snapshot: Partial<HumanPresenceSnapshot> | null | undefined): void;
|
|
9
|
+
export declare const grabbingNodeId: import("@preact/signals-core").Signal<string | null>;
|
|
9
10
|
/** Report the pointer in world coordinates (throttled); null when it leaves the canvas. */
|
|
10
11
|
export declare function reportHumanCursor(cursor: {
|
|
11
12
|
x: number;
|
|
@@ -449,6 +449,26 @@ export declare const allComponentDefinitions: {
|
|
|
449
449
|
readonly showLabels: true;
|
|
450
450
|
};
|
|
451
451
|
};
|
|
452
|
+
Card: {
|
|
453
|
+
slots: string[];
|
|
454
|
+
description: string;
|
|
455
|
+
props: z.ZodObject<{
|
|
456
|
+
title: z.ZodNullable<z.ZodString>;
|
|
457
|
+
description: z.ZodNullable<z.ZodString>;
|
|
458
|
+
maxWidth: z.ZodNullable<z.ZodEnum<{
|
|
459
|
+
sm: "sm";
|
|
460
|
+
md: "md";
|
|
461
|
+
lg: "lg";
|
|
462
|
+
full: "full";
|
|
463
|
+
}>>;
|
|
464
|
+
centered: z.ZodNullable<z.ZodBoolean>;
|
|
465
|
+
className: z.ZodNullable<z.ZodString>;
|
|
466
|
+
}, z.core.$strip>;
|
|
467
|
+
example: {
|
|
468
|
+
title: string;
|
|
469
|
+
description: string;
|
|
470
|
+
};
|
|
471
|
+
};
|
|
452
472
|
Badge: {
|
|
453
473
|
props: z.ZodObject<{
|
|
454
474
|
text: z.ZodString;
|
|
@@ -491,26 +511,6 @@ export declare const allComponentDefinitions: {
|
|
|
491
511
|
};
|
|
492
512
|
events: string[];
|
|
493
513
|
};
|
|
494
|
-
Card: {
|
|
495
|
-
props: z.ZodObject<{
|
|
496
|
-
title: z.ZodNullable<z.ZodString>;
|
|
497
|
-
description: z.ZodNullable<z.ZodString>;
|
|
498
|
-
maxWidth: z.ZodNullable<z.ZodEnum<{
|
|
499
|
-
sm: "sm";
|
|
500
|
-
md: "md";
|
|
501
|
-
lg: "lg";
|
|
502
|
-
full: "full";
|
|
503
|
-
}>>;
|
|
504
|
-
centered: z.ZodNullable<z.ZodBoolean>;
|
|
505
|
-
className: z.ZodNullable<z.ZodString>;
|
|
506
|
-
}, z.core.$strip>;
|
|
507
|
-
slots: string[];
|
|
508
|
-
description: string;
|
|
509
|
-
example: {
|
|
510
|
-
title: string;
|
|
511
|
-
description: string;
|
|
512
|
-
};
|
|
513
|
-
};
|
|
514
514
|
Stack: {
|
|
515
515
|
props: z.ZodObject<{
|
|
516
516
|
direction: z.ZodNullable<z.ZodEnum<{
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
export declare const schema: import("@json-render/core").Schema<{
|
|
2
2
|
spec: import("@json-render/core").SchemaType<"object", {
|
|
3
3
|
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
4
|
+
state: {
|
|
5
|
+
optional: true;
|
|
6
|
+
kind: "record";
|
|
7
|
+
inner?: import("@json-render/core").SchemaType<"any", unknown> | undefined;
|
|
8
|
+
};
|
|
4
9
|
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
5
10
|
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
6
11
|
props: import("@json-render/core").SchemaType<"propsOf", string>;
|
|
7
12
|
children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
8
13
|
visible: import("@json-render/core").SchemaType<"any", unknown>;
|
|
14
|
+
repeat: {
|
|
15
|
+
optional: true;
|
|
16
|
+
kind: "any";
|
|
17
|
+
inner?: unknown;
|
|
18
|
+
};
|
|
19
|
+
watch: {
|
|
20
|
+
optional: true;
|
|
21
|
+
kind: "record";
|
|
22
|
+
inner?: import("@json-render/core").SchemaType<"any", unknown> | undefined;
|
|
23
|
+
};
|
|
24
|
+
slots: {
|
|
25
|
+
optional: true;
|
|
26
|
+
kind: "record";
|
|
27
|
+
inner?: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>> | undefined;
|
|
28
|
+
};
|
|
9
29
|
on: import("@json-render/core").SchemaType<"any", unknown>;
|
|
10
30
|
}>>;
|
|
11
31
|
}>;
|
|
@@ -25,11 +45,31 @@ export declare const schema: import("@json-render/core").Schema<{
|
|
|
25
45
|
export declare const elementTreeSchema: import("@json-render/core").Schema<{
|
|
26
46
|
spec: import("@json-render/core").SchemaType<"object", {
|
|
27
47
|
root: import("@json-render/core").SchemaType<"string", unknown>;
|
|
48
|
+
state: {
|
|
49
|
+
optional: true;
|
|
50
|
+
kind: "record";
|
|
51
|
+
inner?: import("@json-render/core").SchemaType<"any", unknown> | undefined;
|
|
52
|
+
};
|
|
28
53
|
elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
|
|
29
54
|
type: import("@json-render/core").SchemaType<"ref", string>;
|
|
30
55
|
props: import("@json-render/core").SchemaType<"propsOf", string>;
|
|
31
56
|
children: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>>;
|
|
32
57
|
visible: import("@json-render/core").SchemaType<"any", unknown>;
|
|
58
|
+
repeat: {
|
|
59
|
+
optional: true;
|
|
60
|
+
kind: "any";
|
|
61
|
+
inner?: unknown;
|
|
62
|
+
};
|
|
63
|
+
watch: {
|
|
64
|
+
optional: true;
|
|
65
|
+
kind: "record";
|
|
66
|
+
inner?: import("@json-render/core").SchemaType<"any", unknown> | undefined;
|
|
67
|
+
};
|
|
68
|
+
slots: {
|
|
69
|
+
optional: true;
|
|
70
|
+
kind: "record";
|
|
71
|
+
inner?: import("@json-render/core").SchemaType<"array", import("@json-render/core").SchemaType<"string", unknown>> | undefined;
|
|
72
|
+
};
|
|
33
73
|
on: import("@json-render/core").SchemaType<"any", unknown>;
|
|
34
74
|
}>>;
|
|
35
75
|
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
/** SEP-2640 methods are protocol requests, not model-facing canvas tools. */
|
|
3
|
+
export declare function registerSkillExtension(server: McpServer, catalog?: {
|
|
4
|
+
skills: import("../server/bundled-skills.js").SkillEntry[];
|
|
5
|
+
files: Map<string, {
|
|
6
|
+
uri: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
} & ({
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
blob: string;
|
|
12
|
+
})>;
|
|
13
|
+
}): void;
|
|
@@ -16,11 +16,17 @@
|
|
|
16
16
|
* - `canvas://skills` → JSON index
|
|
17
17
|
* - `canvas://skills/<name>` → full markdown content
|
|
18
18
|
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
declare const skillFrontmatterSchema: z.ZodObject<{
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
description: z.ZodString;
|
|
23
|
+
}, z.core.$loose>;
|
|
19
24
|
export interface BundledSkill {
|
|
20
25
|
name: string;
|
|
21
26
|
description: string;
|
|
22
27
|
uri: string;
|
|
23
28
|
filePath: string;
|
|
29
|
+
frontmatter: z.infer<typeof skillFrontmatterSchema>;
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* Resolve the packaged `skills/` directory. Walks parents from this module
|
|
@@ -32,9 +38,31 @@ export interface BundledSkill {
|
|
|
32
38
|
export declare function findBundledSkillsRoot(): string | null;
|
|
33
39
|
/**
|
|
34
40
|
* Enumerate every `<name>/SKILL.md` under the bundled skills root and return
|
|
35
|
-
* a compact index.
|
|
36
|
-
*
|
|
37
|
-
* break the MCP server's resource listing.
|
|
41
|
+
* a compact index. Invalid packaged skills fail explicitly rather than being
|
|
42
|
+
* silently omitted from the Skills extension's integrity manifest.
|
|
38
43
|
*/
|
|
39
|
-
export declare function listBundledSkills(): BundledSkill[];
|
|
44
|
+
export declare function listBundledSkills(root?: string | null): BundledSkill[];
|
|
40
45
|
export declare function readBundledSkill(name: string): string | null;
|
|
46
|
+
export interface SkillEntry {
|
|
47
|
+
uri: string;
|
|
48
|
+
frontmatter: BundledSkill['frontmatter'];
|
|
49
|
+
resources: Array<{
|
|
50
|
+
uri: string;
|
|
51
|
+
digest: string;
|
|
52
|
+
size: number;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
type SkillContent = {
|
|
56
|
+
uri: string;
|
|
57
|
+
mimeType: string;
|
|
58
|
+
} & ({
|
|
59
|
+
text: string;
|
|
60
|
+
} | {
|
|
61
|
+
blob: string;
|
|
62
|
+
});
|
|
63
|
+
/** Capture immutable package bytes once per MCP process, never read caller-supplied paths. */
|
|
64
|
+
export declare function createBundledSkillCatalog(root?: string | null): {
|
|
65
|
+
skills: SkillEntry[];
|
|
66
|
+
files: Map<string, SkillContent>;
|
|
67
|
+
};
|
|
68
|
+
export {};
|
package/docs/http-api.md
CHANGED
|
@@ -88,7 +88,11 @@ curl -X PATCH http://localhost:4313/api/canvas/node/node-abc123 \
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
The title follows the new filename unless the node was renamed away from the
|
|
91
|
-
old one (or the patch sets `title` explicitly).
|
|
91
|
+
old one (or the patch sets `title` explicitly). Relative `path` values (including
|
|
92
|
+
`data.path` in batch updates) resolve against the configured canvas workspace,
|
|
93
|
+
not the daemon's working directory; the stored path is absolute. A missing or
|
|
94
|
+
unreadable target fails the update without changing the node or its existing watcher
|
|
95
|
+
(`400` for a direct PATCH; a failed-operation result in a batch).
|
|
92
96
|
|
|
93
97
|
```bash
|
|
94
98
|
# Raw bytes for a binary file node (PDF viewer, downloads)
|
package/docs/mcp.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# MCP reference
|
|
2
2
|
|
|
3
3
|
PMX Canvas ships an MCP stdio server with **22 tools** + **14 core resources**,
|
|
4
|
-
plus per-skill resources at `canvas://skills/<name
|
|
4
|
+
plus per-skill resources at `canvas://skills/<name>` and `skill://<name>/SKILL.md`. The server emits
|
|
5
5
|
`notifications/resources/updated` when canvas state changes — humans pin
|
|
6
6
|
nodes in the browser, agents are notified immediately.
|
|
7
7
|
|
|
@@ -202,6 +202,40 @@ Individual bundled skills are also readable at `canvas://skills/<name>`.
|
|
|
202
202
|
| `canvas://code-graph` | Auto-detected file dependency graph (JS/TS, Python, Go, Rust) |
|
|
203
203
|
| `canvas://skills` | Index of bundled agent skills + per-skill content at `canvas://skills/<name>` |
|
|
204
204
|
|
|
205
|
+
### Skills extension (SEP-2640)
|
|
206
|
+
|
|
207
|
+
PMX advertises `capabilities.extensions["io.modelcontextprotocol/skills"] = {}`
|
|
208
|
+
alongside the base `resources` capability. This adds protocol methods, not canvas tools:
|
|
209
|
+
|
|
210
|
+
| Method | Parameters | Result |
|
|
211
|
+
|--------|------------|--------|
|
|
212
|
+
| `skills/list` | None or `{}` | `{ resultType: "complete", skills: [...] }` |
|
|
213
|
+
| `skills/get` | `{ uri: "skill://pmx-canvas/SKILL.md" }` | `{ resultType: "complete", skill: {...} }` |
|
|
214
|
+
| `resources/read` | `{ uri: "skill://pmx-canvas/references/installing-pmx-canvas.md" }` | Standard resource content |
|
|
215
|
+
|
|
216
|
+
Each skill entry contains its `SKILL.md` URI, full parsed YAML `frontmatter`, and a complete
|
|
217
|
+
`resources` manifest of `{ uri, digest, size }` for every file. Digests are raw-byte SHA-256
|
|
218
|
+
(`sha256:<hex>`); sizes are bytes. UTF-8 files are returned as text, binary files as base64 blobs.
|
|
219
|
+
Resolve relative references against the skill directory. Files are loaded by the host on demand;
|
|
220
|
+
discovery does not load all instructions into model context or start a canvas daemon.
|
|
221
|
+
|
|
222
|
+
The packaged catalog fits in one page, so no `nextCursor` is returned and supplied cursors are
|
|
223
|
+
rejected with `-32602`. Unknown skill/file URIs also return `-32602`. Optional
|
|
224
|
+
`resources/directory/read` is not advertised or implemented; manifests already enumerate files.
|
|
225
|
+
PMX currently negotiates the SDK's supported base protocol (up to `2025-11-25`), so the
|
|
226
|
+
`2026-07-28` list-caching fields are not emitted.
|
|
227
|
+
|
|
228
|
+
The server snapshots packaged files at MCP startup. Restart the MCP process after an upgrade to
|
|
229
|
+
get new instructions and manifests. Invalid frontmatter, symbolic links, non-regular files, and
|
|
230
|
+
skills exceeding 512 files or 16 MiB fail catalog creation. Reads only address captured package
|
|
231
|
+
files, never arbitrary workspace paths. Scripts are served as data and are never executed by these methods.
|
|
232
|
+
|
|
233
|
+
Hosts must implement the extension's origin tagging, integrity verification, activation, and
|
|
234
|
+
approval rules. Reading a resource does not itself activate a skill or authorize local execution.
|
|
235
|
+
Automatic skill loading depends on host support; ordinary resource reads and filesystem installs
|
|
236
|
+
remain available. Use `pmx-canvas skills sync` for installed local mirrors. See the
|
|
237
|
+
[final specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2640-skills-extension.md).
|
|
238
|
+
|
|
205
239
|
## Node interactions (capability-gated)
|
|
206
240
|
|
|
207
241
|
Eligible nodes emit one normalized, validated interaction envelope
|
package/docs/node-types.md
CHANGED
|
@@ -328,7 +328,7 @@ Groups are spatial containers that visually contain other nodes. They render
|
|
|
328
328
|
as dashed-border frames with a title bar and optional accent color.
|
|
329
329
|
|
|
330
330
|
- Select 2+ nodes and click "Group" in the selection bar
|
|
331
|
-
- Ungroup from the frame's ⋯ menu, the
|
|
331
|
+
- Ungroup from the frame's ⋯ menu, the double-click menu, or Shift+G — the frame dissolves and
|
|
332
332
|
its children stay as independent nodes (one undo step); `canvas_group { action: "ungroup" }`
|
|
333
333
|
does exactly the same
|
|
334
334
|
- Collapsing a group hides children and shows a summary
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
**Work visually with your agents, build understanding as you go, and bring the right knowledge into whatever comes next.**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**The board is the human's extended memory and the agent's working memory.** It is a place for the human to do knowledge work, not just prepare context for an agent: research, analysis, dashboards, discovery, planning, coordination, and starting or orchestrating coding and other work. These are examples of its broad purpose, not a closed list. People can use the board to think and work directly, with agents contributing on the same surface.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
As the work develops, sources, notes, questions, charts, plans, decisions, and outputs accumulate and connect on the board. They contribute to a memory graph and maintained wiki that support both human recall and agent context across tasks. Relevant prior knowledge can then return to a board as material for further work. The board, graph, and wiki are connected views and uses of this knowledge—not three competing sources of truth or three separate filing chores.
|
|
16
|
+
|
|
17
|
+
- **Do and shape the work together.** Read, write, compare evidence, monitor a dashboard, develop a plan, and direct execution on the board. Arrange, connect, pin, and annotate to support human understanding as well as shape agent context. Agents bring questions, proposals, and results back onto the same surface. Spatial layout provides context; not every move or camera gesture expresses intent.
|
|
16
18
|
- **Make memory useful in the next task.** Resume without re-explaining everything. Bring relevant prior learning and authorized cross-team decisions, dependencies, and conflicts into the work—not merely into a growing collection of notes.
|
|
17
19
|
- **Keep context honest as work changes.** Show sources and revisions, surface known changes and conflicts, and make uncertainty and review needs visible. Help people judge what still applies; do not treat a newer timestamp as truth or silently replace confirmed knowledge.
|
|
18
20
|
|
|
@@ -50,12 +52,12 @@ The loop is bidirectional: work produces personal understanding; selected outcom
|
|
|
50
52
|
8. **Treat hosted as more than a login screen.** Revocation, derived-content permissions, backups/restores, uploads, quotas, sandbox isolation, and operational recovery are new responsibilities. Node-level compare-and-set can be a starting conflict policy, but requires visible conflict/retry behavior; it does not settle collaborative text editing.
|
|
51
53
|
9. **Use measurements as evidence, not promises.** The earlier audit describes a particular tree and machine. Current context construction already has text previews and bounded pending steering. The claimed 10,000-token saving depends on what schemas hosts actually load; on-demand help alone does not remove advertised schemas. Measure current payloads before fixing budgets. Net-negative lines are not a user-value release gate.
|
|
52
54
|
|
|
53
|
-
The north star is
|
|
55
|
+
The north star is doing knowledge work on a shared visual surface that extends human memory and supplies agent working memory, with understanding carried into the next task. Validate human usefulness as well as agent behavior: can a person inspect evidence, understand relationships, resume research, monitor a dashboard, or develop and direct a plan? Pins, camera attention, and the time scrubber are mechanisms to validate against that purpose, not the purpose itself.
|
|
54
56
|
|
|
55
57
|
## The user experience
|
|
56
58
|
|
|
57
59
|
1. **Describe your work.** “I lead developer experience, work with these teams, and own these outcomes.” The assistant proposes a small personal home: responsibilities, initiatives, goals, people, and open questions. No mandatory taxonomy or blank-wiki setup project.
|
|
58
|
-
2. **Work on named boards.**
|
|
60
|
+
2. **Work on named boards.** Research, compare evidence, use dashboards, create outputs, and start, plan, or orchestrate coding and other work. Boards remain separate and recoverable. Each board is automatically listed as a source in the owner's context, within its existing permissions; the human can return to it without needing an agent to reconstruct the work.
|
|
59
61
|
3. **Remember without manual filing.** After the user delegates maintenance of specified private sources, the assistant updates existing personal working pages during the task. Source links, a quiet change history, and undo replace a compulsory wiki-edit review queue. Unknowns stay unknown; generated interpretations stay labeled.
|
|
60
62
|
4. **Confirm what matters.** “Keep this as my decision” confirms an exact revision with its evidence and owner. New agent changes remain working revisions, not silently renewed confirmation. Pinning still means attention, not publication.
|
|
61
63
|
5. **Resume with relevant memory.** A new board retrieves labeled personal working knowledge alongside confirmed personal context and authorized workspace knowledge. For shared organizational facts, shared canon has authority; for personal preferences, the person's context applies. Conflicting or newer evidence is shown rather than overwritten.
|
|
@@ -63,11 +65,20 @@ The north star is planning, discovery, analysis, and coordination visually with
|
|
|
63
65
|
|
|
64
66
|
Example: a PM explores options on a discovery board, accepts a decision into personal context, then publishes an appropriately sourced workspace decision. A team lead opens a planning board and receives that decision plus related shared dependencies—not the PM's private stakeholder notes or abandoned brainstorming.
|
|
65
67
|
|
|
68
|
+
### Work becomes memory, and memory supports more work
|
|
69
|
+
|
|
70
|
+
1. **Work on the board:** humans and agents add and use source material, relationships, live views, plans, and results.
|
|
71
|
+
2. **Connect the memory:** retain source and revision links so a person or agent can follow how an observation relates to evidence, a decision, or an outcome. Not every dashboard refresh or scratch note needs a permanent wiki page.
|
|
72
|
+
3. **Maintain understanding:** under the private-maintenance delegation, synthesize useful learning into existing working wiki pages. Preserve uncertainty and rejected alternatives; confirmation and wider sharing remain separate actions.
|
|
73
|
+
4. **Bring it back into work:** humans browse and revisit the graph, wiki, and boards; agents receive relevant, permission-scoped working context. New work adds evidence and can revise the understanding again.
|
|
74
|
+
|
|
75
|
+
The graph holds meaningful connections and provenance; the wiki presents maintained understanding; the board keeps active work spatial and usable. A board can contribute immediately as evidence and working memory without declaring all of its contents authoritative. This loop is the proposed direction, not a claim that automatic graph/wiki maintenance already ships.
|
|
76
|
+
|
|
66
77
|
## One model, distinct scopes
|
|
67
78
|
|
|
68
79
|
| Surface | Responsibility | What it must not imply |
|
|
69
80
|
|---|---|---|
|
|
70
|
-
| Board |
|
|
81
|
+
| Board | Human work surface and extended memory; agent working memory through spatial material, artifacts, asks, and evidence | The human only curates for agents, or everything visible is accepted knowledge |
|
|
71
82
|
| Personal wiki | Durable context owned by one person across boards | Workspace membership grants access |
|
|
72
83
|
| Workspace context | Deliberately shared goals, decisions, ownership, dependencies | A union of everyone's private context |
|
|
73
84
|
| Agent brief | Budgeted, task-specific view over authorized material | A new source of truth or permission grant |
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
## The one-line vision
|
|
9
9
|
|
|
10
|
-
**The board is the
|
|
10
|
+
**The board is the human's extended memory and the agent's working memory—a shared surface for doing work and building understanding.** The human uses it to research, analyze, monitor dashboards, explore ideas, and start, plan, and orchestrate coding or other work. People and agents bring sources, questions, relationships, plans, and results onto the board. This work contributes to a connected memory graph and maintained wiki; relevant knowledge returns to the board to support the next task. The human is a worker and thinker here, not merely a curator of agent context.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Human attention helps select the agent's context; it does not define the whole product. Explicit steering directs agents; annotations, pins, connections, and grouping inform relevance, while spatial layout and human camera attention can contribute weaker cues. The server compiles one budgeted brief for every agent turn. Everything the agent has to say to the human is a card on the board, never a row in a side table. The board owns its own store and its own history, and reaches remote agents through its own authenticated network mode. One journal records every write by either side, so time on the board can be scrubbed like a video. In the long run the board is hosted, and a team and its agents share the working surface and deliberately shared knowledge.
|
|
13
|
+
|
|
14
|
+
**Purpose clarification:** pmx-canvas supports **knowledge work broadly**, including research, analysis, dashboards, planning, discovery, coordination, and orchestrating coding or other work. These are examples, not a closed list or a coding-only boundary. Its promise is to help humans and agents do the work, carry useful understanding into the next task, and see known changes and uncertainty in the context they use. The [companion vision](product-context-vision-2026-09.md#position) develops this loop from board to memory graph and wiki and back. Contributing to memory does not automatically make every item a confirmed fact or shared organizational knowledge; attention does not grant approval or sharing permission.
|
|
13
15
|
|
|
14
16
|
## What the real boards show
|
|
15
17
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Spatial canvas workbench for coding agents
|
|
3
|
+
"version": "0.6.3",
|
|
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",
|
|
7
7
|
"module": "./src/server/index.ts",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
|
|
44
44
|
"lint": "biome check .",
|
|
45
45
|
"lint:fix": "biome check --write .",
|
|
46
|
-
"test": "if [ -d tests/unit ]; then PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit; else echo 'tests/ is not shipped in the npm package
|
|
46
|
+
"test": "if [ -d tests/unit ]; then PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit; else echo 'tests/ is not shipped in the npm package — clone the repo to run the suite'; fi",
|
|
47
47
|
"test:unit": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
48
|
-
"test:client": "if [ -d tests/client ]; then bun test --preload ./tests/client/setup.ts tests/client; else echo 'tests/ is not shipped in the npm package
|
|
48
|
+
"test:client": "if [ -d tests/client ]; then bun test --preload ./tests/client/setup.ts tests/client; else echo 'tests/ is not shipped in the npm package — clone the repo to run the suite'; fi",
|
|
49
49
|
"test:coverage": "PMX_MCP_APP_HOST_STATE_FILE=/tmp/pmx-canvas-test-app-host-state.json PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir coverage && bun scripts/check-coverage.ts",
|
|
50
50
|
"test:web-canvas": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun run build && PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bash scripts/run-playwright.sh",
|
|
51
51
|
"test:e2e": "bun run test:web-canvas",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@joplin/turndown-plugin-gfm": "^1.0.64",
|
|
62
|
-
"@json-render/core": "0.
|
|
63
|
-
"@json-render/directives": "0.
|
|
64
|
-
"@json-render/mcp": "0.
|
|
65
|
-
"@json-render/react": "0.
|
|
66
|
-
"@json-render/shadcn": "0.
|
|
62
|
+
"@json-render/core": "0.21.0",
|
|
63
|
+
"@json-render/directives": "0.21.0",
|
|
64
|
+
"@json-render/mcp": "0.21.0",
|
|
65
|
+
"@json-render/react": "0.21.0",
|
|
66
|
+
"@json-render/shadcn": "0.21.0",
|
|
67
67
|
"@modelcontextprotocol/ext-apps": "^1.3.1",
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
69
69
|
"@preact/signals": "^2.0.0",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@biomejs/biome": "^2.5.2",
|
|
81
81
|
"@happy-dom/global-registrator": "^20.10.6",
|
|
82
|
-
"@json-render/devtools": "0.
|
|
83
|
-
"@json-render/devtools-react": "0.
|
|
82
|
+
"@json-render/devtools": "0.21.0",
|
|
83
|
+
"@json-render/devtools-react": "0.21.0",
|
|
84
84
|
"@playwright/test": "^1.58.2",
|
|
85
85
|
"@tailwindcss/cli": "^4.1.18",
|
|
86
86
|
"@testing-library/preact": "^3.2.4",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: json-render-mcp
|
|
3
|
-
description:
|
|
3
|
+
description: Builds interactive json-render panels with PMX Canvas native tools or standalone MCP Apps. Use for stateful forms, repeated content, and AX actions in PMX Canvas, or MCP-hosted UIs in Claude, ChatGPT, Cursor, and VS Code.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# @json-render/mcp
|
|
@@ -12,7 +12,123 @@ In `pmx-canvas`, prefer the native `canvas_render { action: "add-json-render" }`
|
|
|
12
12
|
directly in canvas node state and render them through the local `pmx-canvas` viewer route without
|
|
13
13
|
needing a separate sidecar server.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## PMX Canvas workflow (json-render 0.21)
|
|
16
|
+
|
|
17
|
+
1. Read `canvas://schema` (or use `canvas_render { action: "describe-schema" }`) for
|
|
18
|
+
the installed component catalog. PMX includes shadcn components and its own charts;
|
|
19
|
+
do not assume every component or action from an upstream example is registered.
|
|
20
|
+
2. Build a complete `{ root, elements, state }` spec. Validate with
|
|
21
|
+
`canvas_render { action: "validate", type: "json-render", spec }`.
|
|
22
|
+
3. Create it with `canvas_render { action: "add-json-render", title, spec }`.
|
|
23
|
+
For incremental generation, use `action: "stream-json-render"`, reuse the returned
|
|
24
|
+
`nodeId` for subsequent patches, and set `done: true` on the final call.
|
|
25
|
+
4. Inspect the panel and exercise its inputs. Schema acceptance does not prove that
|
|
26
|
+
an action reached the server or that a form edit was persisted.
|
|
27
|
+
|
|
28
|
+
### State, forms, and repeated content
|
|
29
|
+
|
|
30
|
+
- Put initial values in spec-level `state`, alongside `root` and `elements`. PMX seeds
|
|
31
|
+
its viewer from this object; do not put `initialState` on a component.
|
|
32
|
+
- Use JSON Pointer paths: `value: { "$bindState": "/form/title" }` for `Input`,
|
|
33
|
+
`Textarea`, or `Select`; `checked: { "$bindState": "/form/urgent" }` for `Checkbox`
|
|
34
|
+
or `Switch`. Supply the catalog's required `label` and `name` props. `$state` reads
|
|
35
|
+
a value only; `$bindState` also writes edits. `props.statePath` is not input binding.
|
|
36
|
+
- `repeat`, `watch`, `visible`, `on`, and `slots` are element fields, not props.
|
|
37
|
+
`repeat: { "statePath": "/projects", "key": "id" }` repeats the container's children.
|
|
38
|
+
Within those children, a nested container can use
|
|
39
|
+
`repeat: { "statePath": { "$item": "tasks" }, "key": "id" }` to iterate the current
|
|
40
|
+
project's tasks. Do not use `"/tasks"` (a root path) for that nested array.
|
|
41
|
+
`$item` and `$index` refer to the innermost repeat; use
|
|
42
|
+
`checked: { "$bindItem": "done" }` to edit that task's `done` field.
|
|
43
|
+
- Card body content uses `children: ["bodyId"]`. Named regions use
|
|
44
|
+
`slots: { "header": ["headingId"], "footer": ["actionsId"] }`. Every referenced ID
|
|
45
|
+
must exist in `elements`; do not put body content in `slots.default`. A header slot
|
|
46
|
+
replaces the Card's title and description props; without it, those props work as before.
|
|
47
|
+
- Watchers skip mount: `watch: { "/form/title": { "action": "setState", "params":
|
|
48
|
+
{ "statePath": "/dirty", "value": true } } }` runs after the value changes, not to
|
|
49
|
+
initialize `/dirty`. Seed initial and derived defaults explicitly. Avoid watchers
|
|
50
|
+
that write back to their own watched paths and create loops.
|
|
51
|
+
|
|
52
|
+
### Actions and persistence boundaries
|
|
53
|
+
|
|
54
|
+
- Bind buttons with `on.press`, for example an action named `ax.work.create` or
|
|
55
|
+
`ax.steer`, with its AX payload in `params`. PMX forwards only registered handlers;
|
|
56
|
+
invented action names cannot execute arbitrary tools, shell commands, or requests.
|
|
57
|
+
The server still validates the payload and the node's AX capabilities.
|
|
58
|
+
- AX handlers are **fire-and-forget** postMessages. Their `onSuccess` callbacks mean
|
|
59
|
+
local dispatch completed, **not server acknowledgment**. Do not clear a draft or
|
|
60
|
+
show “Saved” on that basis. Confirm via reflected AX state under reserved `/ax`
|
|
61
|
+
(where applicable), or query the server's AX state/timeline. Do not use `/ax` for drafts.
|
|
62
|
+
- Built-in `validateForm` writes `{ valid, errors }` to `/formValidation` (or
|
|
63
|
+
`params.statePath`) and returns without running `onSuccess`. Do not chain submission
|
|
64
|
+
through that callback, or assume a following action in an array is validation-gated.
|
|
65
|
+
Use an explicit validation step and state-based gating; server-side validation is
|
|
66
|
+
still required for any submitted data.
|
|
67
|
+
- Form edits, `setState`, and `$bindItem` mutate **viewer-local state**, not the stored
|
|
68
|
+
spec. Iframe reloads, including spec/stream updates, reset it from the latest spec's
|
|
69
|
+
initial state. Finish streaming before collecting input; persist important changes
|
|
70
|
+
through an explicit supported server interaction. Stored canvas nodes survive
|
|
71
|
+
reloads, but unsent local drafts do not.
|
|
72
|
+
|
|
73
|
+
### Example: editable task draft
|
|
74
|
+
|
|
75
|
+
Pass this object to `canvas_render`. Editing either field sets a local dirty flag;
|
|
76
|
+
the button requests a real AX work item without pretending the request was saved.
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"action": "add-json-render",
|
|
81
|
+
"title": "Task draft",
|
|
82
|
+
"spec": {
|
|
83
|
+
"root": "card",
|
|
84
|
+
"state": { "form": { "title": "Review release notes", "detail": "Check the upgrade caveats." }, "dirty": false },
|
|
85
|
+
"elements": {
|
|
86
|
+
"card": {
|
|
87
|
+
"type": "Card",
|
|
88
|
+
"props": { "title": "Task draft" },
|
|
89
|
+
"children": ["fields"],
|
|
90
|
+
"slots": { "footer": ["request"] }
|
|
91
|
+
},
|
|
92
|
+
"fields": {
|
|
93
|
+
"type": "Stack",
|
|
94
|
+
"props": { "direction": "vertical", "gap": "md" },
|
|
95
|
+
"children": ["title", "detail"],
|
|
96
|
+
"watch": {
|
|
97
|
+
"/form/title": { "action": "setState", "params": { "statePath": "/dirty", "value": true } },
|
|
98
|
+
"/form/detail": { "action": "setState", "params": { "statePath": "/dirty", "value": true } }
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"title": {
|
|
102
|
+
"type": "Input",
|
|
103
|
+
"props": { "label": "Title", "name": "title", "value": { "$bindState": "/form/title" } }
|
|
104
|
+
},
|
|
105
|
+
"detail": {
|
|
106
|
+
"type": "Textarea",
|
|
107
|
+
"props": { "label": "Details", "name": "detail", "value": { "$bindState": "/form/detail" } }
|
|
108
|
+
},
|
|
109
|
+
"request": {
|
|
110
|
+
"type": "Button",
|
|
111
|
+
"props": { "label": "Request task", "variant": "primary" },
|
|
112
|
+
"on": {
|
|
113
|
+
"press": {
|
|
114
|
+
"action": "ax.work.create",
|
|
115
|
+
"params": { "title": { "$state": "/form/title" }, "detail": { "$state": "/form/detail" } }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The draft remains visible after dispatch. Verify that the work item appears in AX
|
|
125
|
+
state before reporting success. Repeated clicks can create duplicate work items.
|
|
126
|
+
|
|
127
|
+
## Standalone MCP App quick start
|
|
128
|
+
|
|
129
|
+
Use this path only when building a separate MCP Apps server rather than a native
|
|
130
|
+
PMX panel. PMX's `canvas_render` path needs none of the server, bundler, or client
|
|
131
|
+
configuration below.
|
|
16
132
|
|
|
17
133
|
### Server (Node.js)
|
|
18
134
|
|