pmx-canvas 0.8.2 → 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 (335) 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 +49 -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/intent-bridge.d.ts +11 -8
  53. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  54. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  55. package/dist/types/client/state/presence-store.d.ts +4 -4
  56. package/dist/types/client/state/presentation.d.ts +28 -0
  57. package/dist/types/client/state/relation-store.d.ts +10 -0
  58. package/dist/types/client/state/session-store.d.ts +41 -2
  59. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  60. package/dist/types/client/types.d.ts +13 -1
  61. package/dist/types/client/utils/keyboard.d.ts +2 -0
  62. package/dist/types/client/utils/time-ago.d.ts +4 -0
  63. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  64. package/dist/types/server/agent-presence.d.ts +10 -3
  65. package/dist/types/server/ax-context.d.ts +1 -1
  66. package/dist/types/server/canvas-db.d.ts +30 -7
  67. package/dist/types/server/canvas-operations.d.ts +24 -2
  68. package/dist/types/server/canvas-provenance.d.ts +22 -2
  69. package/dist/types/server/canvas-serialization.d.ts +2 -0
  70. package/dist/types/server/canvas-state.d.ts +93 -15
  71. package/dist/types/server/canvas-validation.d.ts +1 -1
  72. package/dist/types/server/context-brief.d.ts +94 -6
  73. package/dist/types/server/context-reads.d.ts +33 -7
  74. package/dist/types/server/edit-summary.d.ts +20 -0
  75. package/dist/types/server/index.d.ts +71 -10
  76. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  77. package/dist/types/server/lifecycle-status.d.ts +10 -0
  78. package/dist/types/server/memory-graph.d.ts +9 -0
  79. package/dist/types/server/mutation-history.d.ts +2 -0
  80. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  81. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  82. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  83. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  84. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  85. package/dist/types/server/operations/ops/query.d.ts +23 -0
  86. package/dist/types/server/operations/registry.d.ts +1 -0
  87. package/dist/types/server/placement.d.ts +7 -0
  88. package/dist/types/server/scope-fence.d.ts +2 -1
  89. package/dist/types/server/spatial-analysis.d.ts +6 -3
  90. package/dist/types/server/tour-control.d.ts +32 -0
  91. package/dist/types/server/wiki-links.d.ts +14 -0
  92. package/dist/types/server/workbench-auth.d.ts +2 -0
  93. package/dist/types/shared/agent-context.d.ts +2 -0
  94. package/dist/types/shared/agent-presence.d.ts +15 -12
  95. package/dist/types/shared/auto-arrange.d.ts +1 -0
  96. package/dist/types/shared/boards.d.ts +4 -4
  97. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  98. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  99. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  100. package/dist/types/shared/memory-graph.d.ts +53 -0
  101. package/dist/types/shared/relations.d.ts +68 -0
  102. package/dist/types/shared/semantic-attention.d.ts +2 -0
  103. package/dist/types/shared/tour.d.ts +38 -0
  104. package/dist/types/shared/workspace-settings.d.ts +11 -0
  105. package/docs/RELEASE.md +21 -3
  106. package/docs/adr-001-bun-only-runtime.md +7 -1
  107. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  108. package/docs/api-reference.md +109 -0
  109. package/docs/ax-host-adapter-contract.md +1 -1
  110. package/docs/ax-state-contract.md +5 -4
  111. package/docs/cli.md +153 -15
  112. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  113. package/docs/design/AgentContext.dc.html +3 -3
  114. package/docs/design/BoardPins.dc.html +37 -22
  115. package/docs/design/Categories.dc.html +208 -0
  116. package/docs/design/CategoryEditor.dc.html +33 -0
  117. package/docs/design/Chrome.dc.html +52 -9
  118. package/docs/design/ContextChip.dc.html +224 -0
  119. package/docs/design/ContextChip600.dc.html +136 -0
  120. package/docs/design/EmptyState.dc.html +45 -0
  121. package/docs/design/Flows.dc.html +23 -1
  122. package/docs/design/HeaderMarks.dc.html +183 -0
  123. package/docs/design/Home.dc.html +19 -1
  124. package/docs/design/HomeGraph.dc.html +86 -202
  125. package/docs/design/HomeMap.dc.html +23 -1
  126. package/docs/design/HomeTeam.dc.html +32 -14
  127. package/docs/design/Lifecycle.dc.html +22 -0
  128. package/docs/design/Lifecycle600.dc.html +18 -0
  129. package/docs/design/LifecycleVariants.dc.html +157 -0
  130. package/docs/design/LinkAuthorship.dc.html +139 -0
  131. package/docs/design/LinksChanged.dc.html +203 -0
  132. package/docs/design/LinksOptions.dc.html +137 -0
  133. package/docs/design/LinksOptions600.dc.html +134 -0
  134. package/docs/design/NearPin.dc.html +9 -10
  135. package/docs/design/Pane600.dc.html +4 -3
  136. package/docs/design/Relations.dc.html +3 -2
  137. package/docs/design/Restore.dc.html +152 -0
  138. package/docs/design/SeeChange.dc.html +267 -0
  139. package/docs/design/Uses.dc.html +12 -6
  140. package/docs/design/canvas.json +146 -19
  141. package/docs/design.md +449 -16
  142. package/docs/environment.md +0 -1
  143. package/docs/evals/curation-effect.md +128 -0
  144. package/docs/http-api.md +459 -37
  145. package/docs/mcp.md +146 -18
  146. package/docs/node-types.md +58 -3
  147. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  148. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  149. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  150. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  151. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  152. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  153. package/docs/product-context-vision-2026-09.md +17 -1
  154. package/docs/product-vision-2026-09.md +1082 -43
  155. package/docs/product-vision-review-2026-09.md +13 -1
  156. package/docs/screenshots/workbench-live-dark.png +0 -0
  157. package/docs/screenshots/workbench-live-light.png +0 -0
  158. package/docs/sdk.md +62 -2
  159. package/package.json +1 -1
  160. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  161. package/plugins/pmx-canvas/plugin.json +1 -1
  162. package/plugins/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/tools.json +601 -22
  164. package/skills/pmx-canvas/SKILL.md +216 -42
  165. package/skills/pmx-canvas/references/full-reference.md +120 -21
  166. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  167. package/skills/pmx-canvas/references/importing-context.md +132 -0
  168. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  169. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  170. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  171. package/src/cli/agent.ts +2 -0
  172. package/src/cli/commands/ax.ts +12 -0
  173. package/src/cli/commands/boards.ts +87 -11
  174. package/src/cli/commands/edges.ts +28 -0
  175. package/src/cli/commands/groups.ts +7 -0
  176. package/src/cli/commands/nodes.ts +1 -0
  177. package/src/cli/commands/query.ts +34 -2
  178. package/src/cli/commands/record.ts +199 -48
  179. package/src/cli/index.ts +6 -1
  180. package/src/client/App.tsx +177 -70
  181. package/src/client/canvas/AttentionHistory.tsx +0 -2
  182. package/src/client/canvas/BarHint.tsx +40 -2
  183. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  184. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  185. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  186. package/src/client/canvas/BoardPinButton.tsx +33 -0
  187. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  188. package/src/client/canvas/CanvasNode.tsx +360 -237
  189. package/src/client/canvas/CanvasViewport.tsx +45 -7
  190. package/src/client/canvas/CommandBar.tsx +29 -23
  191. package/src/client/canvas/CommandPalette.tsx +5 -12
  192. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  193. package/src/client/canvas/ContextChip.tsx +296 -0
  194. package/src/client/canvas/ContextMenu.tsx +63 -5
  195. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  196. package/src/client/canvas/EdgeLayer.tsx +339 -76
  197. package/src/client/canvas/EmptyState.tsx +33 -18
  198. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  199. package/src/client/canvas/ExternalWriters.tsx +57 -42
  200. package/src/client/canvas/HomeGraphView.tsx +942 -0
  201. package/src/client/canvas/HomeMapView.tsx +641 -0
  202. package/src/client/canvas/HomeReviewView.tsx +167 -0
  203. package/src/client/canvas/HomeView.tsx +687 -160
  204. package/src/client/canvas/IntentLayer.tsx +19 -12
  205. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  206. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  207. package/src/client/canvas/LifecycleFields.tsx +91 -0
  208. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  209. package/src/client/canvas/MemoryInspector.tsx +104 -0
  210. package/src/client/canvas/Minimap.tsx +3 -3
  211. package/src/client/canvas/NearPinLayer.tsx +187 -0
  212. package/src/client/canvas/NodeContextMark.tsx +175 -97
  213. package/src/client/canvas/Presentation.tsx +23 -57
  214. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  215. package/src/client/canvas/RelationsPanel.tsx +82 -0
  216. package/src/client/canvas/SelectionBar.tsx +3 -6
  217. package/src/client/canvas/SessionPanel.tsx +90 -50
  218. package/src/client/canvas/SessionReceipt.tsx +311 -39
  219. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  220. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  221. package/src/client/canvas/ToolRail.tsx +367 -192
  222. package/src/client/canvas/TopBar.tsx +354 -124
  223. package/src/client/canvas/canvas-area.ts +10 -3
  224. package/src/client/canvas/graph-geometry.ts +195 -0
  225. package/src/client/canvas/graph-model.ts +254 -0
  226. package/src/client/canvas/memory-layout.ts +106 -0
  227. package/src/client/canvas/use-chrome-height.ts +20 -0
  228. package/src/client/canvas/use-focus-trap.ts +31 -5
  229. package/src/client/canvas/use-memory-graph.ts +58 -0
  230. package/src/client/canvas/use-node-drag.ts +2 -1
  231. package/src/client/canvas/use-pan-zoom.ts +64 -7
  232. package/src/client/icons.tsx +0 -63
  233. package/src/client/mcp-app/style.css +0 -6
  234. package/src/client/nodes/AxStepControls.tsx +74 -50
  235. package/src/client/nodes/BoardNode.tsx +5 -1
  236. package/src/client/nodes/ContextNode.tsx +18 -11
  237. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  238. package/src/client/nodes/FileNode.tsx +46 -42
  239. package/src/client/nodes/HtmlNode.tsx +32 -0
  240. package/src/client/nodes/ImageNode.tsx +1 -1
  241. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  242. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  243. package/src/client/nodes/MarkdownNode.tsx +377 -109
  244. package/src/client/nodes/McpAppNode.tsx +50 -20
  245. package/src/client/nodes/MdFormatBar.tsx +21 -18
  246. package/src/client/nodes/PromptNode.tsx +1 -1
  247. package/src/client/nodes/ResponseNode.tsx +1 -1
  248. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  249. package/src/client/state/attention-bridge.ts +64 -15
  250. package/src/client/state/boards-store.ts +193 -39
  251. package/src/client/state/canvas-store.ts +99 -12
  252. package/src/client/state/context-chip-store.ts +70 -0
  253. package/src/client/state/context-status-store.ts +53 -0
  254. package/src/client/state/intent-bridge.ts +51 -30
  255. package/src/client/state/knowledge-store.ts +43 -0
  256. package/src/client/state/near-pin-store.ts +115 -6
  257. package/src/client/state/presence-store.ts +7 -10
  258. package/src/client/state/presentation.ts +91 -0
  259. package/src/client/state/relation-store.ts +81 -0
  260. package/src/client/state/session-store.ts +144 -18
  261. package/src/client/state/sse-bridge.ts +206 -29
  262. package/src/client/state/workspace-settings.ts +11 -0
  263. package/src/client/theme/global.css +2724 -476
  264. package/src/client/types.ts +12 -0
  265. package/src/client/utils/keyboard.ts +10 -0
  266. package/src/client/utils/time-ago.ts +14 -0
  267. package/src/json-render/renderer/index.tsx +31 -0
  268. package/src/mcp/canvas-app.ts +12 -2
  269. package/src/mcp/context-uri-template.ts +7 -4
  270. package/src/mcp/server.ts +63 -41
  271. package/src/mcp/workbench-app.ts +39 -6
  272. package/src/server/agent-presence.ts +128 -28
  273. package/src/server/ax-context.ts +27 -13
  274. package/src/server/board-export.ts +12 -1
  275. package/src/server/canvas-db.ts +331 -42
  276. package/src/server/canvas-operations.ts +195 -83
  277. package/src/server/canvas-provenance.ts +53 -22
  278. package/src/server/canvas-serialization.ts +2 -0
  279. package/src/server/canvas-state.ts +950 -122
  280. package/src/server/canvas-validation.ts +9 -2
  281. package/src/server/context-brief.ts +345 -38
  282. package/src/server/context-reads.ts +178 -30
  283. package/src/server/demo-state.json +355 -24
  284. package/src/server/edit-summary.ts +74 -0
  285. package/src/server/html-surface.ts +8 -1
  286. package/src/server/index.ts +181 -11
  287. package/src/server/knowledge-lifecycle.ts +304 -0
  288. package/src/server/lifecycle-status.ts +232 -0
  289. package/src/server/memory-graph.ts +166 -0
  290. package/src/server/mutation-history.ts +18 -6
  291. package/src/server/operations/composites.ts +32 -11
  292. package/src/server/operations/http.ts +7 -2
  293. package/src/server/operations/index.ts +4 -0
  294. package/src/server/operations/mcp.ts +26 -1
  295. package/src/server/operations/ops/ax-presence.ts +1 -1
  296. package/src/server/operations/ops/ax-read.ts +57 -6
  297. package/src/server/operations/ops/ax-timeline.ts +28 -2
  298. package/src/server/operations/ops/backup.ts +5 -3
  299. package/src/server/operations/ops/batch.ts +35 -4
  300. package/src/server/operations/ops/boards.ts +188 -47
  301. package/src/server/operations/ops/canvas-wire.ts +31 -1
  302. package/src/server/operations/ops/edges.ts +36 -2
  303. package/src/server/operations/ops/groups.ts +6 -1
  304. package/src/server/operations/ops/lifecycle.ts +262 -0
  305. package/src/server/operations/ops/memory-graph.ts +45 -0
  306. package/src/server/operations/ops/nodes.ts +107 -24
  307. package/src/server/operations/ops/query.ts +281 -68
  308. package/src/server/operations/ops/snapshots.ts +73 -1
  309. package/src/server/operations/ops/validate.ts +5 -1
  310. package/src/server/operations/ops/viewport.ts +97 -3
  311. package/src/server/operations/registry.ts +74 -25
  312. package/src/server/placement.ts +29 -0
  313. package/src/server/scope-fence.ts +78 -6
  314. package/src/server/server.ts +115 -16
  315. package/src/server/spatial-analysis.ts +48 -23
  316. package/src/server/tour-control.ts +82 -0
  317. package/src/server/wiki-links.ts +137 -0
  318. package/src/server/workbench-auth.ts +11 -0
  319. package/src/shared/agent-context.ts +3 -0
  320. package/src/shared/agent-presence.ts +15 -15
  321. package/src/shared/auto-arrange.ts +2 -0
  322. package/src/shared/boards.ts +4 -4
  323. package/src/shared/knowledge-lifecycle.ts +22 -0
  324. package/src/shared/knowledge-presets.ts +80 -0
  325. package/src/shared/lifecycle-status.ts +26 -0
  326. package/src/shared/memory-graph.ts +74 -0
  327. package/src/shared/relations.ts +98 -0
  328. package/src/shared/semantic-attention.ts +15 -1
  329. package/src/shared/tour.ts +23 -5
  330. package/src/shared/workspace-settings.ts +13 -0
  331. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  332. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  333. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  334. package/dist/types/server/agent-context.d.ts +0 -22
  335. 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
  );