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.
Files changed (109) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/Readme.md +76 -36
  3. package/dist/canvas/global.css +169 -3
  4. package/dist/canvas/index.js +112 -87
  5. package/dist/canvas/mermaid-entry.js +3563 -0
  6. package/dist/json-render/index.css +1 -1
  7. package/dist/types/cli/agent.d.ts +1 -0
  8. package/dist/types/cli/commands/smoke.d.ts +1 -0
  9. package/dist/types/cli/shared.d.ts +13 -1
  10. package/dist/types/client/canvas/CanvasNode.d.ts +11 -0
  11. package/dist/types/client/canvas/EdgeLayer.d.ts +8 -0
  12. package/dist/types/client/mermaid-entry.d.ts +11 -0
  13. package/dist/types/client/nodes/AxStepControls.d.ts +4 -0
  14. package/dist/types/client/nodes/DiffNode.d.ts +20 -0
  15. package/dist/types/client/nodes/ExtAppFrame.d.ts +0 -2
  16. package/dist/types/client/nodes/FileNode.d.ts +14 -0
  17. package/dist/types/client/nodes/MermaidNode.d.ts +12 -0
  18. package/dist/types/client/state/canvas-store.d.ts +10 -0
  19. package/dist/types/client/state/intent-bridge.d.ts +9 -0
  20. package/dist/types/client/types.d.ts +2 -2
  21. package/dist/types/server/ax-flow-loop.d.ts +5 -0
  22. package/dist/types/server/ax-interaction.d.ts +21 -2
  23. package/dist/types/server/canvas-operations.d.ts +5 -0
  24. package/dist/types/server/canvas-provenance.d.ts +1 -1
  25. package/dist/types/server/canvas-state.d.ts +18 -1
  26. package/dist/types/server/canvas-validation.d.ts +28 -1
  27. package/dist/types/server/file-content.d.ts +46 -0
  28. package/dist/types/server/html-primitives.d.ts +13 -1
  29. package/dist/types/server/html-surface.d.ts +8 -0
  30. package/dist/types/server/index.d.ts +5 -0
  31. package/dist/types/server/operations/ops/ax-work.d.ts +1 -0
  32. package/dist/types/server/operations/ops/nodes.d.ts +1 -1
  33. package/dist/types/server/server.d.ts +1 -0
  34. package/dist/types/server/web-artifacts.d.ts +14 -0
  35. package/dist/types/server/workboard.d.ts +46 -0
  36. package/dist/types/shared/ax-flow.d.ts +51 -0
  37. package/dist/types/shared/ax-surface-protocol.d.ts +7 -0
  38. package/dist/types/shared/node-sizes.d.ts +33 -0
  39. package/docs/cli.md +31 -2
  40. package/docs/environment.md +1 -0
  41. package/docs/http-api.md +29 -0
  42. package/docs/mcp.md +13 -1
  43. package/docs/node-types.md +47 -1
  44. package/docs/screenshot.png +0 -0
  45. package/package.json +5 -4
  46. package/skills/pmx-canvas/SKILL.md +123 -25
  47. package/skills/pmx-canvas/evals/evals.json +54 -3
  48. package/skills/pmx-canvas/references/full-reference.md +11 -6
  49. package/skills/pmx-canvas-testing/SKILL.md +3 -0
  50. package/src/cli/agent.ts +2 -0
  51. package/src/cli/commands/ax.ts +28 -3
  52. package/src/cli/commands/history.ts +1 -1
  53. package/src/cli/commands/nodes.ts +2 -2
  54. package/src/cli/commands/query.ts +1 -1
  55. package/src/cli/commands/smoke.ts +212 -0
  56. package/src/cli/commands/view.ts +1 -1
  57. package/src/cli/index.ts +7 -0
  58. package/src/cli/shared.ts +35 -5
  59. package/src/client/App.tsx +28 -15
  60. package/src/client/canvas/CanvasNode.tsx +54 -5
  61. package/src/client/canvas/CanvasViewport.tsx +6 -0
  62. package/src/client/canvas/EdgeLayer.tsx +44 -24
  63. package/src/client/canvas/ExpandedNodeOverlay.tsx +21 -0
  64. package/src/client/canvas/Minimap.tsx +2 -0
  65. package/src/client/canvas/auto-fit.ts +12 -1
  66. package/src/client/canvas/use-pan-zoom.ts +16 -1
  67. package/src/client/icons.tsx +4 -0
  68. package/src/client/mermaid-entry.ts +76 -0
  69. package/src/client/nodes/AxStepControls.tsx +226 -0
  70. package/src/client/nodes/DiffNode.tsx +114 -0
  71. package/src/client/nodes/ExtAppFrame.tsx +259 -84
  72. package/src/client/nodes/FileNode.tsx +249 -51
  73. package/src/client/nodes/MermaidNode.tsx +81 -0
  74. package/src/client/state/canvas-store.ts +19 -0
  75. package/src/client/state/intent-bridge.ts +32 -0
  76. package/src/client/state/node-factory.ts +2 -0
  77. package/src/client/state/sse-bridge.ts +32 -1
  78. package/src/client/theme/global.css +169 -3
  79. package/src/client/types.ts +6 -0
  80. package/src/mcp/server.ts +19 -1
  81. package/src/server/ax-flow-loop.ts +114 -0
  82. package/src/server/ax-interaction.ts +305 -4
  83. package/src/server/canvas-operations.ts +83 -26
  84. package/src/server/canvas-provenance.ts +2 -0
  85. package/src/server/canvas-schema.ts +33 -0
  86. package/src/server/canvas-state.ts +46 -10
  87. package/src/server/canvas-validation.ts +88 -0
  88. package/src/server/demo-state.json +4605 -523
  89. package/src/server/demo.ts +24 -0
  90. package/src/server/file-content.ts +152 -0
  91. package/src/server/file-watcher.ts +7 -10
  92. package/src/server/html-primitives.ts +636 -0
  93. package/src/server/html-surface.ts +11 -0
  94. package/src/server/index.ts +25 -6
  95. package/src/server/operations/composites.ts +3 -2
  96. package/src/server/operations/ops/ax-work.ts +5 -0
  97. package/src/server/operations/ops/batch.ts +8 -0
  98. package/src/server/operations/ops/json-render.ts +44 -0
  99. package/src/server/operations/ops/nodes.ts +24 -2
  100. package/src/server/operations/ops/viewport.ts +15 -2
  101. package/src/server/server.ts +154 -6
  102. package/src/server/web-artifacts.ts +109 -0
  103. package/src/server/workboard.ts +159 -0
  104. package/src/shared/ax-flow.ts +102 -0
  105. package/src/shared/ax-surface-protocol.ts +8 -0
  106. package/src/shared/node-sizes.ts +52 -0
  107. package/src/shared/surface.ts +2 -0
  108. package/dist/types/server/chart-template.d.ts +0 -29
  109. 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 default to a right-docked, collapsed pill when created without
1300
- // an explicit dock position. CREATE-time default only — once placed, updates
1301
- // (including undock → dockPosition null) are respected (see normalizeNode).
1302
- // Skip during suppressed replay (undo/redo re-add) so a deliberately-undocked
1303
- // context node is restored verbatim instead of being snapped back to the dock.
1304
- const seeded =
1305
- node.type === 'context' && node.dockPosition == null && this._suppressRecordingDepth === 0
1306
- ? { ...node, dockPosition: 'right' as const, collapsed: true }
1307
- : node;
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
  }