@flowdrop/flowdrop 1.0.0 → 1.0.1

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.
@@ -0,0 +1,161 @@
1
+ <script module>
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import EdgeDecorator from '../../lib/stories/EdgeDecorator.svelte';
4
+ import { createSampleNodeData } from '../../lib/stories/utils.js';
5
+ import { EDGE_MARKER_SIZES } from '../../lib/config/constants.js';
6
+
7
+ const { Story } = defineMeta({
8
+ title: 'Edges/FlowDropEdge',
9
+ tags: ['autodocs'],
10
+ parameters: {
11
+ layout: 'centered'
12
+ }
13
+ });
14
+
15
+ const sourceNode = createSampleNodeData({
16
+ label: 'Text Input',
17
+ metadata: {
18
+ id: 'text_input',
19
+ name: 'Text Input',
20
+ description: 'Simple text input',
21
+ category: 'inputs',
22
+ version: '1.0.0',
23
+ type: 'simple',
24
+ icon: 'mdi:text',
25
+ color: '#22c55e',
26
+ inputs: [],
27
+ outputs: [
28
+ { id: 'output', name: 'Output', type: 'output', dataType: 'string', required: false }
29
+ ]
30
+ }
31
+ });
32
+
33
+ const targetNode = createSampleNodeData({
34
+ label: 'Text Output',
35
+ metadata: {
36
+ id: 'text_output',
37
+ name: 'Text Output',
38
+ description: 'Display text output',
39
+ category: 'outputs',
40
+ version: '1.0.0',
41
+ type: 'simple',
42
+ icon: 'mdi:text-box',
43
+ color: '#ef4444',
44
+ inputs: [
45
+ { id: 'input', name: 'Input', type: 'input', dataType: 'string', required: false }
46
+ ],
47
+ outputs: []
48
+ }
49
+ });
50
+
51
+ const triggerSource = createSampleNodeData({
52
+ label: 'Trigger Source',
53
+ metadata: {
54
+ id: 'trigger_source',
55
+ name: 'Trigger Source',
56
+ description: 'Emits a trigger signal',
57
+ category: 'processing',
58
+ version: '1.0.0',
59
+ type: 'simple',
60
+ icon: 'mdi:play',
61
+ color: '#3b82f6',
62
+ inputs: [],
63
+ outputs: [
64
+ { id: 'trigger', name: 'Trigger', type: 'output', dataType: 'trigger', required: false }
65
+ ]
66
+ }
67
+ });
68
+
69
+ const triggerTarget = createSampleNodeData({
70
+ label: 'Trigger Target',
71
+ metadata: {
72
+ id: 'trigger_target',
73
+ name: 'Trigger Target',
74
+ description: 'Receives a trigger signal',
75
+ category: 'processing',
76
+ version: '1.0.0',
77
+ type: 'simple',
78
+ icon: 'mdi:lightning-bolt',
79
+ color: '#8b5cf6',
80
+ inputs: [
81
+ { id: 'trigger', name: 'Trigger', type: 'input', dataType: 'trigger', required: false }
82
+ ],
83
+ outputs: []
84
+ }
85
+ });
86
+
87
+ const toolSource = createSampleNodeData({
88
+ label: 'Tool Provider',
89
+ metadata: {
90
+ id: 'tool_provider',
91
+ name: 'Tool Provider',
92
+ description: 'Provides a tool',
93
+ category: 'processing',
94
+ version: '1.0.0',
95
+ type: 'simple',
96
+ icon: 'mdi:wrench',
97
+ color: '#f59e0b',
98
+ inputs: [],
99
+ outputs: [
100
+ { id: 'tool', name: 'Tool', type: 'output', dataType: 'tool', required: false }
101
+ ]
102
+ }
103
+ });
104
+
105
+ const toolTarget = createSampleNodeData({
106
+ label: 'Tool Consumer',
107
+ metadata: {
108
+ id: 'tool_consumer',
109
+ name: 'Tool Consumer',
110
+ description: 'Consumes a tool',
111
+ category: 'processing',
112
+ version: '1.0.0',
113
+ type: 'simple',
114
+ icon: 'mdi:cog',
115
+ color: '#f59e0b',
116
+ inputs: [
117
+ { id: 'tool', name: 'Tool', type: 'input', dataType: 'tool', required: false }
118
+ ],
119
+ outputs: []
120
+ }
121
+ });
122
+ </script>
123
+
124
+ <Story name="Data Edge">
125
+ <EdgeDecorator
126
+ sourceData={sourceNode}
127
+ targetData={targetNode}
128
+ edgeStyle="stroke: var(--fd-edge-data, #64748b);"
129
+ edgeClass="flowdrop--edge--data"
130
+ edgeMarkerColor="#64748b"
131
+ edgeMarkerSize={EDGE_MARKER_SIZES.data}
132
+ sourceHandleId="output"
133
+ targetHandleId="input"
134
+ />
135
+ </Story>
136
+
137
+ <Story name="Trigger Edge">
138
+ <EdgeDecorator
139
+ sourceData={triggerSource}
140
+ targetData={triggerTarget}
141
+ edgeStyle="stroke: var(--fd-edge-trigger, #1e293b); stroke-width: 2px;"
142
+ edgeClass="flowdrop--edge--trigger"
143
+ edgeMarkerColor="#1e293b"
144
+ edgeMarkerSize={EDGE_MARKER_SIZES.trigger}
145
+ sourceHandleId="trigger"
146
+ targetHandleId="trigger"
147
+ />
148
+ </Story>
149
+
150
+ <Story name="Tool Edge">
151
+ <EdgeDecorator
152
+ sourceData={toolSource}
153
+ targetData={toolTarget}
154
+ edgeStyle="stroke: var(--fd-edge-tool, #f59e0b); stroke-dasharray: 5 3;"
155
+ edgeClass="flowdrop--edge--tool"
156
+ edgeMarkerColor="#f59e0b"
157
+ edgeMarkerSize={EDGE_MARKER_SIZES.tool}
158
+ sourceHandleId="tool"
159
+ targetHandleId="tool"
160
+ />
161
+ </Story>
@@ -0,0 +1,26 @@
1
+ export default FlowDropEdge;
2
+ type FlowDropEdge = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const FlowDropEdge: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -0,0 +1,168 @@
1
+ <!--
2
+ FlowDropEdge Component
3
+ Custom bezier edge that draws its own arrowhead so the line stroke ends
4
+ at the arrow base instead of poking through to the tip.
5
+
6
+ Approach:
7
+ 1. Compute the full bezier path (xyflow's getBezierPath)
8
+ 2. Parse the SVG path to extract the cubic bezier control points
9
+ 3. Evaluate the curve near the end to get the true visual tangent
10
+ 4. Shorten the path along that tangent and draw the arrowhead at the target
11
+ -->
12
+
13
+ <script lang="ts">
14
+ import { getBezierPath } from '@xyflow/svelte';
15
+ import { BaseEdge } from '@xyflow/svelte';
16
+ import type { BezierEdgeProps } from '@xyflow/svelte';
17
+ import { ARROW_LENGTH_PX, ARROW_HALF_WIDTH_PX } from '../config/constants.js';
18
+
19
+ let {
20
+ id,
21
+ interactionWidth,
22
+ label,
23
+ labelStyle,
24
+ markerEnd: _markerEnd,
25
+ markerStart,
26
+ pathOptions,
27
+ sourcePosition,
28
+ sourceX,
29
+ sourceY,
30
+ style,
31
+ targetPosition,
32
+ targetX,
33
+ targetY
34
+ }: BezierEdgeProps = $props();
35
+
36
+ /**
37
+ * Extract stroke color from the edge's inline style for the arrowhead fill.
38
+ */
39
+ let strokeColor = $derived.by(() => {
40
+ if (!style) return 'var(--fd-edge-data, #64748b)';
41
+ const match = style.match(/stroke:\s*([^;]+)/);
42
+ return match ? match[1].trim() : 'var(--fd-edge-data, #64748b)';
43
+ });
44
+
45
+ /**
46
+ * Evaluate a cubic bezier at parameter t.
47
+ * P(t) = (1-t)^3 * P0 + 3(1-t)^2 * t * P1 + 3(1-t) * t^2 * P2 + t^3 * P3
48
+ */
49
+ function bezierAt(
50
+ p0x: number,
51
+ p0y: number,
52
+ p1x: number,
53
+ p1y: number,
54
+ p2x: number,
55
+ p2y: number,
56
+ p3x: number,
57
+ p3y: number,
58
+ t: number
59
+ ): { x: number; y: number } {
60
+ const u = 1 - t;
61
+ const uu = u * u;
62
+ const uuu = uu * u;
63
+ const tt = t * t;
64
+ const ttt = tt * t;
65
+ return {
66
+ x: uuu * p0x + 3 * uu * t * p1x + 3 * u * tt * p2x + ttt * p3x,
67
+ y: uuu * p0y + 3 * uu * t * p1y + 3 * u * tt * p2y + ttt * p3y
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Parse the SVG cubic bezier path "M x0,y0 C x1,y1 x2,y2 x3,y3"
73
+ * into the four control points.
74
+ */
75
+ function parseCubicBezier(d: string) {
76
+ const nums = d.match(/-?[\d.]+/g)?.map(Number);
77
+ if (!nums || nums.length < 8) return null;
78
+ return {
79
+ p0x: nums[0],
80
+ p0y: nums[1],
81
+ p1x: nums[2],
82
+ p1y: nums[3],
83
+ p2x: nums[4],
84
+ p2y: nums[5],
85
+ p3x: nums[6],
86
+ p3y: nums[7]
87
+ };
88
+ }
89
+
90
+ // Parameter near the end of the curve for tangent sampling
91
+ const T_SAMPLE = 0.9;
92
+
93
+ let computed = $derived.by(() => {
94
+ // 1. Get the full bezier path from xyflow
95
+ const [fullPath, lx, ly] = getBezierPath({
96
+ sourceX,
97
+ sourceY,
98
+ targetX,
99
+ targetY,
100
+ sourcePosition,
101
+ targetPosition,
102
+ curvature: pathOptions?.curvature
103
+ });
104
+
105
+ // 2. Parse control points from SVG path
106
+ const cp = parseCubicBezier(fullPath);
107
+ if (!cp) {
108
+ return { path: fullPath, labelX: lx, labelY: ly, angleDeg: 0 };
109
+ }
110
+
111
+ // 3. Evaluate the curve at T_SAMPLE to get a reference point
112
+ const ref = bezierAt(
113
+ cp.p0x,
114
+ cp.p0y,
115
+ cp.p1x,
116
+ cp.p1y,
117
+ cp.p2x,
118
+ cp.p2y,
119
+ cp.p3x,
120
+ cp.p3y,
121
+ T_SAMPLE
122
+ );
123
+
124
+ // 4. Tangent direction: from reference point to the target
125
+ const dx = targetX - ref.x;
126
+ const dy = targetY - ref.y;
127
+ const angleDeg = (Math.atan2(dy, dx) * 180) / Math.PI;
128
+ const angleRad = Math.atan2(dy, dx);
129
+
130
+ // 5. Shorten: move the endpoint back along the tangent
131
+ const adjX = targetX - Math.cos(angleRad) * ARROW_LENGTH_PX;
132
+ const adjY = targetY - Math.sin(angleRad) * ARROW_LENGTH_PX;
133
+
134
+ // 6. Recompute the bezier path with the shortened target
135
+ const [shortenedPath] = getBezierPath({
136
+ sourceX,
137
+ sourceY,
138
+ targetX: adjX,
139
+ targetY: adjY,
140
+ sourcePosition,
141
+ targetPosition,
142
+ curvature: pathOptions?.curvature
143
+ });
144
+
145
+ return { path: shortenedPath, labelX: lx, labelY: ly, angleDeg };
146
+ });
147
+ </script>
148
+
149
+ <BaseEdge
150
+ {id}
151
+ path={computed.path}
152
+ labelX={computed.labelX}
153
+ labelY={computed.labelY}
154
+ {label}
155
+ {labelStyle}
156
+ {markerStart}
157
+ {interactionWidth}
158
+ {style}
159
+ />
160
+
161
+ <!-- Manual arrowhead: tip at origin pointing right, rotated to the bezier tangent -->
162
+ <polygon
163
+ points="0,0 {-ARROW_LENGTH_PX},{-ARROW_HALF_WIDTH_PX} {-ARROW_LENGTH_PX},{ARROW_HALF_WIDTH_PX}"
164
+ fill={strokeColor}
165
+ stroke="none"
166
+ transform="translate({targetX},{targetY}) rotate({computed.angleDeg})"
167
+ class="flowdrop-edge-arrow"
168
+ />
@@ -0,0 +1,4 @@
1
+ import type { BezierEdgeProps } from '@xyflow/svelte';
2
+ declare const FlowDropEdge: import("svelte").Component<BezierEdgeProps, {}, "">;
3
+ type FlowDropEdge = ReturnType<typeof FlowDropEdge>;
4
+ export default FlowDropEdge;
@@ -33,6 +33,7 @@
33
33
  import { tick, untrack } from 'svelte';
34
34
  import type { EndpointConfig } from '../config/endpoints.js';
35
35
  import ConnectionLine from './ConnectionLine.svelte';
36
+ import FlowDropEdge from './FlowDropEdge.svelte';
36
37
  import { getWorkflowStore, workflowActions } from '../stores/workflowStore.svelte.js';
37
38
  import { historyActions, setOnRestoreCallback } from '../stores/historyStore.svelte.js';
38
39
  import UniversalNode from './UniversalNode.svelte';
@@ -331,6 +332,11 @@
331
332
  universalNode: UniversalNode
332
333
  };
333
334
 
335
+ // Use custom edge that shortens the path so the stroke ends at the arrow base
336
+ const edgeTypes = {
337
+ default: FlowDropEdge
338
+ };
339
+
334
340
  // Handle arrows in our custom connection handler
335
341
  const defaultEdgeOptions = {};
336
342
 
@@ -759,6 +765,7 @@
759
765
  bind:nodes={flowNodes}
760
766
  bind:edges={flowEdges}
761
767
  {nodeTypes}
768
+ {edgeTypes}
762
769
  {defaultEdgeOptions}
763
770
  onconnect={(connection) =>
764
771
  void handleConnect({
@@ -14,6 +14,14 @@ export declare const NODE_EXECUTION_CACHE_TIMEOUT_MS = 30000;
14
14
  export declare const PIPELINE_API_UNAVAILABLE_DURATION_MS: number;
15
15
  /** Default cache TTL for schema and variable data in milliseconds (5 minutes) */
16
16
  export declare const DEFAULT_CACHE_TTL_MS: number;
17
+ /**
18
+ * Custom arrowhead dimensions (in pixels) drawn by FlowDropEdge.
19
+ * The edge path is shortened by ARROW_LENGTH_PX so the stroke ends
20
+ * at the arrow base, and the arrowhead polygon is rendered separately
21
+ * at the target handle position.
22
+ */
23
+ export declare const ARROW_LENGTH_PX = 8;
24
+ export declare const ARROW_HALF_WIDTH_PX = 4;
17
25
  /** Edge marker arrow sizes by category */
18
26
  export declare const EDGE_MARKER_SIZES: {
19
27
  readonly loopback: {
@@ -14,6 +14,14 @@ export const NODE_EXECUTION_CACHE_TIMEOUT_MS = 30_000;
14
14
  export const PIPELINE_API_UNAVAILABLE_DURATION_MS = 5 * 60 * 1000;
15
15
  /** Default cache TTL for schema and variable data in milliseconds (5 minutes) */
16
16
  export const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
17
+ /**
18
+ * Custom arrowhead dimensions (in pixels) drawn by FlowDropEdge.
19
+ * The edge path is shortened by ARROW_LENGTH_PX so the stroke ends
20
+ * at the arrow base, and the arrowhead polygon is rendered separately
21
+ * at the target handle position.
22
+ */
23
+ export const ARROW_LENGTH_PX = 8;
24
+ export const ARROW_HALF_WIDTH_PX = 4;
17
25
  /** Edge marker arrow sizes by category */
18
26
  export const EDGE_MARKER_SIZES = {
19
27
  loopback: { width: 14, height: 14 },
@@ -59,7 +59,14 @@ export declare const workflowApi: {
59
59
  */
60
60
  deleteWorkflow(id: string): Promise<void>;
61
61
  /**
62
- * Save workflow (create or update)
62
+ * Save workflow (create or update).
63
+ *
64
+ * A workflow is considered existing when it already has an id (any format —
65
+ * integer, UUID, slug). Only a missing/empty id means "truly new".
66
+ *
67
+ * Note: globalSave.ts bypasses this method and calls createWorkflow /
68
+ * updateWorkflow directly so it can capture the new/existing decision before
69
+ * the uuidv4() fallback. This method is kept for external callers.
63
70
  */
64
71
  saveWorkflow(workflow: Workflow): Promise<Workflow>;
65
72
  };
@@ -108,7 +115,14 @@ export declare const api: {
108
115
  */
109
116
  deleteWorkflow(id: string): Promise<void>;
110
117
  /**
111
- * Save workflow (create or update)
118
+ * Save workflow (create or update).
119
+ *
120
+ * A workflow is considered existing when it already has an id (any format —
121
+ * integer, UUID, slug). Only a missing/empty id means "truly new".
122
+ *
123
+ * Note: globalSave.ts bypasses this method and calls createWorkflow /
124
+ * updateWorkflow directly so it can capture the new/existing decision before
125
+ * the uuidv4() fallback. This method is kept for external callers.
112
126
  */
113
127
  saveWorkflow(workflow: Workflow): Promise<Workflow>;
114
128
  };
@@ -185,14 +185,17 @@ export const workflowApi = {
185
185
  await apiRequest('workflows.delete', endpointConfig.endpoints.workflows.delete, { id }, { method: 'DELETE' });
186
186
  },
187
187
  /**
188
- * Save workflow (create or update)
188
+ * Save workflow (create or update).
189
+ *
190
+ * A workflow is considered existing when it already has an id (any format —
191
+ * integer, UUID, slug). Only a missing/empty id means "truly new".
192
+ *
193
+ * Note: globalSave.ts bypasses this method and calls createWorkflow /
194
+ * updateWorkflow directly so it can capture the new/existing decision before
195
+ * the uuidv4() fallback. This method is kept for external callers.
189
196
  */
190
197
  async saveWorkflow(workflow) {
191
- // Check if this is an existing workflow by looking for a valid ID
192
- // Valid IDs should not be a UUID (which indicates a new workflow)
193
- const isExistingWorkflow = workflow.id &&
194
- workflow.id.length > 0 &&
195
- !workflow.id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
198
+ const isExistingWorkflow = !!(workflow.id && workflow.id.length > 0);
196
199
  if (isExistingWorkflow) {
197
200
  // Update existing workflow
198
201
  return this.updateWorkflow(workflow.id, workflow);
@@ -110,6 +110,11 @@ export async function globalSaveWorkflow(options = {}) {
110
110
  await ensureApiConfiguration();
111
111
  // Step 3 — Build the canonical workflow object.
112
112
  // Preserve all existing metadata fields (format, tags, etc.) so nothing is dropped.
113
+ //
114
+ // Determine new vs existing BEFORE the uuidv4() fallback: if the store already
115
+ // has an id (any format — integer, UUID, slug), the workflow came from a backend
116
+ // and must be updated. Only a missing id means "truly new".
117
+ const isExistingWorkflow = !!currentWorkflow.id;
113
118
  const workflowId = currentWorkflow.id || uuidv4();
114
119
  const finalWorkflow = {
115
120
  id: workflowId,
@@ -128,9 +133,6 @@ export async function globalSaveWorkflow(options = {}) {
128
133
  // Step 4 — Persist
129
134
  let savedWorkflow;
130
135
  if (apiClient) {
131
- // Enhanced client path — detects existing workflows by non-UUID ID
132
- const isExistingWorkflow = finalWorkflow.id.length > 0 &&
133
- !finalWorkflow.id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
134
136
  if (isExistingWorkflow) {
135
137
  savedWorkflow = await apiClient.updateWorkflow(finalWorkflow.id, finalWorkflow);
136
138
  }
@@ -140,7 +142,13 @@ export async function globalSaveWorkflow(options = {}) {
140
142
  }
141
143
  else {
142
144
  // Legacy path
143
- savedWorkflow = await workflowApi.saveWorkflow(finalWorkflow);
145
+ if (isExistingWorkflow) {
146
+ savedWorkflow = await workflowApi.updateWorkflow(finalWorkflow.id, finalWorkflow);
147
+ }
148
+ else {
149
+ const { id: _id, ...workflowData } = finalWorkflow;
150
+ savedWorkflow = await workflowApi.createWorkflow(workflowData);
151
+ }
144
152
  }
145
153
  // Step 5 — If the server assigned a new ID, sync the store
146
154
  if (savedWorkflow.id && savedWorkflow.id !== finalWorkflow.id) {
@@ -0,0 +1,122 @@
1
+ <!--
2
+ EdgeDecorator: Renders two connected nodes inside a SvelteFlow canvas
3
+ to showcase edge rendering with arrowhead markers.
4
+ -->
5
+ <script lang="ts">
6
+ import { SvelteFlow, Controls, MarkerType } from '@xyflow/svelte';
7
+ import type { Node, Edge, ColorMode } from '@xyflow/svelte';
8
+ import '@xyflow/svelte/dist/style.css';
9
+ import UniversalNode from '../components/UniversalNode.svelte';
10
+ import FlowDropEdge from '../components/FlowDropEdge.svelte';
11
+ import { registerBuiltinNodes } from '../registry/builtinNodes.js';
12
+ import { EDGE_MARKER_SIZES } from '../config/constants.js';
13
+
14
+ interface Props {
15
+ sourceData: Record<string, unknown>;
16
+ targetData: Record<string, unknown>;
17
+ edgeStyle?: string;
18
+ edgeClass?: string;
19
+ edgeMarkerColor?: string;
20
+ edgeMarkerSize?: { width: number; height: number };
21
+ sourceHandleId?: string;
22
+ targetHandleId?: string;
23
+ }
24
+
25
+ let {
26
+ sourceData,
27
+ targetData,
28
+ edgeStyle = 'stroke: #64748b;',
29
+ edgeClass = 'flowdrop--edge--data',
30
+ edgeMarkerColor = '#64748b',
31
+ edgeMarkerSize = EDGE_MARKER_SIZES.data,
32
+ sourceHandleId = 'output',
33
+ targetHandleId = 'input'
34
+ }: Props = $props();
35
+
36
+ registerBuiltinNodes();
37
+
38
+ const nodeTypes = {
39
+ universalNode: UniversalNode
40
+ };
41
+
42
+ const edgeTypes = {
43
+ default: FlowDropEdge
44
+ };
45
+
46
+ const SOURCE_ID = 'source-node';
47
+ const TARGET_ID = 'target-node';
48
+
49
+ let nodes = $state<Node[]>([
50
+ {
51
+ id: SOURCE_ID,
52
+ type: 'universalNode',
53
+ position: { x: 0, y: 0 },
54
+ data: { ...sourceData, nodeId: SOURCE_ID }
55
+ },
56
+ {
57
+ id: TARGET_ID,
58
+ type: 'universalNode',
59
+ position: { x: 350, y: 0 },
60
+ data: { ...targetData, nodeId: TARGET_ID }
61
+ }
62
+ ]);
63
+
64
+ // Handle IDs follow the format: {nodeId}-{input|output}-{portId}
65
+ let edges = $state<Edge[]>([
66
+ {
67
+ id: 'edge-1',
68
+ source: SOURCE_ID,
69
+ target: TARGET_ID,
70
+ sourceHandle: `${SOURCE_ID}-output-${sourceHandleId}`,
71
+ targetHandle: `${TARGET_ID}-input-${targetHandleId}`,
72
+ style: edgeStyle,
73
+ class: edgeClass,
74
+ markerEnd: {
75
+ type: MarkerType.ArrowClosed,
76
+ ...edgeMarkerSize,
77
+ color: edgeMarkerColor
78
+ }
79
+ }
80
+ ]);
81
+
82
+ let colorMode = $state<ColorMode>(
83
+ (document.documentElement.getAttribute('data-theme') as ColorMode) || 'light'
84
+ );
85
+
86
+ $effect(() => {
87
+ const observer = new MutationObserver(() => {
88
+ colorMode = (document.documentElement.getAttribute('data-theme') as ColorMode) || 'light';
89
+ });
90
+ observer.observe(document.documentElement, {
91
+ attributes: true,
92
+ attributeFilter: ['data-theme']
93
+ });
94
+ return () => observer.disconnect();
95
+ });
96
+ </script>
97
+
98
+ <div class="edge-decorator-wrapper">
99
+ <SvelteFlow
100
+ bind:nodes
101
+ bind:edges
102
+ {nodeTypes}
103
+ {edgeTypes}
104
+ fitView
105
+ fitViewOptions={{ maxZoom: 0.85, padding: 0.3 }}
106
+ {colorMode}
107
+ >
108
+ <Controls />
109
+ </SvelteFlow>
110
+ </div>
111
+
112
+ <style>
113
+ .edge-decorator-wrapper {
114
+ width: 900px;
115
+ height: 400px;
116
+ position: relative;
117
+ }
118
+
119
+ .edge-decorator-wrapper :global(.svelte-flow.dark) {
120
+ --background-color-default: var(--xy-background-color-default);
121
+ }
122
+ </style>
@@ -0,0 +1,17 @@
1
+ import '@xyflow/svelte/dist/style.css';
2
+ interface Props {
3
+ sourceData: Record<string, unknown>;
4
+ targetData: Record<string, unknown>;
5
+ edgeStyle?: string;
6
+ edgeClass?: string;
7
+ edgeMarkerColor?: string;
8
+ edgeMarkerSize?: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ sourceHandleId?: string;
13
+ targetHandleId?: string;
14
+ }
15
+ declare const EdgeDecorator: import("svelte").Component<Props, {}, "">;
16
+ type EdgeDecorator = ReturnType<typeof EdgeDecorator>;
17
+ export default EdgeDecorator;
package/package.json CHANGED
@@ -1,292 +1,297 @@
1
1
  {
2
- "name": "@flowdrop/flowdrop",
3
- "license": "MIT",
4
- "private": false,
5
- "version": "1.0.0",
6
- "scripts": {
7
- "dev": "vite dev",
8
- "build": "vite build && pnpm run prepack",
9
- "build:drupal": "vite build --config vite.config.drupal.ts",
10
- "build:production": "vite build --config vite.config.production.ts",
11
- "watch:build:drupal": "npm-watch build:drupal",
12
- "watch:build:production": "npm-watch build:production",
13
- "watch:build": "npm-watch build",
14
- "preview": "vite preview",
15
- "prepare": "svelte-kit sync || echo ''",
16
- "prepack": "svelte-kit sync && svelte-package && publint",
17
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
18
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
19
- "lint": "eslint . && prettier --check .",
20
- "test": "vitest run",
21
- "test:watch": "vitest",
22
- "test:ui": "vitest --ui",
23
- "test:coverage": "vitest run --coverage",
24
- "test:e2e": "playwright test",
25
- "test:e2e:ui": "playwright test --ui",
26
- "test:e2e:debug": "playwright test --debug",
27
- "test:all": "pnpm run test && pnpm run test:e2e",
28
- "format": "prettier --write .",
29
- "storybook": "storybook dev -p 6006",
30
- "storybook:build": "storybook build",
31
- "schema:generate": "node scripts/generate-schema.mjs",
32
- "schema:check": "node scripts/generate-schema.mjs --check",
33
- "api:lint": "pnpm --dir ../../apps/api-docs run lint",
34
- "api:bundle": "pnpm --dir ../../apps/api-docs run bundle"
35
- },
36
- "watch": {
37
- "build": {
38
- "ignore": "build",
39
- "patterns": [
40
- "src"
41
- ],
42
- "extensions": "js,ts,svelte,html,css,svg",
43
- "quiet": true,
44
- "legacyWatch": true,
45
- "delay": 2500,
46
- "runOnChangeOnly": false
47
- },
48
- "build:drupal": {
49
- "ignore": "build",
50
- "patterns": [
51
- "src"
52
- ],
53
- "extensions": "js,ts,svelte,html,css,svg",
54
- "quiet": true,
55
- "legacyWatch": true,
56
- "delay": 2500,
57
- "runOnChangeOnly": false
58
- },
59
- "build:production": {
60
- "ignore": "build",
61
- "patterns": [
62
- "src"
63
- ],
64
- "extensions": "js,ts,svelte,html,css,svg",
65
- "quiet": true,
66
- "legacyWatch": true,
67
- "delay": 2500,
68
- "runOnChangeOnly": false
69
- }
70
- },
71
- "files": [
72
- "dist",
73
- "!dist/**/*.test.*",
74
- "!dist/**/*.spec.*"
75
- ],
76
- "sideEffects": [
77
- "**/*.css",
78
- "./dist/styles/base.css",
79
- "./dist/editor/index.js"
80
- ],
81
- "svelte": "./dist/index.js",
82
- "types": "./dist/index.d.ts",
83
- "type": "module",
84
- "exports": {
85
- ".": {
86
- "types": "./dist/index.d.ts",
87
- "svelte": "./dist/index.js",
88
- "default": "./dist/index.js"
89
- },
90
- "./core": {
91
- "types": "./dist/core/index.d.ts",
92
- "svelte": "./dist/core/index.js",
93
- "default": "./dist/core/index.js"
94
- },
95
- "./editor": {
96
- "types": "./dist/editor/index.d.ts",
97
- "svelte": "./dist/editor/index.js",
98
- "default": "./dist/editor/index.js"
99
- },
100
- "./form": {
101
- "types": "./dist/form/index.d.ts",
102
- "svelte": "./dist/form/index.js",
103
- "default": "./dist/form/index.js"
104
- },
105
- "./form/code": {
106
- "types": "./dist/form/code.d.ts",
107
- "svelte": "./dist/form/code.js",
108
- "default": "./dist/form/code.js"
109
- },
110
- "./form/markdown": {
111
- "types": "./dist/form/markdown.d.ts",
112
- "svelte": "./dist/form/markdown.js",
113
- "default": "./dist/form/markdown.js"
114
- },
115
- "./form/full": {
116
- "types": "./dist/form/full.d.ts",
117
- "svelte": "./dist/form/full.js",
118
- "default": "./dist/form/full.js"
119
- },
120
- "./display": {
121
- "types": "./dist/display/index.d.ts",
122
- "svelte": "./dist/display/index.js",
123
- "default": "./dist/display/index.js"
124
- },
125
- "./playground": {
126
- "types": "./dist/playground/index.d.ts",
127
- "svelte": "./dist/playground/index.js",
128
- "default": "./dist/playground/index.js"
129
- },
130
- "./settings": {
131
- "types": "./dist/settings/index.d.ts",
132
- "svelte": "./dist/settings/index.js",
133
- "default": "./dist/settings/index.js"
134
- },
135
- "./styles": "./dist/styles/base.css",
136
- "./styles/*": "./dist/styles/*",
137
- "./schema": {
138
- "default": "./dist/schemas/v1/workflow.schema.json"
139
- },
140
- "./schema/v1": {
141
- "default": "./dist/schemas/v1/workflow.schema.json"
142
- }
143
- },
144
- "peerDependencies": {
145
- "@codemirror/autocomplete": "^6.20.0",
146
- "@codemirror/commands": "^6.10.2",
147
- "@codemirror/lang-json": "^6.0.2",
148
- "@codemirror/lang-markdown": "^6.5.0",
149
- "@codemirror/language": "^6.12.1",
150
- "@codemirror/lint": "^6.9.2",
151
- "@codemirror/state": "^6.5.4",
152
- "@codemirror/theme-one-dark": "^6.1.3",
153
- "@codemirror/view": "^6.39.14",
154
- "@iconify/svelte": "^5.0.0",
155
- "@xyflow/svelte": "^1.2",
156
- "codemirror": "^6.0.2",
157
- "svelte": "^5.0.0"
158
- },
159
- "peerDependenciesMeta": {
160
- "@codemirror/autocomplete": {
161
- "optional": true
162
- },
163
- "@codemirror/commands": {
164
- "optional": true
165
- },
166
- "@codemirror/lang-json": {
167
- "optional": true
168
- },
169
- "@codemirror/lang-markdown": {
170
- "optional": true
171
- },
172
- "@codemirror/language": {
173
- "optional": true
174
- },
175
- "@codemirror/lint": {
176
- "optional": true
177
- },
178
- "@codemirror/state": {
179
- "optional": true
180
- },
181
- "@codemirror/theme-one-dark": {
182
- "optional": true
183
- },
184
- "@codemirror/view": {
185
- "optional": true
186
- },
187
- "@iconify/svelte": {
188
- "optional": true
189
- },
190
- "codemirror": {
191
- "optional": true
192
- }
193
- },
194
- "repository": {
195
- "type": "git",
196
- "url": "git+https://github.com/flowdrop-io/flowdrop.git"
197
- },
198
- "devDependencies": {
199
- "@chromatic-com/storybook": "^5.0.1",
200
- "@xyflow/svelte": "^1.2",
201
- "@codemirror/autocomplete": "^6.20.0",
202
- "@codemirror/commands": "^6.10.2",
203
- "@codemirror/lang-json": "^6.0.2",
204
- "@codemirror/lang-markdown": "^6.5.0",
205
- "@codemirror/language": "^6.12.1",
206
- "@codemirror/lint": "^6.9.2",
207
- "@codemirror/state": "^6.5.4",
208
- "@codemirror/theme-one-dark": "^6.1.3",
209
- "@codemirror/view": "^6.39.14",
210
- "codemirror": "^6.0.2",
211
- "@eslint/compat": "^1.2.5",
212
- "@eslint/js": "^9.18.0",
213
- "@iconify/svelte": "^5.0.0",
214
- "@playwright/test": "^1.49.1",
215
- "@storybook/addon-docs": "^10.2.15",
216
- "@storybook/addon-svelte-csf": "^5.0.11",
217
- "@storybook/addon-themes": "^10.2.15",
218
- "@storybook/addon-vitest": "^10.2.15",
219
- "@storybook/sveltekit": "^10.2.15",
220
- "@sveltejs/adapter-auto": "^6.0.0",
221
- "@sveltejs/adapter-node": "^5.4.0",
222
- "@sveltejs/kit": "^2.49.2",
223
- "@sveltejs/package": "^2.0.0",
224
- "@sveltejs/vite-plugin-svelte": "^5.0.0",
225
- "@types/dompurify": "^3.2.0",
226
- "@types/marked": "^6.0.0",
227
- "@types/node": "^20",
228
- "@types/uuid": "^10.0.0",
229
- "@vitest/browser": "^3.2.3",
230
- "@vitest/coverage-v8": "^3.2.4",
231
- "@vitest/ui": "^3.2.4",
232
- "eslint": "^9.18.0",
233
- "eslint-config-prettier": "^10.0.1",
234
- "eslint-plugin-storybook": "^10.2.15",
235
- "eslint-plugin-svelte": "^3.0.0",
236
- "globals": "^16.0.0",
237
- "happy-dom": "^20.0.11",
238
- "msw": "^2.12.7",
239
- "npm-watch": "^0.13.0",
240
- "picomatch": "^4.0.3",
241
- "playwright": "^1.53.0",
242
- "prettier": "^3.4.2",
243
- "prettier-plugin-svelte": "^3.3.3",
244
- "publint": "^0.3.2",
245
- "storybook": "^10.2.15",
246
- "storybook-addon-tag-badges": "^3.0.6",
247
- "svelte": "^5.0.0",
248
- "svelte-check": "^4.0.0",
249
- "terser": "^5.43.1",
250
- "typescript": "^5.0.0",
251
- "typescript-eslint": "^8.20.0",
252
- "vite": "^6.2.6",
253
- "vite-plugin-devtools-json": "^0.2.1",
254
- "vitest": "^3.2.3",
255
- "vitest-browser-svelte": "^0.1.0",
256
- "yaml": "^2.8.2"
257
- },
258
- "overrides": {
259
- "@sveltejs/kit": {
260
- "cookie": "0.7.2"
261
- }
262
- },
263
- "keywords": [
264
- "svelte",
265
- "svelte5",
266
- "workflow",
267
- "editor",
268
- "node-editor",
269
- "graph-editor",
270
- "visual-programming",
271
- "xyflow",
272
- "flowchart",
273
- "dag"
274
- ],
275
- "dependencies": {
276
- "diff": "^8.0.3",
277
- "dompurify": "^3.3.1",
278
- "marked": "^16.1.1",
279
- "svelte-5-french-toast": "^2.0.6",
280
- "uuid": "^11.1.0"
281
- },
282
- "pnpm": {
283
- "overrides": {
284
- "cookie@<0.7.0": "0.7.2"
285
- }
286
- },
287
- "msw": {
288
- "workerDirectory": [
289
- "static"
290
- ]
291
- }
292
- }
2
+ "name": "@flowdrop/flowdrop",
3
+ "description": "A drop-in visual workflow editor for any web application. You own the backend. You own the data. You own the orchestration.",
4
+ "license": "MIT",
5
+ "private": false,
6
+ "version": "1.0.1",
7
+ "author": "Shibin Das (D34dMan)",
8
+ "bugs": {
9
+ "url": "https://github.com/flowdrop-io/flowdrop/issues"
10
+ },
11
+ "watch": {
12
+ "build": {
13
+ "ignore": "build",
14
+ "patterns": [
15
+ "src"
16
+ ],
17
+ "extensions": "js,ts,svelte,html,css,svg",
18
+ "quiet": true,
19
+ "legacyWatch": true,
20
+ "delay": 2500,
21
+ "runOnChangeOnly": false
22
+ },
23
+ "build:drupal": {
24
+ "ignore": "build",
25
+ "patterns": [
26
+ "src"
27
+ ],
28
+ "extensions": "js,ts,svelte,html,css,svg",
29
+ "quiet": true,
30
+ "legacyWatch": true,
31
+ "delay": 2500,
32
+ "runOnChangeOnly": false
33
+ },
34
+ "build:production": {
35
+ "ignore": "build",
36
+ "patterns": [
37
+ "src"
38
+ ],
39
+ "extensions": "js,ts,svelte,html,css,svg",
40
+ "quiet": true,
41
+ "legacyWatch": true,
42
+ "delay": 2500,
43
+ "runOnChangeOnly": false
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "!dist/**/*.test.*",
49
+ "!dist/**/*.spec.*"
50
+ ],
51
+ "sideEffects": [
52
+ "**/*.css",
53
+ "./dist/styles/base.css",
54
+ "./dist/editor/index.js"
55
+ ],
56
+ "svelte": "./dist/index.js",
57
+ "types": "./dist/index.d.ts",
58
+ "type": "module",
59
+ "exports": {
60
+ ".": {
61
+ "types": "./dist/index.d.ts",
62
+ "svelte": "./dist/index.js",
63
+ "default": "./dist/index.js"
64
+ },
65
+ "./core": {
66
+ "types": "./dist/core/index.d.ts",
67
+ "svelte": "./dist/core/index.js",
68
+ "default": "./dist/core/index.js"
69
+ },
70
+ "./editor": {
71
+ "types": "./dist/editor/index.d.ts",
72
+ "svelte": "./dist/editor/index.js",
73
+ "default": "./dist/editor/index.js"
74
+ },
75
+ "./form": {
76
+ "types": "./dist/form/index.d.ts",
77
+ "svelte": "./dist/form/index.js",
78
+ "default": "./dist/form/index.js"
79
+ },
80
+ "./form/code": {
81
+ "types": "./dist/form/code.d.ts",
82
+ "svelte": "./dist/form/code.js",
83
+ "default": "./dist/form/code.js"
84
+ },
85
+ "./form/markdown": {
86
+ "types": "./dist/form/markdown.d.ts",
87
+ "svelte": "./dist/form/markdown.js",
88
+ "default": "./dist/form/markdown.js"
89
+ },
90
+ "./form/full": {
91
+ "types": "./dist/form/full.d.ts",
92
+ "svelte": "./dist/form/full.js",
93
+ "default": "./dist/form/full.js"
94
+ },
95
+ "./display": {
96
+ "types": "./dist/display/index.d.ts",
97
+ "svelte": "./dist/display/index.js",
98
+ "default": "./dist/display/index.js"
99
+ },
100
+ "./playground": {
101
+ "types": "./dist/playground/index.d.ts",
102
+ "svelte": "./dist/playground/index.js",
103
+ "default": "./dist/playground/index.js"
104
+ },
105
+ "./settings": {
106
+ "types": "./dist/settings/index.d.ts",
107
+ "svelte": "./dist/settings/index.js",
108
+ "default": "./dist/settings/index.js"
109
+ },
110
+ "./styles": "./dist/styles/base.css",
111
+ "./styles/*": "./dist/styles/*",
112
+ "./schema": {
113
+ "default": "./dist/schemas/v1/workflow.schema.json"
114
+ },
115
+ "./schema/v1": {
116
+ "default": "./dist/schemas/v1/workflow.schema.json"
117
+ }
118
+ },
119
+ "peerDependencies": {
120
+ "@codemirror/autocomplete": "^6.20.0",
121
+ "@codemirror/commands": "^6.10.2",
122
+ "@codemirror/lang-json": "^6.0.2",
123
+ "@codemirror/lang-markdown": "^6.5.0",
124
+ "@codemirror/language": "^6.12.1",
125
+ "@codemirror/lint": "^6.9.2",
126
+ "@codemirror/state": "^6.5.4",
127
+ "@codemirror/theme-one-dark": "^6.1.3",
128
+ "@codemirror/view": "^6.39.14",
129
+ "@iconify/svelte": "^5.0.0",
130
+ "@xyflow/svelte": "^1.2",
131
+ "codemirror": "^6.0.2",
132
+ "svelte": "^5.0.0"
133
+ },
134
+ "peerDependenciesMeta": {
135
+ "@codemirror/autocomplete": {
136
+ "optional": true
137
+ },
138
+ "@codemirror/commands": {
139
+ "optional": true
140
+ },
141
+ "@codemirror/lang-json": {
142
+ "optional": true
143
+ },
144
+ "@codemirror/lang-markdown": {
145
+ "optional": true
146
+ },
147
+ "@codemirror/language": {
148
+ "optional": true
149
+ },
150
+ "@codemirror/lint": {
151
+ "optional": true
152
+ },
153
+ "@codemirror/state": {
154
+ "optional": true
155
+ },
156
+ "@codemirror/theme-one-dark": {
157
+ "optional": true
158
+ },
159
+ "@codemirror/view": {
160
+ "optional": true
161
+ },
162
+ "@iconify/svelte": {
163
+ "optional": true
164
+ },
165
+ "codemirror": {
166
+ "optional": true
167
+ }
168
+ },
169
+ "repository": {
170
+ "type": "git",
171
+ "url": "git+https://github.com/flowdrop-io/flowdrop.git"
172
+ },
173
+ "devDependencies": {
174
+ "@chromatic-com/storybook": "^5.0.1",
175
+ "@xyflow/svelte": "^1.2",
176
+ "@codemirror/autocomplete": "^6.20.0",
177
+ "@codemirror/commands": "^6.10.2",
178
+ "@codemirror/lang-json": "^6.0.2",
179
+ "@codemirror/lang-markdown": "^6.5.0",
180
+ "@codemirror/language": "^6.12.1",
181
+ "@codemirror/lint": "^6.9.2",
182
+ "@codemirror/state": "^6.5.4",
183
+ "@codemirror/theme-one-dark": "^6.1.3",
184
+ "@codemirror/view": "^6.39.14",
185
+ "codemirror": "^6.0.2",
186
+ "@eslint/compat": "^1.2.5",
187
+ "@eslint/js": "^9.18.0",
188
+ "@iconify/svelte": "^5.0.0",
189
+ "@playwright/test": "^1.49.1",
190
+ "@storybook/addon-docs": "^10.2.15",
191
+ "@storybook/addon-svelte-csf": "^5.0.11",
192
+ "@storybook/addon-themes": "^10.2.15",
193
+ "@storybook/addon-vitest": "^10.2.15",
194
+ "@storybook/sveltekit": "^10.2.15",
195
+ "@sveltejs/adapter-auto": "^6.0.0",
196
+ "@sveltejs/adapter-node": "^5.4.0",
197
+ "@sveltejs/kit": "^2.49.2",
198
+ "@sveltejs/package": "^2.0.0",
199
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
200
+ "@types/dompurify": "^3.2.0",
201
+ "@types/marked": "^6.0.0",
202
+ "@types/node": "^20",
203
+ "@types/uuid": "^10.0.0",
204
+ "@vitest/browser": "^3.2.3",
205
+ "@vitest/coverage-v8": "^3.2.4",
206
+ "@vitest/ui": "^3.2.4",
207
+ "eslint": "^9.18.0",
208
+ "eslint-config-prettier": "^10.0.1",
209
+ "eslint-plugin-storybook": "^10.2.15",
210
+ "eslint-plugin-svelte": "^3.0.0",
211
+ "globals": "^16.0.0",
212
+ "happy-dom": "^20.0.11",
213
+ "msw": "^2.12.7",
214
+ "npm-watch": "^0.13.0",
215
+ "picomatch": "^4.0.3",
216
+ "playwright": "^1.53.0",
217
+ "prettier": "^3.4.2",
218
+ "prettier-plugin-svelte": "^3.3.3",
219
+ "publint": "^0.3.2",
220
+ "storybook": "^10.2.15",
221
+ "storybook-addon-tag-badges": "^3.0.6",
222
+ "svelte": "^5.0.0",
223
+ "svelte-check": "^4.0.0",
224
+ "terser": "^5.43.1",
225
+ "typescript": "^5.0.0",
226
+ "typescript-eslint": "^8.20.0",
227
+ "vite": "^6.2.6",
228
+ "vite-plugin-devtools-json": "^0.2.1",
229
+ "vitest": "^3.2.3",
230
+ "vitest-browser-svelte": "^0.1.0",
231
+ "yaml": "^2.8.2"
232
+ },
233
+ "overrides": {
234
+ "@sveltejs/kit": {
235
+ "cookie": "0.7.2"
236
+ }
237
+ },
238
+ "engines": {
239
+ "node": ">=18"
240
+ },
241
+ "keywords": [
242
+ "svelte",
243
+ "svelte5",
244
+ "workflow",
245
+ "workflow-editor",
246
+ "editor",
247
+ "node-editor",
248
+ "graph-editor",
249
+ "visual-programming",
250
+ "xyflow",
251
+ "flowchart",
252
+ "dag",
253
+ "agent",
254
+ "ai-workflow",
255
+ "embeddable"
256
+ ],
257
+ "dependencies": {
258
+ "diff": "^8.0.3",
259
+ "dompurify": "^3.3.1",
260
+ "marked": "^16.1.1",
261
+ "svelte-5-french-toast": "^2.0.6",
262
+ "uuid": "^11.1.0"
263
+ },
264
+ "msw": {
265
+ "workerDirectory": [
266
+ "static"
267
+ ]
268
+ },
269
+ "scripts": {
270
+ "dev": "vite dev",
271
+ "build": "vite build && pnpm run prepack",
272
+ "build:drupal": "vite build --config vite.config.drupal.ts",
273
+ "build:production": "vite build --config vite.config.production.ts",
274
+ "watch:build:drupal": "npm-watch build:drupal",
275
+ "watch:build:production": "npm-watch build:production",
276
+ "watch:build": "npm-watch build",
277
+ "preview": "vite preview",
278
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
279
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
280
+ "lint": "eslint . && prettier --check .",
281
+ "test": "vitest run",
282
+ "test:watch": "vitest",
283
+ "test:ui": "vitest --ui",
284
+ "test:coverage": "vitest run --coverage",
285
+ "test:e2e": "playwright test",
286
+ "test:e2e:ui": "playwright test --ui",
287
+ "test:e2e:debug": "playwright test --debug",
288
+ "test:all": "pnpm run test && pnpm run test:e2e",
289
+ "format": "prettier --write .",
290
+ "storybook": "storybook dev -p 6006",
291
+ "storybook:build": "storybook build",
292
+ "schema:generate": "node scripts/generate-schema.mjs",
293
+ "schema:check": "node scripts/generate-schema.mjs --check",
294
+ "api:lint": "pnpm --dir ../../apps/api-docs run lint",
295
+ "api:bundle": "pnpm --dir ../../apps/api-docs run bundle"
296
+ }
297
+ }