@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
@@ -21,12 +21,7 @@
21
21
  getEditorSettings,
22
22
  getBehaviorSettings
23
23
  } from '../stores/settingsStore.svelte.js';
24
- import type {
25
- WorkflowNode as WorkflowNodeType,
26
- NodeMetadata,
27
- Workflow,
28
- WorkflowEdge
29
- } from '../types/index.js';
24
+ import type { WorkflowNode as WorkflowNodeType, Workflow, WorkflowEdge } from '../types/index.js';
30
25
  import CanvasBanner from './CanvasBanner.svelte';
31
26
  import CanvasController from './CanvasController.svelte';
32
27
  import FlowDropZone from './FlowDropZone.svelte';
@@ -36,8 +31,8 @@
36
31
  import ConnectionLine from './ConnectionLine.svelte';
37
32
  import FlowDropEdge from './FlowDropEdge.svelte';
38
33
  import { m } from '../messages/index.js';
39
- import { getWorkflowStore, workflowActions } from '../stores/workflowStore.svelte.js';
40
- import { historyActions, setOnRestoreCallback } from '../stores/historyStore.svelte.js';
34
+ import { provideInstance } from '../stores/getInstance.svelte.js';
35
+ import type { FlowDropInstance } from '../stores/instanceContainer.svelte.js';
41
36
  import UniversalNode from './UniversalNode.svelte';
42
37
  import {
43
38
  EdgeStylingHelper,
@@ -57,34 +52,50 @@
57
52
  type ProximityEdgeCandidate
58
53
  } from '../helpers/proximityConnect.js';
59
54
  import PortCoordinateTracker from './PortCoordinateTracker.svelte';
60
- import { getPortCoordinateSnapshot } from '../stores/portCoordinateStore.svelte.js';
61
55
  import { logger } from '../utils/logger.js';
62
56
  import { validateWorkflowData } from '../utils/validation.js';
63
57
  import { createEditorStateMachine } from '../stores/editorStateMachine.svelte.js';
64
58
  import Icon from '@iconify/svelte';
59
+ import { DEV } from 'esm-env';
65
60
 
66
61
  interface Props {
67
- nodes?: NodeMetadata[];
68
62
  endpointConfig?: EndpointConfig;
69
- height?: string | number;
70
- width?: string | number;
71
- isConfigSidebarOpen?: boolean;
72
- selectedNodeForConfig?: WorkflowNodeType | null;
73
63
  openConfigSidebar?: (node: WorkflowNodeType) => void;
74
- closeConfigSidebar?: () => void;
75
- // New configuration options for pipeline status mode
76
- lockWorkflow?: boolean;
77
- readOnly?: boolean;
78
- nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
64
+ /**
65
+ * Editor interaction mode. `'edit'` allows node drag/connect/select and
66
+ * proximity-connect; `'readonly'` and `'locked'` disable all canvas
67
+ * editing (they behave identically today — see App's `mode` prop for the
68
+ * full matrix). Replaces the former `readOnly` + `lockWorkflow` booleans.
69
+ * @default 'edit'
70
+ */
71
+ mode?: 'edit' | 'readonly' | 'locked';
79
72
  // Pipeline ID for fetching node execution info from jobs
80
73
  pipelineId?: string;
74
+ /**
75
+ * Increments to force a re-fetch of node execution info from the server.
76
+ * Used by parents (e.g. PipelineStatus) to push poll ticks / chat-message
77
+ * arrivals down to the canvas without owning a separate status channel.
78
+ */
79
+ refreshTrigger?: number;
81
80
  // Console toggle
82
81
  consoleOpen?: boolean;
83
82
  onToggleConsole?: () => void;
83
+ /** Per-instance state container (created by mount functions). Defaults to the page-default instance. */
84
+ instance?: FlowDropInstance;
84
85
  }
85
86
 
86
87
  let props: Props = $props();
87
88
 
89
+ // Resolve (and provide to children) the per-instance state container.
90
+ // Must run during component init — provideInstance reads/sets Svelte context.
91
+ // The instance never changes for a mounted component, so capturing it once is correct.
92
+ // svelte-ignore state_referenced_locally
93
+ const fd = provideInstance(props.instance);
94
+
95
+ // `mode` is the public API; the canvas only needs to know whether editing is
96
+ // enabled. 'readonly' and 'locked' both disable interaction identically.
97
+ const canvasEditable = $derived((props.mode ?? 'edit') === 'edit');
98
+
88
99
  // ---------------------------------------------------------------------------
89
100
  // Editor State Machine
90
101
  // Centralizes reactive guards — replaces scattered boolean flags
@@ -93,7 +104,7 @@
93
104
  const machine = createEditorStateMachine();
94
105
 
95
106
  // Dev-mode transition logging
96
- if (import.meta.env?.DEV) {
107
+ if (DEV) {
97
108
  machine.onTransition((from, event, to) => {
98
109
  logger.debug(`[EditorFSM] ${from} --${event}--> ${to}`);
99
110
  });
@@ -122,7 +133,7 @@
122
133
  * Key for SvelteFlow component — changes when workflow ID changes.
123
134
  * Forces SvelteFlow to remount with fresh state, allowing fitView to work correctly.
124
135
  */
125
- let svelteFlowKey = $derived(getWorkflowStore()?.id ?? 'default');
136
+ let svelteFlowKey = $derived(fd.workflow.current?.id ?? 'default');
126
137
 
127
138
  /**
128
139
  * Derive snap grid configuration from editor settings
@@ -164,14 +175,14 @@
164
175
  // Helper: sync current flowNodes/flowEdges back to the global store
165
176
  // ---------------------------------------------------------------------------
166
177
  function syncFlowToStore(): void {
167
- const storeValue = untrack(() => getWorkflowStore());
178
+ const storeValue = untrack(() => fd.workflow.current);
168
179
  if (!storeValue) return;
169
180
  const updatedWorkflow = WorkflowOperationsHelper.updateWorkflow(
170
181
  storeValue,
171
182
  flowNodes,
172
183
  flowEdges
173
184
  );
174
- workflowActions.updateWorkflow(updatedWorkflow);
185
+ fd.workflow.updateWorkflow(updatedWorkflow);
175
186
  }
176
187
 
177
188
  // ---------------------------------------------------------------------------
@@ -182,7 +193,7 @@
182
193
  let previousSyncedWorkflowId: string | null = null;
183
194
 
184
195
  $effect(() => {
185
- const storeValue = getWorkflowStore();
196
+ const storeValue = fd.workflow.current;
186
197
 
187
198
  // Suppressed during operations — handlers write to flowNodes directly
188
199
  if (untrack(() => machine.permissions.suppressEffect)) return;
@@ -228,7 +239,7 @@
228
239
  let previousExecPipelineId: string | undefined = undefined;
229
240
 
230
241
  $effect(() => {
231
- const storeValue = getWorkflowStore();
242
+ const storeValue = fd.workflow.current;
232
243
  const pipelineId = props.pipelineId;
233
244
 
234
245
  if (!storeValue || !pipelineId) return;
@@ -241,15 +252,12 @@
241
252
  previousExecWorkflowId = storeValue.id;
242
253
  previousExecPipelineId = pipelineId;
243
254
 
244
- // Cancel any pending timeout / in-flight request
255
+ // Cancel any pending idle/timeout schedule. In-flight fetches are
256
+ // cancelled by loadNodeExecutionInfo() itself when it's re-entered.
245
257
  if (loadExecutionInfoTimeout) {
246
258
  clearTimeout(loadExecutionInfoTimeout);
247
259
  loadExecutionInfoTimeout = null;
248
260
  }
249
- if (executionInfoAbortController) {
250
- executionInfoAbortController.abort();
251
- executionInfoAbortController = null;
252
- }
253
261
 
254
262
  // Schedule loading with requestIdleCallback (falls back to setTimeout)
255
263
  if (typeof requestIdleCallback !== 'undefined') {
@@ -266,44 +274,26 @@
266
274
  }
267
275
  });
268
276
 
269
- // Apply nodeStatuses from the parent (PipelineStatus embedded mode) to flowNodes
270
- // whenever they change. loadNodeExecutionInfo() only fires on pipelineId change,
271
- // so this is the update path for subsequent refreshes (e.g. after HITL resolution).
277
+ // Re-fetch node execution info when the parent bumps refreshTrigger
278
+ // (poll ticks, chat-message arrivals, manual refresh). loadNodeExecutionInfo()
279
+ // is the single writer of executionInfo on flowNodes no parallel channel.
280
+ // svelte-ignore state_referenced_locally
281
+ let _prevExecRefreshTrigger = props.refreshTrigger ?? 0;
272
282
  $effect(() => {
273
- const statuses = props.nodeStatuses;
274
- if (!statuses || Object.keys(statuses).length === 0) return;
275
-
276
- flowNodes = untrack(() => flowNodes).map((node) => {
277
- const rawStatus = statuses[node.id];
278
- if (!rawStatus) return node;
279
-
280
- const existing = node.data.executionInfo ?? {
281
- status: 'idle' as const,
282
- executionCount: 0,
283
- isExecuting: false
284
- };
285
- return {
286
- ...node,
287
- data: {
288
- ...node.data,
289
- executionInfo: {
290
- ...existing,
291
- status: rawStatus === 'error' ? ('failed' as const) : rawStatus,
292
- isExecuting: rawStatus === 'running'
293
- }
294
- }
295
- };
296
- });
283
+ const t = props.refreshTrigger ?? 0;
284
+ if (t === 0 || t === _prevExecRefreshTrigger) return;
285
+ _prevExecRefreshTrigger = t;
286
+ loadNodeExecutionInfo();
297
287
  });
298
288
 
299
289
  // ---------------------------------------------------------------------------
300
290
  // History restore callback
301
291
  // ---------------------------------------------------------------------------
302
292
  $effect(() => {
303
- setOnRestoreCallback((restoredWorkflow: Workflow) => {
293
+ fd.historyBindings.setOnRestoreCallback((restoredWorkflow: Workflow) => {
304
294
  machine.send('START_RESTORE');
305
295
  // Update the store (effect is suppressed during 'restoring')
306
- workflowActions.restoreFromHistory(restoredWorkflow);
296
+ fd.workflow.restoreFromHistory(restoredWorkflow);
307
297
  // Derive flowNodes/flowEdges directly for immediate visual update
308
298
  const derived = buildFlowNodesFromStore(restoredWorkflow);
309
299
  flowNodes = derived.nodes;
@@ -314,26 +304,38 @@
314
304
  });
315
305
 
316
306
  return () => {
317
- setOnRestoreCallback(null);
307
+ // Reinstate the container's default wiring rather than nulling it: the
308
+ // default instance survives unmount, and a bare null would make legacy
309
+ // historyActions.undo() silently restore nothing afterwards.
310
+ fd.historyBindings.setOnRestoreCallback((restored: Workflow) =>
311
+ fd.workflow.restoreFromHistory(restored)
312
+ );
318
313
  };
319
314
  });
320
315
 
321
316
  /**
322
- * Load node execution information for all nodes in the workflow
317
+ * Load node execution information for all nodes in the workflow.
318
+ * Cancels any in-flight fetch so concurrent callers (pipelineId change
319
+ * and refreshTrigger bumps) can't race on flowNodes.
323
320
  */
324
321
  async function loadNodeExecutionInfo(): Promise<void> {
325
- const workflow = untrack(() => getWorkflowStore());
322
+ const workflow = untrack(() => fd.workflow.current);
326
323
  if (!workflow?.nodes || !props.pipelineId) return;
327
324
 
328
- try {
329
- executionInfoAbortController = new AbortController();
325
+ if (executionInfoAbortController) {
326
+ executionInfoAbortController.abort();
327
+ }
328
+ const controller = new AbortController();
329
+ executionInfoAbortController = controller;
330
330
 
331
+ try {
331
332
  const executionInfo = await NodeOperationsHelper.loadNodeExecutionInfo(
333
+ fd.api,
332
334
  workflow,
333
335
  props.pipelineId
334
336
  );
335
337
 
336
- if (executionInfoAbortController?.signal.aborted) return;
338
+ if (controller.signal.aborted) return;
337
339
 
338
340
  const defaultExecutionInfo: NodeExecutionInfo = {
339
341
  status: 'idle' as const,
@@ -350,7 +352,9 @@
350
352
  }
351
353
  }));
352
354
 
353
- executionInfoAbortController = null;
355
+ if (executionInfoAbortController === controller) {
356
+ executionInfoAbortController = null;
357
+ }
354
358
  } catch (error) {
355
359
  if (error instanceof Error && error.name !== 'AbortError') {
356
360
  logger.error('Failed to load node execution info:', error);
@@ -401,12 +405,7 @@
401
405
  nodes: WorkflowNodeType[];
402
406
  event: MouseEvent | TouchEvent;
403
407
  }): void {
404
- if (
405
- !getEditorSettings().proximityConnect ||
406
- !targetNode ||
407
- props.readOnly ||
408
- props.lockWorkflow
409
- ) {
408
+ if (!getEditorSettings().proximityConnect || !targetNode || !canvasEditable) {
410
409
  if (currentProximityCandidates.length > 0) {
411
410
  flowEdges = ProximityConnectHelper.removePreviewEdges(flowEdges);
412
411
  currentProximityCandidates = [];
@@ -422,16 +421,18 @@
422
421
  const baseEdges = ProximityConnectHelper.removePreviewEdges(flowEdges);
423
422
 
424
423
  // Find the best compatible edge using port-to-port distance
425
- const portCoordinates = getPortCoordinateSnapshot();
424
+ const portCoordinates = fd.portCoordinates.coordinates;
426
425
  const candidates =
427
426
  portCoordinates.size > 0
428
427
  ? ProximityConnectHelper.findCompatibleEdgesByPortCoordinates(
428
+ fd.portCompatibility,
429
429
  targetNode.id,
430
430
  portCoordinates,
431
431
  baseEdges,
432
432
  getEditorSettings().proximityConnectDistance
433
433
  )
434
434
  : ProximityConnectHelper.findCompatibleEdges(
435
+ fd.portCompatibility,
435
436
  targetNode,
436
437
  flowNodes,
437
438
  baseEdges,
@@ -478,9 +479,9 @@
478
479
  syncFlowToStore();
479
480
 
480
481
  // Push history AFTER the drag completed
481
- const storeValue = getWorkflowStore();
482
+ const storeValue = fd.workflow.current;
482
483
  if (storeValue) {
483
- workflowActions.pushHistory('Move node', storeValue);
484
+ fd.workflow.pushHistory('Move node', storeValue);
484
485
  }
485
486
 
486
487
  // Transition to idle — sync effect is now unblocked
@@ -488,14 +489,11 @@
488
489
  }
489
490
 
490
491
  /**
491
- * Handle new connections between nodes
492
+ * Handle new connections between nodes.
493
+ * The connection details aren't needed — SvelteFlow auto-creates the edge
494
+ * via bind:edges; this only drives the state machine and history snapshot.
492
495
  */
493
- async function handleConnect(connection: {
494
- source: string;
495
- target: string;
496
- sourceHandle?: string;
497
- targetHandle?: string;
498
- }): Promise<void> {
496
+ async function handleConnect(): Promise<void> {
499
497
  machine.send('START_CONNECT');
500
498
 
501
499
  // SvelteFlow auto-creates the edge via bind:edges — wait for DOM update
@@ -507,9 +505,9 @@
507
505
  // Sync to store
508
506
  syncFlowToStore();
509
507
 
510
- const storeValue = getWorkflowStore();
508
+ const storeValue = fd.workflow.current;
511
509
  if (storeValue) {
512
- workflowActions.pushHistory('Add connection', storeValue);
510
+ fd.workflow.pushHistory('Add connection', storeValue);
513
511
  }
514
512
 
515
513
  machine.send('CONNECTION_MADE');
@@ -585,9 +583,9 @@
585
583
  } else if (edgeCount > 0) {
586
584
  description = `Delete ${edgeCount} connection${edgeCount > 1 ? 's' : ''}`;
587
585
  }
588
- const storeValue = getWorkflowStore();
586
+ const storeValue = fd.workflow.current;
589
587
  if (storeValue) {
590
- workflowActions.pushHistory(description, storeValue);
588
+ fd.workflow.pushHistory(description, storeValue);
591
589
  }
592
590
 
593
591
  machine.send('DELETE_COMPLETE');
@@ -598,7 +596,7 @@
598
596
  // Configure endpoints when props change
599
597
  $effect(() => {
600
598
  if (props.endpointConfig) {
601
- ConfigurationHelper.configureEndpoints(props.endpointConfig);
599
+ ConfigurationHelper.configureEndpoints(fd.api, props.endpointConfig);
602
600
  }
603
601
  });
604
602
 
@@ -635,9 +633,9 @@
635
633
 
636
634
  await tick();
637
635
 
638
- const storeValue = getWorkflowStore();
636
+ const storeValue = fd.workflow.current;
639
637
  if (storeValue) {
640
- workflowActions.pushHistory('Add node', storeValue);
638
+ fd.workflow.pushHistory('Add node', storeValue);
641
639
  }
642
640
  } else {
643
641
  logger.warn('Failed to create node from drop data');
@@ -667,7 +665,7 @@
667
665
  logger.warn('Workflow file drop validation failed:', validation.error);
668
666
  return;
669
667
  }
670
- workflowActions.initialize(data as Workflow);
668
+ fd.workflow.initialize(data as Workflow);
671
669
  } catch (error) {
672
670
  const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
673
671
  logger.error('Workflow file drop import failed:', errorObj);
@@ -693,7 +691,7 @@
693
691
  /**
694
692
  * Update a node's data in the local editor state.
695
693
  * Called by App.svelte AFTER it has already updated the global store via
696
- * workflowActions.updateNode(). We only need to update flowNodes for
694
+ * fd.workflow.updateNode(). We only need to update flowNodes for
697
695
  * immediate visual feedback — no store sync needed.
698
696
  *
699
697
  * @param nodeId - The ID of the node to update
@@ -774,15 +772,15 @@
774
772
  * - Ctrl+Z (or Cmd+Z on Mac): Undo
775
773
  * - Ctrl+Shift+Z (or Cmd+Shift+Z): Redo
776
774
  * - Ctrl+Y (or Cmd+Y): Redo (Windows convention)
775
+ *
776
+ * Also suppresses WebKit's legacy default action for Backspace outside
777
+ * editable content — history back-navigation. SvelteFlow's KeyHandler
778
+ * deletes the selected elements on Backspace but never preventDefaults,
779
+ * so in embedded WebKit the page navigates away mid-delete. Scoped to
780
+ * keydowns originating inside the flow canvas so host-page behavior
781
+ * outside the editor is untouched.
777
782
  */
778
783
  function handleKeydown(event: KeyboardEvent): void {
779
- // Check for Ctrl (Windows/Linux) or Cmd (Mac)
780
- const isModifierPressed = event.ctrlKey || event.metaKey;
781
-
782
- if (!isModifierPressed) {
783
- return;
784
- }
785
-
786
784
  // Don't handle shortcuts if user is typing in an input, textarea, or contenteditable
787
785
  const target = event.target as HTMLElement;
788
786
  const isInputElement =
@@ -792,17 +790,31 @@
792
790
  return;
793
791
  }
794
792
 
793
+ // Backspace/Delete on a canvas element: let SvelteFlow handle the
794
+ // deletion, but block the browser default (WebKit navigates back).
795
+ if ((event.key === 'Backspace' || event.key === 'Delete') && target.closest('.svelte-flow')) {
796
+ event.preventDefault();
797
+ return;
798
+ }
799
+
800
+ // Check for Ctrl (Windows/Linux) or Cmd (Mac)
801
+ const isModifierPressed = event.ctrlKey || event.metaKey;
802
+
803
+ if (!isModifierPressed) {
804
+ return;
805
+ }
806
+
795
807
  // Undo: Ctrl+Z (without Shift)
796
808
  if (event.key === 'z' && !event.shiftKey) {
797
809
  event.preventDefault();
798
- historyActions.undo();
810
+ fd.historyBindings.undo();
799
811
  return;
800
812
  }
801
813
 
802
814
  // Redo: Ctrl+Shift+Z or Ctrl+Y
803
815
  if ((event.key === 'z' && event.shiftKey) || event.key === 'y') {
804
816
  event.preventDefault();
805
- historyActions.redo();
817
+ fd.historyBindings.redo();
806
818
  return;
807
819
  }
808
820
  }
@@ -832,18 +844,13 @@
832
844
  <FlowDropZone ondrop={handleNodeDrop} onfiledrop={handleWorkflowFileDrop}>
833
845
  {#key svelteFlowKey}
834
846
  <SvelteFlow
847
+ id={fd.id}
835
848
  bind:nodes={flowNodes}
836
849
  bind:edges={flowEdges}
837
850
  {nodeTypes}
838
851
  {edgeTypes}
839
852
  {defaultEdgeOptions}
840
- onconnect={(connection) =>
841
- void handleConnect({
842
- source: connection.source,
843
- target: connection.target,
844
- sourceHandle: connection.sourceHandle ?? undefined,
845
- targetHandle: connection.targetHandle ?? undefined
846
- })}
853
+ onconnect={() => void handleConnect()}
847
854
  onbeforedelete={handleBeforeDelete}
848
855
  ondelete={handleNodesDelete}
849
856
  onnodedragstart={handleNodeDragStart}
@@ -859,12 +866,12 @@
859
866
  {initialViewport}
860
867
  colorMode={getResolvedTheme() as ColorMode}
861
868
  fitView={getEditorSettings().fitViewOnLoad}
862
- nodesDraggable={!props.lockWorkflow && !props.readOnly}
863
- nodesConnectable={!props.lockWorkflow && !props.readOnly}
864
- elementsSelectable={!props.lockWorkflow && !props.readOnly}
869
+ nodesDraggable={canvasEditable}
870
+ nodesConnectable={canvasEditable}
871
+ elementsSelectable={canvasEditable}
865
872
  >
866
873
  <Controls />
867
- {#if !props.readOnly && !props.lockWorkflow && props.onToggleConsole}
874
+ {#if canvasEditable && props.onToggleConsole}
868
875
  <button
869
876
  class="flowdrop-console-toggle"
870
877
  class:flowdrop-console-toggle--active={props.consoleOpen}
@@ -1,21 +1,29 @@
1
1
  import '@xyflow/svelte/dist/style.css';
2
- import type { WorkflowNode as WorkflowNodeType, NodeMetadata } from '../types/index.js';
2
+ import type { WorkflowNode as WorkflowNodeType } from '../types/index.js';
3
3
  import type { EndpointConfig } from '../config/endpoints.js';
4
+ import type { FlowDropInstance } from '../stores/instanceContainer.svelte.js';
4
5
  interface Props {
5
- nodes?: NodeMetadata[];
6
6
  endpointConfig?: EndpointConfig;
7
- height?: string | number;
8
- width?: string | number;
9
- isConfigSidebarOpen?: boolean;
10
- selectedNodeForConfig?: WorkflowNodeType | null;
11
7
  openConfigSidebar?: (node: WorkflowNodeType) => void;
12
- closeConfigSidebar?: () => void;
13
- lockWorkflow?: boolean;
14
- readOnly?: boolean;
15
- nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
8
+ /**
9
+ * Editor interaction mode. `'edit'` allows node drag/connect/select and
10
+ * proximity-connect; `'readonly'` and `'locked'` disable all canvas
11
+ * editing (they behave identically today — see App's `mode` prop for the
12
+ * full matrix). Replaces the former `readOnly` + `lockWorkflow` booleans.
13
+ * @default 'edit'
14
+ */
15
+ mode?: 'edit' | 'readonly' | 'locked';
16
16
  pipelineId?: string;
17
+ /**
18
+ * Increments to force a re-fetch of node execution info from the server.
19
+ * Used by parents (e.g. PipelineStatus) to push poll ticks / chat-message
20
+ * arrivals down to the canvas without owning a separate status channel.
21
+ */
22
+ refreshTrigger?: number;
17
23
  consoleOpen?: boolean;
18
24
  onToggleConsole?: () => void;
25
+ /** Per-instance state container (created by mount functions). Defaults to the page-default instance. */
26
+ instance?: FlowDropInstance;
19
27
  }
20
28
  declare const WorkflowEditor: import("svelte").Component<Props, {
21
29
  updateNodeData: (nodeId: string, dataUpdates: Partial<WorkflowNodeType["data"]>) => void;