pmx-canvas 0.9.3 → 0.11.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 +9418 -2016
- package/CHANGELOG.md +54 -1
- package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
- package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
- package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
- package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
- package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
- package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
- package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
- package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
- package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
- package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
- package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
- package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
- package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
- package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
- package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
- package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
- package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
- package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
- package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
- package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
- package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
- package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
- package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
- package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
- package/dist/canvas/chunks/index-194m3hc3.js +2 -0
- package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
- package/dist/canvas/chunks/index-353m7b0p.js +2 -0
- package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
- package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
- package/dist/canvas/chunks/index-6ceya556.js +332 -0
- package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
- package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
- package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
- package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
- package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
- package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
- package/dist/canvas/chunks/index-nprm96xp.js +16 -0
- package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
- package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
- package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
- package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
- package/dist/canvas/chunks/index-s4j678gj.js +2 -0
- package/dist/canvas/chunks/index-tb1wf525.js +179 -0
- package/dist/canvas/chunks/index-vv56h019.js +2 -0
- package/dist/canvas/chunks/index-w2mra17t.js +207 -0
- package/dist/canvas/chunks/index-wgv7en21.js +232 -0
- package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
- package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
- package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
- package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
- package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
- package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
- package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
- package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
- package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
- package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
- package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
- package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
- package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
- package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
- package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
- package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
- package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
- package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
- package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
- package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
- package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
- package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
- package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
- package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
- package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
- package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
- package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
- package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
- package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
- package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
- package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
- package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
- package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
- package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
- package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
- package/dist/canvas/global.css +389 -229
- package/dist/canvas/index.js +141 -211
- package/dist/canvas/mcp-app.html +4205 -554
- package/dist/canvas/mermaid-entry.js +338 -338
- package/dist/canvas/surface-theme.css +6 -6
- package/dist/json-render/index.css +1 -1
- package/dist/json-render/index.js +49 -49
- package/dist/types/client/canvas/BarHint.d.ts +5 -3
- package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
- package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
- package/dist/types/client/canvas/auto-fit.d.ts +4 -5
- package/dist/types/client/mermaid-entry.d.ts +1 -2
- package/dist/types/client/mermaid-render.d.ts +1 -0
- package/dist/types/client/nodes/BoardNode.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
- package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
- package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
- package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
- package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
- package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
- package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
- package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
- package/dist/types/client/state/canvas-store.d.ts +3 -1
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/presentation.d.ts +17 -6
- package/dist/types/json-render/server.d.ts +2 -1
- package/dist/types/mcp/canvas-access.d.ts +1 -12
- package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
- package/dist/types/mcp/server.d.ts +2 -0
- package/dist/types/server/ax-wait.d.ts +1 -0
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/index.d.ts +11 -6
- package/dist/types/server/operations/http.d.ts +10 -0
- package/dist/types/server/operations/index.d.ts +2 -2
- package/dist/types/server/operations/invoker.d.ts +4 -3
- package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
- package/dist/types/server/operations/mcp.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +1 -0
- package/dist/types/server/operations/ops/boards.d.ts +0 -1
- package/dist/types/server/operations/registry.d.ts +2 -2
- package/dist/types/server/operations/types.d.ts +12 -2
- package/dist/types/server/server.d.ts +2 -0
- package/dist/types/server/tour-control.d.ts +13 -3
- package/dist/types/server/webpage-node.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
- package/dist/types/shared/tour.d.ts +8 -0
- package/docs/api-reference.md +2 -2
- package/docs/cli.md +1 -1
- package/docs/design/Chrome.dc.html +32 -6
- package/docs/design/Types1.dc.html +3 -2
- package/docs/design/Types2.dc.html +1 -1
- package/docs/design/Types3.dc.html +1 -1
- package/docs/design/Zoom.dc.html +37 -60
- package/docs/design/canvas.json +1 -1
- package/docs/design.md +169 -4
- package/docs/http-api.md +82 -1
- package/docs/mcp.md +101 -21
- package/docs/node-types.md +38 -3
- package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
- package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
- package/docs/product-vision-2026-09.md +9 -0
- package/docs/sdk.md +5 -2
- package/package.json +2 -2
- 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 +9418 -2016
- package/skills/pmx-canvas/SKILL.md +19 -21
- package/skills/pmx-canvas/references/full-reference.md +44 -6
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
- package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
- package/src/client/canvas/BarHint.tsx +6 -3
- package/src/client/canvas/CanvasNode.tsx +34 -47
- package/src/client/canvas/CanvasViewport.tsx +6 -7
- package/src/client/canvas/ContextMenu.tsx +63 -3
- package/src/client/canvas/EdgeLayer.tsx +365 -169
- package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
- package/src/client/canvas/Presentation.tsx +53 -3
- package/src/client/canvas/ToolRail.tsx +2 -0
- package/src/client/canvas/TopBar.tsx +24 -9
- package/src/client/canvas/auto-fit.ts +9 -11
- package/src/client/canvas/canvas-area.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +6 -1
- package/src/client/mermaid-entry.ts +6 -20
- package/src/client/mermaid-render.ts +39 -0
- package/src/client/nodes/AxStepControls.tsx +45 -69
- package/src/client/nodes/BoardNode.tsx +65 -9
- package/src/client/nodes/ContextNode.tsx +88 -396
- package/src/client/nodes/ExtAppFrame.tsx +283 -1462
- package/src/client/nodes/FileNode.tsx +92 -81
- package/src/client/nodes/HtmlNode.tsx +8 -36
- package/src/client/nodes/LedgerNode.tsx +31 -17
- package/src/client/nodes/MarkdownNode.tsx +8 -17
- package/src/client/nodes/McpAppNode.tsx +48 -154
- package/src/client/nodes/MermaidNode.tsx +80 -69
- package/src/client/nodes/PromptNode.tsx +12 -14
- package/src/client/nodes/ResponseNode.tsx +8 -6
- package/src/client/nodes/StatusNode.tsx +22 -7
- package/src/client/nodes/StatusSummary.tsx +5 -3
- package/src/client/nodes/TraceNode.tsx +8 -8
- package/src/client/nodes/ViewerFrame.tsx +262 -0
- package/src/client/nodes/ViewerHost.tsx +41 -0
- package/src/client/nodes/WebpageNode.tsx +22 -24
- package/src/client/nodes/iframe-document-url.ts +20 -27
- package/src/client/nodes/primitive-copy.ts +97 -0
- package/src/client/nodes/use-iframe-content-height.ts +34 -10
- package/src/client/nodes/use-surface-frame.ts +22 -11
- package/src/client/nodes/viewer-runtime-state.ts +41 -35
- package/src/client/state/canvas-store.ts +18 -12
- package/src/client/state/intent-bridge.ts +1 -0
- package/src/client/state/presentation.ts +135 -15
- package/src/client/state/sse-bridge.ts +18 -19
- package/src/client/theme/global.css +389 -229
- package/src/client/theme/surface-theme.css +6 -6
- package/src/json-render/charts/tufte-components.tsx +44 -2
- package/src/json-render/renderer/index.css +62 -2
- package/src/json-render/renderer/index.tsx +31 -7
- package/src/json-render/server.ts +3 -2
- package/src/mcp/canvas-access.ts +6 -47
- package/src/mcp/resource-subscriptions.ts +181 -0
- package/src/mcp/server.ts +185 -219
- package/src/server/ax-wait.ts +21 -6
- package/src/server/board-export.ts +35 -24
- package/src/server/canvas-geometry.ts +1 -0
- package/src/server/canvas-operations.ts +6 -2
- package/src/server/canvas-state.ts +5 -0
- package/src/server/demo-state.json +106 -100
- package/src/server/html-primitives.ts +3 -9
- package/src/server/html-surface.ts +8 -0
- package/src/server/index.ts +30 -12
- package/src/server/operations/http.ts +69 -21
- package/src/server/operations/index.ts +8 -1
- package/src/server/operations/invoker.ts +53 -5
- package/src/server/operations/mcp-contracts.ts +354 -0
- package/src/server/operations/mcp.ts +153 -18
- package/src/server/operations/ops/ax-await.ts +18 -10
- package/src/server/operations/ops/ax-read.ts +2 -0
- package/src/server/operations/ops/ax-timeline.ts +2 -2
- package/src/server/operations/ops/batch.ts +27 -8
- package/src/server/operations/ops/boards.ts +1 -3
- package/src/server/operations/ops/nodes.ts +18 -3
- package/src/server/operations/ops/viewport.ts +11 -9
- package/src/server/operations/registry.ts +24 -4
- package/src/server/operations/types.ts +16 -2
- package/src/server/server.ts +17 -59
- package/src/server/tour-control.ts +66 -6
- package/src/server/webpage-node.ts +9 -4
- package/src/shared/ax-surface-protocol.ts +0 -11
- package/src/shared/content-height-reporter.ts +4 -3
- package/src/shared/tour.ts +8 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'preact/hooks';
|
|
3
2
|
import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
4
3
|
import { canvasThemeScheme } from '../../shared/themes.js';
|
|
5
4
|
import type { CanvasNodeState } from '../types';
|
|
6
5
|
import { axSurfaceState, canvasTheme } from '../state/canvas-store';
|
|
7
|
-
import {
|
|
8
|
-
import { iframeMode } from '../state/iframe-mode';
|
|
6
|
+
import { RefreshingViewerFrame } from './ViewerFrame';
|
|
9
7
|
import { shouldContentFitIframeNode } from '../canvas/auto-fit';
|
|
10
8
|
import { ExtAppFrame } from './ExtAppFrame';
|
|
11
9
|
import { useAxSurfaceBridge } from './use-ax-surface-bridge';
|
|
@@ -16,6 +14,7 @@ import { isHostedWorkbench } from '../state/workbench-transport';
|
|
|
16
14
|
|
|
17
15
|
function withViewerParams(
|
|
18
16
|
url: string,
|
|
17
|
+
theme: string,
|
|
19
18
|
expanded: boolean,
|
|
20
19
|
specVersion?: number,
|
|
21
20
|
axToken?: string,
|
|
@@ -28,9 +27,7 @@ function withViewerParams(
|
|
|
28
27
|
try {
|
|
29
28
|
const hosted = isHostedWorkbench() && url.startsWith('/');
|
|
30
29
|
const resolved = new URL(url, hosted ? 'https://pmx.invalid' : window.location.origin);
|
|
31
|
-
|
|
32
|
-
// themes (sepia → light, midnight/arctic/ember/forest → dark) to a scheme.
|
|
33
|
-
resolved.searchParams.set('theme', canvasThemeScheme(canvasTheme.value));
|
|
30
|
+
resolved.searchParams.set('theme', theme);
|
|
34
31
|
if (expanded) resolved.searchParams.set('display', 'expanded');
|
|
35
32
|
// Streaming json-render nodes bump specVersion as patches accumulate; including
|
|
36
33
|
// it in the src reloads the iframe so it re-reads the latest accumulated spec.
|
|
@@ -61,139 +58,6 @@ export function isSameOriginFrameDocumentUrl(url: string, origin = window.locati
|
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
type ViewerFrameSource = { src?: string; srcdoc?: string };
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Keep the last painted viewer in front while its replacement navigates. Viewer
|
|
68
|
-
* specs are immutable documents, so updates normally change `?v=` and reload the
|
|
69
|
-
* iframe; putting the new document behind the old one conceals Chromium's white
|
|
70
|
-
* navigation paint without suppressing rendering. Wait for restore and render commit,
|
|
71
|
-
* then two animation frames before the old viewer is removed.
|
|
72
|
-
*/
|
|
73
|
-
export function RefreshingViewerFrame({
|
|
74
|
-
source,
|
|
75
|
-
iframeRef,
|
|
76
|
-
onLoad,
|
|
77
|
-
onDocumentLoad,
|
|
78
|
-
title,
|
|
79
|
-
className = 'mcp-app-frame',
|
|
80
|
-
sandbox = 'allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox',
|
|
81
|
-
allow = 'clipboard-read; clipboard-write',
|
|
82
|
-
tabIndex,
|
|
83
|
-
}: {
|
|
84
|
-
source: ViewerFrameSource;
|
|
85
|
-
iframeRef: RefObject<HTMLIFrameElement>;
|
|
86
|
-
onLoad: () => void;
|
|
87
|
-
onDocumentLoad?: (frame: HTMLIFrameElement, signal: AbortSignal) => void | Promise<boolean | 'cancelled' | void>;
|
|
88
|
-
title: string;
|
|
89
|
-
className?: string;
|
|
90
|
-
sandbox?: string;
|
|
91
|
-
allow?: string;
|
|
92
|
-
tabIndex?: number;
|
|
93
|
-
}) {
|
|
94
|
-
const sourceKey = source.src ?? source.srcdoc ?? '';
|
|
95
|
-
const [painted, setPainted] = useState({ key: sourceKey, source, attempt: 0 });
|
|
96
|
-
const [pending, setPending] = useState<{ key: string; source: ViewerFrameSource; attempt: number } | null>(null);
|
|
97
|
-
const promotionRef = useRef(0);
|
|
98
|
-
const readiness = useRef(new Set<AbortController>());
|
|
99
|
-
|
|
100
|
-
useLayoutEffect(() => {
|
|
101
|
-
promotionRef.current += 1;
|
|
102
|
-
for (const controller of readiness.current) controller.abort();
|
|
103
|
-
readiness.current.clear();
|
|
104
|
-
if (!sourceKey || sourceKey === painted.key) {
|
|
105
|
-
setPending(null);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (!painted.key) {
|
|
109
|
-
setPainted({ key: sourceKey, source, attempt: 0 });
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setPending({ key: sourceKey, source, attempt: 0 });
|
|
113
|
-
}, [sourceKey, painted.key]);
|
|
114
|
-
|
|
115
|
-
useEffect(
|
|
116
|
-
() => () => {
|
|
117
|
-
promotionRef.current += 1;
|
|
118
|
-
for (const controller of readiness.current) controller.abort();
|
|
119
|
-
readiness.current.clear();
|
|
120
|
-
},
|
|
121
|
-
[],
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const frame = (entry: { key: string; source: ViewerFrameSource; attempt: number }, isPending: boolean) => (
|
|
125
|
-
<iframe
|
|
126
|
-
key={`${entry.key}:${entry.attempt}`}
|
|
127
|
-
ref={isPending ? undefined : iframeRef}
|
|
128
|
-
{...entry.source}
|
|
129
|
-
class={className}
|
|
130
|
-
sandbox={sandbox}
|
|
131
|
-
allow={allow}
|
|
132
|
-
tabIndex={tabIndex}
|
|
133
|
-
inert={isPending}
|
|
134
|
-
aria-hidden={isPending ? true : undefined}
|
|
135
|
-
loading={iframeMode.value === 'srcdoc' ? undefined : 'lazy'}
|
|
136
|
-
onLoad={async (event) => {
|
|
137
|
-
// Seed runtime state while a replacement is still behind the painted
|
|
138
|
-
// frame, and wait until that document has committed its rendered UI.
|
|
139
|
-
const token = isPending ? ++promotionRef.current : promotionRef.current;
|
|
140
|
-
const loadedFrame = event.currentTarget;
|
|
141
|
-
const controller = new AbortController();
|
|
142
|
-
readiness.current.add(controller);
|
|
143
|
-
const ready = onDocumentLoad?.(loadedFrame, controller.signal);
|
|
144
|
-
const loaded = ready ? await ready : undefined;
|
|
145
|
-
readiness.current.delete(controller);
|
|
146
|
-
if (promotionRef.current !== token || loaded === 'cancelled') return;
|
|
147
|
-
if (loaded === false) {
|
|
148
|
-
if (entry.attempt === 0) {
|
|
149
|
-
const retry = { ...entry, attempt: 1 };
|
|
150
|
-
if (isPending) setPending(retry);
|
|
151
|
-
else setPainted(retry);
|
|
152
|
-
} else {
|
|
153
|
-
if (isPending) setPending(null);
|
|
154
|
-
showToast(
|
|
155
|
-
'remove',
|
|
156
|
-
'Viewer could not load',
|
|
157
|
-
`${isPending ? 'Keeping the last view. ' : ''}Reload to retry.`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (!isPending) {
|
|
163
|
-
onLoad();
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
requestAnimationFrame(() =>
|
|
167
|
-
requestAnimationFrame(() => {
|
|
168
|
-
if (promotionRef.current !== token) return;
|
|
169
|
-
iframeRef.current = loadedFrame;
|
|
170
|
-
setPainted(entry);
|
|
171
|
-
setPending(null);
|
|
172
|
-
onLoad();
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
}}
|
|
176
|
-
style={{
|
|
177
|
-
position: 'absolute',
|
|
178
|
-
inset: 0,
|
|
179
|
-
width: '100%',
|
|
180
|
-
height: '100%',
|
|
181
|
-
visibility: 'visible',
|
|
182
|
-
zIndex: isPending ? 0 : 1,
|
|
183
|
-
background: 'var(--c-panel-soft)',
|
|
184
|
-
}}
|
|
185
|
-
title={title}
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<div class="mcp-app-frame-stack">
|
|
191
|
-
{frame(painted, false)}
|
|
192
|
-
{pending && frame(pending, true)}
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
61
|
export function McpAppNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
198
62
|
if (node.data.mode === 'ext-app') {
|
|
199
63
|
return <ExtAppFrame node={node} expanded={expanded} />;
|
|
@@ -223,7 +87,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
223
87
|
// overlay frame (fill-down), not sit at its intrinsic in-canvas height.
|
|
224
88
|
const contentFit = shouldContentFitIframeNode(node) && !expanded;
|
|
225
89
|
const frameToken = useMemo(() => (contentFit ? `frame-${crypto.randomUUID()}` : ''), [contentFit]);
|
|
226
|
-
useIframeContentHeight(node, iframeRef, frameToken);
|
|
90
|
+
const measureContent = useIframeContentHeight(node, iframeRef, frameToken);
|
|
227
91
|
|
|
228
92
|
// AX emits forwarded by the json-render viewer — the shared sandboxed-surface
|
|
229
93
|
// trust boundary (M2).
|
|
@@ -257,15 +121,38 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
257
121
|
() => (isJsonViewer ? `ui-${crypto.randomUUID()}` : ''),
|
|
258
122
|
[isJsonViewer, specVersion, node.data.url],
|
|
259
123
|
);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
124
|
+
const theme = canvasTheme.value;
|
|
125
|
+
const urlTheme = isJsonViewer ? null : canvasThemeScheme(theme);
|
|
126
|
+
const pushTheme = () => {
|
|
127
|
+
if (!isJsonViewer) return;
|
|
128
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
129
|
+
{
|
|
130
|
+
source: HTML_SURFACE_PUSH_SOURCE,
|
|
131
|
+
type: 'theme-update',
|
|
132
|
+
token: stateToken,
|
|
133
|
+
theme: node.data.theme ?? theme,
|
|
134
|
+
title: node.data.title,
|
|
135
|
+
},
|
|
136
|
+
'*',
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
useEffect(pushTheme, [theme, stateToken, node.data.theme, node.data.title]);
|
|
140
|
+
// Only JSON/graph understand theme messages and retain drafts across themes.
|
|
141
|
+
// Generic viewers consume a dark/light query and navigate when it changes.
|
|
142
|
+
const url = useMemo(
|
|
143
|
+
() =>
|
|
144
|
+
withViewerParams(
|
|
145
|
+
(node.data.url as string) || '',
|
|
146
|
+
urlTheme ?? theme,
|
|
147
|
+
expanded,
|
|
148
|
+
specVersion,
|
|
149
|
+
axToken || undefined,
|
|
150
|
+
isAxViewer ? node.id : undefined,
|
|
151
|
+
frameToken || undefined,
|
|
152
|
+
contentFit,
|
|
153
|
+
stateToken || undefined,
|
|
154
|
+
),
|
|
155
|
+
[node.data.url, urlTheme, expanded, specVersion, axToken, node.id, frameToken, contentFit, stateToken],
|
|
269
156
|
);
|
|
270
157
|
// src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
|
|
271
158
|
// External viewer URLs always stay src — the hook passes them through.
|
|
@@ -295,7 +182,9 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
295
182
|
>
|
|
296
183
|
{url}
|
|
297
184
|
</a>
|
|
298
|
-
{sourceServer &&
|
|
185
|
+
{sourceServer && (
|
|
186
|
+
<div style={{ color: 'var(--c-dim)', fontSize: 'var(--fs-meta)' }}>Source: {sourceServer}</div>
|
|
187
|
+
)}
|
|
299
188
|
</div>
|
|
300
189
|
);
|
|
301
190
|
}
|
|
@@ -313,7 +202,7 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
313
202
|
<div
|
|
314
203
|
style={{
|
|
315
204
|
padding: '4px 8px',
|
|
316
|
-
fontSize: '
|
|
205
|
+
fontSize: 'var(--fs-meta)',
|
|
317
206
|
background: 'color-mix(in srgb, var(--c-warn) 10%, transparent)',
|
|
318
207
|
color: 'var(--c-warn)',
|
|
319
208
|
borderBottom: '1px solid color-mix(in srgb, var(--c-warn) 15%, transparent)',
|
|
@@ -328,11 +217,16 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
328
217
|
<RefreshingViewerFrame
|
|
329
218
|
source={surfaceFrame}
|
|
330
219
|
iframeRef={iframeRef}
|
|
220
|
+
allow="clipboard-read; clipboard-write"
|
|
331
221
|
onDocumentLoad={(frame, signal) => {
|
|
332
222
|
if (stateToken && frame.contentWindow)
|
|
333
|
-
return restoreViewerRuntimeState(node
|
|
223
|
+
return restoreViewerRuntimeState(node, stateToken, frame.contentWindow, signal);
|
|
224
|
+
}}
|
|
225
|
+
onLoad={() => {
|
|
226
|
+
measureContent();
|
|
227
|
+
pushAxState();
|
|
228
|
+
pushTheme();
|
|
334
229
|
}}
|
|
335
|
-
onLoad={pushAxState}
|
|
336
230
|
title={`MCP App: ${sourceServer}`}
|
|
337
231
|
/>
|
|
338
232
|
</div>
|
|
@@ -1,82 +1,93 @@
|
|
|
1
|
-
import { useEffect,
|
|
2
|
-
import {
|
|
3
|
-
import { canvasTheme } from '../state/canvas-store';
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { renderMermaid } from '../mermaid-render';
|
|
3
|
+
import { canvasTheme, nodes, updateNode } from '../state/canvas-store';
|
|
4
|
+
import { pushCanvasUpdate } from '../state/intent-bridge';
|
|
5
|
+
import { computeContentGrowHeight, contentFitPosition } from '../canvas/auto-fit';
|
|
4
6
|
import type { CanvasNodeState } from '../types';
|
|
5
|
-
import { nodeSurfaceUrl, surfaceContentHash } from './surface-url';
|
|
6
|
-
import { useIframeContentHeight } from './use-iframe-content-height';
|
|
7
|
-
import { useSurfaceFrame } from './use-surface-frame';
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* Mermaid diagram node — a display-only iframe surface. The server renders the
|
|
11
|
-
* surface document (escaped diagram source + /canvas/mermaid-entry.js) at
|
|
12
|
-
* /api/canvas/surface/:id; the iframe and "Open as site" share that one render
|
|
13
|
-
* path, exactly like html nodes. Minimal subset of HtmlNode: theme push and
|
|
14
|
-
* content-height growth, no AX bridge, no presentation mode.
|
|
15
|
-
*/
|
|
16
8
|
export function MermaidNode({ node, expanded = false }: { node: CanvasNodeState; expanded?: boolean }) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const themeToken = useMemo(() => `theme-${crypto.randomUUID()}`, []);
|
|
21
|
-
// Per-mount nonce for the content-height reporter (node grows to fit content).
|
|
22
|
-
const frameToken = useMemo(() => `frame-${crypto.randomUUID()}`, []);
|
|
9
|
+
const container = useRef<HTMLDivElement>(null);
|
|
10
|
+
const [error, setError] = useState('');
|
|
11
|
+
const [attempt, setAttempt] = useState(0);
|
|
23
12
|
const source = typeof node.data.content === 'string' ? node.data.content : '';
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const surfaceSrc = useMemo(
|
|
31
|
-
() => (source ? nodeSurfaceUrl(node.id, { theme, themeToken, v, frameToken }) : ''),
|
|
32
|
-
[source, themeToken, v, node.id, frameToken],
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// src vs fetch()+srcdoc, decided by the boot-wide embed probe (Amp portals).
|
|
36
|
-
const surfaceFrame = useSurfaceFrame(surfaceSrc);
|
|
13
|
+
const theme = canvasTheme.value;
|
|
14
|
+
const fit = node.data.fit !== 'none';
|
|
15
|
+
// Fit is layout, not content: it resizes the painted SVG in place. Re-rendering
|
|
16
|
+
// on a fit change replaced the element and left a detached SVG mid-update.
|
|
17
|
+
const fitRef = useRef(fit);
|
|
18
|
+
fitRef.current = fit;
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
const fitDiagram = () => {
|
|
21
|
+
const element = container.current;
|
|
22
|
+
const svg = element?.querySelector('svg');
|
|
23
|
+
const box = svg?.viewBox.baseVal;
|
|
24
|
+
if (!element || !svg || !box?.width || !box.height) return;
|
|
25
|
+
const scale = fitRef.current ? Math.min(1, element.clientWidth / box.width, element.clientHeight / box.height) : 1;
|
|
26
|
+
svg.style.maxWidth = 'none';
|
|
27
|
+
svg.style.width = `${box.width * scale}px`;
|
|
28
|
+
svg.style.height = `${box.height * scale}px`;
|
|
29
|
+
svg.style.margin = fitRef.current ? '0 auto' : '0';
|
|
30
|
+
};
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
32
|
+
const growToDiagram = () => {
|
|
33
|
+
const element = container.current;
|
|
34
|
+
const box = element?.querySelector('svg')?.viewBox.baseVal;
|
|
35
|
+
const current = nodes.value.get(node.id);
|
|
36
|
+
if (expanded || !element || !box?.width || !current) return;
|
|
37
|
+
const naturalHeight = box.height * (fitRef.current ? Math.min(1, element.clientWidth / box.width) : 1);
|
|
38
|
+
const card = element.closest<HTMLElement>('.canvas-node')!;
|
|
39
|
+
const height = computeContentGrowHeight(current, naturalHeight, card.offsetHeight - element.clientHeight);
|
|
40
|
+
if (height === null) return;
|
|
41
|
+
const position = contentFitPosition(current, height, Array.from(nodes.value.values()));
|
|
42
|
+
const patch = { size: { width: current.size.width, height }, position };
|
|
43
|
+
updateNode(node.id, patch);
|
|
44
|
+
void pushCanvasUpdate([{ id: node.id, ...patch }], { recordHistory: false });
|
|
52
45
|
};
|
|
53
46
|
|
|
54
|
-
useEffect(
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let cancelled = false;
|
|
49
|
+
const element = container.current;
|
|
50
|
+
if (!element || !source) return;
|
|
51
|
+
const observer = new ResizeObserver(fitDiagram);
|
|
52
|
+
observer.observe(element);
|
|
53
|
+
const render = async () => {
|
|
54
|
+
try {
|
|
55
|
+
const svg = await renderMermaid(source);
|
|
56
|
+
if (cancelled) return;
|
|
57
|
+
element.innerHTML = svg;
|
|
58
|
+
setError('');
|
|
59
|
+
element.querySelector('svg')?.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
60
|
+
fitDiagram();
|
|
61
|
+
growToDiagram();
|
|
62
|
+
} catch (failure) {
|
|
63
|
+
if (!cancelled) setError(failure instanceof Error ? failure.message : String(failure));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
void render();
|
|
67
|
+
return () => {
|
|
68
|
+
cancelled = true;
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
};
|
|
71
|
+
}, [source, theme, expanded, attempt]);
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
// Same commit as data-fit, so the attribute and the painted size never disagree.
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
fitDiagram();
|
|
76
|
+
growToDiagram();
|
|
77
|
+
}, [fit]);
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
// (see HtmlNode for the full rationale); the surface route sends a matching
|
|
62
|
-
// CSP sandbox header.
|
|
79
|
+
if (!source) return <div class="viewer-placeholder">No diagram source set</div>;
|
|
63
80
|
return (
|
|
64
|
-
<
|
|
65
|
-
ref={
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
border: 'none',
|
|
76
|
-
background: 'var(--c-bg)',
|
|
77
|
-
borderRadius: '6px',
|
|
78
|
-
display: 'block',
|
|
79
|
-
}}
|
|
80
|
-
/>
|
|
81
|
+
<div class="mermaid-viewer" data-fit={fit ? 'contain' : 'none'}>
|
|
82
|
+
<div ref={container} class="mermaid-diagram" />
|
|
83
|
+
{error && (
|
|
84
|
+
<div class="viewer-fallback" role="status">
|
|
85
|
+
<span>{error}</span>
|
|
86
|
+
<button type="button" onClick={() => setAttempt(attempt + 1)}>
|
|
87
|
+
Retry
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
81
92
|
);
|
|
82
93
|
}
|
|
@@ -57,7 +57,7 @@ function ContextBadge({ count }: { count: number }) {
|
|
|
57
57
|
padding: '4px 8px',
|
|
58
58
|
fontSize: '11px',
|
|
59
59
|
color: 'var(--c-accent)',
|
|
60
|
-
background: '
|
|
60
|
+
background: 'color-mix(in srgb, var(--c-pin) 8%, transparent)',
|
|
61
61
|
borderRadius: 'var(--radius-sm)',
|
|
62
62
|
flexShrink: 0,
|
|
63
63
|
}}
|
|
@@ -75,7 +75,7 @@ function ErrorBanner({ message, onDismiss }: { message: string | null; onDismiss
|
|
|
75
75
|
padding: '4px 8px',
|
|
76
76
|
fontSize: '11px',
|
|
77
77
|
color: 'var(--c-danger)',
|
|
78
|
-
background: '
|
|
78
|
+
background: 'color-mix(in srgb, var(--c-danger) 8%, transparent)',
|
|
79
79
|
borderRadius: 'var(--radius-sm)',
|
|
80
80
|
display: 'flex',
|
|
81
81
|
alignItems: 'center',
|
|
@@ -312,7 +312,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
312
312
|
style={{
|
|
313
313
|
flex: 1,
|
|
314
314
|
resize: 'none',
|
|
315
|
-
background: '
|
|
315
|
+
background: 'var(--c-input-bg)',
|
|
316
316
|
border: '1px solid var(--c-line)',
|
|
317
317
|
borderRadius: 'var(--radius-sm)',
|
|
318
318
|
color: 'var(--c-text)',
|
|
@@ -324,7 +324,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
324
324
|
}}
|
|
325
325
|
/>
|
|
326
326
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
327
|
-
<
|
|
327
|
+
<kbd class="prompt-key-hint">{'\u2318'}+Enter to send</kbd>
|
|
328
328
|
<button
|
|
329
329
|
type="button"
|
|
330
330
|
onClick={handleSubmit}
|
|
@@ -377,10 +377,9 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
377
377
|
>
|
|
378
378
|
<span
|
|
379
379
|
style={{
|
|
380
|
-
fontSize: '
|
|
381
|
-
textTransform: 'uppercase',
|
|
380
|
+
fontSize: 'var(--fs-meta)',
|
|
382
381
|
fontWeight: 600,
|
|
383
|
-
color: isPending ? 'var(--c-
|
|
382
|
+
color: isPending ? 'var(--c-agent)' : isAnswered ? 'var(--c-ok)' : 'var(--c-muted)',
|
|
384
383
|
}}
|
|
385
384
|
>
|
|
386
385
|
{isPending ? 'Sending…' : isAnswered ? 'Answered' : legacyStatus}
|
|
@@ -481,13 +480,12 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
481
480
|
>
|
|
482
481
|
<span
|
|
483
482
|
style={{
|
|
484
|
-
fontSize: '
|
|
485
|
-
textTransform: 'uppercase',
|
|
483
|
+
fontSize: 'var(--fs-meta)',
|
|
486
484
|
fontWeight: 600,
|
|
487
485
|
color: isStreaming
|
|
488
|
-
? 'var(--c-
|
|
486
|
+
? 'var(--c-agent)'
|
|
489
487
|
: isPending
|
|
490
|
-
? 'var(--c-
|
|
488
|
+
? 'var(--c-agent)'
|
|
491
489
|
: isAnswered
|
|
492
490
|
? 'var(--c-ok)'
|
|
493
491
|
: 'var(--c-muted)',
|
|
@@ -495,7 +493,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
495
493
|
>
|
|
496
494
|
{isStreaming ? 'Streaming…' : isPending ? 'Sending…' : isAnswered ? 'Answered' : threadStatus}
|
|
497
495
|
</span>
|
|
498
|
-
<span style={{ fontSize: '
|
|
496
|
+
<span style={{ fontSize: 'var(--fs-meta)', color: 'var(--c-muted)' }}>
|
|
499
497
|
{turns.length} turn{turns.length !== 1 ? 's' : ''}
|
|
500
498
|
</span>
|
|
501
499
|
</div>
|
|
@@ -513,7 +511,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
513
511
|
style={{
|
|
514
512
|
width: '100%',
|
|
515
513
|
resize: 'none',
|
|
516
|
-
background: '
|
|
514
|
+
background: 'var(--c-input-bg)',
|
|
517
515
|
border: '1px solid var(--c-line)',
|
|
518
516
|
borderRadius: 'var(--radius-sm)',
|
|
519
517
|
color: 'var(--c-text)',
|
|
@@ -532,7 +530,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
532
530
|
marginTop: '4px',
|
|
533
531
|
}}
|
|
534
532
|
>
|
|
535
|
-
<
|
|
533
|
+
<kbd class="prompt-key-hint">{'\u2318'}+Enter</kbd>
|
|
536
534
|
<button
|
|
537
535
|
type="button"
|
|
538
536
|
onClick={handleReplySubmit}
|
|
@@ -49,13 +49,16 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
51
|
<div class="response-node-inner" style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
52
|
+
<div class="thread-turn-role" style={{ color: 'var(--c-agent)' }}>
|
|
53
|
+
✦ Agent
|
|
54
|
+
</div>
|
|
52
55
|
{/* Streaming indicator */}
|
|
53
56
|
{isStreaming && (
|
|
54
57
|
<div
|
|
55
58
|
class="response-streaming-bar"
|
|
56
59
|
style={{
|
|
57
60
|
height: '2px',
|
|
58
|
-
background: 'linear-gradient(90deg, transparent, var(--c-
|
|
61
|
+
background: 'linear-gradient(90deg, transparent, var(--c-agent), transparent)',
|
|
59
62
|
animation: 'response-stream-pulse 1.5s ease-in-out infinite',
|
|
60
63
|
borderRadius: '1px',
|
|
61
64
|
marginBottom: '4px',
|
|
@@ -100,10 +103,9 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
100
103
|
>
|
|
101
104
|
<span
|
|
102
105
|
style={{
|
|
103
|
-
fontSize: '
|
|
104
|
-
textTransform: 'uppercase',
|
|
106
|
+
fontSize: 'var(--fs-meta)',
|
|
105
107
|
fontWeight: 600,
|
|
106
|
-
color: isStreaming ? 'var(--c-
|
|
108
|
+
color: isStreaming ? 'var(--c-agent)' : isComplete ? 'var(--c-ok)' : 'var(--c-muted)',
|
|
107
109
|
}}
|
|
108
110
|
>
|
|
109
111
|
{isStreaming ? 'Streaming…' : isComplete ? 'Complete' : status}
|
|
@@ -115,8 +117,8 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
115
117
|
style={{
|
|
116
118
|
padding: '3px 10px',
|
|
117
119
|
fontSize: '11px',
|
|
118
|
-
background: '
|
|
119
|
-
border: '1px solid
|
|
120
|
+
background: 'var(--c-panel-soft)',
|
|
121
|
+
border: '1px solid var(--c-line)',
|
|
120
122
|
borderRadius: 'var(--radius-sm)',
|
|
121
123
|
color: 'var(--c-accent)',
|
|
122
124
|
cursor: 'pointer',
|
|
@@ -19,6 +19,7 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
|
|
|
19
19
|
const level = (node.data.level as string) || 'ok';
|
|
20
20
|
const activeTool = node.data.activeTool as string | null;
|
|
21
21
|
const subagent = node.data.subagent as { state: string; name: string } | undefined;
|
|
22
|
+
const progress = typeof node.data.progress === 'number' ? Math.max(0, Math.min(100, node.data.progress)) : null;
|
|
22
23
|
|
|
23
24
|
const phaseColor = PHASE_COLORS[phase] ?? 'var(--c-muted)';
|
|
24
25
|
const isActive = phase !== 'idle';
|
|
@@ -33,7 +34,6 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
|
|
|
33
34
|
height: '8px',
|
|
34
35
|
borderRadius: '50%',
|
|
35
36
|
background: phaseColor,
|
|
36
|
-
boxShadow: isActive ? `0 0 8px ${phaseColor}` : 'none',
|
|
37
37
|
animation: isActive ? 'pulse 1.5s infinite' : 'none',
|
|
38
38
|
flexShrink: 0,
|
|
39
39
|
}}
|
|
@@ -42,11 +42,13 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
|
|
|
42
42
|
style={{
|
|
43
43
|
fontWeight: 600,
|
|
44
44
|
color: phaseColor,
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
fontSize: 'var(--fs-meta)',
|
|
46
|
+
padding: '2px 8px',
|
|
47
|
+
borderRadius: 'var(--r-pill)',
|
|
48
|
+
background: `color-mix(in srgb, ${phaseColor} 12%, transparent)`,
|
|
47
49
|
}}
|
|
48
50
|
>
|
|
49
|
-
{phase}
|
|
51
|
+
{phase.charAt(0).toUpperCase() + phase.slice(1)}
|
|
50
52
|
</span>
|
|
51
53
|
{detail && (
|
|
52
54
|
<span
|
|
@@ -62,10 +64,23 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
|
|
|
62
64
|
)}
|
|
63
65
|
</div>
|
|
64
66
|
|
|
67
|
+
{progress !== null && (
|
|
68
|
+
<div
|
|
69
|
+
class="status-progress"
|
|
70
|
+
role="progressbar"
|
|
71
|
+
aria-label="Progress"
|
|
72
|
+
aria-valuemin={0}
|
|
73
|
+
aria-valuemax={100}
|
|
74
|
+
aria-valuenow={progress}
|
|
75
|
+
>
|
|
76
|
+
<div style={{ width: `${progress}%`, background: phaseColor }} />
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
|
|
65
80
|
{/* Active tool */}
|
|
66
81
|
{activeTool && (
|
|
67
82
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--c-agent)' }}>
|
|
68
|
-
<span style={{ fontSize: '
|
|
83
|
+
<span style={{ fontSize: 'var(--fs-meta)' }}>⚙</span>
|
|
69
84
|
<span style={{ fontFamily: 'var(--font-code)', fontSize: '11px' }}>{activeTool}</span>
|
|
70
85
|
</div>
|
|
71
86
|
)}
|
|
@@ -73,9 +88,9 @@ export function StatusNode({ node }: { node: CanvasNodeState }) {
|
|
|
73
88
|
{/* Sub-agent */}
|
|
74
89
|
{subagent && subagent.state !== 'completed' && (
|
|
75
90
|
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: 'var(--c-subagent)' }}>
|
|
76
|
-
<span style={{ fontSize: '
|
|
91
|
+
<span style={{ fontSize: 'var(--fs-meta)' }}>⠉</span>
|
|
77
92
|
<span>{subagent.name}</span>
|
|
78
|
-
<span style={{ color: 'var(--c-muted)', fontSize: '
|
|
93
|
+
<span style={{ color: 'var(--c-muted)', fontSize: 'var(--fs-meta)' }}>({subagent.state})</span>
|
|
79
94
|
</div>
|
|
80
95
|
)}
|
|
81
96
|
|
|
@@ -21,14 +21,16 @@ export function StatusSummary({ node }: { node: CanvasNodeState }) {
|
|
|
21
21
|
flexShrink: 0,
|
|
22
22
|
}}
|
|
23
23
|
/>
|
|
24
|
-
<span style={{ color: phaseColor, fontSize: '
|
|
24
|
+
<span style={{ color: phaseColor, fontSize: 'var(--fs-meta)', fontWeight: 600 }}>
|
|
25
|
+
{phase.charAt(0).toUpperCase() + phase.slice(1)}
|
|
26
|
+
</span>
|
|
25
27
|
{activeTool && (
|
|
26
|
-
<span style={{ color: 'var(--c-agent)', fontSize: '
|
|
28
|
+
<span style={{ color: 'var(--c-agent)', fontSize: 'var(--fs-meta)', fontFamily: 'var(--font-code)' }}>
|
|
27
29
|
⚙ {activeTool}
|
|
28
30
|
</span>
|
|
29
31
|
)}
|
|
30
32
|
{subagent && subagent.state !== 'completed' && (
|
|
31
|
-
<span style={{ color: 'var(--c-subagent)', fontSize: '
|
|
33
|
+
<span style={{ color: 'var(--c-subagent)', fontSize: 'var(--fs-meta)' }}>⠉ {subagent.name}</span>
|
|
32
34
|
)}
|
|
33
35
|
</span>
|
|
34
36
|
);
|