pmx-canvas 0.6.4 → 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.
Files changed (204) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +67 -3
  2. package/CHANGELOG.md +73 -0
  3. package/Readme.md +35 -5
  4. package/dist/canvas/global.css +498 -28
  5. package/dist/canvas/index.js +110 -95
  6. package/dist/canvas/mermaid-entry.js +258 -258
  7. package/dist/types/cli/agent.d.ts +4 -0
  8. package/dist/types/cli/commands/backup.d.ts +1 -0
  9. package/dist/types/cli/commands/boards.d.ts +1 -0
  10. package/dist/types/cli/commands/export.d.ts +1 -0
  11. package/dist/types/cli/commands/record.d.ts +12 -0
  12. package/dist/types/client/canvas/BoardCopyDialog.d.ts +5 -0
  13. package/dist/types/client/canvas/BoardFolderDialog.d.ts +7 -0
  14. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -0
  15. package/dist/types/client/canvas/BoardSwitcher.d.ts +8 -0
  16. package/dist/types/client/canvas/ExportDialog.d.ts +7 -0
  17. package/dist/types/client/canvas/HomeView.d.ts +9 -0
  18. package/dist/types/client/canvas/Presentation.d.ts +13 -0
  19. package/dist/types/client/canvas/use-node-drag.d.ts +2 -1
  20. package/dist/types/client/nodes/AttachmentNode.d.ts +6 -0
  21. package/dist/types/client/nodes/BoardNode.d.ts +5 -0
  22. package/dist/types/client/nodes/McpAppNode.d.ts +24 -1
  23. package/dist/types/client/state/boards-store.d.ts +79 -0
  24. package/dist/types/client/state/canvas-store.d.ts +2 -0
  25. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  26. package/dist/types/client/state/presentation.d.ts +3 -0
  27. package/dist/types/client/state/session-store.d.ts +7 -0
  28. package/dist/types/client/types.d.ts +2 -2
  29. package/dist/types/json-render/catalog.d.ts +3 -3
  30. package/dist/types/json-render/charts/definitions.d.ts +1 -1
  31. package/dist/types/json-render/server.d.ts +8 -0
  32. package/dist/types/mcp/canvas-access.d.ts +10 -3
  33. package/dist/types/mcp/context-uri-template.d.ts +6 -0
  34. package/dist/types/server/agent-presence.d.ts +1 -1
  35. package/dist/types/server/attribution.d.ts +11 -0
  36. package/dist/types/server/ax-context.d.ts +3 -1
  37. package/dist/types/server/ax-interaction.d.ts +2 -2
  38. package/dist/types/server/ax-state.d.ts +13 -3
  39. package/dist/types/server/backup.d.ts +36 -0
  40. package/dist/types/server/board-export.d.ts +45 -0
  41. package/dist/types/server/canvas-db.d.ts +65 -9
  42. package/dist/types/server/canvas-operations.d.ts +45 -0
  43. package/dist/types/server/canvas-provenance.d.ts +1 -1
  44. package/dist/types/server/canvas-schema.d.ts +1 -0
  45. package/dist/types/server/canvas-state.d.ts +146 -3
  46. package/dist/types/server/canvas-validation.d.ts +3 -0
  47. package/dist/types/server/context-brief.d.ts +111 -0
  48. package/dist/types/server/context-reads.d.ts +55 -0
  49. package/dist/types/server/document-import.d.ts +66 -0
  50. package/dist/types/server/html-surface.d.ts +1 -1
  51. package/dist/types/server/index.d.ts +107 -4
  52. package/dist/types/server/mutation-history.d.ts +1 -0
  53. package/dist/types/server/operations/invoker.d.ts +2 -0
  54. package/dist/types/server/operations/mcp.d.ts +7 -1
  55. package/dist/types/server/operations/ops/app.d.ts +2 -0
  56. package/dist/types/server/operations/ops/ax-shared.d.ts +2 -2
  57. package/dist/types/server/operations/ops/backup.d.ts +2 -0
  58. package/dist/types/server/operations/ops/boards.d.ts +10 -0
  59. package/dist/types/server/operations/ops/export.d.ts +2 -0
  60. package/dist/types/server/operations/ops/imports.d.ts +2 -0
  61. package/dist/types/server/operations/ops/json-render.d.ts +2 -0
  62. package/dist/types/server/operations/ops/nodes.d.ts +4 -2
  63. package/dist/types/server/operations/ops/query.d.ts +2 -0
  64. package/dist/types/server/operations/registry.d.ts +9 -1
  65. package/dist/types/server/operations/types.d.ts +1 -1
  66. package/dist/types/server/server.d.ts +1 -0
  67. package/dist/types/server/trace-manager.d.ts +7 -0
  68. package/dist/types/server/web-artifacts.d.ts +3 -0
  69. package/dist/types/server/webpage-node.d.ts +22 -1
  70. package/dist/types/server/workbench-auth.d.ts +3 -0
  71. package/dist/types/shared/boards.d.ts +6 -0
  72. package/dist/types/shared/tour.d.ts +72 -0
  73. package/docs/RELEASE.md +5 -0
  74. package/docs/cli.md +157 -0
  75. package/docs/http-api.md +277 -6
  76. package/docs/mcp.md +96 -7
  77. package/docs/node-types.md +106 -0
  78. package/docs/plans/plan-010-attributed-ask-answers.md +39 -0
  79. package/docs/plans/plan-011-context-read-instrumentation.md +54 -0
  80. package/docs/plans/plan-012-many-boards-stage-1.md +98 -0
  81. package/docs/plans/plan-013-static-board-export.md +47 -0
  82. package/docs/plans/plan-014-connected-board-memory.md +140 -0
  83. package/docs/product-vision-2026-09.md +118 -18
  84. package/docs/sdk.md +50 -0
  85. package/package.json +1 -1
  86. package/skills/pmx-canvas/SKILL.md +62 -5
  87. package/skills/pmx-canvas/references/full-reference.md +44 -8
  88. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +3 -0
  89. package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
  90. package/skills/pmx-canvas-orchestration/SKILL.md +5 -0
  91. package/skills/pmx-canvas-testing/SKILL.md +4 -0
  92. package/src/cli/agent.ts +9 -0
  93. package/src/cli/commands/ax.ts +13 -0
  94. package/src/cli/commands/backup.ts +66 -0
  95. package/src/cli/commands/boards.ts +71 -0
  96. package/src/cli/commands/export.ts +30 -0
  97. package/src/cli/commands/nodes.ts +14 -1
  98. package/src/cli/commands/pins.ts +6 -2
  99. package/src/cli/commands/pump.ts +3 -0
  100. package/src/cli/commands/query.ts +33 -7
  101. package/src/cli/commands/record.ts +218 -0
  102. package/src/cli/commands/view.ts +7 -5
  103. package/src/cli/index.ts +21 -9
  104. package/src/client/App.tsx +28 -4
  105. package/src/client/canvas/AgentPresenceLayer.tsx +1 -1
  106. package/src/client/canvas/BoardCopyDialog.tsx +153 -0
  107. package/src/client/canvas/BoardFolderDialog.tsx +120 -0
  108. package/src/client/canvas/BoardLinkDialog.tsx +93 -0
  109. package/src/client/canvas/BoardSwitcher.tsx +149 -0
  110. package/src/client/canvas/CanvasNode.tsx +8 -0
  111. package/src/client/canvas/CanvasViewport.tsx +3 -0
  112. package/src/client/canvas/CommandPalette.tsx +107 -4
  113. package/src/client/canvas/ContextMenu.tsx +4 -0
  114. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -0
  115. package/src/client/canvas/ExportDialog.tsx +197 -0
  116. package/src/client/canvas/HomeView.tsx +277 -0
  117. package/src/client/canvas/IntentLayer.tsx +9 -2
  118. package/src/client/canvas/Presentation.tsx +108 -0
  119. package/src/client/canvas/SessionPanel.tsx +28 -1
  120. package/src/client/canvas/ToolRail.tsx +38 -3
  121. package/src/client/canvas/TopBar.tsx +49 -7
  122. package/src/client/canvas/import-files.ts +19 -1
  123. package/src/client/canvas/kind-colors.ts +1 -0
  124. package/src/client/canvas/use-node-drag.ts +8 -3
  125. package/src/client/icons.tsx +2 -0
  126. package/src/client/mermaid-entry.ts +39 -0
  127. package/src/client/nodes/AttachmentNode.tsx +148 -0
  128. package/src/client/nodes/BoardNode.tsx +35 -0
  129. package/src/client/nodes/FileNode.tsx +6 -0
  130. package/src/client/nodes/HtmlNode.tsx +12 -13
  131. package/src/client/nodes/MarkdownNode.tsx +47 -19
  132. package/src/client/nodes/McpAppNode.tsx +105 -13
  133. package/src/client/nodes/MermaidNode.tsx +3 -2
  134. package/src/client/nodes/ax-node-actions.ts +2 -5
  135. package/src/client/nodes/use-ax-surface-bridge.ts +1 -1
  136. package/src/client/state/boards-store.ts +167 -0
  137. package/src/client/state/canvas-store.ts +22 -0
  138. package/src/client/state/intent-bridge.ts +30 -2
  139. package/src/client/state/intent-store.ts +5 -1
  140. package/src/client/state/node-factory.ts +1 -0
  141. package/src/client/state/presentation.ts +7 -0
  142. package/src/client/state/session-store.ts +8 -0
  143. package/src/client/state/sse-bridge.ts +52 -5
  144. package/src/client/theme/global.css +498 -28
  145. package/src/client/types.ts +2 -0
  146. package/src/json-render/server.ts +45 -9
  147. package/src/mcp/canvas-access.ts +59 -22
  148. package/src/mcp/context-uri-template.ts +28 -0
  149. package/src/mcp/server.ts +122 -15
  150. package/src/server/agent-context.ts +116 -29
  151. package/src/server/agent-presence.ts +15 -9
  152. package/src/server/attribution.ts +29 -0
  153. package/src/server/ax-context.ts +4 -2
  154. package/src/server/ax-interaction.ts +1 -0
  155. package/src/server/ax-state-manager.ts +72 -3
  156. package/src/server/ax-state.ts +41 -0
  157. package/src/server/backup.ts +178 -0
  158. package/src/server/board-export.ts +686 -0
  159. package/src/server/canvas-db.ts +612 -105
  160. package/src/server/canvas-operations.ts +183 -31
  161. package/src/server/canvas-provenance.ts +1 -0
  162. package/src/server/canvas-schema.ts +22 -0
  163. package/src/server/canvas-state.ts +812 -48
  164. package/src/server/canvas-validation.ts +9 -1
  165. package/src/server/context-brief.ts +372 -0
  166. package/src/server/context-reads.ts +238 -0
  167. package/src/server/demo-state.json +670 -9
  168. package/src/server/document-import.ts +223 -0
  169. package/src/server/html-primitives.ts +199 -91
  170. package/src/server/html-surface.ts +8 -3
  171. package/src/server/index.ts +243 -5
  172. package/src/server/mutation-history.ts +10 -13
  173. package/src/server/operations/composites.ts +29 -5
  174. package/src/server/operations/http.ts +9 -3
  175. package/src/server/operations/index.ts +8 -0
  176. package/src/server/operations/invoker.ts +14 -3
  177. package/src/server/operations/mcp.ts +50 -11
  178. package/src/server/operations/ops/app.ts +15 -0
  179. package/src/server/operations/ops/ax-read.ts +10 -6
  180. package/src/server/operations/ops/ax-timeline.ts +75 -0
  181. package/src/server/operations/ops/backup.ts +134 -0
  182. package/src/server/operations/ops/batch.ts +20 -4
  183. package/src/server/operations/ops/boards.ts +351 -0
  184. package/src/server/operations/ops/export.ts +74 -0
  185. package/src/server/operations/ops/ext-app.ts +4 -0
  186. package/src/server/operations/ops/groups.ts +14 -7
  187. package/src/server/operations/ops/imports.ts +220 -0
  188. package/src/server/operations/ops/json-render.ts +37 -5
  189. package/src/server/operations/ops/nodes.ts +99 -21
  190. package/src/server/operations/ops/query.ts +206 -9
  191. package/src/server/operations/ops/viewport.ts +32 -0
  192. package/src/server/operations/registry.ts +129 -31
  193. package/src/server/operations/types.ts +1 -1
  194. package/src/server/scope-fence.ts +1 -1
  195. package/src/server/server.ts +155 -8
  196. package/src/server/spatial-analysis.ts +9 -17
  197. package/src/server/trace-manager.ts +19 -0
  198. package/src/server/web-artifacts.ts +11 -3
  199. package/src/server/webpage-node.ts +173 -13
  200. package/src/server/workbench-auth.ts +11 -0
  201. package/src/server/workboard.ts +3 -3
  202. package/src/shared/boards.ts +18 -0
  203. package/src/shared/node-sizes.ts +1 -0
  204. package/src/shared/tour.ts +87 -0
@@ -5,12 +5,14 @@
5
5
  * WAL-mode persistence. Replaces the previous JSON file-based approach.
6
6
  */
7
7
  import { Database } from 'bun:sqlite';
8
+ import { type Tour } from '../shared/tour.js';
8
9
  import { type CanvasThemeName } from '../shared/themes.js';
9
10
  import type { CanvasAnnotation, CanvasEdge, CanvasNodeState, CanvasSnapshot, CanvasSnapshotListOptions, ViewportState } from './canvas-state.js';
10
11
  import { type PmxAxState, type PmxAxEvent, type PmxAxEvidence, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
11
12
  export type CanvasTheme = CanvasThemeName;
12
13
  export declare function normalizeCanvasTheme(value: unknown, fallback?: CanvasTheme): CanvasTheme;
13
14
  export interface PersistedCanvasState {
15
+ tour?: Tour;
14
16
  version: number;
15
17
  theme?: CanvasTheme;
16
18
  viewport: ViewportState;
@@ -19,8 +21,27 @@ export interface PersistedCanvasState {
19
21
  annotations?: CanvasAnnotation[];
20
22
  contextPins: string[];
21
23
  ax?: PmxAxState;
24
+ revisionState?: PersistedRevisionState;
22
25
  }
23
- export declare function openCanvasDb(dbPath: string): Database;
26
+ export interface PersistedRevisionState {
27
+ revision: number;
28
+ floor: number;
29
+ tombstones: Array<{
30
+ nodeId: string;
31
+ revision: number;
32
+ deletedBy: import('./attribution.js').ActorAttribution;
33
+ }>;
34
+ }
35
+ /** Durable, delivery-based context cursor. This is separate from the bounded diagnostic read log. */
36
+ export declare function readContextBriefCursor(db: Database, boardId: string, consumer: string): number | null;
37
+ /** Monotonic update prevents a stale concurrent response from regressing a consumer. */
38
+ export declare function advanceContextBriefCursor(db: Database, boardId: string, consumer: string, revision: number): void;
39
+ export interface OpenCanvasDbOptions {
40
+ /** Name for the board a pre-boards (0.6.x) database migrates into. */
41
+ migratedBoardName?: string;
42
+ }
43
+ export declare function openCanvasDb(dbPath: string, options?: OpenCanvasDbOptions): Database;
44
+ export declare function createBoardId(): string;
24
45
  export declare function checkpointCanvasDb(db: Database): void;
25
46
  /**
26
47
  * Delete blob rows nothing references any more. Blobs are content-addressed and
@@ -37,17 +58,52 @@ export declare function checkpointCanvasDb(db: Database): void;
37
58
  */
38
59
  export declare function gcBlobsInDB(db: Database): number;
39
60
  export declare function finalizeCanvasDbForClose(db: Database): void;
40
- export declare function saveStateToDB(db: Database, state: PersistedCanvasState): void;
41
- /** Check if the DB has been populated with canvas state at least once. */
42
- export declare function isDbPopulated(db: Database): boolean;
43
- export declare function loadStateFromDB(db: Database): PersistedCanvasState | null;
44
- export declare function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state: PersistedCanvasState): void;
45
- export declare function loadSnapshotFromDB(db: Database, idOrName: string): {
61
+ /** The theme is workspace-wide: it is saved even while no board is open. */
62
+ export declare function saveThemeToDB(db: Database, theme: CanvasTheme | undefined): void;
63
+ export declare function readThemeFromDB(db: Database): CanvasTheme | undefined;
64
+ /** Saves one board's state; every other board's rows are untouched. */
65
+ export declare function saveStateToDB(db: Database, boardId: string, state: PersistedCanvasState): void;
66
+ /** Loads one board (the open board when `boardId` is omitted); null when there is no such board. */
67
+ export declare function loadStateFromDB(db: Database, boardId?: string): PersistedCanvasState | null;
68
+ export interface CanvasBoard {
69
+ id: string;
70
+ name: string;
71
+ /** A human-chosen shelf on Home ("Planning", "Research"); null is uncategorized. */
72
+ category: string | null;
73
+ createdAt: string;
74
+ lastOpenedAt: string | null;
75
+ nodeCount: number;
76
+ /** Markdown node used as this board's introduction. */
77
+ readmeNodeId: string | null;
78
+ }
79
+ /** Most recently opened first, then never-opened boards, newest first. */
80
+ export declare function listBoardsFromDB(db: Database): CanvasBoard[];
81
+ export declare function getBoardFromDB(db: Database, id: string): CanvasBoard | null;
82
+ export declare function createBoardInDB(db: Database, name: string, category?: string | null): CanvasBoard;
83
+ /** Rename and/or re-shelve a board; `category: null` removes it from its category. */
84
+ export declare function updateBoardInDB(db: Database, id: string, patch: {
85
+ name?: string;
86
+ category?: string | null;
87
+ readmeNodeId?: string | null;
88
+ }): boolean;
89
+ /** Create and populate an inactive board as one SQLite transaction. */
90
+ export declare function createBoardWithStateInDB(db: Database, name: string, category: string | null, state: PersistedCanvasState, readmeNodeId: string | null): CanvasBoard;
91
+ /** Deletes a board with its rows and its snapshots. */
92
+ export declare function deleteBoardFromDB(db: Database, id: string): boolean;
93
+ export declare function readMetaFromDB(db: Database, key: string): string | null;
94
+ /** Write a workspace-wide meta value; null removes it. */
95
+ export declare function writeMetaToDB(db: Database, key: string, value: string | null): void;
96
+ /** The board that was open last, if it still exists. */
97
+ export declare function getActiveBoardIdFromDB(db: Database): string | null;
98
+ export declare function setActiveBoardIdInDB(db: Database, id: string | null): void;
99
+ export declare function saveSnapshotToDB(db: Database, boardId: string, snapshot: CanvasSnapshot, state: PersistedCanvasState): void;
100
+ /** By id or name (most recent match) on `boardId`. */
101
+ export declare function loadSnapshotFromDB(db: Database, idOrName: string, boardId: string | null): {
46
102
  snapshot: CanvasSnapshot;
47
103
  state: PersistedCanvasState;
48
104
  } | null;
49
- export declare function listSnapshotsFromDB(db: Database, options?: CanvasSnapshotListOptions): CanvasSnapshot[];
50
- export declare function renameSnapshotInDB(db: Database, id: string, name: string): boolean;
105
+ export declare function listSnapshotsFromDB(db: Database, boardId: string, options?: CanvasSnapshotListOptions): CanvasSnapshot[];
106
+ export declare function renameSnapshotInDB(db: Database, boardId: string, id: string, name: string): boolean;
51
107
  export declare function deleteSnapshotFromDB(db: Database, id: string): boolean;
52
108
  export declare function writeBlobToDB(db: Database, sha256: string, jsonValue: string): number;
53
109
  export declare function readBlobFromDB(db: Database, sha256: string): string | null;
@@ -73,6 +73,21 @@ export declare const LEDGER_NODE_DEFAULT_SIZE: {
73
73
  width: number;
74
74
  height: number;
75
75
  };
76
+ export interface CanvasSizeAdjustment {
77
+ requested: {
78
+ width: number | null;
79
+ height: number | null;
80
+ };
81
+ applied: {
82
+ width: number;
83
+ height: number;
84
+ };
85
+ reason: 'defaulted' | 'clamped-to-minimum' | 'defaulted-and-clamped-to-minimum' | 'fit-to-children';
86
+ }
87
+ export declare function describeCreateSize(requestedWidth: number | undefined, requestedHeight: number | undefined, resolvedWidth: number, resolvedHeight: number, applied: {
88
+ width: number;
89
+ height: number;
90
+ }): CanvasSizeAdjustment | undefined;
76
91
  interface CanvasCreateGroupInput {
77
92
  title?: string;
78
93
  childIds?: string[];
@@ -146,6 +161,7 @@ export declare function addCanvasNode(input: CanvasAddNodeInput): {
146
161
  id: string;
147
162
  node: CanvasNodeState;
148
163
  needsCodeGraphRecompute: boolean;
164
+ sizeAdjustment?: CanvasSizeAdjustment;
149
165
  };
150
166
  export declare function resolveCanvasNode(nodeRef: CanvasNodeLookupInput): {
151
167
  ok: true;
@@ -192,6 +208,29 @@ export declare function saveCanvasSnapshot(name: string): CanvasSnapshot | null;
192
208
  export declare function restoreCanvasSnapshot(idOrName: string): Promise<{
193
209
  ok: boolean;
194
210
  }>;
211
+ /**
212
+ * Open a board (or Home for null): the state layer saves the current board and
213
+ * loads the next, and everything else that holds the old board in memory is
214
+ * reset — undo closures, pending intents, file watchers, app sessions and the
215
+ * code graph (the trace chain restarts itself when its last node is gone).
216
+ * Agent and human presence are kept: they belong to the people and agents,
217
+ * not to a board.
218
+ */
219
+ export declare function openCanvasBoard(id: string | null): {
220
+ ok: boolean;
221
+ };
222
+ /**
223
+ * Restore the whole library from a backup file (the current file is kept as
224
+ * `<db>.before-restore`); the restored library opens on its last open board.
225
+ */
226
+ export declare function restoreCanvasLibrary(file: string): void;
227
+ /**
228
+ * A write that arrives while no board is open (Home) lands on the most recently
229
+ * opened board, or on a new board when the workspace has none — nothing on
230
+ * Home is displaced, because Home holds nothing. Intents and undo history are
231
+ * kept: whatever was signalled on Home was aimed at the board this opens.
232
+ */
233
+ export declare function openBoardForWrite(): void;
195
234
  export declare function deleteCanvasSnapshot(id: string): {
196
235
  ok: boolean;
197
236
  };
@@ -213,11 +252,13 @@ export declare function removeCanvasEdge(id: string): {
213
252
  export declare function createCanvasGroup(input: CanvasCreateGroupInput): {
214
253
  id: string;
215
254
  node: CanvasNodeState;
255
+ sizeAdjustment?: CanvasSizeAdjustment;
216
256
  };
217
257
  export declare function groupCanvasNodes(groupId: string, childIds: string[], options?: {
218
258
  childLayout?: CanvasArrangeMode;
219
259
  }): {
220
260
  ok: boolean;
261
+ sizeAdjustment?: CanvasSizeAdjustment;
221
262
  };
222
263
  export declare function ungroupCanvasNodes(groupId: string): {
223
264
  ok: boolean;
@@ -230,6 +271,8 @@ export declare function createCanvasJsonRenderNode(input: JsonRenderNodeInput):
230
271
  url: string;
231
272
  spec: JsonRenderSpec;
232
273
  node: CanvasNodeState;
274
+ warnings: string[];
275
+ sizeAdjustment?: CanvasSizeAdjustment;
233
276
  };
234
277
  /**
235
278
  * Create an empty streaming json-render node. Unlike createCanvasJsonRenderNode
@@ -248,6 +291,7 @@ export declare function createCanvasStreamingJsonRenderNode(input: {
248
291
  url: string;
249
292
  spec: JsonRenderSpec;
250
293
  node: CanvasNodeState;
294
+ sizeAdjustment?: CanvasSizeAdjustment;
251
295
  };
252
296
  /**
253
297
  * Apply a batch of SpecStream patches to an existing json-render node, bumping
@@ -269,6 +313,7 @@ export declare function createCanvasGraphNode(input: GraphNodeInput): {
269
313
  url: string;
270
314
  spec: JsonRenderSpec;
271
315
  node: CanvasNodeState;
316
+ sizeAdjustment?: CanvasSizeAdjustment;
272
317
  };
273
318
  export declare function setClientViewportSize(width: unknown, height: unknown): void;
274
319
  export declare function getClientViewportSize(): {
@@ -1,4 +1,4 @@
1
- export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
1
+ export type CanvasNodeType = 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'board' | '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 {
@@ -22,6 +22,7 @@ export interface StructuredValidationResult {
22
22
  ok: true;
23
23
  type: 'json-render' | 'graph' | 'html-primitive';
24
24
  normalizedSpec?: JsonRenderSpec;
25
+ warnings?: string[];
25
26
  normalizedPrimitive?: {
26
27
  kind: string;
27
28
  title: string;
@@ -11,8 +11,12 @@
11
11
  * `.pmx-canvas-snapshots/`, loose blob files) are no longer imported as of 0.4.0 —
12
12
  * the one-shot boot migration into SQLite was retired.
13
13
  */
14
- import { type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
14
+ import { type Tour } from '../shared/tour.js';
15
+ import { type CanvasBoard, type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
16
+ import { type DocumentImport, type DocumentImportStatus, type ImportSection, type Attachment } from './document-import.js';
15
17
  import { type PmxAxActivityKind, type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
18
+ import { type ActorAttribution } from './attribution.js';
19
+ import { type ContextRead, type ContextReadConsumerSummary, type ContextReadInput } from './context-reads.js';
16
20
  export declare const PMX_CANVAS_DIR = ".pmx-canvas";
17
21
  export interface PersistedBlobRef {
18
22
  __pmxCanvasBlob: 'v1';
@@ -53,7 +57,7 @@ export interface CanvasSnapshotGcResult {
53
57
  }
54
58
  export interface CanvasNodeState {
55
59
  id: string;
56
- type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
60
+ type: 'markdown' | 'mcp-app' | 'webpage' | 'json-render' | 'graph' | 'board' | 'prompt' | 'response' | 'status' | 'context' | 'ledger' | 'trace' | 'file' | 'diff' | 'mermaid' | 'image' | 'html' | 'group';
57
61
  position: {
58
62
  x: number;
59
63
  y: number;
@@ -66,6 +70,27 @@ export interface CanvasNodeState {
66
70
  collapsed: boolean;
67
71
  pinned: boolean;
68
72
  data: Record<string, unknown>;
73
+ /** Server-owned provenance. Legacy rows normalize to unknown. */
74
+ createdBy?: ActorAttribution;
75
+ lastEditedBy?: ActorAttribution;
76
+ /** Board-monotonic revision of this node's latest semantic content. */
77
+ contentRevision?: number;
78
+ }
79
+ export interface NodeDeletionTombstone {
80
+ nodeId: string;
81
+ revision: number;
82
+ deletedBy: ActorAttribution;
83
+ }
84
+ export interface BoardContentRevision {
85
+ revision: number;
86
+ /** Cursors below this value are expired and must perform a full reset. */
87
+ retentionFloor: number;
88
+ }
89
+ export interface BoardContentDelta extends BoardContentRevision {
90
+ since: number;
91
+ reset: boolean;
92
+ nodes: CanvasNodeState[];
93
+ deleted: NodeDeletionTombstone[];
69
94
  }
70
95
  export interface ViewportState {
71
96
  x: number;
@@ -102,6 +127,7 @@ export interface CanvasAnnotation {
102
127
  createdAt: string;
103
128
  }
104
129
  export interface CanvasLayout {
130
+ tour?: Tour;
105
131
  viewport: ViewportState;
106
132
  theme: CanvasTheme;
107
133
  nodes: CanvasNodeState[];
@@ -121,6 +147,8 @@ export interface CanvasNodeUpdate {
121
147
  collapsed?: boolean;
122
148
  }
123
149
  export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
150
+ /** Name for a board nobody named yet: when it was made, to the minute. */
151
+ export declare function defaultBoardName(): string;
124
152
  export interface MutationRecordInfo {
125
153
  operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'updateEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'arrange' | 'batch' | 'groupNodes' | 'releaseGroupChildren' | 'viewport';
126
154
  description: string;
@@ -140,9 +168,13 @@ declare class CanvasStateManager {
140
168
  private edges;
141
169
  private annotations;
142
170
  private _viewport;
171
+ private _tour;
143
172
  private _theme;
144
173
  private _contextPinnedNodeIds;
145
174
  private _workspaceRoot;
175
+ private _contentRevision;
176
+ private _revisionFloor;
177
+ private _deletionTombstones;
146
178
  private readonly ax;
147
179
  private _changeListeners;
148
180
  /**
@@ -175,12 +207,22 @@ declare class CanvasStateManager {
175
207
  private applyResolvedGroupBounds;
176
208
  private getGroupSnapshot;
177
209
  private normalizeNode;
210
+ private nextContentRevision;
211
+ private revisionState;
212
+ /**
213
+ * Stable delta cursor contract: revisions strictly increase per active board;
214
+ * geometry-only writes do not advance them. `reset` means `since` predates
215
+ * retained tombstones, so callers must replace their view with `nodes`.
216
+ */
217
+ readContentDelta(since?: number): BoardContentDelta;
218
+ getContentRevision(): BoardContentRevision;
178
219
  private nodeForRead;
179
220
  private reflowAllGroups;
180
221
  private translateGroupChildren;
181
222
  private recomputeParentGroupBounds;
182
223
  private compactGroupChildren;
183
224
  private _db;
225
+ private _dbPath;
184
226
  private _saveTimer;
185
227
  /** Workspace root backing persistence and workspace-relative path resolution. */
186
228
  get workspaceRoot(): string;
@@ -198,8 +240,95 @@ declare class CanvasStateManager {
198
240
  private externalizePersistedStateBlobs;
199
241
  getWorkspaceRoot(): string;
200
242
  private emptyPersistedState;
201
- /** Load canvas state from SQLite. Call once on server startup. */
243
+ /**
244
+ * Load the board that was open last. Call once on server startup. With no
245
+ * such board the workspace starts on Home (no board open).
246
+ */
202
247
  loadFromDisk(options?: LoadFromDiskOptions): boolean;
248
+ /** The SQLite file holding every board, or null without a workspace database. */
249
+ get databasePath(): string | null;
250
+ readWorkspaceMeta(key: string): string | null;
251
+ writeWorkspaceMeta(key: string, value: string | null): void;
252
+ /** A consistent copy of the whole library, safe while the server runs. */
253
+ backupDatabaseTo(path: string): void;
254
+ /**
255
+ * Replace the library with `file`: the current file is kept beside it as
256
+ * `<db>.before-restore`, then the restored file opens (migrating an older
257
+ * layout) on the board it had open last.
258
+ */
259
+ replaceDatabase(file: string): void;
260
+ private _activeBoardId;
261
+ private _boardGeneration;
262
+ /** Invalidates asynchronous work whenever the loaded board state is replaced. */
263
+ get boardGeneration(): number;
264
+ /** The open board, or null on Home. */
265
+ get activeBoardId(): string | null;
266
+ getActiveBoard(): CanvasBoard | null;
267
+ /**
268
+ * One board's content without opening it: the open board from memory, any
269
+ * other straight from SQLite. Null when there is no such board.
270
+ */
271
+ readBoard(id: string, includeBlobs?: boolean): {
272
+ board: CanvasBoard;
273
+ state: PersistedCanvasState;
274
+ layout: CanvasLayout;
275
+ } | null;
276
+ /** Every board in the workspace, most recently opened first. */
277
+ listBoards(): CanvasBoard[];
278
+ storeAttachment(input: {
279
+ boardId: string;
280
+ name: string;
281
+ mime: string;
282
+ bytes: Uint8Array;
283
+ }): Attachment | null;
284
+ getAttachment(id: string): Attachment | null;
285
+ readAttachmentBytes(id: string): Uint8Array | null;
286
+ createDocumentImport(attachmentId: string, position: {
287
+ x: number;
288
+ y: number;
289
+ }): DocumentImport | null;
290
+ getDocumentImport(id: string): DocumentImport | null;
291
+ listDocumentImports(boardId?: string): DocumentImport[];
292
+ updateDocumentImport(id: string, status: DocumentImportStatus, fields?: {
293
+ sections?: ImportSection[];
294
+ warnings?: string[];
295
+ agentDescription?: string;
296
+ reason?: string;
297
+ committedNodeIds?: string[];
298
+ }): DocumentImport | null;
299
+ commitDocumentImport(id: string, nodeIds: string[]): DocumentImport | null;
300
+ getContextBriefCursor(boardId: string, consumer: string): number | null;
301
+ advanceContextBriefCursor(boardId: string, consumer: string, revision: number): void;
302
+ /** Creates a board without opening it. */
303
+ createBoard(name: string, category?: string | null): CanvasBoard | null;
304
+ updateBoard(id: string, patch: {
305
+ name?: string;
306
+ category?: string | null;
307
+ }): boolean;
308
+ setBoardReadme(id: string, nodeId: string | null): boolean;
309
+ /** Bounded inactive-board creation; never replaces the state the human has open. */
310
+ createBoardFromBoard(input: {
311
+ sourceBoardId: string;
312
+ name: string;
313
+ category?: string | null;
314
+ nodeIds?: string[];
315
+ includeReadme?: boolean;
316
+ includeStructure?: boolean;
317
+ }): CanvasBoard | null;
318
+ /** Deletes a board and its snapshots; deleting the open board returns to Home first. */
319
+ deleteBoard(id: string): boolean;
320
+ /**
321
+ * Save the open board, then open `id` (or Home for null). The canvas state is
322
+ * replaced wholesale; callers own the runtime side (undo history, app
323
+ * sessions, watchers, SSE) — see `openCanvasBoard` in canvas-operations.
324
+ */
325
+ switchBoard(id: string | null): boolean;
326
+ /**
327
+ * Content written while no board is open (Home) lands on a new board, which
328
+ * opens: nothing the human was looking at is displaced.
329
+ */
330
+ private ensureActiveBoard;
331
+ private hasBoardContent;
203
332
  /** Debounced save — coalesces rapid mutations into a single write. */
204
333
  private scheduleSave;
205
334
  flushToDisk(): void;
@@ -236,12 +365,18 @@ declare class CanvasStateManager {
236
365
  nodes: CanvasNodeState[];
237
366
  edges: CanvasEdge[];
238
367
  annotations: CanvasAnnotation[];
368
+ tour?: Tour;
239
369
  } | null;
240
370
  /** Delete a snapshot. */
241
371
  renameSnapshot(id: string, name: string): boolean;
242
372
  deleteSnapshot(id: string): boolean;
243
373
  /** Remove all snapshots from the DB. Used by test teardown. */
244
374
  clearAllSnapshots(): void;
375
+ private restampRestoredContent;
376
+ private semanticData;
377
+ private nodeContentChanged;
378
+ private setSemanticNode;
379
+ private recordDeletion;
245
380
  get viewport(): ViewportState;
246
381
  addNode(node: CanvasNodeState): void;
247
382
  addJsonRenderNode(node: CanvasNodeState): void;
@@ -261,6 +396,8 @@ declare class CanvasStateManager {
261
396
  private removeEdgesForNode;
262
397
  getLayout(): CanvasLayout;
263
398
  getLayoutForPersistence(): CanvasLayout;
399
+ getTour(): Tour | undefined;
400
+ setTour(tour: Tour | null): void;
264
401
  applyUpdates(updates: CanvasNodeUpdate[], options?: ApplyUpdatesOptions): {
265
402
  applied: number;
266
403
  skipped: number;
@@ -470,6 +607,12 @@ declare class CanvasStateManager {
470
607
  }): PmxAxSteeringMessage[];
471
608
  getPendingSteeringCount(consumer?: string): number;
472
609
  getAxTimelineSummary(): PmxAxTimelineSummary;
610
+ /** Records one agent context read (diagnostics; never notifies, so a read cannot trigger reads). */
611
+ recordContextRead(input: ContextReadInput, boardId?: string | null): ContextRead | null;
612
+ getContextReads(limit?: number): {
613
+ reads: ContextRead[];
614
+ summary: ContextReadConsumerSummary[];
615
+ };
473
616
  getAxTimeline(q?: AxTimelineQuery): {
474
617
  events: PmxAxEvent[];
475
618
  evidence: PmxAxEvidence[];
@@ -26,6 +26,8 @@ export interface CanvasValidationResult {
26
26
  collisions: CanvasValidationPair[];
27
27
  containments: CanvasContainmentIssue[];
28
28
  containmentViolations: CanvasContainmentIssue[];
29
+ /** Group frames overlapping nodes that are not members (advisory). */
30
+ groupFrameOverlaps: CanvasValidationPair[];
29
31
  missingEdgeEndpoints: Array<{
30
32
  edgeId: string;
31
33
  from: string;
@@ -39,6 +41,7 @@ export interface CanvasValidationResult {
39
41
  collisions: number;
40
42
  containments: number;
41
43
  containmentViolations: number;
44
+ groupFrameOverlaps: number;
42
45
  missingEdgeEndpoints: number;
43
46
  sizeWarnings: number;
44
47
  };
@@ -0,0 +1,111 @@
1
+ import type { CanvasNodeState, NodeDeletionTombstone } from './canvas-state.js';
2
+ export type ContextBriefReason = 'pinned' | 'changed' | 'human' | 'ask' | 'steer' | 'linked' | 'category';
3
+ export interface ContextBriefSourceEntry {
4
+ sourceBoardId: string;
5
+ nodeId: string;
6
+ reason: 'human' | 'ask' | 'steer';
7
+ title: string;
8
+ text: string;
9
+ provenance?: {
10
+ kind: 'imported';
11
+ source: string;
12
+ };
13
+ }
14
+ export interface ContextBriefLibraryBoard {
15
+ boardId: string;
16
+ name: string;
17
+ category: string | null;
18
+ readme?: {
19
+ nodeId: string;
20
+ title: string;
21
+ summary: string;
22
+ provenance?: {
23
+ kind: 'imported';
24
+ source: string;
25
+ };
26
+ };
27
+ pinnedTitles: Array<{
28
+ nodeId: string;
29
+ title: string;
30
+ }>;
31
+ /** IDs of link cards on the active board which explicitly target this board. */
32
+ linkIds: string[];
33
+ }
34
+ export interface ContextBriefInput {
35
+ activeBoard: {
36
+ boardId: string;
37
+ name: string;
38
+ category: string | null;
39
+ };
40
+ nodes: readonly CanvasNodeState[];
41
+ pinnedNodeIds: readonly string[];
42
+ contentRevision: number;
43
+ retentionFloor: number;
44
+ tombstones: readonly NodeDeletionTombstone[];
45
+ /** null means a first read. Other values must be non-negative safe integers. */
46
+ since: number | null;
47
+ libraryBoards: readonly ContextBriefLibraryBoard[];
48
+ entries?: readonly ContextBriefSourceEntry[];
49
+ /** Maximum JavaScript string length (UTF-16 code units), including the JSON envelope. */
50
+ budget: number;
51
+ }
52
+ export interface CompiledContextEntry {
53
+ sourceBoardId: string;
54
+ nodeId: string;
55
+ reason: ContextBriefReason;
56
+ title: string;
57
+ text: string;
58
+ /** A linked board's pin title is discovery metadata, not delivery of that pinned card. */
59
+ titleOnly?: true;
60
+ /** The body was shortened to fit; the same revision remains eligible on the next pull. */
61
+ truncated?: true;
62
+ provenance?: {
63
+ kind: 'imported';
64
+ source: string;
65
+ trust: 'source-material-not-instructions';
66
+ };
67
+ }
68
+ export interface ContextBriefDocument {
69
+ version: 1;
70
+ budgetUnit: 'utf16-code-units';
71
+ board: {
72
+ boardId: string;
73
+ name: string;
74
+ category: string | null;
75
+ };
76
+ cursor: {
77
+ requested: number | null;
78
+ next: number | null;
79
+ current: number;
80
+ retentionFloor: number;
81
+ reset: 'first-read' | 'retention-expired' | null;
82
+ valid: boolean;
83
+ };
84
+ entries: CompiledContextEntry[];
85
+ deletions: Array<{
86
+ sourceBoardId: string;
87
+ nodeId: string;
88
+ revision: number;
89
+ }>;
90
+ delivery: {
91
+ truncated: boolean;
92
+ omittedEntries: number;
93
+ omittedDeletions: number;
94
+ };
95
+ }
96
+ export interface ContextBriefResult {
97
+ /** Empty only when the budget cannot hold even the bounded JSON envelope. */
98
+ serialized: string;
99
+ document: ContextBriefDocument | null;
100
+ deliveredEntryIds: string[];
101
+ deliveredDeletionIds: string[];
102
+ nextCursor: number | null;
103
+ reset: ContextBriefDocument['cursor']['reset'];
104
+ invalidCursor: boolean;
105
+ truncated: boolean;
106
+ }
107
+ /**
108
+ * Pure, deterministic compiler. It reads only the supplied snapshot and never
109
+ * treats library visibility as authorization to read or mutate another board.
110
+ */
111
+ export declare function compileContextBrief(input: ContextBriefInput): ContextBriefResult;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Context read instrumentation (vision Part 1 item 4, the delivery half):
3
+ * one row per agent read of canvas context — which surface, by whom, and
4
+ * which pinned nodes were in what came back. Diagnostics only, like the AX
5
+ * timeline: never snapshotted, never cleared with the canvas, bounded, and
6
+ * recording never notifies (a read must not trigger more reads).
7
+ */
8
+ import type { Database } from 'bun:sqlite';
9
+ export declare const CONTEXT_READ_RETENTION = 5000;
10
+ export declare const CONTEXT_READ_CHANNELS: readonly ["operation", "mcp-resource", "mcp-prompt", "adapter"];
11
+ export type ContextReadChannel = (typeof CONTEXT_READ_CHANNELS)[number];
12
+ /** Registry reads that hand an agent canvas context; recorded in executeOperation. */
13
+ export declare const CONTEXT_READ_OPS: Set<string>;
14
+ export interface ContextRead {
15
+ seq: number;
16
+ id: string;
17
+ at: string;
18
+ channel: ContextReadChannel;
19
+ resource: string;
20
+ source: string;
21
+ consumer: string | null;
22
+ agentId: string | null;
23
+ pinnedNodeIds: string[];
24
+ deliveredNodeIds: string[];
25
+ bytes: number;
26
+ /** The board open when the read happened (null on Home). */
27
+ boardId: string | null;
28
+ }
29
+ export type ContextReadInput = Omit<ContextRead, 'seq' | 'id' | 'at' | 'boardId'> & {
30
+ /** Explicit read target, captured before asynchronous formatting/proxy work. */
31
+ boardId?: string | null;
32
+ };
33
+ export interface ContextReadConsumerSummary {
34
+ consumer: string;
35
+ reads: number;
36
+ readsWithPins: number;
37
+ readsDeliveringAllPins: number;
38
+ lastReadAt: string;
39
+ resources: Record<string, number>;
40
+ }
41
+ /**
42
+ * Pinned nodes whose serialized node (an object carrying `"id": "<id>"`) is in
43
+ * what the reader received. A bare id list, a title, or a clipped-off node does
44
+ * not count — the agent got the pin's name, not its content.
45
+ */
46
+ export declare function deliveredPinnedIds(pinnedNodeIds: string[], payloadText: string): string[];
47
+ export declare function contextReadFromPayload(base: Omit<ContextReadInput, 'deliveredNodeIds' | 'bytes'>, payload: unknown): ContextReadInput;
48
+ export declare const CONTEXT_READS_SCHEMA_SQL = "\n CREATE TABLE IF NOT EXISTS context_reads (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n at TEXT NOT NULL,\n channel TEXT NOT NULL,\n resource TEXT NOT NULL,\n source TEXT NOT NULL,\n consumer TEXT,\n agent_id TEXT,\n pinned_node_ids TEXT NOT NULL DEFAULT '[]',\n delivered_node_ids TEXT NOT NULL DEFAULT '[]',\n bytes INTEGER NOT NULL DEFAULT 0,\n board_id TEXT\n );\n";
49
+ export declare function appendContextReadToDB(db: Database, input: ContextReadInput, boardId: string | null): ContextRead;
50
+ /** Newest first. The summary covers every retained row, not just the returned page. */
51
+ export declare function loadContextReadsFromDB(db: Database, limit?: number): {
52
+ reads: ContextRead[];
53
+ summary: ContextReadConsumerSummary[];
54
+ };
55
+ export declare function summarizeContextReads(reads: ContextRead[]): ContextReadConsumerSummary[];