@burdenoff/fluidgrids-fe-sdk 2026.1026.3 → 2026.1026.5

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 (37) hide show
  1. package/README.md +11 -0
  2. package/dist/{ExecutionDetailPage-JZLI5AZU.js → ExecutionDetailPage-QWMR2CST.js} +7 -7
  3. package/dist/{ExecutionsListPage-U26V5MSH.js → ExecutionsListPage-TYYC3WT7.js} +7 -7
  4. package/dist/{WorkflowBuilder-5VUBOFJT.js → WorkflowBuilder-U636SQC4.js} +9 -9
  5. package/dist/{WorkflowsList-IEPZE5WO.js → WorkflowsList-XPARJ4SL.js} +7 -7
  6. package/dist/{chunk-4R6V4O5V.js → chunk-3ZGCACWA.js} +1 -1
  7. package/dist/{chunk-4DJFVQX5.js → chunk-4JR52R3N.js} +26 -4
  8. package/dist/{chunk-7BVVJC7T.js → chunk-4QV27HMI.js} +3 -3
  9. package/dist/{chunk-3XTY6IOW.js → chunk-6HMCSUQM.js} +198 -26
  10. package/dist/{chunk-QM5P3TOP.js → chunk-BTVYWLPL.js} +38 -14
  11. package/dist/{chunk-PHE5K4VJ.js → chunk-DIZ7NKG2.js} +4 -4
  12. package/dist/{chunk-BMEC4O5L.js → chunk-FJMKFZ6L.js} +5 -5
  13. package/dist/{chunk-L7PTW7HR.js → chunk-IGBCNXSB.js} +3 -3
  14. package/dist/{chunk-36WZAQO3.js → chunk-IOAU6GYE.js} +1 -1
  15. package/dist/{chunk-BNA2HFGB.js → chunk-IQLMFGAQ.js} +1 -1
  16. package/dist/{chunk-RYNCNNTL.js → chunk-JOE4LAR4.js} +1 -1
  17. package/dist/{chunk-AI5I26EH.js → chunk-KQWIQJ7E.js} +2 -2
  18. package/dist/{chunk-WSJNHPNR.js → chunk-KVVM3C2R.js} +2 -2
  19. package/dist/{chunk-FLKTTSRJ.js → chunk-L2S5V4YS.js} +1 -1
  20. package/dist/{chunk-BP4WGVSM.js → chunk-NHGKPOJZ.js} +1 -1
  21. package/dist/{chunk-JPSAI56P.js → chunk-NLK5WXH5.js} +2 -2
  22. package/dist/{chunk-S6GZVS57.js → chunk-PKE3R6N3.js} +1 -1
  23. package/dist/{chunk-6EZ7IWRY.js → chunk-SO7UZG76.js} +5 -5
  24. package/dist/{chunk-WXYRP7R3.js → chunk-U5HHDK3P.js} +2 -2
  25. package/dist/{chunk-5K4ZE2DI.js → chunk-W57KG66V.js} +1 -1
  26. package/dist/{chunk-HQQHRK53.js → chunk-WU3ZNMXL.js} +4 -4
  27. package/dist/executions.js +9 -9
  28. package/dist/history.js +4 -4
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.js +31 -31
  31. package/dist/nodes.js +2 -2
  32. package/dist/observability.js +4 -4
  33. package/dist/settings.js +4 -4
  34. package/dist/templates.js +8 -8
  35. package/dist/workflow-builder.js +10 -10
  36. package/dist/workflows-list.js +7 -7
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -17,6 +17,17 @@ declare which operations they support; other operations use their manifest-drive
17
17
 
18
18
  FluidGrids Workflow Builder SDK - Embeddable visual workflow automation for React applications.
19
19
 
20
+ ## Loop Compatibility
21
+
22
+ Loop Reset accepts a boolean or boolean expression. New nodes installed from the
23
+ updated registry persist `batchSize: 1` and `reset: false`; saved explicit values
24
+ and legacy definitions omitting batch size keep their existing behavior (10).
25
+ The shared parameter editors distinguish missing return paths from possible
26
+ conditional exits without rejecting intentional exits. Reset clears the current
27
+ batch/results but not its safety budget. Existing output envelopes are unchanged.
28
+ BOFF-7415's cycle-authoring/continuation fix is required for general loop bodies;
29
+ these Reset controls do not replace that fix. Item-level lineage is not provided.
30
+
20
31
  ## Installation
21
32
 
22
33
  ```bash
@@ -1,16 +1,16 @@
1
- export { ExecutionDetailPage } from './chunk-6EZ7IWRY.js';
2
- import './chunk-WXYRP7R3.js';
3
- import './chunk-AI5I26EH.js';
1
+ export { ExecutionDetailPage } from './chunk-SO7UZG76.js';
2
+ import './chunk-U5HHDK3P.js';
3
+ import './chunk-KQWIQJ7E.js';
4
4
  import './chunk-5ERAJC2I.js';
5
- import './chunk-BNA2HFGB.js';
6
- import './chunk-BP4WGVSM.js';
5
+ import './chunk-IQLMFGAQ.js';
6
+ import './chunk-NHGKPOJZ.js';
7
7
  import './chunk-D3UWK7GY.js';
8
8
  import './chunk-PJAWNFJV.js';
9
- import './chunk-QM5P3TOP.js';
9
+ import './chunk-BTVYWLPL.js';
10
10
  import './chunk-JL7CASN5.js';
11
11
  import './chunk-PXIFUMLA.js';
12
12
  import './chunk-CY6KX4RG.js';
13
13
  import './chunk-WBH2XEWO.js';
14
- import './chunk-FLKTTSRJ.js';
14
+ import './chunk-L2S5V4YS.js';
15
15
  import './chunk-UULVIT25.js';
16
16
  import './chunk-NTWG3CJX.js';
@@ -1,17 +1,17 @@
1
- export { ExecutionsListPage } from './chunk-7BVVJC7T.js';
2
- import './chunk-WXYRP7R3.js';
3
- import './chunk-AI5I26EH.js';
1
+ export { ExecutionsListPage } from './chunk-4QV27HMI.js';
2
+ import './chunk-U5HHDK3P.js';
3
+ import './chunk-KQWIQJ7E.js';
4
4
  import './chunk-5ERAJC2I.js';
5
- import './chunk-BP4WGVSM.js';
6
- import './chunk-JPSAI56P.js';
5
+ import './chunk-NHGKPOJZ.js';
6
+ import './chunk-NLK5WXH5.js';
7
7
  import './chunk-D3UWK7GY.js';
8
8
  import './chunk-PJAWNFJV.js';
9
9
  import './chunk-4VJ3MDOV.js';
10
- import './chunk-QM5P3TOP.js';
10
+ import './chunk-BTVYWLPL.js';
11
11
  import './chunk-JL7CASN5.js';
12
12
  import './chunk-PXIFUMLA.js';
13
13
  import './chunk-CY6KX4RG.js';
14
14
  import './chunk-WBH2XEWO.js';
15
- import './chunk-FLKTTSRJ.js';
15
+ import './chunk-L2S5V4YS.js';
16
16
  import './chunk-UULVIT25.js';
17
17
  import './chunk-NTWG3CJX.js';
@@ -1,20 +1,20 @@
1
- export { WorkflowBuilder } from './chunk-HQQHRK53.js';
2
- import './chunk-WSJNHPNR.js';
3
- import './chunk-3XTY6IOW.js';
1
+ export { WorkflowBuilder } from './chunk-WU3ZNMXL.js';
2
+ import './chunk-KVVM3C2R.js';
3
+ import './chunk-6HMCSUQM.js';
4
4
  import './chunk-GEMNVFPK.js';
5
5
  import './chunk-IEVSFCFS.js';
6
6
  import './chunk-YXMFRQYV.js';
7
7
  import './chunk-BQXVT32O.js';
8
- import './chunk-4R6V4O5V.js';
9
- import './chunk-BNA2HFGB.js';
10
- import './chunk-BP4WGVSM.js';
11
- import './chunk-JPSAI56P.js';
8
+ import './chunk-3ZGCACWA.js';
9
+ import './chunk-IQLMFGAQ.js';
10
+ import './chunk-NHGKPOJZ.js';
11
+ import './chunk-NLK5WXH5.js';
12
12
  import './chunk-4VJ3MDOV.js';
13
- import './chunk-QM5P3TOP.js';
13
+ import './chunk-BTVYWLPL.js';
14
14
  import './chunk-JL7CASN5.js';
15
15
  import './chunk-PXIFUMLA.js';
16
16
  import './chunk-CY6KX4RG.js';
17
17
  import './chunk-WBH2XEWO.js';
18
- import './chunk-FLKTTSRJ.js';
18
+ import './chunk-L2S5V4YS.js';
19
19
  import './chunk-UULVIT25.js';
20
20
  import './chunk-NTWG3CJX.js';
@@ -1,17 +1,17 @@
1
- export { WorkflowsList } from './chunk-BMEC4O5L.js';
2
- import './chunk-WSJNHPNR.js';
3
- import './chunk-AI5I26EH.js';
1
+ export { WorkflowsList } from './chunk-FJMKFZ6L.js';
2
+ import './chunk-KVVM3C2R.js';
3
+ import './chunk-KQWIQJ7E.js';
4
4
  import './chunk-5ERAJC2I.js';
5
- import './chunk-4R6V4O5V.js';
6
- import './chunk-JPSAI56P.js';
5
+ import './chunk-3ZGCACWA.js';
6
+ import './chunk-NLK5WXH5.js';
7
7
  import './chunk-D3UWK7GY.js';
8
8
  import './chunk-PJAWNFJV.js';
9
9
  import './chunk-4VJ3MDOV.js';
10
- import './chunk-QM5P3TOP.js';
10
+ import './chunk-BTVYWLPL.js';
11
11
  import './chunk-JL7CASN5.js';
12
12
  import './chunk-PXIFUMLA.js';
13
13
  import './chunk-CY6KX4RG.js';
14
14
  import './chunk-WBH2XEWO.js';
15
- import './chunk-FLKTTSRJ.js';
15
+ import './chunk-L2S5V4YS.js';
16
16
  import './chunk-UULVIT25.js';
17
17
  import './chunk-NTWG3CJX.js';
@@ -1,4 +1,4 @@
1
- import { useFluidGridsAnalytics, emitWorkflowLimitHit, emitUpgradeClicked, WORKFLOWS_QUOTA_NAME } from './chunk-QM5P3TOP.js';
1
+ import { useFluidGridsAnalytics, emitWorkflowLimitHit, emitUpgradeClicked, WORKFLOWS_QUOTA_NAME } from './chunk-BTVYWLPL.js';
2
2
  import { useTr } from './chunk-UULVIT25.js';
3
3
  import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '@burdenoff/fe-libs/ui';
4
4
  import { useRef, useEffect } from 'react';
@@ -1,7 +1,7 @@
1
- import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-S6GZVS57.js';
1
+ import { useFluidGridsFlag, FLUIDGRIDS_FLAGS, LogStreamingTab } from './chunk-PKE3R6N3.js';
2
2
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
3
- import { useFlowStore } from './chunk-QM5P3TOP.js';
4
- import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery } from './chunk-FLKTTSRJ.js';
3
+ import { useFlowStore } from './chunk-BTVYWLPL.js';
4
+ import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery } from './chunk-L2S5V4YS.js';
5
5
  import { useTr } from './chunk-UULVIT25.js';
6
6
  import { useState, useMemo, useEffect, useCallback } from 'react';
7
7
  import { useNavigate, useParams } from 'react-router';
@@ -29,6 +29,25 @@ function parseOptionalPositiveInt(value) {
29
29
  const parsed = Number.parseInt(value, 10);
30
30
  return Number.isFinite(parsed) ? Math.max(1, parsed) : void 0;
31
31
  }
32
+
33
+ // src/modules/settings/utils/workflow-settings-persistence.ts
34
+ function mergeWorkflowExecutionSettings(existing, next) {
35
+ const existingDataSaving = existing.dataSaving;
36
+ const nextDataSaving = next.dataSaving;
37
+ return {
38
+ ...existing,
39
+ ...next,
40
+ ...existingDataSaving || nextDataSaving ? {
41
+ dataSaving: {
42
+ ...isRecord(existingDataSaving) ? existingDataSaving : {},
43
+ ...nextDataSaving ?? {}
44
+ }
45
+ } : {}
46
+ };
47
+ }
48
+ function isRecord(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
32
51
  var DEFAULT_EXECUTION_SETTINGS = {
33
52
  onError: "stop"
34
53
  };
@@ -213,7 +232,10 @@ function ExecutionTab({ workflowKey }) {
213
232
  try {
214
233
  const definitionJson = {
215
234
  ...workflow.definitionJson,
216
- config: form
235
+ config: mergeWorkflowExecutionSettings(
236
+ workflow.definitionJson.config ?? {},
237
+ form
238
+ )
217
239
  };
218
240
  await updateWorkflowDefinition({
219
241
  variables: { id: workflow.id, input: { definitionJson } }
@@ -1,6 +1,6 @@
1
- import { ExecutionStats, ExecutionFilters, ExecutionTable, ExecutionCards, ExecutionTimeline, Pagination } from './chunk-WXYRP7R3.js';
2
- import { useExecutionsWithStore, useExecutionStats, useExecutionActions, useAutoRefresh } from './chunk-AI5I26EH.js';
3
- import { useWorkflowDefinitions } from './chunk-JPSAI56P.js';
1
+ import { ExecutionStats, ExecutionFilters, ExecutionTable, ExecutionCards, ExecutionTimeline, Pagination } from './chunk-U5HHDK3P.js';
2
+ import { useExecutionsWithStore, useExecutionStats, useExecutionActions, useAutoRefresh } from './chunk-KQWIQJ7E.js';
3
+ import { useWorkflowDefinitions } from './chunk-NLK5WXH5.js';
4
4
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
5
5
  import { useExecutionsStore } from './chunk-CY6KX4RG.js';
6
6
  import { useTr } from './chunk-UULVIT25.js';
@@ -2,16 +2,16 @@ import { deepClone, applyTemplateInputs } from './chunk-GEMNVFPK.js';
2
2
  import { Input as Input$1 } from './chunk-IEVSFCFS.js';
3
3
  import { Dialog as Dialog$1, DialogContent as DialogContent$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, DialogDescription as DialogDescription$1, DialogFooter } from './chunk-YXMFRQYV.js';
4
4
  import { Badge as Badge$1, Button } from './chunk-BQXVT32O.js';
5
- import { QuotaExhaustedDialog } from './chunk-4R6V4O5V.js';
6
- import { EntityTagPicker } from './chunk-BNA2HFGB.js';
7
- import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-JPSAI56P.js';
5
+ import { QuotaExhaustedDialog } from './chunk-3ZGCACWA.js';
6
+ import { EntityTagPicker } from './chunk-IQLMFGAQ.js';
7
+ import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-NLK5WXH5.js';
8
8
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
9
- import { RUNS_QUOTA_NAME, throwIfResultError, useFlowStore, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, STICKY_NOTE_TYPE, fieldsFromValue, getNodeDetail, useFluidGridsAnalytics, buildOperationSeed, isNodeTrigger, buildStickyNode, fieldsFromIO, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, resolveNodeOperation, declaredOutputFields, pathToExpression, ExpressionEditor, formatParameterValue, parseParameterValue, isNodeConfigsInitialized, resolveUiSpec, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, openRunQuotaUpgradePrompt, describeOperationError, isTriggerOperation, canRenderOperationPanel, resolveOperationName, resolveInputHandles } from './chunk-QM5P3TOP.js';
9
+ import { RUNS_QUOTA_NAME, throwIfResultError, useFlowStore, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, STICKY_NOTE_TYPE, fieldsFromValue, getNodeDetail, useFluidGridsAnalytics, buildOperationSeed, isNodeTrigger, buildStickyNode, fieldsFromIO, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, resolveNodeOperation, declaredOutputFields, subscribeWorkflowSaved, pathToExpression, ExpressionEditor, formatParameterValue, parseParameterValue, isNodeConfigsInitialized, resolveUiSpec, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, openRunQuotaUpgradePrompt, describeOperationError, isTriggerOperation, canRenderOperationPanel, resolveOperationName, resolveInputHandles } from './chunk-BTVYWLPL.js';
10
10
  import { cn } from './chunk-JL7CASN5.js';
11
11
  import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
12
12
  import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
13
13
  import { GetMyNodeInstallationsDocument, BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, InstallPurchasedNodeDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument, GetNodeInstallableVersionsDocument } from './chunk-WBH2XEWO.js';
14
- import { WorkspaceRunsQuotaDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, WorkflowGenerationTemplatesDocument, WorkflowGenerationDocument, GenerateWorkflowFromPromptDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-FLKTTSRJ.js';
14
+ import { WorkspaceRunsQuotaDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, WorkflowGenerationTemplatesDocument, WorkflowGenerationDocument, GenerateWorkflowFromPromptDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-L2S5V4YS.js';
15
15
  import { useTr } from './chunk-UULVIT25.js';
16
16
  import { getFluidGridsClient, waitForFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
17
17
  import * as React3 from 'react';
@@ -14951,6 +14951,38 @@ function SwitchRulesEditor({
14951
14951
  ) })
14952
14952
  ] });
14953
14953
  }
14954
+
14955
+ // src/builder/lib/loop-return-status.ts
14956
+ function loopReturnStatus(nodeId, edges, loopHandle = "loop") {
14957
+ const enabled = edges.filter(
14958
+ (edge) => edge.disabled !== true && edge.data?.disabled !== true
14959
+ );
14960
+ const queue = enabled.filter(
14961
+ (edge) => (edge.source ?? edge.from) === nodeId && (edge.sourceHandle ?? edge.fromHandle ?? "loop") === loopHandle
14962
+ ).map((edge) => edge.target ?? edge.to).filter((target) => typeof target === "string");
14963
+ const visited = /* @__PURE__ */ new Set();
14964
+ let returns = false;
14965
+ let exits = false;
14966
+ for (let index = 0; index < queue.length; index++) {
14967
+ const current = queue[index];
14968
+ if (current === nodeId) {
14969
+ returns = true;
14970
+ continue;
14971
+ }
14972
+ if (!current || visited.has(current)) continue;
14973
+ visited.add(current);
14974
+ const outgoing = enabled.filter(
14975
+ (edge) => (edge.source ?? edge.from) === current
14976
+ );
14977
+ if (outgoing.length === 0) exits = true;
14978
+ for (const edge of outgoing) {
14979
+ const target = edge.target ?? edge.to;
14980
+ if (target) queue.push(target);
14981
+ }
14982
+ }
14983
+ if (!returns) return "missing-return";
14984
+ return exits ? "conditional-exit" : "connected";
14985
+ }
14954
14986
  function normalizeCodeLanguage(value) {
14955
14987
  return typeof value === "string" && value.toLowerCase().startsWith("py") ? "python" : "javascript";
14956
14988
  }
@@ -15251,10 +15283,19 @@ function useBuilderReadOnly() {
15251
15283
  return useContext(BuilderReadOnlyContext);
15252
15284
  }
15253
15285
  var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
15286
+ function isSafeHttpUrl(value) {
15287
+ try {
15288
+ const parsed = new URL(value);
15289
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
15290
+ } catch {
15291
+ return false;
15292
+ }
15293
+ }
15254
15294
  function CopyableUrl({
15255
15295
  url,
15256
15296
  copyAriaLabel,
15257
- testId
15297
+ testId,
15298
+ openLabel
15258
15299
  }) {
15259
15300
  const tr = useTr();
15260
15301
  const [copied, setCopied] = useState(false);
@@ -15298,6 +15339,20 @@ function CopyableUrl({
15298
15339
  copied ? tr("fluidgrids.builder.webhookUrlField.copied", "Copied") : tr("fluidgrids.builder.webhookUrlField.copy", "Copy")
15299
15340
  ]
15300
15341
  }
15342
+ ),
15343
+ openLabel && /* @__PURE__ */ jsxs(
15344
+ "a",
15345
+ {
15346
+ href: url,
15347
+ target: "_blank",
15348
+ rel: "noopener noreferrer",
15349
+ "data-testid": `${testId}-open`,
15350
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border bg-background px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted",
15351
+ children: [
15352
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:open-in-new", className: "h-4 w-4" }),
15353
+ openLabel
15354
+ ]
15355
+ }
15301
15356
  )
15302
15357
  ] });
15303
15358
  }
@@ -15340,6 +15395,50 @@ function WebhookUrlField({ webhookUrl, loading }) {
15340
15395
  ) })
15341
15396
  ] });
15342
15397
  }
15398
+ function FormUrlField({ formUrl, loading, error }) {
15399
+ const tr = useTr();
15400
+ const safeUrl = formUrl && isSafeHttpUrl(formUrl) ? formUrl : null;
15401
+ let message = null;
15402
+ if (!safeUrl || error) {
15403
+ if (error || formUrl) {
15404
+ message = tr(
15405
+ "fluidgrids.builder.formUrlField.error",
15406
+ "Unable to load a safe trigger URL."
15407
+ );
15408
+ } else if (loading) {
15409
+ message = tr(
15410
+ "fluidgrids.builder.formUrlField.loading",
15411
+ "Loading form URL\u2026"
15412
+ );
15413
+ } else {
15414
+ message = tr(
15415
+ "fluidgrids.builder.formUrlField.unsaved",
15416
+ "Save the workflow to generate its form URL."
15417
+ );
15418
+ }
15419
+ }
15420
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": "ndv-form-url", children: [
15421
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL2, children: tr("fluidgrids.builder.formUrlField.label", "Form URL") }),
15422
+ safeUrl && !error ? /* @__PURE__ */ jsxs(Fragment, { children: [
15423
+ /* @__PURE__ */ jsx(
15424
+ CopyableUrl,
15425
+ {
15426
+ url: safeUrl,
15427
+ testId: "ndv-form-url-value",
15428
+ copyAriaLabel: tr(
15429
+ "fluidgrids.builder.formUrlField.copyAriaLabel",
15430
+ "Copy form URL"
15431
+ ),
15432
+ openLabel: tr("fluidgrids.builder.formUrlField.open", "Open form")
15433
+ }
15434
+ ),
15435
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: tr(
15436
+ "fluidgrids.builder.formUrlField.hint",
15437
+ "Share this link to collect submissions; each submission starts the workflow's active version."
15438
+ ) })
15439
+ ] }) : /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: message })
15440
+ ] });
15441
+ }
15343
15442
  function useSecondsLeft(expiresAt) {
15344
15443
  const [now, setNow] = useState(() => Date.now());
15345
15444
  useEffect(() => {
@@ -16178,20 +16277,23 @@ function updateMappingDefinition(definition, field, value) {
16178
16277
  return { ...definition, [field]: value };
16179
16278
  }
16180
16279
  function useWebhookRegistration(workflowKey, triggerNodeId, enabled = true) {
16181
- const [registration, setRegistration] = useState(
16182
- null
16183
- );
16184
- const [loading, setLoading] = useState(false);
16185
- const [error, setError] = useState(null);
16280
+ const key = enabled && workflowKey && triggerNodeId ? JSON.stringify([workflowKey, triggerNodeId]) : null;
16281
+ const requestId = useRef(0);
16282
+ const [state, setState] = useState({ key: null, registration: null, loading: false, error: null });
16186
16283
  const fetchData = useCallback(async () => {
16187
- if (!enabled || !workflowKey || !triggerNodeId) {
16188
- setRegistration(null);
16284
+ const currentRequest = ++requestId.current;
16285
+ if (!key || !workflowKey || !triggerNodeId) return;
16286
+ const client = getFluidGridsClientSafe();
16287
+ if (!client) {
16288
+ setState({
16289
+ key,
16290
+ registration: null,
16291
+ loading: false,
16292
+ error: new Error("Workflow client unavailable")
16293
+ });
16189
16294
  return;
16190
16295
  }
16191
- const client = getFluidGridsClientSafe();
16192
- if (!client) return;
16193
- setLoading(true);
16194
- setError(null);
16296
+ setState({ key, registration: null, loading: true, error: null });
16195
16297
  try {
16196
16298
  const result = await client.query({
16197
16299
  query: EventRegistrationsDocument,
@@ -16201,21 +16303,38 @@ function useWebhookRegistration(workflowKey, triggerNodeId, enabled = true) {
16201
16303
  const match = result.data?.eventRegistrations?.find(
16202
16304
  (r) => r.triggerNodeId === triggerNodeId
16203
16305
  ) ?? null;
16204
- setRegistration(match);
16306
+ if (currentRequest === requestId.current)
16307
+ setState({ key, registration: match, loading: false, error: null });
16205
16308
  } catch (err) {
16206
- setError(err);
16207
- } finally {
16208
- setLoading(false);
16309
+ if (currentRequest === requestId.current)
16310
+ setState({
16311
+ key,
16312
+ registration: null,
16313
+ loading: false,
16314
+ error: err instanceof Error ? err : new Error("Unable to load trigger URL")
16315
+ });
16209
16316
  }
16210
- }, [enabled, workflowKey, triggerNodeId]);
16317
+ }, [key, workflowKey, triggerNodeId]);
16211
16318
  useEffect(() => {
16212
16319
  fetchData();
16320
+ return () => {
16321
+ requestId.current += 1;
16322
+ };
16213
16323
  }, [fetchData]);
16324
+ useEffect(() => {
16325
+ if (!enabled || !workflowKey) return;
16326
+ return subscribeWorkflowSaved(workflowKey, () => {
16327
+ void fetchData();
16328
+ });
16329
+ }, [enabled, workflowKey, fetchData]);
16330
+ const current = state.key === key && key !== null ? state : null;
16331
+ const registration = current?.registration ?? null;
16214
16332
  return {
16215
16333
  registration,
16216
16334
  webhookUrl: registration?.webhookUrl ?? null,
16217
- loading,
16218
- error,
16335
+ formUrl: registration?.formUrl ?? null,
16336
+ loading: current?.loading ?? !!key,
16337
+ error: current?.error ?? null,
16219
16338
  refetch: fetchData
16220
16339
  };
16221
16340
  }
@@ -16406,10 +16525,19 @@ function useNodeConfig(target, options) {
16406
16525
  () => !!asRecord(currentOp?.output_fields)?.webhookUrl,
16407
16526
  [currentOp]
16408
16527
  );
16409
- const { webhookUrl, loading: webhookUrlLoading } = useWebhookRegistration(
16528
+ const isFormTrigger = useMemo(
16529
+ () => !!asRecord(currentOp?.output_fields)?.formUrl,
16530
+ [currentOp]
16531
+ );
16532
+ const {
16533
+ webhookUrl,
16534
+ formUrl,
16535
+ error: triggerUrlError,
16536
+ loading: webhookUrlLoading
16537
+ } = useWebhookRegistration(
16410
16538
  workflowKey || void 0,
16411
16539
  node?.id,
16412
- isWebhookTrigger && (options?.webhookEnabled ?? true)
16540
+ (isWebhookTrigger || isFormTrigger) && (options?.webhookEnabled ?? true)
16413
16541
  );
16414
16542
  const pickerFields = useMemo(() => flattenFields(inputFields), [inputFields]);
16415
16543
  const mapping = currentEdge?.mapping?.definition ?? {};
@@ -16622,6 +16750,9 @@ function useNodeConfig(target, options) {
16622
16750
  connectionAdapters,
16623
16751
  selectedConnections,
16624
16752
  isWebhookTrigger,
16753
+ isFormTrigger,
16754
+ formUrl,
16755
+ triggerUrlError,
16625
16756
  isWebhookWait,
16626
16757
  webhookUrl,
16627
16758
  webhookUrlLoading,
@@ -16751,6 +16882,12 @@ function NodeParamsForm({
16751
16882
  testIdPrefix = "ndv"
16752
16883
  }) {
16753
16884
  const tr = useTr();
16885
+ const edges = useFlowStore((state) => state.workflow.edges);
16886
+ const loopStatus = (nodeType === "internal:loop" || nodeType === "loop") && config.node ? loopReturnStatus(
16887
+ config.node.id,
16888
+ edges,
16889
+ typeof config.argsState.loopHandle === "string" ? config.argsState.loopHandle : "loop"
16890
+ ) : void 0;
16754
16891
  const {
16755
16892
  params,
16756
16893
  argsState,
@@ -16759,6 +16896,9 @@ function NodeParamsForm({
16759
16896
  connectionAdapters,
16760
16897
  selectedConnections,
16761
16898
  isWebhookTrigger,
16899
+ isFormTrigger,
16900
+ formUrl,
16901
+ triggerUrlError,
16762
16902
  isWebhookWait,
16763
16903
  webhookUrl,
16764
16904
  webhookUrlLoading,
@@ -16786,6 +16926,30 @@ function NodeParamsForm({
16786
16926
  return "1.0.0";
16787
16927
  })();
16788
16928
  return /* @__PURE__ */ jsxs(Fragment, { children: [
16929
+ loopStatus === "missing-return" && /* @__PURE__ */ jsx(
16930
+ "p",
16931
+ {
16932
+ role: "status",
16933
+ "data-testid": `${testIdPrefix}-loop-return-hint`,
16934
+ className: "text-xs text-muted-foreground",
16935
+ children: tr(
16936
+ "fluidgrids.builder.loopPanel.missingReturn",
16937
+ "No Loop Body return path is connected. Only the first batch will run unless you connect a body node back to this Loop. An intentional early exit is allowed; Done does not run on that exit."
16938
+ )
16939
+ }
16940
+ ),
16941
+ loopStatus === "conditional-exit" && /* @__PURE__ */ jsx(
16942
+ "p",
16943
+ {
16944
+ role: "status",
16945
+ "data-testid": `${testIdPrefix}-loop-return-hint`,
16946
+ className: "text-xs text-muted-foreground",
16947
+ children: tr(
16948
+ "fluidgrids.builder.loopPanel.conditionalExit",
16949
+ "A return path and a possible early exit are connected. Conditional exits are allowed; Done runs only when the Loop finishes or reaches its safety limit."
16950
+ )
16951
+ }
16952
+ ),
16789
16953
  isWebhookTrigger && /* @__PURE__ */ jsxs(Fragment, { children: [
16790
16954
  /* @__PURE__ */ jsx(
16791
16955
  WebhookUrlField,
@@ -16796,6 +16960,14 @@ function NodeParamsForm({
16796
16960
  ),
16797
16961
  /* @__PURE__ */ jsx(WebhookTestListenerField, { nodeId: config.node?.id })
16798
16962
  ] }),
16963
+ isFormTrigger && /* @__PURE__ */ jsx(
16964
+ FormUrlField,
16965
+ {
16966
+ formUrl,
16967
+ loading: webhookUrlLoading,
16968
+ error: triggerUrlError
16969
+ }
16970
+ ),
16799
16971
  isWebhookWait && /* @__PURE__ */ jsx(WaitResumeUrlField, {}),
16800
16972
  connectionAdapters.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `${testIdPrefix}-connections`, children: [
16801
16973
  /* @__PURE__ */ jsx("div", { className: SECTION_LABEL3, children: tr(
@@ -2,7 +2,7 @@ import { cn as cn$1 } from './chunk-JL7CASN5.js';
2
2
  import { getGlobalStoreClient, createGlobalStoreClient, setGlobalStoreClient, resetGlobalStoreClient } from './chunk-PXIFUMLA.js';
3
3
  import { config, getFluidGridsConfig, validateTrustedOrigin, resetFluidGridsExecutionState, setFluidGridsConfig, resetFluidGridsConfig } from './chunk-CY6KX4RG.js';
4
4
  import { GetMyNodeInstallationsDocument } from './chunk-WBH2XEWO.js';
5
- import { fetchAllNodeEndpointConfigs, NodeManifestDocument, UpdateWorkflowDefinitionDocument, CreateWorkflowDefinitionDocument, CreateWorkflowVersionDocument, ExecuteStepDocument, TriggerWorkflowDocument, RunFromNodeDocument, ExecutionDetailDocument, WorkflowRunDocument } from './chunk-FLKTTSRJ.js';
5
+ import { fetchAllNodeEndpointConfigs, NodeManifestDocument, UpdateWorkflowDefinitionDocument, CreateWorkflowDefinitionDocument, CreateWorkflowVersionDocument, ExecuteStepDocument, TriggerWorkflowDocument, RunFromNodeDocument, ExecutionDetailDocument, WorkflowRunDocument } from './chunk-L2S5V4YS.js';
6
6
  import { useTr, ENGLISH_FALLBACK_TRANSLATOR, FluidGridsTranslationProvider } from './chunk-UULVIT25.js';
7
7
  import { waitForFluidGridsClient, getFluidGridsClient, getFluidGridsClientSafe, createFluidGridsClient, setFluidGridsClient, resetFluidGridsClient } from './chunk-NTWG3CJX.js';
8
8
  import { CombinedGraphQLErrors, ServerError } from '@apollo/client/errors';
@@ -5045,6 +5045,21 @@ function mergeStickyNotes(def) {
5045
5045
  return [...nodes, ...stickies.filter((s) => s?.id && !seen.has(s.id))];
5046
5046
  }
5047
5047
 
5048
+ // src/hooks/workflow-saved-events.ts
5049
+ var listeners = /* @__PURE__ */ new Map();
5050
+ function subscribeWorkflowSaved(workflowKey, listener) {
5051
+ const subscribers = listeners.get(workflowKey) ?? /* @__PURE__ */ new Set();
5052
+ subscribers.add(listener);
5053
+ listeners.set(workflowKey, subscribers);
5054
+ return () => {
5055
+ subscribers.delete(listener);
5056
+ if (subscribers.size === 0) listeners.delete(workflowKey);
5057
+ };
5058
+ }
5059
+ function notifyWorkflowSaved(workflowKey) {
5060
+ listeners.get(workflowKey)?.forEach((listener) => listener());
5061
+ }
5062
+
5048
5063
  // src/builder/lib/field-schema.ts
5049
5064
  function isRecord4(value) {
5050
5065
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -5345,6 +5360,13 @@ function normalizeWorkflowParameters(nodes, edges, fallbackOperations) {
5345
5360
  });
5346
5361
  }
5347
5362
 
5363
+ // src/builder/lib/node-persistence.ts
5364
+ function prepareNodeForSave(node, slimData) {
5365
+ const nextData = { ...slimData(node.data ?? {}) };
5366
+ const { nodeData: _slimNodeData, ...flattenedData } = nextData;
5367
+ return { ...node, data: nextData, ...flattenedData };
5368
+ }
5369
+
5348
5370
  // src/builder/hooks/use-save-workflow.ts
5349
5371
  function slimNodeDataToCurrentOperation(data) {
5350
5372
  const nodeData = data.nodeData;
@@ -5359,6 +5381,9 @@ function slimNodeDataToCurrentOperation(data) {
5359
5381
  }
5360
5382
  return { ...data, nodeData: { ...nodeData, operations: [current] } };
5361
5383
  }
5384
+ function prepareNodeForSave2(node) {
5385
+ return prepareNodeForSave(node, slimNodeDataToCurrentOperation);
5386
+ }
5362
5387
  function useSaveWorkflow() {
5363
5388
  const [isSaving, setIsSaving] = useState(false);
5364
5389
  const [quotaExhausted, setQuotaExhausted] = useState(false);
@@ -5388,14 +5413,12 @@ function useSaveWorkflow() {
5388
5413
  partitioned.nodes,
5389
5414
  workflow.edges ?? [],
5390
5415
  (type) => NODE_CONFIGS.find((config2) => config2.type === type)?.operations
5391
- ).map((n) => {
5392
- const slimData = slimNodeDataToCurrentOperation(
5393
- n.data ?? {}
5394
- );
5395
- const { nodeData: _slimNodeData, ...flattenedData } = slimData;
5396
- void _slimNodeData;
5397
- return { ...n, data: slimData, ...flattenedData };
5398
- }),
5416
+ ).map(
5417
+ (n) => prepareNodeForSave2({
5418
+ ...n,
5419
+ data: n.data ?? {}
5420
+ })
5421
+ ),
5399
5422
  stickyNotes: partitioned.stickyNotes
5400
5423
  };
5401
5424
  const definitionJson = {
@@ -5531,6 +5554,7 @@ function useSaveWorkflow() {
5531
5554
  }
5532
5555
  }
5533
5556
  if (savedDbId) {
5557
+ notifyWorkflowSaved(workflowKey);
5534
5558
  const config2 = getFluidGridsConfig();
5535
5559
  if (config2.onWorkflowSaved) {
5536
5560
  try {
@@ -7296,18 +7320,18 @@ function FluidGridsProvider({
7296
7320
  ]);
7297
7321
  const [readiness] = useState(() => {
7298
7322
  let activeIdentity2 = null;
7299
- const listeners = /* @__PURE__ */ new Set();
7323
+ const listeners2 = /* @__PURE__ */ new Set();
7300
7324
  return {
7301
7325
  subscribe: (listener) => {
7302
- listeners.add(listener);
7326
+ listeners2.add(listener);
7303
7327
  return () => {
7304
- listeners.delete(listener);
7328
+ listeners2.delete(listener);
7305
7329
  };
7306
7330
  },
7307
7331
  snapshot: () => activeIdentity2,
7308
7332
  set: (value) => {
7309
7333
  activeIdentity2 = value;
7310
- for (const listener of listeners) listener();
7334
+ for (const listener of listeners2) listener();
7311
7335
  }
7312
7336
  };
7313
7337
  });
@@ -8102,4 +8126,4 @@ function useNodeExecution() {
8102
8126
  };
8103
8127
  }
8104
8128
 
8105
- export { ALLOW_ALL_PERMISSIONS, AVAILABLE_NODES, BILLING_PLANS_PATH, ConnectionFormDialog, ConnectionSelector, ExpressionEditor, FLUIDGRIDS_PERMISSIONS, FluidGridsContext, FluidGridsPermissionsProvider, FluidGridsProvider, NODES, NODES_METADATA, NODE_CONFIGS, NODE_OPERATION_DRAG_DATA_FORMAT, NODE_TYPES, NODE_TYPE_DRAG_DATA_FORMAT, NodePropertyPanel, NodeTypeIcon, RUNS_QUOTA_NAME, STICKY_NOTE_TYPE, SdkPermissionButton, SdkPermissionGate, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WORKFLOWS_QUOTA_NAME, buildOperationSeed, buildStickyNode, canRenderOperationPanel, declaredOutputFields, describeOperationError, emitNodeInstalled, emitRunFailed, emitRunLimitHit, emitRunStarted, emitTemplateUsed, emitUpgradeClicked, emitWorkflowCreated, emitWorkflowLimitHit, emitWorkflowPublished, ensureNodeConfigsLoaded, evaluatePermissionCheck, fieldsFromIO, fieldsFromValue, findQuotaRefusal, formatParameterValue, getNodeDetail, getNodeMetadata, isImageIcon, isNodeConfigsInitialized, isNodeTrigger, isQuotaExhaustedFailure, isTriggerOperation, listCustomBlocks, mergeStickyNotes, nodeAccent, onNodeTypesUpdate, openRunQuotaUpgradePrompt, parseParameterValue, pathToExpression, refetchStoreNodes, registerCustomBlock, registerWorkflowNodeTypes, resolveInputHandles, resolveNodeOperation, resolveOperationName, resolveUiSpec, runTriggerFromMode, stripSubmitActions, structuredParameterType, synchronizeOperationUpdate, throwIfResultError, useConnection, useConnections, useConnectionsByProvider, useCreateConnection, useFlowStore, useFluidGridsAnalytics, useFluidGridsContext, useFluidGridsPermission, useInitiateOAuth, useInsertNode, useIsValidConnection, useNodeExecution, useProviders, useSaveWorkflow };
8129
+ export { ALLOW_ALL_PERMISSIONS, AVAILABLE_NODES, BILLING_PLANS_PATH, ConnectionFormDialog, ConnectionSelector, ExpressionEditor, FLUIDGRIDS_PERMISSIONS, FluidGridsContext, FluidGridsPermissionsProvider, FluidGridsProvider, NODES, NODES_METADATA, NODE_CONFIGS, NODE_OPERATION_DRAG_DATA_FORMAT, NODE_TYPES, NODE_TYPE_DRAG_DATA_FORMAT, NodePropertyPanel, NodeTypeIcon, RUNS_QUOTA_NAME, STICKY_NOTE_TYPE, SdkPermissionButton, SdkPermissionGate, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WORKFLOWS_QUOTA_NAME, buildOperationSeed, buildStickyNode, canRenderOperationPanel, declaredOutputFields, describeOperationError, emitNodeInstalled, emitRunFailed, emitRunLimitHit, emitRunStarted, emitTemplateUsed, emitUpgradeClicked, emitWorkflowCreated, emitWorkflowLimitHit, emitWorkflowPublished, ensureNodeConfigsLoaded, evaluatePermissionCheck, fieldsFromIO, fieldsFromValue, findQuotaRefusal, formatParameterValue, getNodeDetail, getNodeMetadata, isImageIcon, isNodeConfigsInitialized, isNodeTrigger, isQuotaExhaustedFailure, isTriggerOperation, listCustomBlocks, mergeStickyNotes, nodeAccent, onNodeTypesUpdate, openRunQuotaUpgradePrompt, parseParameterValue, pathToExpression, refetchStoreNodes, registerCustomBlock, registerWorkflowNodeTypes, resolveInputHandles, resolveNodeOperation, resolveOperationName, resolveUiSpec, runTriggerFromMode, stripSubmitActions, structuredParameterType, subscribeWorkflowSaved, synchronizeOperationUpdate, throwIfResultError, useConnection, useConnections, useConnectionsByProvider, useCreateConnection, useFlowStore, useFluidGridsAnalytics, useFluidGridsContext, useFluidGridsPermission, useInitiateOAuth, useInsertNode, useIsValidConnection, useNodeExecution, useProviders, useSaveWorkflow };