pmx-canvas 0.11.0 → 0.12.1

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 (300) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +4 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +440 -18
  4. package/CHANGELOG.md +51 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +267 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1168 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1839 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +301 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +117 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/{abnfDiagram-N423BO3Z-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
  35. package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
  36. package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/{cose-bilkent-JH36ORCC-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
  42. package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
  43. package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
  44. package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
  45. package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
  46. package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
  47. package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
  48. package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
  49. package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/{ganttDiagram-PKOTCBZU-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
  55. package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
  56. package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
  57. package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
  58. package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
  59. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  60. package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
  61. package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
  62. package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
  63. package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
  64. package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
  65. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  66. package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
  67. package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
  68. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  69. package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
  70. package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
  71. package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
  72. package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
  73. package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
  74. package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
  75. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  76. package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
  77. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  78. package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
  79. package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
  80. package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
  81. package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
  82. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  83. package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
  84. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  85. package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
  86. package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
  87. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  88. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  89. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  90. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  91. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  92. package/dist/canvas/chunks/{railroadDiagram-AXF67PYL-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
  93. package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
  94. package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
  95. package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
  96. package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
  97. package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
  98. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  99. package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
  100. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  101. package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
  102. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  103. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  104. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  105. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  106. package/dist/canvas/chunks/{wardleyDiagram-T6FBY63Y-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
  107. package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
  108. package/dist/canvas/global.css +94 -1
  109. package/dist/canvas/index.js +154 -145
  110. package/dist/canvas/mcp-app.html +600 -498
  111. package/dist/json-render/index.css +1 -1
  112. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  113. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  114. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  115. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  116. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  117. package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
  118. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  119. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  120. package/dist/types/client/state/canvas-store.d.ts +15 -0
  121. package/dist/types/client/state/human-store.d.ts +2 -0
  122. package/dist/types/client/state/intent-bridge.d.ts +4 -2
  123. package/dist/types/client/state/relation-store.d.ts +11 -0
  124. package/dist/types/client/types.d.ts +1 -1
  125. package/dist/types/json-render/catalog.d.ts +2 -2
  126. package/dist/types/server/agent-presence.d.ts +1 -0
  127. package/dist/types/server/ax-interaction.d.ts +1 -0
  128. package/dist/types/server/ax-state-manager.d.ts +10 -2
  129. package/dist/types/server/board-local.d.ts +4 -0
  130. package/dist/types/server/board-move.d.ts +38 -0
  131. package/dist/types/server/canvas-db.d.ts +12 -2
  132. package/dist/types/server/canvas-operations.d.ts +5 -6
  133. package/dist/types/server/canvas-provenance.d.ts +1 -1
  134. package/dist/types/server/canvas-serialization.d.ts +1 -0
  135. package/dist/types/server/canvas-state.d.ts +84 -20
  136. package/dist/types/server/card-relocation.d.ts +6 -0
  137. package/dist/types/server/context-reads.d.ts +3 -1
  138. package/dist/types/server/human-presence.d.ts +1 -1
  139. package/dist/types/server/index.d.ts +16 -5
  140. package/dist/types/server/intent-registry.d.ts +8 -4
  141. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  142. package/dist/types/server/mutation-history.d.ts +7 -5
  143. package/dist/types/server/operations/registry.d.ts +2 -0
  144. package/dist/types/server/trace-manager.d.ts +1 -0
  145. package/dist/types/shared/ax-kinds.d.ts +6 -0
  146. package/dist/types/shared/ax-surface-protocol.d.ts +8 -0
  147. package/dist/types/shared/human-presence.d.ts +1 -0
  148. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  149. package/docs/RELEASE.md +21 -14
  150. package/docs/agent-plugin.md +22 -2
  151. package/docs/amp-plugin.md +270 -0
  152. package/docs/cli.md +42 -3
  153. package/docs/design/GuestName.dc.html +50 -0
  154. package/docs/design/PluginLogo.svg +7 -0
  155. package/docs/design/WorkCards.dc.html +39 -0
  156. package/docs/design/canvas.json +16 -0
  157. package/docs/design/plugin-logo.png +0 -0
  158. package/docs/design.md +111 -8
  159. package/docs/evals/agents-md-sections.md +38 -0
  160. package/docs/http-api.md +128 -10
  161. package/docs/mcp.md +61 -9
  162. package/docs/node-types.md +71 -1
  163. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  164. package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
  165. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  166. package/docs/product-vision-2026-09.md +33 -17
  167. package/docs/sdk.md +18 -0
  168. package/package.json +6 -3
  169. package/plugins/pmx-canvas/assets/preview.png +0 -0
  170. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +4 -1
  171. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  172. package/plugins/pmx-canvas/plugin.json +2 -2
  173. package/plugins/pmx-canvas/runtime.json +1 -1
  174. package/plugins/pmx-canvas/tools.json +440 -18
  175. package/skills/data-analysis/SKILL.md +13 -26
  176. package/skills/frontend-design/LICENSE.txt +177 -0
  177. package/skills/frontend-design/SKILL.md +3 -0
  178. package/skills/pmx-canvas/SKILL.md +248 -842
  179. package/skills/pmx-canvas/evals/evals.json +110 -5
  180. package/skills/pmx-canvas/references/full-reference.md +142 -28
  181. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  182. package/skills/pmx-canvas/references/installing-pmx-canvas.md +61 -11
  183. package/skills/pmx-canvas/references/tours-and-recording.md +39 -6
  184. package/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  185. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  186. package/skills/published-consumer-e2e/SKILL.md +25 -8
  187. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -75
  188. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  189. package/skills/web-artifacts-builder/SKILL.md +6 -0
  190. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +2 -2
  191. package/skills/web-artifacts-builder/scripts/init-artifact.sh +8 -8
  192. package/src/cli/commands/apps.ts +1 -0
  193. package/src/cli/commands/boards.ts +30 -0
  194. package/src/client/App.tsx +20 -4
  195. package/src/client/canvas/AttentionHistory.tsx +10 -2
  196. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  197. package/src/client/canvas/CanvasNode.tsx +45 -2
  198. package/src/client/canvas/CanvasViewport.tsx +38 -9
  199. package/src/client/canvas/ContextMenu.tsx +34 -12
  200. package/src/client/canvas/EdgeEditor.tsx +149 -0
  201. package/src/client/canvas/EdgeLayer.tsx +87 -16
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
  203. package/src/client/canvas/GroupNavigation.tsx +85 -0
  204. package/src/client/canvas/RelationsPanel.tsx +9 -0
  205. package/src/client/canvas/SelectionBar.tsx +21 -0
  206. package/src/client/canvas/SessionPanel.tsx +35 -1
  207. package/src/client/canvas/TextPrompt.tsx +18 -1
  208. package/src/client/canvas/TopBar.tsx +30 -0
  209. package/src/client/canvas/canvas-area.ts +1 -1
  210. package/src/client/nodes/ExtAppFrame.tsx +26 -10
  211. package/src/client/nodes/MarkdownNode.tsx +6 -7
  212. package/src/client/nodes/PromptNode.tsx +32 -3
  213. package/src/client/nodes/StatusNode.tsx +12 -0
  214. package/src/client/nodes/StatusSummary.tsx +11 -0
  215. package/src/client/nodes/ViewerFrame.tsx +21 -10
  216. package/src/client/nodes/WorkCard.tsx +175 -0
  217. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  218. package/src/client/state/attention-bridge.ts +5 -0
  219. package/src/client/state/attention-store.ts +1 -0
  220. package/src/client/state/canvas-store.ts +185 -15
  221. package/src/client/state/human-store.ts +22 -2
  222. package/src/client/state/intent-bridge.ts +19 -5
  223. package/src/client/state/relation-store.ts +127 -1
  224. package/src/client/theme/global.css +94 -1
  225. package/src/client/types.ts +1 -0
  226. package/src/mcp/server.ts +13 -3
  227. package/src/server/agent-presence.ts +43 -23
  228. package/src/server/ax-flow-loop.ts +6 -0
  229. package/src/server/ax-gate-ttl.ts +8 -1
  230. package/src/server/ax-interaction.ts +16 -10
  231. package/src/server/ax-state-manager.ts +17 -4
  232. package/src/server/ax-wait.ts +20 -6
  233. package/src/server/board-export.ts +19 -6
  234. package/src/server/board-local.ts +20 -0
  235. package/src/server/board-move.ts +297 -0
  236. package/src/server/canvas-db.ts +262 -160
  237. package/src/server/canvas-operations.ts +139 -109
  238. package/src/server/canvas-schema.ts +21 -0
  239. package/src/server/canvas-serialization.ts +2 -0
  240. package/src/server/canvas-state.ts +792 -159
  241. package/src/server/card-relocation.ts +53 -0
  242. package/src/server/code-graph.ts +4 -3
  243. package/src/server/context-reads.ts +83 -55
  244. package/src/server/demo-state.json +35 -15
  245. package/src/server/file-watcher.ts +30 -10
  246. package/src/server/human-presence.ts +18 -5
  247. package/src/server/index.ts +35 -15
  248. package/src/server/intent-registry.ts +43 -5
  249. package/src/server/knowledge-lifecycle.ts +32 -9
  250. package/src/server/lifecycle-status.ts +29 -1
  251. package/src/server/mcp-app-runtime.ts +26 -7
  252. package/src/server/mutation-history.ts +37 -8
  253. package/src/server/operations/composites.ts +6 -3
  254. package/src/server/operations/mcp-contracts.ts +15 -0
  255. package/src/server/operations/mcp.ts +36 -9
  256. package/src/server/operations/ops/annotation.ts +22 -1
  257. package/src/server/operations/ops/ax-await.ts +1 -0
  258. package/src/server/operations/ops/ax-timeline.ts +28 -2
  259. package/src/server/operations/ops/batch.ts +25 -11
  260. package/src/server/operations/ops/boards.ts +44 -3
  261. package/src/server/operations/ops/nodes.ts +70 -0
  262. package/src/server/operations/ops/query.ts +32 -3
  263. package/src/server/operations/ops/snapshots.ts +1 -0
  264. package/src/server/operations/registry.ts +49 -4
  265. package/src/server/server.ts +62 -16
  266. package/src/server/tour-control.ts +37 -12
  267. package/src/server/trace-manager.ts +4 -0
  268. package/src/server/web-artifacts/scripts/bundle-artifact.sh +2 -2
  269. package/src/server/web-artifacts/scripts/init-artifact.sh +8 -8
  270. package/src/shared/agent-context.ts +7 -1
  271. package/src/shared/auto-arrange.ts +5 -4
  272. package/src/shared/ax-kinds.ts +13 -0
  273. package/src/shared/ax-surface-protocol.ts +22 -0
  274. package/src/shared/human-presence.ts +1 -0
  275. package/src/shared/lifecycle-status.ts +2 -0
  276. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
  277. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
  278. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
  279. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
  280. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
  281. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
  282. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
  283. package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
  284. package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
  285. package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
  286. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
  287. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
  288. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
  289. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
  290. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
  291. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
  292. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
  293. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
  294. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
  295. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
  296. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
  297. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
  298. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
  299. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
  300. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
@@ -0,0 +1,149 @@
1
+ import { signal } from '@preact/signals';
2
+ import { useState } from 'preact/hooks';
3
+ import { RELATION_KINDS, relationKind } from '../../shared/relations.js';
4
+ import { activeBoard, activeBoardId, boardIncarnation } from '../state/boards-store';
5
+ import { edges, selectedEdgeId, viewport } from '../state/canvas-store';
6
+ import { removeEdgeFromClient, updateEdgeFromClient } from '../state/intent-bridge';
7
+ import type { CanvasEdge } from '../types';
8
+ import { canvasArea } from './canvas-area';
9
+
10
+ export const edgeEditorAnchor = signal<{ id: string; incarnation: number; point: { x: number; y: number } } | null>(
11
+ null,
12
+ );
13
+
14
+ /** Like EdgeHint, the editor lives in App's screen layer, outside the transformed world. */
15
+ export function SelectedEdgeEditor() {
16
+ const anchor = edgeEditorAnchor.value;
17
+ const edge = anchor ? edges.value.get(anchor.id) : null;
18
+ return anchor && edge && anchor.id === selectedEdgeId.value && anchor.incarnation === boardIncarnation ? (
19
+ <EdgeEditor key={`${anchor.incarnation}:${edge.id}`} edge={edge} point={anchor.point} />
20
+ ) : null;
21
+ }
22
+
23
+ /** Screen-sized controls anchored to the selected relation, not scaled with its SVG. */
24
+ export function EdgeEditor({ edge, point }: { edge: CanvasEdge; point: { x: number; y: number } }) {
25
+ const [field, setField] = useState<'label' | 'reason' | 'kind' | null>(null);
26
+ const [value, setValue] = useState('');
27
+ const [pending, setPending] = useState(false);
28
+ const [error, setError] = useState('');
29
+ const [incarnation] = useState(boardIncarnation);
30
+ const [boardId] = useState(activeBoardId.value);
31
+ const edit = (next: NonNullable<typeof field>) => {
32
+ setField(next);
33
+ setValue(next === 'kind' ? relationKind(edge) : (edge[next] ?? ''));
34
+ setError('');
35
+ };
36
+ const save = async (patch: Parameters<typeof updateEdgeFromClient>[1] | null) => {
37
+ if (pending || incarnation !== boardIncarnation) return;
38
+ setPending(true);
39
+ const result = patch
40
+ ? await updateEdgeFromClient(edge.id, patch, boardId)
41
+ : await removeEdgeFromClient(edge.id, boardId);
42
+ if (incarnation !== boardIncarnation) return;
43
+ setPending(false);
44
+ if (result.ok) {
45
+ setField(null);
46
+ setError('');
47
+ if (!patch && selectedEdgeId.value === edge.id) selectedEdgeId.value = null;
48
+ } else setError('Change refused. Your draft is still here.');
49
+ };
50
+ const area = canvasArea();
51
+ const view = viewport.value;
52
+ const width = Math.min(320, area.width - 24);
53
+ const x = Math.max(
54
+ area.left + 12,
55
+ Math.min(area.left + area.width - width - 12, area.left + point.x * view.scale + view.x - width / 2),
56
+ );
57
+ const y = Math.max(
58
+ area.top + 8,
59
+ Math.min(area.top + area.height - (field ? 180 : 60), area.top + point.y * view.scale + view.y + 20),
60
+ );
61
+ const kinds = [
62
+ ...new Set(
63
+ [...RELATION_KINDS, ...(activeBoard()?.relationVocabulary ?? [])]
64
+ .map((item) => item.kind)
65
+ .concat(relationKind(edge)),
66
+ ),
67
+ ];
68
+ return (
69
+ <form
70
+ class="edge-editor"
71
+ aria-label="Edit relation"
72
+ style={{ left: x, top: y, width }}
73
+ onPointerDown={(event) => event.stopPropagation()}
74
+ onKeyDown={(event) => {
75
+ event.stopPropagation();
76
+ if (event.key === 'Escape' && !pending) {
77
+ event.preventDefault();
78
+ setField(null);
79
+ selectedEdgeId.value = null;
80
+ }
81
+ }}
82
+ onSubmit={(event) => {
83
+ event.preventDefault();
84
+ if (field) void save({ [field]: value });
85
+ }}
86
+ >
87
+ <fieldset disabled={pending}>
88
+ <div class="edge-editor-tools">
89
+ <select
90
+ aria-label="Relation kind"
91
+ value={relationKind(edge)}
92
+ onChange={(event) => {
93
+ const kind = event.currentTarget.value;
94
+ if (kind === '__custom') edit('kind');
95
+ else {
96
+ setField('kind');
97
+ setValue(kind);
98
+ void save({ kind });
99
+ }
100
+ }}
101
+ >
102
+ {kinds.map((kind) => (
103
+ <option key={kind} value={kind}>
104
+ {kind}
105
+ </option>
106
+ ))}
107
+ <option value="__custom">Custom…</option>
108
+ </select>
109
+ <button type="button" onClick={() => edit('label')}>
110
+ Label
111
+ </button>
112
+ <button type="button" onClick={() => edit('reason')}>
113
+ Why
114
+ </button>
115
+ <button type="button" onClick={() => void save(null)}>
116
+ Delete
117
+ </button>
118
+ </div>
119
+ {field && (
120
+ <div class="edge-editor-field">
121
+ <label>
122
+ {field === 'reason' ? 'Why this relation?' : field === 'label' ? 'Label' : 'Custom kind'}
123
+ <input
124
+ autoFocus
125
+ value={value}
126
+ required={field === 'kind'}
127
+ maxLength={field === 'kind' ? 80 : field === 'reason' ? 4000 : 1000}
128
+ onInput={(event) => setValue(event.currentTarget.value)}
129
+ />
130
+ </label>
131
+ <div>
132
+ <button type="submit">Save</button>{' '}
133
+ <button
134
+ type="button"
135
+ onClick={() => {
136
+ setField(null);
137
+ setError('');
138
+ }}
139
+ >
140
+ Cancel
141
+ </button>
142
+ </div>
143
+ </div>
144
+ )}
145
+ {error && <p role="alert">{error}</p>}
146
+ </fieldset>
147
+ </form>
148
+ );
149
+ }
@@ -7,15 +7,23 @@ import {
7
7
  selectedEdgeId,
8
8
  viewport,
9
9
  visibleNodeFor,
10
+ groupContents,
10
11
  } from '../state/canvas-store';
11
12
  import { linkMarks, writerName } from '../state/context-status-store';
12
13
  import { HUMAN_STARTED_SESSION_LABEL } from '../../shared/agent-presence';
13
14
  import { activeSession } from '../state/presence-store';
14
- import { activeBoard } from '../state/boards-store';
15
+ import { activeBoard, boardIncarnation } from '../state/boards-store';
15
16
  import { reportClientViewportSize } from '../state/intent-bridge';
16
17
  import { relationDefinition, relationKind, relationText } from '../../shared/relations.js';
17
18
  import type { CanvasEdge, CanvasNodeState } from '../types';
18
- import { relationFilter, relationFocus, relationHighlight } from '../state/relation-store';
19
+ import {
20
+ dependencyAnalysis,
21
+ relationWarning,
22
+ relationFilter,
23
+ relationFocus,
24
+ relationHighlight,
25
+ } from '../state/relation-store';
26
+ import { edgeEditorAnchor } from './EdgeEditor';
19
27
 
20
28
  /** Filter only the kinds on this board; shared presets are managed in Home. */
21
29
  export function RelationLegend() {
@@ -71,6 +79,17 @@ export function RelationLegend() {
71
79
  </button>
72
80
  );
73
81
  })}
82
+ {dependencyAnalysis.value.hasDependencies && (
83
+ <>
84
+ <span class={dependencyAnalysis.value.cycles ? 'relation-warning' : ''}>
85
+ {dependencyAnalysis.value.cycles ? '⚠ ' : ''}
86
+ {dependencyAnalysis.value.cycles} dependency cycles
87
+ </span>
88
+ {dependencyAnalysis.value.focused && (
89
+ <span>Change selection → {dependencyAnalysis.value.affected.size} affected</span>
90
+ )}
91
+ </>
92
+ )}
74
93
  </div>
75
94
  );
76
95
  }
@@ -312,6 +331,7 @@ function edgeLayout(
312
331
  scale: number,
313
332
  obstacles: LabelObstacle[],
314
333
  textWidth: (text: string, weight?: number) => number,
334
+ parallelCount: number,
315
335
  ) {
316
336
  const dx = end.x - start.x;
317
337
  const dy = end.y - start.y;
@@ -334,7 +354,16 @@ function edgeLayout(
334
354
  const zoom = edgeZoom.value;
335
355
  const marks = linkMarks(edge);
336
356
  // Reasons belong in the hint/inspector, not a long pill covering both endpoints.
337
- const label = edge.label ?? edge.kind;
357
+ const baseLabel = edge.label ?? edge.kind;
358
+ const label = parallelCount > 1 ? `${baseLabel ?? relationKind(edge)} · ×${parallelCount}` : baseLabel;
359
+ const warning = [
360
+ dependencyAnalysis.value.cycleEdges.has(edge.id) ? 'Cycle' : '',
361
+ relationWarning(edge).length ? 'Source needs review' : '',
362
+ ]
363
+ .filter(Boolean)
364
+ .join(' · ');
365
+ const warningText = warning ? `⚠ ${warning}` : '';
366
+ const warningWidth = warning ? textWidth(warningText, 500) + 12 : 0;
338
367
  const showLabel = !!label && zoom >= 0.35;
339
368
  // Pill layout in label units (× labelScale): the label, then a hairline and
340
369
  // "not seen by …", which keeps only its glyph on a short line or below 60% zoom.
@@ -346,12 +375,12 @@ function edgeLayout(
346
375
  // A short line: the full pill plus some line on either side would not fit.
347
376
  const notSeenWords = marks.notSeen && zoom >= 0.6 && dist >= (fullWidth + 60) * labelScale;
348
377
  const notSeenWidth = marks.notSeen ? (labelWidth ? 12 : 0) + (notSeenWords ? wordedNotSeenWidth : 12) : 0;
349
- const pillWidth = pad * 2 + labelWidth + notSeenWidth;
378
+ const pillWidth = pad * 2 + labelWidth + notSeenWidth + warningWidth;
350
379
  const labelX = -pillWidth / 2 + pad;
351
380
  const notSeenX = labelX + labelWidth + (labelWidth ? 12 : 0);
352
- const hasPill = showLabel || marks.notSeen;
353
- const curveMid = hasPill ? bezierMidpoint(start.x, start.y, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y) : null;
354
- const mid = curveMid ? placeLabel(curveMid, pillWidth * labelScale, 20 * labelScale, obstacles, 4 * scale) : null;
381
+ const hasPill = showLabel || marks.notSeen || warningWidth > 0;
382
+ const curveMid = bezierMidpoint(start.x, start.y, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
383
+ const mid = hasPill ? placeLabel(curveMid, pillWidth * labelScale, 20 * labelScale, obstacles, 4 * scale) : null;
355
384
  if (mid) {
356
385
  obstacles.push({
357
386
  x: mid.x - (pillWidth / 2) * labelScale,
@@ -377,6 +406,8 @@ function edgeLayout(
377
406
  notSeenX,
378
407
  curveMid,
379
408
  mid,
409
+ warningText,
410
+ warningX: labelX + labelWidth + notSeenWidth + 6,
380
411
  };
381
412
  }
382
413
 
@@ -406,12 +437,15 @@ function EdgePath({ edge, fromNode, toNode, layout, focused, dimmed, selected, s
406
437
  // that LANDED on an edge must not immediately undo itself.
407
438
  e.stopPropagation();
408
439
  selectedEdgeId.value = edge.id;
440
+ edgeHint.value = null;
409
441
  };
410
442
  const showHint = (anchor: Element, point?: { x: number; y: number }) => {
443
+ if (selectedEdgeId.value === edge.id) return;
411
444
  const text = [
412
445
  `${String(fromNode.data.title ?? fromNode.id)} → ${String(toNode.data.title ?? toNode.id)}`,
413
446
  relationText(edge),
414
447
  linkHint(edge, marks.notSeen),
448
+ ...relationWarning(edge),
415
449
  ]
416
450
  .filter(Boolean)
417
451
  .join('\n');
@@ -511,6 +545,17 @@ function EdgePath({ edge, fromNode, toNode, layout, focused, dimmed, selected, s
511
545
  )}
512
546
  </g>
513
547
  )}
548
+ {layout.warningText && (
549
+ <text
550
+ class="edge-label"
551
+ x={layout.warningX * labelScale}
552
+ text-anchor="start"
553
+ dominant-baseline="central"
554
+ fill="var(--c-warn)"
555
+ >
556
+ {layout.warningText}
557
+ </text>
558
+ )}
514
559
  </g>
515
560
  );
516
561
  }
@@ -615,7 +660,17 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
615
660
  return Math.ceil(Math.max(metrics.width, metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight));
616
661
  };
617
662
  const nodeMap = nodes.value;
618
- const edgeList = Array.from(edges.value.values());
663
+ const inside = groupContents.value;
664
+ const edgeList = Array.from(edges.value.values()).filter(
665
+ (edge) => !inside || inside.has(edge.from) || inside.has(edge.to),
666
+ );
667
+ const resolve = (id: string) => {
668
+ const visible = visibleNodeFor(id);
669
+ return nodeMap.get(visible?.id ?? id) ?? nodeMap.get(id);
670
+ };
671
+ const pairs = new Map<string, number>();
672
+ const pairKey = (edge: CanvasEdge) => JSON.stringify([edge.from, edge.to].sort());
673
+ for (const edge of edgeList) if (!edge.toBoardId) pairs.set(pairKey(edge), (pairs.get(pairKey(edge)) ?? 0) + 1);
619
674
  const scale = edgeChromeScale(edgeZoom.value);
620
675
 
621
676
  // Allocate a port to every incident link, including parallel and reverse links.
@@ -623,8 +678,8 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
623
678
  const sides = new Map<string, Array<{ anchor: Anchor; node: CanvasNodeState; order: number; id: string }>>();
624
679
  for (const edge of edgeList) {
625
680
  if (edge.toBoardId) continue;
626
- const from = visibleNodeFor(edge.from);
627
- const to = visibleNodeFor(edge.to);
681
+ const from = resolve(edge.from);
682
+ const to = resolve(edge.to);
628
683
  if (!from || !to || from.id === to.id) continue;
629
684
  const start = computeAnchor(from, to);
630
685
  const end = computeAnchor(to, from);
@@ -657,7 +712,7 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
657
712
  const obstacles: LabelObstacle[] = [];
658
713
  const portSize = 32 / edgeZoom.value;
659
714
  for (const node of nodeMap.values()) {
660
- if (visibleNodeFor(node.id)?.id !== node.id) continue;
715
+ if (resolve(node.id)?.id !== node.id) continue;
661
716
  const frame = node.type === 'group' && !node.collapsed;
662
717
  const { x, y } = node.position;
663
718
  const { width, height } = node.size;
@@ -681,14 +736,30 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
681
736
  const layouts = new Map<string, ReturnType<typeof edgeLayout>>();
682
737
  for (const edge of edgeList) {
683
738
  const anchors = ports.get(edge.id);
684
- if (anchors) layouts.set(edge.id, edgeLayout(edge, anchors.start, anchors.end, scale, obstacles, textWidth));
739
+ if (anchors)
740
+ layouts.set(
741
+ edge.id,
742
+ edgeLayout(edge, anchors.start, anchors.end, scale, obstacles, textWidth, pairs.get(pairKey(edge)) ?? 1),
743
+ );
685
744
  }
686
- return { nodeMap, edgeList, layouts, scale };
745
+ return { nodeMap, edgeList, layouts, scale, resolve };
687
746
  }),
688
747
  [nodes, edges, textContext, font],
689
748
  );
690
749
 
691
- const { nodeMap, edgeList, layouts, scale } = geometry.value;
750
+ const { nodeMap, edgeList, layouts, scale, resolve } = geometry.value;
751
+ const selected = selectedEdgeId.value;
752
+ const layout = selected ? layouts.get(selected) : null;
753
+ const point = layout?.mid ?? layout?.curveMid;
754
+ useLayoutEffect(() => {
755
+ edgeEditorAnchor.value = selected && point ? { id: selected, incarnation: boardIncarnation, point } : null;
756
+ }, [selected, point?.x, point?.y, boardIncarnation]);
757
+ useEffect(
758
+ () => () => {
759
+ edgeEditorAnchor.value = null;
760
+ },
761
+ [],
762
+ );
692
763
  const focusId = relationFocus.value;
693
764
  const hasFocus = focusId !== null;
694
765
  const searchSet = searchHighlightIds.value;
@@ -758,8 +829,8 @@ export function EdgeLayer({ nodes, edges }: EdgeLayerProps) {
758
829
  // Remote endpoints are navigable links, never a same-ID card on this board.
759
830
  if (edge.toBoardId) return null;
760
831
  // An endpoint hidden behind a collapsed group's chip is drawn to the chip.
761
- const fromNode = visibleNodeFor(edge.from);
762
- const toNode = visibleNodeFor(edge.to);
832
+ const fromNode = resolve(edge.from);
833
+ const toNode = resolve(edge.to);
763
834
  const layout = layouts.get(edge.id);
764
835
  if (!fromNode || !toNode || !layout) return null;
765
836
  const isConnected = hasFocus && (edge.from === focusId || edge.to === focusId);
@@ -1,5 +1,7 @@
1
1
  import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
2
2
  import { BarHint } from './BarHint';
3
+ import { AttentionHistory } from './AttentionHistory';
4
+ import { AttentionToast } from './AttentionToast';
3
5
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
4
6
  import { ContextNode } from '../nodes/ContextNode';
5
7
  import { DiffNode } from '../nodes/DiffNode';
@@ -397,6 +399,8 @@ export function ExpandedNodeOverlay() {
397
399
  {pendingClose ? 'Saving edits…' : 'esc or click outside to close'}
398
400
  </span>
399
401
  </div>
402
+ <AttentionToast />
403
+ <AttentionHistory />
400
404
  {canPresent && presenting && (
401
405
  <div
402
406
  ref={presentationOverlayRef}
@@ -0,0 +1,85 @@
1
+ import { activeBoard } from '../state/boards-store';
2
+ import { useLayoutEffect, useRef } from 'preact/hooks';
3
+ import { reportClientViewportSize } from '../state/intent-bridge';
4
+ import { focusNode, groupContents, groupPath, groupViewNodes, leaveGroup, nodes } from '../state/canvas-store';
5
+
6
+ export function GroupBreadcrumbs() {
7
+ const path = groupPath.value;
8
+ const ref = useRef<HTMLElement>(null);
9
+ useLayoutEffect(() => {
10
+ const element = ref.current;
11
+ if (!element) return;
12
+ const resize = new ResizeObserver(() => void reportClientViewportSize());
13
+ resize.observe(element);
14
+ return () => resize.disconnect();
15
+ }, [path.length > 0]);
16
+ if (!path.length) return null;
17
+ return (
18
+ <nav
19
+ ref={ref}
20
+ class="group-breadcrumbs"
21
+ aria-label="Group navigation"
22
+ onPointerDown={(event) => event.stopPropagation()}
23
+ >
24
+ <button type="button" onClick={() => leaveGroup(0)}>
25
+ {activeBoard()?.name ?? 'Board'}
26
+ </button>
27
+ {path.map((visit, index) => (
28
+ <span key={visit.id}>
29
+ <span aria-hidden="true"> / </span>
30
+ <button
31
+ type="button"
32
+ aria-current={index === path.length - 1 ? 'location' : undefined}
33
+ disabled={index === path.length - 1}
34
+ onClick={() => leaveGroup(index + 1)}
35
+ >
36
+ {String(nodes.value.get(visit.id)?.data.title ?? 'Group')}
37
+ </button>
38
+ </span>
39
+ ))}
40
+ <button type="button" onClick={() => leaveGroup()} aria-label="Leave group">
41
+ Esc · goes up
42
+ </button>
43
+ </nav>
44
+ );
45
+ }
46
+
47
+ export function GroupOutsideLinks() {
48
+ const inside = groupContents.value;
49
+ if (!inside) return null;
50
+ const outside = [...groupViewNodes.value.values()].filter((node) => !inside.has(node.id));
51
+ const children = [...nodes.value.values()].filter((node) => inside.has(node.id));
52
+ const root = nodes.value.get(groupPath.value.at(-1)!.id)!;
53
+ const left = children.length ? Math.min(...children.map((node) => node.position.x)) - 24 : root.position.x;
54
+ const top = children.length ? Math.min(...children.map((node) => node.position.y)) - 40 : root.position.y;
55
+ const right = children.length
56
+ ? Math.max(...children.map((node) => node.position.x + node.size.width)) + 24
57
+ : left + 320;
58
+ const bottom = children.length
59
+ ? Math.max(...children.map((node) => node.position.y + node.size.height)) + 24
60
+ : top + 120;
61
+ return (
62
+ <>
63
+ <div class="group-inside-frame" style={{ left, top, width: right - left, height: bottom - top }}>
64
+ <span>Inside {String(root.data.title ?? 'Group')}</span>
65
+ </div>
66
+ {outside.map((node, index) => (
67
+ <div key={node.id} class="group-outside-endpoint" style={{ left: node.position.x, top: node.position.y }}>
68
+ {index === 0 && <span class="group-outside-label">Outside</span>}
69
+ <button
70
+ type="button"
71
+ class="group-outside-stub"
72
+ onPointerDown={(event) => event.stopPropagation()}
73
+ onClick={(event) => {
74
+ event.stopPropagation();
75
+ focusNode(node.id, { recordHistory: false, persistStacking: false });
76
+ }}
77
+ >
78
+ ↗ {String(node.data.title ?? node.type)}
79
+ <span>{node.type} · Show on canvas</span>
80
+ </button>
81
+ </div>
82
+ ))}
83
+ </>
84
+ );
85
+ }
@@ -4,6 +4,7 @@ import { relationDefinition, relationSourceEnd } from '../../shared/relations.js
4
4
  import { activeBoardId, boardList, openMemoryTarget } from '../state/boards-store';
5
5
  import { edges, nodes } from '../state/canvas-store';
6
6
  import { requestJson } from '../state/intent-bridge';
7
+ import { showRelationsOnCanvas } from '../state/relation-store';
7
8
 
8
9
  /** The expanded card's evidence and uses, from the same graph agents navigate. */
9
10
  export function RelationsPanel({ nodeId }: { nodeId: string }) {
@@ -50,6 +51,14 @@ export function RelationsPanel({ nodeId }: { nodeId: string }) {
50
51
  return (
51
52
  <aside class="relations-panel" aria-label="Relations">
52
53
  <h3>Relations</h3>
54
+ <div class="relation-navigation">
55
+ <button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'lineage')}>
56
+ Show lineage
57
+ </button>
58
+ <button type="button" onClick={() => showRelationsOnCanvas(nodeId, 'connections')}>
59
+ Show on canvas
60
+ </button>
61
+ </div>
53
62
  {!graph && <p role="status">{unavailable ? 'Relations unavailable. Reopen to retry.' : 'Loading relations…'}</p>}
54
63
  {graph && relations.length === 0 && <p>No connections yet.</p>}
55
64
  {relations.map((edge, index) => {
@@ -13,6 +13,7 @@ import { createEdgeFromClient, createGroupFromClient, removeNodeFromClient } fro
13
13
  import { sessionActive } from '../state/presence-store';
14
14
  import { BarHint } from './BarHint';
15
15
  import { useChromeHeight } from './use-chrome-height';
16
+ import { openBoardMove } from './BoardMoveDialog';
16
17
 
17
18
  /**
18
19
  * Floating bottom-center bar for a multi-selection (rail-chrome-v2 phase 7,
@@ -174,6 +175,26 @@ export function SelectionBar() {
174
175
  </BarHint>
175
176
  </>
176
177
  )}
178
+ <BarHint label="Move to new board" side="up">
179
+ <button
180
+ type="button"
181
+ class="selection-bar-icon"
182
+ aria-label="Move to new board"
183
+ onClick={() => openBoardMove([...selectedNodeIds.value])}
184
+ >
185
+ <svg
186
+ width="16"
187
+ height="16"
188
+ viewBox="0 0 16 16"
189
+ fill="none"
190
+ stroke="currentColor"
191
+ stroke-width="1.5"
192
+ aria-hidden="true"
193
+ >
194
+ <path d="M6 3H2v10h12V9M8 8l6-6M9 2h5v5" />
195
+ </svg>
196
+ </button>
197
+ </BarHint>
177
198
  <BarHint label="Pin to agent context" side="up">
178
199
  <button type="button" class="selection-bar-action is-pin" onClick={handlePinContext}>
179
200
  <span aria-hidden="true">✦</span>
@@ -4,7 +4,9 @@ import { IconChevronRight, IconUndo } from '../icons';
4
4
  import { useNow } from './use-now';
5
5
  import { agentPhaseLabel } from '../../shared/agent-presence.js';
6
6
  import { formatCountdown, gateRemainingMs } from '../../shared/approval-gates.js';
7
- import { focusNode, selectedNodeIds } from '../state/canvas-store';
7
+ import { focusNode, nodes, selectedNodeIds } from '../state/canvas-store';
8
+ import { activeBoardId } from '../state/boards-store';
9
+ import { createNodeFromClient } from '../state/intent-bridge';
8
10
  import { activeSession, agentPresences, attachedSessions, writerColor } from '../state/presence-store';
9
11
  import {
10
12
  type ApprovalGateView,
@@ -96,6 +98,35 @@ function clock(iso: string): string {
96
98
  }
97
99
 
98
100
  function WorkItemRow({ item }: { item: WorkItemView }) {
101
+ const [busy, setBusy] = useState(false);
102
+ const [pending, setPending] = useState<{ boardId: string; nodeId: string } | null>(null);
103
+ const boardId = activeBoardId.value;
104
+ const boardNodes = nodes.value;
105
+ const presentation = [...nodes.value.values()].find(
106
+ (node) => node.type === 'status' && node.data.workItemId === item.id,
107
+ );
108
+ useEffect(() => {
109
+ if (!pending) return;
110
+ if (boardId === pending.boardId && !boardNodes.has(pending.nodeId)) return;
111
+ if (boardId === pending.boardId) focusNode(pending.nodeId);
112
+ setPending(null);
113
+ setBusy(false);
114
+ }, [pending, boardId, boardNodes]);
115
+ const show = async () => {
116
+ if (presentation) return focusNode(presentation.id);
117
+ if (!boardId || busy) return;
118
+ setBusy(true);
119
+ const result = await createNodeFromClient({
120
+ type: 'status',
121
+ data: { workItemId: item.id },
122
+ width: 360,
123
+ height: 200,
124
+ expectedBoardId: boardId,
125
+ });
126
+ if (result.ok && result.id && activeBoardId.value === boardId) {
127
+ setPending({ boardId, nodeId: result.id });
128
+ } else setBusy(false);
129
+ };
99
130
  const glyph = WORK_GLYPH[item.status] ?? 'queued';
100
131
  const nodeId = item.nodeIds[0];
101
132
  return (
@@ -106,6 +137,9 @@ function WorkItemRow({ item }: { item: WorkItemView }) {
106
137
  {item.title}
107
138
  </button>
108
139
  {item.detail && <div class="session-item-detail">{item.detail}</div>}
140
+ <button type="button" class="session-show-work" disabled={busy} onClick={() => void show()}>
141
+ {busy ? 'Showing…' : presentation ? 'Show card' : 'Show on board'}
142
+ </button>
109
143
  </div>
110
144
  <span class="session-item-status">{WORK_STATUS_TEXT[item.status] ?? glyph}</span>
111
145
  <span class="session-item-time">{clock(item.updatedAt)}</span>
@@ -1,5 +1,6 @@
1
1
  import { signal } from '@preact/signals';
2
2
  import { useEffect, useRef } from 'preact/hooks';
3
+ import { useFocusTrap } from './use-focus-trap';
3
4
 
4
5
  /**
5
6
  * In-canvas replacement for `window.prompt`, which embedded browser panes
@@ -9,6 +10,8 @@ import { useEffect, useRef } from 'preact/hooks';
9
10
  interface TextPromptRequest {
10
11
  title: string;
11
12
  placeholder: string;
13
+ description?: string;
14
+ maxLength?: number;
12
15
  /** Prefilled (and pre-selected) value — edit flows start from the current text. */
13
16
  initial: string;
14
17
  /** Submitting an empty field resolves '' instead of null — "clear the value" flows. */
@@ -25,7 +28,7 @@ let promptSeq = 0;
25
28
  export function askText(
26
29
  title: string,
27
30
  placeholder: string,
28
- opts: { initial?: string; allowEmpty?: boolean; confirm?: string } = {},
31
+ opts: { initial?: string; allowEmpty?: boolean; confirm?: string; description?: string; maxLength?: number } = {},
29
32
  ): Promise<string | null> {
30
33
  return new Promise((resolve) => {
31
34
  textPromptRequest.value?.resolve(null);
@@ -34,6 +37,8 @@ export function askText(
34
37
  seq: promptSeq,
35
38
  title,
36
39
  placeholder,
40
+ description: opts.description,
41
+ maxLength: opts.maxLength,
37
42
  initial: opts.initial ?? '',
38
43
  allowEmpty: opts.allowEmpty ?? false,
39
44
  confirm: opts.confirm ?? 'Add',
@@ -51,6 +56,8 @@ function settle(value: string | null): void {
51
56
  export function TextPrompt() {
52
57
  const request = textPromptRequest.value;
53
58
  const inputRef = useRef<HTMLInputElement>(null);
59
+ const dialogRef = useRef<HTMLDivElement>(null);
60
+ useFocusTrap(dialogRef, request !== null, { initial: inputRef });
54
61
 
55
62
  useEffect(() => {
56
63
  if (!request) return;
@@ -78,13 +85,21 @@ export function TextPrompt() {
78
85
  return (
79
86
  <div class="text-prompt-backdrop" onPointerDown={() => settle(null)}>
80
87
  <div
88
+ ref={dialogRef}
81
89
  class="text-prompt"
82
90
  role="dialog"
91
+ aria-modal="true"
83
92
  aria-label={request.title}
93
+ aria-describedby={request.description ? 'text-prompt-description' : undefined}
84
94
  data-testid="text-prompt"
85
95
  onPointerDown={(e) => e.stopPropagation()}
86
96
  >
87
97
  <div class="text-prompt-title">{request.title}</div>
98
+ {request.description && (
99
+ <div id="text-prompt-description" class="text-prompt-description">
100
+ {request.description}
101
+ </div>
102
+ )}
88
103
  <input
89
104
  // A fresh element per ask (so defaultValue applies) focused in the
90
105
  // ref callback, which Preact runs synchronously during commit — the
@@ -101,6 +116,8 @@ export function TextPrompt() {
101
116
  type="text"
102
117
  defaultValue={request.initial}
103
118
  placeholder={request.placeholder}
119
+ aria-label={request.title}
120
+ maxLength={request.maxLength}
104
121
  onKeyDown={(e) => {
105
122
  // Contained: the global shortcut layer must not see these.
106
123
  if (e.key === 'Enter') {