pmx-canvas 0.1.14 → 0.1.16
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 +153 -0
- package/Readme.md +108 -1058
- package/dist/canvas/global.css +141 -0
- package/dist/canvas/index.js +124 -74
- package/dist/json-render/index.css +1 -1
- package/dist/types/client/nodes/ContextNode.d.ts +11 -2
- package/dist/types/client/nodes/HtmlNode.d.ts +5 -0
- package/dist/types/client/nodes/StatusNode.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +11 -3
- package/dist/types/client/state/intent-bridge.d.ts +5 -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 +4 -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/index.d.ts +13 -3
- 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 +52 -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/ContextPinBar.tsx +2 -1
- package/src/client/canvas/DockedNode.tsx +112 -13
- package/src/client/canvas/ExpandedNodeOverlay.tsx +5 -0
- package/src/client/canvas/Minimap.tsx +1 -0
- package/src/client/icons.tsx +1 -0
- package/src/client/nodes/ContextNode.tsx +128 -6
- package/src/client/nodes/HtmlNode.tsx +151 -0
- package/src/client/nodes/StatusNode.tsx +16 -1
- package/src/client/nodes/StatusSummary.tsx +2 -1
- package/src/client/state/canvas-store.ts +37 -7
- package/src/client/state/intent-bridge.ts +9 -4
- package/src/client/state/sse-bridge.ts +2 -1
- 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 +178 -25
- package/src/server/agent-context.ts +50 -3
- package/src/server/canvas-operations.ts +20 -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/index.ts +33 -3
- package/src/server/server.ts +98 -14
- package/src/server/web-artifacts.ts +116 -3
- package/src/shared/canvas-node-kind.ts +14 -0
|
@@ -277,6 +277,28 @@ export function toggleCollapsed(id: string): void {
|
|
|
277
277
|
updateNode(id, { collapsed: !existing.collapsed });
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
// Collapse every docked context node. Used to enforce mutual exclusion between
|
|
281
|
+
// the Context side panel and the Updates side panel (they share the same
|
|
282
|
+
// right-edge anchor and would otherwise visually collide).
|
|
283
|
+
export function collapseDockedContextNodes(): void {
|
|
284
|
+
for (const node of nodes.value.values()) {
|
|
285
|
+
if (node.type === 'context' && node.dockPosition === 'right' && !node.collapsed) {
|
|
286
|
+
updateNode(node.id, { collapsed: true });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// True iff at least one docked context node is currently expanded. Used by the
|
|
292
|
+
// Updates pill to hide itself while the Context panel is open.
|
|
293
|
+
export const hasOpenDockedContextPanel = computed(() => {
|
|
294
|
+
for (const node of nodes.value.values()) {
|
|
295
|
+
if (node.type === 'context' && node.dockPosition === 'right' && !node.collapsed) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
});
|
|
301
|
+
|
|
280
302
|
export function dockNode(id: string, position: 'left' | 'right'): void {
|
|
281
303
|
const existing = nodes.value.get(id);
|
|
282
304
|
if (!existing) return;
|
|
@@ -308,9 +330,16 @@ export function replaceViewport(next: ViewportState): void {
|
|
|
308
330
|
}
|
|
309
331
|
|
|
310
332
|
export function commitViewport(next: ViewportState): void {
|
|
333
|
+
commitViewportWithOptions(next);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function commitViewportWithOptions(
|
|
337
|
+
next: ViewportState,
|
|
338
|
+
options: { recordHistory?: boolean } = {},
|
|
339
|
+
): void {
|
|
311
340
|
viewport.value = next;
|
|
312
|
-
persistLayout();
|
|
313
|
-
void updateViewportFromClient(next);
|
|
341
|
+
persistLayout(options);
|
|
342
|
+
void updateViewportFromClient(next, options);
|
|
314
343
|
}
|
|
315
344
|
|
|
316
345
|
export function applyServerCanvasLayout(
|
|
@@ -372,6 +401,7 @@ function easeOutCubic(t: number): number {
|
|
|
372
401
|
export function animateViewport(
|
|
373
402
|
target: ViewportState,
|
|
374
403
|
duration = 300,
|
|
404
|
+
options: { recordHistory?: boolean } = {},
|
|
375
405
|
): void {
|
|
376
406
|
if (animationId !== null) cancelAnimationFrame(animationId);
|
|
377
407
|
|
|
@@ -393,7 +423,7 @@ export function animateViewport(
|
|
|
393
423
|
animationId = requestAnimationFrame(tick);
|
|
394
424
|
} else {
|
|
395
425
|
animationId = null;
|
|
396
|
-
|
|
426
|
+
commitViewportWithOptions(target, options);
|
|
397
427
|
}
|
|
398
428
|
}
|
|
399
429
|
|
|
@@ -411,7 +441,7 @@ export function cancelViewportAnimation(): void {
|
|
|
411
441
|
// ── Persistence ───────────────────────────────────────────────
|
|
412
442
|
const STORAGE_KEY = 'pmx-canvas-layout';
|
|
413
443
|
|
|
414
|
-
export function persistLayout(): void {
|
|
444
|
+
export function persistLayout(options: { recordHistory?: boolean } = {}): void {
|
|
415
445
|
try {
|
|
416
446
|
const allNodes = Array.from(nodes.value.values());
|
|
417
447
|
const nodeUpdates = allNodes.map((n) => ({
|
|
@@ -444,7 +474,7 @@ export function persistLayout(): void {
|
|
|
444
474
|
contextPinnedNodeIds: Array.from(contextPinnedNodeIds.value),
|
|
445
475
|
};
|
|
446
476
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
|
|
447
|
-
void pushCanvasUpdate(nodeUpdates);
|
|
477
|
+
void pushCanvasUpdate(nodeUpdates, options);
|
|
448
478
|
} catch (error) {
|
|
449
479
|
logCanvasStoreError('persistLayout', error);
|
|
450
480
|
}
|
|
@@ -518,7 +548,7 @@ export function fitAll(containerW: number, containerH: number): void {
|
|
|
518
548
|
}
|
|
519
549
|
|
|
520
550
|
// ── Focus node ────────────────────────────────────────────────
|
|
521
|
-
export function focusNode(id: string): void {
|
|
551
|
+
export function focusNode(id: string, options: { recordHistory?: boolean } = {}): void {
|
|
522
552
|
const node = nodes.value.get(id);
|
|
523
553
|
if (!node) return;
|
|
524
554
|
const v = viewport.value;
|
|
@@ -528,7 +558,7 @@ export function focusNode(id: string): void {
|
|
|
528
558
|
x: window.innerWidth / 2 - cx * v.scale,
|
|
529
559
|
y: window.innerHeight / 2 - cy * v.scale,
|
|
530
560
|
scale: v.scale,
|
|
531
|
-
});
|
|
561
|
+
}, 300, options);
|
|
532
562
|
bringToFront(id);
|
|
533
563
|
}
|
|
534
564
|
|
|
@@ -101,7 +101,7 @@ export async function openWorkbenchFile(path: string): Promise<{ ok: boolean }>
|
|
|
101
101
|
|
|
102
102
|
/** Fetch canvas state from server. */
|
|
103
103
|
export async function fetchCanvasState(): Promise<Record<string, unknown>> {
|
|
104
|
-
return requestJson('fetchCanvasState', '/api/canvas/state', {});
|
|
104
|
+
return requestJson('fetchCanvasState', '/api/canvas/state?includeBlobs=true', {});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/** Fetch available slash commands for prompt completion. */
|
|
@@ -148,11 +148,12 @@ export async function pushCanvasUpdate(
|
|
|
148
148
|
collapsed?: boolean;
|
|
149
149
|
dockPosition?: 'left' | 'right' | null;
|
|
150
150
|
}>,
|
|
151
|
+
options: { recordHistory?: boolean } = {},
|
|
151
152
|
): Promise<void> {
|
|
152
153
|
await requestBestEffort('pushCanvasUpdate', '/api/canvas/update', {
|
|
153
154
|
method: 'POST',
|
|
154
155
|
headers: { 'Content-Type': 'application/json' },
|
|
155
|
-
body: JSON.stringify({ updates }),
|
|
156
|
+
body: JSON.stringify({ updates, ...(options.recordHistory === false ? { recordHistory: false } : {}) }),
|
|
156
157
|
});
|
|
157
158
|
}
|
|
158
159
|
|
|
@@ -230,11 +231,15 @@ export async function removeNodeFromClient(id: string): Promise<{ ok: boolean; r
|
|
|
230
231
|
/** Commit the current viewport to the authoritative server state. */
|
|
231
232
|
export async function updateViewportFromClient(
|
|
232
233
|
viewport: { x: number; y: number; scale: number },
|
|
234
|
+
options: { recordHistory?: boolean } = {},
|
|
233
235
|
): Promise<{ ok: boolean }> {
|
|
234
236
|
return requestJson('updateViewportFromClient', '/api/canvas/viewport', { ok: false }, {
|
|
235
237
|
method: 'POST',
|
|
236
238
|
headers: { 'Content-Type': 'application/json' },
|
|
237
|
-
body: JSON.stringify(
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
...viewport,
|
|
241
|
+
...(options.recordHistory === false ? { recordHistory: false } : {}),
|
|
242
|
+
}),
|
|
238
243
|
});
|
|
239
244
|
}
|
|
240
245
|
|
|
@@ -286,7 +291,7 @@ export interface CanvasSnapshotInfo {
|
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
export async function listSnapshots(): Promise<CanvasSnapshotInfo[]> {
|
|
289
|
-
return requestJson<CanvasSnapshotInfo[]>('listSnapshots', '/api/canvas/snapshots', []);
|
|
294
|
+
return requestJson<CanvasSnapshotInfo[]>('listSnapshots', '/api/canvas/snapshots?all=true', []);
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
export async function saveSnapshot(name: string): Promise<{ ok: boolean; snapshot?: CanvasSnapshotInfo }> {
|
|
@@ -83,6 +83,7 @@ const DEFAULT_POSITIONS: Record<
|
|
|
83
83
|
trace: { x: 40, y: 900, w: 200, h: 56 },
|
|
84
84
|
file: { x: 380, y: 80, w: 720, h: 600 },
|
|
85
85
|
image: { x: 380, y: 80, w: 720, h: 520 },
|
|
86
|
+
html: { x: 380, y: 80, w: 720, h: 640 },
|
|
86
87
|
group: { x: 220, y: 60, w: 840, h: 560 },
|
|
87
88
|
prompt: { x: 380, y: 1260, w: 520, h: 400 },
|
|
88
89
|
response: { x: 380, y: 1480, w: 720, h: 400 },
|
|
@@ -228,7 +229,7 @@ function ensureExtAppNode(data: Record<string, unknown>): void {
|
|
|
228
229
|
});
|
|
229
230
|
addNode(node);
|
|
230
231
|
if (!node.dockPosition) {
|
|
231
|
-
focusNode(id);
|
|
232
|
+
focusNode(id, { recordHistory: false });
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
@@ -1844,6 +1844,147 @@ body,
|
|
|
1844
1844
|
max-width: 200px;
|
|
1845
1845
|
}
|
|
1846
1846
|
|
|
1847
|
+
/* Context dock — collapsed pill mirrors Updates pill, sits above it */
|
|
1848
|
+
.context-dock-tab {
|
|
1849
|
+
position: fixed;
|
|
1850
|
+
top: 92px;
|
|
1851
|
+
right: 0;
|
|
1852
|
+
display: flex;
|
|
1853
|
+
align-items: center;
|
|
1854
|
+
gap: 8px;
|
|
1855
|
+
padding: 8px 12px 8px 14px;
|
|
1856
|
+
background: color-mix(in srgb, var(--c-panel-glass) 96%, transparent);
|
|
1857
|
+
backdrop-filter: blur(16px);
|
|
1858
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 82%, var(--c-accent) 18%);
|
|
1859
|
+
border-right: 0;
|
|
1860
|
+
border-radius: 14px 0 0 14px;
|
|
1861
|
+
box-shadow: 0 12px 36px var(--c-shadow);
|
|
1862
|
+
color: var(--c-text);
|
|
1863
|
+
cursor: pointer;
|
|
1864
|
+
font: inherit;
|
|
1865
|
+
font-size: 11px;
|
|
1866
|
+
font-weight: 600;
|
|
1867
|
+
letter-spacing: 0.08em;
|
|
1868
|
+
text-transform: uppercase;
|
|
1869
|
+
z-index: 60;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
.context-dock-tab:hover {
|
|
1873
|
+
border-color: color-mix(in srgb, var(--c-accent) 40%, var(--c-line) 60%);
|
|
1874
|
+
color: var(--c-accent);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
.context-dock-tab svg {
|
|
1878
|
+
display: block;
|
|
1879
|
+
color: var(--c-accent);
|
|
1880
|
+
flex-shrink: 0;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
.context-dock-tab-label {
|
|
1884
|
+
white-space: nowrap;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.context-dock-tab-badge {
|
|
1888
|
+
min-width: 18px;
|
|
1889
|
+
height: 18px;
|
|
1890
|
+
padding: 0 5px;
|
|
1891
|
+
display: inline-flex;
|
|
1892
|
+
align-items: center;
|
|
1893
|
+
justify-content: center;
|
|
1894
|
+
border-radius: 9px;
|
|
1895
|
+
background: var(--c-accent);
|
|
1896
|
+
color: var(--c-contrast-fg);
|
|
1897
|
+
font-size: 10px;
|
|
1898
|
+
font-weight: 700;
|
|
1899
|
+
letter-spacing: 0;
|
|
1900
|
+
text-transform: none;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/* Context dock — expanded panel anchored top-right edge.
|
|
1904
|
+
Mutually exclusive with the Updates panel (see DockedNode.tsx and
|
|
1905
|
+
AttentionHistory.tsx) — opening one collapses the other, so they can both
|
|
1906
|
+
share the same right: 18px anchor without overlapping. */
|
|
1907
|
+
.context-dock-panel {
|
|
1908
|
+
position: fixed;
|
|
1909
|
+
top: 92px;
|
|
1910
|
+
right: 18px;
|
|
1911
|
+
width: min(360px, calc(100vw - 24px));
|
|
1912
|
+
max-height: calc(100vh - 110px);
|
|
1913
|
+
display: flex;
|
|
1914
|
+
flex-direction: column;
|
|
1915
|
+
gap: 10px;
|
|
1916
|
+
padding: 14px;
|
|
1917
|
+
background: color-mix(in srgb, var(--c-panel-glass) 96%, transparent);
|
|
1918
|
+
backdrop-filter: blur(16px);
|
|
1919
|
+
border: 1px solid color-mix(in srgb, var(--c-line) 82%, var(--c-accent) 18%);
|
|
1920
|
+
border-radius: 18px;
|
|
1921
|
+
box-shadow: 0 14px 36px var(--c-shadow), 0 0 0 1px color-mix(in srgb, var(--c-accent) 8%, transparent);
|
|
1922
|
+
z-index: 10001;
|
|
1923
|
+
overflow: hidden;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
.context-dock-header {
|
|
1927
|
+
display: flex;
|
|
1928
|
+
align-items: flex-start;
|
|
1929
|
+
justify-content: space-between;
|
|
1930
|
+
gap: 8px;
|
|
1931
|
+
padding: 2px 2px 4px;
|
|
1932
|
+
flex-shrink: 0;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
.context-dock-header-text {
|
|
1936
|
+
display: flex;
|
|
1937
|
+
flex-direction: column;
|
|
1938
|
+
gap: 2px;
|
|
1939
|
+
min-width: 0;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
.context-dock-title {
|
|
1943
|
+
font-size: 12px;
|
|
1944
|
+
font-weight: 700;
|
|
1945
|
+
letter-spacing: 0.08em;
|
|
1946
|
+
text-transform: uppercase;
|
|
1947
|
+
color: var(--c-text);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.context-dock-subtitle {
|
|
1951
|
+
font-size: 11px;
|
|
1952
|
+
color: var(--c-dim);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
.context-dock-controls {
|
|
1956
|
+
display: flex;
|
|
1957
|
+
gap: 4px;
|
|
1958
|
+
flex-shrink: 0;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.context-dock-icon-button {
|
|
1962
|
+
width: 22px;
|
|
1963
|
+
height: 22px;
|
|
1964
|
+
border: 0;
|
|
1965
|
+
border-radius: 6px;
|
|
1966
|
+
background: transparent;
|
|
1967
|
+
color: var(--c-dim);
|
|
1968
|
+
font-size: 16px;
|
|
1969
|
+
line-height: 1;
|
|
1970
|
+
cursor: pointer;
|
|
1971
|
+
display: grid;
|
|
1972
|
+
place-items: center;
|
|
1973
|
+
padding: 0;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
.context-dock-icon-button:hover {
|
|
1977
|
+
color: var(--c-text);
|
|
1978
|
+
background: var(--c-surface-hover);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
.context-dock-body {
|
|
1982
|
+
flex: 1 1 auto;
|
|
1983
|
+
min-height: 0;
|
|
1984
|
+
overflow-y: auto;
|
|
1985
|
+
padding-right: 2px;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1847
1988
|
.attention-history {
|
|
1848
1989
|
position: fixed;
|
|
1849
1990
|
top: 146px;
|
package/src/client/types.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface CanvasNodeState {
|
|
|
20
20
|
| 'trace'
|
|
21
21
|
| 'file'
|
|
22
22
|
| 'image'
|
|
23
|
+
| 'html'
|
|
23
24
|
| 'group';
|
|
24
25
|
position: { x: number; y: number };
|
|
25
26
|
size: { width: number; height: number };
|
|
@@ -58,6 +59,7 @@ export const TYPE_LABELS: Record<CanvasNodeState['type'], string> = {
|
|
|
58
59
|
trace: 'TRACE',
|
|
59
60
|
file: 'FILE',
|
|
60
61
|
image: 'IMG',
|
|
62
|
+
html: 'HTML',
|
|
61
63
|
group: 'GROUP',
|
|
62
64
|
};
|
|
63
65
|
|
|
@@ -72,6 +74,7 @@ export const EXPANDABLE_TYPES = new Set<CanvasNodeState['type']>([
|
|
|
72
74
|
'ledger',
|
|
73
75
|
'file',
|
|
74
76
|
'image',
|
|
77
|
+
'html',
|
|
75
78
|
]);
|
|
76
79
|
|
|
77
80
|
export const EXCALIDRAW_SERVER_NAME = 'Excalidraw';
|
package/src/mcp/canvas-access.ts
CHANGED
|
@@ -18,6 +18,7 @@ type OpenMcpAppResult = Awaited<ReturnType<PmxCanvas['openMcpApp']>>;
|
|
|
18
18
|
type AddDiagramInput = Parameters<PmxCanvas['addDiagram']>[0];
|
|
19
19
|
type AddJsonRenderNodeInput = Parameters<PmxCanvas['addJsonRenderNode']>[0];
|
|
20
20
|
type AddJsonRenderNodeResult = ReturnType<PmxCanvas['addJsonRenderNode']>;
|
|
21
|
+
type AddHtmlNodeInput = Parameters<PmxCanvas['addHtmlNode']>[0];
|
|
21
22
|
type AddGraphNodeInput = Parameters<PmxCanvas['addGraphNode']>[0];
|
|
22
23
|
type AddGraphNodeResult = ReturnType<PmxCanvas['addGraphNode']>;
|
|
23
24
|
type UpdateNodePatch = Parameters<PmxCanvas['updateNode']>[1];
|
|
@@ -34,8 +35,11 @@ type HistoryResult = ReturnType<PmxCanvas['getHistory']>;
|
|
|
34
35
|
type SetContextPinsResult = ReturnType<PmxCanvas['setContextPins']>;
|
|
35
36
|
type RunBatchInput = Parameters<PmxCanvas['runBatch']>[0];
|
|
36
37
|
type RunBatchResult = Awaited<ReturnType<PmxCanvas['runBatch']>>;
|
|
38
|
+
type SnapshotListOptions = Parameters<PmxCanvas['listSnapshots']>[0];
|
|
37
39
|
type SnapshotList = ReturnType<PmxCanvas['listSnapshots']>;
|
|
38
40
|
type DeleteSnapshotResult = ReturnType<PmxCanvas['deleteSnapshot']>;
|
|
41
|
+
type GcSnapshotsOptions = Parameters<PmxCanvas['gcSnapshots']>[0];
|
|
42
|
+
type GcSnapshotsResult = ReturnType<PmxCanvas['gcSnapshots']>;
|
|
39
43
|
type DiffSnapshotResult = ReturnType<PmxCanvas['diffSnapshot']>;
|
|
40
44
|
type CodeGraphResult = ReturnType<PmxCanvas['getCodeGraph']>;
|
|
41
45
|
type ValidationResult = ReturnType<PmxCanvas['validate']>;
|
|
@@ -97,6 +101,7 @@ export interface CanvasAccess {
|
|
|
97
101
|
openMcpApp(input: OpenMcpAppInput): Promise<OpenMcpAppResult>;
|
|
98
102
|
addDiagram(input: AddDiagramInput): Promise<OpenMcpAppResult>;
|
|
99
103
|
addJsonRenderNode(input: AddJsonRenderNodeInput): Promise<AddJsonRenderNodeResult>;
|
|
104
|
+
addHtmlNode(input: AddHtmlNodeInput): Promise<string>;
|
|
100
105
|
addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult>;
|
|
101
106
|
buildWebArtifact(input: WebArtifactInput): Promise<WebArtifactResult>;
|
|
102
107
|
updateNode(id: string, patch: UpdateNodePatch): Promise<void>;
|
|
@@ -117,10 +122,11 @@ export interface CanvasAccess {
|
|
|
117
122
|
setContextPins(nodeIds: string[], mode?: 'set' | 'add' | 'remove'): Promise<SetContextPinsResult>;
|
|
118
123
|
getPinnedNodeIds(): Promise<string[]>;
|
|
119
124
|
runBatch(operations: RunBatchInput): Promise<RunBatchResult>;
|
|
120
|
-
listSnapshots(): Promise<SnapshotList>;
|
|
125
|
+
listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList>;
|
|
121
126
|
saveSnapshot(name: string): Promise<CanvasSnapshot | null>;
|
|
122
127
|
restoreSnapshot(id: string): Promise<{ ok: boolean }>;
|
|
123
128
|
deleteSnapshot(id: string): Promise<DeleteSnapshotResult>;
|
|
129
|
+
gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult>;
|
|
124
130
|
diffSnapshot(idOrName: string): Promise<DiffSnapshotResult>;
|
|
125
131
|
getCodeGraph(): Promise<CodeGraphResult>;
|
|
126
132
|
validate(): Promise<ValidationResult>;
|
|
@@ -177,6 +183,10 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
177
183
|
return this.canvas.addJsonRenderNode(input);
|
|
178
184
|
}
|
|
179
185
|
|
|
186
|
+
async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
|
|
187
|
+
return this.canvas.addHtmlNode(input);
|
|
188
|
+
}
|
|
189
|
+
|
|
180
190
|
async addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult> {
|
|
181
191
|
return this.canvas.addGraphNode(input);
|
|
182
192
|
}
|
|
@@ -257,8 +267,8 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
257
267
|
return await this.canvas.runBatch(operations);
|
|
258
268
|
}
|
|
259
269
|
|
|
260
|
-
async listSnapshots(): Promise<SnapshotList> {
|
|
261
|
-
return this.canvas.listSnapshots();
|
|
270
|
+
async listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList> {
|
|
271
|
+
return this.canvas.listSnapshots(options);
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
async saveSnapshot(name: string): Promise<CanvasSnapshot | null> {
|
|
@@ -273,6 +283,10 @@ class LocalCanvasAccess implements CanvasAccess {
|
|
|
273
283
|
return this.canvas.deleteSnapshot(id);
|
|
274
284
|
}
|
|
275
285
|
|
|
286
|
+
async gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult> {
|
|
287
|
+
return this.canvas.gcSnapshots(options);
|
|
288
|
+
}
|
|
289
|
+
|
|
276
290
|
async diffSnapshot(idOrName: string): Promise<DiffSnapshotResult> {
|
|
277
291
|
return this.canvas.diffSnapshot(idOrName);
|
|
278
292
|
}
|
|
@@ -359,11 +373,11 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
359
373
|
}
|
|
360
374
|
|
|
361
375
|
async getLayout(): Promise<CanvasLayout> {
|
|
362
|
-
return await this.requestJson<CanvasLayout>('GET', '/api/canvas/state');
|
|
376
|
+
return await this.requestJson<CanvasLayout>('GET', '/api/canvas/state?includeBlobs=true');
|
|
363
377
|
}
|
|
364
378
|
|
|
365
379
|
async getNode(id: string): Promise<CanvasNodeState | undefined> {
|
|
366
|
-
const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}`);
|
|
380
|
+
const response = await fetch(`${this.remoteBaseUrl}/api/canvas/node/${encodeURIComponent(id)}?includeBlobs=true`);
|
|
367
381
|
if (response.status === 404) return undefined;
|
|
368
382
|
const text = await response.text();
|
|
369
383
|
let parsed: unknown = undefined;
|
|
@@ -415,6 +429,10 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
415
429
|
return { id, url: response.url, spec: response.spec };
|
|
416
430
|
}
|
|
417
431
|
|
|
432
|
+
async addHtmlNode(input: AddHtmlNodeInput): Promise<string> {
|
|
433
|
+
return await this.requestNodeId('POST', '/api/canvas/node', { type: 'html', ...input });
|
|
434
|
+
}
|
|
435
|
+
|
|
418
436
|
async addGraphNode(input: AddGraphNodeInput): Promise<AddGraphNodeResult> {
|
|
419
437
|
const response = await this.requestJson<GraphNodeResponse>('POST', '/api/canvas/graph', {
|
|
420
438
|
...input,
|
|
@@ -527,8 +545,13 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
527
545
|
return await this.requestJson<RunBatchResult>('POST', '/api/canvas/batch', { operations });
|
|
528
546
|
}
|
|
529
547
|
|
|
530
|
-
async listSnapshots(): Promise<SnapshotList> {
|
|
531
|
-
|
|
548
|
+
async listSnapshots(options?: SnapshotListOptions): Promise<SnapshotList> {
|
|
549
|
+
const params = new URLSearchParams();
|
|
550
|
+
if (typeof options?.limit === 'number') params.set('limit', String(options.limit));
|
|
551
|
+
if (options?.query) params.set('q', options.query);
|
|
552
|
+
if (options?.all) params.set('all', 'true');
|
|
553
|
+
const query = params.size > 0 ? `?${params.toString()}` : '';
|
|
554
|
+
return await this.requestJson<SnapshotList>('GET', `/api/canvas/snapshots${query}`);
|
|
532
555
|
}
|
|
533
556
|
|
|
534
557
|
async saveSnapshot(name: string): Promise<CanvasSnapshot | null> {
|
|
@@ -544,6 +567,10 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
544
567
|
return await this.requestJson<DeleteSnapshotResult>('DELETE', `/api/canvas/snapshots/${encodeURIComponent(id)}`);
|
|
545
568
|
}
|
|
546
569
|
|
|
570
|
+
async gcSnapshots(options?: GcSnapshotsOptions): Promise<GcSnapshotsResult> {
|
|
571
|
+
return await this.requestJson<GcSnapshotsResult>('POST', '/api/canvas/snapshots/gc', options ?? {});
|
|
572
|
+
}
|
|
573
|
+
|
|
547
574
|
async diffSnapshot(idOrName: string): Promise<DiffSnapshotResult> {
|
|
548
575
|
return await this.requestJson<DiffSnapshotResult>('GET', `/api/canvas/snapshots/${encodeURIComponent(idOrName)}/diff`);
|
|
549
576
|
}
|