@d34dman/flowdrop 0.0.15 → 0.0.17

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 (40) hide show
  1. package/README.md +64 -1
  2. package/dist/api/enhanced-client.d.ts +119 -3
  3. package/dist/api/enhanced-client.js +233 -54
  4. package/dist/components/App.svelte +145 -33
  5. package/dist/components/App.svelte.d.ts +27 -1
  6. package/dist/components/FlowDropZone.svelte +4 -5
  7. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  8. package/dist/components/UniversalNode.svelte +94 -34
  9. package/dist/components/WorkflowEditor.svelte +63 -3
  10. package/dist/config/runtimeConfig.d.ts +2 -2
  11. package/dist/config/runtimeConfig.js +7 -7
  12. package/dist/data/samples.js +9 -9
  13. package/dist/examples/adapter-usage.js +1 -1
  14. package/dist/helpers/workflowEditorHelper.d.ts +44 -4
  15. package/dist/helpers/workflowEditorHelper.js +161 -30
  16. package/dist/index.d.ts +12 -2
  17. package/dist/index.js +20 -1
  18. package/dist/registry/builtinNodes.d.ts +77 -0
  19. package/dist/registry/builtinNodes.js +181 -0
  20. package/dist/registry/index.d.ts +7 -0
  21. package/dist/registry/index.js +10 -0
  22. package/dist/registry/nodeComponentRegistry.d.ts +307 -0
  23. package/dist/registry/nodeComponentRegistry.js +315 -0
  24. package/dist/registry/plugin.d.ts +215 -0
  25. package/dist/registry/plugin.js +249 -0
  26. package/dist/services/draftStorage.d.ts +171 -0
  27. package/dist/services/draftStorage.js +298 -0
  28. package/dist/stores/workflowStore.d.ts +103 -0
  29. package/dist/stores/workflowStore.js +249 -29
  30. package/dist/styles/base.css +15 -0
  31. package/dist/svelte-app.d.ts +110 -28
  32. package/dist/svelte-app.js +150 -27
  33. package/dist/types/auth.d.ts +278 -0
  34. package/dist/types/auth.js +244 -0
  35. package/dist/types/events.d.ts +163 -0
  36. package/dist/types/events.js +30 -0
  37. package/dist/types/index.d.ts +38 -3
  38. package/dist/utils/nodeTypes.d.ts +76 -21
  39. package/dist/utils/nodeTypes.js +180 -32
  40. package/package.json +1 -2
@@ -17,24 +17,44 @@
17
17
  import { sampleNodes } from '../data/samples.js';
18
18
  import { createEndpointConfig } from '../config/endpoints.js';
19
19
  import type { EndpointConfig } from '../config/endpoints.js';
20
- import { workflowStore, workflowActions, workflowName } from '../stores/workflowStore.js';
20
+ import type { AuthProvider } from '../types/auth.js';
21
+ import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
22
+ import { mergeFeatures } from '../types/events.js';
23
+ import {
24
+ workflowStore,
25
+ workflowActions,
26
+ workflowName,
27
+ markAsSaved
28
+ } from '../stores/workflowStore.js';
21
29
  import { apiToasts, dismissToast } from '../services/toastService.js';
22
30
 
23
- // Configuration props for runtime customization
31
+ /**
32
+ * Configuration props for runtime customization
33
+ */
24
34
  interface Props {
35
+ /** Initial workflow to load */
25
36
  workflow?: Workflow;
37
+ /** Pre-loaded node types (if provided, skips API fetch) */
38
+ nodes?: NodeMetadata[];
39
+ /** Editor height */
26
40
  height?: string | number;
41
+ /** Editor width */
27
42
  width?: string | number;
43
+ /** Show the navbar */
28
44
  showNavbar?: boolean;
29
- // New configuration options for pipeline status mode
45
+ /** Disable the node sidebar */
30
46
  disableSidebar?: boolean;
47
+ /** Lock the workflow (prevent changes) */
31
48
  lockWorkflow?: boolean;
49
+ /** Read-only mode */
32
50
  readOnly?: boolean;
51
+ /** Node execution statuses */
33
52
  nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
34
- // Pipeline ID for fetching node execution info from jobs
53
+ /** Pipeline ID for fetching node execution info */
35
54
  pipelineId?: string;
36
- // Navbar customization
55
+ /** Custom navbar title */
37
56
  navbarTitle?: string;
57
+ /** Custom navbar actions */
38
58
  navbarActions?: Array<{
39
59
  label: string;
40
60
  href: string;
@@ -42,13 +62,21 @@
42
62
  variant?: 'primary' | 'secondary' | 'outline';
43
63
  onclick?: (event: Event) => void;
44
64
  }>;
45
- // API configuration - optional, defaults to '/api/flowdrop'
65
+ /** API base URL */
46
66
  apiBaseUrl?: string;
67
+ /** Endpoint configuration */
47
68
  endpointConfig?: EndpointConfig;
69
+ /** Authentication provider */
70
+ authProvider?: AuthProvider;
71
+ /** Event handlers */
72
+ eventHandlers?: FlowDropEventHandlers;
73
+ /** Feature configuration */
74
+ features?: FlowDropFeatures;
48
75
  }
49
76
 
50
77
  let {
51
78
  workflow: initialWorkflow,
79
+ nodes: propNodes,
52
80
  height = '100vh',
53
81
  width = '100%',
54
82
  showNavbar = false,
@@ -60,9 +88,15 @@
60
88
  navbarTitle,
61
89
  navbarActions = [],
62
90
  apiBaseUrl,
63
- endpointConfig: propEndpointConfig
91
+ endpointConfig: propEndpointConfig,
92
+ authProvider,
93
+ eventHandlers,
94
+ features: propFeatures
64
95
  }: Props = $props();
65
96
 
97
+ // Merge features with defaults
98
+ const features = mergeFeatures(propFeatures);
99
+
66
100
  // Create breadcrumb-style title - at top level to avoid store subscription issues
67
101
  let breadcrumbTitle = $derived(() => {
68
102
  // Use custom navbar title if provided
@@ -129,10 +163,19 @@
129
163
 
130
164
  /**
131
165
  * Fetch node types from the server
166
+ *
167
+ * If propNodes is provided, uses those instead of fetching from API.
168
+ * This fixes the bug where propNodes was ignored.
132
169
  */
133
170
  async function fetchNodeTypes(): Promise<void> {
134
- // Show loading toast
135
- const loadingToast = apiToasts.loading('Loading node types');
171
+ // If nodes were provided as props, use them directly (skip API fetch)
172
+ if (propNodes && propNodes.length > 0) {
173
+ nodes = propNodes;
174
+ return;
175
+ }
176
+
177
+ // Show loading toast (if toasts are enabled)
178
+ const loadingToast = features.showToasts ? apiToasts.loading('Loading node types') : null;
136
179
  try {
137
180
  error = null;
138
181
 
@@ -142,13 +185,35 @@
142
185
  error = null;
143
186
 
144
187
  // Dismiss loading toast
145
- dismissToast(loadingToast);
188
+ if (loadingToast) {
189
+ dismissToast(loadingToast);
190
+ }
146
191
  } catch (err) {
147
192
  // Dismiss loading toast and show error toast
148
- dismissToast(loadingToast);
193
+ if (loadingToast) {
194
+ dismissToast(loadingToast);
195
+ }
196
+
197
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
198
+
199
+ // Notify parent via event handler
200
+ if (eventHandlers?.onApiError) {
201
+ const suppressToast = eventHandlers.onApiError(
202
+ err instanceof Error ? err : new Error(errorMessage),
203
+ 'fetchNodes'
204
+ );
205
+ if (suppressToast) {
206
+ // Parent handled the error, don't show default toast
207
+ nodes = sampleNodes;
208
+ return;
209
+ }
210
+ }
211
+
149
212
  // Show error but don't block the UI
150
- error = `API Error: ${err instanceof Error ? err.message : 'Unknown error'}. Using sample data.`;
151
- apiToasts.error('Load node types', err instanceof Error ? err.message : 'Unknown error');
213
+ error = `API Error: ${errorMessage}. Using sample data.`;
214
+ if (features.showToasts) {
215
+ apiToasts.error('Load node types', errorMessage);
216
+ }
152
217
 
153
218
  // Fallback to sample data
154
219
  nodes = sampleNodes;
@@ -283,27 +348,37 @@
283
348
 
284
349
  /**
285
350
  * Save workflow - exposed API function
351
+ *
352
+ * Integrates with event handlers for enterprise customization.
286
353
  */
287
354
  async function saveWorkflow(): Promise<void> {
288
355
  // Wait for any pending DOM updates before saving
289
356
  await tick();
290
357
 
291
- // Show loading toast
292
- const loadingToast = apiToasts.loading('Saving workflow');
358
+ // Use current workflow from global store
359
+ const workflowToSave = $workflowStore;
360
+
361
+ if (!workflowToSave) {
362
+ return;
363
+ }
364
+
365
+ // Call onBeforeSave if provided - allows cancellation
366
+ if (eventHandlers?.onBeforeSave) {
367
+ const shouldContinue = await eventHandlers.onBeforeSave(workflowToSave);
368
+ if (shouldContinue === false) {
369
+ // Save cancelled by event handler
370
+ return;
371
+ }
372
+ }
373
+
374
+ // Show loading toast (if enabled)
375
+ const loadingToast = features.showToasts ? apiToasts.loading('Saving workflow') : null;
293
376
 
294
377
  try {
295
378
  // Import necessary modules
296
379
  const { workflowApi } = await import('../services/api.js');
297
380
  const { v4: uuidv4 } = await import('uuid');
298
381
 
299
- // Use current workflow from global store
300
- const workflowToSave = $workflowStore;
301
-
302
- if (!workflowToSave) {
303
- dismissToast(loadingToast);
304
- return;
305
- }
306
-
307
382
  // Determine the workflow ID
308
383
  let workflowId: string;
309
384
  if (workflowToSave.id) {
@@ -313,7 +388,7 @@
313
388
  }
314
389
 
315
390
  // Create workflow object for saving
316
- const finalWorkflow = {
391
+ const finalWorkflow: Workflow = {
317
392
  id: workflowId,
318
393
  name: workflowToSave.name || 'Untitled Workflow',
319
394
  description: workflowToSave.description || '',
@@ -342,14 +417,45 @@
342
417
  });
343
418
  }
344
419
 
420
+ // Mark as saved (clears dirty state)
421
+ markAsSaved();
422
+
345
423
  // Dismiss loading toast and show success
346
- dismissToast(loadingToast);
347
- apiToasts.success('Save workflow', 'Workflow saved successfully');
424
+ if (loadingToast) {
425
+ dismissToast(loadingToast);
426
+ }
427
+ if (features.showToasts) {
428
+ apiToasts.success('Save workflow', 'Workflow saved successfully');
429
+ }
430
+
431
+ // Call onAfterSave if provided
432
+ if (eventHandlers?.onAfterSave) {
433
+ await eventHandlers.onAfterSave(savedWorkflow);
434
+ }
348
435
  } catch (error) {
349
- // Dismiss loading toast and show error
350
- dismissToast(loadingToast);
351
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
352
- apiToasts.error('Save workflow', errorMessage);
436
+ // Dismiss loading toast
437
+ if (loadingToast) {
438
+ dismissToast(loadingToast);
439
+ }
440
+
441
+ const errorObj = error instanceof Error ? error : new Error('Unknown error occurred');
442
+
443
+ // Call onSaveError if provided
444
+ if (eventHandlers?.onSaveError && workflowToSave) {
445
+ await eventHandlers.onSaveError(errorObj, workflowToSave);
446
+ }
447
+
448
+ // Check if parent wants to handle API errors
449
+ let suppressToast = false;
450
+ if (eventHandlers?.onApiError) {
451
+ suppressToast = eventHandlers.onApiError(errorObj, 'save') === true;
452
+ }
453
+
454
+ // Show error toast if not suppressed
455
+ if (features.showToasts && !suppressToast) {
456
+ apiToasts.error('Save workflow', errorObj.message);
457
+ }
458
+
353
459
  throw error; // Re-throw to allow calling code to handle if needed
354
460
  }
355
461
  }
@@ -398,9 +504,7 @@
398
504
 
399
505
  // Expose save and export functions globally for external access
400
506
  if (typeof window !== 'undefined') {
401
- // @ts-expect-error - Adding to window for external access
402
507
  window.flowdropSave = saveWorkflow;
403
- // @ts-expect-error - Adding to window for external access
404
508
  window.flowdropExport = exportWorkflow;
405
509
  }
406
510
 
@@ -425,6 +529,11 @@
425
529
  // Initialize the workflow store if we have an initial workflow
426
530
  if (initialWorkflow) {
427
531
  workflowActions.initialize(initialWorkflow);
532
+
533
+ // Emit onWorkflowLoad event
534
+ if (eventHandlers?.onWorkflowLoad) {
535
+ eventHandlers.onWorkflowLoad(initialWorkflow);
536
+ }
428
537
  }
429
538
  })();
430
539
 
@@ -635,7 +744,10 @@
635
744
  <div class="flowdrop-config-sidebar__detail">
636
745
  <span class="flowdrop-config-sidebar__detail-label">Node ID:</span>
637
746
  <div class="flowdrop-config-sidebar__detail-value-with-copy">
638
- <span class="flowdrop-config-sidebar__detail-value" style="font-family: monospace;">
747
+ <span
748
+ class="flowdrop-config-sidebar__detail-value"
749
+ style="font-family: monospace;"
750
+ >
639
751
  {selectedNodeForConfig().id}
640
752
  </span>
641
753
  <button
@@ -1,16 +1,34 @@
1
- import type { Workflow } from '../types/index.js';
1
+ import type { NodeMetadata, Workflow } from '../types/index.js';
2
2
  import type { EndpointConfig } from '../config/endpoints.js';
3
+ import type { AuthProvider } from '../types/auth.js';
4
+ import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
5
+ /**
6
+ * Configuration props for runtime customization
7
+ */
3
8
  interface Props {
9
+ /** Initial workflow to load */
4
10
  workflow?: Workflow;
11
+ /** Pre-loaded node types (if provided, skips API fetch) */
12
+ nodes?: NodeMetadata[];
13
+ /** Editor height */
5
14
  height?: string | number;
15
+ /** Editor width */
6
16
  width?: string | number;
17
+ /** Show the navbar */
7
18
  showNavbar?: boolean;
19
+ /** Disable the node sidebar */
8
20
  disableSidebar?: boolean;
21
+ /** Lock the workflow (prevent changes) */
9
22
  lockWorkflow?: boolean;
23
+ /** Read-only mode */
10
24
  readOnly?: boolean;
25
+ /** Node execution statuses */
11
26
  nodeStatuses?: Record<string, 'pending' | 'running' | 'completed' | 'error'>;
27
+ /** Pipeline ID for fetching node execution info */
12
28
  pipelineId?: string;
29
+ /** Custom navbar title */
13
30
  navbarTitle?: string;
31
+ /** Custom navbar actions */
14
32
  navbarActions?: Array<{
15
33
  label: string;
16
34
  href: string;
@@ -18,8 +36,16 @@ interface Props {
18
36
  variant?: 'primary' | 'secondary' | 'outline';
19
37
  onclick?: (event: Event) => void;
20
38
  }>;
39
+ /** API base URL */
21
40
  apiBaseUrl?: string;
41
+ /** Endpoint configuration */
22
42
  endpointConfig?: EndpointConfig;
43
+ /** Authentication provider */
44
+ authProvider?: AuthProvider;
45
+ /** Event handlers */
46
+ eventHandlers?: FlowDropEventHandlers;
47
+ /** Feature configuration */
48
+ features?: FlowDropFeatures;
23
49
  }
24
50
  declare const App: import("svelte").Component<Props, {}, "">;
25
51
  type App = ReturnType<typeof App>;
@@ -5,8 +5,8 @@
5
5
  -->
6
6
 
7
7
  <script lang="ts">
8
- import { useSvelteFlow } from "@xyflow/svelte";
9
- import type { Snippet } from "svelte";
8
+ import { useSvelteFlow } from '@xyflow/svelte';
9
+ import type { Snippet } from 'svelte';
10
10
 
11
11
  interface Props {
12
12
  ondrop: (nodeTypeData: string, position: { x: number; y: number }) => void;
@@ -24,7 +24,7 @@
24
24
  function handleDragOver(e: DragEvent): void {
25
25
  e.preventDefault();
26
26
  if (e.dataTransfer) {
27
- e.dataTransfer.dropEffect = "copy";
27
+ e.dataTransfer.dropEffect = 'copy';
28
28
  }
29
29
  }
30
30
 
@@ -35,7 +35,7 @@
35
35
  e.preventDefault();
36
36
 
37
37
  // Get the data from the drag event
38
- const nodeTypeData = e.dataTransfer?.getData("application/json");
38
+ const nodeTypeData = e.dataTransfer?.getData('application/json');
39
39
  if (nodeTypeData) {
40
40
  // Convert screen coordinates to flow coordinates (accounts for zoom and pan)
41
41
  const position = screenToFlowPosition({
@@ -65,4 +65,3 @@
65
65
  height: 100%;
66
66
  }
67
67
  </style>
68
-
@@ -1,4 +1,4 @@
1
- import type { Snippet } from "svelte";
1
+ import type { Snippet } from 'svelte';
2
2
  interface Props {
3
3
  ondrop: (nodeTypeData: string, position: {
4
4
  x: number;
@@ -1,24 +1,28 @@
1
1
  <!--
2
2
  Universal Node Component
3
- Renders any node type with automatic status overlay injection
4
- This component can replace individual node components in SvelteFlow
3
+ Renders any node type with automatic status overlay injection.
4
+ This component can replace individual node components in SvelteFlow.
5
+
6
+ Uses the node component registry to resolve which component to render,
7
+ enabling custom node types to be registered and used dynamically.
5
8
  -->
6
9
 
7
10
  <script lang="ts">
8
11
  import type { WorkflowNode } from '../types/index.js';
12
+ import { nodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
13
+ import { resolveBuiltinAlias } from '../registry/builtinNodes.js';
14
+ import NodeStatusOverlay from './NodeStatusOverlay.svelte';
15
+ import { shouldShowNodeStatus } from '../utils/nodeWrapper.js';
16
+ import { resolveComponentName } from '../utils/nodeTypes.js';
17
+
18
+ // Fallback components for when registry is not available
19
+ // These are only used as last-resort fallbacks
9
20
  import WorkflowNodeComponent from './WorkflowNode.svelte';
10
21
  import NotesNode from './NotesNode.svelte';
11
22
  import SimpleNode from './SimpleNode.svelte';
12
23
  import SquareNode from './SquareNode.svelte';
13
24
  import ToolNode from './ToolNode.svelte';
14
25
  import GatewayNode from './GatewayNode.svelte';
15
- import NodeStatusOverlay from './NodeStatusOverlay.svelte';
16
- import {
17
- shouldShowNodeStatus,
18
- getOptimalStatusPosition,
19
- getOptimalStatusSize
20
- } from '../utils/nodeWrapper.js';
21
- import { resolveComponentName } from '../utils/nodeTypes.js';
22
26
 
23
27
  let {
24
28
  data,
@@ -31,26 +35,60 @@
31
35
  selected?: boolean;
32
36
  } = $props();
33
37
 
34
- // Determine which node component to render based on node type
35
- // Priority: config.nodeType > metadata.type
36
- // Explicitly track config.nodeType to ensure reactivity
38
+ /**
39
+ * Determine which node component to render based on node type.
40
+ * Priority: config.nodeType > metadata.type
41
+ * Explicitly track config.nodeType to ensure reactivity.
42
+ */
37
43
  let configNodeType = $derived(data.config?.nodeType as string | undefined);
44
+
45
+ /**
46
+ * Resolve the component name from metadata and config.
47
+ * This handles the logic of choosing between config.nodeType and metadata.type.
48
+ */
38
49
  let resolvedComponentName = $derived(
39
50
  data.metadata ? resolveComponentName(data.metadata, configNodeType) : 'workflowNode'
40
51
  );
52
+
53
+ /**
54
+ * Get the node component from the registry.
55
+ * Falls back to built-in components if registry lookup fails.
56
+ */
41
57
  let nodeComponent = $derived(getNodeComponent(resolvedComponentName));
42
58
 
43
- // Get execution info
59
+ /**
60
+ * Get execution info for status overlay
61
+ */
44
62
  let executionInfo = $derived(data.executionInfo);
63
+
64
+ /**
65
+ * Determine if status overlay should be shown.
66
+ * Hide for note nodes as they have their own styling.
67
+ */
45
68
  let shouldShowStatus = $derived(
46
69
  shouldShowNodeStatus(executionInfo) && resolvedComponentName !== 'note'
47
70
  );
48
71
 
49
72
  /**
50
- * Get the appropriate node component based on type
73
+ * Get the node component for the given type.
74
+ * First tries the registry, then falls back to hardcoded components.
75
+ *
76
+ * @param nodeType - The node type identifier
77
+ * @returns The Svelte component to render
51
78
  */
52
79
  function getNodeComponent(nodeType: string) {
53
- switch (nodeType) {
80
+ // Resolve any aliases (e.g., "default" -> "workflowNode")
81
+ const resolvedType = resolveBuiltinAlias(nodeType);
82
+
83
+ // Try registry first
84
+ const registeredComponent = nodeComponentRegistry.getComponent(resolvedType);
85
+ if (registeredComponent) {
86
+ return registeredComponent;
87
+ }
88
+
89
+ // Fallback to hardcoded switch for backwards compatibility
90
+ // This ensures the component works even if registry fails to initialize
91
+ switch (resolvedType) {
54
92
  case 'note':
55
93
  return NotesNode;
56
94
  case 'simple':
@@ -68,40 +106,62 @@
68
106
  }
69
107
 
70
108
  /**
71
- * Get optimal status position for this node type
109
+ * Get optimal status position for this node type.
110
+ * Uses registry if available, otherwise falls back to defaults.
72
111
  */
73
112
  function getStatusPosition(): 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' {
74
- return getOptimalStatusPosition(resolvedComponentName);
113
+ // Try registry first
114
+ const position = nodeComponentRegistry.getStatusPosition(resolvedComponentName);
115
+ if (position) {
116
+ return position;
117
+ }
118
+
119
+ // Fallback based on node type
120
+ switch (resolvedComponentName) {
121
+ case 'tool':
122
+ return 'top-left';
123
+ case 'note':
124
+ return 'bottom-right';
125
+ case 'simple':
126
+ case 'square':
127
+ default:
128
+ return 'top-right';
129
+ }
75
130
  }
76
131
 
77
132
  /**
78
- * Get optimal status size for this node type
133
+ * Get optimal status size for this node type.
134
+ * Uses registry if available, otherwise falls back to defaults.
79
135
  */
80
136
  function getStatusSize(): 'sm' | 'md' | 'lg' {
81
- return getOptimalStatusSize(resolvedComponentName);
137
+ // Try registry first
138
+ const size = nodeComponentRegistry.getStatusSize(resolvedComponentName);
139
+ if (size) {
140
+ return size;
141
+ }
142
+
143
+ // Fallback based on node type
144
+ switch (resolvedComponentName) {
145
+ case 'tool':
146
+ case 'note':
147
+ case 'square':
148
+ return 'sm';
149
+ case 'simple':
150
+ default:
151
+ return 'md';
152
+ }
82
153
  }
83
154
  </script>
84
155
 
85
156
  <div class="universal-node">
86
- <!-- Render the appropriate node component -->
87
- {#if nodeComponent === WorkflowNodeComponent}
88
- <WorkflowNodeComponent {data} {selected} />
89
- {:else if nodeComponent === NotesNode}
90
- <NotesNode {data} {selected} />
91
- {:else if nodeComponent === SimpleNode}
92
- <SimpleNode {data} {selected} />
93
- {:else if nodeComponent === SquareNode}
94
- <SquareNode {data} {selected} />
95
- {:else if nodeComponent === ToolNode}
96
- <ToolNode {data} {selected} />
97
- {:else if nodeComponent === GatewayNode}
98
- <GatewayNode {data} {selected} />
99
- {/if}
157
+ <!-- Render the node component dynamically -->
158
+ <!-- svelte-ignore binding_property_non_reactive -->
159
+ <svelte:component this={nodeComponent} {data} {selected} />
100
160
 
101
161
  <!-- Status overlay - only show if there's meaningful status information -->
102
162
  {#if shouldShowStatus}
103
163
  <NodeStatusOverlay
104
- nodeId={data.nodeId || 'unknown'}
164
+ nodeId={data.nodeId ?? 'unknown'}
105
165
  {executionInfo}
106
166
  position={getStatusPosition()}
107
167
  size={getStatusSize()}
@@ -80,14 +80,21 @@
80
80
 
81
81
  $effect(() => {
82
82
  if (currentWorkflow) {
83
- flowNodes = currentWorkflow.nodes.map((node) => ({
83
+ const nodesWithCallbacks = currentWorkflow.nodes.map((node) => ({
84
84
  ...node,
85
85
  data: {
86
86
  ...node.data,
87
87
  onConfigOpen: props.openConfigSidebar
88
88
  }
89
89
  }));
90
- flowEdges = currentWorkflow.edges;
90
+ flowNodes = nodesWithCallbacks;
91
+
92
+ // Apply edge styling based on source port data type when loading workflow
93
+ const styledEdges = EdgeStylingHelper.updateEdgeStyles(
94
+ currentWorkflow.edges,
95
+ nodesWithCallbacks
96
+ );
97
+ flowEdges = styledEdges;
91
98
 
92
99
  // Only load execution info if we have a pipelineId (pipeline status mode)
93
100
  // and if the workflow or pipeline has changed
@@ -523,7 +530,60 @@
523
530
  pointer-events: all;
524
531
  cursor: crosshair;
525
532
  }
533
+
534
+ /**
535
+ * Edge Styling Based on Source Port Data Type
536
+ * Uses CSS tokens from base.css for consistent theming
537
+ * - Trigger edges: solid dark line (control flow)
538
+ * - Tool edges: dashed amber line (tool connections)
539
+ * - Data edges: normal gray line (data flow)
540
+ */
541
+
542
+ /* Trigger Edge: Solid dark line for control flow */
543
+ :global(.flowdrop--edge--trigger path.svelte-flow__edge-path) {
544
+ stroke: var(--flowdrop-edge-trigger-color);
545
+ stroke-width: var(--flowdrop-edge-trigger-width);
546
+ }
547
+
548
+ :global(.flowdrop--edge--trigger:hover path.svelte-flow__edge-path) {
549
+ stroke: var(--flowdrop-edge-trigger-color-hover);
550
+ stroke-width: var(--flowdrop-edge-trigger-width-hover);
551
+ }
552
+
553
+ :global(.flowdrop--edge--trigger.selected path.svelte-flow__edge-path) {
554
+ stroke: var(--flowdrop-edge-trigger-color-selected);
555
+ stroke-width: var(--flowdrop-edge-trigger-width-hover);
556
+ }
557
+
558
+ /* Tool Edge: Dashed amber line for tool connections */
526
559
  :global(.flowdrop--edge--tool path.svelte-flow__edge-path) {
527
- stroke-dasharray: 5 5;
560
+ stroke: var(--flowdrop-edge-tool-color);
561
+ stroke-dasharray: 5 3;
562
+ }
563
+
564
+ :global(.flowdrop--edge--tool:hover path.svelte-flow__edge-path) {
565
+ stroke: var(--flowdrop-edge-tool-color-hover);
566
+ stroke-width: 2;
567
+ }
568
+
569
+ :global(.flowdrop--edge--tool.selected path.svelte-flow__edge-path) {
570
+ stroke: var(--flowdrop-edge-tool-color-selected);
571
+ stroke-dasharray: 5 3;
572
+ stroke-width: 2;
573
+ }
574
+
575
+ /* Data Edge: Normal gray line for data flow (default) */
576
+ :global(.flowdrop--edge--data path.svelte-flow__edge-path) {
577
+ stroke: var(--flowdrop-edge-data-color);
578
+ }
579
+
580
+ :global(.flowdrop--edge--data:hover path.svelte-flow__edge-path) {
581
+ stroke: var(--flowdrop-edge-data-color-hover);
582
+ stroke-width: 2;
583
+ }
584
+
585
+ :global(.flowdrop--edge--data.selected path.svelte-flow__edge-path) {
586
+ stroke: var(--flowdrop-edge-data-color-selected);
587
+ stroke-width: 2;
528
588
  }
529
589
  </style>
@@ -9,11 +9,11 @@ export interface RuntimeConfig {
9
9
  /** Base URL for the FlowDrop API */
10
10
  apiBaseUrl: string;
11
11
  /** Theme preference */
12
- theme: "light" | "dark" | "auto";
12
+ theme: 'light' | 'dark' | 'auto';
13
13
  /** Request timeout in milliseconds */
14
14
  timeout: number;
15
15
  /** Authentication type */
16
- authType: "none" | "bearer" | "api_key" | "custom";
16
+ authType: 'none' | 'bearer' | 'api_key' | 'custom';
17
17
  /** Authentication token */
18
18
  authToken?: string;
19
19
  /** Application version */