pmx-canvas 0.3.0 → 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 +38 -0
- package/dist/canvas/index.js +2 -2
- 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/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/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- 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 +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 +177 -120
- 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 +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 +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 +108 -1027
- 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
|
@@ -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();
|
|
@@ -2,12 +2,7 @@ function logRequestError(action: string, error: unknown): void {
|
|
|
2
2
|
console.error(`[intent-bridge] ${action} failed`, error);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
async function requestJson<T>(
|
|
6
|
-
action: string,
|
|
7
|
-
url: string,
|
|
8
|
-
fallback: T,
|
|
9
|
-
init?: RequestInit,
|
|
10
|
-
): Promise<T> {
|
|
5
|
+
async function requestJson<T>(action: string, url: string, fallback: T, init?: RequestInit): Promise<T> {
|
|
11
6
|
try {
|
|
12
7
|
const res = await fetch(url, init);
|
|
13
8
|
return (await res.json()) as T;
|
|
@@ -17,11 +12,7 @@ async function requestJson<T>(
|
|
|
17
12
|
}
|
|
18
13
|
}
|
|
19
14
|
|
|
20
|
-
async function requestOk(
|
|
21
|
-
action: string,
|
|
22
|
-
url: string,
|
|
23
|
-
init?: RequestInit,
|
|
24
|
-
): Promise<{ ok: boolean }> {
|
|
15
|
+
async function requestOk(action: string, url: string, init?: RequestInit): Promise<{ ok: boolean }> {
|
|
25
16
|
try {
|
|
26
17
|
const res = await fetch(url, init);
|
|
27
18
|
return { ok: res.ok };
|
|
@@ -31,11 +22,7 @@ async function requestOk(
|
|
|
31
22
|
}
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
async function requestBestEffort(
|
|
35
|
-
action: string,
|
|
36
|
-
url: string,
|
|
37
|
-
init?: RequestInit,
|
|
38
|
-
): Promise<void> {
|
|
25
|
+
async function requestBestEffort(action: string, url: string, init?: RequestInit): Promise<void> {
|
|
39
26
|
try {
|
|
40
27
|
await fetch(url, init);
|
|
41
28
|
} catch (error) {
|
|
@@ -44,15 +31,17 @@ async function requestBestEffort(
|
|
|
44
31
|
}
|
|
45
32
|
|
|
46
33
|
/** Dispatch user intents from the canvas to the server (for TUI consumption). */
|
|
47
|
-
export async function sendIntent(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
34
|
+
export async function sendIntent(type: string, payload: Record<string, unknown> = {}): Promise<{ ok: boolean }> {
|
|
35
|
+
return requestJson(
|
|
36
|
+
'sendIntent',
|
|
37
|
+
'/api/workbench/intent',
|
|
38
|
+
{ ok: false },
|
|
39
|
+
{
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ type, payload }),
|
|
43
|
+
},
|
|
44
|
+
);
|
|
56
45
|
}
|
|
57
46
|
|
|
58
47
|
/**
|
|
@@ -88,11 +77,16 @@ export async function vetoGhostIntent(intent: {
|
|
|
88
77
|
|
|
89
78
|
/** Fetch rendered markdown HTML from the server. */
|
|
90
79
|
export async function renderMarkdown(markdown: string): Promise<string> {
|
|
91
|
-
const data = await requestJson<{ html?: string }>(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
const data = await requestJson<{ html?: string }>(
|
|
81
|
+
'renderMarkdown',
|
|
82
|
+
'/api/render',
|
|
83
|
+
{},
|
|
84
|
+
{
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ markdown }),
|
|
88
|
+
},
|
|
89
|
+
);
|
|
96
90
|
return data.html ?? '';
|
|
97
91
|
}
|
|
98
92
|
|
|
@@ -105,15 +99,17 @@ export async function fetchFile(path: string): Promise<{
|
|
|
105
99
|
}
|
|
106
100
|
|
|
107
101
|
/** Save file content to the server. */
|
|
108
|
-
export async function saveFile(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
export async function saveFile(path: string, content: string): Promise<{ ok: boolean; updatedAt?: string }> {
|
|
103
|
+
return requestJson(
|
|
104
|
+
'saveFile',
|
|
105
|
+
'/api/file/save',
|
|
106
|
+
{ ok: false },
|
|
107
|
+
{
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ path, content }),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
117
113
|
}
|
|
118
114
|
|
|
119
115
|
/** Fetch current workbench state. */
|
|
@@ -136,11 +132,16 @@ export async function fetchCanvasState(): Promise<Record<string, unknown>> {
|
|
|
136
132
|
}
|
|
137
133
|
|
|
138
134
|
export async function saveCanvasTheme(theme: string): Promise<{ ok: boolean; theme?: string }> {
|
|
139
|
-
return requestJson(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
return requestJson(
|
|
136
|
+
'saveCanvasTheme',
|
|
137
|
+
'/api/canvas/theme',
|
|
138
|
+
{ ok: false },
|
|
139
|
+
{
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify({ theme }),
|
|
143
|
+
},
|
|
144
|
+
);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
/** Fetch available slash commands for prompt completion. */
|
|
@@ -157,17 +158,22 @@ export async function submitCanvasPrompt(
|
|
|
157
158
|
threadNodeId?: string,
|
|
158
159
|
): Promise<{ ok: boolean; nodeId?: string; error?: string }> {
|
|
159
160
|
if (!text.trim()) return { ok: false, error: 'Prompt text is required' };
|
|
160
|
-
return requestJson(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
return requestJson(
|
|
162
|
+
'submitCanvasPrompt',
|
|
163
|
+
'/api/canvas/prompt',
|
|
164
|
+
{ ok: false, error: 'Network error' },
|
|
165
|
+
{
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
text,
|
|
170
|
+
...(position ? { position } : {}),
|
|
171
|
+
...(parentNodeId ? { parentNodeId } : {}),
|
|
172
|
+
...(contextNodeIds && contextNodeIds.length > 0 ? { contextNodeIds } : {}),
|
|
173
|
+
...(threadNodeId ? { threadNodeId } : {}),
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
);
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
/** Submit a reply into an existing prompt thread. */
|
|
@@ -203,11 +209,16 @@ export async function createEdgeFromClient(
|
|
|
203
209
|
type: string,
|
|
204
210
|
label?: string,
|
|
205
211
|
): Promise<{ ok: boolean; id?: string }> {
|
|
206
|
-
return requestJson(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
return requestJson(
|
|
213
|
+
'createEdgeFromClient',
|
|
214
|
+
'/api/canvas/edge',
|
|
215
|
+
{ ok: false },
|
|
216
|
+
{
|
|
217
|
+
method: 'POST',
|
|
218
|
+
headers: { 'Content-Type': 'application/json' },
|
|
219
|
+
body: JSON.stringify({ from, to, type, label }),
|
|
220
|
+
},
|
|
221
|
+
);
|
|
211
222
|
}
|
|
212
223
|
|
|
213
224
|
/** Create a canvas node via the server. Returns the new node ID. */
|
|
@@ -220,11 +231,16 @@ export async function createNodeFromClient(opts: {
|
|
|
220
231
|
width?: number;
|
|
221
232
|
height?: number;
|
|
222
233
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
223
|
-
return requestJson(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
return requestJson(
|
|
235
|
+
'createNodeFromClient',
|
|
236
|
+
'/api/canvas/node',
|
|
237
|
+
{ ok: false },
|
|
238
|
+
{
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: { 'Content-Type': 'application/json' },
|
|
241
|
+
body: JSON.stringify(opts),
|
|
242
|
+
},
|
|
243
|
+
);
|
|
228
244
|
}
|
|
229
245
|
|
|
230
246
|
/** Update a canvas node via the server. */
|
|
@@ -241,11 +257,16 @@ export async function updateNodeFromClient(
|
|
|
241
257
|
data?: Record<string, unknown>;
|
|
242
258
|
},
|
|
243
259
|
): Promise<{ ok: boolean; id?: string }> {
|
|
244
|
-
return requestJson(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
return requestJson(
|
|
261
|
+
'updateNodeFromClient',
|
|
262
|
+
`/api/canvas/node/${encodeURIComponent(id)}`,
|
|
263
|
+
{ ok: false },
|
|
264
|
+
{
|
|
265
|
+
method: 'PATCH',
|
|
266
|
+
headers: { 'Content-Type': 'application/json' },
|
|
267
|
+
body: JSON.stringify(patch),
|
|
268
|
+
},
|
|
269
|
+
);
|
|
249
270
|
}
|
|
250
271
|
|
|
251
272
|
/** Refresh a webpage node from its persisted URL on the server. */
|
|
@@ -253,18 +274,28 @@ export async function refreshWebpageNodeFromClient(
|
|
|
253
274
|
id: string,
|
|
254
275
|
url?: string,
|
|
255
276
|
): Promise<{ ok: boolean; id?: string; error?: string }> {
|
|
256
|
-
return requestJson(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
277
|
+
return requestJson(
|
|
278
|
+
'refreshWebpageNodeFromClient',
|
|
279
|
+
`/api/canvas/node/${encodeURIComponent(id)}/refresh`,
|
|
280
|
+
{ ok: false },
|
|
281
|
+
{
|
|
282
|
+
method: 'POST',
|
|
283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
284
|
+
body: JSON.stringify(url ? { url } : {}),
|
|
285
|
+
},
|
|
286
|
+
);
|
|
261
287
|
}
|
|
262
288
|
|
|
263
289
|
/** Remove a canvas node via the server. */
|
|
264
290
|
export async function removeNodeFromClient(id: string): Promise<{ ok: boolean; removed?: string }> {
|
|
265
|
-
return requestJson(
|
|
266
|
-
|
|
267
|
-
|
|
291
|
+
return requestJson(
|
|
292
|
+
'removeNodeFromClient',
|
|
293
|
+
`/api/canvas/node/${encodeURIComponent(id)}`,
|
|
294
|
+
{ ok: false },
|
|
295
|
+
{
|
|
296
|
+
method: 'DELETE',
|
|
297
|
+
},
|
|
298
|
+
);
|
|
268
299
|
}
|
|
269
300
|
|
|
270
301
|
// ── PMX-AX node interactions ──────────────────────────────────
|
|
@@ -292,7 +323,10 @@ export async function fetchAxSurfaceState(): Promise<unknown> {
|
|
|
292
323
|
}
|
|
293
324
|
|
|
294
325
|
/** Ask the server to open a node's surface in the system browser. */
|
|
295
|
-
export async function openNodeInSystemBrowserRequest(
|
|
326
|
+
export async function openNodeInSystemBrowserRequest(
|
|
327
|
+
nodeId: string,
|
|
328
|
+
url?: string,
|
|
329
|
+
): Promise<{ ok: boolean; opened: boolean }> {
|
|
296
330
|
return requestJson<{ ok: boolean; opened: boolean }>(
|
|
297
331
|
'openNodeInSystemBrowserRequest',
|
|
298
332
|
'/api/canvas/open-external',
|
|
@@ -324,14 +358,19 @@ export async function updateViewportFromClient(
|
|
|
324
358
|
viewport: { x: number; y: number; scale: number },
|
|
325
359
|
options: { recordHistory?: boolean } = {},
|
|
326
360
|
): Promise<{ ok: boolean }> {
|
|
327
|
-
return requestJson(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
361
|
+
return requestJson(
|
|
362
|
+
'updateViewportFromClient',
|
|
363
|
+
'/api/canvas/viewport',
|
|
364
|
+
{ ok: false },
|
|
365
|
+
{
|
|
366
|
+
method: 'POST',
|
|
367
|
+
headers: { 'Content-Type': 'application/json' },
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
...viewport,
|
|
370
|
+
...(options.recordHistory === false ? { recordHistory: false } : {}),
|
|
371
|
+
}),
|
|
372
|
+
},
|
|
373
|
+
);
|
|
335
374
|
}
|
|
336
375
|
|
|
337
376
|
// ── Group API ─────────────────────────────────────────────────
|
|
@@ -346,29 +385,44 @@ export async function createGroupFromClient(opts: {
|
|
|
346
385
|
width?: number;
|
|
347
386
|
height?: number;
|
|
348
387
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
349
|
-
return requestJson(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
388
|
+
return requestJson(
|
|
389
|
+
'createGroupFromClient',
|
|
390
|
+
'/api/canvas/group',
|
|
391
|
+
{ ok: false },
|
|
392
|
+
{
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'Content-Type': 'application/json' },
|
|
395
|
+
body: JSON.stringify(opts),
|
|
396
|
+
},
|
|
397
|
+
);
|
|
354
398
|
}
|
|
355
399
|
|
|
356
400
|
/** Add nodes to an existing group. */
|
|
357
401
|
export async function addToGroupFromClient(groupId: string, childIds: string[]): Promise<{ ok: boolean }> {
|
|
358
|
-
return requestJson(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
402
|
+
return requestJson(
|
|
403
|
+
'addToGroupFromClient',
|
|
404
|
+
'/api/canvas/group/add',
|
|
405
|
+
{ ok: false },
|
|
406
|
+
{
|
|
407
|
+
method: 'POST',
|
|
408
|
+
headers: { 'Content-Type': 'application/json' },
|
|
409
|
+
body: JSON.stringify({ groupId, childIds }),
|
|
410
|
+
},
|
|
411
|
+
);
|
|
363
412
|
}
|
|
364
413
|
|
|
365
414
|
/** Ungroup all children from a group. */
|
|
366
415
|
export async function ungroupFromClient(groupId: string): Promise<{ ok: boolean }> {
|
|
367
|
-
return requestJson(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
416
|
+
return requestJson(
|
|
417
|
+
'ungroupFromClient',
|
|
418
|
+
'/api/canvas/group/ungroup',
|
|
419
|
+
{ ok: false },
|
|
420
|
+
{
|
|
421
|
+
method: 'POST',
|
|
422
|
+
headers: { 'Content-Type': 'application/json' },
|
|
423
|
+
body: JSON.stringify({ groupId }),
|
|
424
|
+
},
|
|
425
|
+
);
|
|
372
426
|
}
|
|
373
427
|
|
|
374
428
|
// ── Snapshot API ──────────────────────────────────────────────
|
|
@@ -386,11 +440,16 @@ export async function listSnapshots(): Promise<CanvasSnapshotInfo[]> {
|
|
|
386
440
|
}
|
|
387
441
|
|
|
388
442
|
export async function saveSnapshot(name: string): Promise<{ ok: boolean; snapshot?: CanvasSnapshotInfo }> {
|
|
389
|
-
return requestJson(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
443
|
+
return requestJson(
|
|
444
|
+
'saveSnapshot',
|
|
445
|
+
'/api/canvas/snapshots',
|
|
446
|
+
{ ok: false },
|
|
447
|
+
{
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
450
|
+
body: JSON.stringify({ name }),
|
|
451
|
+
},
|
|
452
|
+
);
|
|
394
453
|
}
|
|
395
454
|
|
|
396
455
|
export async function restoreSnapshot(id: string): Promise<{ ok: boolean }> {
|
|
@@ -403,9 +462,14 @@ export async function deleteSnapshot(id: string): Promise<{ ok: boolean }> {
|
|
|
403
462
|
|
|
404
463
|
/** Remove a canvas edge via the server. */
|
|
405
464
|
export async function removeEdgeFromClient(edgeId: string): Promise<{ ok: boolean }> {
|
|
406
|
-
return requestJson(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
465
|
+
return requestJson(
|
|
466
|
+
'removeEdgeFromClient',
|
|
467
|
+
'/api/canvas/edge',
|
|
468
|
+
{ ok: false },
|
|
469
|
+
{
|
|
470
|
+
method: 'DELETE',
|
|
471
|
+
headers: { 'Content-Type': 'application/json' },
|
|
472
|
+
body: JSON.stringify({ edge_id: edgeId }),
|
|
473
|
+
},
|
|
474
|
+
);
|
|
411
475
|
}
|
|
@@ -63,7 +63,10 @@ function setPhase(id: string, phase: IntentPhase, ms: number, settledNodeId?: st
|
|
|
63
63
|
next.set(id, { ...current, phase, ...(settledNodeId ? { settledNodeId } : {}) });
|
|
64
64
|
writeIntents(next);
|
|
65
65
|
clearExitTimer(id);
|
|
66
|
-
exitTimers.set(
|
|
66
|
+
exitTimers.set(
|
|
67
|
+
id,
|
|
68
|
+
setTimeout(() => removeIntent(id), ms),
|
|
69
|
+
);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/** Resolve a ghost into a real node — the settle morph, then removal. */
|