pmx-canvas 0.8.2 → 0.9.0
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/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +49 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +52 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +216 -42
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +87 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +533 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +0 -1
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +206 -29
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +195 -83
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +178 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +181 -11
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +26 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +74 -25
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { basename, resolve } from 'node:path';
|
|
3
4
|
import { agentPresence } from './agent-presence.js';
|
|
4
5
|
import { recomputeCodeGraph } from './code-graph.js';
|
|
@@ -22,12 +23,14 @@ import {
|
|
|
22
23
|
} from './mcp-app-runtime.js';
|
|
23
24
|
import { mutationHistory } from './mutation-history.js';
|
|
24
25
|
import { intentRegistry } from './intent-registry.js';
|
|
26
|
+
import { OperationError } from './operations/types.js';
|
|
25
27
|
import { clampCreateNodeSize } from './canvas-validation.js';
|
|
26
28
|
import { validCanvasNodeUpdates } from './canvas-geometry.js';
|
|
27
29
|
import { computeGroupBounds, findOpenCanvasPosition } from './placement.js';
|
|
28
30
|
import { searchNodes } from './spatial-analysis.js';
|
|
29
31
|
import { getCanvasNodeTitle } from './canvas-serialization.js';
|
|
30
32
|
import { computeAutoArrange } from '../shared/auto-arrange.js';
|
|
33
|
+
import { relationKindSchema, relationReasonSchema } from '../shared/relations.js';
|
|
31
34
|
import {
|
|
32
35
|
applyJsonRenderStreamPatches,
|
|
33
36
|
buildGraphSpec,
|
|
@@ -93,6 +96,7 @@ export interface CanvasStructuredNodeUpdateInput extends Omit<CanvasGraphNodeUpd
|
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
interface CanvasAddNodeInput {
|
|
99
|
+
prepareOnlyNodes?: CanvasNodeState[];
|
|
96
100
|
type: CanvasNodeState['type'];
|
|
97
101
|
title?: string;
|
|
98
102
|
content?: string;
|
|
@@ -150,6 +154,10 @@ export function describeCreateSize(
|
|
|
150
154
|
// surface's sizeWarnings.
|
|
151
155
|
|
|
152
156
|
interface CanvasCreateGroupInput {
|
|
157
|
+
destinationBoardId?: string;
|
|
158
|
+
requestId?: string;
|
|
159
|
+
expectedBoardId?: string | null;
|
|
160
|
+
intentId?: string;
|
|
153
161
|
title?: string;
|
|
154
162
|
childIds?: string[];
|
|
155
163
|
x?: number;
|
|
@@ -163,6 +171,7 @@ interface CanvasCreateGroupInput {
|
|
|
163
171
|
interface CanvasNodeLookupInput {
|
|
164
172
|
id?: string;
|
|
165
173
|
search?: string;
|
|
174
|
+
board?: string;
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
const MAX_CONTEXT_PINS = 20;
|
|
@@ -424,6 +433,7 @@ export function buildGraphNodeUpdate(
|
|
|
424
433
|
const source = isGraphPayloadLike(input.spec)
|
|
425
434
|
? input.spec
|
|
426
435
|
: Object.fromEntries(Object.entries(input).filter(([key, value]) => key !== 'spec' && value !== undefined));
|
|
436
|
+
const specVersion = (typeof node.data.specVersion === 'number' ? node.data.specVersion : 0) + 1;
|
|
427
437
|
|
|
428
438
|
if (input.spec !== undefined && !isGraphPayloadLike(input.spec)) {
|
|
429
439
|
const spec = normalizeAndValidateJsonRenderSpec(input.spec);
|
|
@@ -436,6 +446,7 @@ export function buildGraphNodeUpdate(
|
|
|
436
446
|
...createJsonRenderNodeData(node.id, title, spec, {
|
|
437
447
|
viewerType: 'graph',
|
|
438
448
|
graphConfig: currentConfig,
|
|
449
|
+
specVersion,
|
|
439
450
|
}),
|
|
440
451
|
},
|
|
441
452
|
};
|
|
@@ -453,6 +464,7 @@ export function buildGraphNodeUpdate(
|
|
|
453
464
|
...createJsonRenderNodeData(node.id, title, spec, {
|
|
454
465
|
viewerType: 'graph',
|
|
455
466
|
graphConfig,
|
|
467
|
+
specVersion,
|
|
456
468
|
}),
|
|
457
469
|
},
|
|
458
470
|
};
|
|
@@ -791,7 +803,7 @@ function buildImageNodeData(input: CanvasAddNodeInput): Record<string, unknown>
|
|
|
791
803
|
}
|
|
792
804
|
|
|
793
805
|
if (!isDataUri && !isUrl && src) {
|
|
794
|
-
const resolved = resolve(src);
|
|
806
|
+
const resolved = resolve(canvasState.getWorkspaceRoot(), src);
|
|
795
807
|
const fileName = basename(resolved) || src;
|
|
796
808
|
const { mimeType } = validateLocalImageFile(resolved);
|
|
797
809
|
return {
|
|
@@ -949,7 +961,7 @@ export function addCanvasNode(input: CanvasAddNodeInput): {
|
|
|
949
961
|
const position =
|
|
950
962
|
input.x !== undefined && input.y !== undefined
|
|
951
963
|
? { x: input.x, y: input.y }
|
|
952
|
-
: findOpenCanvasPosition(canvasState.getLayout().nodes, width, height);
|
|
964
|
+
: findOpenCanvasPosition(input.prepareOnlyNodes ?? canvasState.getLayout().nodes, width, height);
|
|
953
965
|
const id = `node-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
954
966
|
const data = buildNodeData(input);
|
|
955
967
|
const node: CanvasNodeState = {
|
|
@@ -963,6 +975,7 @@ export function addCanvasNode(input: CanvasAddNodeInput): {
|
|
|
963
975
|
data,
|
|
964
976
|
};
|
|
965
977
|
|
|
978
|
+
if (input.prepareOnlyNodes) return { id, node, needsCodeGraphRecompute: false };
|
|
966
979
|
canvasState.addNode(node);
|
|
967
980
|
const storedNode = canvasState.getNode(id) ?? node;
|
|
968
981
|
|
|
@@ -992,8 +1005,12 @@ export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
|
|
|
992
1005
|
ok: false;
|
|
993
1006
|
error: string;
|
|
994
1007
|
} {
|
|
1008
|
+
const remote = nodeRef.board ? canvasState.readBoard(nodeRef.board, true) : null;
|
|
1009
|
+
if (nodeRef.board && !remote) return { ok: false, error: `Board "${nodeRef.board}" not found.` };
|
|
1010
|
+
const getNode = (id: string) =>
|
|
1011
|
+
remote ? remote.layout.nodes.find((node) => node.id === id) : canvasState.getNode(id);
|
|
995
1012
|
if (typeof nodeRef.id === 'string' && nodeRef.id.trim().length > 0) {
|
|
996
|
-
const node =
|
|
1013
|
+
const node = getNode(nodeRef.id.trim());
|
|
997
1014
|
if (!node) {
|
|
998
1015
|
return { ok: false, error: `Node "${nodeRef.id}" not found.` };
|
|
999
1016
|
}
|
|
@@ -1002,7 +1019,7 @@ export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
|
|
|
1002
1019
|
|
|
1003
1020
|
if (typeof nodeRef.search === 'string' && nodeRef.search.trim().length > 0) {
|
|
1004
1021
|
const query = nodeRef.search.trim();
|
|
1005
|
-
const layout = canvasState.getLayout();
|
|
1022
|
+
const layout = remote?.layout ?? canvasState.getLayout();
|
|
1006
1023
|
const exactTitleMatches = layout.nodes.filter((node) => {
|
|
1007
1024
|
const title = getCanvasNodeTitle(node);
|
|
1008
1025
|
return title !== null && title.toLowerCase() === query.toLowerCase();
|
|
@@ -1030,7 +1047,7 @@ export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
|
|
|
1030
1047
|
.join(', ')}`,
|
|
1031
1048
|
};
|
|
1032
1049
|
}
|
|
1033
|
-
const node =
|
|
1050
|
+
const node = getNode(matches[0]!.id);
|
|
1034
1051
|
if (!node) {
|
|
1035
1052
|
return { ok: false, error: `Resolved node "${matches[0]!.id}" disappeared.` };
|
|
1036
1053
|
}
|
|
@@ -1406,7 +1423,8 @@ export function setCanvasContextPins(
|
|
|
1406
1423
|
canvasState.setContextPins(normalizePins([...current, ...normalizedNodeIds]), options);
|
|
1407
1424
|
} else {
|
|
1408
1425
|
const current = Array.from(canvasState.contextPinnedNodeIds);
|
|
1409
|
-
|
|
1426
|
+
// Limit live pins, not a merged receipt's list of IDs to remove.
|
|
1427
|
+
canvasState.setContextPins(current.filter((id) => !nodeIds.includes(id)));
|
|
1410
1428
|
}
|
|
1411
1429
|
|
|
1412
1430
|
return {
|
|
@@ -1522,50 +1540,76 @@ export function gcCanvasSnapshots(
|
|
|
1522
1540
|
}
|
|
1523
1541
|
|
|
1524
1542
|
export function addCanvasEdge(input: {
|
|
1543
|
+
destinationBoardId?: string;
|
|
1544
|
+
requestId?: string;
|
|
1545
|
+
expectedBoardId?: string | null;
|
|
1546
|
+
intentId?: string;
|
|
1525
1547
|
from?: string;
|
|
1526
1548
|
to?: string;
|
|
1549
|
+
toBoardId?: string;
|
|
1527
1550
|
fromSearch?: string;
|
|
1528
1551
|
toSearch?: string;
|
|
1529
1552
|
type: CanvasEdge['type'];
|
|
1553
|
+
kind?: string;
|
|
1554
|
+
reason?: string;
|
|
1530
1555
|
label?: string;
|
|
1531
1556
|
style?: CanvasEdge['style'];
|
|
1532
1557
|
animated?: boolean;
|
|
1533
1558
|
}): CanvasEdge {
|
|
1534
|
-
const
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1559
|
+
const stored =
|
|
1560
|
+
input.destinationBoardId !== undefined || input.requestId !== undefined
|
|
1561
|
+
? storedCreationTarget(input, 'edge.add')
|
|
1562
|
+
: undefined;
|
|
1563
|
+
const build = (): CanvasEdge => {
|
|
1564
|
+
const kind = input.kind === undefined ? undefined : relationKindSchema.parse(input.kind);
|
|
1565
|
+
const reason = input.reason === undefined ? undefined : relationReasonSchema.parse(input.reason);
|
|
1566
|
+
const fromResult = resolveCanvasNode({
|
|
1567
|
+
...(typeof input.from === 'string' ? { id: input.from } : {}),
|
|
1568
|
+
...(typeof input.fromSearch === 'string' ? { search: input.fromSearch } : {}),
|
|
1569
|
+
...(stored ? { board: stored.boardId } : {}),
|
|
1570
|
+
});
|
|
1571
|
+
if (!fromResult.ok) {
|
|
1572
|
+
throw new Error(fromResult.error);
|
|
1573
|
+
}
|
|
1574
|
+
const toResult = resolveCanvasNode({
|
|
1575
|
+
...(typeof input.to === 'string' ? { id: input.to } : {}),
|
|
1576
|
+
...(typeof input.toSearch === 'string' ? { search: input.toSearch } : {}),
|
|
1577
|
+
...(input.toBoardId || stored ? { board: input.toBoardId ?? stored!.boardId } : {}),
|
|
1578
|
+
});
|
|
1579
|
+
if (!toResult.ok) {
|
|
1580
|
+
throw new Error(toResult.error);
|
|
1581
|
+
}
|
|
1548
1582
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1583
|
+
const id = `edge-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1584
|
+
const edge: CanvasEdge = {
|
|
1585
|
+
id,
|
|
1586
|
+
from: fromResult.node.id,
|
|
1587
|
+
to: toResult.node.id,
|
|
1588
|
+
...(input.toBoardId && input.toBoardId !== (stored?.boardId ?? canvasState.activeBoardId)
|
|
1589
|
+
? { toBoardId: input.toBoardId }
|
|
1590
|
+
: {}),
|
|
1591
|
+
type: input.type,
|
|
1592
|
+
...(kind ? { kind } : {}),
|
|
1593
|
+
...(reason ? { reason } : {}),
|
|
1594
|
+
...(input.label ? { label: input.label } : {}),
|
|
1595
|
+
...(input.style ? { style: input.style } : {}),
|
|
1596
|
+
...(input.animated !== undefined ? { animated: input.animated } : {}),
|
|
1597
|
+
};
|
|
1598
|
+
return edge;
|
|
1558
1599
|
};
|
|
1600
|
+
if (stored) return canvasState.createStoredEdge(stored.boardId, stored.requestId, stored.fingerprint, build);
|
|
1601
|
+
const edge = build();
|
|
1559
1602
|
const added = canvasState.addEdge(edge);
|
|
1560
1603
|
if (!added) {
|
|
1561
1604
|
throw new Error('Duplicate or self-edge.');
|
|
1562
1605
|
}
|
|
1563
|
-
|
|
1606
|
+
// The stored link, stamped with its author and revision — the browser replaces its copy with this.
|
|
1607
|
+
return canvasState.getEdges().find((stored) => stored.id === edge.id) ?? edge;
|
|
1564
1608
|
}
|
|
1565
1609
|
|
|
1566
1610
|
export function updateCanvasEdge(
|
|
1567
1611
|
id: string,
|
|
1568
|
-
patch: Partial<Pick<CanvasEdge, 'type' | 'label' | 'style' | 'animated'>>,
|
|
1612
|
+
patch: Partial<Pick<CanvasEdge, 'type' | 'kind' | 'reason' | 'label' | 'style' | 'animated'>>,
|
|
1569
1613
|
): CanvasEdge {
|
|
1570
1614
|
if (!canvasState.getEdges().some((edge) => edge.id === id)) {
|
|
1571
1615
|
throw new Error(`Edge "${id}" not found.`);
|
|
@@ -1586,52 +1630,73 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): {
|
|
|
1586
1630
|
node: CanvasNodeState;
|
|
1587
1631
|
sizeAdjustment?: CanvasSizeAdjustment;
|
|
1588
1632
|
} {
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1633
|
+
const stored =
|
|
1634
|
+
input.destinationBoardId !== undefined || input.requestId !== undefined
|
|
1635
|
+
? storedCreationTarget(
|
|
1636
|
+
{ ...input, title: input.title ?? 'Group', childIds: input.childIds ?? [] },
|
|
1637
|
+
'group.create',
|
|
1638
|
+
)
|
|
1639
|
+
: undefined;
|
|
1593
1640
|
const explicitFrame =
|
|
1594
1641
|
input.x !== undefined || input.y !== undefined || input.width !== undefined || input.height !== undefined;
|
|
1595
|
-
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1642
|
+
const childIds = [...new Set(input.childIds ?? [])];
|
|
1643
|
+
const build = (nodes: CanvasNodeState[]): CanvasNodeState => {
|
|
1644
|
+
const missing = childIds.filter((id) => !nodes.some((node) => node.id === id));
|
|
1645
|
+
if (stored && missing.length)
|
|
1646
|
+
throw new OperationError(`Cannot create group: missing child node IDs: ${missing.join(', ')}.`);
|
|
1647
|
+
// Selecting both a frame and its descendant would create conflicting memberships.
|
|
1648
|
+
for (const id of stored ? childIds : []) {
|
|
1649
|
+
let parent = nodes.find((node) => node.id === id)?.data.parentGroup;
|
|
1650
|
+
while (typeof parent === 'string') {
|
|
1651
|
+
if (childIds.includes(parent)) throw new OperationError('Cannot group both an ancestor and its descendant.');
|
|
1652
|
+
parent = nodes.find((node) => node.id === parent)?.data.parentGroup;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
let x = input.x;
|
|
1656
|
+
let y = input.y;
|
|
1657
|
+
let width = input.width ?? 600;
|
|
1658
|
+
let height = input.height ?? 400;
|
|
1659
|
+
if (childIds.length > 0 && x === undefined && y === undefined) {
|
|
1660
|
+
const childRects = childIds
|
|
1661
|
+
.map((cid) => nodes.find((node) => node.id === cid))
|
|
1662
|
+
.filter((node): node is CanvasNodeState => node !== undefined);
|
|
1663
|
+
const bounds = computeGroupBounds(childRects);
|
|
1664
|
+
if (bounds) {
|
|
1665
|
+
x = bounds.x;
|
|
1666
|
+
y = bounds.y;
|
|
1667
|
+
width = bounds.width;
|
|
1668
|
+
height = bounds.height;
|
|
1669
|
+
}
|
|
1607
1670
|
}
|
|
1608
|
-
}
|
|
1609
1671
|
|
|
1610
|
-
|
|
1611
|
-
x !== undefined && y !== undefined
|
|
1612
|
-
? { x, y }
|
|
1613
|
-
: findOpenCanvasPosition(canvasState.getLayout().nodes, width, height);
|
|
1672
|
+
const position = x !== undefined && y !== undefined ? { x, y } : findOpenCanvasPosition(nodes, width, height);
|
|
1614
1673
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1674
|
+
const id = `group-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1675
|
+
const data: Record<string, unknown> = {
|
|
1676
|
+
title: input.title ?? 'Group',
|
|
1677
|
+
children: [],
|
|
1678
|
+
frameMode: explicitFrame ? 'manual' : 'fit',
|
|
1679
|
+
...(input.color ? { color: input.color } : {}),
|
|
1680
|
+
};
|
|
1622
1681
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1682
|
+
return {
|
|
1683
|
+
id,
|
|
1684
|
+
type: 'group',
|
|
1685
|
+
position,
|
|
1686
|
+
size: { width, height },
|
|
1687
|
+
zIndex: 0,
|
|
1688
|
+
collapsed: false,
|
|
1689
|
+
pinned: false,
|
|
1690
|
+
data: { ...data, children: stored ? childIds : [] },
|
|
1691
|
+
};
|
|
1692
|
+
};
|
|
1693
|
+
const created = stored
|
|
1694
|
+
? canvasState.createStoredGroup(stored.boardId, stored.requestId, stored.fingerprint, build, input.childLayout)
|
|
1695
|
+
: build(canvasState.getLayout().nodes);
|
|
1696
|
+
const id = created.id;
|
|
1697
|
+
if (!stored) canvasState.addNode(created);
|
|
1633
1698
|
|
|
1634
|
-
if (childIds.length > 0) {
|
|
1699
|
+
if (!stored && childIds.length > 0) {
|
|
1635
1700
|
canvasState.groupNodes(id, childIds, {
|
|
1636
1701
|
preservePositions: input.childLayout === undefined,
|
|
1637
1702
|
...(input.childLayout ? { layout: input.childLayout } : {}),
|
|
@@ -1639,7 +1704,7 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): {
|
|
|
1639
1704
|
});
|
|
1640
1705
|
}
|
|
1641
1706
|
|
|
1642
|
-
const node = canvasState.getNode(id);
|
|
1707
|
+
const node = stored ? created : canvasState.getNode(id);
|
|
1643
1708
|
if (!node) {
|
|
1644
1709
|
throw new Error(`Group "${id}" was not created.`);
|
|
1645
1710
|
}
|
|
@@ -1660,6 +1725,41 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): {
|
|
|
1660
1725
|
};
|
|
1661
1726
|
}
|
|
1662
1727
|
|
|
1728
|
+
/** One logical creation identity across node, frame and link operations. */
|
|
1729
|
+
export function storedCreationTarget(input: object, operation: string) {
|
|
1730
|
+
const body = input as Record<string, unknown>;
|
|
1731
|
+
for (const key of ['destinationBoardId', 'requestId']) {
|
|
1732
|
+
if (body[key] !== undefined && (typeof body[key] !== 'string' || !body[key].trim()))
|
|
1733
|
+
throw new OperationError(`${key} must be a non-empty string.`);
|
|
1734
|
+
}
|
|
1735
|
+
if (body.expectedBoardId !== undefined && body.expectedBoardId !== canvasState.activeBoardId)
|
|
1736
|
+
throw new OperationError('The active board changed. Refresh before retrying this action.', 409);
|
|
1737
|
+
if (body.intentId !== undefined)
|
|
1738
|
+
throw new OperationError('Destination/retry creation does not support ghost intents.');
|
|
1739
|
+
const boardId = typeof body.destinationBoardId === 'string' ? body.destinationBoardId : canvasState.activeBoardId;
|
|
1740
|
+
if (!boardId) throw new OperationError('Open a board or provide destinationBoardId.');
|
|
1741
|
+
const ignored = new Set(['requestId', 'destinationBoardId', 'expectedBoardId', 'full', 'verbose', 'agentId']);
|
|
1742
|
+
const canonical = (value: unknown): unknown =>
|
|
1743
|
+
Array.isArray(value)
|
|
1744
|
+
? value.map(canonical)
|
|
1745
|
+
: isRecord(value)
|
|
1746
|
+
? Object.fromEntries(
|
|
1747
|
+
Object.keys(value)
|
|
1748
|
+
.sort()
|
|
1749
|
+
.filter((key) => value[key] !== undefined)
|
|
1750
|
+
.map((key) => [key, canonical(value[key])]),
|
|
1751
|
+
)
|
|
1752
|
+
: value;
|
|
1753
|
+
const fingerprint = createHash('sha256')
|
|
1754
|
+
.update(JSON.stringify(canonical(Object.fromEntries(Object.entries(body).filter(([key]) => !ignored.has(key))))))
|
|
1755
|
+
.digest('hex');
|
|
1756
|
+
return {
|
|
1757
|
+
boardId,
|
|
1758
|
+
requestId: typeof body.requestId === 'string' ? body.requestId : undefined,
|
|
1759
|
+
fingerprint: operation === 'node.add' ? fingerprint : `${operation}:${fingerprint}`,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1663
1763
|
export function groupCanvasNodes(
|
|
1664
1764
|
groupId: string,
|
|
1665
1765
|
childIds: string[],
|
|
@@ -1893,16 +1993,21 @@ export function createCanvasGraphNode(input: GraphNodeInput): {
|
|
|
1893
1993
|
* fit call still win. In-memory only (a live client fact, never persisted);
|
|
1894
1994
|
* with several clients connected, the most recent reporter wins.
|
|
1895
1995
|
*/
|
|
1896
|
-
let lastClientViewportSize: {
|
|
1897
|
-
|
|
1898
|
-
|
|
1996
|
+
let lastClientViewportSize: {
|
|
1997
|
+
width: number;
|
|
1998
|
+
height: number;
|
|
1999
|
+
boardId: string | null;
|
|
2000
|
+
insets?: { top: number; bottom: number };
|
|
2001
|
+
} | null = null;
|
|
2002
|
+
|
|
2003
|
+
export function setClientViewportSize(width: unknown, height: unknown, insets?: { top: number; bottom: number }): void {
|
|
1899
2004
|
if (typeof width !== 'number' || typeof height !== 'number') return;
|
|
1900
2005
|
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
|
|
1901
|
-
lastClientViewportSize = { width, height };
|
|
2006
|
+
lastClientViewportSize = { width, height, boardId: canvasState.activeBoardId, insets };
|
|
1902
2007
|
}
|
|
1903
2008
|
|
|
1904
2009
|
export function getClientViewportSize(): { width: number; height: number } | null {
|
|
1905
|
-
return lastClientViewportSize ? {
|
|
2010
|
+
return lastClientViewportSize ? { width: lastClientViewportSize.width, height: lastClientViewportSize.height } : null;
|
|
1906
2011
|
}
|
|
1907
2012
|
|
|
1908
2013
|
export function fitCanvasView(options: CanvasFitViewOptions = {}): CanvasFitViewResult {
|
|
@@ -1911,7 +2016,8 @@ export function fitCanvasView(options: CanvasFitViewOptions = {}): CanvasFitView
|
|
|
1911
2016
|
const padding = positiveNumber(options.padding, 24); // screen px now, not world units
|
|
1912
2017
|
const maxScale = positiveNumber(options.maxScale, 1);
|
|
1913
2018
|
const nodeIdFilter = options.nodeIds && options.nodeIds.length > 0 ? new Set(options.nodeIds) : null;
|
|
1914
|
-
const
|
|
2019
|
+
const layout = canvasState.getLayout();
|
|
2020
|
+
const targetNodes = layout.nodes.filter((node) => !nodeIdFilter || nodeIdFilter.has(node.id));
|
|
1915
2021
|
|
|
1916
2022
|
if (targetNodes.length === 0) {
|
|
1917
2023
|
const viewport = canvasState.viewport;
|
|
@@ -1933,12 +2039,18 @@ export function fitCanvasView(options: CanvasFitViewOptions = {}): CanvasFitView
|
|
|
1933
2039
|
// Screen-space fit (architecture rule 9): `padding` and the chrome reserve
|
|
1934
2040
|
// are SCREEN pixels — world-space padding shrank with the fit zoom and
|
|
1935
2041
|
// parked nodes under the command bar and minimap (0.5.0 Amp report,
|
|
1936
|
-
// finding A). The
|
|
1937
|
-
//
|
|
1938
|
-
|
|
1939
|
-
|
|
2042
|
+
// finding A). The workbench reports its measured chrome with its size;
|
|
2043
|
+
// without a matching board and viewport size, retain nominal chrome reserves.
|
|
2044
|
+
const insets =
|
|
2045
|
+
lastClientViewportSize?.boardId === canvasState.activeBoardId &&
|
|
2046
|
+
lastClientViewportSize.width === width &&
|
|
2047
|
+
lastClientViewportSize.height === height
|
|
2048
|
+
? lastClientViewportSize.insets
|
|
2049
|
+
: undefined;
|
|
2050
|
+
const bottomReserve = insets?.bottom ?? (agentPresence.snapshot().sessionActive ? 128 : 96);
|
|
2051
|
+
const topReserve = Math.max(padding, insets?.top ?? (layout.edges.length ? 90 : 48));
|
|
1940
2052
|
const usableWidth = Math.max(80, width - padding * 2);
|
|
1941
|
-
const usableHeight = Math.max(80, height -
|
|
2053
|
+
const usableHeight = Math.max(80, height - topReserve - bottomReserve);
|
|
1942
2054
|
const worldWidth = Math.max(1, bounds.width);
|
|
1943
2055
|
const worldHeight = Math.max(1, bounds.height);
|
|
1944
2056
|
const scale = Math.min(maxScale, usableWidth / worldWidth, usableHeight / worldHeight);
|
|
@@ -1946,7 +2058,7 @@ export function fitCanvasView(options: CanvasFitViewOptions = {}): CanvasFitView
|
|
|
1946
2058
|
const centerY = minY + bounds.height / 2;
|
|
1947
2059
|
const viewport = {
|
|
1948
2060
|
x: padding + usableWidth / 2 - centerX * scale,
|
|
1949
|
-
y:
|
|
2061
|
+
y: topReserve + usableHeight / 2 - centerY * scale,
|
|
1950
2062
|
scale,
|
|
1951
2063
|
};
|
|
1952
2064
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { normalizeHtmlNodeSemanticData } from './html-node-summary.js';
|
|
4
|
+
import { OperationError } from './operations/types.js';
|
|
3
5
|
|
|
4
6
|
export type CanvasNodeType =
|
|
5
7
|
| 'markdown'
|
|
@@ -26,7 +28,8 @@ export type CanvasNodeProvenanceSourceKind =
|
|
|
26
28
|
| 'webpage-url'
|
|
27
29
|
| 'mcp-tool'
|
|
28
30
|
| 'artifact-file'
|
|
29
|
-
| 'image-url'
|
|
31
|
+
| 'image-url'
|
|
32
|
+
| 'recipe';
|
|
30
33
|
|
|
31
34
|
export type CanvasNodeRefreshStrategy =
|
|
32
35
|
| 'file-watch'
|
|
@@ -34,7 +37,21 @@ export type CanvasNodeRefreshStrategy =
|
|
|
34
37
|
| 'image-reload'
|
|
35
38
|
| 'webpage-refresh'
|
|
36
39
|
| 'mcp-app-rehydrate'
|
|
37
|
-
| 'artifact-reopen'
|
|
40
|
+
| 'artifact-reopen'
|
|
41
|
+
| 'agent-request';
|
|
42
|
+
|
|
43
|
+
const observedSourceSchema = z.object({
|
|
44
|
+
boardId: z.string().min(1),
|
|
45
|
+
cardId: z.string().min(1),
|
|
46
|
+
contentRevision: z.number().int().nonnegative().optional(),
|
|
47
|
+
observedAt: z.iso.datetime().optional(),
|
|
48
|
+
availability: z.enum(['live', 'cached', 'unavailable']).optional(),
|
|
49
|
+
});
|
|
50
|
+
const recipeSchema = z.object({
|
|
51
|
+
tool: z.string().max(200),
|
|
52
|
+
input: z.unknown().optional(),
|
|
53
|
+
prompt: z.string().max(20000),
|
|
54
|
+
});
|
|
38
55
|
|
|
39
56
|
export interface CanvasNodeProvenance {
|
|
40
57
|
sourceKind: CanvasNodeProvenanceSourceKind;
|
|
@@ -42,6 +59,8 @@ export interface CanvasNodeProvenance {
|
|
|
42
59
|
refreshStrategy: CanvasNodeRefreshStrategy;
|
|
43
60
|
snapshotContent: boolean;
|
|
44
61
|
syncedAt?: string;
|
|
62
|
+
sources?: Array<z.infer<typeof observedSourceSchema>>;
|
|
63
|
+
recipe?: z.infer<typeof recipeSchema>;
|
|
45
64
|
details?: Record<string, unknown>;
|
|
46
65
|
}
|
|
47
66
|
|
|
@@ -65,28 +84,40 @@ function buildMcpToolUri(serverName: string, toolName: string): string {
|
|
|
65
84
|
return `mcp-tool://${encodeURIComponent(serverName)}/${encodeURIComponent(toolName)}`;
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const provenanceSchema = z.object({
|
|
88
|
+
sourceKind: z.enum(['workspace-file', 'webpage-url', 'mcp-tool', 'artifact-file', 'image-url', 'recipe']),
|
|
89
|
+
sourceUri: z.string().trim().min(1),
|
|
90
|
+
refreshStrategy: z.enum([
|
|
91
|
+
'file-watch',
|
|
92
|
+
'file-read-write',
|
|
93
|
+
'image-reload',
|
|
94
|
+
'webpage-refresh',
|
|
95
|
+
'mcp-app-rehydrate',
|
|
96
|
+
'artifact-reopen',
|
|
97
|
+
'agent-request',
|
|
98
|
+
]),
|
|
99
|
+
snapshotContent: z.boolean().default(true),
|
|
100
|
+
syncedAt: z.string().trim().min(1).optional(),
|
|
101
|
+
sources: z
|
|
102
|
+
.array(observedSourceSchema)
|
|
103
|
+
.max(100)
|
|
104
|
+
.refine(
|
|
105
|
+
(sources) =>
|
|
106
|
+
new Set(sources.map((source) => JSON.stringify([source.boardId, source.cardId]))).size === sources.length,
|
|
107
|
+
'Source targets must be unique',
|
|
108
|
+
)
|
|
109
|
+
.optional(),
|
|
110
|
+
recipe: recipeSchema.optional(),
|
|
111
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
112
|
+
});
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
114
|
+
function normalizeExistingProvenance(value: unknown): CanvasNodeProvenance | null {
|
|
115
|
+
if (value === undefined) return null;
|
|
116
|
+
const result = z.object({ provenance: provenanceSchema }).safeParse({ provenance: value });
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
throw new OperationError(`Invalid node data: ${z.prettifyError(result.error)}`);
|
|
87
119
|
}
|
|
88
|
-
|
|
89
|
-
return normalized;
|
|
120
|
+
return result.data.provenance;
|
|
90
121
|
}
|
|
91
122
|
|
|
92
123
|
function mergeProvenance(
|
|
@@ -4,8 +4,10 @@ import type { CanvasAnnotation, CanvasLayout, CanvasNodeState, ViewportState } f
|
|
|
4
4
|
import { normalizeCanvasNodeData, type CanvasNodeProvenance } from './canvas-provenance.js';
|
|
5
5
|
import { getCanvasNodeKind as getSharedCanvasNodeKind } from '../shared/canvas-node-kind.js';
|
|
6
6
|
import { canOpenNodeAsSurface } from '../shared/surface.js';
|
|
7
|
+
import type { LifecycleQualification } from '../shared/lifecycle-status.js';
|
|
7
8
|
|
|
8
9
|
export interface SerializedCanvasNode extends CanvasNodeState {
|
|
10
|
+
qualification?: LifecycleQualification;
|
|
9
11
|
kind: string;
|
|
10
12
|
title: string | null;
|
|
11
13
|
content: string | null;
|