pmx-canvas 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +49 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +52 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +216 -42
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +87 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +533 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +0 -1
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +206 -29
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.tsx +31 -0
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +195 -83
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +178 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +181 -11
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +26 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +74 -25
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- package/src/client/canvas/ContextPinBar.tsx +0 -25
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import type { MemoryGraph } from '../../shared/memory-graph.js';
|
|
3
|
+
import { boardList, catalogEpoch, memoryGraphRevision } from '../state/boards-store';
|
|
4
|
+
import { requestJson } from '../state/intent-bridge';
|
|
5
|
+
|
|
6
|
+
/** Coalesce live discovery and reject responses from an older library revision. */
|
|
7
|
+
export function useMemoryGraph(projection: 'full' | 'boards') {
|
|
8
|
+
const boards = boardList.value;
|
|
9
|
+
const liveRevision = memoryGraphRevision.value;
|
|
10
|
+
const [graph, setGraph] = useState<MemoryGraph | null>(null);
|
|
11
|
+
const [loading, setLoading] = useState(true);
|
|
12
|
+
const [error, setError] = useState(false);
|
|
13
|
+
const refresh = useRef(() => {});
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
let running = false;
|
|
17
|
+
let dirty = false;
|
|
18
|
+
let timer: number | undefined;
|
|
19
|
+
const schedule = () => {
|
|
20
|
+
dirty = true;
|
|
21
|
+
if (!running && timer === undefined) timer = window.setTimeout(load, 120);
|
|
22
|
+
};
|
|
23
|
+
const load = async () => {
|
|
24
|
+
timer = undefined;
|
|
25
|
+
dirty = false;
|
|
26
|
+
running = true;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
const epoch = catalogEpoch;
|
|
29
|
+
const revision = memoryGraphRevision.peek();
|
|
30
|
+
const catalog = boardList.peek();
|
|
31
|
+
const result = await requestJson<MemoryGraph | null>(
|
|
32
|
+
'memoryGraph',
|
|
33
|
+
`/api/canvas/memory-graph?projection=${projection}`,
|
|
34
|
+
null,
|
|
35
|
+
);
|
|
36
|
+
if (cancelled) return;
|
|
37
|
+
running = false;
|
|
38
|
+
if (epoch !== catalogEpoch || revision !== memoryGraphRevision.peek() || catalog !== boardList.peek()) {
|
|
39
|
+
schedule();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (result && Array.isArray(result.nodes) && Array.isArray(result.edges)) {
|
|
43
|
+
setGraph(result);
|
|
44
|
+
setError(false);
|
|
45
|
+
} else setError(true);
|
|
46
|
+
setLoading(false);
|
|
47
|
+
if (dirty) schedule();
|
|
48
|
+
};
|
|
49
|
+
refresh.current = schedule;
|
|
50
|
+
schedule();
|
|
51
|
+
return () => {
|
|
52
|
+
cancelled = true;
|
|
53
|
+
window.clearTimeout(timer);
|
|
54
|
+
};
|
|
55
|
+
}, [projection]);
|
|
56
|
+
useEffect(() => refresh.current(), [liveRevision, boards]);
|
|
57
|
+
return { graph, loading, error, retry: () => refresh.current() };
|
|
58
|
+
}
|
|
@@ -47,6 +47,7 @@ export function useNodeDrag({ nodeId, viewport, onMove, onDragEnd, onClick }: No
|
|
|
47
47
|
const onPointerMove = (ev: PointerEvent) => {
|
|
48
48
|
if (!isDragging.current) return;
|
|
49
49
|
if (!moved && Math.hypot(ev.clientX - e.clientX, ev.clientY - e.clientY) < 4) return;
|
|
50
|
+
if (!moved) document.documentElement.classList.add('is-node-moving');
|
|
50
51
|
moved = true;
|
|
51
52
|
pendingPointer = { x: ev.clientX, y: ev.clientY };
|
|
52
53
|
if (frameId !== null) return;
|
|
@@ -59,7 +60,7 @@ export function useNodeDrag({ nodeId, viewport, onMove, onDragEnd, onClick }: No
|
|
|
59
60
|
flushMove();
|
|
60
61
|
}
|
|
61
62
|
isDragging.current = false;
|
|
62
|
-
document.documentElement.classList.remove('is-node-dragging');
|
|
63
|
+
document.documentElement.classList.remove('is-node-dragging', 'is-node-moving');
|
|
63
64
|
document.removeEventListener('pointermove', onPointerMove);
|
|
64
65
|
document.removeEventListener('pointerup', finishDrag);
|
|
65
66
|
document.removeEventListener('pointercancel', finishDrag);
|
|
@@ -6,6 +6,7 @@ import type { ViewportState } from '../types';
|
|
|
6
6
|
const MIN_SCALE = 0.1;
|
|
7
7
|
const MAX_SCALE = 4;
|
|
8
8
|
const PAN_SPEED = 1;
|
|
9
|
+
const RIGHT_DRAG_THRESHOLD = 5;
|
|
9
10
|
|
|
10
11
|
function clampScale(scale: number): number {
|
|
11
12
|
return Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale));
|
|
@@ -15,6 +16,8 @@ interface PanZoomOptions {
|
|
|
15
16
|
viewport: Signal<ViewportState>;
|
|
16
17
|
onViewportChange: (v: ViewportState) => void;
|
|
17
18
|
onViewportCommit: (v: ViewportState) => void;
|
|
19
|
+
onBackgroundContextMenu?: (e: MouseEvent, worldX: number, worldY: number) => void;
|
|
20
|
+
onEdgeContextMenu?: (e: MouseEvent, edgeId: string) => void;
|
|
18
21
|
disabled?: boolean;
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -23,13 +26,28 @@ interface PanZoomOptions {
|
|
|
23
26
|
* - Wheel + Ctrl/Cmd: zoom centered on pointer
|
|
24
27
|
* - Wheel without modifier: pan
|
|
25
28
|
* - Right or middle pointer drag: pan from anywhere
|
|
29
|
+
* - Right-click on background: creation menu on release, unless dragged
|
|
26
30
|
* - Pointer drag on background in pan mode: pan
|
|
27
31
|
* - Pinch (touch): zoom
|
|
28
32
|
*/
|
|
29
|
-
export function usePanZoom({
|
|
33
|
+
export function usePanZoom({
|
|
34
|
+
viewport,
|
|
35
|
+
onViewportChange,
|
|
36
|
+
onViewportCommit,
|
|
37
|
+
onBackgroundContextMenu,
|
|
38
|
+
onEdgeContextMenu,
|
|
39
|
+
disabled = false,
|
|
40
|
+
}: PanZoomOptions) {
|
|
30
41
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
31
42
|
const isPanning = useRef(false);
|
|
32
43
|
const lastPointer = useRef({ x: 0, y: 0 });
|
|
44
|
+
const rightPress = useRef<{
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
background: boolean;
|
|
48
|
+
edgeId: string | null;
|
|
49
|
+
dragged: boolean;
|
|
50
|
+
} | null>(null);
|
|
33
51
|
const lastPinchDist = useRef(0);
|
|
34
52
|
const wheelCommitTimer = useRef<number | null>(null);
|
|
35
53
|
|
|
@@ -123,6 +141,16 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
123
141
|
// Select tool: background drag belongs to the lasso (CanvasViewport).
|
|
124
142
|
return;
|
|
125
143
|
}
|
|
144
|
+
rightPress.current =
|
|
145
|
+
e.button === 2
|
|
146
|
+
? {
|
|
147
|
+
x: e.clientX,
|
|
148
|
+
y: e.clientY,
|
|
149
|
+
background: e.target === container,
|
|
150
|
+
edgeId: e.target instanceof Element ? e.target.getAttribute('data-edge-id') : null,
|
|
151
|
+
dragged: false,
|
|
152
|
+
}
|
|
153
|
+
: null;
|
|
126
154
|
isPanning.current = true;
|
|
127
155
|
document.documentElement.classList.add('is-canvas-panning');
|
|
128
156
|
lastPointer.current = { x: e.clientX, y: e.clientY };
|
|
@@ -135,6 +163,11 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
135
163
|
(e: PointerEvent) => {
|
|
136
164
|
if (!isPanning.current) return;
|
|
137
165
|
if (disabled) return;
|
|
166
|
+
const press = rightPress.current;
|
|
167
|
+
if (press && !press.dragged) {
|
|
168
|
+
if (Math.hypot(e.clientX - press.x, e.clientY - press.y) <= RIGHT_DRAG_THRESHOLD) return;
|
|
169
|
+
press.dragged = true;
|
|
170
|
+
}
|
|
138
171
|
const dx = e.clientX - lastPointer.current.x;
|
|
139
172
|
const dy = e.clientY - lastPointer.current.y;
|
|
140
173
|
lastPointer.current = { x: e.clientX, y: e.clientY };
|
|
@@ -145,13 +178,37 @@ export function usePanZoom({ viewport, onViewportChange, onViewportCommit, disab
|
|
|
145
178
|
[disabled, viewport, onViewportChange],
|
|
146
179
|
);
|
|
147
180
|
|
|
148
|
-
const handlePointerUp = useCallback(
|
|
149
|
-
|
|
181
|
+
const handlePointerUp = useCallback(
|
|
182
|
+
(e: PointerEvent) => {
|
|
183
|
+
if (!isPanning.current) return;
|
|
184
|
+
// Completing a middle pan must not paste the Linux primary selection.
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
const press = rightPress.current;
|
|
187
|
+
rightPress.current = null;
|
|
188
|
+
isPanning.current = false;
|
|
189
|
+
document.documentElement.classList.remove('is-canvas-panning');
|
|
190
|
+
if (press && !press.dragged) {
|
|
191
|
+
// Native contextmenu may fire on press or release depending on the OS.
|
|
192
|
+
// Decide here instead, and never open on cancellation or lost capture.
|
|
193
|
+
if (!disabled && e.type === 'pointerup') {
|
|
194
|
+
if (press.edgeId) onEdgeContextMenu?.(e, press.edgeId);
|
|
195
|
+
else if (press.background) {
|
|
196
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
197
|
+
if (!rect) return;
|
|
198
|
+
const v = viewport.value;
|
|
199
|
+
onBackgroundContextMenu?.(
|
|
200
|
+
e,
|
|
201
|
+
(e.clientX - rect.left - v.x) / v.scale,
|
|
202
|
+
(e.clientY - rect.top - v.y) / v.scale,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
150
208
|
onViewportCommit(viewport.value);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}, [onViewportCommit, viewport]);
|
|
209
|
+
},
|
|
210
|
+
[disabled, onBackgroundContextMenu, onEdgeContextMenu, onViewportCommit, viewport],
|
|
211
|
+
);
|
|
155
212
|
|
|
156
213
|
// Touch pinch
|
|
157
214
|
const handleTouchMove = useCallback(
|
package/src/client/icons.tsx
CHANGED
|
@@ -192,23 +192,6 @@ export function IconMinimap(p: IconProps): JSX.Element {
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
/** Sun with rays */
|
|
196
|
-
export function IconSun(p: IconProps): JSX.Element {
|
|
197
|
-
return (
|
|
198
|
-
<Icon {...p}>
|
|
199
|
-
<circle cx="8" cy="8" r="3" />
|
|
200
|
-
<line x1="8" y1="1" x2="8" y2="3" />
|
|
201
|
-
<line x1="8" y1="13" x2="8" y2="15" />
|
|
202
|
-
<line x1="1" y1="8" x2="3" y2="8" />
|
|
203
|
-
<line x1="13" y1="8" x2="15" y2="8" />
|
|
204
|
-
<line x1="3.05" y1="3.05" x2="4.46" y2="4.46" />
|
|
205
|
-
<line x1="11.54" y1="11.54" x2="12.95" y2="12.95" />
|
|
206
|
-
<line x1="3.05" y1="12.95" x2="4.46" y2="11.54" />
|
|
207
|
-
<line x1="11.54" y1="4.46" x2="12.95" y2="3.05" />
|
|
208
|
-
</Icon>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
195
|
/** Crescent moon */
|
|
213
196
|
export function IconMoon(p: IconProps): JSX.Element {
|
|
214
197
|
return (
|
|
@@ -253,39 +236,6 @@ export function IconTextAnnotation(p: IconProps): JSX.Element {
|
|
|
253
236
|
);
|
|
254
237
|
}
|
|
255
238
|
|
|
256
|
-
/** Camera — snapshots */
|
|
257
|
-
export function IconSnapshot(p: IconProps): JSX.Element {
|
|
258
|
-
return (
|
|
259
|
-
<Icon {...p}>
|
|
260
|
-
<rect x="1" y="4" width="14" height="10" rx="1.5" />
|
|
261
|
-
<path d="M5.5 4 L6.5 2.5 H9.5 L10.5 4" />
|
|
262
|
-
<circle cx="8" cy="9" r="2.3" />
|
|
263
|
-
</Icon>
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Bullseye — trace toggle */
|
|
268
|
-
export function IconTrace(p: IconProps): JSX.Element {
|
|
269
|
-
return (
|
|
270
|
-
<Icon {...p}>
|
|
271
|
-
<circle cx="8" cy="8" r="6" />
|
|
272
|
-
<circle cx="8" cy="8" r="3" />
|
|
273
|
-
<circle cx="8" cy="8" r="0.8" fill="currentColor" stroke="none" />
|
|
274
|
-
</Icon>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** X in circle — clear trace */
|
|
279
|
-
export function IconClearTrace(p: IconProps): JSX.Element {
|
|
280
|
-
return (
|
|
281
|
-
<Icon {...p}>
|
|
282
|
-
<circle cx="8" cy="8" r="6" />
|
|
283
|
-
<line x1="5.5" y1="5.5" x2="10.5" y2="10.5" />
|
|
284
|
-
<line x1="10.5" y1="5.5" x2="5.5" y2="10.5" />
|
|
285
|
-
</Icon>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
239
|
/** Magnifying glass — search */
|
|
290
240
|
export function IconSearch(p: IconProps): JSX.Element {
|
|
291
241
|
return (
|
|
@@ -296,19 +246,6 @@ export function IconSearch(p: IconProps): JSX.Element {
|
|
|
296
246
|
);
|
|
297
247
|
}
|
|
298
248
|
|
|
299
|
-
/** Keyboard — shortcuts */
|
|
300
|
-
export function IconShortcuts(p: IconProps): JSX.Element {
|
|
301
|
-
return (
|
|
302
|
-
<Icon {...p}>
|
|
303
|
-
<rect x="1" y="3" width="14" height="10" rx="1.5" />
|
|
304
|
-
<line x1="4" y1="6" x2="5" y2="6" />
|
|
305
|
-
<line x1="7.5" y1="6" x2="8.5" y2="6" />
|
|
306
|
-
<line x1="11" y1="6" x2="12" y2="6" />
|
|
307
|
-
<line x1="4" y1="10" x2="12" y2="10" />
|
|
308
|
-
</Icon>
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
249
|
/** Speech bubble — steer the agent (command bar composer). */
|
|
313
250
|
export function IconSteer(p: IconProps): JSX.Element {
|
|
314
251
|
return (
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
.pmx-host-app { height: 100%; display: flex; flex-direction: column; }
|
|
2
2
|
.pmx-host-app > .app-shell { flex: 1; min-height: 0; height: auto; }
|
|
3
|
-
/* Keep context controls clear of the host's floating chat composer. */
|
|
4
|
-
.pmx-host-app .context-pin-bar {
|
|
5
|
-
bottom: 16px;
|
|
6
|
-
left: 16px;
|
|
7
|
-
transform: none;
|
|
8
|
-
}
|
|
9
3
|
/* Guard refusals must remain readable while the full source editor is open. */
|
|
10
4
|
.pmx-host-app .attention-toast.attention-tone-remove { z-index: 100100; }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'preact/hooks';
|
|
2
|
+
import { BarHint } from '../canvas/BarHint';
|
|
2
3
|
import { readAxFlow, readAxStep, type AxFlowStamp } from '../../shared/ax-flow.js';
|
|
3
4
|
import { nodes, updateNodeData } from '../state/canvas-store';
|
|
4
5
|
import { updateNodeFromClient } from '../state/intent-bridge';
|
|
@@ -121,31 +122,43 @@ function FlowLoopControls({ node, flow }: { node: CanvasNodeState; flow: AxFlowS
|
|
|
121
122
|
{running ? `Loop running · run ${run + 1}/${maxRuns}` : `Loop idle · ${run}/${maxRuns} runs`}
|
|
122
123
|
</span>
|
|
123
124
|
{running ? (
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
onClick={(e) => {
|
|
130
|
-
e.stopPropagation();
|
|
131
|
-
void persistLoop(node, flow, { ...flow.loop, running: false });
|
|
132
|
-
}}
|
|
125
|
+
<BarHint
|
|
126
|
+
label="Stop the loop now"
|
|
127
|
+
body="The stop is persisted on this node, so it survives a refresh and no further step is opened."
|
|
128
|
+
side="up"
|
|
129
|
+
fitWithin=".node-content, .expanded-overlay-panel"
|
|
133
130
|
>
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
class="ax-node-action"
|
|
134
|
+
style={activeButtonStyle}
|
|
135
|
+
onClick={(e) => {
|
|
136
|
+
e.stopPropagation();
|
|
137
|
+
void persistLoop(node, flow, { ...flow.loop, running: false });
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
Stop
|
|
141
|
+
</button>
|
|
142
|
+
</BarHint>
|
|
136
143
|
) : (
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
onClick={(e) => {
|
|
143
|
-
e.stopPropagation();
|
|
144
|
-
void handleRun();
|
|
145
|
-
}}
|
|
144
|
+
<BarHint
|
|
145
|
+
label="Run this flow as a loop"
|
|
146
|
+
body="Each completed step opens the next one, and the last step starts the next run — until the run cap is reached or you press Stop."
|
|
147
|
+
side="up"
|
|
148
|
+
fitWithin=".node-content, .expanded-overlay-panel"
|
|
146
149
|
>
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
class="ax-node-action"
|
|
153
|
+
style={axNodeActionButtonStyle}
|
|
154
|
+
onClick={(e) => {
|
|
155
|
+
e.stopPropagation();
|
|
156
|
+
void handleRun();
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
Run loop
|
|
160
|
+
</button>
|
|
161
|
+
</BarHint>
|
|
149
162
|
)}
|
|
150
163
|
<input
|
|
151
164
|
class="ax-flow-steer-input"
|
|
@@ -170,18 +183,24 @@ function FlowLoopControls({ node, flow }: { node: CanvasNodeState; flow: AxFlowS
|
|
|
170
183
|
color: 'var(--c-text)',
|
|
171
184
|
}}
|
|
172
185
|
/>
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
onClick={(e) => {
|
|
179
|
-
e.stopPropagation();
|
|
180
|
-
void handleSteer();
|
|
181
|
-
}}
|
|
186
|
+
<BarHint
|
|
187
|
+
label="Send a steering message to the agent working this flow"
|
|
188
|
+
side="up"
|
|
189
|
+
align="end"
|
|
190
|
+
fitWithin=".node-content, .expanded-overlay-panel"
|
|
182
191
|
>
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
class="ax-node-action"
|
|
195
|
+
style={axNodeActionButtonStyle}
|
|
196
|
+
onClick={(e) => {
|
|
197
|
+
e.stopPropagation();
|
|
198
|
+
void handleSteer();
|
|
199
|
+
}}
|
|
200
|
+
>
|
|
201
|
+
Steer
|
|
202
|
+
</button>
|
|
203
|
+
</BarHint>
|
|
185
204
|
</div>
|
|
186
205
|
);
|
|
187
206
|
}
|
|
@@ -199,25 +218,30 @@ export function AxStepControls({ node }: { node: CanvasNodeState }) {
|
|
|
199
218
|
Step {step.index}/{step.total}
|
|
200
219
|
</span>
|
|
201
220
|
{STEP_ACTIONS.map((action) => (
|
|
202
|
-
<
|
|
221
|
+
<BarHint
|
|
203
222
|
key={action.status}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
style={status === action.status ? activeButtonStyle : axNodeActionButtonStyle}
|
|
208
|
-
title={`Set this step's work item to "${action.status}".`}
|
|
209
|
-
onClick={(e) => {
|
|
210
|
-
e.stopPropagation();
|
|
211
|
-
void runNodeAxInteraction(
|
|
212
|
-
node,
|
|
213
|
-
'ax.work.update',
|
|
214
|
-
{ id: step.workItemId, status: action.status },
|
|
215
|
-
action.toast,
|
|
216
|
-
);
|
|
217
|
-
}}
|
|
223
|
+
label={`Set this step's work item to "${action.status}"`}
|
|
224
|
+
side="up"
|
|
225
|
+
fitWithin=".node-content, .expanded-overlay-panel"
|
|
218
226
|
>
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
class="ax-node-action"
|
|
230
|
+
aria-pressed={status === action.status}
|
|
231
|
+
style={status === action.status ? activeButtonStyle : axNodeActionButtonStyle}
|
|
232
|
+
onClick={(e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
void runNodeAxInteraction(
|
|
235
|
+
node,
|
|
236
|
+
'ax.work.update',
|
|
237
|
+
{ id: step.workItemId, status: action.status },
|
|
238
|
+
action.toast,
|
|
239
|
+
);
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
{action.label}
|
|
243
|
+
</button>
|
|
244
|
+
</BarHint>
|
|
221
245
|
))}
|
|
222
246
|
</div>
|
|
223
247
|
{flow && <FlowLoopControls node={node} flow={flow} />}
|
|
@@ -11,7 +11,11 @@ export function BoardNode({ node }: { node: CanvasNodeState }) {
|
|
|
11
11
|
<div class="board-node-content">
|
|
12
12
|
<div class="board-node-label">Linked board</div>
|
|
13
13
|
<strong class="board-node-title">{target?.name ?? 'Missing board'}</strong>
|
|
14
|
-
<div class="board-node-meta">
|
|
14
|
+
<div class="board-node-meta">
|
|
15
|
+
{target
|
|
16
|
+
? `${target.nodeCount} nodes${target.pin ? ' · pinned, so it travels with you' : ''}`
|
|
17
|
+
: boardId || 'No target id'}
|
|
18
|
+
</div>
|
|
15
19
|
{target?.summary && <p class="board-node-summary">{target.summary}</p>}
|
|
16
20
|
{!!target?.pinnedTitles?.length && (
|
|
17
21
|
<p class="board-node-meta">Pinned: {target.pinnedTitles.map((pin) => pin.title).join(', ')}</p>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { openWorkbenchFile } from '../state/intent-bridge';
|
|
2
|
+
import { BarHint } from '../canvas/BarHint';
|
|
2
3
|
import { TYPE_LABELS, type CanvasNodeState } from '../types';
|
|
3
4
|
import { axNodeActionButtonStyle, runNodeAxInteraction } from './ax-node-actions';
|
|
4
5
|
|
|
@@ -172,18 +173,24 @@ export function ContextNode({
|
|
|
172
173
|
>
|
|
173
174
|
{/* AX: focus the agent on this context node */}
|
|
174
175
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
onClick={(e) => {
|
|
181
|
-
e.stopPropagation();
|
|
182
|
-
void runNodeAxInteraction(node, 'ax.focus.set', undefined, 'Focus set');
|
|
183
|
-
}}
|
|
176
|
+
<BarHint
|
|
177
|
+
label="Point the agent at this node"
|
|
178
|
+
body="Sets it as the agent's current AX focus, so the agent pulls it into context to work on next — a one-click alternative to pinning."
|
|
179
|
+
align="end"
|
|
180
|
+
fitWithin=".node-content"
|
|
184
181
|
>
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="ax-node-action"
|
|
185
|
+
style={axNodeActionButtonStyle}
|
|
186
|
+
onClick={(e) => {
|
|
187
|
+
e.stopPropagation();
|
|
188
|
+
void runNodeAxInteraction(node, 'ax.focus.set', undefined, 'Focus set');
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
Set focus
|
|
192
|
+
</button>
|
|
193
|
+
</BarHint>
|
|
187
194
|
</div>
|
|
188
195
|
|
|
189
196
|
{tokenLimit !== null && tokenLimit > 0 && (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';
|
|
2
|
+
import { BarHint } from '../canvas/BarHint';
|
|
2
3
|
import { IconPlusCircle, IconRefresh } from '../icons';
|
|
3
4
|
import { delimiterForPath, parseDelimitedText } from '../../shared/delimited-text';
|
|
4
5
|
import { updateNodeData } from '../state/canvas-store';
|
|
@@ -243,7 +244,6 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
|
|
|
243
244
|
whiteSpace: 'nowrap',
|
|
244
245
|
flex: 1,
|
|
245
246
|
}}
|
|
246
|
-
title={filePath}
|
|
247
247
|
>
|
|
248
248
|
{filePath}
|
|
249
249
|
</span>
|
|
@@ -255,47 +255,51 @@ function TextFileNode({ node, expanded = false }: { node: CanvasNodeState; expan
|
|
|
255
255
|
{new Date(updatedAt).toLocaleTimeString()}
|
|
256
256
|
</span>
|
|
257
257
|
)}
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
e
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
258
|
+
<BarHint label="Mark this file as AX evidence" align="end" fitWithin=".node-content">
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
class="ax-node-action"
|
|
262
|
+
aria-label="Mark this file as AX evidence"
|
|
263
|
+
onClick={(e) => {
|
|
264
|
+
e.stopPropagation();
|
|
265
|
+
void runNodeAxInteraction(
|
|
266
|
+
node,
|
|
267
|
+
'ax.evidence.add',
|
|
268
|
+
{ kind: 'file', title: filePath.split('/').pop() || filePath, ref: filePath },
|
|
269
|
+
'Marked as evidence',
|
|
270
|
+
);
|
|
271
|
+
}}
|
|
272
|
+
style={{
|
|
273
|
+
background: 'none',
|
|
274
|
+
border: 'none',
|
|
275
|
+
color: 'var(--c-muted)',
|
|
276
|
+
cursor: 'pointer',
|
|
277
|
+
padding: '2px 4px',
|
|
278
|
+
fontSize: '12px',
|
|
279
|
+
flexShrink: 0,
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
<IconPlusCircle size={14} />
|
|
283
|
+
</button>
|
|
284
|
+
</BarHint>
|
|
285
|
+
<BarHint label="Reload file" align="end" fitWithin=".node-content">
|
|
286
|
+
<button
|
|
287
|
+
type="button"
|
|
288
|
+
onClick={handleReload}
|
|
289
|
+
aria-label="Reload file"
|
|
290
|
+
style={{
|
|
291
|
+
background: 'none',
|
|
292
|
+
border: 'none',
|
|
293
|
+
color: 'var(--c-muted)',
|
|
294
|
+
cursor: 'pointer',
|
|
295
|
+
padding: '2px 4px',
|
|
296
|
+
fontSize: '12px',
|
|
297
|
+
flexShrink: 0,
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
<IconRefresh size={14} />
|
|
301
|
+
</button>
|
|
302
|
+
</BarHint>
|
|
299
303
|
</div>
|
|
300
304
|
|
|
301
305
|
{/* Content area */}
|
|
@@ -121,6 +121,37 @@ export function HtmlNode({
|
|
|
121
121
|
return () => window.clearTimeout(id);
|
|
122
122
|
}, [autoFocus, surfaceSrc]);
|
|
123
123
|
|
|
124
|
+
const handleDocumentLoad = (frame: HTMLIFrameElement, signal: AbortSignal): Promise<boolean> => {
|
|
125
|
+
if (signal.aborted) return Promise.resolve(false);
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
const finish = (ready: boolean) => {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
window.removeEventListener('message', receive);
|
|
130
|
+
signal.removeEventListener('abort', cancel);
|
|
131
|
+
resolve(ready);
|
|
132
|
+
};
|
|
133
|
+
const cancel = () => finish(false);
|
|
134
|
+
const receive = (event: MessageEvent) => {
|
|
135
|
+
if (
|
|
136
|
+
event.source !== frame.contentWindow ||
|
|
137
|
+
event.data?.source !== HTML_SURFACE_PUSH_SOURCE ||
|
|
138
|
+
event.data?.token !== themeToken
|
|
139
|
+
)
|
|
140
|
+
return;
|
|
141
|
+
// A healthy offscreen frame can wait for paint without a deadline.
|
|
142
|
+
if (event.data.type === 'mounted') clearTimeout(timer);
|
|
143
|
+
if (event.data.type === 'ready') finish(true);
|
|
144
|
+
};
|
|
145
|
+
const timer = setTimeout(cancel, 10_000);
|
|
146
|
+
window.addEventListener('message', receive);
|
|
147
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
148
|
+
frame.contentWindow?.postMessage(
|
|
149
|
+
{ source: HTML_SURFACE_PUSH_SOURCE, type: 'check-ready', token: themeToken },
|
|
150
|
+
'*',
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
124
155
|
const handleFrameLoad = () => {
|
|
125
156
|
iframeRef.current?.contentWindow?.postMessage(
|
|
126
157
|
{
|
|
@@ -174,6 +205,7 @@ export function HtmlNode({
|
|
|
174
205
|
allow=""
|
|
175
206
|
tabIndex={autoFocus ? 0 : undefined}
|
|
176
207
|
onLoad={handleFrameLoad}
|
|
208
|
+
onDocumentLoad={handleDocumentLoad}
|
|
177
209
|
/>
|
|
178
210
|
);
|
|
179
211
|
}
|