@flowdrop/flowdrop 1.14.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +110 -66
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +45 -40
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +29 -13
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +292 -0
  61. package/dist/components/nodes/AtomNode.svelte.d.ts +26 -0
  62. package/dist/components/nodes/GatewayNode.svelte +19 -10
  63. package/dist/components/nodes/IdeaNode.svelte +7 -0
  64. package/dist/components/nodes/SimpleNode.svelte +11 -6
  65. package/dist/components/nodes/SquareNode.svelte +15 -8
  66. package/dist/components/nodes/TerminalNode.svelte +9 -4
  67. package/dist/components/nodes/ToolNode.svelte +7 -1
  68. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  69. package/dist/components/playground/ChatInput.svelte +11 -14
  70. package/dist/components/playground/ChatPanel.svelte +6 -49
  71. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  72. package/dist/components/playground/ControlPanel.svelte +134 -123
  73. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  74. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  75. package/dist/components/playground/InputCollector.svelte +11 -9
  76. package/dist/components/playground/MessageStream.svelte +17 -23
  77. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  78. package/dist/components/playground/PipelinePanel.svelte +11 -5
  79. package/dist/components/playground/PipelineTableView.svelte +186 -44
  80. package/dist/components/playground/Playground.svelte +95 -92
  81. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  82. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  83. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  84. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  85. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  86. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  87. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  88. package/dist/components/playground/SessionManager.svelte +9 -12
  89. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  90. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  91. package/dist/config/endpoints.d.ts +0 -7
  92. package/dist/config/endpoints.js +2 -10
  93. package/dist/core/index.d.ts +4 -4
  94. package/dist/core/index.js +6 -6
  95. package/dist/display/index.d.ts +0 -2
  96. package/dist/display/index.js +0 -6
  97. package/dist/editor/index.d.ts +19 -20
  98. package/dist/editor/index.js +25 -35
  99. package/dist/form/code.d.ts +25 -15
  100. package/dist/form/code.js +44 -41
  101. package/dist/form/fieldRegistry.d.ts +17 -13
  102. package/dist/form/fieldRegistry.js +32 -12
  103. package/dist/form/full.d.ts +17 -13
  104. package/dist/form/full.js +22 -27
  105. package/dist/form/index.d.ts +3 -3
  106. package/dist/form/index.js +3 -3
  107. package/dist/form/markdown.d.ts +13 -8
  108. package/dist/form/markdown.js +22 -23
  109. package/dist/helpers/proximityConnect.d.ts +7 -3
  110. package/dist/helpers/proximityConnect.js +19 -6
  111. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  112. package/dist/helpers/workflowEditorHelper.js +27 -25
  113. package/dist/index.d.ts +28 -24
  114. package/dist/index.js +27 -50
  115. package/dist/messages/defaults.d.ts +2 -5
  116. package/dist/messages/defaults.js +3 -6
  117. package/dist/messages/index.d.ts +0 -1
  118. package/dist/messages/index.js +0 -1
  119. package/dist/mocks/app-forms.d.ts +6 -2
  120. package/dist/mocks/app-forms.js +11 -4
  121. package/dist/openapi/v1/openapi.yaml +227 -164
  122. package/dist/playground/index.d.ts +2 -3
  123. package/dist/playground/index.js +2 -30
  124. package/dist/playground/mount.d.ts +15 -0
  125. package/dist/playground/mount.js +46 -20
  126. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  127. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  128. package/dist/registry/builtinFormats.d.ts +9 -18
  129. package/dist/registry/builtinFormats.js +9 -39
  130. package/dist/registry/builtinNodes.d.ts +1 -26
  131. package/dist/registry/builtinNodes.js +14 -50
  132. package/dist/registry/index.d.ts +3 -4
  133. package/dist/registry/index.js +4 -6
  134. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  135. package/dist/registry/nodeComponentRegistry.js +235 -17
  136. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  137. package/dist/registry/workflowFormatRegistry.js +24 -8
  138. package/dist/{schema → schemas}/index.d.ts +2 -2
  139. package/dist/{schema → schemas}/index.js +2 -2
  140. package/dist/schemas/v1/workflow.schema.json +53 -6
  141. package/dist/services/agentSpecExecutionService.js +0 -1
  142. package/dist/services/apiVariableService.d.ts +2 -1
  143. package/dist/services/apiVariableService.js +5 -22
  144. package/dist/services/autoSaveService.d.ts +7 -0
  145. package/dist/services/autoSaveService.js +6 -4
  146. package/dist/services/chatService.d.ts +8 -4
  147. package/dist/services/chatService.js +15 -15
  148. package/dist/services/draftStorage.d.ts +129 -13
  149. package/dist/services/draftStorage.js +185 -37
  150. package/dist/services/dynamicSchemaService.d.ts +2 -1
  151. package/dist/services/dynamicSchemaService.js +5 -22
  152. package/dist/services/globalSave.d.ts +13 -12
  153. package/dist/services/globalSave.js +29 -51
  154. package/dist/services/historyService.d.ts +9 -3
  155. package/dist/services/historyService.js +9 -3
  156. package/dist/services/interruptService.d.ts +14 -9
  157. package/dist/services/interruptService.js +27 -27
  158. package/dist/services/nodeExecutionService.d.ts +18 -3
  159. package/dist/services/nodeExecutionService.js +71 -45
  160. package/dist/services/playgroundService.d.ts +14 -9
  161. package/dist/services/playgroundService.js +31 -30
  162. package/dist/services/variableService.d.ts +2 -1
  163. package/dist/services/variableService.js +2 -2
  164. package/dist/services/workflowStorage.js +6 -6
  165. package/dist/stores/apiContext.d.ts +45 -0
  166. package/dist/stores/apiContext.js +65 -0
  167. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  168. package/dist/stores/categoriesStore.svelte.js +70 -64
  169. package/dist/stores/getInstance.svelte.d.ts +39 -0
  170. package/dist/stores/getInstance.svelte.js +65 -0
  171. package/dist/stores/historyStore.svelte.d.ts +77 -93
  172. package/dist/stores/historyStore.svelte.js +134 -160
  173. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  174. package/dist/stores/instanceContainer.svelte.js +114 -0
  175. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  176. package/dist/stores/interruptStore.svelte.js +253 -226
  177. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  178. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  179. package/dist/stores/playgroundStore.svelte.d.ts +169 -216
  180. package/dist/stores/playgroundStore.svelte.js +515 -572
  181. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  182. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  183. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  184. package/dist/stores/settingsStore.svelte.js +47 -12
  185. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  186. package/dist/stores/workflowStore.svelte.js +449 -501
  187. package/dist/stories/EdgeDecorator.svelte +5 -2
  188. package/dist/stories/NodeDecorator.svelte +5 -3
  189. package/dist/svelte-app.d.ts +60 -10
  190. package/dist/svelte-app.js +157 -53
  191. package/dist/types/events.d.ts +6 -3
  192. package/dist/types/index.d.ts +71 -6
  193. package/dist/types/navbar.d.ts +7 -0
  194. package/dist/types/playground.d.ts +18 -3
  195. package/dist/types/settings.d.ts +13 -0
  196. package/dist/types/settings.js +1 -0
  197. package/dist/utils/colors.d.ts +47 -21
  198. package/dist/utils/colors.js +69 -68
  199. package/dist/utils/connections.d.ts +9 -15
  200. package/dist/utils/connections.js +13 -32
  201. package/dist/utils/duration.d.ts +13 -0
  202. package/dist/utils/duration.js +45 -0
  203. package/dist/utils/formMerge.d.ts +36 -0
  204. package/dist/utils/formMerge.js +70 -0
  205. package/dist/utils/icons.d.ts +5 -2
  206. package/dist/utils/icons.js +6 -5
  207. package/dist/utils/nodeSwap.d.ts +6 -2
  208. package/dist/utils/nodeSwap.js +62 -126
  209. package/dist/utils/nodeTypes.d.ts +17 -8
  210. package/dist/utils/nodeTypes.js +27 -19
  211. package/dist/utils/performanceUtils.js +7 -0
  212. package/package.json +6 -5
  213. package/dist/messages/deprecation.d.ts +0 -20
  214. package/dist/messages/deprecation.js +0 -33
  215. package/dist/registry/plugin.d.ts +0 -215
  216. package/dist/registry/plugin.js +0 -249
  217. package/dist/services/api.d.ts +0 -129
  218. package/dist/services/api.js +0 -217
@@ -121,7 +121,8 @@
121
121
  // Stable fingerprint — any change triggers selection clearing.
122
122
  // JSON.stringify gives a canonical string without null-byte ambiguity.
123
123
  const depFingerprint = $derived(JSON.stringify(depParamValues));
124
- // svelte-ignore state_referenced_locally — intentional initial snapshot; the effect below tracks subsequent changes
124
+ // intentional initial snapshot; the effect below tracks subsequent changes
125
+ // svelte-ignore state_referenced_locally
125
126
  let prevDepFingerprint = depFingerprint;
126
127
 
127
128
  $effect(() => {
@@ -135,9 +136,9 @@
135
136
  });
136
137
 
137
138
  // Generate unique IDs for accessibility
138
- // svelte-ignore state_referenced_locally — id prop never changes
139
- const listboxId = `${id}-listbox`;
139
+ // id prop never changes
140
140
  // svelte-ignore state_referenced_locally
141
+ const listboxId = `${id}-listbox`;
141
142
  const getOptionId = (index: number): string => `${id}-option-${index}`;
142
143
 
143
144
  /**
@@ -309,10 +310,9 @@
309
310
  // Open dropdown
310
311
  showDropdown();
311
312
 
312
- // If allowFreeText and single mode, update the value immediately
313
- if (allowFreeText && !multiple) {
314
- onChange(inputValue);
315
- }
313
+ // Free-text single mode commits on intent signals (Enter, blur, option select),
314
+ // NOT per keystroke — see issue #32. Treating the search-box text as the field's
315
+ // committed value pollutes the parent form's edits buffer and history.
316
316
 
317
317
  // Fetch suggestions with debounce
318
318
  debouncedFetch(inputValue);
@@ -353,6 +353,19 @@
353
353
  inputValue = '';
354
354
  }
355
355
  }
356
+
357
+ // Free-text single mode — commit on blur if the user typed something
358
+ // that differs from the current value. An empty inputValue is just the
359
+ // resting state of the search box (the value lives in the chip), so it
360
+ // is NOT an intent signal; clearing happens via the X button or
361
+ // Backspace-on-empty. Replaces the per-keystroke commit dropped from
362
+ // handleInput; see issue #32.
363
+ if (allowFreeText && !multiple && inputValue !== '') {
364
+ const currentSingle = selectedValues[0] ?? '';
365
+ if (inputValue !== currentSingle) {
366
+ onChange(inputValue);
367
+ }
368
+ }
356
369
  }, 200);
357
370
  }
358
371
 
@@ -592,12 +605,14 @@
592
605
  const current = depFingerprint;
593
606
  if (current === prevDepFingerprint) return;
594
607
  prevDepFingerprint = current;
595
- // A dependency field changed — abort any in-flight fetch, clear state
608
+ // A dependency field changed — abort any in-flight fetch and invalidate
609
+ // cached suggestions/labels (they were keyed by the previous dependency
610
+ // values). Do NOT clear this field's value here: that policy lives in the
611
+ // parent form's handleFieldChange so it only fires on user-driven changes,
612
+ // not on undo/redo, programmatic resets, or collaborative edits (#33).
596
613
  abortController?.abort();
597
614
  suggestions = [];
598
615
  labelCache = new Map();
599
- if (multiple) onChange([]);
600
- else onChange('');
601
616
  });
602
617
 
603
618
  /**
@@ -625,7 +640,7 @@
625
640
  class:form-autocomplete--has-value={selectedValues.length > 0}
626
641
  >
627
642
  <!-- Main input container styled like a textfield/textarea -->
628
- <!-- svelte-ignore a11y_no_static_element_interactions — role="presentation"; keyboard interaction is handled by the <input> inside -->
643
+ <!-- role="presentation"; keyboard interaction is handled by the <input> inside -->
629
644
  <div
630
645
  class="form-autocomplete__field"
631
646
  class:form-autocomplete__field--focused={isOpen}
@@ -712,7 +727,7 @@
712
727
  </div>
713
728
 
714
729
  <!-- Dropdown popover (uses Popover API to render in top layer, bypassing stacking contexts) -->
715
- <!-- svelte-ignore a11y_no_static_element_interactions — role="presentation" container; onmousedown prevents focus loss from input -->
730
+ <!-- role="presentation" container; onmousedown prevents focus loss from input -->
716
731
  <div
717
732
  bind:this={popoverElement}
718
733
  id={listboxId}
@@ -743,7 +758,8 @@
743
758
  </li>
744
759
  {:else}
745
760
  {#each suggestions as option, index (option.value)}
746
- <!-- svelte-ignore a11y_click_events_have_key_events — WAI-ARIA combobox: keyboard nav handled on input, not individual options -->
761
+ <!-- WAI-ARIA combobox: keyboard nav handled on input, not individual options -->
762
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
747
763
  <li
748
764
  id={getOptionId(index)}
749
765
  class="form-autocomplete__option"
@@ -45,7 +45,9 @@
45
45
  import { getSchemaOptions } from './types.js';
46
46
  import type { WorkflowNode, WorkflowEdge, AuthProvider } from '../../types/index.js';
47
47
  import { getResolvedTheme } from '../../stores/settingsStore.svelte.js';
48
- import { fieldComponentRegistry } from '../../form/fieldRegistry.js';
48
+ import { getInstance } from '../../stores/getInstance.svelte.js';
49
+
50
+ const fd = getInstance();
49
51
 
50
52
  interface Props {
51
53
  /** Unique key/id for the field */
@@ -226,7 +228,7 @@
226
228
  });
227
229
 
228
230
  const registeredAutocompleteComponent = $derived(
229
- fieldType === 'autocomplete' ? fieldComponentRegistry.resolveFieldComponent(schema) : null
231
+ fieldType === 'autocomplete' ? fd.fields.resolveFieldComponent(schema) : null
230
232
  );
231
233
 
232
234
  /**
@@ -42,8 +42,10 @@
42
42
  import FormSelect from './FormSelect.svelte';
43
43
  import FormCheckboxGroup from './FormCheckboxGroup.svelte';
44
44
  import FormArray from './FormArray.svelte';
45
- import { fieldComponentRegistry } from '../../form/fieldRegistry.js';
45
+ import { getInstance } from '../../stores/getInstance.svelte.js';
46
46
  import { getResolvedTheme } from '../../stores/settingsStore.svelte.js';
47
+
48
+ const fd = getInstance();
47
49
  import type { FieldSchema } from './types.js';
48
50
  import { getSchemaOptions } from './types.js';
49
51
 
@@ -84,7 +86,7 @@
84
86
  /**
85
87
  * Check if there's a registered custom component for this schema
86
88
  */
87
- const registeredComponent = $derived(fieldComponentRegistry.resolveFieldComponent(schema));
89
+ const registeredComponent = $derived(fd.fields.resolveFieldComponent(schema));
88
90
 
89
91
  /**
90
92
  * Determine the field type to render (for non-registered components)
@@ -409,11 +409,12 @@
409
409
  }),
410
410
  EditorView.lineWrapping,
411
411
 
412
- // Accessibility
412
+ // Accessibility + browser spellcheck (off by default, opt-in via spellChecker)
413
413
  ariaLabelCompartment.of(
414
414
  EditorView.contentAttributes.of({
415
415
  'aria-label': m().form.markdown.editor,
416
- 'aria-multiline': 'true'
416
+ 'aria-multiline': 'true',
417
+ spellcheck: String(spellChecker)
417
418
  })
418
419
  )
419
420
  ];
@@ -495,7 +496,8 @@
495
496
  ariaLabelCompartment.reconfigure(
496
497
  EditorView.contentAttributes.of({
497
498
  'aria-label': ariaLabel,
498
- 'aria-multiline': 'true'
499
+ 'aria-multiline': 'true',
500
+ spellcheck: String(spellChecker)
499
501
  })
500
502
  )
501
503
  ]
@@ -525,7 +527,8 @@
525
527
  role="toolbar"
526
528
  aria-label={m().form.markdown.toolbar}
527
529
  >
528
- {#each toolbarActions as item}
530
+ <!-- Separators have no identity of their own — disambiguate repeats by position -->
531
+ {#each toolbarActions as item, i (item === '|' ? `separator-${i}` : item.id)}
529
532
  {#if item === '|'}
530
533
  <span class="form-markdown-editor__separator"></span>
531
534
  {:else}
@@ -536,6 +539,8 @@
536
539
  onclick={item.action}
537
540
  >
538
541
  {#if item.isSvg}
542
+ <!-- item.icon is a compile-time SVG constant from toolbarActions, never user input -->
543
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
539
544
  <span class="form-markdown-editor__btn-svg">{@html item.icon}</span>
540
545
  {:else}
541
546
  <span
@@ -88,6 +88,7 @@
88
88
  {max}
89
89
  {step}
90
90
  {disabled}
91
+ {required}
91
92
  aria-describedby={ariaDescribedBy}
92
93
  aria-valuemin={min}
93
94
  aria-valuemax={max}
@@ -30,7 +30,8 @@
30
30
  tooltips,
31
31
  Decoration,
32
32
  ViewPlugin,
33
- MatchDecorator
33
+ MatchDecorator,
34
+ placeholder as cmPlaceholder
34
35
  } from '@codemirror/view';
35
36
  import { EditorState, Compartment } from '@codemirror/state';
36
37
  import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
@@ -45,6 +46,7 @@
45
46
  } from '../../types/index.js';
46
47
  import { createTemplateAutocomplete } from './templateAutocomplete.js';
47
48
  import { getVariableSchema } from '../../services/variableService.js';
49
+ import { getInstance } from '../../stores/getInstance.svelte.js';
48
50
  import { logger } from '../../utils/logger.js';
49
51
  import { m } from '../../messages/index.js';
50
52
 
@@ -105,6 +107,9 @@
105
107
  authProvider
106
108
  }: Props = $props();
107
109
 
110
+ // Active instance — supplies endpoint configuration for API variable fetching.
111
+ const fd = getInstance();
112
+
108
113
  /** Loading state for API variable fetching */
109
114
  let isLoadingVariables = $state(false);
110
115
 
@@ -139,6 +144,7 @@
139
144
  try {
140
145
  isLoadingVariables = true;
141
146
  effectiveVariableSchema = await getVariableSchema(
147
+ fd.api.config,
142
148
  node,
143
149
  nodes,
144
150
  edges,
@@ -350,7 +356,9 @@
350
356
  }
351
357
  }),
352
358
  EditorView.lineWrapping,
353
- EditorState.tabSize.of(2)
359
+ EditorState.tabSize.of(2),
360
+ // Shown inside the editor while the document is empty
361
+ cmPlaceholder(placeholder)
354
362
  ];
355
363
 
356
364
  // Add autocomplete compartment (can be reconfigured dynamically)
@@ -570,7 +578,7 @@
570
578
  onclick={() => insertVariable(varName)}
571
579
  title={`Insert {{ ${varName} }}`}
572
580
  >
573
- <code>{'{{ '}{varName}{' }}'}</code>
581
+ <code>{`{{ ${varName} }}`}</code>
574
582
  </button>
575
583
  {/each}
576
584
  </div>
@@ -9,7 +9,7 @@
9
9
  -->
10
10
 
11
11
  <script lang="ts">
12
- import { m, warnDeprecatedProp } from '../../messages/index.js';
12
+ import { m } from '../../messages/index.js';
13
13
 
14
14
  interface Props {
15
15
  /** Field identifier */
@@ -17,11 +17,13 @@
17
17
  /** Current value */
18
18
  value: boolean;
19
19
  /**
20
- * @deprecated since v1.8 use `messages.form.toggle.enabled`. Removed in v2.0.
20
+ * Per-instance label for the on state (e.g. "Hidden" for a visibility
21
+ * toggle). Falls back to the global `messages.form.toggle.enabled`.
21
22
  */
22
23
  onLabel?: string;
23
24
  /**
24
- * @deprecated since v1.8 use `messages.form.toggle.disabled`. Removed in v2.0.
25
+ * Per-instance label for the off state. Falls back to the global
26
+ * `messages.form.toggle.disabled`.
25
27
  */
26
28
  offLabel?: string;
27
29
  /** Whether the field is disabled (read-only) */
@@ -42,15 +44,6 @@
42
44
  onChange
43
45
  }: Props = $props();
44
46
 
45
- // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
46
- if (onLabel !== undefined) {
47
- warnDeprecatedProp('FormToggle', 'onLabel', 'messages.form.toggle.enabled');
48
- }
49
- // svelte-ignore state_referenced_locally
50
- if (offLabel !== undefined) {
51
- warnDeprecatedProp('FormToggle', 'offLabel', 'messages.form.toggle.disabled');
52
- }
53
-
54
47
  const resolvedOnLabel = $derived(onLabel ?? m().form.toggle.enabled);
55
48
  const resolvedOffLabel = $derived(offLabel ?? m().form.toggle.disabled);
56
49
 
@@ -4,11 +4,13 @@ interface Props {
4
4
  /** Current value */
5
5
  value: boolean;
6
6
  /**
7
- * @deprecated since v1.8 use `messages.form.toggle.enabled`. Removed in v2.0.
7
+ * Per-instance label for the on state (e.g. "Hidden" for a visibility
8
+ * toggle). Falls back to the global `messages.form.toggle.enabled`.
8
9
  */
9
10
  onLabel?: string;
10
11
  /**
11
- * @deprecated since v1.8 use `messages.form.toggle.disabled`. Removed in v2.0.
12
+ * Per-instance label for the off state. Falls back to the global
13
+ * `messages.form.toggle.disabled`.
12
14
  */
13
15
  offLabel?: string;
14
16
  /** Whether the field is disabled (read-only) */
@@ -11,7 +11,7 @@
11
11
  * @module components/form/templateAutocomplete
12
12
  */
13
13
  import { autocompletion } from '@codemirror/autocomplete';
14
- import { getChildVariables, getArrayIndexSuggestions, isArrayVariable, hasChildren } from '../../services/variableService.js';
14
+ import { getChildVariables, getArrayIndexSuggestions, isArrayVariable } from '../../services/variableService.js';
15
15
  /**
16
16
  * Icon type hints for different variable types in autocomplete dropdown.
17
17
  */
@@ -56,10 +56,6 @@ function extractVariablePath(text, pos) {
56
56
  if (openBracePos === -1) {
57
57
  return null;
58
58
  }
59
- // Check if there's a closing }} after cursor (still inside expression)
60
- const afterCursor = text.slice(pos);
61
- const closingMatch = afterCursor.match(/^\s*\}\}/);
62
- const hasClosing = closingMatch !== null;
63
59
  // Extract the content between {{ and cursor
64
60
  const contentStart = openBracePos + 2;
65
61
  const content = text.slice(contentStart, pos).trimStart();
@@ -95,8 +95,7 @@ export interface BaseFieldProps<T = unknown> {
95
95
  /**
96
96
  * Properties for text input fields
97
97
  */
98
- export interface TextFieldProps extends BaseFieldProps<string> {
99
- }
98
+ export type TextFieldProps = BaseFieldProps<string>;
100
99
  /**
101
100
  * Properties for multiline text fields (textarea)
102
101
  */
@@ -432,18 +431,6 @@ export interface SchemaFormProps {
432
431
  * @default false
433
432
  */
434
433
  showActions?: boolean;
435
- /**
436
- * Label text for the save button.
437
- * Only used when showActions is true.
438
- * @default "Save"
439
- */
440
- saveLabel?: string;
441
- /**
442
- * Label text for the cancel button.
443
- * Only used when showActions is true.
444
- * @default "Cancel"
445
- */
446
- cancelLabel?: string;
447
434
  /**
448
435
  * Callback fired when the Save button is clicked.
449
436
  * Receives the final form values after collecting from DOM.
@@ -44,7 +44,8 @@
44
44
  }: Props = $props();
45
45
 
46
46
  /** Local state for form values */
47
- // svelte-ignore state_referenced_locally — initial default, user fills the form
47
+ // initial default, user fills the form
48
+ // svelte-ignore state_referenced_locally
48
49
  let formValues = $state<Record<string, unknown>>(config.defaultValues ?? {});
49
50
 
50
51
  /** Display values - either resolved or current form values */
@@ -131,7 +132,7 @@
131
132
  <div class="form-prompt__resolved-values">
132
133
  <h4 class="form-prompt__resolved-title">{interrupt.form.submittedValuesTitle}</h4>
133
134
  <div class="form-prompt__values-list">
134
- {#each Object.entries(config.schema.properties ?? {}) as [key, field]}
135
+ {#each Object.entries(config.schema.properties ?? {}) as [key, field] (key)}
135
136
  {@const value = displayValues[key]}
136
137
  {@const fieldTitle = ((field as Record<string, unknown>).title as string) ?? key}
137
138
  <div class="form-prompt__value-item">
@@ -33,11 +33,8 @@
33
33
  getErrorMessage,
34
34
  getResolvedValue
35
35
  } from '../../types/interruptState.js';
36
- import {
37
- getInterruptsMap,
38
- interruptActions,
39
- type InterruptWithState
40
- } from '../../stores/interruptStore.svelte.js';
36
+ import { type InterruptWithState } from '../../stores/interruptStore.svelte.js';
37
+ import { getInstance } from '../../stores/getInstance.svelte.js';
41
38
  import { interruptService } from '../../services/interruptService.js';
42
39
  import { logger } from '../../utils/logger.js';
43
40
  import { m } from '../../messages/index.js';
@@ -72,12 +69,14 @@
72
69
  tags
73
70
  }: Props = $props();
74
71
 
72
+ const fd = getInstance();
73
+
75
74
  /**
76
75
  * Get the current interrupt state from the store.
77
76
  * This ensures we react to store updates (like status changes).
78
77
  */
79
78
  const currentInterrupt = $derived(
80
- getInterruptsMap().get(initialInterrupt.id) ?? addMachineState(initialInterrupt)
79
+ fd.interrupts.getMap().get(initialInterrupt.id) ?? addMachineState(initialInterrupt)
81
80
  );
82
81
 
83
82
  const hierarchyItems = $derived(hierarchy ?? []);
@@ -191,7 +190,7 @@
191
190
  */
192
191
  async function handleResolve(value: unknown): Promise<void> {
193
192
  // Start the submission - state machine validates this transition
194
- const startResult = interruptActions.startSubmit(currentInterrupt.id, value);
193
+ const startResult = fd.interrupts.startSubmit(currentInterrupt.id, value);
195
194
  if (!startResult.valid) {
196
195
  logger.warn('[InterruptBubble] Cannot submit:', startResult.error);
197
196
  return;
@@ -199,19 +198,19 @@
199
198
 
200
199
  try {
201
200
  // Call API if service is configured
202
- if (interruptService.isConfigured()) {
203
- await interruptService.resolveInterrupt(currentInterrupt.id, value);
201
+ if (interruptService.isConfigured(fd.api.config)) {
202
+ await interruptService.resolveInterrupt(fd.api.config, currentInterrupt.id, value);
204
203
  }
205
204
 
206
205
  // Mark as successful - transitions to resolved state
207
- interruptActions.submitSuccess(currentInterrupt.id);
206
+ fd.interrupts.submitSuccess(currentInterrupt.id);
208
207
 
209
208
  // Notify parent to refresh messages
210
209
  onResolved?.();
211
210
  } catch (err) {
212
211
  // Mark as failed - transitions to error state (can retry)
213
212
  const errorMessage = err instanceof Error ? err.message : 'Failed to submit response';
214
- interruptActions.submitFailure(currentInterrupt.id, errorMessage);
213
+ fd.interrupts.submitFailure(currentInterrupt.id, errorMessage);
215
214
  logger.error('[InterruptBubble] Resolve error:', err);
216
215
  }
217
216
  }
@@ -221,7 +220,7 @@
221
220
  */
222
221
  async function handleCancel(): Promise<void> {
223
222
  // Start the cancel - state machine validates this transition
224
- const startResult = interruptActions.startCancel(currentInterrupt.id);
223
+ const startResult = fd.interrupts.startCancel(currentInterrupt.id);
225
224
  if (!startResult.valid) {
226
225
  logger.warn('[InterruptBubble] Cannot cancel:', startResult.error);
227
226
  return;
@@ -229,19 +228,19 @@
229
228
 
230
229
  try {
231
230
  // Call API if service is configured
232
- if (interruptService.isConfigured()) {
233
- await interruptService.cancelInterrupt(currentInterrupt.id);
231
+ if (interruptService.isConfigured(fd.api.config)) {
232
+ await interruptService.cancelInterrupt(fd.api.config, currentInterrupt.id);
234
233
  }
235
234
 
236
235
  // Mark as successful - transitions to cancelled state
237
- interruptActions.submitSuccess(currentInterrupt.id);
236
+ fd.interrupts.submitSuccess(currentInterrupt.id);
238
237
 
239
238
  // Notify parent to refresh messages
240
239
  onResolved?.();
241
240
  } catch (err) {
242
241
  // Mark as failed - transitions to error state (can retry)
243
242
  const errorMessage = err instanceof Error ? err.message : 'Failed to cancel';
244
- interruptActions.submitFailure(currentInterrupt.id, errorMessage);
243
+ fd.interrupts.submitFailure(currentInterrupt.id, errorMessage);
245
244
  logger.error('[InterruptBubble] Cancel error:', err);
246
245
  }
247
246
  }
@@ -250,7 +249,7 @@
250
249
  * Handle retry after error
251
250
  */
252
251
  function handleRetry(): void {
253
- interruptActions.retry(currentInterrupt.id);
252
+ fd.interrupts.retry(currentInterrupt.id);
254
253
  }
255
254
 
256
255
  // Typed config getters for each prompt type
@@ -51,7 +51,8 @@
51
51
  }: Props = $props();
52
52
 
53
53
  /** Local state: map of field -> accepted boolean. Default all to true (accept). */
54
- // svelte-ignore state_referenced_locally — initial default, user toggles during review
54
+ // initial default, user toggles during review
55
+ // svelte-ignore state_referenced_locally
55
56
  let decisions = $state<Record<string, boolean>>(
56
57
  Object.fromEntries(config.changes.map((c) => [c.field, true]))
57
58
  );
@@ -346,9 +347,12 @@
346
347
  <div class="review-prompt__diff-row">
347
348
  <span class="review-prompt__diff-label">{t.original}</span>
348
349
  {#if isHtml && !isRawView}
350
+ <!-- Output of sanitizeHtml() (DOMPurify allowlist) — see utils/sanitize.ts -->
351
+ <!-- eslint-disable svelte/no-at-html-tags -->
349
352
  <span class="review-prompt__diff-value review-prompt__html-content"
350
353
  >{@html sanitizeHtml(String(change.original))}</span
351
354
  >
355
+ <!-- eslint-enable svelte/no-at-html-tags -->
352
356
  {:else if isHtml && isRawView}
353
357
  <code class="review-prompt__diff-value review-prompt__raw-html"
354
358
  >{change.original}</code
@@ -362,10 +366,13 @@
362
366
  <div class="review-prompt__diff-row">
363
367
  <span class="review-prompt__diff-label">{t.proposed}</span>
364
368
  {#if isHtml && !isRawView}
369
+ <!-- Output of sanitizeHtml() (DOMPurify allowlist) — see utils/sanitize.ts -->
370
+ <!-- eslint-disable svelte/no-at-html-tags -->
365
371
  <span
366
372
  class="review-prompt__diff-value review-prompt__diff-value--proposed review-prompt__html-content"
367
373
  >{@html sanitizeHtml(String(change.proposed))}</span
368
374
  >
375
+ <!-- eslint-enable svelte/no-at-html-tags -->
369
376
  {:else if isHtml && isRawView}
370
377
  <code
371
378
  class="review-prompt__diff-value review-prompt__diff-value--proposed review-prompt__raw-html"
@@ -382,14 +389,14 @@
382
389
  <span class="review-prompt__diff-label">{t.diff}</span>
383
390
  {#if isMultiLineDiff(diff)}
384
391
  <pre
385
- class="review-prompt__diff-value review-prompt__diff-block">{#each diff as part}{#if part.added}<span
392
+ class="review-prompt__diff-value review-prompt__diff-block">{#each diff as part, i (i)}{#if part.added}<span
386
393
  class="review-prompt__diff-token--added">{part.value}</span
387
394
  >{:else if part.removed}<span class="review-prompt__diff-token--removed"
388
395
  >{part.value}</span
389
396
  >{:else}<span>{part.value}</span>{/if}{/each}</pre>
390
397
  {:else}
391
398
  <span class="review-prompt__diff-value review-prompt__diff-inline">
392
- {#each diff as part}
399
+ {#each diff as part, i (i)}
393
400
  {#if part.added}
394
401
  <span class="review-prompt__diff-token--added">{part.value}</span>
395
402
  {:else if part.removed}
@@ -47,7 +47,8 @@
47
47
  const t = $derived(m().interrupt.text);
48
48
 
49
49
  /** Local state for input value */
50
- // svelte-ignore state_referenced_locally — initial default, user edits the input
50
+ // initial default, user edits the input
51
+ // svelte-ignore state_referenced_locally
51
52
  let inputValue = $state(config.defaultValue ?? '');
52
53
 
53
54
  /** Display value - either resolved or current input */
@@ -17,6 +17,10 @@
17
17
  * Configuration props for the MainLayout component
18
18
  */
19
19
  interface Props {
20
+ /** Overall layout height (CSS length, or a number of pixels) */
21
+ height?: string | number;
22
+ /** Overall layout width (CSS length, or a number of pixels) */
23
+ width?: string | number;
20
24
  /** Height of the header in pixels */
21
25
  headerHeight?: number;
22
26
  /** Height of the footer in pixels */
@@ -74,6 +78,8 @@
74
78
  }
75
79
 
76
80
  let {
81
+ height = '100vh',
82
+ width = '100%',
77
83
  headerHeight = 60,
78
84
  footerHeight = 48,
79
85
  showHeader = true,
@@ -103,9 +109,12 @@
103
109
  children
104
110
  }: Props = $props();
105
111
 
106
- /** Current width of the left sidebar */
107
- // svelte-ignore state_referenced_locally initial default, component owns draggable state
108
- let leftSidebarWidth = $state(initialLeftWidth);
112
+ /**
113
+ * Current width of the left sidebar.
114
+ * Writable derived: recomputes when the prop changes (external control,
115
+ * e.g. collapsed state), while drag/keyboard resizing assigns over it.
116
+ */
117
+ let leftSidebarWidth = $derived(initialLeftWidth);
109
118
 
110
119
  /** Current width of the right sidebar */
111
120
  // svelte-ignore state_referenced_locally
@@ -115,14 +124,6 @@
115
124
  // svelte-ignore state_referenced_locally
116
125
  let bottomPanelHeightState = $state(initialBottomHeight);
117
126
 
118
- /**
119
- * Sync left sidebar width with prop changes
120
- * This allows external control (e.g., collapsed state) to update the width
121
- */
122
- $effect(() => {
123
- leftSidebarWidth = initialLeftWidth;
124
- });
125
-
126
127
  /** Whether the user is currently dragging the left divider */
127
128
  let isDraggingLeft = $state(false);
128
129
 
@@ -298,6 +299,10 @@
298
299
 
299
300
  /** Computed CSS variable for bottom panel height */
300
301
  const bottomHeightVar = $derived(`${bottomPanelHeightState}px`);
302
+
303
+ /** Bare numbers mean pixels; strings pass through as CSS lengths */
304
+ const layoutHeightVar = $derived(typeof height === 'number' ? `${height}px` : height);
305
+ const layoutWidthVar = $derived(typeof width === 'number' ? `${width}px` : width);
301
306
  </script>
302
307
 
303
308
  <div
@@ -306,6 +311,8 @@
306
311
  class:flowdrop-main-layout--dragging={isDraggingLeft || isDraggingRight || isDraggingBottom}
307
312
  class:flowdrop-main-layout--dragging-vertical={isDraggingBottom}
308
313
  style="
314
+ --layout-height: {layoutHeightVar};
315
+ --layout-width: {layoutWidthVar};
309
316
  --layout-header-height: {headerHeightVar};
310
317
  --layout-footer-height: {footerHeightVar};
311
318
  --layout-left-sidebar-width: {leftWidthVar};
@@ -437,8 +444,8 @@
437
444
  .flowdrop-main-layout {
438
445
  display: flex;
439
446
  flex-direction: column;
440
- height: 100vh;
441
- width: 100%;
447
+ height: var(--layout-height, 100vh);
448
+ width: var(--layout-width, 100%);
442
449
  background: var(
443
450
  --fd-layout-background,
444
451
  linear-gradient(135deg, #f9fafb 0%, #e0e7ff 50%, #c7d2fe 100%)
@@ -2,6 +2,10 @@
2
2
  * Configuration props for the MainLayout component
3
3
  */
4
4
  interface Props {
5
+ /** Overall layout height (CSS length, or a number of pixels) */
6
+ height?: string | number;
7
+ /** Overall layout width (CSS length, or a number of pixels) */
8
+ width?: string | number;
5
9
  /** Height of the header in pixels */
6
10
  headerHeight?: number;
7
11
  /** Height of the footer in pixels */