pmx-canvas 0.7.1 → 0.7.3

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 (76) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +79 -61
  2. package/CHANGELOG.md +29 -1
  3. package/Readme.md +48 -9
  4. package/dist/canvas/global.css +144 -45
  5. package/dist/canvas/index.js +90 -90
  6. package/dist/json-render/index.js +55 -55
  7. package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
  8. package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
  9. package/dist/types/client/icons.d.ts +15 -0
  10. package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
  11. package/dist/types/client/nodes/FileNode.d.ts +0 -12
  12. package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
  13. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
  14. package/dist/types/client/state/canvas-store.d.ts +2 -0
  15. package/dist/types/client/state/intent-bridge.d.ts +1 -1
  16. package/dist/types/json-render/renderer/index.d.ts +2 -1
  17. package/dist/types/json-render/server.d.ts +2 -0
  18. package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
  19. package/dist/types/shared/delimited-text.d.ts +14 -0
  20. package/docs/ax-host-adapter-contract.md +13 -2
  21. package/docs/cli.md +9 -2
  22. package/docs/http-api.md +12 -8
  23. package/docs/mcp.md +8 -1
  24. package/docs/sdk.md +2 -2
  25. package/package.json +1 -1
  26. package/skills/pmx-canvas/SKILL.md +41 -16
  27. package/skills/pmx-canvas/evals/evals.json +112 -2
  28. package/skills/pmx-canvas/references/full-reference.md +9 -4
  29. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
  30. package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
  31. package/skills/pmx-canvas-testing/SKILL.md +49 -0
  32. package/src/cli/commands/backup.ts +15 -4
  33. package/src/cli/commands/boards.ts +16 -3
  34. package/src/client/canvas/AttentionHistory.tsx +2 -1
  35. package/src/client/canvas/BoardSwitcher.tsx +3 -3
  36. package/src/client/canvas/CanvasNode.tsx +211 -197
  37. package/src/client/canvas/CanvasViewport.tsx +6 -0
  38. package/src/client/canvas/ChoiceMenu.tsx +122 -0
  39. package/src/client/canvas/CommandBar.tsx +27 -25
  40. package/src/client/canvas/ContextPinBar.tsx +3 -1
  41. package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
  42. package/src/client/canvas/ExternalWriters.tsx +4 -3
  43. package/src/client/canvas/FeedbackDialog.tsx +12 -9
  44. package/src/client/canvas/IntentLayer.tsx +2 -2
  45. package/src/client/canvas/SelectionBar.tsx +2 -2
  46. package/src/client/canvas/SessionPanel.tsx +6 -4
  47. package/src/client/canvas/SessionReceipt.tsx +2 -1
  48. package/src/client/canvas/SnapshotPanel.tsx +3 -2
  49. package/src/client/canvas/ToolRail.tsx +48 -100
  50. package/src/client/canvas/TopBar.tsx +2 -2
  51. package/src/client/icons.tsx +118 -1
  52. package/src/client/nodes/BoardNode.tsx +2 -1
  53. package/src/client/nodes/ExtAppFrame.tsx +3 -1
  54. package/src/client/nodes/FileNode.tsx +4 -78
  55. package/src/client/nodes/ImageNode.tsx +2 -1
  56. package/src/client/nodes/MarkdownNode.tsx +2 -1
  57. package/src/client/nodes/McpAppNode.tsx +16 -0
  58. package/src/client/nodes/PromptNode.tsx +3 -1
  59. package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
  60. package/src/client/nodes/viewer-runtime-state.ts +78 -0
  61. package/src/client/state/canvas-store.ts +3 -0
  62. package/src/client/state/intent-bridge.ts +14 -2
  63. package/src/client/theme/global.css +144 -45
  64. package/src/json-render/renderer/index.tsx +58 -38
  65. package/src/json-render/server.ts +8 -1
  66. package/src/mcp/server.ts +2 -2
  67. package/src/server/agent-presence.ts +17 -11
  68. package/src/server/board-export.ts +97 -32
  69. package/src/server/index.ts +9 -7
  70. package/src/server/operations/composites.ts +3 -2
  71. package/src/server/operations/ops/ax-presence.ts +7 -3
  72. package/src/server/operations/ops/boards.ts +12 -5
  73. package/src/server/operations/registry.ts +4 -13
  74. package/src/server/server.ts +13 -4
  75. package/src/shared/ax-surface-protocol.ts +3 -0
  76. package/src/shared/delimited-text.ts +75 -0
@@ -430,8 +430,8 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
430
430
 
431
431
  > **Legacy single-purpose tools were removed in v0.3.0.** The old names (`canvas_add_node`,
432
432
  > `canvas_update_node`, `canvas_request_approval`, `canvas_add_work_item`, …) are no longer
433
- > registered — use the composites instead. The authoritative legacy→composite mapping table lives
434
- > in [`docs/mcp.md`](../../../docs/mcp.md) — this skill does not re-enumerate the removed names.
433
+ > registered — use the composites below and fresh MCP `tools/list` for the installed runtime's
434
+ > authoritative action schemas. This skill does not re-enumerate the removed names.
435
435
 
436
436
  ### The 18 composites
437
437
 
@@ -439,11 +439,11 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
439
439
  |-----------|-----------------|--------------|
440
440
  | `canvas_import` | `list` · `read` · `submit` · `unavailable` | Process human-requested imports: inspect metadata/controlled bytes, submit reviewable Markdown with references/warnings/agent description, or report unavailable. Request/cancel/commit are human-only |
441
441
  | `canvas_node` | `add` · `get` · `update` · `remove` | Create / read / mutate / delete a node. **`add` covers html + primitives too**: `{ action:"add", type:"html", html:"…" }` and `{ action:"add", type:"html", primitive:"choice-grid", data:{} }` — no separate add-html tool needed |
442
- | `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` | Schema introspection, spec dry-run validation, and native json-render / graph node creation |
442
+ | `canvas_render` | `describe-schema` · `validate` · `add-json-render` · `stream-json-render` · `add-graph` · `workboard` | Schema introspection, spec dry-run validation, native json-render / graph node creation, and a live AX work-item board |
443
443
  | `canvas_edge` | `add` · `update` · `remove` | Connect / edit / disconnect nodes |
444
444
  | `canvas_group` | `create` · `add` · `ungroup` | Manage spatial group containers |
445
445
  | `canvas_history` | `undo` · `redo` | Time travel through the mutation ring buffer |
446
- | `canvas_board` | `list` · `get` · `create` · `create-from` · `update` | Boards, README designation, links/backlinks, and inactive copies |
446
+ | `canvas_board` | `list` · `get` · `create` · `create-from` · `open` · `update` | Boards, shared-workbench switching, README designation, links/backlinks, and inactive copies |
447
447
  | `canvas_snapshot` | `save` · `list` · `restore` · `delete` · `gc` · `diff` | Named snapshots: save/list/restore/delete, garbage-collect old ones, diff current canvas vs a snapshot (`diff` takes `snapshot`, not `id`) |
448
448
  | `canvas_view` | `arrange` · `focus` · `fit` · `clear` · `remove-annotation` · `get-tour` · `set-tour` | Auto-arrange, pan-to-node, fit viewport, clear the board, delete annotations, read/persist ordered presentation stops (`tour: null` derives group order) |
449
449
  | `canvas_query` | `context` · `search` · `layout` · `validate` | Read the budgeted cross-board brief, find nodes by keyword, read full layout, or **`validate`** the board for node collisions / group-containment / dangling edges |
@@ -485,6 +485,11 @@ creation: `prompt`, `response`, `trace`, and `mcp-app` are excluded; README and
485
485
  group structure are optional; fresh IDs are allocated; pins, AX state, and
486
486
  history are not copied; the source remains unchanged and the new board inactive.
487
487
 
488
+ Use `canvas_board { action: "open", id: "<board-id>" }` to open a created or
489
+ existing board before authoring. Verify the returned `activeBoardId`; opening
490
+ switches the shared visible workbench and all subsequent write targets, not
491
+ just this agent's context. `id: null` opens Home. Deletion stays human-only.
492
+
488
493
  `canvas_ax_gate` takes **two** discriminators, `{ kind, action }` — e.g.
489
494
  `{ kind: "approval", action: "request", title }`,
490
495
  `{ kind: "elicitation", action: "resolve", id, response }`,
@@ -53,6 +53,12 @@ canvas writes — which reach the server as MCP or HTTP tool calls, not as `copi
53
53
  to that session by the server. Nothing to configure; `GET /api/canvas/ax/presence` shows the live
54
54
  state.
55
55
 
56
+ Read presence before starting board work. Reuse the extension's `copilot` session and
57
+ steering consumer; do not attach a second session such as “Copilot – team overview” with
58
+ a new source or `agentId`. Send task names as `detail` on `source: "copilot"`, preserving
59
+ the host's identity and polling route. The adapter owns attach/detach; the agent must not
60
+ end it after each task. Explicit agent IDs are reserved for genuinely independent subagents.
61
+
56
62
  ## What the Adapter Does
57
63
 
58
64
  - Opens the live PMX workbench directly in a native Copilot canvas panel.
@@ -138,14 +144,22 @@ Default discovery order:
138
144
  3. `PMX_CANVAS_PORT` / `PMX_WEB_CANVAS_PORT` / `4313` on loopback.
139
145
  4. Managed server startup for the current workspace when `autoStart` is not `false`.
140
146
 
141
- The adapter rejects an unrelated running PMX server unless `serverUrl` is explicit or
142
- `allowWorkspaceMismatch` is true.
147
+ The adapter rejects an unrelated running PMX server unless `allowWorkspaceMismatch` is true,
148
+ including when `serverUrl` is explicit.
149
+
150
+ If discovery fails, the panel shows a connection page, not a partial workbench. It checks the
151
+ original target automatically; **Check connection** retries immediately and **Start server**
152
+ explicitly allows local startup even if `autoStart` was disabled. After recovery it navigates
153
+ to the server's real `/workbench`, with Home and recent boards. Adapter actions keep using that
154
+ server and refuse writes if its workspace changes. Reopen the canvas explicitly to select a
155
+ different target. The fallback shell's own `/health` or `/api/canvas/boards` returning 404 is
156
+ expected: the shell is not a PMX API proxy.
143
157
 
144
158
  ## Actions
145
159
 
146
160
  | Action | Purpose |
147
161
  |---|---|
148
- | `status` | Return PMX server health and persisted AX state. |
162
+ | `status` | Return PMX server health and persisted AX state, excluding serialized node context (`includeContext=false`). |
149
163
  | `get_ax_context` | Return current pinned + focused AX context. |
150
164
  | `focus_nodes` | Set AX focus with `source: "copilot"`. |
151
165
  | `send_instruction` | Send an explicit prompt into the active Copilot session. |
@@ -4,7 +4,7 @@ Use this reference when the `pmx-canvas` skill is installed but the `pmx-canvas`
4
4
 
5
5
  ## Prerequisites and version
6
6
 
7
- The examples pin **PMX Canvas 0.7.1**, which requires **Bun >=1.4.2** even when
7
+ The examples pin **PMX Canvas 0.7.3**, which requires **Bun >=1.4.2** even when
8
8
  installed through npm: the CLI runs through Bun, not Node. Check `bun --version`
9
9
  first. If missing or older, follow the [Bun installation instructions](https://bun.sh/docs/installation)
10
10
  with the user's authorization. Ensure Bun is on the MCP host's PATH as well as the
@@ -17,11 +17,11 @@ in `SKILL.md`. Do not pair newer checkout guidance with an older runtime unknowi
17
17
  ## Install from npm
18
18
 
19
19
  ```bash
20
- npm install -g pmx-canvas@0.7.1
20
+ npm install -g pmx-canvas@0.7.3
21
21
  pmx-canvas --version
22
22
  ```
23
23
 
24
- Without a global install, use `bunx pmx-canvas@0.7.1` in place of `pmx-canvas` in
24
+ Without a global install, use `bunx pmx-canvas@0.7.3` in place of `pmx-canvas` in
25
25
  the commands below. Do not use unpinned `bunx pmx-canvas` for a version-specific check.
26
26
 
27
27
  ## Choose the workspace and target
@@ -91,7 +91,7 @@ discards empty env values, remove `PMX_CANVAS_URL` from its launch environment i
91
91
  "mcpServers": {
92
92
  "canvas": {
93
93
  "command": "bunx",
94
- "args": ["pmx-canvas@0.7.1", "--mcp"],
94
+ "args": ["pmx-canvas@0.7.3", "--mcp"],
95
95
  "env": {
96
96
  "PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
97
97
  "PMX_CANVAS_PORT": "14313",
@@ -123,6 +123,78 @@ If you are using a local checkout instead of the published package, point the co
123
123
  Restart the host's MCP process after changing configuration. Inspect startup output for
124
124
  fallback ports; do not assume the configured port is the one the MCP server bound.
125
125
 
126
+ ## Amp: connect the agent separately from the portal
127
+
128
+ PMX does not ship a dedicated Amp lifecycle adapter. Amp can use the native MCP tools;
129
+ the portal is only the human's browser UI. Configure MCP inside the orb or runner where
130
+ Amp executes, not only on the user's laptop. Merge this into that workspace's
131
+ `.amp/settings.json`, preserving other settings (Amp does not use the generic `.mcp.json`):
132
+
133
+ ```json
134
+ {
135
+ "amp.mcpServers": {
136
+ "canvas": {
137
+ "command": "bunx",
138
+ "args": ["pmx-canvas@0.7.3", "--mcp"],
139
+ "includeTools": ["canvas_*"],
140
+ "env": {
141
+ "PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
142
+ "PMX_CANVAS_PORT": "14313",
143
+ "PMX_CANVAS_URL": "",
144
+ "PMX_CANVAS_AGENT_SOURCE": "amp"
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ Replace root and port with the **existing portal service's actual workspace and local
152
+ listening port**. A supervised service may use an assigned `PORT`, not 4313 or 14313;
153
+ inspect its status/logs and `/health`. Do not put the public portal URL in the stdio
154
+ command or copy another test run's absolute paths. Use an absolute executable path if
155
+ the MCP process cannot find Bun. The `canvas_*` filter exposes the Canvas tool family.
156
+
157
+ ### Workspace trust is a human approval
158
+
159
+ Run `amp mcp doctor` from this workspace to distinguish `awaiting approval` from a
160
+ missing executable or connection failure. A new workspace MCP command requires the
161
+ user to review and approve it. Give the user this command to run in that workspace's
162
+ orb Terminal or runner terminal (replace `canvas` if the configured server name differs):
163
+
164
+ ```bash
165
+ amp mcp approve canvas
166
+ ```
167
+
168
+ The agent must not run this approval itself or move the command to global settings or
169
+ `--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
170
+ command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
171
+ After approval, reload MCP connections (`reload_mcp` in Amp) and require `connected`.
172
+ Discover `canvas` tools with `tool_search`; for the full unfiltered 0.7.3 server there
173
+ are 24 tools. If still absent, inspect the server status, command/PATH and target instead
174
+ of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
175
+
176
+ ### Verify native tools, then steering
177
+
178
+ 1. Check `/health` for the expected package version and canonical workspace. Through
179
+ Amp's discovered tools, read `canvas_board { action: "get" }` and
180
+ `canvas_query { action: "context" }`; verify the board matches the visible portal.
181
+ A CLI smoke check or SDK-spawned MCP client does not establish native Amp discovery.
182
+ 2. For a requested end-to-end check, use a disposable board. Pin a known card in the
183
+ portal and require a native context read to return that card as pinned. Use one
184
+ stable presence/steering identity (`amp` above, or a unique source per independent
185
+ agent); reuse any adapter-owned session instead of attaching a duplicate.
186
+ 3. Send a fresh browser message addressed to that identity. The running agent claims
187
+ it with `canvas_ax_delivery` using the same `consumer`, reads the relevant card,
188
+ signals intent, and performs the requested edit through native Canvas tools.
189
+ 4. Verify the visible result in the browser before marking the message delivered for
190
+ that consumer. A subsequent claim should omit it. Detach only test-owned sessions.
191
+
192
+ This proves an **active agent can poll and act**, not automatic wake-up of an idle Amp
193
+ thread, background polling, or automatic agent launch for imports. An MCP connection
194
+ does not install a steering pump. Only configure a pump/host integration when requested;
195
+ its startup backlog rules are in the main skill. Tool discovery and resource reads also
196
+ do not establish Amp support for native MCP Skills-extension discovery or activation.
197
+
126
198
  ## Verify the actual target before writes
127
199
 
128
200
  For the local target selected above:
@@ -139,6 +139,12 @@ Four ways a test silently stops discriminating in this repo:
139
139
  - For `graph`, `json-render`, `mcp-app`, webpage, and image nodes, API geometry is not enough.
140
140
  Verify the rendered browser frame when changing sizing: iframe/body `scrollHeight` and
141
141
  `scrollWidth` should fit the available frame unless scrolling is the intended behavior.
142
+ - For JSON focus transitions, edit a bound input and load a multi-item runtime list inline,
143
+ expand and verify both, edit/replace them in focus mode, then close and verify both again.
144
+ Exercise `src` and `srcdoc`, including AX-disabled JSON. No save, spec edit or reload belongs
145
+ between those assertions. Separately verify a real spec revision resets to authored state,
146
+ and that local drafts never entered the server spec. See `tests/e2e/json-render-forms.pw.ts`.
147
+ Eval definitions are not evidence of executing this browser regression or a model benchmark.
142
148
  - When checking embedded frame fit manually, start from a clean seeded state, rebuild stale bundles,
143
149
  and inspect the actual iframe document in a browser. Server dimensions can look correct while the
144
150
  embedded content is still clipped.
@@ -160,6 +166,49 @@ Four ways a test silently stops discriminating in this repo:
160
166
  - If browser tests fail after a client change, confirm the bundle was rebuilt and the server
161
167
  started from the updated code
162
168
 
169
+ ### Keep first-failure evidence before rerunning
170
+
171
+ Use a separate directory for each attempt, including non-browser probes. Save the exact
172
+ command, target/version, exit status, stdout and stderr; keep credentials out of logs.
173
+ Before rerunning Playwright, copy its failure traces/screenshots and results out of the
174
+ runner's overwritten output directory. Never overwrite an initial failure log with a rerun.
175
+ For a shell gate, capture its status even with `set -e`, for example:
176
+
177
+ ```bash
178
+ attempt_dir=$(mktemp -d /tmp/pmx-check.XXXXXX)
179
+ printf '%s\n' 'bun run test' > "$attempt_dir/command.txt"
180
+ status=0
181
+ bun run test > "$attempt_dir/stdout.log" 2> "$attempt_dir/stderr.log" || status=$?
182
+ printf '%s\n' "$status" > "$attempt_dir/exit-status.txt"
183
+ # Inspect this attempt before rerunning; return the gate's original exit status.
184
+ (exit "$status")
185
+ ```
186
+
187
+ Record what changed between attempts. Distinguish harness corrections, product fixes,
188
+ unchanged retries and host approval. A corrected pass does not make the original attempt
189
+ green. If logs were lost, say so; never reconstruct them or claim first-run success.
190
+
191
+ ## Native-host verification is separate from transport checks
192
+
193
+ Record source, packed-package, registry-package, portal and native-host results separately.
194
+ An SDK subprocess's MCP handshake/catalog is not evidence that Amp discovered tools.
195
+ For Amp setup/trust and the pin → read → addressed steer → edit → visible result → ack
196
+ procedure, follow the bundled [installation reference](../pmx-canvas/references/installing-pmx-canvas.md#amp-connect-the-agent-separately-from-the-portal).
197
+ MCP-only consumers should read `skill://pmx-canvas/references/installing-pmx-canvas.md`
198
+ directly with `resources/read`, not traverse outside the testing skill's resource tree.
199
+ Workspace trust approval belongs to the human; report a blocked native check until approved.
200
+ Include a different-recipient negative control for addressed delivery on a disposable board.
201
+ Verify the rendered edit before acknowledging; detach only test-owned sessions afterward.
202
+ Do not label active-agent polling as idle-thread wake-up, a persistent pump, or automatic
203
+ import-agent launch. Native MCP tools do not prove native Skills-extension discovery.
204
+
205
+ List unexercised surfaces explicitly: native Copilot/WebKit, Windows-native behavior,
206
+ physical mobile, OCR and Office extraction need their own evidence. Source adapter tests
207
+ or a source helper pointed at a published server do not establish installed-adapter execution.
208
+ Sampled resource digests are not a full-catalog audit; eval definitions are not scored model
209
+ results; a skipped mirror check is not validation. Do not change the product merely to
210
+ turn an untested surface into a claimed pass.
211
+
163
212
  ## Handoff Standard
164
213
 
165
214
  Before marking work done, report:
@@ -12,12 +12,23 @@ import {
12
12
  showCommandHelp,
13
13
  } from '../shared.js';
14
14
 
15
+ function backupArgs(command: string, args: string[], supported: string[]) {
16
+ const parsed = parseFlags(args, { boolFlags: ['off'] });
17
+ const allowed = new Set(['help', 'h', ...supported]);
18
+ const unknown = Object.keys(parsed.flags).find((flag) => !allowed.has(flag));
19
+ if (unknown) die(`Unknown ${command} flag: --${unknown}`, `Run: pmx-canvas ${command} --help`);
20
+ for (const name of ['to', 'keep', 'every']) {
21
+ if (parsed.flags[name] === true) die(`Missing value for --${name}`, `Run: pmx-canvas ${command} --help`);
22
+ }
23
+ return parsed;
24
+ }
25
+
15
26
  cmd(
16
27
  'backup',
17
28
  'Back up every board to a folder (safe while the server runs)',
18
29
  ['pmx-canvas backup', 'pmx-canvas backup --to ~/Backups/pmx --keep 14'],
19
30
  async (args) => {
20
- const { flags } = parseFlags(args);
31
+ const { flags } = backupArgs('backup', args, ['to', 'keep']);
21
32
  if (flags.help || flags.h) return showCommandHelp('backup');
22
33
  const to = getStringFlag(flags, 'to');
23
34
  const keep = optionalNumberFlag(flags, 'keep', 'pmx-canvas backup --keep <n>');
@@ -26,7 +37,7 @@ cmd(
26
37
  );
27
38
 
28
39
  cmd('backup status', 'Show the backup folder, schedule and backups', ['pmx-canvas backup status'], async (args) => {
29
- const { flags } = parseFlags(args);
40
+ const { flags } = backupArgs('backup status', args, []);
30
41
  if (flags.help || flags.h) return showCommandHelp('backup status');
31
42
  output(await invokeOperation('backup.status', {}));
32
43
  });
@@ -36,7 +47,7 @@ cmd(
36
47
  'Back up on a schedule, run by the server',
37
48
  ['pmx-canvas backup schedule --every 24h --keep 14 --to ~/Backups/pmx', 'pmx-canvas backup schedule --off'],
38
49
  async (args) => {
39
- const { flags } = parseFlags(args);
50
+ const { flags } = backupArgs('backup schedule', args, ['every', 'off', 'to', 'keep']);
40
51
  if (flags.help || flags.h) return showCommandHelp('backup schedule');
41
52
  const every = flags.off ? 'off' : getStringFlag(flags, 'every');
42
53
  if (!every) die('Usage: pmx-canvas backup schedule --every <30m|24h|7d> [--keep <n>] [--to <folder>] | --off');
@@ -57,7 +68,7 @@ cmd(
57
68
  'Replace every board with a backup (the current file is kept as canvas.db.before-restore)',
58
69
  ['pmx-canvas restore ~/Backups/pmx/canvas-20260927T090000000Z.db'],
59
70
  async (args) => {
60
- const { positional, flags } = parseFlags(args);
71
+ const { positional, flags } = backupArgs('restore', args, []);
61
72
  if (flags.help || flags.h) return showCommandHelp('restore');
62
73
  const file = positional[0];
63
74
  if (!file) die('Usage: pmx-canvas restore <backup-file>');
@@ -1,5 +1,4 @@
1
- // Board commands (plan 012): board list, board create, board rename, board category.
2
- // Opening, switching and deleting boards is the human's, in the workbench.
1
+ // Board commands. Opening changes the shared workbench; deletion remains human-only.
3
2
 
4
3
  import { cmd, die, getStringFlag, invokeOperation, output, parseFlags, showCommandHelp } from '../shared.js';
5
4
 
@@ -9,6 +8,20 @@ cmd('board list', 'List boards, most recently opened first', ['pmx-canvas board
9
8
  output(await invokeOperation('board.list', {}));
10
9
  });
11
10
 
11
+ cmd(
12
+ 'board open',
13
+ 'Open a board in the shared workbench, or return Home with --home',
14
+ ['pmx-canvas board open <board-id>', 'pmx-canvas board open --home'],
15
+ async (args) => {
16
+ const { positional, flags } = parseFlags(args, { boolFlags: ['home'] });
17
+ if (flags.help || flags.h) return showCommandHelp('board open');
18
+ if (positional.length > 1 || (flags.home ? positional.length !== 0 : !positional[0])) {
19
+ die('Usage: pmx-canvas board open <board-id> | --home');
20
+ }
21
+ output(await invokeOperation('board.open', { id: flags.home ? null : positional[0] }));
22
+ },
23
+ );
24
+
12
25
  cmd(
13
26
  'board from',
14
27
  'Preview or create a board from selected cards on another board',
@@ -35,7 +48,7 @@ cmd(
35
48
 
36
49
  cmd(
37
50
  'board create',
38
- 'Create an empty board (the human opens it in the workbench)',
51
+ 'Create an empty board (use board open with its returned id before writing)',
39
52
  ['pmx-canvas board create "Q4 planning"', 'pmx-canvas board create "Q4 planning" --category Planning'],
40
53
  async (args) => {
41
54
  const { positional, flags } = parseFlags(args);
@@ -5,6 +5,7 @@ import {
5
5
  closeAttentionHistory,
6
6
  openAttentionHistory,
7
7
  } from '../state/attention-store';
8
+ import { IconClose } from '../icons';
8
9
 
9
10
  function formatTimestamp(timestamp: number): string {
10
11
  return new Date(timestamp).toLocaleTimeString([], {
@@ -73,7 +74,7 @@ export function AttentionHistory() {
73
74
  aria-label="Collapse changes panel"
74
75
  title="Collapse"
75
76
  >
76
- ×
77
+ <IconClose />
77
78
  </button>
78
79
  </div>
79
80
  <div class="attention-history-list">
@@ -1,7 +1,7 @@
1
1
  import { render, type VNode } from 'preact';
2
2
  import { useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
3
3
  import { activeBoard, activeBoardId, boardList, boardsLoaded, openBoard } from '../state/boards-store';
4
- import { IconLogo, IconArrange } from '../icons';
4
+ import { IconLogo, IconArrange, IconChevronDown, IconPlus } from '../icons';
5
5
  import { promptNewBoard } from './HomeView';
6
6
 
7
7
  const RECENT_LIMIT = 8;
@@ -76,7 +76,7 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
76
76
  >
77
77
  <span class="board-switcher-name">{label}</span>
78
78
  <span class="board-switcher-caret" aria-hidden="true">
79
- ▾
79
+ <IconChevronDown />
80
80
  </span>
81
81
  </button>
82
82
  {open && anchor && (
@@ -130,7 +130,7 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
130
130
  }}
131
131
  >
132
132
  <span class="board-switcher-icon" aria-hidden="true">
133
- +
133
+ <IconPlus size={18} />
134
134
  </span>
135
135
  New board…
136
136
  </button>