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
|
@@ -87,13 +87,16 @@ export function SnapshotPanel({
|
|
|
87
87
|
}
|
|
88
88
|
}, [nameInput]);
|
|
89
89
|
|
|
90
|
-
const handleRestore = useCallback(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const handleRestore = useCallback(
|
|
91
|
+
async (id: string) => {
|
|
92
|
+
setConfirming(null);
|
|
93
|
+
setRestoringId(id);
|
|
94
|
+
const result = await restoreSnapshot(id);
|
|
95
|
+
setRestoringId(null);
|
|
96
|
+
if (result.ok) onClose();
|
|
97
|
+
},
|
|
98
|
+
[onClose],
|
|
99
|
+
);
|
|
97
100
|
|
|
98
101
|
const handleDelete = useCallback(async (id: string) => {
|
|
99
102
|
const result = await deleteSnapshot(id);
|
|
@@ -111,20 +114,11 @@ export function SnapshotPanel({
|
|
|
111
114
|
const top = anchorRect ? anchorRect.bottom + 8 : 48;
|
|
112
115
|
|
|
113
116
|
return (
|
|
114
|
-
<div
|
|
115
|
-
ref={panelRef}
|
|
116
|
-
class="snapshot-panel"
|
|
117
|
-
style={{ left: `${left}px`, top: `${top}px` }}
|
|
118
|
-
>
|
|
117
|
+
<div ref={panelRef} class="snapshot-panel" style={{ left: `${left}px`, top: `${top}px` }}>
|
|
119
118
|
{/* Header */}
|
|
120
119
|
<div class="snapshot-panel-header">
|
|
121
120
|
<span class="snapshot-panel-title">Snapshots</span>
|
|
122
|
-
<button
|
|
123
|
-
type="button"
|
|
124
|
-
class="snapshot-panel-close"
|
|
125
|
-
onClick={onClose}
|
|
126
|
-
title="Close"
|
|
127
|
-
>
|
|
121
|
+
<button type="button" class="snapshot-panel-close" onClick={onClose} title="Close">
|
|
128
122
|
{'\u00d7'}
|
|
129
123
|
</button>
|
|
130
124
|
</div>
|
|
@@ -137,99 +131,87 @@ export function SnapshotPanel({
|
|
|
137
131
|
class="snapshot-name-input"
|
|
138
132
|
value={nameInput}
|
|
139
133
|
onInput={(e) => setNameInput((e.target as HTMLInputElement).value)}
|
|
140
|
-
onKeyDown={(e) => {
|
|
134
|
+
onKeyDown={(e) => {
|
|
135
|
+
if (e.key === 'Enter') handleSave();
|
|
136
|
+
}}
|
|
141
137
|
placeholder="Snapshot name..."
|
|
142
138
|
maxLength={80}
|
|
143
139
|
disabled={saving}
|
|
144
140
|
/>
|
|
145
|
-
<button
|
|
146
|
-
type="button"
|
|
147
|
-
class="snapshot-save-btn"
|
|
148
|
-
onClick={handleSave}
|
|
149
|
-
disabled={!nameInput.trim() || saving}
|
|
150
|
-
>
|
|
141
|
+
<button type="button" class="snapshot-save-btn" onClick={handleSave} disabled={!nameInput.trim() || saving}>
|
|
151
142
|
{saving ? '...' : 'Save'}
|
|
152
143
|
</button>
|
|
153
144
|
</div>
|
|
154
145
|
|
|
155
|
-
<div class="snapshot-restore-note">
|
|
156
|
-
Restoring replaces the current canvas. You can undo it if needed.
|
|
157
|
-
</div>
|
|
146
|
+
<div class="snapshot-restore-note">Restoring replaces the current canvas. You can undo it if needed.</div>
|
|
158
147
|
|
|
159
148
|
{/* Snapshot list */}
|
|
160
149
|
<div class="snapshot-list">
|
|
161
|
-
{loading &&
|
|
162
|
-
<div class="snapshot-empty">Loading...</div>
|
|
163
|
-
)}
|
|
150
|
+
{loading && <div class="snapshot-empty">Loading...</div>}
|
|
164
151
|
|
|
165
152
|
{!loading && snapshots.length === 0 && (
|
|
166
|
-
<div class="snapshot-empty">
|
|
167
|
-
No snapshots yet. Save one to capture the current canvas state.
|
|
168
|
-
</div>
|
|
153
|
+
<div class="snapshot-empty">No snapshots yet. Save one to capture the current canvas state.</div>
|
|
169
154
|
)}
|
|
170
155
|
|
|
171
|
-
{!loading &&
|
|
172
|
-
|
|
173
|
-
<div class="snapshot-item
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
? 'Delete'
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
</>
|
|
229
|
-
)}
|
|
156
|
+
{!loading &&
|
|
157
|
+
snapshots.map((snap) => (
|
|
158
|
+
<div key={snap.id} class="snapshot-item">
|
|
159
|
+
<div class="snapshot-item-info">
|
|
160
|
+
<span class="snapshot-item-name">{snap.name}</span>
|
|
161
|
+
<span class="snapshot-item-meta">
|
|
162
|
+
{snap.nodeCount} node{snap.nodeCount !== 1 ? 's' : ''}
|
|
163
|
+
{snap.edgeCount > 0 ? ` \u00b7 ${snap.edgeCount} edge${snap.edgeCount !== 1 ? 's' : ''}` : ''}
|
|
164
|
+
{' \u00b7 '}
|
|
165
|
+
{timeAgo(snap.createdAt)}
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="snapshot-item-actions">
|
|
169
|
+
{confirming?.id === snap.id ? (
|
|
170
|
+
<>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
class={`snapshot-action-btn ${confirming.action === 'delete' ? 'snapshot-action-confirm' : 'snapshot-action-restore'}`}
|
|
174
|
+
onClick={() => (confirming.action === 'delete' ? handleDelete(snap.id) : handleRestore(snap.id))}
|
|
175
|
+
title={confirming.action === 'delete' ? 'Confirm delete' : 'Confirm restore'}
|
|
176
|
+
disabled={restoringId !== null}
|
|
177
|
+
>
|
|
178
|
+
{confirming.action === 'delete' ? 'Delete' : restoringId === snap.id ? 'Restoring...' : 'Confirm'}
|
|
179
|
+
</button>
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
class="snapshot-action-btn"
|
|
183
|
+
onClick={() => setConfirming(null)}
|
|
184
|
+
title="Cancel"
|
|
185
|
+
disabled={restoringId !== null}
|
|
186
|
+
>
|
|
187
|
+
Cancel
|
|
188
|
+
</button>
|
|
189
|
+
</>
|
|
190
|
+
) : (
|
|
191
|
+
<>
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
class="snapshot-action-btn snapshot-action-restore"
|
|
195
|
+
onClick={() => setConfirming({ id: snap.id, action: 'restore' })}
|
|
196
|
+
title="Restore this snapshot"
|
|
197
|
+
disabled={restoringId !== null}
|
|
198
|
+
>
|
|
199
|
+
{restoringId === snap.id ? 'Restoring...' : 'Restore'}
|
|
200
|
+
</button>
|
|
201
|
+
<button
|
|
202
|
+
type="button"
|
|
203
|
+
class="snapshot-action-btn snapshot-action-delete"
|
|
204
|
+
onClick={() => setConfirming({ id: snap.id, action: 'delete' })}
|
|
205
|
+
title="Delete this snapshot"
|
|
206
|
+
disabled={restoringId !== null}
|
|
207
|
+
>
|
|
208
|
+
{'\u2715'}
|
|
209
|
+
</button>
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
230
213
|
</div>
|
|
231
|
-
|
|
232
|
-
))}
|
|
214
|
+
))}
|
|
233
215
|
</div>
|
|
234
216
|
</div>
|
|
235
217
|
);
|
|
@@ -19,11 +19,13 @@ export const AUTO_FIT_BODY_PADDING = 24;
|
|
|
19
19
|
* height bridge (use-iframe-content-height) instead; webpage intentionally
|
|
20
20
|
* scrolls. Excluding them from the DOM path is behaviour-neutral. */
|
|
21
21
|
function isIframeNode(node: CanvasNodeState): boolean {
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
return (
|
|
23
|
+
node.type === 'html' ||
|
|
24
|
+
node.type === 'json-render' ||
|
|
25
|
+
node.type === 'graph' ||
|
|
26
|
+
node.type === 'mcp-app' ||
|
|
27
|
+
node.type === 'webpage'
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/** Authored iframe surfaces whose content has a bounded natural height — they may
|
|
@@ -39,11 +41,13 @@ function isContentFitSurface(node: CanvasNodeState): boolean {
|
|
|
39
41
|
/** Shared exemptions: never auto-size a node the user/agent has fixed or a node
|
|
40
42
|
* whose height is controlled elsewhere. */
|
|
41
43
|
function isAutoSizeExempt(node: CanvasNodeState): boolean {
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
return (
|
|
45
|
+
node.collapsed === true ||
|
|
46
|
+
node.dockPosition != null ||
|
|
47
|
+
node.data.strictSize === true ||
|
|
48
|
+
node.data.userResized === true ||
|
|
49
|
+
node.type === 'group'
|
|
50
|
+
);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/** DOM-content nodes (markdown/status/file/…) whose body scrollHeight is directly
|
|
@@ -71,9 +75,6 @@ export function shouldContentFitIframeNode(node: CanvasNodeState): boolean {
|
|
|
71
75
|
*/
|
|
72
76
|
export function computeContentGrowHeight(node: CanvasNodeState, contentHeight: number): number | null {
|
|
73
77
|
if (!shouldContentFitIframeNode(node) || contentHeight <= 0) return null;
|
|
74
|
-
const want = Math.min(
|
|
75
|
-
contentHeight + AUTO_FIT_TITLEBAR_HEIGHT + AUTO_FIT_BODY_PADDING,
|
|
76
|
-
AUTO_FIT_MAX_HEIGHT_IFRAME,
|
|
77
|
-
);
|
|
78
|
+
const want = Math.min(contentHeight + AUTO_FIT_TITLEBAR_HEIGHT + AUTO_FIT_BODY_PADDING, AUTO_FIT_MAX_HEIGHT_IFRAME);
|
|
78
79
|
return want > node.size.height + 8 ? want : null;
|
|
79
80
|
}
|
|
@@ -19,8 +19,16 @@ export interface SnapResult {
|
|
|
19
19
|
/** Active guide lines to render. Null when not dragging. */
|
|
20
20
|
export const activeGuides = signal<GuideLine[] | null>(null);
|
|
21
21
|
|
|
22
|
-
interface RefEdgeX {
|
|
23
|
-
|
|
22
|
+
interface RefEdgeX {
|
|
23
|
+
val: number;
|
|
24
|
+
minY: number;
|
|
25
|
+
maxY: number;
|
|
26
|
+
}
|
|
27
|
+
interface RefEdgeY {
|
|
28
|
+
val: number;
|
|
29
|
+
minX: number;
|
|
30
|
+
maxX: number;
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
/** Cached reference edges — built once at drag-start, reused on every pointermove. */
|
|
26
34
|
let cachedRefX: RefEdgeX[] = [];
|
|
@@ -37,10 +45,7 @@ function getChildIds(node: CanvasNodeState | undefined): string[] {
|
|
|
37
45
|
return node.data.children.filter((childId): childId is string => typeof childId === 'string');
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
function collectExcludedReferenceIds(
|
|
41
|
-
dragId: string,
|
|
42
|
-
allNodes: CanvasNodeState[],
|
|
43
|
-
): Set<string> {
|
|
48
|
+
function collectExcludedReferenceIds(dragId: string, allNodes: CanvasNodeState[]): Set<string> {
|
|
44
49
|
const nodeMap = new Map(allNodes.map((node) => [node.id, node]));
|
|
45
50
|
const excluded = new Set<string>([dragId]);
|
|
46
51
|
const dragNode = nodeMap.get(dragId);
|
|
@@ -105,12 +110,7 @@ export function clearSnapCache(): void {
|
|
|
105
110
|
* Snap a dragging node's proposed position to cached reference edges.
|
|
106
111
|
* Must call buildSnapCache() before the first call in a drag session.
|
|
107
112
|
*/
|
|
108
|
-
export function snapToGuides(
|
|
109
|
-
proposedX: number,
|
|
110
|
-
proposedY: number,
|
|
111
|
-
nodeW: number,
|
|
112
|
-
nodeH: number,
|
|
113
|
-
): SnapResult {
|
|
113
|
+
export function snapToGuides(proposedX: number, proposedY: number, nodeW: number, nodeH: number): SnapResult {
|
|
114
114
|
const dragEdgesX = [proposedX, proposedX + nodeW / 2, proposedX + nodeW];
|
|
115
115
|
const dragEdgesY = [proposedY, proposedY + nodeH / 2, proposedY + nodeH];
|
|
116
116
|
const offX = [0, nodeW / 2, nodeW];
|
|
@@ -41,11 +41,7 @@ export function useNodeResize({ nodeId, viewport, onResize, onResizeEnd }: NodeR
|
|
|
41
41
|
const scale = viewport.value.scale;
|
|
42
42
|
const dw = (pointer.x - startPointer.current.x) / scale;
|
|
43
43
|
const dh = (pointer.y - startPointer.current.y) / scale;
|
|
44
|
-
onResize(
|
|
45
|
-
nodeId,
|
|
46
|
-
Math.max(MIN_WIDTH, startSize.current.w + dw),
|
|
47
|
-
Math.max(MIN_HEIGHT, startSize.current.h + dh),
|
|
48
|
-
);
|
|
44
|
+
onResize(nodeId, Math.max(MIN_WIDTH, startSize.current.w + dw), Math.max(MIN_HEIGHT, startSize.current.h + dh));
|
|
49
45
|
};
|
|
50
46
|
|
|
51
47
|
const onPointerMove = (ev: PointerEvent) => {
|
|
@@ -31,15 +31,18 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
31
31
|
const lastPinchDist = useRef(0);
|
|
32
32
|
const wheelCommitTimer = useRef<number | null>(null);
|
|
33
33
|
|
|
34
|
-
const scheduleViewportCommit = useCallback(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
wheelCommitTimer.current =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
const scheduleViewportCommit = useCallback(
|
|
35
|
+
(next: ViewportState) => {
|
|
36
|
+
if (wheelCommitTimer.current !== null) {
|
|
37
|
+
window.clearTimeout(wheelCommitTimer.current);
|
|
38
|
+
}
|
|
39
|
+
wheelCommitTimer.current = window.setTimeout(() => {
|
|
40
|
+
wheelCommitTimer.current = null;
|
|
41
|
+
onViewportCommit(next);
|
|
42
|
+
}, 140);
|
|
43
|
+
},
|
|
44
|
+
[onViewportCommit],
|
|
45
|
+
);
|
|
43
46
|
|
|
44
47
|
const handleWheel = useCallback(
|
|
45
48
|
(e: WheelEvent) => {
|
|
@@ -79,15 +82,18 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
79
82
|
[disabled, viewport, onViewportChange, scheduleViewportCommit],
|
|
80
83
|
);
|
|
81
84
|
|
|
82
|
-
const handlePointerDown = useCallback(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const handlePointerDown = useCallback(
|
|
86
|
+
(e: PointerEvent) => {
|
|
87
|
+
if (disabled) return;
|
|
88
|
+
// Only pan when clicking the canvas background (not nodes)
|
|
89
|
+
const container = containerRef.current;
|
|
90
|
+
if (!container || e.target !== container) return;
|
|
91
|
+
isPanning.current = true;
|
|
92
|
+
lastPointer.current = { x: e.clientX, y: e.clientY };
|
|
93
|
+
container.setPointerCapture(e.pointerId);
|
|
94
|
+
},
|
|
95
|
+
[disabled],
|
|
96
|
+
);
|
|
91
97
|
|
|
92
98
|
const handlePointerMove = useCallback(
|
|
93
99
|
(e: PointerEvent) => {
|
|
@@ -183,14 +189,7 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
183
189
|
el.removeEventListener('touchmove', handleTouchMove);
|
|
184
190
|
el.removeEventListener('touchend', handleTouchEnd);
|
|
185
191
|
};
|
|
186
|
-
}, [
|
|
187
|
-
handleWheel,
|
|
188
|
-
handlePointerDown,
|
|
189
|
-
handlePointerMove,
|
|
190
|
-
handlePointerUp,
|
|
191
|
-
handleTouchMove,
|
|
192
|
-
handleTouchEnd,
|
|
193
|
-
]);
|
|
192
|
+
}, [handleWheel, handlePointerDown, handlePointerMove, handlePointerUp, handleTouchMove, handleTouchEnd]);
|
|
194
193
|
|
|
195
194
|
return containerRef;
|
|
196
195
|
}
|
|
@@ -32,10 +32,7 @@ interface JsonRpcErrorResponse extends JsonRpcBase {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
type JsonRpcResponseMessage = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
|
35
|
-
type JsonRpcMessage =
|
|
36
|
-
| JsonRpcRequestMessage
|
|
37
|
-
| JsonRpcNotificationMessage
|
|
38
|
-
| JsonRpcResponseMessage;
|
|
35
|
+
type JsonRpcMessage = JsonRpcRequestMessage | JsonRpcNotificationMessage | JsonRpcResponseMessage;
|
|
39
36
|
|
|
40
37
|
type DisplayMode = 'inline' | 'fullscreen' | 'pip';
|
|
41
38
|
|
|
@@ -256,10 +253,7 @@ export class AppBridge {
|
|
|
256
253
|
this.notificationHandlers.set(method, (params) => callback(params as TParams));
|
|
257
254
|
}
|
|
258
255
|
|
|
259
|
-
private setRequestHandler<TParams, TResult>(
|
|
260
|
-
method: string,
|
|
261
|
-
callback: RequestHandler<TParams, TResult>,
|
|
262
|
-
): void {
|
|
256
|
+
private setRequestHandler<TParams, TResult>(method: string, callback: RequestHandler<TParams, TResult>): void {
|
|
263
257
|
this.requestHandlers.set(method, (params, extra) => callback(params as TParams, extra));
|
|
264
258
|
}
|
|
265
259
|
|
|
@@ -447,10 +441,7 @@ export class AppBridge {
|
|
|
447
441
|
}
|
|
448
442
|
}
|
|
449
443
|
|
|
450
|
-
private async handleRequest(
|
|
451
|
-
message: JsonRpcRequestMessage,
|
|
452
|
-
extra: RequestExtra,
|
|
453
|
-
): Promise<unknown> {
|
|
444
|
+
private async handleRequest(message: JsonRpcRequestMessage, extra: RequestExtra): Promise<unknown> {
|
|
454
445
|
if (message.method === 'ui/initialize') {
|
|
455
446
|
return this.handleInitialize(message.params);
|
|
456
447
|
}
|
package/src/client/icons.tsx
CHANGED
|
@@ -5,7 +5,13 @@ interface IconProps {
|
|
|
5
5
|
class?: string;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
const defaults = {
|
|
8
|
+
const defaults = {
|
|
9
|
+
fill: 'none',
|
|
10
|
+
stroke: 'currentColor',
|
|
11
|
+
'stroke-width': '1.5',
|
|
12
|
+
'stroke-linecap': 'round',
|
|
13
|
+
'stroke-linejoin': 'round',
|
|
14
|
+
} as const;
|
|
9
15
|
|
|
10
16
|
function Icon({ size = 16, children, ...rest }: IconProps & { children: JSX.Element | JSX.Element[] }): JSX.Element {
|
|
11
17
|
return (
|
|
@@ -240,8 +246,28 @@ export function IconLogo({ size = 22, class: className }: IconProps): JSX.Elemen
|
|
|
240
246
|
class={className}
|
|
241
247
|
aria-hidden="true"
|
|
242
248
|
>
|
|
243
|
-
<rect
|
|
244
|
-
|
|
249
|
+
<rect
|
|
250
|
+
x="8"
|
|
251
|
+
y="8"
|
|
252
|
+
width="48"
|
|
253
|
+
height="48"
|
|
254
|
+
rx="7"
|
|
255
|
+
fill="none"
|
|
256
|
+
stroke="currentColor"
|
|
257
|
+
stroke-width="2.2"
|
|
258
|
+
opacity="0.35"
|
|
259
|
+
/>
|
|
260
|
+
<rect
|
|
261
|
+
x="16"
|
|
262
|
+
y="16"
|
|
263
|
+
width="32"
|
|
264
|
+
height="32"
|
|
265
|
+
rx="5"
|
|
266
|
+
fill="none"
|
|
267
|
+
stroke="currentColor"
|
|
268
|
+
stroke-width="2.2"
|
|
269
|
+
opacity="0.6"
|
|
270
|
+
/>
|
|
245
271
|
<rect x="24" y="24" width="16" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="2.2" />
|
|
246
272
|
<rect x="29" y="29" width="6" height="6" rx="1" fill="currentColor" />
|
|
247
273
|
</svg>
|
|
@@ -439,22 +465,39 @@ export function IconNodeGraph(p: IconProps): JSX.Element {
|
|
|
439
465
|
/** Map a node type → its Focus Field icon component. */
|
|
440
466
|
export function getNodeIcon(type: string): (p: IconProps) => JSX.Element {
|
|
441
467
|
switch (type) {
|
|
442
|
-
case 'markdown':
|
|
443
|
-
|
|
444
|
-
case '
|
|
445
|
-
|
|
446
|
-
case '
|
|
447
|
-
|
|
448
|
-
case '
|
|
449
|
-
|
|
450
|
-
case '
|
|
451
|
-
|
|
452
|
-
case '
|
|
453
|
-
|
|
454
|
-
case '
|
|
455
|
-
|
|
456
|
-
case '
|
|
457
|
-
|
|
458
|
-
|
|
468
|
+
case 'markdown':
|
|
469
|
+
return IconNodeMarkdown;
|
|
470
|
+
case 'prompt':
|
|
471
|
+
return IconNodePrompt;
|
|
472
|
+
case 'response':
|
|
473
|
+
return IconNodeResponse;
|
|
474
|
+
case 'file':
|
|
475
|
+
return IconNodeFile;
|
|
476
|
+
case 'image':
|
|
477
|
+
return IconNodeImage;
|
|
478
|
+
case 'webpage':
|
|
479
|
+
return IconNodeWebpage;
|
|
480
|
+
case 'context':
|
|
481
|
+
return IconNodeContext;
|
|
482
|
+
case 'group':
|
|
483
|
+
return IconNodeGroup;
|
|
484
|
+
case 'status':
|
|
485
|
+
return IconNodeStatus;
|
|
486
|
+
case 'trace':
|
|
487
|
+
return IconNodeTrace;
|
|
488
|
+
case 'ledger':
|
|
489
|
+
return IconNodeLedger;
|
|
490
|
+
case 'mcp-app':
|
|
491
|
+
return IconNodeMcpApp;
|
|
492
|
+
case 'ext-app':
|
|
493
|
+
return IconNodeExtApp;
|
|
494
|
+
case 'json-render':
|
|
495
|
+
return IconNodeJsonRender;
|
|
496
|
+
case 'graph':
|
|
497
|
+
return IconNodeGraph;
|
|
498
|
+
case 'html':
|
|
499
|
+
return IconNodeWebpage;
|
|
500
|
+
default:
|
|
501
|
+
return IconNodeMarkdown;
|
|
459
502
|
}
|
|
460
503
|
}
|
|
@@ -82,9 +82,7 @@ function stripHtmlToText(value: string): string {
|
|
|
82
82
|
.trim();
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export function normalizeContextNodeFallback(
|
|
86
|
-
nodeData: Record<string, unknown>,
|
|
87
|
-
): ContextNodeFallbackDisplay | null {
|
|
85
|
+
export function normalizeContextNodeFallback(nodeData: Record<string, unknown>): ContextNodeFallbackDisplay | null {
|
|
88
86
|
const title = asTrimmedString(nodeData.title);
|
|
89
87
|
const content = asTrimmedString(nodeData.content);
|
|
90
88
|
const rendered = stripHtmlToText(asTrimmedString(nodeData.rendered));
|
|
@@ -135,24 +133,23 @@ export function ContextNode({
|
|
|
135
133
|
node,
|
|
136
134
|
expanded = false,
|
|
137
135
|
pinnedNodes = [],
|
|
138
|
-
}: {
|
|
136
|
+
}: {
|
|
137
|
+
node: CanvasNodeState;
|
|
138
|
+
expanded?: boolean;
|
|
139
|
+
pinnedNodes?: CanvasNodeState[];
|
|
140
|
+
}) {
|
|
139
141
|
const cards = (node.data.cards as ContextCard[]) ?? [];
|
|
140
142
|
const auxTabs = (node.data.auxTabs as Array<{ id: string; url: string; reason?: string }>) ?? [];
|
|
141
143
|
const pinnedContext = pinnedNodes.map(normalizePinnedContextDisplay);
|
|
142
144
|
const hasPinnedContext = pinnedContext.length > 0;
|
|
143
|
-
const currentTokens =
|
|
144
|
-
typeof node.data.currentTokens === 'number' ? node.data.currentTokens : null;
|
|
145
|
+
const currentTokens = typeof node.data.currentTokens === 'number' ? node.data.currentTokens : null;
|
|
145
146
|
const tokenLimit = typeof node.data.tokenLimit === 'number' ? node.data.tokenLimit : null;
|
|
146
147
|
const utilization = typeof node.data.utilization === 'number' ? node.data.utilization : null;
|
|
147
|
-
const messagesLength =
|
|
148
|
-
|
|
149
|
-
const percent =
|
|
150
|
-
utilization !== null ? Math.max(0, Math.min(100, Math.round(utilization * 100))) : null;
|
|
148
|
+
const messagesLength = typeof node.data.messagesLength === 'number' ? node.data.messagesLength : null;
|
|
149
|
+
const percent = utilization !== null ? Math.max(0, Math.min(100, Math.round(utilization * 100))) : null;
|
|
151
150
|
const barColor = usageBarColor(utilization ?? 0);
|
|
152
151
|
const fallback =
|
|
153
|
-
!hasPinnedContext && cards.length === 0 && auxTabs.length === 0
|
|
154
|
-
? normalizeContextNodeFallback(node.data)
|
|
155
|
-
: null;
|
|
152
|
+
!hasPinnedContext && cards.length === 0 && auxTabs.length === 0 ? normalizeContextNodeFallback(node.data) : null;
|
|
156
153
|
|
|
157
154
|
const openCard = async (card: ContextCard): Promise<void> => {
|
|
158
155
|
const path = typeof card.path === 'string' ? card.path.trim() : '';
|
|
@@ -254,9 +251,7 @@ export function ContextNode({
|
|
|
254
251
|
borderLeft: '2px solid var(--c-accent)',
|
|
255
252
|
}}
|
|
256
253
|
>
|
|
257
|
-
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>
|
|
258
|
-
{display.title}
|
|
259
|
-
</div>
|
|
254
|
+
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>{display.title}</div>
|
|
260
255
|
{display.summary && (
|
|
261
256
|
<div
|
|
262
257
|
style={{
|
|
@@ -347,9 +342,7 @@ export function ContextNode({
|
|
|
347
342
|
borderLeft: '2px solid var(--c-accent)',
|
|
348
343
|
}}
|
|
349
344
|
>
|
|
350
|
-
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>
|
|
351
|
-
{display.title}
|
|
352
|
-
</div>
|
|
345
|
+
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '2px' }}>{display.title}</div>
|
|
353
346
|
<div
|
|
354
347
|
style={{
|
|
355
348
|
color: 'var(--c-muted)',
|
|
@@ -478,9 +471,7 @@ export function ContextNode({
|
|
|
478
471
|
>
|
|
479
472
|
{tab.url}
|
|
480
473
|
{tab.reason && (
|
|
481
|
-
<span style={{ color: 'var(--c-dim)', fontSize: '10px', marginLeft: '6px' }}>
|
|
482
|
-
({tab.reason})
|
|
483
|
-
</span>
|
|
474
|
+
<span style={{ color: 'var(--c-dim)', fontSize: '10px', marginLeft: '6px' }}>({tab.reason})</span>
|
|
484
475
|
)}
|
|
485
476
|
</a>
|
|
486
477
|
))}
|
|
@@ -496,9 +487,7 @@ export function ContextNode({
|
|
|
496
487
|
borderLeft: '2px solid var(--c-accent)',
|
|
497
488
|
}}
|
|
498
489
|
>
|
|
499
|
-
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '4px' }}>
|
|
500
|
-
{fallback.title}
|
|
501
|
-
</div>
|
|
490
|
+
<div style={{ fontWeight: 600, color: 'var(--c-text)', marginBottom: '4px' }}>{fallback.title}</div>
|
|
502
491
|
{fallback.summary && (
|
|
503
492
|
<div
|
|
504
493
|
style={{
|