@flowdrop/flowdrop 1.6.0 → 1.8.0

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 (78) hide show
  1. package/README.md +10 -0
  2. package/dist/components/App.svelte +153 -84
  3. package/dist/components/App.svelte.d.ts +16 -1
  4. package/dist/components/ConfigModal.svelte +2 -1
  5. package/dist/components/ConfigPanel.svelte +3 -2
  6. package/dist/components/FlowDropZone.svelte +2 -1
  7. package/dist/components/LogsSidebar.svelte +3 -2
  8. package/dist/components/Navbar.svelte +10 -6
  9. package/dist/components/NodeSidebar.svelte +4 -3
  10. package/dist/components/NodeStatusOverlay.svelte +14 -7
  11. package/dist/components/NodeSwapPicker.svelte +2 -1
  12. package/dist/components/PipelineStatus.svelte +10 -7
  13. package/dist/components/ReadOnlyDetails.svelte +4 -2
  14. package/dist/components/SchemaForm.svelte +20 -9
  15. package/dist/components/SchemaForm.svelte.d.ts +2 -4
  16. package/dist/components/SettingsModal.svelte +4 -3
  17. package/dist/components/SettingsPanel.svelte +3 -2
  18. package/dist/components/SwapMappingEditor.svelte +2 -1
  19. package/dist/components/WorkflowEditor.svelte +3 -2
  20. package/dist/components/chat/AIChatPanel.svelte +22 -7
  21. package/dist/components/chat/AIChatPanel.svelte.d.ts +3 -0
  22. package/dist/components/chat/CommandPreview.svelte +10 -6
  23. package/dist/components/console/CommandConsole.svelte +4 -3
  24. package/dist/components/form/FormArray.svelte +33 -20
  25. package/dist/components/form/FormArray.svelte.d.ts +3 -1
  26. package/dist/components/form/FormAutocomplete.svelte +18 -7
  27. package/dist/components/form/FormCodeEditor.svelte +6 -4
  28. package/dist/components/form/FormFieldWrapper.svelte +2 -1
  29. package/dist/components/form/FormMarkdownEditor.svelte +152 -108
  30. package/dist/components/form/FormMarkdownEditor.svelte.d.ts +1 -1
  31. package/dist/components/form/FormTemplateEditor.svelte +2 -1
  32. package/dist/components/form/FormToggle.svelte +23 -5
  33. package/dist/components/form/FormToggle.svelte.d.ts +6 -2
  34. package/dist/components/interrupt/ChoicePrompt.svelte +14 -5
  35. package/dist/components/interrupt/ConfirmationPrompt.svelte +8 -5
  36. package/dist/components/interrupt/FormPrompt.svelte +28 -7
  37. package/dist/components/interrupt/InterruptBubble.svelte +27 -18
  38. package/dist/components/interrupt/ReviewPrompt.svelte +32 -22
  39. package/dist/components/interrupt/TextInputPrompt.svelte +12 -5
  40. package/dist/components/layouts/MainLayout.svelte +4 -3
  41. package/dist/components/nodes/GatewayNode.svelte +8 -3
  42. package/dist/components/nodes/IdeaNode.svelte +2 -1
  43. package/dist/components/nodes/NotesNode.svelte +18 -12
  44. package/dist/components/nodes/SimpleNode.svelte +8 -8
  45. package/dist/components/nodes/WorkflowNode.svelte +8 -3
  46. package/dist/components/playground/ChatPanel.svelte +36 -24
  47. package/dist/components/playground/MessageBubble.svelte +15 -7
  48. package/dist/components/playground/Playground.svelte +2 -1
  49. package/dist/components/playground/PlaygroundModal.svelte +2 -1
  50. package/dist/components/playground/SessionManager.svelte +14 -10
  51. package/dist/core/index.d.ts +2 -0
  52. package/dist/core/index.js +9 -0
  53. package/dist/editor/index.d.ts +1 -1
  54. package/dist/editor/index.js +1 -1
  55. package/dist/messages/context.d.ts +29 -0
  56. package/dist/messages/context.js +38 -0
  57. package/dist/messages/defaults.d.ts +396 -0
  58. package/dist/messages/defaults.js +356 -0
  59. package/dist/messages/deprecation.d.ts +20 -0
  60. package/dist/messages/deprecation.js +33 -0
  61. package/dist/messages/index.d.ts +11 -0
  62. package/dist/messages/index.js +10 -0
  63. package/dist/messages/merge.d.ts +28 -0
  64. package/dist/messages/merge.js +53 -0
  65. package/dist/messages/types.d.ts +29 -0
  66. package/dist/messages/types.js +13 -0
  67. package/dist/schemas/v1/workflow.schema.json +5 -0
  68. package/dist/services/draftStorage.d.ts +13 -0
  69. package/dist/services/draftStorage.js +36 -0
  70. package/dist/stores/workflowStore.svelte.d.ts +1 -0
  71. package/dist/stores/workflowStore.svelte.js +1 -0
  72. package/dist/styles/base.css +13 -4
  73. package/dist/svelte-app.d.ts +11 -0
  74. package/dist/svelte-app.js +11 -2
  75. package/dist/types/index.d.ts +2 -0
  76. package/dist/utils/connections.d.ts +4 -0
  77. package/dist/utils/connections.js +6 -0
  78. package/package.json +1 -1
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Default English strings for every user-facing label, message, and tooltip
3
+ * rendered by FlowDrop.
4
+ *
5
+ * Consumers override any subset by passing `messages={() => partial}` to the
6
+ * root `<FlowDrop>` component (see `./context.ts`).
7
+ *
8
+ * Conventions:
9
+ * - Group by **domain** (form, interrupt, chat, navigation, status, nodes,
10
+ * common), not by component file path. Component paths churn; domains
11
+ * don't.
12
+ * - Parameterised strings are **functions**, not template strings with
13
+ * placeholders. The compiler then enforces the param shape at every call
14
+ * site.
15
+ * - Leaves are either `string` or `(params) => string`. Nothing else.
16
+ *
17
+ * The `as const` assertion is load-bearing: without it, every string widens
18
+ * to `string` and the `Messages` type loses its precision.
19
+ */
20
+ export const defaultMessages = {
21
+ common: {
22
+ save: 'Save',
23
+ cancel: 'Cancel',
24
+ confirm: 'Confirm',
25
+ close: 'Close',
26
+ delete: 'Delete',
27
+ yes: 'Yes',
28
+ no: 'No'
29
+ },
30
+ form: {
31
+ array: {
32
+ // Item-level controls — `n` is the 1-based item position the user sees.
33
+ itemLabel: ({ n }) => `Item ${n}`,
34
+ expandItem: 'Expand item',
35
+ collapseItem: 'Collapse item',
36
+ moveItemUp: ({ n }) => `Move item ${n} up`,
37
+ moveItemDown: ({ n }) => `Move item ${n} down`,
38
+ deleteItem: ({ n }) => `Delete item ${n}`,
39
+ moveUp: 'Move up',
40
+ moveDown: 'Move down',
41
+ delete: 'Delete item',
42
+ // Boolean rendering inside array items.
43
+ yes: 'Yes',
44
+ no: 'No',
45
+ // Empty state and limits.
46
+ empty: 'No items yet',
47
+ add: 'Add Item',
48
+ count: ({ n }) => `${n} item${n !== 1 ? 's' : ''}`,
49
+ min: ({ n }) => `Min: ${n}`,
50
+ max: ({ n }) => `Max: ${n}`,
51
+ unsupported: ({ type }) => `Complex item type "${type}" is not fully supported.`
52
+ },
53
+ markdown: {
54
+ placeholder: 'Write your markdown here...',
55
+ // Toolbar action labels (rendered as `title` and used in `title` with
56
+ // an optional shortcut suffix appended by the component).
57
+ bold: 'Bold',
58
+ italic: 'Italic',
59
+ strikethrough: 'Strikethrough',
60
+ heading1: 'Heading 1',
61
+ heading2: 'Heading 2',
62
+ heading3: 'Heading 3',
63
+ quote: 'Quote',
64
+ unorderedList: 'Unordered List',
65
+ orderedList: 'Ordered List',
66
+ link: 'Link',
67
+ image: 'Image',
68
+ table: 'Table',
69
+ // Region/widget aria-labels.
70
+ editor: 'Markdown editor',
71
+ toolbar: 'Markdown formatting',
72
+ // Status bar metric labels (the metric value is appended after the colon).
73
+ words: 'words',
74
+ lines: 'lines',
75
+ characters: 'characters'
76
+ },
77
+ autocomplete: {
78
+ removeTag: ({ label }) => `Remove ${label}`,
79
+ loading: 'Loading suggestions',
80
+ loadingPending: 'Loading suggestions...',
81
+ clearAll: 'Clear all selections',
82
+ suggestions: 'Suggestions',
83
+ retry: 'Retry',
84
+ noResults: 'No results found'
85
+ },
86
+ field: {
87
+ required: 'required'
88
+ },
89
+ toggle: {
90
+ enabled: 'Enabled',
91
+ disabled: 'Disabled'
92
+ },
93
+ schema: {
94
+ save: 'Save',
95
+ cancel: 'Cancel',
96
+ empty: 'No schema properties defined.'
97
+ },
98
+ code: {
99
+ // FormCodeEditor — JSON editor aria-label.
100
+ editor: 'JSON editor'
101
+ },
102
+ template: {
103
+ // FormTemplateEditor — Mustache/template editor aria-label.
104
+ editor: 'Template editor'
105
+ }
106
+ },
107
+ interrupt: {
108
+ // Shared resolution notice rendered after any interrupt prompt is answered.
109
+ responseSubmitted: 'Response submitted',
110
+ responseSubmittedBy: ({ name }) => `Response submitted by ${name}`,
111
+ confirmation: {
112
+ yes: 'Yes',
113
+ no: 'No'
114
+ },
115
+ choice: {
116
+ submit: 'Submit',
117
+ // Selection counter rendered inside the picker, e.g. "2 of 5 selected".
118
+ selectedCount: ({ n, total }) => `${n} of ${total} selected`,
119
+ min: ({ n }) => `(min: ${n})`,
120
+ max: ({ n }) => `(max: ${n})`
121
+ },
122
+ review: {
123
+ acceptAll: 'Accept All',
124
+ rejectAll: 'Reject All',
125
+ submit: 'Submit Review',
126
+ empty: '(empty)',
127
+ yes: 'Yes',
128
+ no: 'No',
129
+ // Per-row controls.
130
+ acceptItem: ({ label }) => `Accept ${label}`,
131
+ rejectItem: ({ label }) => `Reject ${label}`,
132
+ accept: 'Accept',
133
+ reject: 'Reject',
134
+ accepted: 'Accepted',
135
+ rejected: 'Rejected',
136
+ // Diff/preview controls.
137
+ rendered: 'Rendered',
138
+ rawHtml: 'Raw HTML',
139
+ original: 'Original:',
140
+ proposed: 'Proposed:',
141
+ diff: 'Diff:',
142
+ // Header counter — accepted decisions out of total.
143
+ counter: ({ accepted, total }) => `${accepted} of ${total} accepted`,
144
+ // Footer summary — accepted/rejected breakdown.
145
+ summary: ({ accepted, rejected, total }) => `${accepted} accepted, ${rejected} rejected out of ${total} changes`
146
+ },
147
+ form: {
148
+ submit: 'Submit',
149
+ // Boolean and empty-cell rendering in the submitted-values readout.
150
+ yes: 'Yes',
151
+ no: 'No',
152
+ empty: '—',
153
+ submittedValuesTitle: 'Submitted Values'
154
+ },
155
+ text: {
156
+ placeholder: 'Enter your response...',
157
+ min: ({ n }) => `(min: ${n})`,
158
+ submit: 'Submit'
159
+ },
160
+ bubble: {
161
+ // Pre-resolution status — keyed by interrupt kind.
162
+ required: {
163
+ confirmation: 'Confirmation Required',
164
+ selection: 'Selection Required',
165
+ input: 'Input Required',
166
+ form: 'Form Required',
167
+ review: 'Review Required',
168
+ default: 'Action Required'
169
+ },
170
+ // Post-resolution status.
171
+ submitted: {
172
+ confirmation: 'Confirmation Submitted',
173
+ selection: 'Selection Made',
174
+ input: 'Input Submitted',
175
+ form: 'Form Submitted',
176
+ review: 'Review Submitted',
177
+ default: 'Response Submitted'
178
+ },
179
+ cancelled: 'Cancelled',
180
+ errorRetry: 'Error - Click to Retry',
181
+ retry: 'Retry',
182
+ cancel: 'Cancel',
183
+ fromWorkflow: 'From workflow node',
184
+ nodeIdTooltip: ({ id }) => `Node ID: ${id}`
185
+ }
186
+ },
187
+ navigation: {
188
+ // Navbar branding (rendered when no consumer overrides via title prop).
189
+ appName: 'FlowDrop',
190
+ tagline: 'Visual Workflow Manager',
191
+ breadcrumbAriaLabel: 'Breadcrumb',
192
+ connected: 'Connected',
193
+ settingsTitle: 'Settings',
194
+ settingsAriaLabel: 'Open settings',
195
+ // Default primary action labels rendered when no `navbarActions` prop is supplied.
196
+ save: 'Save',
197
+ export: 'Export',
198
+ import: 'Import',
199
+ workflowSettings: 'Workflow Settings',
200
+ // Right-sidebar workflow settings panel (distinct from the navbar action label above).
201
+ workflowSettingsPanelTitle: 'Workflow Settings',
202
+ workflowSettingsPanelSubtitle: 'Settings',
203
+ nodeConfigDescription: 'Node configuration',
204
+ closeSettings: 'Close settings',
205
+ closeConfigModal: 'Close configuration modal',
206
+ copyId: 'Copy ID to clipboard',
207
+ // Bottom panel tab labels.
208
+ bottomPanel: {
209
+ console: 'Console',
210
+ chat: 'AI Assistant'
211
+ }
212
+ },
213
+ layout: {
214
+ // Sidebar/main-region landmarks.
215
+ componentsSidebar: 'Components sidebar',
216
+ workflowCanvas: 'Workflow canvas',
217
+ executionLogs: 'Execution logs sidebar',
218
+ settingsCategories: 'Settings categories',
219
+ searchComponents: 'Search components',
220
+ commandConsole: 'Command Console (`)',
221
+ backToConfiguration: 'Back to configuration',
222
+ // Resize handle labels — keyboard users tab to these.
223
+ resizeLeftSidebar: 'Resize left sidebar',
224
+ resizeRightSidebar: 'Resize right sidebar',
225
+ resizeBottomPanel: 'Resize bottom panel',
226
+ expandSidebar: 'Expand sidebar',
227
+ collapseSidebar: 'Collapse sidebar',
228
+ closePlaygroundModal: 'Close playground modal',
229
+ closeLogsSidebar: 'Close logs sidebar',
230
+ closeConfigPanel: 'Close panel',
231
+ closeConsole: 'Close console',
232
+ swapNode: 'Swap node',
233
+ backToNodeSelection: 'Back to node selection',
234
+ loadSession: ({ name }) => `Load session: ${name}`
235
+ },
236
+ chat: {
237
+ // AIChatPanel labels.
238
+ aiAssistant: 'AI Assistant',
239
+ requiresBackend: 'AI Assistant requires backend configuration',
240
+ loadWorkflow: 'Load a workflow to start chatting',
241
+ helpBuild: 'Ask the AI to help build your workflow',
242
+ placeholder: 'Describe what you want to build...',
243
+ send: 'Send message',
244
+ autoRetry: ({ attempt, max }) => `Auto-retrying (attempt ${attempt}/${max})…`,
245
+ // CommandPreview labels.
246
+ commandPreview: {
247
+ ariaLabel: 'Command preview',
248
+ applying: 'Applying…',
249
+ applied: 'Applied',
250
+ dismissed: 'Dismissed',
251
+ applyAll: 'Apply All',
252
+ cancel: 'Cancel'
253
+ }
254
+ },
255
+ playground: {
256
+ chat: {
257
+ placeholder: 'Type your message...',
258
+ predefinedRun: 'Run workflow'
259
+ },
260
+ states: {
261
+ viewOnlyTitle: 'View only',
262
+ viewOnlyText: 'This playground is in view-only mode. No inputs are available.',
263
+ newSessionTitle: 'New session',
264
+ newSessionText: 'Test your flow with a prompt',
265
+ readyTitle: 'Ready to run',
266
+ readyText: 'Click Run to execute your workflow',
267
+ processing: 'Processing...',
268
+ viewOnlyHelp: 'View-only mode. Workflow execution is controlled externally.'
269
+ },
270
+ actions: {
271
+ stopTitle: 'Stop execution',
272
+ stop: 'Stop',
273
+ sendTitle: 'Send message',
274
+ send: 'Send',
275
+ runTitle: 'Run workflow',
276
+ runWaitingTitle: 'Waiting for workflow to be ready...',
277
+ run: 'Run'
278
+ },
279
+ // Message author labels.
280
+ roles: {
281
+ you: 'You',
282
+ assistant: 'Assistant',
283
+ system: 'System',
284
+ log: 'Log',
285
+ message: 'Message'
286
+ },
287
+ messageTooltips: {
288
+ nodeId: ({ id }) => `Node ID: ${id}`,
289
+ executionDuration: 'Execution duration'
290
+ },
291
+ sessions: {
292
+ header: 'Sessions',
293
+ newSession: 'New Session',
294
+ empty: 'No sessions yet',
295
+ clickAgainToConfirm: 'Click again to confirm',
296
+ cancel: 'Cancel',
297
+ deleteSession: 'Delete session',
298
+ // Relative timestamp formatting.
299
+ justNow: 'Just now',
300
+ minutesAgo: ({ n }) => `${n}m ago`,
301
+ hoursAgo: ({ n }) => `${n}h ago`,
302
+ daysAgo: ({ n }) => `${n}d ago`
303
+ }
304
+ },
305
+ nodes: {
306
+ notes: {
307
+ placeholder: 'Add your notes here...',
308
+ types: {
309
+ info: 'Info',
310
+ warning: 'Warning',
311
+ success: 'Success',
312
+ error: 'Error',
313
+ default: 'Note'
314
+ },
315
+ processing: 'Processing...',
316
+ errorOccurred: 'Error occurred',
317
+ configure: 'Configure note'
318
+ },
319
+ // SvelteFlow node aria-labels — every visible node and port needs a
320
+ // landmark. The `name`/`title` parameter is the rendered display
321
+ // string (already localised by the workflow author or fallback).
322
+ graph: {
323
+ workflowNode: ({ name }) => `Workflow node: ${name}`,
324
+ gatewayNode: ({ title }) => `Gateway node: ${title}`,
325
+ ideaNode: ({ title }) => `Idea node: ${title}`,
326
+ connectInputPort: ({ name }) => `Connect to ${name} input port`,
327
+ connectOutputPort: ({ name }) => `Connect from ${name} output port`,
328
+ connectBranch: ({ name }) => `Connect from ${name} branch`
329
+ }
330
+ },
331
+ status: {
332
+ // Pipeline status panel.
333
+ pipeline: {
334
+ refresh: 'Refresh Status',
335
+ refreshing: 'Refreshing...',
336
+ viewLogs: 'View Logs',
337
+ home: 'Home',
338
+ workflows: 'Workflows',
339
+ workflow: 'Workflow',
340
+ pipelines: 'Pipelines',
341
+ pipelineCrumb: ({ id, status }) => `Pipeline ${id} - ${status}`
342
+ },
343
+ // NodeStatusOverlay tooltip content. The `status` parameter is the
344
+ // resolved status label (typically from `getStatusLabel()` so it stays
345
+ // consistent with status icons elsewhere); the wrapper text is localized.
346
+ overlay: {
347
+ tooltip: ({ status, count }) => `${status} - Executed ${count} times`,
348
+ ariaLabel: ({ status }) => `Node execution status: ${status}`,
349
+ statusLabel: 'Status:',
350
+ executionsLabel: 'Executions:',
351
+ lastRunLabel: 'Last Run:',
352
+ durationLabel: 'Duration:',
353
+ errorLabel: 'Error:'
354
+ }
355
+ }
356
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * One-shot deprecation warning helper for the messages migration.
3
+ *
4
+ * Components that previously accepted per-string label props (e.g.
5
+ * `saveLabel`, `cancelLabel`, `placeholder`, `addLabel`) keep accepting them
6
+ * during the v1.x deprecation window. When a consumer passes one explicitly
7
+ * we emit a single console.warn per (component, prop) pair so the noise is
8
+ * bounded even if the prop is rebound on every render.
9
+ *
10
+ * Production builds skip the warning entirely.
11
+ *
12
+ * Removed in v2.0 along with the props themselves.
13
+ */
14
+ export declare function warnDeprecatedProp(component: string, prop: string, replacement: string): void;
15
+ /**
16
+ * Test-only: clear the warned-once cache so subsequent renders re-emit the
17
+ * warning. Storybook stories and Vitest specs that share a module instance
18
+ * need this to assert the warning fires per case.
19
+ */
20
+ export declare function __resetDeprecationWarningsForTests(): void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One-shot deprecation warning helper for the messages migration.
3
+ *
4
+ * Components that previously accepted per-string label props (e.g.
5
+ * `saveLabel`, `cancelLabel`, `placeholder`, `addLabel`) keep accepting them
6
+ * during the v1.x deprecation window. When a consumer passes one explicitly
7
+ * we emit a single console.warn per (component, prop) pair so the noise is
8
+ * bounded even if the prop is rebound on every render.
9
+ *
10
+ * Production builds skip the warning entirely.
11
+ *
12
+ * Removed in v2.0 along with the props themselves.
13
+ */
14
+ const warned = new Set();
15
+ export function warnDeprecatedProp(component, prop, replacement) {
16
+ if (import.meta.env.PROD)
17
+ return;
18
+ const key = `${component}.${prop}`;
19
+ if (warned.has(key))
20
+ return;
21
+ warned.add(key);
22
+ // eslint-disable-next-line no-console
23
+ console.warn(`[flowdrop] <${component}> prop \`${prop}\` is deprecated and will be removed in v2.0. ` +
24
+ `Use \`messages\` on <FlowDrop> instead: ${replacement}`);
25
+ }
26
+ /**
27
+ * Test-only: clear the warned-once cache so subsequent renders re-emit the
28
+ * warning. Storybook stories and Vitest specs that share a module instance
29
+ * need this to assert the warning fires per case.
30
+ */
31
+ export function __resetDeprecationWarningsForTests() {
32
+ warned.clear();
33
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Messages module — typed, overridable, translation-ready user-facing strings.
3
+ *
4
+ * See `./defaults.ts` for the canonical shape, `./context.ts` for the runtime
5
+ * plumbing, and the i18n guide in `apps/docs` for consumer recipes.
6
+ */
7
+ export { defaultMessages } from './defaults.js';
8
+ export { mergeMessages } from './merge.js';
9
+ export { setMessages, getMessages, m } from './context.js';
10
+ export { warnDeprecatedProp, __resetDeprecationWarningsForTests } from './deprecation.js';
11
+ export type { Messages, MessagesOverride, DeepPartial } from './types.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Messages module — typed, overridable, translation-ready user-facing strings.
3
+ *
4
+ * See `./defaults.ts` for the canonical shape, `./context.ts` for the runtime
5
+ * plumbing, and the i18n guide in `apps/docs` for consumer recipes.
6
+ */
7
+ export { defaultMessages } from './defaults.js';
8
+ export { mergeMessages } from './merge.js';
9
+ export { setMessages, getMessages, m } from './context.js';
10
+ export { warnDeprecatedProp, __resetDeprecationWarningsForTests } from './deprecation.js';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Deep-merge a `MessagesOverride` onto the canonical `Messages` tree.
3
+ *
4
+ * Hand-written rather than pulled from lodash because the shape is known and
5
+ * fixed: leaves are strings or functions, branches are plain objects. The
6
+ * walker is smaller than the lodash import and has no edge cases we don't
7
+ * want.
8
+ *
9
+ * Rules:
10
+ * - `undefined` partial returns the base reference unchanged (cheap path
11
+ * for the no-override case).
12
+ * - Override branches are walked recursively and merged with their base.
13
+ * - Override leaves replace base leaves wholesale. Functions and strings
14
+ * are interchangeable per `DeepPartial` (see `./types.ts`).
15
+ * - Keys not present in the override fall through to the base.
16
+ *
17
+ * Identity preservation: when a subtree of `partial` resolves to values that
18
+ * are already `===` to the corresponding base values (string interning makes
19
+ * this the common case for paraglide-style overrides — `p.save()` returns
20
+ * the same `'Save'` string each call), the base reference is returned
21
+ * unchanged. This stops downstream `$derived(m().branch)` reads in components
22
+ * from invalidating on every parent re-render when the consumer passes an
23
+ * inline `messages={{...}}` literal whose contents are stable but whose
24
+ * outer identity churns. Function leaves still create fresh identities, but
25
+ * they aren't read in render hotspots.
26
+ */
27
+ import type { Messages, MessagesOverride } from './types.js';
28
+ export declare function mergeMessages(base: Messages, partial: MessagesOverride | undefined): Messages;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Deep-merge a `MessagesOverride` onto the canonical `Messages` tree.
3
+ *
4
+ * Hand-written rather than pulled from lodash because the shape is known and
5
+ * fixed: leaves are strings or functions, branches are plain objects. The
6
+ * walker is smaller than the lodash import and has no edge cases we don't
7
+ * want.
8
+ *
9
+ * Rules:
10
+ * - `undefined` partial returns the base reference unchanged (cheap path
11
+ * for the no-override case).
12
+ * - Override branches are walked recursively and merged with their base.
13
+ * - Override leaves replace base leaves wholesale. Functions and strings
14
+ * are interchangeable per `DeepPartial` (see `./types.ts`).
15
+ * - Keys not present in the override fall through to the base.
16
+ *
17
+ * Identity preservation: when a subtree of `partial` resolves to values that
18
+ * are already `===` to the corresponding base values (string interning makes
19
+ * this the common case for paraglide-style overrides — `p.save()` returns
20
+ * the same `'Save'` string each call), the base reference is returned
21
+ * unchanged. This stops downstream `$derived(m().branch)` reads in components
22
+ * from invalidating on every parent re-render when the consumer passes an
23
+ * inline `messages={{...}}` literal whose contents are stable but whose
24
+ * outer identity churns. Function leaves still create fresh identities, but
25
+ * they aren't read in render hotspots.
26
+ */
27
+ const isPlainObject = (value) => typeof value === 'object' &&
28
+ value !== null &&
29
+ !Array.isArray(value) &&
30
+ typeof value !== 'function';
31
+ export function mergeMessages(base, partial) {
32
+ if (partial === undefined)
33
+ return base;
34
+ return mergeNode(base, partial);
35
+ }
36
+ function mergeNode(base, partial) {
37
+ if (!isPlainObject(base) || !isPlainObject(partial))
38
+ return partial ?? base;
39
+ let out = null;
40
+ for (const key of Object.keys(partial)) {
41
+ const baseChild = base[key];
42
+ const partialChild = partial[key];
43
+ const merged = isPlainObject(baseChild) && isPlainObject(partialChild)
44
+ ? mergeNode(baseChild, partialChild)
45
+ : (partialChild ?? baseChild);
46
+ if (merged !== baseChild) {
47
+ if (out === null)
48
+ out = { ...base };
49
+ out[key] = merged;
50
+ }
51
+ }
52
+ return out ?? base;
53
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Type machinery for the FlowDrop messages system.
3
+ *
4
+ * `Messages` is the canonical shape of every user-facing string in the library.
5
+ * It is derived from `defaultMessages` so the defaults file is the single
6
+ * source of truth — adding a key there immediately widens the type.
7
+ *
8
+ * Consumers pass `DeepPartial<Messages>` to override any subset. Function
9
+ * leaves (parameterised strings) may be overridden with either a function of
10
+ * the same signature OR a plain string for cases where the override doesn't
11
+ * need the parameters.
12
+ */
13
+ import type { defaultMessages } from './defaults.js';
14
+ export type Messages = typeof defaultMessages;
15
+ type AnyFn = (...args: any[]) => unknown;
16
+ /**
17
+ * A recursive partial that:
18
+ * - allows skipping any key at any level,
19
+ * - preserves function leaves with their original signature — overrides
20
+ * of parameterised messages MUST stay functions, because call sites
21
+ * invoke them with the params object,
22
+ * - widens string leaves to `string` so `as const` literal defaults
23
+ * (e.g. `'Cancel'`) accept arbitrary translations.
24
+ */
25
+ export type DeepPartial<T> = T extends AnyFn ? T : T extends string ? string : T extends object ? {
26
+ [K in keyof T]?: DeepPartial<T[K]>;
27
+ } : T;
28
+ export type MessagesOverride = DeepPartial<Messages>;
29
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Type machinery for the FlowDrop messages system.
3
+ *
4
+ * `Messages` is the canonical shape of every user-facing string in the library.
5
+ * It is derived from `defaultMessages` so the defaults file is the single
6
+ * source of truth — adding a key there immediately widens the type.
7
+ *
8
+ * Consumers pass `DeepPartial<Messages>` to override any subset. Function
9
+ * leaves (parameterised strings) may be overridden with either a function of
10
+ * the same signature OR a plain string for cases where the override doesn't
11
+ * need the parameters.
12
+ */
13
+ export {};
@@ -36,6 +36,11 @@
36
36
  },
37
37
  "metadata": {
38
38
  "$ref": "#/$defs/WorkflowMetadata"
39
+ },
40
+ "config": {
41
+ "type": "object",
42
+ "description": "Custom workflow-level configuration values. Populated when a workflowSettingsSchema is provided to the editor.",
43
+ "additionalProperties": true
39
44
  }
40
45
  },
41
46
  "required": [
@@ -75,6 +75,19 @@ export declare function hasDraft(storageKey: string): boolean;
75
75
  * @returns Draft metadata, or null if not found
76
76
  */
77
77
  export declare function getDraftMetadata(storageKey: string): DraftMetadata | null;
78
+ /**
79
+ * Clear all FlowDrop drafts from localStorage
80
+ *
81
+ * Removes every key beginning with `flowdrop:draft:`. Intended to be called
82
+ * from a host application's logout handler so workflow drafts do not persist
83
+ * across user sessions on shared devices.
84
+ *
85
+ * @param extraKeys - Additional explicit keys to remove. Pass any custom
86
+ * `draftStorageKey` values configured at mount time so they are cleared
87
+ * alongside the default-prefixed keys.
88
+ * @returns The number of entries removed.
89
+ */
90
+ export declare function clearAllDrafts(extraKeys?: readonly string[]): number;
78
91
  /**
79
92
  * Draft auto-save manager
80
93
  *
@@ -120,6 +120,42 @@ export function getDraftMetadata(storageKey) {
120
120
  const draft = loadDraft(storageKey);
121
121
  return draft?.metadata ?? null;
122
122
  }
123
+ /**
124
+ * Clear all FlowDrop drafts from localStorage
125
+ *
126
+ * Removes every key beginning with `flowdrop:draft:`. Intended to be called
127
+ * from a host application's logout handler so workflow drafts do not persist
128
+ * across user sessions on shared devices.
129
+ *
130
+ * @param extraKeys - Additional explicit keys to remove. Pass any custom
131
+ * `draftStorageKey` values configured at mount time so they are cleared
132
+ * alongside the default-prefixed keys.
133
+ * @returns The number of entries removed.
134
+ */
135
+ export function clearAllDrafts(extraKeys = []) {
136
+ try {
137
+ const keysToRemove = new Set();
138
+ for (let i = 0; i < localStorage.length; i++) {
139
+ const key = localStorage.key(i);
140
+ if (key && key.startsWith(`${STORAGE_KEY_PREFIX}:`)) {
141
+ keysToRemove.add(key);
142
+ }
143
+ }
144
+ for (const key of extraKeys) {
145
+ if (localStorage.getItem(key) !== null) {
146
+ keysToRemove.add(key);
147
+ }
148
+ }
149
+ for (const key of keysToRemove) {
150
+ localStorage.removeItem(key);
151
+ }
152
+ return keysToRemove.size;
153
+ }
154
+ catch (error) {
155
+ logger.warn('Failed to clear drafts from localStorage:', error);
156
+ return 0;
157
+ }
158
+ }
123
159
  /**
124
160
  * Draft auto-save manager
125
161
  *
@@ -263,6 +263,7 @@ export declare const workflowActions: {
263
263
  name?: string;
264
264
  description?: string;
265
265
  metadata?: Partial<Workflow["metadata"]>;
266
+ config?: Record<string, unknown>;
266
267
  }) => void;
267
268
  /**
268
269
  * Swap a node — atomically replaces nodes and edges with a descriptive history entry.
@@ -632,6 +632,7 @@ export const workflowActions = {
632
632
  ...(updates.description !== undefined && {
633
633
  description: updates.description
634
634
  }),
635
+ ...(updates.config !== undefined && { config: updates.config }),
635
636
  metadata: buildMetadata(workflowState.metadata, updates.metadata ?? undefined)
636
637
  };
637
638
  bumpVersion();