@flowdrop/flowdrop 1.15.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 (215) 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 +71 -47
  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 +18 -25
  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 +8 -6
  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 +17 -5
  61. package/dist/components/nodes/GatewayNode.svelte +19 -10
  62. package/dist/components/nodes/IdeaNode.svelte +7 -0
  63. package/dist/components/nodes/SimpleNode.svelte +11 -6
  64. package/dist/components/nodes/SquareNode.svelte +15 -8
  65. package/dist/components/nodes/TerminalNode.svelte +9 -4
  66. package/dist/components/nodes/ToolNode.svelte +7 -1
  67. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  68. package/dist/components/playground/ChatInput.svelte +11 -14
  69. package/dist/components/playground/ChatPanel.svelte +6 -49
  70. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  71. package/dist/components/playground/ControlPanel.svelte +134 -123
  72. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  73. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  74. package/dist/components/playground/InputCollector.svelte +11 -9
  75. package/dist/components/playground/MessageStream.svelte +17 -23
  76. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  77. package/dist/components/playground/PipelinePanel.svelte +11 -5
  78. package/dist/components/playground/PipelineTableView.svelte +186 -44
  79. package/dist/components/playground/Playground.svelte +90 -92
  80. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  81. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  82. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  83. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  84. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  85. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  86. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  87. package/dist/components/playground/SessionManager.svelte +9 -12
  88. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  89. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  90. package/dist/config/endpoints.d.ts +0 -7
  91. package/dist/config/endpoints.js +2 -10
  92. package/dist/core/index.d.ts +4 -4
  93. package/dist/core/index.js +6 -6
  94. package/dist/display/index.d.ts +0 -2
  95. package/dist/display/index.js +0 -6
  96. package/dist/editor/index.d.ts +19 -20
  97. package/dist/editor/index.js +25 -35
  98. package/dist/form/code.d.ts +25 -15
  99. package/dist/form/code.js +44 -41
  100. package/dist/form/fieldRegistry.d.ts +17 -13
  101. package/dist/form/fieldRegistry.js +32 -12
  102. package/dist/form/full.d.ts +17 -13
  103. package/dist/form/full.js +22 -27
  104. package/dist/form/index.d.ts +3 -3
  105. package/dist/form/index.js +3 -3
  106. package/dist/form/markdown.d.ts +13 -8
  107. package/dist/form/markdown.js +22 -23
  108. package/dist/helpers/proximityConnect.d.ts +3 -2
  109. package/dist/helpers/proximityConnect.js +2 -5
  110. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  111. package/dist/helpers/workflowEditorHelper.js +27 -25
  112. package/dist/index.d.ts +28 -24
  113. package/dist/index.js +27 -50
  114. package/dist/messages/defaults.d.ts +2 -5
  115. package/dist/messages/defaults.js +3 -6
  116. package/dist/messages/index.d.ts +0 -1
  117. package/dist/messages/index.js +0 -1
  118. package/dist/mocks/app-forms.d.ts +6 -2
  119. package/dist/mocks/app-forms.js +11 -4
  120. package/dist/openapi/v1/openapi.yaml +3 -3
  121. package/dist/playground/index.d.ts +2 -3
  122. package/dist/playground/index.js +2 -30
  123. package/dist/playground/mount.d.ts +15 -0
  124. package/dist/playground/mount.js +46 -20
  125. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  126. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  127. package/dist/registry/builtinFormats.d.ts +9 -18
  128. package/dist/registry/builtinFormats.js +9 -39
  129. package/dist/registry/builtinNodes.d.ts +0 -25
  130. package/dist/registry/builtinNodes.js +1 -50
  131. package/dist/registry/index.d.ts +3 -4
  132. package/dist/registry/index.js +4 -6
  133. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  134. package/dist/registry/nodeComponentRegistry.js +235 -17
  135. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  136. package/dist/registry/workflowFormatRegistry.js +24 -8
  137. package/dist/{schema → schemas}/index.d.ts +2 -2
  138. package/dist/{schema → schemas}/index.js +2 -2
  139. package/dist/schemas/v1/workflow.schema.json +3 -3
  140. package/dist/services/agentSpecExecutionService.js +0 -1
  141. package/dist/services/apiVariableService.d.ts +2 -1
  142. package/dist/services/apiVariableService.js +5 -22
  143. package/dist/services/autoSaveService.d.ts +7 -0
  144. package/dist/services/autoSaveService.js +6 -4
  145. package/dist/services/chatService.d.ts +8 -4
  146. package/dist/services/chatService.js +15 -15
  147. package/dist/services/draftStorage.d.ts +129 -13
  148. package/dist/services/draftStorage.js +185 -37
  149. package/dist/services/dynamicSchemaService.d.ts +2 -1
  150. package/dist/services/dynamicSchemaService.js +5 -22
  151. package/dist/services/globalSave.d.ts +13 -12
  152. package/dist/services/globalSave.js +29 -51
  153. package/dist/services/historyService.d.ts +9 -3
  154. package/dist/services/historyService.js +9 -3
  155. package/dist/services/interruptService.d.ts +14 -9
  156. package/dist/services/interruptService.js +27 -27
  157. package/dist/services/nodeExecutionService.d.ts +18 -3
  158. package/dist/services/nodeExecutionService.js +71 -45
  159. package/dist/services/playgroundService.d.ts +14 -9
  160. package/dist/services/playgroundService.js +31 -30
  161. package/dist/services/variableService.d.ts +2 -1
  162. package/dist/services/variableService.js +2 -2
  163. package/dist/services/workflowStorage.js +6 -6
  164. package/dist/stores/apiContext.d.ts +45 -0
  165. package/dist/stores/apiContext.js +65 -0
  166. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  167. package/dist/stores/categoriesStore.svelte.js +70 -64
  168. package/dist/stores/getInstance.svelte.d.ts +39 -0
  169. package/dist/stores/getInstance.svelte.js +65 -0
  170. package/dist/stores/historyStore.svelte.d.ts +77 -93
  171. package/dist/stores/historyStore.svelte.js +134 -160
  172. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  173. package/dist/stores/instanceContainer.svelte.js +114 -0
  174. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  175. package/dist/stores/interruptStore.svelte.js +253 -226
  176. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  177. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  178. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  179. package/dist/stores/playgroundStore.svelte.js +515 -580
  180. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  181. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  182. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  183. package/dist/stores/settingsStore.svelte.js +47 -12
  184. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  185. package/dist/stores/workflowStore.svelte.js +449 -501
  186. package/dist/stories/EdgeDecorator.svelte +5 -2
  187. package/dist/stories/NodeDecorator.svelte +5 -3
  188. package/dist/svelte-app.d.ts +60 -10
  189. package/dist/svelte-app.js +157 -53
  190. package/dist/types/events.d.ts +6 -3
  191. package/dist/types/index.d.ts +33 -3
  192. package/dist/types/navbar.d.ts +7 -0
  193. package/dist/types/playground.d.ts +18 -3
  194. package/dist/types/settings.d.ts +13 -0
  195. package/dist/types/settings.js +1 -0
  196. package/dist/utils/colors.d.ts +47 -21
  197. package/dist/utils/colors.js +69 -68
  198. package/dist/utils/connections.d.ts +9 -15
  199. package/dist/utils/connections.js +13 -32
  200. package/dist/utils/duration.d.ts +13 -0
  201. package/dist/utils/duration.js +45 -0
  202. package/dist/utils/icons.d.ts +5 -2
  203. package/dist/utils/icons.js +6 -5
  204. package/dist/utils/nodeSwap.d.ts +6 -2
  205. package/dist/utils/nodeSwap.js +62 -126
  206. package/dist/utils/nodeTypes.d.ts +17 -8
  207. package/dist/utils/nodeTypes.js +26 -19
  208. package/dist/utils/performanceUtils.js +7 -0
  209. package/package.json +6 -5
  210. package/dist/messages/deprecation.d.ts +0 -20
  211. package/dist/messages/deprecation.js +0 -33
  212. package/dist/registry/plugin.d.ts +0 -215
  213. package/dist/registry/plugin.js +0 -249
  214. package/dist/services/api.d.ts +0 -129
  215. package/dist/services/api.js +0 -217
@@ -45,6 +45,15 @@ export class PortCompatibilityChecker {
45
45
  this.compatibilityMap = new Map();
46
46
  this.buildCompatibilityMap();
47
47
  }
48
+ /**
49
+ * Replace the port configuration and rebuild the compatibility map in place.
50
+ * Used by mount to apply the backend-fetched port config to an instance's
51
+ * checker without swapping the instance field.
52
+ */
53
+ reinitialize(portConfig) {
54
+ this.portConfig = portConfig;
55
+ this.buildCompatibilityMap();
56
+ }
48
57
  /**
49
58
  * Build the compatibility map from configuration rules
50
59
  */
@@ -106,41 +115,16 @@ export class PortCompatibilityChecker {
106
115
  return this.portConfig.dataTypes.filter((dt) => dt.enabled !== false);
107
116
  }
108
117
  }
109
- // Global instance - will be initialized with configuration
110
- let globalCompatibilityChecker = null;
111
- /**
112
- * Initialize the global port compatibility checker
113
- */
114
- export function initializePortCompatibility(portConfig) {
115
- globalCompatibilityChecker = new PortCompatibilityChecker(portConfig);
116
- }
117
- /**
118
- * Returns true if the port compatibility checker has been initialized.
119
- */
120
- export function isPortCompatibilityInitialized() {
121
- return globalCompatibilityChecker !== null;
122
- }
123
- /**
124
- * Get the global port compatibility checker
125
- */
126
- export function getPortCompatibilityChecker() {
127
- if (!globalCompatibilityChecker) {
128
- throw new Error('Port compatibility checker not initialized. Call initializePortCompatibility() first.');
129
- }
130
- return globalCompatibilityChecker;
131
- }
132
118
  /**
133
119
  * Get all possible connections from a source node to target nodes
134
120
  */
135
- export function getPossibleConnections(sourceNode, targetNodes, nodeTypes) {
121
+ export function getPossibleConnections(checker, sourceNode, targetNodes, nodeTypes) {
136
122
  const sourceMetadata = nodeTypes.find((nt) => nt.id === sourceNode.data.metadata.id);
137
123
  if (!sourceMetadata)
138
124
  return [];
139
125
  const possibleConnections = [];
140
126
  // Get all output ports from source node
141
127
  const sourceOutputs = sourceMetadata.outputs;
142
- // Get the compatibility checker instance
143
- const checker = getPortCompatibilityChecker();
144
128
  // Check each target node
145
129
  for (const targetNode of targetNodes) {
146
130
  if (targetNode.id === sourceNode.id)
@@ -171,7 +155,7 @@ export function getPossibleConnections(sourceNode, targetNodes, nodeTypes) {
171
155
  /**
172
156
  * Validate if a specific connection is valid
173
157
  */
174
- export function validateConnection(sourceNodeId, sourcePortId, targetNodeId, targetPortId, nodes, nodeTypes) {
158
+ export function validateConnection(checker, sourceNodeId, sourcePortId, targetNodeId, targetPortId, nodes, nodeTypes) {
175
159
  // Check if nodes exist
176
160
  const sourceNode = nodes.find((n) => n.id === sourceNodeId);
177
161
  const targetNode = nodes.find((n) => n.id === targetNodeId);
@@ -200,8 +184,7 @@ export function validateConnection(sourceNodeId, sourcePortId, targetNodeId, tar
200
184
  if (!targetPort) {
201
185
  return { valid: false, error: 'Target port not found' };
202
186
  }
203
- // Check data type compatibility using the global checker
204
- const checker = getPortCompatibilityChecker();
187
+ // Check data type compatibility using the instance's checker
205
188
  if (!checker.areDataTypesCompatible(sourcePort.dataType, targetPort.dataType)) {
206
189
  return {
207
190
  valid: false,
@@ -213,7 +196,7 @@ export function validateConnection(sourceNodeId, sourcePortId, targetNodeId, tar
213
196
  /**
214
197
  * Get connection suggestions for a node
215
198
  */
216
- export function getConnectionSuggestions(nodeId, nodes, nodeTypes) {
199
+ export function getConnectionSuggestions(checker, nodeId, nodes, nodeTypes) {
217
200
  const node = nodes.find((n) => n.id === nodeId);
218
201
  if (!node)
219
202
  return [];
@@ -223,8 +206,6 @@ export function getConnectionSuggestions(nodeId, nodes, nodeTypes) {
223
206
  const suggestions = [];
224
207
  // Get all other nodes
225
208
  const otherNodes = nodes.filter((n) => n.id !== nodeId);
226
- // Get the compatibility checker instance
227
- const checker = getPortCompatibilityChecker();
228
209
  for (const otherNode of otherNodes) {
229
210
  const otherMetadata = nodeTypes.find((nt) => nt.id === otherNode.data.metadata.id);
230
211
  if (!otherMetadata)
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Duration formatting helpers.
3
+ *
4
+ * Mirrors the backend `Drupal\flowdrop\Utility\Duration::formatMicroseconds()`
5
+ * tiers so the playground and the Drupal admin pages render identical values.
6
+ */
7
+ /**
8
+ * Formats a duration in human-readable form from microseconds.
9
+ *
10
+ * Examples: `150µs`, `2.5ms`, `25.1ms`, `250ms`, `1.23s`, `45s`,
11
+ * `2m 30s`, `1h 30m`.
12
+ */
13
+ export declare function formatMicroseconds(microseconds: number | null | undefined): string | null;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Duration formatting helpers.
3
+ *
4
+ * Mirrors the backend `Drupal\flowdrop\Utility\Duration::formatMicroseconds()`
5
+ * tiers so the playground and the Drupal admin pages render identical values.
6
+ */
7
+ /**
8
+ * Formats a duration in human-readable form from microseconds.
9
+ *
10
+ * Examples: `150µs`, `2.5ms`, `25.1ms`, `250ms`, `1.23s`, `45s`,
11
+ * `2m 30s`, `1h 30m`.
12
+ */
13
+ export function formatMicroseconds(microseconds) {
14
+ if (microseconds == null || !Number.isFinite(microseconds) || microseconds < 0)
15
+ return null;
16
+ const us = Math.round(microseconds);
17
+ if (us < 1000) {
18
+ return `${us}µs`;
19
+ }
20
+ if (us < 1_000_000) {
21
+ const ms = us / 1000;
22
+ if (us < 100_000) {
23
+ // 2 decimals below 10ms, 1 decimal below 100ms — matches PHP round().
24
+ const decimals = us < 10_000 ? 2 : 1;
25
+ // Drop trailing zeros the way PHP round() renders (2.50 → 2.5, 3.00 → 3).
26
+ return `${parseFloat(ms.toFixed(decimals))}ms`;
27
+ }
28
+ return `${Math.round(ms)}ms`;
29
+ }
30
+ if (us < 10_000_000) {
31
+ return `${parseFloat((us / 1_000_000).toFixed(2))}s`;
32
+ }
33
+ if (us < 60_000_000) {
34
+ return `${Math.floor(us / 1_000_000)}s`;
35
+ }
36
+ const totalSeconds = Math.floor(us / 1_000_000);
37
+ if (us < 3_600_000_000) {
38
+ const minutes = Math.floor(totalSeconds / 60);
39
+ const secs = totalSeconds % 60;
40
+ return secs > 0 ? `${minutes}m ${secs}s` : `${minutes}m`;
41
+ }
42
+ const hours = Math.floor(totalSeconds / 3600);
43
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
44
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
45
+ }
@@ -3,6 +3,7 @@
3
3
  * Ensures consistent icon usage across all components
4
4
  */
5
5
  import type { NodeCategory } from '../types/index.js';
6
+ import type { CategoriesStore } from '../stores/categoriesStore.svelte.js';
6
7
  /**
7
8
  * Default icons for different contexts
8
9
  */
@@ -72,19 +73,21 @@ export declare const DEFAULT_ICONS: {
72
73
  export declare const CATEGORY_ICONS: Record<string, string>;
73
74
  /**
74
75
  * Get the appropriate icon for a node
76
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
75
77
  * @param nodeIcon - The node's specific icon
76
78
  * @param category - The node's category
77
79
  * @returns The icon to use
78
80
  */
79
- export declare function getNodeIcon(nodeIcon?: string, category?: NodeCategory): string;
81
+ export declare function getNodeIcon(categories: CategoriesStore, nodeIcon?: string, category?: NodeCategory): string;
80
82
  /**
81
83
  * Get the appropriate icon for a category.
82
84
  * Checks the categories store first (which includes API overrides),
83
85
  * then falls back to the static CATEGORY_ICONS map, then to the default.
86
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
84
87
  * @param category - The category
85
88
  * @returns The icon to use
86
89
  */
87
- export declare function getCategoryIcon(category: NodeCategory): string;
90
+ export declare function getCategoryIcon(categories: CategoriesStore, category: NodeCategory): string;
88
91
  /**
89
92
  * Get a default icon by key
90
93
  * @param key - The icon key from DEFAULT_ICONS
@@ -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[];