pmx-canvas 0.9.3 → 0.11.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 (237) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +9418 -2016
  3. package/CHANGELOG.md +54 -1
  4. package/dist/canvas/chunks/abnfDiagram-N423BO3Z-p8dtp6va.js +1 -0
  5. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +1 -0
  6. package/dist/canvas/chunks/architectureDiagram-T3A2C74G-5jhmxrzf.js +36 -0
  7. package/dist/canvas/chunks/blockDiagram-VBNYF7ZC-pw1fa9yb.js +132 -0
  8. package/dist/canvas/chunks/c4Diagram-5PPSVZJV-x07nq0tt.js +10 -0
  9. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +1 -0
  10. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +1 -0
  11. package/dist/canvas/chunks/cose-bilkent-JH36ORCC-nm39npq0.js +1 -0
  12. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +1 -0
  13. package/dist/canvas/chunks/cynefinDiagram-MW4NZA55-vdxrwahz.js +62 -0
  14. package/dist/canvas/chunks/dagre-VZM6K2ZE-bqdm7wpd.js +4 -0
  15. package/dist/canvas/chunks/diagram-7IWD3JNH-gs5gpr93.js +30 -0
  16. package/dist/canvas/chunks/diagram-B4RE2ZJO-cgwjg47s.js +3 -0
  17. package/dist/canvas/chunks/diagram-LBJQPF4R-w3vahpav.js +24 -0
  18. package/dist/canvas/chunks/diagram-Q27KOJAE-42837z6h.js +24 -0
  19. package/dist/canvas/chunks/diagram-UB23O5K3-4fz83q59.js +41 -0
  20. package/dist/canvas/chunks/ebnfDiagram-BXEA7PRR-zphtgegj.js +1 -0
  21. package/dist/canvas/chunks/erDiagram-JOGREHBK-zvbbdxp4.js +85 -0
  22. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +1 -0
  23. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +1 -0
  24. package/dist/canvas/chunks/ganttDiagram-PKOTCBZU-282jzycm.js +292 -0
  25. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +1 -0
  26. package/dist/canvas/chunks/gitGraphDiagram-DS77QQ5N-vqqy6p1b.js +106 -0
  27. package/dist/canvas/chunks/index-0rk4gp1g.js +2 -0
  28. package/dist/canvas/chunks/index-194m3hc3.js +2 -0
  29. package/dist/canvas/chunks/index-1r72hdzb.js +301 -0
  30. package/dist/canvas/chunks/index-353m7b0p.js +2 -0
  31. package/dist/canvas/chunks/index-3cy1bzwh.js +2 -0
  32. package/dist/canvas/chunks/index-5tv8v1hk.js +1 -0
  33. package/dist/canvas/chunks/index-6ceya556.js +332 -0
  34. package/dist/canvas/chunks/index-6qmsnxwy.js +4 -0
  35. package/dist/canvas/chunks/index-d3b3gqe9.js +2 -0
  36. package/dist/canvas/chunks/index-fjtdggjn.js +2 -0
  37. package/dist/canvas/chunks/index-g3pwnqeb.js +2 -0
  38. package/dist/canvas/chunks/index-gf4e0et4.js +2 -0
  39. package/dist/canvas/chunks/index-hj99nz1f.js +1 -0
  40. package/dist/canvas/chunks/index-nprm96xp.js +16 -0
  41. package/dist/canvas/chunks/index-qj8fxwsf.js +2 -0
  42. package/dist/canvas/chunks/index-r2fyc9w1.js +89 -0
  43. package/dist/canvas/chunks/index-rft2hcz5.js +2 -0
  44. package/dist/canvas/chunks/index-rh7nv68n.js +2 -0
  45. package/dist/canvas/chunks/index-s4j678gj.js +2 -0
  46. package/dist/canvas/chunks/index-tb1wf525.js +179 -0
  47. package/dist/canvas/chunks/index-vv56h019.js +2 -0
  48. package/dist/canvas/chunks/index-w2mra17t.js +207 -0
  49. package/dist/canvas/chunks/index-wgv7en21.js +232 -0
  50. package/dist/canvas/chunks/index-yw8h9vha.js +157 -0
  51. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +1 -0
  52. package/dist/canvas/chunks/infoDiagram-6WML65LV-v1yvggqj.js +2 -0
  53. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +70 -0
  54. package/dist/canvas/chunks/journeyDiagram-NVQOT4AX-zjcffveh.js +139 -0
  55. package/dist/canvas/chunks/kanban-definition-27J2QSJJ-dakqtfp1.js +89 -0
  56. package/dist/canvas/chunks/katex-dmg8k4zm.js +257 -0
  57. package/dist/canvas/chunks/mermaid.core-j2geswrd.js +12 -0
  58. package/dist/canvas/chunks/mindmap-definition-FAOFIHXS-142xhr3k.js +96 -0
  59. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +1 -0
  60. package/dist/canvas/chunks/pegDiagram-VL7TDLO6-5wvk7vsd.js +1 -0
  61. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +1 -0
  62. package/dist/canvas/chunks/pieDiagram-7S7Q4E2Y-ztdhwycc.js +39 -0
  63. package/dist/canvas/chunks/quadrantDiagram-CIZ2JOQS-c5p9gmg6.js +7 -0
  64. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +1 -0
  65. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +1 -0
  66. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +1 -0
  67. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +1 -0
  68. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +1 -0
  69. package/dist/canvas/chunks/railroadDiagram-AXF67PYL-xkmqkjwf.js +1 -0
  70. package/dist/canvas/chunks/requirementDiagram-LRYGKXZP-9wm2dyaw.js +84 -0
  71. package/dist/canvas/chunks/sankeyDiagram-W5VNT64P-2w19etgb.js +40 -0
  72. package/dist/canvas/chunks/sequenceDiagram-SI44F4Z6-0jn0vwmz.js +162 -0
  73. package/dist/canvas/chunks/sizeCapture-X5ZJPWSS-x75epwdq.js +1 -0
  74. package/dist/canvas/chunks/stateDiagram-OKZ733FA-schjpy6d.js +1 -0
  75. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +1 -0
  76. package/dist/canvas/chunks/swimlanes-SLNWSIFB-gkw4xce3.js +1 -0
  77. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +8 -0
  78. package/dist/canvas/chunks/timeline-definition-Z64GVDOM-x7nkcy01.js +120 -0
  79. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +1 -0
  80. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +1 -0
  81. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +34 -0
  82. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +1 -0
  83. package/dist/canvas/chunks/wardleyDiagram-T6FBY63Y-t1j52t9r.js +78 -0
  84. package/dist/canvas/chunks/xychartDiagram-ELKLHX3M-38t16pme.js +7 -0
  85. package/dist/canvas/global.css +389 -229
  86. package/dist/canvas/index.js +141 -211
  87. package/dist/canvas/mcp-app.html +4205 -554
  88. package/dist/canvas/mermaid-entry.js +338 -338
  89. package/dist/canvas/surface-theme.css +6 -6
  90. package/dist/json-render/index.css +1 -1
  91. package/dist/json-render/index.js +49 -49
  92. package/dist/types/client/canvas/BarHint.d.ts +5 -3
  93. package/dist/types/client/canvas/CanvasNode.d.ts +0 -11
  94. package/dist/types/client/canvas/EdgeLayer.d.ts +1 -2
  95. package/dist/types/client/canvas/auto-fit.d.ts +4 -5
  96. package/dist/types/client/mermaid-entry.d.ts +1 -2
  97. package/dist/types/client/mermaid-render.d.ts +1 -0
  98. package/dist/types/client/nodes/BoardNode.d.ts +1 -1
  99. package/dist/types/client/nodes/ExtAppFrame.d.ts +7 -90
  100. package/dist/types/client/nodes/McpAppNode.d.ts +1 -25
  101. package/dist/types/client/nodes/MermaidNode.d.ts +1 -8
  102. package/dist/types/client/nodes/ViewerFrame.d.ts +29 -0
  103. package/dist/types/client/nodes/ViewerHost.d.ts +6 -0
  104. package/dist/types/client/nodes/iframe-document-url.d.ts +3 -9
  105. package/dist/types/client/nodes/primitive-copy.d.ts +5 -0
  106. package/dist/types/client/nodes/use-iframe-content-height.d.ts +1 -1
  107. package/dist/types/client/nodes/use-surface-frame.d.ts +4 -6
  108. package/dist/types/client/nodes/viewer-runtime-state.d.ts +2 -1
  109. package/dist/types/client/state/canvas-store.d.ts +3 -1
  110. package/dist/types/client/state/intent-bridge.d.ts +1 -0
  111. package/dist/types/client/state/presentation.d.ts +17 -6
  112. package/dist/types/json-render/server.d.ts +2 -1
  113. package/dist/types/mcp/canvas-access.d.ts +1 -12
  114. package/dist/types/mcp/resource-subscriptions.d.ts +7 -0
  115. package/dist/types/mcp/server.d.ts +2 -0
  116. package/dist/types/server/ax-wait.d.ts +1 -0
  117. package/dist/types/server/canvas-operations.d.ts +1 -0
  118. package/dist/types/server/canvas-state.d.ts +2 -0
  119. package/dist/types/server/index.d.ts +11 -6
  120. package/dist/types/server/operations/http.d.ts +10 -0
  121. package/dist/types/server/operations/index.d.ts +2 -2
  122. package/dist/types/server/operations/invoker.d.ts +4 -3
  123. package/dist/types/server/operations/mcp-contracts.d.ts +20 -0
  124. package/dist/types/server/operations/mcp.d.ts +2 -0
  125. package/dist/types/server/operations/ops/batch.d.ts +1 -0
  126. package/dist/types/server/operations/ops/boards.d.ts +0 -1
  127. package/dist/types/server/operations/registry.d.ts +2 -2
  128. package/dist/types/server/operations/types.d.ts +12 -2
  129. package/dist/types/server/server.d.ts +2 -0
  130. package/dist/types/server/tour-control.d.ts +13 -3
  131. package/dist/types/server/webpage-node.d.ts +1 -0
  132. package/dist/types/shared/ax-surface-protocol.d.ts +0 -9
  133. package/dist/types/shared/tour.d.ts +8 -0
  134. package/docs/api-reference.md +2 -2
  135. package/docs/cli.md +1 -1
  136. package/docs/design/Chrome.dc.html +32 -6
  137. package/docs/design/Types1.dc.html +3 -2
  138. package/docs/design/Types2.dc.html +1 -1
  139. package/docs/design/Types3.dc.html +1 -1
  140. package/docs/design/Zoom.dc.html +37 -60
  141. package/docs/design/canvas.json +1 -1
  142. package/docs/design.md +169 -4
  143. package/docs/http-api.md +82 -1
  144. package/docs/mcp.md +101 -21
  145. package/docs/node-types.md +38 -3
  146. package/docs/plans/plan-009-tech-debt-backlog.md +2 -2
  147. package/docs/plans/plan-019-post-0.9-roadmap.md +402 -13
  148. package/docs/product-vision-2026-09.md +9 -0
  149. package/docs/sdk.md +5 -2
  150. package/package.json +2 -2
  151. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  152. package/plugins/pmx-canvas/plugin.json +1 -1
  153. package/plugins/pmx-canvas/runtime.json +1 -1
  154. package/plugins/pmx-canvas/tools.json +9418 -2016
  155. package/skills/pmx-canvas/SKILL.md +19 -21
  156. package/skills/pmx-canvas/references/full-reference.md +44 -6
  157. package/skills/pmx-canvas/references/installing-pmx-canvas.md +7 -7
  158. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +3 -5
  159. package/src/client/canvas/BarHint.tsx +6 -3
  160. package/src/client/canvas/CanvasNode.tsx +34 -47
  161. package/src/client/canvas/CanvasViewport.tsx +6 -7
  162. package/src/client/canvas/ContextMenu.tsx +63 -3
  163. package/src/client/canvas/EdgeLayer.tsx +365 -169
  164. package/src/client/canvas/ExpandedNodeOverlay.tsx +1 -2
  165. package/src/client/canvas/Presentation.tsx +53 -3
  166. package/src/client/canvas/ToolRail.tsx +2 -0
  167. package/src/client/canvas/TopBar.tsx +24 -9
  168. package/src/client/canvas/auto-fit.ts +9 -11
  169. package/src/client/canvas/canvas-area.ts +1 -1
  170. package/src/client/canvas/use-focus-trap.ts +6 -1
  171. package/src/client/mermaid-entry.ts +6 -20
  172. package/src/client/mermaid-render.ts +39 -0
  173. package/src/client/nodes/AxStepControls.tsx +45 -69
  174. package/src/client/nodes/BoardNode.tsx +65 -9
  175. package/src/client/nodes/ContextNode.tsx +88 -396
  176. package/src/client/nodes/ExtAppFrame.tsx +283 -1462
  177. package/src/client/nodes/FileNode.tsx +92 -81
  178. package/src/client/nodes/HtmlNode.tsx +8 -36
  179. package/src/client/nodes/LedgerNode.tsx +31 -17
  180. package/src/client/nodes/MarkdownNode.tsx +8 -17
  181. package/src/client/nodes/McpAppNode.tsx +48 -154
  182. package/src/client/nodes/MermaidNode.tsx +80 -69
  183. package/src/client/nodes/PromptNode.tsx +12 -14
  184. package/src/client/nodes/ResponseNode.tsx +8 -6
  185. package/src/client/nodes/StatusNode.tsx +22 -7
  186. package/src/client/nodes/StatusSummary.tsx +5 -3
  187. package/src/client/nodes/TraceNode.tsx +8 -8
  188. package/src/client/nodes/ViewerFrame.tsx +262 -0
  189. package/src/client/nodes/ViewerHost.tsx +41 -0
  190. package/src/client/nodes/WebpageNode.tsx +22 -24
  191. package/src/client/nodes/iframe-document-url.ts +20 -27
  192. package/src/client/nodes/primitive-copy.ts +97 -0
  193. package/src/client/nodes/use-iframe-content-height.ts +34 -10
  194. package/src/client/nodes/use-surface-frame.ts +22 -11
  195. package/src/client/nodes/viewer-runtime-state.ts +41 -35
  196. package/src/client/state/canvas-store.ts +18 -12
  197. package/src/client/state/intent-bridge.ts +1 -0
  198. package/src/client/state/presentation.ts +135 -15
  199. package/src/client/state/sse-bridge.ts +18 -19
  200. package/src/client/theme/global.css +389 -229
  201. package/src/client/theme/surface-theme.css +6 -6
  202. package/src/json-render/charts/tufte-components.tsx +44 -2
  203. package/src/json-render/renderer/index.css +62 -2
  204. package/src/json-render/renderer/index.tsx +31 -7
  205. package/src/json-render/server.ts +3 -2
  206. package/src/mcp/canvas-access.ts +6 -47
  207. package/src/mcp/resource-subscriptions.ts +181 -0
  208. package/src/mcp/server.ts +185 -219
  209. package/src/server/ax-wait.ts +21 -6
  210. package/src/server/board-export.ts +35 -24
  211. package/src/server/canvas-geometry.ts +1 -0
  212. package/src/server/canvas-operations.ts +6 -2
  213. package/src/server/canvas-state.ts +5 -0
  214. package/src/server/demo-state.json +106 -100
  215. package/src/server/html-primitives.ts +3 -9
  216. package/src/server/html-surface.ts +8 -0
  217. package/src/server/index.ts +30 -12
  218. package/src/server/operations/http.ts +69 -21
  219. package/src/server/operations/index.ts +8 -1
  220. package/src/server/operations/invoker.ts +53 -5
  221. package/src/server/operations/mcp-contracts.ts +354 -0
  222. package/src/server/operations/mcp.ts +153 -18
  223. package/src/server/operations/ops/ax-await.ts +18 -10
  224. package/src/server/operations/ops/ax-read.ts +2 -0
  225. package/src/server/operations/ops/ax-timeline.ts +2 -2
  226. package/src/server/operations/ops/batch.ts +27 -8
  227. package/src/server/operations/ops/boards.ts +1 -3
  228. package/src/server/operations/ops/nodes.ts +18 -3
  229. package/src/server/operations/ops/viewport.ts +11 -9
  230. package/src/server/operations/registry.ts +24 -4
  231. package/src/server/operations/types.ts +16 -2
  232. package/src/server/server.ts +17 -59
  233. package/src/server/tour-control.ts +66 -6
  234. package/src/server/webpage-node.ts +9 -4
  235. package/src/shared/ax-surface-protocol.ts +0 -11
  236. package/src/shared/content-height-reporter.ts +4 -3
  237. package/src/shared/tour.ts +8 -0
@@ -12,7 +12,7 @@
12
12
  import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
14
14
  import { Marked, Renderer, type RendererThis, type Tokens } from 'marked';
15
- import { canvasThemeScheme } from '../shared/themes.js';
15
+ import { canvasThemeScheme, normalizeCanvasThemeName, type CanvasThemeName } from '../shared/themes.js';
16
16
  import { delimiterForPath, parseDelimitedText } from '../shared/delimited-text.js';
17
17
  import { buildJsonRenderViewerHtml, escapeInlineScriptSource, readJsonRenderBundle } from '../json-render/server.js';
18
18
  import type { JsonRenderSpec } from '../json-render/server.js';
@@ -58,6 +58,7 @@ interface ExportCard {
58
58
  w: number;
59
59
  h: number;
60
60
  group?: true;
61
+ heading?: true;
61
62
  html?: string;
62
63
  frame?: string;
63
64
  }
@@ -66,7 +67,7 @@ interface Collected {
66
67
  manifest: ExportManifest;
67
68
  cards: ExportCard[];
68
69
  edges: CanvasEdge[];
69
- scheme: 'dark' | 'light';
70
+ theme: CanvasThemeName;
70
71
  needsJsonRender: boolean;
71
72
  needsMermaid: boolean;
72
73
  }
@@ -207,9 +208,7 @@ async function collect(
207
208
  const read = canvasState.readBoard(boardId);
208
209
  if (!read) return null;
209
210
  const { board, state } = read;
210
- const theme = canvasThemeScheme(state.theme ?? canvasState.theme);
211
- // Exports collapse to a scheme; surface documents take the theme that draws it.
212
- const surfaceTheme = theme === 'light' ? 'daylight' : 'harbor';
211
+ const theme = normalizeCanvasThemeName(state.theme ?? canvasState.theme);
213
212
  const remoteImages = new Set<string>();
214
213
  const links = new Set<string>();
215
214
  const frameNetworkDestinations = new Set<string>();
@@ -278,6 +277,7 @@ async function collect(
278
277
  card.group = true;
279
278
  break;
280
279
  case 'markdown':
280
+ if (data.display === 'heading') card.heading = true;
281
281
  card.html = `<div class="md">${await markdown.parse(text(data.content))}</div>`;
282
282
  break;
283
283
  case 'status':
@@ -385,8 +385,7 @@ async function collect(
385
385
  const html = text(data.html) || text(data.content);
386
386
  collectFrameNetworkDestinations(html, frameNetworkDestinations);
387
387
  manifest.frames += 1;
388
- if (withFrames)
389
- card.frame = buildHtmlSurfaceDocument(html, { theme: surfaceTheme, title, inlineThemeCss: themeCss });
388
+ if (withFrames) card.frame = buildHtmlSurfaceDocument(html, { theme, title, inlineThemeCss: themeCss });
390
389
  break;
391
390
  }
392
391
  case 'mermaid':
@@ -398,7 +397,7 @@ async function collect(
398
397
  '<script src="/canvas/mermaid-entry.js"></script>',
399
398
  `<script src="${MERMAID_SRC}"></script>`,
400
399
  ),
401
- { theme: surfaceTheme, title, inlineThemeCss: themeCss },
400
+ { theme, title, inlineThemeCss: themeCss },
402
401
  );
403
402
  }
404
403
  break;
@@ -415,7 +414,8 @@ async function collect(
415
414
  title,
416
415
  spec: data.spec as JsonRenderSpec,
417
416
  theme,
418
- display: 'site',
417
+ // The saved card owns the viewport, just as on the live canvas.
418
+ // Omitting display selects embedded layout and its duplicate-title policy.
419
419
  });
420
420
  }
421
421
  break;
@@ -444,7 +444,7 @@ async function collect(
444
444
  manifest.links = [...links];
445
445
  manifest.frameNetworkDestinations = [...frameNetworkDestinations];
446
446
  manifest.embeddedCodeCanAccessNetwork = manifest.frames > 0;
447
- return { manifest, cards, edges: state.edges, scheme: theme, needsJsonRender, needsMermaid };
447
+ return { manifest, cards, edges: state.edges, theme, needsJsonRender, needsMermaid };
448
448
  }
449
449
 
450
450
  /** What an export of `boardId` would put in the file, without building it. */
@@ -467,7 +467,7 @@ export async function buildBoardExport(
467
467
  ): Promise<{ html: string; manifest: ExportManifest } | null> {
468
468
  const collected = await collect(boardId, includeFiles, true, includeDerivedText);
469
469
  if (!collected) return null;
470
- const { manifest, cards, edges: snapshotEdges, scheme, needsJsonRender, needsMermaid } = collected;
470
+ const { manifest, cards, edges: snapshotEdges, theme, needsJsonRender, needsMermaid } = collected;
471
471
 
472
472
  // Store each viewer bundle once; frames reference it by placeholder.
473
473
  const assets: Record<string, string> = {};
@@ -496,7 +496,7 @@ export async function buildBoardExport(
496
496
  const exportedAt = new Date().toISOString();
497
497
  const html = exportPage({
498
498
  title: manifest.boardName,
499
- scheme,
499
+ theme,
500
500
  exportedAt,
501
501
  board: { cards, edges },
502
502
  assets,
@@ -536,7 +536,7 @@ export function exportsFolder(): string | null {
536
536
 
537
537
  function exportPage(input: {
538
538
  title: string;
539
- scheme: 'dark' | 'light';
539
+ theme: CanvasThemeName;
540
540
  exportedAt: string;
541
541
  board: {
542
542
  cards: ExportCard[];
@@ -545,14 +545,22 @@ function exportPage(input: {
545
545
  assets: Record<string, string>;
546
546
  }): string {
547
547
  const date = new Date(input.exportedAt).toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' });
548
+ // Use the workbench's shipped faces and shared palette without offline requests.
549
+ const fonts = (readAsset('global.css').match(/@font-face\s*\{[^}]*\}/g) ?? [])
550
+ .join('\n')
551
+ .replace(
552
+ /url\("fonts\/([\w.-]+\.woff2)"\)/g,
553
+ (_, file: string) =>
554
+ `url("data:font/woff2;base64,${readFileSync(join(canvasBundleDir(), 'fonts', file)).toString('base64')}")`,
555
+ );
548
556
  return `<!doctype html>
549
- <html lang="en" data-scheme="${input.scheme}">
557
+ <html lang="en" data-scheme="${canvasThemeScheme(input.theme)}" data-theme="${input.theme}">
550
558
  <head>
551
559
  <meta charset="utf-8">
552
560
  <meta name="viewport" content="width=device-width, initial-scale=1">
553
561
  <meta name="generator" content="PMX Canvas">
554
562
  <title>${escapeHtml(input.title)}</title>
555
- <style>${EXPORT_CSS}</style>
563
+ <style>${fonts}\n${readAsset('surface-theme.css')}\n${EXPORT_CSS}</style>
556
564
  </head>
557
565
  <body>
558
566
  <header class="bar">
@@ -574,10 +582,9 @@ function exportPage(input: {
574
582
  }
575
583
 
576
584
  const EXPORT_CSS = `
577
- :root{--bg:#0b1016;--panel:#121a23;--line:#263241;--text:#e8edf2;--soft:#9aa7b4;--accent:#4bbcff;--add:#2ea66f;--del:#e25d6e}
578
- [data-scheme=light]{--bg:#f4f6f8;--panel:#ffffff;--line:#d5dbe1;--text:#17202a;--soft:#5b6875;--accent:#0a6fc2;--add:#1c7a4f;--del:#b3263a}
585
+ :root{--bg:var(--c-bg);--panel:var(--c-panel);--line:var(--c-line);--text:var(--c-text);--soft:var(--c-muted);--accent:var(--c-accent);--add:var(--c-ok);--del:var(--c-danger)}
579
586
  *{box-sizing:border-box}
580
- html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:15px/1.65 system-ui,-apple-system,Segoe UI,sans-serif}
587
+ html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:13px/1.6 var(--font-ui)}
581
588
  .bar{position:fixed;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 16px;background:var(--panel);border-bottom:1px solid var(--line);z-index:5}
582
589
  .bar .name{display:flex;flex-direction:column;min-width:0}
583
590
  .bar .name strong,.bar .name span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@@ -592,12 +599,16 @@ button:focus-visible,a:focus-visible,[tabindex]:focus-visible{outline:2px solid
592
599
  #edges{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
593
600
  #edges line{stroke:var(--soft);stroke-width:1.5}
594
601
  #edges text{fill:var(--soft);font-size:12px;text-anchor:middle}
595
- .card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;box-shadow:0 6px 24px rgba(0,0,0,.18)}
602
+ .card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:var(--r-node);overflow:hidden;box-shadow:var(--e-1)}
596
603
  .card.group{background:color-mix(in srgb,var(--accent) 5%,transparent);border-style:dashed;box-shadow:none}
597
- .card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:12px 42px 12px 18px;font-weight:600;font-size:13px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:color-mix(in srgb,var(--soft) 5%,var(--panel))}
604
+ .card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:7px 38px 7px 12px;font-weight:600;font-size:12px;line-height:22px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:transparent}
598
605
  button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--soft);font-size:16px}
599
606
  .card.group .card-title{border-bottom:none;cursor:default;color:var(--soft)}
600
- .card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:20px;overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
607
+ .card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:10px 14px;font-size:var(--fs-ui);color:var(--c-text-soft);overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
608
+ .card.heading{background:transparent;border-color:transparent;box-shadow:none}
609
+ .card.heading .card-title{padding:8px 38px 0 16px;border:0;font-size:var(--fs-heading);line-height:28px;letter-spacing:-.01em}
610
+ .card.heading .card-body{padding:0 16px 8px;color:var(--soft)}
611
+ .card.heading:not(:hover):not(:focus-within) .card-title:after{visibility:hidden}
601
612
  .card-body>:first-child,.md>:first-child{margin-top:0}.card-body>:last-child,.md>:last-child{margin-bottom:0}
602
613
  .card iframe,#overlay-body iframe{flex:1;min-height:0;width:100%;height:100%;border:0;background:var(--bg)}
603
614
  .md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:1.4em 0 .6em;line-height:1.3;font-weight:650;letter-spacing:-.02em}
@@ -607,7 +618,7 @@ button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--s
607
618
  .md input[type=checkbox]{accent-color:var(--accent);margin-right:.5em}
608
619
  .md blockquote{margin:1em 0;padding:10px 16px;border-left:3px solid var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));color:var(--soft)}
609
620
  .md blockquote>:last-child{margin-bottom:0}.md hr{border:0;border-top:1px solid var(--line);margin:24px 0}
610
- .md code,pre{font:13px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace;tab-size:2}
621
+ .md code,pre{font:13px/1.65 var(--font-code);tab-size:2}
611
622
  .md :not(pre)>code{padding:2px 5px;border:1px solid var(--line);border-radius:4px;background:color-mix(in srgb,var(--soft) 8%,var(--panel))}
612
623
  pre{margin:0 0 16px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--soft) 5%,var(--panel));white-space:pre;overflow:auto;overflow-wrap:normal;overscroll-behavior:contain}
613
624
  .prose{white-space:pre-wrap}.table-scroll{width:100%;max-width:100%;overflow:auto;margin:16px 0;border:1px solid var(--line);border-radius:8px;overscroll-behavior:contain}
@@ -629,7 +640,7 @@ details{margin-top:16px}summary{cursor:pointer;color:var(--soft);margin-bottom:1
629
640
  #overlay .sheet{width:min(1100px,100%);height:min(90vh,100%);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
630
641
  #overlay .sheet>header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line)}
631
642
  #overlay-body{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}
632
- #overlay-body .card-body{padding:28px 32px}#overlay-body .md{max-width:960px;margin:0 auto}
643
+ #overlay-body .card-body{padding:28px 32px;font-size:15px}#overlay-body .md{max-width:960px;margin:0 auto}
633
644
  @media(max-width:600px){#overlay{padding:8px}#overlay .sheet{height:100%}#overlay-body .card-body{padding:18px}.bar{padding:0 10px}.bar .name span{display:none}}
634
645
  `;
635
646
 
@@ -660,7 +671,7 @@ const body = (card) => {
660
671
  const ordered = [...board.cards].sort((a, b) => (b.group ? 1 : 0) - (a.group ? 1 : 0));
661
672
  for (const card of ordered) {
662
673
  const el = document.createElement('section');
663
- el.className = 'card' + (card.group ? ' group' : '');
674
+ el.className = 'card' + (card.group ? ' group' : '') + (card.heading ? ' heading' : '');
664
675
  el.dataset.nodeId = card.id;
665
676
  el.dataset.nodeType = card.kind;
666
677
  Object.assign(el.style, { left: card.x + 'px', top: card.y + 'px', width: card.w + 'px', height: card.h + 'px' });
@@ -58,6 +58,7 @@ export function validCanvasNodeUpdates(input: unknown): CanvasNodeUpdate[] {
58
58
  return (
59
59
  typeof update.id === 'string' &&
60
60
  (update.collapsed === undefined || typeof update.collapsed === 'boolean') &&
61
+ (update.zIndex === undefined || finite(update.zIndex) !== undefined) &&
61
62
  validateCanvasNodePatch(update) === null
62
63
  );
63
64
  });
@@ -1059,8 +1059,9 @@ export function resolveCanvasNode(nodeRef: CanvasNodeLookupInput):
1059
1059
 
1060
1060
  export async function refreshCanvasWebpageNode(
1061
1061
  id: string,
1062
- options: { url?: string } = {},
1062
+ options: { url?: string; signal?: AbortSignal } = {},
1063
1063
  ): Promise<{ ok: boolean; id: string; error?: string }> {
1064
+ options.signal?.throwIfAborted();
1064
1065
  const existing = canvasState.getNode(id);
1065
1066
  if (!existing || existing.type !== 'webpage') {
1066
1067
  return { ok: false, id, error: `Webpage node "${id}" not found.` };
@@ -1094,7 +1095,8 @@ export async function refreshCanvasWebpageNode(
1094
1095
  }
1095
1096
 
1096
1097
  try {
1097
- const snapshot = await fetchWebpageSnapshot(normalizedUrl);
1098
+ const snapshot = await fetchWebpageSnapshot(normalizedUrl, { signal: options.signal });
1099
+ options.signal?.throwIfAborted();
1098
1100
  if (canvasState.boardGeneration !== boardGeneration) {
1099
1101
  return { ok: false, id, error: 'The board changed during webpage refresh.' };
1100
1102
  }
@@ -1131,6 +1133,8 @@ export async function refreshCanvasWebpageNode(
1131
1133
  canvasState.updateNode(id, { data: nextData });
1132
1134
  return { ok: true, id };
1133
1135
  } catch (error) {
1136
+ // Cancellation leaves the previous content/status intact, not an error card.
1137
+ options.signal?.throwIfAborted();
1134
1138
  if (canvasState.boardGeneration !== boardGeneration) {
1135
1139
  return { ok: false, id, error: 'The board changed during webpage refresh.' };
1136
1140
  }
@@ -364,6 +364,7 @@ export interface CanvasNodeUpdate {
364
364
  position?: { x: number; y: number };
365
365
  size?: { width: number; height: number };
366
366
  collapsed?: boolean;
367
+ zIndex?: number;
367
368
  }
368
369
 
369
370
  export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
@@ -3220,6 +3221,9 @@ class CanvasStateManager {
3220
3221
  if (update.collapsed !== undefined && update.collapsed !== existing.collapsed) {
3221
3222
  nextPatch.collapsed = update.collapsed;
3222
3223
  }
3224
+ if (update.zIndex !== undefined && update.zIndex !== existing.zIndex) {
3225
+ nextPatch.zIndex = update.zIndex;
3226
+ }
3223
3227
  if (Object.keys(nextPatch).length === 0) {
3224
3228
  skipped++;
3225
3229
  continue;
@@ -3977,4 +3981,5 @@ class CanvasStateManager {
3977
3981
  // Module-level singleton — safe because Bun is single-threaded and this
3978
3982
  // module is imported once per process. Agent tools and the HTTP server share
3979
3983
  // the same instance; no locking needed.
3984
+ export const boardCatalogEpoch = randomUUID();
3980
3985
  export const canvasState = new CanvasStateManager();