@flowdrop/flowdrop 1.14.0 → 2.0.0-beta.1

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 (218) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +110 -66
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +45 -40
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +29 -13
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +292 -0
  61. package/dist/components/nodes/AtomNode.svelte.d.ts +26 -0
  62. package/dist/components/nodes/GatewayNode.svelte +19 -10
  63. package/dist/components/nodes/IdeaNode.svelte +7 -0
  64. package/dist/components/nodes/SimpleNode.svelte +11 -6
  65. package/dist/components/nodes/SquareNode.svelte +15 -8
  66. package/dist/components/nodes/TerminalNode.svelte +9 -4
  67. package/dist/components/nodes/ToolNode.svelte +7 -1
  68. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  69. package/dist/components/playground/ChatInput.svelte +11 -14
  70. package/dist/components/playground/ChatPanel.svelte +6 -49
  71. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  72. package/dist/components/playground/ControlPanel.svelte +134 -123
  73. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  74. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  75. package/dist/components/playground/InputCollector.svelte +11 -9
  76. package/dist/components/playground/MessageStream.svelte +17 -23
  77. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  78. package/dist/components/playground/PipelinePanel.svelte +11 -5
  79. package/dist/components/playground/PipelineTableView.svelte +186 -44
  80. package/dist/components/playground/Playground.svelte +95 -92
  81. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  82. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  83. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  84. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  85. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  86. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  87. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  88. package/dist/components/playground/SessionManager.svelte +9 -12
  89. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  90. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  91. package/dist/config/endpoints.d.ts +0 -7
  92. package/dist/config/endpoints.js +2 -10
  93. package/dist/core/index.d.ts +4 -4
  94. package/dist/core/index.js +6 -6
  95. package/dist/display/index.d.ts +0 -2
  96. package/dist/display/index.js +0 -6
  97. package/dist/editor/index.d.ts +19 -20
  98. package/dist/editor/index.js +25 -35
  99. package/dist/form/code.d.ts +25 -15
  100. package/dist/form/code.js +44 -41
  101. package/dist/form/fieldRegistry.d.ts +17 -13
  102. package/dist/form/fieldRegistry.js +32 -12
  103. package/dist/form/full.d.ts +17 -13
  104. package/dist/form/full.js +22 -27
  105. package/dist/form/index.d.ts +3 -3
  106. package/dist/form/index.js +3 -3
  107. package/dist/form/markdown.d.ts +13 -8
  108. package/dist/form/markdown.js +22 -23
  109. package/dist/helpers/proximityConnect.d.ts +7 -3
  110. package/dist/helpers/proximityConnect.js +19 -6
  111. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  112. package/dist/helpers/workflowEditorHelper.js +27 -25
  113. package/dist/index.d.ts +28 -24
  114. package/dist/index.js +27 -50
  115. package/dist/messages/defaults.d.ts +2 -5
  116. package/dist/messages/defaults.js +3 -6
  117. package/dist/messages/index.d.ts +0 -1
  118. package/dist/messages/index.js +0 -1
  119. package/dist/mocks/app-forms.d.ts +6 -2
  120. package/dist/mocks/app-forms.js +11 -4
  121. package/dist/openapi/v1/openapi.yaml +227 -164
  122. package/dist/playground/index.d.ts +2 -3
  123. package/dist/playground/index.js +2 -30
  124. package/dist/playground/mount.d.ts +15 -0
  125. package/dist/playground/mount.js +46 -20
  126. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  127. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  128. package/dist/registry/builtinFormats.d.ts +9 -18
  129. package/dist/registry/builtinFormats.js +9 -39
  130. package/dist/registry/builtinNodes.d.ts +1 -26
  131. package/dist/registry/builtinNodes.js +14 -50
  132. package/dist/registry/index.d.ts +3 -4
  133. package/dist/registry/index.js +4 -6
  134. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  135. package/dist/registry/nodeComponentRegistry.js +235 -17
  136. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  137. package/dist/registry/workflowFormatRegistry.js +24 -8
  138. package/dist/{schema → schemas}/index.d.ts +2 -2
  139. package/dist/{schema → schemas}/index.js +2 -2
  140. package/dist/schemas/v1/workflow.schema.json +53 -6
  141. package/dist/services/agentSpecExecutionService.js +0 -1
  142. package/dist/services/apiVariableService.d.ts +2 -1
  143. package/dist/services/apiVariableService.js +5 -22
  144. package/dist/services/autoSaveService.d.ts +7 -0
  145. package/dist/services/autoSaveService.js +6 -4
  146. package/dist/services/chatService.d.ts +8 -4
  147. package/dist/services/chatService.js +15 -15
  148. package/dist/services/draftStorage.d.ts +129 -13
  149. package/dist/services/draftStorage.js +185 -37
  150. package/dist/services/dynamicSchemaService.d.ts +2 -1
  151. package/dist/services/dynamicSchemaService.js +5 -22
  152. package/dist/services/globalSave.d.ts +13 -12
  153. package/dist/services/globalSave.js +29 -51
  154. package/dist/services/historyService.d.ts +9 -3
  155. package/dist/services/historyService.js +9 -3
  156. package/dist/services/interruptService.d.ts +14 -9
  157. package/dist/services/interruptService.js +27 -27
  158. package/dist/services/nodeExecutionService.d.ts +18 -3
  159. package/dist/services/nodeExecutionService.js +71 -45
  160. package/dist/services/playgroundService.d.ts +14 -9
  161. package/dist/services/playgroundService.js +31 -30
  162. package/dist/services/variableService.d.ts +2 -1
  163. package/dist/services/variableService.js +2 -2
  164. package/dist/services/workflowStorage.js +6 -6
  165. package/dist/stores/apiContext.d.ts +45 -0
  166. package/dist/stores/apiContext.js +65 -0
  167. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  168. package/dist/stores/categoriesStore.svelte.js +70 -64
  169. package/dist/stores/getInstance.svelte.d.ts +39 -0
  170. package/dist/stores/getInstance.svelte.js +65 -0
  171. package/dist/stores/historyStore.svelte.d.ts +77 -93
  172. package/dist/stores/historyStore.svelte.js +134 -160
  173. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  174. package/dist/stores/instanceContainer.svelte.js +114 -0
  175. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  176. package/dist/stores/interruptStore.svelte.js +253 -226
  177. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  178. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  179. package/dist/stores/playgroundStore.svelte.d.ts +169 -216
  180. package/dist/stores/playgroundStore.svelte.js +515 -572
  181. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  182. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  183. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  184. package/dist/stores/settingsStore.svelte.js +47 -12
  185. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  186. package/dist/stores/workflowStore.svelte.js +449 -501
  187. package/dist/stories/EdgeDecorator.svelte +5 -2
  188. package/dist/stories/NodeDecorator.svelte +5 -3
  189. package/dist/svelte-app.d.ts +60 -10
  190. package/dist/svelte-app.js +157 -53
  191. package/dist/types/events.d.ts +6 -3
  192. package/dist/types/index.d.ts +71 -6
  193. package/dist/types/navbar.d.ts +7 -0
  194. package/dist/types/playground.d.ts +18 -3
  195. package/dist/types/settings.d.ts +13 -0
  196. package/dist/types/settings.js +1 -0
  197. package/dist/utils/colors.d.ts +47 -21
  198. package/dist/utils/colors.js +69 -68
  199. package/dist/utils/connections.d.ts +9 -15
  200. package/dist/utils/connections.js +13 -32
  201. package/dist/utils/duration.d.ts +13 -0
  202. package/dist/utils/duration.js +45 -0
  203. package/dist/utils/formMerge.d.ts +36 -0
  204. package/dist/utils/formMerge.js +70 -0
  205. package/dist/utils/icons.d.ts +5 -2
  206. package/dist/utils/icons.js +6 -5
  207. package/dist/utils/nodeSwap.d.ts +6 -2
  208. package/dist/utils/nodeSwap.js +62 -126
  209. package/dist/utils/nodeTypes.d.ts +17 -8
  210. package/dist/utils/nodeTypes.js +27 -19
  211. package/dist/utils/performanceUtils.js +7 -0
  212. package/package.json +6 -5
  213. package/dist/messages/deprecation.d.ts +0 -20
  214. package/dist/messages/deprecation.js +0 -33
  215. package/dist/registry/plugin.d.ts +0 -215
  216. package/dist/registry/plugin.js +0 -249
  217. package/dist/services/api.d.ts +0 -129
  218. package/dist/services/api.js +0 -217
@@ -2,7 +2,6 @@
2
2
  * Centralized icon management for FlowDrop
3
3
  * Ensures consistent icon usage across all components
4
4
  */
5
- import { getCategoryIcon as getCategoryIconFromStore } from '../stores/categoriesStore.svelte.js';
6
5
  /**
7
6
  * Default icons for different contexts
8
7
  */
@@ -103,18 +102,19 @@ export const CATEGORY_ICONS = {
103
102
  };
104
103
  /**
105
104
  * Get the appropriate icon for a node
105
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
106
106
  * @param nodeIcon - The node's specific icon
107
107
  * @param category - The node's category
108
108
  * @returns The icon to use
109
109
  */
110
- export function getNodeIcon(nodeIcon, category) {
110
+ export function getNodeIcon(categories, nodeIcon, category) {
111
111
  // If node has a specific icon, use it
112
112
  if (nodeIcon) {
113
113
  return nodeIcon;
114
114
  }
115
115
  // If category is provided, use category icon from store (which includes API overrides)
116
116
  if (category) {
117
- return getCategoryIconFromStore(category);
117
+ return categories.getIcon(category);
118
118
  }
119
119
  // Fallback to default node icon
120
120
  return DEFAULT_ICONS.NODE;
@@ -123,11 +123,12 @@ export function getNodeIcon(nodeIcon, category) {
123
123
  * Get the appropriate icon for a category.
124
124
  * Checks the categories store first (which includes API overrides),
125
125
  * then falls back to the static CATEGORY_ICONS map, then to the default.
126
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
126
127
  * @param category - The category
127
128
  * @returns The icon to use
128
129
  */
129
- export function getCategoryIcon(category) {
130
- return getCategoryIconFromStore(category);
130
+ export function getCategoryIcon(categories, category) {
131
+ return categories.getIcon(category);
131
132
  }
132
133
  /**
133
134
  * Get a default icon by key
@@ -161,6 +161,10 @@ export declare function mapConfig(oldConfig: ConfigValues, newConfigSchema: Conf
161
161
  *
162
162
  * This does NOT mutate anything — it returns a preview that can be displayed
163
163
  * to the user for confirmation before executing the swap.
164
+ *
165
+ * Thin wrapper over {@link computeSwapPreviewWithOptions} — with no strategies
166
+ * or overrides the cascade reduces to the built-in exact matching, so both
167
+ * entry points share one implementation.
164
168
  */
165
169
  export declare function computeSwapPreview(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], checker?: PortCompatibilityChecker | null): SwapPreview;
166
170
  /**
@@ -183,7 +187,7 @@ export declare function getVersionUpgrade(currentMetadata: NodeMetadata, allNode
183
187
  *
184
188
  * Resolution order:
185
189
  * 1. Check strategies — first canHandle() match wins for mapPorts()/mapConfig()
186
- * 2. Fall through to built-in 3-pass for ports not covered by strategy
190
+ * 2. Fall through to built-in exact matching (ID, then name) for ports not covered by strategy
187
191
  * 3. Apply portOverrides on top (highest priority — user's manual overrides)
188
192
  * 4. Same cascade for config
189
193
  */
@@ -199,7 +203,7 @@ export declare function computeInteractiveState(oldNode: WorkflowNode, newMetada
199
203
  * Convert user-edited InteractiveSwapState back into a SwapPreview
200
204
  * for executeSwap(). Pure function, no side effects.
201
205
  */
202
- export declare function buildSwapPreviewFromState(state: InteractiveSwapState, allEdges: WorkflowEdge[]): SwapPreview;
206
+ export declare function buildSwapPreviewFromState(state: InteractiveSwapState, _allEdges: WorkflowEdge[]): SwapPreview;
203
207
  /**
204
208
  * Headless one-shot swap with full validation.
205
209
  *
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module utils/nodeSwap
8
8
  */
9
- import { buildHandleId, extractPortId, extractDirection } from './handleIds.js';
9
+ import { buildHandleId, extractPortId } from './handleIds.js';
10
10
  import { generateNodeId } from './nodeIds.js';
11
11
  /** Error class for swap validation failures. */
12
12
  export class SwapValidationError extends Error {
@@ -86,16 +86,17 @@ export function mapConfig(oldConfig, newConfigSchema, newDefaults = {}) {
86
86
  }
87
87
  return { config, carriedOver, reset };
88
88
  }
89
- // =========================================================================
90
- // Port matching
91
- // =========================================================================
92
89
  /**
93
90
  * Find the best matching port on the new node for a given old port.
94
91
  *
95
- * Three-pass strategy:
92
+ * Two-pass strategy — exact matches only:
96
93
  * 1. Exact port ID match with compatible dataType
97
- * 2. Port name match (case-insensitive) with compatible dataType
98
- * 3. First available port with compatible dataType
94
+ * 2. Exact port name match (case-insensitive) with compatible dataType
95
+ *
96
+ * There is deliberately no fuzzy "first compatible dataType" fallback:
97
+ * it silently rewired edges to the wrong port (e.g. a `text` edge landing
98
+ * on `assistant_message`). If neither ID nor name matches, the edge is
99
+ * dropped and reported instead.
99
100
  */
100
101
  function findMatchingPort(oldPort, newPorts, usedPortIds, checker) {
101
102
  const available = newPorts.filter((p) => p.type === oldPort.type && !usedPortIds.has(p.id));
@@ -116,11 +117,7 @@ function findMatchingPort(oldPort, newPorts, usedPortIds, checker) {
116
117
  // Pass 2: name match (case-insensitive)
117
118
  const oldNameLower = oldPort.name.toLowerCase();
118
119
  const nameMatch = available.find((p) => p.name.toLowerCase() === oldNameLower && isCompatible(oldPort, p));
119
- if (nameMatch)
120
- return nameMatch;
121
- // Pass 3: first compatible dataType
122
- const typeMatch = available.find((p) => isCompatible(oldPort, p));
123
- return typeMatch ?? null;
120
+ return nameMatch ?? null;
124
121
  }
125
122
  /**
126
123
  * Resolve the port metadata for an edge endpoint on a given node.
@@ -142,65 +139,13 @@ function resolvePort(node, handleId, direction) {
142
139
  *
143
140
  * This does NOT mutate anything — it returns a preview that can be displayed
144
141
  * to the user for confirmation before executing the swap.
142
+ *
143
+ * Thin wrapper over {@link computeSwapPreviewWithOptions} — with no strategies
144
+ * or overrides the cascade reduces to the built-in exact matching, so both
145
+ * entry points share one implementation.
145
146
  */
146
147
  export function computeSwapPreview(oldNode, newMetadata, edges, allNodes, checker = null) {
147
- const oldNodeId = oldNode.id;
148
- const newNodeId = generateNodeId(newMetadata.id, allNodes);
149
- // Collect all edges connected to the old node
150
- const connectedEdges = edges.filter((e) => e.source === oldNodeId || e.target === oldNodeId);
151
- // Track which ports on the new node have been claimed
152
- const usedInputPortIds = new Set();
153
- const usedOutputPortIds = new Set();
154
- const keptEdges = [];
155
- const droppedEdges = [];
156
- for (const edge of connectedEdges) {
157
- const isSource = edge.source === oldNodeId;
158
- const direction = isSource ? 'output' : 'input';
159
- const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
160
- const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
161
- // Resolve the old port
162
- const oldPort = resolvePort(oldNode, handleId, direction);
163
- if (!oldPort) {
164
- droppedEdges.push({
165
- edge,
166
- reason: `Port not found on original node`
167
- });
168
- continue;
169
- }
170
- // Find matching port on new node
171
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
172
- const match = findMatchingPort(oldPort, newPorts, usedPorts, checker);
173
- if (!match) {
174
- droppedEdges.push({
175
- edge,
176
- reason: `No compatible ${direction} port found on "${newMetadata.name}"`
177
- });
178
- continue;
179
- }
180
- usedPorts.add(match.id);
181
- // Build the rewritten edge
182
- const newHandleId = buildHandleId(newNodeId, direction, match.id);
183
- const newEdge = { ...edge };
184
- if (isSource) {
185
- newEdge.source = newNodeId;
186
- newEdge.sourceHandle = newHandleId;
187
- }
188
- else {
189
- newEdge.target = newNodeId;
190
- newEdge.targetHandle = newHandleId;
191
- }
192
- keptEdges.push({ edge, newEdge });
193
- }
194
- // Config mapping preview
195
- const { carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
196
- return {
197
- keptEdges,
198
- droppedEdges,
199
- hasDataLoss: droppedEdges.length > 0,
200
- newNodeId,
201
- configCarriedOver: carriedOver,
202
- configReset: reset
203
- };
148
+ return computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNodes, { checker });
204
149
  }
205
150
  // =========================================================================
206
151
  // Swap execution
@@ -231,6 +176,9 @@ export function executeSwap(oldNode, newMetadata, preview, allNodes, allEdges) {
231
176
  label: newMetadata.name,
232
177
  config: mappedConfig,
233
178
  metadata: newMetadata,
179
+ // Node components derive their handle IDs from data.nodeId — without it
180
+ // every edge anchored to this node silently disappears from the canvas.
181
+ nodeId: newNodeId,
234
182
  extensions
235
183
  }
236
184
  };
@@ -298,7 +246,7 @@ function classifyMatch(oldPort, matchedPort) {
298
246
  *
299
247
  * Resolution order:
300
248
  * 1. Check strategies — first canHandle() match wins for mapPorts()/mapConfig()
301
- * 2. Fall through to built-in 3-pass for ports not covered by strategy
249
+ * 2. Fall through to built-in exact matching (ID, then name) for ports not covered by strategy
302
250
  * 3. Apply portOverrides on top (highest priority — user's manual overrides)
303
251
  * 4. Same cascade for config
304
252
  */
@@ -332,84 +280,73 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
332
280
  for (const override of options.portOverrides ?? []) {
333
281
  portOverrideLookup.set(`${override.direction}:${override.oldPortId}`, override.newPortId);
334
282
  }
335
- // Track used ports
283
+ // Track ports on the new node claimed by a DIFFERENT old port
336
284
  const usedInputPortIds = new Set();
337
285
  const usedOutputPortIds = new Set();
338
- const keptEdges = [];
339
- const droppedEdges = [];
340
- for (const edge of connectedEdges) {
341
- const isSource = edge.source === oldNodeId;
342
- const direction = isSource ? 'output' : 'input';
343
- const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
344
- const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
345
- const oldPort = resolvePort(oldNode, handleId, direction);
346
- if (!oldPort) {
347
- droppedEdges.push({ edge, reason: 'Port not found on original node' });
348
- continue;
349
- }
286
+ // Memoized old-port → new-port resolutions so every edge anchored on the
287
+ // same old port maps to the same new port (multi-connection ports, fan-outs).
288
+ const portResolutions = new Map();
289
+ /** Resolve a new port for an old port through the priority cascade. */
290
+ const resolveNewPort = (oldPort, direction, usedPorts) => {
291
+ const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
350
292
  // Priority 1: Manual port override
351
293
  const overrideKey = `${direction}:${oldPort.id}`;
352
294
  if (portOverrideLookup.has(overrideKey)) {
353
295
  const overrideNewPortId = portOverrideLookup.get(overrideKey);
354
296
  if (overrideNewPortId === null) {
355
- droppedEdges.push({ edge, reason: 'Manually dropped' });
356
- continue;
297
+ return { port: null, reason: 'Manually dropped' };
357
298
  }
358
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
359
299
  const overridePort = newPorts.find((p) => p.id === overrideNewPortId);
360
300
  if (overridePort) {
361
- usedPorts.add(overridePort.id);
362
- const newHandleId = buildHandleId(newNodeId, direction, overridePort.id);
363
- const newEdge = { ...edge };
364
- if (isSource) {
365
- newEdge.source = newNodeId;
366
- newEdge.sourceHandle = newHandleId;
367
- }
368
- else {
369
- newEdge.target = newNodeId;
370
- newEdge.targetHandle = newHandleId;
371
- }
372
- keptEdges.push({ edge, newEdge });
373
- continue;
301
+ return { port: overridePort };
374
302
  }
375
303
  }
376
304
  // Priority 2: Strategy port mapping
377
305
  if (strategyPortMap && oldPort.id in strategyPortMap) {
378
306
  const strategyNewPortId = strategyPortMap[oldPort.id];
379
307
  if (strategyNewPortId === null) {
380
- droppedEdges.push({ edge, reason: 'Dropped by strategy' });
381
- continue;
308
+ return { port: null, reason: 'Dropped by strategy' };
382
309
  }
383
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
384
310
  const strategyPort = newPorts.find((p) => p.id === strategyNewPortId);
385
311
  if (strategyPort && !usedPorts.has(strategyPort.id)) {
386
- usedPorts.add(strategyPort.id);
387
- const newHandleId = buildHandleId(newNodeId, direction, strategyPort.id);
388
- const newEdge = { ...edge };
389
- if (isSource) {
390
- newEdge.source = newNodeId;
391
- newEdge.sourceHandle = newHandleId;
392
- }
393
- else {
394
- newEdge.target = newNodeId;
395
- newEdge.targetHandle = newHandleId;
396
- }
397
- keptEdges.push({ edge, newEdge });
398
- continue;
312
+ return { port: strategyPort };
399
313
  }
400
314
  }
401
- // Priority 3: Built-in 3-pass matching
402
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
315
+ // Priority 3: Built-in exact matching (ID, then name)
403
316
  const match = findMatchingPort(oldPort, newPorts, usedPorts, checker);
404
317
  if (!match) {
405
- droppedEdges.push({
406
- edge,
407
- reason: `No compatible ${direction} port found on "${newMetadata.name}"`
408
- });
318
+ return {
319
+ port: null,
320
+ reason: `No matching ${direction} port for "${oldPort.id}" on "${newMetadata.name}"`
321
+ };
322
+ }
323
+ return { port: match };
324
+ };
325
+ const keptEdges = [];
326
+ const droppedEdges = [];
327
+ for (const edge of connectedEdges) {
328
+ const isSource = edge.source === oldNodeId;
329
+ const direction = isSource ? 'output' : 'input';
330
+ const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
331
+ const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
332
+ const oldPort = resolvePort(oldNode, handleId, direction);
333
+ if (!oldPort) {
334
+ droppedEdges.push({ edge, reason: 'Port not found on original node' });
409
335
  continue;
410
336
  }
411
- usedPorts.add(match.id);
412
- const newHandleId = buildHandleId(newNodeId, direction, match.id);
337
+ const matchKey = `${direction}:${oldPort.id}`;
338
+ let resolved = portResolutions.get(matchKey);
339
+ if (!resolved) {
340
+ resolved = resolveNewPort(oldPort, direction, usedPorts);
341
+ portResolutions.set(matchKey, resolved);
342
+ if (resolved.port)
343
+ usedPorts.add(resolved.port.id);
344
+ }
345
+ if (!resolved.port) {
346
+ droppedEdges.push({ edge, reason: resolved.reason });
347
+ continue;
348
+ }
349
+ const newHandleId = buildHandleId(newNodeId, direction, resolved.port.id);
413
350
  const newEdge = { ...edge };
414
351
  if (isSource) {
415
352
  newEdge.source = newNodeId;
@@ -422,7 +359,7 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
422
359
  keptEdges.push({ edge, newEdge });
423
360
  }
424
361
  // Config mapping — apply strategy then overrides
425
- const { config: baseConfig, carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
362
+ const { carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
426
363
  // Apply strategy config overrides
427
364
  if (strategyConfigMap) {
428
365
  for (const [key, mapping] of Object.entries(strategyConfigMap)) {
@@ -482,7 +419,6 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
482
419
  * with match quality annotations and isFlat flags.
483
420
  */
484
421
  export function computeInteractiveState(oldNode, newMetadata, edges, allNodes, options = {}) {
485
- const checker = options.checker ?? null;
486
422
  const oldNodeId = oldNode.id;
487
423
  const newNodeId = generateNodeId(newMetadata.id, allNodes);
488
424
  const connectedEdges = edges.filter((e) => e.source === oldNodeId || e.target === oldNodeId);
@@ -567,7 +503,7 @@ function isPrimitive(value) {
567
503
  * Convert user-edited InteractiveSwapState back into a SwapPreview
568
504
  * for executeSwap(). Pure function, no side effects.
569
505
  */
570
- export function buildSwapPreviewFromState(state, allEdges) {
506
+ export function buildSwapPreviewFromState(state, _allEdges) {
571
507
  const keptEdges = [];
572
508
  const droppedEdges = [];
573
509
  for (const mapping of state.portMappings) {
@@ -10,14 +10,16 @@
10
10
  * Works with both built-in types and custom registered types.
11
11
  */
12
12
  import type { NodeType, NodeMetadata } from '../types/index.js';
13
+ import type { NodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
13
14
  /**
14
15
  * Gets the SvelteFlow component name for a given NodeType.
15
16
  * Uses the node component registry to resolve types.
16
17
  *
18
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
17
19
  * @param nodeType - The node type identifier
18
20
  * @returns The component name to use
19
21
  */
20
- export declare function getComponentNameForNodeType(nodeType: NodeType | string): string;
22
+ export declare function getComponentNameForNodeType(registry: NodeComponentRegistry, nodeType: NodeType | string): string;
21
23
  /**
22
24
  * Gets the available node types for a given NodeMetadata.
23
25
  * Priority: supportedTypes > type > "default"
@@ -43,28 +45,31 @@ export declare function getPrimaryNodeType(metadata: NodeMetadata): NodeType | s
43
45
  * 3. First supportedType
44
46
  * 4. "default"
45
47
  *
48
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
46
49
  * @param metadata - The node metadata
47
50
  * @param configNodeType - Optional type from user config
48
51
  * @returns The resolved node type
49
52
  */
50
- export declare function resolveNodeType(metadata: NodeMetadata, configNodeType?: string): NodeType | string;
53
+ export declare function resolveNodeType(registry: NodeComponentRegistry, metadata: NodeMetadata, configNodeType?: string): NodeType | string;
51
54
  /**
52
55
  * Gets the SvelteFlow component name for resolved node type.
53
56
  * This is the main function used by UniversalNode to determine which component to render.
54
57
  *
58
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
55
59
  * @param metadata - The node metadata
56
60
  * @param configNodeType - Optional type from user config
57
61
  * @returns The component name to use
58
62
  */
59
- export declare function resolveComponentName(metadata: NodeMetadata, configNodeType?: string): string;
63
+ export declare function resolveComponentName(registry: NodeComponentRegistry, metadata: NodeMetadata, configNodeType?: string): string;
60
64
  /**
61
65
  * Validates if a node type is supported by the given metadata.
62
66
  *
67
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
63
68
  * @param metadata - The node metadata
64
69
  * @param nodeType - The type to check
65
70
  * @returns true if the type is supported
66
71
  */
67
- export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: NodeType | string): boolean;
72
+ export declare function isNodeTypeSupported(registry: NodeComponentRegistry, metadata: NodeMetadata, nodeType: NodeType | string): boolean;
68
73
  /**
69
74
  * Gets oneOf options for node type configuration.
70
75
  * Used in config schemas to show available options with labels.
@@ -73,11 +78,12 @@ export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: No
73
78
  * - Types specified in metadata.supportedTypes
74
79
  * - Registered custom types (optionally filtered)
75
80
  *
81
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
76
82
  * @param metadata - The node metadata
77
83
  * @param includeCustomTypes - Whether to include registered custom types
78
84
  * @returns Array of oneOf items with const (type value) and title (display name)
79
85
  */
80
- export declare function getNodeTypeOneOfOptions(metadata: NodeMetadata, includeCustomTypes?: boolean): Array<{
86
+ export declare function getNodeTypeOneOfOptions(registry: NodeComponentRegistry, metadata: NodeMetadata, includeCustomTypes?: boolean): Array<{
81
87
  const: string;
82
88
  title: string;
83
89
  }>;
@@ -88,11 +94,12 @@ export declare function getNodeTypeOneOfOptions(metadata: NodeMetadata, includeC
88
94
  * Uses JSON Schema `oneOf` pattern with `const`/`title` for labeled options,
89
95
  * which is the standard approach supported by form components.
90
96
  *
97
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
91
98
  * @param metadata - The node metadata
92
99
  * @param defaultType - Optional default type override
93
100
  * @returns Config schema property object with oneOf for labeled options
94
101
  */
95
- export declare function createNodeTypeConfigProperty(metadata: NodeMetadata, defaultType?: NodeType | string): {
102
+ export declare function createNodeTypeConfigProperty(registry: NodeComponentRegistry, metadata: NodeMetadata, defaultType?: NodeType | string): {
96
103
  type: "string";
97
104
  title: string;
98
105
  description: string;
@@ -105,13 +112,15 @@ export declare function createNodeTypeConfigProperty(metadata: NodeMetadata, def
105
112
  /**
106
113
  * Check if a type string represents a valid registered or built-in type.
107
114
  *
115
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
108
116
  * @param type - The type to check
109
117
  * @returns true if the type is valid
110
118
  */
111
- export declare function isValidNodeType(type: string): boolean;
119
+ export declare function isValidNodeType(registry: NodeComponentRegistry, type: string): boolean;
112
120
  /**
113
121
  * Get all available node types (built-in + registered).
114
122
  *
123
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
115
124
  * @returns Array of all valid node type identifiers
116
125
  */
117
- export declare function getAllNodeTypes(): string[];
126
+ export declare function getAllNodeTypes(registry: NodeComponentRegistry): string[];
@@ -9,7 +9,6 @@
9
9
  *
10
10
  * Works with both built-in types and custom registered types.
11
11
  */
12
- import { nodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
13
12
  import { resolveBuiltinAlias, isBuiltinType } from '../registry/builtinNodes.js';
14
13
  /**
15
14
  * Display names for built-in node types.
@@ -18,6 +17,7 @@ const TYPE_DISPLAY_NAMES = {
18
17
  note: 'Note (sticky note style)',
19
18
  simple: 'Simple (compact layout)',
20
19
  square: 'Square (geometric layout)',
20
+ atom: 'Atom (minimal value/transform)',
21
21
  tool: 'Tool (specialized for agent tools)',
22
22
  gateway: 'Gateway (branching control flow)',
23
23
  terminal: 'Terminal (start/end/exit)',
@@ -27,14 +27,15 @@ const TYPE_DISPLAY_NAMES = {
27
27
  * Gets the SvelteFlow component name for a given NodeType.
28
28
  * Uses the node component registry to resolve types.
29
29
  *
30
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
30
31
  * @param nodeType - The node type identifier
31
32
  * @returns The component name to use
32
33
  */
33
- export function getComponentNameForNodeType(nodeType) {
34
+ export function getComponentNameForNodeType(registry, nodeType) {
34
35
  // Resolve aliases first (e.g., "default" -> "workflowNode")
35
36
  const resolvedType = resolveBuiltinAlias(nodeType);
36
37
  // Check if it's registered in the registry
37
- if (nodeComponentRegistry.has(resolvedType)) {
38
+ if (registry.has(resolvedType)) {
38
39
  return resolvedType;
39
40
  }
40
41
  // Unknown type - return workflowNode as default
@@ -76,11 +77,12 @@ export function getPrimaryNodeType(metadata) {
76
77
  * 3. First supportedType
77
78
  * 4. "default"
78
79
  *
80
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
79
81
  * @param metadata - The node metadata
80
82
  * @param configNodeType - Optional type from user config
81
83
  * @returns The resolved node type
82
84
  */
83
- export function resolveNodeType(metadata, configNodeType) {
85
+ export function resolveNodeType(registry, metadata, configNodeType) {
84
86
  const availableTypes = getAvailableNodeTypes(metadata);
85
87
  // Check if configNodeType is valid for this metadata
86
88
  if (configNodeType) {
@@ -92,7 +94,7 @@ export function resolveNodeType(metadata, configNodeType) {
92
94
  return configNodeType;
93
95
  }
94
96
  // Check if it's a registered custom type
95
- if (nodeComponentRegistry.has(configNodeType) || nodeComponentRegistry.has(resolvedConfig)) {
97
+ if (registry.has(configNodeType) || registry.has(resolvedConfig)) {
96
98
  return configNodeType;
97
99
  }
98
100
  }
@@ -103,22 +105,24 @@ export function resolveNodeType(metadata, configNodeType) {
103
105
  * Gets the SvelteFlow component name for resolved node type.
104
106
  * This is the main function used by UniversalNode to determine which component to render.
105
107
  *
108
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
106
109
  * @param metadata - The node metadata
107
110
  * @param configNodeType - Optional type from user config
108
111
  * @returns The component name to use
109
112
  */
110
- export function resolveComponentName(metadata, configNodeType) {
111
- const nodeType = resolveNodeType(metadata, configNodeType);
112
- return getComponentNameForNodeType(nodeType);
113
+ export function resolveComponentName(registry, metadata, configNodeType) {
114
+ const nodeType = resolveNodeType(registry, metadata, configNodeType);
115
+ return getComponentNameForNodeType(registry, nodeType);
113
116
  }
114
117
  /**
115
118
  * Validates if a node type is supported by the given metadata.
116
119
  *
120
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
117
121
  * @param metadata - The node metadata
118
122
  * @param nodeType - The type to check
119
123
  * @returns true if the type is supported
120
124
  */
121
- export function isNodeTypeSupported(metadata, nodeType) {
125
+ export function isNodeTypeSupported(registry, metadata, nodeType) {
122
126
  const availableTypes = getAvailableNodeTypes(metadata);
123
127
  // Check direct match
124
128
  if (availableTypes.includes(nodeType)) {
@@ -130,7 +134,7 @@ export function isNodeTypeSupported(metadata, nodeType) {
130
134
  return true;
131
135
  }
132
136
  // Check if it's a registered custom type that's in the available list
133
- if (nodeComponentRegistry.has(nodeType)) {
137
+ if (registry.has(nodeType)) {
134
138
  return availableTypes.some((t) => t === nodeType || resolveBuiltinAlias(t) === nodeType);
135
139
  }
136
140
  return false;
@@ -143,18 +147,19 @@ export function isNodeTypeSupported(metadata, nodeType) {
143
147
  * - Types specified in metadata.supportedTypes
144
148
  * - Registered custom types (optionally filtered)
145
149
  *
150
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
146
151
  * @param metadata - The node metadata
147
152
  * @param includeCustomTypes - Whether to include registered custom types
148
153
  * @returns Array of oneOf items with const (type value) and title (display name)
149
154
  */
150
- export function getNodeTypeOneOfOptions(metadata, includeCustomTypes = false) {
155
+ export function getNodeTypeOneOfOptions(registry, metadata, includeCustomTypes = false) {
151
156
  const availableTypes = getAvailableNodeTypes(metadata);
152
157
  const options = [];
153
158
  const includedTypes = new Set();
154
159
  for (const type of availableTypes) {
155
160
  includedTypes.add(type);
156
161
  // Get display name from registry or fallback to built-in names
157
- const registration = nodeComponentRegistry.get(type);
162
+ const registration = registry.get(type);
158
163
  let title;
159
164
  if (registration) {
160
165
  title = registration.displayName;
@@ -170,7 +175,7 @@ export function getNodeTypeOneOfOptions(metadata, includeCustomTypes = false) {
170
175
  }
171
176
  // Optionally include all registered custom types
172
177
  if (includeCustomTypes) {
173
- const registrations = nodeComponentRegistry.filter({
178
+ const registrations = registry.filter({
174
179
  predicate: (reg) => !isBuiltinType(reg.type) && !includedTypes.has(reg.type)
175
180
  });
176
181
  for (const reg of registrations) {
@@ -186,12 +191,13 @@ export function getNodeTypeOneOfOptions(metadata, includeCustomTypes = false) {
186
191
  * Uses JSON Schema `oneOf` pattern with `const`/`title` for labeled options,
187
192
  * which is the standard approach supported by form components.
188
193
  *
194
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
189
195
  * @param metadata - The node metadata
190
196
  * @param defaultType - Optional default type override
191
197
  * @returns Config schema property object with oneOf for labeled options
192
198
  */
193
- export function createNodeTypeConfigProperty(metadata, defaultType) {
194
- const oneOf = getNodeTypeOneOfOptions(metadata);
199
+ export function createNodeTypeConfigProperty(registry, metadata, defaultType) {
200
+ const oneOf = getNodeTypeOneOfOptions(registry, metadata);
195
201
  const primaryType = defaultType ?? getPrimaryNodeType(metadata);
196
202
  return {
197
203
  type: 'string',
@@ -204,19 +210,21 @@ export function createNodeTypeConfigProperty(metadata, defaultType) {
204
210
  /**
205
211
  * Check if a type string represents a valid registered or built-in type.
206
212
  *
213
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
207
214
  * @param type - The type to check
208
215
  * @returns true if the type is valid
209
216
  */
210
- export function isValidNodeType(type) {
211
- return isBuiltinType(type) || nodeComponentRegistry.has(type);
217
+ export function isValidNodeType(registry, type) {
218
+ return isBuiltinType(type) || registry.has(type);
212
219
  }
213
220
  /**
214
221
  * Get all available node types (built-in + registered).
215
222
  *
223
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
216
224
  * @returns Array of all valid node type identifiers
217
225
  */
218
- export function getAllNodeTypes() {
219
- return nodeComponentRegistry.getTypes();
226
+ export function getAllNodeTypes(registry) {
227
+ return registry.getTypes();
220
228
  }
221
229
  /**
222
230
  * Format a type name for display when no display name is registered.
@@ -51,6 +51,9 @@ export function areEdgeArraysEqual(edges1, edges2) {
51
51
  * Throttle function execution to reduce frequency
52
52
  * Uses requestAnimationFrame for smooth UI updates
53
53
  */
54
+ // `any[]` in a generic constraint is the variance-correct idiom (it's how the TS
55
+ // stdlib types Parameters<T>); `unknown[]` would reject every concrete callback.
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
57
  export function throttle(func, wait) {
55
58
  let timeout = null;
56
59
  let lastRan = 0;
@@ -75,6 +78,8 @@ export function throttle(func, wait) {
75
78
  * Debounce function execution to reduce frequency
76
79
  * Waits for a pause in calls before executing
77
80
  */
81
+ // `any[]` constraint: see throttle() above.
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
83
  export function debounce(func, wait) {
79
84
  let timeout = null;
80
85
  return function (...args) {
@@ -90,6 +95,8 @@ export function debounce(func, wait) {
90
95
  * RequestAnimationFrame-based throttle for smooth animations
91
96
  * Better for visual updates like node dragging
92
97
  */
98
+ // `any[]` constraint: see throttle() above.
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
100
  export function rafThrottle(func) {
94
101
  let rafId = null;
95
102
  let lastArgs = null;