pmx-canvas 0.1.22 → 0.1.23

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 CHANGED
@@ -3,6 +3,75 @@
3
3
  All notable changes to `pmx-canvas` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.1.23] - 2026-05-12
7
+
8
+ Persistence overhaul. Canvas state, snapshots, context pins, and the
9
+ large-payload blob store all move from filesystem JSON files into a
10
+ single SQLite database at `.pmx-canvas/canvas.db` (WAL mode). The
11
+ old `state.json`, `snapshots/`, and per-blob files are auto-imported
12
+ on first boot and renamed to `.bak`. Adds dock-aware validation and
13
+ arrange behavior, accepts the documented `?type=` query string on
14
+ node creation, and grows the schema-metadata kebab-case aliases to
15
+ include both singular and plural variants.
16
+
17
+ ### Added
18
+
19
+ - **SQLite persistence (`src/server/canvas-db.ts`, 710 lines).**
20
+ Canvas state, snapshots, context pins, and the blob sidecar
21
+ store now live in `.pmx-canvas/canvas.db` (Bun SQLite in WAL
22
+ mode). Shape preserved across the migration: nodes, edges,
23
+ annotations, viewport, context pins, history, snapshots, and
24
+ blob payloads with checksum validation.
25
+ - Override DB path: `PMX_CANVAS_DB_PATH` env var.
26
+ - Backward-compatible legacy path: `PMX_CANVAS_STATE_FILE` (if
27
+ you set a `.db` path there, it's used as the DB; if not, it's
28
+ treated as a legacy JSON path for migration).
29
+ - `stopCanvasServer()` now calls `canvasState.close()` which
30
+ checkpoints WAL data into the DB file — stop the server (or
31
+ flush/close the SDK) before committing `canvas.db`.
32
+ - SQLite WAL/SHM files (`*.db-wal`, `*.db-shm`) are gitignored;
33
+ `canvas.db` itself is git-committable.
34
+ - **Legacy migration on first boot.** Existing
35
+ `.pmx-canvas/state.json`, root `.pmx-canvas.json`,
36
+ `.pmx-canvas/snapshots/`, `.pmx-canvas-snapshots/`, and blob
37
+ files are imported into the SQLite database and renamed to
38
+ `.bak` on first start. The migration is idempotent and only
39
+ runs when the DB is empty.
40
+ - **HTTP node create accepts `?type=` query string.** `POST
41
+ /api/canvas/node?type=html-primitive` with the body's `data`
42
+ fields is accepted as an alternative to passing `type` in the
43
+ body — handy for `curl` and shell-based agents. The body form
44
+ still wins when both are present.
45
+
46
+ ### Changed
47
+
48
+ - **Docked nodes are excluded from layout collision validation.**
49
+ `validateCanvasLayout` no longer flags `dockPosition !== null`
50
+ nodes as overlap or containment violations. Docked HUD-style
51
+ nodes intentionally sit on top of canvas content; the validator
52
+ now models that.
53
+ - **Docked nodes are treated as arrange-locked.** `arrange()`
54
+ now skips translating nodes with `dockPosition !== null` along
55
+ with pinned and explicitly arrange-locked nodes. Dock geometry
56
+ is anchored to the HUD layer, not the world grid.
57
+ - **Schema kebab-case aliases include plural forms.** `--embedded-
58
+ node-ids`, `--embedded-urls`, and `--slide-titles` are now
59
+ documented aliases for the array-shaped HTML sidecar fields,
60
+ alongside the existing singular `--embedded-node-id`,
61
+ `--embedded-url`, and `--slide-title`.
62
+ - **Bun engine bumped to `>=1.3.14`.** `package.json#engines.bun`
63
+ raised from `>=1.3.12` to `>=1.3.14` to pick up the
64
+ `bun:sqlite` improvements the new persistence layer depends on.
65
+
66
+ ### Internal
67
+
68
+ - Regression coverage for: `validateCanvasLayout` ignoring docked
69
+ nodes as collision candidates, html primitive node creation
70
+ accepting the documented query-string `?type=` form, and the
71
+ existing canvas-state and operations suites continuing to pass
72
+ against the SQLite-backed persistence (including snapshot
73
+ save/restore, blob round-trip, and undo/redo history).
74
+
6
75
  ## [0.1.22] - 2026-05-12
7
76
 
8
77
  CLI ergonomics and response-size polish on top of 0.1.21. Adds a
@@ -1045,6 +1114,7 @@ otherwise have to discover by trial and error.
1045
1114
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
1046
1115
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
1047
1116
 
1117
+ [0.1.23]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.23
1048
1118
  [0.1.22]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.22
1049
1119
  [0.1.21]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.21
1050
1120
  [0.1.20]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.20
package/Readme.md CHANGED
@@ -57,11 +57,12 @@ picks up immediately.
57
57
 
58
58
  ### 05 / Save
59
59
 
60
- Spatial state auto-saves to `.pmx-canvas/state.json` (debounced ~500 ms) —
60
+ Spatial state auto-saves to `.pmx-canvas/canvas.db` (debounced ~500 ms) —
61
61
  git-committable, shareable across machines, and survives both browser
62
62
  refresh and server restart. Named [snapshots](docs/mcp.md#tools), full
63
63
  undo/redo, and an auto-detected code graph (JS/TS, Python, Go, Rust) make
64
- the canvas durable rather than throwaway.
64
+ the canvas durable rather than throwaway. Stop the server before committing
65
+ the DB so SQLite WAL data is checkpointed into the file.
65
66
 
66
67
  ### 06 / Any agent
67
68
 
@@ -168,7 +169,7 @@ the agent can read `canvas://skills` and pull in companion skills
168
169
  one machine. No built-in multi-user auth or presence — collaboration means
169
170
  human ↔ agent on the same machine, plus any other browser tab/agent
170
171
  pointed at the same `localhost:4313`. To share across machines, commit
171
- `.pmx-canvas/state.json`.
172
+ `.pmx-canvas/canvas.db`.
172
173
  - **What leaves your machine.** The core canvas runs entirely on
173
174
  `localhost`. Network egress only happens for explicit, opt-in flows:
174
175
  `webpage` nodes fetch the URL you give them; `mcp-app` /
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SQLite persistence layer for canvas state.
3
+ *
4
+ * Uses Bun's built-in `bun:sqlite` for zero-dependency, synchronous,
5
+ * WAL-mode persistence. Replaces the previous JSON file-based approach.
6
+ */
7
+ import { Database } from 'bun:sqlite';
8
+ import type { CanvasAnnotation, CanvasEdge, CanvasNodeState, CanvasSnapshot, CanvasSnapshotListOptions, ViewportState } from './canvas-state.js';
9
+ export interface PersistedCanvasState {
10
+ version: number;
11
+ viewport: ViewportState;
12
+ nodes: CanvasNodeState[];
13
+ edges: CanvasEdge[];
14
+ annotations?: CanvasAnnotation[];
15
+ contextPins: string[];
16
+ }
17
+ export declare function openCanvasDb(dbPath: string): Database;
18
+ export declare function checkpointCanvasDb(db: Database): void;
19
+ export declare function finalizeCanvasDbForClose(db: Database): void;
20
+ export declare function saveStateToDB(db: Database, state: PersistedCanvasState): void;
21
+ /** Check if the DB has been populated with canvas state at least once. */
22
+ export declare function isDbPopulated(db: Database): boolean;
23
+ export declare function loadStateFromDB(db: Database): PersistedCanvasState | null;
24
+ export declare function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state: PersistedCanvasState): void;
25
+ export declare function loadSnapshotFromDB(db: Database, idOrName: string): {
26
+ snapshot: CanvasSnapshot;
27
+ state: PersistedCanvasState;
28
+ } | null;
29
+ export declare function listSnapshotsFromDB(db: Database, options?: CanvasSnapshotListOptions): CanvasSnapshot[];
30
+ export declare function deleteSnapshotFromDB(db: Database, id: string): boolean;
31
+ export declare function writeBlobToDB(db: Database, sha256: string, jsonValue: string): number;
32
+ export declare function readBlobFromDB(db: Database, sha256: string): string | null;
33
+ export declare function hasBlobInDB(db: Database, sha256: string): boolean;
@@ -5,9 +5,11 @@
5
5
  * - Agent tools (Phase 3) can read/mutate canvas state
6
6
  * - Client syncs bidirectionally (SSE for server→client, POST for client→server)
7
7
  *
8
- * Persistence: canvas state auto-saves to `.pmx-canvas/state.json` in the
9
- * workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
8
+ * Persistence: canvas state auto-saves to `.pmx-canvas/canvas.db` (SQLite WAL mode)
9
+ * in the workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
10
+ * Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
10
11
  */
12
+ import { type PersistedCanvasState } from './canvas-db.js';
11
13
  export declare const PMX_CANVAS_DIR = ".pmx-canvas";
12
14
  export interface PersistedBlobRef {
13
15
  __pmxCanvasBlob: 'v1';
@@ -17,14 +19,7 @@ export interface PersistedBlobRef {
17
19
  bytes: number;
18
20
  jsonBytes: number;
19
21
  }
20
- interface PersistedCanvasState {
21
- version: number;
22
- viewport: ViewportState;
23
- nodes: CanvasNodeState[];
24
- edges: CanvasEdge[];
25
- annotations?: CanvasAnnotation[];
26
- contextPins: string[];
27
- }
22
+ export type { PersistedCanvasState } from './canvas-db.js';
28
23
  interface LoadFromDiskOptions {
29
24
  clearExisting?: boolean;
30
25
  }
@@ -166,6 +161,7 @@ declare class CanvasStateManager {
166
161
  private recomputeParentGroupBounds;
167
162
  private compactGroupChildren;
168
163
  private _stateFilePath;
164
+ private _db;
169
165
  private _saveTimer;
170
166
  /** Set the workspace root to enable auto-persistence. */
171
167
  setWorkspaceRoot(workspaceRoot: string): void;
@@ -185,15 +181,22 @@ declare class CanvasStateManager {
185
181
  * No-op when the new layout already exists.
186
182
  */
187
183
  private migrateLegacyLayout;
184
+ /**
185
+ * One-time migration: import state.json + snapshot JSON files + blob files
186
+ * into the SQLite database. Renames originals to `.bak`.
187
+ */
188
+ private migrateJsonToSqlite;
188
189
  getWorkspaceRoot(): string;
189
190
  private emptyPersistedState;
190
- /** Load canvas state from disk. Call once on server startup. */
191
+ /** Load canvas state from SQLite (or legacy JSON fallback). Call once on server startup. */
191
192
  loadFromDisk(options?: LoadFromDiskOptions): boolean;
192
- /** Debounced save — coalesces rapid mutations into a single disk write. */
193
+ /** Debounced save — coalesces rapid mutations into a single write. */
193
194
  private scheduleSave;
194
195
  flushToDisk(): void;
195
- /** Write current state to disk immediately. */
196
+ /** Write current state to SQLite immediately. */
196
197
  private saveToDisk;
198
+ /** Close the SQLite database cleanly. Call on server shutdown. */
199
+ close(): void;
197
200
  private get snapshotsDir();
198
201
  private applyPersistedState;
199
202
  private readResolvedSnapshot;
@@ -217,6 +220,8 @@ declare class CanvasStateManager {
217
220
  } | null;
218
221
  /** Delete a snapshot. */
219
222
  deleteSnapshot(id: string): boolean;
223
+ /** Remove all snapshots from the DB. Used by test teardown. */
224
+ clearAllSnapshots(): void;
220
225
  get viewport(): ViewportState;
221
226
  addNode(node: CanvasNodeState): void;
222
227
  addJsonRenderNode(node: CanvasNodeState): void;
@@ -250,4 +255,3 @@ declare class CanvasStateManager {
250
255
  clear(): void;
251
256
  }
252
257
  export declare const canvasState: CanvasStateManager;
253
- export {};
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
5
5
  "type": "module",
6
6
  "main": "./src/server/index.ts",
@@ -106,6 +106,6 @@
106
106
  },
107
107
  "homepage": "https://github.com/pskoett/pmx-canvas#readme",
108
108
  "engines": {
109
- "bun": ">=1.3.12"
109
+ "bun": ">=1.3.14"
110
110
  }
111
111
  }
@@ -960,12 +960,16 @@ When the human wants to explore a different approach without losing current work
960
960
 
961
961
  ## Persistence
962
962
 
963
- Canvas state auto-saves to `.pmx-canvas/state.json` on every mutation (debounced 500ms). State
964
- loads automatically on server start. The file is git-committable — spatial knowledge
963
+ Canvas state auto-saves to `.pmx-canvas/canvas.db` on every mutation (debounced 500ms). State
964
+ loads automatically on server start. The SQLite DB is git-committable — spatial knowledge
965
965
  persists across sessions.
966
966
 
967
- Snapshots save to `.pmx-canvas/snapshots/`. Web artifacts land in `.pmx-canvas/artifacts/`.
968
- Legacy `.pmx-canvas.json` and `.pmx-canvas-snapshots/` are auto-migrated on first boot.
967
+ Snapshots, context pins, and large node blobs are stored in the same DB. Web artifacts land in
968
+ `.pmx-canvas/artifacts/`. Legacy JSON state, snapshot, and blob files are auto-imported into
969
+ SQLite and renamed to `.bak` on first boot.
970
+
971
+ Stop the server or flush/close the SDK before committing `canvas.db`; shutdown checkpoints SQLite
972
+ WAL data into the DB file.
969
973
 
970
974
  ## Real-Time Collaboration
971
975