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

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 (87) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +5 -5
  3. package/dist/adapters/WorkflowAdapter.js +4 -5
  4. package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -3
  5. package/dist/adapters/agentspec/defaultNodeTypes.js +9 -9
  6. package/dist/commands/executor.js +5 -6
  7. package/dist/commands/types.js +5 -5
  8. package/dist/components/App.svelte +19 -150
  9. package/dist/components/Button.stories.svelte +65 -0
  10. package/dist/components/Button.stories.svelte.d.ts +19 -0
  11. package/dist/components/Button.svelte +62 -0
  12. package/dist/components/Button.svelte.d.ts +24 -0
  13. package/dist/components/ConfigForm.svelte +4 -4
  14. package/dist/components/EditorStatusBar.stories.svelte +44 -0
  15. package/dist/components/EditorStatusBar.stories.svelte.d.ts +27 -0
  16. package/dist/components/EditorStatusBar.svelte +99 -0
  17. package/dist/components/EditorStatusBar.svelte.d.ts +15 -0
  18. package/dist/components/IconButton.svelte +80 -0
  19. package/dist/components/IconButton.svelte.d.ts +30 -0
  20. package/dist/components/Input.svelte +74 -0
  21. package/dist/components/Input.svelte.d.ts +17 -0
  22. package/dist/components/Navbar.svelte +9 -4
  23. package/dist/components/Navbar.svelte.d.ts +3 -0
  24. package/dist/components/NodeSidebar.svelte +17 -115
  25. package/dist/components/NodeSwapPicker.svelte +12 -28
  26. package/dist/components/Select.svelte +53 -0
  27. package/dist/components/Select.svelte.d.ts +15 -0
  28. package/dist/components/Textarea.svelte +39 -0
  29. package/dist/components/Textarea.svelte.d.ts +12 -0
  30. package/dist/components/ThemeToggle.svelte +15 -89
  31. package/dist/components/UniversalNode.svelte +5 -4
  32. package/dist/components/UniversalNode.svelte.d.ts +1 -1
  33. package/dist/components/console/ConsoleInput.svelte +3 -3
  34. package/dist/components/form/FormArray.svelte +37 -157
  35. package/dist/components/form/FormCheckboxGroup.svelte +1 -1
  36. package/dist/components/form/FormField.svelte +5 -44
  37. package/dist/components/form/FormFieldLight.svelte +5 -44
  38. package/dist/components/form/FormFieldset.svelte +1 -1
  39. package/dist/components/form/FormNumberField.svelte +4 -32
  40. package/dist/components/form/FormRangeField.svelte +17 -7
  41. package/dist/components/form/FormSelect.svelte +13 -79
  42. package/dist/components/form/FormTextField.svelte +3 -39
  43. package/dist/components/form/FormTextarea.svelte +4 -43
  44. package/dist/components/form/resolveFieldType.d.ts +24 -0
  45. package/dist/components/form/resolveFieldType.js +55 -0
  46. package/dist/components/icons/CloseIcon.svelte +6 -0
  47. package/dist/components/icons/CloseIcon.svelte.d.ts +26 -0
  48. package/dist/components/nodes/AtomNode.svelte +3 -3
  49. package/dist/components/nodes/AtomNode.svelte.d.ts +1 -1
  50. package/dist/components/nodes/GatewayNode.svelte +8 -11
  51. package/dist/components/nodes/GatewayNode.svelte.d.ts +1 -1
  52. package/dist/components/nodes/IdeaNode.svelte +10 -8
  53. package/dist/components/nodes/IdeaNode.svelte.d.ts +8 -4
  54. package/dist/components/nodes/NotesNode.svelte +6 -4
  55. package/dist/components/nodes/NotesNode.svelte.d.ts +8 -4
  56. package/dist/components/nodes/SimpleNode.svelte +10 -8
  57. package/dist/components/nodes/SimpleNode.svelte.d.ts +4 -4
  58. package/dist/components/nodes/SquareNode.svelte +10 -8
  59. package/dist/components/nodes/SquareNode.svelte.d.ts +4 -4
  60. package/dist/components/nodes/TerminalNode.svelte +10 -8
  61. package/dist/components/nodes/TerminalNode.svelte.d.ts +4 -4
  62. package/dist/components/nodes/ToolNode.svelte +10 -8
  63. package/dist/components/nodes/ToolNode.svelte.d.ts +6 -6
  64. package/dist/components/nodes/WorkflowNode.svelte +7 -10
  65. package/dist/components/nodes/WorkflowNode.svelte.d.ts +1 -1
  66. package/dist/components/playground/InputCollector.svelte +11 -46
  67. package/dist/components/playground/PipelineKanbanView.svelte +2 -2
  68. package/dist/components/playground/PipelineTableView.svelte +2 -2
  69. package/dist/helpers/workflowEditorHelper.js +4 -5
  70. package/dist/messages/index.d.ts +1 -1
  71. package/dist/messages/index.js +1 -1
  72. package/dist/openapi/v1/openapi.yaml +2 -2
  73. package/dist/registry/nodeComponentRegistry.d.ts +2 -1
  74. package/dist/services/dynamicSchemaService.d.ts +2 -2
  75. package/dist/services/dynamicSchemaService.js +8 -8
  76. package/dist/skins/drafter.js +41 -28
  77. package/dist/stores/playgroundStore.svelte.js +1 -1
  78. package/dist/stores/workflowStore.svelte.js +0 -18
  79. package/dist/styles/base.css +247 -5
  80. package/dist/styles/tokens.css +6 -0
  81. package/dist/svelte-app.js +68 -107
  82. package/dist/types/index.d.ts +6 -7
  83. package/dist/utils/connections.js +20 -56
  84. package/dist/utils/nodeIds.d.ts +1 -1
  85. package/dist/utils/nodeIds.js +1 -1
  86. package/dist/utils/nodeSwap.js +3 -6
  87. package/package.json +1 -1
@@ -16,9 +16,10 @@ import { BaseRegistry } from './BaseRegistry.svelte.js';
16
16
  * Any component registered in the registry must be compatible with these props.
17
17
  */
18
18
  export interface NodeComponentProps {
19
+ /** The SvelteFlow node id */
20
+ id: string;
19
21
  /** Node data containing label, config, metadata, executionInfo */
20
22
  data: WorkflowNode['data'] & {
21
- nodeId?: string;
22
23
  onConfigOpen?: (node: {
23
24
  id: string;
24
25
  type: string;
@@ -33,7 +33,7 @@ export interface DynamicSchemaResult {
33
33
  * const endpoint: DynamicSchemaEndpoint = {
34
34
  * url: "/api/nodes/{nodeTypeId}/schema",
35
35
  * method: "GET",
36
- * parameterMapping: { nodeTypeId: "metadata.id" }
36
+ * parameterMapping: { nodeTypeId: "metadata.node_type_id" }
37
37
  * };
38
38
  *
39
39
  * const result = await fetchDynamicSchema(endpoint, node);
@@ -56,7 +56,7 @@ export declare function fetchDynamicSchema(endpointConfig: EndpointConfig | null
56
56
  * ```typescript
57
57
  * const link: ExternalEditLink = {
58
58
  * url: "https://admin.example.com/nodes/{nodeTypeId}/edit/{instanceId}",
59
- * parameterMapping: { nodeTypeId: "metadata.id", instanceId: "id" }
59
+ * parameterMapping: { nodeTypeId: "metadata.node_type_id", instanceId: "id" }
60
60
  * };
61
61
  *
62
62
  * const url = resolveExternalEditUrl(link, node, workflowId);
@@ -15,16 +15,16 @@ const schemaCache = new Map();
15
15
  const DEFAULT_CACHE_TTL = DEFAULT_CACHE_TTL_MS;
16
16
  /**
17
17
  * Resolves a template variable path from the node context.
18
- * Supports dot-notation paths like "metadata.id", "config.apiKey", "id"
18
+ * Supports dot-notation paths like "metadata.node_type_id", "config.apiKey", "id"
19
19
  *
20
20
  * @param context - The node context containing all available data
21
- * @param path - Dot-notation path to resolve (e.g., "metadata.id")
21
+ * @param path - Dot-notation path to resolve (e.g., "metadata.node_type_id")
22
22
  * @returns The resolved value as a string, or undefined if not found
23
23
  *
24
24
  * @example
25
25
  * ```typescript
26
- * const context = { id: "node-1", metadata: { id: "llm-node" } };
27
- * resolveVariablePath(context, "metadata.id"); // Returns "llm-node"
26
+ * const context = { id: "node-1", metadata: { node_type_id: "llm-node" } };
27
+ * resolveVariablePath(context, "metadata.node_type_id"); // Returns "llm-node"
28
28
  * resolveVariablePath(context, "id"); // Returns "node-1"
29
29
  * ```
30
30
  */
@@ -60,8 +60,8 @@ function resolveVariablePath(context, path) {
60
60
  * @example
61
61
  * ```typescript
62
62
  * const url = "/api/nodes/{nodeTypeId}/schema?instance={instanceId}";
63
- * const mapping = { nodeTypeId: "metadata.id", instanceId: "id" };
64
- * const context = { id: "node-1", metadata: { id: "llm-node" } };
63
+ * const mapping = { nodeTypeId: "metadata.node_type_id", instanceId: "id" };
64
+ * const context = { id: "node-1", metadata: { node_type_id: "llm-node" } };
65
65
  * resolveTemplate(url, mapping, context);
66
66
  * // Returns "/api/nodes/llm-node/schema?instance=node-1"
67
67
  * ```
@@ -127,7 +127,7 @@ function isCacheValid(entry, ttl = DEFAULT_CACHE_TTL) {
127
127
  * const endpoint: DynamicSchemaEndpoint = {
128
128
  * url: "/api/nodes/{nodeTypeId}/schema",
129
129
  * method: "GET",
130
- * parameterMapping: { nodeTypeId: "metadata.id" }
130
+ * parameterMapping: { nodeTypeId: "metadata.node_type_id" }
131
131
  * };
132
132
  *
133
133
  * const result = await fetchDynamicSchema(endpoint, node);
@@ -279,7 +279,7 @@ export async function fetchDynamicSchema(endpointConfig, endpoint, node, workflo
279
279
  * ```typescript
280
280
  * const link: ExternalEditLink = {
281
281
  * url: "https://admin.example.com/nodes/{nodeTypeId}/edit/{instanceId}",
282
- * parameterMapping: { nodeTypeId: "metadata.id", instanceId: "id" }
282
+ * parameterMapping: { nodeTypeId: "metadata.node_type_id", instanceId: "id" }
283
283
  * };
284
284
  *
285
285
  * const url = resolveExternalEditUrl(link, node, workflowId);
@@ -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
  };
@@ -212,7 +212,7 @@ export class PlaygroundStore {
212
212
  // Find input nodes in the workflow
213
213
  workflow.nodes.forEach((node) => {
214
214
  const category = node.data.metadata?.category;
215
- const nodeTypeId = node.data.metadata?.id ?? node.type;
215
+ const nodeTypeId = node.data.metadata?.node_type_id ?? node.type;
216
216
  // Check if this is an input-type node
217
217
  // The category can be "inputs" (standard) or variations like "input"
218
218
  const categoryStr = String(category || '');
@@ -94,23 +94,6 @@ function hasWorkflowDataChanged(currentWorkflow, newNodes, newEdges) {
94
94
  }
95
95
  return false;
96
96
  }
97
- /**
98
- * Heal nodes that are missing `data.nodeId`.
99
- *
100
- * Node components derive their handle IDs from `data.nodeId` — a node without
101
- * it renders with zero handles and SvelteFlow silently drops every edge
102
- * anchored to it, making an intact graph look corrupted on the canvas.
103
- * `data.nodeId` always equals the node's own `id`, so it is safe to restore.
104
- */
105
- function healMissingNodeIds(workflow) {
106
- if (!workflow.nodes?.some((node) => !node.data.nodeId)) {
107
- return workflow;
108
- }
109
- return {
110
- ...workflow,
111
- nodes: workflow.nodes.map((node) => node.data.nodeId ? node : { ...node, data: { ...node.data, nodeId: node.id } })
112
- };
113
- }
114
97
  // =========================================================================
115
98
  // WorkflowStore (per-instance reactive state)
116
99
  // =========================================================================
@@ -373,7 +356,6 @@ export class WorkflowStore {
373
356
  */
374
357
  initialize(workflow) {
375
358
  workflow = normalizeWorkflowMetadata(workflow);
376
- workflow = healMissingNodeIds(workflow);
377
359
  this.#workflow = workflow;
378
360
  // Reset version counters — workflow is "clean" after initialization
379
361
  this.#editVersion = 0;
@@ -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 */