pmx-canvas 0.3.0 → 0.3.2
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 +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -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/ExtAppFrame.d.ts +9 -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/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- 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/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- 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 +194 -80
- 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 +35 -21
- package/src/mcp/server.ts +132 -70
- 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 +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- 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 +320 -217
- package/src/server/intent-registry.ts +2 -5
- 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 +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- 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 +41 -15
- 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 +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- 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 +122 -1028
- 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/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
|
@@ -32,10 +32,7 @@ function RenderedMarkdown({ html }: { html: string }) {
|
|
|
32
32
|
return <div ref={containerRef} />;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function ResponseNode({
|
|
36
|
-
node,
|
|
37
|
-
expanded = false,
|
|
38
|
-
}: { node: CanvasNodeState; expanded?: boolean }) {
|
|
35
|
+
export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
39
36
|
const content = (node.data.content as string) || '';
|
|
40
37
|
const status = (node.data.status as string) || 'streaming';
|
|
41
38
|
const [rendered, setRendered] = useState('');
|
|
@@ -59,18 +56,11 @@ export function ResponseNode({
|
|
|
59
56
|
}, [content]);
|
|
60
57
|
|
|
61
58
|
const handleReply = useCallback(() => {
|
|
62
|
-
submitCanvasPrompt(
|
|
63
|
-
'',
|
|
64
|
-
{ x: node.position.x, y: node.position.y + node.size.height + 24 },
|
|
65
|
-
node.id,
|
|
66
|
-
);
|
|
59
|
+
submitCanvasPrompt('', { x: node.position.x, y: node.position.y + node.size.height + 24 }, node.id);
|
|
67
60
|
}, [node]);
|
|
68
61
|
|
|
69
62
|
return (
|
|
70
|
-
<div
|
|
71
|
-
class="response-node-inner"
|
|
72
|
-
style={{ display: 'flex', flexDirection: 'column', height: '100%' }}
|
|
73
|
-
>
|
|
63
|
+
<div class="response-node-inner" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
74
64
|
{/* Streaming indicator */}
|
|
75
65
|
{isStreaming && (
|
|
76
66
|
<div
|
|
@@ -2,17 +2,13 @@ import { PHASE_COLORS } from '../theme/tokens';
|
|
|
2
2
|
import type { CanvasNodeState } from '../types';
|
|
3
3
|
|
|
4
4
|
export function getStatusDisplayPhase(node: CanvasNodeState): string {
|
|
5
|
-
const phase = typeof node.data.phase === 'string' && node.data.phase.trim().length > 0
|
|
6
|
-
? node.data.phase.trim()
|
|
7
|
-
: '';
|
|
5
|
+
const phase = typeof node.data.phase === 'string' && node.data.phase.trim().length > 0 ? node.data.phase.trim() : '';
|
|
8
6
|
if (phase) return phase;
|
|
9
|
-
const content =
|
|
10
|
-
? node.data.content.trim()
|
|
11
|
-
: '';
|
|
7
|
+
const content =
|
|
8
|
+
typeof node.data.content === 'string' && node.data.content.trim().length > 0 ? node.data.content.trim() : '';
|
|
12
9
|
if (content) return content;
|
|
13
|
-
const status =
|
|
14
|
-
? node.data.status.trim()
|
|
15
|
-
: '';
|
|
10
|
+
const status =
|
|
11
|
+
typeof node.data.status === 'string' && node.data.status.trim().length > 0 ? node.data.status.trim() : '';
|
|
16
12
|
return status || 'idle';
|
|
17
13
|
}
|
|
18
14
|
|
|
@@ -21,15 +21,9 @@ export function StatusSummary({ node }: { node: CanvasNodeState }) {
|
|
|
21
21
|
flexShrink: 0,
|
|
22
22
|
}}
|
|
23
23
|
/>
|
|
24
|
-
<span
|
|
25
|
-
style={{ color: phaseColor, fontSize: '10px', textTransform: 'uppercase', fontWeight: 600 }}
|
|
26
|
-
>
|
|
27
|
-
{phase}
|
|
28
|
-
</span>
|
|
24
|
+
<span style={{ color: phaseColor, fontSize: '10px', textTransform: 'uppercase', fontWeight: 600 }}>{phase}</span>
|
|
29
25
|
{activeTool && (
|
|
30
|
-
<span style={{ color: 'var(--c-warn)', fontSize: '10px', fontFamily: 'var(--mono)' }}>
|
|
31
|
-
⚙ {activeTool}
|
|
32
|
-
</span>
|
|
26
|
+
<span style={{ color: 'var(--c-warn)', fontSize: '10px', fontFamily: 'var(--mono)' }}>⚙ {activeTool}</span>
|
|
33
27
|
)}
|
|
34
28
|
{subagent && subagent.state !== 'completed' && (
|
|
35
29
|
<span style={{ color: 'var(--c-subagent)', fontSize: '10px' }}>⠉ {subagent.name}</span>
|
|
@@ -20,11 +20,12 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
20
20
|
const url = typeof node.data.url === 'string' ? node.data.url : '';
|
|
21
21
|
const pageTitle = typeof node.data.pageTitle === 'string' ? node.data.pageTitle : '';
|
|
22
22
|
const description = typeof node.data.description === 'string' ? node.data.description : '';
|
|
23
|
-
const excerpt =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const excerpt =
|
|
24
|
+
typeof node.data.excerpt === 'string'
|
|
25
|
+
? node.data.excerpt
|
|
26
|
+
: typeof node.data.content === 'string'
|
|
27
|
+
? node.data.content
|
|
28
|
+
: '';
|
|
28
29
|
const status = typeof node.data.status === 'string' ? node.data.status : 'idle';
|
|
29
30
|
const error = typeof node.data.error === 'string' ? node.data.error : '';
|
|
30
31
|
const fetchedAt = formatFetchedAt(typeof node.data.fetchedAt === 'string' ? node.data.fetchedAt : undefined);
|
|
@@ -55,12 +56,7 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
55
56
|
return <div style={{ color: 'var(--c-dim)', fontStyle: 'italic', padding: '12px' }}>No webpage URL set</div>;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
const statusTone =
|
|
59
|
-
status === 'ready'
|
|
60
|
-
? 'var(--c-ok)'
|
|
61
|
-
: status === 'error'
|
|
62
|
-
? 'var(--c-danger)'
|
|
63
|
-
: 'var(--c-warn)';
|
|
59
|
+
const statusTone = status === 'ready' ? 'var(--c-ok)' : status === 'error' ? 'var(--c-danger)' : 'var(--c-warn)';
|
|
64
60
|
|
|
65
61
|
return (
|
|
66
62
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', height: '100%' }}>
|
|
@@ -123,7 +119,9 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
123
119
|
/>
|
|
124
120
|
)}
|
|
125
121
|
{description && (
|
|
126
|
-
<p
|
|
122
|
+
<p
|
|
123
|
+
style={{ margin: 0, color: 'var(--c-text-soft)', lineHeight: 1.5, fontSize: expanded ? '14px' : '12px' }}
|
|
124
|
+
>
|
|
127
125
|
{description}
|
|
128
126
|
</p>
|
|
129
127
|
)}
|
|
@@ -232,7 +230,8 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
232
230
|
background: 'rgba(255,255,255,0.03)',
|
|
233
231
|
}}
|
|
234
232
|
>
|
|
235
|
-
Live preview (best effort). If this stays blank, the site likely blocks framing. The cached text snapshot
|
|
233
|
+
Live preview (best effort). If this stays blank, the site likely blocks framing. The cached text snapshot
|
|
234
|
+
below still works.
|
|
236
235
|
</div>
|
|
237
236
|
<iframe
|
|
238
237
|
class="webpage-node-iframe"
|
|
@@ -274,7 +273,14 @@ export function WebpageNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
274
273
|
}}
|
|
275
274
|
>
|
|
276
275
|
{excerpt ? (
|
|
277
|
-
<div
|
|
276
|
+
<div
|
|
277
|
+
style={{
|
|
278
|
+
whiteSpace: 'pre-wrap',
|
|
279
|
+
lineHeight: 1.55,
|
|
280
|
+
color: 'var(--c-text)',
|
|
281
|
+
fontSize: expanded ? '14px' : '12px',
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
278
284
|
{excerpt}
|
|
279
285
|
</div>
|
|
280
286
|
) : (
|
|
@@ -7,12 +7,14 @@ interface FrameDocumentCreateResponse {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function isFrameDocumentCreateResponse(value: unknown): value is FrameDocumentCreateResponse {
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
&&
|
|
14
|
-
&&
|
|
15
|
-
|
|
10
|
+
return (
|
|
11
|
+
Boolean(value) &&
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
value !== null &&
|
|
14
|
+
!Array.isArray(value) &&
|
|
15
|
+
'ok' in value &&
|
|
16
|
+
typeof (value as { ok: unknown }).ok === 'boolean'
|
|
17
|
+
);
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export async function createIframeDocumentUrl(html: string, sandbox: string): Promise<string> {
|
|
@@ -21,17 +23,21 @@ export async function createIframeDocumentUrl(html: string, sandbox: string): Pr
|
|
|
21
23
|
headers: { 'Content-Type': 'application/json' },
|
|
22
24
|
body: JSON.stringify({ html, sandbox }),
|
|
23
25
|
});
|
|
24
|
-
const json = await response.json() as unknown;
|
|
26
|
+
const json = (await response.json()) as unknown;
|
|
25
27
|
if (!response.ok || !isFrameDocumentCreateResponse(json) || !json.ok || typeof json.url !== 'string') {
|
|
26
|
-
const message =
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const message =
|
|
29
|
+
isFrameDocumentCreateResponse(json) && json.error
|
|
30
|
+
? json.error
|
|
31
|
+
: `Frame document request failed with HTTP ${response.status}`;
|
|
29
32
|
throw new Error(message);
|
|
30
33
|
}
|
|
31
34
|
return json.url;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
export function useIframeDocument(
|
|
37
|
+
export function useIframeDocument(
|
|
38
|
+
html: string,
|
|
39
|
+
sandbox: string,
|
|
40
|
+
): { attributes: { src?: string }; ready: boolean; key: string } {
|
|
35
41
|
const [src, setSrc] = useState<string | null>(null);
|
|
36
42
|
|
|
37
43
|
useEffect(() => {
|
|
@@ -50,9 +56,12 @@ export function useIframeDocument(html: string, sandbox: string): { attributes:
|
|
|
50
56
|
};
|
|
51
57
|
}, [html, sandbox]);
|
|
52
58
|
|
|
53
|
-
return useMemo(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
return useMemo(
|
|
60
|
+
() => ({
|
|
61
|
+
attributes: src ? { src } : {},
|
|
62
|
+
ready: Boolean(src),
|
|
63
|
+
key: src ?? '',
|
|
64
|
+
}),
|
|
65
|
+
[src],
|
|
66
|
+
);
|
|
58
67
|
}
|
|
@@ -52,13 +52,7 @@ function metadataWarnings(node: CanvasNodeState): ImageNodeWarning[] {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function looksLikeLoginCapture(node: CanvasNodeState): boolean {
|
|
55
|
-
const haystack = [
|
|
56
|
-
node.data.title,
|
|
57
|
-
node.data.caption,
|
|
58
|
-
node.data.alt,
|
|
59
|
-
node.data.src,
|
|
60
|
-
node.data.path,
|
|
61
|
-
]
|
|
55
|
+
const haystack = [node.data.title, node.data.caption, node.data.alt, node.data.src, node.data.path]
|
|
62
56
|
.map((value) => readTrimmedString(value)?.toLowerCase() ?? '')
|
|
63
57
|
.join(' ');
|
|
64
58
|
|
|
@@ -168,11 +168,26 @@ export function getSelectionRect(ta: HTMLTextAreaElement): { top: number; left:
|
|
|
168
168
|
const mirror = document.createElement('div');
|
|
169
169
|
const style = getComputedStyle(ta);
|
|
170
170
|
const props = [
|
|
171
|
-
'fontFamily',
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
171
|
+
'fontFamily',
|
|
172
|
+
'fontSize',
|
|
173
|
+
'fontWeight',
|
|
174
|
+
'lineHeight',
|
|
175
|
+
'letterSpacing',
|
|
176
|
+
'wordSpacing',
|
|
177
|
+
'textIndent',
|
|
178
|
+
'whiteSpace',
|
|
179
|
+
'wordWrap',
|
|
180
|
+
'overflowWrap',
|
|
181
|
+
'paddingTop',
|
|
182
|
+
'paddingRight',
|
|
183
|
+
'paddingBottom',
|
|
184
|
+
'paddingLeft',
|
|
185
|
+
'borderTopWidth',
|
|
186
|
+
'borderRightWidth',
|
|
187
|
+
'borderBottomWidth',
|
|
188
|
+
'borderLeftWidth',
|
|
189
|
+
'boxSizing',
|
|
190
|
+
'width',
|
|
176
191
|
] as const;
|
|
177
192
|
for (const p of props) {
|
|
178
193
|
mirror.style[p] = style[p];
|
|
@@ -106,10 +106,7 @@ function entryFromContextPin(event: ContextPinWatchEvent): AttentionEntry | null
|
|
|
106
106
|
'context',
|
|
107
107
|
'Context updated',
|
|
108
108
|
detail,
|
|
109
|
-
[
|
|
110
|
-
...event.added.map((node) => node.id),
|
|
111
|
-
...event.removed.map((node) => node.id),
|
|
112
|
-
],
|
|
109
|
+
[...event.added.map((node) => node.id), ...event.removed.map((node) => node.id)],
|
|
113
110
|
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
114
111
|
);
|
|
115
112
|
}
|
|
@@ -148,10 +145,7 @@ function entryFromRemove(event: RemoveWatchEvent): AttentionEntry | null {
|
|
|
148
145
|
'remove',
|
|
149
146
|
'Items removed',
|
|
150
147
|
parts.join(' · '),
|
|
151
|
-
[
|
|
152
|
-
...event.nodes.map((node) => node.id),
|
|
153
|
-
...event.edges.flatMap((edge) => [edge.fromId, edge.toId]),
|
|
154
|
-
],
|
|
148
|
+
[...event.nodes.map((node) => node.id), ...event.edges.flatMap((edge) => [edge.fromId, edge.toId])],
|
|
155
149
|
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
156
150
|
);
|
|
157
151
|
}
|
|
@@ -318,7 +312,8 @@ export function resetAttentionBridge(): void {
|
|
|
318
312
|
}
|
|
319
313
|
|
|
320
314
|
export function syncAttentionFromSse(message: SseMessage): void {
|
|
321
|
-
const entries = reducer
|
|
315
|
+
const entries = reducer
|
|
316
|
+
.handleMessage(message)
|
|
322
317
|
.map((event) => entryFromEvent(event))
|
|
323
318
|
.filter((entry): entry is AttentionEntry => entry !== null);
|
|
324
319
|
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { signal } from '@preact/signals';
|
|
2
2
|
import type { SemanticAttentionRegion } from '../../shared/semantic-attention.js';
|
|
3
3
|
|
|
4
|
-
export type AttentionTone =
|
|
5
|
-
| 'context'
|
|
6
|
-
| 'relationship'
|
|
7
|
-
| 'group'
|
|
8
|
-
| 'cluster'
|
|
9
|
-
| 'neighborhood'
|
|
10
|
-
| 'remove';
|
|
4
|
+
export type AttentionTone = 'context' | 'relationship' | 'group' | 'cluster' | 'neighborhood' | 'remove';
|
|
11
5
|
|
|
12
6
|
export interface AttentionEntry {
|
|
13
7
|
id: string;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { batch, computed, signal } from '@preact/signals';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isExcalidrawNode,
|
|
4
|
+
type CanvasAnnotation,
|
|
5
|
+
type CanvasEdge,
|
|
6
|
+
type CanvasLayout,
|
|
7
|
+
type CanvasNodeState,
|
|
8
|
+
type ConnectionStatus,
|
|
9
|
+
type ViewportState,
|
|
10
|
+
} from '../types';
|
|
3
11
|
import { computeAutoArrange } from '../../shared/auto-arrange';
|
|
4
12
|
import { pushCanvasUpdate, updateViewportFromClient } from './intent-bridge';
|
|
5
13
|
|
|
@@ -29,7 +37,7 @@ export const axSurfaceState = signal<unknown>(null);
|
|
|
29
37
|
export const expandedNodeId = signal<string | null>(null);
|
|
30
38
|
export const pendingExpandedNodeCloseId = signal<string | null>(null);
|
|
31
39
|
let expandedCloseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
32
|
-
let pendingCloseInitialCheckpointAt: unknown
|
|
40
|
+
let pendingCloseInitialCheckpointAt: unknown;
|
|
33
41
|
const EXCALIDRAW_CLOSE_POLL_MS = 100;
|
|
34
42
|
const EXCALIDRAW_CLOSE_MAX_WAIT_MS = 2500;
|
|
35
43
|
|
|
@@ -54,10 +62,7 @@ export const selectedNodeIds = signal<Set<string>>(new Set());
|
|
|
54
62
|
// ── Context pins (persistent context for agent queries) ──────
|
|
55
63
|
export const contextPinnedNodeIds = signal<Set<string>>(new Set());
|
|
56
64
|
|
|
57
|
-
export function getNeighborNodeIds(
|
|
58
|
-
nodeId: string | null,
|
|
59
|
-
edgeMap: Map<string, CanvasEdge>,
|
|
60
|
-
): Set<string> {
|
|
65
|
+
export function getNeighborNodeIds(nodeId: string | null, edgeMap: Map<string, CanvasEdge>): Set<string> {
|
|
61
66
|
if (!nodeId) return new Set();
|
|
62
67
|
|
|
63
68
|
const neighborIds = new Set<string>();
|
|
@@ -392,10 +397,7 @@ export function commitViewport(next: ViewportState): void {
|
|
|
392
397
|
commitViewportWithOptions(next);
|
|
393
398
|
}
|
|
394
399
|
|
|
395
|
-
function commitViewportWithOptions(
|
|
396
|
-
next: ViewportState,
|
|
397
|
-
options: { recordHistory?: boolean } = {},
|
|
398
|
-
): void {
|
|
400
|
+
function commitViewportWithOptions(next: ViewportState, options: { recordHistory?: boolean } = {}): void {
|
|
399
401
|
viewport.value = next;
|
|
400
402
|
persistLayout(options);
|
|
401
403
|
void updateViewportFromClient(next, options);
|
|
@@ -412,9 +414,7 @@ export function applyServerCanvasLayout(
|
|
|
412
414
|
if (node.zIndex >= nextMaxZ) nextMaxZ = node.zIndex + 1;
|
|
413
415
|
}
|
|
414
416
|
|
|
415
|
-
const edgeSource = layout.edges.filter(
|
|
416
|
-
(edge) => nextNodes.has(edge.from) && nextNodes.has(edge.to),
|
|
417
|
-
);
|
|
417
|
+
const edgeSource = layout.edges.filter((edge) => nextNodes.has(edge.from) && nextNodes.has(edge.to));
|
|
418
418
|
const nextEdges = new Map<string, CanvasEdge>();
|
|
419
419
|
for (const edge of edgeSource) {
|
|
420
420
|
nextEdges.set(edge.id, edge);
|
|
@@ -424,8 +424,7 @@ export function applyServerCanvasLayout(
|
|
|
424
424
|
nextAnnotations.set(annotation.id, annotation);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
const nextActiveNodeId =
|
|
428
|
-
activeNodeId.value !== null && nextNodes.has(activeNodeId.value) ? activeNodeId.value : null;
|
|
427
|
+
const nextActiveNodeId = activeNodeId.value !== null && nextNodes.has(activeNodeId.value) ? activeNodeId.value : null;
|
|
429
428
|
const nextExpandedNodeId =
|
|
430
429
|
expandedNodeId.value !== null && nextNodes.has(expandedNodeId.value) ? expandedNodeId.value : null;
|
|
431
430
|
const nextSelectedNodeIds = filterNodeIdSet(selectedNodeIds.value, nextNodes);
|
|
@@ -618,11 +617,15 @@ export function focusNode(id: string, options: { recordHistory?: boolean } = {})
|
|
|
618
617
|
const v = viewport.value;
|
|
619
618
|
const cx = node.position.x + node.size.width / 2;
|
|
620
619
|
const cy = node.position.y + node.size.height / 2;
|
|
621
|
-
animateViewport(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
620
|
+
animateViewport(
|
|
621
|
+
{
|
|
622
|
+
x: window.innerWidth / 2 - cx * v.scale,
|
|
623
|
+
y: window.innerHeight / 2 - cy * v.scale,
|
|
624
|
+
scale: v.scale,
|
|
625
|
+
},
|
|
626
|
+
300,
|
|
627
|
+
options,
|
|
628
|
+
);
|
|
626
629
|
bringToFront(id);
|
|
627
630
|
}
|
|
628
631
|
|
|
@@ -669,10 +672,18 @@ export function walkGraph(direction: 'up' | 'down' | 'left' | 'right'): void {
|
|
|
669
672
|
// Dot product with direction vector, normalized by distance
|
|
670
673
|
let dot: number;
|
|
671
674
|
switch (direction) {
|
|
672
|
-
case 'up':
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
case '
|
|
675
|
+
case 'up':
|
|
676
|
+
dot = -dy;
|
|
677
|
+
break;
|
|
678
|
+
case 'down':
|
|
679
|
+
dot = dy;
|
|
680
|
+
break;
|
|
681
|
+
case 'left':
|
|
682
|
+
dot = -dx;
|
|
683
|
+
break;
|
|
684
|
+
case 'right':
|
|
685
|
+
dot = dx;
|
|
686
|
+
break;
|
|
676
687
|
}
|
|
677
688
|
|
|
678
689
|
// Only consider nodes that are at least somewhat in the right direction
|
|
@@ -727,10 +738,7 @@ export function collapseExpandedNode(): void {
|
|
|
727
738
|
const pollForSave = () => {
|
|
728
739
|
const latestNode = nodes.value.get(closingNodeId);
|
|
729
740
|
const latestCheckpointAt = (latestNode?.data.appCheckpoint as { updatedAt?: unknown } | undefined)?.updatedAt;
|
|
730
|
-
if (
|
|
731
|
-
latestCheckpointAt !== undefined &&
|
|
732
|
-
latestCheckpointAt !== pendingCloseInitialCheckpointAt
|
|
733
|
-
) {
|
|
741
|
+
if (latestCheckpointAt !== undefined && latestCheckpointAt !== pendingCloseInitialCheckpointAt) {
|
|
734
742
|
finishExpandedNodeClose(closingNodeId);
|
|
735
743
|
return;
|
|
736
744
|
}
|
|
@@ -762,10 +770,14 @@ export function autoArrange(): void {
|
|
|
762
770
|
updateNode(id, { position });
|
|
763
771
|
}
|
|
764
772
|
for (const [groupId, bounds] of result.groupBounds.entries()) {
|
|
765
|
-
updateNodeWithOptions(
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
773
|
+
updateNodeWithOptions(
|
|
774
|
+
groupId,
|
|
775
|
+
{
|
|
776
|
+
position: { x: bounds.x, y: bounds.y },
|
|
777
|
+
size: { width: bounds.width, height: bounds.height },
|
|
778
|
+
},
|
|
779
|
+
{ skipGroupChildTranslation: true },
|
|
780
|
+
);
|
|
769
781
|
}
|
|
770
782
|
});
|
|
771
783
|
persistLayout();
|
|
@@ -780,10 +792,14 @@ export function forceDirectedArrange(): void {
|
|
|
780
792
|
updateNode(id, { position });
|
|
781
793
|
}
|
|
782
794
|
for (const [groupId, bounds] of result.groupBounds.entries()) {
|
|
783
|
-
updateNodeWithOptions(
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
795
|
+
updateNodeWithOptions(
|
|
796
|
+
groupId,
|
|
797
|
+
{
|
|
798
|
+
position: { x: bounds.x, y: bounds.y },
|
|
799
|
+
size: { width: bounds.width, height: bounds.height },
|
|
800
|
+
},
|
|
801
|
+
{ skipGroupChildTranslation: true },
|
|
802
|
+
);
|
|
787
803
|
}
|
|
788
804
|
});
|
|
789
805
|
persistLayout();
|