pmx-canvas 0.7.3 → 0.8.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 (235) hide show
  1. package/.github/extensions/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  2. package/.github/extensions/pmx-canvas/extension.mjs +25 -172
  3. package/.github/extensions/pmx-canvas/mcp-bootstrap.ts +198 -0
  4. package/.github/extensions/pmx-canvas/runtime.d.mts +41 -0
  5. package/.github/extensions/pmx-canvas/runtime.json +3 -0
  6. package/.github/extensions/pmx-canvas/runtime.mjs +194 -0
  7. package/.github/extensions/pmx-canvas/start-mcp.mjs +51 -0
  8. package/.github/extensions/pmx-canvas/tools.json +2560 -0
  9. package/CHANGELOG.md +47 -0
  10. package/Readme.md +21 -7
  11. package/dist/canvas/fonts/OFL.txt +93 -0
  12. package/dist/canvas/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  13. package/dist/canvas/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  14. package/dist/canvas/fonts/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  15. package/dist/canvas/fonts/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  16. package/dist/canvas/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  17. package/dist/canvas/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  18. package/dist/canvas/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  19. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-400-normal.woff2 +0 -0
  20. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-500-normal.woff2 +0 -0
  21. package/dist/canvas/fonts/ibm-plex-sans-latin-ext-600-normal.woff2 +0 -0
  22. package/dist/canvas/global.css +723 -672
  23. package/dist/canvas/index.js +90 -88
  24. package/dist/canvas/mcp-app.html +7878 -0
  25. package/dist/canvas/mermaid-entry.js +1 -1
  26. package/dist/canvas/surface-theme.css +151 -46
  27. package/dist/json-render/index.css +1 -1
  28. package/dist/types/cli/commands/record.d.ts +1 -1
  29. package/dist/types/client/canvas/NodeContextMark.d.ts +24 -0
  30. package/dist/types/client/canvas/SessionReceipt.d.ts +3 -1
  31. package/dist/types/client/icons.d.ts +1 -2
  32. package/dist/types/client/mcp-app/index.d.ts +1 -0
  33. package/dist/types/client/nodes/ax-node-actions.d.ts +2 -2
  34. package/dist/types/client/nodes/markdown-sanitize.d.ts +2 -0
  35. package/dist/types/client/nodes/use-workbench-asset.d.ts +2 -0
  36. package/dist/types/client/state/attention-store.d.ts +0 -5
  37. package/dist/types/client/state/context-status-store.d.ts +53 -0
  38. package/dist/types/client/state/intent-bridge.d.ts +3 -0
  39. package/dist/types/client/state/near-pin-store.d.ts +16 -0
  40. package/dist/types/client/state/presence-store.d.ts +5 -0
  41. package/dist/types/client/state/session-store.d.ts +33 -3
  42. package/dist/types/client/state/workbench-transport.d.ts +16 -0
  43. package/dist/types/client/theme/tokens.d.ts +1 -3
  44. package/dist/types/client/types.d.ts +9 -0
  45. package/dist/types/json-render/server.d.ts +1 -1
  46. package/dist/types/mcp/app-server.d.ts +3 -0
  47. package/dist/types/mcp/canvas-access.d.ts +1 -1
  48. package/dist/types/mcp/canvas-app.d.ts +4 -0
  49. package/dist/types/mcp/workbench-app.d.ts +4 -0
  50. package/dist/types/server/agent-presence.d.ts +1 -1
  51. package/dist/types/server/ax-state.d.ts +1 -1
  52. package/dist/types/server/canvas-db.d.ts +8 -0
  53. package/dist/types/server/canvas-geometry.d.ts +25 -0
  54. package/dist/types/server/canvas-operations.d.ts +1 -11
  55. package/dist/types/server/canvas-state.d.ts +25 -3
  56. package/dist/types/server/context-brief.d.ts +8 -1
  57. package/dist/types/server/context-reads.d.ts +30 -7
  58. package/dist/types/server/index.d.ts +6 -1
  59. package/dist/types/server/mcp-app-runtime.d.ts +1 -0
  60. package/dist/types/server/operations/http.d.ts +2 -0
  61. package/dist/types/server/operations/invoker.d.ts +4 -2
  62. package/dist/types/server/operations/ops/nodes.d.ts +0 -6
  63. package/dist/types/server/server.d.ts +1 -0
  64. package/dist/types/server/spatial-analysis.d.ts +2 -0
  65. package/dist/types/shared/agent-context.d.ts +22 -0
  66. package/dist/types/shared/agent-presence.d.ts +3 -10
  67. package/dist/types/shared/canvas-app.d.ts +54 -0
  68. package/dist/types/shared/themes.d.ts +1 -1
  69. package/docs/RELEASE.md +7 -3
  70. package/docs/agent-plugin.md +123 -0
  71. package/docs/ax-state-contract.md +40 -0
  72. package/docs/cli.md +7 -2
  73. package/docs/design/AgentContext.dc.html +186 -0
  74. package/docs/design/Before.dc.html +208 -0
  75. package/docs/design/BoardPins.dc.html +151 -0
  76. package/docs/design/Chrome.dc.html +208 -0
  77. package/docs/design/Context.dc.html +239 -0
  78. package/docs/design/Flows.dc.html +181 -0
  79. package/docs/design/Home.dc.html +209 -0
  80. package/docs/design/HomeGraph.dc.html +211 -0
  81. package/docs/design/HomeMap.dc.html +117 -0
  82. package/docs/design/HomeTeam.dc.html +176 -0
  83. package/docs/design/Light.dc.html +28 -0
  84. package/docs/design/Main.dc.html +280 -0
  85. package/docs/design/NearPin.dc.html +260 -0
  86. package/docs/design/Pane600.dc.html +120 -0
  87. package/docs/design/Relations.dc.html +297 -0
  88. package/docs/design/Themes.dc.html +121 -0
  89. package/docs/design/TokenSystem.dc.html +136 -0
  90. package/docs/design/Tokens.dc.html +163 -0
  91. package/docs/design/Types1.dc.html +177 -0
  92. package/docs/design/Types2.dc.html +226 -0
  93. package/docs/design/Types3.dc.html +195 -0
  94. package/docs/design/Uses.dc.html +152 -0
  95. package/docs/design/Vision.dc.html +116 -0
  96. package/docs/design/Zoom.dc.html +82 -0
  97. package/docs/design/canvas.json +274 -0
  98. package/docs/design.md +214 -0
  99. package/docs/environment.md +1 -1
  100. package/docs/http-api.md +80 -15
  101. package/docs/mcp.md +198 -3
  102. package/docs/node-types.md +34 -10
  103. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  104. package/docs/plans/plan-015-design-foundation-wave-0.md +177 -0
  105. package/docs/plans/plan-016-context-made-visible-wave-1.md +148 -0
  106. package/docs/product-vision-2026-09.md +47 -7
  107. package/docs/screenshot.png +0 -0
  108. package/docs/sdk.md +1 -0
  109. package/package.json +10 -4
  110. package/plugins/pmx-canvas/LICENSE +21 -0
  111. package/plugins/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  112. package/plugins/pmx-canvas/assets/preview.png +0 -0
  113. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +930 -0
  114. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +3 -0
  115. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.mjs +194 -0
  116. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/steering-delivery.mjs +79 -0
  117. package/plugins/pmx-canvas/mcp.json +12 -0
  118. package/plugins/pmx-canvas/plugin.json +24 -0
  119. package/plugins/pmx-canvas/runtime.json +3 -0
  120. package/plugins/pmx-canvas/runtime.mjs +194 -0
  121. package/plugins/pmx-canvas/start-mcp.mjs +51 -0
  122. package/plugins/pmx-canvas/tools.json +2560 -0
  123. package/skills/pmx-canvas/SKILL.md +10 -7
  124. package/skills/pmx-canvas/references/full-reference.md +11 -7
  125. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +20 -8
  126. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  127. package/skills/pmx-canvas-testing/SKILL.md +5 -4
  128. package/src/cli/commands/copilot.ts +5 -0
  129. package/src/cli/commands/view.ts +1 -1
  130. package/src/cli/index.ts +8 -3
  131. package/src/client/App.tsx +6 -1
  132. package/src/client/canvas/AgentPresenceLayer.tsx +3 -3
  133. package/src/client/canvas/AnnotationLayer.tsx +1 -1
  134. package/src/client/canvas/CanvasNode.tsx +32 -49
  135. package/src/client/canvas/CanvasViewport.tsx +0 -2
  136. package/src/client/canvas/CommandPalette.tsx +3 -2
  137. package/src/client/canvas/ContextMenu.tsx +11 -0
  138. package/src/client/canvas/EdgeLayer.tsx +1 -1
  139. package/src/client/canvas/ExportDialog.tsx +33 -6
  140. package/src/client/canvas/IntentLayer.tsx +2 -2
  141. package/src/client/canvas/Minimap.tsx +12 -7
  142. package/src/client/canvas/NodeContextMark.tsx +156 -0
  143. package/src/client/canvas/SessionPanel.tsx +4 -7
  144. package/src/client/canvas/SessionReceipt.tsx +114 -17
  145. package/src/client/canvas/ShortcutOverlay.tsx +6 -1
  146. package/src/client/canvas/SnapshotPanel.tsx +11 -2
  147. package/src/client/canvas/ToolRail.tsx +12 -9
  148. package/src/client/canvas/TopBar.tsx +16 -11
  149. package/src/client/icons.tsx +2 -12
  150. package/src/client/mcp-app/index.tsx +137 -0
  151. package/src/client/mcp-app/style.css +10 -0
  152. package/src/client/nodes/AttachmentNode.tsx +8 -1
  153. package/src/client/nodes/AxStepControls.tsx +1 -1
  154. package/src/client/nodes/ContextNode.tsx +9 -9
  155. package/src/client/nodes/ExtAppFrame.tsx +13 -8
  156. package/src/client/nodes/FileNode.tsx +8 -5
  157. package/src/client/nodes/ImageNode.tsx +4 -2
  158. package/src/client/nodes/InlineMarkdownEditor.tsx +2 -1
  159. package/src/client/nodes/LedgerNode.tsx +2 -2
  160. package/src/client/nodes/MarkdownNode.tsx +44 -9
  161. package/src/client/nodes/McpAppNode.tsx +6 -4
  162. package/src/client/nodes/PromptNode.tsx +5 -17
  163. package/src/client/nodes/ResponseNode.tsx +3 -15
  164. package/src/client/nodes/StatusNode.tsx +2 -2
  165. package/src/client/nodes/StatusSummary.tsx +3 -1
  166. package/src/client/nodes/TraceNode.tsx +6 -4
  167. package/src/client/nodes/WebpageNode.tsx +1 -1
  168. package/src/client/nodes/ax-node-actions.ts +2 -2
  169. package/src/client/nodes/iframe-document-url.ts +3 -2
  170. package/src/client/nodes/markdown-sanitize.ts +10 -0
  171. package/src/client/nodes/surface-url.ts +10 -0
  172. package/src/client/nodes/use-surface-frame.ts +3 -1
  173. package/src/client/nodes/use-workbench-asset.ts +26 -0
  174. package/src/client/state/attention-bridge.ts +0 -8
  175. package/src/client/state/attention-store.ts +0 -17
  176. package/src/client/state/canvas-store.ts +4 -2
  177. package/src/client/state/context-status-store.ts +66 -0
  178. package/src/client/state/human-store.ts +2 -0
  179. package/src/client/state/iframe-mode.ts +5 -0
  180. package/src/client/state/intent-bridge.ts +10 -4
  181. package/src/client/state/near-pin-store.ts +53 -0
  182. package/src/client/state/presence-store.ts +5 -10
  183. package/src/client/state/session-store.ts +106 -9
  184. package/src/client/state/sse-bridge.ts +26 -7
  185. package/src/client/state/theme-override.ts +1 -1
  186. package/src/client/state/workbench-transport.ts +36 -0
  187. package/src/client/theme/global.css +723 -672
  188. package/src/client/theme/surface-theme.css +151 -46
  189. package/src/client/theme/tokens.ts +7 -11
  190. package/src/client/types.ts +10 -0
  191. package/src/json-render/server.ts +71 -14
  192. package/src/mcp/app-server.ts +64 -0
  193. package/src/mcp/canvas-access.ts +14 -11
  194. package/src/mcp/canvas-app.ts +318 -0
  195. package/src/mcp/server.ts +1 -1
  196. package/src/mcp/workbench-app.ts +211 -0
  197. package/src/server/agent-presence.ts +45 -6
  198. package/src/server/ax-context.ts +1 -1
  199. package/src/server/ax-state.ts +1 -1
  200. package/src/server/board-export.ts +5 -2
  201. package/src/server/canvas-db.ts +46 -12
  202. package/src/server/canvas-geometry.ts +64 -0
  203. package/src/server/canvas-operations.ts +6 -24
  204. package/src/server/canvas-state.ts +105 -15
  205. package/src/server/canvas-validation.ts +19 -4
  206. package/src/server/context-brief.ts +39 -2
  207. package/src/server/context-reads.ts +86 -15
  208. package/src/server/demo-state.json +31 -31
  209. package/src/server/html-surface.ts +4 -5
  210. package/src/server/index.ts +19 -3
  211. package/src/server/mcp-app-runtime.ts +11 -0
  212. package/src/server/operations/composites.ts +4 -3
  213. package/src/server/operations/http.ts +4 -4
  214. package/src/server/operations/invoker.ts +13 -4
  215. package/src/server/operations/ops/ax-read.ts +1 -1
  216. package/src/server/operations/ops/ax-timeline.ts +37 -0
  217. package/src/server/operations/ops/boards.ts +1 -1
  218. package/src/server/operations/ops/canvas-wire.ts +3 -6
  219. package/src/server/operations/ops/intent.ts +31 -2
  220. package/src/server/operations/ops/json-render.ts +1 -1
  221. package/src/server/operations/ops/nodes.ts +8 -76
  222. package/src/server/operations/ops/query.ts +8 -2
  223. package/src/server/operations/ops/snapshots.ts +4 -1
  224. package/src/server/operations/ops/viewport.ts +29 -5
  225. package/src/server/operations/registry.ts +38 -4
  226. package/src/server/scope-fence.ts +55 -10
  227. package/src/server/server.ts +83 -5
  228. package/src/server/spatial-analysis.ts +2 -2
  229. package/src/server/workboard.ts +6 -1
  230. package/src/{server → shared}/agent-context.ts +2 -2
  231. package/src/shared/agent-presence.ts +3 -15
  232. package/src/shared/canvas-app.ts +62 -0
  233. package/src/shared/themes.ts +5 -5
  234. package/src/client/canvas/FocusFieldLayer.tsx +0 -116
  235. package/src/client/canvas/kind-colors.ts +0 -23
@@ -20,7 +20,8 @@ At the start of board work, read `canvas://context?consumer=<stable-unique-id>`
20
20
  (or `canvas_query { action: "context", consumer: "<stable-unique-id>" }`) before
21
21
  falling back to broader resources. The budget is UTF-16 code units, not tokens.
22
22
  Use one stable consumer ID per agent/adapter only when durable deltas are wanted;
23
- omit it for a fresh read. Pins are highest priority. If a source card is clipped,
23
+ omit it for a fresh read. Pins are highest priority, then the cards near each pin
24
+ (reason `near`, title + short summary only). If a source card is clipped or near,
24
25
  pull that card explicitly by its returned board/card IDs.
25
26
 
26
27
  When authoring a board, establish its README without waiting for the human to
@@ -43,7 +44,7 @@ for the authoritative stop, viewport, persistence, reset, group-order, and captu
43
44
 
44
45
  ## Runtime prerequisites
45
46
 
46
- PMX Canvas 0.7.3 requires **Bun >=1.4.2**, including when installed through npm.
47
+ PMX Canvas 0.8.0 requires **Bun >=1.4.2**, including when installed through npm.
47
48
  Check `bun --version` and ensure Bun is on the MCP host's PATH, not just your shell's;
48
49
  use an absolute executable path if needed. Install/configure only when requested.
49
50
  For pinned installation, MCP configuration, managed services, and disposable verification,
@@ -116,7 +117,8 @@ the served catalog is a startup snapshot. Local mirrors still use `skills sync`
116
117
  `canvas_view { action: "fit", nodeIds: [...] }` with exactly the new ids. See
117
118
  **In-View Placement & Sizing** below — auto-placement is board-relative, not camera-relative.
118
119
  8. **Verify context pins.** Pin with `canvas_pin_nodes` or the browser's **Pin as context**, then
119
- read `canvas://pinned-context`.
120
+ read `canvas://pinned-context`. When you pin, pass a short `reason` — the human sees who pinned a
121
+ node and why.
120
122
  9. **Clean up temporary nodes.** Remove retry/test fixtures and restore the baseline snapshot when
121
123
  the task requires leaving the board unchanged.
122
124
 
@@ -219,7 +221,7 @@ always authoritative, independent of the installed release version.
219
221
  | `canvas_ax_state` | `get`, `set-focus`, `set-policy`, `report-capability`, `presence`, `set-presence` |
220
222
  | `canvas_ax_work` | `add`, `update`, `annotate` |
221
223
  | `canvas_ax_gate` | `request`, `resolve`, `await` with `approval`, `elicitation`, or `mode` |
222
- | `canvas_ax_timeline` | `read`, `record-event`, `add-evidence`, `send-steering`, `reads` |
224
+ | `canvas_ax_timeline` | `read`, `record-event`, `add-evidence`, `send-steering`, `reads`, `read-status` |
223
225
  | `canvas_ax_delivery` | `claim`, `mark` |
224
226
  | `canvas_board` | `list`, `get`, `create`, `create-from`, `open`, `update` |
225
227
  | `canvas_snapshot` | `save`, `list`, `restore`, `delete`, `gc`, `diff` |
@@ -303,8 +305,9 @@ reports any node below its floor as an advisory `sizeWarnings` entry — treat a
303
305
  as layout work left to do.
304
306
  Creation responses may include `sizeAdjustment` (`defaulted`, `clamped-to-minimum`, or
305
307
  `fit-to-children`); use its `applied` geometry rather than assuming the request won. Json-render
306
- validation/creation may also return warnings for form controls whose value is not bound with
307
- `$bindState`; the panel can render, but edits do not update its runtime state until the binding is fixed.
308
+ validation/creation binds a literal form-control `value` to `state.form` automatically; it still
309
+ warns for `$state` expressions, controls inside a repeat, or a non-object `state.form`, which render
310
+ but do not update runtime state until bound with `$bindState`/`$bindItem`.
308
311
  Bound JSON input and dynamic lists are local UI state, not saved canvas data. Focus-mode
309
312
  expand/close preserves that state for the same spec in the current tab; an authored spec update,
310
313
  board switch or page reload resets it. Persist important edits explicitly through the app's
@@ -456,7 +459,7 @@ Use the visible workbench when the human is actively curating layout:
456
459
  - Embedding hosts can open `/workbench?theme=<name>` (or `?theme=auto` to follow the host's
457
460
  light/dark appearance) for a session-local default theme that never changes the server-global
458
461
  theme other clients see; an explicit pick from the theme menu ends the override. The bundled
459
- Copilot extension opens its panel with `?theme=light`.
462
+ Copilot extension opens its panel with `?theme=daylight`.
460
463
 
461
464
  After changing files under `src/client/`, rebuild with `bun run build` before manual browser
462
465
  verification.
@@ -128,11 +128,11 @@ pmx-canvas # Start and open browser (port 4313)
128
128
  pmx-canvas --no-open # Start without opening browser (for agents)
129
129
  pmx-canvas --port=8080 # Custom port
130
130
  pmx-canvas --demo # Start with sample content
131
- pmx-canvas --theme=light # Light theme
131
+ pmx-canvas --theme=daylight # Daylight theme
132
132
  pmx-canvas --version # Print installed version and exit
133
133
  ```
134
134
 
135
- `--theme` accepts `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the rail's theme picker lists the same nine). Same value can be set via
135
+ `--theme` accepts `harbor` (default), `daylight`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the rail's theme picker lists the same nine). Same value can be set via
136
136
  the `PMX_CANVAS_THEME` environment variable, or picked live from the left tool rail.
137
137
 
138
138
  Start the canvas once per session, then reuse it. Use `--no-open` when running as an agent — the
@@ -452,7 +452,7 @@ single-purpose tools behind an `action` (and, for `canvas_ax_gate`, a `kind`) di
452
452
  | `canvas_ax_state` | `get` · `set-focus` · `set-policy` · `report-capability` · `presence` · `set-presence` | Read AX state; set AX focus; patch tool/prompt policy (the scope fence is human-set, read-only here); report host capability; read / set agent presence (phase, cursor, focus, attached session) |
453
453
  | `canvas_ax_work` | `add` · `update` · `annotate` | Canvas-bound work items + review annotations |
454
454
  | `canvas_ax_gate` | `request` · `resolve` · `await` × `kind` `approval` \| `elicitation` \| `mode` | Attributed gate lifecycle (request → await → resolve); attribution does not itself grant authority |
455
- | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` · `reads` | The bounded AX diagnostics timeline, and the context read log (`reads`) |
455
+ | `canvas_ax_timeline` | `read` · `record-event` · `add-evidence` · `send-steering` · `reads` · `read-status` | The bounded AX diagnostics timeline, and the context read log (`reads`) |
456
456
  | `canvas_ax_delivery` | `claim` · `mark` | Adapterless steering delivery (claim → act → mark) |
457
457
  | `canvas_intent` | `signal` · `update` · `clear` | Ghost Cursor of Intent — announce a move before making it |
458
458
 
@@ -508,7 +508,7 @@ coming and can veto it mid-thought. Intents are ephemeral presence: never
508
508
  persisted, auto-expiring (~8s), never in `canvas_query layout`.
509
509
 
510
510
  Narrate → linked mutation → automatic settle:
511
- 1. `canvas_intent { action: "signal", kind: "create", position: { x, y }, nodeType: "markdown", label: "Add evidence", reason: "capturing the failing test", confidence: 0.8 }` → returns `intent.id`.
511
+ 1. `canvas_intent { action: "signal", kind: "create", nodeType: "markdown", label: "Add evidence", reason: "capturing the failing test", confidence: 0.8 }` → returns `intent.id` and the `position` the canvas chose.
512
512
  2. Make the real move and pass that id: `canvas_node { action: "add", intentId: <intent.id>, ... }`.
513
513
  3. PMX rejects the mutation if the intent was vetoed or expired; otherwise the
514
514
  successful mutation settles the ghost into the resulting node automatically.
@@ -522,9 +522,13 @@ mcp-app) and `canvas_webview` do **not** accept an `intentId` and reject it with
522
522
  400 — to telegraph one of those, signal a ghost, then `clear` it (or let it
523
523
  expire) and run the open *without* an `intentId`.
524
524
 
525
- Per kind, pass the anchor it renders against: `position` for `create`/`move`,
526
- `nodeId` for `move`/`edit`/`remove`, `edge: { from, to, type }` for `connect`. The
527
- payoff is **legibility** — `reason` is shown beneath the ghost.
525
+ Per kind, pass the anchor it renders against: `position` for `move` (optional for
526
+ `create`), `nodeId` for `move`/`edit`/`remove`, `edge: { from, to, type }` for
527
+ `connect`. **Omit `position` on a create unless you know where the node goes:** the
528
+ canvas then forms the ghost where it will auto-place the node, keeps staged ghosts
529
+ from stacking, and lands the linked create on its ghost. Never pass a placeholder
530
+ such as `{ x: 0, y: 0 }`. The payoff is **legibility** — `reason` is shown beneath
531
+ the ghost.
528
532
 
529
533
  **When to use vs skip.** Signal for adds, removes, and moves of visible nodes;
530
534
  connecting nodes; creating groups; layout reorganizations; meaningful title/content
@@ -4,6 +4,10 @@ Use this reference when PMX Canvas is running inside the GitHub Copilot app as a
4
4
  extension. The adapter is intentionally thin: PMX Canvas remains the state owner, and the extension
5
5
  maps Copilot SDK features onto PMX AX primitives.
6
6
 
7
+ The [standalone agent plugin](../../../docs/agent-plugin.md) bundles this adapter and PMX's
8
+ MCP launcher. Its native canvas is Copilot-only; other Agent Plugins-compatible clients use
9
+ the portable MCP tools and browser workbench.
10
+
7
11
  ## Adapter Identity
8
12
 
9
13
  - Extension path: `.github/extensions/pmx-canvas/extension.mjs`
@@ -17,9 +21,9 @@ maps Copilot SDK features onto PMX AX primitives.
17
21
  packaged extension directory into the repository:
18
22
  `mkdir -p .github/extensions && cp -R node_modules/pmx-canvas/.github/extensions/pmx-canvas .github/extensions/`
19
23
  2. Reload Copilot app extensions with `extensions_reload` so `project:pmx-canvas` is registered.
20
- 3. Start or confirm a PMX Canvas daemon for the workspace: `pmx-canvas serve --daemon`
21
- and `pmx-canvas serve status`. The adapter can auto-start in many local sessions, but a running
22
- daemon is the most reliable setup for fresh agents.
24
+ 3. The adapter discovers a matching workspace daemon or starts the pinned package version
25
+ in `runtime.json` with Bun. It needs Bun on `PATH`, not a global `pmx-canvas` executable.
26
+ The shared daemon stays running when the extension exits.
23
27
  4. Open the canvas with `extensionId: "project:pmx-canvas"`, `canvasId: "pmx-canvas"`, and a stable
24
28
  `instanceId`.
25
29
  5. If the first `invoke_canvas_action` immediately after `open_canvas` returns
@@ -66,6 +70,8 @@ end it after each task. Explicit agent IDs are reserved for genuinely independen
66
70
  The Copilot app webview can leave nested `srcdoc` and `blob:` iframes blank, so PMX should route
67
71
  generated frame HTML through `/api/canvas/frame-documents/...` instead.
68
72
  - Connects to a matching local PMX server for the current workspace, or starts one when needed.
73
+ - Uses the session's working directory for startup, context, presence, and steering; Copilot's
74
+ `workspacePath` points to session artifacts and is not the project root.
69
75
  - Reads `/api/canvas/ax/context` and injects pinned/focused context from
70
76
  `onUserPromptSubmitted`.
71
77
  - Exposes adapter actions for status, AX context refresh, AX focus, and explicit session steering.
@@ -137,16 +143,22 @@ All fields are optional:
137
143
  }
138
144
  ```
139
145
 
140
- Default discovery order:
146
+ Default discovery:
141
147
 
142
- 1. `serverUrl` input.
143
- 2. `PMX_CANVAS_URL`.
144
- 3. `PMX_CANVAS_PORT` / `PMX_WEB_CANVAS_PORT` / `4313` on loopback.
145
- 4. Managed server startup for the current workspace when `autoStart` is not `false`.
148
+ 1. An explicit `serverUrl` input or `PMX_CANVAS_URL`, without silently choosing another target.
149
+ 2. Workspace daemon pid-file ports, the preferred `PMX_CANVAS_PORT` / `PMX_WEB_CANVAS_PORT`,
150
+ and the normal MCP fallback ports, verified against the workspace's real path.
151
+ 3. Serialized daemon startup for the session working directory when `autoStart` is not `false`.
146
152
 
147
153
  The adapter rejects an unrelated running PMX server unless `allowWorkspaceMismatch` is true,
148
154
  including when `serverUrl` is explicit.
149
155
 
156
+ To package this adapter in another plugin, copy `extension.mjs`, `steering-delivery.mjs`,
157
+ `runtime.mjs`, and `runtime.json`. For the portable MCP connector, also copy `start-mcp.mjs`,
158
+ `tools.json`, and `THIRD_PARTY_LICENSES.txt`, and invoke `start-mcp.mjs` with Bun. The agent calls
159
+ `canvas_connect_workspace` with its absolute project directory before using board tools;
160
+ plugin initialization must never adopt the installation directory as a project.
161
+
150
162
  If discovery fails, the panel shows a connection page, not a partial workbench. It checks the
151
163
  original target automatically; **Check connection** retries immediately and **Start server**
152
164
  explicitly allows local startup even if `autoStart` was disabled. After recovery it navigates
@@ -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.3**, which requires **Bun >=1.4.2** even when
7
+ The examples pin **PMX Canvas 0.8.0**, 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.3
20
+ npm install -g pmx-canvas@0.8.0
21
21
  pmx-canvas --version
22
22
  ```
23
23
 
24
- Without a global install, use `bunx pmx-canvas@0.7.3` in place of `pmx-canvas` in
24
+ Without a global install, use `bunx pmx-canvas@0.8.0` 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.3", "--mcp"],
94
+ "args": ["pmx-canvas@0.8.0", "--mcp"],
95
95
  "env": {
96
96
  "PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
97
97
  "PMX_CANVAS_PORT": "14313",
@@ -135,7 +135,7 @@ Amp executes, not only on the user's laptop. Merge this into that workspace's
135
135
  "amp.mcpServers": {
136
136
  "canvas": {
137
137
  "command": "bunx",
138
- "args": ["pmx-canvas@0.7.3", "--mcp"],
138
+ "args": ["pmx-canvas@0.8.0", "--mcp"],
139
139
  "includeTools": ["canvas_*"],
140
140
  "env": {
141
141
  "PMX_CANVAS_WORKSPACE_ROOT": "/absolute/path/to/project",
@@ -169,7 +169,7 @@ The agent must not run this approval itself or move the command to global settin
169
169
  `--mcp-config` to bypass workspace trust. Approval authorizes execution of this MCP
170
170
  command; it is not a new Canvas import consent or permission to mutate arbitrary boards.
171
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
172
+ Discover `canvas` tools with `tool_search`; for the full unfiltered 0.8.0 server there
173
173
  are 24 tools. If still absent, inspect the server status, command/PATH and target instead
174
174
  of reinstalling blindly. See [Amp MCP documentation](https://ampcode.com/docs/customize/mcp).
175
175
 
@@ -68,10 +68,11 @@ and check `$?`, then read the file.
68
68
 
69
69
  - In the product source checkout, Bun tests live under `tests/unit/`.
70
70
  - In the product source checkout, Playwright browser smoke lives under `tests/e2e/`.
71
- - Playwright runs two projects: `desktop` (1440×900, the whole suite) and `pane-600` (600×900,
72
- `tests/e2e/reference-pane.pw.ts` only). `pane-600` is the release reference surface: every node
73
- type must paint there. A new node type fails its coverage test until it gets a reference case.
74
- Run it alone with `bash scripts/run-playwright.sh --project=pane-600`.
71
+ - Playwright runs `desktop` (1440×900, the whole suite except the reference cases) and three
72
+ reference projects for `tests/e2e/reference-pane.pw.ts`: `narrow-600`, `middle-1024` and
73
+ `wide-1920`. The workbench lives anywhere from a narrow side pane to a full window, so every
74
+ node type must paint at all three. A new node type fails its coverage test until it gets a
75
+ reference case. Run one width alone with `bash scripts/run-playwright.sh --project=narrow-600`.
75
76
  - Product CI runs those checkout suites: Bun coverage plus the browser smoke flow.
76
77
  - An isolated installed consumer instead tests the package's public transports and host behavior;
77
78
  it does not run or depend on the product checkout's source suites.
@@ -20,6 +20,11 @@ cmd(
20
20
  const sourcePaths = [
21
21
  fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/extension.mjs', import.meta.url)),
22
22
  fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/steering-delivery.mjs', import.meta.url)),
23
+ fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/runtime.mjs', import.meta.url)),
24
+ fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/runtime.json', import.meta.url)),
25
+ fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/start-mcp.mjs', import.meta.url)),
26
+ fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/tools.json', import.meta.url)),
27
+ fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/THIRD_PARTY_LICENSES.txt', import.meta.url)),
23
28
  ];
24
29
  const missingSource = sourcePaths.find((sourcePath) => !existsSync(sourcePath));
25
30
  if (missingSource) {
@@ -179,7 +179,7 @@ cmd(
179
179
  [
180
180
  'pmx-canvas serve',
181
181
  'pmx-canvas serve --port=8080 --no-open',
182
- 'pmx-canvas serve --demo --theme=light',
182
+ 'pmx-canvas serve --demo --theme=daylight',
183
183
  'pmx-canvas --no-open --webview-automation',
184
184
  ],
185
185
  async (_args) => {
package/src/cli/index.ts CHANGED
@@ -149,9 +149,10 @@ function commandAfterGlobalTargetFlags(argv: string[]): string {
149
149
  return '';
150
150
  }
151
151
 
152
- function runMcpServerProcess(): Promise<void> {
152
+ function runMcpServerProcess(app = false): Promise<void> {
153
153
  return new Promise((resolvePromise, rejectPromise) => {
154
- const child = spawn(process.execPath, ['run', mcpServerEntry], {
154
+ const entry = app ? resolve(cliDir, '..', 'mcp', 'app-server.ts') : mcpServerEntry;
155
+ const child = spawn(process.execPath, ['run', entry], {
155
156
  stdio: 'inherit',
156
157
  env: process.env,
157
158
  });
@@ -222,6 +223,9 @@ if (firstArg === 'serve') {
222
223
  const routedCommand = commandAfterGlobalTargetFlags(args);
223
224
  if (AGENT_COMMANDS.has(routedCommand) && routedCommand !== 'serve') {
224
225
  await runAgentCli(args);
226
+ } else if (args.includes('--mcp-app')) {
227
+ // Private MCP Apps profile: focused tools and a self-contained UI resource.
228
+ await runMcpServerProcess(true);
225
229
  } else if (args.includes('--mcp')) {
226
230
  // MCP server mode: stdio transport, auto-starts canvas on first tool call
227
231
  await runMcpServerProcess();
@@ -274,7 +278,7 @@ Server options:
274
278
  --log-file=PATH Daemon log file (default: ./.pmx-canvas/daemon-${port}.log)
275
279
  --pid-file=PATH Optional daemon PID file (default: ./.pmx-canvas/daemon-${port}.pid)
276
280
  --wait-ms=MS Health-check wait budget for daemon mode (default: 10000)
277
- --theme=THEME Theme: dark (default), light, high-contrast, midnight, sepia, arctic, ember, forest, volt
281
+ --theme=THEME Theme: harbor (default), daylight, high-contrast, midnight, sepia, arctic, ember, forest, volt
278
282
  --webview-automation Start a headless Bun.WebView automation session for /workbench
279
283
  --webview-backend=BACKEND Bun.WebView backend: chrome or webkit
280
284
  --webview-width=PX Automation WebView width (default: 1280)
@@ -283,6 +287,7 @@ Server options:
283
287
  --webview-chrome-argv=CSV Extra Chrome args for Bun.WebView, comma-separated
284
288
  --webview-data-dir=PATH Persist automation browser storage in PATH
285
289
  --mcp Run as MCP server (stdio transport)
290
+ --mcp-app Run the private MCP App profile (stdio; shared active board)
286
291
  --help, -h Show this help
287
292
 
288
293
  Agent CLI (works against running server):
@@ -54,6 +54,7 @@ import {
54
54
  import { connectSSE } from './state/sse-bridge';
55
55
  import { intents } from './state/intent-store';
56
56
  import { sessionActive } from './state/presence-store';
57
+ import { isHostedWorkbench } from './state/workbench-transport';
57
58
  import {
58
59
  createGroupFromClient,
59
60
  removeEdgeFromClient,
@@ -376,7 +377,11 @@ export function App() {
376
377
  intents.value.size === 0 && <EmptyState onOpenPalette={() => setPaletteOpen(true)} />
377
378
  )}
378
379
  {selectedNodeIds.value.size > 0 && <SelectionBar />}
379
- {sessionIsActive ? <CommandBar /> : contextPinnedNodeIds.value.size > 0 && <ContextPinBar />}
380
+ {sessionIsActive && !isHostedWorkbench() ? (
381
+ <CommandBar />
382
+ ) : (
383
+ contextPinnedNodeIds.value.size > 0 && <ContextPinBar />
384
+ )}
380
385
  <SessionReceipt onOpenSnapshots={() => setSnapshotOpen(true)} />
381
386
  <TextPrompt />
382
387
  <ExportDialog />
@@ -1,7 +1,7 @@
1
1
  import { useRef } from 'preact/hooks';
2
- import { agentIdentityHue, agentPhaseLabel, type AgentPresence } from '../../shared/agent-presence.js';
2
+ import { agentPhaseLabel, type AgentPresence } from '../../shared/agent-presence.js';
3
3
  import { nodes, viewport } from '../state/canvas-store';
4
- import { agentPresences, presenceWorldPosition } from '../state/presence-store';
4
+ import { agentPresences, presenceWorldPosition, writerColor } from '../state/presence-store';
5
5
 
6
6
  /**
7
7
  * Agent presence layer (rail-chrome-v2 phase 3): one cursor + phase chip per
@@ -49,7 +49,7 @@ export function AgentPresenceLayer() {
49
49
  data-session-id={presence.sessionId}
50
50
  style={{
51
51
  transform: `translate(${position.x}px, ${position.y}px)`,
52
- '--identity-color': `hsl(${agentIdentityHue(presence.sessionId)} 65% 62%)`,
52
+ '--identity-color': writerColor(presence.sessionId),
53
53
  }}
54
54
  >
55
55
  <div class="agent-cursor-inner" style={{ transform: `scale(${counterScale})` }}>
@@ -23,7 +23,7 @@ export function AnnotationLayer({ annotations }: { annotations: CanvasAnnotation
23
23
  y={point.y}
24
24
  fill={color}
25
25
  font-size={annotation.width}
26
- font-family="var(--font)"
26
+ font-family="var(--font-ui)"
27
27
  font-weight="700"
28
28
  opacity="0.95"
29
29
  >
@@ -1,6 +1,8 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
2
2
  import { mutatingNodeIds, sessionActive } from '../state/presence-store';
3
- import { attentionPrimaryNodeIds, attentionPulseNodeIds, attentionSecondaryNodeIds } from '../state/attention-store';
3
+ import { attentionPulseNodeIds } from '../state/attention-store';
4
+ import { isAgentPin, NearPinMark, NodeContextMark } from './NodeContextMark';
5
+ import { activityLensNodeIds } from '../state/session-store';
4
6
  import {
5
7
  activeNodeId,
6
8
  activeNeighborNodeIds,
@@ -34,20 +36,9 @@ import {
34
36
  ungroupFromClient,
35
37
  updateNodeFromClient,
36
38
  } from '../state/intent-bridge';
37
- import { KIND_COLOR } from './kind-colors';
38
39
  import { reportHumanGrab, takeOverNode, yieldedNodes } from '../state/human-store';
39
40
  import { AxStepControls } from '../nodes/AxStepControls';
40
- import { canOpenAsSite, openNodeAsSite } from '../nodes/surface-url';
41
- import {
42
- getNodeIcon,
43
- IconChevronRight,
44
- IconChevronDown,
45
- IconClose,
46
- IconMore,
47
- IconExternalLink,
48
- IconExpand,
49
- IconSparkle,
50
- } from '../icons';
41
+ import { getNodeIcon, IconChevronRight, IconClose, IconMore, IconExpand, IconPin } from '../icons';
51
42
  import { EXPANDABLE_TYPES, TYPE_LABELS } from '../types';
52
43
  import type { CanvasNodeState } from '../types';
53
44
  import { AUTO_FIT_TITLEBAR_HEIGHT, computeAutoFitHeight, shouldAutoFitNode } from './auto-fit';
@@ -137,8 +128,6 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
137
128
  const isActive = activeNodeId.value === node.id;
138
129
  const isSelected = selectedNodeIds.value.has(node.id);
139
130
  const isContextPinned = contextPinnedNodeIds.value.has(node.id);
140
- const isAttentionPrimary = attentionPrimaryNodeIds.value.has(node.id);
141
- const isAttentionSecondary = !isAttentionPrimary && attentionSecondaryNodeIds.value.has(node.id);
142
131
  const isAttentionPulse = attentionPulseNodeIds.value.has(node.id);
143
132
  const isNeighbor = !isActive && activeNeighborNodeIds.value.has(node.id);
144
133
  const searchSet = searchHighlightIds.value;
@@ -428,8 +417,6 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
428
417
  isSearchDimmed ? 'search-dimmed' : '',
429
418
  isSelected ? 'selected' : '',
430
419
  isContextPinned ? 'context-pinned' : '',
431
- isAttentionPrimary ? 'attention-focus-primary' : '',
432
- isAttentionSecondary ? 'attention-focus-secondary' : '',
433
420
  isAttentionPulse ? 'attention-pulse' : '',
434
421
  isPinned ? 'pinned' : '',
435
422
  isTrace ? 'trace-node' : '',
@@ -437,6 +424,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
437
424
  isGroup ? 'group-node' : '',
438
425
  isStrictSize ? 'strict-size' : '',
439
426
  isAgentMutating ? 'agent-mutating' : '',
427
+ activityLensNodeIds.value && !activityLensNodeIds.value.has(node.id) ? 'lens-dimmed' : '',
440
428
  ]
441
429
  .filter(Boolean)
442
430
  .join(' ');
@@ -488,7 +476,7 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
488
476
  <div class="group-chip-name">{title}</div>
489
477
  <div class="group-chip-meta">
490
478
  {groupKindDots.map((kind) => (
491
- <span key={kind} class="group-kind-dot" style={{ background: KIND_COLOR[kind] }} aria-hidden="true" />
479
+ <span key={kind} class="group-kind-dot" aria-hidden="true" />
492
480
  ))}
493
481
  <span class="group-chip-count">
494
482
  {groupChildren.length} node{groupChildren.length === 1 ? '' : 's'}
@@ -679,40 +667,16 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
679
667
  {title}
680
668
  </span>
681
669
  )}
670
+ <NearPinMark node={node} pinned={isContextPinned} />
671
+ <NodeContextMark node={node} pinned={isContextPinned} />
682
672
  <div class="node-controls">
683
673
  {isPinned && (
684
674
  <span class="pin-indicator" title="Pinned">
685
675
  ⊙
686
676
  </span>
687
677
  )}
688
- <button
689
- type="button"
690
- class={`ctx-pin-btn${isContextPinned ? ' ctx-pin-active' : ''}`}
691
- onClick={(e) => {
692
- e.stopPropagation();
693
- toggleContextPin(node.id);
694
- }}
695
- title={isContextPinned ? 'Remove from context' : 'Add to context'}
696
- >
697
- <IconSparkle size={14} />
698
- </button>
699
- {/* Open as site — full-page standalone view of this node's surface,
700
- served from /api/canvas/surface/:id (same document as the canvas
701
- iframe). Opens via the system browser so embedded hosts do not trap
702
- it in their own webview. */}
703
- {canOpenAsSite(node) && (
704
- <button
705
- type="button"
706
- onClick={(e) => {
707
- e.stopPropagation();
708
- void openNodeAsSite(node);
709
- }}
710
- title="Open as site"
711
- >
712
- <IconExternalLink size={14} />
713
- </button>
714
- )}
715
- {/* Expand — opens node as full-viewport overlay for focused work */}
678
+ {/* Expand and the context pin always show; the rest sit behind ⋯ and ×
679
+ on hover (docs/design/Chrome.dc.html). */}
716
680
  {EXPANDABLE_TYPES.has(node.type) && (
717
681
  <button
718
682
  type="button"
@@ -727,18 +691,37 @@ export function CanvasNode({ node, children, onContextMenu }: CanvasNodeProps) {
727
691
  )}
728
692
  <button
729
693
  type="button"
694
+ class={`ctx-pin-btn${isContextPinned ? ' ctx-pin-active' : ''}`}
730
695
  onClick={(e) => {
731
696
  e.stopPropagation();
732
- toggleCollapsed(node.id);
697
+ toggleContextPin(node.id);
698
+ }}
699
+ title={isContextPinned ? 'Remove from context' : 'Add to context'}
700
+ aria-label={isContextPinned ? 'Remove from context' : 'Add to context'}
701
+ aria-pressed={isContextPinned}
702
+ >
703
+ <span class="ctx-pin-mark">
704
+ <IconPin />
705
+ {isContextPinned && isAgentPin(node.id) && <span class="ctx-pin-agent-dot" aria-hidden="true" />}
706
+ </span>
707
+ </button>
708
+ <button
709
+ type="button"
710
+ class="node-hover-control"
711
+ onClick={(e) => {
712
+ e.stopPropagation();
713
+ onContextMenu?.(e, node.id);
733
714
  }}
734
- title={node.collapsed ? 'Expand' : 'Collapse'}
715
+ title="More actions"
716
+ aria-label="More actions"
735
717
  >
736
- {node.collapsed ? <IconChevronRight size={14} /> : <IconChevronDown size={14} />}
718
+ <IconMore size={14} />
737
719
  </button>
738
720
  {/* Report #64: status nodes get the same remove control as every other
739
721
  node type (backend removal + undo/history handle status uniformly). */}
740
722
  <button
741
723
  type="button"
724
+ class="node-hover-control"
742
725
  onClick={(e) => {
743
726
  e.stopPropagation();
744
727
  void removeNodeFromClient(node.id);
@@ -38,7 +38,6 @@ import {
38
38
  } from '../state/canvas-store';
39
39
  import { createEdgeFromClient, createNodeFromClient } from '../state/intent-bridge';
40
40
  import type { AnnotationTool, CanvasAnnotation, CanvasNodeState } from '../types';
41
- import { FocusFieldLayer } from './FocusFieldLayer';
42
41
  import { importFiles } from './import-files';
43
42
  import { IntentLayer } from './IntentLayer';
44
43
  import { AgentPresenceLayer } from './AgentPresenceLayer';
@@ -855,7 +854,6 @@ export function CanvasViewport({
855
854
  left: 0,
856
855
  }}
857
856
  >
858
- <FocusFieldLayer />
859
857
  <ScopeFenceLayer />
860
858
  <IntentLayer />
861
859
  <AgentPresenceLayer />
@@ -16,6 +16,7 @@ import { startSession } from '../state/session-store';
16
16
  import { TYPE_LABELS, type CanvasNodeState } from '../types';
17
17
  import { invalidateTokenCache } from '../theme/tokens';
18
18
  import { clearThemeOverride } from '../state/theme-override';
19
+ import { canvasThemeScheme } from '../../shared/themes.js';
19
20
  import { getNodeIcon, IconArrange, IconFitAll, IconMinimap, IconMoon, IconNodeMarkdown, IconSteer } from '../icons';
20
21
  import { activeBoardId, openBoard } from '../state/boards-store';
21
22
  import { requestJson } from '../state/intent-bridge';
@@ -269,11 +270,11 @@ export function CommandPalette({ onClose, onToggleMinimap }: { onClose: () => vo
269
270
  },
270
271
  },
271
272
  {
272
- label: 'Toggle theme (dark/light)',
273
+ label: 'Toggle theme (Harbor/Daylight)',
273
274
  icon: IconMoon,
274
275
  iconTone: 'muted',
275
276
  action: () => {
276
- const next = canvasTheme.value === 'dark' ? 'light' : 'dark';
277
+ const next = canvasThemeScheme(canvasTheme.value) === 'dark' ? 'daylight' : 'harbor';
277
278
  // An explicit pick ends any ?theme= session override, same as the
278
279
  // toolbar picker — otherwise the session stays sticky-overridden.
279
280
  clearThemeOverride();
@@ -26,6 +26,7 @@ import {
26
26
  updateEdgeFromClient,
27
27
  updateNodeFromClient,
28
28
  } from '../state/intent-bridge';
29
+ import { canOpenAsSite, openNodeAsSite } from '../nodes/surface-url';
29
30
  import { askText } from './TextPrompt';
30
31
  import { EXPANDABLE_TYPES } from '../types';
31
32
  import type { CanvasNodeState } from '../types';
@@ -498,6 +499,16 @@ function buildNodeMenuItems(node: CanvasNodeState): MenuItem[] {
498
499
  action: () => toggleCollapsed(node.id),
499
500
  });
500
501
 
502
+ // Open as site — full-page standalone view of this node's surface, served
503
+ // from /api/canvas/surface/:id (same document as the canvas iframe). Opens
504
+ // via the system browser so embedded hosts do not trap it in their webview.
505
+ if (canOpenAsSite(node)) {
506
+ items.push({
507
+ label: 'Open in new tab',
508
+ action: () => void openNodeAsSite(node),
509
+ });
510
+ }
511
+
501
512
  // Context pin — add/remove from the human-curated agent context (report #63).
502
513
  // This is the PRIMARY "pin" in PMX's model ("pin nodes to curate context"); it
503
514
  // matches the SelectionBar's "Pin as context" and updates the context count + the
@@ -12,7 +12,7 @@ import type { CanvasEdge, CanvasNodeState } from '../types';
12
12
  // ── Edge type visual styles ──────────────────────────────────
13
13
  const EDGE_COLORS: Record<CanvasEdge['type'], string> = {
14
14
  relation: 'var(--c-muted)',
15
- 'depends-on': 'var(--c-warn)',
15
+ 'depends-on': 'var(--c-rel-informs)',
16
16
  flow: 'var(--c-accent)',
17
17
  // --c-dim is a very low-contrast hairline in dark palettes (forest: #5D7566
18
18
  // on #0C1712); the dashed style is what distinguishes `references`.