@d34dman/flowdrop 0.0.44 → 0.0.46

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 (67) hide show
  1. package/README.md +2 -2
  2. package/dist/components/ConfigForm.svelte +4 -20
  3. package/dist/components/Navbar.svelte +6 -7
  4. package/dist/components/NodeSidebar.svelte +6 -2
  5. package/dist/components/SchemaForm.svelte +2 -10
  6. package/dist/components/WorkflowEditor.svelte +143 -13
  7. package/dist/components/form/FormAutocomplete.svelte +5 -9
  8. package/dist/components/form/FormCheckboxGroup.svelte +11 -1
  9. package/dist/components/form/FormCheckboxGroup.svelte.d.ts +2 -0
  10. package/dist/components/form/FormCodeEditor.svelte +16 -7
  11. package/dist/components/form/FormCodeEditor.svelte.d.ts +2 -0
  12. package/dist/components/form/FormField.svelte +20 -1
  13. package/dist/components/form/FormMarkdownEditor.svelte +29 -19
  14. package/dist/components/form/FormMarkdownEditor.svelte.d.ts +2 -0
  15. package/dist/components/form/FormNumberField.svelte +4 -0
  16. package/dist/components/form/FormNumberField.svelte.d.ts +2 -0
  17. package/dist/components/form/FormRangeField.svelte +4 -0
  18. package/dist/components/form/FormRangeField.svelte.d.ts +2 -0
  19. package/dist/components/form/FormSelect.svelte +4 -0
  20. package/dist/components/form/FormSelect.svelte.d.ts +2 -0
  21. package/dist/components/form/FormTemplateEditor.svelte +16 -7
  22. package/dist/components/form/FormTemplateEditor.svelte.d.ts +2 -0
  23. package/dist/components/form/FormTextField.svelte +4 -0
  24. package/dist/components/form/FormTextField.svelte.d.ts +2 -0
  25. package/dist/components/form/FormTextarea.svelte +4 -0
  26. package/dist/components/form/FormTextarea.svelte.d.ts +2 -0
  27. package/dist/components/form/FormToggle.svelte +4 -0
  28. package/dist/components/form/FormToggle.svelte.d.ts +2 -0
  29. package/dist/components/form/types.d.ts +5 -0
  30. package/dist/components/form/types.js +1 -1
  31. package/dist/components/layouts/MainLayout.svelte +5 -2
  32. package/dist/components/nodes/GatewayNode.svelte +99 -86
  33. package/dist/components/nodes/IdeaNode.svelte +20 -35
  34. package/dist/components/nodes/NotesNode.svelte +6 -2
  35. package/dist/components/nodes/SimpleNode.svelte +32 -31
  36. package/dist/components/nodes/SquareNode.svelte +35 -45
  37. package/dist/components/nodes/TerminalNode.svelte +25 -61
  38. package/dist/components/nodes/ToolNode.svelte +36 -18
  39. package/dist/components/nodes/WorkflowNode.svelte +97 -73
  40. package/dist/components/playground/Playground.svelte +43 -38
  41. package/dist/editor/index.d.ts +3 -1
  42. package/dist/editor/index.js +5 -1
  43. package/dist/helpers/nodeLayoutHelper.d.ts +14 -0
  44. package/dist/helpers/nodeLayoutHelper.js +19 -0
  45. package/dist/helpers/workflowEditorHelper.js +1 -2
  46. package/dist/services/autoSaveService.js +5 -5
  47. package/dist/services/historyService.d.ts +207 -0
  48. package/dist/services/historyService.js +317 -0
  49. package/dist/services/settingsService.d.ts +2 -2
  50. package/dist/services/settingsService.js +15 -21
  51. package/dist/services/toastService.d.ts +1 -1
  52. package/dist/services/toastService.js +10 -10
  53. package/dist/stores/historyStore.d.ts +133 -0
  54. package/dist/stores/historyStore.js +188 -0
  55. package/dist/stores/settingsStore.d.ts +1 -1
  56. package/dist/stores/settingsStore.js +40 -42
  57. package/dist/stores/themeStore.d.ts +2 -2
  58. package/dist/stores/themeStore.js +30 -32
  59. package/dist/stores/workflowStore.d.ts +52 -2
  60. package/dist/stores/workflowStore.js +102 -2
  61. package/dist/styles/base.css +67 -7
  62. package/dist/styles/toast.css +3 -1
  63. package/dist/styles/tokens.css +38 -2
  64. package/dist/types/settings.d.ts +3 -3
  65. package/dist/types/settings.js +13 -19
  66. package/dist/utils/colors.js +18 -18
  67. package/package.json +1 -1
@@ -14,7 +14,11 @@
14
14
  import { dynamicPortToNodePort } from '../../types/index.js';
15
15
  import Icon from '@iconify/svelte';
16
16
  import { getNodeIcon } from '../../utils/icons.js';
17
- import { getDataTypeColorToken, getCategoryColorToken, getPortBackgroundColor } from '../../utils/colors.js';
17
+ import {
18
+ getDataTypeColorToken,
19
+ getCategoryColorToken,
20
+ getPortBackgroundColor
21
+ } from '../../utils/colors.js';
18
22
  import { connectedHandles } from '../../stores/workflowStore.js';
19
23
 
20
24
  interface Props {
@@ -176,15 +180,18 @@
176
180
  aria-label="Workflow node: {props.data.metadata.name}"
177
181
  aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
178
182
  >
179
- <!-- Default Node Header -->
183
+ <!-- Default Node Header: expands in multiples of 10 (title row 40px + gap 10px + description 20px per line) -->
180
184
  <div class="flowdrop-workflow-node__header">
181
- <div class="flowdrop-flex flowdrop-gap--3 flowdrop-items--center">
185
+ <div class="flowdrop-workflow-node__header-title">
182
186
  <!-- Node Icon with Squircle Background -->
183
187
  <div
184
188
  class="flowdrop-workflow-node__icon-wrapper"
185
189
  style="--_icon-color: {getCategoryColorToken(props.data.metadata.category)}"
186
190
  >
187
- <Icon icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)} class="flowdrop-workflow-node__icon" />
191
+ <Icon
192
+ icon={getNodeIcon(props.data.metadata.icon, props.data.metadata.category)}
193
+ class="flowdrop-workflow-node__icon"
194
+ />
188
195
  </div>
189
196
 
190
197
  <!-- Node Title - Icon and Title on same line -->
@@ -197,9 +204,9 @@
197
204
  <!-- Status indicators removed - using outer NodeStatusOverlay instead -->
198
205
  </div>
199
206
  </div>
200
- <!-- Node Description - on new line below -->
207
+ <!-- Node Description - line-height 20px so header grows in steps of 10 -->
201
208
  <p
202
- class="flowdrop-text--xs flowdrop-text--gray flowdrop-truncate flowdrop-mt--1"
209
+ class="flowdrop-workflow-node__header-desc"
203
210
  id="node-description-{props.data.nodeId || 'unknown'}"
204
211
  >
205
212
  {displayDescription}
@@ -209,31 +216,35 @@
209
216
  <!-- Input Ports Container -->
210
217
  {#if visibleInputPorts.length > 0}
211
218
  <div class="flowdrop-workflow-node__ports">
212
- <div class="flowdrop-workflow-node__ports-header">
213
- <h5 class="flowdrop-workflow-node__ports-title">Inputs</h5>
214
- </div>
215
219
  <div class="flowdrop-workflow-node__ports-list">
216
- {#each visibleInputPorts as port (port.id)}
220
+ {#each visibleInputPorts as port, inputIndex (port.id)}
217
221
  <div class="flowdrop-workflow-node__port">
218
- <!-- Input Handle -->
222
+ <!-- Input Handle: centered in row, at node edge (ports have no padding) -->
219
223
  <Handle
220
224
  type="target"
221
225
  position={Position.Left}
222
226
  id={`${props.data.nodeId}-input-${port.id}`}
223
227
  class="flowdrop-workflow-node__handle"
224
- style="top: 50%; transform: translateY(-50%); margin-left: -32px; background-color: {getDataTypeColorToken(port.dataType)}; border-color: var(--fd-handle-border);"
228
+ style="top: 50%; transform: translateY(-50%); left: calc(-1 * (var(--fd-handle-size) / 2)); --fd-handle-fill: {getDataTypeColorToken(
229
+ port.dataType
230
+ )}; --fd-handle-border-color: var(--fd-handle-border);"
225
231
  role="button"
226
232
  tabindex={0}
227
233
  aria-label="Connect to {port.name} input port"
228
234
  />
229
235
 
230
- <!-- Port Info -->
231
- <div class="flowdrop-flex--1 flowdrop-min-w--0">
236
+ <!-- Port Info: padding lives here so handle position is simple -->
237
+ <div class="flowdrop-workflow-node__port-content flowdrop-flex--1 flowdrop-min-w--0">
232
238
  <div class="flowdrop-flex flowdrop-gap--2">
233
239
  <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
234
240
  <span
235
241
  class="flowdrop-badge flowdrop-badge--sm"
236
- style="background-color: {getPortBackgroundColor(port.dataType, 15)}; color: {getDataTypeColorToken(port.dataType)}; border: 1px solid {getPortBackgroundColor(port.dataType, 30)};"
242
+ style="background-color: {getPortBackgroundColor(
243
+ port.dataType,
244
+ 15
245
+ )}; color: {getDataTypeColorToken(
246
+ port.dataType
247
+ )}; border: 1px solid {getPortBackgroundColor(port.dataType, 30)};"
237
248
  >
238
249
  {port.dataType}
239
250
  </span>
@@ -258,19 +269,23 @@
258
269
  <!-- Output Ports Container -->
259
270
  {#if visibleOutputPorts.length > 0}
260
271
  <div class="flowdrop-workflow-node__ports">
261
- <div class="flowdrop-workflow-node__ports-header">
262
- <h5 class="flowdrop-workflow-node__ports-title">Outputs</h5>
263
- </div>
264
272
  <div class="flowdrop-workflow-node__ports-list">
265
- {#each visibleOutputPorts as port (port.id)}
273
+ {#each visibleOutputPorts as port, outputIndex (port.id)}
266
274
  <div class="flowdrop-workflow-node__port">
267
- <!-- Port Info -->
268
- <div class="flowdrop-flex--1 flowdrop-min-w--0 flowdrop-text--right">
275
+ <!-- Port Info: padding lives here so handle position is simple -->
276
+ <div
277
+ class="flowdrop-workflow-node__port-content flowdrop-flex--1 flowdrop-min-w--0 flowdrop-text--right"
278
+ >
269
279
  <div class="flowdrop-flex flowdrop-gap--2 flowdrop-justify--end">
270
280
  <span class="flowdrop-text--xs flowdrop-font--medium">{port.name}</span>
271
281
  <span
272
282
  class="flowdrop-badge flowdrop-badge--sm"
273
- style="background-color: {getPortBackgroundColor(port.dataType, 15)}; color: {getDataTypeColorToken(port.dataType)}; border: 1px solid {getPortBackgroundColor(port.dataType, 30)};"
283
+ style="background-color: {getPortBackgroundColor(
284
+ port.dataType,
285
+ 15
286
+ )}; color: {getDataTypeColorToken(
287
+ port.dataType
288
+ )}; border: 1px solid {getPortBackgroundColor(port.dataType, 30)};"
274
289
  >
275
290
  {port.dataType}
276
291
  </span>
@@ -282,13 +297,15 @@
282
297
  {/if}
283
298
  </div>
284
299
 
285
- <!-- Output Handle -->
300
+ <!-- Output Handle: centered in row, at node edge (ports have no padding) -->
286
301
  <Handle
287
302
  type="source"
288
303
  position={Position.Right}
289
304
  id={`${props.data.nodeId}-output-${port.id}`}
290
305
  class="flowdrop-workflow-node__handle"
291
- style="top: 50%; transform: translateY(-50%); margin-right: -32px; background-color: {getDataTypeColorToken(port.dataType)}; border-color: var(--fd-handle-border);"
306
+ style="top: 50%; transform: translateY(-50%); right: calc(-1 * (var(--fd-handle-size) / 2)); --fd-handle-fill: {getDataTypeColorToken(
307
+ port.dataType
308
+ )}; --fd-handle-border-color: var(--fd-handle-border);"
292
309
  role="button"
293
310
  tabindex={0}
294
311
  aria-label="Connect from {port.name} output port"
@@ -318,7 +335,7 @@
318
335
  border: 1.5px solid var(--fd-node-border);
319
336
  border-radius: var(--fd-radius-xl);
320
337
  box-shadow: var(--fd-shadow-md);
321
- width: 18rem;
338
+ width: var(--fd-node-default-width);
322
339
  z-index: 10;
323
340
  color: var(--fd-foreground);
324
341
  transition: all var(--fd-transition-fast);
@@ -330,12 +347,16 @@
330
347
  }
331
348
 
332
349
  .flowdrop-workflow-node--selected {
333
- box-shadow: 0 0 0 2px var(--fd-primary-muted), var(--fd-shadow-lg);
350
+ box-shadow:
351
+ 0 0 0 2px var(--fd-primary-muted),
352
+ var(--fd-shadow-lg);
334
353
  border-color: var(--fd-primary);
335
354
  }
336
355
 
337
356
  .flowdrop-workflow-node--selected:hover {
338
- box-shadow: 0 0 0 2px var(--fd-primary-muted), var(--fd-shadow-lg);
357
+ box-shadow:
358
+ 0 0 0 2px var(--fd-primary-muted),
359
+ var(--fd-shadow-lg);
339
360
  border-color: var(--fd-primary);
340
361
  }
341
362
 
@@ -345,11 +366,41 @@
345
366
  }
346
367
 
347
368
  .flowdrop-workflow-node__header {
348
- padding: var(--fd-space-4);
369
+ box-sizing: border-box;
370
+ padding: var(--fd-node-header-gap) var(--fd-space-4);
349
371
  border-bottom: 1px solid var(--fd-border-muted);
350
372
  background: var(--fd-header);
351
373
  border-top-left-radius: var(--fd-radius-xl);
352
374
  border-top-right-radius: var(--fd-radius-xl);
375
+ display: flex;
376
+ flex-direction: column;
377
+ gap: var(--fd-node-header-gap);
378
+ min-height: calc(
379
+ var(--fd-node-header-gap) * 2 + var(--fd-node-header-title-height) +
380
+ var(--fd-node-header-desc-line)
381
+ );
382
+ }
383
+
384
+ .flowdrop-workflow-node__header-title {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: var(--fd-space-3);
388
+ min-height: var(--fd-node-header-title-height);
389
+ flex-shrink: 0;
390
+ }
391
+
392
+ .flowdrop-workflow-node__header-desc {
393
+ margin: 0;
394
+ font-size: var(--fd-text-xs);
395
+ color: var(--fd-muted-foreground);
396
+ line-height: var(--fd-node-header-desc-line);
397
+ min-height: var(--fd-node-header-desc-line);
398
+ overflow: hidden;
399
+ text-overflow: ellipsis;
400
+ display: -webkit-box;
401
+ -webkit-line-clamp: 3;
402
+ line-clamp: 3;
403
+ -webkit-box-orient: vertical;
353
404
  }
354
405
 
355
406
  /* Squircle icon wrapper - Apple-style rounded square background */
@@ -360,13 +411,17 @@
360
411
  width: 2.25rem;
361
412
  height: 2.25rem;
362
413
  border-radius: 0.5rem;
363
- background: color-mix(in srgb, var(--_icon-color) 15%, transparent);
414
+ background: color-mix(in srgb, var(--_icon-color) var(--fd-node-icon-bg-opacity), transparent);
364
415
  flex-shrink: 0;
365
416
  transition: all var(--fd-transition-normal);
366
417
  }
367
418
 
368
419
  .flowdrop-workflow-node:hover .flowdrop-workflow-node__icon-wrapper {
369
- background: color-mix(in srgb, var(--_icon-color) 22%, transparent);
420
+ background: color-mix(
421
+ in srgb,
422
+ var(--_icon-color) var(--fd-node-icon-bg-opacity-hover),
423
+ transparent
424
+ );
370
425
  transform: scale(1.05);
371
426
  }
372
427
 
@@ -376,7 +431,7 @@
376
431
  color: var(--fd-node-icon);
377
432
  }
378
433
 
379
- .flowdrop-workflow-node__header h3 {
434
+ .flowdrop-workflow-node__header-title h3 {
380
435
  margin: 0;
381
436
  line-height: 1;
382
437
  }
@@ -394,36 +449,29 @@
394
449
  }
395
450
 
396
451
  .flowdrop-workflow-node__ports {
397
- padding: var(--fd-space-3) var(--fd-space-4);
398
- }
399
-
400
- .flowdrop-workflow-node__ports-header {
401
- margin-bottom: var(--fd-space-2);
402
- }
403
-
404
- .flowdrop-workflow-node__ports-title {
405
- margin: 0;
406
- font-size: var(--fd-text-xs);
407
- font-weight: 600;
408
- color: var(--fd-foreground);
409
- text-transform: uppercase;
410
- letter-spacing: 0.05em;
452
+ padding: 0;
411
453
  }
412
454
 
413
455
  .flowdrop-workflow-node__ports-list {
414
456
  display: flex;
415
457
  flex-direction: column;
416
- gap: var(--fd-space-2);
458
+ gap: var(--fd-node-header-gap);
459
+ padding: var(--fd-node-header-gap) 0;
417
460
  }
418
461
 
419
462
  .flowdrop-workflow-node__port {
420
463
  display: flex;
421
464
  align-items: center;
422
- gap: var(--fd-space-2);
465
+ gap: 0;
466
+ min-height: var(--fd-node-port-row-height);
423
467
  padding: var(--fd-space-1) 0;
424
468
  position: relative;
425
469
  }
426
470
 
471
+ .flowdrop-workflow-node__port-content {
472
+ padding: 0 var(--fd-space-4);
473
+ }
474
+
427
475
  .flowdrop-badge {
428
476
  padding: 0.125rem var(--fd-space-1);
429
477
  border-radius: var(--fd-radius-sm);
@@ -443,25 +491,9 @@
443
491
  padding: 0.125rem var(--fd-space-1);
444
492
  }
445
493
 
446
- /* Handle styles */
447
- :global(.flowdrop-workflow-node__handle) {
448
- width: 0.75rem;
449
- height: 0.75rem;
450
- background-color: var(--fd-muted-foreground);
451
- border: 2px solid var(--fd-handle-border);
452
- border-radius: 50%;
453
- transition: all var(--fd-transition-normal);
454
- cursor: pointer;
455
- }
456
-
494
+ /* Handle overrides: hover scale (base 20px/12px from base.css) */
457
495
  :global(.flowdrop-workflow-node__handle:hover) {
458
- background-color: var(--fd-primary);
459
- transform: scale(1.2);
460
- }
461
-
462
- :global(.flowdrop-workflow-node__handle:focus) {
463
- outline: 2px solid var(--fd-ring);
464
- outline-offset: 2px;
496
+ transform: translateY(-50%) scale(1.2);
465
497
  }
466
498
 
467
499
  /* Utility classes */
@@ -477,10 +509,6 @@
477
509
  gap: var(--fd-space-2);
478
510
  }
479
511
 
480
- .flowdrop-gap--3 {
481
- gap: var(--fd-space-3);
482
- }
483
-
484
512
  .flowdrop-items--center {
485
513
  align-items: center;
486
514
  }
@@ -517,10 +545,6 @@
517
545
  white-space: nowrap;
518
546
  }
519
547
 
520
- .flowdrop-mt--1 {
521
- margin-top: var(--fd-space-1);
522
- }
523
-
524
548
  .flowdrop-text--right {
525
549
  text-align: right;
526
550
  }
@@ -556,43 +556,43 @@
556
556
  <p class="playground__sessions-hint">Click a session to load it</p>
557
557
  {/if}
558
558
  <div class="playground__sessions">
559
- {#if $sessions.length === 0 && !$isLoading}
560
- <div class="playground__sessions-empty">
561
- <span>No sessions yet</span>
562
- </div>
563
- {:else}
564
- {#each $sessions as session (session.id)}
565
- <div
566
- class="playground__session"
567
- class:playground__session--active={$currentSession?.id === session.id}
568
- role="button"
569
- tabindex="0"
570
- title="Click to load this session"
571
- aria-label="Load session: {session.name}"
572
- onclick={() => handleSelectSession(session.id)}
573
- onkeydown={(e) => e.key === 'Enter' && handleSelectSession(session.id)}
574
- >
575
- <span class="playground__session-name" title={session.name}>
576
- {session.name}
577
- </span>
578
- <button
579
- type="button"
580
- class="playground__session-menu"
581
- class:playground__session-menu--delete={pendingDeleteId === session.id}
582
- onclick={(e) => handleDeleteClick(e, session.id)}
583
- title={pendingDeleteId === session.id
584
- ? 'Click to confirm delete'
585
- : 'Delete session'}
586
- >
587
- {#if pendingDeleteId === session.id}
588
- <Icon icon="mdi:check" />
589
- {:else}
590
- <Icon icon="mdi:dots-horizontal" />
591
- {/if}
592
- </button>
559
+ {#if $sessions.length === 0 && !$isLoading}
560
+ <div class="playground__sessions-empty">
561
+ <span>No sessions yet</span>
593
562
  </div>
594
- {/each}
595
- {/if}
563
+ {:else}
564
+ {#each $sessions as session (session.id)}
565
+ <div
566
+ class="playground__session"
567
+ class:playground__session--active={$currentSession?.id === session.id}
568
+ role="button"
569
+ tabindex="0"
570
+ title="Click to load this session"
571
+ aria-label="Load session: {session.name}"
572
+ onclick={() => handleSelectSession(session.id)}
573
+ onkeydown={(e) => e.key === 'Enter' && handleSelectSession(session.id)}
574
+ >
575
+ <span class="playground__session-name" title={session.name}>
576
+ {session.name}
577
+ </span>
578
+ <button
579
+ type="button"
580
+ class="playground__session-menu"
581
+ class:playground__session-menu--delete={pendingDeleteId === session.id}
582
+ onclick={(e) => handleDeleteClick(e, session.id)}
583
+ title={pendingDeleteId === session.id
584
+ ? 'Click to confirm delete'
585
+ : 'Delete session'}
586
+ >
587
+ {#if pendingDeleteId === session.id}
588
+ <Icon icon="mdi:check" />
589
+ {:else}
590
+ <Icon icon="mdi:dots-horizontal" />
591
+ {/if}
592
+ </button>
593
+ </div>
594
+ {/each}
595
+ {/if}
596
596
  </div>
597
597
  </div>
598
598
  </div>
@@ -774,7 +774,10 @@
774
774
  font-size: 0.875rem;
775
775
  font-weight: 500;
776
776
  cursor: pointer;
777
- transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
777
+ transition:
778
+ background-color 0.15s ease,
779
+ border-color 0.15s ease,
780
+ transform 0.1s ease;
778
781
  box-sizing: border-box;
779
782
  }
780
783
 
@@ -839,7 +842,9 @@
839
842
  border-radius: var(--fd-radius-md);
840
843
  border-left: 3px solid transparent;
841
844
  cursor: pointer;
842
- transition: background-color 0.15s ease, border-left-color 0.15s ease;
845
+ transition:
846
+ background-color 0.15s ease,
847
+ border-left-color 0.15s ease;
843
848
  }
844
849
 
845
850
  .playground__session:hover {
@@ -63,7 +63,9 @@ export { default as MessageBubble } from '../components/playground/MessageBubble
63
63
  export { mountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from '../svelte-app.js';
64
64
  export { nodeComponentRegistry, createNamespacedType, parseNamespacedType, BUILTIN_NODE_COMPONENTS, BUILTIN_NODE_TYPES, FLOWDROP_SOURCE, registerBuiltinNodes, areBuiltinsRegistered, isBuiltinType, getBuiltinTypes, resolveBuiltinAlias, registerFlowDropPlugin, unregisterFlowDropPlugin, registerCustomNode, createPlugin, isValidNamespace, getRegisteredPlugins, getPluginNodeCount } from '../registry/index.js';
65
65
  export { EdgeStylingHelper, NodeOperationsHelper, WorkflowOperationsHelper, ConfigurationHelper } from '../helpers/workflowEditorHelper.js';
66
- export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, connectedHandles, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from '../stores/workflowStore.js';
66
+ export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, connectedHandles, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange, setHistoryEnabled, isHistoryEnabled, setRestoringFromHistory } from '../stores/workflowStore.js';
67
+ export { historyStateStore, canUndo, canRedo, historyActions, setOnRestoreCallback, historyService, HistoryService } from '../stores/historyStore.js';
68
+ export type { HistoryEntry, HistoryState, PushOptions } from '../stores/historyStore.js';
67
69
  export * from '../services/api.js';
68
70
  export { showSuccess, showError, showWarning, showInfo, showLoading, dismissToast, dismissAllToasts, showPromise, showConfirmation, apiToasts, workflowToasts, pipelineToasts } from '../services/toastService.js';
69
71
  export { NodeExecutionService, nodeExecutionService } from '../services/nodeExecutionService.js';
@@ -98,7 +98,11 @@ export { EdgeStylingHelper, NodeOperationsHelper, WorkflowOperationsHelper, Conf
98
98
  // ============================================================================
99
99
  export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, connectedHandles,
100
100
  // Dirty state tracking
101
- isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from '../stores/workflowStore.js';
101
+ isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange,
102
+ // History control
103
+ setHistoryEnabled, isHistoryEnabled, setRestoringFromHistory } from '../stores/workflowStore.js';
104
+ // History Store and Service
105
+ export { historyStateStore, canUndo, canRedo, historyActions, setOnRestoreCallback, historyService, HistoryService } from '../stores/historyStore.js';
102
106
  // ============================================================================
103
107
  // Services
104
108
  // ============================================================================
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Node layout helper
3
+ * Utilities for node dimensions and port positions aligned to a 10px grid.
4
+ * Used so edge connection points (handle centers) land on multiples of 10.
5
+ */
6
+ /**
7
+ * Y position (in px) for the center of a port handle in a list-based node
8
+ * (WorkflowNode, GatewayNode). Layout: header 60px, then section title row 20px,
9
+ * then port rows 20px each. First port center = 90px, then 110, 130, … (multiples of 10).
10
+ *
11
+ * @param portIndex - Zero-based index in the combined list (section title counts as row 0; first port is index 0)
12
+ * @returns Y coordinate in px for the handle center (use with transform: translateY(-50%))
13
+ */
14
+ export declare function getPortCenterY(portIndex: number): number;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Node layout helper
3
+ * Utilities for node dimensions and port positions aligned to a 10px grid.
4
+ * Used so edge connection points (handle centers) land on multiples of 10.
5
+ */
6
+ /**
7
+ * Y position (in px) for the center of a port handle in a list-based node
8
+ * (WorkflowNode, GatewayNode). Layout: header 60px, then section title row 20px,
9
+ * then port rows 20px each. First port center = 90px, then 110, 130, … (multiples of 10).
10
+ *
11
+ * @param portIndex - Zero-based index in the combined list (section title counts as row 0; first port is index 0)
12
+ * @returns Y coordinate in px for the handle center (use with transform: translateY(-50%))
13
+ */
14
+ export function getPortCenterY(portIndex) {
15
+ const headerHeight = 60;
16
+ const sectionTitleHeight = 20;
17
+ const rowHeight = 20;
18
+ return headerHeight + sectionTitleHeight + (portIndex + 0.5) * rowHeight;
19
+ }
@@ -189,8 +189,7 @@ export class EdgeStylingHelper {
189
189
  break;
190
190
  case 'trigger':
191
191
  // Trigger edges: solid dark line for control flow
192
- edge.style =
193
- 'stroke: var(--fd-edge-trigger); stroke-width: var(--fd-edge-trigger-width);';
192
+ edge.style = 'stroke: var(--fd-edge-trigger); stroke-width: var(--fd-edge-trigger-width);';
194
193
  edge.class = 'flowdrop--edge--trigger';
195
194
  edge.markerEnd = {
196
195
  type: MarkerType.ArrowClosed,
@@ -6,9 +6,9 @@
6
6
  *
7
7
  * @module services/autoSaveService
8
8
  */
9
- import { get } from "svelte/store";
10
- import { behaviorSettings } from "../stores/settingsStore.js";
11
- import { isDirtyStore, isDirty } from "../stores/workflowStore.js";
9
+ import { get } from 'svelte/store';
10
+ import { behaviorSettings } from '../stores/settingsStore.js';
11
+ import { isDirtyStore, isDirty } from '../stores/workflowStore.js';
12
12
  /**
13
13
  * Initialize auto-save functionality based on user settings
14
14
  *
@@ -62,7 +62,7 @@ export function initAutoSave(options) {
62
62
  }
63
63
  catch (error) {
64
64
  const err = error instanceof Error ? error : new Error(String(error));
65
- console.error("Auto-save failed:", err);
65
+ console.error('Auto-save failed:', err);
66
66
  onError?.(err);
67
67
  }
68
68
  finally {
@@ -213,7 +213,7 @@ export class AutoSaveManager {
213
213
  }
214
214
  catch (error) {
215
215
  const err = error instanceof Error ? error : new Error(String(error));
216
- console.error("Auto-save failed:", err);
216
+ console.error('Auto-save failed:', err);
217
217
  this.onError?.(err);
218
218
  }
219
219
  finally {