pmx-canvas 0.8.2 → 0.9.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 (338) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +601 -22
  3. package/CHANGELOG.md +59 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2724 -476
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2899 -596
  8. package/dist/json-render/index.js +20 -20
  9. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  10. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  11. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  12. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  13. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  14. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  15. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  16. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  17. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  18. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  19. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  20. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  21. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  22. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  23. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  24. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  25. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  26. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  27. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  28. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  29. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  30. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  31. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  32. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  33. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  34. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  35. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  36. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  37. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  38. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  39. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  40. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  41. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  42. package/dist/types/client/icons.d.ts +0 -10
  43. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  44. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  45. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  46. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  47. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  48. package/dist/types/client/state/boards-store.d.ts +81 -9
  49. package/dist/types/client/state/canvas-store.d.ts +14 -1
  50. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  51. package/dist/types/client/state/context-status-store.d.ts +35 -0
  52. package/dist/types/client/state/human-store.d.ts +2 -1
  53. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  54. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  55. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  56. package/dist/types/client/state/presence-store.d.ts +4 -4
  57. package/dist/types/client/state/presentation.d.ts +28 -0
  58. package/dist/types/client/state/relation-store.d.ts +10 -0
  59. package/dist/types/client/state/session-store.d.ts +41 -2
  60. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  61. package/dist/types/client/types.d.ts +13 -1
  62. package/dist/types/client/utils/keyboard.d.ts +2 -0
  63. package/dist/types/client/utils/time-ago.d.ts +4 -0
  64. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  65. package/dist/types/server/agent-presence.d.ts +10 -3
  66. package/dist/types/server/ax-context.d.ts +1 -1
  67. package/dist/types/server/canvas-db.d.ts +30 -7
  68. package/dist/types/server/canvas-operations.d.ts +24 -2
  69. package/dist/types/server/canvas-provenance.d.ts +22 -2
  70. package/dist/types/server/canvas-serialization.d.ts +2 -0
  71. package/dist/types/server/canvas-state.d.ts +93 -15
  72. package/dist/types/server/canvas-validation.d.ts +1 -1
  73. package/dist/types/server/context-brief.d.ts +94 -6
  74. package/dist/types/server/context-reads.d.ts +33 -7
  75. package/dist/types/server/edit-summary.d.ts +20 -0
  76. package/dist/types/server/index.d.ts +71 -10
  77. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  78. package/dist/types/server/lifecycle-status.d.ts +10 -0
  79. package/dist/types/server/memory-graph.d.ts +9 -0
  80. package/dist/types/server/mutation-history.d.ts +2 -0
  81. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  82. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  83. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  84. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  85. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  86. package/dist/types/server/operations/ops/query.d.ts +23 -0
  87. package/dist/types/server/operations/registry.d.ts +1 -0
  88. package/dist/types/server/placement.d.ts +7 -0
  89. package/dist/types/server/scope-fence.d.ts +2 -1
  90. package/dist/types/server/spatial-analysis.d.ts +6 -3
  91. package/dist/types/server/tour-control.d.ts +32 -0
  92. package/dist/types/server/wiki-links.d.ts +14 -0
  93. package/dist/types/server/workbench-auth.d.ts +2 -0
  94. package/dist/types/shared/agent-context.d.ts +2 -0
  95. package/dist/types/shared/agent-presence.d.ts +15 -12
  96. package/dist/types/shared/auto-arrange.d.ts +1 -0
  97. package/dist/types/shared/boards.d.ts +4 -4
  98. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  99. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  100. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  101. package/dist/types/shared/memory-graph.d.ts +53 -0
  102. package/dist/types/shared/relations.d.ts +68 -0
  103. package/dist/types/shared/semantic-attention.d.ts +2 -0
  104. package/dist/types/shared/tour.d.ts +38 -0
  105. package/dist/types/shared/workspace-settings.d.ts +11 -0
  106. package/docs/RELEASE.md +21 -3
  107. package/docs/adr-001-bun-only-runtime.md +7 -1
  108. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  109. package/docs/api-reference.md +109 -0
  110. package/docs/ax-host-adapter-contract.md +1 -1
  111. package/docs/ax-state-contract.md +5 -4
  112. package/docs/cli.md +153 -15
  113. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  114. package/docs/design/AgentContext.dc.html +3 -3
  115. package/docs/design/BoardPins.dc.html +37 -22
  116. package/docs/design/Categories.dc.html +208 -0
  117. package/docs/design/CategoryEditor.dc.html +33 -0
  118. package/docs/design/Chrome.dc.html +52 -9
  119. package/docs/design/ContextChip.dc.html +224 -0
  120. package/docs/design/ContextChip600.dc.html +136 -0
  121. package/docs/design/EmptyState.dc.html +45 -0
  122. package/docs/design/Flows.dc.html +23 -1
  123. package/docs/design/HeaderMarks.dc.html +183 -0
  124. package/docs/design/Home.dc.html +19 -1
  125. package/docs/design/HomeGraph.dc.html +86 -202
  126. package/docs/design/HomeMap.dc.html +23 -1
  127. package/docs/design/HomeTeam.dc.html +32 -14
  128. package/docs/design/Lifecycle.dc.html +22 -0
  129. package/docs/design/Lifecycle600.dc.html +18 -0
  130. package/docs/design/LifecycleVariants.dc.html +157 -0
  131. package/docs/design/LinkAuthorship.dc.html +139 -0
  132. package/docs/design/LinksChanged.dc.html +203 -0
  133. package/docs/design/LinksOptions.dc.html +137 -0
  134. package/docs/design/LinksOptions600.dc.html +134 -0
  135. package/docs/design/NearPin.dc.html +9 -10
  136. package/docs/design/Pane600.dc.html +4 -3
  137. package/docs/design/Relations.dc.html +3 -2
  138. package/docs/design/Restore.dc.html +152 -0
  139. package/docs/design/SeeChange.dc.html +267 -0
  140. package/docs/design/Uses.dc.html +12 -6
  141. package/docs/design/canvas.json +146 -19
  142. package/docs/design.md +449 -16
  143. package/docs/environment.md +0 -1
  144. package/docs/evals/curation-effect.md +128 -0
  145. package/docs/http-api.md +459 -37
  146. package/docs/mcp.md +146 -18
  147. package/docs/node-types.md +58 -3
  148. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  149. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  150. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  151. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  152. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  153. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  154. package/docs/product-context-vision-2026-09.md +17 -1
  155. package/docs/product-vision-2026-09.md +1082 -43
  156. package/docs/product-vision-review-2026-09.md +13 -1
  157. package/docs/screenshots/workbench-live-dark.png +0 -0
  158. package/docs/screenshots/workbench-live-light.png +0 -0
  159. package/docs/sdk.md +62 -2
  160. package/package.json +1 -1
  161. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  162. package/plugins/pmx-canvas/plugin.json +1 -1
  163. package/plugins/pmx-canvas/runtime.json +1 -1
  164. package/plugins/pmx-canvas/tools.json +601 -22
  165. package/skills/pmx-canvas/SKILL.md +216 -42
  166. package/skills/pmx-canvas/evals/evals.json +224 -0
  167. package/skills/pmx-canvas/references/full-reference.md +120 -21
  168. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  169. package/skills/pmx-canvas/references/importing-context.md +132 -0
  170. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  171. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  172. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  173. package/src/cli/agent.ts +2 -0
  174. package/src/cli/commands/ax.ts +12 -0
  175. package/src/cli/commands/boards.ts +88 -11
  176. package/src/cli/commands/edges.ts +28 -0
  177. package/src/cli/commands/groups.ts +7 -0
  178. package/src/cli/commands/nodes.ts +1 -0
  179. package/src/cli/commands/query.ts +34 -2
  180. package/src/cli/commands/record.ts +199 -48
  181. package/src/cli/index.ts +6 -1
  182. package/src/client/App.tsx +177 -70
  183. package/src/client/canvas/AttentionHistory.tsx +0 -2
  184. package/src/client/canvas/BarHint.tsx +40 -2
  185. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  186. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  187. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  188. package/src/client/canvas/BoardPinButton.tsx +33 -0
  189. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  190. package/src/client/canvas/CanvasNode.tsx +360 -237
  191. package/src/client/canvas/CanvasViewport.tsx +45 -7
  192. package/src/client/canvas/CommandBar.tsx +29 -23
  193. package/src/client/canvas/CommandPalette.tsx +5 -12
  194. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  195. package/src/client/canvas/ContextChip.tsx +296 -0
  196. package/src/client/canvas/ContextMenu.tsx +63 -5
  197. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  198. package/src/client/canvas/EdgeLayer.tsx +339 -76
  199. package/src/client/canvas/EmptyState.tsx +33 -18
  200. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  201. package/src/client/canvas/ExternalWriters.tsx +57 -42
  202. package/src/client/canvas/HomeGraphView.tsx +942 -0
  203. package/src/client/canvas/HomeMapView.tsx +641 -0
  204. package/src/client/canvas/HomeReviewView.tsx +167 -0
  205. package/src/client/canvas/HomeView.tsx +687 -160
  206. package/src/client/canvas/IntentLayer.tsx +19 -12
  207. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  208. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  209. package/src/client/canvas/LifecycleFields.tsx +91 -0
  210. package/src/client/canvas/LifecyclePanel.tsx +543 -0
  211. package/src/client/canvas/MemoryInspector.tsx +104 -0
  212. package/src/client/canvas/Minimap.tsx +3 -3
  213. package/src/client/canvas/NearPinLayer.tsx +187 -0
  214. package/src/client/canvas/NodeContextMark.tsx +175 -97
  215. package/src/client/canvas/Presentation.tsx +23 -57
  216. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  217. package/src/client/canvas/RelationsPanel.tsx +82 -0
  218. package/src/client/canvas/SelectionBar.tsx +3 -6
  219. package/src/client/canvas/SessionPanel.tsx +90 -50
  220. package/src/client/canvas/SessionReceipt.tsx +311 -39
  221. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  222. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  223. package/src/client/canvas/ToolRail.tsx +367 -192
  224. package/src/client/canvas/TopBar.tsx +354 -124
  225. package/src/client/canvas/canvas-area.ts +10 -3
  226. package/src/client/canvas/graph-geometry.ts +195 -0
  227. package/src/client/canvas/graph-model.ts +254 -0
  228. package/src/client/canvas/memory-layout.ts +106 -0
  229. package/src/client/canvas/use-chrome-height.ts +20 -0
  230. package/src/client/canvas/use-focus-trap.ts +31 -5
  231. package/src/client/canvas/use-memory-graph.ts +58 -0
  232. package/src/client/canvas/use-node-drag.ts +2 -1
  233. package/src/client/canvas/use-pan-zoom.ts +64 -7
  234. package/src/client/icons.tsx +0 -63
  235. package/src/client/mcp-app/style.css +0 -6
  236. package/src/client/nodes/AxStepControls.tsx +74 -50
  237. package/src/client/nodes/BoardNode.tsx +5 -1
  238. package/src/client/nodes/ContextNode.tsx +18 -11
  239. package/src/client/nodes/ExtAppFrame.tsx +6 -3
  240. package/src/client/nodes/FileNode.tsx +46 -42
  241. package/src/client/nodes/HtmlNode.tsx +32 -0
  242. package/src/client/nodes/ImageNode.tsx +1 -1
  243. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  244. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  245. package/src/client/nodes/MarkdownNode.tsx +377 -109
  246. package/src/client/nodes/McpAppNode.tsx +50 -20
  247. package/src/client/nodes/MdFormatBar.tsx +21 -18
  248. package/src/client/nodes/PromptNode.tsx +1 -1
  249. package/src/client/nodes/ResponseNode.tsx +1 -1
  250. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  251. package/src/client/state/attention-bridge.ts +64 -15
  252. package/src/client/state/boards-store.ts +193 -39
  253. package/src/client/state/canvas-store.ts +99 -12
  254. package/src/client/state/context-chip-store.ts +70 -0
  255. package/src/client/state/context-status-store.ts +53 -0
  256. package/src/client/state/human-store.ts +13 -7
  257. package/src/client/state/intent-bridge.ts +51 -30
  258. package/src/client/state/knowledge-store.ts +43 -0
  259. package/src/client/state/near-pin-store.ts +115 -6
  260. package/src/client/state/presence-store.ts +7 -10
  261. package/src/client/state/presentation.ts +91 -0
  262. package/src/client/state/relation-store.ts +81 -0
  263. package/src/client/state/session-store.ts +144 -18
  264. package/src/client/state/sse-bridge.ts +235 -32
  265. package/src/client/state/workspace-settings.ts +11 -0
  266. package/src/client/theme/global.css +2724 -476
  267. package/src/client/types.ts +12 -0
  268. package/src/client/utils/keyboard.ts +10 -0
  269. package/src/client/utils/time-ago.ts +14 -0
  270. package/src/json-render/renderer/index.tsx +31 -0
  271. package/src/mcp/canvas-app.ts +12 -2
  272. package/src/mcp/context-uri-template.ts +7 -4
  273. package/src/mcp/server.ts +63 -41
  274. package/src/mcp/workbench-app.ts +39 -6
  275. package/src/server/agent-presence.ts +128 -28
  276. package/src/server/ax-context.ts +27 -13
  277. package/src/server/board-export.ts +12 -1
  278. package/src/server/canvas-db.ts +331 -42
  279. package/src/server/canvas-operations.ts +195 -83
  280. package/src/server/canvas-provenance.ts +53 -22
  281. package/src/server/canvas-serialization.ts +2 -0
  282. package/src/server/canvas-state.ts +950 -122
  283. package/src/server/canvas-validation.ts +9 -2
  284. package/src/server/context-brief.ts +345 -38
  285. package/src/server/context-reads.ts +178 -30
  286. package/src/server/demo-state.json +355 -24
  287. package/src/server/edit-summary.ts +74 -0
  288. package/src/server/html-surface.ts +8 -1
  289. package/src/server/index.ts +181 -11
  290. package/src/server/knowledge-lifecycle.ts +304 -0
  291. package/src/server/lifecycle-status.ts +232 -0
  292. package/src/server/memory-graph.ts +166 -0
  293. package/src/server/mutation-history.ts +18 -6
  294. package/src/server/operations/composites.ts +32 -11
  295. package/src/server/operations/http.ts +7 -2
  296. package/src/server/operations/index.ts +4 -0
  297. package/src/server/operations/mcp.ts +26 -1
  298. package/src/server/operations/ops/ax-presence.ts +1 -1
  299. package/src/server/operations/ops/ax-read.ts +57 -6
  300. package/src/server/operations/ops/ax-timeline.ts +28 -2
  301. package/src/server/operations/ops/backup.ts +5 -3
  302. package/src/server/operations/ops/batch.ts +35 -4
  303. package/src/server/operations/ops/boards.ts +188 -47
  304. package/src/server/operations/ops/canvas-wire.ts +31 -1
  305. package/src/server/operations/ops/edges.ts +36 -2
  306. package/src/server/operations/ops/groups.ts +6 -1
  307. package/src/server/operations/ops/lifecycle.ts +262 -0
  308. package/src/server/operations/ops/memory-graph.ts +45 -0
  309. package/src/server/operations/ops/nodes.ts +107 -24
  310. package/src/server/operations/ops/query.ts +281 -68
  311. package/src/server/operations/ops/snapshots.ts +73 -1
  312. package/src/server/operations/ops/validate.ts +5 -1
  313. package/src/server/operations/ops/viewport.ts +97 -3
  314. package/src/server/operations/registry.ts +74 -25
  315. package/src/server/placement.ts +29 -0
  316. package/src/server/scope-fence.ts +78 -6
  317. package/src/server/server.ts +115 -16
  318. package/src/server/spatial-analysis.ts +48 -23
  319. package/src/server/tour-control.ts +82 -0
  320. package/src/server/wiki-links.ts +137 -0
  321. package/src/server/workbench-auth.ts +11 -0
  322. package/src/shared/agent-context.ts +3 -0
  323. package/src/shared/agent-presence.ts +15 -15
  324. package/src/shared/auto-arrange.ts +2 -0
  325. package/src/shared/boards.ts +4 -4
  326. package/src/shared/knowledge-lifecycle.ts +22 -0
  327. package/src/shared/knowledge-presets.ts +80 -0
  328. package/src/shared/lifecycle-status.ts +26 -0
  329. package/src/shared/memory-graph.ts +74 -0
  330. package/src/shared/relations.ts +98 -0
  331. package/src/shared/semantic-attention.ts +15 -1
  332. package/src/shared/tour.ts +23 -5
  333. package/src/shared/workspace-settings.ts +13 -0
  334. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  335. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  336. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  337. package/dist/types/server/agent-context.d.ts +0 -22
  338. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -1,4 +1,5 @@
1
1
  import { HTML_SURFACE_PUSH_SOURCE } from '../../shared/ax-surface-protocol.js';
2
+ import { BarHint } from './BarHint';
2
3
  import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
3
4
  import { ContextNode } from '../nodes/ContextNode';
4
5
  import { DiffNode } from '../nodes/DiffNode';
@@ -17,6 +18,10 @@ import { AxStepControls } from '../nodes/AxStepControls';
17
18
  import { ResponseNode } from '../nodes/ResponseNode';
18
19
  import { TraceNode } from '../nodes/TraceNode';
19
20
  import { BoardNode } from '../nodes/BoardNode';
21
+ import { RelationsPanel } from './RelationsPanel';
22
+ import { CrossBoardReferences } from './CrossBoardReferences';
23
+ import { lifecycleTarget, openLifecycle } from '../state/knowledge-store';
24
+ import { knowledgeLifecycleEnabled } from '../state/workspace-settings';
20
25
  import {
21
26
  collapseExpandedNode,
22
27
  contextPinnedNodeIds,
@@ -129,6 +134,7 @@ export function ExpandedNodeOverlay() {
129
134
  const nodeId = expandedNodeId.value;
130
135
  const node = nodeId ? nodes.value.get(nodeId) : undefined;
131
136
  const [copied, setCopied] = useState(false);
137
+ const [relationsFor, setRelationsFor] = useState<string | null>(null);
132
138
  const [presenting, setPresenting] = useState(false);
133
139
  const [presentationExitToken, setPresentationExitToken] = useState('');
134
140
  const presentationOverlayRef = useRef<HTMLDivElement>(null);
@@ -243,7 +249,17 @@ export function ExpandedNodeOverlay() {
243
249
  () => (nodeId ? document.querySelector<HTMLElement>(`.canvas-node[data-node-id="${CSS.escape(nodeId)}"]`) : null),
244
250
  [nodeId],
245
251
  );
246
- useFocusTrap(panelRef, node !== undefined, { restoreTo: restoreToNode });
252
+ const reviewing = knowledgeLifecycleEnabled.value && lifecycleTarget.value?.nodeId === nodeId;
253
+ const suspendingFocusedReview =
254
+ !knowledgeLifecycleEnabled.value &&
255
+ lifecycleTarget.value?.nodeId === nodeId &&
256
+ !!document.activeElement?.closest('.lifecycle-dialog');
257
+ useFocusTrap(panelRef, node !== undefined && !reviewing, { restoreTo: restoreToNode });
258
+ useLayoutEffect(() => {
259
+ // Resuming the expanded card's trap must not steal the hidden review's return focus.
260
+ if (suspendingFocusedReview)
261
+ document.querySelector<HTMLElement>('button[aria-label="Settings"]')?.focus({ preventScroll: true });
262
+ }, [reviewing]);
247
263
 
248
264
  if (!node) return null;
249
265
 
@@ -282,7 +298,7 @@ export function ExpandedNodeOverlay() {
282
298
  ref={panelRef}
283
299
  class={`expanded-overlay-panel${isCtxPinned ? ' is-pinned' : ''}`}
284
300
  role="dialog"
285
- aria-modal="true"
301
+ aria-modal={reviewing ? undefined : 'true'}
286
302
  aria-label={title}
287
303
  data-testid="expanded-node"
288
304
  >
@@ -293,56 +309,73 @@ export function ExpandedNodeOverlay() {
293
309
  </span>
294
310
  <span class="expanded-title">{title}</span>
295
311
  <span class="expanded-kind-pill">{TYPE_LABELS[node.type]}</span>
296
- <button
297
- type="button"
298
- class={`expanded-pin${isCtxPinned ? ' is-on' : ''}`}
299
- onClick={handleToggleCtxPin}
300
- title={isCtxPinned ? 'In agent context — click to unpin' : 'Pin as agent context'}
301
- aria-pressed={isCtxPinned}
302
- >
303
- ✦
304
- </button>
312
+ <BarHint label={isCtxPinned ? 'In agent context — click to unpin' : 'Pin as agent context'}>
313
+ <button
314
+ type="button"
315
+ class={`expanded-pin${isCtxPinned ? ' is-on' : ''}`}
316
+ onClick={handleToggleCtxPin}
317
+ aria-label={isCtxPinned ? 'In agent context — click to unpin' : 'Pin as agent context'}
318
+ aria-pressed={isCtxPinned}
319
+ >
320
+ ✦
321
+ </button>
322
+ </BarHint>
305
323
  <span class="expanded-spacer" />
306
324
  <div class="expanded-actions">
307
- {hasText && (
308
- <button type="button" class="expanded-action-btn" onClick={handleCopy} title="Copy content to clipboard">
309
- {copied ? 'Copied!' : 'Copy'}
325
+ {knowledgeLifecycleEnabled.value && (
326
+ <button type="button" class="expanded-action-btn" onClick={() => openLifecycle(node.id)}>
327
+ Review
310
328
  </button>
311
329
  )}
330
+ <button
331
+ type="button"
332
+ class="expanded-action-btn"
333
+ aria-pressed={relationsFor === nodeId}
334
+ onClick={() => setRelationsFor(relationsFor === nodeId ? null : nodeId)}
335
+ >
336
+ Relations
337
+ </button>
338
+ {hasText && (
339
+ <BarHint label="Copy content to clipboard">
340
+ <button type="button" class="expanded-action-btn" onClick={handleCopy}>
341
+ {copied ? 'Copied!' : 'Copy'}
342
+ </button>
343
+ </BarHint>
344
+ )}
312
345
  {canOpenAsSite(node) && (
313
- <button
314
- type="button"
315
- class="expanded-action-btn"
316
- onClick={() => void openNodeAsSite(node)}
317
- title="Open as a full-page site in the system browser"
318
- >
319
- Open in tab <IconExternalLink size={14} />
320
- </button>
346
+ <BarHint label="Open as a full-page site in the system browser">
347
+ <button type="button" class="expanded-action-btn" onClick={() => void openNodeAsSite(node)}>
348
+ Open in tab <IconExternalLink size={14} />
349
+ </button>
350
+ </BarHint>
321
351
  )}
322
352
  {canPresent && (
323
- <button
324
- type="button"
325
- class="expanded-action-btn expanded-action-primary"
326
- onClick={handlePresent}
327
- title="Present this HTML node fullscreen"
328
- >
329
- Present
330
- </button>
353
+ <BarHint label="Present this HTML node fullscreen">
354
+ <button type="button" class="expanded-action-btn expanded-action-primary" onClick={handlePresent}>
355
+ Present
356
+ </button>
357
+ </BarHint>
331
358
  )}
332
359
  </div>
333
- <button type="button" class="expanded-close" onClick={handleClose} title="Close (Esc)" aria-label="Close">
334
- <IconClose />
335
- </button>
360
+ <BarHint label="Close" shortcut="Esc" align="end">
361
+ <button type="button" class="expanded-close" onClick={handleClose} aria-label="Close">
362
+ <IconClose />
363
+ </button>
364
+ </BarHint>
336
365
  </div>
337
366
 
338
367
  {/* Body: the surface at full size */}
339
- <div class={`expanded-body${isEmbeddedViewer ? ' is-embedded' : ''}`}>
340
- {isEmbeddedViewer ? (
341
- <div style={{ flex: 1, minHeight: 0, display: 'flex' }}>{renderContent(node, true)}</div>
342
- ) : (
343
- renderContent(node, true)
344
- )}
368
+ <div class="expanded-content-row">
369
+ <div class={`expanded-body${isEmbeddedViewer ? ' is-embedded' : ''}`}>
370
+ {isEmbeddedViewer ? (
371
+ <div style={{ flex: 1, minHeight: 0, display: 'flex' }}>{renderContent(node, true)}</div>
372
+ ) : (
373
+ renderContent(node, true)
374
+ )}
375
+ </div>
376
+ {relationsFor === nodeId && <RelationsPanel key={nodeId} nodeId={node.id} />}
345
377
  </div>
378
+ <CrossBoardReferences node={node} />
346
379
  {/* AX step controls follow the node into focus mode. Without this,
347
380
  expanding a materialized flow step showed only its text — the
348
381
  Start/Done/Blocked and loop controls live in CanvasNode's body,
@@ -380,7 +413,6 @@ export function ExpandedNodeOverlay() {
380
413
  type="button"
381
414
  class="html-presentation-exit"
382
415
  onClick={handleExitPresentation}
383
- title="Exit presentation (Esc)"
384
416
  aria-label="Exit presentation"
385
417
  >
386
418
  Exit presentation
@@ -1,4 +1,5 @@
1
- import { useEffect, useRef } from 'preact/hooks';
1
+ import { useLayoutEffect, useRef } from 'preact/hooks';
2
+ import { BarHint } from './BarHint';
2
3
  import { IconClose, IconExternalLink } from '../icons';
3
4
  import type { AgentPresence } from '../../shared/agent-presence.js';
4
5
  import { vetoGhostIntent } from '../state/intent-bridge';
@@ -10,11 +11,12 @@ import {
10
11
  agentPresences,
11
12
  externalWriterPresences,
12
13
  relativeAge,
13
- sessionActive,
14
+ sessionChromeActive,
14
15
  writerColor,
15
16
  writerInitial,
16
17
  writersSheetOpen,
17
18
  } from '../state/presence-store';
19
+ import { agentSessionsEnabled } from '../state/workspace-settings';
18
20
  import { scopeFence, pendingGates, startSession } from '../state/session-store';
19
21
  import { sessionId } from '../state/canvas-store';
20
22
  import { useFocusTrap } from './use-focus-trap';
@@ -46,32 +48,38 @@ function Avatar({ presence, size = 18 }: { presence: Pick<AgentPresence, 'sessio
46
48
  /** Top-bar indicator: avatar cluster, "3 writers", op count, pulsing dot. */
47
49
  export function ExternalWriterIndicator() {
48
50
  const writers = externalWriterPresences.value;
49
- if (sessionActive.value || writers.length === 0) return null;
51
+ if (sessionChromeActive.value || writers.length === 0) return null;
50
52
  const ops = writers.reduce((sum, writer) => sum + writer.opCount, 0);
51
53
  const label = writers.length === 1 ? writers[0]!.label : `${writers.length} writers`;
52
54
  const open = activityFeedOpen.value;
53
55
  return (
54
- <button
55
- type="button"
56
- class="external-indicator"
57
- data-testid="external-indicator"
58
- aria-expanded={open}
59
- title={`${writers.length} external writer${writers.length === 1 ? '' : 's'} on this board — click for activity`}
60
- onClick={() => {
61
- activityFeedOpen.value = !open;
62
- }}
56
+ <BarHint
57
+ label={`${writers.length} external writer${writers.length === 1 ? '' : 's'} on this board`}
58
+ body="Click for their activity."
59
+ align="end"
63
60
  >
64
- <span class="external-indicator-avatars">
65
- {writers.slice(0, 3).map((writer) => (
66
- <Avatar key={writer.sessionId} presence={writer} />
67
- ))}
68
- </span>
69
- <span class="external-indicator-label">{label}</span>
70
- <span class="external-indicator-ops hud-collapsible-text">
71
- {ops} op{ops === 1 ? '' : 's'}
72
- </span>
73
- <span class="external-indicator-dot" aria-hidden="true" />
74
- </button>
61
+ <button
62
+ type="button"
63
+ class="external-indicator"
64
+ data-testid="external-indicator"
65
+ aria-label={`${label} external activity, ${ops} operation${ops === 1 ? '' : 's'}`}
66
+ aria-expanded={open}
67
+ onClick={() => {
68
+ activityFeedOpen.value = !open;
69
+ }}
70
+ >
71
+ <span class="external-indicator-avatars">
72
+ {writers.slice(0, 3).map((writer) => (
73
+ <Avatar key={writer.sessionId} presence={writer} />
74
+ ))}
75
+ </span>
76
+ <span class="external-indicator-label">{label}</span>
77
+ <span class="external-indicator-ops hud-collapsible-text">
78
+ {ops} op{ops === 1 ? '' : 's'}
79
+ </span>
80
+ <span class="external-indicator-dot" aria-hidden="true" />
81
+ </button>
82
+ </BarHint>
75
83
  );
76
84
  }
77
85
 
@@ -100,17 +108,18 @@ export function ActivityFeed() {
100
108
  const writers = externalWriterPresences.value;
101
109
  const now = useNow(open ? 10_000 : 0);
102
110
  // Esc closes the top-most surface: the sheet first, then the feed.
103
- useEffect(() => {
111
+ useLayoutEffect(() => {
104
112
  if (!open) return;
105
113
  const onKey = (e: KeyboardEvent) => {
106
- if (e.key !== 'Escape') return;
114
+ if (e.key !== 'Escape' || e.defaultPrevented) return;
115
+ e.preventDefault();
107
116
  if (writersSheetOpen.value) writersSheetOpen.value = false;
108
117
  else activityFeedOpen.value = false;
109
118
  };
110
119
  window.addEventListener('keydown', onKey);
111
120
  return () => window.removeEventListener('keydown', onKey);
112
121
  }, [open]);
113
- if (!open || sessionActive.value || writers.length === 0) return null;
122
+ if (!open || sessionChromeActive.value || writers.length === 0) return null;
114
123
 
115
124
  const filter = activityFilter.value;
116
125
  const proposals = pendingProposals().filter((intent) => !filter || (intent.source ?? '') === filter);
@@ -139,7 +148,7 @@ export function ActivityFeed() {
139
148
  </div>
140
149
  <div class="activity-feed-sub">
141
150
  {writers.reduce((sum, writer) => sum + writer.opCount, 0)} writes · last{' '}
142
- {relativeAge(latestWrite(writers), now)} · no session attached
151
+ {relativeAge(latestWrite(writers), now)} · live writers
143
152
  </div>
144
153
  </div>
145
154
  <button
@@ -219,16 +228,18 @@ export function ActivityFeed() {
219
228
  >
220
229
  Writers
221
230
  </button>
222
- <button
223
- type="button"
224
- class="activity-feed-link is-accent"
225
- onClick={() => {
226
- activityFeedOpen.value = false;
227
- void startSession();
228
- }}
229
- >
230
- Start session <IconExternalLink size={14} />
231
- </button>
231
+ {agentSessionsEnabled.value && (
232
+ <button
233
+ type="button"
234
+ class="activity-feed-link is-accent"
235
+ onClick={() => {
236
+ activityFeedOpen.value = false;
237
+ void startSession();
238
+ }}
239
+ >
240
+ Start session <IconExternalLink size={14} />
241
+ </button>
242
+ )}
232
243
  </div>
233
244
  </div>
234
245
  );
@@ -248,8 +259,8 @@ export function WritersSheet() {
248
259
  const sheetRef = useRef<HTMLDivElement>(null);
249
260
  useFocusTrap(sheetRef, open);
250
261
  if (!open) return null;
251
- const sessions = agentPresences.value.filter((presence) => presence.attached);
252
- const external = agentPresences.value.filter((presence) => !presence.attached);
262
+ const sessions = agentSessionsEnabled.value ? agentPresences.value.filter((presence) => presence.attached) : [];
263
+ const external = externalWriterPresences.value;
253
264
  const close = () => {
254
265
  writersSheetOpen.value = false;
255
266
  };
@@ -272,8 +283,12 @@ export function WritersSheet() {
272
283
  </button>
273
284
  </div>
274
285
  <div class="writers-body">
275
- <div class="writers-section">Agent sessions</div>
276
- {sessions.length === 0 && <div class="writers-empty">None attached — start one from the top bar.</div>}
286
+ {agentSessionsEnabled.value && (
287
+ <>
288
+ <div class="writers-section">Agent sessions</div>
289
+ {sessions.length === 0 && <div class="writers-empty">None attached — start one from the top bar.</div>}
290
+ </>
291
+ )}
277
292
  {sessions.map((presence) => (
278
293
  <div key={presence.sessionId} class="writers-row">
279
294
  <Avatar presence={presence} size={20} />
@@ -281,7 +296,7 @@ export function WritersSheet() {
281
296
  <span class="writers-config">{sessionConfig()}</span>
282
297
  </div>
283
298
  ))}
284
- <div class="writers-section">External writers · MCP</div>
299
+ <div class="writers-section">{agentSessionsEnabled.value ? 'External writers · MCP' : 'Live writers'}</div>
285
300
  {external.length === 0 && <div class="writers-empty">No external writers right now.</div>}
286
301
  {external.map((presence) => (
287
302
  <div key={presence.sessionId} class="writers-row">