pmx-canvas 0.6.5 → 0.7.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/extension.mjs +67 -3
- package/CHANGELOG.md +54 -0
- package/Readme.md +29 -5
- package/dist/canvas/global.css +440 -24
- package/dist/canvas/index.js +90 -75
- package/dist/types/cli/agent.d.ts +3 -0
- package/dist/types/cli/commands/backup.d.ts +1 -0
- package/dist/types/cli/commands/boards.d.ts +1 -0
- package/dist/types/cli/commands/export.d.ts +1 -0
- package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
- package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
- package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -0
- package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
- package/dist/types/client/nodes/BoardNode.d.ts +5 -0
- package/dist/types/client/state/boards-store.d.ts +79 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -0
- package/dist/types/client/state/session-store.d.ts +7 -0
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/json-render/catalog.d.ts +1 -1
- package/dist/types/json-render/charts/definitions.d.ts +1 -1
- package/dist/types/json-render/server.d.ts +6 -0
- package/dist/types/mcp/canvas-access.d.ts +10 -3
- package/dist/types/mcp/context-uri-template.d.ts +6 -0
- package/dist/types/server/agent-presence.d.ts +1 -1
- package/dist/types/server/attribution.d.ts +11 -0
- package/dist/types/server/ax-context.d.ts +3 -1
- package/dist/types/server/ax-interaction.d.ts +2 -2
- package/dist/types/server/ax-state.d.ts +13 -3
- package/dist/types/server/backup.d.ts +36 -0
- package/dist/types/server/board-export.d.ts +45 -0
- package/dist/types/server/canvas-db.d.ts +63 -9
- package/dist/types/server/canvas-operations.d.ts +23 -0
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +140 -3
- package/dist/types/server/context-brief.d.ts +111 -0
- package/dist/types/server/context-reads.d.ts +55 -0
- package/dist/types/server/document-import.d.ts +66 -0
- package/dist/types/server/index.d.ts +103 -4
- package/dist/types/server/mutation-history.d.ts +1 -0
- package/dist/types/server/operations/invoker.d.ts +2 -0
- package/dist/types/server/operations/mcp.d.ts +7 -1
- package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
- package/dist/types/server/operations/ops/backup.d.ts +2 -0
- package/dist/types/server/operations/ops/boards.d.ts +10 -0
- package/dist/types/server/operations/ops/export.d.ts +2 -0
- package/dist/types/server/operations/ops/imports.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +1 -1
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +9 -1
- package/dist/types/server/operations/types.d.ts +1 -1
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/trace-manager.d.ts +7 -0
- package/dist/types/server/webpage-node.d.ts +22 -1
- package/dist/types/server/workbench-auth.d.ts +3 -0
- package/dist/types/shared/boards.d.ts +6 -0
- package/docs/RELEASE.md +5 -0
- package/docs/cli.md +68 -0
- package/docs/http-api.md +198 -2
- package/docs/mcp.md +71 -6
- package/docs/node-types.md +26 -0
- package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
- package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
- package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
- package/docs/plans/plan-013-static-board-export.md +47 -0
- package/docs/plans/plan-014-connected-board-memory.md +140 -0
- package/docs/product-vision-2026-09.md +118 -18
- package/docs/sdk.md +50 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +53 -4
- package/skills/pmx-canvas/references/full-reference.md +43 -7
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
- package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
- package/skills/pmx-canvas-testing/SKILL.md +4 -0
- package/src/cli/agent.ts +4 -0
- package/src/cli/commands/ax.ts +13 -0
- package/src/cli/commands/backup.ts +66 -0
- package/src/cli/commands/boards.ts +71 -0
- package/src/cli/commands/export.ts +30 -0
- package/src/cli/commands/nodes.ts +4 -1
- package/src/cli/commands/pins.ts +6 -2
- package/src/cli/commands/pump.ts +3 -0
- package/src/cli/commands/query.ts +33 -7
- package/src/cli/commands/view.ts +7 -5
- package/src/cli/index.ts +19 -9
- package/src/client/App.tsx +21 -3
- package/src/client/canvas/BoardCopyDialog.tsx +153 -0
- package/src/client/canvas/BoardFolderDialog.tsx +120 -0
- package/src/client/canvas/BoardLinkDialog.tsx +93 -0
- package/src/client/canvas/BoardSwitcher.tsx +149 -0
- package/src/client/canvas/CanvasViewport.tsx +3 -0
- package/src/client/canvas/CommandPalette.tsx +107 -4
- package/src/client/canvas/ContextMenu.tsx +4 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
- package/src/client/canvas/ExportDialog.tsx +197 -0
- package/src/client/canvas/HomeView.tsx +277 -0
- package/src/client/canvas/SessionPanel.tsx +28 -1
- package/src/client/canvas/ToolRail.tsx +38 -3
- package/src/client/canvas/TopBar.tsx +34 -18
- package/src/client/canvas/import-files.ts +19 -1
- package/src/client/canvas/kind-colors.ts +1 -0
- package/src/client/icons.tsx +2 -0
- package/src/client/nodes/AttachmentNode.tsx +148 -0
- package/src/client/nodes/BoardNode.tsx +35 -0
- package/src/client/nodes/FileNode.tsx +6 -0
- package/src/client/nodes/MarkdownNode.tsx +47 -19
- package/src/client/nodes/ax-node-actions.ts +2 -5
- package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
- package/src/client/state/boards-store.ts +167 -0
- package/src/client/state/canvas-store.ts +22 -0
- package/src/client/state/intent-bridge.ts +30 -2
- package/src/client/state/node-factory.ts +1 -0
- package/src/client/state/session-store.ts +5 -0
- package/src/client/state/sse-bridge.ts +49 -5
- package/src/client/theme/global.css +440 -24
- package/src/client/types.ts +2 -0
- package/src/json-render/server.ts +16 -9
- package/src/mcp/canvas-access.ts +59 -22
- package/src/mcp/context-uri-template.ts +28 -0
- package/src/mcp/server.ts +122 -15
- package/src/server/agent-context.ts +116 -29
- package/src/server/attribution.ts +29 -0
- package/src/server/ax-context.ts +4 -2
- package/src/server/ax-interaction.ts +1 -0
- package/src/server/ax-state-manager.ts +72 -3
- package/src/server/ax-state.ts +41 -0
- package/src/server/backup.ts +178 -0
- package/src/server/board-export.ts +686 -0
- package/src/server/canvas-db.ts +599 -107
- package/src/server/canvas-operations.ts +68 -9
- package/src/server/canvas-provenance.ts +1 -0
- package/src/server/canvas-schema.ts +12 -0
- package/src/server/canvas-state.ts +774 -45
- package/src/server/context-brief.ts +372 -0
- package/src/server/context-reads.ts +238 -0
- package/src/server/demo-state.json +659 -2
- package/src/server/document-import.ts +223 -0
- package/src/server/index.ts +231 -5
- package/src/server/mutation-history.ts +10 -13
- package/src/server/operations/composites.ts +26 -4
- package/src/server/operations/http.ts +9 -3
- package/src/server/operations/index.ts +8 -0
- package/src/server/operations/invoker.ts +14 -3
- package/src/server/operations/mcp.ts +50 -11
- package/src/server/operations/ops/app.ts +5 -0
- package/src/server/operations/ops/ax-read.ts +10 -6
- package/src/server/operations/ops/ax-timeline.ts +75 -0
- package/src/server/operations/ops/backup.ts +134 -0
- package/src/server/operations/ops/batch.ts +8 -1
- package/src/server/operations/ops/boards.ts +351 -0
- package/src/server/operations/ops/export.ts +74 -0
- package/src/server/operations/ops/ext-app.ts +4 -0
- package/src/server/operations/ops/imports.ts +220 -0
- package/src/server/operations/ops/nodes.ts +57 -5
- package/src/server/operations/ops/query.ts +206 -9
- package/src/server/operations/registry.ts +123 -31
- package/src/server/operations/types.ts +1 -1
- package/src/server/server.ts +146 -2
- package/src/server/spatial-analysis.ts +9 -17
- package/src/server/trace-manager.ts +19 -0
- package/src/server/web-artifacts.ts +2 -1
- package/src/server/webpage-node.ts +173 -13
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/boards.ts +18 -0
- package/src/shared/node-sizes.ts +1 -0
|
@@ -144,6 +144,28 @@ export function clearSelection(): void {
|
|
|
144
144
|
selectedNodeIds.value = new Set();
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/** Clear transient UI state that must never follow a node id onto another board. */
|
|
148
|
+
export function resetCanvasInteractionState(): void {
|
|
149
|
+
if (expandedCloseTimer !== null) {
|
|
150
|
+
clearTimeout(expandedCloseTimer);
|
|
151
|
+
expandedCloseTimer = null;
|
|
152
|
+
}
|
|
153
|
+
batch(() => {
|
|
154
|
+
activeNodeId.value = null;
|
|
155
|
+
expandedNodeId.value = null;
|
|
156
|
+
pendingExpandedNodeCloseId.value = null;
|
|
157
|
+
pendingCloseInitialCheckpointAt = undefined;
|
|
158
|
+
selectedNodeIds.value = new Set();
|
|
159
|
+
selectedEdgeId.value = null;
|
|
160
|
+
pendingConnection.value = null;
|
|
161
|
+
draggingEdge.value = null;
|
|
162
|
+
searchHighlightIds.value = null;
|
|
163
|
+
dragDropTarget.value = null;
|
|
164
|
+
contextPinnedNodeIds.value = new Set();
|
|
165
|
+
});
|
|
166
|
+
dropSuppressedForDrag = false;
|
|
167
|
+
}
|
|
168
|
+
|
|
147
169
|
// ── Groups v2 (rail-chrome-v2 phase 7, item 20) ──────────────────────
|
|
148
170
|
|
|
149
171
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canvasArea } from '../canvas/canvas-area';
|
|
2
|
+
import { showToast } from './attention-bridge';
|
|
2
3
|
import type { AgentPresenceSnapshot } from '../../shared/agent-presence.js';
|
|
3
4
|
function logRequestError(action: string, error: unknown): void {
|
|
4
5
|
console.error(`[intent-bridge] ${action} failed`, error);
|
|
@@ -31,14 +32,38 @@ function withWorkbenchMarker(init?: RequestInit): RequestInit {
|
|
|
31
32
|
headers['x-pmx-source'] = tabAgentKey;
|
|
32
33
|
} else {
|
|
33
34
|
headers['X-PMX-Workbench'] = '1';
|
|
35
|
+
const token = (window as Window & { __PMX_WORKBENCH_TOKEN?: unknown }).__PMX_WORKBENCH_TOKEN;
|
|
36
|
+
if (typeof token === 'string') headers['X-PMX-Workbench-Token'] = token;
|
|
34
37
|
}
|
|
35
38
|
return { ...init, headers };
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A write the server refused (fence, edit lock, validation) must not fail
|
|
43
|
+
* silently: the human sees the server's reason. Reads stay quiet.
|
|
44
|
+
*/
|
|
45
|
+
function reportRefusedWrite(res: Response, init: RequestInit | undefined, body: unknown): void {
|
|
46
|
+
if (res.ok || (init?.method ?? 'GET').toUpperCase() === 'GET') return;
|
|
47
|
+
const error =
|
|
48
|
+
body !== null && typeof body === 'object' && 'error' in body && typeof body.error === 'string' ? body.error : '';
|
|
49
|
+
showToast('remove', 'Change refused', error || `The server answered ${res.status}.`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function readJsonBody(res: Response): Promise<unknown> {
|
|
53
|
+
try {
|
|
54
|
+
return await res.json();
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
export async function requestJson<T>(action: string, url: string, fallback: T, init?: RequestInit): Promise<T> {
|
|
39
61
|
try {
|
|
40
62
|
const res = await fetch(url, withWorkbenchMarker(init));
|
|
41
|
-
|
|
63
|
+
const body = await readJsonBody(res);
|
|
64
|
+
reportRefusedWrite(res, init, body);
|
|
65
|
+
if (body === null) throw new Error(`${res.status} response had no JSON body`);
|
|
66
|
+
return body as T;
|
|
42
67
|
} catch (error) {
|
|
43
68
|
logRequestError(action, error);
|
|
44
69
|
return fallback;
|
|
@@ -48,6 +73,7 @@ export async function requestJson<T>(action: string, url: string, fallback: T, i
|
|
|
48
73
|
export async function requestOk(action: string, url: string, init?: RequestInit): Promise<{ ok: boolean }> {
|
|
49
74
|
try {
|
|
50
75
|
const res = await fetch(url, withWorkbenchMarker(init));
|
|
76
|
+
if (!res.ok) reportRefusedWrite(res, init, await readJsonBody(res));
|
|
51
77
|
return { ok: res.ok };
|
|
52
78
|
} catch (error) {
|
|
53
79
|
logRequestError(action, error);
|
|
@@ -57,7 +83,8 @@ export async function requestOk(action: string, url: string, init?: RequestInit)
|
|
|
57
83
|
|
|
58
84
|
export async function requestBestEffort(action: string, url: string, init?: RequestInit): Promise<void> {
|
|
59
85
|
try {
|
|
60
|
-
await fetch(url, withWorkbenchMarker(init));
|
|
86
|
+
const res = await fetch(url, withWorkbenchMarker(init));
|
|
87
|
+
if (!res.ok) reportRefusedWrite(res, init, await readJsonBody(res));
|
|
61
88
|
} catch (error) {
|
|
62
89
|
logRequestError(action, error);
|
|
63
90
|
}
|
|
@@ -262,6 +289,7 @@ export async function createNodeFromClient(opts: {
|
|
|
262
289
|
y?: number;
|
|
263
290
|
width?: number;
|
|
264
291
|
height?: number;
|
|
292
|
+
boardId?: string;
|
|
265
293
|
}): Promise<{ ok: boolean; id?: string }> {
|
|
266
294
|
return requestJson(
|
|
267
295
|
'createNodeFromClient',
|
|
@@ -27,6 +27,7 @@ export const DEFAULT_POSITIONS: Record<CanvasNodeState['type'], { x: number; y:
|
|
|
27
27
|
webpage: { x: 380, y: 80, w: 520, h: 420 },
|
|
28
28
|
'json-render': { x: 380, y: 720, w: 840, h: 620 },
|
|
29
29
|
graph: { x: 380, y: 720, w: 760, h: 520 },
|
|
30
|
+
board: { x: 380, y: 80, w: 360, h: 160 },
|
|
30
31
|
ledger: { x: 1130, y: 520, w: 320, h: 280 },
|
|
31
32
|
trace: { x: 40, y: 900, w: 200, h: 56 },
|
|
32
33
|
file: { x: 380, y: 80, w: 720, h: 600 },
|
|
@@ -26,6 +26,8 @@ export interface ApprovalGateView {
|
|
|
26
26
|
createdAt: string;
|
|
27
27
|
/** Unattended-approval TTL: when a pending gate auto-holds. */
|
|
28
28
|
expiresAt: string | null;
|
|
29
|
+
resolvedBy?: { actor: 'human' | 'agent' | 'system' | 'unknown'; source: string; agentId?: string } | null;
|
|
30
|
+
selfAnswer?: boolean;
|
|
29
31
|
}
|
|
30
32
|
export interface AxEventView {
|
|
31
33
|
id: string;
|
|
@@ -106,6 +108,9 @@ export async function setScopeFence(nodeIds: string[] | null): Promise<boolean>
|
|
|
106
108
|
|
|
107
109
|
/** Auto-held gates: the policy said no on the human's behalf; they can be reopened. */
|
|
108
110
|
export const heldGates = computed(() => sessionGates.value.filter((gate) => gate.status === 'held'));
|
|
111
|
+
export const resolvedGates = computed(() =>
|
|
112
|
+
sessionGates.value.filter((gate) => gate.status === 'approved' || gate.status === 'rejected'),
|
|
113
|
+
);
|
|
109
114
|
|
|
110
115
|
export interface AxTimelineView {
|
|
111
116
|
events: AxEventView[];
|
|
@@ -27,10 +27,13 @@ import {
|
|
|
27
27
|
workbenchConnectionEpoch,
|
|
28
28
|
reconnectAttempt,
|
|
29
29
|
reconnectDelay,
|
|
30
|
+
resetCanvasInteractionState,
|
|
30
31
|
} from './canvas-store';
|
|
32
|
+
import { applyBoards, loadBoards, setBoardSwitchHandler } from './boards-store';
|
|
31
33
|
import {
|
|
32
34
|
fetchAgentPresence,
|
|
33
35
|
fetchAxSurfaceState,
|
|
36
|
+
fetchCanvasState,
|
|
34
37
|
reportClientViewportSize,
|
|
35
38
|
requestJson,
|
|
36
39
|
tabAgentKey,
|
|
@@ -304,6 +307,7 @@ function isCanvasNodeType(value: unknown): value is CanvasNodeState['type'] {
|
|
|
304
307
|
value === 'webpage' ||
|
|
305
308
|
value === 'json-render' ||
|
|
306
309
|
value === 'graph' ||
|
|
310
|
+
value === 'board' ||
|
|
307
311
|
value === 'prompt' ||
|
|
308
312
|
value === 'response' ||
|
|
309
313
|
value === 'status' ||
|
|
@@ -456,14 +460,44 @@ function handleAgentSessionEnded(data: Record<string, unknown>): void {
|
|
|
456
460
|
}
|
|
457
461
|
|
|
458
462
|
let stopHumanPresence: (() => void) | null = null;
|
|
463
|
+
let boardResyncGeneration = 0;
|
|
464
|
+
let layoutRevision = 0;
|
|
465
|
+
let pinsRevision = 0;
|
|
459
466
|
|
|
460
467
|
function handleHumanPresence(data: Record<string, unknown>): void {
|
|
461
468
|
applyHumanSnapshot(data as Partial<HumanPresenceSnapshot>);
|
|
462
469
|
}
|
|
463
470
|
|
|
471
|
+
/**
|
|
472
|
+
* The open board changed (this tab, another tab, or a restart): the canvas is
|
|
473
|
+
* a different board now, so take its layout like a fresh connection would —
|
|
474
|
+
* camera included — and drop what belonged to the old board.
|
|
475
|
+
*/
|
|
476
|
+
function resyncForBoardSwitch(): void {
|
|
477
|
+
const generation = ++boardResyncGeneration;
|
|
478
|
+
savedLayout = null;
|
|
479
|
+
hasInitialServerLayout.value = false;
|
|
480
|
+
resetIntents();
|
|
481
|
+
resetCanvasInteractionState();
|
|
482
|
+
axSurfaceState.value = null;
|
|
483
|
+
void refreshAxSurface();
|
|
484
|
+
const layoutAtRequest = layoutRevision;
|
|
485
|
+
const pinsAtRequest = pinsRevision;
|
|
486
|
+
void fetchCanvasState().then((layout) => {
|
|
487
|
+
if (generation === boardResyncGeneration && layoutAtRequest === layoutRevision)
|
|
488
|
+
handleCanvasLayoutUpdate({ layout });
|
|
489
|
+
});
|
|
490
|
+
void requestJson<{ nodeIds?: unknown }>('fetchPinnedContext', '/api/canvas/pinned-context', {}).then((data) => {
|
|
491
|
+
if (generation === boardResyncGeneration && pinsAtRequest === pinsRevision)
|
|
492
|
+
handleContextPinsChanged({ nodeIds: data.nodeIds });
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
setBoardSwitchHandler(resyncForBoardSwitch);
|
|
496
|
+
|
|
464
497
|
function handleConnected(data: Record<string, unknown>): void {
|
|
465
498
|
sessionId.value = (data.sessionId as string) || '';
|
|
466
499
|
connectionStatus.value = 'connected';
|
|
500
|
+
void loadBoards();
|
|
467
501
|
// Reconnect marker for holders of server-minted URLs (Finding S).
|
|
468
502
|
workbenchConnectionEpoch.value += 1;
|
|
469
503
|
reloadIfServerUpgraded(data.version, data.bundleStamp);
|
|
@@ -486,8 +520,7 @@ function handleConnected(data: Record<string, unknown>): void {
|
|
|
486
520
|
// The AX surface snapshot (work items, gates) likewise — it used to arrive
|
|
487
521
|
// only on the first ax-state-changed, so a fresh load showed an empty
|
|
488
522
|
// session panel while persisted work items existed.
|
|
489
|
-
void
|
|
490
|
-
axSurfaceState.value = state;
|
|
523
|
+
void refreshAxSurface().then(() => {
|
|
491
524
|
if (sessionActive.value) void refreshTimeline();
|
|
492
525
|
});
|
|
493
526
|
// A ?theme= session override (host-default theming) wins over the
|
|
@@ -903,6 +936,7 @@ function handleCanvasLayoutUpdate(data: Record<string, unknown>): void {
|
|
|
903
936
|
}
|
|
904
937
|
| undefined;
|
|
905
938
|
if (!layout?.nodes) return;
|
|
939
|
+
layoutRevision += 1;
|
|
906
940
|
const shouldApplyViewport = !hasInitialServerLayout.value;
|
|
907
941
|
hasInitialServerLayout.value = true;
|
|
908
942
|
|
|
@@ -993,6 +1027,7 @@ function handleThemeChanged(data: Record<string, unknown>): void {
|
|
|
993
1027
|
}
|
|
994
1028
|
|
|
995
1029
|
function handleContextPinsChanged(data: Record<string, unknown>): void {
|
|
1030
|
+
pinsRevision += 1;
|
|
996
1031
|
const nodeIds = Array.isArray(data.nodeIds) ? data.nodeIds.filter((id): id is string => typeof id === 'string') : [];
|
|
997
1032
|
replaceContextPinsFromServer(nodeIds);
|
|
998
1033
|
syncAttentionFromSse({ event: 'context-pins-changed', data });
|
|
@@ -1003,13 +1038,21 @@ function handleContextPinsChanged(data: Record<string, unknown>): void {
|
|
|
1003
1038
|
// (debounced). The snapshot feeds AX-enabled surfaces (HtmlNode/McpAppNode push it
|
|
1004
1039
|
// into their iframes), so authored boards reflect the live work queue / focus.
|
|
1005
1040
|
let axRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1041
|
+
let axRefreshSequence = 0;
|
|
1042
|
+
async function refreshAxSurface(): Promise<void> {
|
|
1043
|
+
const sequence = ++axRefreshSequence;
|
|
1044
|
+
const generation = boardResyncGeneration;
|
|
1045
|
+
const state = await fetchAxSurfaceState();
|
|
1046
|
+
if (sequence === axRefreshSequence && generation === boardResyncGeneration) axSurfaceState.value = state;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1006
1049
|
function handleAxStateChanged(): void {
|
|
1007
1050
|
if (axRefreshTimer) clearTimeout(axRefreshTimer);
|
|
1051
|
+
// Invalidate any in-flight read before the debounced replacement starts.
|
|
1052
|
+
axRefreshSequence += 1;
|
|
1008
1053
|
axRefreshTimer = setTimeout(() => {
|
|
1009
1054
|
axRefreshTimer = null;
|
|
1010
|
-
void
|
|
1011
|
-
axSurfaceState.value = state;
|
|
1012
|
-
});
|
|
1055
|
+
void refreshAxSurface();
|
|
1013
1056
|
// The session panel's timeline — only worth fetching while it is mounted.
|
|
1014
1057
|
if (sessionActive.value) void refreshTimeline();
|
|
1015
1058
|
}, 150);
|
|
@@ -1064,6 +1107,7 @@ export const EVENT_HANDLERS: Record<string, (data: Record<string, unknown>) => v
|
|
|
1064
1107
|
'ext-app-update': handleExtAppUpdate,
|
|
1065
1108
|
'ext-app-result': handleExtAppResult,
|
|
1066
1109
|
'context-pins-changed': handleContextPinsChanged,
|
|
1110
|
+
'boards-changed': applyBoards,
|
|
1067
1111
|
'canvas-layout-update': handleCanvasLayoutUpdate,
|
|
1068
1112
|
'canvas-focus-node': handleCanvasFocusNode,
|
|
1069
1113
|
'canvas-viewport-update': handleCanvasViewportUpdate,
|