pmx-canvas 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/Readme.md +76 -36
- package/dist/canvas/global.css +169 -3
- package/dist/canvas/index.js +112 -87
- package/dist/canvas/mermaid-entry.js +3563 -0
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/smoke.d.ts +1 -0
- package/dist/types/cli/shared.d.ts +13 -1
- package/dist/types/client/canvas/CanvasNode.d.ts +11 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
- package/dist/types/client/mermaid-entry.d.ts +11 -0
- package/dist/types/client/nodes/AxStepControls.d.ts +4 -0
- package/dist/types/client/nodes/DiffNode.d.ts +20 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +0 -2
- package/dist/types/client/nodes/FileNode.d.ts +14 -0
- package/dist/types/client/nodes/MermaidNode.d.ts +12 -0
- package/dist/types/client/state/canvas-store.d.ts +10 -0
- package/dist/types/client/state/intent-bridge.d.ts +9 -0
- package/dist/types/client/types.d.ts +2 -2
- package/dist/types/server/ax-flow-loop.d.ts +5 -0
- package/dist/types/server/ax-interaction.d.ts +21 -2
- package/dist/types/server/canvas-operations.d.ts +5 -0
- package/dist/types/server/canvas-provenance.d.ts +1 -1
- package/dist/types/server/canvas-state.d.ts +18 -1
- package/dist/types/server/canvas-validation.d.ts +28 -1
- package/dist/types/server/file-content.d.ts +46 -0
- package/dist/types/server/html-primitives.d.ts +13 -1
- package/dist/types/server/html-surface.d.ts +8 -0
- package/dist/types/server/index.d.ts +5 -0
- package/dist/types/server/operations/ops/ax-work.d.ts +1 -0
- package/dist/types/server/operations/ops/nodes.d.ts +1 -1
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/server/web-artifacts.d.ts +14 -0
- package/dist/types/server/workboard.d.ts +46 -0
- package/dist/types/shared/ax-flow.d.ts +51 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +7 -0
- package/dist/types/shared/node-sizes.d.ts +33 -0
- package/docs/cli.md +31 -2
- package/docs/environment.md +1 -0
- package/docs/http-api.md +29 -0
- package/docs/mcp.md +13 -1
- package/docs/node-types.md +47 -1
- package/docs/screenshot.png +0 -0
- package/package.json +5 -4
- package/skills/pmx-canvas/SKILL.md +123 -25
- package/skills/pmx-canvas/evals/evals.json +54 -3
- package/skills/pmx-canvas/references/full-reference.md +11 -6
- package/skills/pmx-canvas-testing/SKILL.md +3 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +28 -3
- package/src/cli/commands/history.ts +1 -1
- package/src/cli/commands/nodes.ts +2 -2
- package/src/cli/commands/query.ts +1 -1
- package/src/cli/commands/smoke.ts +212 -0
- package/src/cli/commands/view.ts +1 -1
- package/src/cli/index.ts +7 -0
- package/src/cli/shared.ts +35 -5
- package/src/client/App.tsx +28 -15
- package/src/client/canvas/CanvasNode.tsx +54 -5
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/EdgeLayer.tsx +44 -24
- package/src/client/canvas/ExpandedNodeOverlay.tsx +21 -0
- package/src/client/canvas/Minimap.tsx +2 -0
- package/src/client/canvas/auto-fit.ts +12 -1
- package/src/client/canvas/use-pan-zoom.ts +16 -1
- package/src/client/icons.tsx +4 -0
- package/src/client/mermaid-entry.ts +76 -0
- package/src/client/nodes/AxStepControls.tsx +226 -0
- package/src/client/nodes/DiffNode.tsx +114 -0
- package/src/client/nodes/ExtAppFrame.tsx +259 -84
- package/src/client/nodes/FileNode.tsx +249 -51
- package/src/client/nodes/MermaidNode.tsx +81 -0
- package/src/client/state/canvas-store.ts +19 -0
- package/src/client/state/intent-bridge.ts +32 -0
- package/src/client/state/node-factory.ts +2 -0
- package/src/client/state/sse-bridge.ts +32 -1
- package/src/client/theme/global.css +169 -3
- package/src/client/types.ts +6 -0
- package/src/mcp/server.ts +19 -1
- package/src/server/ax-flow-loop.ts +114 -0
- package/src/server/ax-interaction.ts +305 -4
- package/src/server/canvas-operations.ts +83 -26
- package/src/server/canvas-provenance.ts +2 -0
- package/src/server/canvas-schema.ts +33 -0
- package/src/server/canvas-state.ts +46 -10
- package/src/server/canvas-validation.ts +88 -0
- package/src/server/demo-state.json +4605 -523
- package/src/server/demo.ts +24 -0
- package/src/server/file-content.ts +152 -0
- package/src/server/file-watcher.ts +7 -10
- package/src/server/html-primitives.ts +636 -0
- package/src/server/html-surface.ts +11 -0
- package/src/server/index.ts +25 -6
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-work.ts +5 -0
- package/src/server/operations/ops/batch.ts +8 -0
- package/src/server/operations/ops/json-render.ts +44 -0
- package/src/server/operations/ops/nodes.ts +24 -2
- package/src/server/operations/ops/viewport.ts +15 -2
- package/src/server/server.ts +154 -6
- package/src/server/web-artifacts.ts +109 -0
- package/src/server/workboard.ts +159 -0
- package/src/shared/ax-flow.ts +102 -0
- package/src/shared/ax-surface-protocol.ts +8 -0
- package/src/shared/node-sizes.ts +52 -0
- package/src/shared/surface.ts +2 -0
- package/dist/types/server/chart-template.d.ts +0 -29
- package/src/server/chart-template.ts +0 -448
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
* import cycle (canvas-state → canvas-provenance must not pull this in).
|
|
16
16
|
*/
|
|
17
17
|
import { z } from 'zod';
|
|
18
|
-
import type { CanvasNodeState } from './canvas-state.js';
|
|
18
|
+
import type { CanvasEdge, CanvasLayout, CanvasNodeState } from './canvas-state.js';
|
|
19
19
|
import type { CanvasNodeType } from './canvas-provenance.js';
|
|
20
20
|
import type { PmxAxApprovalGate, PmxAxElicitation, PmxAxEvent, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxMode, PmxAxModeRequest, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxSource, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
|
|
21
|
-
export declare const AX_INTERACTION_TYPES: readonly ["ax.event.record", "ax.steer", "ax.work.create", "ax.work.update", "ax.evidence.add", "ax.approval.request", "ax.approval.resolve", "ax.review.add", "ax.focus.set", "ax.command.invoke", "ax.elicitation.request", "ax.mode.request"];
|
|
21
|
+
export declare const AX_INTERACTION_TYPES: readonly ["ax.event.record", "ax.steer", "ax.work.create", "ax.work.update", "ax.evidence.add", "ax.approval.request", "ax.approval.resolve", "ax.review.add", "ax.focus.set", "ax.command.invoke", "ax.elicitation.request", "ax.mode.request", "ax.flow.materialize"];
|
|
22
22
|
export type AxInteractionType = (typeof AX_INTERACTION_TYPES)[number];
|
|
23
23
|
export type AxDeliveryMode = 'record-only' | 'notify-agent' | 'send-to-agent';
|
|
24
24
|
export interface NodeAxCapabilities {
|
|
@@ -59,6 +59,7 @@ declare const InteractionEnvelopeSchema: z.ZodObject<{
|
|
|
59
59
|
"ax.command.invoke": "ax.command.invoke";
|
|
60
60
|
"ax.elicitation.request": "ax.elicitation.request";
|
|
61
61
|
"ax.mode.request": "ax.mode.request";
|
|
62
|
+
"ax.flow.materialize": "ax.flow.materialize";
|
|
62
63
|
}>;
|
|
63
64
|
sourceNodeId: z.ZodString;
|
|
64
65
|
sourceSurface: z.ZodOptional<z.ZodEnum<{
|
|
@@ -98,6 +99,13 @@ export interface AxInteractionInput {
|
|
|
98
99
|
*/
|
|
99
100
|
export interface AxInteractionManager {
|
|
100
101
|
getNode(id: string): CanvasNodeState | undefined;
|
|
102
|
+
/** Canvas read for `ax.flow.materialize` (placement obstacles + stale flow sweep). */
|
|
103
|
+
getLayout(): CanvasLayout;
|
|
104
|
+
addNode(node: CanvasNodeState): void;
|
|
105
|
+
updateNode(id: string, patch: Partial<CanvasNodeState>): void;
|
|
106
|
+
/** Also drops the node's edges — the flow's replace path relies on that. */
|
|
107
|
+
removeNode(id: string): void;
|
|
108
|
+
addEdge(edge: CanvasEdge): boolean;
|
|
101
109
|
recordAxEvent(input: {
|
|
102
110
|
kind: PmxAxEventKind;
|
|
103
111
|
summary: string;
|
|
@@ -200,6 +208,17 @@ export interface AxInteractionResult {
|
|
|
200
208
|
result: AxInteractionPublicResult;
|
|
201
209
|
events: AxInteractionEvent[];
|
|
202
210
|
}
|
|
211
|
+
export interface AxFlowMaterializeInput {
|
|
212
|
+
title?: string;
|
|
213
|
+
steps: Array<{
|
|
214
|
+
title: string;
|
|
215
|
+
detail?: string;
|
|
216
|
+
}>;
|
|
217
|
+
loop?: {
|
|
218
|
+
enabled?: boolean;
|
|
219
|
+
maxRuns?: number;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
203
222
|
/**
|
|
204
223
|
* Validate + execute a node-originated AX interaction. Returns the public result
|
|
205
224
|
* plus the SSE events the caller should emit (accepted/rejected outcome + the
|
|
@@ -259,5 +259,10 @@ export declare function createCanvasGraphNode(input: GraphNodeInput): {
|
|
|
259
259
|
spec: JsonRenderSpec;
|
|
260
260
|
node: CanvasNodeState;
|
|
261
261
|
};
|
|
262
|
+
export declare function setClientViewportSize(width: unknown, height: unknown): void;
|
|
263
|
+
export declare function getClientViewportSize(): {
|
|
264
|
+
width: number;
|
|
265
|
+
height: number;
|
|
266
|
+
} | null;
|
|
262
267
|
export declare function fitCanvasView(options?: CanvasFitViewOptions): CanvasFitViewResult;
|
|
263
268
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
1
|
+
export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
|
|
2
2
|
export type CanvasNodeProvenanceSourceKind = 'workspace-file' | 'webpage-url' | 'mcp-tool' | 'artifact-file' | 'image-url';
|
|
3
3
|
export type CanvasNodeRefreshStrategy = 'file-watch' | 'file-read-write' | 'image-reload' | 'webpage-refresh' | 'mcp-app-rehydrate' | 'artifact-reopen';
|
|
4
4
|
export interface CanvasNodeProvenance {
|
|
@@ -53,7 +53,7 @@ export interface CanvasSnapshotGcResult {
|
|
|
53
53
|
}
|
|
54
54
|
export interface CanvasNodeState {
|
|
55
55
|
id: string;
|
|
56
|
-
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'image' | 'html' | 'group';
|
|
56
|
+
type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
|
|
57
57
|
position: {
|
|
58
58
|
x: number;
|
|
59
59
|
y: number;
|
|
@@ -154,6 +154,15 @@ declare class CanvasStateManager {
|
|
|
154
154
|
* — e.g. the long-lived MCP subscription — may ignore the return value).
|
|
155
155
|
*/
|
|
156
156
|
onChange(cb: (type: CanvasChangeType) => void): () => void;
|
|
157
|
+
private _workItemsChangedListener;
|
|
158
|
+
/**
|
|
159
|
+
* Register THE work-item change listener (single slot, last-write-wins).
|
|
160
|
+
* Fired after addWorkItem/updateWorkItem completes (including the node
|
|
161
|
+
* status mirror), so live views like the workboard can rebuild from the
|
|
162
|
+
* fresh work-item list.
|
|
163
|
+
*/
|
|
164
|
+
setWorkItemsChangedListener(listener: (() => void) | null): void;
|
|
165
|
+
private notifyWorkItemsChanged;
|
|
157
166
|
private notifyChange;
|
|
158
167
|
private _mutationRecorder;
|
|
159
168
|
private _suppressRecordingDepth;
|
|
@@ -268,6 +277,14 @@ declare class CanvasStateManager {
|
|
|
268
277
|
setTheme(theme: CanvasTheme): CanvasTheme;
|
|
269
278
|
get contextPinnedNodeIds(): Set<string>;
|
|
270
279
|
getAxState(): PmxAxState;
|
|
280
|
+
/**
|
|
281
|
+
* Replace the canvas-bound AX partition from a persisted blob, normalized against
|
|
282
|
+
* the current node set — the same operation snapshot restore performs internally
|
|
283
|
+
* (`applyPersistedState`), exposed for the demo seed. Both restore nodes whose
|
|
284
|
+
* `data.axStep.workItemId` only resolves if the work items come back with their
|
|
285
|
+
* original ids, so this preserves them verbatim rather than re-creating them.
|
|
286
|
+
*/
|
|
287
|
+
applyPersistedAxState(ax: unknown): void;
|
|
271
288
|
getAxFocus(): PmxAxFocusState;
|
|
272
289
|
setAxFocus(nodeIds: string[], options?: {
|
|
273
290
|
source?: PmxAxSource;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CanvasLayout } from './canvas-state.js';
|
|
1
|
+
import type { CanvasLayout, CanvasNodeState } from './canvas-state.js';
|
|
2
|
+
export { NODE_MIN_SIZES, clampCreateNodeSize, nodeMinSize } from '../shared/node-sizes.js';
|
|
2
3
|
export interface CanvasValidationPair {
|
|
3
4
|
aId: string;
|
|
4
5
|
aTitle: string | null;
|
|
@@ -11,6 +12,21 @@ export interface CanvasContainmentIssue {
|
|
|
11
12
|
childId: string;
|
|
12
13
|
childTitle: string | null;
|
|
13
14
|
}
|
|
15
|
+
export interface CanvasSizeWarning {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string | null;
|
|
18
|
+
type: CanvasNodeState['type'];
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
minWidth: number;
|
|
22
|
+
minHeight: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CanvasHiddenEdgeEndpoint {
|
|
25
|
+
edgeId: string;
|
|
26
|
+
nodeId: string;
|
|
27
|
+
nodeTitle: string | null;
|
|
28
|
+
dockPosition: 'left' | 'right';
|
|
29
|
+
}
|
|
14
30
|
export interface CanvasValidationResult {
|
|
15
31
|
ok: boolean;
|
|
16
32
|
collisions: CanvasValidationPair[];
|
|
@@ -21,6 +37,15 @@ export interface CanvasValidationResult {
|
|
|
21
37
|
from: string;
|
|
22
38
|
to: string;
|
|
23
39
|
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Edges whose endpoint node is DOCKED — it renders in the HUD column, not on
|
|
42
|
+
* the canvas, so the edge visually terminates in empty space even though both
|
|
43
|
+
* endpoint IDs resolve (0.4.6 orb feedback #1). Advisory: reported for
|
|
44
|
+
* diagnosis, but does NOT fail `ok` (see the note at the return site).
|
|
45
|
+
*/
|
|
46
|
+
hiddenEdgeEndpoints: CanvasHiddenEdgeEndpoint[];
|
|
47
|
+
/** Nodes below their type's readable minimum (advisory — does not fail `ok`). */
|
|
48
|
+
sizeWarnings: CanvasSizeWarning[];
|
|
24
49
|
summary: {
|
|
25
50
|
nodes: number;
|
|
26
51
|
edges: number;
|
|
@@ -28,6 +53,8 @@ export interface CanvasValidationResult {
|
|
|
28
53
|
containments: number;
|
|
29
54
|
containmentViolations: number;
|
|
30
55
|
missingEdgeEndpoints: number;
|
|
56
|
+
hiddenEdgeEndpoints: number;
|
|
57
|
+
sizeWarnings: number;
|
|
31
58
|
};
|
|
32
59
|
}
|
|
33
60
|
export declare function validateCanvasLayout(layout: CanvasLayout): CanvasValidationResult;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared disk reader for `file` canvas nodes.
|
|
3
|
+
*
|
|
4
|
+
* Both entry points that put a file on the canvas — `buildFileNodeData`
|
|
5
|
+
* (creation) and the file watcher (live updates) — go through here so their
|
|
6
|
+
* notion of "what this file is" can never drift. Reading a file blind with
|
|
7
|
+
* `readFileSync(path, 'utf-8')` turned binaries into mojibake and had no size
|
|
8
|
+
* ceiling at all, so a multi-GB file was pulled into memory as a string.
|
|
9
|
+
*/
|
|
10
|
+
/** Text past this many bytes is stored truncated instead of whole. */
|
|
11
|
+
export declare const MAX_FILE_TEXT_BYTES: number;
|
|
12
|
+
export type FileNodeContent =
|
|
13
|
+
/** Missing, unreadable, or not a regular file — the node stays path-backed. */
|
|
14
|
+
{
|
|
15
|
+
kind: 'unavailable';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'binary';
|
|
18
|
+
byteSize: number;
|
|
19
|
+
mtimeMs: number;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'text';
|
|
23
|
+
byteSize: number;
|
|
24
|
+
mtimeMs: number;
|
|
25
|
+
text: string;
|
|
26
|
+
lineCount: number;
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* PDF check straight from a path, for the byte route's server-derived
|
|
31
|
+
* Content-Type. Reads only the magic-byte prefix. Any read failure is "not a
|
|
32
|
+
* PDF" — the caller then serves an opaque download rather than an inline type.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isPdfFilePath(filePath: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Classify a file and read at most `MAX_FILE_TEXT_BYTES` of it.
|
|
37
|
+
*
|
|
38
|
+
* Never throws: an unreadable path is reported as `unavailable`, matching the
|
|
39
|
+
* pre-existing behavior where a bad path still rendered as a file node.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readFileNodeContent(filePath: string): FileNodeContent;
|
|
42
|
+
/**
|
|
43
|
+
* Map a read result onto `file` node data in place, clearing the fields the new
|
|
44
|
+
* state contradicts (a file that turns binary must not keep stale decoded text).
|
|
45
|
+
*/
|
|
46
|
+
export declare function applyFileContentToNodeData(data: Record<string, unknown>, content: FileNodeContent): Record<string, unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AxInteractionType } from './ax-interaction.js';
|
|
2
|
+
export declare const HTML_PRIMITIVE_KINDS: readonly ["choice-grid", "plan-timeline", "review-sheet", "pr-writeup", "system-map", "code-walkthrough", "design-sheet", "component-gallery", "interaction-prototype", "flowchart", "deck", "presentation", "illustration-set", "explainer", "status-report", "incident-report", "triage-board", "config-editor", "prompt-tuner", "ax-board", "ax-flow"];
|
|
2
3
|
export type HtmlPrimitiveKind = (typeof HTML_PRIMITIVE_KINDS)[number];
|
|
3
4
|
export interface HtmlPrimitiveDescriptor {
|
|
4
5
|
kind: HtmlPrimitiveKind;
|
|
@@ -11,6 +12,17 @@ export interface HtmlPrimitiveDescriptor {
|
|
|
11
12
|
};
|
|
12
13
|
dataShape: string;
|
|
13
14
|
example: Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* AX capabilities this kind REQUIRES to function. `html` is AX-opt-in, so a
|
|
17
|
+
* primitive that emits interactions is inert without them — node creation
|
|
18
|
+
* applies this to `data.axCapabilities` (an explicit caller value still wins,
|
|
19
|
+
* and the server clamps both to the `html` type ceiling). Only set it on kinds
|
|
20
|
+
* that actually emit; every other kind stays AX-free.
|
|
21
|
+
*/
|
|
22
|
+
axCapabilities?: {
|
|
23
|
+
enabled: true;
|
|
24
|
+
allowed: AxInteractionType[];
|
|
25
|
+
};
|
|
14
26
|
}
|
|
15
27
|
export interface HtmlPrimitiveInput {
|
|
16
28
|
kind: HtmlPrimitiveKind;
|
|
@@ -53,6 +53,14 @@ export declare function buildAxStateBridge(axToken: string, snapshotJson: string
|
|
|
53
53
|
* and the json-render injection site stay byte-identical (no drift).
|
|
54
54
|
*/
|
|
55
55
|
export declare function buildContentHeightReporter(frameToken: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Author-HTML fragment for a `mermaid` node surface: the HTML-escaped diagram
|
|
58
|
+
* source in a hidden `<pre>` the entry script reads back via textContent, plus
|
|
59
|
+
* the separately-built renderer bundle (dist/canvas/mermaid-entry.js — kept out
|
|
60
|
+
* of the main SPA bundle). Escaping is mandatory: the source is arbitrary node
|
|
61
|
+
* data and must not be parsed as markup.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildMermaidSurfaceHtml(source: string): string;
|
|
56
64
|
export interface HtmlSurfaceOptions {
|
|
57
65
|
theme: SurfaceTheme;
|
|
58
66
|
/**
|
|
@@ -161,9 +161,12 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
161
161
|
data?: Record<string, unknown> | null;
|
|
162
162
|
}, options?: {
|
|
163
163
|
source?: PmxAxSource;
|
|
164
|
+
agentId?: string | null;
|
|
164
165
|
}): PmxAxEvent;
|
|
165
166
|
sendSteering(message: string, options?: {
|
|
166
167
|
source?: PmxAxSource;
|
|
168
|
+
agentId?: string | null;
|
|
169
|
+
target?: string | null;
|
|
167
170
|
}): PmxAxSteeringMessage;
|
|
168
171
|
markSteeringDelivered(id: string): boolean;
|
|
169
172
|
/**
|
|
@@ -198,6 +201,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
198
201
|
status?: PmxAxWorkItemStatus;
|
|
199
202
|
detail?: string | null;
|
|
200
203
|
nodeIds?: string[];
|
|
204
|
+
agentId?: string | null;
|
|
201
205
|
}, options?: {
|
|
202
206
|
source?: PmxAxSource;
|
|
203
207
|
}): PmxAxWorkItem;
|
|
@@ -206,6 +210,7 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
206
210
|
status?: PmxAxWorkItemStatus;
|
|
207
211
|
detail?: string | null;
|
|
208
212
|
nodeIds?: string[];
|
|
213
|
+
agentId?: string | null;
|
|
209
214
|
}, options?: {
|
|
210
215
|
source?: PmxAxSource;
|
|
211
216
|
}): PmxAxWorkItem | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CanvasLayout, type CanvasNodeState } from '../../canvas-state.js';
|
|
2
2
|
import { type Operation } from '../types.js';
|
|
3
|
-
export declare const NODE_TYPES: readonly ["markdown", "status", "context", "ledger", "trace", "file", "image", "mcp-app", "webpage", "html", "group"];
|
|
3
|
+
export declare const NODE_TYPES: readonly ["markdown", "status", "context", "ledger", "trace", "file", "diff", "mermaid", "image", "mcp-app", "webpage", "html", "group"];
|
|
4
4
|
/** Per-type default node frame size (formerly copy-pasted ladders). */
|
|
5
5
|
export declare function defaultNodeSize(type: string): {
|
|
6
6
|
width: number;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* - POST /api/canvas/json-render -> create a native json-render node
|
|
23
23
|
* - POST /api/canvas/graph -> create a native graph node
|
|
24
24
|
* - GET /api/canvas/json-render/view?nodeId=... -> local json-render viewer
|
|
25
|
+
* - GET /api/canvas/file-bytes?nodeId=... -> raw bytes for a file node
|
|
25
26
|
* - POST /api/canvas/web-artifact -> build bundled HTML artifact + optional canvas node
|
|
26
27
|
* - GET /api/workbench/events -> SSE event stream
|
|
27
28
|
* - GET /api/workbench/poll -> proxy-safe polling transport (same events, JSON)
|
|
@@ -53,6 +53,20 @@ export interface WebArtifactCanvasBuildResult extends WebArtifactBuildOutput {
|
|
|
53
53
|
}
|
|
54
54
|
export declare function resolveWorkspacePath(pathLike: string, cwd?: string): string;
|
|
55
55
|
export declare function resolveWebArtifactScriptPath(kind: 'init' | 'bundle'): string;
|
|
56
|
+
export type NodeVersionCheck = {
|
|
57
|
+
ok: true;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Pure preflight comparison used before any package-manager invocation.
|
|
64
|
+
* `detected` is raw `node --version` output ("v20.11.1"), or null when no node
|
|
65
|
+
* executable answered; `required` is a `major.minor` floor; `packageManager` is
|
|
66
|
+
* the pin that imposes it (e.g. "pnpm@11.1.2"). Returns an actionable message
|
|
67
|
+
* instead of throwing so every caller surfaces it on its own error channel.
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkWebArtifactNodeVersion(detected: string | null, required: string, packageManager: string): NodeVersionCheck;
|
|
56
70
|
export declare function executeWebArtifactBuild(input: WebArtifactBuildInput): Promise<WebArtifactBuildOutput>;
|
|
57
71
|
export declare function openWebArtifactInCanvas(input: {
|
|
58
72
|
title: string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live AX work-item board, materialized as a json-render node.
|
|
3
|
+
*
|
|
4
|
+
* `buildWorkboardSpec` is the pure spec builder (status columns of work-item
|
|
5
|
+
* cards); `renderWorkboard` is the operation core (create-or-rebuild the
|
|
6
|
+
* single `data.workboard === true` json-render node); `refreshWorkboardNodes`
|
|
7
|
+
* is the live-refresh hook this module registers on `canvasState` so every
|
|
8
|
+
* addWorkItem/updateWorkItem rebuilds the board from the fresh work-item list
|
|
9
|
+
* through the SAME spec/update path the operation uses.
|
|
10
|
+
*
|
|
11
|
+
* The refresh runs inside `withSuppressedRecording` so live board rebuilds do
|
|
12
|
+
* not spam undo/redo history (persistence and SSE emits are unaffected).
|
|
13
|
+
*/
|
|
14
|
+
import type { JsonRenderSpec } from '../json-render/server.js';
|
|
15
|
+
import type { PmxAxWorkItem } from './ax-state.js';
|
|
16
|
+
export declare const WORKBOARD_NODE_TITLE = "Work Board";
|
|
17
|
+
export declare const WORKBOARD_NODE_SIZE: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Build the workboard json-render spec from a work-item list: one column per
|
|
23
|
+
* status (todo → in-progress → blocked → done → cancelled; empty statuses are
|
|
24
|
+
* omitted), each item a Card with an agentId Badge and detail Text when
|
|
25
|
+
* present. An empty list renders a single muted "No work items" text block.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildWorkboardSpec(workItems: PmxAxWorkItem[]): JsonRenderSpec;
|
|
28
|
+
/**
|
|
29
|
+
* Operation core for `render.workboard`: rebuild the existing workboard node
|
|
30
|
+
* in place, or create one (tagged `data.workboard: true`) when none exists.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderWorkboard(input?: {
|
|
33
|
+
x?: number;
|
|
34
|
+
y?: number;
|
|
35
|
+
}): {
|
|
36
|
+
ok: true;
|
|
37
|
+
id: string;
|
|
38
|
+
created: boolean;
|
|
39
|
+
itemCount: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Live refresh: rebuild every workboard node's spec from the current work-item
|
|
43
|
+
* list. Registered below as the canvasState work-item change listener. Node
|
|
44
|
+
* updates never touch work items, so this cannot recurse.
|
|
45
|
+
*/
|
|
46
|
+
export declare function refreshWorkboardNodes(): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The node-data contract for a materialized AX flow.
|
|
3
|
+
*
|
|
4
|
+
* `ax.flow.materialize` stamps every step node with `data.axStep` and the anchor
|
|
5
|
+
* (first) step with `data.axFlow`. Three consumers read those stamps and none of
|
|
6
|
+
* them owns the shape, so it lives here:
|
|
7
|
+
* - the server loop (`ax-flow-loop.ts`) advances the flow from work-item changes
|
|
8
|
+
* - the browser renders native Start/Done/Blocked + Run loop/Stop controls
|
|
9
|
+
* - snapshots/restore carry the stamps as ordinary node data
|
|
10
|
+
*
|
|
11
|
+
* Node data is `Record<string, unknown>` on both sides, so the readers below are
|
|
12
|
+
* the ONLY way to narrow it: they validate rather than cast, because a stamp can
|
|
13
|
+
* also arrive from a restored snapshot or a hand-edited node.
|
|
14
|
+
*/
|
|
15
|
+
/** Hard ceiling on loop passes, whatever a node's stamped `maxRuns` claims. */
|
|
16
|
+
export declare const AX_FLOW_LOOP_HARD_CAP = 20;
|
|
17
|
+
/** One step of a flow, as recorded on the anchor node. */
|
|
18
|
+
export interface AxFlowStepRef {
|
|
19
|
+
/** 1-based position in the flow. */
|
|
20
|
+
index: number;
|
|
21
|
+
nodeId: string;
|
|
22
|
+
workItemId: string;
|
|
23
|
+
title: string;
|
|
24
|
+
}
|
|
25
|
+
export interface AxFlowLoopState {
|
|
26
|
+
/** The loop only advances while this is true; Stop persists `false`. */
|
|
27
|
+
running: boolean;
|
|
28
|
+
/** Completed passes. `run >= maxRuns` ends the loop. */
|
|
29
|
+
run: number;
|
|
30
|
+
maxRuns: number;
|
|
31
|
+
}
|
|
32
|
+
/** `data.axStep` — stamped on EVERY step node, including the anchor. */
|
|
33
|
+
export interface AxFlowStepStamp {
|
|
34
|
+
flowId: string;
|
|
35
|
+
index: number;
|
|
36
|
+
total: number;
|
|
37
|
+
workItemId: string;
|
|
38
|
+
}
|
|
39
|
+
/** `data.axFlow` — stamped on the anchor (first) step node only. */
|
|
40
|
+
export interface AxFlowStamp {
|
|
41
|
+
flowId: string;
|
|
42
|
+
title: string;
|
|
43
|
+
steps: AxFlowStepRef[];
|
|
44
|
+
loop: AxFlowLoopState;
|
|
45
|
+
}
|
|
46
|
+
/** Clamp a stamped `maxRuns` into [1, AX_FLOW_LOOP_HARD_CAP]. */
|
|
47
|
+
export declare function clampAxFlowMaxRuns(value: unknown): number;
|
|
48
|
+
/** Read `data.axStep`, or null when the node carries no valid step stamp. */
|
|
49
|
+
export declare function readAxStep(data: Record<string, unknown>): AxFlowStepStamp | null;
|
|
50
|
+
/** Read `data.axFlow`, or null when the node is not a flow anchor. */
|
|
51
|
+
export declare function readAxFlow(data: Record<string, unknown>): AxFlowStamp | null;
|
|
@@ -16,3 +16,10 @@ export declare const AX_SURFACE_ACK_SOURCE = "pmx-canvas-ax-ack";
|
|
|
16
16
|
export declare const HTML_SURFACE_PUSH_SOURCE = "pmx-canvas-html-node";
|
|
17
17
|
/** Ext-app boot beacon: the iframe's scripts executed (WebKit watchdog liveness). */
|
|
18
18
|
export declare const EXT_APP_BOOT_BEACON_SOURCE = "pmx-canvas-ext-app-alive";
|
|
19
|
+
/**
|
|
20
|
+
* Parent→ext-app paint probe (Finding N): the parent asks the app document to
|
|
21
|
+
* answer with a double-rAF `paint-tick` beacon. A frame whose rendering
|
|
22
|
+
* pipeline is live answers within a frame or two; silence past the timeout is
|
|
23
|
+
* the paint-fail signal that drives the recovery ladder.
|
|
24
|
+
*/
|
|
25
|
+
export declare const EXT_APP_PAINT_PROBE_SOURCE = "pmx-canvas-ext-app-paint-probe";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-type minimum node size — the readability floor.
|
|
3
|
+
*
|
|
4
|
+
* Agents frequently create nodes with explicit frames far too small for their
|
|
5
|
+
* content (clipped markdown, charts squeezed behind inner scrollbars), so the
|
|
6
|
+
* server clamps undersized explicit sizes UP at creation and `validate` reports
|
|
7
|
+
* anything below the floor.
|
|
8
|
+
*
|
|
9
|
+
* This lives in `shared/` because BOTH sides must honor it. The 0.4.6 report's
|
|
10
|
+
* Finding AA: the server clamped a 200x100 markdown node up to 360x180, and
|
|
11
|
+
* then the browser's DOM auto-fit measured the (short) content and persisted
|
|
12
|
+
* 360x132 — undoing the guarantee on the most common path, a connected
|
|
13
|
+
* workbench. A floor only one side knows about is not a floor.
|
|
14
|
+
*
|
|
15
|
+
* `strictSize: true` is the escape hatch for a deliberately small fixed frame,
|
|
16
|
+
* and it exempts a node from both the creation clamp and the auto-fit floor.
|
|
17
|
+
* Types absent from the table are intentionally unclamped — `trace` is small by
|
|
18
|
+
* design, `group` sizes to its children.
|
|
19
|
+
*/
|
|
20
|
+
export declare const NODE_MIN_SIZES: Record<string, {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}>;
|
|
24
|
+
/** The readability floor for a node type, or undefined when it has none. */
|
|
25
|
+
export declare function nodeMinSize(type: string): {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
} | undefined;
|
|
29
|
+
/** Creation-time clamp: an explicit size below the floor is raised to it. */
|
|
30
|
+
export declare function clampCreateNodeSize(type: string, width: number, height: number, strictSize?: boolean): {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
};
|
package/docs/cli.md
CHANGED
|
@@ -17,7 +17,7 @@ pmx-canvas --server-url http://127.0.0.1:4750 status # same, by URL
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pmx-canvas # Start canvas, open browser
|
|
20
|
-
pmx-canvas --demo # Start with the
|
|
20
|
+
pmx-canvas --demo # Start with the showcase demo board
|
|
21
21
|
pmx-canvas --port=8080 # Custom port
|
|
22
22
|
pmx-canvas --no-open # Headless (for agents/CI)
|
|
23
23
|
pmx-canvas --theme=light # dark | light | high-contrast | midnight | sepia | arctic | ember | forest | volt
|
|
@@ -119,11 +119,28 @@ pmx-canvas watch --events context-pin,move-end
|
|
|
119
119
|
pmx-canvas watch --json --events context-pin --max-events 1
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
## Focus
|
|
122
|
+
## Focus and fit
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
pmx-canvas focus <node-id> # Pan viewport to a node
|
|
126
126
|
pmx-canvas focus <node-id> --no-pan # Select/raise without panning
|
|
127
|
+
pmx-canvas fit # Fit the viewport to every node
|
|
128
|
+
pmx-canvas fit <id-a> <id-b> # Fit to just these nodes
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`fit` sizes itself to the connected browser window, so you rarely need to pass
|
|
132
|
+
`--width` / `--height`. After creating output a human should see, focus a single
|
|
133
|
+
node or fit the new ids — auto-placement is board-relative, not camera-relative.
|
|
134
|
+
|
|
135
|
+
## Snapshots
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pmx-canvas snapshot save --name "before-refactor"
|
|
139
|
+
pmx-canvas snapshot list
|
|
140
|
+
pmx-canvas snapshot restore <snapshot-id>
|
|
141
|
+
pmx-canvas snapshot delete <snapshot-id>
|
|
142
|
+
pmx-canvas snapshot gc --keep 20 # Delete all but the newest N
|
|
143
|
+
pmx-canvas diff <snapshot-id> # Compare the canvas against a snapshot
|
|
127
144
|
```
|
|
128
145
|
|
|
129
146
|
## AX context
|
|
@@ -201,6 +218,18 @@ pmx-canvas ax policy get
|
|
|
201
218
|
pmx-canvas ax policy set --excluded-tools shell,write --mode concise
|
|
202
219
|
```
|
|
203
220
|
|
|
221
|
+
## Environment smoke check
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pmx-canvas smoke # one-command check of a running canvas
|
|
225
|
+
pmx-canvas smoke --port 4750 # target a specific daemon
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Verifies server health + workspace, CLI/server version skew, the MCP
|
|
229
|
+
initialize handshake, a temporary node create/search/remove round-trip (the
|
|
230
|
+
temp node is always removed, including on failure), and board validation.
|
|
231
|
+
Prints a JSON report and exits 1 if any check fails.
|
|
232
|
+
|
|
204
233
|
## Copilot adapter
|
|
205
234
|
|
|
206
235
|
Install the bundled GitHub Copilot extension adapter into a repo. The adapter
|
package/docs/environment.md
CHANGED
|
@@ -29,6 +29,7 @@ optional; defaults are listed.
|
|
|
29
29
|
| `AMP_ORB` | server | Set by Amp orb services. When present, the served canvas page tells the browser it is running in an orb, which skips the iframe embed probe, forces `srcdoc` surface rendering when embedded (the orb portal blocks `src`-URL child iframes and the probe is unreliable there), and defaults the event transport to polling instead of SSE (the portal proxy buffers streams; `?transport=sse` overrides for diagnosis). |
|
|
30
30
|
| `PORT` | server | Portal-assigned service port, honored ONLY when `AMP_ORB` is also set (Amp orbs) — after `--port`, `PMX_WEB_CANVAS_PORT`, and `PMX_CANVAS_PORT`, before the 4313 default. A stray `PORT` in normal shells never changes the port. |
|
|
31
31
|
| `PMX_CANVAS_DISABLE_BROWSER_OPEN` | server | `1` suppresses auto-opening the browser (used by tests/CI). |
|
|
32
|
+
| `PMX_CANVAS_AUTO_INTENT` | server | `0` turns off the auto-ghost: agent mutations that arrive without an explicit `canvas_intent` signal stop synthesizing a short ghost cursor. Any other value (or unset) leaves it on. |
|
|
32
33
|
| `PMX_CANVAS_DIST` | server | Explicit client bundle directory to serve instead of the packaged `dist/canvas`. |
|
|
33
34
|
| `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` | server | Startup timeout for the Bun.WebView automation session. |
|
|
34
35
|
|
package/docs/http-api.md
CHANGED
|
@@ -64,6 +64,23 @@ A node creation request must resolve a `type` — pass it in the body (`{ "type"
|
|
|
64
64
|
... }`) or as a `?type=` query param. An empty / type-less body returns `400`
|
|
65
65
|
rather than silently creating a markdown node.
|
|
66
66
|
|
|
67
|
+
### File node content and bytes
|
|
68
|
+
|
|
69
|
+
A `file` node reads its file server-side. Text is stored on the node as
|
|
70
|
+
`data.fileContent` (with `data.lineCount`); `data.byteSize` is always set.
|
|
71
|
+
Non-text files are never decoded into mojibake — they carry `data.binary: true`
|
|
72
|
+
with no `fileContent`, plus `data.mimeType` when the type is known
|
|
73
|
+
(`application/pdf` for PDFs). A text file larger than 2 MB is stored truncated
|
|
74
|
+
with `data.truncated: true`.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Raw bytes for a binary file node (PDF viewer, downloads)
|
|
78
|
+
curl http://localhost:4313/api/canvas/file-bytes?nodeId=node-abc123
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The byte route is workspace-scoped: a file node whose path resolves outside the
|
|
82
|
+
workspace root returns `403`, and an unknown node id returns `404`.
|
|
83
|
+
|
|
67
84
|
## Edges
|
|
68
85
|
|
|
69
86
|
```bash
|
|
@@ -306,6 +323,18 @@ curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?settledNodeId=no
|
|
|
306
323
|
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?vetoed=true"
|
|
307
324
|
```
|
|
308
325
|
|
|
326
|
+
## Live work board
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Materialize (or refresh) the one board node showing all AX work items by status.
|
|
330
|
+
curl -X POST http://localhost:4313/api/canvas/workboard \
|
|
331
|
+
-H 'Content-Type: application/json' -d '{}'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The board is a `json-render` node tagged `data.workboard: true`. Repeat calls
|
|
335
|
+
are idempotent (`created: false`) and the board refreshes itself whenever a
|
|
336
|
+
work item changes — no polling needed.
|
|
337
|
+
|
|
309
338
|
## Diagrams (Excalidraw preset)
|
|
310
339
|
|
|
311
340
|
```bash
|
package/docs/mcp.md
CHANGED
|
@@ -38,7 +38,7 @@ its `action` to the same operation the legacy tool used, so results are identica
|
|
|
38
38
|
| Composite | `action` values | Replaced (removed in v0.3.0) |
|
|
39
39
|
|-----------|-----------------|----------|
|
|
40
40
|
| `canvas_node` | `add` · `get` · `update` · `remove` | `canvas_add_node`, `canvas_get_node`, `canvas_update_node`, `canvas_remove_node`, `canvas_add_html_node` (`add` + `type:"html"`), `canvas_add_html_primitive` (`add` + `type:"html"`, `primitive:"<kind>"`), `canvas_refresh_webpage_node` (`update` + `refresh:true`) |
|
|
41
|
-
| `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` | `canvas_describe_schema`, `canvas_validate_spec`, `canvas_add_json_render_node`, `canvas_stream_json_render_node`, `canvas_add_graph_node` |
|
|
41
|
+
| `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` · `workboard` | `canvas_describe_schema`, `canvas_validate_spec`, `canvas_add_json_render_node`, `canvas_stream_json_render_node`, `canvas_add_graph_node` |
|
|
42
42
|
| `canvas_edge` | `add` · `remove` | `canvas_add_edge`, `canvas_remove_edge` |
|
|
43
43
|
| `canvas_group` | `create` · `add` · `ungroup` | `canvas_create_group`, `canvas_group_nodes`, `canvas_ungroup` |
|
|
44
44
|
| `canvas_history` | `undo` · `redo` | `canvas_undo`, `canvas_redo` |
|
|
@@ -227,6 +227,18 @@ interaction against the node's effective capabilities.
|
|
|
227
227
|
- **Commands:** `canvas_invoke_command` runs a registry command (`pmx.plan`,
|
|
228
228
|
`pmx.execute`, `pmx.promote-context`, `pmx.summarize`, `pmx.review`); unknown
|
|
229
229
|
names are rejected and a successful call records a `command` agent-event.
|
|
230
|
+
Commands *record* a signal for the agent — they do not execute anything
|
|
231
|
+
server-side.
|
|
232
|
+
- **`ax.flow.materialize`:** the one interaction that creates canvas nodes, and
|
|
233
|
+
it is deliberately narrow. The caller supplies text only — up to 12 steps with
|
|
234
|
+
a title (<= 120 chars) and optional detail — and the SERVER owns the resulting
|
|
235
|
+
shape: one `markdown` node per step, `flow` edges between them, a dashed
|
|
236
|
+
`references` loop edge when looping, and one work item per step linked to its
|
|
237
|
+
node (so the existing status-chip mirroring shows progress). The surface never
|
|
238
|
+
names a node type, geometry, or node data. Re-materializing replaces the
|
|
239
|
+
previous flow: the source node keeps a manifest of the ids it created, and
|
|
240
|
+
only nodes on that manifest that still carry the matching flow id are removed,
|
|
241
|
+
so a surface can never delete anything it did not create.
|
|
230
242
|
|
|
231
243
|
## Change notifications
|
|
232
244
|
|