@kiberon-labs/behave-graph-flow 2.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 (251) hide show
  1. package/.storybook/manager.ts +6 -0
  2. package/.storybook/preview.ts +49 -1
  3. package/.storybook/styles.css +9 -3
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +368 -0
  6. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  7. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  8. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  9. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  10. package/dist/index.css +36 -33
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.d.ts +1865 -550
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +14357 -11221
  15. package/dist/index.js.map +1 -1
  16. package/dist/noteImpl-KkrrWgJd.js +242 -0
  17. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  18. package/dist/styles.module-CvmpDkZj.css +3 -0
  19. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  20. package/dist/styles.module-DZxg8aW9.js +271 -0
  21. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  22. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  23. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  24. package/docs/protocol.md +43 -20
  25. package/package.json +5 -9
  26. package/src/components/FloatingToolbar/index.module.css +5 -13
  27. package/src/components/FloatingToolbar/index.tsx +9 -9
  28. package/src/components/Flow.tsx +34 -23
  29. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  30. package/src/components/contextMenus/NodePicker.module.css +13 -13
  31. package/src/components/contextMenus/edge.tsx +9 -95
  32. package/src/components/contextMenus/node.tsx +9 -149
  33. package/src/components/contextMenus/selection.tsx +5 -71
  34. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  35. package/src/components/controls/any/index.tsx +13 -2
  36. package/src/components/edges/index.tsx +75 -69
  37. package/src/components/layoutController/index.module.css +3 -0
  38. package/src/components/layoutController/index.tsx +24 -1
  39. package/src/components/layoutController/utils.ts +46 -3
  40. package/src/components/menubar/defaults.tsx +55 -19
  41. package/src/components/menubar/menuItem.module.css +18 -3
  42. package/src/components/menubar/menuItem.tsx +34 -1
  43. package/src/components/nodes/behave/NodeContainer.module.css +26 -25
  44. package/src/components/nodes/group/index.tsx +3 -3
  45. package/src/components/nodes/wrapper/styles.module.css +6 -32
  46. package/src/components/panels/alignment/index.module.css +0 -10
  47. package/src/components/panels/alignment/index.tsx +4 -4
  48. package/src/components/panels/base/styles.module.css +2 -2
  49. package/src/components/panels/common/PanelHeader.module.css +24 -0
  50. package/src/components/panels/common/PanelHeader.tsx +22 -0
  51. package/src/components/panels/common/SectionTitle.module.css +13 -0
  52. package/src/components/panels/common/SectionTitle.tsx +10 -0
  53. package/src/components/panels/events/EditEventPanel.tsx +14 -5
  54. package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
  55. package/src/components/panels/events/styles.module.css +6 -64
  56. package/src/components/panels/graphProperties/index.tsx +125 -0
  57. package/src/components/panels/history/index.tsx +2 -2
  58. package/src/components/panels/history/styles.module.css +0 -9
  59. package/src/components/panels/keymaps/index.module.css +3 -13
  60. package/src/components/panels/keymaps/index.tsx +1 -2
  61. package/src/components/panels/layers/index.tsx +20 -15
  62. package/src/components/panels/layers/styles.module.css +9 -12
  63. package/src/components/panels/legend/index.tsx +1 -1
  64. package/src/components/panels/logs/index.module.css +25 -19
  65. package/src/components/panels/logs/index.tsx +7 -7
  66. package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
  67. package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
  68. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
  69. package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
  70. package/src/components/panels/nodeInputs/index.module.css +99 -75
  71. package/src/components/panels/nodeInputs/index.tsx +21 -11
  72. package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
  73. package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
  74. package/src/components/panels/nodePicker/index.tsx +8 -8
  75. package/src/components/panels/panel/index.module.css +7 -7
  76. package/src/components/panels/search/index.module.css +0 -50
  77. package/src/components/panels/search/index.tsx +2 -2
  78. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  79. package/src/components/panels/systemSettings/index.tsx +221 -176
  80. package/src/components/panels/systemSettings/styles.module.css +135 -8
  81. package/src/components/panels/traces/GridLines.tsx +1 -1
  82. package/src/components/panels/traces/TimeGrid.tsx +3 -3
  83. package/src/components/panels/traces/TraceLane.tsx +1 -1
  84. package/src/components/panels/traces/index.module.css +1 -8
  85. package/src/components/panels/traces/index.tsx +8 -4
  86. package/src/components/panels/traces/useDerivedSpans.ts +241 -146
  87. package/src/components/panels/traces/utils.ts +8 -0
  88. package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
  89. package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
  90. package/src/components/panels/variables/index.tsx +2 -2
  91. package/src/components/panels/variables/styles.module.css +4 -91
  92. package/src/components/primitives/icon.module.css +4 -4
  93. package/src/components/sockets/input/index.tsx +9 -2
  94. package/src/components/sockets/input/styles.module.css +2 -3
  95. package/src/components/sockets/output/index.tsx +10 -3
  96. package/src/components/sockets/output/styles.module.css +1 -6
  97. package/src/css/notes.css +135 -0
  98. package/src/css/prosemirror.css +3 -3
  99. package/src/css/rc-dock.css +143 -43
  100. package/src/css/rc-menu.css +56 -55
  101. package/src/css/themes/kiberon.css +127 -0
  102. package/src/css/vars.css +197 -13
  103. package/src/css/vscode-elements.css +124 -0
  104. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  105. package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
  106. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  107. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  108. package/src/generators/SequenceGenerator.tsx +2 -2
  109. package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
  110. package/src/generators/SwitchOnStringGenerator.tsx +2 -2
  111. package/src/generators/callSubgraphSync.ts +126 -0
  112. package/src/generators/registerDefaultGenerators.ts +21 -0
  113. package/src/generators/registerDefaults.ts +26 -0
  114. package/src/hooks/useBehaveGraphFlow.ts +2 -2
  115. package/src/hooks/useFlowHandlers.ts +47 -9
  116. package/src/hooks/useWasdPan.ts +26 -4
  117. package/src/index.css +4 -16
  118. package/src/index.ts +17 -0
  119. package/src/manifest/contributionRegistry.ts +93 -0
  120. package/src/manifest/index.ts +4 -0
  121. package/src/manifest/loadManifest.ts +82 -0
  122. package/src/manifest/manifestPlugin.ts +29 -0
  123. package/src/manifest/passthroughValueType.ts +40 -0
  124. package/src/plugin/alignment/index.ts +22 -12
  125. package/src/plugin/autosave/controller.ts +366 -0
  126. package/src/plugin/autosave/index.tsx +114 -0
  127. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  128. package/src/plugin/autosave/panel/index.tsx +1 -0
  129. package/src/plugin/autosave/panel/styles.module.css +56 -0
  130. package/src/plugin/autosave/settings.ts +65 -0
  131. package/src/plugin/autosave/storage.ts +147 -0
  132. package/src/plugin/docs/index.tsx +2 -4
  133. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  134. package/src/plugin/docs/panel/index.tsx +15 -194
  135. package/src/plugin/docs/panel/styles.module.css +8 -8
  136. package/src/plugin/graphrunner/actions.ts +258 -185
  137. package/src/plugin/graphrunner/buttons.tsx +34 -26
  138. package/src/plugin/graphrunner/client.ts +4 -1
  139. package/src/plugin/graphrunner/index.tsx +29 -100
  140. package/src/plugin/graphrunner/panel.tsx +2 -2
  141. package/src/plugin/graphrunner/runController.ts +283 -0
  142. package/src/plugin/graphrunner/runner.ts +21 -192
  143. package/src/plugin/graphrunner/store.ts +14 -24
  144. package/src/plugin/graphrunner/styles.module.css +17 -57
  145. package/src/plugin/graphrunner/transport.ts +26 -0
  146. package/src/plugin/graphrunner/types.ts +21 -0
  147. package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
  148. package/src/plugin/graphrunner-local/index.tsx +8 -2
  149. package/src/plugin/graphrunner-local/panel.tsx +131 -175
  150. package/src/plugin/graphrunner-local/styles.module.css +57 -76
  151. package/src/plugin/graphrunner-local/transport.ts +151 -184
  152. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
  153. package/src/plugin/graphrunner-webworker/index.tsx +4 -10
  154. package/src/plugin/graphrunner-webworker/store.ts +9 -0
  155. package/src/plugin/kitchen-sink/index.ts +38 -0
  156. package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
  157. package/src/{layout → plugin/layout}/elk.ts +22 -6
  158. package/src/plugin/layout/index.ts +80 -0
  159. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  160. package/src/plugin/notes/index.tsx +191 -0
  161. package/src/plugin/notes/nodeActions.ts +100 -0
  162. package/src/plugin/notes/note.tsx +20 -0
  163. package/src/plugin/notes/noteImpl.tsx +89 -0
  164. package/src/plugin/realtime/realtimeRunner.ts +58 -4
  165. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
  166. package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
  167. package/src/specifics/VariableGetSpecific.tsx +2 -2
  168. package/src/specifics/VariableSetSpecific.tsx +2 -2
  169. package/src/store/actions.tsx +5 -5
  170. package/src/store/commands.ts +278 -0
  171. package/src/store/contextMenu.ts +192 -0
  172. package/src/store/conversions.ts +47 -0
  173. package/src/store/flow.tsx +23 -38
  174. package/src/store/graphMeta.ts +39 -0
  175. package/src/store/hotKeys.tsx +301 -260
  176. package/src/store/layers.ts +3 -3
  177. package/src/store/registry.ts +12 -4
  178. package/src/store/selection.ts +3 -3
  179. package/src/store/settings.ts +82 -82
  180. package/src/store/settingsSchema.ts +210 -0
  181. package/src/store/tabs.ts +5 -1
  182. package/src/store/traces.ts +3 -3
  183. package/src/system/graph.ts +11 -14
  184. package/src/system/graphSession.ts +172 -0
  185. package/src/system/index.ts +3 -0
  186. package/src/system/notifications.ts +13 -0
  187. package/src/system/persistence.ts +82 -0
  188. package/src/system/plugin.ts +28 -0
  189. package/src/system/provider.tsx +64 -0
  190. package/src/system/system.ts +518 -88
  191. package/src/system/tabLoader.tsx +70 -32
  192. package/src/system/undoRedo.ts +1 -1
  193. package/src/transformers/Uigraph.ts +5 -4
  194. package/src/transformers/contract.ts +87 -0
  195. package/src/transformers/flowToBehave.ts +13 -5
  196. package/src/types/nodes.ts +8 -3
  197. package/src/types.ts +2 -0
  198. package/src/util/autoConvert.ts +200 -0
  199. package/src/util/isValidConnection.ts +23 -2
  200. package/stories/defaults/defaultStoryProvider.tsx +17 -14
  201. package/stories/defaults/systemGenerator.ts +6 -1
  202. package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
  203. package/tests/autoConvert.test.ts +329 -0
  204. package/tests/autosavePlugin.test.ts +204 -0
  205. package/tests/callSubgraphSync.test.ts +148 -0
  206. package/tests/commandRegistry.test.ts +137 -0
  207. package/tests/contract.test.ts +51 -0
  208. package/tests/contractSerialize.test.ts +62 -0
  209. package/tests/deriveSpans.test.ts +71 -0
  210. package/tests/flowToBehave.test.ts +2 -1
  211. package/tests/hotkeys.test.ts +79 -0
  212. package/tests/keepAliveLifecycle.test.ts +167 -0
  213. package/tests/loadManifest.test.ts +113 -0
  214. package/tests/noteMarkdown.test.ts +65 -0
  215. package/tests/notesPlugin.test.ts +162 -0
  216. package/tests/persistence.test.ts +51 -0
  217. package/tests/saveLoad.test.ts +7 -6
  218. package/tests/settings.test.ts +178 -0
  219. package/tests/traceStore.test.ts +46 -0
  220. package/tests/visual/README.md +2 -2
  221. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  222. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  223. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  224. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  225. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  226. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  227. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  228. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  229. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  230. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  231. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  232. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  233. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  234. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  235. package/tests/visual/panels.visual.test.tsx +3 -3
  236. package/tests/wasdPan.test.ts +71 -0
  237. package/vitest.config.ts +1 -1
  238. package/vitest.visual.config.ts +7 -0
  239. package/.storybook/vscode.css +0 -814
  240. package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
  241. package/src/components/nodes/comment/comment.tsx +0 -103
  242. package/src/components/nodes/comment/styles.module.css +0 -150
  243. package/src/components/panels/conversation/index.module.css +0 -151
  244. package/src/components/panels/conversation/index.tsx +0 -162
  245. package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
  246. package/src/css/vscode.css +0 -13
  247. package/src/hooks/useDetachNodes.ts +0 -39
  248. package/src/plugin/graphrunner-webworker/types.ts +0 -17
  249. package/src/specifics/registerDefaultSpecifics.ts +0 -5
  250. package/src/store/chat.ts +0 -73
  251. package/src/store/graphRunnerClient.ts +0 -110
@@ -9,91 +9,91 @@ export const EDGE_TYPE = {
9
9
 
10
10
  export type EdgeType = (typeof EDGE_TYPE)[keyof typeof EDGE_TYPE];
11
11
 
12
- export const LAYOUT_TYPE = {
13
- dagre: 'Dagre',
14
- elkForce: 'Elk - Force',
15
- elkRect: 'Elk - Rect',
16
- elkLayered: 'Elk - Layered'
17
- } as const;
12
+ export type GeneratorLocation = 'inline' | 'panel';
18
13
 
19
- export type LayoutType = (typeof LAYOUT_TYPE)[keyof typeof LAYOUT_TYPE];
14
+ /**
15
+ * Single source of truth for editor settings. Each entry's `default` defines the
16
+ * value (and its type); `persist: false` excludes it from the saved settings.
17
+ * The store shape, setters, and the persisted-key list are all derived from this
18
+ * , adding a setting here is the only edit needed (no setter boilerplate, no
19
+ * separate persist allow-list to keep in sync).
20
+ */
21
+ const SETTINGS = {
22
+ edgeType: { default: EDGE_TYPE.bezier as EdgeType },
23
+ debugMode: { default: false as boolean },
24
+ showMenu: { default: true as boolean },
25
+ showTimings: { default: false as boolean },
26
+ showMinimap: { default: false as boolean },
27
+ showGrid: { default: true as boolean },
28
+ // Transient UI state , not persisted.
29
+ showSearch: { default: false as boolean, persist: false },
30
+ /** Delay applying a node value change until the user commits it. */
31
+ delayedUpdate: { default: false as boolean },
32
+ /** Show value types inline on nodes. */
33
+ inlineTypes: { default: false as boolean },
34
+ /** Show values inline on nodes. */
35
+ inlineValues: { default: false as boolean },
36
+ connectOnClick: { default: true as boolean },
37
+ /**
38
+ * When connecting sockets of different-but-convertible value types,
39
+ * automatically insert a conversion node between them.
40
+ */
41
+ autoConvert: { default: true as boolean },
42
+ snapGrid: { default: false as boolean },
43
+ gridSize: { default: 15 as number },
44
+ generatorLocation: { default: 'panel' as GeneratorLocation }
45
+ } satisfies Record<string, { default: unknown; persist?: boolean }>;
20
46
 
21
- export type GeneratorLocation = 'inline' | 'panel';
47
+ type SettingsSchema = typeof SETTINGS;
48
+
49
+ type SettingsState = {
50
+ [K in keyof SettingsSchema]: SettingsSchema[K]['default'];
51
+ };
22
52
 
23
- export type SystemSettingsStore = {
24
- debugMode: boolean;
25
- showTimings: boolean;
26
- showMinimap: boolean;
27
- showGrid: boolean;
28
- showSearch: boolean;
29
- showMenu: boolean;
30
- edgeType: EdgeType;
31
- layoutType: LayoutType;
32
- delayedUpdate: boolean;
33
- inlineTypes: boolean;
34
- inlineValues: boolean;
35
- connectOnClick: boolean;
36
- snapGrid: boolean;
37
- gridSize: number;
38
- generatorLocation: GeneratorLocation;
39
- setDebugMode: (debugMode: boolean) => void;
40
- setShowTimings: (showTimings: boolean) => void;
41
- setShowMinimap: (showMinimap: boolean) => void;
42
- setShowGrid: (showGrid: boolean) => void;
43
- setShowSearch: (showSearch: boolean) => void;
44
- setEdgeType: (edgeType: EdgeType) => void;
45
- setLayoutType: (layoutType: LayoutType) => void;
46
- setDelayedUpdate: (delayedUpdate: boolean) => void;
47
- setInlineTypes: (inlineTypes: boolean) => void;
48
- setInlineValues: (inlineValues: boolean) => void;
49
- setConnectOnClick: (connectOnClick: boolean) => void;
50
- setSnapGrid: (snapGrid: boolean) => void;
51
- setGridSize: (gridSize: number) => void;
52
- setGeneratorLocation: (generatorLocation: GeneratorLocation) => void;
53
+ type SettingsSetters = {
54
+ [K in keyof SettingsSchema as `set${Capitalize<K & string>}`]: (
55
+ value: SettingsSchema[K]['default']
56
+ ) => void;
53
57
  };
54
58
 
59
+ /**
60
+ * Generic accessors so plugin-contributed settings (dynamic keys not in the
61
+ * static {@link SETTINGS} schema) live in the same store as the typed built-ins.
62
+ * The auto-generated panel writes every row through {@link SystemSettingsStore.setSetting};
63
+ * the typed per-key setters above remain for existing typed consumers.
64
+ */
65
+ type SettingsDynamicAccess = {
66
+ /** Dynamic plugin-contributed setting values, keyed by descriptor key. */
67
+ [key: string]: unknown;
68
+ /** Set any setting by key (used by the schema-driven panel + plugins). */
69
+ setSetting: (key: string, value: unknown) => void;
70
+ };
71
+
72
+ export type SystemSettingsStore = SettingsState &
73
+ SettingsSetters &
74
+ SettingsDynamicAccess;
75
+
76
+ const setterName = (key: string): string =>
77
+ `set${key.charAt(0).toUpperCase()}${key.slice(1)}`;
78
+
79
+ /** Settings round-tripped to persisted storage (everything except `persist:false`). */
80
+ export const PERSISTED_SETTING_KEYS = (
81
+ Object.entries(SETTINGS) as [keyof SettingsSchema, { persist?: boolean }][]
82
+ )
83
+ .filter(([, spec]) => spec.persist !== false)
84
+ .map(([key]) => key);
85
+
55
86
  export const systemSettingsFactory = () =>
56
- create<SystemSettingsStore>((set) => ({
57
- edgeType: EDGE_TYPE.bezier,
58
- layoutType: LAYOUT_TYPE.dagre,
59
- debugMode: false,
60
- showMenu: true,
61
- showTimings: false,
62
- showMinimap: false,
63
- showGrid: true,
64
- showSearch: false,
65
- /**
66
- * Whether to delay the update of a node when a value is changed
67
- */
68
- delayedUpdate: false,
69
- /**
70
- * Whether to show the types inline with the nodes
71
- */
72
- inlineTypes: false,
73
- /**
74
- * Whether to show the values inline with the nodes
75
- */
76
- inlineValues: false,
77
- connectOnClick: true,
78
- snapGrid: false,
79
- gridSize: 15,
80
- generatorLocation: 'panel',
81
- setShowMenu: (showMenu: boolean) => set(() => ({ showMenu })),
82
- setEdgeType: (edgeType: EdgeType) => set(() => ({ edgeType })),
83
- setLayoutType: (layoutType: LayoutType) => set(() => ({ layoutType })),
84
- setDebugMode: (debugMode: boolean) => set(() => ({ debugMode })),
85
- setShowTimings: (showTimings: boolean) => set(() => ({ showTimings })),
86
- setShowMinimap: (showMinimap: boolean) => set(() => ({ showMinimap })),
87
- setShowGrid: (showGrid: boolean) => set(() => ({ showGrid })),
88
- setShowSearch: (showSearch: boolean) => set(() => ({ showSearch })),
89
- setDelayedUpdate: (delayedUpdate: boolean) =>
90
- set(() => ({ delayedUpdate })),
91
- setInlineTypes: (inlineTypes: boolean) => set(() => ({ inlineTypes })),
92
- setInlineValues: (inlineValues: boolean) => set(() => ({ inlineValues })),
93
- setConnectOnClick: (connectOnClick: boolean) =>
94
- set(() => ({ connectOnClick })),
95
- setSnapGrid: (snapGrid: boolean) => set(() => ({ snapGrid })),
96
- setGridSize: (gridSize: number) => set(() => ({ gridSize })),
97
- setGeneratorLocation: (generatorLocation: GeneratorLocation) =>
98
- set(() => ({ generatorLocation }))
99
- }));
87
+ create<SystemSettingsStore>((set) => {
88
+ const initial: Record<string, unknown> = {};
89
+ for (const [key, spec] of Object.entries(SETTINGS)) {
90
+ initial[key] = spec.default;
91
+ initial[setterName(key)] = (value: unknown) =>
92
+ set({ [key]: value } as Partial<SystemSettingsStore>);
93
+ }
94
+ // Generic setter for dynamic (plugin-contributed) keys and for the
95
+ // schema-driven panel, which writes every row by key.
96
+ initial.setSetting = (key: string, value: unknown) =>
97
+ set({ [key]: value } as Partial<SystemSettingsStore>);
98
+ return initial as SystemSettingsStore;
99
+ });
@@ -0,0 +1,210 @@
1
+ import { createStore, type StoreApi } from 'zustand/vanilla';
2
+ import type { FC } from 'react';
3
+ import { EDGE_TYPE } from './settings';
4
+
5
+ /**
6
+ * Schema-driven settings.
7
+ *
8
+ * The Settings panel is auto-generated from a registry of {@link SettingDescriptor}s
9
+ * rather than hand-coded JSX. Built-in settings are seeded as {@link DEFAULT_SETTINGS};
10
+ * plugins contribute their own via `system.registerSetting(...)`, and the panel
11
+ * renders them the same way — so a plugin can surface settings without forking
12
+ * the panel.
13
+ *
14
+ * Descriptors are intentionally plain data (everything except `render`/`when` is
15
+ * JSON-serializable), so a future declarative manifest `contributes.configuration`
16
+ * section can feed this same registry.
17
+ */
18
+
19
+ export type SettingEnumOption = { value: string; label: string };
20
+
21
+ /** Live values bag passed to a descriptor's {@link SettingDescriptorBase.when}. */
22
+ export type SettingsValues = Record<string, unknown>;
23
+
24
+ /** Props handed to a `type: 'custom'` descriptor's `render` component. */
25
+ export type SettingControlProps = {
26
+ value: unknown;
27
+ setValue: (value: unknown) => void;
28
+ descriptor: SettingDescriptor;
29
+ };
30
+
31
+ type SettingDescriptorBase = {
32
+ /**
33
+ * Unique key. Built-ins are flat (`edgeType`) and match the value-store keys
34
+ * 1:1; plugin settings should be namespaced (`graphRunner.autoStart`) to avoid
35
+ * collisions.
36
+ */
37
+ key: string;
38
+ /** Category heading the row is grouped under, e.g. `"Layout"`. */
39
+ section: string;
40
+ /** Sort order within the section (falls back to registration order). */
41
+ order?: number;
42
+ /** Row label. */
43
+ title?: string;
44
+ /** Secondary description text under the label. */
45
+ description?: string;
46
+ /** Excluded from persisted settings when `false`. Defaults to `true`. */
47
+ persist?: boolean;
48
+ /** Hide the row unless this returns true for the current values. */
49
+ when?: (values: SettingsValues) => boolean;
50
+ };
51
+
52
+ export type SettingDescriptor =
53
+ | (SettingDescriptorBase & { type: 'boolean'; default: boolean })
54
+ | (SettingDescriptorBase & {
55
+ type: 'number';
56
+ default: number;
57
+ min?: number;
58
+ max?: number;
59
+ step?: number;
60
+ })
61
+ | (SettingDescriptorBase & {
62
+ type: 'string';
63
+ default: string;
64
+ placeholder?: string;
65
+ })
66
+ | (SettingDescriptorBase & {
67
+ type: 'enum';
68
+ default: string;
69
+ options: SettingEnumOption[];
70
+ })
71
+ | (SettingDescriptorBase & {
72
+ type: 'custom';
73
+ default?: unknown;
74
+ render: FC<SettingControlProps>;
75
+ });
76
+
77
+ export type SettingsSchemaStore = {
78
+ /** Registered descriptors, in registration order. */
79
+ settings: SettingDescriptor[];
80
+ /** Section names in first-seen order, for stable display grouping. */
81
+ sectionOrder: string[];
82
+ registerSetting: (descriptor: SettingDescriptor) => void;
83
+ registerSettings: (descriptors: SettingDescriptor[]) => void;
84
+ };
85
+
86
+ const toOptions = (values: readonly string[]): SettingEnumOption[] =>
87
+ values.map((value) => ({ value, label: value }));
88
+
89
+ /**
90
+ * Built-in editor settings, mirroring the previously hand-coded panel. Keys
91
+ * match the value-store keys in {@link file://./settings.ts} exactly, so typed
92
+ * consumers (`settings.edgeType`, …) are unaffected.
93
+ */
94
+ export const DEFAULT_SETTINGS: SettingDescriptor[] = [
95
+ {
96
+ key: 'edgeType',
97
+ section: 'Layout',
98
+ type: 'enum',
99
+ default: EDGE_TYPE.bezier,
100
+ options: toOptions(Object.values(EDGE_TYPE)),
101
+ title: 'Edge Type',
102
+ description: 'Select the type of edge to use in the graph editor.'
103
+ },
104
+ {
105
+ key: 'inlineTypes',
106
+ section: 'Accessibility',
107
+ type: 'boolean',
108
+ default: false,
109
+ title: 'Show inline types',
110
+ description:
111
+ 'Adds additional spans to help differentiate types for colorblind users.'
112
+ },
113
+ {
114
+ key: 'delayedUpdate',
115
+ section: 'Interaction',
116
+ type: 'boolean',
117
+ default: false,
118
+ title: 'Use delayed interaction',
119
+ description: 'Forces a user to click save to update port.'
120
+ },
121
+ {
122
+ key: 'connectOnClick',
123
+ section: 'Interaction',
124
+ type: 'boolean',
125
+ default: true,
126
+ title: 'Click to connect',
127
+ description: 'Allows you to quick connect nodes by clicking on the 2 port.'
128
+ },
129
+ {
130
+ key: 'autoConvert',
131
+ section: 'Interaction',
132
+ type: 'boolean',
133
+ default: true,
134
+ title: 'Auto-convert types',
135
+ description:
136
+ 'When connecting different but convertible types, insert a conversion node automatically.'
137
+ },
138
+ {
139
+ key: 'showMinimap',
140
+ section: 'Display',
141
+ type: 'boolean',
142
+ default: false,
143
+ title: 'Show Minimap',
144
+ description: 'Shows the minimap in the graph editing area.'
145
+ },
146
+ {
147
+ key: 'showGrid',
148
+ section: 'Display',
149
+ type: 'boolean',
150
+ default: true,
151
+ title: 'Show Grid',
152
+ description: 'Shows the grid in the graph editing area.'
153
+ },
154
+ {
155
+ key: 'snapGrid',
156
+ section: 'Display',
157
+ type: 'boolean',
158
+ default: false,
159
+ title: 'Snap to Grid',
160
+ description: 'Snaps nodes to the grid while dragging.'
161
+ },
162
+ {
163
+ key: 'showTimings',
164
+ section: 'Performance',
165
+ type: 'boolean',
166
+ default: false,
167
+ title: 'Show execution time',
168
+ description: 'Shows how long it takes for a node to process.'
169
+ }
170
+ ];
171
+
172
+ export const settingsSchemaStoreFactory = (
173
+ initial: SettingDescriptor[] = DEFAULT_SETTINGS
174
+ ): StoreApi<SettingsSchemaStore> =>
175
+ createStore<SettingsSchemaStore>((set, get) => {
176
+ const registerSetting = (descriptor: SettingDescriptor): void => {
177
+ const { settings, sectionOrder } = get();
178
+ if (settings.some((existing) => existing.key === descriptor.key)) {
179
+ console.warn(
180
+ `Setting "${descriptor.key}" is already registered; ignoring duplicate.`
181
+ );
182
+ return;
183
+ }
184
+ set({
185
+ settings: [...settings, descriptor],
186
+ sectionOrder: sectionOrder.includes(descriptor.section)
187
+ ? sectionOrder
188
+ : [...sectionOrder, descriptor.section]
189
+ });
190
+ };
191
+
192
+ // Seed the built-in descriptors as the starting state.
193
+ const settings: SettingDescriptor[] = [];
194
+ const sectionOrder: string[] = [];
195
+ for (const descriptor of initial) {
196
+ if (settings.some((existing) => existing.key === descriptor.key))
197
+ continue;
198
+ settings.push(descriptor);
199
+ if (!sectionOrder.includes(descriptor.section)) {
200
+ sectionOrder.push(descriptor.section);
201
+ }
202
+ }
203
+
204
+ return {
205
+ settings,
206
+ sectionOrder,
207
+ registerSetting,
208
+ registerSettings: (descriptors) => descriptors.forEach(registerSetting)
209
+ };
210
+ });
package/src/store/tabs.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { BoxBase, LayoutBase, PanelBase, TabBase } from 'rc-dock';
2
2
  import { create } from 'zustand';
3
+ import { isGraphTabId } from '@/components/layoutController/utils';
3
4
 
4
5
  function isPanelBase(value: BoxBase | PanelBase): value is PanelBase {
5
6
  return Array.isArray((value as PanelBase).tabs);
@@ -120,6 +121,9 @@ const defaultLayout: LayoutBase = {
120
121
  children: [
121
122
  {
122
123
  tabs: [
124
+ {
125
+ id: 'graphProperties'
126
+ },
123
127
  {
124
128
  id: 'variables'
125
129
  },
@@ -226,7 +230,7 @@ export const tabStoreFactory = () =>
226
230
 
227
231
  // 2) Otherwise, insert it into a target panel (prefer explicit panelId).
228
232
  const targetPanelId =
229
- options?.panelId ?? (tabId === 'graph' ? 'graphs' : undefined);
233
+ options?.panelId ?? (isGraphTabId(tabId) ? 'graphs' : undefined);
230
234
 
231
235
  if (targetPanelId) {
232
236
  const res = recurseUpsertTabInPanelId(
@@ -1,6 +1,6 @@
1
1
  import type { Engine, INode } from '@kiberon-labs/behave-graph';
2
2
  import { create } from 'zustand';
3
- import type { System } from '@/system';
3
+ import type { GraphSession } from '@/system/graphSession';
4
4
 
5
5
  export type TraceSpan = {
6
6
  id: number;
@@ -74,11 +74,11 @@ export type TraceStore = {
74
74
  connectEngine: (engine?: Engine) => void;
75
75
  recordNodeStart: (node: INode, startTime?: number) => void;
76
76
  recordNodeEnd: (node: INode, endTime?: number) => void;
77
- addSpan: (span: Omit<TraceSpan, 'id'>) => void;
77
+ addSpan: (span: Omit<TraceSpan, 'id' | 'lane'> & { lane?: number }) => void;
78
78
  updateSpan: (nodeId: string, updates: Partial<TraceSpan>) => void;
79
79
  };
80
80
 
81
- export const traceStoreFactory = (_: System) => {
81
+ export const traceStoreFactory = (_: GraphSession) => {
82
82
  let connectedEngine: Engine | undefined;
83
83
  let onStart: ((node: INode) => void) | undefined;
84
84
  let onEnd: ((node: INode) => void) | undefined;
@@ -1,5 +1,5 @@
1
1
  import type { Viewport } from 'reactflow';
2
- import type { System } from './system';
2
+ import type { GraphSession } from './graphSession';
3
3
  import type { Edge } from 'reactflow';
4
4
  import type { UIGraphJSON } from '@/types/graph';
5
5
 
@@ -9,12 +9,10 @@ import type { UIGraphJSON } from '@/types/graph';
9
9
  */
10
10
  export class Graph {
11
11
  public readonly viewports: Viewport[] = [];
12
- private sys: System;
12
+ private sys: GraphSession;
13
13
 
14
- protected annotations: { [key: string]: any } = {};
15
-
16
- constructor(system: System) {
17
- this.sys = system;
14
+ constructor(session: GraphSession) {
15
+ this.sys = session;
18
16
  }
19
17
 
20
18
  setViewport(index: number, viewport: Viewport) {
@@ -83,13 +81,14 @@ export class Graph {
83
81
  }
84
82
 
85
83
  serialize(): UIGraphJSON {
84
+ const meta = this.sys.metaStore.getState();
86
85
  return {
87
86
  v: '1.0.0',
88
- name: 'Untitled Graph',
87
+ name: meta.name,
89
88
  user: {
90
89
  viewport: this.viewports[0] || { x: 0, y: 0, zoom: 1 }
91
90
  },
92
- annotations: this.annotations,
91
+ annotations: { ...meta.metadata },
93
92
  data: {
94
93
  layers: this.sys.layerStore.getState().serialize()
95
94
  },
@@ -100,14 +99,11 @@ export class Graph {
100
99
  }
101
100
 
102
101
  getAnnotations() {
103
- return { ...this.annotations };
102
+ return { ...this.sys.metaStore.getState().metadata };
104
103
  }
105
104
 
106
105
  setAnnotations(annotations: { [key: string]: any }) {
107
- this.annotations = {
108
- ...this.annotations,
109
- ...annotations
110
- };
106
+ this.sys.metaStore.getState().mergeMetadata(annotations);
111
107
  this.sys.pubsub.publish('graphAnnotationsChanged', this.getAnnotations());
112
108
  }
113
109
 
@@ -115,7 +111,8 @@ export class Graph {
115
111
  //Load nodes
116
112
  this.sys.nodeStore.getState().setNodes(data.nodes);
117
113
  this.sys.edgeStore.getState().setEdges(data.edges);
118
- this.annotations = data.annotations || {};
114
+ this.sys.metaStore.getState().setMetadata(data.annotations || {});
115
+ if (data.name) this.sys.metaStore.getState().setName(data.name);
119
116
  this.sys.layerStore.getState().deserialize(data.data?.layers);
120
117
 
121
118
  // Restore viewport
@@ -0,0 +1,172 @@
1
+ import { type StoreApi } from 'zustand';
2
+ import { UndoManager } from './undoRedo';
3
+ import { PubSub } from './pubsub';
4
+ import { Graph } from './graph';
5
+ import type { System, GraphPubSys } from './system';
6
+ import {
7
+ edgeStoreFactory,
8
+ flowStoreFactory,
9
+ nodeStoreFactory,
10
+ type EdgeStore,
11
+ type FlowStore,
12
+ type NodeStore
13
+ } from '@/store/flow';
14
+ import { controlsStoreFactory, type ControlsStore } from '@/store/controls';
15
+ import { variableStoreFactory, type VariableStore } from '@/store/variables';
16
+ import { selectionStoreFactory, type SelectionStore } from '@/store/selection';
17
+ import { refStoreFactory, type RefStore } from '@/store/refs';
18
+ import { actionStoreFactory, type ActionStore } from '@/store/actions';
19
+ import { traceStoreFactory, type TraceStore } from '@/store/traces';
20
+ import { eventsStoreFactory, type EventsStore } from '@/store/events';
21
+ import { layerStoreFactory, type LayerStore } from '@/store/layers';
22
+ import { logStoreFactory, type LogStore } from '@/store/logs';
23
+ import { graphMetaStoreFactory, type GraphMetaStore } from '@/store/graphMeta';
24
+ import type { UIGraphJSON } from '@/types/graph';
25
+
26
+ /**
27
+ * Augmentable surface for per-graph state contributed by plugins. Mirrors
28
+ * {@link ISystem} at the editor level: a plugin adds typed properties to every
29
+ * graph by augmenting this interface and assigning them via
30
+ * {@link GraphSession.decorate} from inside a session extension.
31
+ *
32
+ * @example
33
+ * declare module '@/system/graphSession' {
34
+ * interface IGraphSession {
35
+ * myController?: MyController;
36
+ * }
37
+ * }
38
+ */
39
+ export interface IGraphSession {}
40
+
41
+ /**
42
+ * Merge the augmentable surface into the class instance type. Declaration
43
+ * merging (same-named interface + class) makes plugin-added members , set via
44
+ * {@link GraphSession.decorate} , readable as `session.x`, which `implements`
45
+ * alone would NOT provide.
46
+ */
47
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
48
+ export interface GraphSession extends IGraphSession {}
49
+
50
+ /**
51
+ * A single open graph. Owns all per-graph state , nodes, edges, variables,
52
+ * selection, traces, layers, logs, undo history and a private pubsub , so that
53
+ * multiple graphs can be open simultaneously in complete isolation.
54
+ *
55
+ * Shared, editor-level resources (registry, specs, settings, notifications, ...)
56
+ * are reached through {@link GraphSession.editor}.
57
+ *
58
+ * Per-graph state contributed by editor plugins is attached on creation via
59
+ * session extensions (`system.registerSessionExtension`), which may register
60
+ * teardown through {@link GraphSession.onDispose}.
61
+ */
62
+ export class GraphSession {
63
+ public readonly id: string;
64
+ public readonly editor: System;
65
+ /** Reactive graph-level properties (name + metadata). */
66
+ public readonly metaStore: StoreApi<GraphMetaStore>;
67
+ public readonly pubsub = new PubSub<GraphPubSys>();
68
+ public readonly undoManager = new UndoManager();
69
+
70
+ /** Display name, used as the graph tab title. Backed by {@link metaStore}. */
71
+ get name(): string {
72
+ return this.metaStore.getState().name;
73
+ }
74
+ set name(value: string) {
75
+ this.metaStore.getState().setName(value);
76
+ }
77
+
78
+ public readonly controlStore: StoreApi<ControlsStore>;
79
+ public readonly variableStore: StoreApi<VariableStore>;
80
+ public readonly refStore: StoreApi<RefStore>;
81
+ public readonly logsStore: StoreApi<LogStore>;
82
+ public readonly eventsStore: StoreApi<EventsStore>;
83
+ public readonly nodeStore: StoreApi<NodeStore>;
84
+ public readonly edgeStore: StoreApi<EdgeStore>;
85
+ public readonly flowStore: StoreApi<FlowStore>;
86
+ public readonly selectionStore: StoreApi<SelectionStore>;
87
+ public readonly actionStore: StoreApi<ActionStore>;
88
+ public readonly layerStore: StoreApi<LayerStore>;
89
+ public readonly traceStore: StoreApi<TraceStore>;
90
+ public readonly graph: Graph;
91
+
92
+ /** Cleanups registered by session extensions, run (LIFO) on {@link dispose}. */
93
+ private readonly disposers: Array<() => void> = [];
94
+
95
+ constructor(editor: System, id = 'graph', name = 'Graph') {
96
+ this.id = id;
97
+ this.editor = editor;
98
+ this.metaStore = graphMetaStoreFactory(name);
99
+
100
+ // Construction order preserves the dependency wiring of the old System ctor.
101
+ this.controlStore = controlsStoreFactory();
102
+ this.variableStore = variableStoreFactory();
103
+ this.refStore = refStoreFactory();
104
+ this.logsStore = logStoreFactory();
105
+ this.eventsStore = eventsStoreFactory();
106
+ this.nodeStore = nodeStoreFactory(this);
107
+ this.edgeStore = edgeStoreFactory(this);
108
+ this.flowStore = flowStoreFactory(this);
109
+ this.selectionStore = selectionStoreFactory(this);
110
+ this.actionStore = actionStoreFactory(this);
111
+ this.layerStore = layerStoreFactory(this);
112
+ this.traceStore = traceStoreFactory(this);
113
+ this.graph = new Graph(this);
114
+
115
+ // Per-session behaviours (alignment, ...) are contributed by opt-in plugins
116
+ // via system.registerSessionExtension(...), not wired here. See the
117
+ // alignment plugin (`@/plugin/alignment`).
118
+ }
119
+
120
+ /**
121
+ * Convenience access to the shared editor notifications service.
122
+ */
123
+ get notifications() {
124
+ return this.editor.notifications;
125
+ }
126
+
127
+ /**
128
+ * Attach a plugin-contributed property to this session. The companion to the
129
+ * {@link IGraphSession} augmentation; mirrors {@link System.decorate}.
130
+ */
131
+ decorate<K extends keyof IGraphSession>(
132
+ name: K,
133
+ val: IGraphSession[K]
134
+ ): void {
135
+ (this as IGraphSession)[name] = val;
136
+ }
137
+
138
+ /**
139
+ * Register a cleanup to run when this session is disposed. Session extensions
140
+ * use this (via their returned cleanup) to tear down per-graph state they
141
+ * attached. Cleanups run in reverse registration order.
142
+ */
143
+ onDispose(cleanup: () => void): void {
144
+ this.disposers.push(cleanup);
145
+ }
146
+
147
+ serialize(): UIGraphJSON {
148
+ return this.graph.serialize();
149
+ }
150
+
151
+ /**
152
+ * Tear down this session's reactive wiring. Called when its tab is closed so
153
+ * its pubsub subscriptions and trace flush loop don't leak. Extension-supplied
154
+ * cleanups run first (LIFO), then the built-in teardown.
155
+ */
156
+ dispose(): void {
157
+ for (let i = this.disposers.length - 1; i >= 0; i--) {
158
+ try {
159
+ this.disposers[i]!();
160
+ } catch (err) {
161
+ console.error('GraphSession disposer failed', err);
162
+ }
163
+ }
164
+ this.disposers.length = 0;
165
+
166
+ // Per-session plugin state (e.g. the graph runner's runController) is torn
167
+ // down by the extension cleanups registered above via onDispose , core no
168
+ // longer reaches into plugin-owned fields here.
169
+ this.traceStore.getState().connectEngine(undefined);
170
+ this.pubsub.clearAllSubscriptions();
171
+ }
172
+ }
@@ -1,3 +1,6 @@
1
1
  export * from './system';
2
+ export * from './persistence';
3
+ export * from './graphSession';
2
4
  export * from './provider';
5
+ export * from './plugin';
3
6
  export { subscribeToRef, useRefFromStore } from '@/store/refs';