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

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 (149) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/MIGRATION-2.0.md +173 -3
  3. package/dist/api/enhanced-client.js +6 -11
  4. package/dist/components/App.svelte +22 -45
  5. package/dist/components/App.svelte.d.ts +2 -7
  6. package/dist/components/CanvasIconButton.svelte +76 -0
  7. package/dist/components/CanvasIconButton.svelte.d.ts +18 -0
  8. package/dist/components/ConfigForm.svelte +6 -21
  9. package/dist/components/ConfigPanel.svelte +4 -3
  10. package/dist/components/LogoWordmark.svelte +113 -0
  11. package/dist/components/LogoWordmark.svelte.d.ts +26 -0
  12. package/dist/components/Navbar.svelte +8 -59
  13. package/dist/components/NodeSidebar.svelte +4 -11
  14. package/dist/components/NodeSwapPicker.svelte +0 -2
  15. package/dist/components/PipelineStatus.svelte +6 -1
  16. package/dist/components/PipelineStatus.svelte.d.ts +3 -0
  17. package/dist/components/PortMappingRow.svelte +0 -2
  18. package/dist/components/SchemaForm.svelte +4 -14
  19. package/dist/components/SettingsModal.svelte +0 -5
  20. package/dist/components/SettingsPanel.svelte +2 -6
  21. package/dist/components/ThemeToggle.svelte +0 -5
  22. package/dist/components/UniversalNode.svelte +32 -1
  23. package/dist/components/WorkflowEditor.svelte +66 -52
  24. package/dist/components/WorkflowEditor.svelte.d.ts +21 -0
  25. package/dist/components/chat/AIChatPanel.svelte +7 -2
  26. package/dist/components/console/ConsoleAutocomplete.svelte +1 -1
  27. package/dist/components/console/ConsoleOutput.svelte +2 -2
  28. package/dist/components/form/FormArray.svelte +0 -16
  29. package/dist/components/form/FormAutocomplete.svelte +18 -15
  30. package/dist/components/form/FormCheckboxGroup.svelte +0 -4
  31. package/dist/components/form/FormCodeEditor.svelte +9 -7
  32. package/dist/components/form/FormFieldLight.svelte +33 -4
  33. package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
  34. package/dist/components/form/FormMarkdownEditor.svelte +8 -5
  35. package/dist/components/form/FormNumberField.svelte +0 -4
  36. package/dist/components/form/FormRangeField.svelte +1 -20
  37. package/dist/components/form/FormSelect.svelte +10 -6
  38. package/dist/components/form/FormTemplateEditor.svelte +6 -4
  39. package/dist/components/form/FormTextField.svelte +10 -6
  40. package/dist/components/form/FormTextarea.svelte +10 -6
  41. package/dist/components/form/FormToggle.svelte +0 -4
  42. package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
  43. package/dist/components/icons/CommandLineIcon.svelte +15 -0
  44. package/dist/components/icons/CommandLineIcon.svelte.d.ts +26 -0
  45. package/dist/components/icons/MenuIcon.svelte +4 -0
  46. package/dist/components/icons/MenuIcon.svelte.d.ts +26 -0
  47. package/dist/components/icons/MenuOpenIcon.svelte +6 -0
  48. package/dist/components/icons/MenuOpenIcon.svelte.d.ts +26 -0
  49. package/dist/components/interrupt/ChoicePrompt.svelte +0 -10
  50. package/dist/components/interrupt/ConfirmationPrompt.svelte +0 -5
  51. package/dist/components/interrupt/InterruptBubble.svelte +11 -12
  52. package/dist/components/interrupt/ReviewPrompt.svelte +0 -20
  53. package/dist/components/interrupt/TextInputPrompt.svelte +0 -6
  54. package/dist/components/layouts/MainLayout.svelte +4 -5
  55. package/dist/components/nodes/AtomNode.svelte +46 -34
  56. package/dist/components/nodes/GatewayNode.svelte +91 -99
  57. package/dist/components/nodes/IdeaNode.svelte +62 -90
  58. package/dist/components/nodes/NodeConfigButton.svelte +86 -0
  59. package/dist/components/nodes/NodeConfigButton.svelte.d.ts +15 -0
  60. package/dist/components/nodes/NotesNode.svelte +70 -81
  61. package/dist/components/nodes/SimpleNode.svelte +28 -78
  62. package/dist/components/nodes/SquareNode.svelte +79 -109
  63. package/dist/components/nodes/TerminalNode.svelte +28 -86
  64. package/dist/components/nodes/ToolNode.svelte +82 -95
  65. package/dist/components/nodes/WorkflowNode.svelte +91 -100
  66. package/dist/components/playground/ChatInput.svelte +0 -1
  67. package/dist/components/playground/InputCollector.svelte +0 -2
  68. package/dist/components/playground/PipelineKanbanView.svelte +4 -2
  69. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
  70. package/dist/components/playground/PipelinePanel.svelte +20 -3
  71. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
  72. package/dist/components/playground/PipelineTableView.svelte +4 -2
  73. package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
  74. package/dist/components/playground/Playground.svelte +76 -25
  75. package/dist/components/playground/Playground.svelte.d.ts +3 -0
  76. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  77. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  78. package/dist/components/playground/PlaygroundModal.svelte +5 -0
  79. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  80. package/dist/components/playground/PlaygroundStudio.svelte +7 -6
  81. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  82. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +2 -1
  83. package/dist/components/playground/pipelineViewUtils.svelte.js +2 -2
  84. package/dist/config/endpoints.d.ts +23 -0
  85. package/dist/config/endpoints.js +28 -0
  86. package/dist/core/index.d.ts +1 -2
  87. package/dist/core/index.js +2 -6
  88. package/dist/display/index.d.ts +6 -1
  89. package/dist/display/index.js +9 -1
  90. package/dist/editor/index.d.ts +1 -1
  91. package/dist/editor/index.js +1 -1
  92. package/dist/form/full.d.ts +2 -1
  93. package/dist/form/full.js +4 -1
  94. package/dist/form/index.d.ts +0 -1
  95. package/dist/form/index.js +3 -2
  96. package/dist/helpers/workflowEditorHelper.d.ts +4 -2
  97. package/dist/helpers/workflowEditorHelper.js +4 -3
  98. package/dist/playground/index.d.ts +2 -2
  99. package/dist/playground/index.js +2 -2
  100. package/dist/playground/mount.d.ts +19 -5
  101. package/dist/playground/mount.js +16 -8
  102. package/dist/registry/builtinNodeTypes.d.ts +53 -0
  103. package/dist/registry/builtinNodeTypes.js +67 -0
  104. package/dist/registry/builtinNodes.d.ts +2 -39
  105. package/dist/registry/builtinNodes.js +6 -53
  106. package/dist/services/agentSpecExecutionService.d.ts +0 -2
  107. package/dist/services/agentSpecExecutionService.js +0 -2
  108. package/dist/services/apiVariableService.js +12 -26
  109. package/dist/services/categoriesApi.js +3 -6
  110. package/dist/services/chatService.d.ts +4 -3
  111. package/dist/services/chatService.js +13 -18
  112. package/dist/services/interruptService.d.ts +7 -6
  113. package/dist/services/interruptService.js +19 -21
  114. package/dist/services/playgroundService.d.ts +9 -8
  115. package/dist/services/playgroundService.js +23 -25
  116. package/dist/services/portConfigApi.js +3 -6
  117. package/dist/services/settingsService.d.ts +9 -4
  118. package/dist/services/settingsService.js +23 -12
  119. package/dist/skins/drafter.d.ts +30 -0
  120. package/dist/skins/drafter.js +185 -0
  121. package/dist/skins/index.d.ts +2 -1
  122. package/dist/skins/index.js +4 -2
  123. package/dist/stores/apiContext.d.ts +11 -0
  124. package/dist/stores/apiContext.js +15 -0
  125. package/dist/stores/categoriesStore.svelte.js +0 -1
  126. package/dist/stores/playgroundStore.svelte.js +0 -2
  127. package/dist/styles/base.css +38 -9
  128. package/dist/styles/tokens.css +54 -2
  129. package/dist/svelte-app.d.ts +6 -0
  130. package/dist/svelte-app.js +4 -2
  131. package/dist/themes/drafter.d.ts +2 -0
  132. package/dist/themes/drafter.js +15 -0
  133. package/dist/themes/index.d.ts +2 -1
  134. package/dist/themes/index.js +8 -2
  135. package/dist/types/auth.d.ts +9 -51
  136. package/dist/types/auth.js +4 -54
  137. package/dist/types/events.d.ts +18 -0
  138. package/dist/types/events.js +2 -1
  139. package/dist/types/index.d.ts +4 -2
  140. package/dist/types/index.js +0 -1
  141. package/dist/types/settings.d.ts +1 -1
  142. package/dist/types/settings.js +1 -1
  143. package/dist/types/skin.d.ts +1 -1
  144. package/dist/types/theme.d.ts +16 -2
  145. package/dist/utils/edgeStyling.js +9 -5
  146. package/dist/utils/fetchWithAuth.d.ts +36 -15
  147. package/dist/utils/fetchWithAuth.js +53 -23
  148. package/dist/utils/nodeTypes.js +1 -1
  149. package/package.json +2 -1
@@ -12,6 +12,7 @@
12
12
  import { Position, Handle } from '@xyflow/svelte';
13
13
  import type { WorkflowNode, NodePort, Branch } from '../../types/index.js';
14
14
  import Icon from '@iconify/svelte';
15
+ import NodeConfigButton from './NodeConfigButton.svelte';
15
16
  import { getNodeIcon } from '../../utils/icons.js';
16
17
  import {
17
18
  getDataTypeColorToken,
@@ -120,13 +121,6 @@
120
121
  */
121
122
  const visibleBranches = $derived(branches.filter((branch) => isBranchVisible(branch.name)));
122
123
 
123
- /**
124
- * Handle node click - only handle selection, no config opening
125
- */
126
- function handleNodeClick(): void {
127
- // Node selection is handled by Svelte Flow
128
- }
129
-
130
124
  /**
131
125
  * Handle double-click to open config
132
126
  */
@@ -140,16 +134,6 @@
140
134
  }
141
135
  }
142
136
 
143
- /**
144
- * Handle keyboard events for accessibility
145
- */
146
- function handleKeydown(event: KeyboardEvent): void {
147
- if (event.key === 'Enter' || event.key === ' ') {
148
- event.preventDefault();
149
- handleNodeClick();
150
- }
151
- }
152
-
153
137
  /**
154
138
  * Check if a branch is active
155
139
  */
@@ -159,14 +143,13 @@
159
143
  </script>
160
144
 
161
145
  <!-- Node Container -->
146
+ <!-- Presentational: focus, keyboard and selection live on xyflow's node
147
+ wrapper (see UniversalNode). double-click is a mouse convenience. -->
148
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
162
149
  <div
163
150
  class="flowdrop-workflow-node flowdrop-workflow-node--gateway"
164
151
  class:flowdrop-workflow-node--selected={props.selected}
165
- onclick={handleNodeClick}
166
152
  ondblclick={handleNodeDoubleClick}
167
- onkeydown={handleKeydown}
168
- role="button"
169
- tabindex="0"
170
153
  aria-label={graph.gatewayNode({ title: displayTitle })}
171
154
  aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
172
155
  >
@@ -185,7 +168,7 @@
185
168
  </div>
186
169
 
187
170
  <!-- Node Title - uses instanceTitle override if set -->
188
- <h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-truncate flowdrop-flex--1">
171
+ <h3 class="flowdrop-text--sm flowdrop-font--medium flowdrop-flex--1">
189
172
  {displayTitle}
190
173
  </h3>
191
174
  </div>
@@ -204,13 +187,13 @@
204
187
  <div class="flowdrop-workflow-node__ports-list">
205
188
  {#each visibleInputPorts as port (port.id)}
206
189
  <div class="flowdrop-workflow-node__port">
207
- <!-- Input Handle: centered in row, at node edge (ports have no padding) -->
190
+ <!-- Input Handle: one grid row (20px) from the top so it aligns with the label, at node edge -->
208
191
  <Handle
209
192
  type="target"
210
193
  position={Position.Left}
211
194
  id={`${props.data.nodeId}-input-${port.id}`}
212
195
  class="flowdrop-workflow-node__handle"
213
- style="top: 50%; transform: translateY(-50%); --fd-handle-fill: {getDataTypeColorToken(
196
+ style="top: var(--fd-node-port-row-height); transform: translateY(-50%); --fd-handle-fill: {getDataTypeColorToken(
214
197
  checker,
215
198
  port.dataType
216
199
  )}; --fd-handle-border-color: var(--fd-handle-border);"
@@ -293,13 +276,13 @@
293
276
  </div>
294
277
  </div>
295
278
 
296
- <!-- Output Handle: centered in row, at node edge (ports have no padding) -->
279
+ <!-- Output Handle: one grid row (20px) from the top so it aligns with the label, at node edge -->
297
280
  <Handle
298
281
  type="source"
299
282
  position={Position.Right}
300
283
  id={`${props.data.nodeId}-output-${branch.name}`}
301
284
  class={`flowdrop-workflow-node__handle ${isActive ? 'flowdrop-workflow-node__handle--active' : ''}`}
302
- style="top: 50%; transform: translateY(-50%); --fd-handle-fill: {isActive
285
+ style="top: var(--fd-node-port-row-height); transform: translateY(-50%); --fd-handle-fill: {isActive
303
286
  ? getDataTypeColorToken(checker, 'trigger')
304
287
  : getDataTypeColorToken(
305
288
  checker,
@@ -323,22 +306,17 @@
323
306
  <!-- Note: When all branches are hidden due to hideUnconnectedHandles, we don't show anything -->
324
307
 
325
308
  <!-- Config button -->
326
- <button
327
- class="flowdrop-workflow-node__config-btn"
328
- onclick={handleNodeDoubleClick}
329
- title="Configure node"
330
- >
331
- <Icon icon="mdi:cog" />
332
- </button>
309
+ <NodeConfigButton onclick={handleNodeDoubleClick} title="Configure node" />
333
310
  </div>
334
311
 
335
312
  <style>
336
313
  .flowdrop-workflow-node {
337
314
  position: relative;
338
- background-color: var(--fd-card);
339
- border: 1.5px solid var(--fd-node-border);
340
- border-radius: var(--fd-radius-xl);
341
- box-shadow: var(--fd-shadow-md);
315
+ background-color: var(--fd-node-bg);
316
+ backdrop-filter: var(--fd-node-backdrop-filter);
317
+ border: var(--fd-node-border-width) solid var(--fd-node-border);
318
+ border-radius: var(--fd-node-radius);
319
+ box-shadow: var(--fd-node-shadow);
342
320
  width: var(--fd-node-default-width);
343
321
  z-index: 10;
344
322
  color: var(--fd-foreground);
@@ -350,42 +328,49 @@
350
328
  }
351
329
 
352
330
  .flowdrop-workflow-node:hover {
353
- box-shadow: var(--fd-shadow-lg);
331
+ box-shadow: var(--fd-node-shadow-hover);
354
332
  border-color: var(--fd-node-border-hover);
355
333
  }
356
334
 
357
335
  .flowdrop-workflow-node--selected {
358
336
  box-shadow:
359
337
  0 0 0 2px var(--fd-primary-muted),
360
- var(--fd-shadow-lg);
338
+ var(--fd-node-shadow-hover);
361
339
  border-color: var(--fd-primary);
362
340
  }
363
341
 
364
342
  .flowdrop-workflow-node--selected:hover {
365
343
  box-shadow:
366
344
  0 0 0 2px var(--fd-primary-muted),
367
- var(--fd-shadow-lg);
345
+ var(--fd-node-shadow-hover);
368
346
  border-color: var(--fd-primary);
369
347
  }
370
348
 
371
- .flowdrop-workflow-node:focus-visible {
372
- outline: 2px solid var(--fd-ring);
373
- outline-offset: 2px;
374
- }
349
+ /* Focus ring is centralized in base.css (drawn on the .svelte-flow__node
350
+ wrapper, which is the focusable element). */
375
351
 
376
352
  .flowdrop-workflow-node__header {
377
353
  box-sizing: border-box;
378
- padding: var(--fd-node-header-gap) var(--fd-space-xl);
379
- border-bottom: 1px solid var(--fd-border-muted);
380
- background: var(--fd-header);
381
- border-top-left-radius: var(--fd-radius-xl);
382
- border-top-right-radius: var(--fd-radius-xl);
354
+ /* Bottom padding absorbs BOTH the node's own top border and the header
355
+ divider, so the body below the header lands on the 20px grid measured
356
+ from the node's outer top edge: node-border + header = 100/120/140. */
357
+ padding: var(--fd-node-header-gap) var(--fd-space-xl)
358
+ calc(
359
+ var(--fd-node-header-gap) - var(--fd-node-border-width) -
360
+ var(--fd-node-header-divider-width)
361
+ );
362
+ border-bottom: var(--fd-node-header-divider-width) solid var(--fd-node-header-divider-color);
363
+ background: var(--fd-node-header-bg);
364
+ border-top-left-radius: var(--fd-node-radius);
365
+ border-top-right-radius: var(--fd-node-radius);
383
366
  display: flex;
384
367
  flex-direction: column;
385
- gap: var(--fd-node-header-gap);
368
+ gap: calc(var(--fd-node-header-gap) * 2);
369
+ /* node-border (1.5) + header = 100/120/140. Header itself is
370
+ 4*gap + title + desc-line - node-border; each extra desc line adds 20. */
386
371
  min-height: calc(
387
- var(--fd-node-header-gap) * 2 + var(--fd-node-header-title-height) +
388
- var(--fd-node-header-desc-line)
372
+ var(--fd-node-header-gap) * 4 + var(--fd-node-header-title-height) +
373
+ var(--fd-node-header-desc-line) - var(--fd-node-border-width)
389
374
  );
390
375
  }
391
376
 
@@ -416,9 +401,10 @@
416
401
  display: flex;
417
402
  align-items: center;
418
403
  justify-content: center;
419
- width: 2.25rem;
420
- height: 2.25rem;
421
- border-radius: 0.5rem;
404
+ /* px (not rem) so the icon stays grid-locked regardless of root font-size */
405
+ width: 36px;
406
+ height: 36px;
407
+ border-radius: 8px;
422
408
  background: color-mix(in srgb, var(--_icon-color) var(--fd-node-icon-bg-opacity), transparent);
423
409
  flex-shrink: 0;
424
410
  transition: all var(--fd-transition-normal);
@@ -434,15 +420,22 @@
434
420
  }
435
421
 
436
422
  .flowdrop-workflow-node__icon-wrapper :global(.flowdrop-workflow-node__icon) {
437
- width: 1.25rem;
438
- height: 1.25rem;
423
+ width: 20px;
424
+ height: 20px;
439
425
  color: var(--fd-node-icon);
440
426
  }
441
427
 
442
428
  .flowdrop-workflow-node__header-title h3 {
443
429
  margin: 0;
444
- line-height: 1;
430
+ /* half the title block so two lines fill it exactly on the 20px grid */
431
+ line-height: calc(var(--fd-node-header-title-height) / 2);
445
432
  color: var(--fd-foreground);
433
+ display: -webkit-box;
434
+ -webkit-line-clamp: 2;
435
+ line-clamp: 2;
436
+ -webkit-box-orient: vertical;
437
+ overflow: hidden;
438
+ min-width: 0;
446
439
  }
447
440
 
448
441
  .flowdrop-workflow-node__ports {
@@ -452,27 +445,52 @@
452
445
  .flowdrop-workflow-node__ports-list {
453
446
  display: flex;
454
447
  flex-direction: column;
455
- gap: var(--fd-node-header-gap);
456
- padding: var(--fd-node-header-gap) 0;
448
+ gap: 0;
449
+ /* No vertical padding: sections stack flush and node height stays a
450
+ multiple of 20. The one exception is the clearance below the header
451
+ divider, applied to the first section only (below). */
452
+ padding: 0;
453
+ }
454
+
455
+ /* The first port section sits directly below the header divider; give it a
456
+ full 20px grid row of clearance so the first port lands on the grid. */
457
+ .flowdrop-workflow-node__header
458
+ + .flowdrop-workflow-node__ports
459
+ .flowdrop-workflow-node__ports-list {
460
+ padding-top: calc(var(--fd-node-header-gap) * 2);
457
461
  }
458
462
 
459
463
  .flowdrop-workflow-node__port {
460
464
  display: flex;
461
- align-items: center;
465
+ align-items: flex-start;
462
466
  gap: 0;
463
- min-height: var(--fd-node-port-row-height);
464
- padding: var(--fd-space-3xs) 0;
467
+ /* Fixed three-row (60px) height for every port — node height stays
468
+ predictable whether or not a port carries a description. */
469
+ height: calc(var(--fd-node-port-row-height) * 3);
470
+ padding: 0;
465
471
  position: relative;
466
472
  }
467
473
 
468
474
  .flowdrop-workflow-node__port-content {
469
- padding: 0 var(--fd-space-xl);
475
+ padding: var(--fd-node-header-gap) var(--fd-space-xl) 0;
476
+ }
477
+
478
+ /* Each line in a port occupies one 20px grid row: a label-only port
479
+ centers its single row, a label + description fills both. */
480
+ .flowdrop-workflow-node__port-content > div {
481
+ min-height: var(--fd-node-port-row-height);
482
+ align-items: center;
483
+ }
484
+
485
+ .flowdrop-workflow-node__port-content > p {
486
+ min-height: var(--fd-node-port-row-height);
487
+ line-height: var(--fd-node-port-row-height);
470
488
  }
471
489
 
472
490
  .flowdrop-badge {
473
- padding: 0.125rem var(--fd-space-3xs);
491
+ padding: 2px 4px;
474
492
  border-radius: var(--fd-radius-sm);
475
- font-size: 0.625rem;
493
+ font-size: 10px;
476
494
  font-weight: 500;
477
495
  text-transform: uppercase;
478
496
  letter-spacing: 0.05em;
@@ -484,8 +502,8 @@
484
502
  }
485
503
 
486
504
  .flowdrop-badge--sm {
487
- font-size: 0.625rem;
488
- padding: 0.125rem var(--fd-space-3xs);
505
+ font-size: 10px;
506
+ padding: 2px 4px;
489
507
  }
490
508
 
491
509
  .workflow-node__no-branches {
@@ -537,12 +555,12 @@
537
555
 
538
556
  .flowdrop-text--xs {
539
557
  font-size: var(--fd-text-xs);
540
- line-height: 1rem;
558
+ line-height: 16px;
541
559
  }
542
560
 
543
561
  .flowdrop-text--sm {
544
562
  font-size: var(--fd-text-sm);
545
- line-height: 1.25rem;
563
+ line-height: 20px;
546
564
  }
547
565
 
548
566
  .flowdrop-text--gray {
@@ -568,34 +586,8 @@
568
586
  text-align: right;
569
587
  }
570
588
 
571
- .flowdrop-workflow-node__config-btn {
572
- position: absolute;
573
- top: var(--fd-space-xs);
574
- right: var(--fd-space-xs);
575
- width: 1.5rem;
576
- height: 1.5rem;
577
- background-color: var(--fd-backdrop);
578
- border: 1px solid var(--fd-border);
579
- border-radius: var(--fd-radius-sm);
580
- color: var(--fd-muted-foreground);
581
- cursor: pointer;
582
- display: flex;
583
- align-items: center;
584
- justify-content: center;
585
- opacity: 0;
586
- transition: all var(--fd-transition-normal);
587
- backdrop-filter: blur(4px);
588
- z-index: 15;
589
- font-size: var(--fd-text-sm);
590
- }
591
-
592
- .flowdrop-workflow-node:hover .flowdrop-workflow-node__config-btn {
593
- opacity: 1;
594
- }
595
-
596
- .flowdrop-workflow-node__config-btn:hover {
597
- background-color: var(--fd-muted);
598
- border-color: var(--fd-border-strong);
599
- color: var(--fd-foreground);
589
+ /* Reveal the NodeConfigButton (gear) when the node is hovered. */
590
+ .flowdrop-workflow-node:hover {
591
+ --fd-config-btn-opacity: 1;
600
592
  }
601
593
  </style>
@@ -10,6 +10,7 @@
10
10
  import { Position, Handle } from '@xyflow/svelte';
11
11
  import type { ConfigValues, NodeMetadata } from '../../types/index.js';
12
12
  import Icon from '@iconify/svelte';
13
+ import NodeConfigButton from './NodeConfigButton.svelte';
13
14
  import { getDataTypeColor } from '../../utils/colors.js';
14
15
  import { getInstance } from '../../stores/getInstance.svelte.js';
15
16
  import { m } from '../../messages/index.js';
@@ -114,38 +115,19 @@
114
115
  function handleDoubleClick(): void {
115
116
  openConfigSidebar();
116
117
  }
117
-
118
- /**
119
- * Handle single click - selection handled by SvelteFlow
120
- */
121
- function handleClick(): void {
122
- // Node selection is handled by Svelte Flow
123
- }
124
-
125
- /**
126
- * Handles keyboard events for accessibility
127
- * @param event - The keyboard event
128
- */
129
- function handleKeydown(event: KeyboardEvent): void {
130
- if (event.key === 'Enter' || event.key === ' ') {
131
- event.preventDefault();
132
- handleDoubleClick();
133
- }
134
- }
135
118
  </script>
136
119
 
137
120
  <!-- Idea Node -->
121
+ <!-- Presentational: focus, keyboard and selection live on xyflow's node
122
+ wrapper (see UniversalNode). double-click is a mouse convenience. -->
123
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
138
124
  <div
139
125
  class="flowdrop-idea-node"
140
126
  class:flowdrop-idea-node--selected={props.selected}
141
127
  class:flowdrop-idea-node--processing={props.isProcessing}
142
128
  class:flowdrop-idea-node--error={props.isError}
143
129
  style="--idea-accent-color: {ideaColor};"
144
- onclick={handleClick}
145
130
  ondblclick={handleDoubleClick}
146
- onkeydown={handleKeydown}
147
- role="button"
148
- tabindex="0"
149
131
  aria-label={m().nodes.graph.ideaNode({ title: displayTitle })}
150
132
  >
151
133
  <!-- Left Port (Target/Input): center at top 40px (multiple of 10), 20px connection area -->
@@ -161,7 +143,7 @@
161
143
  />
162
144
  {/if}
163
145
 
164
- <!-- Top Port (Target/Input): center at left 150px (multiple of 10), 20px connection area -->
146
+ <!-- Top Port (Target/Input): center at left 140px (node midpoint, 20px grid), 20px connection area -->
165
147
  {#if enableTopPort}
166
148
  <Handle
167
149
  type="target"
@@ -169,7 +151,7 @@
169
151
  style="--fd-handle-fill: {getDataTypeColor(
170
152
  checker,
171
153
  IDEA_DATA_TYPE
172
- )}; --fd-handle-border-color: var(--fd-handle-border); left: 150px; transform: translateX(-50%); z-index: 30;"
154
+ )}; --fd-handle-border-color: var(--fd-handle-border); left: 140px; transform: translateX(-50%); z-index: 30;"
173
155
  id={`${props.data.nodeId}-input-top`}
174
156
  />
175
157
  {/if}
@@ -210,9 +192,7 @@
210
192
  </div>
211
193
 
212
194
  <!-- Config button -->
213
- <button class="flowdrop-idea-node__config-btn" onclick={openConfigSidebar} title="Configure idea">
214
- <Icon icon="mdi:cog" />
215
- </button>
195
+ <NodeConfigButton onclick={openConfigSidebar} title="Configure idea" />
216
196
 
217
197
  <!-- Right Port (Source/Output): center at top 40px (multiple of 10), 20px connection area -->
218
198
  {#if enableRightPort}
@@ -227,7 +207,7 @@
227
207
  />
228
208
  {/if}
229
209
 
230
- <!-- Bottom Port (Source/Output): center at left 150px (multiple of 10), 20px connection area -->
210
+ <!-- Bottom Port (Source/Output): center at left 140px (node midpoint, 20px grid), 20px connection area -->
231
211
  {#if enableBottomPort}
232
212
  <Handle
233
213
  type="source"
@@ -235,7 +215,7 @@
235
215
  style="--fd-handle-fill: {getDataTypeColor(
236
216
  checker,
237
217
  IDEA_DATA_TYPE
238
- )}; --fd-handle-border-color: var(--fd-handle-border); left: 150px; transform: translateX(-50%); z-index: 30;"
218
+ )}; --fd-handle-border-color: var(--fd-handle-border); left: 140px; transform: translateX(-50%); z-index: 30;"
239
219
  id={`${props.data.nodeId}-output-bottom`}
240
220
  />
241
221
  {/if}
@@ -245,7 +225,11 @@
245
225
  .flowdrop-idea-node {
246
226
  position: relative;
247
227
  width: var(--fd-node-default-width);
248
- min-height: var(--fd-node-simple-height);
228
+ /* Floor at 80px, then grow in 20px steps. Fixed chrome = accent 4px +
229
+ header 48px + body bottom 6px = 58px, plus the card's 1px×2 border = 60px;
230
+ each description line adds 20px (line-height), so the card lands on
231
+ 80 / 100 / 120px gridlines. */
232
+ min-height: 80px;
249
233
  cursor: pointer;
250
234
  transition: all var(--fd-transition-normal);
251
235
  z-index: 10;
@@ -254,22 +238,21 @@
254
238
 
255
239
  .flowdrop-idea-node__card {
256
240
  background-color: var(--fd-background);
257
- border-radius: var(--fd-radius-xl);
241
+ border-radius: var(--fd-node-radius);
258
242
  border: 1px solid var(--fd-border);
259
- box-shadow: var(--fd-shadow-md);
243
+ box-shadow: var(--fd-node-shadow);
260
244
  overflow: hidden;
261
245
  transition: all var(--fd-transition-normal);
262
246
  }
263
247
 
264
248
  .flowdrop-idea-node:hover .flowdrop-idea-node__card {
265
- box-shadow: var(--fd-shadow-lg);
266
- transform: translateY(-1px);
249
+ box-shadow: var(--fd-node-shadow-hover);
267
250
  }
268
251
 
269
252
  .flowdrop-idea-node--selected .flowdrop-idea-node__card {
270
253
  border-color: var(--fd-primary);
271
254
  box-shadow:
272
- var(--fd-shadow-lg),
255
+ var(--fd-node-shadow-hover),
273
256
  0 0 0 3px color-mix(in srgb, var(--fd-primary) 30%, transparent);
274
257
  }
275
258
 
@@ -293,52 +276,69 @@
293
276
  .flowdrop-idea-node__header {
294
277
  display: flex;
295
278
  align-items: center;
296
- gap: 0.625rem;
297
- padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xs);
279
+ /* px (not rem) on the 20px grid; 8px top/bottom + 32px icon = 48px header */
280
+ gap: 10px;
281
+ padding: 8px 16px;
298
282
  }
299
283
 
284
+ /* Squircle icon wrapper - px (not rem) so the icon stays grid-locked
285
+ regardless of root font-size */
300
286
  .flowdrop-idea-node__icon-wrapper {
301
287
  display: flex;
302
288
  align-items: center;
303
289
  justify-content: center;
304
- width: 2rem;
305
- height: 2rem;
290
+ width: 32px;
291
+ height: 32px;
306
292
  background-color: color-mix(
307
293
  in srgb,
308
294
  var(--idea-accent-color, var(--fd-accent)) var(--fd-node-icon-bg-opacity),
309
295
  transparent
310
296
  );
311
- border-radius: var(--fd-radius-lg);
297
+ border-radius: 8px;
312
298
  flex-shrink: 0;
299
+ transition: all var(--fd-transition-normal);
300
+ }
301
+
302
+ /* Icon hover effect — matches SimpleNode/ToolNode/NotesNode */
303
+ .flowdrop-idea-node:hover .flowdrop-idea-node__icon-wrapper {
304
+ background-color: color-mix(
305
+ in srgb,
306
+ var(--idea-accent-color, var(--fd-accent)) var(--fd-node-icon-bg-opacity-hover),
307
+ transparent
308
+ );
309
+ transform: scale(1.05);
313
310
  }
314
311
 
315
312
  :global(.flowdrop-idea-node__icon) {
316
- width: 1.25rem;
317
- height: 1.25rem;
313
+ width: 20px;
314
+ height: 20px;
318
315
  color: var(--fd-node-icon);
319
316
  }
320
317
 
321
318
  .flowdrop-idea-node__title {
322
- font-size: 0.9375rem;
319
+ font-size: 15px;
323
320
  font-weight: 600;
324
321
  color: var(--fd-foreground);
325
322
  margin: 0;
326
- line-height: 1.3;
323
+ /* px line-height so multi-line text stays on the 20px grid */
324
+ line-height: 20px;
327
325
  overflow: hidden;
328
326
  text-overflow: ellipsis;
329
327
  white-space: nowrap;
330
328
  }
331
329
 
332
- /* Body section */
330
+ /* Body section: 6px bottom (not 8px) compensates the card's 1px×2 border
331
+ so the card height stays a multiple of 20px. */
333
332
  .flowdrop-idea-node__body {
334
- padding: 0 var(--fd-space-xl) var(--fd-space-md);
333
+ padding: 0 16px 6px;
335
334
  }
336
335
 
337
336
  .flowdrop-idea-node__description {
338
- font-size: 0.8125rem;
337
+ font-size: 13px;
339
338
  color: var(--fd-muted-foreground);
340
339
  margin: 0;
341
- line-height: 1.5;
340
+ /* px line-height so the 3-line clamp lands on the 20px grid (3 × 20px) */
341
+ line-height: 20px;
342
342
  display: -webkit-box;
343
343
  -webkit-line-clamp: 3;
344
344
  line-clamp: 3;
@@ -350,16 +350,16 @@
350
350
  .flowdrop-idea-node__processing {
351
351
  display: flex;
352
352
  align-items: center;
353
- gap: var(--fd-space-xs);
354
- padding: var(--fd-space-xs) var(--fd-space-xl);
353
+ gap: 8px;
354
+ padding: 8px 16px;
355
355
  font-size: var(--fd-text-xs);
356
356
  color: var(--fd-muted-foreground);
357
357
  border-top: 1px solid var(--fd-border-muted);
358
358
  }
359
359
 
360
360
  .flowdrop-idea-node__spinner {
361
- width: 0.875rem;
362
- height: 0.875rem;
361
+ width: 14px;
362
+ height: 14px;
363
363
  border: 2px solid var(--fd-border);
364
364
  border-top-color: var(--idea-accent-color, var(--fd-accent));
365
365
  border-radius: 50%;
@@ -370,8 +370,8 @@
370
370
  .flowdrop-idea-node__error {
371
371
  display: flex;
372
372
  align-items: center;
373
- gap: var(--fd-space-xs);
374
- padding: var(--fd-space-xs) var(--fd-space-xl);
373
+ gap: 8px;
374
+ padding: 8px 16px;
375
375
  font-size: var(--fd-text-xs);
376
376
  color: var(--fd-error);
377
377
  border-top: 1px solid color-mix(in srgb, var(--fd-error) 30%, transparent);
@@ -379,8 +379,8 @@
379
379
  }
380
380
 
381
381
  :global(.flowdrop-idea-node__error-icon) {
382
- width: 0.875rem;
383
- height: 0.875rem;
382
+ width: 14px;
383
+ height: 14px;
384
384
  }
385
385
 
386
386
  @keyframes idea-spin {
@@ -389,37 +389,9 @@
389
389
  }
390
390
  }
391
391
 
392
- /* Config button */
393
- .flowdrop-idea-node__config-btn {
394
- position: absolute;
395
- top: 0.625rem;
396
- right: 0.625rem;
397
- width: 1.5rem;
398
- height: 1.5rem;
399
- background-color: var(--fd-backdrop);
400
- border: 1px solid var(--fd-border);
401
- border-radius: var(--fd-radius-md);
402
- color: var(--fd-muted-foreground);
403
- cursor: pointer;
404
- display: flex;
405
- align-items: center;
406
- justify-content: center;
407
- opacity: 0;
408
- transition: all var(--fd-transition-normal);
409
- backdrop-filter: var(--fd-backdrop-blur);
410
- z-index: 15;
411
- font-size: var(--fd-text-sm);
412
- }
413
-
414
- .flowdrop-idea-node:hover .flowdrop-idea-node__config-btn {
415
- opacity: 1;
416
- }
417
-
418
- .flowdrop-idea-node__config-btn:hover {
419
- background-color: var(--fd-muted);
420
- border-color: var(--fd-border-strong);
421
- color: var(--fd-foreground);
422
- transform: scale(1.05);
392
+ /* Reveal the NodeConfigButton (gear) when the node is hovered. */
393
+ .flowdrop-idea-node:hover {
394
+ --fd-config-btn-opacity: 1;
423
395
  }
424
396
 
425
397
  /* Handle: 20px/12px from base.css; position offsets for 20px handle */
@@ -441,15 +413,15 @@
441
413
  /* Responsive design */
442
414
  @media (max-width: 640px) {
443
415
  .flowdrop-idea-node {
444
- width: 16rem;
416
+ width: 256px;
445
417
  }
446
418
 
447
419
  .flowdrop-idea-node__header {
448
- padding: 0.625rem 0.75rem 0.375rem;
420
+ padding: 8px 12px;
449
421
  }
450
422
 
451
423
  .flowdrop-idea-node__body {
452
- padding: 0 0.75rem 0.625rem;
424
+ padding: 0 12px 6px;
453
425
  }
454
426
 
455
427
  .flowdrop-idea-node__title {