pmx-canvas 0.1.13 → 0.1.15
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 +163 -0
- package/Readme.md +108 -1058
- package/dist/canvas/global.css +141 -0
- package/dist/canvas/index.js +137 -87
- package/dist/json-render/index.css +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -3
- package/dist/types/client/nodes/HtmlNode.d.ts +5 -0
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +5 -1
- package/dist/types/client/state/intent-bridge.d.ts +3 -1
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/json-render/catalog.d.ts +1 -1
- package/dist/types/mcp/canvas-access.d.ts +7 -1
- package/dist/types/server/agent-context.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +12 -2
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-serialization.d.ts +3 -0
- package/dist/types/server/canvas-state.d.ts +51 -4
- package/dist/types/server/demo.d.ts +5 -0
- package/dist/types/server/diagram-presets.d.ts +4 -0
- package/dist/types/server/index.d.ts +21 -3
- package/dist/types/server/mcp-app-runtime.d.ts +1 -0
- package/dist/types/server/web-artifacts.d.ts +18 -0
- package/dist/types/shared/canvas-node-kind.d.ts +5 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +43 -0
- package/skills/pmx-canvas-testing/SKILL.md +17 -0
- package/src/cli/agent.ts +66 -5
- package/src/cli/index.ts +2 -23
- package/src/client/canvas/AttentionHistory.tsx +14 -1
- package/src/client/canvas/CanvasNode.tsx +1 -1
- package/src/client/canvas/CanvasViewport.tsx +3 -0
- package/src/client/canvas/DockedNode.tsx +110 -12
- package/src/client/canvas/ExpandedNodeOverlay.tsx +8 -3
- package/src/client/canvas/Minimap.tsx +1 -0
- package/src/client/icons.tsx +1 -0
- package/src/client/nodes/ExtAppFrame.tsx +10 -35
- package/src/client/nodes/HtmlNode.tsx +151 -0
- package/src/client/nodes/McpAppNode.tsx +2 -2
- package/src/client/state/canvas-store.ts +24 -2
- package/src/client/state/intent-bridge.ts +4 -3
- package/src/client/state/sse-bridge.ts +2 -0
- package/src/client/theme/global.css +141 -0
- package/src/client/types.ts +3 -0
- package/src/mcp/canvas-access.ts +34 -7
- package/src/mcp/server.ts +199 -26
- package/src/server/agent-context.ts +50 -3
- package/src/server/canvas-operations.ts +55 -3
- package/src/server/canvas-provenance.ts +2 -1
- package/src/server/canvas-serialization.ts +38 -13
- package/src/server/canvas-state.ts +305 -34
- package/src/server/demo.ts +792 -0
- package/src/server/diagram-presets.ts +45 -25
- package/src/server/index.ts +64 -7
- package/src/server/mcp-app-runtime.ts +15 -5
- package/src/server/server.ts +169 -63
- package/src/server/web-artifacts.ts +116 -3
- package/src/shared/canvas-node-kind.ts +14 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import { canvasState, type CanvasEdge, type CanvasNodeState } from './canvas-state.js';
|
|
2
|
+
import { EXCALIDRAW_CREATE_VIEW_TOOL, EXCALIDRAW_SERVER_NAME, buildExcalidrawCheckpointId, normalizeExcalidrawElementsForToolInput } from './diagram-presets.js';
|
|
3
|
+
import { createJsonRenderNodeData, buildGraphConfig, buildGraphSpec, type GraphNodeInput, type JsonRenderSpec } from '../json-render/server.js';
|
|
4
|
+
|
|
5
|
+
type DemoNodeType = CanvasNodeState['type'];
|
|
6
|
+
|
|
7
|
+
interface DemoNodeInput {
|
|
8
|
+
id: string;
|
|
9
|
+
type: DemoNodeType;
|
|
10
|
+
title: string;
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
data?: Record<string, unknown>;
|
|
16
|
+
content?: string;
|
|
17
|
+
zIndex?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const DEMO_NODE_TYPES = [
|
|
21
|
+
'markdown',
|
|
22
|
+
'status',
|
|
23
|
+
'context',
|
|
24
|
+
'ledger',
|
|
25
|
+
'trace',
|
|
26
|
+
'file',
|
|
27
|
+
'image',
|
|
28
|
+
'webpage',
|
|
29
|
+
'json-render',
|
|
30
|
+
'graph',
|
|
31
|
+
'mcp-app',
|
|
32
|
+
'group',
|
|
33
|
+
] satisfies DemoNodeType[];
|
|
34
|
+
|
|
35
|
+
const groupDefinitions = [
|
|
36
|
+
{
|
|
37
|
+
id: 'demo-group-overview',
|
|
38
|
+
title: '1. Project Overview',
|
|
39
|
+
x: 20,
|
|
40
|
+
y: 20,
|
|
41
|
+
width: 1260,
|
|
42
|
+
height: 1450,
|
|
43
|
+
color: '#46b6ff',
|
|
44
|
+
children: ['demo-md-welcome', 'demo-status-health', 'demo-image-architecture', 'demo-excalidraw-architecture'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'demo-group-control-surfaces',
|
|
48
|
+
title: '2. Control Surfaces',
|
|
49
|
+
x: 1320,
|
|
50
|
+
y: 20,
|
|
51
|
+
width: 1610,
|
|
52
|
+
height: 1270,
|
|
53
|
+
color: '#a855f7',
|
|
54
|
+
children: ['demo-md-surfaces', 'demo-json-dashboard', 'demo-graph-capabilities'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'demo-group-persistence',
|
|
58
|
+
title: '3. Persistence + Data',
|
|
59
|
+
x: 20,
|
|
60
|
+
y: 1480,
|
|
61
|
+
width: 1260,
|
|
62
|
+
height: 1190,
|
|
63
|
+
color: '#22c55e',
|
|
64
|
+
children: ['demo-ledger-persistence', 'demo-file-server', 'demo-webpage-docs', 'demo-graph-mix'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'demo-group-agent-context',
|
|
68
|
+
title: '4. Agent Context Loop',
|
|
69
|
+
x: 1320,
|
|
70
|
+
y: 1300,
|
|
71
|
+
width: 1610,
|
|
72
|
+
height: 820,
|
|
73
|
+
color: '#f59e0b',
|
|
74
|
+
children: ['demo-context-pins', 'demo-mcp-app', 'demo-trace-api', 'demo-trace-build'],
|
|
75
|
+
},
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
function makeNode(input: DemoNodeInput): CanvasNodeState {
|
|
79
|
+
return {
|
|
80
|
+
id: input.id,
|
|
81
|
+
type: input.type,
|
|
82
|
+
position: { x: input.x, y: input.y },
|
|
83
|
+
size: { width: input.width, height: input.height },
|
|
84
|
+
zIndex: input.zIndex ?? (input.type === 'group' ? 0 : 1),
|
|
85
|
+
collapsed: false,
|
|
86
|
+
pinned: false,
|
|
87
|
+
dockPosition: null,
|
|
88
|
+
data: {
|
|
89
|
+
...(input.data ?? {}),
|
|
90
|
+
title: input.title,
|
|
91
|
+
...(input.content !== undefined ? { content: input.content } : {}),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function makeEdge(
|
|
97
|
+
id: string,
|
|
98
|
+
from: string,
|
|
99
|
+
to: string,
|
|
100
|
+
type: CanvasEdge['type'],
|
|
101
|
+
label: string,
|
|
102
|
+
options: Pick<CanvasEdge, 'style' | 'animated'> = {},
|
|
103
|
+
): CanvasEdge {
|
|
104
|
+
return { id, from, to, type, label, ...options };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildProjectTourSpec(): JsonRenderSpec {
|
|
108
|
+
return {
|
|
109
|
+
root: 'card',
|
|
110
|
+
elements: {
|
|
111
|
+
card: {
|
|
112
|
+
type: 'Card',
|
|
113
|
+
props: {
|
|
114
|
+
title: 'PMX Canvas Capability Map',
|
|
115
|
+
description: 'Native json-render components inside a canvas node.',
|
|
116
|
+
maxWidth: 'full',
|
|
117
|
+
centered: false,
|
|
118
|
+
},
|
|
119
|
+
children: ['stack'],
|
|
120
|
+
},
|
|
121
|
+
stack: {
|
|
122
|
+
type: 'Stack',
|
|
123
|
+
props: { direction: 'vertical', gap: 'md', align: 'stretch' },
|
|
124
|
+
children: ['lede', 'badges', 'progress', 'table'],
|
|
125
|
+
},
|
|
126
|
+
lede: {
|
|
127
|
+
type: 'Text',
|
|
128
|
+
props: {
|
|
129
|
+
text: 'Use the canvas as shared spatial memory: agents mutate state, humans curate context, and the browser renders a persistent workspace.',
|
|
130
|
+
variant: 'lead',
|
|
131
|
+
},
|
|
132
|
+
children: [],
|
|
133
|
+
},
|
|
134
|
+
badges: {
|
|
135
|
+
type: 'Stack',
|
|
136
|
+
props: { direction: 'horizontal', gap: 'sm', align: 'center', justify: 'start' },
|
|
137
|
+
children: ['b1', 'b2', 'b3', 'b4'],
|
|
138
|
+
},
|
|
139
|
+
b1: { type: 'Badge', props: { text: 'HTTP API', variant: 'default' }, children: [] },
|
|
140
|
+
b2: { type: 'Badge', props: { text: 'MCP tools', variant: 'secondary' }, children: [] },
|
|
141
|
+
b3: { type: 'Badge', props: { text: 'Bun SDK', variant: 'outline' }, children: [] },
|
|
142
|
+
b4: { type: 'Badge', props: { text: 'SSE sync', variant: 'outline' }, children: [] },
|
|
143
|
+
progress: {
|
|
144
|
+
type: 'Progress',
|
|
145
|
+
props: { value: 100, max: 100, label: 'All core surfaces share the same server-side state' },
|
|
146
|
+
children: [],
|
|
147
|
+
},
|
|
148
|
+
table: {
|
|
149
|
+
type: 'Table',
|
|
150
|
+
props: {
|
|
151
|
+
columns: ['Layer', 'What it demonstrates', 'Why it matters'],
|
|
152
|
+
rows: [
|
|
153
|
+
['CanvasStateManager', 'authoritative layout + persistence', 'refresh-safe collaboration'],
|
|
154
|
+
['Browser renderer', 'nodes, edges, groups, minimap', 'humans see agent work live'],
|
|
155
|
+
['MCP/HTTP/SDK', 'same operations through different clients', 'agents pick the right integration'],
|
|
156
|
+
['Pinned context', 'human-curated agent grounding', 'less prompt stuffing'],
|
|
157
|
+
],
|
|
158
|
+
caption: 'This dashboard is schema-driven; no custom web artifact build is needed.',
|
|
159
|
+
},
|
|
160
|
+
children: [],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildGraphNode(input: Omit<GraphNodeInput, 'heightPx'> & { id: string; heightPx?: number }): CanvasNodeState {
|
|
167
|
+
const spec = buildGraphSpec(input);
|
|
168
|
+
const title = input.title ?? 'Graph';
|
|
169
|
+
const chartHeight = input.height ?? 320;
|
|
170
|
+
return makeNode({
|
|
171
|
+
id: input.id,
|
|
172
|
+
type: 'graph',
|
|
173
|
+
title,
|
|
174
|
+
x: input.x ?? 0,
|
|
175
|
+
y: input.y ?? 0,
|
|
176
|
+
width: input.width ?? 760,
|
|
177
|
+
height: input.heightPx ?? chartHeight + 300,
|
|
178
|
+
data: createJsonRenderNodeData(input.id, title, spec, {
|
|
179
|
+
viewerType: 'graph',
|
|
180
|
+
graphConfig: buildGraphConfig(input),
|
|
181
|
+
strictSize: true,
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildExcalidrawDemoElements(): Array<Record<string, unknown>> {
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
type: 'rectangle',
|
|
190
|
+
id: 'agent',
|
|
191
|
+
x: 80,
|
|
192
|
+
y: 110,
|
|
193
|
+
width: 190,
|
|
194
|
+
height: 92,
|
|
195
|
+
roundness: { type: 3 },
|
|
196
|
+
backgroundColor: '#a5d8ff',
|
|
197
|
+
fillStyle: 'solid',
|
|
198
|
+
label: { text: 'Agent\nMCP / HTTP / SDK', fontSize: 19 },
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: 'rectangle',
|
|
202
|
+
id: 'server',
|
|
203
|
+
x: 350,
|
|
204
|
+
y: 92,
|
|
205
|
+
width: 220,
|
|
206
|
+
height: 128,
|
|
207
|
+
roundness: { type: 3 },
|
|
208
|
+
backgroundColor: '#b2f2bb',
|
|
209
|
+
fillStyle: 'solid',
|
|
210
|
+
label: { text: 'CanvasStateManager\nauthoritative state', fontSize: 19 },
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
type: 'rectangle',
|
|
214
|
+
id: 'browser',
|
|
215
|
+
x: 660,
|
|
216
|
+
y: 110,
|
|
217
|
+
width: 190,
|
|
218
|
+
height: 92,
|
|
219
|
+
roundness: { type: 3 },
|
|
220
|
+
backgroundColor: '#d0bfff',
|
|
221
|
+
fillStyle: 'solid',
|
|
222
|
+
label: { text: 'Browser\nlive renderer', fontSize: 19 },
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: 'diamond',
|
|
226
|
+
id: 'pins',
|
|
227
|
+
x: 382,
|
|
228
|
+
y: 300,
|
|
229
|
+
width: 156,
|
|
230
|
+
height: 116,
|
|
231
|
+
backgroundColor: '#ffec99',
|
|
232
|
+
fillStyle: 'solid',
|
|
233
|
+
label: { text: 'Pins\nhuman intent', fontSize: 18 },
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: 'rectangle',
|
|
237
|
+
id: 'disk',
|
|
238
|
+
x: 650,
|
|
239
|
+
y: 306,
|
|
240
|
+
width: 200,
|
|
241
|
+
height: 98,
|
|
242
|
+
roundness: { type: 3 },
|
|
243
|
+
backgroundColor: '#ffd8a8',
|
|
244
|
+
fillStyle: 'solid',
|
|
245
|
+
label: { text: '.pmx-canvas\nstate + blobs', fontSize: 18 },
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: 'arrow',
|
|
249
|
+
id: 'agent-server',
|
|
250
|
+
x: 272,
|
|
251
|
+
y: 155,
|
|
252
|
+
points: [[0, 0], [74, 0]],
|
|
253
|
+
strokeColor: '#1971c2',
|
|
254
|
+
endArrowhead: 'arrow',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
type: 'arrow',
|
|
258
|
+
id: 'server-browser',
|
|
259
|
+
x: 572,
|
|
260
|
+
y: 155,
|
|
261
|
+
points: [[0, 0], [84, 0]],
|
|
262
|
+
strokeColor: '#7048e8',
|
|
263
|
+
endArrowhead: 'arrow',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
type: 'arrow',
|
|
267
|
+
id: 'browser-pins',
|
|
268
|
+
x: 718,
|
|
269
|
+
y: 205,
|
|
270
|
+
points: [[0, 0], [-166, 112]],
|
|
271
|
+
strokeColor: '#e67700',
|
|
272
|
+
endArrowhead: 'arrow',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: 'arrow',
|
|
276
|
+
id: 'pins-agent',
|
|
277
|
+
x: 383,
|
|
278
|
+
y: 342,
|
|
279
|
+
points: [[0, 0], [-196, -132]],
|
|
280
|
+
strokeColor: '#087f5b',
|
|
281
|
+
endArrowhead: 'arrow',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'arrow',
|
|
285
|
+
id: 'server-disk',
|
|
286
|
+
x: 562,
|
|
287
|
+
y: 218,
|
|
288
|
+
points: [[0, 0], [118, 84]],
|
|
289
|
+
strokeColor: '#5c940d',
|
|
290
|
+
endArrowhead: 'arrow',
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: 'text',
|
|
294
|
+
id: 'caption',
|
|
295
|
+
x: 86,
|
|
296
|
+
y: 470,
|
|
297
|
+
width: 760,
|
|
298
|
+
height: 34,
|
|
299
|
+
text: 'Every client mutates the same server state; pins turn human spatial curation into agent-readable context.',
|
|
300
|
+
fontSize: 22,
|
|
301
|
+
strokeColor: '#ced4da',
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildExcalidrawDemoHtml(): string {
|
|
307
|
+
return `<!doctype html>
|
|
308
|
+
<html lang="en">
|
|
309
|
+
<head>
|
|
310
|
+
<meta charset="utf-8" />
|
|
311
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
312
|
+
<style>
|
|
313
|
+
:root { color-scheme: dark; }
|
|
314
|
+
* { box-sizing: border-box; }
|
|
315
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #0a0f1e; color: #e2e8f0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
|
316
|
+
main { width: min(100%, 1080px); padding: 24px; }
|
|
317
|
+
.canvas { position: relative; height: 540px; border: 1px solid rgba(148, 163, 184, 0.26); border-radius: 22px; background: radial-gradient(circle at 24px 24px, rgba(148, 163, 184, 0.14) 2px, transparent 0) 0 0 / 32px 32px, linear-gradient(145deg, rgba(15, 23, 42, 0.96), rgba(30, 41, 59, 0.88)); overflow: hidden; }
|
|
318
|
+
.node { position: absolute; display: grid; place-items: center; text-align: center; border: 2px solid rgba(15, 23, 42, 0.75); border-radius: 22px; padding: 12px; color: #07111f; font-weight: 800; line-height: 1.22; box-shadow: 0 18px 40px rgba(0, 0, 0, 0.26); }
|
|
319
|
+
.agent { left: 76px; top: 118px; width: 184px; height: 90px; background: #a5d8ff; }
|
|
320
|
+
.server { left: 420px; top: 92px; width: 240px; height: 132px; background: #b2f2bb; }
|
|
321
|
+
.browser { right: 76px; top: 118px; width: 184px; height: 90px; background: #d0bfff; }
|
|
322
|
+
.pins { left: 446px; top: 342px; width: 150px; height: 102px; border-radius: 30px; rotate: 45deg; background: #ffec99; }
|
|
323
|
+
.pins span { rotate: -45deg; }
|
|
324
|
+
.disk { right: 82px; top: 350px; width: 190px; height: 90px; background: #ffd8a8; }
|
|
325
|
+
svg { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
326
|
+
path { fill: none; stroke-width: 3; stroke-linecap: round; stroke-dasharray: 8 8; }
|
|
327
|
+
header { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 14px; align-items: end; }
|
|
328
|
+
h1 { margin: 0; font-size: 22px; letter-spacing: -0.03em; }
|
|
329
|
+
p { margin: 0; max-width: 680px; color: #94a3b8; font-size: 13px; line-height: 1.5; }
|
|
330
|
+
.badge { color: #fbbf24; border: 1px solid rgba(251, 191, 36, 0.36); border-radius: 999px; padding: 6px 10px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.14em; }
|
|
331
|
+
</style>
|
|
332
|
+
</head>
|
|
333
|
+
<body>
|
|
334
|
+
<main>
|
|
335
|
+
<header>
|
|
336
|
+
<div><h1>How PMX Canvas Works</h1><p>Static Excalidraw-style preview for the fast demo. Open with canvas_add_diagram for an editable hosted Excalidraw board.</p></div>
|
|
337
|
+
<div class="badge">Excalidraw</div>
|
|
338
|
+
</header>
|
|
339
|
+
<div class="canvas" aria-label="PMX Canvas architecture diagram">
|
|
340
|
+
<svg viewBox="0 0 1080 540" aria-hidden="true">
|
|
341
|
+
<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#7dd3fc" stroke="none"/></marker></defs>
|
|
342
|
+
<path d="M260 160 C330 118 372 118 420 152" stroke="#7dd3fc" marker-end="url(#arrow)"/>
|
|
343
|
+
<path d="M660 152 C724 118 798 118 828 160" stroke="#c4b5fd" marker-end="url(#arrow)"/>
|
|
344
|
+
<path d="M828 208 C744 290 672 336 596 394" stroke="#fbbf24" marker-end="url(#arrow)"/>
|
|
345
|
+
<path d="M446 394 C330 330 238 260 168 208" stroke="#86efac" marker-end="url(#arrow)"/>
|
|
346
|
+
<path d="M660 210 C754 282 816 318 808 350" stroke="#fdba74" marker-end="url(#arrow)"/>
|
|
347
|
+
</svg>
|
|
348
|
+
<div class="node agent">Agent<br/>MCP / HTTP / SDK</div>
|
|
349
|
+
<div class="node server">CanvasStateManager<br/>authoritative state</div>
|
|
350
|
+
<div class="node browser">Browser<br/>live renderer</div>
|
|
351
|
+
<div class="node pins"><span>Pins<br/>human intent</span></div>
|
|
352
|
+
<div class="node disk">.pmx-canvas<br/>state + blobs</div>
|
|
353
|
+
</div>
|
|
354
|
+
</main>
|
|
355
|
+
</body>
|
|
356
|
+
</html>`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function buildDemoMcpAppHtml(): string {
|
|
360
|
+
return `<!doctype html>
|
|
361
|
+
<html lang="en">
|
|
362
|
+
<head>
|
|
363
|
+
<meta charset="utf-8" />
|
|
364
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
365
|
+
<style>
|
|
366
|
+
:root { color-scheme: dark; }
|
|
367
|
+
* { box-sizing: border-box; }
|
|
368
|
+
body {
|
|
369
|
+
margin: 0;
|
|
370
|
+
min-height: 100vh;
|
|
371
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
372
|
+
color: #e5f7ff;
|
|
373
|
+
background:
|
|
374
|
+
radial-gradient(circle at 18% 16%, rgba(70, 182, 255, 0.32), transparent 36%),
|
|
375
|
+
radial-gradient(circle at 88% 12%, rgba(168, 85, 247, 0.22), transparent 30%),
|
|
376
|
+
linear-gradient(145deg, #08111f 0%, #0e1629 52%, #111827 100%);
|
|
377
|
+
}
|
|
378
|
+
main { padding: 18px; display: grid; gap: 14px; }
|
|
379
|
+
.eyebrow { color: #7dd3fc; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; }
|
|
380
|
+
h1 { margin: 0; font-size: 24px; line-height: 1.05; letter-spacing: -0.04em; }
|
|
381
|
+
p { margin: 0; color: rgba(229, 247, 255, 0.72); line-height: 1.45; font-size: 13px; }
|
|
382
|
+
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
|
383
|
+
.metric { border: 1px solid rgba(125, 211, 252, 0.22); border-radius: 14px; padding: 10px; background: rgba(8, 17, 31, 0.58); }
|
|
384
|
+
.metric strong { display: block; color: #ffffff; font-size: 21px; }
|
|
385
|
+
.metric span { color: rgba(229, 247, 255, 0.58); font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
386
|
+
.rail { height: 8px; border-radius: 999px; overflow: hidden; background: rgba(255,255,255,0.08); }
|
|
387
|
+
.fill { height: 100%; width: 86%; background: linear-gradient(90deg, #22c55e, #46b6ff); }
|
|
388
|
+
</style>
|
|
389
|
+
</head>
|
|
390
|
+
<body>
|
|
391
|
+
<main>
|
|
392
|
+
<div class="eyebrow">custom mcp-app node</div>
|
|
393
|
+
<h1>Embedded agents can bring their own UI.</h1>
|
|
394
|
+
<p>This lightweight iframe stands in for an MCP App: fast to load, self-contained, and paired with native graph/context nodes so agents still have semantic context.</p>
|
|
395
|
+
<div class="grid">
|
|
396
|
+
<div class="metric"><strong>39</strong><span>MCP tools</span></div>
|
|
397
|
+
<div class="metric"><strong>${DEMO_NODE_TYPES.length}</strong><span>demo node types</span></div>
|
|
398
|
+
<div class="metric"><strong>7</strong><span>resources</span></div>
|
|
399
|
+
</div>
|
|
400
|
+
<div class="rail" aria-label="demo readiness"><div class="fill"></div></div>
|
|
401
|
+
</main>
|
|
402
|
+
</body>
|
|
403
|
+
</html>`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildArchitectureSvgDataUri(): string {
|
|
407
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 360">
|
|
408
|
+
<defs>
|
|
409
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#07111f"/><stop offset="1" stop-color="#16213a"/></linearGradient>
|
|
410
|
+
<linearGradient id="node" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#12324f"/><stop offset="1" stop-color="#0f766e"/></linearGradient>
|
|
411
|
+
</defs>
|
|
412
|
+
<rect width="720" height="360" rx="28" fill="url(#bg)"/>
|
|
413
|
+
<g fill="none" stroke="#46b6ff" stroke-width="4" opacity="0.7">
|
|
414
|
+
<path d="M170 110h140M410 110h140M360 145v78M220 250h280"/>
|
|
415
|
+
</g>
|
|
416
|
+
<g font-family="Menlo, monospace" text-anchor="middle">
|
|
417
|
+
<g transform="translate(80 70)"><rect width="140" height="82" rx="16" fill="url(#node)"/><text x="70" y="37" fill="#e5f7ff" font-size="17">MCP</text><text x="70" y="58" fill="#9bdcff" font-size="11">tools + resources</text></g>
|
|
418
|
+
<g transform="translate(290 70)"><rect width="140" height="82" rx="16" fill="#1d4ed8"/><text x="70" y="37" fill="#fff" font-size="17">Server</text><text x="70" y="58" fill="#bfdbfe" font-size="11">single source</text></g>
|
|
419
|
+
<g transform="translate(500 70)"><rect width="140" height="82" rx="16" fill="#7c3aed"/><text x="70" y="37" fill="#fff" font-size="17">Browser</text><text x="70" y="58" fill="#ddd6fe" font-size="11">live renderer</text></g>
|
|
420
|
+
<g transform="translate(180 220)"><rect width="160" height="82" rx="16" fill="#15803d"/><text x="80" y="37" fill="#fff" font-size="17">Persistence</text><text x="80" y="58" fill="#bbf7d0" font-size="11">state + snapshots</text></g>
|
|
421
|
+
<g transform="translate(390 220)"><rect width="160" height="82" rx="16" fill="#b45309"/><text x="80" y="37" fill="#fff" font-size="17">Pins</text><text x="80" y="58" fill="#fde68a" font-size="11">human intent</text></g>
|
|
422
|
+
</g>
|
|
423
|
+
</svg>`;
|
|
424
|
+
return `data:image/svg+xml;base64,${Buffer.from(svg, 'utf-8').toString('base64')}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function createDemoNodes(): CanvasNodeState[] {
|
|
428
|
+
const projectSpec = buildProjectTourSpec();
|
|
429
|
+
const nodes: CanvasNodeState[] = [
|
|
430
|
+
...groupDefinitions.map((group) => makeNode({
|
|
431
|
+
id: group.id,
|
|
432
|
+
type: 'group',
|
|
433
|
+
title: group.title,
|
|
434
|
+
x: group.x,
|
|
435
|
+
y: group.y,
|
|
436
|
+
width: group.width,
|
|
437
|
+
height: group.height,
|
|
438
|
+
zIndex: 0,
|
|
439
|
+
data: { children: group.children, frameMode: 'manual', color: group.color },
|
|
440
|
+
})),
|
|
441
|
+
makeNode({
|
|
442
|
+
id: 'demo-md-welcome',
|
|
443
|
+
type: 'markdown',
|
|
444
|
+
title: 'Start Here: PMX Canvas',
|
|
445
|
+
x: 70,
|
|
446
|
+
y: 90,
|
|
447
|
+
width: 560,
|
|
448
|
+
height: 420,
|
|
449
|
+
content: [
|
|
450
|
+
'# PMX Canvas',
|
|
451
|
+
'',
|
|
452
|
+
'A spatial workbench where humans and coding agents share the same visible state.',
|
|
453
|
+
'',
|
|
454
|
+
'Read this board left-to-right, then down:',
|
|
455
|
+
'- native nodes explain the project without a build step',
|
|
456
|
+
'- labeled edges show how server state, browser UI, MCP, and persistence connect',
|
|
457
|
+
'- json-render, graph, file, webpage, image, trace, and MCP-app nodes show the real surfaces',
|
|
458
|
+
'- translucent groups label the four regions without covering the content',
|
|
459
|
+
'',
|
|
460
|
+
'> Pin nodes to turn human spatial curation into agent-readable context.',
|
|
461
|
+
].join('\n'),
|
|
462
|
+
}),
|
|
463
|
+
makeNode({
|
|
464
|
+
id: 'demo-status-health',
|
|
465
|
+
type: 'status',
|
|
466
|
+
title: 'Workbench Health',
|
|
467
|
+
x: 670,
|
|
468
|
+
y: 90,
|
|
469
|
+
width: 560,
|
|
470
|
+
height: 190,
|
|
471
|
+
data: {
|
|
472
|
+
phase: 'review',
|
|
473
|
+
detail: 'demo seed loaded',
|
|
474
|
+
message: 'One server-side canvas state powers HTTP, MCP, SDK, browser rendering, pins, and persistence.',
|
|
475
|
+
level: 'ok',
|
|
476
|
+
activeTool: 'pmx-canvas --demo',
|
|
477
|
+
},
|
|
478
|
+
}),
|
|
479
|
+
makeNode({
|
|
480
|
+
id: 'demo-image-architecture',
|
|
481
|
+
type: 'image',
|
|
482
|
+
title: 'Architecture Sketch',
|
|
483
|
+
x: 670,
|
|
484
|
+
y: 330,
|
|
485
|
+
width: 560,
|
|
486
|
+
height: 300,
|
|
487
|
+
data: {
|
|
488
|
+
src: buildArchitectureSvgDataUri(),
|
|
489
|
+
alt: 'PMX Canvas architecture sketch',
|
|
490
|
+
caption: 'MCP, HTTP, SDK, browser, and persistence all meet at server-side canvas state.',
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
makeNode({
|
|
494
|
+
id: 'demo-excalidraw-architecture',
|
|
495
|
+
type: 'mcp-app',
|
|
496
|
+
title: 'Excalidraw: How It Works',
|
|
497
|
+
x: 70,
|
|
498
|
+
y: 650,
|
|
499
|
+
width: 1160,
|
|
500
|
+
height: 780,
|
|
501
|
+
data: {
|
|
502
|
+
mode: 'ext-app',
|
|
503
|
+
html: buildExcalidrawDemoHtml(),
|
|
504
|
+
serverName: EXCALIDRAW_SERVER_NAME,
|
|
505
|
+
toolName: EXCALIDRAW_CREATE_VIEW_TOOL,
|
|
506
|
+
toolCallId: 'demo-excalidraw-architecture',
|
|
507
|
+
resourceUri: 'ui://excalidraw/pmx-canvas-demo.html',
|
|
508
|
+
sessionStatus: 'ready',
|
|
509
|
+
sessionError: null,
|
|
510
|
+
toolInput: { elements: normalizeExcalidrawElementsForToolInput(buildExcalidrawDemoElements()) },
|
|
511
|
+
toolResult: {
|
|
512
|
+
content: [{ type: 'text', text: 'PMX Canvas architecture diagram loaded.' }],
|
|
513
|
+
structuredContent: { checkpointId: buildExcalidrawCheckpointId('demo-excalidraw-architecture') },
|
|
514
|
+
},
|
|
515
|
+
toolDefinition: {
|
|
516
|
+
name: EXCALIDRAW_CREATE_VIEW_TOOL,
|
|
517
|
+
title: 'Create Excalidraw View',
|
|
518
|
+
description: 'Static fast-demo payload shaped like the hosted Excalidraw MCP app result.',
|
|
519
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
520
|
+
},
|
|
521
|
+
resourceMeta: {
|
|
522
|
+
name: 'Excalidraw PMX Canvas Demo',
|
|
523
|
+
description: 'Architecture diagram showing PMX Canvas data flow.',
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
}),
|
|
527
|
+
makeNode({
|
|
528
|
+
id: 'demo-md-surfaces',
|
|
529
|
+
type: 'markdown',
|
|
530
|
+
title: 'How To Drive It',
|
|
531
|
+
x: 1370,
|
|
532
|
+
y: 90,
|
|
533
|
+
width: 540,
|
|
534
|
+
height: 360,
|
|
535
|
+
content: [
|
|
536
|
+
'## Control Surfaces',
|
|
537
|
+
'',
|
|
538
|
+
'Every client mutates the same server-side canvas state:',
|
|
539
|
+
'',
|
|
540
|
+
'```bash',
|
|
541
|
+
'pmx-canvas node add --type markdown --title "Note"',
|
|
542
|
+
'pmx-canvas pin --list',
|
|
543
|
+
'pmx-canvas snapshot list --limit 10',
|
|
544
|
+
'```',
|
|
545
|
+
'',
|
|
546
|
+
'MCP tools expose the same core operations for agent harnesses.',
|
|
547
|
+
].join('\n'),
|
|
548
|
+
}),
|
|
549
|
+
makeNode({
|
|
550
|
+
id: 'demo-json-dashboard',
|
|
551
|
+
type: 'json-render',
|
|
552
|
+
title: 'Native UI Dashboard',
|
|
553
|
+
x: 1950,
|
|
554
|
+
y: 90,
|
|
555
|
+
width: 940,
|
|
556
|
+
height: 700,
|
|
557
|
+
data: createJsonRenderNodeData('demo-json-dashboard', 'Native UI Dashboard', projectSpec, {
|
|
558
|
+
viewerType: 'json-render',
|
|
559
|
+
strictSize: true,
|
|
560
|
+
}),
|
|
561
|
+
}),
|
|
562
|
+
buildGraphNode({
|
|
563
|
+
id: 'demo-graph-capabilities',
|
|
564
|
+
title: 'Capability Coverage Radar',
|
|
565
|
+
graphType: 'radar',
|
|
566
|
+
data: [
|
|
567
|
+
{ capability: 'State', native: 5, agent: 4 },
|
|
568
|
+
{ capability: 'UI', native: 4, agent: 3 },
|
|
569
|
+
{ capability: 'MCP', native: 4, agent: 5 },
|
|
570
|
+
{ capability: 'Persistence', native: 5, agent: 4 },
|
|
571
|
+
{ capability: 'Review', native: 3, agent: 5 },
|
|
572
|
+
],
|
|
573
|
+
axisKey: 'capability',
|
|
574
|
+
metrics: ['native', 'agent'],
|
|
575
|
+
x: 1370,
|
|
576
|
+
y: 520,
|
|
577
|
+
width: 540,
|
|
578
|
+
height: 360,
|
|
579
|
+
heightPx: 720,
|
|
580
|
+
showLegend: true,
|
|
581
|
+
}),
|
|
582
|
+
makeNode({
|
|
583
|
+
id: 'demo-ledger-persistence',
|
|
584
|
+
type: 'ledger',
|
|
585
|
+
title: 'Persistence Ledger',
|
|
586
|
+
x: 70,
|
|
587
|
+
y: 1550,
|
|
588
|
+
width: 420,
|
|
589
|
+
height: 280,
|
|
590
|
+
data: {
|
|
591
|
+
stateFile: '.pmx-canvas/state.json',
|
|
592
|
+
snapshots: '.pmx-canvas/snapshots',
|
|
593
|
+
blobSidecars: 'large app payloads',
|
|
594
|
+
undoRedoEntries: 200,
|
|
595
|
+
defaultSnapshotList: '20 newest',
|
|
596
|
+
},
|
|
597
|
+
}),
|
|
598
|
+
makeNode({
|
|
599
|
+
id: 'demo-file-server',
|
|
600
|
+
type: 'file',
|
|
601
|
+
title: 'src/server/canvas-state.ts',
|
|
602
|
+
x: 540,
|
|
603
|
+
y: 1550,
|
|
604
|
+
width: 690,
|
|
605
|
+
height: 320,
|
|
606
|
+
content: 'src/server/canvas-state.ts',
|
|
607
|
+
data: { path: 'src/server/canvas-state.ts' },
|
|
608
|
+
}),
|
|
609
|
+
makeNode({
|
|
610
|
+
id: 'demo-webpage-docs',
|
|
611
|
+
type: 'webpage',
|
|
612
|
+
title: 'Docs Snapshot',
|
|
613
|
+
x: 70,
|
|
614
|
+
y: 1910,
|
|
615
|
+
width: 420,
|
|
616
|
+
height: 330,
|
|
617
|
+
data: {
|
|
618
|
+
url: 'https://github.com/pskoett/pmx-canvas',
|
|
619
|
+
pageTitle: 'PMX Canvas repository',
|
|
620
|
+
description: 'Persisted webpage nodes keep URL context and cached text available for later agent grounding.',
|
|
621
|
+
excerpt: 'PMX Canvas is a spatial canvas workbench for coding agents with nodes, edges, groups, MCP tools, HTTP API, and a Bun SDK.',
|
|
622
|
+
content: 'PMX Canvas is a spatial canvas workbench for coding agents with nodes, edges, groups, MCP tools, HTTP API, and a Bun SDK.',
|
|
623
|
+
fetchedAt: '2026-05-04T00:00:00.000Z',
|
|
624
|
+
status: 'ready',
|
|
625
|
+
statusCode: 200,
|
|
626
|
+
contentType: 'text/html; charset=utf-8',
|
|
627
|
+
frameBlocked: true,
|
|
628
|
+
frameBlockedReason: 'Demo uses a cached snapshot so startup never waits on the network.',
|
|
629
|
+
},
|
|
630
|
+
}),
|
|
631
|
+
buildGraphNode({
|
|
632
|
+
id: 'demo-graph-mix',
|
|
633
|
+
title: 'Node Mix In This Demo',
|
|
634
|
+
graphType: 'bar',
|
|
635
|
+
data: [
|
|
636
|
+
{ type: 'Narrative', count: 4 },
|
|
637
|
+
{ type: 'Structured', count: 2 },
|
|
638
|
+
{ type: 'Telemetry', count: 4 },
|
|
639
|
+
{ type: 'Collaboration', count: 4 },
|
|
640
|
+
],
|
|
641
|
+
xKey: 'type',
|
|
642
|
+
yKey: 'count',
|
|
643
|
+
color: '#46b6ff',
|
|
644
|
+
x: 540,
|
|
645
|
+
y: 1910,
|
|
646
|
+
width: 690,
|
|
647
|
+
heightPx: 700,
|
|
648
|
+
height: 340,
|
|
649
|
+
showLegend: false,
|
|
650
|
+
}),
|
|
651
|
+
makeNode({
|
|
652
|
+
id: 'demo-context-pins',
|
|
653
|
+
type: 'context',
|
|
654
|
+
title: 'Human Curated Context',
|
|
655
|
+
x: 1370,
|
|
656
|
+
y: 1380,
|
|
657
|
+
width: 540,
|
|
658
|
+
height: 540,
|
|
659
|
+
data: {
|
|
660
|
+
currentTokens: 18400,
|
|
661
|
+
tokenLimit: 128000,
|
|
662
|
+
utilization: 0.14,
|
|
663
|
+
messagesLength: 9,
|
|
664
|
+
cards: [
|
|
665
|
+
{ label: 'Pinned intent', summary: 'Humans pin the nodes an agent should treat as current context.', category: 'planning', state: 'loaded', sourceKind: 'workspace', required: true },
|
|
666
|
+
{ label: 'Spatial memory', summary: 'Nearby unpinned nodes are exposed as neighborhoods instead of pasted prompt text.', category: 'memory', state: 'loaded', sourceKind: 'workspace' },
|
|
667
|
+
{ label: 'Fast honest demo', summary: 'The tour uses native nodes only; no fake ask/answer flow or web-artifact build.', category: 'profile', state: 'loaded', sourceKind: 'global' },
|
|
668
|
+
],
|
|
669
|
+
},
|
|
670
|
+
}),
|
|
671
|
+
makeNode({
|
|
672
|
+
id: 'demo-trace-api',
|
|
673
|
+
type: 'trace',
|
|
674
|
+
title: 'Trace: canvas_get_layout',
|
|
675
|
+
x: 1940,
|
|
676
|
+
y: 1830,
|
|
677
|
+
width: 250,
|
|
678
|
+
height: 120,
|
|
679
|
+
data: {
|
|
680
|
+
toolName: 'canvas_get_layout',
|
|
681
|
+
category: 'mcp',
|
|
682
|
+
status: 'success',
|
|
683
|
+
duration: '18ms',
|
|
684
|
+
resultSummary: 'Compact default layout returned for agents',
|
|
685
|
+
},
|
|
686
|
+
}),
|
|
687
|
+
makeNode({
|
|
688
|
+
id: 'demo-trace-build',
|
|
689
|
+
type: 'trace',
|
|
690
|
+
title: 'Trace: browser SSE',
|
|
691
|
+
x: 2210,
|
|
692
|
+
y: 1830,
|
|
693
|
+
width: 250,
|
|
694
|
+
height: 120,
|
|
695
|
+
data: {
|
|
696
|
+
toolName: 'workbench/events',
|
|
697
|
+
category: 'other',
|
|
698
|
+
status: 'running',
|
|
699
|
+
duration: 'live',
|
|
700
|
+
resultSummary: 'Canvas updates stream to the browser',
|
|
701
|
+
},
|
|
702
|
+
}),
|
|
703
|
+
makeNode({
|
|
704
|
+
id: 'demo-mcp-app',
|
|
705
|
+
type: 'mcp-app',
|
|
706
|
+
title: 'Custom MCP App Preview',
|
|
707
|
+
x: 1940,
|
|
708
|
+
y: 1380,
|
|
709
|
+
width: 950,
|
|
710
|
+
height: 420,
|
|
711
|
+
data: {
|
|
712
|
+
mode: 'ext-app',
|
|
713
|
+
html: buildDemoMcpAppHtml(),
|
|
714
|
+
serverName: 'PMX Demo App',
|
|
715
|
+
toolName: 'demo_canvas_overview',
|
|
716
|
+
toolCallId: 'demo-canvas-overview',
|
|
717
|
+
resourceUri: 'ui://pmx-demo/overview.html',
|
|
718
|
+
sessionStatus: 'ready',
|
|
719
|
+
sessionError: null,
|
|
720
|
+
toolInput: { nodeTypes: DEMO_NODE_TYPES.length, fastStart: true },
|
|
721
|
+
toolResult: {
|
|
722
|
+
content: [{ type: 'text', text: 'PMX Canvas demo app loaded.' }],
|
|
723
|
+
structuredContent: { tools: 39, nodeTypes: DEMO_NODE_TYPES.length, resources: 7 },
|
|
724
|
+
},
|
|
725
|
+
toolDefinition: {
|
|
726
|
+
name: 'demo_canvas_overview',
|
|
727
|
+
title: 'PMX Canvas Demo Overview',
|
|
728
|
+
description: 'Static custom MCP app payload used by the built-in demo.',
|
|
729
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
730
|
+
},
|
|
731
|
+
resourceMeta: {
|
|
732
|
+
name: 'PMX Canvas Demo Overview',
|
|
733
|
+
description: 'Self-contained iframe preview for the demo board.',
|
|
734
|
+
},
|
|
735
|
+
},
|
|
736
|
+
}),
|
|
737
|
+
];
|
|
738
|
+
return withGroupMembership(nodes);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function withGroupMembership(nodes: CanvasNodeState[]): CanvasNodeState[] {
|
|
742
|
+
const parentByChild = new Map<string, string>();
|
|
743
|
+
for (const group of groupDefinitions) {
|
|
744
|
+
for (const childId of group.children) parentByChild.set(childId, group.id);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return nodes.map((node) => {
|
|
748
|
+
const parentGroup = parentByChild.get(node.id);
|
|
749
|
+
if (!parentGroup) return node;
|
|
750
|
+
return {
|
|
751
|
+
...node,
|
|
752
|
+
data: {
|
|
753
|
+
...node.data,
|
|
754
|
+
parentGroup,
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function createDemoEdges(): CanvasEdge[] {
|
|
761
|
+
return [
|
|
762
|
+
makeEdge('demo-edge-welcome-health', 'demo-md-welcome', 'demo-status-health', 'flow', 'starts server'),
|
|
763
|
+
makeEdge('demo-edge-health-sketch', 'demo-status-health', 'demo-image-architecture', 'references', 'architecture'),
|
|
764
|
+
makeEdge('demo-edge-surfaces-dashboard', 'demo-md-surfaces', 'demo-json-dashboard', 'references', 'renders structured UI'),
|
|
765
|
+
makeEdge('demo-edge-dashboard-radar', 'demo-json-dashboard', 'demo-graph-capabilities', 'references', 'radar variant'),
|
|
766
|
+
makeEdge('demo-edge-excalidraw-state', 'demo-excalidraw-architecture', 'demo-ledger-persistence', 'references', 'diagram explains state'),
|
|
767
|
+
makeEdge('demo-edge-state-file', 'demo-ledger-persistence', 'demo-file-server', 'depends-on', 'source of truth'),
|
|
768
|
+
makeEdge('demo-edge-ledger-webpage', 'demo-ledger-persistence', 'demo-webpage-docs', 'references', 'cached context'),
|
|
769
|
+
makeEdge('demo-edge-file-graph', 'demo-file-server', 'demo-graph-mix', 'references', 'node mix'),
|
|
770
|
+
makeEdge('demo-edge-context-trace', 'demo-context-pins', 'demo-trace-api', 'references', 'agent grounding'),
|
|
771
|
+
makeEdge('demo-edge-app-context', 'demo-mcp-app', 'demo-context-pins', 'relation', 'opaque app paired with context', { style: 'dotted' }),
|
|
772
|
+
makeEdge('demo-edge-app-trace-api', 'demo-mcp-app', 'demo-trace-api', 'references', 'tool evidence'),
|
|
773
|
+
makeEdge('demo-edge-trace-stream', 'demo-trace-api', 'demo-trace-build', 'flow', 'streams updates', { animated: true }),
|
|
774
|
+
];
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export function seedDemoCanvas(): { nodes: number; edges: number; groups: number } {
|
|
778
|
+
const nodes = createDemoNodes();
|
|
779
|
+
const edges = createDemoEdges();
|
|
780
|
+
canvasState.withSuppressedRecording(() => {
|
|
781
|
+
for (const node of nodes) canvasState.addNode(node);
|
|
782
|
+
for (const edge of edges) canvasState.addEdge(edge);
|
|
783
|
+
canvasState.setContextPins(['demo-md-welcome', 'demo-json-dashboard', 'demo-context-pins']);
|
|
784
|
+
canvasState.setViewport({ x: 80, y: 52, scale: 0.58 });
|
|
785
|
+
});
|
|
786
|
+
canvasState.flushToDisk();
|
|
787
|
+
return {
|
|
788
|
+
nodes: nodes.length,
|
|
789
|
+
edges: edges.length,
|
|
790
|
+
groups: groupDefinitions.length,
|
|
791
|
+
};
|
|
792
|
+
}
|