@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,203 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { useStore } from 'zustand';
3
+ import {
4
+ VscodeButton,
5
+ VscodeOption,
6
+ VscodeSingleSelect
7
+ } from '@vscode-elements/react-elements';
8
+ import { Trash } from 'iconoir-react';
9
+ import { useSystem } from '@/system/provider';
10
+ import styles from './styles.module.css';
11
+
12
+ /**
13
+ * Editor for the custom automatic type conversions used by auto-convert. Rules
14
+ * defined here override the generic spec-derived defaults and are persisted with
15
+ * the editor settings.
16
+ *
17
+ * A rule pins the converter node AND the specific ports to wire: which input
18
+ * receives the `from` value and which output produces the `to` value. The port
19
+ * choices are filtered to type-compatible sockets, so converter nodes with more
20
+ * than one input/output resolve unambiguously.
21
+ */
22
+ export const ConversionsSettings: React.FC = () => {
23
+ const sys = useSystem();
24
+ const values = useStore(sys.registry, (s) => s.values);
25
+ const specs = useStore(sys.specStore, (s) => s.specs);
26
+ const conversions = useStore(sys.conversionStore, (s) => s.conversions);
27
+
28
+ const valueTypes = useMemo(
29
+ () => Object.keys(values).filter((t) => t !== 'flow'),
30
+ [values]
31
+ );
32
+ const nodeTypes = useMemo(() => specs.map((s) => s.type).sort(), [specs]);
33
+
34
+ const [from, setFrom] = useState('');
35
+ const [to, setTo] = useState('');
36
+ const [nodeType, setNodeType] = useState('');
37
+ const [inputKey, setInputKey] = useState('');
38
+ const [outputKey, setOutputKey] = useState('');
39
+
40
+ const selectedSpec = useMemo(
41
+ () => specs.find((s) => s.type === nodeType),
42
+ [specs, nodeType]
43
+ );
44
+
45
+ // Only ports whose type matches the conversion endpoints are valid targets:
46
+ // the spliced node must accept `from` and emit `to`.
47
+ const inputPorts = useMemo(
48
+ () =>
49
+ (selectedSpec?.inputs ?? []).filter(
50
+ (i) => i.valueType !== 'flow' && (!from || i.valueType === from)
51
+ ),
52
+ [selectedSpec, from]
53
+ );
54
+ const outputPorts = useMemo(
55
+ () =>
56
+ (selectedSpec?.outputs ?? []).filter(
57
+ (o) => o.valueType !== 'flow' && (!to || o.valueType === to)
58
+ ),
59
+ [selectedSpec, to]
60
+ );
61
+
62
+ // Default the port selection to the first compatible socket whenever the node
63
+ // or endpoints change; the user can still override when several ports match.
64
+ useEffect(() => {
65
+ setInputKey(inputPorts[0]?.name ?? '');
66
+ }, [inputPorts]);
67
+ useEffect(() => {
68
+ setOutputKey(outputPorts[0]?.name ?? '');
69
+ }, [outputPorts]);
70
+
71
+ const nodeChosen = Boolean(nodeType);
72
+ const portsMissing =
73
+ nodeChosen && (inputPorts.length === 0 || outputPorts.length === 0);
74
+ const canAdd =
75
+ Boolean(from && to && nodeType && inputKey && outputKey) && from !== to;
76
+
77
+ const add = () => {
78
+ if (!canAdd) return;
79
+ sys.conversionStore
80
+ .getState()
81
+ .registerConversion({ from, to, nodeType, inputKey, outputKey });
82
+ setFrom('');
83
+ setTo('');
84
+ setNodeType('');
85
+ setInputKey('');
86
+ setOutputKey('');
87
+ };
88
+
89
+ return (
90
+ <div className={styles.conversions}>
91
+ <span className={styles.conversionsLabel}>
92
+ Custom rules override the built-in defaults.
93
+ </span>
94
+ {conversions.length === 0 && (
95
+ <div className={styles.empty}>No custom conversions.</div>
96
+ )}
97
+ {conversions.map((rule) => (
98
+ <div key={`${rule.from}->${rule.to}`} className={styles.rule}>
99
+ <span className={styles.ruleText}>
100
+ {rule.from} → {rule.to}{' '}
101
+ <span className={styles.ruleNode}>
102
+ ({rule.nodeType}
103
+ {rule.inputKey && rule.outputKey
104
+ ? `: ${rule.inputKey} → ${rule.outputKey}`
105
+ : ''}
106
+ )
107
+ </span>
108
+ </span>
109
+ <VscodeButton
110
+ secondary
111
+ iconOnly
112
+ title="Remove"
113
+ onClick={() =>
114
+ sys.conversionStore
115
+ .getState()
116
+ .removeConversion(rule.from, rule.to)
117
+ }
118
+ >
119
+ <Trash />
120
+ </VscodeButton>
121
+ </div>
122
+ ))}
123
+
124
+ <div className={styles.addForm}>
125
+ <VscodeSingleSelect
126
+ value={from}
127
+ onChange={(e: any) => setFrom(String(e?.target?.value ?? ''))}
128
+ >
129
+ <VscodeOption value="">from…</VscodeOption>
130
+ {valueTypes.map((t) => (
131
+ <VscodeOption key={t} value={t}>
132
+ {t}
133
+ </VscodeOption>
134
+ ))}
135
+ </VscodeSingleSelect>
136
+ <VscodeSingleSelect
137
+ value={to}
138
+ onChange={(e: any) => setTo(String(e?.target?.value ?? ''))}
139
+ >
140
+ <VscodeOption value="">to…</VscodeOption>
141
+ {valueTypes.map((t) => (
142
+ <VscodeOption key={t} value={t}>
143
+ {t}
144
+ </VscodeOption>
145
+ ))}
146
+ </VscodeSingleSelect>
147
+ <VscodeSingleSelect
148
+ value={nodeType}
149
+ onChange={(e: any) => setNodeType(String(e?.target?.value ?? ''))}
150
+ >
151
+ <VscodeOption value="">converter node…</VscodeOption>
152
+ {nodeTypes.map((t) => (
153
+ <VscodeOption key={t} value={t}>
154
+ {t}
155
+ </VscodeOption>
156
+ ))}
157
+ </VscodeSingleSelect>
158
+
159
+ {nodeChosen && !portsMissing && (
160
+ <>
161
+ <VscodeSingleSelect
162
+ value={inputKey}
163
+ onChange={(e: any) => setInputKey(String(e?.target?.value ?? ''))}
164
+ >
165
+ <VscodeOption value="">input port…</VscodeOption>
166
+ {inputPorts.map((p) => (
167
+ <VscodeOption key={p.name} value={p.name}>
168
+ {p.name} ({p.valueType})
169
+ </VscodeOption>
170
+ ))}
171
+ </VscodeSingleSelect>
172
+ <VscodeSingleSelect
173
+ value={outputKey}
174
+ onChange={(e: any) =>
175
+ setOutputKey(String(e?.target?.value ?? ''))
176
+ }
177
+ >
178
+ <VscodeOption value="">output port…</VscodeOption>
179
+ {outputPorts.map((p) => (
180
+ <VscodeOption key={p.name} value={p.name}>
181
+ {p.name} ({p.valueType})
182
+ </VscodeOption>
183
+ ))}
184
+ </VscodeSingleSelect>
185
+ </>
186
+ )}
187
+
188
+ {portsMissing && (
189
+ <span className={styles.empty}>
190
+ “{nodeType}” has no {inputPorts.length === 0 ? `${from} input` : ''}
191
+ {inputPorts.length === 0 && outputPorts.length === 0 ? ' / ' : ''}
192
+ {outputPorts.length === 0 ? `${to} output` : ''} , not a valid
193
+ converter for {from} → {to}.
194
+ </span>
195
+ )}
196
+
197
+ <VscodeButton secondary disabled={!canAdd} onClick={add}>
198
+ + Add conversion
199
+ </VscodeButton>
200
+ </div>
201
+ </div>
202
+ );
203
+ };
@@ -1,206 +1,251 @@
1
- import { useSystem } from '@/system/provider';
2
- import {
3
- EDGE_TYPE,
4
- LAYOUT_TYPE,
5
- type EdgeType,
6
- type LayoutType
7
- } from '@/store/settings';
8
-
1
+ import { Fragment, useMemo, useState } from 'react';
2
+ import { useStore } from 'zustand';
9
3
  import {
10
4
  VscodeCheckbox,
11
5
  VscodeDivider,
12
- VscodeLabel,
13
6
  VscodeOption,
14
- VscodeSingleSelect
7
+ VscodeSingleSelect,
8
+ VscodeTextfield
15
9
  } from '@vscode-elements/react-elements';
16
- import { useStore } from 'zustand';
10
+ import { Undo } from 'iconoir-react';
11
+ import { useSystem } from '@/system/provider';
12
+ import type { SettingDescriptor, SettingsValues } from '@/store/settingsSchema';
17
13
  import styles from './styles.module.css';
18
14
  import { BasePanel } from '../base';
15
+ import { SectionTitle } from '../common/SectionTitle';
16
+ import { ConversionsSettings } from './ConversionsSettings';
19
17
 
20
- const EdgeValues = Object.values(EDGE_TYPE);
21
- const LayoutValues = Object.values(LAYOUT_TYPE);
22
-
23
- const SectionTitle = ({ children }: { children: React.ReactNode }) => {
24
- return <div className={styles.title}>{children}</div>;
18
+ type RowProps = {
19
+ descriptor: SettingDescriptor;
20
+ value: unknown;
21
+ setValue: (value: unknown) => void;
25
22
  };
26
23
 
27
- const Description = ({ children }: { children: React.ReactNode }) => {
28
- return (
29
- <div
30
- style={{
31
- fontSize: '0.85em',
32
- color: 'var(--vscode-descriptionForeground)',
33
- opacity: 0.9
34
- }}
35
- >
36
- {children}
37
- </div>
38
- );
24
+ /** The input control for a descriptor, chosen by its `type`. */
25
+ const SettingControl = ({ descriptor, value, setValue }: RowProps) => {
26
+ switch (descriptor.type) {
27
+ case 'boolean':
28
+ return (
29
+ <VscodeCheckbox
30
+ checked={Boolean(value)}
31
+ onChange={(e: any) => setValue(Boolean(e?.target?.checked))}
32
+ />
33
+ );
34
+ case 'enum':
35
+ return (
36
+ <VscodeSingleSelect
37
+ value={String(value ?? '')}
38
+ onChange={(e: any) => {
39
+ const next = e?.target?.value as string | undefined;
40
+ if (next !== undefined) setValue(next);
41
+ }}
42
+ >
43
+ {descriptor.options.map((option) => (
44
+ <VscodeOption key={option.value} value={option.value}>
45
+ {option.label}
46
+ </VscodeOption>
47
+ ))}
48
+ </VscodeSingleSelect>
49
+ );
50
+ case 'number':
51
+ return (
52
+ <VscodeTextfield
53
+ type="number"
54
+ value={String(value ?? '')}
55
+ min={descriptor.min}
56
+ max={descriptor.max}
57
+ step={descriptor.step}
58
+ style={{ width: '100%' }}
59
+ onChange={(e: any) => {
60
+ const next = Number(e?.target?.value);
61
+ if (!Number.isNaN(next)) setValue(next);
62
+ }}
63
+ />
64
+ );
65
+ case 'string':
66
+ return (
67
+ <VscodeTextfield
68
+ value={String(value ?? '')}
69
+ placeholder={descriptor.placeholder}
70
+ style={{ width: '100%' }}
71
+ onChange={(e: any) => setValue(String(e?.target?.value ?? ''))}
72
+ />
73
+ );
74
+ case 'custom': {
75
+ const Render = descriptor.render;
76
+ return (
77
+ <Render value={value} setValue={setValue} descriptor={descriptor} />
78
+ );
79
+ }
80
+ }
39
81
  };
40
82
 
41
- const SelectSetting = ({
42
- label,
43
- description,
44
- value,
45
- onChange,
46
- children
83
+ const ResetButton = ({ onClick }: { onClick: () => void }) => (
84
+ <button
85
+ type="button"
86
+ className={styles.reset}
87
+ title="Reset to default"
88
+ aria-label="Reset to default"
89
+ onClick={onClick}
90
+ >
91
+ <Undo width={13} height={13} />
92
+ </button>
93
+ );
94
+
95
+ const Label = ({
96
+ descriptor,
97
+ modified
47
98
  }: {
48
- label: string;
49
- description: React.ReactNode;
50
- value: string;
51
- onChange: (value: string) => void;
52
- children: React.ReactNode;
53
- }) => {
54
- return (
55
- <div className="flex flex-col gap-1 justify-start">
56
- <VscodeLabel>{label}</VscodeLabel>
57
- <Description>{description}</Description>
58
- <VscodeSingleSelect
59
- value={value}
60
- onChange={(e: any) => {
61
- const next = e?.target?.value as string | undefined;
62
- if (next) onChange(next);
63
- }}
64
- >
65
- {children}
66
- </VscodeSingleSelect>
67
- </div>
68
- );
69
- };
99
+ descriptor: SettingDescriptor;
100
+ modified: boolean;
101
+ }) => (
102
+ <span className={styles.label}>
103
+ {descriptor.title}
104
+ {modified && <span className={styles.modified} title="Modified" />}
105
+ </span>
106
+ );
107
+
108
+ /** A single auto-generated setting row. Booleans put the control inline with the
109
+ * label (toggle layout); other types stack the control under the label. */
110
+ const SettingRow = ({ descriptor, value, setValue }: RowProps) => {
111
+ const modified = descriptor.type !== 'custom' && value !== descriptor.default;
112
+ const reset = () => setValue(descriptor.default);
113
+
114
+ if (descriptor.type === 'custom') {
115
+ return (
116
+ <div className={styles.setting}>
117
+ {descriptor.title && (
118
+ <span className={styles.label}>{descriptor.title}</span>
119
+ )}
120
+ {descriptor.description && (
121
+ <span className={styles.description}>{descriptor.description}</span>
122
+ )}
123
+ <SettingControl
124
+ descriptor={descriptor}
125
+ value={value}
126
+ setValue={setValue}
127
+ />
128
+ </div>
129
+ );
130
+ }
131
+
132
+ if (descriptor.type === 'boolean') {
133
+ return (
134
+ <div className={`${styles.setting} ${styles.toggle}`}>
135
+ <SettingControl
136
+ descriptor={descriptor}
137
+ value={value}
138
+ setValue={setValue}
139
+ />
140
+ <div className={styles.body}>
141
+ <Label descriptor={descriptor} modified={modified} />
142
+ {descriptor.description && (
143
+ <span className={styles.description}>{descriptor.description}</span>
144
+ )}
145
+ </div>
146
+ {modified && <ResetButton onClick={reset} />}
147
+ </div>
148
+ );
149
+ }
70
150
 
71
- const ToggleSetting = ({
72
- label,
73
- description,
74
- checked,
75
- onChange
76
- }: {
77
- label: string;
78
- description: React.ReactNode;
79
- checked: boolean;
80
- onChange: (checked: boolean) => void;
81
- }) => {
82
151
  return (
83
- <div className="flex gap-2 justify-start">
84
- <VscodeCheckbox
85
- toggle
86
- checked={checked}
87
- onChange={(event: any) => onChange(Boolean(event?.target?.checked))}
88
- />
89
- <div className="flex flex-col justify-start gap-0.5">
90
- <VscodeLabel>{label}</VscodeLabel>
91
- <Description>{description}</Description>
152
+ <div className={styles.setting}>
153
+ <div className={styles.labelRow}>
154
+ <Label descriptor={descriptor} modified={modified} />
155
+ {modified && <ResetButton onClick={reset} />}
92
156
  </div>
157
+ {descriptor.description && (
158
+ <span className={styles.description}>{descriptor.description}</span>
159
+ )}
160
+ <SettingControl
161
+ descriptor={descriptor}
162
+ value={value}
163
+ setValue={setValue}
164
+ />
93
165
  </div>
94
166
  );
95
167
  };
96
168
 
169
+ /**
170
+ * Schema-driven Settings panel. Rows are auto-generated from the descriptor
171
+ * registry (`system.settingsSchema`) — built-in and plugin-contributed settings
172
+ * render through the same path — grouped by section, filterable, with reset.
173
+ */
97
174
  export const Settings = () => {
98
175
  const system = useSystem();
99
-
100
- const settings = useStore(system.systemSettings);
176
+ const descriptors = useStore(system.settingsSchema, (s) => s.settings);
177
+ const sectionOrder = useStore(system.settingsSchema, (s) => s.sectionOrder);
178
+ const values = useStore(system.systemSettings) as SettingsValues & {
179
+ setSetting: (key: string, value: unknown) => void;
180
+ };
181
+ const [query, setQuery] = useState('');
182
+
183
+ const bySection = useMemo(() => {
184
+ const q = query.trim().toLowerCase();
185
+ const matches = (d: SettingDescriptor) =>
186
+ !q ||
187
+ [d.title, d.description, d.section, d.key].some(
188
+ (field) => typeof field === 'string' && field.toLowerCase().includes(q)
189
+ );
190
+ const visible = (d: SettingDescriptor) => (d.when ? d.when(values) : true);
191
+
192
+ const map = new Map<string, SettingDescriptor[]>();
193
+ for (const descriptor of descriptors) {
194
+ if (!matches(descriptor) || !visible(descriptor)) continue;
195
+ const rows = map.get(descriptor.section) ?? [];
196
+ rows.push(descriptor);
197
+ map.set(descriptor.section, rows);
198
+ }
199
+ for (const rows of map.values()) {
200
+ rows.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
201
+ }
202
+ return map;
203
+ }, [descriptors, query, values]);
204
+
205
+ const visibleSections = sectionOrder.filter(
206
+ (section) => (bySection.get(section)?.length ?? 0) > 0
207
+ );
101
208
 
102
209
  return (
103
210
  <BasePanel>
104
- <div className="flex flex-col gap-3">
105
- <SectionTitle>Layout</SectionTitle>
106
- <SelectSetting
107
- label="Edge Type"
108
- description="Select the type of edge to use in the graph editor."
109
- value={settings.edgeType}
110
- onChange={(value) => settings.setEdgeType(value as EdgeType)}
111
- >
112
- <VscodeOption value="">Select an edge type...</VscodeOption>
113
- {EdgeValues.map((type) => (
114
- <VscodeOption key={type} value={type}>
115
- {type}
116
- </VscodeOption>
117
- ))}
118
- </SelectSetting>
119
-
120
- <SelectSetting
121
- label="Layout Type"
122
- description="Select the type of layout engine to use in the graph editor."
123
- value={settings.layoutType}
124
- onChange={(value) => settings.setLayoutType(value as LayoutType)}
125
- >
126
- <VscodeOption value="">Select a layout type...</VscodeOption>
127
- {LayoutValues.map((type) => (
128
- <VscodeOption key={type} value={type}>
129
- {type}
130
- </VscodeOption>
131
- ))}
132
- </SelectSetting>
133
-
134
- <VscodeDivider />
135
-
136
- <SectionTitle>Accessibility</SectionTitle>
137
- <ToggleSetting
138
- label="Show inline types"
139
- description={
140
- 'Adds additional spans to help differentiate types for colorblind users.'
141
- }
142
- checked={settings.inlineTypes}
143
- onChange={(value) => settings.setInlineTypes(value)}
144
- />
145
-
146
- <VscodeDivider />
147
-
148
- <SectionTitle>Interaction</SectionTitle>
149
- <ToggleSetting
150
- label="Use delayed interaction"
151
- description={'Forces a user to click save to update port.'}
152
- checked={settings.delayedUpdate}
153
- onChange={(value) => settings.setDelayedUpdate(value)}
154
- />
155
- <ToggleSetting
156
- label="Click to connect"
157
- description={
158
- 'Allows you to quick connect nodes by clicking on the 2 port.'
159
- }
160
- checked={settings.connectOnClick}
161
- onChange={(value) => settings.setConnectOnClick(value)}
162
- />
163
-
164
- <VscodeDivider />
165
-
166
- <SectionTitle>Display</SectionTitle>
167
- <ToggleSetting
168
- label="Show inline values"
169
- description={
170
- 'Shows values directly on the node. Useful for debugging but can be cluttered.'
171
- }
172
- checked={settings.inlineValues}
173
- onChange={(value) => settings.setInlineValues(value)}
174
- />
175
- <ToggleSetting
176
- label="Show Minimap"
177
- description={'Shows the minimap in the graph editing area.'}
178
- checked={settings.showMinimap}
179
- onChange={(value) => settings.setShowMinimap(value)}
180
- />
181
- <ToggleSetting
182
- label="Show Grid"
183
- description={'Shows the grid in the graph editing area.'}
184
- checked={settings.showGrid}
185
- onChange={(value) => settings.setShowGrid(value)}
186
- />
187
- <ToggleSetting
188
- label="Snap to Grid"
189
- description={'Snaps nodes to the grid while dragging.'}
190
- checked={settings.snapGrid}
191
- onChange={(value) => settings.setSnapGrid(value)}
192
- />
193
-
194
- <VscodeDivider />
195
-
196
- <SectionTitle>Performance</SectionTitle>
197
- <ToggleSetting
198
- label="Show execution time"
199
- description={'Shows how long it takes for a node to process.'}
200
- checked={settings.showTimings}
201
- onChange={(value) => settings.setShowTimings(value)}
211
+ <div className={styles.searchRow}>
212
+ <VscodeTextfield
213
+ value={query}
214
+ placeholder="Search settings"
215
+ style={{ width: '100%' }}
216
+ onChange={(e: any) => setQuery(String(e?.target?.value ?? ''))}
202
217
  />
203
218
  </div>
219
+
220
+ {visibleSections.length === 0 ? (
221
+ <div className={styles.empty}>No settings match “{query}”.</div>
222
+ ) : (
223
+ <div className={styles.list}>
224
+ {visibleSections.map((section, index) => (
225
+ <Fragment key={section}>
226
+ {index > 0 && <VscodeDivider className={styles.divider} />}
227
+ <SectionTitle>{section}</SectionTitle>
228
+ {bySection.get(section)!.map((descriptor) => (
229
+ <Fragment key={descriptor.key}>
230
+ <SettingRow
231
+ descriptor={descriptor}
232
+ // Fall back to the descriptor default so plugin-registered
233
+ // settings (whose value is not seeded into the settings
234
+ // store until first changed) show their real initial state.
235
+ value={values[descriptor.key] ?? descriptor.default}
236
+ setValue={(next) => values.setSetting(descriptor.key, next)}
237
+ />
238
+ {/* The conversions editor is a bespoke built-in control shown
239
+ under Auto-convert when it is enabled. */}
240
+ {descriptor.key === 'autoConvert' && values.autoConvert ? (
241
+ <ConversionsSettings />
242
+ ) : null}
243
+ </Fragment>
244
+ ))}
245
+ </Fragment>
246
+ ))}
247
+ </div>
248
+ )}
204
249
  </BasePanel>
205
250
  );
206
251
  };