@d34dman/flowdrop 0.0.17 → 0.0.18

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.
package/README.md CHANGED
@@ -272,6 +272,48 @@ Override CSS custom properties:
272
272
 
273
273
  ### Node Types
274
274
 
275
+ FlowDrop includes 7 built-in visual node types that control how nodes are rendered:
276
+
277
+ | Type | Description | Use Case |
278
+ | ---------- | ------------------------------------------------------- | ------------------------ |
279
+ | `default` | Full-featured workflow node with inputs/outputs display | Standard nodes |
280
+ | `simple` | Compact layout with header, icon, and description | Space-efficient nodes |
281
+ | `square` | Minimal square node showing only an icon | Icon-only representation |
282
+ | `tool` | Specialized node for agent tools with tool metadata | AI agent tools |
283
+ | `gateway` | Branching control flow with multiple output branches | Conditional logic |
284
+ | `terminal` | Circular node for workflow start/end/exit points | Workflow boundaries |
285
+ | `note` | Documentation note with markdown support | Comments/documentation |
286
+
287
+ #### Terminal Nodes
288
+
289
+ Terminal nodes are special circular nodes for workflow boundaries:
290
+
291
+ ```typescript
292
+ // Start node - output only
293
+ const startNode: NodeMetadata = {
294
+ id: 'workflow-start',
295
+ name: 'Start',
296
+ type: 'terminal',
297
+ tags: ['start'],
298
+ inputs: [],
299
+ outputs: [{ id: 'trigger', name: 'Go', type: 'output', dataType: 'trigger' }]
300
+ };
301
+
302
+ // End node - input only
303
+ const endNode: NodeMetadata = {
304
+ id: 'workflow-end',
305
+ name: 'End',
306
+ type: 'terminal',
307
+ tags: ['end'],
308
+ inputs: [{ id: 'done', name: 'Done', type: 'input', dataType: 'trigger' }],
309
+ outputs: []
310
+ };
311
+ ```
312
+
313
+ Terminal variants are auto-detected from `id`, `name`, or `tags` containing: `start`, `end`, `exit`, `abort`, `entry`, `finish`, `complete`.
314
+
315
+ #### Custom Node Types
316
+
275
317
  Define custom node types:
276
318
 
277
319
  ```typescript
@@ -0,0 +1,565 @@
1
+ <!--
2
+ Terminal Node Component
3
+ A circular node for workflow terminal points (start, end, exit/abort)
4
+ Configurable via metadata to display different variants:
5
+ - start: Green with play icon, output-only
6
+ - end: Gray with stop icon, input-only
7
+ - exit: Red with X icon, input-only (for abort/error exits)
8
+ Styled with BEM syntax
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import { Position, Handle } from "@xyflow/svelte";
13
+ import type { NodeConfig, NodeMetadata } from "../types/index.js";
14
+ import Icon from "@iconify/svelte";
15
+ import { getDataTypeColor } from "../utils/colors.js";
16
+
17
+ /**
18
+ * Terminal node variant types
19
+ */
20
+ type TerminalVariant = "start" | "end" | "exit";
21
+
22
+ /**
23
+ * Configuration for each terminal variant
24
+ */
25
+ interface VariantConfig {
26
+ /** Default icon for this variant */
27
+ icon: string;
28
+ /** Default color for this variant */
29
+ color: string;
30
+ /** Default label for this variant */
31
+ label: string;
32
+ /** Whether this variant has input handles */
33
+ hasInputs: boolean;
34
+ /** Whether this variant has output handles */
35
+ hasOutputs: boolean;
36
+ }
37
+
38
+ /**
39
+ * Variant configurations mapping
40
+ */
41
+ const VARIANT_CONFIGS: Record<TerminalVariant, VariantConfig> = {
42
+ start: {
43
+ icon: "mdi:play-circle",
44
+ color: "#10b981",
45
+ label: "Start",
46
+ hasInputs: false,
47
+ hasOutputs: true
48
+ },
49
+ end: {
50
+ icon: "mdi:stop-circle",
51
+ color: "#6b7280",
52
+ label: "End",
53
+ hasInputs: true,
54
+ hasOutputs: false
55
+ },
56
+ exit: {
57
+ icon: "mdi:close-circle",
58
+ color: "#ef4444",
59
+ label: "Exit",
60
+ hasInputs: true,
61
+ hasOutputs: false
62
+ }
63
+ };
64
+
65
+ const props = $props<{
66
+ data: {
67
+ label: string;
68
+ config: NodeConfig;
69
+ metadata: NodeMetadata;
70
+ nodeId?: string;
71
+ onConfigOpen?: (node: {
72
+ id: string;
73
+ type: string;
74
+ data: { label: string; config: NodeConfig; metadata: NodeMetadata };
75
+ }) => void;
76
+ };
77
+ selected?: boolean;
78
+ isProcessing?: boolean;
79
+ isError?: boolean;
80
+ }>();
81
+
82
+ /**
83
+ * Determine terminal variant from config or metadata
84
+ * Priority: config.variant > metadata tag detection > default to "start"
85
+ */
86
+ function getVariant(): TerminalVariant {
87
+ // Check config first
88
+ const configVariant = props.data.config?.variant as string | undefined;
89
+ if (configVariant && configVariant in VARIANT_CONFIGS) {
90
+ return configVariant as TerminalVariant;
91
+ }
92
+
93
+ // Check metadata tags for variant hints
94
+ const tags = props.data.metadata?.tags || [];
95
+ if (tags.includes("start") || tags.includes("entry")) {
96
+ return "start";
97
+ }
98
+ if (tags.includes("exit") || tags.includes("abort") || tags.includes("error")) {
99
+ return "exit";
100
+ }
101
+ if (tags.includes("end") || tags.includes("finish") || tags.includes("complete")) {
102
+ return "end";
103
+ }
104
+
105
+ // Check metadata id/name for hints
106
+ const idLower = (props.data.metadata?.id || "").toLowerCase();
107
+ const nameLower = (props.data.metadata?.name || "").toLowerCase();
108
+ if (idLower.includes("start") || nameLower.includes("start")) {
109
+ return "start";
110
+ }
111
+ if (idLower.includes("exit") || idLower.includes("abort") || nameLower.includes("exit") || nameLower.includes("abort")) {
112
+ return "exit";
113
+ }
114
+ if (idLower.includes("end") || nameLower.includes("end")) {
115
+ return "end";
116
+ }
117
+
118
+ // Default to start
119
+ return "start";
120
+ }
121
+
122
+ let variant = $derived(getVariant());
123
+
124
+ /**
125
+ * Get current variant configuration
126
+ */
127
+ let variantConfig = $derived(VARIANT_CONFIGS[variant]);
128
+
129
+ /**
130
+ * Get icon - prioritize metadata/config over variant default
131
+ */
132
+ let terminalIcon = $derived(
133
+ (props.data.metadata?.icon as string) ||
134
+ (props.data.config?.icon as string) ||
135
+ variantConfig.icon
136
+ );
137
+
138
+ /**
139
+ * Get color - prioritize metadata/config over variant default
140
+ */
141
+ let terminalColor = $derived(
142
+ (props.data.metadata?.color as string) ||
143
+ (props.data.config?.color as string) ||
144
+ variantConfig.color
145
+ );
146
+
147
+ /**
148
+ * Get display label
149
+ */
150
+ let displayLabel = $derived(
151
+ props.data.label ||
152
+ props.data.metadata?.name ||
153
+ variantConfig.label
154
+ );
155
+
156
+ /**
157
+ * Check if metadata explicitly defines inputs (including empty array)
158
+ * This allows API to control ports:
159
+ * - undefined: use variant default
160
+ * - []: explicitly no inputs
161
+ * - [{...}]: use these inputs
162
+ */
163
+ let hasExplicitInputs = $derived(Array.isArray(props.data.metadata?.inputs));
164
+
165
+ /**
166
+ * Check if metadata explicitly defines outputs (including empty array)
167
+ */
168
+ let hasExplicitOutputs = $derived(Array.isArray(props.data.metadata?.outputs));
169
+
170
+ /**
171
+ * Default trigger input port for end/exit nodes
172
+ */
173
+ const DEFAULT_INPUT_PORT = {
174
+ id: "trigger",
175
+ name: "Trigger",
176
+ type: "input" as const,
177
+ dataType: "trigger",
178
+ description: "Workflow trigger input"
179
+ };
180
+
181
+ /**
182
+ * Default trigger output port for start nodes
183
+ */
184
+ const DEFAULT_OUTPUT_PORT = {
185
+ id: "trigger",
186
+ name: "Trigger",
187
+ type: "output" as const,
188
+ dataType: "trigger",
189
+ description: "Workflow trigger output"
190
+ };
191
+
192
+ /**
193
+ * Get input ports from metadata or create default trigger input
194
+ * Priority:
195
+ * 1. If metadata.inputs is defined (even empty array), use it exactly
196
+ * 2. Otherwise, use variant default (trigger port for end/exit)
197
+ */
198
+ let inputPorts = $derived(
199
+ hasExplicitInputs
200
+ ? props.data.metadata.inputs
201
+ : variantConfig.hasInputs
202
+ ? [DEFAULT_INPUT_PORT]
203
+ : []
204
+ );
205
+
206
+ /**
207
+ * Get output ports from metadata or create default trigger output
208
+ * Priority:
209
+ * 1. If metadata.outputs is defined (even empty array), use it exactly
210
+ * 2. Otherwise, use variant default (trigger port for start)
211
+ */
212
+ let outputPorts = $derived(
213
+ hasExplicitOutputs
214
+ ? props.data.metadata.outputs
215
+ : variantConfig.hasOutputs
216
+ ? [DEFAULT_OUTPUT_PORT]
217
+ : []
218
+ );
219
+
220
+ /**
221
+ * Determine if we should show inputs based on ports
222
+ */
223
+ let showInputs = $derived(inputPorts.length > 0);
224
+
225
+ /**
226
+ * Determine if we should show outputs based on ports
227
+ */
228
+ let showOutputs = $derived(outputPorts.length > 0);
229
+
230
+ /**
231
+ * Handle configuration sidebar - using global ConfigSidebar
232
+ */
233
+ function openConfigSidebar(): void {
234
+ if (props.data.onConfigOpen) {
235
+ const nodeForConfig = {
236
+ id: props.data.nodeId || "unknown",
237
+ type: "terminal",
238
+ data: props.data
239
+ };
240
+ props.data.onConfigOpen(nodeForConfig);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Handle double-click to open config
246
+ */
247
+ function handleDoubleClick(): void {
248
+ openConfigSidebar();
249
+ }
250
+
251
+ /**
252
+ * Handle single click - only handle selection
253
+ */
254
+ function handleClick(): void {
255
+ // Node selection is handled by Svelte Flow
256
+ }
257
+
258
+ /**
259
+ * Handle keyboard events for accessibility
260
+ */
261
+ function handleKeydown(event: KeyboardEvent): void {
262
+ if (event.key === "Enter" || event.key === " ") {
263
+ event.preventDefault();
264
+ handleDoubleClick();
265
+ }
266
+ }
267
+ </script>
268
+
269
+ <!-- Terminal Node -->
270
+ <div
271
+ class="flowdrop-terminal-node"
272
+ class:flowdrop-terminal-node--selected={props.selected}
273
+ class:flowdrop-terminal-node--processing={props.isProcessing}
274
+ class:flowdrop-terminal-node--error={props.isError}
275
+ class:flowdrop-terminal-node--start={variant === "start"}
276
+ class:flowdrop-terminal-node--end={variant === "end"}
277
+ class:flowdrop-terminal-node--exit={variant === "exit"}
278
+ style="--terminal-color: {terminalColor};"
279
+ onclick={handleClick}
280
+ ondblclick={handleDoubleClick}
281
+ onkeydown={handleKeydown}
282
+ role="button"
283
+ tabindex="0"
284
+ aria-label="{variant} node: {displayLabel}"
285
+ >
286
+ <!-- Config button at top -->
287
+ <button
288
+ class="flowdrop-terminal-node__config-btn"
289
+ onclick={openConfigSidebar}
290
+ title="Configure node"
291
+ >
292
+ <Icon icon="mdi:cog" />
293
+ </button>
294
+
295
+ <!-- Circle wrapper for proper handle positioning -->
296
+ <div class="flowdrop-terminal-node__circle-wrapper">
297
+ <!-- Input Handles (for end/exit variants) -->
298
+ {#if showInputs}
299
+ {#each inputPorts as port (port.id)}
300
+ <Handle
301
+ type="target"
302
+ position={Position.Left}
303
+ style="background-color: {getDataTypeColor(port.dataType)}; border-color: #ffffff; top: 50%; transform: translateY(-50%); z-index: 30;"
304
+ id={`${props.data.nodeId}-input-${port.id}`}
305
+ />
306
+ {/each}
307
+ {/if}
308
+
309
+ <!-- Circular content with icon -->
310
+ <div class="flowdrop-terminal-node__content">
311
+ <Icon
312
+ icon={terminalIcon}
313
+ class="flowdrop-terminal-node__icon"
314
+ style="color: {terminalColor}; font-size: 2.5rem;"
315
+ />
316
+ </div>
317
+
318
+ <!-- Output Handles (for start variant) -->
319
+ {#if showOutputs}
320
+ {#each outputPorts as port (port.id)}
321
+ <Handle
322
+ type="source"
323
+ position={Position.Right}
324
+ id={`${props.data.nodeId}-output-${port.id}`}
325
+ style="background-color: {getDataTypeColor(port.dataType)}; border-color: #ffffff; top: 50%; transform: translateY(-50%); z-index: 30;"
326
+ />
327
+ {/each}
328
+ {/if}
329
+ </div>
330
+
331
+ <!-- Label below the circle -->
332
+ <div class="flowdrop-terminal-node__label">
333
+ {displayLabel}
334
+ </div>
335
+
336
+ <!-- Processing indicator -->
337
+ {#if props.isProcessing}
338
+ <div class="flowdrop-terminal-node__processing">
339
+ <div class="flowdrop-terminal-node__spinner"></div>
340
+ </div>
341
+ {/if}
342
+
343
+ <!-- Error indicator -->
344
+ {#if props.isError}
345
+ <div class="flowdrop-terminal-node__error">
346
+ <Icon icon="mdi:alert-circle" class="flowdrop-terminal-node__error-icon" />
347
+ </div>
348
+ {/if}
349
+ </div>
350
+
351
+ <style>
352
+ .flowdrop-terminal-node {
353
+ position: relative;
354
+ display: flex;
355
+ flex-direction: column;
356
+ align-items: center;
357
+ gap: 0.5rem;
358
+ cursor: pointer;
359
+ transition: all 0.2s ease-in-out;
360
+ z-index: 10;
361
+ }
362
+
363
+ /* Wrapper for circle and handles - ensures handles are vertically centered to circle */
364
+ .flowdrop-terminal-node__circle-wrapper {
365
+ position: relative;
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ }
370
+
371
+ .flowdrop-terminal-node__content {
372
+ width: 72px;
373
+ height: 72px;
374
+ background-color: #ffffff;
375
+ border: 3px solid var(--terminal-color, #6b7280);
376
+ border-radius: 50%;
377
+ display: flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
381
+ transition: all 0.2s ease-in-out;
382
+ }
383
+
384
+ .flowdrop-terminal-node:hover .flowdrop-terminal-node__content {
385
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
386
+ transform: scale(1.05);
387
+ }
388
+
389
+ .flowdrop-terminal-node--selected .flowdrop-terminal-node__content {
390
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 0 0 3px rgba(59, 130, 246, 0.5);
391
+ border-color: #3b82f6;
392
+ }
393
+
394
+ .flowdrop-terminal-node--processing .flowdrop-terminal-node__content {
395
+ opacity: 0.7;
396
+ }
397
+
398
+ .flowdrop-terminal-node--error .flowdrop-terminal-node__content {
399
+ border-color: #ef4444 !important;
400
+ background-color: #fef2f2 !important;
401
+ }
402
+
403
+ /* Variant-specific glow effects */
404
+ .flowdrop-terminal-node--start .flowdrop-terminal-node__content {
405
+ box-shadow: 0 4px 6px -1px rgba(16, 185, 129, 0.2), 0 2px 4px -1px rgba(16, 185, 129, 0.1);
406
+ }
407
+
408
+ .flowdrop-terminal-node--start:hover .flowdrop-terminal-node__content {
409
+ box-shadow: 0 10px 15px -3px rgba(16, 185, 129, 0.3), 0 4px 6px -2px rgba(16, 185, 129, 0.15);
410
+ }
411
+
412
+ .flowdrop-terminal-node--exit .flowdrop-terminal-node__content {
413
+ box-shadow: 0 4px 6px -1px rgba(239, 68, 68, 0.2), 0 2px 4px -1px rgba(239, 68, 68, 0.1);
414
+ }
415
+
416
+ .flowdrop-terminal-node--exit:hover .flowdrop-terminal-node__content {
417
+ box-shadow: 0 10px 15px -3px rgba(239, 68, 68, 0.3), 0 4px 6px -2px rgba(239, 68, 68, 0.15);
418
+ }
419
+
420
+ :global(.flowdrop-terminal-node__icon) {
421
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
422
+ }
423
+
424
+ .flowdrop-terminal-node__label {
425
+ font-size: 0.75rem;
426
+ font-weight: 500;
427
+ color: #374151;
428
+ text-align: center;
429
+ max-width: 100px;
430
+ overflow: hidden;
431
+ text-overflow: ellipsis;
432
+ white-space: nowrap;
433
+ background-color: rgba(255, 255, 255, 0.9);
434
+ padding: 0.125rem 0.5rem;
435
+ border-radius: 0.25rem;
436
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
437
+ }
438
+
439
+ .flowdrop-terminal-node__processing {
440
+ position: absolute;
441
+ top: 1.5rem;
442
+ right: 0;
443
+ }
444
+
445
+ .flowdrop-terminal-node__spinner {
446
+ width: 14px;
447
+ height: 14px;
448
+ border: 2px solid rgba(255, 255, 255, 0.3);
449
+ border-top: 2px solid var(--terminal-color, #6b7280);
450
+ border-radius: 50%;
451
+ animation: spin 1s linear infinite;
452
+ }
453
+
454
+ .flowdrop-terminal-node__error {
455
+ position: absolute;
456
+ top: 1.5rem;
457
+ right: 0;
458
+ color: #ef4444;
459
+ }
460
+
461
+ :global(.flowdrop-terminal-node__error-icon) {
462
+ width: 14px;
463
+ height: 14px;
464
+ }
465
+
466
+ /* Config button positioned at top center */
467
+ .flowdrop-terminal-node__config-btn {
468
+ position: absolute;
469
+ top: -1.5rem;
470
+ left: 50%;
471
+ transform: translateX(-50%);
472
+ width: 1.5rem;
473
+ height: 1.5rem;
474
+ background-color: rgba(255, 255, 255, 0.95);
475
+ border: 1px solid #e5e7eb;
476
+ border-radius: 50%;
477
+ color: #6b7280;
478
+ cursor: pointer;
479
+ display: flex;
480
+ align-items: center;
481
+ justify-content: center;
482
+ opacity: 0;
483
+ transition: all 0.2s ease-in-out;
484
+ backdrop-filter: blur(4px);
485
+ z-index: 15;
486
+ font-size: 0.75rem;
487
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
488
+ }
489
+
490
+ .flowdrop-terminal-node:hover .flowdrop-terminal-node__config-btn {
491
+ opacity: 1;
492
+ }
493
+
494
+ .flowdrop-terminal-node__config-btn:hover {
495
+ background-color: #f9fafb;
496
+ border-color: #d1d5db;
497
+ color: #374151;
498
+ transform: translateX(-50%) scale(1.1);
499
+ }
500
+
501
+ @keyframes spin {
502
+ to {
503
+ transform: rotate(360deg);
504
+ }
505
+ }
506
+
507
+ /* Handle styles - positioned relative to circle wrapper */
508
+ :global(.flowdrop-terminal-node__circle-wrapper .svelte-flow__handle) {
509
+ width: 16px !important;
510
+ height: 16px !important;
511
+ border-radius: 50% !important;
512
+ border: 2px solid #ffffff !important;
513
+ transition: all 0.2s ease-in-out !important;
514
+ cursor: pointer !important;
515
+ z-index: 20 !important;
516
+ pointer-events: auto !important;
517
+ }
518
+
519
+ :global(.flowdrop-terminal-node__circle-wrapper .svelte-flow__handle-left) {
520
+ left: -8px !important;
521
+ }
522
+
523
+ :global(.flowdrop-terminal-node__circle-wrapper .svelte-flow__handle-right) {
524
+ right: -8px !important;
525
+ }
526
+
527
+ :global(.flowdrop-terminal-node__circle-wrapper .svelte-flow__handle:hover) {
528
+ transform: translateY(-50%) scale(1.2) !important;
529
+ }
530
+
531
+ :global(.flowdrop-terminal-node__circle-wrapper .svelte-flow__handle:focus) {
532
+ outline: 2px solid #3b82f6 !important;
533
+ outline-offset: 2px !important;
534
+ }
535
+
536
+ /* Also keep node-level handle styles for fallback */
537
+ :global(.svelte-flow__node-terminal .svelte-flow__handle) {
538
+ width: 16px !important;
539
+ height: 16px !important;
540
+ border-radius: 50% !important;
541
+ border: 2px solid #ffffff !important;
542
+ transition: all 0.2s ease-in-out !important;
543
+ cursor: pointer !important;
544
+ z-index: 20 !important;
545
+ pointer-events: auto !important;
546
+ }
547
+
548
+ :global(.svelte-flow__node-terminal .svelte-flow__handle-left) {
549
+ left: -8px !important;
550
+ }
551
+
552
+ :global(.svelte-flow__node-terminal .svelte-flow__handle-right) {
553
+ right: -8px !important;
554
+ }
555
+
556
+ :global(.svelte-flow__node-terminal .svelte-flow__handle:hover) {
557
+ transform: translateY(-50%) scale(1.2) !important;
558
+ }
559
+
560
+ :global(.svelte-flow__node-terminal .svelte-flow__handle:focus) {
561
+ outline: 2px solid #3b82f6 !important;
562
+ outline-offset: 2px !important;
563
+ }
564
+ </style>
565
+
@@ -0,0 +1,24 @@
1
+ import type { NodeConfig, NodeMetadata } from "../types/index.js";
2
+ type $$ComponentProps = {
3
+ data: {
4
+ label: string;
5
+ config: NodeConfig;
6
+ metadata: NodeMetadata;
7
+ nodeId?: string;
8
+ onConfigOpen?: (node: {
9
+ id: string;
10
+ type: string;
11
+ data: {
12
+ label: string;
13
+ config: NodeConfig;
14
+ metadata: NodeMetadata;
15
+ };
16
+ }) => void;
17
+ };
18
+ selected?: boolean;
19
+ isProcessing?: boolean;
20
+ isError?: boolean;
21
+ };
22
+ declare const TerminalNode: import("svelte").Component<$$ComponentProps, {}, "">;
23
+ type TerminalNode = ReturnType<typeof TerminalNode>;
24
+ export default TerminalNode;
@@ -70,7 +70,7 @@ export declare function getBuiltinTypes(): string[];
70
70
  * Type for built-in node types.
71
71
  * Use this when you specifically need a built-in type.
72
72
  */
73
- export type BuiltinNodeType = 'workflowNode' | 'simple' | 'square' | 'tool' | 'gateway' | 'note';
73
+ export type BuiltinNodeType = 'workflowNode' | 'simple' | 'square' | 'tool' | 'gateway' | 'note' | 'terminal';
74
74
  /**
75
75
  * Array of built-in type strings for runtime validation.
76
76
  */
@@ -12,6 +12,7 @@ import SquareNode from '../components/SquareNode.svelte';
12
12
  import ToolNode from '../components/ToolNode.svelte';
13
13
  import GatewayNode from '../components/GatewayNode.svelte';
14
14
  import NotesNode from '../components/NotesNode.svelte';
15
+ import TerminalNode from '../components/TerminalNode.svelte';
15
16
  /**
16
17
  * Source identifier for built-in FlowDrop components
17
18
  */
@@ -86,6 +87,17 @@ export const BUILTIN_NODE_COMPONENTS = [
86
87
  source: FLOWDROP_SOURCE,
87
88
  statusPosition: 'bottom-right',
88
89
  statusSize: 'sm'
90
+ },
91
+ {
92
+ type: 'terminal',
93
+ displayName: 'Terminal (Start/End/Exit)',
94
+ description: 'Circular terminal node for workflow start, end, or exit points',
95
+ component: TerminalNode,
96
+ icon: 'mdi:circle-double',
97
+ category: 'functional',
98
+ source: FLOWDROP_SOURCE,
99
+ statusPosition: 'top-right',
100
+ statusSize: 'sm'
89
101
  }
90
102
  ];
91
103
  /**
@@ -175,7 +187,8 @@ export const BUILTIN_NODE_TYPES = [
175
187
  'square',
176
188
  'tool',
177
189
  'gateway',
178
- 'note'
190
+ 'note',
191
+ 'terminal'
179
192
  ];
180
193
  // Auto-register built-ins when this module is imported
181
194
  registerBuiltinNodes();
@@ -74,12 +74,12 @@ export interface NodePort {
74
74
  * Built-in node types for explicit component rendering.
75
75
  * These are the node types that ship with FlowDrop.
76
76
  */
77
- export type BuiltinNodeType = 'note' | 'simple' | 'square' | 'tool' | 'gateway' | 'default';
77
+ export type BuiltinNodeType = 'note' | 'simple' | 'square' | 'tool' | 'gateway' | 'terminal' | 'default';
78
78
  /**
79
79
  * Node type for component rendering.
80
80
  * Includes built-in types and allows custom registered types.
81
81
  *
82
- * Built-in types: note, simple, square, tool, gateway, default
82
+ * Built-in types: note, simple, square, tool, gateway, terminal, default
83
83
  * Custom types: Any string registered via nodeComponentRegistry
84
84
  *
85
85
  * @example
@@ -21,6 +21,7 @@ const NODE_TYPE_TO_COMPONENT_MAP = {
21
21
  square: 'square',
22
22
  tool: 'tool',
23
23
  gateway: 'gateway',
24
+ terminal: 'terminal',
24
25
  default: 'workflowNode'
25
26
  };
26
27
  /**
@@ -32,6 +33,7 @@ const TYPE_DISPLAY_NAMES = {
32
33
  square: 'Square (geometric layout)',
33
34
  tool: 'Tool (specialized for agent tools)',
34
35
  gateway: 'Gateway (branching control flow)',
36
+ terminal: 'Terminal (start/end/exit)',
35
37
  default: 'Default (standard workflow node)'
36
38
  };
37
39
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@d34dman/flowdrop",
3
3
  "license": "MIT",
4
4
  "private": false,
5
- "version": "0.0.17",
5
+ "version": "0.0.18",
6
6
  "scripts": {
7
7
  "dev": "vite dev",
8
8
  "build": "vite build && npm run prepack",