pmx-canvas 0.1.34 → 0.1.35

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
@@ -5,6 +5,18 @@ All notable changes to `pmx-canvas` are documented here. This project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.35] - 2026-06-08
9
+
10
+ ### Fixed
11
+
12
+ - **HTTP batch accepts a bare-array body again (report #49).** `POST /api/canvas/batch`
13
+ with a top-level `[ ... ]` array created nothing and returned `{ ok: true, results: [] }`
14
+ — the 0.1.33 `readJson` hardening (empty/whitespace/non-object → `{}`) also coerced
15
+ top-level arrays to `{}`, so the handler's bare-array branch was dead. Batch now reads
16
+ the body with an array-preserving variant, so both documented shapes work: the canonical
17
+ `{ "operations": [...] }` and a bare `[...]`. `readJson` is unchanged for every other
18
+ endpoint (still object-only), so the malformed-body robustness is preserved.
19
+
8
20
  ## [0.1.34] - 2026-06-08
9
21
 
10
22
  ### Added
@@ -1763,6 +1775,7 @@ otherwise have to discover by trial and error.
1763
1775
  - Regression coverage for snapshot flat-`id` aliases on both MCP and
1764
1776
  HTTP surfaces, plus async / top-level-`await` WebView script bodies.
1765
1777
 
1778
+ [0.1.35]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.35
1766
1779
  [0.1.34]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.34
1767
1780
  [0.1.33]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.33
1768
1781
  [0.1.32]: https://github.com/pskoett/pmx-canvas/releases/tag/v0.1.32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmx-canvas",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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",
@@ -1072,6 +1072,34 @@ async function readJson(req: Request): Promise<Record<string, unknown>> {
1072
1072
  }
1073
1073
  }
1074
1074
 
1075
+ /**
1076
+ * Like {@link readJson}, but PRESERVES a top-level JSON array. For endpoints that
1077
+ * accept either an object or a bare array (e.g. `/api/canvas/batch`, whose CLI
1078
+ * help and handler both document a bare `[...]` form). readJson coerces arrays to
1079
+ * `{}` so object-shaped handlers never crash on `body.field`; this variant keeps
1080
+ * the array so the handler's array branch can run. Empty/whitespace/malformed
1081
+ * bodies still resolve to `{}`.
1082
+ */
1083
+ async function readJsonObjectOrArray(req: Request): Promise<Record<string, unknown> | unknown[]> {
1084
+ let text = '';
1085
+ try {
1086
+ text = await req.text();
1087
+ } catch (error) {
1088
+ logWorkbenchWarning('readJson', error);
1089
+ return {};
1090
+ }
1091
+ if (!text.trim()) return {};
1092
+ try {
1093
+ const value = JSON.parse(text) as unknown;
1094
+ if (Array.isArray(value)) return value;
1095
+ if (!value || typeof value !== 'object') return {};
1096
+ return value as Record<string, unknown>;
1097
+ } catch (error) {
1098
+ logWorkbenchWarning('readJson', error);
1099
+ return {};
1100
+ }
1101
+ }
1102
+
1075
1103
  function htmlEscape(value: string): string {
1076
1104
  return value
1077
1105
  .replaceAll('&', '&amp;')
@@ -2495,8 +2523,12 @@ async function handleCanvasAddGraph(req: Request): Promise<Response> {
2495
2523
  }
2496
2524
 
2497
2525
  async function handleCanvasBatch(req: Request): Promise<Response> {
2498
- const body = await readJson(req);
2499
- const operations = Array.isArray(body.operations) ? body.operations : Array.isArray(body) ? body : [];
2526
+ // Accept both documented shapes: { operations: [...] } and a bare [...] array.
2527
+ // Uses the array-preserving reader so the bare-array form isn't coerced to {}.
2528
+ const body = await readJsonObjectOrArray(req);
2529
+ const operations = Array.isArray(body)
2530
+ ? body
2531
+ : Array.isArray(body.operations) ? body.operations : [];
2500
2532
  const normalized = operations
2501
2533
  .filter((operation): operation is Record<string, unknown> => operation && typeof operation === 'object' && !Array.isArray(operation))
2502
2534
  .map((operation) => ({