pmx-canvas 0.8.2 → 0.9.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/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +59 -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/human-store.d.ts +2 -1
- 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/evals/evals.json +224 -0
- 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 +88 -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 +543 -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 +6 -3
- 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/human-store.ts +13 -7
- 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 +235 -32
- 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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { RefObject } from 'preact';
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
3
3
|
import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
|
|
4
4
|
import { canvasThemeScheme } from '../../shared/themes.js';
|
|
5
5
|
import type { CanvasNodeState } from '../types';
|
|
6
6
|
import { axSurfaceState, canvasTheme } from '../state/canvas-store';
|
|
7
|
+
import { showToast } from '../state/attention-bridge';
|
|
7
8
|
import { iframeMode } from '../state/iframe-mode';
|
|
8
9
|
import { shouldContentFitIframeNode } from '../canvas/auto-fit';
|
|
9
10
|
import { ExtAppFrame } from './ExtAppFrame';
|
|
@@ -65,9 +66,9 @@ type ViewerFrameSource = { src?: string; srcdoc?: string };
|
|
|
65
66
|
/**
|
|
66
67
|
* Keep the last painted viewer in front while its replacement navigates. Viewer
|
|
67
68
|
* specs are immutable documents, so updates normally change `?v=` and reload the
|
|
68
|
-
* iframe; putting the new document
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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.
|
|
71
72
|
*/
|
|
72
73
|
export function RefreshingViewerFrame({
|
|
73
74
|
source,
|
|
@@ -83,7 +84,7 @@ export function RefreshingViewerFrame({
|
|
|
83
84
|
source: ViewerFrameSource;
|
|
84
85
|
iframeRef: RefObject<HTMLIFrameElement>;
|
|
85
86
|
onLoad: () => void;
|
|
86
|
-
onDocumentLoad?: (frame: HTMLIFrameElement) => void
|
|
87
|
+
onDocumentLoad?: (frame: HTMLIFrameElement, signal: AbortSignal) => void | Promise<boolean | 'cancelled' | void>;
|
|
87
88
|
title: string;
|
|
88
89
|
className?: string;
|
|
89
90
|
sandbox?: string;
|
|
@@ -91,50 +92,77 @@ export function RefreshingViewerFrame({
|
|
|
91
92
|
tabIndex?: number;
|
|
92
93
|
}) {
|
|
93
94
|
const sourceKey = source.src ?? source.srcdoc ?? '';
|
|
94
|
-
const [painted, setPainted] = useState({ key: sourceKey, source });
|
|
95
|
-
const [pending, setPending] = useState<{ key: string; source: ViewerFrameSource } | null>(null);
|
|
95
|
+
const [painted, setPainted] = useState({ key: sourceKey, source, attempt: 0 });
|
|
96
|
+
const [pending, setPending] = useState<{ key: string; source: ViewerFrameSource; attempt: number } | null>(null);
|
|
96
97
|
const promotionRef = useRef(0);
|
|
98
|
+
const readiness = useRef(new Set<AbortController>());
|
|
97
99
|
|
|
98
|
-
|
|
100
|
+
useLayoutEffect(() => {
|
|
99
101
|
promotionRef.current += 1;
|
|
102
|
+
for (const controller of readiness.current) controller.abort();
|
|
103
|
+
readiness.current.clear();
|
|
100
104
|
if (!sourceKey || sourceKey === painted.key) {
|
|
101
105
|
setPending(null);
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
108
|
if (!painted.key) {
|
|
105
|
-
setPainted({ key: sourceKey, source });
|
|
109
|
+
setPainted({ key: sourceKey, source, attempt: 0 });
|
|
106
110
|
return;
|
|
107
111
|
}
|
|
108
|
-
setPending({ key: sourceKey, source });
|
|
112
|
+
setPending({ key: sourceKey, source, attempt: 0 });
|
|
109
113
|
}, [sourceKey, painted.key]);
|
|
110
114
|
|
|
111
115
|
useEffect(
|
|
112
116
|
() => () => {
|
|
113
117
|
promotionRef.current += 1;
|
|
118
|
+
for (const controller of readiness.current) controller.abort();
|
|
119
|
+
readiness.current.clear();
|
|
114
120
|
},
|
|
115
121
|
[],
|
|
116
122
|
);
|
|
117
123
|
|
|
118
|
-
const frame = (entry: { key: string; source: ViewerFrameSource }, isPending: boolean) => (
|
|
124
|
+
const frame = (entry: { key: string; source: ViewerFrameSource; attempt: number }, isPending: boolean) => (
|
|
119
125
|
<iframe
|
|
120
|
-
key={entry.key}
|
|
126
|
+
key={`${entry.key}:${entry.attempt}`}
|
|
121
127
|
ref={isPending ? undefined : iframeRef}
|
|
122
128
|
{...entry.source}
|
|
123
129
|
class={className}
|
|
124
130
|
sandbox={sandbox}
|
|
125
131
|
allow={allow}
|
|
126
132
|
tabIndex={tabIndex}
|
|
133
|
+
inert={isPending}
|
|
134
|
+
aria-hidden={isPending ? true : undefined}
|
|
127
135
|
loading={iframeMode.value === 'srcdoc' ? undefined : 'lazy'}
|
|
128
|
-
onLoad={(event) => {
|
|
136
|
+
onLoad={async (event) => {
|
|
129
137
|
// Seed runtime state while a replacement is still behind the painted
|
|
130
|
-
// frame,
|
|
131
|
-
|
|
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
|
+
}
|
|
132
162
|
if (!isPending) {
|
|
133
163
|
onLoad();
|
|
134
164
|
return;
|
|
135
165
|
}
|
|
136
|
-
const token = ++promotionRef.current;
|
|
137
|
-
const loadedFrame = event.currentTarget;
|
|
138
166
|
requestAnimationFrame(() =>
|
|
139
167
|
requestAnimationFrame(() => {
|
|
140
168
|
if (promotionRef.current !== token) return;
|
|
@@ -150,7 +178,8 @@ export function RefreshingViewerFrame({
|
|
|
150
178
|
inset: 0,
|
|
151
179
|
width: '100%',
|
|
152
180
|
height: '100%',
|
|
153
|
-
visibility:
|
|
181
|
+
visibility: 'visible',
|
|
182
|
+
zIndex: isPending ? 0 : 1,
|
|
154
183
|
background: 'var(--c-panel-soft)',
|
|
155
184
|
}}
|
|
156
185
|
title={title}
|
|
@@ -299,8 +328,9 @@ function McpAppViewer({ node, expanded }: { node: CanvasNodeState; expanded: boo
|
|
|
299
328
|
<RefreshingViewerFrame
|
|
300
329
|
source={surfaceFrame}
|
|
301
330
|
iframeRef={iframeRef}
|
|
302
|
-
onDocumentLoad={(frame) => {
|
|
303
|
-
if (stateToken && frame.contentWindow)
|
|
331
|
+
onDocumentLoad={(frame, signal) => {
|
|
332
|
+
if (stateToken && frame.contentWindow)
|
|
333
|
+
return restoreViewerRuntimeState(node.id, stateToken, frame.contentWindow, signal);
|
|
304
334
|
}}
|
|
305
335
|
onLoad={pushAxState}
|
|
306
336
|
title={`MCP App: ${sourceServer}`}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { BarHint } from '../canvas/BarHint';
|
|
2
3
|
import { modChord } from '../utils/platform';
|
|
3
4
|
import { type FormatAction, FORMAT_ACTIONS, getSelectionRect } from './md-format';
|
|
4
5
|
|
|
@@ -83,27 +84,29 @@ export function MdFormatBar({ textareaRef }: { textareaRef: { current: HTMLTextA
|
|
|
83
84
|
onMouseDown={(e) => e.preventDefault()}
|
|
84
85
|
>
|
|
85
86
|
{PRIMARY_ACTIONS.map((a) => (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
<BarHint key={a.key} label={a.label} shortcut={modChord(a.shortcut!.toUpperCase())}>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class={`md-format-btn md-format-btn-${a.key}`}
|
|
91
|
+
aria-label={a.label}
|
|
92
|
+
onClick={() => runAction(a)}
|
|
93
|
+
>
|
|
94
|
+
{a.icon}
|
|
95
|
+
</button>
|
|
96
|
+
</BarHint>
|
|
95
97
|
))}
|
|
96
98
|
<div class="md-format-divider" />
|
|
97
99
|
{SECONDARY_ACTIONS.map((a) => (
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
<BarHint key={a.key} label={a.label}>
|
|
101
|
+
<button
|
|
102
|
+
type="button"
|
|
103
|
+
class={`md-format-btn md-format-btn-${a.key}`}
|
|
104
|
+
aria-label={a.label}
|
|
105
|
+
onClick={() => runAction(a)}
|
|
106
|
+
>
|
|
107
|
+
{a.icon}
|
|
108
|
+
</button>
|
|
109
|
+
</BarHint>
|
|
107
110
|
))}
|
|
108
111
|
</div>
|
|
109
112
|
);
|
|
@@ -169,7 +169,7 @@ export function PromptNode({ node, expanded = false }: { node: CanvasNodeState;
|
|
|
169
169
|
Promise.all(
|
|
170
170
|
assistantTurns.map(async ({ turn, index }) => {
|
|
171
171
|
const html = await renderMarkdown(turn.text);
|
|
172
|
-
return { index, html: sanitizeMarkdownHtml(html) };
|
|
172
|
+
return { index, html: sanitizeMarkdownHtml(html ?? '') };
|
|
173
173
|
}),
|
|
174
174
|
).then((results) => {
|
|
175
175
|
if (cancelled) return;
|
|
@@ -36,7 +36,7 @@ export function ResponseNode({ node, expanded = false }: { node: CanvasNodeState
|
|
|
36
36
|
}
|
|
37
37
|
let cancelled = false;
|
|
38
38
|
renderMarkdown(content).then((html) => {
|
|
39
|
-
if (!cancelled) setRendered(sanitizeMarkdownHtml(html));
|
|
39
|
+
if (!cancelled) setRendered(sanitizeMarkdownHtml(html ?? ''));
|
|
40
40
|
});
|
|
41
41
|
return () => {
|
|
42
42
|
cancelled = true;
|
|
@@ -7,7 +7,7 @@ type Snapshot = {
|
|
|
7
7
|
version: unknown;
|
|
8
8
|
documentUrl: unknown;
|
|
9
9
|
state: Record<string, unknown> | null;
|
|
10
|
-
owners: Array<{ token: string; source: Window }>;
|
|
10
|
+
owners: Array<{ token: string; source: Window; mounted: () => void; ready: () => void; cancel: () => void }>;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// Tab-local UI state only: never persist form drafts in canvas data or AX. Keep
|
|
@@ -17,7 +17,12 @@ type Snapshot = {
|
|
|
17
17
|
const snapshots = new Map<string, Snapshot>();
|
|
18
18
|
let listening = false;
|
|
19
19
|
|
|
20
|
-
export function restoreViewerRuntimeState(
|
|
20
|
+
export function restoreViewerRuntimeState(
|
|
21
|
+
nodeId: string,
|
|
22
|
+
token: string,
|
|
23
|
+
source: Window,
|
|
24
|
+
signal?: AbortSignal,
|
|
25
|
+
): Promise<boolean | 'cancelled'> {
|
|
21
26
|
if (!listening) {
|
|
22
27
|
listening = true;
|
|
23
28
|
effect(() => {
|
|
@@ -30,8 +35,10 @@ export function restoreViewerRuntimeState(nodeId: string, token: string, source:
|
|
|
30
35
|
snapshot.epoch !== epoch ||
|
|
31
36
|
snapshot.version !== node.data.specVersion ||
|
|
32
37
|
snapshot.documentUrl !== node.data.url
|
|
33
|
-
)
|
|
38
|
+
) {
|
|
39
|
+
for (const owner of snapshot.owners) owner.cancel();
|
|
34
40
|
snapshots.delete(id);
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
});
|
|
37
44
|
window.addEventListener('message', (event: MessageEvent) => {
|
|
@@ -42,37 +49,62 @@ export function restoreViewerRuntimeState(nodeId: string, token: string, source:
|
|
|
42
49
|
nodeId?: string;
|
|
43
50
|
state?: unknown;
|
|
44
51
|
} | null;
|
|
45
|
-
if (
|
|
46
|
-
!message ||
|
|
47
|
-
message.source !== JSON_VIEWER_STATE_SOURCE ||
|
|
48
|
-
message.type !== 'snapshot' ||
|
|
49
|
-
typeof message.nodeId !== 'string'
|
|
50
|
-
)
|
|
51
|
-
return;
|
|
52
|
+
if (!message || message.source !== JSON_VIEWER_STATE_SOURCE || typeof message.nodeId !== 'string') return;
|
|
52
53
|
const snapshot = snapshots.get(message.nodeId);
|
|
53
54
|
const ownerIndex =
|
|
54
55
|
snapshot?.owners.findIndex((owner) => owner.token === message.token && owner.source === event.source) ?? -1;
|
|
55
56
|
if (!snapshot || ownerIndex < 0) return;
|
|
57
|
+
if (message.type === 'mounted') {
|
|
58
|
+
snapshot.owners[ownerIndex]!.mounted();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (message.type === 'ready') {
|
|
62
|
+
snapshot.owners[ownerIndex]!.ready();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (message.type !== 'snapshot') return;
|
|
56
66
|
if (!message.state || typeof message.state !== 'object' || Array.isArray(message.state)) return;
|
|
57
67
|
const { ax: _ax, ...state } = message.state as Record<string, unknown>;
|
|
58
68
|
snapshot.state = state;
|
|
59
69
|
// Once the replacement emits, the retired document cannot overwrite it.
|
|
70
|
+
const retired = snapshot.owners.slice(0, ownerIndex);
|
|
60
71
|
snapshot.owners = snapshot.owners.slice(ownerIndex);
|
|
72
|
+
for (const owner of retired) owner.cancel();
|
|
61
73
|
});
|
|
62
74
|
}
|
|
63
75
|
const node = nodes.value.get(nodeId);
|
|
64
|
-
if (!node) return;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
if (!node || signal?.aborted) return Promise.resolve('cancelled');
|
|
77
|
+
const snapshot: Snapshot = snapshots.get(nodeId) ?? {
|
|
78
|
+
epoch: canvasInteractionEpoch.value,
|
|
79
|
+
version: node.data.specVersion,
|
|
80
|
+
documentUrl: node.data.url,
|
|
81
|
+
state: null,
|
|
82
|
+
owners: [],
|
|
83
|
+
};
|
|
84
|
+
snapshots.set(nodeId, snapshot);
|
|
85
|
+
const ready = new Promise<boolean | 'cancelled'>((resolve) => {
|
|
86
|
+
const finish = (loaded: boolean | 'cancelled') => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
signal?.removeEventListener('abort', owner.cancel);
|
|
89
|
+
if (loaded !== true) snapshot.owners = snapshot.owners.filter((entry) => entry !== owner);
|
|
90
|
+
resolve(loaded);
|
|
73
91
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
const owner = {
|
|
93
|
+
token,
|
|
94
|
+
source,
|
|
95
|
+
mounted: () => clearTimeout(timer),
|
|
96
|
+
ready: () => finish(true),
|
|
97
|
+
cancel: () => finish('cancelled'),
|
|
98
|
+
};
|
|
99
|
+
// A missing runtime fails, but a healthy offscreen document can wait for
|
|
100
|
+
// paint indefinitely. Its synchronous mount acknowledgement ends the deadline;
|
|
101
|
+
// only the later frame-gated ready message may promote it.
|
|
102
|
+
const timer = setTimeout(() => finish(false), 10_000);
|
|
103
|
+
for (const previous of snapshot.owners.filter((entry) => entry.source === source)) previous.cancel();
|
|
104
|
+
while (snapshot.owners.length > 1) snapshot.owners[0]!.cancel();
|
|
105
|
+
snapshot.owners.push(owner);
|
|
106
|
+
signal?.addEventListener('abort', owner.cancel, { once: true });
|
|
107
|
+
});
|
|
77
108
|
source.postMessage({ source: JSON_VIEWER_STATE_SOURCE, type: 'restore', token, state: snapshot.state }, '*');
|
|
109
|
+
return ready;
|
|
78
110
|
}
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
type AttentionEntry,
|
|
17
17
|
type AttentionTone,
|
|
18
18
|
} from './attention-store';
|
|
19
|
+
import { nearPins } from './near-pin-store';
|
|
20
|
+
import { activeBoardId } from './boards-store';
|
|
19
21
|
|
|
20
22
|
let reducer = new SemanticWatchReducer();
|
|
21
23
|
let toastQueue: AttentionEntry[] = [];
|
|
@@ -77,13 +79,39 @@ function formatReason(reason: string): string {
|
|
|
77
79
|
if (reason === 'left cluster') return 'moved away from its cluster';
|
|
78
80
|
if (reason === 'cluster changed') return 'shifted into a different cluster';
|
|
79
81
|
if (reason === 'pinned neighborhood changed') return 'changed the local focus field';
|
|
80
|
-
const enteredMatch = /^entered pinned neighborhood of "(.+)"$/.exec(reason);
|
|
81
|
-
if (enteredMatch) return `moved into focus around ${enteredMatch[1]}`;
|
|
82
|
-
const leftMatch = /^left pinned neighborhood of "(.+)"$/.exec(reason);
|
|
83
|
-
if (leftMatch) return `moved out of focus around ${leftMatch[1]}`;
|
|
84
82
|
return reason;
|
|
85
83
|
}
|
|
86
84
|
|
|
85
|
+
function pinTitlesFrom(reasons: string[], verb: 'entered' | 'left'): string[] {
|
|
86
|
+
const pattern = new RegExp(`^${verb} pinned neighborhood of "(.+)"$`);
|
|
87
|
+
return reasons.flatMap((reason) => pattern.exec(reason)?.[1] ?? []);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* NearPin.dc.html §3: one entry naming the node and the pin, saying what the
|
|
92
|
+
* agent gets. "Out of the brief" only once the node is near no pin at all.
|
|
93
|
+
*/
|
|
94
|
+
function nearPinEntry(name: string, nodeId: string, reasons: string[]): { title: string; body: string } | null {
|
|
95
|
+
const quoted = (titles: string[]) => titles.map((title) => `“${title}”`).join(' and ');
|
|
96
|
+
const entered = pinTitlesFrom(reasons, 'entered');
|
|
97
|
+
if (entered.length > 0) {
|
|
98
|
+
return {
|
|
99
|
+
title: 'Near a pin',
|
|
100
|
+
body: `“${name}” is now near ${quoted(entered)} — the agent gets its title and summary.`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const left = pinTitlesFrom(reasons, 'left');
|
|
104
|
+
if (left.length === 0) return null;
|
|
105
|
+
const still = nearPins.value.get(nodeId) ?? [];
|
|
106
|
+
return {
|
|
107
|
+
title: 'Left a pin',
|
|
108
|
+
body:
|
|
109
|
+
still.length > 0
|
|
110
|
+
? `“${name}” left ${quoted(left)} — still near ${quoted(still.map((pin) => pin.pinTitle))}.`
|
|
111
|
+
: `“${name}” left ${quoted(left)} — it is out of the agent’s brief.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
87
115
|
function entryFromContextPin(event: ContextPinWatchEvent): AttentionEntry | null {
|
|
88
116
|
const addedNames = event.added.map((node) => quoteLabel(node.title, node.id));
|
|
89
117
|
const removedNames = event.removed.map((node) => quoteLabel(node.title, node.id));
|
|
@@ -118,7 +146,7 @@ function entryFromConnect(event: ConnectWatchEvent): AttentionEntry | null {
|
|
|
118
146
|
'relationship',
|
|
119
147
|
'Relationship added',
|
|
120
148
|
`${quoteLabel(edge.fromTitle, edge.fromId)} linked to ${quoteLabel(edge.toTitle, edge.toId)}`,
|
|
121
|
-
[edge.fromId, edge.toId],
|
|
149
|
+
[edge.fromId, ...(!edge.toBoardId || edge.toBoardId === activeBoardId.value ? [edge.toId] : [])],
|
|
122
150
|
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
123
151
|
);
|
|
124
152
|
}
|
|
@@ -127,13 +155,33 @@ function entryFromConnect(event: ConnectWatchEvent): AttentionEntry | null {
|
|
|
127
155
|
'relationship',
|
|
128
156
|
'Relationships added',
|
|
129
157
|
`${event.edges.length} connections changed the board structure`,
|
|
130
|
-
event.edges.flatMap((edge) => [
|
|
158
|
+
event.edges.flatMap((edge) => [
|
|
159
|
+
edge.fromId,
|
|
160
|
+
...(!edge.toBoardId || edge.toBoardId === activeBoardId.value ? [edge.toId] : []),
|
|
161
|
+
]),
|
|
131
162
|
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
132
163
|
);
|
|
133
164
|
}
|
|
134
165
|
|
|
135
166
|
function entryFromRemove(event: RemoveWatchEvent): AttentionEntry | null {
|
|
136
167
|
if (event.nodes.length === 0 && event.edges.length === 0) return null;
|
|
168
|
+
// LinksChanged.dc.html: a removed link leaves no line to mark, so Updates names it.
|
|
169
|
+
if (event.nodes.length === 0) {
|
|
170
|
+
const links = event.edges.map(
|
|
171
|
+
(edge) =>
|
|
172
|
+
`“${quoteLabel(edge.fromTitle, edge.fromId)}” – “${quoteLabel(edge.toTitle, edge.toId)}” (${edge.edgeType})`,
|
|
173
|
+
);
|
|
174
|
+
return makeEntry(
|
|
175
|
+
'remove',
|
|
176
|
+
links.length === 1 ? 'Link removed' : 'Links removed',
|
|
177
|
+
summarizeNames(links),
|
|
178
|
+
event.edges.flatMap((edge) => [
|
|
179
|
+
edge.fromId,
|
|
180
|
+
...(!edge.toBoardId || edge.toBoardId === activeBoardId.value ? [edge.toId] : []),
|
|
181
|
+
]),
|
|
182
|
+
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
137
185
|
const nodeNames = event.nodes.map((node) => quoteLabel(node.title, node.id));
|
|
138
186
|
const parts: string[] = [];
|
|
139
187
|
if (nodeNames.length > 0) parts.push(summarizeNames(nodeNames));
|
|
@@ -144,7 +192,13 @@ function entryFromRemove(event: RemoveWatchEvent): AttentionEntry | null {
|
|
|
144
192
|
'remove',
|
|
145
193
|
'Items removed',
|
|
146
194
|
parts.join(' · '),
|
|
147
|
-
[
|
|
195
|
+
[
|
|
196
|
+
...event.nodes.map((node) => node.id),
|
|
197
|
+
...event.edges.flatMap((edge) => [
|
|
198
|
+
edge.fromId,
|
|
199
|
+
...(!edge.toBoardId || edge.toBoardId === activeBoardId.value ? [edge.toId] : []),
|
|
200
|
+
]),
|
|
201
|
+
],
|
|
148
202
|
event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now(),
|
|
149
203
|
);
|
|
150
204
|
}
|
|
@@ -184,14 +238,9 @@ function entryFromMoveEnd(event: MoveEndWatchEvent): AttentionEntry | null {
|
|
|
184
238
|
const createdAt = event.timestamp ? Date.parse(event.timestamp) || Date.now() : Date.now();
|
|
185
239
|
|
|
186
240
|
if (reasons.some((reason) => reason.includes('pinned neighborhood'))) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
'Neighborhood changed',
|
|
191
|
-
`${names[0]} ${formatReason(event.nodes[0].reasons[0])}`,
|
|
192
|
-
event.nodes.map((node) => node.id),
|
|
193
|
-
createdAt,
|
|
194
|
-
);
|
|
241
|
+
const near = event.nodes.length === 1 ? nearPinEntry(names[0], event.nodes[0].id, event.nodes[0].reasons) : null;
|
|
242
|
+
if (near) {
|
|
243
|
+
return makeEntry('neighborhood', near.title, near.body, [event.nodes[0].id], createdAt);
|
|
195
244
|
}
|
|
196
245
|
return makeEntry(
|
|
197
246
|
'neighborhood',
|