@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.2

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 (235) hide show
  1. package/CHANGELOG.md +508 -0
  2. package/MIGRATION-2.0.md +629 -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/api/enhanced-client.js +6 -11
  8. package/dist/chat/batchFeedback.d.ts +39 -0
  9. package/dist/chat/batchFeedback.js +51 -0
  10. package/dist/commands/executor.js +15 -1
  11. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  12. package/dist/commands/storeIntegration.svelte.js +26 -21
  13. package/dist/commands/types.d.ts +2 -0
  14. package/dist/components/App.svelte +163 -192
  15. package/dist/components/App.svelte.d.ts +47 -8
  16. package/dist/components/ConfigForm.svelte +77 -49
  17. package/dist/components/ConfigModal.svelte +7 -2
  18. package/dist/components/ConnectionLine.svelte +4 -2
  19. package/dist/components/Navbar.svelte +61 -1
  20. package/dist/components/NodeSidebar.svelte +27 -45
  21. package/dist/components/NodeStatusOverlay.svelte +94 -6
  22. package/dist/components/NodeSwapPicker.svelte +10 -8
  23. package/dist/components/PipelineStatus.svelte +22 -68
  24. package/dist/components/PipelineStatus.svelte.d.ts +3 -0
  25. package/dist/components/PortCoordinateTracker.svelte +5 -6
  26. package/dist/components/SchemaForm.stories.svelte +1 -3
  27. package/dist/components/SchemaForm.svelte +22 -27
  28. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  29. package/dist/components/SettingsModal.svelte +8 -3
  30. package/dist/components/SettingsPanel.svelte +20 -4
  31. package/dist/components/SwapMappingEditor.svelte +67 -49
  32. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  33. package/dist/components/UniversalNode.svelte +9 -7
  34. package/dist/components/WorkflowEditor.svelte +121 -111
  35. package/dist/components/WorkflowEditor.svelte.d.ts +21 -10
  36. package/dist/components/chat/AIChatPanel.svelte +98 -89
  37. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  38. package/dist/components/chat/CommandPreview.svelte +2 -1
  39. package/dist/components/console/CommandConsole.svelte +7 -5
  40. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  41. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  42. package/dist/components/console/ConsoleInput.svelte +15 -6
  43. package/dist/components/console/ConsoleOutput.svelte +2 -1
  44. package/dist/components/form/FormArray.svelte +5 -9
  45. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  46. package/dist/components/form/FormAutocomplete.svelte +16 -15
  47. package/dist/components/form/FormField.svelte +4 -2
  48. package/dist/components/form/FormFieldLight.svelte +34 -3
  49. package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
  50. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  51. package/dist/components/form/FormRangeField.svelte +1 -0
  52. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  53. package/dist/components/form/FormToggle.svelte +5 -12
  54. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  55. package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
  56. package/dist/components/form/templateAutocomplete.js +1 -5
  57. package/dist/components/form/types.d.ts +1 -14
  58. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  59. package/dist/components/interrupt/InterruptBubble.svelte +25 -17
  60. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  61. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  62. package/dist/components/layouts/MainLayout.svelte +20 -13
  63. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  64. package/dist/components/nodes/AtomNode.svelte +17 -5
  65. package/dist/components/nodes/GatewayNode.svelte +19 -10
  66. package/dist/components/nodes/IdeaNode.svelte +7 -0
  67. package/dist/components/nodes/SimpleNode.svelte +11 -6
  68. package/dist/components/nodes/SquareNode.svelte +15 -8
  69. package/dist/components/nodes/TerminalNode.svelte +9 -4
  70. package/dist/components/nodes/ToolNode.svelte +7 -1
  71. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  72. package/dist/components/playground/ChatInput.svelte +11 -14
  73. package/dist/components/playground/ChatPanel.svelte +6 -49
  74. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  75. package/dist/components/playground/ControlPanel.svelte +134 -123
  76. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  77. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  78. package/dist/components/playground/InputCollector.svelte +11 -9
  79. package/dist/components/playground/MessageStream.svelte +17 -23
  80. package/dist/components/playground/PipelineKanbanView.svelte +69 -8
  81. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
  82. package/dist/components/playground/PipelinePanel.svelte +31 -8
  83. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
  84. package/dist/components/playground/PipelineTableView.svelte +188 -44
  85. package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
  86. package/dist/components/playground/Playground.svelte +154 -105
  87. package/dist/components/playground/Playground.svelte.d.ts +5 -0
  88. package/dist/components/playground/PlaygroundApp.svelte +11 -1
  89. package/dist/components/playground/PlaygroundApp.svelte.d.ts +6 -0
  90. package/dist/components/playground/PlaygroundModal.svelte +18 -3
  91. package/dist/components/playground/PlaygroundModal.svelte.d.ts +6 -0
  92. package/dist/components/playground/PlaygroundStudio.svelte +40 -32
  93. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +6 -0
  94. package/dist/components/playground/SessionManager.svelte +9 -12
  95. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +30 -1
  96. package/dist/components/playground/pipelineViewUtils.svelte.js +40 -3
  97. package/dist/config/endpoints.d.ts +23 -7
  98. package/dist/config/endpoints.js +30 -10
  99. package/dist/core/index.d.ts +5 -6
  100. package/dist/core/index.js +8 -12
  101. package/dist/display/index.d.ts +6 -3
  102. package/dist/display/index.js +7 -5
  103. package/dist/editor/index.d.ts +20 -21
  104. package/dist/editor/index.js +26 -36
  105. package/dist/form/code.d.ts +25 -15
  106. package/dist/form/code.js +44 -41
  107. package/dist/form/fieldRegistry.d.ts +17 -13
  108. package/dist/form/fieldRegistry.js +32 -12
  109. package/dist/form/full.d.ts +19 -14
  110. package/dist/form/full.js +26 -28
  111. package/dist/form/index.d.ts +3 -4
  112. package/dist/form/index.js +6 -5
  113. package/dist/form/markdown.d.ts +13 -8
  114. package/dist/form/markdown.js +22 -23
  115. package/dist/helpers/proximityConnect.d.ts +3 -2
  116. package/dist/helpers/proximityConnect.js +2 -5
  117. package/dist/helpers/workflowEditorHelper.d.ts +14 -5
  118. package/dist/helpers/workflowEditorHelper.js +28 -25
  119. package/dist/index.d.ts +28 -24
  120. package/dist/index.js +27 -50
  121. package/dist/messages/defaults.d.ts +2 -5
  122. package/dist/messages/defaults.js +3 -6
  123. package/dist/messages/index.d.ts +0 -1
  124. package/dist/messages/index.js +0 -1
  125. package/dist/mocks/app-forms.d.ts +6 -2
  126. package/dist/mocks/app-forms.js +11 -4
  127. package/dist/openapi/v1/openapi.yaml +3 -3
  128. package/dist/playground/index.d.ts +4 -5
  129. package/dist/playground/index.js +4 -32
  130. package/dist/playground/mount.d.ts +25 -0
  131. package/dist/playground/mount.js +50 -20
  132. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  133. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  134. package/dist/registry/builtinFormats.d.ts +9 -18
  135. package/dist/registry/builtinFormats.js +9 -39
  136. package/dist/registry/builtinNodeTypes.d.ts +53 -0
  137. package/dist/registry/builtinNodeTypes.js +67 -0
  138. package/dist/registry/builtinNodes.d.ts +2 -64
  139. package/dist/registry/builtinNodes.js +7 -103
  140. package/dist/registry/index.d.ts +3 -4
  141. package/dist/registry/index.js +4 -6
  142. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  143. package/dist/registry/nodeComponentRegistry.js +235 -17
  144. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  145. package/dist/registry/workflowFormatRegistry.js +24 -8
  146. package/dist/{schema → schemas}/index.d.ts +2 -2
  147. package/dist/{schema → schemas}/index.js +2 -2
  148. package/dist/schemas/v1/workflow.schema.json +3 -3
  149. package/dist/services/agentSpecExecutionService.d.ts +0 -2
  150. package/dist/services/agentSpecExecutionService.js +0 -3
  151. package/dist/services/apiVariableService.d.ts +2 -1
  152. package/dist/services/apiVariableService.js +16 -47
  153. package/dist/services/autoSaveService.d.ts +7 -0
  154. package/dist/services/autoSaveService.js +6 -4
  155. package/dist/services/categoriesApi.js +3 -6
  156. package/dist/services/chatService.d.ts +9 -4
  157. package/dist/services/chatService.js +23 -28
  158. package/dist/services/draftStorage.d.ts +129 -13
  159. package/dist/services/draftStorage.js +185 -37
  160. package/dist/services/dynamicSchemaService.d.ts +2 -1
  161. package/dist/services/dynamicSchemaService.js +5 -22
  162. package/dist/services/globalSave.d.ts +13 -12
  163. package/dist/services/globalSave.js +29 -51
  164. package/dist/services/historyService.d.ts +9 -3
  165. package/dist/services/historyService.js +9 -3
  166. package/dist/services/interruptService.d.ts +15 -9
  167. package/dist/services/interruptService.js +35 -37
  168. package/dist/services/nodeExecutionService.d.ts +18 -3
  169. package/dist/services/nodeExecutionService.js +71 -45
  170. package/dist/services/playgroundService.d.ts +16 -10
  171. package/dist/services/playgroundService.js +42 -43
  172. package/dist/services/portConfigApi.js +3 -6
  173. package/dist/services/settingsService.d.ts +9 -4
  174. package/dist/services/settingsService.js +23 -12
  175. package/dist/services/variableService.d.ts +2 -1
  176. package/dist/services/variableService.js +2 -2
  177. package/dist/services/workflowStorage.js +6 -6
  178. package/dist/stores/apiContext.d.ts +56 -0
  179. package/dist/stores/apiContext.js +80 -0
  180. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  181. package/dist/stores/categoriesStore.svelte.js +69 -64
  182. package/dist/stores/getInstance.svelte.d.ts +39 -0
  183. package/dist/stores/getInstance.svelte.js +65 -0
  184. package/dist/stores/historyStore.svelte.d.ts +77 -93
  185. package/dist/stores/historyStore.svelte.js +134 -160
  186. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  187. package/dist/stores/instanceContainer.svelte.js +114 -0
  188. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  189. package/dist/stores/interruptStore.svelte.js +253 -226
  190. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  191. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  192. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  193. package/dist/stores/playgroundStore.svelte.js +513 -580
  194. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  195. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  196. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  197. package/dist/stores/settingsStore.svelte.js +47 -12
  198. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  199. package/dist/stores/workflowStore.svelte.js +449 -501
  200. package/dist/stories/EdgeDecorator.svelte +5 -2
  201. package/dist/stories/NodeDecorator.svelte +5 -3
  202. package/dist/svelte-app.d.ts +60 -10
  203. package/dist/svelte-app.js +159 -54
  204. package/dist/types/auth.d.ts +9 -51
  205. package/dist/types/auth.js +4 -54
  206. package/dist/types/events.d.ts +6 -3
  207. package/dist/types/index.d.ts +37 -5
  208. package/dist/types/index.js +0 -1
  209. package/dist/types/navbar.d.ts +7 -0
  210. package/dist/types/playground.d.ts +18 -3
  211. package/dist/types/settings.d.ts +13 -0
  212. package/dist/types/settings.js +1 -0
  213. package/dist/utils/colors.d.ts +47 -21
  214. package/dist/utils/colors.js +69 -68
  215. package/dist/utils/connections.d.ts +9 -15
  216. package/dist/utils/connections.js +13 -32
  217. package/dist/utils/duration.d.ts +13 -0
  218. package/dist/utils/duration.js +45 -0
  219. package/dist/utils/edgeStyling.js +9 -5
  220. package/dist/utils/fetchWithAuth.d.ts +36 -15
  221. package/dist/utils/fetchWithAuth.js +53 -23
  222. package/dist/utils/icons.d.ts +5 -2
  223. package/dist/utils/icons.js +6 -5
  224. package/dist/utils/nodeSwap.d.ts +6 -2
  225. package/dist/utils/nodeSwap.js +62 -126
  226. package/dist/utils/nodeTypes.d.ts +17 -8
  227. package/dist/utils/nodeTypes.js +27 -20
  228. package/dist/utils/performanceUtils.js +7 -0
  229. package/package.json +7 -5
  230. package/dist/messages/deprecation.d.ts +0 -20
  231. package/dist/messages/deprecation.js +0 -33
  232. package/dist/registry/plugin.d.ts +0 -215
  233. package/dist/registry/plugin.js +0 -249
  234. package/dist/services/api.d.ts +0 -129
  235. package/dist/services/api.js +0 -217
@@ -17,8 +17,14 @@
17
17
  formatExecutionDuration,
18
18
  formatLastExecuted
19
19
  } from '../utils/nodeStatus.js';
20
+ import { formatMicroseconds } from '../utils/duration.js';
20
21
  import { m } from '../messages/index.js';
21
22
 
23
+ /** Prefer the precise µs duration; fall back to the legacy ms value. */
24
+ function formatDuration(us: number | null | undefined, ms: number | null | undefined): string {
25
+ return formatMicroseconds(us) ?? formatExecutionDuration(ms ?? undefined);
26
+ }
27
+
22
28
  interface Props {
23
29
  nodeId?: string;
24
30
  executionInfo?: NodeExecutionInfo;
@@ -81,6 +87,13 @@
81
87
  executionInfo.status !== 'idle' || executionInfo.executionCount > 0 || executionInfo.isExecuting
82
88
  );
83
89
 
90
+ // Number of jobs behind this node's status. Loop iterations create
91
+ // multiple jobs per node — including a never-started job swept to
92
+ // "skipped" when the loop exits — so the per-job history (when known)
93
+ // beats the started-runs count: a skipped node with an earlier completed
94
+ // run must still flag that there is more to inspect.
95
+ let runCount = $derived(executionInfo.jobs?.length ?? executionInfo.executionCount);
96
+
84
97
  // Hoist the overlay branch — seven reads in the template.
85
98
  const overlay = $derived(m().status.overlay);
86
99
  </script>
@@ -88,6 +101,7 @@
88
101
  {#if shouldShow}
89
102
  <div
90
103
  class="node-status-overlay"
104
+ data-node-id={props.nodeId}
91
105
  class:node-status-overlay--hovered={isHovered}
92
106
  class:node-status-overlay--top-left={true}
93
107
  class:node-status-overlay--sm={size === 'sm'}
@@ -126,10 +140,11 @@
126
140
  />
127
141
  </div>
128
142
 
129
- <!-- Execution Count Badge -->
130
- {#if executionInfo.executionCount > 0}
143
+ <!-- Run Count Badge: only meaningful when the node has more than one
144
+ job (loop iterations) — a "1" on every executed node is noise -->
145
+ {#if runCount > 1}
131
146
  <div class="node-status-overlay__count">
132
- {executionInfo.executionCount}
147
+ ×{runCount}
133
148
  </div>
134
149
  {/if}
135
150
 
@@ -154,11 +169,14 @@
154
169
  >
155
170
  </div>
156
171
  {/if}
157
- {#if executionInfo.lastExecutionDuration}
172
+ {#if executionInfo.lastExecutionDurationUs || executionInfo.lastExecutionDuration}
158
173
  <div class="node-status-overlay__detail-item">
159
174
  <span class="node-status-overlay__detail-label">{overlay.durationLabel}</span>
160
175
  <span class="node-status-overlay__detail-value"
161
- >{formatExecutionDuration(executionInfo.lastExecutionDuration)}</span
176
+ >{formatDuration(
177
+ executionInfo.lastExecutionDurationUs,
178
+ executionInfo.lastExecutionDuration
179
+ )}</span
162
180
  >
163
181
  </div>
164
182
  {/if}
@@ -168,6 +186,33 @@
168
186
  <span class="node-status-overlay__detail-value">{executionInfo.lastError}</span>
169
187
  </div>
170
188
  {/if}
189
+ <!-- Per-job history: loop iterations create multiple jobs for the
190
+ same node; list them so earlier runs stay inspectable -->
191
+ {#if executionInfo.jobs && executionInfo.jobs.length > 1}
192
+ <div class="node-status-overlay__history">
193
+ <span class="node-status-overlay__detail-label">{overlay.historyLabel}</span>
194
+ {#each executionInfo.jobs as job, i (job.id ?? i)}
195
+ <div class="node-status-overlay__history-item">
196
+ <span
197
+ class="node-status-overlay__history-dot"
198
+ style="background-color: {getStatusColor(job.status)}"
199
+ ></span>
200
+ <span class="node-status-overlay__history-label" title={job.label}
201
+ >{job.label ?? `#${i + 1}`}</span
202
+ >
203
+ <span
204
+ class="node-status-overlay__history-status"
205
+ style="color: {getStatusColor(job.status)}">{getStatusLabel(job.status)}</span
206
+ >
207
+ {#if (job.executionTimeUs != null && job.executionTimeUs > 0) || (job.executionTime != null && job.executionTime > 0)}
208
+ <span class="node-status-overlay__history-duration"
209
+ >{formatDuration(job.executionTimeUs, job.executionTime)}</span
210
+ >
211
+ {/if}
212
+ </div>
213
+ {/each}
214
+ </div>
215
+ {/if}
171
216
  </div>
172
217
  {/if}
173
218
  </div>
@@ -180,7 +225,9 @@
180
225
  align-items: center;
181
226
  gap: 0.75rem;
182
227
  z-index: 1000;
183
- pointer-events: none;
228
+ /* Must receive pointer events at rest — the hover details panel (and
229
+ per-job history) is only reachable if mouseenter can fire here. */
230
+ pointer-events: auto;
184
231
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
185
232
  height: 48px;
186
233
  width: auto;
@@ -304,6 +351,47 @@
304
351
  color: #ef4444;
305
352
  }
306
353
 
354
+ .node-status-overlay__history {
355
+ margin-top: 0.5rem;
356
+ padding-top: 0.5rem;
357
+ border-top: 1px solid #e5e7eb;
358
+ }
359
+
360
+ .node-status-overlay__history-item {
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 0.375rem;
364
+ margin-top: 0.25rem;
365
+ font-size: 0.75rem;
366
+ }
367
+
368
+ .node-status-overlay__history-dot {
369
+ width: 0.5rem;
370
+ height: 0.5rem;
371
+ border-radius: 50%;
372
+ flex-shrink: 0;
373
+ }
374
+
375
+ .node-status-overlay__history-label {
376
+ flex: 1;
377
+ min-width: 0;
378
+ font-weight: 500;
379
+ color: #374151;
380
+ overflow: hidden;
381
+ text-overflow: ellipsis;
382
+ white-space: nowrap;
383
+ }
384
+
385
+ .node-status-overlay__history-status {
386
+ font-weight: 600;
387
+ white-space: nowrap;
388
+ }
389
+
390
+ .node-status-overlay__history-duration {
391
+ color: #6b7280;
392
+ white-space: nowrap;
393
+ }
394
+
307
395
  /* Size variants */
308
396
  .node-status-overlay--sm {
309
397
  gap: 0.125rem;
@@ -11,7 +11,7 @@
11
11
  import { getNodeIcon, getCategoryIcon } from '../utils/icons.js';
12
12
  import { getCategoryColorToken } from '../utils/colors.js';
13
13
  import { m } from '../messages/index.js';
14
- import { getCategoryLabel } from '../stores/categoriesStore.svelte.js';
14
+ import { getInstance } from '../stores/getInstance.svelte.js';
15
15
  import { getVersionUpgrade } from '../utils/nodeSwap.js';
16
16
 
17
17
  interface Props {
@@ -29,6 +29,8 @@
29
29
 
30
30
  const { currentNode, availableNodes, activeFormat, onSelect, onCancel }: Props = $props();
31
31
 
32
+ const fd = getInstance();
33
+
32
34
  let searchInput = $state('');
33
35
 
34
36
  /** Check version upgrade availability */
@@ -134,14 +136,14 @@
134
136
  <!-- Flat style: dot + name rows (shown in minimal skin) -->
135
137
  <div class="swap-picker__flat-section">
136
138
  <div class="swap-picker__flat-category">
137
- {getCategoryLabel(category).toUpperCase()}
139
+ {fd.categories.getLabel(category).toUpperCase()}
138
140
  </div>
139
141
  <div class="swap-picker__flat-list">
140
142
  {#each categoryNodes as nodeType (nodeType.id)}
141
143
  <button class="swap-picker__flat-item" onclick={() => onSelect(nodeType)}>
142
144
  <span
143
145
  class="swap-picker__flat-dot"
144
- style="background: {getCategoryColorToken(nodeType.category)}"
146
+ style="background: {getCategoryColorToken(fd.categories, nodeType.category)}"
145
147
  ></span>
146
148
  <span class="swap-picker__flat-name">{nodeType.name}</span>
147
149
  </button>
@@ -154,12 +156,12 @@
154
156
  <div class="swap-picker__category-header">
155
157
  <span
156
158
  class="swap-picker__category-icon"
157
- style="--_icon-color: {getCategoryColorToken(category)}"
159
+ style="--_icon-color: {getCategoryColorToken(fd.categories, category)}"
158
160
  >
159
- <Icon icon={getCategoryIcon(category)} />
161
+ <Icon icon={getCategoryIcon(fd.categories, category)} />
160
162
  </span>
161
163
  <span class="swap-picker__category-name">
162
- {getCategoryLabel(category)}
164
+ {fd.categories.getLabel(category)}
163
165
  </span>
164
166
  <span class="swap-picker__category-count">
165
167
  {categoryNodes.length}
@@ -170,9 +172,9 @@
170
172
  <button class="swap-picker__item" onclick={() => onSelect(nodeType)}>
171
173
  <span
172
174
  class="swap-picker__item-icon"
173
- style="--_icon-color: {getCategoryColorToken(nodeType.category)}"
175
+ style="--_icon-color: {getCategoryColorToken(fd.categories, nodeType.category)}"
174
176
  >
175
- <Icon icon={getNodeIcon(nodeType.icon, nodeType.category)} />
177
+ <Icon icon={getNodeIcon(fd.categories, nodeType.icon, nodeType.category)} />
176
178
  </span>
177
179
  <div class="swap-picker__item-info">
178
180
  <span class="swap-picker__item-name">{nodeType.name}</span>
@@ -12,6 +12,7 @@
12
12
  import { createEndpointConfig } from '../config/endpoints.js';
13
13
  import type { Workflow } from '../types/index.js';
14
14
  import type { EndpointConfig } from '../config/endpoints.js';
15
+ import type { AuthProvider } from '../types/auth.js';
15
16
  import { logger } from '../utils/logger.js';
16
17
  import { m } from '../messages/index.js';
17
18
 
@@ -21,6 +22,8 @@
21
22
  apiClient?: EnhancedFlowDropApiClient;
22
23
  baseUrl?: string;
23
24
  endpointConfig?: EndpointConfig;
25
+ /** Auth provider used when this component builds its own API client from endpointConfig/baseUrl. */
26
+ authProvider?: AuthProvider;
24
27
  runLabel?: string;
25
28
  /** When true, suppresses breadcrumb and layout events (used inside playground panel) */
26
29
  isEmbedded?: boolean;
@@ -43,6 +46,7 @@
43
46
  apiClient,
44
47
  baseUrl,
45
48
  endpointConfig,
49
+ authProvider,
46
50
  onActionsReady,
47
51
  runLabel,
48
52
  isEmbedded = false,
@@ -54,46 +58,23 @@
54
58
  let _prevRefreshTrigger = refreshTrigger;
55
59
 
56
60
  // Initialize API client if not provided
57
- // svelte-ignore state_referenced_locally — client created once from props
61
+ // client created once from props
62
+ // svelte-ignore state_referenced_locally
58
63
  const client =
59
64
  apiClient ||
60
65
  new EnhancedFlowDropApiClient(
61
- endpointConfig ?? createEndpointConfig(baseUrl || '/api/flowdrop')
66
+ endpointConfig ?? createEndpointConfig(baseUrl || '/api/flowdrop'),
67
+ authProvider
62
68
  );
63
69
 
64
- // Pipeline status and job data
70
+ // Pipeline status — drives breadcrumb label and the 5s poll while running
65
71
  let pipelineStatus = $state<string>('unknown');
66
- interface PipelineNodeStatus {
67
- status: string;
68
- [key: string]: unknown;
69
- }
70
-
71
- let jobStatusData = $state<{
72
- jobs: Record<string, unknown>[];
73
- node_statuses: Record<string, PipelineNodeStatus>;
74
- status_summary: {
75
- total: number;
76
- pending: number;
77
- running: number;
78
- completed: number;
79
- failed: number;
80
- cancelled: number;
81
- };
82
- }>({
83
- jobs: [],
84
- node_statuses: {},
85
- status_summary: {
86
- total: 0,
87
- pending: 0,
88
- running: 0,
89
- completed: 0,
90
- failed: 0,
91
- cancelled: 0
92
- }
93
- });
72
+ let totalJobs = $state<number>(0);
94
73
 
95
- // Node statuses for visual indicators
96
- let nodeStatuses = $state<Record<string, 'pending' | 'running' | 'completed' | 'error'>>({});
74
+ // Child refresh counter increments on every successful fetch so the
75
+ // embedded canvas (WorkflowEditor) re-pulls execution info from the server.
76
+ // PipelineStatus no longer owns a denormalized status channel; it just signals.
77
+ let childRefreshTrigger = $state(0);
97
78
 
98
79
  // Loading and error states
99
80
  let isLoadingJobStatus = $state(false);
@@ -113,39 +94,13 @@
113
94
  const pipelineData = await client.getPipelineData(pipelineId);
114
95
 
115
96
  pipelineStatus = pipelineData.status;
116
- jobStatusData = {
117
- jobs: pipelineData.jobs || [],
118
- node_statuses: pipelineData.node_statuses || {},
119
- status_summary: pipelineData.job_status_summary || {
120
- total: 0,
121
- pending: 0,
122
- running: 0,
123
- completed: 0,
124
- failed: 0,
125
- cancelled: 0
126
- }
127
- };
128
-
129
- // Update node statuses based on job data — only set what the server reported
130
- if (jobStatusData.node_statuses) {
131
- const newNodeStatuses: Record<string, 'pending' | 'running' | 'completed' | 'error'> = {};
132
-
133
- for (const nodeId in jobStatusData.node_statuses) {
134
- const status = jobStatusData.node_statuses[nodeId].status;
135
- if (status === 'failed' || status === 'cancelled') {
136
- newNodeStatuses[nodeId] = 'error';
137
- } else if (status === 'running' || status === 'paused' || status === 'interrupted') {
138
- newNodeStatuses[nodeId] = 'running';
139
- } else if (status === 'completed' || status === 'skipped') {
140
- newNodeStatuses[nodeId] = 'completed';
141
- } else if (status === 'pending' || status === 'idle') {
142
- newNodeStatuses[nodeId] = 'pending';
143
- }
144
- }
145
- nodeStatuses = newNodeStatuses;
146
- }
97
+ totalJobs = pipelineData.job_status_summary?.total ?? 0;
98
+
99
+ // Signal the embedded canvas to re-fetch its own node execution info.
100
+ // It owns the read; we just tell it when to look again.
101
+ childRefreshTrigger += 1;
147
102
 
148
- addLog('info', `Job status updated: ${jobStatusData.status_summary.total} total jobs`);
103
+ addLog('info', `Job status updated: ${totalJobs} total jobs`);
149
104
  } catch (error) {
150
105
  logger.error('Failed to fetch pipeline data:', error);
151
106
  addLog(
@@ -317,10 +272,9 @@
317
272
  width="100%"
318
273
  showNavbar={false}
319
274
  disableSidebar={true}
320
- lockWorkflow={true}
321
- readOnly={true}
322
- {nodeStatuses}
275
+ mode="locked"
323
276
  {pipelineId}
277
+ refreshTrigger={childRefreshTrigger}
324
278
  {endpointConfig}
325
279
  />
326
280
 
@@ -1,12 +1,15 @@
1
1
  import { EnhancedFlowDropApiClient } from '../api/enhanced-client.js';
2
2
  import type { Workflow } from '../types/index.js';
3
3
  import type { EndpointConfig } from '../config/endpoints.js';
4
+ import type { AuthProvider } from '../types/auth.js';
4
5
  interface Props {
5
6
  pipelineId: string;
6
7
  workflow: Workflow;
7
8
  apiClient?: EnhancedFlowDropApiClient;
8
9
  baseUrl?: string;
9
10
  endpointConfig?: EndpointConfig;
11
+ /** Auth provider used when this component builds its own API client from endpointConfig/baseUrl. */
12
+ authProvider?: AuthProvider;
10
13
  runLabel?: string;
11
14
  /** When true, suppresses breadcrumb and layout events (used inside playground panel) */
12
15
  isEmbedded?: boolean;
@@ -10,10 +10,7 @@
10
10
  <script lang="ts">
11
11
  import { useSvelteFlow, type InternalNode } from '@xyflow/svelte';
12
12
  import type { WorkflowNode as WorkflowNodeType } from '../types/index.js';
13
- import {
14
- rebuildAllPortCoordinates,
15
- updateNodePortCoordinates
16
- } from '../stores/portCoordinateStore.svelte.js';
13
+ import { getInstance } from '../stores/getInstance.svelte.js';
17
14
 
18
15
  interface Props {
19
16
  /** Node to update coordinates for (e.g., during drag). Set to null when not dragging. */
@@ -26,6 +23,8 @@
26
23
 
27
24
  let { nodeToUpdate, rebuildTrigger, nodes }: Props = $props();
28
25
 
26
+ const fd = getInstance();
27
+
29
28
  const { getInternalNode } = useSvelteFlow();
30
29
 
31
30
  // Cast the getInternalNode function for our use
@@ -40,7 +39,7 @@
40
39
  const _trigger = rebuildTrigger;
41
40
  if (_trigger > 0) {
42
41
  const timeout = setTimeout(() => {
43
- rebuildAllPortCoordinates(nodes, getInternal);
42
+ fd.portCoordinates.rebuildAll(nodes, getInternal);
44
43
  }, 150);
45
44
  return () => clearTimeout(timeout);
46
45
  }
@@ -52,7 +51,7 @@
52
51
  */
53
52
  $effect(() => {
54
53
  if (nodeToUpdate) {
55
- updateNodePortCoordinates(nodeToUpdate, getInternal);
54
+ fd.portCoordinates.updateNode(nodeToUpdate, getInternal);
56
55
  }
57
56
  });
58
57
  </script>
@@ -87,9 +87,7 @@
87
87
  required: ['email']
88
88
  },
89
89
  values: { email: '', notifications: true },
90
- showActions: true,
91
- saveLabel: 'Save Settings',
92
- cancelLabel: 'Reset'
90
+ showActions: true
93
91
  }}
94
92
  />
95
93
 
@@ -58,10 +58,13 @@
58
58
  import Icon from '@iconify/svelte';
59
59
  import type { ConfigSchema, AuthProvider } from '../types/index.js';
60
60
  import type { UISchemaElement } from '../types/uischema.js';
61
- import { FormField } from './form/index.js';
61
+ import { provideInstance } from '../stores/getInstance.svelte.js';
62
+ // Registry-based light field factory — heavy editors (CodeMirror) are resolved
63
+ // at runtime via fd.fields, so importing SchemaForm never statically pulls them.
64
+ import FormField from './form/FormFieldLight.svelte';
62
65
  import FormUISchemaRenderer from './form/FormUISchemaRenderer.svelte';
63
- import type { FieldSchema } from './form/index.js';
64
- import { m, warnDeprecatedProp } from '../messages/index.js';
66
+ import type { FieldSchema } from './form/types.js';
67
+ import { m } from '../messages/index.js';
65
68
  import { mergeWithDefaults, cascadeClearAutocompleteDependents } from '../utils/formMerge.js';
66
69
 
67
70
  /**
@@ -101,16 +104,6 @@
101
104
  */
102
105
  showActions?: boolean;
103
106
 
104
- /**
105
- * @deprecated since v1.8 — use `messages.form.schema.save`. Removed in v2.0.
106
- */
107
- saveLabel?: string;
108
-
109
- /**
110
- * @deprecated since v1.8 — use `messages.form.schema.cancel`. Removed in v2.0.
111
- */
112
- cancelLabel?: string;
113
-
114
107
  /**
115
108
  * Callback fired when the Save button is clicked.
116
109
  * Receives the final form values.
@@ -160,8 +153,6 @@
160
153
  values = {},
161
154
  onChange,
162
155
  showActions = false,
163
- saveLabel,
164
- cancelLabel,
165
156
  onSave,
166
157
  onCancel,
167
158
  loading = false,
@@ -171,17 +162,20 @@
171
162
  baseUrl = ''
172
163
  }: Props = $props();
173
164
 
174
- // svelte-ignore state_referenced_locally deprecation warns once per mount; later prop rebinds aren't relevant
175
- if (saveLabel !== undefined) {
176
- warnDeprecatedProp('SchemaForm', 'saveLabel', 'messages.form.schema.save');
177
- }
178
- // svelte-ignore state_referenced_locally
179
- if (cancelLabel !== undefined) {
180
- warnDeprecatedProp('SchemaForm', 'cancelLabel', 'messages.form.schema.cancel');
181
- }
182
-
183
- const resolvedSaveLabel = $derived(saveLabel ?? m().form.schema.save);
184
- const resolvedCancelLabel = $derived(cancelLabel ?? m().form.schema.cancel);
165
+ // SchemaForm is a standalone container: its leaf <FormField>s call
166
+ // getInstance() and require an instance in context. When rendered without an
167
+ // <App>/<WorkflowEditor> ancestor (a bare embed), self-provide one so those
168
+ // leaves resolve. provideInstance() carries the established SSR semantics:
169
+ // - context instance present -> reuse it (nested inside a provider)
170
+ // - browser, no context -> the shared page-default instance
171
+ // - server, no context -> a fresh per-render instance (no leakage)
172
+ // We never destroy here: the browser path returns the shared default (must
173
+ // outlive this form) and the server path has no teardown — matching how
174
+ // <App> defers instance lifecycle to whoever created it.
175
+ provideInstance();
176
+
177
+ const resolvedSaveLabel = $derived(m().form.schema.save);
178
+ const resolvedCancelLabel = $derived(m().form.schema.cancel);
185
179
 
186
180
  // Set context for child components (e.g., FormAutocomplete)
187
181
  // Use getter functions to ensure child components always get the current prop value,
@@ -207,7 +201,8 @@
207
201
 
208
202
  // Drop edits when the schema reference changes (different form mounted).
209
203
  // Identity comparison only — value churn in `values` preserves in-flight edits.
210
- // svelte-ignore state_referenced_locally — capturing the initial prop reference is intentional; later changes are picked up by the effect below
204
+ // capturing the initial prop reference is intentional; later changes are picked up by the effect below
205
+ // svelte-ignore state_referenced_locally
211
206
  let prevSchemaRef = schema;
212
207
  $effect.pre(() => {
213
208
  if (schema !== prevSchemaRef) {
@@ -32,14 +32,6 @@ interface Props {
32
32
  * @default false
33
33
  */
34
34
  showActions?: boolean;
35
- /**
36
- * @deprecated since v1.8 — use `messages.form.schema.save`. Removed in v2.0.
37
- */
38
- saveLabel?: string;
39
- /**
40
- * @deprecated since v1.8 — use `messages.form.schema.cancel`. Removed in v2.0.
41
- */
42
- cancelLabel?: string;
43
35
  /**
44
36
  * Callback fired when the Save button is clicked.
45
37
  * Receives the final form values.
@@ -59,6 +59,11 @@
59
59
  class: className = ''
60
60
  }: Props = $props();
61
61
 
62
+ // Unique per component instance so two FlowDrop editors on one page
63
+ // don't render colliding DOM ids (a11y).
64
+ const uid = $props.id();
65
+ const titleId = `${uid}-settings-modal-title`;
66
+
62
67
  /**
63
68
  * Reference to the modal dialog element
64
69
  */
@@ -118,19 +123,19 @@
118
123
  }
119
124
  </script>
120
125
 
121
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions — native <dialog> backdrop click-to-close pattern -->
126
+ <!-- native <dialog> backdrop click-to-close pattern -->
122
127
  <dialog
123
128
  bind:this={dialogRef}
124
129
  class="flowdrop-settings-modal {className}"
125
130
  onclick={handleBackdropClick}
126
131
  onkeydown={handleKeydown}
127
132
  onclose={handleDialogClose}
128
- aria-labelledby="settings-modal-title"
133
+ aria-labelledby={titleId}
129
134
  >
130
135
  <div class="flowdrop-settings-modal__container">
131
136
  <!-- Header -->
132
137
  <div class="flowdrop-settings-modal__header">
133
- <h2 id="settings-modal-title" class="flowdrop-settings-modal__title">
138
+ <h2 id={titleId} class="flowdrop-settings-modal__title">
134
139
  <Icon icon="mdi:cog" class="flowdrop-settings-modal__title-icon" />
135
140
  {m().navigation.settingsTitle}
136
141
  </h2>
@@ -74,10 +74,15 @@
74
74
  class: className = ''
75
75
  }: Props = $props();
76
76
 
77
+ // Unique per component instance so two FlowDrop editors on one page
78
+ // don't render colliding tab/panel DOM ids (a11y).
79
+ const uid = $props.id();
80
+
77
81
  /**
78
82
  * Currently active tab
79
83
  */
80
- // svelte-ignore state_referenced_locally — initial default, user switches tabs
84
+ // initial default, user switches tabs
85
+ // svelte-ignore state_referenced_locally
81
86
  let activeTab = $state<SettingsCategory>(categories[0] ?? 'theme');
82
87
 
83
88
  /**
@@ -216,6 +221,16 @@
216
221
  maximum: 300000,
217
222
  default: 30000
218
223
  },
224
+ storeDraftsInBrowser: {
225
+ type: 'boolean',
226
+ title: 'Store Drafts in Browser',
227
+ description:
228
+ 'Keep unsaved workflow drafts in browser storage so they survive page reloads. ' +
229
+ 'Warning: drafts (including node configuration values) may stay stored on this ' +
230
+ 'device even after the tab or browser is closed, until they are saved or cleared. ' +
231
+ 'Turn off on shared devices.',
232
+ default: true
233
+ },
219
234
  undoHistoryLimit: {
220
235
  type: 'number',
221
236
  title: 'Undo History Limit',
@@ -373,7 +388,8 @@
373
388
  class:flowdrop-settings-panel__tab--active={activeTab === category}
374
389
  role="tab"
375
390
  aria-selected={activeTab === category}
376
- aria-controls="panel-{category}"
391
+ aria-controls="{uid}-panel-{category}"
392
+ id="{uid}-tab-{category}"
377
393
  data-tab={category}
378
394
  tabindex={activeTab === category ? 0 : -1}
379
395
  onclick={() => (activeTab = category)}
@@ -389,11 +405,11 @@
389
405
  <div class="flowdrop-settings-panel__content">
390
406
  {#each categories as category (category)}
391
407
  <div
392
- id="panel-{category}"
408
+ id="{uid}-panel-{category}"
393
409
  class="flowdrop-settings-panel__panel"
394
410
  class:flowdrop-settings-panel__panel--active={activeTab === category}
395
411
  role="tabpanel"
396
- aria-labelledby="tab-{category}"
412
+ aria-labelledby="{uid}-tab-{category}"
397
413
  hidden={activeTab !== category}
398
414
  >
399
415
  {#if activeTab === category}