@flowdrop/flowdrop 2.0.0-beta.3 → 2.0.0-beta.4

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 (49) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -5
  3. package/dist/components/App.svelte +15 -146
  4. package/dist/components/Button.stories.svelte +65 -0
  5. package/dist/components/Button.stories.svelte.d.ts +19 -0
  6. package/dist/components/Button.svelte +62 -0
  7. package/dist/components/Button.svelte.d.ts +24 -0
  8. package/dist/components/ConfigForm.svelte +4 -4
  9. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  10. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  11. package/dist/components/EditorStatusBar.svelte +99 -0
  12. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  13. package/dist/components/IconButton.svelte +80 -0
  14. package/dist/components/IconButton.svelte.d.ts +30 -0
  15. package/dist/components/Input.svelte +74 -0
  16. package/dist/components/Input.svelte.d.ts +17 -0
  17. package/dist/components/Navbar.svelte +9 -4
  18. package/dist/components/Navbar.svelte.d.ts +3 -0
  19. package/dist/components/NodeSidebar.svelte +13 -111
  20. package/dist/components/NodeSwapPicker.svelte +10 -26
  21. package/dist/components/Select.svelte +53 -0
  22. package/dist/components/Select.svelte.d.ts +15 -0
  23. package/dist/components/Textarea.svelte +39 -0
  24. package/dist/components/Textarea.svelte.d.ts +12 -0
  25. package/dist/components/ThemeToggle.svelte +15 -89
  26. package/dist/components/form/FormArray.svelte +37 -157
  27. package/dist/components/form/FormCheckboxGroup.svelte +1 -1
  28. package/dist/components/form/FormField.svelte +5 -44
  29. package/dist/components/form/FormFieldLight.svelte +5 -44
  30. package/dist/components/form/FormFieldset.svelte +1 -1
  31. package/dist/components/form/FormNumberField.svelte +4 -32
  32. package/dist/components/form/FormRangeField.svelte +17 -7
  33. package/dist/components/form/FormSelect.svelte +13 -79
  34. package/dist/components/form/FormTextField.svelte +3 -39
  35. package/dist/components/form/FormTextarea.svelte +4 -43
  36. package/dist/components/form/resolveFieldType.d.ts +24 -0
  37. package/dist/components/form/resolveFieldType.js +55 -0
  38. package/dist/components/icons/CloseIcon.svelte +6 -0
  39. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  40. package/dist/components/playground/InputCollector.svelte +11 -46
  41. package/dist/messages/index.d.ts +1 -1
  42. package/dist/messages/index.js +1 -1
  43. package/dist/openapi/v1/openapi.yaml +2 -2
  44. package/dist/skins/drafter.js +41 -28
  45. package/dist/styles/base.css +247 -5
  46. package/dist/styles/tokens.css +6 -0
  47. package/dist/svelte-app.js +68 -107
  48. package/dist/utils/connections.js +14 -50
  49. package/package.json +1 -1
@@ -28,12 +28,16 @@
28
28
  */
29
29
  export const drafterSkin = {
30
30
  tokens: {
31
- /* ----- Shell surfaces: fresh white chrome, not all-cyan ----- */
31
+ /* ----- Shell surfaces: fresh white shell + pale mint chrome ----- */
32
+ /* Neutral greys read as cold admin chrome and fight the mint canvas, so the
33
+ quiet surfaces carry a faint aqua-mint tint instead. muted-foreground is
34
+ a darker teal-slate (≈6.5:1 on white) so secondary/helper text clears
35
+ WCAG AA comfortably — grey body text was the accessibility problem. */
32
36
  background: '#ffffff',
33
37
  foreground: '#10201c',
34
- muted: '#f4f6f7',
35
- 'muted-foreground': '#5f6f6b',
36
- subtle: '#eef3f2',
38
+ muted: '#eaf5f0',
39
+ 'muted-foreground': '#46635b',
40
+ subtle: '#e1f0ea',
37
41
  card: '#ffffff',
38
42
  'card-foreground': '#10201c',
39
43
  header: '#ffffff',
@@ -44,9 +48,9 @@ export const drafterSkin = {
44
48
  'panel-bg': 'rgba(255, 255, 255, 0.94)',
45
49
  'panel-backdrop-filter': 'blur(10px) saturate(1.04)',
46
50
  backdrop: 'rgba(255, 255, 255, 0.92)',
47
- /* ----- Borders: neutral chrome; strong/focus = teal-cyan accent ----- */
48
- border: '#e3e8e7',
49
- 'border-muted': '#eef2f2',
51
+ /* ----- Borders: soft mint hairlines; strong/focus = teal-cyan accent ----- */
52
+ border: '#d3e6df',
53
+ 'border-muted': '#e4f0eb',
50
54
  'border-strong': 'rgba(15, 118, 110, 0.42)',
51
55
  ring: '#06b6d4',
52
56
  /* ----- Canvas: fresh aqua-mint, soft cyan/teal square grid ----- */
@@ -68,7 +72,6 @@ export const drafterSkin = {
68
72
  /* Notes read as cool annotations, not filled success cards */
69
73
  'note-border': '#5b7891',
70
74
  'note-border-hover': '#48617a',
71
- /* Translucent status tints so note/instruction cards stay light + glassy */
72
75
  /* Status hues tuned to the cool palette: danger = rose (teal's warm
73
76
  complement, not a generic red), warning = golden amber. Success/info stay
74
77
  on the emerald/cyan family so they read as part of the theme. */
@@ -80,15 +83,23 @@ export const drafterSkin = {
80
83
  'warning-hover': '#b45309',
81
84
  error: '#e11d48',
82
85
  'error-hover': '#be123c',
83
- 'info-muted': 'rgba(8, 145, 178, 0.1)',
84
- 'success-muted': 'rgba(13, 148, 136, 0.1)',
85
- 'warning-muted': 'rgba(217, 119, 6, 0.12)',
86
- 'error-muted': 'rgba(225, 29, 72, 0.1)',
86
+ /* Status/accent tints are FLAT + OPAQUE (not alpha) so the action buttons
87
+ that use them as fills (form add/move/delete, canvas toggles, secondary)
88
+ read as solid chips, never see-through to the grid. Values are the opaque
89
+ equivalent of the former translucent tints over the white shell. */
90
+ 'info-muted': '#e6f4f7',
91
+ 'success-muted': '#e7f4f3',
92
+ 'warning-muted': '#faefe1',
93
+ 'error-muted': '#fce8ed',
87
94
  /* ----- Crisp drafting geometry + flat chrome (2–6px radius, no soft shadow) ----- */
88
95
  'radius-sm': '2px',
89
96
  'radius-md': '3px',
90
97
  'radius-lg': '4px',
91
98
  'radius-xl': '6px',
99
+ /* Form controls + their group containers (fields, array item boxes,
100
+ fieldsets) get the sharpest 2px corner so the config panel reads as
101
+ crisp drafting geometry, not rounded admin chrome. */
102
+ 'control-radius': '2px',
92
103
  'scrollbar-radius': '0',
93
104
  'shadow-sm': 'none',
94
105
  'shadow-md': 'none',
@@ -98,23 +109,23 @@ export const drafterSkin = {
98
109
  'minimap-mask-stroke': 'rgba(15, 118, 110, 0.35)',
99
110
  'minimap-node-bg': 'rgba(15, 118, 110, 0.3)',
100
111
  'minimap-node-stroke': 'rgba(15, 118, 110, 0.45)',
101
- 'controls-button-bg': 'rgba(255, 255, 255, 0.86)',
102
- 'controls-button-bg-hover': 'rgba(204, 251, 241, 0.9)',
112
+ 'controls-button-bg': '#ffffff',
113
+ 'controls-button-bg-hover': '#ccfbf1',
103
114
  'controls-button-color': '#0f766e',
104
115
  'controls-button-color-hover': '#0c5f59',
105
116
  'controls-button-border': 'rgba(15, 118, 110, 0.25)',
106
117
  /* ----- Secondary = mint, accent = cyan, primary = the one allowed green ----- */
107
- secondary: 'rgba(206, 230, 223, 0.55)',
108
- 'secondary-hover': 'rgba(186, 219, 210, 0.7)',
118
+ secondary: '#e4f1ed',
119
+ 'secondary-hover': '#cfe6e0',
109
120
  'secondary-foreground': '#0f3d36',
110
121
  accent: '#06b6d4',
111
122
  'accent-hover': '#0891b2',
112
123
  'accent-foreground': '#ffffff',
113
- 'accent-muted': 'rgba(6, 182, 212, 0.12)',
124
+ 'accent-muted': '#e1f6fa',
114
125
  primary: '#10b981',
115
126
  'primary-hover': '#059669',
116
127
  'primary-foreground': '#ffffff',
117
- 'primary-muted': 'rgba(16, 185, 129, 0.14)'
128
+ 'primary-muted': '#def5ed'
118
129
  },
119
130
  darkTokens: {
120
131
  /* ----- Shell surfaces: deep teal-slate glass ----- */
@@ -156,30 +167,32 @@ export const drafterSkin = {
156
167
  'warning-hover': '#fcd34d',
157
168
  error: '#fb7185',
158
169
  'error-hover': '#fda4af',
159
- 'info-muted': 'rgba(34, 211, 238, 0.12)',
160
- 'success-muted': 'rgba(45, 212, 191, 0.12)',
161
- 'warning-muted': 'rgba(251, 191, 36, 0.14)',
162
- 'error-muted': 'rgba(251, 113, 133, 0.12)',
170
+ /* Flat + opaque tints (opaque equivalent of the former alpha tints over the
171
+ deep teal-slate shell) so button fills never go see-through. */
172
+ 'info-muted': '#10393a',
173
+ 'success-muted': '#123934',
174
+ 'warning-muted': '#2f3a21',
175
+ 'error-muted': '#2a2d2d',
163
176
  'minimap-bg': 'rgba(11, 31, 28, 0.7)',
164
177
  'minimap-mask-bg': 'rgba(45, 212, 191, 0.06)',
165
178
  'minimap-mask-stroke': 'rgba(45, 212, 191, 0.3)',
166
179
  'minimap-node-bg': 'rgba(45, 212, 191, 0.28)',
167
180
  'minimap-node-stroke': 'rgba(45, 212, 191, 0.45)',
168
- 'controls-button-bg': 'rgba(17, 38, 35, 0.8)',
169
- 'controls-button-bg-hover': 'rgba(13, 47, 44, 0.9)',
181
+ 'controls-button-bg': '#102522',
182
+ 'controls-button-bg-hover': '#0d2d2a',
170
183
  'controls-button-color': '#2dd4bf',
171
184
  'controls-button-color-hover': '#5eead4',
172
185
  'controls-button-border': 'rgba(45, 212, 191, 0.3)',
173
- secondary: 'rgba(13, 47, 44, 0.8)',
174
- 'secondary-hover': 'rgba(20, 60, 54, 0.9)',
186
+ secondary: '#0d2d2a',
187
+ 'secondary-hover': '#133a34',
175
188
  'secondary-foreground': '#d8f0ea',
176
189
  accent: '#22d3ee',
177
190
  'accent-hover': '#67e8f9',
178
191
  'accent-foreground': '#03291c',
179
- 'accent-muted': 'rgba(34, 211, 238, 0.14)',
192
+ 'accent-muted': '#113d3e',
180
193
  primary: '#34d399',
181
194
  'primary-hover': '#6ee7b7',
182
195
  'primary-foreground': '#03291c',
183
- 'primary-muted': 'rgba(52, 211, 153, 0.16)'
196
+ 'primary-muted': '#144034'
184
197
  }
185
198
  };
@@ -149,6 +149,7 @@ p {
149
149
  display: inline-flex;
150
150
  align-items: center;
151
151
  justify-content: center;
152
+ gap: var(--fd-space-xs);
152
153
  padding: var(--fd-space-xs) var(--fd-space-xl);
153
154
  border: 1px solid transparent;
154
155
  border-radius: var(--fd-radius-md);
@@ -224,37 +225,278 @@ p {
224
225
  min-height: var(--fd-space-6xl);
225
226
  }
226
227
 
227
- /* Input styles */
228
+ /* ───────────────────────────────────────────────────────────────────────────
229
+ Icon buttons — square, icon-only. The typed primitive IconButton.svelte
230
+ routes here (the same way Button.svelte routes through .flowdrop-btn). It
231
+ composes onto .flowdrop-btn, then overrides geometry to a fixed square and
232
+ layers a tint variant. Corners follow --fd-control-radius so the Drafter
233
+ theme gets crisp icon buttons alongside its form controls.
234
+
235
+ Sizes: md (default) is 32px with a 16px glyph — the dominant size across the
236
+ library (form-array actions, modal closes, toolbars). sm maps to the
237
+ canonical --fd-size-icon-btn token (28px); lg is 36px (canvas-scale). */
238
+ .flowdrop-btn--icon {
239
+ width: 2rem;
240
+ height: 2rem;
241
+ min-height: 0;
242
+ padding: 0;
243
+ border-radius: var(--fd-control-radius);
244
+ color: var(--fd-muted-foreground);
245
+ }
246
+
247
+ .flowdrop-btn--icon svg {
248
+ width: 1rem;
249
+ height: 1rem;
250
+ }
251
+
252
+ .flowdrop-btn--icon.flowdrop-btn--sm {
253
+ width: var(--fd-size-icon-btn);
254
+ height: var(--fd-size-icon-btn);
255
+ padding: 0;
256
+ }
257
+
258
+ .flowdrop-btn--icon.flowdrop-btn--sm svg {
259
+ width: 0.875rem;
260
+ height: 0.875rem;
261
+ }
262
+
263
+ .flowdrop-btn--icon.flowdrop-btn--lg {
264
+ width: 2.25rem;
265
+ height: 2.25rem;
266
+ padding: 0;
267
+ }
268
+
269
+ .flowdrop-btn--icon.flowdrop-btn--lg svg {
270
+ width: 1.125rem;
271
+ height: 1.125rem;
272
+ }
273
+
274
+ /* Ghost (default for icon buttons) — transparent until hover. */
275
+ .flowdrop-btn--icon.flowdrop-btn--ghost:hover:not(:disabled) {
276
+ background-color: var(--fd-muted);
277
+ color: var(--fd-foreground);
278
+ border-color: transparent;
279
+ }
280
+
281
+ /* Default — flat --fd-background surface with a resting border. */
282
+ .flowdrop-btn--icon-default {
283
+ background-color: var(--fd-background);
284
+ border-color: var(--fd-border);
285
+ }
286
+
287
+ .flowdrop-btn--icon-default:hover:not(:disabled) {
288
+ background-color: var(--fd-muted);
289
+ color: var(--fd-foreground);
290
+ }
291
+
292
+ /* Tinted semantic variants — muted fill + semantic border/text, solid on press.
293
+ Unlike the solid text-button --primary, the icon flavour stays a quiet tint
294
+ so a row of action buttons reads as a group, not a wall of colour. */
295
+ .flowdrop-btn--icon-primary {
296
+ background-color: var(--fd-primary-muted);
297
+ border-color: var(--fd-primary);
298
+ color: var(--fd-primary-hover);
299
+ }
300
+
301
+ .flowdrop-btn--icon-primary:hover:not(:disabled) {
302
+ border-color: var(--fd-primary-hover);
303
+ color: var(--fd-primary-hover);
304
+ }
305
+
306
+ .flowdrop-btn--icon-primary:active:not(:disabled) {
307
+ background-color: var(--fd-primary);
308
+ }
309
+
310
+ .flowdrop-btn--icon-danger {
311
+ background-color: var(--fd-error-muted);
312
+ border-color: var(--fd-error);
313
+ color: var(--fd-error);
314
+ }
315
+
316
+ .flowdrop-btn--icon-danger:hover:not(:disabled) {
317
+ border-color: var(--fd-error-hover);
318
+ color: var(--fd-error-hover);
319
+ }
320
+
321
+ .flowdrop-btn--icon-danger:active:not(:disabled) {
322
+ background-color: var(--fd-error);
323
+ }
324
+
325
+ .flowdrop-btn--icon-success {
326
+ background-color: var(--fd-success-muted);
327
+ border-color: var(--fd-success);
328
+ color: var(--fd-success);
329
+ }
330
+
331
+ .flowdrop-btn--icon-success:hover:not(:disabled) {
332
+ border-color: var(--fd-success-hover);
333
+ color: var(--fd-success-hover);
334
+ }
335
+
336
+ .flowdrop-btn--icon-success:active:not(:disabled) {
337
+ background-color: var(--fd-success);
338
+ }
339
+
340
+ /* Active / toggled state (e.g. a pressed canvas control) — primary tint. */
341
+ .flowdrop-btn--icon.is-active {
342
+ color: var(--fd-primary);
343
+ background-color: var(--fd-primary-muted);
344
+ border-color: var(--fd-primary);
345
+ }
346
+
347
+ .flowdrop-btn--icon:disabled {
348
+ opacity: 0.35;
349
+ cursor: not-allowed;
350
+ }
351
+
352
+ /* ───────────────────────────────────────────────────────────────────────────
353
+ Form controls — THE single source of truth for input/select/textarea look.
354
+
355
+ The typed primitives Input.svelte / Select.svelte / Textarea.svelte route
356
+ here (the same way Button.svelte routes through .flowdrop-btn) so every field
357
+ across the library — config form, sidebar search, playground, prompts —
358
+ renders identically. Resting fields are ALWAYS the flat --fd-background
359
+ surface; `:disabled` is the ONLY muted state. Components must not re-declare
360
+ control backgrounds/borders locally. */
228
361
  .flowdrop-input {
229
362
  display: block;
230
363
  width: 100%;
231
- padding: var(--fd-space-xs) var(--fd-space-md);
364
+ padding: 0.625rem 0.875rem;
232
365
  border: 1px solid var(--fd-border);
233
- border-radius: var(--fd-radius-md);
366
+ border-radius: var(--fd-control-radius);
234
367
  font-size: var(--fd-text-sm);
368
+ font-family: inherit;
235
369
  line-height: 1.25rem;
236
370
  color: var(--fd-foreground);
237
371
  background-color: var(--fd-background);
372
+ box-shadow: var(--fd-shadow-sm);
238
373
  transition:
239
374
  border-color var(--fd-transition-normal),
240
375
  box-shadow var(--fd-transition-normal);
241
376
  }
242
377
 
378
+ .flowdrop-input::placeholder {
379
+ color: var(--fd-muted-foreground);
380
+ }
381
+
243
382
  /* Active-field hint only; the focus ring itself is centralized (top of file). */
383
+ .flowdrop-input:hover:not(:disabled):not(:focus) {
384
+ border-color: var(--fd-border-strong);
385
+ }
386
+
244
387
  .flowdrop-input:focus {
245
388
  border-color: var(--fd-ring);
246
389
  }
247
390
 
391
+ .flowdrop-input:disabled {
392
+ background-color: var(--fd-muted);
393
+ border-color: var(--fd-border-muted);
394
+ color: var(--fd-muted-foreground);
395
+ cursor: not-allowed;
396
+ }
397
+
398
+ /* Invalid keeps its error border even on hover. */
399
+ .flowdrop-input--invalid,
400
+ .flowdrop-input--invalid:hover:not(:disabled) {
401
+ border-color: var(--fd-error);
402
+ }
403
+
404
+ /* Sizes */
248
405
  .flowdrop-input--sm {
249
- padding: var(--fd-space-2xs) var(--fd-space-xs);
406
+ padding: 0.375rem 0.625rem;
250
407
  font-size: var(--fd-text-xs);
251
408
  }
252
409
 
253
410
  .flowdrop-input--lg {
254
- padding: var(--fd-space-md) var(--fd-space-xl);
411
+ padding: 0.75rem 1rem;
255
412
  font-size: var(--fd-text-base);
256
413
  }
257
414
 
415
+ /* Tabular figures for numeric entry (aligned digits). */
416
+ .flowdrop-input--numeric {
417
+ font-variant-numeric: tabular-nums;
418
+ }
419
+
420
+ /* Multiline */
421
+ .flowdrop-input--textarea {
422
+ min-height: 5rem;
423
+ line-height: 1.5;
424
+ resize: vertical;
425
+ }
426
+
427
+ /* Select: room for the chevron overlay; native arrow removed. */
428
+ .flowdrop-input--select {
429
+ padding-right: 2.5rem;
430
+ cursor: pointer;
431
+ appearance: none;
432
+ -webkit-appearance: none;
433
+ }
434
+
435
+ /* Select shell carries the chevron; the <select> keeps the field look. */
436
+ .flowdrop-select-wrap {
437
+ position: relative;
438
+ width: 100%;
439
+ }
440
+
441
+ .flowdrop-select-wrap__icon {
442
+ position: absolute;
443
+ right: 0.75rem;
444
+ top: 50%;
445
+ transform: translateY(-50%);
446
+ display: flex;
447
+ pointer-events: none;
448
+ color: var(--fd-muted-foreground);
449
+ transition: color var(--fd-transition-fast);
450
+ }
451
+
452
+ .flowdrop-select-wrap:focus-within .flowdrop-select-wrap__icon {
453
+ color: var(--fd-primary);
454
+ }
455
+
456
+ .flowdrop-select-wrap__icon svg {
457
+ width: 1rem;
458
+ height: 1rem;
459
+ }
460
+
461
+ /* Input with a leading/trailing affordance (e.g. a search magnifier). */
462
+ .flowdrop-input-wrap {
463
+ position: relative;
464
+ width: 100%;
465
+ display: flex;
466
+ align-items: center;
467
+ }
468
+
469
+ .flowdrop-input-wrap__icon {
470
+ position: absolute;
471
+ top: 50%;
472
+ transform: translateY(-50%);
473
+ display: flex;
474
+ align-items: center;
475
+ pointer-events: none;
476
+ color: var(--fd-muted-foreground);
477
+ }
478
+
479
+ .flowdrop-input-wrap__icon--leading {
480
+ left: 0.75rem;
481
+ }
482
+
483
+ .flowdrop-input-wrap__icon--trailing {
484
+ right: 0.75rem;
485
+ }
486
+
487
+ .flowdrop-input-wrap__icon svg {
488
+ width: 1rem;
489
+ height: 1rem;
490
+ }
491
+
492
+ .flowdrop-input--has-leading {
493
+ padding-left: 2.25rem;
494
+ }
495
+
496
+ .flowdrop-input--has-trailing {
497
+ padding-right: 2.25rem;
498
+ }
499
+
258
500
  /* Card styles */
259
501
  .flowdrop-card {
260
502
  background-color: var(--fd-card);
@@ -253,6 +253,12 @@
253
253
  --fd-radius-2xl: 1rem; /* @public 16px */
254
254
  --fd-radius-full: 9999px; /* @public */
255
255
 
256
+ /* Corner radius shared by form controls (input/select/textarea) and their
257
+ group containers (array item boxes, fieldsets, checkbox groups, config
258
+ sections). Defaults to --fd-radius-lg; themes can tighten it independently
259
+ of cards/panels — e.g. Drafter sets 2px for crisp drafting corners. */
260
+ --fd-control-radius: var(--fd-radius-lg); /* @public */
261
+
256
262
  /* ----- SHADOWS (Refined layered shadows for modern depth) ----- */
257
263
  --fd-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.04), 0 1px 3px rgb(0 0 0 / 0.06); /* @public */
258
264
  --fd-shadow-md: 0 4px 8px rgb(0 0 0 / 0.06), 0 2px 4px rgb(0 0 0 / 0.04); /* @public */
@@ -55,6 +55,58 @@ function releaseInstance(fd, isDefault) {
55
55
  fd.destroy();
56
56
  }
57
57
  }
58
+ /**
59
+ * Resolve endpoint config, port config and categories from mount options and
60
+ * apply them to the given instance (API context, port-compatibility checker,
61
+ * categories). Shared by `mountFlowDropApp` and `mountWorkflowEditor`.
62
+ *
63
+ * @returns the resolved {@link EndpointConfig} (merged with defaults), which the
64
+ * callers forward to their mounted component.
65
+ */
66
+ async function configureInstance(fd, options) {
67
+ const { endpointConfig, portConfig, categories, authProvider } = options;
68
+ // Create endpoint configuration, merging with defaults so all required
69
+ // endpoints are present.
70
+ const { defaultEndpointConfig } = await import('./config/endpoints.js');
71
+ const config = endpointConfig
72
+ ? {
73
+ ...defaultEndpointConfig,
74
+ ...endpointConfig,
75
+ endpoints: {
76
+ ...defaultEndpointConfig.endpoints,
77
+ ...endpointConfig.endpoints
78
+ }
79
+ }
80
+ : defaultEndpointConfig;
81
+ // Initialize port configuration (fetch from API when not supplied).
82
+ let finalPortConfig = portConfig;
83
+ if (!finalPortConfig) {
84
+ try {
85
+ finalPortConfig = await fetchPortConfig(config, authProvider);
86
+ }
87
+ catch (error) {
88
+ logger.warn('Failed to fetch port config from API, using default:', error);
89
+ finalPortConfig = DEFAULT_PORT_CONFIG;
90
+ }
91
+ }
92
+ // Configure this instance's API context and port compatibility checker.
93
+ fd.api.configure(config, authProvider);
94
+ fd.portCompatibility.reinitialize(finalPortConfig);
95
+ // Initialize this instance's categories (fetch from API when not supplied).
96
+ if (categories) {
97
+ fd.categories.initialize(categories);
98
+ }
99
+ else {
100
+ try {
101
+ const fetchedCategories = await fetchCategories(config, authProvider);
102
+ fd.categories.initialize(fetchedCategories);
103
+ }
104
+ catch (error) {
105
+ logger.warn('Failed to fetch categories from API, using defaults:', error);
106
+ }
107
+ }
108
+ return config;
109
+ }
58
110
  /**
59
111
  * Mount the full FlowDrop App with navbar, sidebars, and workflow editor
60
112
  *
@@ -103,61 +155,14 @@ export async function mountFlowDropApp(container, options = {}) {
103
155
  await initializeSettings({
104
156
  defaults: initialSettings
105
157
  });
106
- // Create endpoint configuration
107
- let config;
108
- if (endpointConfig) {
109
- // Merge with default configuration to ensure all required endpoints are present
110
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
111
- config = {
112
- ...defaultEndpointConfig,
113
- ...endpointConfig,
114
- endpoints: {
115
- ...defaultEndpointConfig.endpoints,
116
- ...endpointConfig.endpoints
117
- }
118
- };
119
- }
120
- else {
121
- // Use default configuration if none provided
122
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
123
- config = defaultEndpointConfig;
124
- }
125
- // Initialize port configuration
126
- let finalPortConfig = portConfig;
127
- if (!finalPortConfig && config) {
128
- // Try to fetch port configuration from API
129
- try {
130
- finalPortConfig = await fetchPortConfig(config, authProvider);
131
- }
132
- catch (error) {
133
- logger.warn('Failed to fetch port config from API, using default:', error);
134
- finalPortConfig = DEFAULT_PORT_CONFIG;
135
- }
136
- }
137
- else if (!finalPortConfig) {
138
- finalPortConfig = DEFAULT_PORT_CONFIG;
139
- }
140
- // Configure this instance's API context (endpoints + auth provider) so
141
- // <App> and services resolve it via getInstance().api.
142
- if (config) {
143
- fd.api.configure(config, authProvider);
144
- }
145
- // Re-initialize this instance's port compatibility checker with the resolved
146
- // config (it was seeded with DEFAULT_PORT_CONFIG at construction).
147
- fd.portCompatibility.reinitialize(finalPortConfig);
148
- // Initialize this instance's categories
149
- if (categories) {
150
- fd.categories.initialize(categories);
151
- }
152
- else if (config) {
153
- try {
154
- const fetchedCategories = await fetchCategories(config, authProvider);
155
- fd.categories.initialize(fetchedCategories);
156
- }
157
- catch (error) {
158
- logger.warn('Failed to fetch categories from API, using defaults:', error);
159
- }
160
- }
158
+ // Resolve and apply endpoint config, port config and categories to this
159
+ // instance (see configureInstance).
160
+ const config = await configureInstance(fd, {
161
+ endpointConfig,
162
+ portConfig,
163
+ categories,
164
+ authProvider
165
+ });
161
166
  // Set up event handler callbacks in this instance's store
162
167
  if (eventHandlers?.onDirtyStateChange) {
163
168
  fd.workflow.setOnDirtyStateChange(eventHandlers.onDirtyStateChange);
@@ -338,58 +343,14 @@ export async function mountWorkflowEditor(container, options = {}) {
338
343
  const { workflow, endpointConfig, portConfig, categories, authProvider, instanceId, builtinEditors } = options;
339
344
  // Per-instance state container (see mountFlowDropApp)
340
345
  const { fd, isDefault } = acquireInstance(instanceId);
341
- // Create endpoint configuration
342
- let config;
343
- if (endpointConfig) {
344
- // Merge with default configuration to ensure all required endpoints are present
345
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
346
- config = {
347
- ...defaultEndpointConfig,
348
- ...endpointConfig,
349
- endpoints: {
350
- ...defaultEndpointConfig.endpoints,
351
- ...endpointConfig.endpoints
352
- }
353
- };
354
- }
355
- else {
356
- // Use default configuration if none provided
357
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
358
- config = defaultEndpointConfig;
359
- }
360
- // Initialize port configuration
361
- let finalPortConfig = portConfig;
362
- if (!finalPortConfig && config) {
363
- // Try to fetch port configuration from API
364
- try {
365
- finalPortConfig = await fetchPortConfig(config, authProvider);
366
- }
367
- catch (error) {
368
- logger.warn('Failed to fetch port config from API, using default:', error);
369
- finalPortConfig = DEFAULT_PORT_CONFIG;
370
- }
371
- }
372
- else if (!finalPortConfig) {
373
- finalPortConfig = DEFAULT_PORT_CONFIG;
374
- }
375
- // Configure this instance's API context and port compatibility checker.
376
- if (config) {
377
- fd.api.configure(config, authProvider);
378
- }
379
- fd.portCompatibility.reinitialize(finalPortConfig);
380
- // Initialize this instance's categories
381
- if (categories) {
382
- fd.categories.initialize(categories);
383
- }
384
- else if (config) {
385
- try {
386
- const fetchedCategories = await fetchCategories(config, authProvider);
387
- fd.categories.initialize(fetchedCategories);
388
- }
389
- catch (error) {
390
- logger.warn('Failed to fetch categories from API, using defaults:', error);
391
- }
392
- }
346
+ // Resolve and apply endpoint config, port config and categories to this
347
+ // instance (see configureInstance).
348
+ const config = await configureInstance(fd, {
349
+ endpointConfig,
350
+ portConfig,
351
+ categories,
352
+ authProvider
353
+ });
393
354
  // Seed the instance's workflow before mounting so the editor renders it
394
355
  // immediately. (1.x accepted this option but silently ignored it.)
395
356
  if (workflow) {