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.
- package/.github/extensions/pmx-canvas/extension.mjs +79 -61
- package/CHANGELOG.md +29 -1
- package/Readme.md +48 -9
- package/dist/canvas/global.css +144 -45
- package/dist/canvas/index.js +90 -90
- package/dist/json-render/index.js +55 -55
- package/dist/types/client/canvas/ChoiceMenu.d.ts +13 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
- package/dist/types/client/icons.d.ts +15 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +0 -12
- package/dist/types/client/nodes/McpAppNode.d.ts +2 -1
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -0
- package/dist/types/client/state/canvas-store.d.ts +2 -0
- package/dist/types/client/state/intent-bridge.d.ts +1 -1
- package/dist/types/json-render/renderer/index.d.ts +2 -1
- package/dist/types/json-render/server.d.ts +2 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +2 -0
- package/dist/types/shared/delimited-text.d.ts +14 -0
- package/docs/ax-host-adapter-contract.md +13 -2
- package/docs/cli.md +9 -2
- package/docs/http-api.md +12 -8
- package/docs/mcp.md +8 -1
- package/docs/sdk.md +2 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +41 -16
- package/skills/pmx-canvas/evals/evals.json +112 -2
- package/skills/pmx-canvas/references/full-reference.md +9 -4
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +76 -4
- package/skills/pmx-canvas-testing/SKILL.md +49 -0
- package/src/cli/commands/backup.ts +15 -4
- package/src/cli/commands/boards.ts +16 -3
- package/src/client/canvas/AttentionHistory.tsx +2 -1
- package/src/client/canvas/BoardSwitcher.tsx +3 -3
- package/src/client/canvas/CanvasNode.tsx +211 -197
- package/src/client/canvas/CanvasViewport.tsx +6 -0
- package/src/client/canvas/ChoiceMenu.tsx +122 -0
- package/src/client/canvas/CommandBar.tsx +27 -25
- package/src/client/canvas/ContextPinBar.tsx +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/canvas/ExternalWriters.tsx +4 -3
- package/src/client/canvas/FeedbackDialog.tsx +12 -9
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/SelectionBar.tsx +2 -2
- package/src/client/canvas/SessionPanel.tsx +6 -4
- package/src/client/canvas/SessionReceipt.tsx +2 -1
- package/src/client/canvas/SnapshotPanel.tsx +3 -2
- package/src/client/canvas/ToolRail.tsx +48 -100
- package/src/client/canvas/TopBar.tsx +2 -2
- package/src/client/icons.tsx +118 -1
- package/src/client/nodes/BoardNode.tsx +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +3 -1
- package/src/client/nodes/FileNode.tsx +4 -78
- package/src/client/nodes/ImageNode.tsx +2 -1
- package/src/client/nodes/MarkdownNode.tsx +2 -1
- package/src/client/nodes/McpAppNode.tsx +16 -0
- package/src/client/nodes/PromptNode.tsx +3 -1
- package/src/client/nodes/use-ax-surface-bridge.ts +4 -2
- package/src/client/nodes/viewer-runtime-state.ts +78 -0
- package/src/client/state/canvas-store.ts +3 -0
- package/src/client/state/intent-bridge.ts +14 -2
- package/src/client/theme/global.css +144 -45
- package/src/json-render/renderer/index.tsx +58 -38
- package/src/json-render/server.ts +8 -1
- package/src/mcp/server.ts +2 -2
- package/src/server/agent-presence.ts +17 -11
- package/src/server/board-export.ts +97 -32
- package/src/server/index.ts +9 -7
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-presence.ts +7 -3
- package/src/server/operations/ops/boards.ts +12 -5
- package/src/server/operations/registry.ts +4 -13
- package/src/server/server.ts +13 -4
- package/src/shared/ax-surface-protocol.ts +3 -0
- 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
|
|
434
|
-
>
|
|
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,
|
|
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 `
|
|
142
|
-
`
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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
|
|
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 (
|
|
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>
|