pmx-canvas 0.2.7 → 0.3.1
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 +148 -1
- package/Readme.md +13 -11
- package/dist/canvas/index.js +2 -2
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/mcp/canvas-access.d.ts +0 -7
- package/dist/types/server/ax-state-manager.d.ts +3 -2
- package/dist/types/server/bundled-skills.d.ts +3 -3
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/ext-app-lookup.d.ts +1 -3
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/composites.d.ts +29 -23
- package/dist/types/server/operations/http.d.ts +2 -1
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/shared/ax-intent.d.ts +1 -1
- package/docs/RELEASE.md +23 -8
- package/docs/ax-host-adapter-contract.md +7 -7
- package/docs/ax-state-contract.md +7 -6
- package/docs/bun-webview-integration.md +5 -5
- package/docs/cli.md +1 -1
- package/docs/http-api.md +35 -3
- package/docs/mcp.md +121 -100
- package/docs/node-types.md +45 -35
- package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
- package/docs/plans/plan-008-registry-finish.md +2 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +1 -1
- package/docs/tech-debt-assessment-2026-07.md +135 -0
- package/package.json +5 -2
- package/skills/data-analysis/SKILL.md +3 -3
- package/skills/frontend-design/SKILL.md +3 -2
- package/skills/json-render-mcp/SKILL.md +4 -3
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/pmx-canvas/SKILL.md +12 -7
- package/skills/pmx-canvas/evals/evals.json +2 -2
- package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
- package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
- package/skills/pmx-canvas/references/full-reference.md +58 -56
- package/skills/pmx-canvas/references/html-primitives.md +3 -3
- package/skills/tufte-viz/SKILL.md +5 -4
- package/skills/web-artifacts-builder/SKILL.md +4 -4
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +40 -121
- package/src/mcp/server.ts +206 -271
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +185 -41
- package/src/server/ax-state.ts +142 -47
- package/src/server/bundled-skills.ts +3 -3
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +500 -102
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +155 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +20 -7
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +4 -20
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +321 -218
- package/src/server/intent-registry.ts +3 -6
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +38 -40
- package/src/server/operations/http.ts +5 -5
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +29 -36
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +102 -77
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +300 -111
- package/src/server/operations/ops/query.ts +62 -33
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +121 -1012
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/ax-intent.ts +1 -1
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -35,13 +35,13 @@ import {
|
|
|
35
35
|
type JsonRenderNodeInput,
|
|
36
36
|
type JsonRenderSpec,
|
|
37
37
|
} from '../json-render/server.js';
|
|
38
|
-
import {
|
|
39
|
-
fetchWebpageSnapshot,
|
|
40
|
-
getWebpageFetchErrorDetails,
|
|
41
|
-
normalizeWebpageUrl,
|
|
42
|
-
} from './webpage-node.js';
|
|
38
|
+
import { fetchWebpageSnapshot, getWebpageFetchErrorDetails, normalizeWebpageUrl } from './webpage-node.js';
|
|
43
39
|
import { validateLocalImageFile } from './image-source.js';
|
|
44
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
buildExcalidrawRestoreCheckpointToolInput,
|
|
42
|
+
ensureExcalidrawCheckpointId,
|
|
43
|
+
isExcalidrawCreateView,
|
|
44
|
+
} from './diagram-presets.js';
|
|
45
45
|
|
|
46
46
|
export type CanvasArrangeMode = 'grid' | 'column' | 'flow';
|
|
47
47
|
export type CanvasPinMode = 'set' | 'add' | 'remove';
|
|
@@ -175,13 +175,16 @@ function isJsonRenderSpecLike(value: unknown): boolean {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
function isGraphPayloadLike(value: unknown): value is Record<string, unknown> {
|
|
178
|
-
return
|
|
179
|
-
|
|
178
|
+
return (
|
|
179
|
+
isRecord(value) &&
|
|
180
|
+
!isJsonRenderSpecLike(value) &&
|
|
181
|
+
(Array.isArray(value.data) || typeof value.graphType === 'string')
|
|
180
182
|
);
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
function hasGraphUpdateFields(input: Record<string, unknown>): boolean {
|
|
184
|
-
return
|
|
186
|
+
return (
|
|
187
|
+
input.graphType !== undefined ||
|
|
185
188
|
input.type !== undefined ||
|
|
186
189
|
Array.isArray(input.data) ||
|
|
187
190
|
input.xKey !== undefined ||
|
|
@@ -200,18 +203,13 @@ function hasGraphUpdateFields(input: Record<string, unknown>): boolean {
|
|
|
200
203
|
input.color !== undefined ||
|
|
201
204
|
input.barColor !== undefined ||
|
|
202
205
|
input.lineColor !== undefined ||
|
|
203
|
-
input.chartHeight !== undefined
|
|
206
|
+
input.chartHeight !== undefined
|
|
207
|
+
);
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
function graphUpdateInput(input: CanvasStructuredNodeUpdateInput): CanvasGraphNodeUpdateInput {
|
|
207
211
|
const data = pickGraphData(input as Record<string, unknown>, 'data');
|
|
208
|
-
const {
|
|
209
|
-
data: _data,
|
|
210
|
-
content: _content,
|
|
211
|
-
arrangeLocked: _arrangeLocked,
|
|
212
|
-
chartHeight,
|
|
213
|
-
...graphFields
|
|
214
|
-
} = input;
|
|
212
|
+
const { data: _data, content: _content, arrangeLocked: _arrangeLocked, chartHeight, ...graphFields } = input;
|
|
215
213
|
return {
|
|
216
214
|
...graphFields,
|
|
217
215
|
...(data ? { data } : {}),
|
|
@@ -251,10 +249,13 @@ export function buildStructuredNodeUpdate(
|
|
|
251
249
|
throw new Error('json-render structured updates require a spec.');
|
|
252
250
|
}
|
|
253
251
|
return {
|
|
254
|
-
data: mergeNodeDataFields(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
252
|
+
data: mergeNodeDataFields(
|
|
253
|
+
buildJsonRenderNodeUpdate(node, {
|
|
254
|
+
...(typeof input.title === 'string' ? { title: input.title } : {}),
|
|
255
|
+
spec: input.spec,
|
|
256
|
+
}).data,
|
|
257
|
+
input,
|
|
258
|
+
),
|
|
258
259
|
};
|
|
259
260
|
}
|
|
260
261
|
|
|
@@ -264,7 +265,9 @@ export function buildStructuredNodeUpdate(
|
|
|
264
265
|
};
|
|
265
266
|
}
|
|
266
267
|
|
|
267
|
-
throw new Error(
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Structured spec and graph updates can only be used with json-render or graph nodes, not ${node.type} nodes.`,
|
|
270
|
+
);
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
function graphConfigToInput(config: Record<string, unknown>, fallbackTitle: string): GraphNodeInput | null {
|
|
@@ -296,7 +299,8 @@ function graphConfigToInput(config: Record<string, unknown>, fallbackTitle: stri
|
|
|
296
299
|
|
|
297
300
|
function mergeGraphInput(source: Record<string, unknown>, fallback: GraphNodeInput | null): GraphNodeInput {
|
|
298
301
|
const data = pickGraphData(source, 'data') ?? fallback?.data;
|
|
299
|
-
if (!data)
|
|
302
|
+
if (!data)
|
|
303
|
+
throw new Error('Graph update requires a data array, either in the update payload or the existing graphConfig.');
|
|
300
304
|
return {
|
|
301
305
|
title: pickString(source, 'title') ?? fallback?.title ?? 'Graph',
|
|
302
306
|
graphType: pickString(source, 'graphType') ?? pickString(source, 'type') ?? fallback?.graphType ?? 'line',
|
|
@@ -304,24 +308,48 @@ function mergeGraphInput(source: Record<string, unknown>, fallback: GraphNodeInp
|
|
|
304
308
|
...((pickString(source, 'xKey') ?? fallback?.xKey) ? { xKey: pickString(source, 'xKey') ?? fallback?.xKey } : {}),
|
|
305
309
|
...((pickString(source, 'yKey') ?? fallback?.yKey) ? { yKey: pickString(source, 'yKey') ?? fallback?.yKey } : {}),
|
|
306
310
|
...((pickString(source, 'zKey') ?? fallback?.zKey) ? { zKey: pickString(source, 'zKey') ?? fallback?.zKey } : {}),
|
|
307
|
-
...((pickString(source, 'nameKey') ?? fallback?.nameKey)
|
|
308
|
-
|
|
311
|
+
...((pickString(source, 'nameKey') ?? fallback?.nameKey)
|
|
312
|
+
? { nameKey: pickString(source, 'nameKey') ?? fallback?.nameKey }
|
|
313
|
+
: {}),
|
|
314
|
+
...((pickString(source, 'valueKey') ?? fallback?.valueKey)
|
|
315
|
+
? { valueKey: pickString(source, 'valueKey') ?? fallback?.valueKey }
|
|
316
|
+
: {}),
|
|
309
317
|
...(typeof source.showLegend === 'boolean' || typeof fallback?.showLegend === 'boolean'
|
|
310
318
|
? { showLegend: typeof source.showLegend === 'boolean' ? source.showLegend : fallback?.showLegend }
|
|
311
319
|
: {}),
|
|
312
320
|
...(typeof source.showLabels === 'boolean' || typeof fallback?.showLabels === 'boolean'
|
|
313
321
|
? { showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : fallback?.showLabels }
|
|
314
322
|
: {}),
|
|
315
|
-
...((pickString(source, 'axisKey') ?? fallback?.axisKey)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
...((
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
...((
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
...((
|
|
323
|
+
...((pickString(source, 'axisKey') ?? fallback?.axisKey)
|
|
324
|
+
? { axisKey: pickString(source, 'axisKey') ?? fallback?.axisKey }
|
|
325
|
+
: {}),
|
|
326
|
+
...((pickStringArray(source, 'metrics') ?? fallback?.metrics)
|
|
327
|
+
? { metrics: pickStringArray(source, 'metrics') ?? fallback?.metrics }
|
|
328
|
+
: {}),
|
|
329
|
+
...((pickStringArray(source, 'series') ?? fallback?.series)
|
|
330
|
+
? { series: pickStringArray(source, 'series') ?? fallback?.series }
|
|
331
|
+
: {}),
|
|
332
|
+
...((pickString(source, 'barKey') ?? fallback?.barKey)
|
|
333
|
+
? { barKey: pickString(source, 'barKey') ?? fallback?.barKey }
|
|
334
|
+
: {}),
|
|
335
|
+
...((pickString(source, 'lineKey') ?? fallback?.lineKey)
|
|
336
|
+
? { lineKey: pickString(source, 'lineKey') ?? fallback?.lineKey }
|
|
337
|
+
: {}),
|
|
338
|
+
...((pickAggregate(source, 'aggregate') ?? fallback?.aggregate)
|
|
339
|
+
? { aggregate: pickAggregate(source, 'aggregate') ?? fallback?.aggregate }
|
|
340
|
+
: {}),
|
|
341
|
+
...((pickString(source, 'color') ?? fallback?.color)
|
|
342
|
+
? { color: pickString(source, 'color') ?? fallback?.color }
|
|
343
|
+
: {}),
|
|
344
|
+
...((pickString(source, 'barColor') ?? fallback?.barColor)
|
|
345
|
+
? { barColor: pickString(source, 'barColor') ?? fallback?.barColor }
|
|
346
|
+
: {}),
|
|
347
|
+
...((pickString(source, 'lineColor') ?? fallback?.lineColor)
|
|
348
|
+
? { lineColor: pickString(source, 'lineColor') ?? fallback?.lineColor }
|
|
349
|
+
: {}),
|
|
350
|
+
...((pickNumber(source, 'height') ?? fallback?.height) !== undefined
|
|
351
|
+
? { height: pickNumber(source, 'height') ?? fallback?.height }
|
|
352
|
+
: {}),
|
|
325
353
|
};
|
|
326
354
|
}
|
|
327
355
|
|
|
@@ -351,9 +379,7 @@ export function buildGraphNodeUpdate(
|
|
|
351
379
|
const fallback = graphConfigToInput(currentConfig, fallbackTitle);
|
|
352
380
|
const source = isGraphPayloadLike(input.spec)
|
|
353
381
|
? input.spec
|
|
354
|
-
: Object.fromEntries(
|
|
355
|
-
Object.entries(input).filter(([key, value]) => key !== 'spec' && value !== undefined),
|
|
356
|
-
);
|
|
382
|
+
: Object.fromEntries(Object.entries(input).filter(([key, value]) => key !== 'spec' && value !== undefined));
|
|
357
383
|
|
|
358
384
|
if (input.spec !== undefined && !isGraphPayloadLike(input.spec)) {
|
|
359
385
|
const spec = normalizeAndValidateJsonRenderSpec(input.spec);
|
|
@@ -531,7 +557,8 @@ function prepareExtAppNodesForSessionSync(forceRehydrate: boolean): string[] {
|
|
|
531
557
|
setExtAppRuntimeState(node.id, {
|
|
532
558
|
appSessionId: null,
|
|
533
559
|
sessionStatus: 'error',
|
|
534
|
-
sessionError:
|
|
560
|
+
sessionError:
|
|
561
|
+
'Saved app session cannot be restored because its transport details are missing. Reopen the app to restore interactivity.',
|
|
535
562
|
});
|
|
536
563
|
continue;
|
|
537
564
|
}
|
|
@@ -549,9 +576,7 @@ function prepareExtAppNodesForSessionSync(forceRehydrate: boolean): string[] {
|
|
|
549
576
|
return targetIds;
|
|
550
577
|
}
|
|
551
578
|
|
|
552
|
-
export function primeCanvasRuntimeBackends(
|
|
553
|
-
options: { forceRehydrateExtApps?: boolean } = {},
|
|
554
|
-
): { targetIds: string[] } {
|
|
579
|
+
export function primeCanvasRuntimeBackends(options: { forceRehydrateExtApps?: boolean } = {}): { targetIds: string[] } {
|
|
555
580
|
const forceRehydrateExtApps = options.forceRehydrateExtApps === true;
|
|
556
581
|
rewatchAllFileNodes();
|
|
557
582
|
|
|
@@ -574,11 +599,13 @@ export function primeCanvasRuntimeBackends(
|
|
|
574
599
|
export async function syncCanvasRuntimeBackends(
|
|
575
600
|
options: { forceRehydrateExtApps?: boolean; alreadyPrimed?: boolean } = {},
|
|
576
601
|
): Promise<{ rehydrated: number; failed: number }> {
|
|
577
|
-
const targetIds =
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
602
|
+
const targetIds =
|
|
603
|
+
options.alreadyPrimed === true
|
|
604
|
+
? canvasState
|
|
605
|
+
.getLayout()
|
|
606
|
+
.nodes.filter((node) => isExtAppNode(node) && node.data.sessionStatus === 'rehydrating')
|
|
607
|
+
.map((node) => node.id)
|
|
608
|
+
: primeCanvasRuntimeBackends(options).targetIds;
|
|
582
609
|
let rehydrated = 0;
|
|
583
610
|
let failed = 0;
|
|
584
611
|
|
|
@@ -593,7 +620,8 @@ export async function syncCanvasRuntimeBackends(
|
|
|
593
620
|
setExtAppRuntimeState(nodeId, {
|
|
594
621
|
appSessionId: null,
|
|
595
622
|
sessionStatus: 'error',
|
|
596
|
-
sessionError:
|
|
623
|
+
sessionError:
|
|
624
|
+
'Saved app session cannot be restored because its launch metadata is incomplete. Reopen the app to restore interactivity.',
|
|
597
625
|
});
|
|
598
626
|
});
|
|
599
627
|
failed++;
|
|
@@ -696,9 +724,8 @@ function buildFileNodeData(input: CanvasAddNodeInput): Record<string, unknown> {
|
|
|
696
724
|
};
|
|
697
725
|
}
|
|
698
726
|
|
|
699
|
-
const rawPath =
|
|
700
|
-
? input.data.path
|
|
701
|
-
: (input.content ?? '');
|
|
727
|
+
const rawPath =
|
|
728
|
+
typeof input.data?.path === 'string' && input.data.path.length > 0 ? input.data.path : (input.content ?? '');
|
|
702
729
|
const resolved = resolve(rawPath);
|
|
703
730
|
const fileName = resolved.split('/').pop() ?? rawPath;
|
|
704
731
|
const data: Record<string, unknown> = {
|
|
@@ -754,20 +781,20 @@ function buildImageNodeData(input: CanvasAddNodeInput): Record<string, unknown>
|
|
|
754
781
|
return {
|
|
755
782
|
...(input.data ?? {}),
|
|
756
783
|
src,
|
|
757
|
-
title: input.title ?? (isUrl ? src.split('/').pop() ?? 'Image' : 'Image'),
|
|
784
|
+
title: input.title ?? (isUrl ? (src.split('/').pop() ?? 'Image') : 'Image'),
|
|
758
785
|
};
|
|
759
786
|
}
|
|
760
787
|
|
|
761
788
|
function buildWebpageNodeData(input: CanvasAddNodeInput): Record<string, unknown> {
|
|
762
|
-
const rawUrl =
|
|
763
|
-
? input.data.url
|
|
764
|
-
: (input.content ?? '');
|
|
789
|
+
const rawUrl =
|
|
790
|
+
typeof input.data?.url === 'string' && input.data.url.length > 0 ? input.data.url : (input.content ?? '');
|
|
765
791
|
const url = normalizeWebpageUrl(rawUrl);
|
|
766
|
-
const explicitTitle =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
792
|
+
const explicitTitle =
|
|
793
|
+
typeof input.title === 'string' && input.title.trim().length > 0
|
|
794
|
+
? input.title.trim()
|
|
795
|
+
: typeof input.data?.title === 'string' && input.data.title.trim().length > 0
|
|
796
|
+
? input.data.title.trim()
|
|
797
|
+
: '';
|
|
771
798
|
|
|
772
799
|
return {
|
|
773
800
|
...(input.data ?? {}),
|
|
@@ -873,9 +900,10 @@ export function addCanvasNode(input: CanvasAddNodeInput): {
|
|
|
873
900
|
|
|
874
901
|
const width = input.width ?? input.defaultWidth ?? 720;
|
|
875
902
|
const height = input.height ?? input.defaultHeight ?? 600;
|
|
876
|
-
const position =
|
|
877
|
-
|
|
878
|
-
|
|
903
|
+
const position =
|
|
904
|
+
input.x !== undefined && input.y !== undefined
|
|
905
|
+
? { x: input.x, y: input.y }
|
|
906
|
+
: findOpenCanvasPosition(canvasState.getLayout().nodes, width, height);
|
|
879
907
|
const id = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
880
908
|
const data = buildNodeData(input);
|
|
881
909
|
const node: CanvasNodeState = {
|
|
@@ -901,13 +929,15 @@ export function addCanvasNode(input: CanvasAddNodeInput): {
|
|
|
901
929
|
return { id, node: storedNode, needsCodeGraphRecompute: input.type === 'file' };
|
|
902
930
|
}
|
|
903
931
|
|
|
904
|
-
export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
932
|
+
export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
|
|
933
|
+
| {
|
|
934
|
+
ok: true;
|
|
935
|
+
node: CanvasNodeState;
|
|
936
|
+
}
|
|
937
|
+
| {
|
|
938
|
+
ok: false;
|
|
939
|
+
error: string;
|
|
940
|
+
} {
|
|
911
941
|
if (typeof nodeRef.id === 'string' && nodeRef.id.trim().length > 0) {
|
|
912
942
|
const node = canvasState.getNode(nodeRef.id.trim());
|
|
913
943
|
if (!node) {
|
|
@@ -940,7 +970,10 @@ export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput): {
|
|
|
940
970
|
if (matches.length > 1) {
|
|
941
971
|
return {
|
|
942
972
|
ok: false,
|
|
943
|
-
error: `Search "${query}" is ambiguous. Matches: ${matches
|
|
973
|
+
error: `Search "${query}" is ambiguous. Matches: ${matches
|
|
974
|
+
.slice(0, 5)
|
|
975
|
+
.map((match) => `${match.title ?? match.id} (${match.id})`)
|
|
976
|
+
.join(', ')}`,
|
|
944
977
|
};
|
|
945
978
|
}
|
|
946
979
|
const node = canvasState.getNode(matches[0]!.id);
|
|
@@ -963,11 +996,12 @@ export async function refreshCanvasWebpageNode(
|
|
|
963
996
|
}
|
|
964
997
|
|
|
965
998
|
const currentData = existing.data;
|
|
966
|
-
const configuredUrl =
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
999
|
+
const configuredUrl =
|
|
1000
|
+
typeof options.url === 'string' && options.url.trim().length > 0
|
|
1001
|
+
? options.url
|
|
1002
|
+
: typeof currentData.url === 'string'
|
|
1003
|
+
? currentData.url
|
|
1004
|
+
: '';
|
|
971
1005
|
|
|
972
1006
|
let normalizedUrl: string;
|
|
973
1007
|
try {
|
|
@@ -1005,9 +1039,8 @@ export async function refreshCanvasWebpageNode(
|
|
|
1005
1039
|
const latestData = latest.data;
|
|
1006
1040
|
const titleSource = latestData.titleSource === 'user' ? 'user' : 'page';
|
|
1007
1041
|
const currentTitle = typeof latestData.title === 'string' ? latestData.title.trim() : '';
|
|
1008
|
-
const nextTitle =
|
|
1009
|
-
? currentTitle
|
|
1010
|
-
: snapshot.pageTitle ?? snapshot.url;
|
|
1042
|
+
const nextTitle =
|
|
1043
|
+
titleSource === 'user' && currentTitle.length > 0 ? currentTitle : (snapshot.pageTitle ?? snapshot.url);
|
|
1011
1044
|
|
|
1012
1045
|
const nextData: Record<string, unknown> = {
|
|
1013
1046
|
...latestData,
|
|
@@ -1203,9 +1236,9 @@ function shiftGridUpdatesBelowObstacles(
|
|
|
1203
1236
|
const deltaY = blockerBottom + GRID_OBSTACLE_GAP_Y - minPlannedY;
|
|
1204
1237
|
if (deltaY <= 0) return shifted;
|
|
1205
1238
|
|
|
1206
|
-
shifted = shifted.map((update) =>
|
|
1207
|
-
? { ...update, position: { x: update.position.x, y: update.position.y + deltaY } }
|
|
1208
|
-
|
|
1239
|
+
shifted = shifted.map((update) =>
|
|
1240
|
+
update.position ? { ...update, position: { x: update.position.x, y: update.position.y + deltaY } } : update,
|
|
1241
|
+
);
|
|
1209
1242
|
}
|
|
1210
1243
|
|
|
1211
1244
|
return shifted;
|
|
@@ -1213,9 +1246,8 @@ function shiftGridUpdatesBelowObstacles(
|
|
|
1213
1246
|
|
|
1214
1247
|
export function arrangeCanvasNodes(layout: CanvasArrangeMode): { arranged: number; layout: CanvasArrangeMode } {
|
|
1215
1248
|
const nodes = canvasState.getLayoutForPersistence().nodes;
|
|
1216
|
-
const excludedIds =
|
|
1217
|
-
? collectGridArrangeExcludedNodeIds(nodes)
|
|
1218
|
-
: collectArrangeExcludedNodeIds(nodes);
|
|
1249
|
+
const excludedIds =
|
|
1250
|
+
layout === 'grid' ? collectGridArrangeExcludedNodeIds(nodes) : collectArrangeExcludedNodeIds(nodes);
|
|
1219
1251
|
const movableNodes = nodes.filter((node) => !excludedIds.has(node.id));
|
|
1220
1252
|
const oldPositions = nodes.map((node) => ({ id: node.id, position: { ...node.position } }));
|
|
1221
1253
|
const oldSizes = nodes.map((node) => ({ id: node.id, size: { ...node.size } }));
|
|
@@ -1268,26 +1300,34 @@ export function arrangeCanvasNodes(layout: CanvasArrangeMode): { arranged: numbe
|
|
|
1268
1300
|
mutationHistory.record({
|
|
1269
1301
|
description: `Auto-arranged ${movableNodes.length} nodes (${layout})`,
|
|
1270
1302
|
operationType: 'arrange',
|
|
1271
|
-
forward: () =>
|
|
1272
|
-
canvasState.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1303
|
+
forward: () =>
|
|
1304
|
+
canvasState.withSuppressedRecording(() => {
|
|
1305
|
+
canvasState.applyUpdates(
|
|
1306
|
+
newPositions.map((position) => {
|
|
1307
|
+
const size = newSizesById.get(position.id);
|
|
1308
|
+
return {
|
|
1309
|
+
id: position.id,
|
|
1310
|
+
position: position.position,
|
|
1311
|
+
...(size ? { size } : {}),
|
|
1312
|
+
};
|
|
1313
|
+
}),
|
|
1314
|
+
layout === 'grid' ? { skipGroupChildTranslation: true } : {},
|
|
1315
|
+
);
|
|
1316
|
+
}),
|
|
1317
|
+
inverse: () =>
|
|
1318
|
+
canvasState.withSuppressedRecording(() => {
|
|
1319
|
+
canvasState.applyUpdates(
|
|
1320
|
+
oldPositions.map((position) => {
|
|
1321
|
+
const size = oldSizesById.get(position.id);
|
|
1322
|
+
return {
|
|
1323
|
+
id: position.id,
|
|
1324
|
+
position: position.position,
|
|
1325
|
+
...(size ? { size } : {}),
|
|
1326
|
+
};
|
|
1327
|
+
}),
|
|
1328
|
+
layout === 'grid' ? { skipGroupChildTranslation: true } : {},
|
|
1329
|
+
);
|
|
1330
|
+
}),
|
|
1291
1331
|
});
|
|
1292
1332
|
|
|
1293
1333
|
return { arranged: movableNodes.length, layout };
|
|
@@ -1302,7 +1342,8 @@ export function setCanvasContextPins(
|
|
|
1302
1342
|
nodeIds: string[],
|
|
1303
1343
|
mode: CanvasPinMode = 'set',
|
|
1304
1344
|
): { count: number; nodeIds: string[] } {
|
|
1305
|
-
const normalizePins = (ids: string[]): string[] =>
|
|
1345
|
+
const normalizePins = (ids: string[]): string[] =>
|
|
1346
|
+
ids.filter((id, index) => ids.indexOf(id) === index).slice(0, MAX_CONTEXT_PINS);
|
|
1306
1347
|
const normalizedNodeIds = normalizePins(nodeIds);
|
|
1307
1348
|
if (mode === 'set') {
|
|
1308
1349
|
canvasState.setContextPins(normalizedNodeIds);
|
|
@@ -1344,7 +1385,9 @@ export function deleteCanvasSnapshot(id: string): { ok: boolean } {
|
|
|
1344
1385
|
return { ok: canvasState.deleteSnapshot(id) };
|
|
1345
1386
|
}
|
|
1346
1387
|
|
|
1347
|
-
export function gcCanvasSnapshots(
|
|
1388
|
+
export function gcCanvasSnapshots(
|
|
1389
|
+
options?: Parameters<typeof canvasState.gcSnapshots>[0],
|
|
1390
|
+
): ReturnType<typeof canvasState.gcSnapshots> {
|
|
1348
1391
|
return canvasState.gcSnapshots(options);
|
|
1349
1392
|
}
|
|
1350
1393
|
|
|
@@ -1399,7 +1442,8 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): { id: string;
|
|
|
1399
1442
|
let y = input.y;
|
|
1400
1443
|
let width = input.width ?? 600;
|
|
1401
1444
|
let height = input.height ?? 400;
|
|
1402
|
-
const explicitFrame =
|
|
1445
|
+
const explicitFrame =
|
|
1446
|
+
input.x !== undefined || input.y !== undefined || input.width !== undefined || input.height !== undefined;
|
|
1403
1447
|
|
|
1404
1448
|
const childIds = input.childIds ?? [];
|
|
1405
1449
|
if (childIds.length > 0 && x === undefined && y === undefined) {
|
|
@@ -1415,9 +1459,10 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): { id: string;
|
|
|
1415
1459
|
}
|
|
1416
1460
|
}
|
|
1417
1461
|
|
|
1418
|
-
const position =
|
|
1419
|
-
|
|
1420
|
-
|
|
1462
|
+
const position =
|
|
1463
|
+
x !== undefined && y !== undefined
|
|
1464
|
+
? { x, y }
|
|
1465
|
+
: findOpenCanvasPosition(canvasState.getLayout().nodes, width, height);
|
|
1421
1466
|
|
|
1422
1467
|
const id = `group-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1423
1468
|
const data: Record<string, unknown> = {
|
|
@@ -1480,9 +1525,12 @@ export function clearCanvas(): { ok: boolean } {
|
|
|
1480
1525
|
return { ok: true };
|
|
1481
1526
|
}
|
|
1482
1527
|
|
|
1483
|
-
export function createCanvasJsonRenderNode(
|
|
1484
|
-
|
|
1485
|
-
|
|
1528
|
+
export function createCanvasJsonRenderNode(input: JsonRenderNodeInput): {
|
|
1529
|
+
id: string;
|
|
1530
|
+
url: string;
|
|
1531
|
+
spec: JsonRenderSpec;
|
|
1532
|
+
node: CanvasNodeState;
|
|
1533
|
+
} {
|
|
1486
1534
|
const spec = normalizeAndValidateJsonRenderSpec(input.spec);
|
|
1487
1535
|
const width = input.width ?? JSON_RENDER_NODE_SIZE.width;
|
|
1488
1536
|
const height = input.height ?? JSON_RENDER_NODE_SIZE.height;
|
|
@@ -1561,7 +1609,14 @@ export function appendCanvasJsonRenderStream(
|
|
|
1561
1609
|
patches: unknown[],
|
|
1562
1610
|
done: boolean,
|
|
1563
1611
|
):
|
|
1564
|
-
| {
|
|
1612
|
+
| {
|
|
1613
|
+
ok: true;
|
|
1614
|
+
applied: number;
|
|
1615
|
+
skipped: number;
|
|
1616
|
+
specVersion: number;
|
|
1617
|
+
elementCount: number;
|
|
1618
|
+
streamStatus: 'open' | 'closed';
|
|
1619
|
+
}
|
|
1565
1620
|
| { ok: false; error: string } {
|
|
1566
1621
|
const node = canvasState.getNode(nodeId);
|
|
1567
1622
|
if (!node) return { ok: false, error: `Node "${nodeId}" not found.` };
|
|
@@ -1577,14 +1632,16 @@ export function appendCanvasJsonRenderStream(
|
|
|
1577
1632
|
data: { ...node.data, spec, specVersion, streamStatus },
|
|
1578
1633
|
});
|
|
1579
1634
|
|
|
1580
|
-
const elementCount =
|
|
1581
|
-
spec.elements && typeof spec.elements === 'object' ? Object.keys(spec.elements).length : 0;
|
|
1635
|
+
const elementCount = spec.elements && typeof spec.elements === 'object' ? Object.keys(spec.elements).length : 0;
|
|
1582
1636
|
return { ok: true, applied, skipped, specVersion, elementCount, streamStatus };
|
|
1583
1637
|
}
|
|
1584
1638
|
|
|
1585
|
-
export function createCanvasGraphNode(
|
|
1586
|
-
|
|
1587
|
-
|
|
1639
|
+
export function createCanvasGraphNode(input: GraphNodeInput): {
|
|
1640
|
+
id: string;
|
|
1641
|
+
url: string;
|
|
1642
|
+
spec: JsonRenderSpec;
|
|
1643
|
+
node: CanvasNodeState;
|
|
1644
|
+
} {
|
|
1588
1645
|
const title = input.title?.trim() || 'Graph';
|
|
1589
1646
|
const spec = buildGraphSpec(input);
|
|
1590
1647
|
const width = input.width ?? GRAPH_NODE_SIZE.width;
|
|
@@ -225,10 +225,7 @@ export function inferCanvasNodeProvenance(
|
|
|
225
225
|
return null;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
export function normalizeCanvasNodeData<T extends Record<string, unknown>>(
|
|
229
|
-
nodeType: CanvasNodeType,
|
|
230
|
-
data: T,
|
|
231
|
-
): T {
|
|
228
|
+
export function normalizeCanvasNodeData<T extends Record<string, unknown>>(nodeType: CanvasNodeType, data: T): T {
|
|
232
229
|
const semanticData = nodeType === 'html' ? normalizeHtmlNodeSemanticData(data) : data;
|
|
233
230
|
const existing = normalizeExistingProvenance(semanticData.provenance);
|
|
234
231
|
const inferred = inferCanvasNodeProvenance(nodeType, semanticData);
|