@kiberon-labs/behave-graph-flow 1.0.0 → 3.0.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 (378) hide show
  1. package/.fallowrc.json +16 -0
  2. package/.storybook/main.ts +32 -0
  3. package/.storybook/manager.ts +6 -0
  4. package/.storybook/preview.ts +64 -0
  5. package/.storybook/styles.css +16 -0
  6. package/.turbo/turbo-build.log +7 -0
  7. package/CHANGELOG.md +368 -0
  8. package/LICENSE +6 -0
  9. package/README.md +2 -2
  10. package/data/Polynomial.json +510 -0
  11. package/data/sequence.json +337 -0
  12. package/data/trigger-event.json +241 -0
  13. package/data/variable-change.json +210 -0
  14. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  15. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  16. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  17. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  18. package/dist/entry.css +4 -0
  19. package/dist/index.css +42 -0
  20. package/dist/index.css.map +1 -0
  21. package/dist/index.d.ts +3597 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +18009 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/noteImpl-KkrrWgJd.js +242 -0
  26. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  27. package/dist/styles.module-CvmpDkZj.css +3 -0
  28. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  29. package/dist/styles.module-DZxg8aW9.js +271 -0
  30. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  31. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  32. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  33. package/docs/notifications.md +246 -0
  34. package/docs/protocol.md +702 -0
  35. package/docs/specifics.md +191 -0
  36. package/package.json +82 -22
  37. package/postcss.config.ts +3 -4
  38. package/src/annotations/index.ts +32 -0
  39. package/src/components/FloatingToolbar/index.module.css +37 -0
  40. package/src/components/FloatingToolbar/index.tsx +256 -0
  41. package/src/components/Flow.tsx +287 -75
  42. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  43. package/src/components/contextMenus/NodePicker.module.css +274 -0
  44. package/src/components/contextMenus/NodePicker.tsx +481 -0
  45. package/src/components/contextMenus/edge.tsx +22 -0
  46. package/src/components/contextMenus/node.tsx +15 -0
  47. package/src/components/contextMenus/selection.tsx +11 -0
  48. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  49. package/src/components/controls/any/index.tsx +19 -0
  50. package/src/components/controls/boolean/index.tsx +13 -0
  51. package/src/components/controls/colorPicker/InputPopover.module.css +100 -0
  52. package/src/components/controls/colorPicker/InputPopover.tsx +31 -0
  53. package/src/components/controls/colorPicker/index.module.css +18 -0
  54. package/src/components/controls/colorPicker/index.tsx +61 -0
  55. package/src/components/controls/number/index.tsx +35 -0
  56. package/src/components/controls/string/index.tsx +16 -0
  57. package/src/components/edges/index.tsx +475 -0
  58. package/src/components/edges/offsetBezier.ts +134 -0
  59. package/src/components/hotKeys.tsx +20 -0
  60. package/src/components/layoutController/index.module.css +13 -0
  61. package/src/components/layoutController/index.tsx +140 -0
  62. package/src/components/layoutController/utils.ts +248 -0
  63. package/src/components/menubar/defaults.tsx +516 -0
  64. package/src/components/menubar/index.tsx +49 -0
  65. package/src/components/menubar/menuItem.module.css +31 -0
  66. package/src/components/menubar/menuItem.tsx +65 -0
  67. package/src/components/nodes/behave/Node.module.css +23 -0
  68. package/src/components/nodes/behave/Node.tsx +176 -0
  69. package/src/components/nodes/behave/NodeContainer.module.css +88 -0
  70. package/src/components/nodes/behave/NodeContainer.tsx +46 -0
  71. package/src/components/nodes/behave/index.tsx +14 -0
  72. package/src/components/nodes/group/index.tsx +109 -0
  73. package/src/components/nodes/wrapper/index.tsx +73 -0
  74. package/src/components/nodes/wrapper/styles.module.css +87 -0
  75. package/src/components/notifications/NotificationProvider.tsx +81 -0
  76. package/src/components/notifications/index.ts +2 -0
  77. package/src/components/notifications/utils.ts +71 -0
  78. package/src/components/panels/alignment/index.module.css +10 -0
  79. package/src/components/panels/alignment/index.tsx +244 -0
  80. package/src/components/panels/base/index.tsx +5 -0
  81. package/src/components/panels/base/styles.module.css +12 -0
  82. package/src/components/panels/common/PanelHeader.module.css +24 -0
  83. package/src/components/panels/common/PanelHeader.tsx +22 -0
  84. package/src/components/panels/common/SectionTitle.module.css +13 -0
  85. package/src/components/panels/common/SectionTitle.tsx +10 -0
  86. package/src/components/panels/events/EditEventPanel.tsx +324 -0
  87. package/src/components/panels/events/ManageEventsPanel.tsx +101 -0
  88. package/src/components/panels/events/index.tsx +23 -0
  89. package/src/components/panels/events/styles.module.css +178 -0
  90. package/src/components/panels/graphProperties/index.tsx +125 -0
  91. package/src/components/panels/history/index.tsx +92 -0
  92. package/src/components/panels/history/styles.module.css +97 -0
  93. package/src/components/panels/keymaps/index.module.css +68 -0
  94. package/src/components/panels/keymaps/index.tsx +166 -0
  95. package/src/components/panels/layers/index.tsx +245 -0
  96. package/src/components/panels/layers/styles.module.css +107 -0
  97. package/src/components/panels/legend/index.module.css +6 -0
  98. package/src/components/panels/legend/index.tsx +76 -0
  99. package/src/components/panels/logs/index.module.css +218 -0
  100. package/src/components/panels/logs/index.tsx +288 -0
  101. package/src/components/panels/nodeInputs/InputControl.tsx +63 -0
  102. package/src/components/panels/nodeInputs/InputsGroup.tsx +65 -0
  103. package/src/components/panels/nodeInputs/MultipleNodesView.tsx +37 -0
  104. package/src/components/panels/nodeInputs/NodeSettings.tsx +92 -0
  105. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +125 -0
  106. package/src/components/panels/nodeInputs/OutputsGroup.tsx +55 -0
  107. package/src/components/panels/nodeInputs/SocketGenerators.tsx +32 -0
  108. package/src/components/panels/nodeInputs/index.module.css +308 -0
  109. package/src/components/panels/nodeInputs/index.tsx +349 -0
  110. package/src/components/panels/nodeInputs/useNodeHandlers.ts +76 -0
  111. package/src/components/panels/nodeInputs/useNodeInputsData.ts +153 -0
  112. package/src/components/panels/nodePicker/index.tsx +115 -0
  113. package/src/components/panels/panel/index.module.css +66 -0
  114. package/src/components/panels/panel/index.tsx +88 -0
  115. package/src/components/panels/search/index.module.css +16 -0
  116. package/src/components/panels/search/index.tsx +215 -0
  117. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  118. package/src/components/panels/systemSettings/index.tsx +251 -0
  119. package/src/components/panels/systemSettings/styles.module.css +138 -0
  120. package/src/components/panels/traces/GridLines.tsx +38 -0
  121. package/src/components/panels/traces/TimeGrid.tsx +48 -0
  122. package/src/components/panels/traces/TraceLane.tsx +62 -0
  123. package/src/components/panels/traces/TraceTooltip.tsx +22 -0
  124. package/src/components/panels/traces/TracesHeader.tsx +56 -0
  125. package/src/components/panels/traces/index.module.css +159 -0
  126. package/src/components/panels/traces/index.tsx +298 -0
  127. package/src/components/panels/traces/types.ts +48 -0
  128. package/src/components/panels/traces/useDerivedSpans.ts +307 -0
  129. package/src/components/panels/traces/utils.ts +33 -0
  130. package/src/components/panels/variables/CreateVariableScreen.tsx +162 -0
  131. package/src/components/panels/variables/ManageVariablesScreen.tsx +147 -0
  132. package/src/components/panels/variables/index.tsx +125 -0
  133. package/src/components/panels/variables/styles.module.css +149 -0
  134. package/src/components/primitives/icon.module.css +45 -0
  135. package/src/components/primitives/icon.tsx +38 -0
  136. package/src/components/sockets/input/index.tsx +83 -0
  137. package/src/components/sockets/input/styles.module.css +26 -0
  138. package/src/components/sockets/output/index.tsx +68 -0
  139. package/src/components/sockets/output/styles.module.css +22 -0
  140. package/src/css/notes.css +135 -0
  141. package/src/css/prosemirror.css +57 -0
  142. package/src/css/rc-dock.css +212 -0
  143. package/src/css/rc-menu.css +101 -0
  144. package/src/css/themes/kiberon.css +127 -0
  145. package/src/css/vars.css +198 -0
  146. package/src/css/vscode-elements.css +124 -0
  147. package/src/entry.css +4 -0
  148. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  149. package/src/generators/CustomEventOnTriggeredGenerator.tsx +85 -0
  150. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  151. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  152. package/src/generators/SequenceGenerator.tsx +104 -0
  153. package/src/generators/SwitchOnIntegerGenerator.tsx +256 -0
  154. package/src/generators/SwitchOnStringGenerator.tsx +263 -0
  155. package/src/generators/callSubgraphSync.ts +126 -0
  156. package/src/generators/registerDefaultGenerators.ts +55 -0
  157. package/src/generators/registerDefaults.ts +26 -0
  158. package/src/hooks/useBehaveGraphFlow.ts +17 -16
  159. package/src/hooks/useFlowHandlers.ts +154 -30
  160. package/src/hooks/useWasdPan.ts +210 -0
  161. package/src/index.css +134 -0
  162. package/src/index.ts +53 -18
  163. package/src/manifest/contributionRegistry.ts +93 -0
  164. package/src/manifest/index.ts +4 -0
  165. package/src/manifest/loadManifest.ts +82 -0
  166. package/src/manifest/manifestPlugin.ts +29 -0
  167. package/src/manifest/passthroughValueType.ts +40 -0
  168. package/src/plugin/alignment/index.ts +91 -0
  169. package/src/plugin/autosave/controller.ts +366 -0
  170. package/src/plugin/autosave/index.tsx +114 -0
  171. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  172. package/src/plugin/autosave/panel/index.tsx +1 -0
  173. package/src/plugin/autosave/panel/styles.module.css +56 -0
  174. package/src/plugin/autosave/settings.ts +65 -0
  175. package/src/plugin/autosave/storage.ts +147 -0
  176. package/src/plugin/docs/index.tsx +297 -0
  177. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  178. package/src/plugin/docs/panel/index.tsx +21 -0
  179. package/src/plugin/docs/panel/styles.module.css +174 -0
  180. package/src/plugin/graphrunner/actions.ts +326 -0
  181. package/src/plugin/graphrunner/buttons.tsx +95 -0
  182. package/src/plugin/graphrunner/client.ts +707 -0
  183. package/src/plugin/graphrunner/index.tsx +184 -0
  184. package/src/plugin/graphrunner/panel.tsx +386 -0
  185. package/src/plugin/graphrunner/runController.ts +283 -0
  186. package/src/plugin/graphrunner/runner.ts +187 -0
  187. package/src/plugin/graphrunner/session.ts +243 -0
  188. package/src/plugin/graphrunner/store.ts +196 -0
  189. package/src/plugin/graphrunner/styles.module.css +171 -0
  190. package/src/plugin/graphrunner/transport.ts +250 -0
  191. package/src/plugin/graphrunner/types.ts +693 -0
  192. package/src/plugin/graphrunner-local/execution-utils.ts +637 -0
  193. package/src/plugin/graphrunner-local/index.tsx +172 -0
  194. package/src/plugin/graphrunner-local/panel.tsx +187 -0
  195. package/src/plugin/graphrunner-local/store.ts +41 -0
  196. package/src/plugin/graphrunner-local/styles.module.css +82 -0
  197. package/src/plugin/graphrunner-local/transport.ts +1339 -0
  198. package/src/plugin/graphrunner-local/types.ts +10 -0
  199. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +635 -0
  200. package/src/plugin/graphrunner-webworker/index.tsx +140 -0
  201. package/src/plugin/graphrunner-webworker/panel.tsx +173 -0
  202. package/src/plugin/graphrunner-webworker/store.ts +98 -0
  203. package/src/plugin/graphrunner-webworker/worker-transport.ts +123 -0
  204. package/src/plugin/kitchen-sink/index.ts +38 -0
  205. package/src/plugin/layout/dagre.ts +131 -0
  206. package/src/plugin/layout/elk.ts +216 -0
  207. package/src/plugin/layout/index.ts +80 -0
  208. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  209. package/src/plugin/notes/index.tsx +191 -0
  210. package/src/plugin/notes/nodeActions.ts +100 -0
  211. package/src/plugin/notes/note.tsx +20 -0
  212. package/src/plugin/notes/noteImpl.tsx +89 -0
  213. package/src/plugin/realtime/realtimeRunner.ts +624 -0
  214. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +92 -0
  215. package/src/specifics/CustomEventTriggerSpecific.tsx +141 -0
  216. package/src/specifics/VariableGetSpecific.tsx +110 -0
  217. package/src/specifics/VariableSetSpecific.tsx +110 -0
  218. package/src/store/actions.tsx +698 -0
  219. package/src/store/commands.ts +278 -0
  220. package/src/store/contextMenu.ts +192 -0
  221. package/src/store/controls.tsx +62 -0
  222. package/src/store/conversions.ts +47 -0
  223. package/src/store/documentation.tsx +69 -0
  224. package/src/store/events.tsx +116 -0
  225. package/src/store/flow.tsx +230 -0
  226. package/src/store/graphMeta.ts +39 -0
  227. package/src/store/hotKeys.tsx +364 -0
  228. package/src/store/layers.ts +259 -0
  229. package/src/store/legend.tsx +76 -0
  230. package/src/store/logs.ts +28 -0
  231. package/src/store/menubar.ts +41 -0
  232. package/src/store/refs.ts +84 -0
  233. package/src/store/registry.ts +51 -0
  234. package/src/store/selection.ts +22 -0
  235. package/src/store/settings.ts +99 -0
  236. package/src/store/settingsSchema.ts +210 -0
  237. package/src/store/socketGenerator.tsx +54 -0
  238. package/src/store/specific.tsx +75 -0
  239. package/src/store/specs.tsx +35 -0
  240. package/src/store/tabs.ts +282 -0
  241. package/src/store/toolbar.tsx +45 -0
  242. package/src/store/traces.ts +240 -0
  243. package/src/store/variables.ts +37 -0
  244. package/src/system/graph.ts +131 -0
  245. package/src/system/graphSession.ts +172 -0
  246. package/src/system/index.ts +6 -0
  247. package/src/system/notifications.ts +111 -0
  248. package/src/system/persistence.ts +82 -0
  249. package/src/system/plugin.ts +55 -0
  250. package/src/system/provider.tsx +86 -0
  251. package/src/system/pubsub.ts +323 -0
  252. package/src/system/system.ts +653 -0
  253. package/src/system/tabLoader.tsx +303 -0
  254. package/src/system/undoRedo.ts +103 -0
  255. package/src/transformers/Uigraph.ts +61 -0
  256. package/src/transformers/behaveToFlow.ts +16 -4
  257. package/src/transformers/contract.ts +87 -0
  258. package/src/transformers/flowToBehave.ts +40 -12
  259. package/src/types/NodeMetadata.ts +27 -0
  260. package/src/types/graph.ts +49 -0
  261. package/src/types/nodes.ts +50 -0
  262. package/src/types.ts +18 -0
  263. package/src/util/autoConvert.ts +200 -0
  264. package/src/util/colors.ts +1 -29
  265. package/src/util/downloadJson.ts +18 -0
  266. package/src/util/extractNodeMetadata.ts +16 -0
  267. package/src/util/getPickerFilters.ts +1 -1
  268. package/src/util/isBehaveNode.ts +6 -0
  269. package/src/util/isValidConnection.ts +51 -17
  270. package/src/util/mergeSockets.ts +29 -0
  271. package/src/util/serializeVariables.ts +66 -0
  272. package/src/util/sockets.ts +43 -0
  273. package/stories/apex/layoutController/example-graph.worker.ts +39 -0
  274. package/stories/apex/layoutController/index.stories.tsx +48 -0
  275. package/stories/apex/layoutController/webworker.stories.tsx +103 -0
  276. package/stories/apex/menubar/menubar.stories.tsx +19 -0
  277. package/stories/components/colorpicker/index.stories.tsx +20 -0
  278. package/stories/components/contextMenus/edge.stories.tsx +32 -0
  279. package/stories/components/contextMenus/node.stories.tsx +26 -0
  280. package/stories/components/contextMenus/nodePicker.stories.tsx +115 -0
  281. package/stories/components/controls/any/index.stories.tsx +19 -0
  282. package/stories/components/controls/boolean/index.stories.tsx +19 -0
  283. package/stories/components/controls/colorPicker/index.stories.tsx +49 -0
  284. package/stories/components/controls/number/index.stories.tsx +19 -0
  285. package/stories/components/controls/string/index.stories.tsx +19 -0
  286. package/stories/components/nodes/behaveNode.stories.tsx +108 -0
  287. package/stories/components/panels/alignment.stories.tsx +24 -0
  288. package/stories/components/panels/events.stories.tsx +38 -0
  289. package/stories/components/panels/graphRunner.stories.tsx +317 -0
  290. package/stories/components/panels/history.stories.tsx +37 -0
  291. package/stories/components/panels/keymaps.stories.tsx +21 -0
  292. package/stories/components/panels/legend.stories.tsx +37 -0
  293. package/stories/components/panels/logs.stories.tsx +24 -0
  294. package/stories/components/panels/nodeInputs.stories.tsx +21 -0
  295. package/stories/components/panels/nodePicker.stories.tsx +37 -0
  296. package/stories/components/panels/panel.stories.tsx +39 -0
  297. package/stories/components/panels/search.stories.tsx +24 -0
  298. package/stories/components/panels/systemSettings.stories.tsx +26 -0
  299. package/stories/components/panels/traces.stories.tsx +225 -0
  300. package/stories/components/panels/variables.stories.tsx +24 -0
  301. package/stories/defaults/defaultStoryProvider.tsx +170 -0
  302. package/stories/defaults/systemGenerator.ts +43 -0
  303. package/stories/plugins/notes.stories.tsx +100 -0
  304. package/tests/autoConvert.test.ts +329 -0
  305. package/tests/autosavePlugin.test.ts +204 -0
  306. package/tests/callSubgraphSync.test.ts +148 -0
  307. package/tests/commandRegistry.test.ts +137 -0
  308. package/tests/components/edges/offsetBezier.test.ts +51 -0
  309. package/tests/components/layoutController/utils.test.ts +68 -0
  310. package/tests/components/panels/traces/utils.test.ts +52 -0
  311. package/tests/contract.test.ts +51 -0
  312. package/tests/contractSerialize.test.ts +62 -0
  313. package/tests/deriveSpans.test.ts +71 -0
  314. package/tests/flowToBehave.test.ts +27 -4
  315. package/tests/hotkeys.test.ts +79 -0
  316. package/tests/keepAliveLifecycle.test.ts +167 -0
  317. package/tests/loadManifest.test.ts +113 -0
  318. package/tests/noteMarkdown.test.ts +65 -0
  319. package/tests/notesPlugin.test.ts +162 -0
  320. package/tests/notifications.test.ts +87 -0
  321. package/tests/persistence.test.ts +51 -0
  322. package/tests/saveLoad.test.ts +373 -0
  323. package/tests/settings.test.ts +178 -0
  324. package/tests/traceStore.test.ts +46 -0
  325. package/tests/util/calculateNewEdge.test.ts +98 -0
  326. package/tests/util/getSocketsByNodeTypeAndHandleType.test.ts +31 -0
  327. package/tests/util/hasPositionMetaData.test.ts +33 -0
  328. package/tests/util/isBehaveNode.test.ts +22 -0
  329. package/tests/util/isHandleConnected.test.ts +37 -0
  330. package/tests/util/mergeSockets.test.ts +43 -0
  331. package/tests/visual/README.md +64 -0
  332. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-alignment-chromium-win32.png +0 -0
  333. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  334. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  335. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  336. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  337. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  338. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  339. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  340. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  341. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  342. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  343. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  344. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  345. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  346. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-traces-chromium-win32.png +0 -0
  347. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  348. package/tests/visual/panels.visual.test.tsx +76 -0
  349. package/tests/wasdPan.test.ts +71 -0
  350. package/tsconfig.base.json +39 -0
  351. package/tsconfig.json +18 -59
  352. package/tsconfig.prod.json +23 -0
  353. package/tsdown.config.ts +15 -3
  354. package/typedoc.json +7 -7
  355. package/vite.config.js +7 -0
  356. package/vitest.config.ts +5 -2
  357. package/vitest.visual.config.ts +55 -0
  358. package/src/components/AutoSizeInput.tsx +0 -65
  359. package/src/components/Controls.tsx +0 -87
  360. package/src/components/InputSocket.tsx +0 -142
  361. package/src/components/Node.tsx +0 -68
  362. package/src/components/NodeContainer.tsx +0 -46
  363. package/src/components/NodePicker.tsx +0 -77
  364. package/src/components/OutputSocket.tsx +0 -58
  365. package/src/components/modals/ClearModal.tsx +0 -40
  366. package/src/components/modals/HelpModal.tsx +0 -36
  367. package/src/components/modals/LoadModal.tsx +0 -96
  368. package/src/components/modals/Modal.tsx +0 -64
  369. package/src/components/modals/SaveModal.tsx +0 -60
  370. package/src/hooks/useCustomNodeTypes.tsx +0 -31
  371. package/src/hooks/useGraphRunner.ts +0 -104
  372. package/src/hooks/useMergeMap.ts +0 -14
  373. package/src/hooks/useNodeSpecJson.ts +0 -20
  374. package/src/hooks/useQueriableDefinitions.ts +0 -22
  375. package/src/styles.css +0 -8
  376. package/tailwind.config.ts +0 -19
  377. package/tests/tsconfig.json +0 -10
  378. /package/src/{types.d.ts → types-declarations.d.ts} +0 -0
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { Node } from 'reactflow';
3
+ import {
4
+ registerCoreProfile,
5
+ writeNodeSpecsToJSON
6
+ } from '@kiberon-labs/behave-graph';
7
+ import { System } from '../src/system/system.js';
8
+ import {
9
+ commandStoreFactory,
10
+ type CommandContext
11
+ } from '../src/store/commands.js';
12
+ import {
13
+ contextMenuStoreFactory,
14
+ type ContextMenuItem
15
+ } from '../src/store/contextMenu.js';
16
+
17
+ const buildSystem = () => {
18
+ const reg = registerCoreProfile({
19
+ nodes: {},
20
+ values: {},
21
+ dependencies: {} as any
22
+ });
23
+ return new System({ values: reg.values, specs: writeNodeSpecsToJSON(reg) });
24
+ };
25
+
26
+ const node = (id: string): Node => ({
27
+ id,
28
+ type: 'behaveNode',
29
+ position: { x: 0, y: 0 },
30
+ data: { type: 'debug/log', configuration: {}, ports: {} } as any
31
+ });
32
+
33
+ // The command/context-menu registries are framework-free; a stub context is fine.
34
+ const ctx = {} as CommandContext;
35
+
36
+ describe('command registry', () => {
37
+ it('registers, runs, and unregisters by id', () => {
38
+ const store = commandStoreFactory().getState();
39
+ let ran = 0;
40
+ const off = store.register({ id: 'x.do', run: () => void ran++ });
41
+
42
+ expect(store.get('x.do')?.id).toBe('x.do');
43
+ store.run('x.do', ctx);
44
+ expect(ran).toBe(1);
45
+
46
+ off();
47
+ expect(store.get('x.do')).toBeUndefined();
48
+ store.run('x.do', ctx); // no-op, no throw
49
+ expect(ran).toBe(1);
50
+ });
51
+
52
+ it('register replaces an existing id (idempotent override)', () => {
53
+ const store = commandStoreFactory().getState();
54
+ store.register({ id: 'x', run: () => {} });
55
+ store.register({ id: 'x', title: 'Second', run: () => {} });
56
+ expect(store.list()).toHaveLength(1);
57
+ expect(store.get('x')?.title).toBe('Second');
58
+ });
59
+
60
+ it('skips a disabled command', () => {
61
+ const store = commandStoreFactory().getState();
62
+ let ran = false;
63
+ store.register({
64
+ id: 'x',
65
+ isEnabled: () => false,
66
+ run: () => {
67
+ ran = true;
68
+ }
69
+ });
70
+ store.run('x', ctx);
71
+ expect(ran).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe('context-menu registry', () => {
76
+ const item = (over: Partial<ContextMenuItem>): ContextMenuItem => ({
77
+ id: 'i',
78
+ target: 'node',
79
+ label: 'L',
80
+ ...over
81
+ });
82
+
83
+ it('returns items for a target sorted by order', () => {
84
+ const store = contextMenuStoreFactory().getState();
85
+ store.register(item({ id: 'b', order: 20 }));
86
+ store.register(item({ id: 'a', order: 10 }));
87
+ store.register(item({ id: 'e', target: 'edge', order: 5 }));
88
+
89
+ const nodeIds = store.getItems('node').map((i) => i.id);
90
+ expect(nodeIds).toEqual(['a', 'b']); // edge item excluded, sorted
91
+ });
92
+
93
+ it('register replaces by id; unregister removes', () => {
94
+ const store = contextMenuStoreFactory().getState();
95
+ store.register(item({ id: 'a', label: 'One' }));
96
+ store.register(item({ id: 'a', label: 'Two' }));
97
+ expect(store.getItems('node')).toHaveLength(1);
98
+ expect(store.getItems('node')[0]!.label).toBe('Two');
99
+
100
+ store.unregister('a');
101
+ expect(store.getItems('node')).toHaveLength(0);
102
+ });
103
+ });
104
+
105
+ describe('System.runCommand (hotkeys/menubar dispatch path)', () => {
106
+ it('dispatches a default command against the focused session', () => {
107
+ const system = buildSystem();
108
+ const session = system.createSession('g'); // activates by default
109
+ session.nodeStore.getState().setNodes(() => [node('n1'), node('n2')]);
110
+
111
+ system.runCommand('selection.selectAll');
112
+
113
+ expect(session.nodeStore.getState().nodes.every((n) => n.selected)).toBe(
114
+ true
115
+ );
116
+ });
117
+
118
+ it('no-ops when there is no focused graph', () => {
119
+ const system = buildSystem();
120
+ expect(() => system.runCommand('selection.selectAll')).not.toThrow();
121
+ });
122
+
123
+ it('threads target context (e.g. nodeId) into the command', () => {
124
+ const system = buildSystem();
125
+ system.createSession('g');
126
+ let captured: string | undefined;
127
+ system.commandStore.getState().register({
128
+ id: 'probe',
129
+ run: (ctx) => {
130
+ captured = ctx.nodeId;
131
+ }
132
+ });
133
+
134
+ system.runCommand('probe', { nodeId: 'abc' });
135
+ expect(captured).toBe('abc');
136
+ });
137
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Position } from 'reactflow';
3
+ import { getBetterBezierPath } from '@/components/edges/offsetBezier';
4
+
5
+ describe('getBetterBezierPath', () => {
6
+ it('returns a bezier path string plus label/offset coordinates', () => {
7
+ const result = getBetterBezierPath({
8
+ sourceX: 0,
9
+ sourceY: 0,
10
+ targetX: 0,
11
+ targetY: 200
12
+ });
13
+
14
+ expect(result).toHaveLength(5);
15
+ const [path, labelX, labelY] = result;
16
+ expect(typeof path).toBe('string');
17
+ expect(path.startsWith('M0,0 C')).toBe(true);
18
+ expect(typeof labelX).toBe('number');
19
+ expect(typeof labelY).toBe('number');
20
+ });
21
+
22
+ it('offsets the control points vertically for bottom->top connections', () => {
23
+ const [path] = getBetterBezierPath({
24
+ sourceX: 0,
25
+ sourceY: 0,
26
+ sourcePosition: Position.Bottom,
27
+ targetX: 0,
28
+ targetY: 200,
29
+ targetPosition: Position.Top,
30
+ offset: 75
31
+ });
32
+
33
+ // source control = (0, 0 + 75), target control = (0, 200 - 75)
34
+ expect(path).toBe('M0,0 C0,75 0,125 0,200');
35
+ });
36
+
37
+ it('offsets the control points horizontally for left/right connections', () => {
38
+ const [path] = getBetterBezierPath({
39
+ sourceX: 0,
40
+ sourceY: 0,
41
+ sourcePosition: Position.Right,
42
+ targetX: 200,
43
+ targetY: 0,
44
+ targetPosition: Position.Left,
45
+ offset: 50
46
+ });
47
+
48
+ // source control = (0 + 50, 0), target control = (200 - 50, 0)
49
+ expect(path).toBe('M0,0 C50,0 150,0 200,0');
50
+ });
51
+ });
@@ -0,0 +1,68 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { LayoutBase } from 'rc-dock';
3
+ import {
4
+ findGraphPanel,
5
+ findTabInLayout,
6
+ removeTabFromLayout
7
+ } from '@/components/layoutController/utils';
8
+
9
+ const makeLayout = (): LayoutBase =>
10
+ ({
11
+ dockbox: {
12
+ id: 'root',
13
+ mode: 'horizontal',
14
+ children: [
15
+ {
16
+ id: 'graphs',
17
+ tabs: [{ id: 'tab-a' }, { id: 'tab-b' }]
18
+ },
19
+ {
20
+ id: 'sidebar',
21
+ tabs: [{ id: 'tab-c' }]
22
+ }
23
+ ]
24
+ }
25
+ }) as unknown as LayoutBase;
26
+
27
+ describe('layoutController/utils', () => {
28
+ describe('findGraphPanel', () => {
29
+ it('locates the panel whose id is "graphs"', () => {
30
+ const panel = findGraphPanel(makeLayout());
31
+ expect(panel?.id).toBe('graphs');
32
+ });
33
+
34
+ it('returns null when no graph panel exists', () => {
35
+ const layout = {
36
+ dockbox: { id: 'root', children: [{ id: 'other', tabs: [] }] }
37
+ } as unknown as LayoutBase;
38
+ expect(findGraphPanel(layout)).toBeNull();
39
+ });
40
+ });
41
+
42
+ describe('findTabInLayout', () => {
43
+ it('finds the panel that owns a given tab', () => {
44
+ expect(findTabInLayout(makeLayout(), 'tab-b')?.id).toBe('graphs');
45
+ expect(findTabInLayout(makeLayout(), 'tab-c')?.id).toBe('sidebar');
46
+ });
47
+
48
+ it('returns null for an unknown tab', () => {
49
+ expect(findTabInLayout(makeLayout(), 'missing')).toBeNull();
50
+ });
51
+ });
52
+
53
+ describe('removeTabFromLayout', () => {
54
+ it('removes a tab while keeping the panel that still has tabs', () => {
55
+ const updated = removeTabFromLayout(makeLayout(), 'tab-a');
56
+ const graphsPanel = findTabInLayout(updated, 'tab-b');
57
+ expect(graphsPanel?.id).toBe('graphs');
58
+ expect(findTabInLayout(updated, 'tab-a')).toBeNull();
59
+ });
60
+
61
+ it('does not mutate the original layout', () => {
62
+ const layout = makeLayout();
63
+ removeTabFromLayout(layout, 'tab-a');
64
+ // original still has tab-a
65
+ expect(findTabInLayout(layout, 'tab-a')?.id).toBe('graphs');
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ calculateTimeInterval,
4
+ clamp,
5
+ hashToHue
6
+ } from '@/components/panels/traces/utils';
7
+
8
+ describe('traces/utils', () => {
9
+ describe('hashToHue', () => {
10
+ it('is deterministic for the same input', () => {
11
+ expect(hashToHue('node-a')).toBe(hashToHue('node-a'));
12
+ });
13
+
14
+ it('always returns a hue in the [0, 360) range', () => {
15
+ for (const s of ['', 'a', 'flow/branch', 'a-very-long-identifier-123']) {
16
+ const hue = hashToHue(s);
17
+ expect(hue).toBeGreaterThanOrEqual(0);
18
+ expect(hue).toBeLessThan(360);
19
+ expect(Number.isInteger(hue)).toBe(true);
20
+ }
21
+ });
22
+
23
+ it('generally produces different hues for different inputs', () => {
24
+ expect(hashToHue('alpha')).not.toBe(hashToHue('beta'));
25
+ });
26
+ });
27
+
28
+ describe('clamp', () => {
29
+ it('returns the value when within range', () => {
30
+ expect(clamp(5, 0, 10)).toBe(5);
31
+ });
32
+ it('clamps below the minimum', () => {
33
+ expect(clamp(-3, 0, 10)).toBe(0);
34
+ });
35
+ it('clamps above the maximum', () => {
36
+ expect(clamp(42, 0, 10)).toBe(10);
37
+ });
38
+ });
39
+
40
+ describe('calculateTimeInterval', () => {
41
+ it('picks a "nice" interval roughly targeting 8 ticks', () => {
42
+ expect(calculateTimeInterval(8)).toBe(1);
43
+ expect(calculateTimeInterval(80)).toBe(10);
44
+ expect(calculateTimeInterval(800)).toBe(100);
45
+ expect(calculateTimeInterval(8000)).toBe(1000);
46
+ });
47
+
48
+ it('caps at the largest nice interval for very large ranges', () => {
49
+ expect(calculateTimeInterval(10_000_000)).toBe(10000);
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { Node } from 'reactflow';
3
+ import { deriveContract } from '../src/transformers/contract.js';
4
+
5
+ const boundaryNode = (
6
+ id: string,
7
+ type: 'graph/input' | 'graph/output',
8
+ parameters: Array<{ name: string; valueTypeName: string; defaultValue?: any }>
9
+ ): Node => ({
10
+ id,
11
+ type: 'behaveNode',
12
+ position: { x: 0, y: 0 },
13
+ data: { type, configuration: { parameters }, ports: {} }
14
+ });
15
+
16
+ describe('graph contract derivation', () => {
17
+ it('derives graphInputs/graphOutputs from boundary nodes', () => {
18
+ const nodes: Node[] = [
19
+ boundaryNode('in', 'graph/input', [
20
+ { name: 'x', valueTypeName: 'float', defaultValue: 1 },
21
+ { name: 'label', valueTypeName: 'string' }
22
+ ]),
23
+ boundaryNode('out', 'graph/output', [
24
+ { name: 'y', valueTypeName: 'float' }
25
+ ]),
26
+ // a non-boundary node should be ignored
27
+ {
28
+ id: 'n',
29
+ type: 'behaveNode',
30
+ position: { x: 0, y: 0 },
31
+ data: { type: 'debug/log', configuration: {}, ports: {} }
32
+ }
33
+ ];
34
+
35
+ const { graphInputs, graphOutputs } = deriveContract(nodes);
36
+
37
+ expect(graphInputs).toEqual([
38
+ { key: 'x', valueType: 'float', defaultValue: 1, label: 'x' },
39
+ { key: 'label', valueType: 'string', label: 'label' }
40
+ ]);
41
+ expect(graphOutputs).toEqual([
42
+ { key: 'y', valueType: 'float', label: 'y' }
43
+ ]);
44
+ });
45
+
46
+ it('returns empty contract when there are no boundary nodes', () => {
47
+ const { graphInputs, graphOutputs } = deriveContract([]);
48
+ expect(graphInputs).toEqual([]);
49
+ expect(graphOutputs).toEqual([]);
50
+ });
51
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { Node } from 'reactflow';
3
+ import {
4
+ registerCoreProfile,
5
+ writeNodeSpecsToJSON
6
+ } from '@kiberon-labs/behave-graph';
7
+ import { System } from '../src/system/system.js';
8
+ import { flowToBehave } from '../src/transformers/flowToBehave.js';
9
+
10
+ const boundary = (
11
+ id: string,
12
+ type: 'graph/input' | 'graph/output',
13
+ params: Array<{ name: string; valueTypeName: string }>
14
+ ): Node => ({
15
+ id,
16
+ type: 'behaveNode',
17
+ position: { x: 0, y: 0 },
18
+ data: { type, configuration: { parameters: params }, ports: {} }
19
+ });
20
+
21
+ describe('contract serialization through flowToBehave', () => {
22
+ it('emits graphInputs/graphOutputs derived from boundary nodes', () => {
23
+ const coreRegistry = registerCoreProfile({
24
+ nodes: {},
25
+ values: {},
26
+ dependencies: {} as any
27
+ });
28
+ const specs = writeNodeSpecsToJSON(coreRegistry);
29
+ const system = new System({ values: coreRegistry.values, specs });
30
+ const session = system.createSession('graph');
31
+
32
+ const nodes: Node[] = [
33
+ boundary('in', 'graph/input', [{ name: 'x', valueTypeName: 'float' }]),
34
+ boundary('out', 'graph/output', [{ name: 'y', valueTypeName: 'float' }])
35
+ ];
36
+
37
+ const graph = flowToBehave(session, nodes, [], specs);
38
+
39
+ expect(graph.graphInputs).toEqual([
40
+ { key: 'x', valueType: 'float', label: 'x' }
41
+ ]);
42
+ expect(graph.graphOutputs).toEqual([
43
+ { key: 'y', valueType: 'float', label: 'y' }
44
+ ]);
45
+ });
46
+
47
+ it('omits the contract when there are no boundary nodes', () => {
48
+ const coreRegistry = registerCoreProfile({
49
+ nodes: {},
50
+ values: {},
51
+ dependencies: {} as any
52
+ });
53
+ const specs = writeNodeSpecsToJSON(coreRegistry);
54
+ const system = new System({ values: coreRegistry.values, specs });
55
+ const session = system.createSession('graph');
56
+
57
+ const graph = flowToBehave(session, [], [], specs);
58
+
59
+ expect(graph.graphInputs).toBeUndefined();
60
+ expect(graph.graphOutputs).toBeUndefined();
61
+ });
62
+ });
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import type { SpanCollector, TraceSpan } from '../src/store/traces.js';
3
+ import { computeDerivedSpans } from '../src/components/panels/traces/useDerivedSpans.js';
4
+ import type { ViewState } from '../src/components/panels/traces/types.js';
5
+
6
+ const FOLLOW: ViewState = { start: 0, range: 5000, follow: true };
7
+
8
+ /** Minimal collector holding the given spans (lane defaults to 0). */
9
+ const collectorOf = (spans: TraceSpan[]): SpanCollector => ({
10
+ capacity: 16,
11
+ spans,
12
+ writeIndex: spans.length,
13
+ size: spans.length,
14
+ nextId: spans.length + 1,
15
+ openByNodeId: new Map(),
16
+ laneOpen: []
17
+ });
18
+
19
+ const span = (over: Partial<TraceSpan>): TraceSpan => ({
20
+ id: 1,
21
+ nodeId: 'n',
22
+ name: 'n',
23
+ start: 0,
24
+ end: 0,
25
+ lane: 0,
26
+ ...over
27
+ });
28
+
29
+ describe('computeDerivedSpans , instant span visibility', () => {
30
+ it('renders an instant (start === end) span instead of collapsing to nothing', () => {
31
+ // A node that executes in 0ms: start === end. Previously maxEnd <= minStart
32
+ // made the derivation bail and render no lanes.
33
+ const derived = computeDerivedSpans(
34
+ collectorOf([span({ start: 0, end: 0 })]),
35
+ 5000,
36
+ FOLLOW
37
+ );
38
+
39
+ expect(derived.size).toBe(1);
40
+ expect(derived.laneData).toHaveLength(1);
41
+ const visuals = derived.laneData[0]!.visualSpans;
42
+ expect(visuals).toHaveLength(1);
43
+ // It has real, clickable width…
44
+ expect(visuals[0]!.widthPct).toBeGreaterThan(0);
45
+ // …but its reported duration is still the true 0ms.
46
+ expect(visuals[0]!.durationMs).toBe(0);
47
+ });
48
+
49
+ it('reports the true duration for a normal span and keeps it visible', () => {
50
+ const derived = computeDerivedSpans(
51
+ collectorOf([span({ start: 2, end: 7 })]),
52
+ 5000,
53
+ FOLLOW
54
+ );
55
+ const v = derived.laneData[0]!.visualSpans[0]!;
56
+ expect(v.durationMs).toBe(5);
57
+ expect(v.widthPct).toBeGreaterThan(0);
58
+ });
59
+
60
+ it('labels ticks relative to the first span, not the raw clock', () => {
61
+ // Start at a large absolute time (as performance.now() produces); the first
62
+ // tick label should be relative (~0), not the absolute value.
63
+ const derived = computeDerivedSpans(
64
+ collectorOf([span({ start: 1_000_000, end: 1_000_010 })]),
65
+ 0, // window 0 ⇒ fit all
66
+ FOLLOW
67
+ );
68
+ expect(derived.ticks.length).toBeGreaterThan(0);
69
+ expect(derived.ticks[0]!.time).toBeLessThan(1000);
70
+ });
71
+ });
@@ -8,18 +8,41 @@ import rawFlowGraph from '../../../graphs/react-flow/graph.json';
8
8
  import { behaveToFlow } from '../src/transformers/behaveToFlow.js';
9
9
  import { flowToBehave } from '../src/transformers/flowToBehave.js';
10
10
  import { it, expect } from 'vitest';
11
+ import { System } from '@/system/system';
11
12
 
12
13
  const flowGraph = rawFlowGraph as GraphJSON;
13
14
 
14
15
  const [nodes, edges] = behaveToFlow(flowGraph);
15
16
 
16
17
  it('transforms from flow to behave', () => {
17
- const registry = registerCoreProfile({
18
+ const coreRegistry = registerCoreProfile({
18
19
  nodes: {},
19
20
  values: {},
20
21
  dependencies: {}
21
22
  });
22
- const specJSON = writeNodeSpecsToJSON(registry);
23
- const output = flowToBehave(nodes, edges, specJSON);
24
- expect(output).toEqual(flowGraph);
23
+ const specJSON = writeNodeSpecsToJSON(coreRegistry);
24
+ const registry = {
25
+ values: coreRegistry.values,
26
+ specs: specJSON
27
+ };
28
+ const system = new System(registry);
29
+ const session = system.createSession('graph');
30
+ const output = flowToBehave(session, nodes, edges, specJSON);
31
+
32
+ // Remove position metadata from expected graph since we no longer include it
33
+ const expectedGraph = {
34
+ ...flowGraph,
35
+ nodes: flowGraph.nodes.map((node) => {
36
+ const { metadata, ...rest } = node as any;
37
+ if (metadata) {
38
+ const { positionX, positionY, ...remainingMetadata } = metadata;
39
+ return Object.keys(remainingMetadata).length > 0
40
+ ? { ...rest, metadata: remainingMetadata }
41
+ : rest;
42
+ }
43
+ return node;
44
+ })
45
+ };
46
+
47
+ expect(output).toEqual(expectedGraph);
25
48
  });
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ registerCoreProfile,
4
+ writeNodeSpecsToJSON
5
+ } from '@kiberon-labs/behave-graph';
6
+ import { System } from '../src/system/system.js';
7
+ import { formatTrigger } from '../src/store/hotKeys.js';
8
+
9
+ const buildSystem = () => {
10
+ const reg = registerCoreProfile({
11
+ nodes: {},
12
+ values: {},
13
+ dependencies: {} as any
14
+ });
15
+ return new System({ values: reg.values, specs: writeNodeSpecsToJSON(reg) });
16
+ };
17
+
18
+ describe('formatTrigger', () => {
19
+ it('capitalizes modifiers and single keys', () => {
20
+ expect(formatTrigger('ctrl+s')).toBe('Ctrl+S');
21
+ expect(formatTrigger('shift+alt+f')).toBe('Shift+Alt+F');
22
+ });
23
+
24
+ it('maps arrow keys to glyphs', () => {
25
+ expect(formatTrigger('ctrl+shift+left')).toBe('Ctrl+Shift+←');
26
+ expect(formatTrigger('ctrl+shift+right')).toBe('Ctrl+Shift+→');
27
+ });
28
+
29
+ it('prefers the ctrl variant when several triggers are bound', () => {
30
+ expect(formatTrigger(['command+c', 'ctrl+c'])).toBe('Ctrl+C');
31
+ });
32
+
33
+ it('returns undefined for empty/unset triggers', () => {
34
+ expect(formatTrigger(undefined)).toBeUndefined();
35
+ expect(formatTrigger('')).toBeUndefined();
36
+ expect(formatTrigger([])).toBeUndefined();
37
+ });
38
+ });
39
+
40
+ describe('getCommandKeybinding', () => {
41
+ it('auto-detects the shortcut for a command-backed binding', () => {
42
+ const sys = buildSystem();
43
+ const store = sys.hotKeyStore.getState();
44
+ expect(store.getCommandKeybinding('selection.copy')).toBe('Ctrl+C');
45
+ expect(store.getCommandKeybinding('editor.save')).toBe('Ctrl+S');
46
+ });
47
+
48
+ it('resolves handler-only bindings via hintCommand', () => {
49
+ const sys = buildSystem();
50
+ const store = sys.hotKeyStore.getState();
51
+ expect(store.getCommandKeybinding('node.traceUpstream')).toBe(
52
+ 'Ctrl+Shift+←'
53
+ );
54
+ });
55
+
56
+ it('returns undefined for a command with no bound key', () => {
57
+ const sys = buildSystem();
58
+ const store = sys.hotKeyStore.getState();
59
+ expect(store.getCommandKeybinding('node.focus')).toBeUndefined();
60
+ expect(store.getCommandKeybinding('does.not.exist')).toBeUndefined();
61
+ });
62
+
63
+ it('reflects a rebind live, and picks up runtime-registered commands', () => {
64
+ const sys = buildSystem();
65
+ const store = sys.hotKeyStore.getState();
66
+
67
+ // Rebinding the action updates the derived hint.
68
+ store.register({ action: 'SAVE', trigger: 'ctrl+k' });
69
+ expect(sys.hotKeyStore.getState().getCommandKeybinding('editor.save')).toBe(
70
+ 'Ctrl+K'
71
+ );
72
+
73
+ // A runtime binding that names its command becomes resolvable.
74
+ store.register({ action: 'RUN', trigger: 'p', command: 'graph.run' });
75
+ expect(sys.hotKeyStore.getState().getCommandKeybinding('graph.run')).toBe(
76
+ 'P'
77
+ );
78
+ });
79
+ });