@flowdrop/flowdrop 2.0.0-beta.4 → 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 (44) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/adapters/WorkflowAdapter.js +4 -5
  3. package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -3
  4. package/dist/adapters/agentspec/defaultNodeTypes.js +9 -9
  5. package/dist/commands/executor.js +5 -6
  6. package/dist/commands/types.js +5 -5
  7. package/dist/components/App.svelte +4 -4
  8. package/dist/components/NodeSidebar.svelte +4 -4
  9. package/dist/components/NodeSwapPicker.svelte +2 -2
  10. package/dist/components/UniversalNode.svelte +5 -4
  11. package/dist/components/UniversalNode.svelte.d.ts +1 -1
  12. package/dist/components/console/ConsoleInput.svelte +3 -3
  13. package/dist/components/nodes/AtomNode.svelte +3 -3
  14. package/dist/components/nodes/AtomNode.svelte.d.ts +1 -1
  15. package/dist/components/nodes/GatewayNode.svelte +8 -11
  16. package/dist/components/nodes/GatewayNode.svelte.d.ts +1 -1
  17. package/dist/components/nodes/IdeaNode.svelte +10 -8
  18. package/dist/components/nodes/IdeaNode.svelte.d.ts +8 -4
  19. package/dist/components/nodes/NotesNode.svelte +6 -4
  20. package/dist/components/nodes/NotesNode.svelte.d.ts +8 -4
  21. package/dist/components/nodes/SimpleNode.svelte +10 -8
  22. package/dist/components/nodes/SimpleNode.svelte.d.ts +4 -4
  23. package/dist/components/nodes/SquareNode.svelte +10 -8
  24. package/dist/components/nodes/SquareNode.svelte.d.ts +4 -4
  25. package/dist/components/nodes/TerminalNode.svelte +10 -8
  26. package/dist/components/nodes/TerminalNode.svelte.d.ts +4 -4
  27. package/dist/components/nodes/ToolNode.svelte +10 -8
  28. package/dist/components/nodes/ToolNode.svelte.d.ts +6 -6
  29. package/dist/components/nodes/WorkflowNode.svelte +7 -10
  30. package/dist/components/nodes/WorkflowNode.svelte.d.ts +1 -1
  31. package/dist/components/playground/PipelineKanbanView.svelte +2 -2
  32. package/dist/components/playground/PipelineTableView.svelte +2 -2
  33. package/dist/helpers/workflowEditorHelper.js +4 -5
  34. package/dist/registry/nodeComponentRegistry.d.ts +2 -1
  35. package/dist/services/dynamicSchemaService.d.ts +2 -2
  36. package/dist/services/dynamicSchemaService.js +8 -8
  37. package/dist/stores/playgroundStore.svelte.js +1 -1
  38. package/dist/stores/workflowStore.svelte.js +0 -18
  39. package/dist/types/index.d.ts +6 -7
  40. package/dist/utils/connections.js +6 -6
  41. package/dist/utils/nodeIds.d.ts +1 -1
  42. package/dist/utils/nodeIds.js +1 -1
  43. package/dist/utils/nodeSwap.js +3 -6
  44. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0-beta.5] - 2026-06-20
11
+
12
+ Fifth 2.0 beta, published under the npm `beta` dist-tag (`npm install @flowdrop/flowdrop@beta`). `latest` remains 1.15.0 until 2.0.0 GA. This release is a node-identity cleanup: node components now derive their instance id from the canonical `id` prop SvelteFlow already passes, dropping the duplicate `data.nodeId` copy and its load-time healing, and the node-_type_ entity id is renamed `metadata.id` → `metadata.node_type_id` to disambiguate it from the instance id. Six node components were also moved off the type-erasing `$props<T>()` onto a typed `interface Props`, which surfaced and fixed latent config-access errors in `ToolNode`.
13
+
14
+ ### Breaking Changes
15
+
16
+ - **Node-type entity id renamed `metadata.id` → `metadata.node_type_id`.** The node-type entity id collided conceptually with the node _instance_ id; it is now `metadata.node_type_id` to disambiguate the two. It is a server-owned wire field, so the library preserves the snake_case name as a non-opinionated passthrough — adapters, the command type-map, connection validation, and the dynamic-schema paths were all updated to match. Consumers reading `metadata.id` for the node type must read `metadata.node_type_id`.
17
+ - **Dropped the duplicate `data.nodeId` field.** Node components now derive their id from the canonical `id` prop that SvelteFlow already passes to every node, instead of a copy stored in `data.nodeId`. Handle ids, config-open callbacks, and status overlays read `id` directly. The field and its load-time "healing" are gone, so an intact graph can no longer look corrupted from a missing copy.
18
+
19
+ ### Changed (internal — no API change)
20
+
21
+ - **Six node components typed properly.** The node components that used `$props<T>()` — which returns `any` and silently disabled type-checking — now declare an `interface Props` with `let props: Props = $props()`. This surfaced and fixed latent config-access errors in `ToolNode`.
22
+
10
23
  ## [2.0.0-beta.4] - 2026-06-13
11
24
 
12
25
  Fourth 2.0 beta, published under the npm `beta` dist-tag (`npm install @flowdrop/flowdrop@beta`). `latest` remains 1.15.0 until 2.0.0 GA. This release is mostly an internal design-system consolidation pass — new shared `Button` / `IconButton` / `Input` / `Select` / `Textarea` primitives route every control through the `base.css` class system as the single source of truth, so themes (including Drafter) style fields and buttons consistently. User-visible changes are a navbar `end` snippet for embedding custom trailing controls, a new `--fd-control-radius` theming token, a range-slider fill fix, and opaque Drafter buttons. The archived Astro docs site is retired in favor of the Mintlify docs at `flowdrop.mintlify.app`.
@@ -46,7 +46,7 @@ export class WorkflowAdapter {
46
46
  * Add a node to a workflow
47
47
  */
48
48
  addNode(workflow, nodeType, position, config) {
49
- const metadata = this.nodeTypes.find((nt) => nt.id === nodeType);
49
+ const metadata = this.nodeTypes.find((nt) => nt.node_type_id === nodeType);
50
50
  if (!metadata) {
51
51
  throw new Error(`Node type '${nodeType}' not found`);
52
52
  }
@@ -248,7 +248,7 @@ export class WorkflowAdapter {
248
248
  description: svelteFlowWorkflow.description,
249
249
  nodes: svelteFlowWorkflow.nodes.map((node) => ({
250
250
  id: node.id,
251
- type: node.data.metadata.id,
251
+ type: node.data.metadata.node_type_id,
252
252
  position: node.position,
253
253
  data: {
254
254
  label: node.data.label,
@@ -282,8 +282,7 @@ export class WorkflowAdapter {
282
282
  data: {
283
283
  label: node.data.label,
284
284
  config: node.data.config,
285
- metadata: node.data.metadata,
286
- nodeId: node.id
285
+ metadata: node.data.metadata
287
286
  }
288
287
  })),
289
288
  edges: workflow.edges.map((edge) => ({
@@ -326,7 +325,7 @@ export class WorkflowAdapter {
326
325
  const nodeTypeCounts = new Map();
327
326
  cloned.nodes.forEach((node) => {
328
327
  const oldId = node.id;
329
- const nodeTypeId = node.data.metadata.id;
328
+ const nodeTypeId = node.data.metadata.node_type_id;
330
329
  // Get the current count for this node type
331
330
  const currentCount = nodeTypeCounts.get(nodeTypeId) || 0;
332
331
  const newCount = currentCount + 1;
@@ -266,7 +266,7 @@ export class AgentSpecAdapter {
266
266
  metadata: {
267
267
  'flowdrop:position': node.position,
268
268
  'flowdrop:node_id': node.id,
269
- 'flowdrop:node_type_id': node.data.metadata.id
269
+ 'flowdrop:node_type_id': node.data.metadata.node_type_id
270
270
  }
271
271
  };
272
272
  // Add type-specific attributes from config
@@ -362,7 +362,7 @@ export class AgentSpecAdapter {
362
362
  return ext;
363
363
  }
364
364
  // Infer from FlowDrop node type ID
365
- const fromId = extractComponentType(node.data.metadata.id);
365
+ const fromId = extractComponentType(node.data.metadata.node_type_id);
366
366
  if (fromId)
367
367
  return fromId;
368
368
  // Infer from FlowDrop visual type + category
@@ -404,7 +404,7 @@ export class AgentSpecAdapter {
404
404
  const nodeTypeId = asNode.metadata?.['flowdrop:node_type_id'] ||
405
405
  `${AGENTSPEC_NAMESPACE}.${asNode.component_type}`;
406
406
  const metadata = {
407
- id: nodeTypeId,
407
+ node_type_id: nodeTypeId,
408
408
  name: defaults.defaultName,
409
409
  type: defaults.visualType,
410
410
  description: asNode.description || defaults.defaultDescription,
@@ -30,7 +30,7 @@ function buildRegistry() {
30
30
  registry.set('start_node', {
31
31
  componentType: 'start_node',
32
32
  metadata: {
33
- id: `${AGENTSPEC_NS}.start_node`,
33
+ node_type_id: `${AGENTSPEC_NS}.start_node`,
34
34
  name: 'Start',
35
35
  type: 'terminal',
36
36
  description: 'Flow entry point. Defines the initial inputs for the flow.',
@@ -55,7 +55,7 @@ function buildRegistry() {
55
55
  registry.set('end_node', {
56
56
  componentType: 'end_node',
57
57
  metadata: {
58
- id: `${AGENTSPEC_NS}.end_node`,
58
+ node_type_id: `${AGENTSPEC_NS}.end_node`,
59
59
  name: 'End',
60
60
  type: 'terminal',
61
61
  description: 'Flow exit point. Defines the final outputs of the flow.',
@@ -80,7 +80,7 @@ function buildRegistry() {
80
80
  registry.set('llm_node', {
81
81
  componentType: 'llm_node',
82
82
  metadata: {
83
- id: `${AGENTSPEC_NS}.llm_node`,
83
+ node_type_id: `${AGENTSPEC_NS}.llm_node`,
84
84
  name: 'LLM',
85
85
  type: 'default',
86
86
  description: 'Generate text using a large language model with configurable prompts.',
@@ -145,7 +145,7 @@ function buildRegistry() {
145
145
  registry.set('branching_node', {
146
146
  componentType: 'branching_node',
147
147
  metadata: {
148
- id: `${AGENTSPEC_NS}.branching_node`,
148
+ node_type_id: `${AGENTSPEC_NS}.branching_node`,
149
149
  name: 'Branch',
150
150
  type: 'gateway',
151
151
  description: 'Route execution to different paths based on conditions.',
@@ -213,7 +213,7 @@ function buildRegistry() {
213
213
  registry.set('tool_node', {
214
214
  componentType: 'tool_node',
215
215
  metadata: {
216
- id: `${AGENTSPEC_NS}.tool_node`,
216
+ node_type_id: `${AGENTSPEC_NS}.tool_node`,
217
217
  name: 'Tool',
218
218
  type: 'tool',
219
219
  description: 'Execute a tool function with inputs and receive outputs.',
@@ -271,7 +271,7 @@ function buildRegistry() {
271
271
  registry.set('api_node', {
272
272
  componentType: 'api_node',
273
273
  metadata: {
274
- id: `${AGENTSPEC_NS}.api_node`,
274
+ node_type_id: `${AGENTSPEC_NS}.api_node`,
275
275
  name: 'API Call',
276
276
  type: 'default',
277
277
  description: 'Make an HTTP API call with configurable endpoint, method, and headers.',
@@ -342,7 +342,7 @@ function buildRegistry() {
342
342
  registry.set('agent_node', {
343
343
  componentType: 'agent_node',
344
344
  metadata: {
345
- id: `${AGENTSPEC_NS}.agent_node`,
345
+ node_type_id: `${AGENTSPEC_NS}.agent_node`,
346
346
  name: 'Agent',
347
347
  type: 'default',
348
348
  description: 'Run a multi-round agent conversation within the flow.',
@@ -400,7 +400,7 @@ function buildRegistry() {
400
400
  registry.set('flow_node', {
401
401
  componentType: 'flow_node',
402
402
  metadata: {
403
- id: `${AGENTSPEC_NS}.flow_node`,
403
+ node_type_id: `${AGENTSPEC_NS}.flow_node`,
404
404
  name: 'Sub-Flow',
405
405
  type: 'simple',
406
406
  description: 'Execute another flow as a sub-routine within this flow.',
@@ -451,7 +451,7 @@ function buildRegistry() {
451
451
  registry.set('map_node', {
452
452
  componentType: 'map_node',
453
453
  metadata: {
454
- id: `${AGENTSPEC_NS}.map_node`,
454
+ node_type_id: `${AGENTSPEC_NS}.map_node`,
455
455
  name: 'Map',
456
456
  type: 'default',
457
457
  description: 'Apply a flow or operation to each item in a collection (map-reduce).',
@@ -76,7 +76,7 @@ function executeAddNode(command, context) {
76
76
  };
77
77
  }
78
78
  const position = command.position ?? computeAutoPosition(workflow.nodes);
79
- const nodeId = generateNodeId(metadata.id, workflow.nodes);
79
+ const nodeId = generateNodeId(metadata.node_type_id, workflow.nodes);
80
80
  const config = extractConfigDefaults(metadata.configSchema);
81
81
  const node = {
82
82
  id: nodeId,
@@ -86,8 +86,7 @@ function executeAddNode(command, context) {
86
86
  data: {
87
87
  label: metadata.name,
88
88
  config,
89
- metadata,
90
- nodeId
89
+ metadata
91
90
  }
92
91
  };
93
92
  context.dispatch.addNode(node);
@@ -357,7 +356,7 @@ function executeInfo(command, context) {
357
356
  const resultData = {
358
357
  nodeId: shortId,
359
358
  label: node.data.label ?? metadata?.name ?? '',
360
- type: metadata?.id ? toShortTypeId(metadata.id) : '',
359
+ type: metadata?.node_type_id ? toShortTypeId(metadata.node_type_id) : '',
361
360
  position: node.position,
362
361
  config: node.data.config ?? {},
363
362
  inputs,
@@ -578,7 +577,7 @@ function executeListNodes(context) {
578
577
  const nodes = workflow.nodes.map((n) => ({
579
578
  nodeId: toShortId(n.id),
580
579
  label: n.data.label ?? n.data.metadata?.name ?? '',
581
- type: n.data.metadata?.id ? toShortTypeId(n.data.metadata.id) : ''
580
+ type: n.data.metadata?.node_type_id ? toShortTypeId(n.data.metadata.node_type_id) : ''
582
581
  }));
583
582
  const resultData = { nodes };
584
583
  return {
@@ -610,7 +609,7 @@ function executeListEdges(context) {
610
609
  }
611
610
  function executeListTypes(context) {
612
611
  const types = context.nodeTypes.map((m) => ({
613
- typeId: toShortTypeId(m.id),
612
+ typeId: toShortTypeId(m.node_type_id),
614
613
  name: m.name,
615
614
  category: m.category
616
615
  }));
@@ -24,18 +24,18 @@ export function buildTypeMap(nodeTypes) {
24
24
  const shortIdCounts = new Map();
25
25
  // First pass: register full IDs and count short ID occurrences
26
26
  for (const metadata of nodeTypes) {
27
- map.set(metadata.id, metadata);
28
- const dotIndex = metadata.id.indexOf('.');
27
+ map.set(metadata.node_type_id, metadata);
28
+ const dotIndex = metadata.node_type_id.indexOf('.');
29
29
  if (dotIndex !== -1) {
30
- const shortId = metadata.id.substring(dotIndex + 1);
30
+ const shortId = metadata.node_type_id.substring(dotIndex + 1);
31
31
  shortIdCounts.set(shortId, (shortIdCounts.get(shortId) ?? 0) + 1);
32
32
  }
33
33
  }
34
34
  // Second pass: register unique short IDs
35
35
  for (const metadata of nodeTypes) {
36
- const dotIndex = metadata.id.indexOf('.');
36
+ const dotIndex = metadata.node_type_id.indexOf('.');
37
37
  if (dotIndex !== -1) {
38
- const shortId = metadata.id.substring(dotIndex + 1);
38
+ const shortId = metadata.node_type_id.substring(dotIndex + 1);
39
39
  if (shortIdCounts.get(shortId) === 1) {
40
40
  map.set(shortId, metadata);
41
41
  }
@@ -412,8 +412,8 @@
412
412
  if (propNodes && propNodes.length > 0) {
413
413
  // Merge format-provided nodes with prop nodes (deduplicate by ID, props take priority)
414
414
  const formatNodes = fd.formats.getAllFormatNodes();
415
- const existingIds = new Set(propNodes.map((n) => n.id));
416
- const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
415
+ const existingIds = new Set(propNodes.map((n) => n.node_type_id));
416
+ const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.node_type_id));
417
417
  nodes = [...propNodes, ...uniqueFormatNodes];
418
418
  nodeTypesLoading = false;
419
419
  return;
@@ -429,8 +429,8 @@
429
429
 
430
430
  // Merge format-provided nodes with API nodes (deduplicate by ID, API takes priority)
431
431
  const formatNodes = fd.formats.getAllFormatNodes();
432
- const existingIds = new Set(fetchedNodes.map((n) => n.id));
433
- const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.id));
432
+ const existingIds = new Set(fetchedNodes.map((n) => n.node_type_id));
433
+ const uniqueFormatNodes = formatNodes.filter((n) => !existingIds.has(n.node_type_id));
434
434
  nodes = [...fetchedNodes, ...uniqueFormatNodes];
435
435
  error = null;
436
436
  nodeTypesLoading = false;
@@ -121,7 +121,7 @@
121
121
  // Create a new node instance from the node type
122
122
  const newNodeData = {
123
123
  type: 'node',
124
- nodeType: nodeType.id,
124
+ nodeType: nodeType.node_type_id,
125
125
  nodeData: {
126
126
  label: nodeType.name,
127
127
  config: extractConfigDefaults(nodeType.configSchema),
@@ -246,7 +246,7 @@
246
246
  </div>
247
247
  {:else}
248
248
  <div class="flowdrop-node-list">
249
- {#each filteredNodes as nodeType (nodeType.id)}
249
+ {#each filteredNodes as nodeType (nodeType.node_type_id)}
250
250
  <div
251
251
  class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
252
252
  draggable="true"
@@ -299,7 +299,7 @@
299
299
  {getCategoryDisplayName(category).toUpperCase()}
300
300
  </div>
301
301
  <div class="fd-sidebar-flat-list" role="list">
302
- {#each categoryNodes as nodeType (nodeType.id)}
302
+ {#each categoryNodes as nodeType (nodeType.node_type_id)}
303
303
  <div
304
304
  class="fd-sidebar-flat-item"
305
305
  role="listitem"
@@ -339,7 +339,7 @@
339
339
  </summary>
340
340
  <div class="flowdrop-details__content">
341
341
  <div class="flowdrop-node-list" role="list">
342
- {#each categoryNodes as nodeType (nodeType.id)}
342
+ {#each categoryNodes as nodeType (nodeType.node_type_id)}
343
343
  <div
344
344
  class="flowdrop-card flowdrop-card--compact flowdrop-node-item"
345
345
  role="listitem"
@@ -145,7 +145,7 @@
145
145
  {fd.categories.getLabel(category).toUpperCase()}
146
146
  </div>
147
147
  <div class="swap-picker__flat-list">
148
- {#each categoryNodes as nodeType (nodeType.id)}
148
+ {#each categoryNodes as nodeType (nodeType.node_type_id)}
149
149
  <button class="swap-picker__flat-item" onclick={() => onSelect(nodeType)}>
150
150
  <span
151
151
  class="swap-picker__flat-dot"
@@ -174,7 +174,7 @@
174
174
  </span>
175
175
  </div>
176
176
  <div class="swap-picker__category-items">
177
- {#each categoryNodes as nodeType (nodeType.id)}
177
+ {#each categoryNodes as nodeType (nodeType.node_type_id)}
178
178
  <button class="swap-picker__item" onclick={() => onSelect(nodeType)}>
179
179
  <span
180
180
  class="swap-picker__item-icon"
@@ -21,11 +21,12 @@
21
21
  let universalNodeEl: HTMLDivElement;
22
22
 
23
23
  let {
24
+ id,
24
25
  data,
25
26
  selected = false
26
27
  }: {
28
+ id: string;
27
29
  data: WorkflowNode['data'] & {
28
- nodeId?: string;
29
30
  onConfigOpen?: (node: { id: string; type: string; data: WorkflowNode['data'] }) => void;
30
31
  };
31
32
  selected?: boolean;
@@ -81,7 +82,7 @@
81
82
  if (event.key === 'Enter' || event.key === ' ') {
82
83
  event.preventDefault();
83
84
  data.onConfigOpen?.({
84
- id: data.nodeId ?? 'unknown',
85
+ id,
85
86
  type: resolvedComponentName,
86
87
  data
87
88
  });
@@ -165,13 +166,13 @@
165
166
  {#if nodeComponent}
166
167
  <!-- Svelte 5 dynamic component limitation; reactivity maintained via $derived -->
167
168
  {@const NodeComponent = nodeComponent}
168
- <NodeComponent {data} {selected} />
169
+ <NodeComponent {id} {data} {selected} />
169
170
  {/if}
170
171
 
171
172
  <!-- Status overlay - only show if there's meaningful status information -->
172
173
  {#if shouldShowStatus}
173
174
  <NodeStatusOverlay
174
- nodeId={data.nodeId ?? 'unknown'}
175
+ nodeId={id}
175
176
  {executionInfo}
176
177
  position={getStatusPosition()}
177
178
  size={getStatusSize()}
@@ -1,7 +1,7 @@
1
1
  import type { WorkflowNode } from '../types/index.js';
2
2
  type $$ComponentProps = {
3
+ id: string;
3
4
  data: WorkflowNode['data'] & {
4
- nodeId?: string;
5
5
  onConfigOpen?: (node: {
6
6
  id: string;
7
7
  type: string;
@@ -341,10 +341,10 @@
341
341
  if (nodeTypeContext !== null) {
342
342
  const prefix = nodeTypeContext.toLowerCase();
343
343
  return nodeTypes
344
- .filter((nt) => nt.id.toLowerCase().startsWith(prefix))
344
+ .filter((nt) => nt.node_type_id.toLowerCase().startsWith(prefix))
345
345
  .map((nt) => ({
346
- value: nt.id,
347
- label: nt.id,
346
+ value: nt.node_type_id,
347
+ label: nt.node_type_id,
348
348
  detail: `${nt.name} (${nt.category})`
349
349
  }))
350
350
  .slice(0, 50);
@@ -28,7 +28,6 @@
28
28
  label: string;
29
29
  config: ConfigValues;
30
30
  metadata: NodeMetadata;
31
- nodeId?: string;
32
31
  extensions?: NodeExtensions;
33
32
  onConfigOpen?: (node: {
34
33
  id: string;
@@ -38,18 +37,19 @@
38
37
  }
39
38
 
40
39
  interface Props {
40
+ id: string;
41
41
  data: AtomNodeData;
42
42
  selected?: boolean;
43
43
  isProcessing?: boolean;
44
44
  isError?: boolean;
45
45
  }
46
46
 
47
- let { data, selected, isProcessing, isError }: Props = $props();
47
+ let { id, data, selected, isProcessing, isError }: Props = $props();
48
48
 
49
49
  const fd = getInstance();
50
50
  const checker = fd.portCompatibility;
51
51
 
52
- const nodeId = $derived(data.nodeId ?? 'unknown');
52
+ const nodeId = $derived(id);
53
53
  const nodeType = $derived(data.metadata?.type ?? 'atom');
54
54
 
55
55
  // Instance extensions override node-type defaults.
@@ -3,7 +3,6 @@ interface AtomNodeData {
3
3
  label: string;
4
4
  config: ConfigValues;
5
5
  metadata: NodeMetadata;
6
- nodeId?: string;
7
6
  extensions?: NodeExtensions;
8
7
  onConfigOpen?: (node: {
9
8
  id: string;
@@ -16,6 +15,7 @@ interface AtomNodeData {
16
15
  }) => void;
17
16
  }
18
17
  interface Props {
18
+ id: string;
19
19
  data: AtomNodeData;
20
20
  selected?: boolean;
21
21
  isProcessing?: boolean;
@@ -23,8 +23,8 @@
23
23
  import { m } from '../../messages/index.js';
24
24
 
25
25
  interface Props {
26
+ id: string;
26
27
  data: WorkflowNode['data'] & {
27
- nodeId?: string;
28
28
  onConfigOpen?: (node: { id: string; type: string; data: WorkflowNode['data'] }) => void;
29
29
  };
30
30
  selected?: boolean;
@@ -83,7 +83,7 @@
83
83
  }
84
84
 
85
85
  // Check if port is connected
86
- const handleId = `${props.data.nodeId}-${type}-${port.id}`;
86
+ const handleId = `${props.id}-${type}-${port.id}`;
87
87
  return fd.workflow.connectedHandles.has(handleId);
88
88
  }
89
89
 
@@ -106,7 +106,7 @@
106
106
  }
107
107
 
108
108
  // Check if branch output is connected
109
- const handleId = `${props.data.nodeId}-output-${branchName}`;
109
+ const handleId = `${props.id}-output-${branchName}`;
110
110
  return fd.workflow.connectedHandles.has(handleId);
111
111
  }
112
112
 
@@ -127,7 +127,7 @@
127
127
  function handleNodeDoubleClick(): void {
128
128
  if (props.data.onConfigOpen) {
129
129
  props.data.onConfigOpen({
130
- id: props.data.nodeId || '',
130
+ id: props.id,
131
131
  type: 'gateway',
132
132
  data: props.data
133
133
  });
@@ -151,7 +151,7 @@
151
151
  class:flowdrop-workflow-node--selected={props.selected}
152
152
  ondblclick={handleNodeDoubleClick}
153
153
  aria-label={graph.gatewayNode({ title: displayTitle })}
154
- aria-describedby="node-description-{props.data.nodeId || 'unknown'}"
154
+ aria-describedby="node-description-{props.id}"
155
155
  >
156
156
  <!-- Node Header: expands in multiples of 10 (title row 40px + gap 10px + description 20px per line) -->
157
157
  <div class="flowdrop-workflow-node__header">
@@ -173,10 +173,7 @@
173
173
  </h3>
174
174
  </div>
175
175
  <!-- Node Description - line-height 20px so header grows in steps of 10 -->
176
- <p
177
- class="flowdrop-workflow-node__header-desc"
178
- id="node-description-{props.data.nodeId || 'unknown'}"
179
- >
176
+ <p class="flowdrop-workflow-node__header-desc" id="node-description-{props.id}">
180
177
  {displayDescription}
181
178
  </p>
182
179
  </div>
@@ -191,7 +188,7 @@
191
188
  <Handle
192
189
  type="target"
193
190
  position={Position.Left}
194
- id={`${props.data.nodeId}-input-${port.id}`}
191
+ id={`${props.id}-input-${port.id}`}
195
192
  class="flowdrop-workflow-node__handle"
196
193
  style="top: var(--fd-node-port-row-height); transform: translateY(-50%); --fd-handle-fill: {getDataTypeColorToken(
197
194
  checker,
@@ -280,7 +277,7 @@
280
277
  <Handle
281
278
  type="source"
282
279
  position={Position.Right}
283
- id={`${props.data.nodeId}-output-${branch.name}`}
280
+ id={`${props.id}-output-${branch.name}`}
284
281
  class={`flowdrop-workflow-node__handle ${isActive ? 'flowdrop-workflow-node__handle--active' : ''}`}
285
282
  style="top: var(--fd-node-port-row-height); transform: translateY(-50%); --fd-handle-fill: {isActive
286
283
  ? getDataTypeColorToken(checker, 'trigger')
@@ -1,7 +1,7 @@
1
1
  import type { WorkflowNode } from '../../types/index.js';
2
2
  interface Props {
3
+ id: string;
3
4
  data: WorkflowNode['data'] & {
4
- nodeId?: string;
5
5
  onConfigOpen?: (node: {
6
6
  id: string;
7
7
  type: string;
@@ -19,12 +19,12 @@
19
19
  * IdeaNode component props
20
20
  * Displays a card-style node for conceptual flow diagrams
21
21
  */
22
- const props = $props<{
22
+ interface Props {
23
+ id: string;
23
24
  data: {
24
25
  label: string;
25
26
  config: ConfigValues;
26
27
  metadata: NodeMetadata;
27
- nodeId?: string;
28
28
  onConfigOpen?: (node: {
29
29
  id: string;
30
30
  type: string;
@@ -34,7 +34,9 @@
34
34
  selected?: boolean;
35
35
  isProcessing?: boolean;
36
36
  isError?: boolean;
37
- }>();
37
+ }
38
+
39
+ let props: Props = $props();
38
40
 
39
41
  const checker = getInstance().portCompatibility;
40
42
 
@@ -101,7 +103,7 @@
101
103
  function openConfigSidebar(): void {
102
104
  if (props.data.onConfigOpen) {
103
105
  const nodeForConfig = {
104
- id: props.data.nodeId || 'unknown',
106
+ id: props.id,
105
107
  type: 'idea',
106
108
  data: props.data
107
109
  };
@@ -139,7 +141,7 @@
139
141
  checker,
140
142
  IDEA_DATA_TYPE
141
143
  )}; --fd-handle-border-color: var(--fd-handle-border); top: 40px; transform: translateY(-50%); z-index: 30;"
142
- id={`${props.data.nodeId}-input-left`}
144
+ id={`${props.id}-input-left`}
143
145
  />
144
146
  {/if}
145
147
 
@@ -152,7 +154,7 @@
152
154
  checker,
153
155
  IDEA_DATA_TYPE
154
156
  )}; --fd-handle-border-color: var(--fd-handle-border); left: 140px; transform: translateX(-50%); z-index: 30;"
155
- id={`${props.data.nodeId}-input-top`}
157
+ id={`${props.id}-input-top`}
156
158
  />
157
159
  {/if}
158
160
 
@@ -203,7 +205,7 @@
203
205
  checker,
204
206
  IDEA_DATA_TYPE
205
207
  )}; --fd-handle-border-color: var(--fd-handle-border); top: 40px; transform: translateY(-50%); z-index: 30;"
206
- id={`${props.data.nodeId}-output-right`}
208
+ id={`${props.id}-output-right`}
207
209
  />
208
210
  {/if}
209
211
 
@@ -216,7 +218,7 @@
216
218
  checker,
217
219
  IDEA_DATA_TYPE
218
220
  )}; --fd-handle-border-color: var(--fd-handle-border); left: 140px; transform: translateX(-50%); z-index: 30;"
219
- id={`${props.data.nodeId}-output-bottom`}
221
+ id={`${props.id}-output-bottom`}
220
222
  />
221
223
  {/if}
222
224
  </div>
@@ -1,10 +1,14 @@
1
1
  import type { ConfigValues, NodeMetadata } from '../../types/index.js';
2
- type $$ComponentProps = {
2
+ /**
3
+ * IdeaNode component props
4
+ * Displays a card-style node for conceptual flow diagrams
5
+ */
6
+ interface Props {
7
+ id: string;
3
8
  data: {
4
9
  label: string;
5
10
  config: ConfigValues;
6
11
  metadata: NodeMetadata;
7
- nodeId?: string;
8
12
  onConfigOpen?: (node: {
9
13
  id: string;
10
14
  type: string;
@@ -18,7 +22,7 @@ type $$ComponentProps = {
18
22
  selected?: boolean;
19
23
  isProcessing?: boolean;
20
24
  isError?: boolean;
21
- };
22
- declare const IdeaNode: import("svelte").Component<$$ComponentProps, {}, "">;
25
+ }
26
+ declare const IdeaNode: import("svelte").Component<Props, {}, "">;
23
27
  type IdeaNode = ReturnType<typeof IdeaNode>;
24
28
  export default IdeaNode;
@@ -9,12 +9,12 @@
9
9
  * NotesNode component props
10
10
  * Displays a styled note with markdown content
11
11
  */
12
- const props = $props<{
12
+ interface Props {
13
+ id: string;
13
14
  data: {
14
15
  label: string;
15
16
  config: ConfigValues;
16
17
  metadata: NodeMetadata;
17
- nodeId?: string;
18
18
  onConfigOpen?: (node: {
19
19
  id: string;
20
20
  type: string;
@@ -24,7 +24,9 @@
24
24
  selected?: boolean;
25
25
  isProcessing?: boolean;
26
26
  isError?: boolean;
27
- }>();
27
+ }
28
+
29
+ let props: Props = $props();
28
30
 
29
31
  // Hoist the notes branch — read for placeholder, every type name, processing,
30
32
  // error, and configure tooltip.
@@ -75,7 +77,7 @@
75
77
  function openConfigSidebar(): void {
76
78
  if (props.data.onConfigOpen) {
77
79
  const nodeForConfig = {
78
- id: props.data.nodeId || 'unknown',
80
+ id: props.id,
79
81
  type: 'note',
80
82
  data: props.data
81
83
  };
@@ -1,10 +1,14 @@
1
1
  import type { ConfigValues, NodeMetadata } from '../../types/index.js';
2
- type $$ComponentProps = {
2
+ /**
3
+ * NotesNode component props
4
+ * Displays a styled note with markdown content
5
+ */
6
+ interface Props {
7
+ id: string;
3
8
  data: {
4
9
  label: string;
5
10
  config: ConfigValues;
6
11
  metadata: NodeMetadata;
7
- nodeId?: string;
8
12
  onConfigOpen?: (node: {
9
13
  id: string;
10
14
  type: string;
@@ -18,7 +22,7 @@ type $$ComponentProps = {
18
22
  selected?: boolean;
19
23
  isProcessing?: boolean;
20
24
  isError?: boolean;
21
- };
22
- declare const NotesNode: import("svelte").Component<$$ComponentProps, {}, "">;
25
+ }
26
+ declare const NotesNode: import("svelte").Component<Props, {}, "">;
23
27
  type NotesNode = ReturnType<typeof NotesNode>;
24
28
  export default NotesNode;