@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +71 -47
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +18 -25
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +8 -6
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +17 -5
  61. package/dist/components/nodes/GatewayNode.svelte +19 -10
  62. package/dist/components/nodes/IdeaNode.svelte +7 -0
  63. package/dist/components/nodes/SimpleNode.svelte +11 -6
  64. package/dist/components/nodes/SquareNode.svelte +15 -8
  65. package/dist/components/nodes/TerminalNode.svelte +9 -4
  66. package/dist/components/nodes/ToolNode.svelte +7 -1
  67. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  68. package/dist/components/playground/ChatInput.svelte +11 -14
  69. package/dist/components/playground/ChatPanel.svelte +6 -49
  70. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  71. package/dist/components/playground/ControlPanel.svelte +134 -123
  72. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  73. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  74. package/dist/components/playground/InputCollector.svelte +11 -9
  75. package/dist/components/playground/MessageStream.svelte +17 -23
  76. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  77. package/dist/components/playground/PipelinePanel.svelte +11 -5
  78. package/dist/components/playground/PipelineTableView.svelte +186 -44
  79. package/dist/components/playground/Playground.svelte +90 -92
  80. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  81. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  82. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  83. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  84. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  85. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  86. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  87. package/dist/components/playground/SessionManager.svelte +9 -12
  88. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  89. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  90. package/dist/config/endpoints.d.ts +0 -7
  91. package/dist/config/endpoints.js +2 -10
  92. package/dist/core/index.d.ts +4 -4
  93. package/dist/core/index.js +6 -6
  94. package/dist/display/index.d.ts +0 -2
  95. package/dist/display/index.js +0 -6
  96. package/dist/editor/index.d.ts +19 -20
  97. package/dist/editor/index.js +25 -35
  98. package/dist/form/code.d.ts +25 -15
  99. package/dist/form/code.js +44 -41
  100. package/dist/form/fieldRegistry.d.ts +17 -13
  101. package/dist/form/fieldRegistry.js +32 -12
  102. package/dist/form/full.d.ts +17 -13
  103. package/dist/form/full.js +22 -27
  104. package/dist/form/index.d.ts +3 -3
  105. package/dist/form/index.js +3 -3
  106. package/dist/form/markdown.d.ts +13 -8
  107. package/dist/form/markdown.js +22 -23
  108. package/dist/helpers/proximityConnect.d.ts +3 -2
  109. package/dist/helpers/proximityConnect.js +2 -5
  110. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  111. package/dist/helpers/workflowEditorHelper.js +27 -25
  112. package/dist/index.d.ts +28 -24
  113. package/dist/index.js +27 -50
  114. package/dist/messages/defaults.d.ts +2 -5
  115. package/dist/messages/defaults.js +3 -6
  116. package/dist/messages/index.d.ts +0 -1
  117. package/dist/messages/index.js +0 -1
  118. package/dist/mocks/app-forms.d.ts +6 -2
  119. package/dist/mocks/app-forms.js +11 -4
  120. package/dist/openapi/v1/openapi.yaml +3 -3
  121. package/dist/playground/index.d.ts +2 -3
  122. package/dist/playground/index.js +2 -30
  123. package/dist/playground/mount.d.ts +15 -0
  124. package/dist/playground/mount.js +46 -20
  125. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  126. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  127. package/dist/registry/builtinFormats.d.ts +9 -18
  128. package/dist/registry/builtinFormats.js +9 -39
  129. package/dist/registry/builtinNodes.d.ts +0 -25
  130. package/dist/registry/builtinNodes.js +1 -50
  131. package/dist/registry/index.d.ts +3 -4
  132. package/dist/registry/index.js +4 -6
  133. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  134. package/dist/registry/nodeComponentRegistry.js +235 -17
  135. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  136. package/dist/registry/workflowFormatRegistry.js +24 -8
  137. package/dist/{schema → schemas}/index.d.ts +2 -2
  138. package/dist/{schema → schemas}/index.js +2 -2
  139. package/dist/schemas/v1/workflow.schema.json +3 -3
  140. package/dist/services/agentSpecExecutionService.js +0 -1
  141. package/dist/services/apiVariableService.d.ts +2 -1
  142. package/dist/services/apiVariableService.js +5 -22
  143. package/dist/services/autoSaveService.d.ts +7 -0
  144. package/dist/services/autoSaveService.js +6 -4
  145. package/dist/services/chatService.d.ts +8 -4
  146. package/dist/services/chatService.js +15 -15
  147. package/dist/services/draftStorage.d.ts +129 -13
  148. package/dist/services/draftStorage.js +185 -37
  149. package/dist/services/dynamicSchemaService.d.ts +2 -1
  150. package/dist/services/dynamicSchemaService.js +5 -22
  151. package/dist/services/globalSave.d.ts +13 -12
  152. package/dist/services/globalSave.js +29 -51
  153. package/dist/services/historyService.d.ts +9 -3
  154. package/dist/services/historyService.js +9 -3
  155. package/dist/services/interruptService.d.ts +14 -9
  156. package/dist/services/interruptService.js +27 -27
  157. package/dist/services/nodeExecutionService.d.ts +18 -3
  158. package/dist/services/nodeExecutionService.js +71 -45
  159. package/dist/services/playgroundService.d.ts +14 -9
  160. package/dist/services/playgroundService.js +31 -30
  161. package/dist/services/variableService.d.ts +2 -1
  162. package/dist/services/variableService.js +2 -2
  163. package/dist/services/workflowStorage.js +6 -6
  164. package/dist/stores/apiContext.d.ts +45 -0
  165. package/dist/stores/apiContext.js +65 -0
  166. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  167. package/dist/stores/categoriesStore.svelte.js +70 -64
  168. package/dist/stores/getInstance.svelte.d.ts +39 -0
  169. package/dist/stores/getInstance.svelte.js +65 -0
  170. package/dist/stores/historyStore.svelte.d.ts +77 -93
  171. package/dist/stores/historyStore.svelte.js +134 -160
  172. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  173. package/dist/stores/instanceContainer.svelte.js +114 -0
  174. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  175. package/dist/stores/interruptStore.svelte.js +253 -226
  176. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  177. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  178. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  179. package/dist/stores/playgroundStore.svelte.js +515 -580
  180. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  181. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  182. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  183. package/dist/stores/settingsStore.svelte.js +47 -12
  184. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  185. package/dist/stores/workflowStore.svelte.js +449 -501
  186. package/dist/stories/EdgeDecorator.svelte +5 -2
  187. package/dist/stories/NodeDecorator.svelte +5 -3
  188. package/dist/svelte-app.d.ts +60 -10
  189. package/dist/svelte-app.js +157 -53
  190. package/dist/types/events.d.ts +6 -3
  191. package/dist/types/index.d.ts +33 -3
  192. package/dist/types/navbar.d.ts +7 -0
  193. package/dist/types/playground.d.ts +18 -3
  194. package/dist/types/settings.d.ts +13 -0
  195. package/dist/types/settings.js +1 -0
  196. package/dist/utils/colors.d.ts +47 -21
  197. package/dist/utils/colors.js +69 -68
  198. package/dist/utils/connections.d.ts +9 -15
  199. package/dist/utils/connections.js +13 -32
  200. package/dist/utils/duration.d.ts +13 -0
  201. package/dist/utils/duration.js +45 -0
  202. package/dist/utils/icons.d.ts +5 -2
  203. package/dist/utils/icons.js +6 -5
  204. package/dist/utils/nodeSwap.d.ts +6 -2
  205. package/dist/utils/nodeSwap.js +62 -126
  206. package/dist/utils/nodeTypes.d.ts +17 -8
  207. package/dist/utils/nodeTypes.js +26 -19
  208. package/dist/utils/performanceUtils.js +7 -0
  209. package/package.json +6 -5
  210. package/dist/messages/deprecation.d.ts +0 -20
  211. package/dist/messages/deprecation.js +0 -33
  212. package/dist/registry/plugin.d.ts +0 -215
  213. package/dist/registry/plugin.js +0 -249
  214. package/dist/services/api.d.ts +0 -129
  215. package/dist/services/api.js +0 -217
@@ -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>
@@ -54,46 +54,22 @@
54
54
  let _prevRefreshTrigger = refreshTrigger;
55
55
 
56
56
  // Initialize API client if not provided
57
- // svelte-ignore state_referenced_locally — client created once from props
57
+ // client created once from props
58
+ // svelte-ignore state_referenced_locally
58
59
  const client =
59
60
  apiClient ||
60
61
  new EnhancedFlowDropApiClient(
61
62
  endpointConfig ?? createEndpointConfig(baseUrl || '/api/flowdrop')
62
63
  );
63
64
 
64
- // Pipeline status and job data
65
+ // Pipeline status — drives breadcrumb label and the 5s poll while running
65
66
  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
- });
67
+ let totalJobs = $state<number>(0);
94
68
 
95
- // Node statuses for visual indicators
96
- let nodeStatuses = $state<Record<string, 'pending' | 'running' | 'completed' | 'error'>>({});
69
+ // Child refresh counter increments on every successful fetch so the
70
+ // embedded canvas (WorkflowEditor) re-pulls execution info from the server.
71
+ // PipelineStatus no longer owns a denormalized status channel; it just signals.
72
+ let childRefreshTrigger = $state(0);
97
73
 
98
74
  // Loading and error states
99
75
  let isLoadingJobStatus = $state(false);
@@ -113,39 +89,13 @@
113
89
  const pipelineData = await client.getPipelineData(pipelineId);
114
90
 
115
91
  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
- }
92
+ totalJobs = pipelineData.job_status_summary?.total ?? 0;
93
+
94
+ // Signal the embedded canvas to re-fetch its own node execution info.
95
+ // It owns the read; we just tell it when to look again.
96
+ childRefreshTrigger += 1;
147
97
 
148
- addLog('info', `Job status updated: ${jobStatusData.status_summary.total} total jobs`);
98
+ addLog('info', `Job status updated: ${totalJobs} total jobs`);
149
99
  } catch (error) {
150
100
  logger.error('Failed to fetch pipeline data:', error);
151
101
  addLog(
@@ -317,10 +267,9 @@
317
267
  width="100%"
318
268
  showNavbar={false}
319
269
  disableSidebar={true}
320
- lockWorkflow={true}
321
- readOnly={true}
322
- {nodeStatuses}
270
+ mode="locked"
323
271
  {pipelineId}
272
+ refreshTrigger={childRefreshTrigger}
324
273
  {endpointConfig}
325
274
  />
326
275
 
@@ -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,11 @@
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 { provideInstance } from '../stores/getInstance.svelte.js';
61
62
  import { FormField } from './form/index.js';
62
63
  import FormUISchemaRenderer from './form/FormUISchemaRenderer.svelte';
63
64
  import type { FieldSchema } from './form/index.js';
64
- import { m, warnDeprecatedProp } from '../messages/index.js';
65
+ import { m } from '../messages/index.js';
65
66
  import { mergeWithDefaults, cascadeClearAutocompleteDependents } from '../utils/formMerge.js';
66
67
 
67
68
  /**
@@ -101,16 +102,6 @@
101
102
  */
102
103
  showActions?: boolean;
103
104
 
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
105
  /**
115
106
  * Callback fired when the Save button is clicked.
116
107
  * Receives the final form values.
@@ -160,8 +151,6 @@
160
151
  values = {},
161
152
  onChange,
162
153
  showActions = false,
163
- saveLabel,
164
- cancelLabel,
165
154
  onSave,
166
155
  onCancel,
167
156
  loading = false,
@@ -171,17 +160,20 @@
171
160
  baseUrl = ''
172
161
  }: Props = $props();
173
162
 
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);
163
+ // SchemaForm is a standalone container: its leaf <FormField>s call
164
+ // getInstance() and require an instance in context. When rendered without an
165
+ // <App>/<WorkflowEditor> ancestor (a bare embed), self-provide one so those
166
+ // leaves resolve. provideInstance() carries the established SSR semantics:
167
+ // - context instance present -> reuse it (nested inside a provider)
168
+ // - browser, no context -> the shared page-default instance
169
+ // - server, no context -> a fresh per-render instance (no leakage)
170
+ // We never destroy here: the browser path returns the shared default (must
171
+ // outlive this form) and the server path has no teardown — matching how
172
+ // <App> defers instance lifecycle to whoever created it.
173
+ provideInstance();
174
+
175
+ const resolvedSaveLabel = $derived(m().form.schema.save);
176
+ const resolvedCancelLabel = $derived(m().form.schema.cancel);
185
177
 
186
178
  // Set context for child components (e.g., FormAutocomplete)
187
179
  // Use getter functions to ensure child components always get the current prop value,
@@ -207,7 +199,8 @@
207
199
 
208
200
  // Drop edits when the schema reference changes (different form mounted).
209
201
  // 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
202
+ // capturing the initial prop reference is intentional; later changes are picked up by the effect below
203
+ // svelte-ignore state_referenced_locally
211
204
  let prevSchemaRef = schema;
212
205
  $effect.pre(() => {
213
206
  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}