@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.
- package/.storybook/manager.ts +6 -0
- package/.storybook/preview.ts +49 -1
- package/.storybook/styles.css +9 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +368 -0
- package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
- package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
- package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
- package/dist/index.css +36 -33
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1865 -550
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14357 -11221
- package/dist/index.js.map +1 -1
- package/dist/noteImpl-KkrrWgJd.js +242 -0
- package/dist/noteImpl-KkrrWgJd.js.map +1 -0
- package/dist/styles.module-CvmpDkZj.css +3 -0
- package/dist/styles.module-CvmpDkZj.css.map +1 -0
- package/dist/styles.module-DZxg8aW9.js +271 -0
- package/dist/styles.module-DZxg8aW9.js.map +1 -0
- package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
- package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
- package/docs/protocol.md +43 -20
- package/package.json +5 -9
- package/src/components/FloatingToolbar/index.module.css +5 -13
- package/src/components/FloatingToolbar/index.tsx +9 -9
- package/src/components/Flow.tsx +34 -23
- package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
- package/src/components/contextMenus/NodePicker.module.css +13 -13
- package/src/components/contextMenus/edge.tsx +9 -95
- package/src/components/contextMenus/node.tsx +9 -149
- package/src/components/contextMenus/selection.tsx +5 -71
- package/src/components/controls/any/AnyControlImpl.tsx +14 -0
- package/src/components/controls/any/index.tsx +13 -2
- package/src/components/edges/index.tsx +75 -69
- package/src/components/layoutController/index.module.css +3 -0
- package/src/components/layoutController/index.tsx +24 -1
- package/src/components/layoutController/utils.ts +46 -3
- package/src/components/menubar/defaults.tsx +55 -19
- package/src/components/menubar/menuItem.module.css +18 -3
- package/src/components/menubar/menuItem.tsx +34 -1
- package/src/components/nodes/behave/NodeContainer.module.css +26 -25
- package/src/components/nodes/group/index.tsx +3 -3
- package/src/components/nodes/wrapper/styles.module.css +6 -32
- package/src/components/panels/alignment/index.module.css +0 -10
- package/src/components/panels/alignment/index.tsx +4 -4
- package/src/components/panels/base/styles.module.css +2 -2
- package/src/components/panels/common/PanelHeader.module.css +24 -0
- package/src/components/panels/common/PanelHeader.tsx +22 -0
- package/src/components/panels/common/SectionTitle.module.css +13 -0
- package/src/components/panels/common/SectionTitle.tsx +10 -0
- package/src/components/panels/events/EditEventPanel.tsx +14 -5
- package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
- package/src/components/panels/events/styles.module.css +6 -64
- package/src/components/panels/graphProperties/index.tsx +125 -0
- package/src/components/panels/history/index.tsx +2 -2
- package/src/components/panels/history/styles.module.css +0 -9
- package/src/components/panels/keymaps/index.module.css +3 -13
- package/src/components/panels/keymaps/index.tsx +1 -2
- package/src/components/panels/layers/index.tsx +20 -15
- package/src/components/panels/layers/styles.module.css +9 -12
- package/src/components/panels/legend/index.tsx +1 -1
- package/src/components/panels/logs/index.module.css +25 -19
- package/src/components/panels/logs/index.tsx +7 -7
- package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
- package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
- package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
- package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
- package/src/components/panels/nodeInputs/index.module.css +99 -75
- package/src/components/panels/nodeInputs/index.tsx +21 -11
- package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
- package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
- package/src/components/panels/nodePicker/index.tsx +8 -8
- package/src/components/panels/panel/index.module.css +7 -7
- package/src/components/panels/search/index.module.css +0 -50
- package/src/components/panels/search/index.tsx +2 -2
- package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
- package/src/components/panels/systemSettings/index.tsx +221 -176
- package/src/components/panels/systemSettings/styles.module.css +135 -8
- package/src/components/panels/traces/GridLines.tsx +1 -1
- package/src/components/panels/traces/TimeGrid.tsx +3 -3
- package/src/components/panels/traces/TraceLane.tsx +1 -1
- package/src/components/panels/traces/index.module.css +1 -8
- package/src/components/panels/traces/index.tsx +8 -4
- package/src/components/panels/traces/useDerivedSpans.ts +241 -146
- package/src/components/panels/traces/utils.ts +8 -0
- package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
- package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
- package/src/components/panels/variables/index.tsx +2 -2
- package/src/components/panels/variables/styles.module.css +4 -91
- package/src/components/primitives/icon.module.css +4 -4
- package/src/components/sockets/input/index.tsx +9 -2
- package/src/components/sockets/input/styles.module.css +2 -3
- package/src/components/sockets/output/index.tsx +10 -3
- package/src/components/sockets/output/styles.module.css +1 -6
- package/src/css/notes.css +135 -0
- package/src/css/prosemirror.css +3 -3
- package/src/css/rc-dock.css +143 -43
- package/src/css/rc-menu.css +56 -55
- package/src/css/themes/kiberon.css +127 -0
- package/src/css/vars.css +197 -13
- package/src/css/vscode-elements.css +124 -0
- package/src/generators/CallSubgraphGenerator.tsx +136 -0
- package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
- package/src/generators/GraphBoundaryGenerator.module.css +32 -0
- package/src/generators/GraphBoundaryGenerator.tsx +193 -0
- package/src/generators/SequenceGenerator.tsx +2 -2
- package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
- package/src/generators/SwitchOnStringGenerator.tsx +2 -2
- package/src/generators/callSubgraphSync.ts +126 -0
- package/src/generators/registerDefaultGenerators.ts +21 -0
- package/src/generators/registerDefaults.ts +26 -0
- package/src/hooks/useBehaveGraphFlow.ts +2 -2
- package/src/hooks/useFlowHandlers.ts +47 -9
- package/src/hooks/useWasdPan.ts +26 -4
- package/src/index.css +4 -16
- package/src/index.ts +17 -0
- package/src/manifest/contributionRegistry.ts +93 -0
- package/src/manifest/index.ts +4 -0
- package/src/manifest/loadManifest.ts +82 -0
- package/src/manifest/manifestPlugin.ts +29 -0
- package/src/manifest/passthroughValueType.ts +40 -0
- package/src/plugin/alignment/index.ts +22 -12
- package/src/plugin/autosave/controller.ts +366 -0
- package/src/plugin/autosave/index.tsx +114 -0
- package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
- package/src/plugin/autosave/panel/index.tsx +1 -0
- package/src/plugin/autosave/panel/styles.module.css +56 -0
- package/src/plugin/autosave/settings.ts +65 -0
- package/src/plugin/autosave/storage.ts +147 -0
- package/src/plugin/docs/index.tsx +2 -4
- package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
- package/src/plugin/docs/panel/index.tsx +15 -194
- package/src/plugin/docs/panel/styles.module.css +8 -8
- package/src/plugin/graphrunner/actions.ts +258 -185
- package/src/plugin/graphrunner/buttons.tsx +34 -26
- package/src/plugin/graphrunner/client.ts +4 -1
- package/src/plugin/graphrunner/index.tsx +29 -100
- package/src/plugin/graphrunner/panel.tsx +2 -2
- package/src/plugin/graphrunner/runController.ts +283 -0
- package/src/plugin/graphrunner/runner.ts +21 -192
- package/src/plugin/graphrunner/store.ts +14 -24
- package/src/plugin/graphrunner/styles.module.css +17 -57
- package/src/plugin/graphrunner/transport.ts +26 -0
- package/src/plugin/graphrunner/types.ts +21 -0
- package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
- package/src/plugin/graphrunner-local/index.tsx +8 -2
- package/src/plugin/graphrunner-local/panel.tsx +131 -175
- package/src/plugin/graphrunner-local/styles.module.css +57 -76
- package/src/plugin/graphrunner-local/transport.ts +151 -184
- package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
- package/src/plugin/graphrunner-webworker/index.tsx +4 -10
- package/src/plugin/graphrunner-webworker/store.ts +9 -0
- package/src/plugin/kitchen-sink/index.ts +38 -0
- package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
- package/src/{layout → plugin/layout}/elk.ts +22 -6
- package/src/plugin/layout/index.ts +80 -0
- package/src/plugin/notes/FormatToolbar.tsx +200 -0
- package/src/plugin/notes/index.tsx +191 -0
- package/src/plugin/notes/nodeActions.ts +100 -0
- package/src/plugin/notes/note.tsx +20 -0
- package/src/plugin/notes/noteImpl.tsx +89 -0
- package/src/plugin/realtime/realtimeRunner.ts +58 -4
- package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
- package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
- package/src/specifics/VariableGetSpecific.tsx +2 -2
- package/src/specifics/VariableSetSpecific.tsx +2 -2
- package/src/store/actions.tsx +5 -5
- package/src/store/commands.ts +278 -0
- package/src/store/contextMenu.ts +192 -0
- package/src/store/conversions.ts +47 -0
- package/src/store/flow.tsx +23 -38
- package/src/store/graphMeta.ts +39 -0
- package/src/store/hotKeys.tsx +301 -260
- package/src/store/layers.ts +3 -3
- package/src/store/registry.ts +12 -4
- package/src/store/selection.ts +3 -3
- package/src/store/settings.ts +82 -82
- package/src/store/settingsSchema.ts +210 -0
- package/src/store/tabs.ts +5 -1
- package/src/store/traces.ts +3 -3
- package/src/system/graph.ts +11 -14
- package/src/system/graphSession.ts +172 -0
- package/src/system/index.ts +3 -0
- package/src/system/notifications.ts +13 -0
- package/src/system/persistence.ts +82 -0
- package/src/system/plugin.ts +28 -0
- package/src/system/provider.tsx +64 -0
- package/src/system/system.ts +518 -88
- package/src/system/tabLoader.tsx +70 -32
- package/src/system/undoRedo.ts +1 -1
- package/src/transformers/Uigraph.ts +5 -4
- package/src/transformers/contract.ts +87 -0
- package/src/transformers/flowToBehave.ts +13 -5
- package/src/types/nodes.ts +8 -3
- package/src/types.ts +2 -0
- package/src/util/autoConvert.ts +200 -0
- package/src/util/isValidConnection.ts +23 -2
- package/stories/defaults/defaultStoryProvider.tsx +17 -14
- package/stories/defaults/systemGenerator.ts +6 -1
- package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
- package/tests/autoConvert.test.ts +329 -0
- package/tests/autosavePlugin.test.ts +204 -0
- package/tests/callSubgraphSync.test.ts +148 -0
- package/tests/commandRegistry.test.ts +137 -0
- package/tests/contract.test.ts +51 -0
- package/tests/contractSerialize.test.ts +62 -0
- package/tests/deriveSpans.test.ts +71 -0
- package/tests/flowToBehave.test.ts +2 -1
- package/tests/hotkeys.test.ts +79 -0
- package/tests/keepAliveLifecycle.test.ts +167 -0
- package/tests/loadManifest.test.ts +113 -0
- package/tests/noteMarkdown.test.ts +65 -0
- package/tests/notesPlugin.test.ts +162 -0
- package/tests/persistence.test.ts +51 -0
- package/tests/saveLoad.test.ts +7 -6
- package/tests/settings.test.ts +178 -0
- package/tests/traceStore.test.ts +46 -0
- package/tests/visual/README.md +2 -2
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
- package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
- package/tests/visual/panels.visual.test.tsx +3 -3
- package/tests/wasdPan.test.ts +71 -0
- package/vitest.config.ts +1 -1
- package/vitest.visual.config.ts +7 -0
- package/.storybook/vscode.css +0 -814
- package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
- package/src/components/nodes/comment/comment.tsx +0 -103
- package/src/components/nodes/comment/styles.module.css +0 -150
- package/src/components/panels/conversation/index.module.css +0 -151
- package/src/components/panels/conversation/index.tsx +0 -162
- package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
- package/src/css/vscode.css +0 -13
- package/src/hooks/useDetachNodes.ts +0 -39
- package/src/plugin/graphrunner-webworker/types.ts +0 -17
- package/src/specifics/registerDefaultSpecifics.ts +0 -5
- package/src/store/chat.ts +0 -73
- package/src/store/graphRunnerClient.ts +0 -110
|
@@ -0,0 +1,148 @@
|
|
|
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 { setupCallSubgraphSync } from '../src/generators/callSubgraphSync.js';
|
|
9
|
+
|
|
10
|
+
const buildSystem = () => {
|
|
11
|
+
const registry = registerCoreProfile({
|
|
12
|
+
nodes: {},
|
|
13
|
+
values: {},
|
|
14
|
+
dependencies: {} as any
|
|
15
|
+
});
|
|
16
|
+
const specs = writeNodeSpecsToJSON(registry);
|
|
17
|
+
return new System({ values: registry.values, specs });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const outputBoundary = (
|
|
21
|
+
id: string,
|
|
22
|
+
params: Array<{ id?: string; name: string; valueTypeName: string }>
|
|
23
|
+
): Node => ({
|
|
24
|
+
id,
|
|
25
|
+
type: 'behaveNode',
|
|
26
|
+
position: { x: 0, y: 0 },
|
|
27
|
+
data: {
|
|
28
|
+
type: 'graph/output',
|
|
29
|
+
configuration: { parameters: params },
|
|
30
|
+
ports: {}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const callNode = (id: string, subgraphId: string): Node => ({
|
|
35
|
+
id,
|
|
36
|
+
type: 'behaveNode',
|
|
37
|
+
position: { x: 0, y: 0 },
|
|
38
|
+
data: { type: 'flow/callSubgraph', configuration: { subgraphId }, ports: {} }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const getNode = (session: { nodeStore: any }, id: string): Node =>
|
|
42
|
+
session.nodeStore.getState().nodes.find((n: Node) => n.id === id);
|
|
43
|
+
|
|
44
|
+
describe('call subgraph contract sync', () => {
|
|
45
|
+
it('updates a call node when the referenced subgraph contract changes after creation', () => {
|
|
46
|
+
const system = buildSystem();
|
|
47
|
+
const caller = system.createSession('caller');
|
|
48
|
+
const dispose = setupCallSubgraphSync(system);
|
|
49
|
+
|
|
50
|
+
// `sub` is created AFTER the sync is registered , exercises the session
|
|
51
|
+
// extension applying to future graphs, not just existing ones.
|
|
52
|
+
const sub = system.createSession('sub');
|
|
53
|
+
|
|
54
|
+
// A call node referencing `sub`, created while `sub` has no outputs yet.
|
|
55
|
+
caller.nodeStore.getState().setNodes(() => [callNode('call1', sub.id)]);
|
|
56
|
+
expect(getNode(caller, 'call1').data.configuration.outputs ?? []).toEqual(
|
|
57
|
+
[]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Now author the subgraph's output contract.
|
|
61
|
+
sub.nodeStore
|
|
62
|
+
.getState()
|
|
63
|
+
.setNodes(() => [
|
|
64
|
+
outputBoundary('out', [
|
|
65
|
+
{ id: 'o1', name: 'result', valueTypeName: 'float' }
|
|
66
|
+
])
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
// The call node should have picked up the new contract reactively , no
|
|
70
|
+
// re-selection required.
|
|
71
|
+
const synced = getNode(caller, 'call1');
|
|
72
|
+
expect(synced.data.configuration.outputs).toEqual([
|
|
73
|
+
{ id: 'o1', name: 'result', valueTypeName: 'float' }
|
|
74
|
+
]);
|
|
75
|
+
expect(synced.data.dynamicPorts.outputs).toEqual([
|
|
76
|
+
{ name: 'o1', key: 'o1', label: 'result', valueType: 'float' }
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
// Rename the param: the call node tracks the change (stable id, new label).
|
|
80
|
+
sub.nodeStore
|
|
81
|
+
.getState()
|
|
82
|
+
.setNodes(() => [
|
|
83
|
+
outputBoundary('out', [
|
|
84
|
+
{ id: 'o1', name: 'renamed', valueTypeName: 'float' }
|
|
85
|
+
])
|
|
86
|
+
]);
|
|
87
|
+
expect(getNode(caller, 'call1').data.configuration.outputs).toEqual([
|
|
88
|
+
{ id: 'o1', name: 'renamed', valueTypeName: 'float' }
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
dispose();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('does not rewrite call nodes on non-contract edits (e.g. dragging)', () => {
|
|
95
|
+
const system = buildSystem();
|
|
96
|
+
const caller = system.createSession('caller');
|
|
97
|
+
const sub = system.createSession('sub');
|
|
98
|
+
const dispose = setupCallSubgraphSync(system);
|
|
99
|
+
|
|
100
|
+
sub.nodeStore
|
|
101
|
+
.getState()
|
|
102
|
+
.setNodes(() => [
|
|
103
|
+
outputBoundary('out', [
|
|
104
|
+
{ id: 'o1', name: 'result', valueTypeName: 'float' }
|
|
105
|
+
])
|
|
106
|
+
]);
|
|
107
|
+
caller.nodeStore.getState().setNodes(() => [callNode('call1', sub.id)]);
|
|
108
|
+
|
|
109
|
+
const before = getNode(caller, 'call1');
|
|
110
|
+
// A position-only change to the subgraph must not churn the call node.
|
|
111
|
+
sub.nodeStore
|
|
112
|
+
.getState()
|
|
113
|
+
.setNodes((prev: Node[]) =>
|
|
114
|
+
prev.map((n) =>
|
|
115
|
+
n.id === 'out' ? { ...n, position: { x: 50, y: 50 } } : n
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
const after = getNode(caller, 'call1');
|
|
119
|
+
|
|
120
|
+
// Same object identity ⇒ the call node was not rewritten.
|
|
121
|
+
expect(after).toBe(before);
|
|
122
|
+
|
|
123
|
+
dispose();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('stops syncing after dispose', () => {
|
|
127
|
+
const system = buildSystem();
|
|
128
|
+
const caller = system.createSession('caller');
|
|
129
|
+
const sub = system.createSession('sub');
|
|
130
|
+
const dispose = setupCallSubgraphSync(system);
|
|
131
|
+
|
|
132
|
+
caller.nodeStore.getState().setNodes(() => [callNode('call1', sub.id)]);
|
|
133
|
+
dispose();
|
|
134
|
+
|
|
135
|
+
sub.nodeStore
|
|
136
|
+
.getState()
|
|
137
|
+
.setNodes(() => [
|
|
138
|
+
outputBoundary('out', [
|
|
139
|
+
{ id: 'o1', name: 'result', valueTypeName: 'float' }
|
|
140
|
+
])
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// No sync after dispose ⇒ contract not propagated.
|
|
144
|
+
expect(getNode(caller, 'call1').data.configuration.outputs ?? []).toEqual(
|
|
145
|
+
[]
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -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, 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
|
+
});
|
|
@@ -26,7 +26,8 @@ it('transforms from flow to behave', () => {
|
|
|
26
26
|
specs: specJSON
|
|
27
27
|
};
|
|
28
28
|
const system = new System(registry);
|
|
29
|
-
const
|
|
29
|
+
const session = system.createSession('graph');
|
|
30
|
+
const output = flowToBehave(session, nodes, edges, specJSON);
|
|
30
31
|
|
|
31
32
|
// Remove position metadata from expected graph since we no longer include it
|
|
32
33
|
const expectedGraph = {
|
|
@@ -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
|
+
});
|