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
|
@@ -175,6 +175,8 @@ export interface CanvasNodeState {
|
|
|
175
175
|
| 'ledger'
|
|
176
176
|
| 'trace'
|
|
177
177
|
| 'file'
|
|
178
|
+
| 'diff'
|
|
179
|
+
| 'mermaid'
|
|
178
180
|
| 'image'
|
|
179
181
|
| 'html'
|
|
180
182
|
| 'group';
|
|
@@ -368,6 +370,28 @@ class CanvasStateManager {
|
|
|
368
370
|
};
|
|
369
371
|
}
|
|
370
372
|
|
|
373
|
+
// ── Work-item change listener (single slot, for live workboard refresh) ──
|
|
374
|
+
private _workItemsChangedListener: (() => void) | null = null;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Register THE work-item change listener (single slot, last-write-wins).
|
|
378
|
+
* Fired after addWorkItem/updateWorkItem completes (including the node
|
|
379
|
+
* status mirror), so live views like the workboard can rebuild from the
|
|
380
|
+
* fresh work-item list.
|
|
381
|
+
*/
|
|
382
|
+
setWorkItemsChangedListener(listener: (() => void) | null): void {
|
|
383
|
+
this._workItemsChangedListener = listener;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private notifyWorkItemsChanged(): void {
|
|
387
|
+
if (!this._workItemsChangedListener) return;
|
|
388
|
+
try {
|
|
389
|
+
this._workItemsChangedListener();
|
|
390
|
+
} catch (error) {
|
|
391
|
+
logCanvasStateWarning('work-items-changed listener failed', error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
371
395
|
private notifyChange(type: CanvasChangeType): void {
|
|
372
396
|
// Iterate a snapshot: a listener (e.g. a blocking-wait via onChange) may dispose
|
|
373
397
|
// itself synchronously here, and splicing the live array mid-iteration would skip
|
|
@@ -1296,16 +1320,15 @@ class CanvasStateManager {
|
|
|
1296
1320
|
}
|
|
1297
1321
|
|
|
1298
1322
|
addNode(node: CanvasNodeState): void {
|
|
1299
|
-
// Context nodes
|
|
1300
|
-
//
|
|
1301
|
-
//
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const cloned = structuredClone(this.normalizeNode(seeded));
|
|
1323
|
+
// Docking is EXPLICIT (0.4.6). Context nodes used to auto-dock to a collapsed
|
|
1324
|
+
// right-hand pill when created without a dock position, which meant an agent
|
|
1325
|
+
// creating a context node — even at explicit coordinates — got a node that
|
|
1326
|
+
// existed in the API and passed validation but never rendered on the canvas,
|
|
1327
|
+
// and any edge to it terminated in empty space (0.4.6 orb feedback #1). The
|
|
1328
|
+
// seeded HUD widgets (status-main / context-main) pass `dockPosition` on
|
|
1329
|
+
// create, so the docked pills are unaffected; anything else reaches the
|
|
1330
|
+
// canvas where the caller put it. Dock deliberately via node.update.
|
|
1331
|
+
const cloned = structuredClone(this.normalizeNode(node));
|
|
1309
1332
|
this.nodes.set(node.id, cloned);
|
|
1310
1333
|
this.scheduleSave();
|
|
1311
1334
|
this.notifyChange('nodes');
|
|
@@ -1698,6 +1721,17 @@ class CanvasStateManager {
|
|
|
1698
1721
|
return this.ax.getAxState();
|
|
1699
1722
|
}
|
|
1700
1723
|
|
|
1724
|
+
/**
|
|
1725
|
+
* Replace the canvas-bound AX partition from a persisted blob, normalized against
|
|
1726
|
+
* the current node set — the same operation snapshot restore performs internally
|
|
1727
|
+
* (`applyPersistedState`), exposed for the demo seed. Both restore nodes whose
|
|
1728
|
+
* `data.axStep.workItemId` only resolves if the work items come back with their
|
|
1729
|
+
* original ids, so this preserves them verbatim rather than re-creating them.
|
|
1730
|
+
*/
|
|
1731
|
+
applyPersistedAxState(ax: unknown): void {
|
|
1732
|
+
this.ax.applyPersistedAx(ax);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1701
1735
|
getAxFocus(): PmxAxFocusState {
|
|
1702
1736
|
return this.ax.getAxFocus();
|
|
1703
1737
|
}
|
|
@@ -1727,6 +1761,7 @@ class CanvasStateManager {
|
|
|
1727
1761
|
): PmxAxWorkItem {
|
|
1728
1762
|
const item = this.ax.addWorkItem(input, options);
|
|
1729
1763
|
this.mirrorAxWorkStatusToNodes(item.nodeIds, [], item.status);
|
|
1764
|
+
this.notifyWorkItemsChanged();
|
|
1730
1765
|
return item;
|
|
1731
1766
|
}
|
|
1732
1767
|
|
|
@@ -1745,6 +1780,7 @@ class CanvasStateManager {
|
|
|
1745
1780
|
const item = this.ax.updateWorkItem(id, patch, options);
|
|
1746
1781
|
if (!item) return null;
|
|
1747
1782
|
this.mirrorAxWorkStatusToNodes(item.nodeIds, previousNodeIds, item.status);
|
|
1783
|
+
this.notifyWorkItemsChanged();
|
|
1748
1784
|
return item;
|
|
1749
1785
|
}
|
|
1750
1786
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { CanvasLayout, CanvasNodeState } from './canvas-state.js';
|
|
2
2
|
import { getCanvasNodeTitle } from './canvas-serialization.js';
|
|
3
|
+
// The readability floor is SHARED with the client: the server clamps it at
|
|
4
|
+
// creation and `validate` reports violations, but the browser's auto-fit must
|
|
5
|
+
// honor the same floor or it silently undoes the clamp (0.4.6 Finding AA).
|
|
6
|
+
import { nodeMinSize } from '../shared/node-sizes.js';
|
|
7
|
+
|
|
8
|
+
export { NODE_MIN_SIZES, clampCreateNodeSize, nodeMinSize } from '../shared/node-sizes.js';
|
|
3
9
|
|
|
4
10
|
export interface CanvasValidationPair {
|
|
5
11
|
aId: string;
|
|
@@ -15,12 +21,38 @@ export interface CanvasContainmentIssue {
|
|
|
15
21
|
childTitle: string | null;
|
|
16
22
|
}
|
|
17
23
|
|
|
24
|
+
export interface CanvasSizeWarning {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string | null;
|
|
27
|
+
type: CanvasNodeState['type'];
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
minWidth: number;
|
|
31
|
+
minHeight: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CanvasHiddenEdgeEndpoint {
|
|
35
|
+
edgeId: string;
|
|
36
|
+
nodeId: string;
|
|
37
|
+
nodeTitle: string | null;
|
|
38
|
+
dockPosition: 'left' | 'right';
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
export interface CanvasValidationResult {
|
|
19
42
|
ok: boolean;
|
|
20
43
|
collisions: CanvasValidationPair[];
|
|
21
44
|
containments: CanvasContainmentIssue[];
|
|
22
45
|
containmentViolations: CanvasContainmentIssue[];
|
|
23
46
|
missingEdgeEndpoints: Array<{ edgeId: string; from: string; to: string }>;
|
|
47
|
+
/**
|
|
48
|
+
* Edges whose endpoint node is DOCKED — it renders in the HUD column, not on
|
|
49
|
+
* the canvas, so the edge visually terminates in empty space even though both
|
|
50
|
+
* endpoint IDs resolve (0.4.6 orb feedback #1). Advisory: reported for
|
|
51
|
+
* diagnosis, but does NOT fail `ok` (see the note at the return site).
|
|
52
|
+
*/
|
|
53
|
+
hiddenEdgeEndpoints: CanvasHiddenEdgeEndpoint[];
|
|
54
|
+
/** Nodes below their type's readable minimum (advisory — does not fail `ok`). */
|
|
55
|
+
sizeWarnings: CanvasSizeWarning[];
|
|
24
56
|
summary: {
|
|
25
57
|
nodes: number;
|
|
26
58
|
edges: number;
|
|
@@ -28,6 +60,8 @@ export interface CanvasValidationResult {
|
|
|
28
60
|
containments: number;
|
|
29
61
|
containmentViolations: number;
|
|
30
62
|
missingEdgeEndpoints: number;
|
|
63
|
+
hiddenEdgeEndpoints: number;
|
|
64
|
+
sizeWarnings: number;
|
|
31
65
|
};
|
|
32
66
|
}
|
|
33
67
|
|
|
@@ -109,12 +143,64 @@ export function validateCanvasLayout(layout: CanvasLayout): CanvasValidationResu
|
|
|
109
143
|
.filter((edge) => !nodeIds.has(edge.from) || !nodeIds.has(edge.to))
|
|
110
144
|
.map((edge) => ({ edgeId: edge.id, from: edge.from, to: edge.to }));
|
|
111
145
|
|
|
146
|
+
// An edge to a DOCKED node cannot be drawn: docked nodes live in the HUD
|
|
147
|
+
// column, and the world-space edge layer skips them, so the edge trails off
|
|
148
|
+
// into empty canvas. Structural validation used to miss this entirely because
|
|
149
|
+
// both endpoint IDs resolve (0.4.6 orb feedback #1).
|
|
150
|
+
const dockedById = new Map(
|
|
151
|
+
layout.nodes.filter((node) => node.dockPosition != null).map((node) => [node.id, node] as const),
|
|
152
|
+
);
|
|
153
|
+
const hiddenEdgeEndpoints: CanvasHiddenEdgeEndpoint[] = layout.edges.flatMap((edge) =>
|
|
154
|
+
[edge.from, edge.to].flatMap((nodeId) => {
|
|
155
|
+
const docked = dockedById.get(nodeId);
|
|
156
|
+
if (!docked || docked.dockPosition == null) return [];
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
edgeId: edge.id,
|
|
160
|
+
nodeId,
|
|
161
|
+
nodeTitle: getCanvasNodeTitle(docked),
|
|
162
|
+
dockPosition: docked.dockPosition,
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Advisory: nodes below their type's readable floor (clipped/unreadable
|
|
169
|
+
// content — 0.4.5 report follow-up). Creation clamps these, but resized or
|
|
170
|
+
// strictSize-created nodes can still be undersized; surface them so an
|
|
171
|
+
// agent's validate pass sees the problem. Collapsed/docked nodes render as
|
|
172
|
+
// bars, and strictSize is the deliberate opt-out — all skipped.
|
|
173
|
+
const sizeWarnings: CanvasSizeWarning[] = layout.nodes
|
|
174
|
+
.filter((node) => !node.collapsed && node.dockPosition == null && node.data?.strictSize !== true)
|
|
175
|
+
.flatMap((node) => {
|
|
176
|
+
const min = nodeMinSize(node.type);
|
|
177
|
+
if (!min || (node.size.width >= min.width && node.size.height >= min.height)) return [];
|
|
178
|
+
return [
|
|
179
|
+
{
|
|
180
|
+
id: node.id,
|
|
181
|
+
title: getCanvasNodeTitle(node),
|
|
182
|
+
type: node.type,
|
|
183
|
+
width: node.size.width,
|
|
184
|
+
height: node.size.height,
|
|
185
|
+
minWidth: min.width,
|
|
186
|
+
minHeight: min.height,
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
});
|
|
190
|
+
|
|
112
191
|
return {
|
|
192
|
+
// `hiddenEdgeEndpoints` is ADVISORY (like sizeWarnings): a docked endpoint
|
|
193
|
+
// is a rendering observation, not a structural defect, and folding it into
|
|
194
|
+
// `ok` would flip previously-healthy boards to ok:false with no user change
|
|
195
|
+
// — `ok` is a documented response field of a frozen surface, so that is a
|
|
196
|
+
// contract break, not a patch. It is still reported for diagnosis.
|
|
113
197
|
ok: collisions.length === 0 && containmentViolations.length === 0 && missingEdgeEndpoints.length === 0,
|
|
114
198
|
collisions,
|
|
115
199
|
containments,
|
|
116
200
|
containmentViolations,
|
|
117
201
|
missingEdgeEndpoints,
|
|
202
|
+
hiddenEdgeEndpoints,
|
|
203
|
+
sizeWarnings,
|
|
118
204
|
summary: {
|
|
119
205
|
nodes: layout.nodes.length,
|
|
120
206
|
edges: layout.edges.length,
|
|
@@ -122,6 +208,8 @@ export function validateCanvasLayout(layout: CanvasLayout): CanvasValidationResu
|
|
|
122
208
|
containments: containments.length,
|
|
123
209
|
containmentViolations: containmentViolations.length,
|
|
124
210
|
missingEdgeEndpoints: missingEdgeEndpoints.length,
|
|
211
|
+
hiddenEdgeEndpoints: hiddenEdgeEndpoints.length,
|
|
212
|
+
sizeWarnings: sizeWarnings.length,
|
|
125
213
|
},
|
|
126
214
|
};
|
|
127
215
|
}
|