@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
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { System } from '../src/system/system.js';
3
+ import { registerDefaults } from '../src/generators/registerDefaults.js';
4
+
5
+ describe('editor settings serialization', () => {
6
+ it('round-trips toggles and custom conversions', () => {
7
+ const a = new System();
8
+ a.systemSettings.getState().setAutoConvert(false);
9
+ a.systemSettings.getState().setGridSize(42);
10
+ a.registerConversion({ from: 'integer', to: 'string', nodeType: 'x/conv' });
11
+
12
+ const json = a.serializeSettings();
13
+
14
+ const b = new System();
15
+ b.applySettings(json);
16
+
17
+ expect(b.systemSettings.getState().autoConvert).toBe(false);
18
+ expect(b.systemSettings.getState().gridSize).toBe(42);
19
+ expect(b.conversionStore.getState().conversions).toEqual([
20
+ { from: 'integer', to: 'string', nodeType: 'x/conv' }
21
+ ]);
22
+ });
23
+
24
+ it('round-trips settings that the old hand-written store dropped', () => {
25
+ // inlineValues lacked a setter (so applySettings could not restore it) and
26
+ // setShowMenu was untyped , both fixed by the declarative schema.
27
+ const a = new System();
28
+ a.systemSettings.getState().setInlineValues(true);
29
+ a.systemSettings.getState().setShowMenu(false);
30
+
31
+ const b = new System();
32
+ b.applySettings(a.serializeSettings());
33
+
34
+ expect(b.systemSettings.getState().inlineValues).toBe(true);
35
+ expect(b.systemSettings.getState().showMenu).toBe(false);
36
+ });
37
+
38
+ it('does not persist transient settings (showSearch)', () => {
39
+ const a = new System();
40
+ a.systemSettings.getState().setShowSearch(true);
41
+ expect(a.serializeSettings().settings).not.toHaveProperty('showSearch');
42
+ });
43
+ });
44
+
45
+ describe('plugin-contributed settings (schema registry)', () => {
46
+ it('seeds the built-in descriptors so the panel auto-generates', () => {
47
+ const sys = new System();
48
+ const keys = sys.settingsSchema.getState().settings.map((s) => s.key);
49
+ expect(keys).toContain('edgeType');
50
+ expect(keys).toContain('autoConvert');
51
+ expect(sys.settingsSchema.getState().sectionOrder).toContain('Layout');
52
+ });
53
+
54
+ it('registerSetting adds a descriptor and seeds its default value', () => {
55
+ const sys = new System();
56
+ sys.registerSetting({
57
+ key: 'myPlugin.apiUrl',
58
+ section: 'My Plugin',
59
+ type: 'string',
60
+ default: 'https://example.com',
61
+ title: 'API URL'
62
+ });
63
+
64
+ expect(sys.getSetting('myPlugin.apiUrl')).toBe('https://example.com');
65
+ expect(
66
+ sys.settingsSchema
67
+ .getState()
68
+ .settings.some((s) => s.key === 'myPlugin.apiUrl')
69
+ ).toBe(true);
70
+ expect(sys.settingsSchema.getState().sectionOrder).toContain('My Plugin');
71
+ });
72
+
73
+ it('ignores a duplicate key without clobbering the original', () => {
74
+ const sys = new System();
75
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
76
+ try {
77
+ // edgeType is a built-in; a plugin must not be able to redefine it.
78
+ sys.registerSetting({
79
+ key: 'edgeType',
80
+ section: 'Hijack',
81
+ type: 'boolean',
82
+ default: true
83
+ });
84
+ expect(warn).toHaveBeenCalled();
85
+ const edge = sys.settingsSchema
86
+ .getState()
87
+ .settings.find((s) => s.key === 'edgeType');
88
+ expect(edge?.section).toBe('Layout');
89
+ } finally {
90
+ warn.mockRestore();
91
+ }
92
+ });
93
+
94
+ it('round-trips plugin setting values through persistence', () => {
95
+ const a = new System();
96
+ a.registerSetting({
97
+ key: 'myPlugin.autoStart',
98
+ section: 'My Plugin',
99
+ type: 'boolean',
100
+ default: false
101
+ });
102
+ a.setSetting('myPlugin.autoStart', true);
103
+
104
+ const json = a.serializeSettings();
105
+ expect(json.settings).toHaveProperty('myPlugin.autoStart', true);
106
+
107
+ const b = new System();
108
+ b.registerSetting({
109
+ key: 'myPlugin.autoStart',
110
+ section: 'My Plugin',
111
+ type: 'boolean',
112
+ default: false
113
+ });
114
+ b.applySettings(json);
115
+ expect(b.getSetting('myPlugin.autoStart')).toBe(true);
116
+ });
117
+
118
+ it('excludes persist:false plugin settings from serialization', () => {
119
+ const sys = new System();
120
+ sys.registerSetting({
121
+ key: 'myPlugin.scratch',
122
+ section: 'My Plugin',
123
+ type: 'boolean',
124
+ default: false,
125
+ persist: false
126
+ });
127
+ sys.setSetting('myPlugin.scratch', true);
128
+ expect(sys.serializeSettings().settings).not.toHaveProperty(
129
+ 'myPlugin.scratch'
130
+ );
131
+ });
132
+ });
133
+
134
+ describe('registerDefaults', () => {
135
+ it('registers built-in generators and is idempotent per editor', () => {
136
+ const sys = new System();
137
+ registerDefaults(sys);
138
+ const first = sys.socketGeneratorStore.getState().generators.length;
139
+ expect(first).toBeGreaterThan(0);
140
+
141
+ // Second call (e.g. another graph tab mounting) is a no-op.
142
+ expect(() => registerDefaults(sys)).not.toThrow();
143
+ expect(sys.socketGeneratorStore.getState().generators.length).toBe(first);
144
+ });
145
+
146
+ it('persists to and loads from a storage adapter', () => {
147
+ vi.useFakeTimers();
148
+ try {
149
+ const backing: Record<string, string> = {};
150
+ const storage = {
151
+ getItem: (k: string) => backing[k] ?? null,
152
+ setItem: (k: string, v: string) => {
153
+ backing[k] = v;
154
+ }
155
+ };
156
+
157
+ const a = new System();
158
+ a.enableSettingsPersistence(storage);
159
+ a.registerConversion({ from: 'float', to: 'string', nodeType: 'f/conv' });
160
+ a.systemSettings.getState().setShowGrid(false);
161
+ vi.advanceTimersByTime(400); // flush the debounced save
162
+
163
+ expect(Object.keys(backing).length).toBe(1);
164
+
165
+ // A fresh system loads the saved state on init.
166
+ const b = new System();
167
+ b.enableSettingsPersistence(storage);
168
+ expect(b.systemSettings.getState().showGrid).toBe(false);
169
+ expect(b.conversionStore.getState().conversions).toContainEqual({
170
+ from: 'float',
171
+ to: 'string',
172
+ nodeType: 'f/conv'
173
+ });
174
+ } finally {
175
+ vi.useRealTimers();
176
+ }
177
+ });
178
+ });
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { traceStoreFactory } from '../src/store/traces.js';
3
+ import type { TraceSpan } from '../src/store/traces.js';
4
+
5
+ const newStore = () => traceStoreFactory({} as any).getState();
6
+
7
+ const usedLanes = (spans: TraceSpan[], size: number): Set<number> =>
8
+ new Set(spans.slice(0, size).map((s) => s.lane));
9
+
10
+ describe('trace store lane allocation', () => {
11
+ it('reuses a lane for sequential spans (closed before the next opens)', () => {
12
+ const s = newStore();
13
+ // A opens and closes, then B opens and closes , never overlapping.
14
+ s.addSpan({ nodeId: 'a', name: 'a', start: 0, end: Number.NaN });
15
+ s.updateSpan('a', { end: 1 });
16
+ s.addSpan({ nodeId: 'b', name: 'b', start: 2, end: Number.NaN });
17
+ s.updateSpan('b', { end: 3 });
18
+
19
+ const c = s.collector;
20
+ expect(c.size).toBe(2);
21
+ // Both land in lane 0 → the panel shows a single lane.
22
+ expect(usedLanes(c.spans, c.size)).toEqual(new Set([0]));
23
+ });
24
+
25
+ it('allocates separate lanes for concurrent (overlapping) spans', () => {
26
+ const s = newStore();
27
+ // Both open at once (no end between them) → must not share a lane.
28
+ s.addSpan({ nodeId: 'a', name: 'a', start: 0, end: Number.NaN });
29
+ s.addSpan({ nodeId: 'b', name: 'b', start: 0, end: Number.NaN });
30
+
31
+ const c = s.collector;
32
+ expect(c.size).toBe(2);
33
+ expect(usedLanes(c.spans, c.size)).toEqual(new Set([0, 1]));
34
+ });
35
+
36
+ it('keeps an open span as open (NaN end) until updateSpan closes it', () => {
37
+ const s = newStore();
38
+ s.addSpan({ nodeId: 'a', name: 'a', start: 5, end: Number.NaN });
39
+
40
+ const open = s.collector.spans[0]!;
41
+ expect(Number.isNaN(open.end)).toBe(true);
42
+
43
+ s.updateSpan('a', { end: 9 });
44
+ expect(s.collector.spans[0]!.end).toBe(9);
45
+ });
46
+ });
@@ -32,7 +32,7 @@ pnpm test:visual:update
32
32
 
33
33
  ## How it works
34
34
 
35
- - Config: [`vitest.visual.config.ts`](../../vitest.visual.config.ts) browser
35
+ - Config: [`vitest.visual.config.ts`](../../vitest.visual.config.ts) , browser
36
36
  mode, Playwright/Chromium, headless, fixed `900x700` viewport.
37
37
  - Test files: `tests/visual/**/*.visual.test.tsx`.
38
38
  - Each panel is rendered inside a fixed-size `640x480` frame on the editor
@@ -60,5 +60,5 @@ When a new panel is added under `src/components/panels`, add one entry to the
60
60
  ```
61
61
 
62
62
  Then run `pnpm test:visual:update` to create the baseline, eyeball the generated
63
- PNG, and commit it. Keep it to a single representative snapshot per panel the
63
+ PNG, and commit it. Keep it to a single representative snapshot per panel , the
64
64
  unit tests under `tests/util` and `tests/components` cover finer-grained logic.
@@ -9,7 +9,6 @@ import { cleanup, render } from 'vitest-browser-react';
9
9
  import { DefaultSystemProvider } from '../../stories/defaults/defaultStoryProvider';
10
10
 
11
11
  import { AlignmentPanel } from '@/components/panels/alignment';
12
- import { ConversationPanel } from '@/components/panels/conversation';
13
12
  import { EventsPanel } from '@/components/panels/events';
14
13
  import { HistoryPanel } from '@/components/panels/history';
15
14
  import { KeymapsPanel } from '@/components/panels/keymaps';
@@ -23,6 +22,7 @@ import { SearchPanel } from '@/components/panels/search';
23
22
  import { Settings } from '@/components/panels/systemSettings';
24
23
  import { TracesPanel } from '@/components/panels/traces';
25
24
  import { VariablesPanel } from '@/components/panels/variables';
25
+ import { LocalGraphRunnerPanel } from '@/plugin/graphrunner-local';
26
26
 
27
27
  /**
28
28
  * One pixel-snapshot per panel. The shared {@link DefaultSystemProvider} (the
@@ -31,7 +31,6 @@ import { VariablesPanel } from '@/components/panels/variables';
31
31
  */
32
32
  const panels: ReadonlyArray<readonly [name: string, element: ReactElement]> = [
33
33
  ['alignment', <AlignmentPanel />],
34
- ['conversation', <ConversationPanel />],
35
34
  ['events', <EventsPanel />],
36
35
  ['history', <HistoryPanel />],
37
36
  ['keymaps', <KeymapsPanel />],
@@ -44,7 +43,8 @@ const panels: ReadonlyArray<readonly [name: string, element: ReactElement]> = [
44
43
  ['search', <SearchPanel />],
45
44
  ['systemSettings', <Settings />],
46
45
  ['traces', <TracesPanel />],
47
- ['variables', <VariablesPanel />]
46
+ ['variables', <VariablesPanel />],
47
+ ['localGraphRunner', <LocalGraphRunnerPanel />]
48
48
  ];
49
49
 
50
50
  afterEach(() => {
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isEventFromEditable } from '../src/hooks/useWasdPan.js';
3
+
4
+ const keydownOn = (element: HTMLElement): KeyboardEvent => {
5
+ document.body.appendChild(element);
6
+ let captured: KeyboardEvent | undefined;
7
+ element.addEventListener('keydown', (e) => {
8
+ captured = e;
9
+ });
10
+ element.dispatchEvent(
11
+ new KeyboardEvent('keydown', { key: 'w', bubbles: true })
12
+ );
13
+ element.remove();
14
+ if (!captured) throw new Error('keydown was not captured');
15
+ return captured;
16
+ };
17
+
18
+ describe('WASD pan editable guard', () => {
19
+ it('treats form fields as editable (no pan)', () => {
20
+ expect(
21
+ isEventFromEditable(keydownOn(document.createElement('input')))
22
+ ).toBe(true);
23
+ expect(
24
+ isEventFromEditable(keydownOn(document.createElement('textarea')))
25
+ ).toBe(true);
26
+ expect(
27
+ isEventFromEditable(keydownOn(document.createElement('select')))
28
+ ).toBe(true);
29
+ });
30
+
31
+ it('treats contenteditable divs (e.g. the note editor) as editable', () => {
32
+ const editor = document.createElement('div');
33
+ editor.contentEditable = 'true';
34
+ expect(isEventFromEditable(keydownOn(editor))).toBe(true);
35
+ });
36
+
37
+ it('treats inputs inside shadow DOM (e.g. vscode-textfield) as editable', () => {
38
+ // The conversation panel's chat input is a web component; at a window-level
39
+ // listener the event is retargeted to the host element, so the guard must
40
+ // look through composedPath. Simulate that with a shadow-DOM input and a
41
+ // document-level capture listener (composedPath is only populated during
42
+ // dispatch).
43
+ const host = document.createElement('x-textfield');
44
+ const shadow = host.attachShadow({ mode: 'open' });
45
+ const inner = document.createElement('input');
46
+ shadow.appendChild(inner);
47
+ document.body.appendChild(host);
48
+
49
+ let result: boolean | undefined;
50
+ const listener = (e: Event) => {
51
+ result = isEventFromEditable(e as KeyboardEvent);
52
+ };
53
+ document.addEventListener('keydown', listener, { capture: true });
54
+ inner.dispatchEvent(
55
+ new KeyboardEvent('keydown', { key: 'w', bubbles: true, composed: true })
56
+ );
57
+ document.removeEventListener('keydown', listener, { capture: true });
58
+ host.remove();
59
+
60
+ expect(result).toBe(true);
61
+ });
62
+
63
+ it('does not treat plain elements as editable (pan proceeds)', () => {
64
+ expect(isEventFromEditable(keydownOn(document.createElement('div')))).toBe(
65
+ false
66
+ );
67
+ expect(isEventFromEditable(keydownOn(document.createElement('span')))).toBe(
68
+ false
69
+ );
70
+ });
71
+ });
package/vitest.config.ts CHANGED
@@ -4,7 +4,7 @@ import { configDefaults, defineConfig } from 'vitest/config';
4
4
  export default defineConfig({
5
5
  test: {
6
6
  // Visual/pixel regression tests run in a real browser via the dedicated
7
- // vitest.visual.config.ts keep them out of the happy-dom unit run.
7
+ // vitest.visual.config.ts , keep them out of the happy-dom unit run.
8
8
  exclude: [...configDefaults.exclude, '**/*.visual.test.{ts,tsx}'],
9
9
  watch: false,
10
10
  environment: 'happy-dom'
@@ -40,6 +40,13 @@ export default defineConfig({
40
40
  }
41
41
  },
42
42
  resolve: {
43
+ // The browser runner renders through vitest-browser-react, which can pull
44
+ // its own React/React-DOM copy (a different patch than the app's pinned
45
+ // 19.2.3). Two React instances means the renderer sets the hook dispatcher
46
+ // on one copy while the panels call hooks on the other, producing "Invalid
47
+ // hook call / more than one copy of React" and a null dispatcher. Force a
48
+ // single instance so the renderer and the components share it.
49
+ dedupe: ['react', 'react-dom'],
43
50
  alias: {
44
51
  '~': path.resolve(__dirname, './src'),
45
52
  '@': path.resolve(__dirname, './src')