@burdenoff/fluidgrids-fe-sdk 2026.1115.7 → 2026.1115.9

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 (73) hide show
  1. package/dist/{AiStudioPage-tzv59pH4.js → AiStudioPage-CZTaVeQE.js} +4 -4
  2. package/dist/{DataSinkDashboardPage-BtWSidiD.js → DataSinkDashboardPage-CPq6-jZM.js} +1 -1
  3. package/dist/{ExecutionDetailPage-CO3BWG_V.js → ExecutionDetailPage-d29WPJDN.js} +92 -27
  4. package/dist/ExecutionSettingsFields-DzKJDOjU.js +1003 -0
  5. package/dist/{ExecutionsListPage-CaO9A4Ph.js → ExecutionsListPage-xQthHGAJ.js} +8 -26
  6. package/dist/{FailureTriagePanel-CBGgU4Tl.js → FailureTriagePanel-Cka9Rc1_.js} +1 -1
  7. package/dist/{FluidGridsProvider-Bemnt09v.js → FluidGridsProvider-BRC0WlAa.js} +248 -3045
  8. package/dist/{HistoryPage-DPGYma-Z.js → HistoryPage-Drdk9j_Q.js} +1 -1
  9. package/dist/{InsightsPage-C6b9WuBc.js → InsightsPage-mYWZvyGR.js} +5 -5
  10. package/dist/{OpenTemplateAsCodeButton-BemhA1u4.js → OpenTemplateAsCodeButton-CQV-YHlJ.js} +2 -2
  11. package/dist/{PaidTemplateConfirm-bNEOQ_Se.js → PaidTemplateConfirm-Dog5moZa.js} +1 -1
  12. package/dist/{agent-form-D0uLvzx4.js → RunLogAppendedDocument-D1_BfMZa.js} +1 -82
  13. package/dist/{SettingsPage-CTd4LQPW.js → SettingsPage-DFnqjkpT.js} +239 -894
  14. package/dist/{TaskQueuesPage-ESjeGUg3.js → TaskQueuesPage-CZQUO33o.js} +1 -1
  15. package/dist/{TemplateCard-D7jG3RaU.js → TemplateCard-D2zt-kXW.js} +5 -5
  16. package/dist/{TemplateConfigurationModal-DMyuHZ3O.js → TemplateConfigurationModal-D8eQsJXJ.js} +1 -1
  17. package/dist/{TemplatePreview-CNbCxOsc.js → TemplatePreview-BgdkcCF-.js} +5 -5
  18. package/dist/{TemplatesPage-DLOVSXa9.js → TemplatesPage-t7rGU-BP.js} +10 -10
  19. package/dist/{WorkflowBuilder-YJlFY071.js → WorkflowBuilder-BS9kVcgU.js} +3 -3
  20. package/dist/{WorkflowBuilder-DhvPS34U.js → WorkflowBuilder-CtTM79lP.js} +6 -5
  21. package/dist/{WorkflowSearchRedirect-vyY1d6nn.js → WorkflowSearchRedirect-Bqdxr833.js} +1 -1
  22. package/dist/WorkflowSettingsDrawer-BiqTf_tV.js +191 -0
  23. package/dist/WorkflowSettingsDrawer-MVU1BKfY.d.ts +18 -0
  24. package/dist/{WorkflowShareDialog-qyGW0cSr.js → WorkflowShareDialog-D_G3TUGQ.js} +1 -1
  25. package/dist/{WorkflowsList-DQq_-Gaq.js → WorkflowsList-DjKePEwo.js} +2035 -517
  26. package/dist/agent-form-Bfvm32j1.js +83 -0
  27. package/dist/{agentTools-uC7Kw2uc.js → agentTools-DrVlHNeP.js} +1 -1
  28. package/dist/{ai-usage-yzPWZgAC.js → ai-usage-DhSKp0tV.js} +1 -1
  29. package/dist/{api-o5UUDbr8.js → api-D1cWC1yx.js} +190 -1
  30. package/dist/{assert-rules-FOiGC7-z.js → assert-rules-D9PNDTMK.js} +1 -1
  31. package/dist/{canvasNoteTools-DedWIXHZ.js → canvasNoteTools-Bo2QldkD.js} +1 -1
  32. package/dist/executions.js +2 -2
  33. package/dist/{flow-builder-BfkigZeg.js → flow-builder-CWfNAFd8.js} +73 -18
  34. package/dist/{graphql-types-C2heV7ot.js → graphql-types-CxkVzQqA.js} +25 -1
  35. package/dist/history.js +1 -1
  36. package/dist/{index-CIRkv8Dr.d.ts → index-C2ehY2Ld.d.ts} +180 -10
  37. package/dist/index.d.ts +3 -3
  38. package/dist/index.js +45 -42
  39. package/dist/{node-detail-view-B-nHEP6Z.js → node-detail-view-D_N3kLtA.js} +7 -7
  40. package/dist/{node-editor-tab-body-CZHYnHmY.js → node-editor-tab-body-BQ9JZa72.js} +2 -2
  41. package/dist/observability.d.ts +1 -1
  42. package/dist/observability.js +2 -2
  43. package/dist/{useAgentDefinitions-CVBnPMnJ.js → operations-qq_bbgXP.js} +3 -223
  44. package/dist/{popover-C2ohJ0Rd.js → popover-DZdTN21a.js} +3 -3
  45. package/dist/{quick-config-panel-ButILqM4.js → quick-config-panel-BCUY4tQy.js} +3 -3
  46. package/dist/relative-time-Dxt8ZIIu.js +55 -0
  47. package/dist/{run-list-links-sTAexiqj.js → run-list-links-BwsCiBPB.js} +1 -1
  48. package/dist/{run-mode-DjXIqsz3.js → run-mode-vpIlmfgY.js} +1 -1
  49. package/dist/{schedule-panel-BZuH1ltc.js → schedule-panel-B6FrpX9d.js} +9 -8
  50. package/dist/settings.d.ts +2 -2
  51. package/dist/settings.js +3 -2
  52. package/dist/{sidebar-node-editor-B0-72udR.js → sidebar-node-editor-CWrmlFlZ.js} +3 -3
  53. package/dist/{template-code-DrS_vlf_.js → template-code-9dL1SUji.js} +1 -1
  54. package/dist/templates.js +2 -2
  55. package/dist/{use-workflow-code-editor-DfWsRWC7.js → use-workflow-code-editor-DJMH7-Xx.js} +1 -1
  56. package/dist/{use-workflow-tests-CVVKinFU.js → use-workflow-tests-C4e6aJZS.js} +2 -2
  57. package/dist/useAgentDefinitions-DX5GvEIp.js +223 -0
  58. package/dist/{useDurableParity-gnKk3TyL.js → useDurableParity-BtWk4d-i.js} +1 -1
  59. package/dist/{useExecutions-Dwa7jo1o.js → useExecutions-C9pVKexC.js} +3 -2
  60. package/dist/{useGlobalStoreClient-4BH2Xbru.js → useGlobalStoreClient-C2P50Gj5.js} +1 -1
  61. package/dist/{useObservability-Bp4LtS1S.js → useObservability-DsHX86Xl.js} +1 -1
  62. package/dist/{useRunHistory-Zqh59sXN.js → useRunHistory-DUNLNOKA.js} +2 -2
  63. package/dist/{useWorkflowCodeExport-C93cPJQ5.js → useWorkflowCodeExport-DSbfcehl.js} +13 -3
  64. package/dist/{useWorkflows-BodVSonO.js → useWorkflows-B13Cp8tk.js} +90 -10
  65. package/dist/workflow-builder.js +4 -4
  66. package/dist/{workflow-description-DxiRgyxy.js → workflow-description-F3inwvof.js} +1 -1
  67. package/dist/workflow-pages.d.ts +2 -2
  68. package/dist/workflow-pages.js +14 -14
  69. package/dist/workflow-save-state-DUE9lNwQ.js +2813 -0
  70. package/dist/{workflow-transfer-BlDyxa9L.js → workflow-transfer-CsXh0mIi.js} +1 -1
  71. package/dist/workflows-list.js +1 -1
  72. package/package.json +3 -3
  73. package/dist/SettingsPage-DMHQFj5-.d.ts +0 -9
@@ -0,0 +1,1003 @@
1
+ "use client";
2
+ import { o as throwIfResultError } from "./errors-xPZ-IbQd.js";
3
+ import { n as getFluidGridsClient } from "./client-Ctl4jmY3.js";
4
+ import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
5
+ import { c as findSaveConflict } from "./workflow-save-state-DUE9lNwQ.js";
6
+ import { At as useUpdateWorkflowDefinitionMutation, Ft as useWorkflowDefinitionQuery, Rt as useWorkflowDefinitionsQuery } from "./operations-qq_bbgXP.js";
7
+ import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-65QV61nQ.js";
8
+ import { useCallback, useEffect, useMemo, useState } from "react";
9
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
+ import { gql } from "@apollo/client";
11
+ import { Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@burdenoff/fe-libs/ui";
12
+
13
+ // src/generated/documents/wspace/UpdateWorkflowNotificationSettingsDocument.ts
14
+ const UpdateWorkflowNotificationSettingsDocument = {
15
+ "kind": "Document",
16
+ "definitions": [{
17
+ "kind": "OperationDefinition",
18
+ "operation": "mutation",
19
+ "name": {
20
+ "kind": "Name",
21
+ "value": "UpdateWorkflowNotificationSettings"
22
+ },
23
+ "variableDefinitions": [{
24
+ "kind": "VariableDefinition",
25
+ "variable": {
26
+ "kind": "Variable",
27
+ "name": {
28
+ "kind": "Name",
29
+ "value": "input"
30
+ }
31
+ },
32
+ "type": {
33
+ "kind": "NonNullType",
34
+ "type": {
35
+ "kind": "NamedType",
36
+ "name": {
37
+ "kind": "Name",
38
+ "value": "UpdateWorkflowNotificationSettingsInput"
39
+ }
40
+ }
41
+ },
42
+ "directives": []
43
+ }],
44
+ "directives": [],
45
+ "selectionSet": {
46
+ "kind": "SelectionSet",
47
+ "selections": [{
48
+ "kind": "Field",
49
+ "name": {
50
+ "kind": "Name",
51
+ "value": "updateWorkflowNotificationSettings"
52
+ },
53
+ "arguments": [{
54
+ "kind": "Argument",
55
+ "name": {
56
+ "kind": "Name",
57
+ "value": "input"
58
+ },
59
+ "value": {
60
+ "kind": "Variable",
61
+ "name": {
62
+ "kind": "Name",
63
+ "value": "input"
64
+ }
65
+ }
66
+ }],
67
+ "directives": [],
68
+ "selectionSet": {
69
+ "kind": "SelectionSet",
70
+ "selections": [
71
+ {
72
+ "kind": "Field",
73
+ "name": {
74
+ "kind": "Name",
75
+ "value": "workflowKey"
76
+ },
77
+ "arguments": [],
78
+ "directives": []
79
+ },
80
+ {
81
+ "kind": "Field",
82
+ "name": {
83
+ "kind": "Name",
84
+ "value": "notifyOnFailure"
85
+ },
86
+ "arguments": [],
87
+ "directives": []
88
+ },
89
+ {
90
+ "kind": "Field",
91
+ "name": {
92
+ "kind": "Name",
93
+ "value": "notifyOnSuccess"
94
+ },
95
+ "arguments": [],
96
+ "directives": []
97
+ },
98
+ {
99
+ "kind": "Field",
100
+ "name": {
101
+ "kind": "Name",
102
+ "value": "notifyOnApprovalRequest"
103
+ },
104
+ "arguments": [],
105
+ "directives": []
106
+ },
107
+ {
108
+ "kind": "Field",
109
+ "name": {
110
+ "kind": "Name",
111
+ "value": "notifyOnTriggerDisabled"
112
+ },
113
+ "arguments": [],
114
+ "directives": []
115
+ },
116
+ {
117
+ "kind": "Field",
118
+ "name": {
119
+ "kind": "Name",
120
+ "value": "pushEnabled"
121
+ },
122
+ "arguments": [],
123
+ "directives": []
124
+ },
125
+ {
126
+ "kind": "Field",
127
+ "name": {
128
+ "kind": "Name",
129
+ "value": "throttleMinutes"
130
+ },
131
+ "arguments": [],
132
+ "directives": []
133
+ },
134
+ {
135
+ "kind": "Field",
136
+ "name": {
137
+ "kind": "Name",
138
+ "value": "customized"
139
+ },
140
+ "arguments": [],
141
+ "directives": []
142
+ },
143
+ {
144
+ "kind": "Field",
145
+ "name": {
146
+ "kind": "Name",
147
+ "value": "updatedAt"
148
+ },
149
+ "arguments": [],
150
+ "directives": []
151
+ }
152
+ ]
153
+ }
154
+ }]
155
+ }
156
+ }]
157
+ };
158
+
159
+ // src/generated/documents/wspace/WorkflowNotificationSettingsDocument.ts
160
+ const WorkflowNotificationSettingsDocument = {
161
+ "kind": "Document",
162
+ "definitions": [{
163
+ "kind": "OperationDefinition",
164
+ "operation": "query",
165
+ "name": {
166
+ "kind": "Name",
167
+ "value": "WorkflowNotificationSettings"
168
+ },
169
+ "variableDefinitions": [{
170
+ "kind": "VariableDefinition",
171
+ "variable": {
172
+ "kind": "Variable",
173
+ "name": {
174
+ "kind": "Name",
175
+ "value": "workflowKey"
176
+ }
177
+ },
178
+ "type": {
179
+ "kind": "NonNullType",
180
+ "type": {
181
+ "kind": "NamedType",
182
+ "name": {
183
+ "kind": "Name",
184
+ "value": "String"
185
+ }
186
+ }
187
+ },
188
+ "directives": []
189
+ }],
190
+ "directives": [],
191
+ "selectionSet": {
192
+ "kind": "SelectionSet",
193
+ "selections": [{
194
+ "kind": "Field",
195
+ "name": {
196
+ "kind": "Name",
197
+ "value": "workflowNotificationSettings"
198
+ },
199
+ "arguments": [{
200
+ "kind": "Argument",
201
+ "name": {
202
+ "kind": "Name",
203
+ "value": "workflowKey"
204
+ },
205
+ "value": {
206
+ "kind": "Variable",
207
+ "name": {
208
+ "kind": "Name",
209
+ "value": "workflowKey"
210
+ }
211
+ }
212
+ }],
213
+ "directives": [],
214
+ "selectionSet": {
215
+ "kind": "SelectionSet",
216
+ "selections": [
217
+ {
218
+ "kind": "Field",
219
+ "name": {
220
+ "kind": "Name",
221
+ "value": "workflowKey"
222
+ },
223
+ "arguments": [],
224
+ "directives": []
225
+ },
226
+ {
227
+ "kind": "Field",
228
+ "name": {
229
+ "kind": "Name",
230
+ "value": "notifyOnFailure"
231
+ },
232
+ "arguments": [],
233
+ "directives": []
234
+ },
235
+ {
236
+ "kind": "Field",
237
+ "name": {
238
+ "kind": "Name",
239
+ "value": "notifyOnSuccess"
240
+ },
241
+ "arguments": [],
242
+ "directives": []
243
+ },
244
+ {
245
+ "kind": "Field",
246
+ "name": {
247
+ "kind": "Name",
248
+ "value": "notifyOnApprovalRequest"
249
+ },
250
+ "arguments": [],
251
+ "directives": []
252
+ },
253
+ {
254
+ "kind": "Field",
255
+ "name": {
256
+ "kind": "Name",
257
+ "value": "notifyOnTriggerDisabled"
258
+ },
259
+ "arguments": [],
260
+ "directives": []
261
+ },
262
+ {
263
+ "kind": "Field",
264
+ "name": {
265
+ "kind": "Name",
266
+ "value": "pushEnabled"
267
+ },
268
+ "arguments": [],
269
+ "directives": []
270
+ },
271
+ {
272
+ "kind": "Field",
273
+ "name": {
274
+ "kind": "Name",
275
+ "value": "throttleMinutes"
276
+ },
277
+ "arguments": [],
278
+ "directives": []
279
+ },
280
+ {
281
+ "kind": "Field",
282
+ "name": {
283
+ "kind": "Name",
284
+ "value": "customized"
285
+ },
286
+ "arguments": [],
287
+ "directives": []
288
+ },
289
+ {
290
+ "kind": "Field",
291
+ "name": {
292
+ "kind": "Name",
293
+ "value": "updatedAt"
294
+ },
295
+ "arguments": [],
296
+ "directives": []
297
+ }
298
+ ]
299
+ }
300
+ }]
301
+ }
302
+ }]
303
+ };
304
+
305
+ // src/modules/settings/hooks/useWorkflowNotificationSettings.ts
306
+ /**
307
+ * A workflow's notification preferences (G08) for Settings → Notifications
308
+ * (BOFF-9974): which events notify the workflow's person in the app's bell
309
+ * and, unless switched off, by push to their devices, and how often.
310
+ *
311
+ * Read with `workflowNotificationSettings` (the defaults when nobody saved
312
+ * any) and saved with `updateWorkflowNotificationSettings`, a partial update.
313
+ */
314
+ /** The throttle window the service accepts, in minutes (0 sends every one). */
315
+ const NOTIFICATION_THROTTLE_RANGE = {
316
+ min: 0,
317
+ max: 1440
318
+ };
319
+ async function fetchWorkflowNotificationSettings(workflowKey) {
320
+ const result = await getFluidGridsClient().query({
321
+ query: WorkflowNotificationSettingsDocument,
322
+ variables: { workflowKey },
323
+ fetchPolicy: "network-only"
324
+ });
325
+ throwIfResultError(result);
326
+ return result.data?.workflowNotificationSettings ?? null;
327
+ }
328
+ async function saveWorkflowNotificationSettings(workflowKey, changes) {
329
+ const result = await getFluidGridsClient().mutate({
330
+ mutation: UpdateWorkflowNotificationSettingsDocument,
331
+ variables: { input: {
332
+ workflowKey,
333
+ ...changes
334
+ } }
335
+ });
336
+ throwIfResultError(result);
337
+ const saved = result.data?.updateWorkflowNotificationSettings;
338
+ if (!saved) throw new Error("The notification settings were not saved.");
339
+ return saved;
340
+ }
341
+ function useWorkflowNotificationSettings(workflowKey) {
342
+ const [loaded, setLoaded] = useState(null);
343
+ useEffect(() => {
344
+ if (!workflowKey) return;
345
+ let cancelled = false;
346
+ fetchWorkflowNotificationSettings(workflowKey).then((settings) => {
347
+ if (!cancelled) setLoaded({
348
+ workflowKey,
349
+ settings,
350
+ error: null
351
+ });
352
+ }, (err) => {
353
+ if (!cancelled) setLoaded({
354
+ workflowKey,
355
+ settings: null,
356
+ error: err instanceof Error ? err : new Error(String(err))
357
+ });
358
+ });
359
+ return () => {
360
+ cancelled = true;
361
+ };
362
+ }, [workflowKey]);
363
+ const save = useCallback(async (changes) => {
364
+ if (!workflowKey) throw new Error("No workflow selected.");
365
+ const saved = await saveWorkflowNotificationSettings(workflowKey, changes);
366
+ setLoaded({
367
+ workflowKey,
368
+ settings: saved,
369
+ error: null
370
+ });
371
+ return saved;
372
+ }, [workflowKey]);
373
+ const current = loaded?.workflowKey === workflowKey ? loaded : null;
374
+ return {
375
+ settings: current?.settings ?? null,
376
+ loading: !!workflowKey && current === null,
377
+ error: current?.error ?? null,
378
+ save
379
+ };
380
+ }
381
+
382
+ // src/modules/settings/utils/error-trigger.ts
383
+ /**
384
+ * Error Workflow setting (BOFF-7514): the chosen workflow starts at its Error
385
+ * Trigger node, so one without that node never starts. Mirrors the backend's
386
+ * `findErrorTriggerNodeId` in wspace-workflow-svc.
387
+ */
388
+ const ERROR_TRIGGER_NODE_TYPE = "internal:error-trigger";
389
+ function asRecord(value) {
390
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
391
+ }
392
+ /** Reads `data.nodes`, falling back to the legacy top-level `nodes`. */
393
+ function hasErrorTriggerNode(definitionJson) {
394
+ const definition = asRecord(definitionJson);
395
+ const dataNodes = asRecord(definition?.data)?.nodes;
396
+ const nodes = Array.isArray(dataNodes) ? dataNodes : definition?.nodes;
397
+ return Array.isArray(nodes) && nodes.some((node) => asRecord(node)?.type === "internal:error-trigger");
398
+ }
399
+
400
+ // src/modules/settings/utils/run-data.ts
401
+ function toSavingChoice(value) {
402
+ if (value === void 0) return "default";
403
+ return value ? "save" : "skip";
404
+ }
405
+ function fromSavingChoice(choice) {
406
+ if (choice === "save") return true;
407
+ if (choice === "skip") return false;
408
+ }
409
+ /** Limits the backend applies to a workflow's redacted fields (run-data-privacy.ts, BOFF-9752). */
410
+ const MAX_REDACT_FIELDS = 50;
411
+ const MAX_REDACT_FIELD_LENGTH = 200;
412
+ const MAX_REDACT_PATH_SEGMENTS = 16;
413
+ /**
414
+ * Read the "Redacted fields" input: entries separated by commas or new lines, each a field name
415
+ * or a dotted path. Entries the backend would ignore (empty segments, too long, too deep) and
416
+ * repeats are dropped, and the list is capped. An empty list means "none" (undefined).
417
+ */
418
+ function parseRedactFields(text) {
419
+ const seen = /* @__PURE__ */ new Set();
420
+ const fields = [];
421
+ for (const raw of text.split(/[,\n]/)) {
422
+ const segments = raw.trim().split(".").map((segment) => segment.trim());
423
+ const field = segments.join(".");
424
+ if (field.length === 0 || field.length > MAX_REDACT_FIELD_LENGTH || segments.length > MAX_REDACT_PATH_SEGMENTS || segments.some((segment) => segment.length === 0)) continue;
425
+ const key = field.toLowerCase();
426
+ if (seen.has(key)) continue;
427
+ seen.add(key);
428
+ fields.push(field);
429
+ if (fields.length >= 50) break;
430
+ }
431
+ return fields.length > 0 ? fields : void 0;
432
+ }
433
+ /** Show a saved list in the "Redacted fields" input. */
434
+ function formatRedactFields(fields) {
435
+ return (fields ?? []).join(", ");
436
+ }
437
+ /**
438
+ * Drop the keys left unset, and the whole object when nothing is set, so a workflow that follows
439
+ * its workspace saves no override at all.
440
+ */
441
+ function compactOverrides(value) {
442
+ if (!value) return void 0;
443
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0);
444
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
445
+ }
446
+ /** Bounds the backend accepts (wspace-workflow-svc run-data-lifecycle.service). */
447
+ const RETENTION_DAYS_RANGE = {
448
+ min: 1,
449
+ max: 3650
450
+ };
451
+ const RETENTION_MAX_RUNS_RANGE = {
452
+ min: 1,
453
+ max: 1e6
454
+ };
455
+ /**
456
+ * An empty input means "no override"; anything else is read whole (so `1e3` is 1000, not 1),
457
+ * truncated to a whole number and clamped to its range.
458
+ */
459
+ function parseBoundedInt(value, range) {
460
+ if (value.trim() === "") return void 0;
461
+ const parsed = Number(value);
462
+ if (!Number.isFinite(parsed)) return void 0;
463
+ return Math.min(range.max, Math.max(range.min, Math.trunc(parsed)));
464
+ }
465
+
466
+ // src/modules/settings/utils/workflow-settings-persistence.ts
467
+ /** The nested fields the Settings page edits; any other nested key is kept as saved. */
468
+ const DATA_SAVING_KEYS = [
469
+ "saveSuccessExecutions",
470
+ "saveFailedExecutions",
471
+ "saveManualExecutions"
472
+ ];
473
+ const RETENTION_KEYS = ["maxAgeDays", "maxRuns"];
474
+ const DATA_PRIVACY_KEYS = ["doNotStoreRunData", "redactFields"];
475
+ const BUDGET_KEYS = [
476
+ "maxSteps",
477
+ "maxWallTimeSec",
478
+ "maxTokens",
479
+ "maxCostMicroUsd"
480
+ ];
481
+ /**
482
+ * Merge the edited settings into the saved config without mutating it.
483
+ *
484
+ * Keys the form does not know (engine-only flags, fields added later) are kept. For the nested
485
+ * run-data objects the form is authoritative over the fields it edits: a field it leaves unset was
486
+ * cleared to "follow the workspace" (BOFF-7514), so it must not come back from the saved config.
487
+ */
488
+ function mergeWorkflowExecutionSettings(existing, next) {
489
+ const merged = {
490
+ ...existing,
491
+ ...next
492
+ };
493
+ for (const [key, editable] of [
494
+ ["dataSaving", DATA_SAVING_KEYS],
495
+ ["retention", RETENTION_KEYS],
496
+ ["dataPrivacy", DATA_PRIVACY_KEYS],
497
+ ["budget", BUDGET_KEYS]
498
+ ]) {
499
+ const nested = mergeNested(existing[key], next[key], editable);
500
+ if (nested) merged[key] = nested;
501
+ else delete merged[key];
502
+ }
503
+ return merged;
504
+ }
505
+ function mergeNested(existing, next, editable) {
506
+ const editableKeys = new Set(editable);
507
+ const kept = Object.fromEntries(Object.entries(isRecord(existing) ? existing : {}).filter(([key]) => !editableKeys.has(key)));
508
+ const result = Object.fromEntries(Object.entries({
509
+ ...kept,
510
+ ...next ?? {}
511
+ }).filter(([, value]) => value !== void 0));
512
+ return Object.keys(result).length > 0 ? result : void 0;
513
+ }
514
+ function isRecord(value) {
515
+ return typeof value === "object" && value !== null && !Array.isArray(value);
516
+ }
517
+
518
+ // src/modules/settings/hooks/useRunDataSettings.ts
519
+ /**
520
+ * The workspace's run-data settings (BOFF-7514): which finished runs keep their data and how long
521
+ * finished runs are kept, for every workflow that does not override them in its own Settings.
522
+ */
523
+ const FIELDS = `
524
+ saveSuccessfulRuns
525
+ saveFailedRuns
526
+ saveManualRuns
527
+ retentionDays
528
+ retentionMaxRunsPerWorkflow
529
+ customized
530
+ updatedAt
531
+ `;
532
+ const RUN_DATA_SETTINGS_QUERY = gql`
533
+ query WorkflowExecutionSettings {
534
+ workflowExecutionSettings {
535
+ ${FIELDS}
536
+ }
537
+ }
538
+ `;
539
+ const UPDATE_RUN_DATA_SETTINGS = gql`
540
+ mutation UpdateWorkflowExecutionSettings($input: UpdateWorkflowExecutionSettingsInput!) {
541
+ updateWorkflowExecutionSettings(input: $input) {
542
+ ${FIELDS}
543
+ }
544
+ }
545
+ `;
546
+ function useRunDataSettings() {
547
+ const [settings, setSettings] = useState(null);
548
+ const [loading, setLoading] = useState(true);
549
+ const [error, setError] = useState(null);
550
+ useEffect(() => {
551
+ let cancelled = false;
552
+ getFluidGridsClient().query({
553
+ query: RUN_DATA_SETTINGS_QUERY,
554
+ fetchPolicy: "network-only"
555
+ }).then((result) => {
556
+ throwIfResultError(result);
557
+ if (!cancelled) setSettings(result.data?.workflowExecutionSettings ?? null);
558
+ }).catch((err) => {
559
+ if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
560
+ }).finally(() => {
561
+ if (!cancelled) setLoading(false);
562
+ });
563
+ return () => {
564
+ cancelled = true;
565
+ };
566
+ }, []);
567
+ return {
568
+ settings,
569
+ loading,
570
+ error,
571
+ save: useCallback(async (changes) => {
572
+ const result = await getFluidGridsClient().mutate({
573
+ mutation: UPDATE_RUN_DATA_SETTINGS,
574
+ variables: { input: changes }
575
+ });
576
+ throwIfResultError(result);
577
+ const saved = result.data?.updateWorkflowExecutionSettings;
578
+ if (!saved) throw new Error("No settings returned");
579
+ setSettings(saved);
580
+ return saved;
581
+ }, [])
582
+ };
583
+ }
584
+
585
+ // src/modules/settings/hooks/useWorkflowExecutionSettingsForm.ts
586
+ /**
587
+ * One workflow's execution settings as an editable form (BOFF-7512): the
588
+ * values in `definitionJson.config`, the lists the form's pickers need, and a
589
+ * save that never overwrites a canvas someone changed meanwhile.
590
+ *
591
+ * Shared by Settings › Execution and the Navigation v2 settings drawer
592
+ * (BOFF-11843), so both read, check and save exactly the same way.
593
+ */
594
+ /** What "On workflow error" means when nobody chose (the engine's default). */
595
+ const DEFAULT_ON_ERROR = "stop";
596
+ /**
597
+ * The stored definition with the form's settings applied to its `config`.
598
+ * Only `config` changes; every other part of the definition is sent back as
599
+ * it was read.
600
+ */
601
+ function definitionWithSettings(definitionJson, form) {
602
+ const definition = definitionJson ?? {};
603
+ return {
604
+ ...definition,
605
+ config: mergeWorkflowExecutionSettings(definition.config ?? {}, form)
606
+ };
607
+ }
608
+ /** Whether saving `form` would change the stored `config`. */
609
+ function settingsChanged(persisted, form) {
610
+ const before = mergeWorkflowExecutionSettings(persisted, persisted);
611
+ const after = mergeWorkflowExecutionSettings(persisted, form);
612
+ return JSON.stringify(sortKeys(before)) !== JSON.stringify(sortKeys(after));
613
+ }
614
+ function sortKeys(value) {
615
+ if (Array.isArray(value)) return value.map(sortKeys);
616
+ if (typeof value !== "object" || value === null) return value;
617
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])]));
618
+ }
619
+ function useWorkflowExecutionSettingsForm(workflowKey) {
620
+ const tr = useTr();
621
+ const { data, loading, refetch } = useWorkflowDefinitionQuery({
622
+ variables: { workflowKey },
623
+ skip: !workflowKey,
624
+ fetchPolicy: "cache-and-network"
625
+ });
626
+ const [updateWorkflowDefinition, { loading: saving }] = useUpdateWorkflowDefinitionMutation();
627
+ const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
628
+ const locked = loading || saving || !canEdit || !data?.workflowDefinition;
629
+ const { data: otherWorkflowsData, loading: otherWorkflowsLoading } = useWorkflowDefinitionsQuery({
630
+ variables: { input: {
631
+ latestOnly: true,
632
+ limit: 100
633
+ } },
634
+ skip: !workflowKey
635
+ });
636
+ const errorWorkflowOptions = useMemo(() => (otherWorkflowsData?.workflowDefinitions?.items ?? []).filter((w) => w.workflowKey !== workflowKey), [otherWorkflowsData, workflowKey]);
637
+ const workflow = data?.workflowDefinition;
638
+ const persistedConfig = useMemo(() => {
639
+ return (workflow?.definitionJson)?.config ?? {};
640
+ }, [workflow?.definitionJson]);
641
+ const [form, setForm] = useState(persistedConfig);
642
+ const [redactFieldsText, setRedactFieldsText] = useState(() => formatRedactFields(persistedConfig.dataPrivacy?.redactFields));
643
+ useEffect(() => {
644
+ setForm(persistedConfig);
645
+ setRedactFieldsText(formatRedactFields(persistedConfig.dataPrivacy?.redactFields));
646
+ }, [persistedConfig]);
647
+ const runData = useRunDataSettings();
648
+ const selectedErrorWorkflowKey = form.errorWorkflowKey;
649
+ const { data: errorWorkflowData } = useWorkflowDefinitionQuery({
650
+ variables: { workflowKey: selectedErrorWorkflowKey ?? "" },
651
+ skip: !selectedErrorWorkflowKey
652
+ });
653
+ const selectedErrorWorkflow = errorWorkflowData?.workflowDefinition;
654
+ const errorWorkflowLacksTrigger = Boolean(selectedErrorWorkflowKey && selectedErrorWorkflow && !hasErrorTriggerNode(selectedErrorWorkflow.definitionJson));
655
+ /**
656
+ * Saves the form into the workflow's `config`. Resolves once saved, throws
657
+ * the service's refusal otherwise, and throws when the workflow could not be
658
+ * read, so a caller never reports a save that did not happen. A conflict (the workflow changed since
659
+ * it was read) is retried once on the latest version: settings only touch
660
+ * `config`, so they apply to it unchanged.
661
+ */
662
+ const save = useCallback(async () => {
663
+ if (!workflow?.id) throw new Error(tr("fluidgrids.settings.execution.workflowNotFound", "This workflow couldn't be found. It may have been deleted, or you may no longer have access to it."));
664
+ const send = (stored) => updateWorkflowDefinition({ variables: {
665
+ id: stored.id,
666
+ input: {
667
+ definitionJson: definitionWithSettings(stored.definitionJson, form),
668
+ ...typeof stored.revision === "number" ? { expectedRevision: stored.revision } : {}
669
+ }
670
+ } });
671
+ try {
672
+ await send(workflow);
673
+ } catch (err) {
674
+ if (!findSaveConflict(err)) throw err;
675
+ const latest = (await refetch()).data?.workflowDefinition;
676
+ if (!latest?.id) throw err;
677
+ await send(latest);
678
+ }
679
+ refetch();
680
+ }, [
681
+ workflow,
682
+ form,
683
+ updateWorkflowDefinition,
684
+ refetch,
685
+ tr
686
+ ]);
687
+ return {
688
+ workflow,
689
+ loading,
690
+ saving,
691
+ canEdit,
692
+ locked,
693
+ form,
694
+ setForm,
695
+ persistedConfig,
696
+ dirty: settingsChanged(persistedConfig, form),
697
+ redactFieldsText,
698
+ setRedactFieldsText,
699
+ runData,
700
+ errorWorkflowOptions,
701
+ otherWorkflowsLoading,
702
+ errorWorkflowLacksTrigger,
703
+ save
704
+ };
705
+ }
706
+
707
+ // src/modules/settings/utils/optional-positive-int.ts
708
+ /**
709
+ * Number inputs whose empty value means "no limit" — Max parallel executions
710
+ * (BOFF-7647) and Timeout in seconds (BOFF-7514). Empty is also what an unset
711
+ * value means to the engine; anything else is clamped to a whole number of at
712
+ * least 1.
713
+ */
714
+ function parseOptionalPositiveInt(value) {
715
+ if (value.trim() === "") return void 0;
716
+ const parsed = Number(value);
717
+ return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : void 0;
718
+ }
719
+
720
+ // src/modules/settings/components/SettingRow.tsx
721
+ /**
722
+ * One labelled setting: label and description on the left, its control on the right.
723
+ * `stacked` puts the control under the label at every width, for narrow panels such
724
+ * as the workflow settings drawer (BOFF-11843).
725
+ */
726
+ function SettingRow({ label, description, stacked = false, children }) {
727
+ return /* @__PURE__ */ jsxs("div", {
728
+ className: stacked ? "flex flex-col gap-2 py-3" : "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 py-4",
729
+ children: [/* @__PURE__ */ jsxs("div", {
730
+ className: "flex-1 min-w-0",
731
+ children: [/* @__PURE__ */ jsx(Label, {
732
+ className: "text-sm font-medium text-text-primary",
733
+ children: label
734
+ }), description && /* @__PURE__ */ jsx("p", {
735
+ className: "text-xs text-text-secondary mt-0.5",
736
+ children: description
737
+ })]
738
+ }), /* @__PURE__ */ jsx("div", {
739
+ className: "shrink-0",
740
+ children
741
+ })]
742
+ });
743
+ }
744
+
745
+ // src/modules/settings/components/ExecutionSettingsFields.tsx
746
+ /**
747
+ * The per-workflow execution settings rows (BOFF-7512, BOFF-7514), shared by
748
+ * Settings › Execution and the Navigation v2 settings drawer (BOFF-11843).
749
+ * Each row reads and writes the form of `useWorkflowExecutionSettingsForm`.
750
+ */
751
+ /** The "On workflow error" choices, in the order they are offered. */
752
+ const ON_ERROR_CHOICES = [
753
+ "stop",
754
+ "continue",
755
+ "retry"
756
+ ];
757
+ function isOnErrorChoice(value) {
758
+ return ON_ERROR_CHOICES.includes(value);
759
+ }
760
+ function TimeoutSetting({ ctl, stacked }) {
761
+ const tr = useTr();
762
+ const label = tr("fluidgrids.settings.execution.timeoutLabel", "Timeout");
763
+ return /* @__PURE__ */ jsx(SettingRow, {
764
+ stacked,
765
+ label,
766
+ description: tr("fluidgrids.settings.execution.defaultTimeoutDescription", "Stop a run that keeps running longer than this. Time a run spends paused (a Wait over a minute, an approval, a person's answer) does not count. Leave empty for no limit."),
767
+ children: /* @__PURE__ */ jsxs("div", {
768
+ className: "flex items-center gap-2",
769
+ children: [/* @__PURE__ */ jsx(Input, {
770
+ type: "number",
771
+ min: 1,
772
+ value: ctl.form.timeout ?? "",
773
+ placeholder: tr("fluidgrids.settings.execution.timeoutPlaceholder", "No timeout"),
774
+ "aria-label": label,
775
+ "data-testid": "execution-settings-timeout",
776
+ onChange: (e) => ctl.setForm((f) => ({
777
+ ...f,
778
+ timeout: parseOptionalPositiveInt(e.target.value)
779
+ })),
780
+ disabled: ctl.locked,
781
+ className: "w-[100px] text-right"
782
+ }), /* @__PURE__ */ jsx("span", {
783
+ className: "text-xs text-text-secondary",
784
+ children: tr("fluidgrids.settings.execution.defaultTimeoutUnit", "seconds")
785
+ })]
786
+ })
787
+ });
788
+ }
789
+ function MaxParallelSetting({ ctl, stacked }) {
790
+ const tr = useTr();
791
+ const label = tr("fluidgrids.settings.execution.maxParallelLabel", "Max parallel executions");
792
+ return /* @__PURE__ */ jsx(SettingRow, {
793
+ stacked,
794
+ label,
795
+ description: tr("fluidgrids.settings.execution.maxParallelDescription", "How many production runs of this workflow may run at once. Extra runs wait in a queue and start in order; manual test runs are never held back. Leave empty for no limit."),
796
+ children: /* @__PURE__ */ jsx(Input, {
797
+ type: "number",
798
+ min: 1,
799
+ value: ctl.form.maxParallelism ?? "",
800
+ placeholder: tr("fluidgrids.settings.execution.maxParallelPlaceholder", "No limit"),
801
+ "aria-label": label,
802
+ "data-testid": "execution-settings-max-parallel",
803
+ onChange: (e) => ctl.setForm((f) => ({
804
+ ...f,
805
+ maxParallelism: parseOptionalPositiveInt(e.target.value)
806
+ })),
807
+ disabled: ctl.locked,
808
+ className: "w-[100px] text-right"
809
+ })
810
+ });
811
+ }
812
+ /**
813
+ * "On workflow error": Stop, Continue or Retry. An fe-libs Select (it was a
814
+ * native `<select>`, which ignored the theme in its open list).
815
+ */
816
+ function OnErrorSetting({ ctl, stacked }) {
817
+ const tr = useTr();
818
+ const label = tr("fluidgrids.settings.execution.onErrorLabel", "On workflow error");
819
+ const choiceLabel = {
820
+ stop: tr("fluidgrids.settings.execution.onErrorStop", "Stop"),
821
+ continue: tr("fluidgrids.settings.execution.onErrorContinue", "Continue"),
822
+ retry: tr("fluidgrids.settings.execution.onErrorRetry", "Retry")
823
+ };
824
+ return /* @__PURE__ */ jsx(SettingRow, {
825
+ stacked,
826
+ label,
827
+ description: tr("fluidgrids.settings.execution.onErrorDescription", "What to do when this workflow's execution fails"),
828
+ children: /* @__PURE__ */ jsxs(Select, {
829
+ value: ctl.form.onError ?? "stop",
830
+ onValueChange: (value) => {
831
+ if (!isOnErrorChoice(value)) return;
832
+ ctl.setForm((f) => ({
833
+ ...f,
834
+ onError: value
835
+ }));
836
+ },
837
+ disabled: ctl.locked,
838
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
839
+ "aria-label": label,
840
+ "data-testid": "execution-settings-on-error",
841
+ className: "w-[160px] bg-surface border-border-default",
842
+ children: /* @__PURE__ */ jsx(SelectValue, {})
843
+ }), /* @__PURE__ */ jsx(SelectContent, { children: ON_ERROR_CHOICES.map((choice) => /* @__PURE__ */ jsx(SelectItem, {
844
+ value: choice,
845
+ children: choiceLabel[choice]
846
+ }, choice)) })]
847
+ })
848
+ });
849
+ }
850
+ const NO_ERROR_WORKFLOW = "__none__";
851
+ function ErrorWorkflowSetting({ ctl, stacked }) {
852
+ const tr = useTr();
853
+ const label = tr("fluidgrids.settings.execution.errorWorkflowLabel", "Error workflow");
854
+ return /* @__PURE__ */ jsx(SettingRow, {
855
+ stacked,
856
+ label,
857
+ description: tr("fluidgrids.settings.execution.errorWorkflowDescription", "Runs when a production run of this workflow fails, starting at that workflow's Error Trigger (optional)"),
858
+ children: /* @__PURE__ */ jsxs("div", {
859
+ className: stacked ? "flex flex-col gap-1" : "flex flex-col items-end gap-1",
860
+ children: [/* @__PURE__ */ jsxs(Select, {
861
+ value: ctl.form.errorWorkflowKey ?? NO_ERROR_WORKFLOW,
862
+ onValueChange: (val) => ctl.setForm((f) => ({
863
+ ...f,
864
+ errorWorkflowKey: val === NO_ERROR_WORKFLOW ? void 0 : val
865
+ })),
866
+ disabled: ctl.locked || ctl.otherWorkflowsLoading,
867
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
868
+ "aria-label": label,
869
+ "data-testid": "execution-settings-error-workflow",
870
+ className: "w-[220px] bg-surface border-border-default",
871
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: tr("fluidgrids.settings.execution.errorWorkflowPlaceholder", "None") })
872
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
873
+ value: NO_ERROR_WORKFLOW,
874
+ children: tr("fluidgrids.settings.execution.errorWorkflowNone", "None")
875
+ }), ctl.errorWorkflowOptions.map((w) => /* @__PURE__ */ jsx(SelectItem, {
876
+ value: w.workflowKey,
877
+ children: w.name
878
+ }, w.workflowKey))] })]
879
+ }), ctl.errorWorkflowLacksTrigger && /* @__PURE__ */ jsx("p", {
880
+ className: stacked ? "max-w-[260px] text-xs text-status-warning-text" : "max-w-[260px] text-right text-xs text-status-warning-text",
881
+ "data-testid": "error-workflow-no-trigger",
882
+ children: tr("fluidgrids.settings.execution.errorWorkflowNoTrigger", "This workflow has no Error Trigger, so it will not start. Add an Error Trigger node to it.")
883
+ })]
884
+ })
885
+ });
886
+ }
887
+ const SAVING_ROWS = [
888
+ {
889
+ key: "saveSuccessExecutions",
890
+ workspaceKey: "saveSuccessfulRuns",
891
+ labelKey: "fluidgrids.settings.execution.saveSuccessLabel",
892
+ label: "Keep data of successful runs",
893
+ testId: "execution-settings-keep-success"
894
+ },
895
+ {
896
+ key: "saveFailedExecutions",
897
+ workspaceKey: "saveFailedRuns",
898
+ labelKey: "fluidgrids.settings.execution.saveFailedLabel",
899
+ label: "Keep data of failed runs",
900
+ testId: "execution-settings-keep-failed"
901
+ },
902
+ {
903
+ key: "saveManualExecutions",
904
+ workspaceKey: "saveManualRuns",
905
+ labelKey: "fluidgrids.settings.execution.saveManualLabel",
906
+ label: "Keep data of test runs",
907
+ testId: "execution-settings-keep-test"
908
+ }
909
+ ];
910
+ /** Keep data of successful / failed / test runs: workspace default, keep, or don't. */
911
+ function RunDataSavingSettings({ ctl, stacked }) {
912
+ const tr = useTr();
913
+ return /* @__PURE__ */ jsx(Fragment$1, { children: SAVING_ROWS.map((row) => {
914
+ const inherited = ctl.runData.settings?.[row.workspaceKey];
915
+ return /* @__PURE__ */ jsx(SettingRow, {
916
+ stacked,
917
+ label: tr(row.labelKey, row.label),
918
+ children: /* @__PURE__ */ jsxs(Select, {
919
+ value: toSavingChoice(ctl.form.dataSaving?.[row.key]),
920
+ onValueChange: (choice) => ctl.setForm((f) => ({
921
+ ...f,
922
+ dataSaving: compactOverrides({
923
+ ...f.dataSaving,
924
+ [row.key]: fromSavingChoice(choice)
925
+ })
926
+ })),
927
+ disabled: ctl.locked,
928
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
929
+ "aria-label": tr(row.labelKey, row.label),
930
+ "data-testid": row.testId,
931
+ className: "w-[220px] bg-surface border-border-default",
932
+ children: /* @__PURE__ */ jsx(SelectValue, {})
933
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [
934
+ /* @__PURE__ */ jsx(SelectItem, {
935
+ value: "default",
936
+ children: inherited === void 0 ? tr("fluidgrids.settings.execution.savingDefault", "Workspace default") : inherited ? tr("fluidgrids.settings.execution.savingDefaultOn", "Workspace default (keep)") : tr("fluidgrids.settings.execution.savingDefaultOff", "Workspace default (don't keep)")
937
+ }),
938
+ /* @__PURE__ */ jsx(SelectItem, {
939
+ value: "save",
940
+ children: tr("fluidgrids.settings.execution.savingKeep", "Keep")
941
+ }),
942
+ /* @__PURE__ */ jsx(SelectItem, {
943
+ value: "skip",
944
+ children: tr("fluidgrids.settings.execution.savingDontKeep", "Don't keep")
945
+ })
946
+ ] })]
947
+ })
948
+ }, row.key);
949
+ }) });
950
+ }
951
+ /** Delete runs after (days) and Runs kept: empty follows the workspace. */
952
+ function RetentionSettings({ ctl, stacked }) {
953
+ const tr = useTr();
954
+ const daysLabel = tr("fluidgrids.settings.execution.retentionDaysLabel", "Delete runs after (days)");
955
+ const runsLabel = tr("fluidgrids.settings.execution.maxRunsLabel", "Runs kept");
956
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SettingRow, {
957
+ stacked,
958
+ label: daysLabel,
959
+ description: tr("fluidgrids.settings.execution.retentionDaysDescription", "Leave empty to follow the workspace's Run data settings."),
960
+ children: /* @__PURE__ */ jsx(Input, {
961
+ type: "number",
962
+ min: RETENTION_DAYS_RANGE.min,
963
+ max: RETENTION_DAYS_RANGE.max,
964
+ "aria-label": daysLabel,
965
+ "data-testid": "execution-settings-retention-days",
966
+ value: ctl.form.retention?.maxAgeDays ?? "",
967
+ placeholder: ctl.runData.settings ? String(ctl.runData.settings.retentionDays) : "",
968
+ onChange: (e) => ctl.setForm((f) => ({
969
+ ...f,
970
+ retention: compactOverrides({
971
+ ...f.retention,
972
+ maxAgeDays: parseBoundedInt(e.target.value, RETENTION_DAYS_RANGE)
973
+ })
974
+ })),
975
+ disabled: ctl.locked,
976
+ className: "w-[100px] text-right"
977
+ })
978
+ }), /* @__PURE__ */ jsx(SettingRow, {
979
+ stacked,
980
+ label: runsLabel,
981
+ description: tr("fluidgrids.settings.execution.maxRunsDescription", "Leave empty to follow the workspace's Run data settings."),
982
+ children: /* @__PURE__ */ jsx(Input, {
983
+ type: "number",
984
+ min: RETENTION_MAX_RUNS_RANGE.min,
985
+ max: RETENTION_MAX_RUNS_RANGE.max,
986
+ "aria-label": runsLabel,
987
+ "data-testid": "execution-settings-max-runs",
988
+ value: ctl.form.retention?.maxRuns ?? "",
989
+ placeholder: ctl.runData.settings ? String(ctl.runData.settings.retentionMaxRunsPerWorkflow) : "",
990
+ onChange: (e) => ctl.setForm((f) => ({
991
+ ...f,
992
+ retention: compactOverrides({
993
+ ...f.retention,
994
+ maxRuns: parseBoundedInt(e.target.value, RETENTION_MAX_RUNS_RANGE)
995
+ })
996
+ })),
997
+ disabled: ctl.locked,
998
+ className: "w-[120px] text-right"
999
+ })
1000
+ })] });
1001
+ }
1002
+
1003
+ export { WorkflowNotificationSettingsDocument as _, RunDataSavingSettings as a, useWorkflowExecutionSettingsForm as c, RETENTION_MAX_RUNS_RANGE as d, compactOverrides as f, useWorkflowNotificationSettings as g, NOTIFICATION_THROTTLE_RANGE as h, RetentionSettings as i, useRunDataSettings as l, parseRedactFields as m, MaxParallelSetting as n, TimeoutSetting as o, parseBoundedInt as p, OnErrorSetting as r, SettingRow as s, ErrorWorkflowSetting as t, RETENTION_DAYS_RANGE as u, UpdateWorkflowNotificationSettingsDocument as v };