@burdenoff/fluidgrids-fe-sdk 2026.1029.6 → 2026.1029.8

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 (45) hide show
  1. package/dist/ExecutionDetailPage-H6R3ZIXN.js +22 -0
  2. package/dist/{WorkflowsList-VA2HRYFG.js → ExecutionsListPage-TLD2E442.js} +9 -9
  3. package/dist/{WorkflowBuilder-PORH3H6P.js → WorkflowBuilder-XVHY7XWJ.js} +12 -12
  4. package/dist/{ExecutionsListPage-SCPWUXUL.js → WorkflowsList-3GXXXURR.js} +9 -8
  5. package/dist/{chunk-MNY2UNQH.js → chunk-2NG5VZWI.js} +1 -1
  6. package/dist/{chunk-CY6KX4RG.js → chunk-6AMNEX2Y.js} +6 -1
  7. package/dist/{chunk-LPGQTSI2.js → chunk-6CKDGT2N.js} +3 -3
  8. package/dist/{chunk-U6KI4AKI.js → chunk-6TMLIQHB.js} +199 -1
  9. package/dist/{chunk-LTVAUZ3M.js → chunk-G3KZOX52.js} +3 -3
  10. package/dist/{chunk-7PP6CRXS.js → chunk-GQTEHWWY.js} +26 -8
  11. package/dist/{chunk-LB62TAF7.js → chunk-H4AZBUWM.js} +1 -1
  12. package/dist/{chunk-57RS7DXZ.js → chunk-HWEKWYXQ.js} +117 -8
  13. package/dist/{chunk-7B2GRTUL.js → chunk-LJGC7RAL.js} +164 -101
  14. package/dist/{chunk-4TGUNOQQ.js → chunk-NAYPOEYM.js} +391 -112
  15. package/dist/{chunk-BWAUFSHC.js → chunk-NNPXN4DI.js} +5 -5
  16. package/dist/{chunk-QZM6EDGK.js → chunk-NUBPALBK.js} +1 -1
  17. package/dist/{chunk-YSUSSIWU.js → chunk-R7VHURPQ.js} +1 -1
  18. package/dist/{chunk-7SSZUX6E.js → chunk-S4JYBJ4X.js} +2 -2
  19. package/dist/{chunk-WTGBHE3J.js → chunk-T22LICGX.js} +118 -96
  20. package/dist/{chunk-63ZMEHZ4.js → chunk-TJ6RUV4R.js} +937 -26
  21. package/dist/{chunk-H364USFF.js → chunk-UIDJT3YK.js} +1 -1
  22. package/dist/{chunk-4XAPSIHY.js → chunk-UWNCFMHV.js} +183 -26
  23. package/dist/{chunk-MDPBO52Q.js → chunk-V5PA5KGV.js} +2 -2
  24. package/dist/{chunk-MQI7NH4Z.js → chunk-VEZ7G2FE.js} +145 -8
  25. package/dist/{chunk-YHTRCQ5M.js → chunk-WEXO3KAY.js} +4 -4
  26. package/dist/{chunk-7LFAWWVF.js → chunk-WTP343PG.js} +2 -2
  27. package/dist/{chunk-Z6EWB5ZG.js → chunk-X2J4ZXKD.js} +1492 -98
  28. package/dist/{chunk-6XZCYLAQ.js → chunk-XJBCRDWZ.js} +3 -3
  29. package/dist/{chunk-AVMI3XLM.js → chunk-XMKYZP6Q.js} +282 -18
  30. package/dist/{chunk-5GD6OXYW.js → chunk-Z6I6A47T.js} +1 -1
  31. package/dist/{config-T7BQ7AO2.js → config-VM7FTHMZ.js} +1 -1
  32. package/dist/executions.js +13 -13
  33. package/dist/history.js +5 -5
  34. package/dist/index.d.ts +2588 -26
  35. package/dist/index.js +36 -36
  36. package/dist/nodes.js +2 -2
  37. package/dist/observability.js +7 -7
  38. package/dist/settings.js +7 -7
  39. package/dist/templates.js +11 -11
  40. package/dist/{workflow-builder-CZFczcE7.d.ts → workflow-builder-BMGVSwpy.d.ts} +7 -0
  41. package/dist/workflow-builder.d.ts +1 -1
  42. package/dist/workflow-builder.js +13 -13
  43. package/dist/workflows-list.js +9 -9
  44. package/package.json +1 -1
  45. package/dist/ExecutionDetailPage-M6JAWY4Q.js +0 -21
@@ -2,18 +2,18 @@ import { deepClone, applyTemplateInputs } from './chunk-GEMNVFPK.js';
2
2
  import { Input } 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, useWorkflowCodeExport, orderedExportFiles, fileBaseName, downloadWorkflowCodeExport, isLosslessReport, sortFidelityItems, isLossyFidelityItem, lineOfNode } from './chunk-7SSZUX6E.js';
6
- import { EntityTagPicker, useWorkflowProposal, WorkflowProposalCard, useRunHistory, RunHistoryView, SignalRunDialog } from './chunk-WTGBHE3J.js';
5
+ import { QuotaExhaustedDialog, useWorkflowCodeExport, orderedExportFiles, fileBaseName, downloadWorkflowCodeExport, isLosslessReport, sortFidelityItems, isLossyFidelityItem, lineOfNode } from './chunk-S4JYBJ4X.js';
6
+ import { EntityTagPicker, useWorkflowProposal, WorkflowProposalCard, useRunHistory, RunHistoryView, SignalRunDialog } from './chunk-T22LICGX.js';
7
7
  import { AiCreditBalanceSlot } from './chunk-Y53CVZVV.js';
8
- import { useRestoreWorkflowVersion, useWorkflowVersions, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-LTVAUZ3M.js';
9
- import { isAgentNodeType, AgentDefinitionSelectField } from './chunk-QZM6EDGK.js';
8
+ import { useRestoreWorkflowVersion, useWorkflowVersions, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-G3KZOX52.js';
9
+ import { isAgentNodeType, AgentDefinitionSelectField } from './chunk-NUBPALBK.js';
10
10
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
11
- import { NODE_CONFIGS, createConfiguredNode, resolveOutputHandles, resolveInputHandles, isNodeConfigsInitialized, useFlowStore, getNodeDetail, useSaveStatusStore, isNodeTypeHiddenFromPalette, liveInternalOperations, resolveNodeOperation, readDisplayOptions, parseParameterValue, canAutosave, STICKY_NOTE_TYPE, isNodeTrigger, resolveUiSpec, RUNS_QUOTA_NAME, throwIfResultError, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, useDurableFunctionNodesEnabled, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, CODE_WORKFLOW_NODE_TYPE, SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, fieldsFromValue, filterPaletteNodes, useFluidGridsAnalytics, useSaveWorkflow, workflowFingerprint, buildStickyNode, useNodeRegistryRevision, fieldsFromIO, useFluidGridsPermission, readCodeWorkflowState, useCodeWorkflowInterface, clearCodeWorkflowDefinitionCache, isParamVisible, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, useConnections, ConnectionFormDialog, useFluidGridsFlag, FLUIDGRIDS_FLAGS, saveIndicatorOf, inAppLeaveTarget, declaredOutputFields, subscribeWorkflowSaved, pathToExpression, diagnosticsByPath, interfaceHandlers, declaredSignals, ExpressionEditor, toCodeMirrorDiagnostics, formatParameterValue, leaveNeedsConfirmation, buildOperationSeed, snapshotForSave, editStateOf, childRunIdOf, defaultSourcePath, describeOperationError, projectLanguage, changedSourceFiles, isEditableSourcePath, sourceLanguageOf, interfaceFields, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, openRunQuotaUpgradePrompt, isTriggerOperation, canRenderOperationPanel, runPreparationOf, resolveOperationName, savesInPlace, AUTOSAVE_RETRY_DELAYS_MS, AUTOSAVE_DEBOUNCE_MS } from './chunk-4TGUNOQQ.js';
11
+ import { leaseCallQueue, NODE_CONFIGS, createConfiguredNode, resolveOutputHandles, resolveInputHandles, isNodeConfigsInitialized, useFlowStore, getNodeDetail, useSaveStatusStore, isNodeTypeHiddenFromPalette, liveInternalOperations, resolveNodeOperation, readDisplayOptions, parseParameterValue, canAutosave, STICKY_NOTE_TYPE, isNodeTrigger, resolveUiSpec, RUNS_QUOTA_NAME, throwIfResultError, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, useEditLeaseStore, useEditLeaseLocked, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, useDurableFunctionNodesEnabled, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, BuilderReadOnlyProvider, refetchStoreNodes, AVAILABLE_NODES, EDIT_SESSION_ID, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, CODE_WORKFLOW_NODE_TYPE, SdkPermissionButton, bannerOf, useSaveWorkflow, useBuilderReadOnly, fieldsFromValue, filterPaletteNodes, useFluidGridsAnalytics, sessionBeforeReload, forgetSessionForReload, releasesOnClose, editStateOf, workflowFingerprint, isLocked, buildStickyNode, useNodeRegistryRevision, fieldsFromIO, readCodeWorkflowState, useCodeWorkflowInterface, clearCodeWorkflowDefinitionCache, isParamVisible, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, shouldReleaseForIdle, tickAction, rememberSessionForReload, useDurableParityEnabled, useConnections, ConnectionFormDialog, useFluidGridsFlag, FLUIDGRIDS_FLAGS, saveIndicatorOf, inAppLeaveTarget, declaredOutputFields, subscribeWorkflowSaved, pathToExpression, diagnosticsByPath, interfaceHandlers, declaredSignals, ExpressionEditor, toCodeMirrorDiagnostics, formatParameterValue, leaveNeedsConfirmation, phaseAfterAcquire, buildOperationSeed, describeOperationError, rememberOwnStatusChange, snapshotForSave, childRunIdOf, defaultSourcePath, projectLanguage, changedSourceFiles, isEditableSourcePath, sourceLanguageOf, interfaceFields, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, loadDefinitionJson, openRunQuotaUpgradePrompt, isTriggerOperation, canRenderOperationPanel, runPreparationOf, resolveOperationName, savesInPlace, AUTOSAVE_RETRY_DELAYS_MS, AUTOSAVE_DEBOUNCE_MS, rememberRevision, phaseAfterTakeOver } from './chunk-NAYPOEYM.js';
12
12
  import { cn } from './chunk-JL7CASN5.js';
13
13
  import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
14
- import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
14
+ import { getFluidGridsConfig } from './chunk-6AMNEX2Y.js';
15
15
  import { GetMyNodeInstallationsDocument, BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, InstallPurchasedNodeDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument, GetNodeInstallableVersionsDocument, OrganizationPrivateNodesDocument, InstallOrganizationPrivateNodeDocument, UninstallOrganizationPrivateNodeDocument } from './chunk-JUUYPCFY.js';
16
- import { WorkspaceRunsQuotaDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, WorkflowGenerationTemplatesDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, WorkflowGitRepositoryDocument, WorkflowGitPushesDocument, WorkflowGitCommitsDocument, LinkWorkflowGitRepositoryDocument, UnlinkWorkflowGitRepositoryDocument, PushWorkflowToGitDocument, WorkflowGitDiffDocument, RestoreWorkflowFromGitDocument, WorkflowAuthoringDocument, NodeStatesDocument, PreviewScheduleDocument, WorkflowCodeSourceDocument, WorkflowEditLeaseStatusDocument, BuildWorkflowCodeDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-7PP6CRXS.js';
16
+ import { WorkspaceRunsQuotaDocument, ReleaseWorkflowEditLeaseDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, AcquireWorkflowEditLeaseDocument, WorkflowGenerationTemplatesDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, WorkflowGitRepositoryDocument, WorkflowGitPushesDocument, WorkflowGitCommitsDocument, LinkWorkflowGitRepositoryDocument, UnlinkWorkflowGitRepositoryDocument, PushWorkflowToGitDocument, WorkflowGitDiffDocument, RestoreWorkflowFromGitDocument, WorkflowAuthoringDocument, NodeStatesDocument, PreviewScheduleDocument, WorkflowEditLeaseDocument, WorkflowVersioningDocument, WorkflowVersionDrainageDocument, ActivateWorkflowVersionCheckedDocument, WorkflowDefinitionDocument, WorkflowCodeSourceDocument, WorkflowEditLeaseStatusDocument, BuildWorkflowCodeDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument, SetWorkflowRolloutDocument, DeleteWorkflowRolloutDocument, TakeOverWorkflowEditLeaseDocument } from './chunk-GQTEHWWY.js';
17
17
  import { useTr } from './chunk-UULVIT25.js';
18
18
  import { getFluidGridsClient, waitForFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
19
19
  import { __export } from './chunk-62GWEYJ5.js';
@@ -29,7 +29,8 @@ import { useEventBus } from '@burdenoff/fe-libs/shared/events';
29
29
  import { useQuery, useMutation } from '@apollo/client/react';
30
30
  import { gql as gql$1 } from '@apollo/client/core';
31
31
  import 'jszip';
32
- import { Sparkles, Star, Download, Eye, Maximize2, Undo2, Redo2, LayoutGrid, Keyboard, Plus, Trash2, GitBranch, Loader2, AlertCircle, Search, RefreshCw, History, GitCompareArrows, Play, RotateCcw, Layers, Upload, Rocket, ArrowRight, Check, X, Link2, Unlink, GitCompare, ExternalLink, GitCommitHorizontal, CircleHelp, CheckCircle2, Package, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, AlertTriangle } from 'lucide-react';
32
+ import { Sparkles, Star, Download, Eye, Maximize2, Undo2, Redo2, LayoutGrid, Keyboard, Plus, Trash2, GitBranch, Loader2, AlertCircle, Search, RefreshCw, History, GitCompareArrows, Play, RotateCcw, Layers, Upload, Rocket, ArrowRight, Check, X, Link2, Unlink, GitCompare, ExternalLink, GitCommitHorizontal, CircleHelp, CheckCircle2, Package, SlidersHorizontal, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, AlertTriangle } from 'lucide-react';
33
+ import { create } from 'zustand';
33
34
  import { Icon } from '@iconify/react';
34
35
  import { toast } from 'sonner';
35
36
  import { useShallow, shallow } from 'zustand/shallow';
@@ -43,7 +44,6 @@ import { ThemeProvider as ThemeProvider$1 } from '@burdenoff/fe-libs/shared/prov
43
44
  import '@xyflow/react/dist/style.css';
44
45
  import { edgeColor, nodeCategoryColor } from '@burdenoff/fe-libs/shared/utils';
45
46
  import { buildSchema, GraphQLObjectType, GraphQLInputObjectType, GraphQLEnumType, GraphQLInterfaceType, GraphQLUnionType, GraphQLNonNull, GraphQLList } from 'graphql';
46
- import { create } from 'zustand';
47
47
  import { nanoid as nanoid$1 } from 'nanoid/non-secure';
48
48
  import { defineAgentTool, AgentToolError, useAgentTools } from '@burdenoff/fe-libs/shared/agent-tools';
49
49
  import CodeMirror, { EditorView } from '@uiw/react-codemirror';
@@ -21676,6 +21676,266 @@ function FluidGridsGlyph() {
21676
21676
  }
21677
21677
  );
21678
21678
  }
21679
+ var useWorkflowReloadStore = create()((set2) => ({
21680
+ nonce: 0,
21681
+ request: () => set2((state) => ({ nonce: state.nonce + 1 }))
21682
+ }));
21683
+ function requestWorkflowReload() {
21684
+ useWorkflowReloadStore.getState().request();
21685
+ }
21686
+ var TICK_MS = 5e3;
21687
+ var UNAVAILABLE = {
21688
+ available: false,
21689
+ held: false,
21690
+ heldBySelf: false
21691
+ };
21692
+ var leaseCalls = leaseCallQueue();
21693
+ function sessionStore() {
21694
+ try {
21695
+ return typeof window === "undefined" ? void 0 : window.sessionStorage;
21696
+ } catch {
21697
+ return void 0;
21698
+ }
21699
+ }
21700
+ function openWorkflow() {
21701
+ const { workflow } = useFlowStore.getState();
21702
+ const workflowKey = workflow.workflowKey || workflow.id;
21703
+ if (!workflow.dbId || !workflowKey) return null;
21704
+ return { workflowKey, version: workflow.version ?? "" };
21705
+ }
21706
+ function snapshotOpen() {
21707
+ const open2 = openWorkflow();
21708
+ if (!open2) return null;
21709
+ return { ...open2, generation: useFlowStore.getState().loadGeneration };
21710
+ }
21711
+ function stillOpen(snapshot) {
21712
+ const now = openWorkflow();
21713
+ return now?.workflowKey === snapshot.workflowKey && now.version === snapshot.version && useFlowStore.getState().loadGeneration === snapshot.generation && useEditLeaseStore.getState().workflowKey === snapshot.workflowKey;
21714
+ }
21715
+ function discardStaleAnswer(snapshot, answer) {
21716
+ if (!answer.heldBySelf) return;
21717
+ if (useEditLeaseStore.getState().workflowKey === snapshot.workflowKey) return;
21718
+ void releaseLease(snapshot.workflowKey);
21719
+ }
21720
+ function acquireLease(workflowKey, version2, resumeSessionId) {
21721
+ return leaseCalls.run(workflowKey, async () => {
21722
+ try {
21723
+ const { data } = throwIfResultError(
21724
+ await getFluidGridsClient().mutate({
21725
+ mutation: AcquireWorkflowEditLeaseDocument,
21726
+ variables: {
21727
+ input: {
21728
+ workflowKey,
21729
+ sessionId: EDIT_SESSION_ID,
21730
+ version: version2 || null,
21731
+ resumeSessionId
21732
+ }
21733
+ }
21734
+ })
21735
+ );
21736
+ return data?.acquireWorkflowEditLease ?? UNAVAILABLE;
21737
+ } catch {
21738
+ return UNAVAILABLE;
21739
+ }
21740
+ });
21741
+ }
21742
+ async function peekLease(workflowKey) {
21743
+ try {
21744
+ const { data } = throwIfResultError(
21745
+ await getFluidGridsClient().query({
21746
+ query: WorkflowEditLeaseDocument,
21747
+ variables: { workflowKey, sessionId: EDIT_SESSION_ID },
21748
+ fetchPolicy: "network-only"
21749
+ })
21750
+ );
21751
+ return data?.workflowEditLease ?? UNAVAILABLE;
21752
+ } catch {
21753
+ return UNAVAILABLE;
21754
+ }
21755
+ }
21756
+ function sendRelease(workflowKey, keepalive) {
21757
+ return getFluidGridsClient().mutate({
21758
+ mutation: ReleaseWorkflowEditLeaseDocument,
21759
+ variables: { workflowKey, sessionId: EDIT_SESSION_ID },
21760
+ ...keepalive ? { context: { fetchOptions: { keepalive: true } } } : {}
21761
+ }).then(() => void 0).catch(() => void 0);
21762
+ }
21763
+ function releaseLease(workflowKey) {
21764
+ return leaseCalls.run(workflowKey, () => sendRelease(workflowKey, false));
21765
+ }
21766
+ function recordAcquired(answer) {
21767
+ const lease = useEditLeaseStore.getState();
21768
+ lease.actions.record(phaseAfterAcquire(lease.phase, answer), answer);
21769
+ }
21770
+ async function takeOverEditLease() {
21771
+ const open2 = snapshotOpen();
21772
+ if (!open2) return;
21773
+ const answer = await leaseCalls.run(open2.workflowKey, async () => {
21774
+ try {
21775
+ const { data } = throwIfResultError(
21776
+ await getFluidGridsClient().mutate({
21777
+ mutation: TakeOverWorkflowEditLeaseDocument,
21778
+ variables: {
21779
+ input: {
21780
+ workflowKey: open2.workflowKey,
21781
+ sessionId: EDIT_SESSION_ID,
21782
+ version: open2.version || null
21783
+ }
21784
+ }
21785
+ })
21786
+ );
21787
+ return data?.takeOverWorkflowEditLease ?? UNAVAILABLE;
21788
+ } catch {
21789
+ return UNAVAILABLE;
21790
+ }
21791
+ });
21792
+ if (!stillOpen(open2)) {
21793
+ discardStaleAnswer(open2, answer);
21794
+ return;
21795
+ }
21796
+ const next = phaseAfterTakeOver(answer);
21797
+ useEditLeaseStore.getState().actions.record(next, answer);
21798
+ if (next === "reloading") requestWorkflowReload();
21799
+ }
21800
+ async function resumeEditLease() {
21801
+ const open2 = snapshotOpen();
21802
+ if (!open2) return;
21803
+ const answer = await acquireLease(open2.workflowKey, open2.version, null);
21804
+ if (!stillOpen(open2)) {
21805
+ discardStaleAnswer(open2, answer);
21806
+ return;
21807
+ }
21808
+ recordAcquired(answer);
21809
+ }
21810
+ function releaseLeaseKeptForReload() {
21811
+ const lease = useEditLeaseStore.getState();
21812
+ const workflowKey = lease.workflowKey;
21813
+ if (lease.phase !== "reloading" || !workflowKey) return;
21814
+ void releaseLease(workflowKey);
21815
+ lease.actions.reset();
21816
+ }
21817
+ function reloadLatestVersion() {
21818
+ const lease = useEditLeaseStore.getState();
21819
+ if (lease.phase === "holding") lease.actions.record("reloading");
21820
+ requestWorkflowReload();
21821
+ }
21822
+ function useEditLease(enabled) {
21823
+ const workflowKey = useFlowStore(
21824
+ (s) => s.workflow.dbId ? s.workflow.workflowKey || s.workflow.id : ""
21825
+ );
21826
+ useEffect(() => {
21827
+ const lease = useEditLeaseStore.getState();
21828
+ if (!enabled || !workflowKey) {
21829
+ lease.actions.reset();
21830
+ return;
21831
+ }
21832
+ lease.actions.begin(workflowKey);
21833
+ let disposed = false;
21834
+ let inFlight = false;
21835
+ let lastRenewAt = 0;
21836
+ let lastPeekAt = 0;
21837
+ let lastInputAt = Date.now();
21838
+ const visible = () => typeof document === "undefined" || document.visibilityState !== "hidden";
21839
+ const phase = () => useEditLeaseStore.getState().phase;
21840
+ const version2 = () => useFlowStore.getState().workflow.version ?? "";
21841
+ const renew = async (resumeSessionId = null) => {
21842
+ if (inFlight) return;
21843
+ inFlight = true;
21844
+ lastRenewAt = Date.now();
21845
+ try {
21846
+ const answer = await acquireLease(
21847
+ workflowKey,
21848
+ version2(),
21849
+ resumeSessionId
21850
+ );
21851
+ if (!disposed && phase() !== "idle") recordAcquired(answer);
21852
+ } finally {
21853
+ inFlight = false;
21854
+ }
21855
+ };
21856
+ const peek = async () => {
21857
+ if (inFlight) return;
21858
+ inFlight = true;
21859
+ lastPeekAt = Date.now();
21860
+ try {
21861
+ const answer = await peekLease(workflowKey);
21862
+ if (disposed || !answer.available) return;
21863
+ const current = useEditLeaseStore.getState();
21864
+ current.actions.record(current.phase, answer);
21865
+ } finally {
21866
+ inFlight = false;
21867
+ }
21868
+ };
21869
+ const tick = () => {
21870
+ if (disposed) return;
21871
+ const now = Date.now();
21872
+ if (shouldReleaseForIdle(phase(), lastInputAt, now)) {
21873
+ useEditLeaseStore.getState().actions.record("idle");
21874
+ void releaseLease(workflowKey);
21875
+ return;
21876
+ }
21877
+ const action = tickAction(
21878
+ phase(),
21879
+ { lastRenewAt, lastPeekAt, visible: visible() },
21880
+ now
21881
+ );
21882
+ if (action === "renew") void renew();
21883
+ else if (action === "peek") void peek();
21884
+ };
21885
+ const resume = sessionBeforeReload(sessionStore(), workflowKey);
21886
+ forgetSessionForReload(sessionStore(), workflowKey);
21887
+ void renew(resume);
21888
+ const timer = setInterval(tick, TICK_MS);
21889
+ let reloadSeen = false;
21890
+ const unsubscribeFlow = useFlowStore.subscribe((state, previous) => {
21891
+ if (state.loadGeneration !== previous.loadGeneration) reloadSeen = true;
21892
+ if (reloadSeen && state.workflow.dbId && phase() === "reloading") {
21893
+ reloadSeen = false;
21894
+ void renew();
21895
+ }
21896
+ });
21897
+ const onVisible = () => {
21898
+ if (!visible()) return;
21899
+ lastRenewAt = 0;
21900
+ lastPeekAt = 0;
21901
+ tick();
21902
+ };
21903
+ const onInput = () => {
21904
+ lastInputAt = Date.now();
21905
+ };
21906
+ const onPageHide = () => {
21907
+ if (phase() !== "holding") return;
21908
+ rememberSessionForReload(sessionStore(), workflowKey, EDIT_SESSION_ID);
21909
+ void sendRelease(workflowKey, true);
21910
+ };
21911
+ const onPageShow = (event) => {
21912
+ if (event.persisted) void renew(EDIT_SESSION_ID);
21913
+ };
21914
+ const inputEvents = ["pointerdown", "keydown", "wheel"];
21915
+ document.addEventListener("visibilitychange", onVisible);
21916
+ window.addEventListener("online", onVisible);
21917
+ window.addEventListener("pagehide", onPageHide);
21918
+ window.addEventListener("pageshow", onPageShow);
21919
+ for (const name of inputEvents) {
21920
+ window.addEventListener(name, onInput, { capture: true, passive: true });
21921
+ }
21922
+ return () => {
21923
+ disposed = true;
21924
+ clearInterval(timer);
21925
+ unsubscribeFlow();
21926
+ document.removeEventListener("visibilitychange", onVisible);
21927
+ window.removeEventListener("online", onVisible);
21928
+ window.removeEventListener("pagehide", onPageHide);
21929
+ window.removeEventListener("pageshow", onPageShow);
21930
+ for (const name of inputEvents) {
21931
+ window.removeEventListener(name, onInput, { capture: true });
21932
+ }
21933
+ const current = useEditLeaseStore.getState();
21934
+ if (releasesOnClose(current.phase)) void releaseLease(workflowKey);
21935
+ if (current.phase !== "reloading") current.actions.reset();
21936
+ };
21937
+ }, [enabled, workflowKey]);
21938
+ }
21679
21939
  function findEdges(node2, connectedEdges) {
21680
21940
  const outgoingEdges = connectedEdges.filter(
21681
21941
  (edge) => edge.source === node2.id
@@ -21752,6 +22012,9 @@ function useFlowValidator(onValidate) {
21752
22012
  }
21753
22013
 
21754
22014
  // src/builder/lib/save-guards.ts
22015
+ function editingLeaseLocked() {
22016
+ return isLocked(useEditLeaseStore.getState().phase);
22017
+ }
21755
22018
  function autosaveAllowedFor(workflow) {
21756
22019
  return useSaveStatusStore.getState().autosaveEnabled && canAutosave(workflow);
21757
22020
  }
@@ -21768,7 +22031,7 @@ function editStateFor(state) {
21768
22031
  }
21769
22032
  function departingSave(state) {
21770
22033
  const status = useSaveStatusStore.getState();
21771
- if (status.autosavePaused) return null;
22034
+ if (status.autosavePaused || editingLeaseLocked()) return null;
21772
22035
  if (editStateFor(state) !== "dirty") return null;
21773
22036
  if (!autosaveAllowedFor(state.workflow)) return null;
21774
22037
  const { nodes, edges } = state.workflow;
@@ -21784,7 +22047,9 @@ function unsavedState() {
21784
22047
  return {
21785
22048
  // Checked fresh: the dirty flag trails the last edit by a moment.
21786
22049
  dirty: status.dirty || editStateFor(flow) === "dirty",
21787
- autosaveAllowed: autosaveAllowedFor(flow.workflow),
22050
+ // A paused autosave (a refused save), or a page someone else is editing
22051
+ // from, does not save on the way out either.
22052
+ autosaveAllowed: autosaveAllowedFor(flow.workflow) && !status.autosavePaused && !editingLeaseLocked(),
21788
22053
  incomplete: status.incomplete || !isFlowComplete(flow.workflow.nodes, flow.workflow.edges),
21789
22054
  failed: status.failure !== null
21790
22055
  };
@@ -21814,6 +22079,10 @@ function watchLeavingLosesChanges(onChange, delayMs) {
21814
22079
  if (timer) clearTimeout(timer);
21815
22080
  timer = setTimeout(settle2, delayMs);
21816
22081
  };
22082
+ const settleNow = () => {
22083
+ if (timer) clearTimeout(timer);
22084
+ settle2();
22085
+ };
21817
22086
  settle2();
21818
22087
  const unsubscribeFlow = useFlowStore.subscribe((state, previous) => {
21819
22088
  if (!touchesSavedFields(state, previous)) return;
@@ -21821,9 +22090,13 @@ function watchLeavingLosesChanges(onChange, delayMs) {
21821
22090
  settleLater();
21822
22091
  });
21823
22092
  const unsubscribeStatus = useSaveStatusStore.subscribe(settleLater);
22093
+ const unsubscribeLease = useEditLeaseStore.subscribe((state, previous) => {
22094
+ if (isLocked(state.phase) !== isLocked(previous.phase)) settleNow();
22095
+ });
21824
22096
  return () => {
21825
22097
  unsubscribeFlow();
21826
22098
  unsubscribeStatus();
22099
+ unsubscribeLease();
21827
22100
  if (timer) clearTimeout(timer);
21828
22101
  };
21829
22102
  }
@@ -25200,7 +25473,7 @@ function DesktopSidebarFragment({
25200
25473
  if (nodesToInstall.length > 0) {
25201
25474
  const { getGlobalStoreClient: getStore } = await import('./global-client-KLOVTVPP.js');
25202
25475
  const { InstallWorkflowNodeDocument: InstallWorkflowNodeDocument2 } = await import('./global-operations-D2USVOPM.js');
25203
- const { getFluidGridsConfig: getFluidGridsConfig3 } = await import('./config-T7BQ7AO2.js');
25476
+ const { getFluidGridsConfig: getFluidGridsConfig3 } = await import('./config-VM7FTHMZ.js');
25204
25477
  const storeClient = getStore();
25205
25478
  const cfg = getFluidGridsConfig3();
25206
25479
  for (let i = 0; i < nodesToInstall.length; i++) {
@@ -25261,7 +25534,7 @@ function DesktopSidebarFragment({
25261
25534
  );
25262
25535
  }
25263
25536
  const { getFluidGridsClient: getFluidGridsClient2 } = await import('./client-T7BHWM2H.js');
25264
- const { getFluidGridsConfig: getFluidGridsConfig2 } = await import('./config-T7BQ7AO2.js');
25537
+ const { getFluidGridsConfig: getFluidGridsConfig2 } = await import('./config-VM7FTHMZ.js');
25265
25538
  const { gql: gql10 } = await import('@apollo/client/core');
25266
25539
  const IMPORT_MUTATION = gql10`
25267
25540
  mutation ImportWorkflowTemplate(
@@ -26743,8 +27016,8 @@ var reducer = (state, action) => {
26743
27016
  if (toastId) {
26744
27017
  addToRemoveQueue(toastId);
26745
27018
  } else {
26746
- state.toasts.forEach((toast17) => {
26747
- addToRemoveQueue(toast17.id);
27019
+ state.toasts.forEach((toast18) => {
27020
+ addToRemoveQueue(toast18.id);
26748
27021
  });
26749
27022
  }
26750
27023
  return {
@@ -28120,7 +28393,7 @@ function VisualMapperContent({
28120
28393
  mapping,
28121
28394
  onMappingChange
28122
28395
  }) {
28123
- const { toast: toast17 } = useToast();
28396
+ const { toast: toast18 } = useToast();
28124
28397
  const tr = useTr();
28125
28398
  const reactFlowWrapper = useRef(null);
28126
28399
  const [selectedSourceNode, setSelectedSourceNode] = useState(null);
@@ -28140,7 +28413,7 @@ function VisualMapperContent({
28140
28413
  targetOperation
28141
28414
  );
28142
28415
  if (generatedNodes.length === 0) {
28143
- toast17({
28416
+ toast18({
28144
28417
  title: tr(
28145
28418
  "fluidgrids.builder.visualMapper.noFieldsFoundTitle",
28146
28419
  "No fields found"
@@ -28166,7 +28439,7 @@ function VisualMapperContent({
28166
28439
  targetNode,
28167
28440
  targetOperation,
28168
28441
  setNodes,
28169
- toast17,
28442
+ toast18,
28170
28443
  tr
28171
28444
  ]);
28172
28445
  useEffect(() => {
@@ -28273,7 +28546,7 @@ function VisualMapperContent({
28273
28546
  }
28274
28547
  onMappingChange(updatedMapping);
28275
28548
  if (isCompatible) {
28276
- toast17({
28549
+ toast18({
28277
28550
  title: tr(
28278
28551
  "fluidgrids.builder.visualMapper.mappingCreatedTitle",
28279
28552
  "Mapping created"
@@ -28281,7 +28554,7 @@ function VisualMapperContent({
28281
28554
  description: `Mapped ${sourcePath} (${sourceType}) to ${targetPath} (${targetType})`
28282
28555
  });
28283
28556
  } else {
28284
- toast17({
28557
+ toast18({
28285
28558
  title: tr(
28286
28559
  "fluidgrids.builder.visualMapper.typeMismatchTitle",
28287
28560
  "Type mismatch warning"
@@ -28291,7 +28564,7 @@ function VisualMapperContent({
28291
28564
  });
28292
28565
  }
28293
28566
  } else {
28294
- toast17({
28567
+ toast18({
28295
28568
  title: tr(
28296
28569
  "fluidgrids.builder.visualMapper.invalidMappingTitle",
28297
28570
  "Invalid mapping"
@@ -28304,7 +28577,7 @@ function VisualMapperContent({
28304
28577
  });
28305
28578
  }
28306
28579
  },
28307
- [nodes, mapping, onMappingChange, toast17, tr, setEdges]
28580
+ [nodes, mapping, onMappingChange, toast18, tr, setEdges]
28308
28581
  );
28309
28582
  const onEdgeDelete = useCallback(
28310
28583
  (edge) => {
@@ -28361,7 +28634,7 @@ function VisualMapperContent({
28361
28634
  const handleSaveMappingDialog = (expression, targetFieldName) => {
28362
28635
  const targetPath = targetFieldName || selectedTargetNode?.data?.path;
28363
28636
  if (!targetPath) {
28364
- toast17({
28637
+ toast18({
28365
28638
  title: tr(
28366
28639
  "fluidgrids.builder.visualMapper.noTargetFieldTitle",
28367
28640
  "No target field specified"
@@ -28393,7 +28666,7 @@ function VisualMapperContent({
28393
28666
  }
28394
28667
  onMappingChange(updatedMapping);
28395
28668
  const mappingType = detectMappingType(expression);
28396
- toast17({
28669
+ toast18({
28397
28670
  title: `${mappingType.charAt(0).toUpperCase() + mappingType.slice(1)} mapping created`,
28398
28671
  description: `${tr("fluidgrids.builder.visualMapper.appliedMappingTo", "Applied mapping to")} ${targetPath}`
28399
28672
  });
@@ -28692,7 +28965,7 @@ function MappingEditor({
28692
28965
  setEdges,
28693
28966
  edgeId
28694
28967
  }) {
28695
- const { toast: toast17 } = useToast();
28968
+ const { toast: toast18 } = useToast();
28696
28969
  const tr = useTr();
28697
28970
  const [mappingJson, setMappingJson] = useState({});
28698
28971
  const [jsonDataString, setJsonDataString] = useState(
@@ -28816,7 +29089,7 @@ function MappingEditor({
28816
29089
  const handleSaveMapping = () => {
28817
29090
  try {
28818
29091
  checkJsonValidity();
28819
- toast17({
29092
+ toast18({
28820
29093
  title: tr(
28821
29094
  "fluidgrids.builder.mappingEditor.mappingSavedTitle",
28822
29095
  "Mapping saved"
@@ -28827,7 +29100,7 @@ function MappingEditor({
28827
29100
  )
28828
29101
  });
28829
29102
  } catch (error62) {
28830
- toast17({
29103
+ toast18({
28831
29104
  title: tr(
28832
29105
  "fluidgrids.builder.mappingEditor.errorSavingTitle",
28833
29106
  "Error saving mapping"
@@ -28846,7 +29119,7 @@ function MappingEditor({
28846
29119
  const parsedData = checkJsonValidity();
28847
29120
  const result = await applyMapping(parsedData, sampleInput);
28848
29121
  setPreviewOutput(result);
28849
- toast17({
29122
+ toast18({
28850
29123
  title: tr(
28851
29124
  "fluidgrids.builder.mappingEditor.mappingTestedTitle",
28852
29125
  "Mapping tested"
@@ -28858,7 +29131,7 @@ function MappingEditor({
28858
29131
  });
28859
29132
  } catch (error62) {
28860
29133
  console.error("Test mapping error:", error62);
28861
- toast17({
29134
+ toast18({
28862
29135
  title: tr(
28863
29136
  "fluidgrids.builder.mappingEditor.mappingTestFailedTitle",
28864
29137
  "Mapping test failed"
@@ -31501,6 +31774,7 @@ function CustomDeletableEdge(props) {
31501
31774
  markerEnd
31502
31775
  } = props;
31503
31776
  const { setEdges, getEdges, getNode } = useReactFlow();
31777
+ const readOnly = useBuilderReadOnly();
31504
31778
  const tr = useTr();
31505
31779
  const nodeStatuses = useFlowStore(
31506
31780
  useShallow((s) => s.execution.nodeStatuses)
@@ -31522,6 +31796,7 @@ function CustomDeletableEdge(props) {
31522
31796
  targetPosition
31523
31797
  });
31524
31798
  useEffect(() => {
31799
+ if (readOnly) return;
31525
31800
  const edges = getEdges();
31526
31801
  const currentEdge = edges?.find((e) => e.id === id);
31527
31802
  if (currentEdge && !currentEdge.conditions) {
@@ -31534,10 +31809,13 @@ function CustomDeletableEdge(props) {
31534
31809
  });
31535
31810
  });
31536
31811
  }
31537
- }, [id, setEdges, getEdges]);
31538
- const [showModal, setShowModal] = useState(false);
31539
- const [showConditionModal, setConditionShowModal] = useState(false);
31540
- const [isHovered, setIsHovered] = useState(false);
31812
+ }, [id, setEdges, getEdges, readOnly]);
31813
+ const [mapperRequested, setShowModal] = useState(false);
31814
+ const [conditionRequested, setConditionShowModal] = useState(false);
31815
+ const [hovered, setIsHovered] = useState(false);
31816
+ const showModal = mapperRequested && !readOnly;
31817
+ const showConditionModal = conditionRequested && !readOnly;
31818
+ const isHovered = hovered && !readOnly;
31541
31819
  const [sourceSchema, setSourceSchema] = useState(null);
31542
31820
  const [targetSchema, setTargetSchema] = useState(null);
31543
31821
  const [isLoadingSchemas, setIsLoadingSchemas] = useState(false);
@@ -33122,7 +33400,8 @@ function ControlBtn({
33122
33400
  function CanvasControls() {
33123
33401
  const tr = useTr();
33124
33402
  const searchParams = new URLSearchParams(window.location.search);
33125
- const isReadOnly = searchParams.get("readonly") === "true";
33403
+ const leaseLocked = useEditLeaseLocked();
33404
+ const isReadOnly = searchParams.get("readonly") === "true" || leaseLocked;
33126
33405
  const [showMinimap, setShowMinimap] = useState(false);
33127
33406
  const [edgesHidden, setEdgesHidden] = useState(false);
33128
33407
  const [shortcutsHelpOpen, setShortcutsHelpOpen] = useState(false);
@@ -34569,6 +34848,24 @@ function nodeSubtitle(node2) {
34569
34848
  const data = asRecord(node2?.data);
34570
34849
  return typeof data?.operation_name === "string" ? data.operation_name : void 0;
34571
34850
  }
34851
+ function leaveUnchanged() {
34852
+ }
34853
+ function readOnlyNodeConfig(api) {
34854
+ return {
34855
+ ...api,
34856
+ writeFixed: leaveUnchanged,
34857
+ writeMapping: leaveUnchanged,
34858
+ handleConnectionsChange: leaveUnchanged,
34859
+ toggleMode: leaveUnchanged,
34860
+ // A field dropped on a read-only form is refused, not opened as a link.
34861
+ handleDropOnParam: () => (e) => e.preventDefault(),
34862
+ writeErrorConfig: leaveUnchanged,
34863
+ writeNotes: leaveUnchanged,
34864
+ writeExecutionSettings: leaveUnchanged,
34865
+ writeNodeDataField: leaveUnchanged,
34866
+ readOnly: true
34867
+ };
34868
+ }
34572
34869
  function runOnOf(executionSettings) {
34573
34870
  const runOn = asRecord(executionSettings?.runOn);
34574
34871
  return runOn?.kind === "runner" && typeof runOn.runnerId === "string" ? { kind: "runner", runnerId: runOn.runnerId } : null;
@@ -34577,6 +34874,7 @@ function useNodeConfig(target, options) {
34577
34874
  const nodes = useFlowStore((s) => s.workflow.nodes);
34578
34875
  const edges = useFlowStore((s) => s.workflow.edges);
34579
34876
  const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
34877
+ const readOnly = useBuilderReadOnly();
34580
34878
  const executionLogs = useFlowStore((s) => s.execution.logs);
34581
34879
  const currentRunId = useFlowStore((s) => s.execution.currentRunId);
34582
34880
  const nodeId2 = target?.nodeId;
@@ -34841,7 +35139,7 @@ function useNodeConfig(target, options) {
34841
35139
  setModeByField((m) => ({ ...m, [field]: "expression" }));
34842
35140
  }
34843
35141
  };
34844
- return {
35142
+ const api = {
34845
35143
  node: node2,
34846
35144
  nodes,
34847
35145
  incoming,
@@ -34882,8 +35180,10 @@ function useNodeConfig(target, options) {
34882
35180
  executeOnce,
34883
35181
  runOn,
34884
35182
  writeExecutionSettings,
34885
- writeNodeDataField
35183
+ writeNodeDataField,
35184
+ readOnly: false
34886
35185
  };
35186
+ return readOnly ? readOnlyNodeConfig(api) : api;
34887
35187
  }
34888
35188
 
34889
35189
  // src/builder/agentTools.ts
@@ -35993,7 +36293,7 @@ function CodeEditorField({
35993
36293
  placeholder,
35994
36294
  label,
35995
36295
  onChange,
35996
- readOnly = false,
36296
+ readOnly: readOnlyProp = false,
35997
36297
  diagnostics,
35998
36298
  minHeight = "180px",
35999
36299
  maxHeight = "420px",
@@ -36002,6 +36302,8 @@ function CodeEditorField({
36002
36302
  }) {
36003
36303
  const dark = useIsDarkTheme();
36004
36304
  const withLint = diagnostics !== void 0;
36305
+ const builderReadOnly = useBuilderReadOnly();
36306
+ const readOnly = readOnlyProp || builderReadOnly;
36005
36307
  const extensions = useMemo(
36006
36308
  () => [
36007
36309
  languageExtension(language),
@@ -36298,11 +36600,6 @@ function useWebhookTestListener(target, onReceived) {
36298
36600
  }, [workflowKey, version2, triggerNodeId, stopOpenListener]);
36299
36601
  return { listener, busy, error: error62, listen, stop };
36300
36602
  }
36301
- var BuilderReadOnlyContext = createContext(false);
36302
- var BuilderReadOnlyProvider = BuilderReadOnlyContext.Provider;
36303
- function useBuilderReadOnly() {
36304
- return useContext(BuilderReadOnlyContext);
36305
- }
36306
36603
  var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
36307
36604
  function isSafeHttpUrl(value) {
36308
36605
  try {
@@ -39717,6 +40014,21 @@ function Field({
39717
40014
  children
39718
40015
  ] });
39719
40016
  }
40017
+ function ReadOnlyFieldset({
40018
+ className,
40019
+ children
40020
+ }) {
40021
+ const readOnly = useBuilderReadOnly();
40022
+ return /* @__PURE__ */ jsx(
40023
+ "fieldset",
40024
+ {
40025
+ disabled: readOnly,
40026
+ "data-readonly": readOnly ? "true" : void 0,
40027
+ className: cn$1("m-0 min-w-0 border-0 p-0", className),
40028
+ children
40029
+ }
40030
+ );
40031
+ }
39720
40032
  function useMediaQuery(query) {
39721
40033
  const [matches, setMatches] = React4.useState(false);
39722
40034
  React4.useEffect(() => {
@@ -40910,7 +41222,7 @@ function NodeDetailView() {
40910
41222
  const [pinNodeData, { loading: pinning }] = usePinNodeDataMutation();
40911
41223
  const [unpinNodeData, { loading: unpinning }] = useUnpinNodeDataMutation();
40912
41224
  const togglePin = async () => {
40913
- if (!workflowDbId || !ndv?.nodeId) return;
41225
+ if (!workflowDbId || !ndv?.nodeId || config2.readOnly) return;
40914
41226
  try {
40915
41227
  if (isPinned) {
40916
41228
  await unpinNodeData({
@@ -41282,21 +41594,21 @@ function NodeDetailView() {
41282
41594
  {
41283
41595
  className: "min-h-0 flex-1 space-y-4 overflow-auto p-4",
41284
41596
  "data-testid": "ndv-params",
41285
- children: /* @__PURE__ */ jsx(
41597
+ children: /* @__PURE__ */ jsx(ReadOnlyFieldset, { className: "space-y-4", children: /* @__PURE__ */ jsx(
41286
41598
  NodeParamsForm,
41287
41599
  {
41288
41600
  config: config2,
41289
41601
  nodeType: node2?.type,
41290
41602
  testIdPrefix: "ndv"
41291
41603
  }
41292
- )
41604
+ ) })
41293
41605
  }
41294
41606
  ) : /* @__PURE__ */ jsx(
41295
41607
  "div",
41296
41608
  {
41297
41609
  className: "min-h-0 flex-1 overflow-auto p-4",
41298
41610
  "data-testid": "ndv-settings",
41299
- children: /* @__PURE__ */ jsx(NodeSettingsPanel, { config: config2 })
41611
+ children: /* @__PURE__ */ jsx(ReadOnlyFieldset, { children: /* @__PURE__ */ jsx(NodeSettingsPanel, { config: config2 }) })
41300
41612
  }
41301
41613
  )
41302
41614
  ]
@@ -41340,7 +41652,7 @@ function NodeDetailView() {
41340
41652
  {
41341
41653
  type: "button",
41342
41654
  onClick: togglePin,
41343
- disabled: pinning || unpinning,
41655
+ disabled: pinning || unpinning || config2.readOnly,
41344
41656
  "data-testid": "ndv-pin-toggle",
41345
41657
  title: tr(
41346
41658
  isPinned ? "fluidgrids.builder.nodeDetailView.unpinHint" : "fluidgrids.builder.nodeDetailView.pinHint",
@@ -41983,14 +42295,14 @@ function QuickConfigPanelInner({
41983
42295
  setShowMapper(true);
41984
42296
  }
41985
42297
  },
41986
- children: /* @__PURE__ */ jsx(
42298
+ children: /* @__PURE__ */ jsx(ReadOnlyFieldset, { className: "space-y-4", children: /* @__PURE__ */ jsx(
41987
42299
  NodeParamsForm,
41988
42300
  {
41989
42301
  config: config2,
41990
42302
  nodeType: node2.type,
41991
42303
  testIdPrefix: "qc"
41992
42304
  }
41993
- )
42305
+ ) })
41994
42306
  }
41995
42307
  ),
41996
42308
  /* @__PURE__ */ jsx(
@@ -42115,10 +42427,11 @@ function useAutosave(readOnly) {
42115
42427
  });
42116
42428
  };
42117
42429
  const autosave = createAutosaveRunner({
42118
- mayStart: () => !status().autosavePaused && autosaveAllowedFor(useFlowStore.getState().workflow),
42430
+ mayStart: () => !status().autosavePaused && !editingLeaseLocked() && autosaveAllowedFor(useFlowStore.getState().workflow),
42119
42431
  gate: () => {
42120
42432
  const { workflow } = useFlowStore.getState();
42121
42433
  if (status().autosavePaused || !status().dirty) return "skip";
42434
+ if (editingLeaseLocked()) return "skip";
42122
42435
  if (!autosaveAllowedFor(workflow)) return "skip";
42123
42436
  const complete = isFlowComplete(workflow.nodes, workflow.edges);
42124
42437
  actions.setIncomplete(!complete);
@@ -42175,9 +42488,18 @@ function useAutosave(readOnly) {
42175
42488
  }
42176
42489
  }
42177
42490
  );
42491
+ const unsubscribeLease = useEditLeaseStore.subscribe((state, previous) => {
42492
+ if (!isLocked(previous.phase) || isLocked(state.phase)) return;
42493
+ if (status().failure?.conflict?.reason === "EDIT_LEASE_HELD") {
42494
+ actions.clearFailure();
42495
+ actions.setAutosavePaused(false);
42496
+ }
42497
+ scheduleCheck();
42498
+ });
42178
42499
  return () => {
42179
42500
  unsubscribeFlow();
42180
42501
  unsubscribeStatus();
42502
+ unsubscribeLease();
42181
42503
  if (checkTimer) clearTimeout(checkTimer);
42182
42504
  autosave.cancel();
42183
42505
  saveDeparting(useFlowStore.getState());
@@ -42249,6 +42571,125 @@ function useLeaveGuards(readOnly) {
42249
42571
  };
42250
42572
  }, [readOnly]);
42251
42573
  }
42574
+ function useActorName(actorId) {
42575
+ const config2 = getFluidGridsConfig();
42576
+ const actors = useMemo(
42577
+ () => actorId ? [{ actorId, actorType: "user" }] : [],
42578
+ [actorId]
42579
+ );
42580
+ const profiles = useActorProfiles(actors, {
42581
+ workspaceId: config2.workspaceId || void 0,
42582
+ orgId: config2.organizationId,
42583
+ authToken: config2.authToken
42584
+ });
42585
+ if (!actorId) return null;
42586
+ return profiles[actorId]?.displayName ?? buildActorFallback({ actorId, actorType: "user" }).displayName;
42587
+ }
42588
+ function SaveConflictDialog() {
42589
+ const tr = useTr();
42590
+ const open2 = useSaveStatusStore((s) => s.conflictOpen);
42591
+ const conflict = useSaveStatusStore((s) => s.failure?.conflict);
42592
+ const statusActions = useSaveStatusStore((s) => s.actions);
42593
+ const dbId = useFlowStore((s) => s.workflow.dbId);
42594
+ const { saveToBackend } = useSaveWorkflow();
42595
+ const name = useActorName(conflict?.updatedById);
42596
+ const [busy, setBusy] = useState(false);
42597
+ if (!conflict || conflict.reason !== "STALE_REVISION") return null;
42598
+ const someone = name ?? tr("fluidgrids.builder.saveConflict.someone", "Someone else");
42599
+ const stillDraft = conflict.currentStatus === "DRAFT";
42600
+ const canOverwrite = stillDraft && conflict.currentRevision !== null && !!dbId;
42601
+ const run = (action) => async () => {
42602
+ setBusy(true);
42603
+ try {
42604
+ await action();
42605
+ } finally {
42606
+ setBusy(false);
42607
+ }
42608
+ };
42609
+ const loadLatest = () => {
42610
+ const flow = useFlowStore.getState();
42611
+ statusActions.setBaseline({
42612
+ fingerprint: workflowFingerprint(flow.workflow),
42613
+ generation: flow.loadGeneration
42614
+ });
42615
+ statusActions.setConflictOpen(false);
42616
+ reloadLatestVersion();
42617
+ };
42618
+ const keepAsNewVersion = run(() => saveToBackend({ asNewVersion: true }));
42619
+ const overwrite = run(() => {
42620
+ if (dbId && conflict.currentRevision !== null) {
42621
+ rememberRevision(dbId, conflict.currentRevision);
42622
+ }
42623
+ return saveToBackend();
42624
+ });
42625
+ return /* @__PURE__ */ jsx(
42626
+ AlertDialog,
42627
+ {
42628
+ open: open2,
42629
+ onOpenChange: (next) => {
42630
+ if (!next && !busy) statusActions.setConflictOpen(false);
42631
+ },
42632
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { "data-testid": "fluidgrids-save-conflict-dialog", children: [
42633
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
42634
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: tr(
42635
+ "fluidgrids.builder.saveConflict.title",
42636
+ "This workflow changed after you opened it"
42637
+ ) }),
42638
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: stillDraft ? tr(
42639
+ "fluidgrids.builder.saveConflict.changedDescription",
42640
+ "{{name}} saved changes to this version, so your save was stopped to keep them. Choose what to keep.",
42641
+ { name: someone }
42642
+ ) : tr(
42643
+ "fluidgrids.builder.saveConflict.publishedDescription",
42644
+ "This version is no longer a draft, so your changes were not saved into it. Keep them as a new version, or load the latest one."
42645
+ ) })
42646
+ ] }),
42647
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { className: "flex-wrap gap-2", children: [
42648
+ /* @__PURE__ */ jsx(AlertDialogCancel, { disabled: busy, children: tr("fluidgrids.builder.saveConflict.decideLater", "Decide later") }),
42649
+ /* @__PURE__ */ jsx(
42650
+ Button$2,
42651
+ {
42652
+ variant: "outline",
42653
+ disabled: busy,
42654
+ "data-testid": "fluidgrids-save-conflict-load-latest",
42655
+ onClick: loadLatest,
42656
+ children: tr(
42657
+ "fluidgrids.builder.saveConflict.loadLatest",
42658
+ "Discard mine and load latest"
42659
+ )
42660
+ }
42661
+ ),
42662
+ canOverwrite ? /* @__PURE__ */ jsx(
42663
+ Button$2,
42664
+ {
42665
+ variant: "outline",
42666
+ disabled: busy,
42667
+ "data-testid": "fluidgrids-save-conflict-overwrite",
42668
+ onClick: () => void overwrite(),
42669
+ children: tr(
42670
+ "fluidgrids.builder.saveConflict.overwrite",
42671
+ "Overwrite their changes"
42672
+ )
42673
+ }
42674
+ ) : null,
42675
+ /* @__PURE__ */ jsx(
42676
+ Button$2,
42677
+ {
42678
+ "data-agent": "confirm",
42679
+ disabled: busy,
42680
+ "data-testid": "fluidgrids-save-conflict-keep",
42681
+ onClick: () => void keepAsNewVersion(),
42682
+ children: tr(
42683
+ "fluidgrids.builder.saveConflict.keepAsNewVersion",
42684
+ "Keep mine as a new version"
42685
+ )
42686
+ }
42687
+ )
42688
+ ] })
42689
+ ] })
42690
+ }
42691
+ );
42692
+ }
42252
42693
  function LeaveChangesDialog() {
42253
42694
  const tr = useTr();
42254
42695
  const navigate = useNavigate();
@@ -42339,14 +42780,18 @@ function SaveQuotaDialog() {
42339
42780
  }
42340
42781
  );
42341
42782
  }
42342
- function BuilderSaveController({ readOnly }) {
42783
+ function BuilderSaveController({
42784
+ readOnly,
42785
+ locked = false
42786
+ }) {
42343
42787
  useAutosave(readOnly);
42344
- useSaveShortcut(readOnly);
42788
+ useSaveShortcut(readOnly || locked);
42345
42789
  useLeaveGuards(readOnly);
42346
42790
  if (readOnly) return null;
42347
42791
  return /* @__PURE__ */ jsxs(Fragment, { children: [
42348
42792
  /* @__PURE__ */ jsx(LeaveChangesDialog, {}),
42349
- /* @__PURE__ */ jsx(SaveQuotaDialog, {})
42793
+ /* @__PURE__ */ jsx(SaveQuotaDialog, {}),
42794
+ /* @__PURE__ */ jsx(SaveConflictDialog, {})
42350
42795
  ] });
42351
42796
  }
42352
42797
 
@@ -42444,6 +42889,34 @@ function FailedStatus({ className, compact }) {
42444
42889
  const tr = useTr();
42445
42890
  const manualSave = useManualSave();
42446
42891
  const message = useSaveStatusStore((s) => s.failure?.message);
42892
+ const staleConflict = useSaveStatusStore(
42893
+ (s) => s.failure?.conflict?.reason === "STALE_REVISION"
42894
+ );
42895
+ const setConflictOpen = useSaveStatusStore((s) => s.actions.setConflictOpen);
42896
+ if (staleConflict) {
42897
+ return /* @__PURE__ */ jsxs(
42898
+ StatusFrame,
42899
+ {
42900
+ state: "conflict",
42901
+ title: message,
42902
+ tone: "text-status-warning-text",
42903
+ className,
42904
+ children: [
42905
+ /* @__PURE__ */ jsx(Icon, { icon: "mynaui:git-branch", className: "size-3.5" }),
42906
+ /* @__PURE__ */ jsx(Label3, { compact, children: tr("fluidgrids.builder.saveStatus.conflict", "Changed elsewhere") }),
42907
+ /* @__PURE__ */ jsx(
42908
+ "button",
42909
+ {
42910
+ type: "button",
42911
+ onClick: () => setConflictOpen(true),
42912
+ className: "ml-1 rounded px-1 font-medium underline-offset-2 hover:underline",
42913
+ children: tr("fluidgrids.builder.saveStatus.resolve", "Resolve")
42914
+ }
42915
+ )
42916
+ ]
42917
+ }
42918
+ );
42919
+ }
42447
42920
  return /* @__PURE__ */ jsxs(
42448
42921
  StatusFrame,
42449
42922
  {
@@ -43055,15 +43528,24 @@ var ActivateWorkflowButton = ({
43055
43528
  });
43056
43529
  if (result) {
43057
43530
  setWorkflow({ status: result.status });
43058
- toast.success("Workflow activated", {
43059
- description: `Version ${result.version} is now ACTIVE. Future saves will create a new draft version.`,
43060
- dismissible: true
43061
- });
43531
+ rememberOwnStatusChange(result.id, result.revision);
43532
+ if (result.status === "ACTIVE") {
43533
+ toast.success("Workflow activated", {
43534
+ description: `Version ${result.version} is now ACTIVE. Future saves will create a new draft version.`,
43535
+ dismissible: true
43536
+ });
43537
+ } else {
43538
+ toast.info("Activation is waiting for a replay check", {
43539
+ description: `Version ${result.version} activates once open auto-upgrade runs replay against it. See Versions.`,
43540
+ dismissible: true
43541
+ });
43542
+ }
43062
43543
  }
43063
43544
  } else {
43064
43545
  const result = await deactivateWorkflow(dbId);
43065
43546
  if (result) {
43066
43547
  setWorkflow({ status: result.status });
43548
+ rememberOwnStatusChange(result.id, result.revision);
43067
43549
  toast.success("Workflow deactivated", {
43068
43550
  description: `Version ${result.version} is now a DRAFT. Saves will update in-place.`,
43069
43551
  dismissible: true
@@ -43107,6 +43589,24 @@ var ActivateWorkflowButton = ({
43107
43589
  );
43108
43590
  };
43109
43591
 
43592
+ // src/builder/hooks/refresh-open-revision.ts
43593
+ async function refreshOpenRevision() {
43594
+ const dbId = useFlowStore.getState().workflow.dbId;
43595
+ if (!dbId) return;
43596
+ try {
43597
+ const { data } = await getFluidGridsClient().query(
43598
+ {
43599
+ query: WorkflowDefinitionDocument,
43600
+ variables: { id: dbId },
43601
+ fetchPolicy: "network-only"
43602
+ }
43603
+ );
43604
+ const stored = data?.workflowDefinition;
43605
+ rememberOwnStatusChange(stored?.id, stored?.revision);
43606
+ } catch {
43607
+ }
43608
+ }
43609
+
43110
43610
  // src/utils/workflow-definition-diff.ts
43111
43611
  function isRecord5(value) {
43112
43612
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -43254,6 +43754,520 @@ function CurrentVersionAiBadge({ iconOnly }) {
43254
43754
  const facts = useCurrentVersionAuthoring();
43255
43755
  return /* @__PURE__ */ jsx(AiAuthoredBadge, { facts, iconOnly });
43256
43756
  }
43757
+ var DRAINAGE_VERSION_LIMIT = 10;
43758
+ async function fetchWorkflowVersioning(workflowKey) {
43759
+ const result = await getFluidGridsClient().query({
43760
+ query: WorkflowVersioningDocument,
43761
+ variables: { workflowKey },
43762
+ fetchPolicy: "network-only"
43763
+ });
43764
+ throwIfResultError(result);
43765
+ return {
43766
+ versions: result.data?.workflowVersions ?? [],
43767
+ rollouts: result.data?.workflowRollouts ?? []
43768
+ };
43769
+ }
43770
+ async function fetchVersionDrainage(workflowKey, version2) {
43771
+ const result = await getFluidGridsClient().query({
43772
+ query: WorkflowVersionDrainageDocument,
43773
+ variables: { workflowKey, version: version2 },
43774
+ fetchPolicy: "network-only"
43775
+ });
43776
+ throwIfResultError(result);
43777
+ return result.data?.workflowVersionDrainage ?? null;
43778
+ }
43779
+ async function saveWorkflowRollout(input2) {
43780
+ const result = await getFluidGridsClient().mutate({
43781
+ mutation: SetWorkflowRolloutDocument,
43782
+ variables: {
43783
+ workflowKey: input2.workflowKey,
43784
+ input: {
43785
+ fromVersion: input2.fromVersion ?? null,
43786
+ toVersion: input2.toVersion,
43787
+ percent: input2.percent,
43788
+ environment: "PROD" /* Prod */
43789
+ }
43790
+ }
43791
+ });
43792
+ throwIfResultError(result);
43793
+ const data = result.data?.setWorkflowRollout;
43794
+ if (!data) throw new Error("The rollout returned no result");
43795
+ return data;
43796
+ }
43797
+ async function removeWorkflowRollout(workflowKey) {
43798
+ const result = await getFluidGridsClient().mutate({
43799
+ mutation: DeleteWorkflowRolloutDocument,
43800
+ variables: { workflowKey, environment: "PROD" /* Prod */ }
43801
+ });
43802
+ throwIfResultError(result);
43803
+ return result.data?.deleteWorkflowRollout ?? false;
43804
+ }
43805
+ async function forceActivateVersion(id) {
43806
+ const result = await getFluidGridsClient().mutate({
43807
+ mutation: ActivateWorkflowVersionCheckedDocument,
43808
+ variables: { id, force: true },
43809
+ refetchQueries: ["WorkflowVersions", "WorkflowDefinitions"]
43810
+ });
43811
+ throwIfResultError(result);
43812
+ const data = result.data?.activateWorkflowDefinition;
43813
+ if (!data) throw new Error("Activation returned no result");
43814
+ return data;
43815
+ }
43816
+ function prodRollout(rollouts) {
43817
+ return rollouts.find(
43818
+ (rollout) => rollout.environment === "PROD" /* Prod */
43819
+ ) ?? null;
43820
+ }
43821
+ function isDurableDefinition(definitionJson) {
43822
+ if (!isRecord6(definitionJson)) return false;
43823
+ const code = definitionJson.code;
43824
+ if (!isRecord6(code) || typeof code.buildId !== "string") return false;
43825
+ const workflows = isRecord6(code.exports) ? code.exports.workflows : null;
43826
+ return isRecord6(workflows) && Object.keys(workflows).length > 0;
43827
+ }
43828
+ function isRecord6(value) {
43829
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43830
+ }
43831
+ var EMPTY_STATE = {
43832
+ durable: false,
43833
+ byVersion: /* @__PURE__ */ new Map(),
43834
+ rollout: null,
43835
+ drainage: /* @__PURE__ */ new Map(),
43836
+ candidates: []
43837
+ };
43838
+ async function durableVersions(workflowKey, versions) {
43839
+ const flags = await Promise.all(
43840
+ versions.map(
43841
+ (version2) => loadDefinitionJson(workflowKey, version2).then(
43842
+ isDurableDefinition,
43843
+ () => false
43844
+ )
43845
+ )
43846
+ );
43847
+ return new Set(versions.filter((_, index) => flags[index]));
43848
+ }
43849
+ function useWorkflowVersioning(workflowKey, versions, enabled) {
43850
+ const [state, setState] = useState(EMPTY_STATE);
43851
+ const [error62, setError] = useState(null);
43852
+ const request = useRef(0);
43853
+ const reference = versions.find((entry) => entry.status === "ACTIVE") ?? versions[0];
43854
+ const newest = versions.slice(0, DRAINAGE_VERSION_LIMIT);
43855
+ const versionsKey = JSON.stringify({
43856
+ reference: reference ? { version: reference.version, status: reference.status ?? null } : null,
43857
+ newest: newest.map((entry) => [entry.version, entry.status ?? null])
43858
+ });
43859
+ const refetch = useCallback(async () => {
43860
+ if (!enabled || !workflowKey) return;
43861
+ const id = ++request.current;
43862
+ const parsed = JSON.parse(versionsKey);
43863
+ if (!parsed.reference) {
43864
+ setState(EMPTY_STATE);
43865
+ setError(null);
43866
+ return;
43867
+ }
43868
+ const active = parsed.reference.status === "ACTIVE" ? parsed.reference.version : null;
43869
+ const checked = [
43870
+ .../* @__PURE__ */ new Set([
43871
+ parsed.reference.version,
43872
+ ...parsed.newest.map(([version2]) => version2)
43873
+ ])
43874
+ ];
43875
+ try {
43876
+ const durable = await durableVersions(workflowKey, checked);
43877
+ if (id !== request.current) return;
43878
+ if (!durable.has(parsed.reference.version)) {
43879
+ setState(EMPTY_STATE);
43880
+ setError(null);
43881
+ return;
43882
+ }
43883
+ const drainable = parsed.newest.map(([version2]) => version2).filter((version2) => durable.has(version2));
43884
+ const [versioning, drained] = await Promise.all([
43885
+ fetchWorkflowVersioning(workflowKey),
43886
+ Promise.all(
43887
+ drainable.map(
43888
+ (version2) => fetchVersionDrainage(workflowKey, version2).catch(() => null)
43889
+ )
43890
+ )
43891
+ ]);
43892
+ if (id !== request.current) return;
43893
+ const drainage = /* @__PURE__ */ new Map();
43894
+ for (const entry of drained)
43895
+ if (entry) drainage.set(entry.version, entry);
43896
+ const checkedSet = new Set(checked);
43897
+ setState({
43898
+ durable: true,
43899
+ // A version known to be canvas gets no parity badges.
43900
+ byVersion: new Map(
43901
+ versioning.versions.filter(
43902
+ (entry) => !checkedSet.has(entry.version) || durable.has(entry.version)
43903
+ ).map((entry) => [entry.version, entry])
43904
+ ),
43905
+ rollout: prodRollout(versioning.rollouts),
43906
+ drainage,
43907
+ candidates: parsed.newest.filter(
43908
+ ([version2, status]) => status === "DRAFT" && version2 !== active && durable.has(version2)
43909
+ ).map(([version2]) => version2)
43910
+ });
43911
+ setError(null);
43912
+ } catch (err) {
43913
+ if (id !== request.current) return;
43914
+ setError(describeOperationError(err));
43915
+ }
43916
+ }, [enabled, workflowKey, versionsKey]);
43917
+ useEffect(() => {
43918
+ void refetch();
43919
+ }, [refetch]);
43920
+ return enabled ? { ...state, error: error62, refetch } : { ...EMPTY_STATE, error: null, refetch };
43921
+ }
43922
+ var CHECK_OPEN = /* @__PURE__ */ new Set([
43923
+ "PENDING" /* Pending */,
43924
+ "VERIFYING" /* Verifying */
43925
+ ]);
43926
+ function visibleActivationCheck(versioning) {
43927
+ const check2 = versioning?.activationCheck;
43928
+ if (!check2) return null;
43929
+ if (CHECK_OPEN.has(check2.status) || check2.status === "REJECTED" /* Rejected */)
43930
+ return check2;
43931
+ return null;
43932
+ }
43933
+ function showDrainage(drainage, isActive) {
43934
+ if (!drainage || isActive) return false;
43935
+ return drainage.status === "DRAINING" /* Draining */ || drainage.rolloutTarget || drainage.lastRunStartedAt !== null;
43936
+ }
43937
+ function VersionParityBadges({
43938
+ versioning,
43939
+ drainage,
43940
+ isActive
43941
+ }) {
43942
+ const tr = useTr();
43943
+ const check2 = visibleActivationCheck(versioning);
43944
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
43945
+ versioning?.versioningBehavior === "AUTO_UPGRADE" /* AutoUpgrade */ && /* @__PURE__ */ jsx(
43946
+ Badge,
43947
+ {
43948
+ variant: "outline",
43949
+ className: "h-5 px-1.5 py-0 text-[10px]",
43950
+ "data-testid": "version-behavior-badge",
43951
+ children: tr("fluidgrids.builder.versionHistory.autoUpgrade", "Auto-upgrade")
43952
+ }
43953
+ ),
43954
+ showDrainage(drainage, isActive) && /* @__PURE__ */ jsx(
43955
+ Badge,
43956
+ {
43957
+ variant: drainage.status === "DRAINING" /* Draining */ ? "secondary" : "outline",
43958
+ className: "h-5 px-1.5 py-0 text-[10px]",
43959
+ "data-testid": "version-drainage-badge",
43960
+ title: tr(
43961
+ "fluidgrids.builder.versionHistory.drainageTooltip",
43962
+ "Open durable-function runs this version's bundle serves"
43963
+ ),
43964
+ children: drainage.status === "DRAINING" /* Draining */ ? tr(
43965
+ "fluidgrids.builder.versionHistory.draining",
43966
+ "Draining \xB7 {{count}} open",
43967
+ { count: drainage.openRuns }
43968
+ ) : drainage.status === "ACTIVE" /* Active */ ? tr("fluidgrids.builder.versionHistory.rolloutTarget", "Rollout") : tr("fluidgrids.builder.versionHistory.drained", "Drained")
43969
+ }
43970
+ ),
43971
+ check2 && /* @__PURE__ */ jsx(
43972
+ Badge,
43973
+ {
43974
+ variant: check2.status === "REJECTED" /* Rejected */ ? "destructive" : "secondary",
43975
+ className: "h-5 px-1.5 py-0 text-[10px]",
43976
+ "data-testid": "version-activation-check",
43977
+ children: check2.status === "REJECTED" /* Rejected */ ? tr(
43978
+ "fluidgrids.builder.versionHistory.checkRejected",
43979
+ "Activation check failed"
43980
+ ) : tr(
43981
+ "fluidgrids.builder.versionHistory.checkRunning",
43982
+ "Activation check running"
43983
+ )
43984
+ }
43985
+ )
43986
+ ] });
43987
+ }
43988
+ function RolloutControl({
43989
+ workflowKey,
43990
+ activeVersion,
43991
+ candidates,
43992
+ rollout,
43993
+ canPublish,
43994
+ onChanged
43995
+ }) {
43996
+ const tr = useTr();
43997
+ const [toVersion, setToVersion] = useState(rollout?.toVersion ?? "");
43998
+ const [percent, setPercent] = useState(rollout?.percent ?? 0);
43999
+ const [saving, setSaving] = useState(false);
44000
+ const [confirmPromote, setConfirmPromote] = useState(false);
44001
+ useEffect(() => {
44002
+ setToVersion(rollout?.toVersion ?? "");
44003
+ setPercent(rollout?.percent ?? 0);
44004
+ }, [rollout?.toVersion, rollout?.percent]);
44005
+ if (!activeVersion || !rollout && candidates.length === 0) return null;
44006
+ const targets = rollout && !candidates.includes(rollout.toVersion) ? [rollout.toVersion, ...candidates] : [...candidates];
44007
+ const unchanged = !!rollout && rollout.toVersion === toVersion && rollout.percent === percent;
44008
+ const locked = !canPublish || saving;
44009
+ const check2 = rollout?.activationCheck;
44010
+ const save = async () => {
44011
+ setSaving(true);
44012
+ try {
44013
+ const saved = await saveWorkflowRollout({
44014
+ workflowKey,
44015
+ toVersion,
44016
+ percent
44017
+ });
44018
+ toast.success(
44019
+ saved.promoted ? tr(
44020
+ "fluidgrids.builder.versionHistory.rolloutPromoted",
44021
+ "Promotion started"
44022
+ ) : tr(
44023
+ "fluidgrids.builder.versionHistory.rolloutSaved",
44024
+ "Rollout saved"
44025
+ )
44026
+ );
44027
+ onChanged();
44028
+ } catch (error62) {
44029
+ toast.error(
44030
+ tr(
44031
+ "fluidgrids.builder.versionHistory.rolloutError",
44032
+ "Could not save the rollout"
44033
+ ),
44034
+ { description: describeOperationError(error62) }
44035
+ );
44036
+ } finally {
44037
+ setSaving(false);
44038
+ setConfirmPromote(false);
44039
+ }
44040
+ };
44041
+ const remove = async () => {
44042
+ setSaving(true);
44043
+ try {
44044
+ await removeWorkflowRollout(workflowKey);
44045
+ toast.success(
44046
+ tr(
44047
+ "fluidgrids.builder.versionHistory.rolloutRemoved",
44048
+ "Rollout removed"
44049
+ )
44050
+ );
44051
+ onChanged();
44052
+ } catch (error62) {
44053
+ toast.error(
44054
+ tr(
44055
+ "fluidgrids.builder.versionHistory.rolloutError",
44056
+ "Could not save the rollout"
44057
+ ),
44058
+ { description: describeOperationError(error62) }
44059
+ );
44060
+ } finally {
44061
+ setSaving(false);
44062
+ }
44063
+ };
44064
+ return /* @__PURE__ */ jsxs(
44065
+ "section",
44066
+ {
44067
+ className: "mb-4 rounded-lg border border-border bg-card p-3",
44068
+ "aria-label": tr(
44069
+ "fluidgrids.builder.versionHistory.rolloutTitle",
44070
+ "Gradual rollout"
44071
+ ),
44072
+ "data-testid": "version-rollout",
44073
+ children: [
44074
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2 text-sm font-semibold", children: [
44075
+ /* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-4" }),
44076
+ tr(
44077
+ "fluidgrids.builder.versionHistory.rolloutTitle",
44078
+ "Gradual rollout"
44079
+ )
44080
+ ] }),
44081
+ /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: tr(
44082
+ "fluidgrids.builder.versionHistory.rolloutHint",
44083
+ "A share of new automatic starts runs another version instead of {{version}}.",
44084
+ { version: activeVersion }
44085
+ ) }),
44086
+ /* @__PURE__ */ jsxs(
44087
+ "select",
44088
+ {
44089
+ value: toVersion,
44090
+ onChange: (event) => setToVersion(event.target.value),
44091
+ disabled: locked,
44092
+ className: "mb-2 h-8 w-full rounded-md border border-border bg-background px-2 text-xs",
44093
+ "aria-label": tr(
44094
+ "fluidgrids.builder.versionHistory.rolloutTarget",
44095
+ "Rollout"
44096
+ ),
44097
+ "data-testid": "version-rollout-target",
44098
+ children: [
44099
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
44100
+ "fluidgrids.builder.versionHistory.rolloutChoose",
44101
+ "Choose a version"
44102
+ ) }),
44103
+ targets.map((version2) => /* @__PURE__ */ jsx("option", { value: version2, children: version2 }, version2))
44104
+ ]
44105
+ }
44106
+ ),
44107
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
44108
+ /* @__PURE__ */ jsx(
44109
+ "input",
44110
+ {
44111
+ type: "range",
44112
+ min: 0,
44113
+ max: 100,
44114
+ step: 1,
44115
+ value: percent,
44116
+ disabled: locked || !toVersion,
44117
+ onChange: (event) => setPercent(Number(event.target.value)),
44118
+ className: "min-w-0 flex-1 accent-primary",
44119
+ "aria-label": tr(
44120
+ "fluidgrids.builder.versionHistory.rolloutPercent",
44121
+ "Share of new starts"
44122
+ ),
44123
+ "data-testid": "version-rollout-percent"
44124
+ }
44125
+ ),
44126
+ /* @__PURE__ */ jsxs("span", { className: "w-10 text-right font-mono text-xs", children: [
44127
+ percent,
44128
+ "%"
44129
+ ] })
44130
+ ] }),
44131
+ rollout && /* @__PURE__ */ jsxs(
44132
+ "p",
44133
+ {
44134
+ className: "mt-1 text-[11px] text-muted-foreground",
44135
+ "data-testid": "version-rollout-verified",
44136
+ children: [
44137
+ tr(
44138
+ "fluidgrids.builder.versionHistory.rolloutVerified",
44139
+ "In-flight auto-upgrade runs follow {{verified}}% (replay-checked)",
44140
+ { verified: rollout.verifiedPercent }
44141
+ ),
44142
+ check2 && (check2.status === "PENDING" /* Pending */ || check2.status === "VERIFYING" /* Verifying */) ? ` \xB7 ${tr("fluidgrids.builder.versionHistory.checkRunning", "Activation check running")}` : ""
44143
+ ]
44144
+ }
44145
+ ),
44146
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex gap-2", children: [
44147
+ /* @__PURE__ */ jsxs(
44148
+ Button$1,
44149
+ {
44150
+ "data-agent": "confirm",
44151
+ size: "sm",
44152
+ variant: "secondary",
44153
+ className: "flex-1",
44154
+ disabled: locked || !toVersion || unchanged,
44155
+ onClick: () => percent === 100 ? setConfirmPromote(true) : void save(),
44156
+ "data-testid": "version-rollout-save",
44157
+ children: [
44158
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-2 size-3.5 animate-spin" }),
44159
+ tr("fluidgrids.builder.versionHistory.rolloutSave", "Save rollout")
44160
+ ]
44161
+ }
44162
+ ),
44163
+ rollout && /* @__PURE__ */ jsx(
44164
+ Button$1,
44165
+ {
44166
+ "data-agent": "confirm",
44167
+ size: "sm",
44168
+ variant: "ghost",
44169
+ disabled: locked,
44170
+ onClick: () => void remove(),
44171
+ "aria-label": tr(
44172
+ "fluidgrids.builder.versionHistory.rolloutRemove",
44173
+ "Remove rollout"
44174
+ ),
44175
+ "data-testid": "version-rollout-remove",
44176
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
44177
+ }
44178
+ )
44179
+ ] }),
44180
+ /* @__PURE__ */ jsx(AlertDialog, { open: confirmPromote, onOpenChange: setConfirmPromote, children: /* @__PURE__ */ jsxs(AlertDialogContent, { className: "z-[120]", children: [
44181
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
44182
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: tr(
44183
+ "fluidgrids.builder.versionHistory.promoteTitle",
44184
+ "Promote {{version}}?",
44185
+ { version: toVersion }
44186
+ ) }),
44187
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: tr(
44188
+ "fluidgrids.builder.versionHistory.promoteBody",
44189
+ "At 100% the version becomes the active one once open auto-upgrade runs replay against it, and the rollout is removed."
44190
+ ) })
44191
+ ] }),
44192
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
44193
+ /* @__PURE__ */ jsx(AlertDialogCancel, { disabled: saving, children: tr(
44194
+ "fluidgrids.builder.versionHistory.deleteDialogCancel",
44195
+ "Cancel"
44196
+ ) }),
44197
+ /* @__PURE__ */ jsx(
44198
+ AlertDialogAction,
44199
+ {
44200
+ "data-agent": "confirm",
44201
+ disabled: saving,
44202
+ onClick: () => void save(),
44203
+ children: tr(
44204
+ "fluidgrids.builder.versionHistory.promoteConfirm",
44205
+ "Promote"
44206
+ )
44207
+ }
44208
+ )
44209
+ ] })
44210
+ ] }) })
44211
+ ]
44212
+ }
44213
+ );
44214
+ }
44215
+ function ForceActivateDialog({
44216
+ version: version2,
44217
+ violations,
44218
+ busy,
44219
+ onConfirm,
44220
+ onCancel
44221
+ }) {
44222
+ const tr = useTr();
44223
+ return /* @__PURE__ */ jsx(
44224
+ AlertDialog,
44225
+ {
44226
+ open: version2 !== null,
44227
+ onOpenChange: (open2) => !open2 && onCancel(),
44228
+ children: /* @__PURE__ */ jsxs(
44229
+ AlertDialogContent,
44230
+ {
44231
+ className: "z-[120]",
44232
+ "data-testid": "force-activate-dialog",
44233
+ children: [
44234
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
44235
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: tr(
44236
+ "fluidgrids.builder.versionHistory.forceTitle",
44237
+ "Activate {{version}} anyway?",
44238
+ { version: version2 ?? "" }
44239
+ ) }),
44240
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: tr(
44241
+ "fluidgrids.builder.versionHistory.forceBody",
44242
+ "{{count}} open auto-upgrade runs did not replay against this version. They keep failing their workflow task until a fix ships. The activation is audited.",
44243
+ { count: violations }
44244
+ ) })
44245
+ ] }),
44246
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
44247
+ /* @__PURE__ */ jsx(AlertDialogCancel, { disabled: busy, children: tr(
44248
+ "fluidgrids.builder.versionHistory.deleteDialogCancel",
44249
+ "Cancel"
44250
+ ) }),
44251
+ /* @__PURE__ */ jsx(
44252
+ AlertDialogAction,
44253
+ {
44254
+ "data-agent": "confirm",
44255
+ disabled: busy,
44256
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
44257
+ onClick: onConfirm,
44258
+ children: tr(
44259
+ "fluidgrids.builder.versionHistory.forceConfirm",
44260
+ "Activate anyway"
44261
+ )
44262
+ }
44263
+ )
44264
+ ] })
44265
+ ]
44266
+ }
44267
+ )
44268
+ }
44269
+ );
44270
+ }
43257
44271
  function relativeTime(dateStr) {
43258
44272
  if (!dateStr) return "";
43259
44273
  const then = new Date(dateStr).getTime();
@@ -43365,6 +44379,18 @@ function VersionHistoryPanel({
43365
44379
  const [compareLoading, setCompareLoading] = useState(false);
43366
44380
  const [busy, setBusy] = useState(null);
43367
44381
  const [deleteTarget, setDeleteTarget] = useState(null);
44382
+ const parityEnabled = useDurableParityEnabled();
44383
+ const { allowed: canPublish } = useFluidGridsPermission(
44384
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_PUBLISH
44385
+ );
44386
+ const versioning = useWorkflowVersioning(
44387
+ workflowKey,
44388
+ versions,
44389
+ parityEnabled
44390
+ );
44391
+ const refetchVersioning = versioning.refetch;
44392
+ const [forceTarget, setForceTarget] = useState(null);
44393
+ const activeVersion = versions.find((entry) => entry.status === "ACTIVE")?.version ?? null;
43368
44394
  useEffect(() => {
43369
44395
  if (versions.length < 2) {
43370
44396
  setCompareFrom("");
@@ -43470,6 +44496,23 @@ function VersionHistoryPanel({
43470
44496
  try {
43471
44497
  const result = await activateWorkflow(id);
43472
44498
  if (!result) throw new Error("Activation returned no result");
44499
+ if (result.status !== "ACTIVE") {
44500
+ toast.info(
44501
+ tr(
44502
+ "fluidgrids.builder.versionHistory.activationHeld",
44503
+ "Activation is waiting for a replay check"
44504
+ ),
44505
+ {
44506
+ description: tr(
44507
+ "fluidgrids.builder.versionHistory.activationHeldDescription",
44508
+ "Open auto-upgrade runs replay against this version first."
44509
+ ),
44510
+ dismissible: true
44511
+ }
44512
+ );
44513
+ await Promise.all([refetch(), refetchVersioning()]);
44514
+ return;
44515
+ }
43473
44516
  toast.success(`Version ${version2} is now ACTIVE`, {
43474
44517
  description: tr(
43475
44518
  "fluidgrids.builder.versionHistory.activateSuccessDescription",
@@ -43479,10 +44522,12 @@ function VersionHistoryPanel({
43479
44522
  });
43480
44523
  if (version2 === currentVersion) {
43481
44524
  setWorkflow({ status: "ACTIVE" });
44525
+ rememberOwnStatusChange(result.id, result.revision);
43482
44526
  } else {
43483
44527
  const refreshed = await refetch();
43484
44528
  const current = refreshed.find((v) => v.version === currentVersion);
43485
44529
  if (current?.status) setWorkflow({ status: current.status });
44530
+ rememberOwnStatusChange(current?.id, current?.revision);
43486
44531
  }
43487
44532
  await refetch();
43488
44533
  } catch (error62) {
@@ -43502,6 +44547,39 @@ function VersionHistoryPanel({
43502
44547
  setBusy(null);
43503
44548
  }
43504
44549
  };
44550
+ const handleForceActivate = async () => {
44551
+ if (!forceTarget) return;
44552
+ const { id, version: version2 } = forceTarget;
44553
+ setBusy(`activate:${version2}`);
44554
+ try {
44555
+ const result = await forceActivateVersion(id);
44556
+ toast.success(`Version ${version2} is now ${result.status}`, {
44557
+ dismissible: true
44558
+ });
44559
+ if (version2 === currentVersion) setWorkflow({ status: result.status });
44560
+ await Promise.all([
44561
+ refetch(),
44562
+ refetchVersioning(),
44563
+ refreshOpenRevision()
44564
+ ]);
44565
+ } catch (error62) {
44566
+ toast.error(
44567
+ tr(
44568
+ "fluidgrids.builder.versionHistory.activateErrorTitle",
44569
+ "Failed to activate version"
44570
+ ),
44571
+ {
44572
+ description: error62 instanceof Error ? error62.message : tr(
44573
+ "fluidgrids.builder.versionHistory.activateUnknownError",
44574
+ "Unknown error"
44575
+ )
44576
+ }
44577
+ );
44578
+ } finally {
44579
+ setBusy(null);
44580
+ setForceTarget(null);
44581
+ }
44582
+ };
43505
44583
  const handleDelete = async (version2) => {
43506
44584
  setBusy(`delete:${version2}`);
43507
44585
  try {
@@ -43546,7 +44624,10 @@ function VersionHistoryPanel({
43546
44624
  "fluidgrids.builder.versionHistory.triggerButton",
43547
44625
  "Versions"
43548
44626
  ) : void 0,
43549
- onClick: () => refetch(),
44627
+ onClick: () => {
44628
+ void refetch();
44629
+ void refetchVersioning();
44630
+ },
43550
44631
  children: [
43551
44632
  /* @__PURE__ */ jsx(History, { className: "size-4" }),
43552
44633
  !compact && tr("fluidgrids.builder.versionHistory.triggerButton", "Versions")
@@ -43574,6 +44655,20 @@ function VersionHistoryPanel({
43574
44655
  ] })
43575
44656
  ] }),
43576
44657
  /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs("div", { className: "mt-4 overflow-y-auto max-h-[calc(100vh-140px)] pr-1", children: [
44658
+ parityEnabled && versioning.durable && !loading && /* @__PURE__ */ jsx(
44659
+ RolloutControl,
44660
+ {
44661
+ workflowKey,
44662
+ activeVersion,
44663
+ candidates: versioning.candidates,
44664
+ rollout: versioning.rollout,
44665
+ canPublish,
44666
+ onChanged: () => {
44667
+ void refetch();
44668
+ void refetchVersioning();
44669
+ }
44670
+ }
44671
+ ),
43577
44672
  !loading && versions.length > 1 && /* @__PURE__ */ jsxs(
43578
44673
  "section",
43579
44674
  {
@@ -43786,7 +44881,15 @@ function VersionHistoryPanel({
43786
44881
  ]
43787
44882
  }
43788
44883
  ),
43789
- /* @__PURE__ */ jsx(AiAuthoredBadge, { facts: v })
44884
+ /* @__PURE__ */ jsx(AiAuthoredBadge, { facts: v }),
44885
+ parityEnabled && versioning.durable && /* @__PURE__ */ jsx(
44886
+ VersionParityBadges,
44887
+ {
44888
+ versioning: versioning.byVersion.get(v.version),
44889
+ drainage: versioning.drainage.get(v.version),
44890
+ isActive
44891
+ }
44892
+ )
43790
44893
  ] }),
43791
44894
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center", children: [
43792
44895
  !isCurrent && /* @__PURE__ */ jsx(
@@ -43815,6 +44918,26 @@ function VersionHistoryPanel({
43815
44918
  onClick: () => handleActivate(v.id, v.version)
43816
44919
  }
43817
44920
  ),
44921
+ parityEnabled && versioning.durable && canPublish && !isActive && visibleActivationCheck(
44922
+ versioning.byVersion.get(v.version)
44923
+ )?.status === "REJECTED" /* Rejected */ && /* @__PURE__ */ jsx(
44924
+ ActionButton,
44925
+ {
44926
+ confirm: true,
44927
+ destructive: true,
44928
+ label: tr(
44929
+ "fluidgrids.builder.versionHistory.actionForce",
44930
+ "Activate anyway"
44931
+ ),
44932
+ icon: /* @__PURE__ */ jsx(Play, { className: "size-3.5" }),
44933
+ disabled: busy !== null,
44934
+ onClick: () => setForceTarget({
44935
+ id: v.id,
44936
+ version: v.version,
44937
+ violations: versioning.byVersion.get(v.version)?.activationCheck?.violations.length ?? 0
44938
+ })
44939
+ }
44940
+ ),
43818
44941
  !isCurrent && /* @__PURE__ */ jsx(
43819
44942
  ActionButton,
43820
44943
  {
@@ -43863,6 +44986,16 @@ function VersionHistoryPanel({
43863
44986
  }) })
43864
44987
  ] }) })
43865
44988
  ] }),
44989
+ parityEnabled && /* @__PURE__ */ jsx(
44990
+ ForceActivateDialog,
44991
+ {
44992
+ version: forceTarget?.version ?? null,
44993
+ violations: forceTarget?.violations ?? 0,
44994
+ busy: busy !== null,
44995
+ onConfirm: () => void handleForceActivate(),
44996
+ onCancel: () => setForceTarget(null)
44997
+ }
44998
+ ),
43866
44999
  /* @__PURE__ */ jsx(
43867
45000
  AlertDialog,
43868
45001
  {
@@ -44169,12 +45302,15 @@ function EnvironmentsPanel({
44169
45302
  const pending = view?.pendingPromotions ?? [];
44170
45303
  const policy = view?.policy;
44171
45304
  const dev = environments.find((state) => state.environment === "DEV");
44172
- const afterProdChange = (promotedVersion) => {
45305
+ const afterProdChange = async (promotedVersion) => {
44173
45306
  if (promotedVersion === currentVersion) {
44174
45307
  setWorkflow({ status: "ACTIVE" });
44175
45308
  } else if (openStatus === "ACTIVE") {
44176
45309
  setWorkflow({ status: "ARCHIVED" });
45310
+ } else {
45311
+ return;
44177
45312
  }
45313
+ await refreshOpenRevision();
44178
45314
  };
44179
45315
  const run = async (key, action, errorTitle) => {
44180
45316
  setBusy(key);
@@ -44229,7 +45365,8 @@ function EnvironmentsPanel({
44229
45365
  `${request.version} ${tr("fluidgrids.builder.environments.promotedTo", "promoted to")} ${envLabel(current.target)}`,
44230
45366
  { dismissible: true }
44231
45367
  );
44232
- if (current.target === "PROD") afterProdChange(request.version);
45368
+ if (current.target === "PROD")
45369
+ await afterProdChange(request.version);
44233
45370
  }
44234
45371
  },
44235
45372
  tr("fluidgrids.builder.environments.promoteError", "Promotion failed")
@@ -44245,7 +45382,7 @@ function EnvironmentsPanel({
44245
45382
  `${current.label} ${tr("fluidgrids.builder.environments.approved", "approved and deployed")}`,
44246
45383
  { dismissible: true }
44247
45384
  );
44248
- if (current.target === "PROD") afterProdChange(current.version);
45385
+ if (current.target === "PROD") await afterProdChange(current.version);
44249
45386
  },
44250
45387
  tr("fluidgrids.builder.environments.approveError", "Approval failed")
44251
45388
  );
@@ -45949,23 +47086,23 @@ function useAiWorkflowTemplates(enabled) {
45949
47086
 
45950
47087
  // src/builder/lib/ai-draft.ts
45951
47088
  var AI_DRAFT_MAX_PROMPT_LENGTH = 4e3;
45952
- function isRecord6(value) {
47089
+ function isRecord7(value) {
45953
47090
  return typeof value === "object" && value !== null && !Array.isArray(value);
45954
47091
  }
45955
47092
  function stringOr(value, fallback) {
45956
47093
  return typeof value === "string" && value.trim() !== "" ? value : fallback;
45957
47094
  }
45958
47095
  function draftToCanvas(definitionJson) {
45959
- const data = isRecord6(definitionJson) && isRecord6(definitionJson.data) ? definitionJson.data : {};
47096
+ const data = isRecord7(definitionJson) && isRecord7(definitionJson.data) ? definitionJson.data : {};
45960
47097
  const rawNodes = Array.isArray(data.nodes) ? data.nodes : [];
45961
47098
  const rawEdges = Array.isArray(data.edges) ? data.edges : [];
45962
- const nodes = rawNodes.filter(isRecord6).flatMap((raw) => {
47099
+ const nodes = rawNodes.filter(isRecord7).flatMap((raw) => {
45963
47100
  if (typeof raw.id !== "string" || typeof raw.type !== "string") return [];
45964
- const position = isRecord6(raw.position) ? {
47101
+ const position = isRecord7(raw.position) ? {
45965
47102
  x: typeof raw.position.x === "number" ? raw.position.x : 0,
45966
47103
  y: typeof raw.position.y === "number" ? raw.position.y : 0
45967
47104
  } : { x: 0, y: 0 };
45968
- const nodeData = isRecord6(raw.data) ? raw.data : {};
47105
+ const nodeData = isRecord7(raw.data) ? raw.data : {};
45969
47106
  const node2 = {
45970
47107
  ...raw,
45971
47108
  id: raw.id,
@@ -45977,7 +47114,7 @@ function draftToCanvas(definitionJson) {
45977
47114
  return [node2];
45978
47115
  });
45979
47116
  const ids = new Set(nodes.map((node2) => node2.id));
45980
- const edges = rawEdges.filter(isRecord6).flatMap((raw) => {
47117
+ const edges = rawEdges.filter(isRecord7).flatMap((raw) => {
45981
47118
  if (typeof raw.source !== "string" || typeof raw.target !== "string") {
45982
47119
  return [];
45983
47120
  }
@@ -45996,7 +47133,7 @@ function draftToCanvas(definitionJson) {
45996
47133
  }
45997
47134
  function enrichDraftNodes(nodes, seed) {
45998
47135
  return nodes.map((node2) => {
45999
- const data = isRecord6(node2.data) ? node2.data : {};
47136
+ const data = isRecord7(node2.data) ? node2.data : {};
46000
47137
  const rootOperation = node2.operation_name;
46001
47138
  let operationName;
46002
47139
  if (typeof data.operation_name === "string") {
@@ -46007,10 +47144,10 @@ function enrichDraftNodes(nodes, seed) {
46007
47144
  if (!node2.type || !operationName) return node2;
46008
47145
  const seeded = seed(node2.type, operationName, data);
46009
47146
  if (!seeded) return node2;
46010
- const seededArgs = isRecord6(seeded.args) ? seeded.args : {};
46011
- const defaults = isRecord6(seededArgs.state) ? seededArgs.state : {};
46012
- const draftArgs = isRecord6(data.args) ? data.args : {};
46013
- const draftState = isRecord6(draftArgs.state) ? draftArgs.state : {};
47147
+ const seededArgs = isRecord7(seeded.args) ? seeded.args : {};
47148
+ const defaults = isRecord7(seededArgs.state) ? seededArgs.state : {};
47149
+ const draftArgs = isRecord7(data.args) ? data.args : {};
47150
+ const draftState = isRecord7(draftArgs.state) ? draftArgs.state : {};
46014
47151
  return {
46015
47152
  ...node2,
46016
47153
  data: {
@@ -46063,7 +47200,7 @@ function AiDraftPanel({
46063
47200
  ...template !== NO_TEMPLATE ? { template } : {}
46064
47201
  });
46065
47202
  }, [openVersion, proposal, template, trimmed, workflowKey]);
46066
- const openWorkflow = useCallback(
47203
+ const openWorkflow2 = useCallback(
46067
47204
  (key, version2) => {
46068
47205
  setOpen(false);
46069
47206
  proposal.reset();
@@ -46115,7 +47252,7 @@ function AiDraftPanel({
46115
47252
  { version: version2, newVersion: result.workflow.version }
46116
47253
  )
46117
47254
  );
46118
- openWorkflow(result.workflow.workflowKey, result.workflow.version);
47255
+ openWorkflow2(result.workflow.workflowKey, result.workflow.version);
46119
47256
  } catch (error62) {
46120
47257
  toast.error(
46121
47258
  tr(
@@ -46130,7 +47267,7 @@ function AiDraftPanel({
46130
47267
  setReverting(false);
46131
47268
  }
46132
47269
  },
46133
- [openWorkflow, restoreVersion, tr]
47270
+ [openWorkflow2, restoreVersion, tr]
46134
47271
  );
46135
47272
  const putOnCanvas = useCallback(() => {
46136
47273
  if (!current?.definitionJson) return;
@@ -46319,7 +47456,7 @@ function AiDraftPanel({
46319
47456
  onApply: () => void handleApply(),
46320
47457
  onReject: () => void handleReject(),
46321
47458
  onCancel: () => void proposal.cancel(),
46322
- onOpenWorkflow: openWorkflow,
47459
+ onOpenWorkflow: openWorkflow2,
46323
47460
  onRevert: (key, version2) => void handleRevert(key, version2)
46324
47461
  }
46325
47462
  ),
@@ -47245,6 +48382,236 @@ function CodeVersionNotice() {
47245
48382
  }
47246
48383
  );
47247
48384
  }
48385
+ function actorOf(banner) {
48386
+ return banner.kind === "editing" || banner.kind === "taken" ? banner.actorId : null;
48387
+ }
48388
+ function unsavedWorkActions(tr, commands) {
48389
+ return [
48390
+ {
48391
+ key: "keep",
48392
+ label: tr(
48393
+ "fluidgrids.builder.editLease.keepAsNewVersion",
48394
+ "Keep my changes as a new version"
48395
+ ),
48396
+ onClick: commands.keepMine,
48397
+ primary: true
48398
+ },
48399
+ {
48400
+ key: "take-over",
48401
+ label: tr(
48402
+ "fluidgrids.builder.editLease.discardAndTakeOver",
48403
+ "Discard my changes and take over"
48404
+ ),
48405
+ onClick: commands.takeOver
48406
+ },
48407
+ {
48408
+ key: "latest",
48409
+ label: tr(
48410
+ "fluidgrids.builder.editLease.discardAndViewLatest",
48411
+ "Discard my changes and view latest"
48412
+ ),
48413
+ onClick: commands.refresh
48414
+ }
48415
+ ];
48416
+ }
48417
+ function unsavedHere(tr) {
48418
+ return tr(
48419
+ "fluidgrids.builder.editLease.unsavedHere",
48420
+ "Your latest changes are only on this screen."
48421
+ );
48422
+ }
48423
+ function editingView(self, someone, dirty, tr, commands) {
48424
+ const editing = self ? tr(
48425
+ "fluidgrids.builder.editLease.editingElsewhereSelf",
48426
+ "You are editing this workflow in another tab or window, so it is read-only here."
48427
+ ) : tr(
48428
+ "fluidgrids.builder.editLease.editingElsewhere",
48429
+ "{{name}} is editing this workflow, so it is read-only for you.",
48430
+ { name: someone }
48431
+ );
48432
+ if (dirty) {
48433
+ return {
48434
+ icon: "mynaui:lock",
48435
+ message: `${editing} ${unsavedHere(tr)}`,
48436
+ actions: unsavedWorkActions(tr, commands)
48437
+ };
48438
+ }
48439
+ return {
48440
+ icon: "mynaui:lock",
48441
+ message: editing,
48442
+ actions: [
48443
+ {
48444
+ key: "take-over",
48445
+ label: self ? tr("fluidgrids.builder.editLease.editHere", "Edit here") : tr("fluidgrids.builder.editLease.takeOver", "Take over"),
48446
+ onClick: commands.takeOver,
48447
+ primary: true
48448
+ },
48449
+ {
48450
+ key: "refresh",
48451
+ label: tr("fluidgrids.builder.editLease.refresh", "Refresh"),
48452
+ onClick: commands.refresh
48453
+ }
48454
+ ]
48455
+ };
48456
+ }
48457
+ function takenView(self, someone, dirty, tr, commands) {
48458
+ const taken = self ? tr(
48459
+ "fluidgrids.builder.editLease.takenSelf",
48460
+ "You started editing this workflow in another tab or window."
48461
+ ) : tr(
48462
+ "fluidgrids.builder.editLease.taken",
48463
+ "{{name}} took over editing this workflow.",
48464
+ { name: someone }
48465
+ );
48466
+ if (dirty) {
48467
+ return {
48468
+ icon: "mynaui:lock",
48469
+ message: `${taken} ${unsavedHere(tr)}`,
48470
+ actions: unsavedWorkActions(tr, commands)
48471
+ };
48472
+ }
48473
+ return {
48474
+ icon: "mynaui:lock",
48475
+ message: taken,
48476
+ actions: [
48477
+ {
48478
+ key: "take-over",
48479
+ label: tr("fluidgrids.builder.editLease.takeOver", "Take over"),
48480
+ onClick: commands.takeOver,
48481
+ primary: true
48482
+ },
48483
+ {
48484
+ key: "latest",
48485
+ label: tr("fluidgrids.builder.editLease.viewLatest", "View latest"),
48486
+ onClick: commands.refresh
48487
+ }
48488
+ ]
48489
+ };
48490
+ }
48491
+ function bannerView(banner, context, commands) {
48492
+ const { someone, dirty, tr } = context;
48493
+ switch (banner.kind) {
48494
+ case "editing":
48495
+ return editingView(banner.self, someone, dirty, tr, commands);
48496
+ case "taken":
48497
+ return takenView(banner.self, someone, dirty, tr, commands);
48498
+ case "free":
48499
+ return {
48500
+ icon: "mynaui:lock-open",
48501
+ message: tr(
48502
+ "fluidgrids.builder.editLease.free",
48503
+ "Nobody is editing this workflow now."
48504
+ ),
48505
+ actions: [
48506
+ {
48507
+ key: "edit",
48508
+ label: tr("fluidgrids.builder.editLease.edit", "Edit"),
48509
+ onClick: commands.takeOver,
48510
+ primary: true
48511
+ }
48512
+ ]
48513
+ };
48514
+ case "idle":
48515
+ return {
48516
+ icon: "mynaui:pause",
48517
+ message: tr(
48518
+ "fluidgrids.builder.editLease.idle",
48519
+ "Editing paused after 15 minutes without activity, so others can edit this workflow."
48520
+ ),
48521
+ actions: [
48522
+ {
48523
+ key: "resume",
48524
+ label: tr("fluidgrids.builder.editLease.resume", "Resume editing"),
48525
+ onClick: commands.resume,
48526
+ primary: true
48527
+ }
48528
+ ]
48529
+ };
48530
+ case "reloading":
48531
+ return {
48532
+ icon: "mynaui:spinner",
48533
+ message: tr(
48534
+ "fluidgrids.builder.editLease.reloading",
48535
+ "Loading the latest version\u2026"
48536
+ ),
48537
+ actions: []
48538
+ };
48539
+ }
48540
+ }
48541
+ function EditLeaseBanner() {
48542
+ const tr = useTr();
48543
+ const lease = useEditLeaseStore(
48544
+ useShallow((s) => ({
48545
+ phase: s.phase,
48546
+ held: s.held,
48547
+ holderActorId: s.holderActorId,
48548
+ heldByCaller: s.heldByCaller,
48549
+ takenBy: s.takenBy,
48550
+ takenByCaller: s.takenByCaller
48551
+ }))
48552
+ );
48553
+ const banner = bannerOf(lease);
48554
+ const name = useActorName(banner ? actorOf(banner) : null);
48555
+ const workflowName = useFlowStore((s) => s.workflow.name);
48556
+ const statusDirty = useSaveStatusStore((s) => s.dirty);
48557
+ useSaveStatusStore((s) => s.baseline);
48558
+ const canvasDirty = useFlowStore((s) => editStateFor(s) === "dirty");
48559
+ const dirty = statusDirty || canvasDirty;
48560
+ const { saveToBackend } = useSaveWorkflow();
48561
+ const [busy, setBusy] = useState(false);
48562
+ if (!banner) return null;
48563
+ const run = (action) => () => {
48564
+ setBusy(true);
48565
+ void Promise.resolve().then(action).finally(() => setBusy(false));
48566
+ };
48567
+ const view = bannerView(
48568
+ banner,
48569
+ {
48570
+ someone: name ?? tr("fluidgrids.builder.editLease.someone", "Someone else"),
48571
+ dirty,
48572
+ tr
48573
+ },
48574
+ {
48575
+ takeOver: run(takeOverEditLease),
48576
+ refresh: reloadLatestVersion,
48577
+ keepMine: run(() => saveToBackend({ asNewVersion: true })),
48578
+ resume: run(resumeEditLease)
48579
+ }
48580
+ );
48581
+ return /* @__PURE__ */ jsxs(
48582
+ "div",
48583
+ {
48584
+ role: "status",
48585
+ "data-testid": "fluidgrids-edit-lease-banner",
48586
+ "data-state": banner.kind,
48587
+ className: "mx-4 mt-2 flex flex-wrap items-center gap-x-3 gap-y-2 rounded-t-xl border border-status-warning-border bg-status-warning-bg-subtle px-4 py-2 text-sm text-status-warning-text",
48588
+ children: [
48589
+ /* @__PURE__ */ jsx(
48590
+ Icon,
48591
+ {
48592
+ icon: view.icon,
48593
+ className: banner.kind === "reloading" ? "size-4 animate-spin" : "size-4",
48594
+ "aria-hidden": true
48595
+ }
48596
+ ),
48597
+ workflowName ? /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: workflowName }) : null,
48598
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1", children: view.message }),
48599
+ view.actions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: view.actions.map((action) => /* @__PURE__ */ jsx(
48600
+ Button$2,
48601
+ {
48602
+ size: "sm",
48603
+ variant: action.primary ? "default" : "outline",
48604
+ disabled: busy,
48605
+ "data-testid": `fluidgrids-edit-lease-${action.key}`,
48606
+ onClick: action.onClick,
48607
+ children: action.label
48608
+ },
48609
+ action.key
48610
+ )) }) : null
48611
+ ]
48612
+ }
48613
+ );
48614
+ }
47248
48615
  var edgeTypes2 = {
47249
48616
  deletable: CustomDeletableEdge
47250
48617
  };
@@ -47261,9 +48628,38 @@ var edgeOptions = {
47261
48628
  strokeWidth: 1.5
47262
48629
  }
47263
48630
  };
48631
+ function canvasEditingProps(blocked2, editing) {
48632
+ if (blocked2) {
48633
+ return {
48634
+ nodesDraggable: false,
48635
+ nodesConnectable: false,
48636
+ elementsSelectable: false,
48637
+ multiSelectionKeyCode: null,
48638
+ deleteKeyCode: null
48639
+ };
48640
+ }
48641
+ return {
48642
+ ...editing,
48643
+ nodesDraggable: true,
48644
+ nodesConnectable: true,
48645
+ elementsSelectable: true,
48646
+ // Ctrl/Cmd-click adds a node to the selection; box-select via Shift-drag
48647
+ // on empty canvas is React Flow's own default and needs no extra prop.
48648
+ multiSelectionKeyCode: ["Meta", "Control"]
48649
+ };
48650
+ }
48651
+ function useEditingBlocked(isReadOnly) {
48652
+ const { allowed: canEdit } = useFluidGridsPermission(
48653
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE
48654
+ );
48655
+ useEditLease(!isReadOnly && canEdit);
48656
+ const leaseLocked = useEditLeaseLocked() && !isReadOnly;
48657
+ return { leaseLocked, editingBlocked: isReadOnly || leaseLocked };
48658
+ }
47264
48659
  var FlowBuilder = ({ readOnly = false }) => {
47265
48660
  const searchParams = new URLSearchParams(window.location.search);
47266
48661
  const isReadOnly = readOnly || searchParams.get("readonly") === "true";
48662
+ const { leaseLocked, editingBlocked } = useEditingBlocked(isReadOnly);
47267
48663
  const durableFunctionsEnabled = useDurableFunctionNodesEnabled();
47268
48664
  const durableFunctionsEnabledRef = useRef(durableFunctionsEnabled);
47269
48665
  useEffect(() => {
@@ -47275,7 +48671,7 @@ var FlowBuilder = ({ readOnly = false }) => {
47275
48671
  }),
47276
48672
  []
47277
48673
  );
47278
- useAgentTools(agentTools, !isReadOnly);
48674
+ useAgentTools(agentTools, !editingBlocked);
47279
48675
  const [nodes, edges, onNodesChange, onEdgesChange, onConnect] = useFlowStore(
47280
48676
  useShallow((s) => {
47281
48677
  return [
@@ -47367,10 +48763,11 @@ var FlowBuilder = ({ readOnly = false }) => {
47367
48763
  onNodesChange
47368
48764
  ]
47369
48765
  );
47370
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full w-full overflow-hidden", children: [
47371
- !isReadOnly && /* @__PURE__ */ jsx(BuilderHeader, {}),
48766
+ return /* @__PURE__ */ jsx(BuilderReadOnlyProvider, { value: editingBlocked, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full w-full overflow-hidden", children: [
48767
+ !editingBlocked && /* @__PURE__ */ jsx(BuilderHeader, {}),
47372
48768
  !isReadOnly && /* @__PURE__ */ jsx(CodeVersionNotice, {}),
47373
- /* @__PURE__ */ jsx(BuilderSaveController, { readOnly: isReadOnly }),
48769
+ leaseLocked && /* @__PURE__ */ jsx(EditLeaseBanner, {}),
48770
+ /* @__PURE__ */ jsx(BuilderSaveController, { readOnly: isReadOnly, locked: leaseLocked }),
47374
48771
  /* @__PURE__ */ jsxs(
47375
48772
  "div",
47376
48773
  {
@@ -47404,21 +48801,19 @@ var FlowBuilder = ({ readOnly = false }) => {
47404
48801
  edgeTypes: edgeTypes2,
47405
48802
  edges,
47406
48803
  onEdgesChange,
47407
- onConnect: isReadOnly ? void 0 : onConnect,
47408
- onConnectEnd: isReadOnly ? void 0 : handleOnEdgeDropConnectEnd,
47409
- onDrop: isReadOnly ? void 0 : onDrop,
47410
- onDragOver: isReadOnly ? void 0 : onDragOver,
48804
+ ...canvasEditingProps(editingBlocked, {
48805
+ onConnect,
48806
+ onConnectEnd: handleOnEdgeDropConnectEnd,
48807
+ onDrop,
48808
+ onDragOver,
48809
+ deleteKeyCode
48810
+ }),
47411
48811
  onNodeDragStop: (_, node2) => {
47412
48812
  autoAdjustNode(node2);
47413
48813
  },
47414
48814
  onPaneClick: closeQuickConfig,
47415
48815
  onNodesDelete,
47416
48816
  isValidConnection,
47417
- nodesDraggable: !isReadOnly,
47418
- nodesConnectable: !isReadOnly,
47419
- elementsSelectable: !isReadOnly,
47420
- multiSelectionKeyCode: isReadOnly ? null : ["Meta", "Control"],
47421
- deleteKeyCode: isReadOnly ? null : deleteKeyCode,
47422
48817
  snapGrid: [16, 16],
47423
48818
  snapToGrid: true,
47424
48819
  fitView: true,
@@ -47428,7 +48823,7 @@ var FlowBuilder = ({ readOnly = false }) => {
47428
48823
  }
47429
48824
  ),
47430
48825
  /* @__PURE__ */ jsx(FluidGridsMark, {}),
47431
- nodes.length === 0 && !isReadOnly && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [
48826
+ nodes.length === 0 && !editingBlocked && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [
47432
48827
  /* @__PURE__ */ jsx("div", { className: "size-16 flex items-center justify-center rounded-full bg-primary/10 border-2 border-dashed border-primary/30", children: /* @__PURE__ */ jsx(
47433
48828
  Icon,
47434
48829
  {
@@ -47444,10 +48839,8 @@ var FlowBuilder = ({ readOnly = false }) => {
47444
48839
  ),
47445
48840
  /* @__PURE__ */ jsx(ChatPanel, {}),
47446
48841
  /* @__PURE__ */ jsx(LogsPanel, {}),
47447
- /* @__PURE__ */ jsxs(BuilderReadOnlyProvider, { value: isReadOnly, children: [
47448
- /* @__PURE__ */ jsx(NodeDetailView, {}),
47449
- /* @__PURE__ */ jsx(QuickConfigPanel, {})
47450
- ] }),
48842
+ /* @__PURE__ */ jsx(NodeDetailView, {}),
48843
+ /* @__PURE__ */ jsx(QuickConfigPanel, {}),
47451
48844
  !isReadOnly && /* @__PURE__ */ jsx(WebhookRunFollower, {}),
47452
48845
  /* @__PURE__ */ jsx(
47453
48846
  "div",
@@ -47459,7 +48852,7 @@ var FlowBuilder = ({ readOnly = false }) => {
47459
48852
  children: /* @__PURE__ */ jsx("div", { ref: floatingMenuWrapperRef, className: "relative size-full", children: /* @__PURE__ */ jsx(AddNodeFloatingMenu, { onNodeAdd: handleAddConnectedNode }) })
47460
48853
  }
47461
48854
  )
47462
- ] });
48855
+ ] }) });
47463
48856
  };
47464
48857
  function IntegrationReturnHandler() {
47465
48858
  const { setNodes, getNodes } = useReactFlow();
@@ -47551,6 +48944,7 @@ var FlowBuilderPage = ({
47551
48944
  const setWorkflow = useFlowStore((s) => s.actions.setWorkflow);
47552
48945
  const searchParams = new URLSearchParams(window.location.search);
47553
48946
  const isReadOnly = readOnly || searchParams.get("readonly") === "true";
48947
+ const leaseLocked = useEditLeaseLocked();
47554
48948
  const resetWorkflow = useFlowStore((s) => s.actions.resetWorkflow);
47555
48949
  const [isStoreReady, setIsStoreReady] = useState(false);
47556
48950
  const syncedWorkflowRef = useRef(null);
@@ -47591,9 +48985,9 @@ var FlowBuilderPage = ({
47591
48985
  /* @__PURE__ */ jsx(HistoricalRunLoader, {}),
47592
48986
  /* @__PURE__ */ jsx("div", { className: "flex flex-col h-full", children: /* @__PURE__ */ jsxs("div", { className: "relative flex grow min-h-0 divide-x divide-card-foreground/10", children: [
47593
48987
  /* @__PURE__ */ jsx("div", { className: "grow min-w-0 bg-card md:bg-transparent", children: /* @__PURE__ */ jsx(FlowBuilder, { readOnly: isReadOnly }) }),
47594
- !isReadOnly && /* @__PURE__ */ jsx(SidebarModule, {})
48988
+ !isReadOnly && !leaseLocked && /* @__PURE__ */ jsx(SidebarModule, {})
47595
48989
  ] }) })
47596
48990
  ] });
47597
48991
  };
47598
48992
 
47599
- export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, FlowBuilderPage, FluidGridsMark, Popover, PopoverContent, PopoverTrigger, RUNNER_INSTALL_SCRIPT_URL, RunOnSection, RunnerStatusPill, RunsUsageMeter, RunsUsageMeterView, TemplateCard, buildNodeCheck, buildRequiredNodeTypes, external_exports, readRunsQuota, runnerInstallCommand, runsQuotaLevel, summarizeRunsQuota, useImportWorkflowTemplate, useInstalledNodes, useRunnerActions, useRunners, useRunsQuota, useWorkflowTemplates };
48993
+ export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, FlowBuilderPage, FluidGridsMark, Popover, PopoverContent, PopoverTrigger, RUNNER_INSTALL_SCRIPT_URL, RunOnSection, RunnerStatusPill, RunsUsageMeter, RunsUsageMeterView, TemplateCard, buildNodeCheck, buildRequiredNodeTypes, external_exports, readRunsQuota, releaseLeaseKeptForReload, runnerInstallCommand, runsQuotaLevel, summarizeRunsQuota, useImportWorkflowTemplate, useInstalledNodes, useRunnerActions, useRunners, useRunsQuota, useWorkflowReloadStore, useWorkflowTemplates };