pmx-canvas 0.8.0 → 0.9.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 (341) 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 +66 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2759 -510
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2936 -632
  8. package/dist/json-render/index.css +81 -1
  9. package/dist/json-render/index.js +20 -20
  10. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  11. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  12. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  13. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  15. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  17. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  18. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  19. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  20. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  21. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  22. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  23. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  24. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  25. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  26. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  27. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  28. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  29. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  30. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  31. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  32. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  33. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  34. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  35. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  36. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  37. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  38. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  39. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  40. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  41. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  42. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  43. package/dist/types/client/icons.d.ts +0 -10
  44. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  45. package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
  46. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  47. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  48. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  49. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  50. package/dist/types/client/state/boards-store.d.ts +81 -9
  51. package/dist/types/client/state/canvas-store.d.ts +14 -1
  52. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  53. package/dist/types/client/state/context-status-store.d.ts +35 -0
  54. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  55. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  56. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  57. package/dist/types/client/state/presence-store.d.ts +4 -4
  58. package/dist/types/client/state/presentation.d.ts +28 -0
  59. package/dist/types/client/state/relation-store.d.ts +10 -0
  60. package/dist/types/client/state/session-store.d.ts +41 -2
  61. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  62. package/dist/types/client/types.d.ts +13 -1
  63. package/dist/types/client/utils/keyboard.d.ts +2 -0
  64. package/dist/types/client/utils/time-ago.d.ts +4 -0
  65. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  66. package/dist/types/server/agent-presence.d.ts +10 -3
  67. package/dist/types/server/ax-context.d.ts +1 -1
  68. package/dist/types/server/canvas-db.d.ts +30 -7
  69. package/dist/types/server/canvas-operations.d.ts +24 -2
  70. package/dist/types/server/canvas-provenance.d.ts +22 -2
  71. package/dist/types/server/canvas-serialization.d.ts +2 -0
  72. package/dist/types/server/canvas-state.d.ts +93 -15
  73. package/dist/types/server/canvas-validation.d.ts +1 -1
  74. package/dist/types/server/context-brief.d.ts +94 -6
  75. package/dist/types/server/context-reads.d.ts +33 -7
  76. package/dist/types/server/edit-summary.d.ts +20 -0
  77. package/dist/types/server/index.d.ts +71 -10
  78. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  79. package/dist/types/server/lifecycle-status.d.ts +10 -0
  80. package/dist/types/server/memory-graph.d.ts +9 -0
  81. package/dist/types/server/mutation-history.d.ts +2 -0
  82. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  83. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  84. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  85. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  86. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  87. package/dist/types/server/operations/ops/query.d.ts +23 -0
  88. package/dist/types/server/operations/registry.d.ts +1 -0
  89. package/dist/types/server/placement.d.ts +7 -0
  90. package/dist/types/server/scope-fence.d.ts +2 -1
  91. package/dist/types/server/spatial-analysis.d.ts +6 -3
  92. package/dist/types/server/tour-control.d.ts +32 -0
  93. package/dist/types/server/wiki-links.d.ts +14 -0
  94. package/dist/types/server/workbench-auth.d.ts +2 -0
  95. package/dist/types/shared/agent-context.d.ts +2 -0
  96. package/dist/types/shared/agent-presence.d.ts +15 -12
  97. package/dist/types/shared/auto-arrange.d.ts +1 -0
  98. package/dist/types/shared/boards.d.ts +4 -4
  99. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  100. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  101. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  102. package/dist/types/shared/memory-graph.d.ts +53 -0
  103. package/dist/types/shared/relations.d.ts +68 -0
  104. package/dist/types/shared/semantic-attention.d.ts +2 -0
  105. package/dist/types/shared/tour.d.ts +38 -0
  106. package/dist/types/shared/workspace-settings.d.ts +11 -0
  107. package/docs/RELEASE.md +21 -3
  108. package/docs/adr-001-bun-only-runtime.md +7 -1
  109. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  110. package/docs/api-reference.md +109 -0
  111. package/docs/ax-host-adapter-contract.md +1 -1
  112. package/docs/ax-state-contract.md +5 -4
  113. package/docs/cli.md +153 -15
  114. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  115. package/docs/design/AgentContext.dc.html +3 -3
  116. package/docs/design/BoardPins.dc.html +37 -22
  117. package/docs/design/Categories.dc.html +208 -0
  118. package/docs/design/CategoryEditor.dc.html +33 -0
  119. package/docs/design/Chrome.dc.html +55 -9
  120. package/docs/design/ContextChip.dc.html +224 -0
  121. package/docs/design/ContextChip600.dc.html +136 -0
  122. package/docs/design/EmptyState.dc.html +45 -0
  123. package/docs/design/Flows.dc.html +23 -1
  124. package/docs/design/HeaderMarks.dc.html +183 -0
  125. package/docs/design/Home.dc.html +19 -1
  126. package/docs/design/HomeGraph.dc.html +86 -202
  127. package/docs/design/HomeMap.dc.html +23 -1
  128. package/docs/design/HomeTeam.dc.html +32 -14
  129. package/docs/design/Lifecycle.dc.html +22 -0
  130. package/docs/design/Lifecycle600.dc.html +18 -0
  131. package/docs/design/LifecycleVariants.dc.html +157 -0
  132. package/docs/design/LinkAuthorship.dc.html +139 -0
  133. package/docs/design/LinksChanged.dc.html +203 -0
  134. package/docs/design/LinksOptions.dc.html +137 -0
  135. package/docs/design/LinksOptions600.dc.html +134 -0
  136. package/docs/design/NearPin.dc.html +9 -10
  137. package/docs/design/Pane600.dc.html +4 -3
  138. package/docs/design/Relations.dc.html +3 -2
  139. package/docs/design/Restore.dc.html +152 -0
  140. package/docs/design/SeeChange.dc.html +267 -0
  141. package/docs/design/Uses.dc.html +12 -6
  142. package/docs/design/canvas.json +146 -19
  143. package/docs/design.md +454 -18
  144. package/docs/environment.md +0 -1
  145. package/docs/evals/curation-effect.md +128 -0
  146. package/docs/http-api.md +465 -38
  147. package/docs/mcp.md +150 -19
  148. package/docs/node-types.md +58 -3
  149. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  150. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  151. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  152. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  153. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  154. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  155. package/docs/product-context-vision-2026-09.md +17 -1
  156. package/docs/product-vision-2026-09.md +1082 -43
  157. package/docs/product-vision-review-2026-09.md +13 -1
  158. package/docs/screenshots/workbench-live-dark.png +0 -0
  159. package/docs/screenshots/workbench-live-light.png +0 -0
  160. package/docs/sdk.md +62 -2
  161. package/package.json +1 -1
  162. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/plugin.json +1 -1
  164. package/plugins/pmx-canvas/runtime.json +1 -1
  165. package/plugins/pmx-canvas/tools.json +601 -22
  166. package/skills/pmx-canvas/SKILL.md +234 -43
  167. package/skills/pmx-canvas/evals/evals.json +62 -0
  168. package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
  169. package/skills/pmx-canvas/references/full-reference.md +120 -21
  170. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  171. package/skills/pmx-canvas/references/importing-context.md +132 -0
  172. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  173. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  174. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  175. package/src/cli/agent.ts +2 -0
  176. package/src/cli/commands/ax.ts +12 -0
  177. package/src/cli/commands/boards.ts +87 -11
  178. package/src/cli/commands/edges.ts +28 -0
  179. package/src/cli/commands/groups.ts +7 -0
  180. package/src/cli/commands/nodes.ts +4 -2
  181. package/src/cli/commands/query.ts +34 -2
  182. package/src/cli/commands/record.ts +199 -48
  183. package/src/cli/index.ts +7 -1
  184. package/src/client/App.tsx +177 -70
  185. package/src/client/canvas/AttentionHistory.tsx +0 -2
  186. package/src/client/canvas/BarHint.tsx +40 -2
  187. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  188. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  189. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  190. package/src/client/canvas/BoardPinButton.tsx +33 -0
  191. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  192. package/src/client/canvas/CanvasNode.tsx +360 -237
  193. package/src/client/canvas/CanvasViewport.tsx +45 -7
  194. package/src/client/canvas/CommandBar.tsx +29 -23
  195. package/src/client/canvas/CommandPalette.tsx +5 -12
  196. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  197. package/src/client/canvas/ContextChip.tsx +296 -0
  198. package/src/client/canvas/ContextMenu.tsx +63 -5
  199. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  200. package/src/client/canvas/EdgeLayer.tsx +339 -76
  201. package/src/client/canvas/EmptyState.tsx +33 -18
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  203. package/src/client/canvas/ExternalWriters.tsx +57 -42
  204. package/src/client/canvas/HomeGraphView.tsx +942 -0
  205. package/src/client/canvas/HomeMapView.tsx +641 -0
  206. package/src/client/canvas/HomeReviewView.tsx +167 -0
  207. package/src/client/canvas/HomeView.tsx +687 -160
  208. package/src/client/canvas/IntentLayer.tsx +19 -12
  209. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  210. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  211. package/src/client/canvas/LifecycleFields.tsx +91 -0
  212. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  213. package/src/client/canvas/MemoryInspector.tsx +104 -0
  214. package/src/client/canvas/Minimap.tsx +3 -3
  215. package/src/client/canvas/NearPinLayer.tsx +187 -0
  216. package/src/client/canvas/NodeContextMark.tsx +175 -97
  217. package/src/client/canvas/Presentation.tsx +23 -57
  218. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  219. package/src/client/canvas/RelationsPanel.tsx +82 -0
  220. package/src/client/canvas/SelectionBar.tsx +3 -6
  221. package/src/client/canvas/SessionPanel.tsx +90 -50
  222. package/src/client/canvas/SessionReceipt.tsx +311 -39
  223. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  224. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  225. package/src/client/canvas/ToolRail.tsx +367 -192
  226. package/src/client/canvas/TopBar.tsx +354 -124
  227. package/src/client/canvas/canvas-area.ts +10 -3
  228. package/src/client/canvas/graph-geometry.ts +195 -0
  229. package/src/client/canvas/graph-model.ts +254 -0
  230. package/src/client/canvas/memory-layout.ts +106 -0
  231. package/src/client/canvas/use-chrome-height.ts +20 -0
  232. package/src/client/canvas/use-focus-trap.ts +31 -5
  233. package/src/client/canvas/use-memory-graph.ts +58 -0
  234. package/src/client/canvas/use-node-drag.ts +2 -1
  235. package/src/client/canvas/use-pan-zoom.ts +64 -7
  236. package/src/client/icons.tsx +0 -63
  237. package/src/client/mcp-app/style.css +0 -6
  238. package/src/client/nodes/AxStepControls.tsx +74 -50
  239. package/src/client/nodes/BoardNode.tsx +5 -1
  240. package/src/client/nodes/ContextNode.tsx +18 -11
  241. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  242. package/src/client/nodes/FileNode.tsx +46 -42
  243. package/src/client/nodes/HtmlNode.tsx +44 -2
  244. package/src/client/nodes/ImageNode.tsx +1 -1
  245. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  246. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  247. package/src/client/nodes/MarkdownNode.tsx +377 -109
  248. package/src/client/nodes/McpAppNode.tsx +50 -20
  249. package/src/client/nodes/MdFormatBar.tsx +21 -18
  250. package/src/client/nodes/PromptNode.tsx +1 -1
  251. package/src/client/nodes/ResponseNode.tsx +1 -1
  252. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  253. package/src/client/state/attention-bridge.ts +64 -15
  254. package/src/client/state/boards-store.ts +193 -39
  255. package/src/client/state/canvas-store.ts +99 -12
  256. package/src/client/state/context-chip-store.ts +70 -0
  257. package/src/client/state/context-status-store.ts +53 -0
  258. package/src/client/state/intent-bridge.ts +51 -30
  259. package/src/client/state/knowledge-store.ts +43 -0
  260. package/src/client/state/near-pin-store.ts +115 -6
  261. package/src/client/state/presence-store.ts +7 -10
  262. package/src/client/state/presentation.ts +91 -0
  263. package/src/client/state/relation-store.ts +81 -0
  264. package/src/client/state/session-store.ts +144 -18
  265. package/src/client/state/sse-bridge.ts +206 -29
  266. package/src/client/state/workspace-settings.ts +11 -0
  267. package/src/client/theme/global.css +2759 -510
  268. package/src/client/types.ts +12 -0
  269. package/src/client/utils/keyboard.ts +10 -0
  270. package/src/client/utils/time-ago.ts +14 -0
  271. package/src/json-render/renderer/index.css +6 -1
  272. package/src/json-render/renderer/index.tsx +31 -1
  273. package/src/mcp/canvas-app.ts +12 -2
  274. package/src/mcp/context-uri-template.ts +7 -4
  275. package/src/mcp/server.ts +63 -41
  276. package/src/mcp/workbench-app.ts +39 -6
  277. package/src/server/agent-presence.ts +128 -28
  278. package/src/server/ax-context.ts +27 -13
  279. package/src/server/board-export.ts +12 -1
  280. package/src/server/canvas-db.ts +331 -42
  281. package/src/server/canvas-operations.ts +196 -84
  282. package/src/server/canvas-provenance.ts +53 -22
  283. package/src/server/canvas-serialization.ts +2 -0
  284. package/src/server/canvas-state.ts +950 -122
  285. package/src/server/canvas-validation.ts +9 -2
  286. package/src/server/context-brief.ts +345 -38
  287. package/src/server/context-reads.ts +179 -30
  288. package/src/server/demo-state.json +355 -24
  289. package/src/server/edit-summary.ts +74 -0
  290. package/src/server/html-surface.ts +8 -1
  291. package/src/server/index.ts +203 -13
  292. package/src/server/knowledge-lifecycle.ts +304 -0
  293. package/src/server/lifecycle-status.ts +232 -0
  294. package/src/server/memory-graph.ts +166 -0
  295. package/src/server/mutation-history.ts +18 -6
  296. package/src/server/operations/composites.ts +32 -11
  297. package/src/server/operations/http.ts +7 -2
  298. package/src/server/operations/index.ts +4 -0
  299. package/src/server/operations/mcp.ts +32 -1
  300. package/src/server/operations/ops/ax-presence.ts +1 -1
  301. package/src/server/operations/ops/ax-read.ts +57 -6
  302. package/src/server/operations/ops/ax-timeline.ts +28 -2
  303. package/src/server/operations/ops/backup.ts +5 -3
  304. package/src/server/operations/ops/batch.ts +35 -4
  305. package/src/server/operations/ops/boards.ts +188 -47
  306. package/src/server/operations/ops/canvas-wire.ts +31 -1
  307. package/src/server/operations/ops/edges.ts +36 -2
  308. package/src/server/operations/ops/groups.ts +6 -1
  309. package/src/server/operations/ops/json-render.ts +5 -1
  310. package/src/server/operations/ops/lifecycle.ts +262 -0
  311. package/src/server/operations/ops/memory-graph.ts +45 -0
  312. package/src/server/operations/ops/nodes.ts +107 -24
  313. package/src/server/operations/ops/query.ts +281 -68
  314. package/src/server/operations/ops/snapshots.ts +73 -1
  315. package/src/server/operations/ops/validate.ts +5 -1
  316. package/src/server/operations/ops/viewport.ts +97 -3
  317. package/src/server/operations/registry.ts +77 -26
  318. package/src/server/placement.ts +29 -0
  319. package/src/server/scope-fence.ts +78 -6
  320. package/src/server/server.ts +115 -16
  321. package/src/server/spatial-analysis.ts +48 -23
  322. package/src/server/tour-control.ts +82 -0
  323. package/src/server/wiki-links.ts +137 -0
  324. package/src/server/workbench-auth.ts +11 -0
  325. package/src/shared/agent-context.ts +3 -0
  326. package/src/shared/agent-presence.ts +15 -15
  327. package/src/shared/auto-arrange.ts +2 -0
  328. package/src/shared/boards.ts +4 -4
  329. package/src/shared/knowledge-lifecycle.ts +22 -0
  330. package/src/shared/knowledge-presets.ts +80 -0
  331. package/src/shared/lifecycle-status.ts +26 -0
  332. package/src/shared/memory-graph.ts +74 -0
  333. package/src/shared/relations.ts +98 -0
  334. package/src/shared/semantic-attention.ts +15 -1
  335. package/src/shared/tour.ts +23 -5
  336. package/src/shared/workspace-settings.ts +13 -0
  337. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  338. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  339. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  340. package/dist/types/server/agent-context.d.ts +0 -22
  341. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -1,19 +1,95 @@
1
- import { useRef, useState } from 'preact/hooks';
2
- import { activeBoardId, boardList } from '../state/boards-store';
3
- import { requestJson } from '../state/intent-bridge';
1
+ import { useEffect, useRef, useState } from 'preact/hooks';
2
+ import { activeBoardId, boardList, loadBoards, openMemoryTarget } from '../state/boards-store';
3
+ import { createEdgeFromClient, requestJson, updateNodeFromClient } from '../state/intent-bridge';
4
+ import type { MemoryGraph } from '../../shared/memory-graph.js';
4
5
  import { canvasAreaCenter } from './canvas-area';
5
- import { viewport } from '../state/canvas-store';
6
+ import { nodes, viewport } from '../state/canvas-store';
6
7
  import { useFocusTrap } from './use-focus-trap';
7
8
 
8
- export function BoardLinkDialog({ onClose }: { onClose: () => void }) {
9
+ export function BoardLinkDialog({
10
+ onClose,
11
+ sourceNodeId,
12
+ wikiRef,
13
+ }: {
14
+ onClose: () => void;
15
+ sourceNodeId?: string;
16
+ wikiRef?: string;
17
+ }) {
9
18
  const dialogRef = useRef<HTMLDivElement>(null);
10
19
  const searchRef = useRef<HTMLInputElement>(null);
20
+ const sourceBoardId = useRef(activeBoardId.value).current;
21
+ const sourceNode = useRef(sourceNodeId ? nodes.value.get(sourceNodeId) : undefined).current;
22
+ const [graph, setGraph] = useState<MemoryGraph | null>(null);
11
23
  const [query, setQuery] = useState('');
12
24
  const [error, setError] = useState('');
25
+ const [busy, setBusy] = useState(false);
13
26
  useFocusTrap(dialogRef, true, { initial: searchRef });
27
+ useEffect(() => {
28
+ let cancelled = false;
29
+ if (sourceNodeId && !wikiRef)
30
+ void requestJson<MemoryGraph | null>('linkCardChoices', '/api/canvas/memory-graph', null).then((result) => {
31
+ if (!cancelled) {
32
+ setGraph(result);
33
+ if (!result) setError('Could not load cards. Close and retry.');
34
+ }
35
+ });
36
+ return () => {
37
+ cancelled = true;
38
+ };
39
+ }, [sourceNodeId, wikiRef]);
14
40
  const choices = boardList.value.filter(
15
- (board) => board.id !== activeBoardId.value && board.name.toLowerCase().includes(query.trim().toLowerCase()),
41
+ (board) => board.id !== sourceBoardId && board.name.toLowerCase().includes(query.trim().toLowerCase()),
16
42
  );
43
+ const cards = (graph?.nodes ?? []).filter(
44
+ (node) =>
45
+ node.kind === 'card' &&
46
+ node.boardId !== sourceBoardId &&
47
+ `${node.title} ${boardList.value.find((board) => board.id === node.boardId)?.name ?? ''}`
48
+ .toLowerCase()
49
+ .includes(query.trim().toLowerCase()),
50
+ );
51
+ const createWikiBoard = async () => {
52
+ if (!wikiRef || !sourceNode || activeBoardId.value !== sourceBoardId)
53
+ return setError('The source board changed. Reopen it before creating the link.');
54
+ setBusy(true);
55
+ const created = await requestJson<{ board?: { id: string } } | null>(
56
+ 'createWikiBoard',
57
+ '/api/canvas/boards',
58
+ null,
59
+ {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({ name: wikiRef, expectedBoardId: sourceBoardId }),
63
+ },
64
+ );
65
+ if (!created?.board) {
66
+ setBusy(false);
67
+ return setError('Could not create the board.');
68
+ }
69
+ const saved = await updateNodeFromClient(sourceNode.id, {
70
+ content: typeof sourceNode.data.content === 'string' ? sourceNode.data.content : '',
71
+ expectedBoardId: sourceBoardId,
72
+ expectedContentRevision: sourceNode.contentRevision ?? 0,
73
+ });
74
+ await loadBoards();
75
+ setBusy(false);
76
+ if (!saved.ok)
77
+ return setError('Board created, but the source changed. Save its Markdown again to resolve the link.');
78
+ onClose();
79
+ await openMemoryTarget({ boardId: created.board.id });
80
+ };
81
+ const connect = async (boardId: string, cardId: string) => {
82
+ if (!sourceNodeId) return;
83
+ setBusy(true);
84
+ const result = await createEdgeFromClient(sourceNodeId, cardId, 'references', undefined, {
85
+ toBoardId: boardId,
86
+ expectedBoardId: sourceBoardId,
87
+ });
88
+ setBusy(false);
89
+ if (!result.ok)
90
+ return setError('Could not add the connection. Check the source board and target card, then retry.');
91
+ onClose();
92
+ };
17
93
  const create = async (boardId: string) => {
18
94
  const target = boardList.value.find((board) => board.id === boardId);
19
95
  if (!target) return setError('That board is no longer available.');
@@ -27,6 +103,7 @@ export function BoardLinkDialog({ onClose }: { onClose: () => void }) {
27
103
  method: 'POST',
28
104
  headers: { 'Content-Type': 'application/json' },
29
105
  body: JSON.stringify({
106
+ expectedBoardId: sourceBoardId,
30
107
  type: 'board',
31
108
  title: target.name,
32
109
  x: Math.round((center.x - v.x) / v.scale - 180),
@@ -50,39 +127,67 @@ export function BoardLinkDialog({ onClose }: { onClose: () => void }) {
50
127
  aria-labelledby="board-link-title"
51
128
  onMouseDown={(e) => e.stopPropagation()}
52
129
  >
53
- <h2 id="board-link-title">Link to a board</h2>
54
- <input
55
- ref={searchRef}
56
- type="search"
57
- class="text-prompt-input"
58
- aria-label="Find a board to link"
59
- placeholder="Find a board…"
60
- value={query}
61
- onInput={(e) => setQuery(e.currentTarget.value)}
62
- />
130
+ <h2 id="board-link-title">
131
+ {wikiRef
132
+ ? 'Create unresolved board'
133
+ : sourceNodeId
134
+ ? 'Connect to a card on another board'
135
+ : 'Link to a board'}
136
+ </h2>
137
+ {wikiRef ? (
138
+ <p>Create “{wikiRef}” and save this link to its board ID?</p>
139
+ ) : (
140
+ <input
141
+ ref={searchRef}
142
+ type="search"
143
+ class="text-prompt-input"
144
+ aria-label={sourceNodeId ? 'Find a card to connect' : 'Find a board to link'}
145
+ placeholder={sourceNodeId ? 'Find a board or card…' : 'Find a board…'}
146
+ value={query}
147
+ onInput={(e) => setQuery(e.currentTarget.value)}
148
+ />
149
+ )}
63
150
  {error && (
64
151
  <div role="alert" class="board-copy-error">
65
152
  {error}
66
153
  </div>
67
154
  )}
68
- <ul class="board-link-results">
69
- {choices.map((board) => (
70
- <li key={board.id}>
71
- <button type="button" onClick={() => void create(board.id)}>
72
- <span>{board.name}</span>
73
- <small>{board.nodeCount} nodes</small>
74
- </button>
75
- </li>
76
- ))}
77
- {choices.length === 0 && (
78
- <li class="board-copy-status">
79
- {boardList.value.length <= 1
80
- ? 'Create another board first, then link it here.'
81
- : 'No boards match your search.'}
82
- </li>
83
- )}
84
- </ul>
155
+ {!wikiRef && (
156
+ <ul class="board-link-results">
157
+ {sourceNodeId
158
+ ? cards.map((card) => (
159
+ <li key={card.key}>
160
+ <button type="button" disabled={busy} onClick={() => void connect(card.boardId, card.cardId!)}>
161
+ <span>{card.title}</span>
162
+ <small>{boardList.value.find((board) => board.id === card.boardId)?.name ?? card.boardId}</small>
163
+ </button>
164
+ </li>
165
+ ))
166
+ : choices.map((board) => (
167
+ <li key={board.id}>
168
+ <button type="button" onClick={() => void create(board.id)}>
169
+ <span>{board.name}</span>
170
+ <small>{board.nodeCount} nodes</small>
171
+ </button>
172
+ </li>
173
+ ))}
174
+ {(sourceNodeId ? cards.length : choices.length) === 0 && (
175
+ <li class="board-copy-status">
176
+ {boardList.value.length <= 1
177
+ ? 'Create another board first, then link it here.'
178
+ : sourceNodeId && !graph
179
+ ? 'Loading cards…'
180
+ : 'No matches. Try another search.'}
181
+ </li>
182
+ )}
183
+ </ul>
184
+ )}
85
185
  <div class="text-prompt-actions">
186
+ {wikiRef && (
187
+ <button type="button" disabled={busy} onClick={() => void createWikiBoard()}>
188
+ {busy ? 'Creating…' : 'Create board'}
189
+ </button>
190
+ )}
86
191
  <button type="button" onClick={onClose}>
87
192
  Cancel
88
193
  </button>
@@ -0,0 +1,33 @@
1
+ import { IconPin } from '../icons';
2
+ import { type BoardSummary, setBoardPinned } from '../state/boards-store';
3
+
4
+ /**
5
+ * Board pins (docs/design/BoardPins.dc.html): the same pin as a card's, in the
6
+ * same place, at board level. Pinned, the board's README and pinned cards reach
7
+ * the agent's brief whichever board is open.
8
+ */
9
+ export function BoardPinButton({ board, inNodeHeader = false }: { board: BoardSummary; inNodeHeader?: boolean }) {
10
+ const pinned = !!board.pin;
11
+ // No board name in the label: rows already name the board, and a name here would
12
+ // make role+name lookups for the board's own open button ambiguous.
13
+ const label = pinned ? "Unpin board from the agent's context" : "Pin board to the agent's context";
14
+ return (
15
+ <button
16
+ type="button"
17
+ // In a node header it takes the header's own control sizing, like the card pin it replaces.
18
+ class={`ctx-pin-btn${inNodeHeader ? '' : ' board-pin-btn'}${pinned ? ' ctx-pin-active' : ''}`}
19
+ onClick={(event) => {
20
+ event.stopPropagation();
21
+ void setBoardPinned(board.id, !pinned);
22
+ }}
23
+ aria-label={label}
24
+ aria-pressed={pinned}
25
+ data-testid="board-pin"
26
+ >
27
+ <span class="ctx-pin-mark">
28
+ <IconPin />
29
+ {pinned && board.pin?.pinnedBy.actor === 'agent' && <span class="ctx-pin-agent-dot" aria-hidden="true" />}
30
+ </span>
31
+ </button>
32
+ );
33
+ }
@@ -1,25 +1,12 @@
1
- import { render, type VNode } from 'preact';
2
- import { useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
3
- import { activeBoard, activeBoardId, boardList, boardsLoaded, openBoard } from '../state/boards-store';
1
+ import { BoardPinButton } from './BoardPinButton';
2
+ import { BarPortal } from './BarHint';
3
+ import { useLayoutEffect, useRef, useState } from 'preact/hooks';
4
+ import { activeBoard, activeBoardId, boardList, boardsLoaded, browsingMemory, openBoard } from '../state/boards-store';
4
5
  import { IconLogo, IconArrange, IconChevronDown, IconPlus } from '../icons';
5
6
  import { promptNewBoard } from './HomeView';
6
7
 
7
8
  const RECENT_LIMIT = 8;
8
9
 
9
- /** Escape the top bar's filter/clip without installing React-compat event hooks. */
10
- function BoardMenuPortal({ children }: { children: VNode }) {
11
- const host = useMemo(() => document.createElement('div'), []);
12
- useLayoutEffect(() => {
13
- document.body.appendChild(host);
14
- return () => {
15
- render(null, host);
16
- host.remove();
17
- };
18
- }, [host]);
19
- useLayoutEffect(() => render(children, host), [children, host]);
20
- return null;
21
- }
22
-
23
10
  /**
24
11
  * The top bar's board identity (plan 012): the open board's name, or "Home".
25
12
  * Clicking it lists recent boards, New board, and Home. The menu is fixed-
@@ -54,8 +41,10 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
54
41
  }, [open]);
55
42
 
56
43
  const board = activeBoard();
57
- const label = !boardsLoaded.value ? fallbackName : board ? board.name : 'Home';
58
- const recent = boardList.value.filter((entry) => entry.id !== activeBoardId.value).slice(0, RECENT_LIMIT);
44
+ const label = !boardsLoaded.value ? fallbackName : board && !browsingMemory.value ? board.name : 'Home';
45
+ const recent = boardList.value
46
+ .filter((entry) => browsingMemory.value || entry.id !== activeBoardId.value)
47
+ .slice(0, RECENT_LIMIT);
59
48
  const anchor = buttonRef.current?.getBoundingClientRect();
60
49
 
61
50
  const choose = (id: string | null) => {
@@ -80,7 +69,7 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
80
69
  </span>
81
70
  </button>
82
71
  {open && anchor && (
83
- <BoardMenuPortal>
72
+ <BarPortal>
84
73
  <div
85
74
  ref={menuRef}
86
75
  class="toolbar-menu board-switcher-menu"
@@ -97,26 +86,23 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
97
86
  {recent.length > 0 && <div class="board-switcher-heading">Recent boards</div>}
98
87
  <div class="board-switcher-recents">
99
88
  {recent.map((entry) => (
100
- <button
101
- key={entry.id}
102
- type="button"
103
- role="menuitem"
104
- class="toolbar-menu-item"
105
- onClick={() => choose(entry.id)}
106
- >
107
- <span class="board-switcher-icon" aria-hidden="true">
108
- <IconLogo size={18} />
109
- </span>
110
- <span class="board-switcher-item-copy">
111
- <span class="board-switcher-item-name">{entry.name}</span>
112
- <span class="board-switcher-item-folder">
113
- {entry.category?.split('/').join(' / ') || 'Unfiled'}
89
+ <div key={entry.id} class="board-switcher-row">
90
+ <button type="button" role="menuitem" class="toolbar-menu-item" onClick={() => choose(entry.id)}>
91
+ <span class="board-switcher-icon" aria-hidden="true">
92
+ <IconLogo size={18} />
93
+ </span>
94
+ <span class="board-switcher-item-copy">
95
+ <span class="board-switcher-item-name">{entry.name}</span>
96
+ <span class="board-switcher-item-folder">
97
+ {entry.folder?.split('/').join(' / ') || 'Unfiled'}
98
+ </span>
99
+ </span>
100
+ <span class="board-switcher-item-meta">
101
+ {entry.nodeCount} {entry.nodeCount === 1 ? 'node' : 'nodes'}
114
102
  </span>
115
- </span>
116
- <span class="board-switcher-item-meta">
117
- {entry.nodeCount} {entry.nodeCount === 1 ? 'node' : 'nodes'}
118
- </span>
119
- </button>
103
+ </button>
104
+ <BoardPinButton board={entry} />
105
+ </div>
120
106
  ))}
121
107
  </div>
122
108
  <div class="board-switcher-actions">
@@ -142,7 +128,7 @@ export function BoardSwitcher({ fallbackName }: { fallbackName: string }) {
142
128
  </button>
143
129
  </div>
144
130
  </div>
145
- </BoardMenuPortal>
131
+ </BarPortal>
146
132
  )}
147
133
  </span>
148
134
  );