@d34dman/flowdrop 0.0.60 → 0.0.62

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 (207) hide show
  1. package/README.md +6 -0
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/agentspec/AgentSpecAdapter.js +3 -1
  4. package/dist/api/client.d.ts +4 -0
  5. package/dist/api/client.js +6 -1
  6. package/dist/api/enhanced-client.js +7 -6
  7. package/dist/components/App.svelte +143 -219
  8. package/dist/components/CanvasBanner.stories.svelte +25 -0
  9. package/dist/components/CanvasBanner.stories.svelte.d.ts +27 -0
  10. package/dist/components/CanvasBanner.svelte +2 -2
  11. package/dist/components/ConfigForm.svelte +37 -36
  12. package/dist/components/ConfigPanel.stories.svelte +38 -0
  13. package/dist/components/ConfigPanel.stories.svelte.d.ts +27 -0
  14. package/dist/components/ConfigPanel.svelte +2 -2
  15. package/dist/components/ConnectionLine.svelte +2 -2
  16. package/dist/components/FlowDropZone.svelte +18 -2
  17. package/dist/components/FlowDropZone.svelte.d.ts +2 -0
  18. package/dist/components/LoadingSpinner.stories.svelte +30 -0
  19. package/dist/components/LoadingSpinner.stories.svelte.d.ts +27 -0
  20. package/dist/components/Logo.stories.svelte +22 -0
  21. package/dist/components/Logo.stories.svelte.d.ts +27 -0
  22. package/dist/components/Logo.svelte +33 -13
  23. package/dist/components/Logo.svelte.d.ts +1 -1
  24. package/dist/components/MarkdownDisplay.stories.svelte +21 -0
  25. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +27 -0
  26. package/dist/components/MarkdownDisplay.svelte +4 -3
  27. package/dist/components/Navbar.stories.svelte +41 -0
  28. package/dist/components/Navbar.stories.svelte.d.ts +27 -0
  29. package/dist/components/Navbar.svelte +4 -4
  30. package/dist/components/NodeSidebar.svelte +12 -12
  31. package/dist/components/NodeStatusOverlay.stories.svelte +74 -0
  32. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +27 -0
  33. package/dist/components/PipelineStatus.svelte +11 -4
  34. package/dist/components/PortCoordinateTracker.svelte +1 -1
  35. package/dist/components/SchemaForm.stories.svelte +101 -0
  36. package/dist/components/SchemaForm.stories.svelte.d.ts +27 -0
  37. package/dist/components/SchemaForm.svelte +17 -12
  38. package/dist/components/SettingsModal.svelte +3 -3
  39. package/dist/components/SettingsPanel.svelte +23 -22
  40. package/dist/components/StatusIcon.stories.svelte +60 -0
  41. package/dist/components/StatusIcon.stories.svelte.d.ts +27 -0
  42. package/dist/components/StatusIcon.svelte +7 -0
  43. package/dist/components/StatusLabel.stories.svelte +17 -0
  44. package/dist/components/StatusLabel.stories.svelte.d.ts +27 -0
  45. package/dist/components/ThemeToggle.stories.svelte +25 -0
  46. package/dist/components/ThemeToggle.stories.svelte.d.ts +27 -0
  47. package/dist/components/ThemeToggle.svelte +8 -8
  48. package/dist/components/UniversalNode.svelte +1 -1
  49. package/dist/components/WorkflowEditor.svelte +298 -294
  50. package/dist/components/form/FormAutocomplete.svelte +20 -19
  51. package/dist/components/form/FormCheckboxGroup.stories.svelte +28 -0
  52. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +27 -0
  53. package/dist/components/form/FormField.svelte +3 -3
  54. package/dist/components/form/FormFieldLight.svelte +2 -2
  55. package/dist/components/form/FormFieldWrapper.stories.svelte +31 -0
  56. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +27 -0
  57. package/dist/components/form/FormFieldset.svelte +7 -7
  58. package/dist/components/form/FormNumberField.stories.svelte +33 -0
  59. package/dist/components/form/FormNumberField.stories.svelte.d.ts +27 -0
  60. package/dist/components/form/FormRangeField.stories.svelte +31 -0
  61. package/dist/components/form/FormRangeField.stories.svelte.d.ts +27 -0
  62. package/dist/components/form/FormSelect.stories.svelte +50 -0
  63. package/dist/components/form/FormSelect.stories.svelte.d.ts +27 -0
  64. package/dist/components/form/FormTemplateEditor.svelte +2 -1
  65. package/dist/components/form/FormTextField.stories.svelte +30 -0
  66. package/dist/components/form/FormTextField.stories.svelte.d.ts +27 -0
  67. package/dist/components/form/FormTextarea.stories.svelte +31 -0
  68. package/dist/components/form/FormTextarea.stories.svelte.d.ts +27 -0
  69. package/dist/components/form/FormToggle.stories.svelte +30 -0
  70. package/dist/components/form/FormToggle.stories.svelte.d.ts +27 -0
  71. package/dist/components/form/FormUISchemaRenderer.svelte +1 -1
  72. package/dist/components/form/types.d.ts +15 -47
  73. package/dist/components/interrupt/ChoicePrompt.stories.svelte +43 -0
  74. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +27 -0
  75. package/dist/components/interrupt/ChoicePrompt.svelte +24 -24
  76. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +49 -0
  77. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +27 -0
  78. package/dist/components/interrupt/ConfirmationPrompt.svelte +19 -19
  79. package/dist/components/interrupt/FormPrompt.svelte +15 -15
  80. package/dist/components/interrupt/InterruptBubble.svelte +202 -236
  81. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +1 -1
  82. package/dist/components/interrupt/ReviewPrompt.stories.svelte +46 -0
  83. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +27 -0
  84. package/dist/components/interrupt/ReviewPrompt.svelte +842 -0
  85. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +23 -0
  86. package/dist/components/interrupt/TextInputPrompt.stories.svelte +34 -0
  87. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +27 -0
  88. package/dist/components/interrupt/TextInputPrompt.svelte +21 -21
  89. package/dist/components/nodes/GatewayNode.stories.svelte +76 -0
  90. package/dist/components/nodes/GatewayNode.stories.svelte.d.ts +26 -0
  91. package/dist/components/nodes/GatewayNode.svelte +19 -17
  92. package/dist/components/nodes/IdeaNode.stories.svelte +48 -0
  93. package/dist/components/nodes/IdeaNode.stories.svelte.d.ts +26 -0
  94. package/dist/components/nodes/IdeaNode.svelte +10 -26
  95. package/dist/components/nodes/NotesNode.stories.svelte +69 -0
  96. package/dist/components/nodes/NotesNode.stories.svelte.d.ts +26 -0
  97. package/dist/components/nodes/NotesNode.svelte +8 -8
  98. package/dist/components/nodes/SimpleNode.stories.svelte +101 -0
  99. package/dist/components/nodes/SimpleNode.stories.svelte.d.ts +26 -0
  100. package/dist/components/nodes/SimpleNode.svelte +16 -24
  101. package/dist/components/nodes/SquareNode.stories.svelte +56 -0
  102. package/dist/components/nodes/SquareNode.stories.svelte.d.ts +26 -0
  103. package/dist/components/nodes/SquareNode.svelte +13 -21
  104. package/dist/components/nodes/TerminalNode.stories.svelte +25 -0
  105. package/dist/components/nodes/TerminalNode.stories.svelte.d.ts +26 -0
  106. package/dist/components/nodes/TerminalNode.svelte +6 -6
  107. package/dist/components/nodes/ToolNode.stories.svelte +71 -0
  108. package/dist/components/nodes/ToolNode.stories.svelte.d.ts +26 -0
  109. package/dist/components/nodes/ToolNode.svelte +7 -15
  110. package/dist/components/nodes/WorkflowNode.stories.svelte +50 -0
  111. package/dist/components/nodes/WorkflowNode.stories.svelte.d.ts +26 -0
  112. package/dist/components/nodes/WorkflowNode.svelte +13 -13
  113. package/dist/components/playground/ChatPanel.svelte +48 -48
  114. package/dist/components/playground/ExecutionLogs.svelte +23 -23
  115. package/dist/components/playground/InputCollector.svelte +24 -24
  116. package/dist/components/playground/MessageBubble.stories.svelte +49 -0
  117. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +27 -0
  118. package/dist/components/playground/MessageBubble.svelte +49 -46
  119. package/dist/components/playground/Playground.svelte +203 -172
  120. package/dist/components/playground/PlaygroundModal.svelte +5 -5
  121. package/dist/components/playground/SessionManager.svelte +26 -26
  122. package/dist/config/constants.d.ts +22 -0
  123. package/dist/config/constants.js +22 -0
  124. package/dist/config/endpoints.d.ts +19 -0
  125. package/dist/config/runtimeConfig.js +2 -1
  126. package/dist/core/index.d.ts +5 -2
  127. package/dist/core/index.js +9 -1
  128. package/dist/editor/index.d.ts +13 -9
  129. package/dist/editor/index.js +15 -11
  130. package/dist/form/code.d.ts +2 -1
  131. package/dist/form/code.js +1 -3
  132. package/dist/form/markdown.d.ts +2 -1
  133. package/dist/form/markdown.js +1 -3
  134. package/dist/helpers/workflowEditorHelper.js +13 -9
  135. package/dist/mocks/app-forms.js +1 -0
  136. package/dist/mocks/app-navigation.js +3 -1
  137. package/dist/mocks/app-stores.d.ts +4 -4
  138. package/dist/playground/index.d.ts +5 -4
  139. package/dist/playground/index.js +15 -11
  140. package/dist/playground/mount.d.ts +20 -1
  141. package/dist/playground/mount.js +24 -6
  142. package/dist/services/agentSpecExecutionService.js +2 -1
  143. package/dist/services/api.js +10 -18
  144. package/dist/services/apiVariableService.js +2 -1
  145. package/dist/services/autoSaveService.d.ts +3 -3
  146. package/dist/services/autoSaveService.js +21 -17
  147. package/dist/services/categoriesApi.js +13 -5
  148. package/dist/services/draftStorage.js +5 -4
  149. package/dist/services/dynamicSchemaService.js +4 -4
  150. package/dist/services/globalSave.d.ts +60 -11
  151. package/dist/services/globalSave.js +160 -83
  152. package/dist/services/historyService.d.ts +2 -1
  153. package/dist/services/historyService.js +7 -3
  154. package/dist/services/interruptService.js +9 -8
  155. package/dist/services/nodeExecutionService.js +14 -6
  156. package/dist/services/playgroundService.d.ts +3 -2
  157. package/dist/services/playgroundService.js +8 -7
  158. package/dist/services/portConfigApi.js +11 -7
  159. package/dist/services/toastService.d.ts +1 -1
  160. package/dist/services/toastService.js +6 -5
  161. package/dist/services/variableService.js +3 -2
  162. package/dist/settings/index.d.ts +1 -1
  163. package/dist/settings/index.js +1 -1
  164. package/dist/stores/{categoriesStore.d.ts → categoriesStore.svelte.d.ts} +3 -3
  165. package/dist/stores/{categoriesStore.js → categoriesStore.svelte.js} +15 -18
  166. package/dist/stores/editorStateMachine.svelte.d.ts +42 -0
  167. package/dist/stores/editorStateMachine.svelte.js +132 -0
  168. package/dist/stores/{historyStore.d.ts → historyStore.svelte.d.ts} +18 -15
  169. package/dist/stores/{historyStore.js → historyStore.svelte.js} +40 -21
  170. package/dist/stores/{interruptStore.d.ts → interruptStore.svelte.d.ts} +16 -15
  171. package/dist/stores/{interruptStore.js → interruptStore.svelte.js} +85 -94
  172. package/dist/stores/{playgroundStore.d.ts → playgroundStore.svelte.d.ts} +52 -34
  173. package/dist/stores/{playgroundStore.js → playgroundStore.svelte.js} +193 -100
  174. package/dist/stores/{portCoordinateStore.d.ts → portCoordinateStore.svelte.d.ts} +10 -4
  175. package/dist/stores/{portCoordinateStore.js → portCoordinateStore.svelte.js} +38 -35
  176. package/dist/stores/{settingsStore.d.ts → settingsStore.svelte.d.ts} +45 -28
  177. package/dist/stores/{settingsStore.js → settingsStore.svelte.js} +169 -128
  178. package/dist/stores/{workflowStore.d.ts → workflowStore.svelte.d.ts} +101 -65
  179. package/dist/stores/{workflowStore.js → workflowStore.svelte.js} +285 -239
  180. package/dist/stories/CanvasDecorator.svelte +50 -0
  181. package/dist/stories/CanvasDecorator.svelte.d.ts +8 -0
  182. package/dist/stories/NodeDecorator.svelte +74 -0
  183. package/dist/stories/NodeDecorator.svelte.d.ts +8 -0
  184. package/dist/stories/utils.d.ts +93 -0
  185. package/dist/stories/utils.js +122 -0
  186. package/dist/styles/base.css +114 -61
  187. package/dist/styles/toast.css +2 -2
  188. package/dist/styles/tokens.css +250 -185
  189. package/dist/svelte-app.d.ts +0 -6
  190. package/dist/svelte-app.js +13 -31
  191. package/dist/types/index.d.ts +2 -0
  192. package/dist/types/interrupt.d.ts +89 -5
  193. package/dist/types/interrupt.js +13 -1
  194. package/dist/types/playground.d.ts +42 -1
  195. package/dist/types/playground.js +38 -0
  196. package/dist/types/settings.js +1 -1
  197. package/dist/utils/colors.js +4 -4
  198. package/dist/utils/connections.js +33 -8
  199. package/dist/utils/icons.js +1 -1
  200. package/dist/utils/logger.d.ts +47 -0
  201. package/dist/utils/logger.js +72 -0
  202. package/dist/utils/nodeWrapper.js +1 -1
  203. package/dist/utils/sanitize.d.ts +19 -0
  204. package/dist/utils/sanitize.js +31 -0
  205. package/dist/utils/validation.d.ts +29 -0
  206. package/dist/utils/validation.js +39 -0
  207. package/package.json +243 -232
@@ -1158,6 +1158,8 @@ export interface NodeExecutionInfo {
1158
1158
  lastError?: string;
1159
1159
  /** Whether the node is currently being executed */
1160
1160
  isExecuting: boolean;
1161
+ /** Execution output data (e.g., active branches for gateway nodes) */
1162
+ output?: Record<string, unknown>;
1161
1163
  }
1162
1164
  /**
1163
1165
  * Workflow execution status
@@ -17,7 +17,7 @@ export { initialState, transition, isTerminalState, isSubmitting, hasError, canS
17
17
  * - `text`: Free-form text input
18
18
  * - `form`: JSON Schema-based form
19
19
  */
20
- export type InterruptType = 'confirmation' | 'choice' | 'text' | 'form';
20
+ export type InterruptType = 'confirmation' | 'choice' | 'text' | 'form' | 'review';
21
21
  /**
22
22
  * Status of an interrupt request
23
23
  *
@@ -101,10 +101,86 @@ export interface FormConfig {
101
101
  /** Default values for form fields */
102
102
  defaultValues?: Record<string, unknown>;
103
103
  }
104
+ /**
105
+ * A single field change proposed for review
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const change: ReviewChange = {
110
+ * field: "title",
111
+ * label: "Page Title",
112
+ * original: "About Us",
113
+ * proposed: "About Our Company"
114
+ * };
115
+ * ```
116
+ */
117
+ export interface ReviewChange {
118
+ /** Field identifier (machine key) */
119
+ field: string;
120
+ /** Human-readable field label */
121
+ label: string;
122
+ /** Original value before the proposed change */
123
+ original: unknown;
124
+ /** Proposed new value */
125
+ proposed: unknown;
126
+ }
127
+ /**
128
+ * Configuration for review-type interrupts
129
+ *
130
+ * Displays a list of proposed field changes for the user to
131
+ * accept or reject individually before submitting.
132
+ */
133
+ export interface ReviewConfig {
134
+ /** The prompt message to display */
135
+ message: string;
136
+ /** List of field changes to review */
137
+ changes: ReviewChange[];
138
+ /** Label for the "Accept All" button */
139
+ acceptAllLabel?: string;
140
+ /** Label for the "Reject All" button */
141
+ rejectAllLabel?: string;
142
+ /** Label for the submit button */
143
+ submitLabel?: string;
144
+ }
145
+ /**
146
+ * Per-field decision in a review resolution
147
+ */
148
+ export interface ReviewFieldDecision {
149
+ /** Whether the proposed change was accepted */
150
+ accepted: boolean;
151
+ /** The effective value (proposed if accepted, original if rejected) */
152
+ value: unknown;
153
+ }
154
+ /**
155
+ * Resolution value for review-type interrupts
156
+ *
157
+ * Contains individual decisions for each field and a summary count.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const resolution: ReviewResolution = {
162
+ * decisions: {
163
+ * title: { accepted: true, value: "About Our Company" },
164
+ * meta_description: { accepted: false, value: "Learn about us" }
165
+ * },
166
+ * summary: { accepted: 1, rejected: 1, total: 2 }
167
+ * };
168
+ * ```
169
+ */
170
+ export interface ReviewResolution {
171
+ /** Map of field identifier to the user's decision */
172
+ decisions: Record<string, ReviewFieldDecision>;
173
+ /** Summary counts of accepted/rejected fields */
174
+ summary: {
175
+ accepted: number;
176
+ rejected: number;
177
+ total: number;
178
+ };
179
+ }
104
180
  /**
105
181
  * Union type for interrupt-specific configuration
106
182
  */
107
- export type InterruptConfig = ConfirmationConfig | ChoiceConfig | TextConfig | FormConfig;
183
+ export type InterruptConfig = ConfirmationConfig | ChoiceConfig | TextConfig | FormConfig | ReviewConfig;
108
184
  /**
109
185
  * Core interrupt data structure
110
186
  *
@@ -268,6 +344,14 @@ export interface InterruptMessageMetadata {
268
344
  resolvedByUserName?: string;
269
345
  /** User ID of the person who resolved the interrupt */
270
346
  resolvedByUserId?: string;
347
+ /** Review: list of field changes to review */
348
+ changes?: ReviewChange[];
349
+ /** Review: label for the "Accept All" button */
350
+ accept_all_label?: string;
351
+ /** Review: label for the "Reject All" button */
352
+ reject_all_label?: string;
353
+ /** Review: label for the submit button */
354
+ submit_label?: string;
271
355
  }
272
356
  /**
273
357
  * Type guard to check if message metadata indicates an interrupt
@@ -275,7 +359,7 @@ export interface InterruptMessageMetadata {
275
359
  * @param metadata - Message metadata to check
276
360
  * @returns True if the metadata indicates an interrupt request
277
361
  */
278
- export declare function isInterruptMetadata(metadata: Record<string, unknown> | undefined): boolean;
362
+ export declare function isInterruptMetadata(metadata: Record<string, unknown> | undefined): metadata is Record<string, unknown>;
279
363
  /**
280
364
  * Safely extract interrupt metadata from a generic record
281
365
  *
@@ -299,9 +383,9 @@ export interface InterruptPollingConfig {
299
383
  /** Whether to enable dedicated interrupt polling */
300
384
  enabled: boolean;
301
385
  /** Polling interval in milliseconds */
302
- interval?: number;
386
+ interval: number;
303
387
  /** Maximum polling backoff interval in milliseconds */
304
- maxBackoff?: number;
388
+ maxBackoff: number;
305
389
  }
306
390
  /**
307
391
  * Default interrupt polling configuration
@@ -50,7 +50,11 @@ export function extractInterruptMetadata(metadata) {
50
50
  min_selections: metadata.min_selections,
51
51
  max_selections: metadata.max_selections,
52
52
  resolvedByUserName: metadata.resolvedByUserName,
53
- resolvedByUserId: metadata.resolvedByUserId
53
+ resolvedByUserId: metadata.resolvedByUserId,
54
+ changes: metadata.changes,
55
+ accept_all_label: metadata.accept_all_label,
56
+ reject_all_label: metadata.reject_all_label,
57
+ submit_label: metadata.submit_label
54
58
  };
55
59
  }
56
60
  /**
@@ -119,6 +123,14 @@ function buildInterruptConfig(metadata, message) {
119
123
  schema: metadata.schema ?? { type: 'object', properties: {} },
120
124
  defaultValues: metadata.default_value
121
125
  };
126
+ case 'review':
127
+ return {
128
+ message,
129
+ changes: metadata.changes ?? [],
130
+ acceptAllLabel: metadata.accept_all_label,
131
+ rejectAllLabel: metadata.reject_all_label,
132
+ submitLabel: metadata.submit_label
133
+ };
122
134
  default:
123
135
  return { message };
124
136
  }
@@ -10,7 +10,31 @@ import type { ConfigProperty } from './index.js';
10
10
  /**
11
11
  * Status of a playground session
12
12
  */
13
- export type PlaygroundSessionStatus = 'idle' | 'running' | 'completed' | 'failed';
13
+ export type PlaygroundSessionStatus = 'idle' | 'running' | 'awaiting_input' | 'completed' | 'failed';
14
+ /**
15
+ * Statuses that stop polling by default (resource efficiency)
16
+ */
17
+ export declare const DEFAULT_STOP_POLLING_STATUSES: PlaygroundSessionStatus[];
18
+ /**
19
+ * Statuses that are considered terminal by default (clears isExecuting)
20
+ */
21
+ export declare const DEFAULT_TERMINAL_STATUSES: PlaygroundSessionStatus[];
22
+ /**
23
+ * Default implementation for determining if polling should stop.
24
+ * Consumers can override this via PlaygroundConfig.shouldStopPolling.
25
+ *
26
+ * @param status - The current session status
27
+ * @returns True if polling should stop
28
+ */
29
+ export declare function defaultShouldStopPolling(status: PlaygroundSessionStatus): boolean;
30
+ /**
31
+ * Default implementation for determining if a status is terminal (clears isExecuting).
32
+ * Consumers can override this via PlaygroundConfig.isTerminalStatus.
33
+ *
34
+ * @param status - The current session status
35
+ * @returns True if the status is terminal
36
+ */
37
+ export declare function defaultIsTerminalStatus(status: PlaygroundSessionStatus): boolean;
14
38
  /**
15
39
  * Role of a message sender in the playground
16
40
  *
@@ -219,6 +243,11 @@ export interface PlaygroundConfig {
219
243
  * Note: Only runs once per session - subsequent runs require clicking the Run button.
220
244
  */
221
245
  autoRun?: boolean;
246
+ /**
247
+ * Width of the sidebar in CSS units (default: "280px")
248
+ * Accepts any valid CSS width value, e.g. "300px", "20rem".
249
+ */
250
+ sidebarWidth?: string;
222
251
  /**
223
252
  * Whether to show the sidebar with session list (default: true)
224
253
  * When false, the sidebar is hidden, creating a minimal chat widget experience.
@@ -231,6 +260,18 @@ export interface PlaygroundConfig {
231
260
  * Typically used together with showSidebar: false for minimal UI.
232
261
  */
233
262
  showSessionHeader?: boolean;
263
+ /**
264
+ * Determines if polling should stop for a given session status.
265
+ * Override to customize which statuses pause polling.
266
+ * @default defaultShouldStopPolling (stops on idle, completed, failed, awaiting_input)
267
+ */
268
+ shouldStopPolling?: (status: PlaygroundSessionStatus) => boolean;
269
+ /**
270
+ * Determines if a session status is terminal (clears isExecuting).
271
+ * Override to customize which statuses end the executing state.
272
+ * @default defaultIsTerminalStatus (terminal on idle, completed, failed, awaiting_input)
273
+ */
274
+ isTerminalStatus?: (status: PlaygroundSessionStatus) => boolean;
234
275
  }
235
276
  /**
236
277
  * Metadata field to control Run button state from backend.
@@ -6,6 +6,44 @@
6
6
  *
7
7
  * @module types/playground
8
8
  */
9
+ /**
10
+ * Statuses that stop polling by default (resource efficiency)
11
+ */
12
+ export const DEFAULT_STOP_POLLING_STATUSES = [
13
+ 'idle',
14
+ 'completed',
15
+ 'failed',
16
+ 'awaiting_input'
17
+ ];
18
+ /**
19
+ * Statuses that are considered terminal by default (clears isExecuting)
20
+ */
21
+ export const DEFAULT_TERMINAL_STATUSES = [
22
+ 'idle',
23
+ 'completed',
24
+ 'failed',
25
+ 'awaiting_input'
26
+ ];
27
+ /**
28
+ * Default implementation for determining if polling should stop.
29
+ * Consumers can override this via PlaygroundConfig.shouldStopPolling.
30
+ *
31
+ * @param status - The current session status
32
+ * @returns True if polling should stop
33
+ */
34
+ export function defaultShouldStopPolling(status) {
35
+ return DEFAULT_STOP_POLLING_STATUSES.includes(status);
36
+ }
37
+ /**
38
+ * Default implementation for determining if a status is terminal (clears isExecuting).
39
+ * Consumers can override this via PlaygroundConfig.isTerminalStatus.
40
+ *
41
+ * @param status - The current session status
42
+ * @returns True if the status is terminal
43
+ */
44
+ export function defaultIsTerminalStatus(status) {
45
+ return DEFAULT_TERMINAL_STATUSES.includes(status);
46
+ }
9
47
  /**
10
48
  * Metadata field to control Run button state from backend.
11
49
  * When a message contains this field set to true, the Run button becomes enabled.
@@ -66,7 +66,7 @@ export const DEFAULT_UI_SETTINGS = {
66
66
  export const DEFAULT_BEHAVIOR_SETTINGS = {
67
67
  autoSave: false,
68
68
  autoSaveInterval: 30000,
69
- undoHistoryLimit: 0,
69
+ undoHistoryLimit: 50,
70
70
  confirmDelete: false
71
71
  };
72
72
  /**
@@ -4,7 +4,8 @@
4
4
  * Uses BEM syntax for CSS classes
5
5
  */
6
6
  import { getPortCompatibilityChecker } from './connections.js';
7
- import { getCategoryColor as getCategoryColorFromStore } from '../stores/categoriesStore.js';
7
+ import { getCategoryColor as getCategoryColorFromStore } from '../stores/categoriesStore.svelte.js';
8
+ import { logger } from './logger.js';
8
9
  /**
9
10
  * Category color mapping to design tokens (CSS variables)
10
11
  * Uses --fd-node-* tokens from tokens.css
@@ -83,7 +84,6 @@ export function getDataTypeColorToken(dataType) {
83
84
  }
84
85
  catch {
85
86
  // Fallback to static color mapping if port checker not initialized
86
- // console.warn("Port compatibility checker not initialized, using fallback colors");
87
87
  }
88
88
  return DEFAULT_DATA_TYPE_COLORS[dataType.toLowerCase()] || 'var(--fd-node-slate)';
89
89
  }
@@ -96,7 +96,7 @@ export function getDataTypeConfig(dataType) {
96
96
  return checker.getDataTypeConfig(dataType);
97
97
  }
98
98
  catch (error) {
99
- console.warn('Port compatibility checker not initialized:', error);
99
+ logger.warn('Port compatibility checker not initialized:', error);
100
100
  return undefined;
101
101
  }
102
102
  }
@@ -109,7 +109,7 @@ export function getAvailableDataTypes() {
109
109
  return checker.getEnabledDataTypes();
110
110
  }
111
111
  catch (error) {
112
- console.warn('Port compatibility checker not initialized:', error);
112
+ logger.warn('Port compatibility checker not initialized:', error);
113
113
  return [];
114
114
  }
115
115
  }
@@ -266,6 +266,18 @@ export function getConnectionSuggestions(nodeId, nodes, nodeTypes) {
266
266
  * @returns True if any cycle exists in the workflow
267
267
  */
268
268
  export function hasCycles(nodes, edges) {
269
+ // Build adjacency map once (O(E)) so the DFS inner loop is O(1) per lookup
270
+ // instead of scanning all edges on every recursive call (which was O(V*E)).
271
+ const adjacencyMap = new Map();
272
+ for (const node of nodes) {
273
+ adjacencyMap.set(node.id, []);
274
+ }
275
+ for (const edge of edges) {
276
+ const neighbors = adjacencyMap.get(edge.source);
277
+ if (neighbors) {
278
+ neighbors.push(edge.target);
279
+ }
280
+ }
269
281
  const visited = new Set();
270
282
  const recursionStack = new Set();
271
283
  function hasCycleUtil(nodeId) {
@@ -275,10 +287,10 @@ export function hasCycles(nodes, edges) {
275
287
  return false;
276
288
  visited.add(nodeId);
277
289
  recursionStack.add(nodeId);
278
- // Get all outgoing edges from this node
279
- const outgoingEdges = edges.filter((e) => e.source === nodeId);
280
- for (const edge of outgoingEdges) {
281
- if (hasCycleUtil(edge.target))
290
+ // Use pre-built adjacency map instead of filtering all edges each call
291
+ const neighbors = adjacencyMap.get(nodeId) || [];
292
+ for (const target of neighbors) {
293
+ if (hasCycleUtil(target))
282
294
  return true;
283
295
  }
284
296
  recursionStack.delete(nodeId);
@@ -312,6 +324,19 @@ export function hasCycles(nodes, edges) {
312
324
  export function hasInvalidCycles(nodes, edges) {
313
325
  // Filter out loopback edges - these create valid cycles for loop iteration
314
326
  const nonLoopbackEdges = edges.filter((edge) => !isLoopbackEdge(edge));
327
+ // Build adjacency map from non-loopback edges once (O(E)) so the DFS inner
328
+ // loop is O(1) per lookup instead of scanning all edges on every recursive
329
+ // call (which was O(V*E)).
330
+ const adjacencyMap = new Map();
331
+ for (const node of nodes) {
332
+ adjacencyMap.set(node.id, []);
333
+ }
334
+ for (const edge of nonLoopbackEdges) {
335
+ const neighbors = adjacencyMap.get(edge.source);
336
+ if (neighbors) {
337
+ neighbors.push(edge.target);
338
+ }
339
+ }
315
340
  // Check for cycles using only non-loopback edges
316
341
  const visited = new Set();
317
342
  const recursionStack = new Set();
@@ -327,10 +352,10 @@ export function hasInvalidCycles(nodes, edges) {
327
352
  return false;
328
353
  visited.add(nodeId);
329
354
  recursionStack.add(nodeId);
330
- // Get all outgoing non-loopback edges from this node
331
- const outgoingEdges = nonLoopbackEdges.filter((e) => e.source === nodeId);
332
- for (const edge of outgoingEdges) {
333
- if (hasCycleUtil(edge.target))
355
+ // Use pre-built adjacency map instead of filtering all edges each call
356
+ const neighbors = adjacencyMap.get(nodeId) || [];
357
+ for (const target of neighbors) {
358
+ if (hasCycleUtil(target))
334
359
  return true;
335
360
  }
336
361
  recursionStack.delete(nodeId);
@@ -2,7 +2,7 @@
2
2
  * Centralized icon management for FlowDrop
3
3
  * Ensures consistent icon usage across all components
4
4
  */
5
- import { getCategoryIcon as getCategoryIconFromStore } from '../stores/categoriesStore.js';
5
+ import { getCategoryIcon as getCategoryIconFromStore } from '../stores/categoriesStore.svelte.js';
6
6
  /**
7
7
  * Default icons for different contexts
8
8
  */
@@ -0,0 +1,47 @@
1
+ /**
2
+ * FlowDrop Logger
3
+ *
4
+ * Lightweight configurable logger for the FlowDrop library.
5
+ * Disabled by default — consumers enable it for debugging.
6
+ *
7
+ * @module utils/logger
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { setLogLevel } from '@d34dman/flowdrop/core';
12
+ *
13
+ * // Enable debug logging during development
14
+ * setLogLevel('debug');
15
+ *
16
+ * // Enable only warnings and errors
17
+ * setLogLevel('warn');
18
+ *
19
+ * // Disable all logging (default)
20
+ * setLogLevel('none');
21
+ * ```
22
+ */
23
+ /** Log severity levels. `'none'` disables all output. */
24
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
25
+ /**
26
+ * Set the minimum log level for FlowDrop library output.
27
+ * Messages below this level are silently discarded.
28
+ *
29
+ * @param level - The minimum severity to display. Use `'none'` to disable all output.
30
+ */
31
+ export declare function setLogLevel(level: LogLevel): void;
32
+ /**
33
+ * Get the current log level.
34
+ */
35
+ export declare function getLogLevel(): LogLevel;
36
+ /**
37
+ * FlowDrop library logger.
38
+ *
39
+ * All methods are no-ops when the log level is set to `'none'` (default).
40
+ * Enable output by calling `setLogLevel()`.
41
+ */
42
+ export declare const logger: {
43
+ debug(message: string, ...args: unknown[]): void;
44
+ info(message: string, ...args: unknown[]): void;
45
+ warn(message: string, ...args: unknown[]): void;
46
+ error(message: string, ...args: unknown[]): void;
47
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * FlowDrop Logger
3
+ *
4
+ * Lightweight configurable logger for the FlowDrop library.
5
+ * Disabled by default — consumers enable it for debugging.
6
+ *
7
+ * @module utils/logger
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { setLogLevel } from '@d34dman/flowdrop/core';
12
+ *
13
+ * // Enable debug logging during development
14
+ * setLogLevel('debug');
15
+ *
16
+ * // Enable only warnings and errors
17
+ * setLogLevel('warn');
18
+ *
19
+ * // Disable all logging (default)
20
+ * setLogLevel('none');
21
+ * ```
22
+ */
23
+ const LOG_PRIORITY = {
24
+ debug: 0,
25
+ info: 1,
26
+ warn: 2,
27
+ error: 3,
28
+ none: 4
29
+ };
30
+ let currentLevel = 'none';
31
+ /**
32
+ * Set the minimum log level for FlowDrop library output.
33
+ * Messages below this level are silently discarded.
34
+ *
35
+ * @param level - The minimum severity to display. Use `'none'` to disable all output.
36
+ */
37
+ export function setLogLevel(level) {
38
+ currentLevel = level;
39
+ }
40
+ /**
41
+ * Get the current log level.
42
+ */
43
+ export function getLogLevel() {
44
+ return currentLevel;
45
+ }
46
+ function shouldLog(level) {
47
+ return LOG_PRIORITY[level] >= LOG_PRIORITY[currentLevel];
48
+ }
49
+ /**
50
+ * FlowDrop library logger.
51
+ *
52
+ * All methods are no-ops when the log level is set to `'none'` (default).
53
+ * Enable output by calling `setLogLevel()`.
54
+ */
55
+ export const logger = {
56
+ debug(message, ...args) {
57
+ if (shouldLog('debug'))
58
+ console.debug(`[FlowDrop] ${message}`, ...args);
59
+ },
60
+ info(message, ...args) {
61
+ if (shouldLog('info'))
62
+ console.info(`[FlowDrop] ${message}`, ...args);
63
+ },
64
+ warn(message, ...args) {
65
+ if (shouldLog('warn'))
66
+ console.warn(`[FlowDrop] ${message}`, ...args);
67
+ },
68
+ error(message, ...args) {
69
+ if (shouldLog('error'))
70
+ console.error(`[FlowDrop] ${message}`, ...args);
71
+ }
72
+ };
@@ -19,7 +19,7 @@ export function createNodeWrapperConfig(nodeId, executionInfo, config = {}) {
19
19
  executionInfo,
20
20
  statusPosition: config.position || DEFAULT_NODE_STATUS_CONFIG.position,
21
21
  statusSize: config.size || DEFAULT_NODE_STATUS_CONFIG.size,
22
- showStatusDetails: config.showDetails ?? DEFAULT_NODE_STATUS_CONFIG.showDetails
22
+ showStatusDetails: config.showDetails ?? DEFAULT_NODE_STATUS_CONFIG.showDetails ?? true
23
23
  };
24
24
  }
25
25
  /**
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Sanitize an HTML string to prevent XSS attacks.
3
+ *
4
+ * Uses DOMPurify defaults which strip dangerous elements (script, iframe, object)
5
+ * and event handler attributes (onerror, onclick, etc.) while preserving safe HTML
6
+ * elements typically produced by markdown renderers.
7
+ *
8
+ * @param dirty - The untrusted HTML string to sanitize
9
+ * @returns Sanitized HTML string safe for use with {@html}
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { sanitizeHtml } from '@d34dman/flowdrop/core';
14
+ * import { marked } from 'marked';
15
+ *
16
+ * const safeHtml = sanitizeHtml(marked.parse(userInput) as string);
17
+ * ```
18
+ */
19
+ export declare function sanitizeHtml(dirty: string): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * HTML Sanitization Utility
3
+ *
4
+ * Provides XSS protection for rendered HTML content using DOMPurify.
5
+ * Used internally by components that render user-provided or API-provided HTML
6
+ * (e.g., markdown output, interrupt review diffs).
7
+ *
8
+ * @module utils/sanitize
9
+ */
10
+ import DOMPurify from 'dompurify';
11
+ /**
12
+ * Sanitize an HTML string to prevent XSS attacks.
13
+ *
14
+ * Uses DOMPurify defaults which strip dangerous elements (script, iframe, object)
15
+ * and event handler attributes (onerror, onclick, etc.) while preserving safe HTML
16
+ * elements typically produced by markdown renderers.
17
+ *
18
+ * @param dirty - The untrusted HTML string to sanitize
19
+ * @returns Sanitized HTML string safe for use with {@html}
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { sanitizeHtml } from '@d34dman/flowdrop/core';
24
+ * import { marked } from 'marked';
25
+ *
26
+ * const safeHtml = sanitizeHtml(marked.parse(userInput) as string);
27
+ * ```
28
+ */
29
+ export function sanitizeHtml(dirty) {
30
+ return DOMPurify.sanitize(dirty);
31
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * FlowDrop Workflow Validation
3
+ *
4
+ * Lightweight validation utilities for workflow JSON data.
5
+ * Checks minimum required fields without relying on a full JSON Schema validator library.
6
+ *
7
+ * @module utils/validation
8
+ */
9
+ /**
10
+ * Result of a workflow validation check.
11
+ */
12
+ export interface WorkflowValidationResult {
13
+ valid: boolean;
14
+ /** Human-readable description of the first validation failure, or undefined if valid. */
15
+ error?: string;
16
+ }
17
+ /**
18
+ * Validate that the given value has the minimum required fields of a FlowDrop Workflow.
19
+ *
20
+ * Required fields (matching the Workflow type and workflow.schema.json):
21
+ * - `id` — string
22
+ * - `name` — string
23
+ * - `nodes` — array
24
+ * - `edges` — array
25
+ *
26
+ * @param data - The parsed JSON value to validate.
27
+ * @returns A `WorkflowValidationResult` indicating whether the data is valid.
28
+ */
29
+ export declare function validateWorkflowData(data: unknown): WorkflowValidationResult;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * FlowDrop Workflow Validation
3
+ *
4
+ * Lightweight validation utilities for workflow JSON data.
5
+ * Checks minimum required fields without relying on a full JSON Schema validator library.
6
+ *
7
+ * @module utils/validation
8
+ */
9
+ /**
10
+ * Validate that the given value has the minimum required fields of a FlowDrop Workflow.
11
+ *
12
+ * Required fields (matching the Workflow type and workflow.schema.json):
13
+ * - `id` — string
14
+ * - `name` — string
15
+ * - `nodes` — array
16
+ * - `edges` — array
17
+ *
18
+ * @param data - The parsed JSON value to validate.
19
+ * @returns A `WorkflowValidationResult` indicating whether the data is valid.
20
+ */
21
+ export function validateWorkflowData(data) {
22
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
23
+ return { valid: false, error: 'Workflow must be a JSON object.' };
24
+ }
25
+ const obj = data;
26
+ if (typeof obj.id !== 'string' || obj.id.trim() === '') {
27
+ return { valid: false, error: 'Workflow JSON must contain a non-empty string "id" field.' };
28
+ }
29
+ if (typeof obj.name !== 'string' || obj.name.trim() === '') {
30
+ return { valid: false, error: 'Workflow JSON must contain a non-empty string "name" field.' };
31
+ }
32
+ if (!Array.isArray(obj.nodes)) {
33
+ return { valid: false, error: 'Workflow JSON must contain a "nodes" array.' };
34
+ }
35
+ if (!Array.isArray(obj.edges)) {
36
+ return { valid: false, error: 'Workflow JSON must contain an "edges" array.' };
37
+ }
38
+ return { valid: true };
39
+ }