pmx-canvas 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +4 -1
  2. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  3. package/.github/extensions/pmx-canvas/tools.json +440 -18
  4. package/CHANGELOG.md +51 -0
  5. package/dist/amp-plugin/pmx-canvas/LICENSE +21 -0
  6. package/dist/amp-plugin/pmx-canvas/THIRD_PARTY_LICENSES.txt +331 -0
  7. package/dist/amp-plugin/pmx-canvas/index.js +5445 -0
  8. package/dist/amp-plugin/pmx-canvas/runtime.json +3 -0
  9. package/dist/amp-plugin/pmx-canvas/shared-runtime.mjs +194 -0
  10. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/SKILL.md +267 -0
  11. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/evals.json +1168 -0
  12. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/jwt.ts +17 -0
  13. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/login.ts +12 -0
  14. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/auth/middleware.ts +13 -0
  15. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/code-exploration/src/routes/auth.ts +13 -0
  16. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/evals/fixtures/investigation-board/src/handlers/users.ts +27 -0
  17. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/mcp.json +36 -0
  18. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/ax-html-control-surface.md +101 -0
  19. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/codex-app-adapter.md +151 -0
  20. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +150 -0
  21. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/full-reference.md +1839 -0
  22. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/github-copilot-app-adapter.md +223 -0
  23. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/html-primitives.md +139 -0
  24. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/importing-context.md +132 -0
  25. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/installing-pmx-canvas.md +301 -0
  26. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/tours-and-recording.md +117 -0
  27. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  28. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-orchestration/SKILL.md +141 -0
  29. package/dist/amp-plugin/pmx-canvas/skills/pmx-canvas-testing/SKILL.md +266 -0
  30. package/dist/amp-plugin/pmx-canvas/start-mcp.mjs +14857 -0
  31. package/dist/amp-plugin/pmx-canvas/tools.json +10963 -0
  32. package/dist/canvas/chunks/{abnfDiagram-N423BO3Z-p8dtp6va.js → abnfDiagram-N423BO3Z-cwtsds5d.js} +1 -1
  33. package/dist/canvas/chunks/architecture-TIHT7OUA-x63cwejs.js +1 -0
  34. package/dist/canvas/chunks/{architectureDiagram-T3A2C74G-5jhmxrzf.js → architectureDiagram-T3A2C74G-b4xc9tpz.js} +3 -3
  35. package/dist/canvas/chunks/{blockDiagram-VBNYF7ZC-pw1fa9yb.js → blockDiagram-VBNYF7ZC-ahp3yxxs.js} +7 -7
  36. package/dist/canvas/chunks/{c4Diagram-5PPSVZJV-x07nq0tt.js → c4Diagram-5PPSVZJV-nappnay7.js} +4 -4
  37. package/dist/canvas/chunks/classDiagram-JCYQIIEL-k1j2xc1t.js +1 -0
  38. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-33x08xf2.js +1 -0
  39. package/dist/canvas/chunks/{cose-bilkent-JH36ORCC-nm39npq0.js → cose-bilkent-JH36ORCC-a428syda.js} +1 -1
  40. package/dist/canvas/chunks/cynefin-VYW2F7L2-8v6jxfz8.js +1 -0
  41. package/dist/canvas/chunks/{cynefinDiagram-MW4NZA55-vdxrwahz.js → cynefinDiagram-MW4NZA55-nspsqebj.js} +1 -1
  42. package/dist/canvas/chunks/{dagre-VZM6K2ZE-bqdm7wpd.js → dagre-VZM6K2ZE-8y6177fh.js} +2 -2
  43. package/dist/canvas/chunks/{diagram-7IWD3JNH-gs5gpr93.js → diagram-7IWD3JNH-vym028wn.js} +4 -4
  44. package/dist/canvas/chunks/{diagram-B4RE2ZJO-cgwjg47s.js → diagram-B4RE2ZJO-jddqb8sy.js} +1 -1
  45. package/dist/canvas/chunks/{diagram-LBJQPF4R-w3vahpav.js → diagram-LBJQPF4R-bjwbpkrn.js} +1 -1
  46. package/dist/canvas/chunks/{diagram-Q27KOJAE-42837z6h.js → diagram-Q27KOJAE-j6ph6hcx.js} +2 -2
  47. package/dist/canvas/chunks/{diagram-UB23O5K3-4fz83q59.js → diagram-UB23O5K3-p09w8jej.js} +1 -1
  48. package/dist/canvas/chunks/{ebnfDiagram-BXEA7PRR-zphtgegj.js → ebnfDiagram-BXEA7PRR-j975ycq5.js} +1 -1
  49. package/dist/canvas/chunks/{erDiagram-JOGREHBK-zvbbdxp4.js → erDiagram-JOGREHBK-sxp6hjn8.js} +5 -5
  50. package/dist/canvas/chunks/eventmodeling-45OFAUF4-xzfgn6xg.js +1 -0
  51. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-2nsmkskb.js +1 -0
  52. package/dist/canvas/chunks/{ganttDiagram-PKOTCBZU-282jzycm.js → ganttDiagram-PKOTCBZU-4tv230y2.js} +2 -2
  53. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-macbhand.js +1 -0
  54. package/dist/canvas/chunks/{gitGraphDiagram-DS77QQ5N-vqqy6p1b.js → gitGraphDiagram-DS77QQ5N-mqgmnw9g.js} +1 -1
  55. package/dist/canvas/chunks/{index-wgv7en21.js → index-037z5dz2.js} +2 -2
  56. package/dist/canvas/chunks/{index-w2mra17t.js → index-081n3har.js} +1 -1
  57. package/dist/canvas/chunks/{index-353m7b0p.js → index-5n03dmb9.js} +1 -1
  58. package/dist/canvas/chunks/{index-yw8h9vha.js → index-60pgzdqe.js} +1 -1
  59. package/dist/canvas/chunks/index-8g1vb57n.js +2 -0
  60. package/dist/canvas/chunks/{index-vv56h019.js → index-8rjes9r6.js} +1 -1
  61. package/dist/canvas/chunks/{index-nprm96xp.js → index-9022d1n9.js} +1 -1
  62. package/dist/canvas/chunks/{index-194m3hc3.js → index-9b02042t.js} +1 -1
  63. package/dist/canvas/chunks/{index-0rk4gp1g.js → index-ap4q4vez.js} +1 -1
  64. package/dist/canvas/chunks/{index-rh7nv68n.js → index-dyp2nhd3.js} +1 -1
  65. package/dist/canvas/chunks/index-frvta4tm.js +2 -0
  66. package/dist/canvas/chunks/{index-s4j678gj.js → index-h9ycj8n2.js} +1 -1
  67. package/dist/canvas/chunks/{index-fjtdggjn.js → index-mergmj97.js} +2 -2
  68. package/dist/canvas/chunks/index-nwsya6dj.js +301 -0
  69. package/dist/canvas/chunks/{index-3cy1bzwh.js → index-nz8qbd1p.js} +1 -1
  70. package/dist/canvas/chunks/{index-d3b3gqe9.js → index-qhfrazv3.js} +1 -1
  71. package/dist/canvas/chunks/{index-6qmsnxwy.js → index-v119nznk.js} +4 -4
  72. package/dist/canvas/chunks/{index-r2fyc9w1.js → index-vpt37m3d.js} +6 -6
  73. package/dist/canvas/chunks/{index-qj8fxwsf.js → index-y9ma6azy.js} +1 -1
  74. package/dist/canvas/chunks/{index-tb1wf525.js → index-zx0j3a2z.js} +26 -26
  75. package/dist/canvas/chunks/info-DKCQHKI2-a0ykzs9z.js +1 -0
  76. package/dist/canvas/chunks/{infoDiagram-6WML65LV-v1yvggqj.js → infoDiagram-6WML65LV-qv97qq6a.js} +2 -2
  77. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-p9306gh0.js +70 -0
  78. package/dist/canvas/chunks/{journeyDiagram-NVQOT4AX-zjcffveh.js → journeyDiagram-NVQOT4AX-sv67zats.js} +5 -5
  79. package/dist/canvas/chunks/{kanban-definition-27J2QSJJ-dakqtfp1.js → kanban-definition-27J2QSJJ-k2pfam6p.js} +1 -1
  80. package/dist/canvas/chunks/{mermaid.core-j2geswrd.js → mermaid.core-83r0fdce.js} +4 -4
  81. package/dist/canvas/chunks/{mindmap-definition-FAOFIHXS-142xhr3k.js → mindmap-definition-FAOFIHXS-vg2newm1.js} +1 -1
  82. package/dist/canvas/chunks/packet-7NZHBO7P-tqnabcdv.js +1 -0
  83. package/dist/canvas/chunks/{pegDiagram-VL7TDLO6-5wvk7vsd.js → pegDiagram-VL7TDLO6-k2h7zc5n.js} +1 -1
  84. package/dist/canvas/chunks/pie-RZYD4A2V-wke6gyhe.js +1 -0
  85. package/dist/canvas/chunks/{pieDiagram-7S7Q4E2Y-ztdhwycc.js → pieDiagram-7S7Q4E2Y-hg169p1v.js} +2 -2
  86. package/dist/canvas/chunks/{quadrantDiagram-CIZ2JOQS-c5p9gmg6.js → quadrantDiagram-CIZ2JOQS-bxtvsq0b.js} +5 -5
  87. package/dist/canvas/chunks/radar-I7S5WNFK-w7ka0drw.js +1 -0
  88. package/dist/canvas/chunks/railroad-3IZDKUUU-y3vg318x.js +1 -0
  89. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-6hqvcz3c.js +1 -0
  90. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-vcae260m.js +1 -0
  91. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-0dbyme23.js +1 -0
  92. package/dist/canvas/chunks/{railroadDiagram-AXF67PYL-xkmqkjwf.js → railroadDiagram-AXF67PYL-njyhcp4p.js} +1 -1
  93. package/dist/canvas/chunks/{requirementDiagram-LRYGKXZP-9wm2dyaw.js → requirementDiagram-LRYGKXZP-2z6v09gm.js} +4 -4
  94. package/dist/canvas/chunks/{sankeyDiagram-W5VNT64P-2w19etgb.js → sankeyDiagram-W5VNT64P-15mzam0e.js} +1 -1
  95. package/dist/canvas/chunks/{sequenceDiagram-SI44F4Z6-0jn0vwmz.js → sequenceDiagram-SI44F4Z6-rbshyd2c.js} +12 -12
  96. package/dist/canvas/chunks/{sizeCapture-X5ZJPWSS-x75epwdq.js → sizeCapture-X5ZJPWSS-sg26xwts.js} +1 -1
  97. package/dist/canvas/chunks/{stateDiagram-OKZ733FA-schjpy6d.js → stateDiagram-OKZ733FA-6eahp98e.js} +1 -1
  98. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-tty0ggn5.js +1 -0
  99. package/dist/canvas/chunks/{swimlanes-SLNWSIFB-gkw4xce3.js → swimlanes-SLNWSIFB-ze5db10t.js} +1 -1
  100. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-thze75gq.js +8 -0
  101. package/dist/canvas/chunks/{timeline-definition-Z64GVDOM-x7nkcy01.js → timeline-definition-Z64GVDOM-vnp7jyy8.js} +3 -3
  102. package/dist/canvas/chunks/treeView-QDETBFTQ-6ysffnm6.js +1 -0
  103. package/dist/canvas/chunks/treemap-6X3UGDF4-m3h9p066.js +1 -0
  104. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-0qrnb7mj.js +34 -0
  105. package/dist/canvas/chunks/wardley-OPB4EBWU-dp37drwx.js +1 -0
  106. package/dist/canvas/chunks/{wardleyDiagram-T6FBY63Y-t1j52t9r.js → wardleyDiagram-T6FBY63Y-8nxg8wqt.js} +4 -4
  107. package/dist/canvas/chunks/{xychartDiagram-ELKLHX3M-38t16pme.js → xychartDiagram-ELKLHX3M-nj921e7s.js} +5 -5
  108. package/dist/canvas/global.css +94 -1
  109. package/dist/canvas/index.js +154 -145
  110. package/dist/canvas/mcp-app.html +600 -498
  111. package/dist/json-render/index.css +1 -1
  112. package/dist/types/client/canvas/BoardMoveDialog.d.ts +2 -0
  113. package/dist/types/client/canvas/EdgeEditor.d.ts +19 -0
  114. package/dist/types/client/canvas/GroupNavigation.d.ts +2 -0
  115. package/dist/types/client/canvas/TextPrompt.d.ts +4 -0
  116. package/dist/types/client/nodes/PromptNode.d.ts +1 -0
  117. package/dist/types/client/nodes/ViewerFrame.d.ts +1 -1
  118. package/dist/types/client/nodes/WorkCard.d.ts +7 -0
  119. package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +1 -0
  120. package/dist/types/client/state/canvas-store.d.ts +15 -0
  121. package/dist/types/client/state/human-store.d.ts +2 -0
  122. package/dist/types/client/state/intent-bridge.d.ts +4 -2
  123. package/dist/types/client/state/relation-store.d.ts +11 -0
  124. package/dist/types/client/types.d.ts +1 -1
  125. package/dist/types/json-render/catalog.d.ts +2 -2
  126. package/dist/types/server/agent-presence.d.ts +1 -0
  127. package/dist/types/server/ax-interaction.d.ts +1 -0
  128. package/dist/types/server/ax-state-manager.d.ts +10 -2
  129. package/dist/types/server/board-local.d.ts +4 -0
  130. package/dist/types/server/board-move.d.ts +38 -0
  131. package/dist/types/server/canvas-db.d.ts +12 -2
  132. package/dist/types/server/canvas-operations.d.ts +5 -6
  133. package/dist/types/server/canvas-provenance.d.ts +1 -1
  134. package/dist/types/server/canvas-serialization.d.ts +1 -0
  135. package/dist/types/server/canvas-state.d.ts +84 -20
  136. package/dist/types/server/card-relocation.d.ts +6 -0
  137. package/dist/types/server/context-reads.d.ts +3 -1
  138. package/dist/types/server/human-presence.d.ts +1 -1
  139. package/dist/types/server/index.d.ts +16 -5
  140. package/dist/types/server/intent-registry.d.ts +8 -4
  141. package/dist/types/server/knowledge-lifecycle.d.ts +4 -2
  142. package/dist/types/server/mutation-history.d.ts +7 -5
  143. package/dist/types/server/operations/registry.d.ts +2 -0
  144. package/dist/types/server/trace-manager.d.ts +1 -0
  145. package/dist/types/shared/ax-kinds.d.ts +6 -0
  146. package/dist/types/shared/ax-surface-protocol.d.ts +8 -0
  147. package/dist/types/shared/human-presence.d.ts +1 -0
  148. package/dist/types/shared/lifecycle-status.d.ts +2 -0
  149. package/docs/RELEASE.md +21 -14
  150. package/docs/agent-plugin.md +22 -2
  151. package/docs/amp-plugin.md +270 -0
  152. package/docs/cli.md +42 -3
  153. package/docs/design/GuestName.dc.html +50 -0
  154. package/docs/design/PluginLogo.svg +7 -0
  155. package/docs/design/WorkCards.dc.html +39 -0
  156. package/docs/design/canvas.json +16 -0
  157. package/docs/design/plugin-logo.png +0 -0
  158. package/docs/design.md +111 -8
  159. package/docs/evals/agents-md-sections.md +38 -0
  160. package/docs/http-api.md +128 -10
  161. package/docs/mcp.md +61 -9
  162. package/docs/node-types.md +71 -1
  163. package/docs/plans/plan-009-tech-debt-backlog.md +1 -1
  164. package/docs/plans/plan-019-post-0.9-roadmap.md +44 -20
  165. package/docs/plans/plan-020-wave-5-work-planning.md +553 -0
  166. package/docs/product-vision-2026-09.md +33 -17
  167. package/docs/sdk.md +18 -0
  168. package/package.json +6 -3
  169. package/plugins/pmx-canvas/assets/preview.png +0 -0
  170. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/extension.mjs +4 -1
  171. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  172. package/plugins/pmx-canvas/plugin.json +2 -2
  173. package/plugins/pmx-canvas/runtime.json +1 -1
  174. package/plugins/pmx-canvas/tools.json +440 -18
  175. package/skills/data-analysis/SKILL.md +13 -26
  176. package/skills/frontend-design/LICENSE.txt +177 -0
  177. package/skills/frontend-design/SKILL.md +3 -0
  178. package/skills/pmx-canvas/SKILL.md +248 -842
  179. package/skills/pmx-canvas/evals/evals.json +110 -5
  180. package/skills/pmx-canvas/references/full-reference.md +142 -28
  181. package/skills/pmx-canvas/references/html-primitives.md +7 -0
  182. package/skills/pmx-canvas/references/installing-pmx-canvas.md +61 -11
  183. package/skills/pmx-canvas/references/tours-and-recording.md +39 -6
  184. package/skills/pmx-canvas/references/workbench-workflows.md +908 -0
  185. package/skills/pmx-canvas-testing/SKILL.md +7 -5
  186. package/skills/published-consumer-e2e/SKILL.md +25 -8
  187. package/skills/published-consumer-e2e/scripts/run-published-consumer-e2e.sh +51 -75
  188. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  189. package/skills/web-artifacts-builder/SKILL.md +6 -0
  190. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +2 -2
  191. package/skills/web-artifacts-builder/scripts/init-artifact.sh +8 -8
  192. package/src/cli/commands/apps.ts +1 -0
  193. package/src/cli/commands/boards.ts +30 -0
  194. package/src/client/App.tsx +20 -4
  195. package/src/client/canvas/AttentionHistory.tsx +10 -2
  196. package/src/client/canvas/BoardMoveDialog.tsx +225 -0
  197. package/src/client/canvas/CanvasNode.tsx +45 -2
  198. package/src/client/canvas/CanvasViewport.tsx +38 -9
  199. package/src/client/canvas/ContextMenu.tsx +34 -12
  200. package/src/client/canvas/EdgeEditor.tsx +149 -0
  201. package/src/client/canvas/EdgeLayer.tsx +87 -16
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +4 -0
  203. package/src/client/canvas/GroupNavigation.tsx +85 -0
  204. package/src/client/canvas/RelationsPanel.tsx +9 -0
  205. package/src/client/canvas/SelectionBar.tsx +21 -0
  206. package/src/client/canvas/SessionPanel.tsx +35 -1
  207. package/src/client/canvas/TextPrompt.tsx +18 -1
  208. package/src/client/canvas/TopBar.tsx +30 -0
  209. package/src/client/canvas/canvas-area.ts +1 -1
  210. package/src/client/nodes/ExtAppFrame.tsx +26 -10
  211. package/src/client/nodes/MarkdownNode.tsx +6 -7
  212. package/src/client/nodes/PromptNode.tsx +32 -3
  213. package/src/client/nodes/StatusNode.tsx +12 -0
  214. package/src/client/nodes/StatusSummary.tsx +11 -0
  215. package/src/client/nodes/ViewerFrame.tsx +21 -10
  216. package/src/client/nodes/WorkCard.tsx +175 -0
  217. package/src/client/nodes/use-ax-surface-bridge.ts +18 -14
  218. package/src/client/state/attention-bridge.ts +5 -0
  219. package/src/client/state/attention-store.ts +1 -0
  220. package/src/client/state/canvas-store.ts +185 -15
  221. package/src/client/state/human-store.ts +22 -2
  222. package/src/client/state/intent-bridge.ts +19 -5
  223. package/src/client/state/relation-store.ts +127 -1
  224. package/src/client/theme/global.css +94 -1
  225. package/src/client/types.ts +1 -0
  226. package/src/mcp/server.ts +13 -3
  227. package/src/server/agent-presence.ts +43 -23
  228. package/src/server/ax-flow-loop.ts +6 -0
  229. package/src/server/ax-gate-ttl.ts +8 -1
  230. package/src/server/ax-interaction.ts +16 -10
  231. package/src/server/ax-state-manager.ts +17 -4
  232. package/src/server/ax-wait.ts +20 -6
  233. package/src/server/board-export.ts +19 -6
  234. package/src/server/board-local.ts +20 -0
  235. package/src/server/board-move.ts +297 -0
  236. package/src/server/canvas-db.ts +262 -160
  237. package/src/server/canvas-operations.ts +139 -109
  238. package/src/server/canvas-schema.ts +21 -0
  239. package/src/server/canvas-serialization.ts +2 -0
  240. package/src/server/canvas-state.ts +792 -159
  241. package/src/server/card-relocation.ts +53 -0
  242. package/src/server/code-graph.ts +4 -3
  243. package/src/server/context-reads.ts +83 -55
  244. package/src/server/demo-state.json +35 -15
  245. package/src/server/file-watcher.ts +30 -10
  246. package/src/server/human-presence.ts +18 -5
  247. package/src/server/index.ts +35 -15
  248. package/src/server/intent-registry.ts +43 -5
  249. package/src/server/knowledge-lifecycle.ts +32 -9
  250. package/src/server/lifecycle-status.ts +29 -1
  251. package/src/server/mcp-app-runtime.ts +26 -7
  252. package/src/server/mutation-history.ts +37 -8
  253. package/src/server/operations/composites.ts +6 -3
  254. package/src/server/operations/mcp-contracts.ts +15 -0
  255. package/src/server/operations/mcp.ts +36 -9
  256. package/src/server/operations/ops/annotation.ts +22 -1
  257. package/src/server/operations/ops/ax-await.ts +1 -0
  258. package/src/server/operations/ops/ax-timeline.ts +28 -2
  259. package/src/server/operations/ops/batch.ts +25 -11
  260. package/src/server/operations/ops/boards.ts +44 -3
  261. package/src/server/operations/ops/nodes.ts +70 -0
  262. package/src/server/operations/ops/query.ts +32 -3
  263. package/src/server/operations/ops/snapshots.ts +1 -0
  264. package/src/server/operations/registry.ts +49 -4
  265. package/src/server/server.ts +62 -16
  266. package/src/server/tour-control.ts +37 -12
  267. package/src/server/trace-manager.ts +4 -0
  268. package/src/server/web-artifacts/scripts/bundle-artifact.sh +2 -2
  269. package/src/server/web-artifacts/scripts/init-artifact.sh +8 -8
  270. package/src/shared/agent-context.ts +7 -1
  271. package/src/shared/auto-arrange.ts +5 -4
  272. package/src/shared/ax-kinds.ts +13 -0
  273. package/src/shared/ax-surface-protocol.ts +22 -0
  274. package/src/shared/human-presence.ts +1 -0
  275. package/src/shared/lifecycle-status.ts +2 -0
  276. package/dist/canvas/chunks/architecture-TIHT7OUA-9tffvpsv.js +0 -1
  277. package/dist/canvas/chunks/classDiagram-JCYQIIEL-nc6g5x1r.js +0 -1
  278. package/dist/canvas/chunks/classDiagram-v2-OCEON4UE-0qnbpwvh.js +0 -1
  279. package/dist/canvas/chunks/cynefin-VYW2F7L2-ys8p46ts.js +0 -1
  280. package/dist/canvas/chunks/eventmodeling-45OFAUF4-v9q6dn4c.js +0 -1
  281. package/dist/canvas/chunks/flowDiagram-UKHOOZJN-yrr5z8tc.js +0 -1
  282. package/dist/canvas/chunks/gitGraph-TEB2WS4Q-kdwb8m0a.js +0 -1
  283. package/dist/canvas/chunks/index-1r72hdzb.js +0 -301
  284. package/dist/canvas/chunks/index-g3pwnqeb.js +0 -2
  285. package/dist/canvas/chunks/index-rft2hcz5.js +0 -2
  286. package/dist/canvas/chunks/info-DKCQHKI2-wpzs6sz3.js +0 -1
  287. package/dist/canvas/chunks/ishikawaDiagram-WSZJBQD7-5hnq1a9p.js +0 -70
  288. package/dist/canvas/chunks/packet-7NZHBO7P-xn439f0s.js +0 -1
  289. package/dist/canvas/chunks/pie-RZYD4A2V-0kf16rwh.js +0 -1
  290. package/dist/canvas/chunks/radar-I7S5WNFK-0g2pz36t.js +0 -1
  291. package/dist/canvas/chunks/railroad-3IZDKUUU-fcaran6j.js +0 -1
  292. package/dist/canvas/chunks/railroad-abnf-AHOZXSZD-wgdya41q.js +0 -1
  293. package/dist/canvas/chunks/railroad-ebnf-EBAXGLYW-gy07a4ka.js +0 -1
  294. package/dist/canvas/chunks/railroad-peg-LSFZ7HO6-ewk6wtkh.js +0 -1
  295. package/dist/canvas/chunks/stateDiagram-v2-UEYNNEHI-k8sfp2e7.js +0 -1
  296. package/dist/canvas/chunks/swimlanesDiagram-ULZ7WXOC-nqysw67j.js +0 -8
  297. package/dist/canvas/chunks/treeView-QDETBFTQ-cesw6gp0.js +0 -1
  298. package/dist/canvas/chunks/treemap-6X3UGDF4-zdv4knp9.js +0 -1
  299. package/dist/canvas/chunks/vennDiagram-T6HMQDX7-x8dqn9yf.js +0 -34
  300. package/dist/canvas/chunks/wardley-OPB4EBWU-44pdddez.js +0 -1
@@ -8,6 +8,8 @@
8
8
  import { Database } from 'bun:sqlite';
9
9
  import type { ActorAttribution } from './attribution.js';
10
10
  import { CONTEXT_READS_SCHEMA_SQL } from './context-reads.js';
11
+ import { OperationError } from './operations/types.js';
12
+ import { boardMoveBoundary } from './card-relocation.js';
11
13
  import { MIGRATED_SNAPSHOT_FOLDER, normalizeBoardFolder } from '../shared/boards.js';
12
14
  import { wikiLinksFromData } from '../shared/memory-graph.js';
13
15
  import { relationVocabularySchema, type RelationDefinition } from '../shared/relations.js';
@@ -56,6 +58,20 @@ export function normalizeCanvasTheme(value: unknown, fallback: CanvasTheme = 'ha
56
58
  }
57
59
 
58
60
  const SCHEMA_SQL = `
61
+ CREATE TABLE IF NOT EXISTS board_moves (
62
+ request_id TEXT PRIMARY KEY, fingerprint TEXT NOT NULL, result TEXT NOT NULL, created_at TEXT NOT NULL
63
+ );
64
+ CREATE TABLE IF NOT EXISTS board_move_boundaries (
65
+ board_id TEXT PRIMARY KEY, request_id TEXT NOT NULL
66
+ );
67
+ CREATE TABLE IF NOT EXISTS card_relocations (
68
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT, request_id TEXT NOT NULL, node_id TEXT NOT NULL,
69
+ from_board TEXT NOT NULL, to_board TEXT NOT NULL,
70
+ review_after INTEGER NOT NULL, review_through INTEGER NOT NULL,
71
+ read_after INTEGER NOT NULL, read_through INTEGER NOT NULL
72
+ );
73
+ CREATE INDEX IF NOT EXISTS relocation_destination ON card_relocations(to_board, node_id, sequence);
74
+ CREATE INDEX IF NOT EXISTS relocation_source ON card_relocations(from_board, node_id, sequence);
59
75
  CREATE TABLE IF NOT EXISTS node_creation_requests (
60
76
  request_id TEXT PRIMARY KEY, board_id TEXT NOT NULL, fingerprint TEXT NOT NULL, node TEXT NOT NULL
61
77
  );
@@ -357,6 +373,10 @@ function parsePersistedAxState(raw: string | null | undefined): PmxAxState {
357
373
  // ── Persisted State Interface ───────────────────────────────────
358
374
 
359
375
  export interface PersistedCanvasState {
376
+ /** Optimistic storage baseline, independent of content/read/review clocks. New boards start at zero. */
377
+ storageVersion?: number;
378
+ /** Snapshot restore boundary, not an authored content version. */
379
+ moveBoundary?: string | null;
360
380
  tour?: Tour;
361
381
  version: number;
362
382
  theme?: CanvasTheme;
@@ -462,6 +482,19 @@ export function openCanvasDb(dbPath: string, options: OpenCanvasDbOptions = {}):
462
482
  // worth the fsync.
463
483
  db.exec('PRAGMA synchronous=FULL');
464
484
  if (needsBoardsMigration(db)) migrateToBoards(db, dbPath, options.migratedBoardName ?? 'Board');
485
+ const empty = !db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' LIMIT 1").get();
486
+ if (empty) {
487
+ // One durable commit for a fresh schema. Another initializer may win the
488
+ // lock first, so all schema checks still run inside the transaction.
489
+ db.transaction(() => initializeCanvasSchema(db)).immediate();
490
+ } else {
491
+ // Current databases must remain readable while another connection writes.
492
+ initializeCanvasSchema(db);
493
+ }
494
+ return db;
495
+ }
496
+
497
+ function initializeCanvasSchema(db: Database): void {
465
498
  db.exec(SCHEMA_SQL);
466
499
  db.exec(CONTEXT_READS_SCHEMA_SQL);
467
500
 
@@ -513,14 +546,34 @@ export function openCanvasDb(dbPath: string, options: OpenCanvasDbOptions = {}):
513
546
  ensureColumn(db, 'snapshot_edges', 'revision', 'revision INTEGER NOT NULL DEFAULT 0');
514
547
  ensureColumn(db, 'snapshot_edges', 'attribution', "attribution TEXT NOT NULL DEFAULT '{}'");
515
548
  ensureColumn(db, 'document_imports', 'committed_node_ids', 'committed_node_ids TEXT');
549
+ ensureColumn(db, 'boards', 'storage_version', 'storage_version INTEGER NOT NULL DEFAULT 0');
550
+ // Cover direct writers as well as full-state saves. A relocation updates both
551
+ // board baselines; a stale source or destination payload must never recreate it.
552
+ for (const table of ['nodes', 'edges', 'annotations', 'context_pins', 'ax_state']) {
553
+ db.exec(`
554
+ CREATE TRIGGER IF NOT EXISTS ${table}_storage_insert AFTER INSERT ON ${table} BEGIN
555
+ UPDATE boards SET storage_version = storage_version + 1 WHERE id = NEW.board_id;
556
+ END;
557
+ CREATE TRIGGER IF NOT EXISTS ${table}_storage_delete AFTER DELETE ON ${table} BEGIN
558
+ UPDATE boards SET storage_version = storage_version + 1 WHERE id = OLD.board_id;
559
+ END;
560
+ CREATE TRIGGER IF NOT EXISTS ${table}_storage_update AFTER UPDATE ON ${table} BEGIN
561
+ UPDATE boards SET storage_version = storage_version + 1 WHERE id IN (OLD.board_id, NEW.board_id);
562
+ END;
563
+ `);
564
+ }
565
+ db.exec(`
566
+ CREATE TRIGGER IF NOT EXISTS boards_storage_view
567
+ AFTER UPDATE OF viewport_x, viewport_y, viewport_scale, tour ON boards BEGIN
568
+ UPDATE boards SET storage_version = storage_version + 1 WHERE id = NEW.id;
569
+ END;
570
+ `);
516
571
 
517
572
  // Set schema version if not present
518
573
  const row = db.query<{ value: string }, [string]>('SELECT value FROM meta WHERE key = ?').get('schema_version');
519
574
  if (!row) {
520
575
  db.run('INSERT INTO meta (key, value) VALUES (?, ?)', ['schema_version', String(SCHEMA_VERSION)]);
521
576
  }
522
-
523
- return db;
524
577
  }
525
578
 
526
579
  // ── Many boards migration (schema 1 → 2) ────────────────────────
@@ -762,9 +815,26 @@ export function readThemeFromDB(db: Database): CanvasTheme | undefined {
762
815
  return value ? normalizeCanvasTheme(value) : undefined;
763
816
  }
764
817
 
765
- /** Saves one board's state; every other board's rows are untouched. */
766
- export function saveStateToDB(db: Database, boardId: string, state: PersistedCanvasState): void {
818
+ export function boardStorageVersion(db: Database, boardId: string): number {
819
+ const board = db
820
+ .query<{ storage_version: number }, [string]>('SELECT storage_version FROM boards WHERE id = ?')
821
+ .get(boardId);
822
+ if (!board) throw new OperationError(`Board "${boardId}" not found.`, 404);
823
+ return board.storage_version;
824
+ }
825
+
826
+ export function assertBoardStorageVersion(db: Database, boardId: string, expected: number): void {
827
+ if (boardStorageVersion(db, boardId) !== expected)
828
+ throw new OperationError(
829
+ `Board "${boardId}" changed in another writer. Unsaved edits have been retained; reconcile them before saving.`,
830
+ 409,
831
+ );
832
+ }
833
+
834
+ /** Save one board, returning its new baseline. Publish the baseline only after the outer transaction commits. */
835
+ export function saveStateToDB(db: Database, boardId: string, state: PersistedCanvasState): number {
767
836
  const transaction = db.transaction(() => {
837
+ assertBoardStorageVersion(db, boardId, state.storageVersion ?? 0);
768
838
  // Rows are upserted only when a column actually differs, and rows that are
769
839
  // gone are deleted by id. This used to be DELETE-all + INSERT-all, which
770
840
  // rewrote every row on every debounced save: dragging one node rewrote the
@@ -920,157 +990,164 @@ export function saveStateToDB(db: Database, boardId: string, state: PersistedCan
920
990
  WHERE value IS NOT excluded.value`,
921
991
  [boardId, 'revision', JSON.stringify(state.revisionState ?? { revision: 0, floor: 0, tombstones: [] })],
922
992
  );
993
+ return boardStorageVersion(db, boardId);
923
994
  });
924
995
 
925
- transaction();
996
+ return transaction.immediate();
926
997
  }
927
998
 
928
999
  /** Loads one board (the open board when `boardId` is omitted); null when there is no such board. */
929
1000
  export function loadStateFromDB(db: Database, boardId?: string): PersistedCanvasState | null {
930
- const id = boardId ?? getActiveBoardIdFromDB(db);
931
- if (!id) return null;
932
- interface BoardRow {
933
- viewport_x: number;
934
- viewport_y: number;
935
- viewport_scale: number;
936
- tour: string;
937
- }
938
- const board = db
939
- .query<BoardRow, [string]>('SELECT viewport_x, viewport_y, viewport_scale, tour FROM boards WHERE id = ?')
940
- .get(id);
941
- if (!board) return null;
942
-
943
- const viewport: ViewportState = {
944
- x: board.viewport_x,
945
- y: board.viewport_y,
946
- scale: board.viewport_scale || 1,
947
- };
948
- const theme = readThemeFromDB(db);
1001
+ return db.transaction(() => {
1002
+ const id = boardId ?? getActiveBoardIdFromDB(db);
1003
+ if (!id) return null;
1004
+ interface BoardRow {
1005
+ viewport_x: number;
1006
+ viewport_y: number;
1007
+ viewport_scale: number;
1008
+ tour: string;
1009
+ storage_version: number;
1010
+ }
1011
+ const board = db
1012
+ .query<BoardRow, [string]>(
1013
+ 'SELECT viewport_x, viewport_y, viewport_scale, tour, storage_version FROM boards WHERE id = ?',
1014
+ )
1015
+ .get(id);
1016
+ if (!board) return null;
949
1017
 
950
- // Load nodes
951
- interface NodeRow {
952
- id: string;
953
- type: string;
954
- pos_x: number;
955
- pos_y: number;
956
- width: number;
957
- height: number;
958
- z_index: number;
959
- collapsed: number;
960
- pinned: number;
961
- data: string;
962
- attribution: string;
963
- content_revision: number;
964
- links_revision: number;
965
- }
966
- const nodeRows = db.query<NodeRow, [string]>('SELECT * FROM nodes WHERE board_id = ?').all(id);
967
- const nodes: CanvasNodeState[] = nodeRows.map((row) => {
968
- const attribution = JSON.parse(row.attribution || '{}') as Partial<
969
- Pick<CanvasNodeState, 'createdBy' | 'lastEditedBy' | 'contentChangedAt' | 'freshnessStartedAt'>
970
- >;
971
- return {
972
- id: row.id,
973
- type: row.type as CanvasNodeState['type'],
974
- position: { x: row.pos_x, y: row.pos_y },
975
- size: { width: row.width, height: row.height },
976
- zIndex: row.z_index,
977
- collapsed: row.collapsed === 1,
978
- pinned: row.pinned === 1,
979
- data: JSON.parse(row.data) as Record<string, unknown>,
980
- createdBy: attribution.createdBy,
981
- lastEditedBy: attribution.lastEditedBy,
982
- contentChangedAt: attribution.contentChangedAt,
983
- freshnessStartedAt: attribution.freshnessStartedAt,
984
- contentRevision: row.content_revision,
985
- linksRevision: row.links_revision,
1018
+ const viewport: ViewportState = {
1019
+ x: board.viewport_x,
1020
+ y: board.viewport_y,
1021
+ scale: board.viewport_scale || 1,
986
1022
  };
987
- });
988
-
989
- // Load edges
990
- interface EdgeRow {
991
- id: string;
992
- from_node: string;
993
- to_node: string;
994
- to_board_id: string | null;
995
- type: string;
996
- kind: string | null;
997
- reason: string | null;
998
- label: string | null;
999
- style: string | null;
1000
- animated: number;
1001
- revision: number;
1002
- attribution: string;
1003
- }
1004
- const edgeRows = db.query<EdgeRow, [string]>('SELECT * FROM edges WHERE board_id = ?').all(id);
1005
- const edges: CanvasEdge[] = edgeRows.map((row) => ({
1006
- id: row.id,
1007
- from: row.from_node,
1008
- to: row.to_node,
1009
- ...(row.to_board_id ? { toBoardId: row.to_board_id } : {}),
1010
- type: row.type as CanvasEdge['type'],
1011
- ...(row.kind ? { kind: row.kind } : {}),
1012
- ...(row.reason ? { reason: row.reason } : {}),
1013
- ...(row.label ? { label: row.label } : {}),
1014
- ...(row.style ? { style: row.style as CanvasEdge['style'] } : {}),
1015
- ...(row.animated ? { animated: true } : {}),
1016
- ...(row.revision ? { revision: row.revision } : {}),
1017
- ...(JSON.parse(row.attribution || '{}') as EdgeAttribution),
1018
- }));
1019
-
1020
- // Load annotations
1021
- interface AnnotationRow {
1022
- id: string;
1023
- type: string;
1024
- points: string;
1025
- bounds: string;
1026
- color: string;
1027
- width: number;
1028
- text: string | null;
1029
- label: string | null;
1030
- created_at: string;
1031
- }
1032
- const annotationRows = db.query<AnnotationRow, [string]>('SELECT * FROM annotations WHERE board_id = ?').all(id);
1033
- const annotations: CanvasAnnotation[] = annotationRows.map((row) => ({
1034
- id: row.id,
1035
- type: row.type as CanvasAnnotation['type'],
1036
- points: JSON.parse(row.points),
1037
- bounds: JSON.parse(row.bounds),
1038
- color: row.color,
1039
- width: row.width,
1040
- ...(row.text ? { text: row.text } : {}),
1041
- ...(row.label ? { label: row.label } : {}),
1042
- createdAt: row.created_at,
1043
- }));
1044
-
1045
- // Load context pins
1046
- interface PinRow {
1047
- node_id: string;
1048
- meta: string | null;
1049
- }
1050
- const pinRows = db.query<PinRow, [string]>('SELECT node_id, meta FROM context_pins WHERE board_id = ?').all(id);
1051
- const contextPins = pinRows.map((row) => row.node_id);
1052
- const contextPinMeta = pinMetaFromRows(pinRows);
1023
+ const theme = readThemeFromDB(db);
1024
+
1025
+ // Load nodes
1026
+ interface NodeRow {
1027
+ id: string;
1028
+ type: string;
1029
+ pos_x: number;
1030
+ pos_y: number;
1031
+ width: number;
1032
+ height: number;
1033
+ z_index: number;
1034
+ collapsed: number;
1035
+ pinned: number;
1036
+ data: string;
1037
+ attribution: string;
1038
+ content_revision: number;
1039
+ links_revision: number;
1040
+ }
1041
+ const nodeRows = db.query<NodeRow, [string]>('SELECT * FROM nodes WHERE board_id = ?').all(id);
1042
+ const nodes: CanvasNodeState[] = nodeRows.map((row) => {
1043
+ const attribution = JSON.parse(row.attribution || '{}') as Partial<
1044
+ Pick<CanvasNodeState, 'createdBy' | 'lastEditedBy' | 'contentChangedAt' | 'freshnessStartedAt'>
1045
+ >;
1046
+ return {
1047
+ id: row.id,
1048
+ type: row.type as CanvasNodeState['type'],
1049
+ position: { x: row.pos_x, y: row.pos_y },
1050
+ size: { width: row.width, height: row.height },
1051
+ zIndex: row.z_index,
1052
+ collapsed: row.collapsed === 1,
1053
+ pinned: row.pinned === 1,
1054
+ data: JSON.parse(row.data) as Record<string, unknown>,
1055
+ createdBy: attribution.createdBy,
1056
+ lastEditedBy: attribution.lastEditedBy,
1057
+ contentChangedAt: attribution.contentChangedAt,
1058
+ freshnessStartedAt: attribution.freshnessStartedAt,
1059
+ contentRevision: row.content_revision,
1060
+ linksRevision: row.links_revision,
1061
+ };
1062
+ });
1063
+
1064
+ // Load edges
1065
+ interface EdgeRow {
1066
+ id: string;
1067
+ from_node: string;
1068
+ to_node: string;
1069
+ to_board_id: string | null;
1070
+ type: string;
1071
+ kind: string | null;
1072
+ reason: string | null;
1073
+ label: string | null;
1074
+ style: string | null;
1075
+ animated: number;
1076
+ revision: number;
1077
+ attribution: string;
1078
+ }
1079
+ const edgeRows = db.query<EdgeRow, [string]>('SELECT * FROM edges WHERE board_id = ?').all(id);
1080
+ const edges: CanvasEdge[] = edgeRows.map((row) => ({
1081
+ id: row.id,
1082
+ from: row.from_node,
1083
+ to: row.to_node,
1084
+ ...(row.to_board_id ? { toBoardId: row.to_board_id } : {}),
1085
+ type: row.type as CanvasEdge['type'],
1086
+ ...(row.kind ? { kind: row.kind } : {}),
1087
+ ...(row.reason ? { reason: row.reason } : {}),
1088
+ ...(row.label ? { label: row.label } : {}),
1089
+ ...(row.style ? { style: row.style as CanvasEdge['style'] } : {}),
1090
+ ...(row.animated ? { animated: true } : {}),
1091
+ ...(row.revision ? { revision: row.revision } : {}),
1092
+ ...(JSON.parse(row.attribution || '{}') as EdgeAttribution),
1093
+ }));
1094
+
1095
+ // Load annotations
1096
+ interface AnnotationRow {
1097
+ id: string;
1098
+ type: string;
1099
+ points: string;
1100
+ bounds: string;
1101
+ color: string;
1102
+ width: number;
1103
+ text: string | null;
1104
+ label: string | null;
1105
+ created_at: string;
1106
+ }
1107
+ const annotationRows = db.query<AnnotationRow, [string]>('SELECT * FROM annotations WHERE board_id = ?').all(id);
1108
+ const annotations: CanvasAnnotation[] = annotationRows.map((row) => ({
1109
+ id: row.id,
1110
+ type: row.type as CanvasAnnotation['type'],
1111
+ points: JSON.parse(row.points),
1112
+ bounds: JSON.parse(row.bounds),
1113
+ color: row.color,
1114
+ width: row.width,
1115
+ ...(row.text ? { text: row.text } : {}),
1116
+ ...(row.label ? { label: row.label } : {}),
1117
+ createdAt: row.created_at,
1118
+ }));
1119
+
1120
+ // Load context pins
1121
+ interface PinRow {
1122
+ node_id: string;
1123
+ meta: string | null;
1124
+ }
1125
+ const pinRows = db.query<PinRow, [string]>('SELECT node_id, meta FROM context_pins WHERE board_id = ?').all(id);
1126
+ const contextPins = pinRows.map((row) => row.node_id);
1127
+ const contextPinMeta = pinMetaFromRows(pinRows);
1053
1128
 
1054
- const axRow = db
1055
- .query<{ value: string }, [string, string]>('SELECT value FROM ax_state WHERE board_id = ? AND key = ?')
1056
- .get(id, 'state');
1129
+ const axRow = db
1130
+ .query<{ value: string }, [string, string]>('SELECT value FROM ax_state WHERE board_id = ? AND key = ?')
1131
+ .get(id, 'state');
1057
1132
 
1058
- const revisionRaw = db
1059
- .query<{ value: string }, [string, string]>('SELECT value FROM ax_state WHERE board_id = ? AND key = ?')
1060
- .get(id, 'revision')?.value;
1061
- return {
1062
- version: 1,
1063
- theme,
1064
- tour: readTour(board.tour),
1065
- viewport,
1066
- nodes,
1067
- edges,
1068
- annotations,
1069
- contextPins,
1070
- contextPinMeta,
1071
- ax: parsePersistedAxState(axRow?.value),
1072
- revisionState: revisionRaw ? (JSON.parse(revisionRaw) as PersistedRevisionState) : undefined,
1073
- };
1133
+ const revisionRaw = db
1134
+ .query<{ value: string }, [string, string]>('SELECT value FROM ax_state WHERE board_id = ? AND key = ?')
1135
+ .get(id, 'revision')?.value;
1136
+ return {
1137
+ storageVersion: board.storage_version,
1138
+ version: 1,
1139
+ theme,
1140
+ tour: readTour(board.tour),
1141
+ viewport,
1142
+ nodes,
1143
+ edges,
1144
+ annotations,
1145
+ contextPins,
1146
+ contextPinMeta,
1147
+ ax: parsePersistedAxState(axRow?.value),
1148
+ revisionState: revisionRaw ? (JSON.parse(revisionRaw) as PersistedRevisionState) : undefined,
1149
+ };
1150
+ })();
1074
1151
  }
1075
1152
 
1076
1153
  // ── Boards ──────────────────────────────────────────────────────
@@ -1387,6 +1464,7 @@ export function saveSnapshotToDB(
1387
1464
  state: PersistedCanvasState,
1388
1465
  ): void {
1389
1466
  const transaction = db.transaction(() => {
1467
+ assertBoardStorageVersion(db, boardId, state.storageVersion ?? 0);
1390
1468
  // Insert snapshot metadata
1391
1469
  db.run('INSERT INTO snapshots (id, board_id, name, created_at, node_count, edge_count) VALUES (?, ?, ?, ?, ?, ?)', [
1392
1470
  snapshot.id,
@@ -1402,6 +1480,11 @@ export function saveSnapshotToDB(
1402
1480
  'tour',
1403
1481
  JSON.stringify(state.tour ?? null),
1404
1482
  ]);
1483
+ db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
1484
+ snapshot.id,
1485
+ 'move_boundary',
1486
+ boardMoveBoundary(db, boardId) ?? '',
1487
+ ]);
1405
1488
  // Insert snapshot viewport meta
1406
1489
  db.run('INSERT INTO snapshot_meta (snapshot_id, key, value) VALUES (?, ?, ?)', [
1407
1490
  snapshot.id,
@@ -1508,7 +1591,7 @@ export function saveSnapshotToDB(
1508
1591
  ]);
1509
1592
  });
1510
1593
 
1511
- transaction();
1594
+ transaction.immediate();
1512
1595
  }
1513
1596
 
1514
1597
  /** By id or name (most recent match) on `boardId`. */
@@ -1679,6 +1762,7 @@ export function loadSnapshotFromDB(
1679
1762
  return {
1680
1763
  snapshot,
1681
1764
  state: {
1765
+ moveBoundary: metaMap.get('move_boundary') || null,
1682
1766
  version: 1,
1683
1767
  tour: readTour(metaMap.get('tour')),
1684
1768
  viewport,
@@ -2019,28 +2103,37 @@ export function loadAxSteeringFromDB(
2019
2103
 
2020
2104
  export function loadPendingAxSteeringFromDB(
2021
2105
  db: Database,
2022
- options: { consumer?: string; limit?: number } = {},
2106
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
2023
2107
  ): PmxAxSteeringMessage[] {
2108
+ if (options.addressedOnly && !options.consumer) return [];
2024
2109
  // FIFO (oldest undelivered first); exclude the consumer's own steering (loop
2025
2110
  // prevention) and any steering addressed to a DIFFERENT consumer (target scoping)
2026
2111
  // in SQL so the LIMIT is applied AFTER both filters, not before.
2027
2112
  const limit = clampTimelineLimit(options.limit);
2028
2113
  const rows = options.consumer
2029
2114
  ? db
2030
- .query<AxSteeringRow, [string, string, string, string, string, number]>(
2115
+ .query<AxSteeringRow, [string, string, string, string, string, number, number]>(
2031
2116
  // A broadcast stays pending PER CONSUMER: it leaves this consumer's
2032
2117
  // queue only once THIS consumer marked it (or an anonymous global ack
2033
2118
  // set delivered).
2034
- 'SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq ASC LIMIT ?',
2119
+ `SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) ${options.addressedOnly ? 'AND s.target IS NOT NULL' : ''} AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq ASC LIMIT ? OFFSET ?`,
2120
+ )
2121
+ .all(
2122
+ options.consumer,
2123
+ options.consumer,
2124
+ options.consumer,
2125
+ broadcastCutoffIso(),
2126
+ options.consumer,
2127
+ limit,
2128
+ options.offset ?? 0,
2035
2129
  )
2036
- .all(options.consumer, options.consumer, options.consumer, broadcastCutoffIso(), options.consumer, limit)
2037
2130
  : db
2038
- .query<AxSteeringRow, [string, number]>(
2131
+ .query<AxSteeringRow, [string, number, number]>(
2039
2132
  // No consumer given: only broadcasts — addressed steering is claimable
2040
2133
  // solely by the consumer it names.
2041
- 'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL AND created_at > ? ORDER BY seq ASC LIMIT ?',
2134
+ 'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL AND created_at > ? ORDER BY seq ASC LIMIT ? OFFSET ?',
2042
2135
  )
2043
- .all(broadcastCutoffIso(), limit);
2136
+ .all(broadcastCutoffIso(), limit, options.offset ?? 0);
2044
2137
  return rows.map(mapAxSteeringRow).filter((s): s is PmxAxSteeringMessage => s !== null);
2045
2138
  }
2046
2139
 
@@ -2054,20 +2147,29 @@ export function loadPendingAxSteeringFromDB(
2054
2147
  */
2055
2148
  export function loadNewestPendingAxSteeringFromDB(
2056
2149
  db: Database,
2057
- options: { consumer?: string; limit?: number } = {},
2150
+ options: { consumer?: string; limit?: number; addressedOnly?: boolean; offset?: number } = {},
2058
2151
  ): PmxAxSteeringMessage[] {
2152
+ if (options.addressedOnly && !options.consumer) return [];
2059
2153
  const limit = clampTimelineLimit(options.limit);
2060
2154
  const rows = options.consumer
2061
2155
  ? db
2062
- .query<AxSteeringRow, [string, string, string, string, string, number]>(
2063
- 'SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq DESC LIMIT ?',
2156
+ .query<AxSteeringRow, [string, string, string, string, string, number, number]>(
2157
+ `SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) ${options.addressedOnly ? 'AND s.target IS NOT NULL' : ''} AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq DESC LIMIT ? OFFSET ?`,
2158
+ )
2159
+ .all(
2160
+ options.consumer,
2161
+ options.consumer,
2162
+ options.consumer,
2163
+ broadcastCutoffIso(),
2164
+ options.consumer,
2165
+ limit,
2166
+ options.offset ?? 0,
2064
2167
  )
2065
- .all(options.consumer, options.consumer, options.consumer, broadcastCutoffIso(), options.consumer, limit)
2066
2168
  : db
2067
- .query<AxSteeringRow, [number]>(
2068
- 'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL ORDER BY seq DESC LIMIT ?',
2169
+ .query<AxSteeringRow, [number, number]>(
2170
+ 'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL ORDER BY seq DESC LIMIT ? OFFSET ?',
2069
2171
  )
2070
- .all(limit);
2172
+ .all(limit, options.offset ?? 0);
2071
2173
  return rows.map(mapAxSteeringRow).filter((s): s is PmxAxSteeringMessage => s !== null);
2072
2174
  }
2073
2175