@burdenoff/fluidgrids-fe-sdk 2026.1029.7 → 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 (41) hide show
  1. package/dist/{ExecutionDetailPage-L7LZYFOT.js → ExecutionDetailPage-H6R3ZIXN.js} +10 -10
  2. package/dist/{ExecutionsListPage-7NMW6S5A.js → ExecutionsListPage-TLD2E442.js} +7 -7
  3. package/dist/{WorkflowBuilder-RXVUBDZJ.js → WorkflowBuilder-XVHY7XWJ.js} +10 -10
  4. package/dist/{WorkflowsList-5U74DYQ3.js → WorkflowsList-3GXXXURR.js} +7 -7
  5. package/dist/{chunk-FFOZ5VUJ.js → chunk-2NG5VZWI.js} +1 -1
  6. package/dist/{chunk-BZTVAUOQ.js → chunk-6CKDGT2N.js} +3 -3
  7. package/dist/{chunk-OJPJ2QXD.js → chunk-G3KZOX52.js} +2 -2
  8. package/dist/{chunk-YTUIGIVP.js → chunk-GQTEHWWY.js} +12 -8
  9. package/dist/{chunk-TYYSYSAL.js → chunk-H4AZBUWM.js} +1 -1
  10. package/dist/{chunk-AXMEATLG.js → chunk-HWEKWYXQ.js} +2 -2
  11. package/dist/{chunk-RZR3BDDZ.js → chunk-LJGC7RAL.js} +162 -99
  12. package/dist/{chunk-K4BF7PAW.js → chunk-NAYPOEYM.js} +290 -27
  13. package/dist/{chunk-VIZ7W5WJ.js → chunk-NNPXN4DI.js} +5 -5
  14. package/dist/{chunk-4MHWFZJV.js → chunk-NUBPALBK.js} +1 -1
  15. package/dist/{chunk-US4LPOB3.js → chunk-R7VHURPQ.js} +1 -1
  16. package/dist/{chunk-DEGCZCHP.js → chunk-S4JYBJ4X.js} +2 -2
  17. package/dist/{chunk-IDGDMJMJ.js → chunk-T22LICGX.js} +3 -3
  18. package/dist/{chunk-HAFUSBYP.js → chunk-TJ6RUV4R.js} +8 -8
  19. package/dist/{chunk-NL4JQFFA.js → chunk-UIDJT3YK.js} +1 -1
  20. package/dist/{chunk-7CTIVBBA.js → chunk-UWNCFMHV.js} +4 -4
  21. package/dist/{chunk-ZE5KS2YL.js → chunk-V5PA5KGV.js} +2 -2
  22. package/dist/{chunk-SZTKLZZR.js → chunk-VEZ7G2FE.js} +4 -4
  23. package/dist/{chunk-Z4YQTXCD.js → chunk-WEXO3KAY.js} +4 -4
  24. package/dist/{chunk-E7UAY3JJ.js → chunk-WTP343PG.js} +1 -1
  25. package/dist/{chunk-EGIEFGOV.js → chunk-X2J4ZXKD.js} +822 -62
  26. package/dist/{chunk-KANARCPR.js → chunk-XJBCRDWZ.js} +3 -3
  27. package/dist/{chunk-6SZXW2BF.js → chunk-XMKYZP6Q.js} +32 -15
  28. package/dist/{chunk-LAVHDFHI.js → chunk-Z6I6A47T.js} +1 -1
  29. package/dist/executions.js +11 -11
  30. package/dist/history.js +4 -4
  31. package/dist/index.d.ts +76 -3
  32. package/dist/index.js +34 -34
  33. package/dist/nodes.js +2 -2
  34. package/dist/observability.js +5 -5
  35. package/dist/settings.js +6 -6
  36. package/dist/templates.js +9 -9
  37. package/dist/{workflow-builder-CZFczcE7.d.ts → workflow-builder-BMGVSwpy.d.ts} +7 -0
  38. package/dist/workflow-builder.d.ts +1 -1
  39. package/dist/workflow-builder.js +11 -11
  40. package/dist/workflows-list.js +7 -7
  41. package/package.json +1 -1
@@ -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-DEGCZCHP.js';
6
- import { EntityTagPicker, useWorkflowProposal, WorkflowProposalCard, useRunHistory, RunHistoryView, SignalRunDialog } from './chunk-IDGDMJMJ.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-OJPJ2QXD.js';
9
- import { isAgentNodeType, AgentDefinitionSelectField } from './chunk-4MHWFZJV.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, useDurableParityEnabled, useConnections, ConnectionFormDialog, useFluidGridsFlag, FLUIDGRIDS_FLAGS, saveIndicatorOf, inAppLeaveTarget, declaredOutputFields, subscribeWorkflowSaved, pathToExpression, diagnosticsByPath, interfaceHandlers, declaredSignals, ExpressionEditor, toCodeMirrorDiagnostics, formatParameterValue, leaveNeedsConfirmation, buildOperationSeed, describeOperationError, snapshotForSave, editStateOf, 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 } from './chunk-K4BF7PAW.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
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, WorkflowVersioningDocument, WorkflowVersionDrainageDocument, ActivateWorkflowVersionCheckedDocument, WorkflowCodeSourceDocument, WorkflowEditLeaseStatusDocument, BuildWorkflowCodeDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument, SetWorkflowRolloutDocument, DeleteWorkflowRolloutDocument } from './chunk-YTUIGIVP.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';
@@ -30,6 +30,7 @@ import { useQuery, useMutation } from '@apollo/client/react';
30
30
  import { gql as gql$1 } from '@apollo/client/core';
31
31
  import 'jszip';
32
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
  }
@@ -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,6 +43528,7 @@ var ActivateWorkflowButton = ({
43055
43528
  });
43056
43529
  if (result) {
43057
43530
  setWorkflow({ status: result.status });
43531
+ rememberOwnStatusChange(result.id, result.revision);
43058
43532
  if (result.status === "ACTIVE") {
43059
43533
  toast.success("Workflow activated", {
43060
43534
  description: `Version ${result.version} is now ACTIVE. Future saves will create a new draft version.`,
@@ -43071,6 +43545,7 @@ var ActivateWorkflowButton = ({
43071
43545
  const result = await deactivateWorkflow(dbId);
43072
43546
  if (result) {
43073
43547
  setWorkflow({ status: result.status });
43548
+ rememberOwnStatusChange(result.id, result.revision);
43074
43549
  toast.success("Workflow deactivated", {
43075
43550
  description: `Version ${result.version} is now a DRAFT. Saves will update in-place.`,
43076
43551
  dismissible: true
@@ -43114,6 +43589,24 @@ var ActivateWorkflowButton = ({
43114
43589
  );
43115
43590
  };
43116
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
+
43117
43610
  // src/utils/workflow-definition-diff.ts
43118
43611
  function isRecord5(value) {
43119
43612
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -44029,10 +44522,12 @@ function VersionHistoryPanel({
44029
44522
  });
44030
44523
  if (version2 === currentVersion) {
44031
44524
  setWorkflow({ status: "ACTIVE" });
44525
+ rememberOwnStatusChange(result.id, result.revision);
44032
44526
  } else {
44033
44527
  const refreshed = await refetch();
44034
44528
  const current = refreshed.find((v) => v.version === currentVersion);
44035
44529
  if (current?.status) setWorkflow({ status: current.status });
44530
+ rememberOwnStatusChange(current?.id, current?.revision);
44036
44531
  }
44037
44532
  await refetch();
44038
44533
  } catch (error62) {
@@ -44062,7 +44557,11 @@ function VersionHistoryPanel({
44062
44557
  dismissible: true
44063
44558
  });
44064
44559
  if (version2 === currentVersion) setWorkflow({ status: result.status });
44065
- await Promise.all([refetch(), refetchVersioning()]);
44560
+ await Promise.all([
44561
+ refetch(),
44562
+ refetchVersioning(),
44563
+ refreshOpenRevision()
44564
+ ]);
44066
44565
  } catch (error62) {
44067
44566
  toast.error(
44068
44567
  tr(
@@ -44803,12 +45302,15 @@ function EnvironmentsPanel({
44803
45302
  const pending = view?.pendingPromotions ?? [];
44804
45303
  const policy = view?.policy;
44805
45304
  const dev = environments.find((state) => state.environment === "DEV");
44806
- const afterProdChange = (promotedVersion) => {
45305
+ const afterProdChange = async (promotedVersion) => {
44807
45306
  if (promotedVersion === currentVersion) {
44808
45307
  setWorkflow({ status: "ACTIVE" });
44809
45308
  } else if (openStatus === "ACTIVE") {
44810
45309
  setWorkflow({ status: "ARCHIVED" });
45310
+ } else {
45311
+ return;
44811
45312
  }
45313
+ await refreshOpenRevision();
44812
45314
  };
44813
45315
  const run = async (key, action, errorTitle) => {
44814
45316
  setBusy(key);
@@ -44863,7 +45365,8 @@ function EnvironmentsPanel({
44863
45365
  `${request.version} ${tr("fluidgrids.builder.environments.promotedTo", "promoted to")} ${envLabel(current.target)}`,
44864
45366
  { dismissible: true }
44865
45367
  );
44866
- if (current.target === "PROD") afterProdChange(request.version);
45368
+ if (current.target === "PROD")
45369
+ await afterProdChange(request.version);
44867
45370
  }
44868
45371
  },
44869
45372
  tr("fluidgrids.builder.environments.promoteError", "Promotion failed")
@@ -44879,7 +45382,7 @@ function EnvironmentsPanel({
44879
45382
  `${current.label} ${tr("fluidgrids.builder.environments.approved", "approved and deployed")}`,
44880
45383
  { dismissible: true }
44881
45384
  );
44882
- if (current.target === "PROD") afterProdChange(current.version);
45385
+ if (current.target === "PROD") await afterProdChange(current.version);
44883
45386
  },
44884
45387
  tr("fluidgrids.builder.environments.approveError", "Approval failed")
44885
45388
  );
@@ -46697,7 +47200,7 @@ function AiDraftPanel({
46697
47200
  ...template !== NO_TEMPLATE ? { template } : {}
46698
47201
  });
46699
47202
  }, [openVersion, proposal, template, trimmed, workflowKey]);
46700
- const openWorkflow = useCallback(
47203
+ const openWorkflow2 = useCallback(
46701
47204
  (key, version2) => {
46702
47205
  setOpen(false);
46703
47206
  proposal.reset();
@@ -46749,7 +47252,7 @@ function AiDraftPanel({
46749
47252
  { version: version2, newVersion: result.workflow.version }
46750
47253
  )
46751
47254
  );
46752
- openWorkflow(result.workflow.workflowKey, result.workflow.version);
47255
+ openWorkflow2(result.workflow.workflowKey, result.workflow.version);
46753
47256
  } catch (error62) {
46754
47257
  toast.error(
46755
47258
  tr(
@@ -46764,7 +47267,7 @@ function AiDraftPanel({
46764
47267
  setReverting(false);
46765
47268
  }
46766
47269
  },
46767
- [openWorkflow, restoreVersion, tr]
47270
+ [openWorkflow2, restoreVersion, tr]
46768
47271
  );
46769
47272
  const putOnCanvas = useCallback(() => {
46770
47273
  if (!current?.definitionJson) return;
@@ -46953,7 +47456,7 @@ function AiDraftPanel({
46953
47456
  onApply: () => void handleApply(),
46954
47457
  onReject: () => void handleReject(),
46955
47458
  onCancel: () => void proposal.cancel(),
46956
- onOpenWorkflow: openWorkflow,
47459
+ onOpenWorkflow: openWorkflow2,
46957
47460
  onRevert: (key, version2) => void handleRevert(key, version2)
46958
47461
  }
46959
47462
  ),
@@ -47879,6 +48382,236 @@ function CodeVersionNotice() {
47879
48382
  }
47880
48383
  );
47881
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
+ }
47882
48615
  var edgeTypes2 = {
47883
48616
  deletable: CustomDeletableEdge
47884
48617
  };
@@ -47895,9 +48628,38 @@ var edgeOptions = {
47895
48628
  strokeWidth: 1.5
47896
48629
  }
47897
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
+ }
47898
48659
  var FlowBuilder = ({ readOnly = false }) => {
47899
48660
  const searchParams = new URLSearchParams(window.location.search);
47900
48661
  const isReadOnly = readOnly || searchParams.get("readonly") === "true";
48662
+ const { leaseLocked, editingBlocked } = useEditingBlocked(isReadOnly);
47901
48663
  const durableFunctionsEnabled = useDurableFunctionNodesEnabled();
47902
48664
  const durableFunctionsEnabledRef = useRef(durableFunctionsEnabled);
47903
48665
  useEffect(() => {
@@ -47909,7 +48671,7 @@ var FlowBuilder = ({ readOnly = false }) => {
47909
48671
  }),
47910
48672
  []
47911
48673
  );
47912
- useAgentTools(agentTools, !isReadOnly);
48674
+ useAgentTools(agentTools, !editingBlocked);
47913
48675
  const [nodes, edges, onNodesChange, onEdgesChange, onConnect] = useFlowStore(
47914
48676
  useShallow((s) => {
47915
48677
  return [
@@ -48001,10 +48763,11 @@ var FlowBuilder = ({ readOnly = false }) => {
48001
48763
  onNodesChange
48002
48764
  ]
48003
48765
  );
48004
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full w-full overflow-hidden", children: [
48005
- !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, {}),
48006
48768
  !isReadOnly && /* @__PURE__ */ jsx(CodeVersionNotice, {}),
48007
- /* @__PURE__ */ jsx(BuilderSaveController, { readOnly: isReadOnly }),
48769
+ leaseLocked && /* @__PURE__ */ jsx(EditLeaseBanner, {}),
48770
+ /* @__PURE__ */ jsx(BuilderSaveController, { readOnly: isReadOnly, locked: leaseLocked }),
48008
48771
  /* @__PURE__ */ jsxs(
48009
48772
  "div",
48010
48773
  {
@@ -48038,21 +48801,19 @@ var FlowBuilder = ({ readOnly = false }) => {
48038
48801
  edgeTypes: edgeTypes2,
48039
48802
  edges,
48040
48803
  onEdgesChange,
48041
- onConnect: isReadOnly ? void 0 : onConnect,
48042
- onConnectEnd: isReadOnly ? void 0 : handleOnEdgeDropConnectEnd,
48043
- onDrop: isReadOnly ? void 0 : onDrop,
48044
- onDragOver: isReadOnly ? void 0 : onDragOver,
48804
+ ...canvasEditingProps(editingBlocked, {
48805
+ onConnect,
48806
+ onConnectEnd: handleOnEdgeDropConnectEnd,
48807
+ onDrop,
48808
+ onDragOver,
48809
+ deleteKeyCode
48810
+ }),
48045
48811
  onNodeDragStop: (_, node2) => {
48046
48812
  autoAdjustNode(node2);
48047
48813
  },
48048
48814
  onPaneClick: closeQuickConfig,
48049
48815
  onNodesDelete,
48050
48816
  isValidConnection,
48051
- nodesDraggable: !isReadOnly,
48052
- nodesConnectable: !isReadOnly,
48053
- elementsSelectable: !isReadOnly,
48054
- multiSelectionKeyCode: isReadOnly ? null : ["Meta", "Control"],
48055
- deleteKeyCode: isReadOnly ? null : deleteKeyCode,
48056
48817
  snapGrid: [16, 16],
48057
48818
  snapToGrid: true,
48058
48819
  fitView: true,
@@ -48062,7 +48823,7 @@ var FlowBuilder = ({ readOnly = false }) => {
48062
48823
  }
48063
48824
  ),
48064
48825
  /* @__PURE__ */ jsx(FluidGridsMark, {}),
48065
- 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: [
48066
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(
48067
48828
  Icon,
48068
48829
  {
@@ -48078,10 +48839,8 @@ var FlowBuilder = ({ readOnly = false }) => {
48078
48839
  ),
48079
48840
  /* @__PURE__ */ jsx(ChatPanel, {}),
48080
48841
  /* @__PURE__ */ jsx(LogsPanel, {}),
48081
- /* @__PURE__ */ jsxs(BuilderReadOnlyProvider, { value: isReadOnly, children: [
48082
- /* @__PURE__ */ jsx(NodeDetailView, {}),
48083
- /* @__PURE__ */ jsx(QuickConfigPanel, {})
48084
- ] }),
48842
+ /* @__PURE__ */ jsx(NodeDetailView, {}),
48843
+ /* @__PURE__ */ jsx(QuickConfigPanel, {}),
48085
48844
  !isReadOnly && /* @__PURE__ */ jsx(WebhookRunFollower, {}),
48086
48845
  /* @__PURE__ */ jsx(
48087
48846
  "div",
@@ -48093,7 +48852,7 @@ var FlowBuilder = ({ readOnly = false }) => {
48093
48852
  children: /* @__PURE__ */ jsx("div", { ref: floatingMenuWrapperRef, className: "relative size-full", children: /* @__PURE__ */ jsx(AddNodeFloatingMenu, { onNodeAdd: handleAddConnectedNode }) })
48094
48853
  }
48095
48854
  )
48096
- ] });
48855
+ ] }) });
48097
48856
  };
48098
48857
  function IntegrationReturnHandler() {
48099
48858
  const { setNodes, getNodes } = useReactFlow();
@@ -48185,6 +48944,7 @@ var FlowBuilderPage = ({
48185
48944
  const setWorkflow = useFlowStore((s) => s.actions.setWorkflow);
48186
48945
  const searchParams = new URLSearchParams(window.location.search);
48187
48946
  const isReadOnly = readOnly || searchParams.get("readonly") === "true";
48947
+ const leaseLocked = useEditLeaseLocked();
48188
48948
  const resetWorkflow = useFlowStore((s) => s.actions.resetWorkflow);
48189
48949
  const [isStoreReady, setIsStoreReady] = useState(false);
48190
48950
  const syncedWorkflowRef = useRef(null);
@@ -48225,9 +48985,9 @@ var FlowBuilderPage = ({
48225
48985
  /* @__PURE__ */ jsx(HistoricalRunLoader, {}),
48226
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: [
48227
48987
  /* @__PURE__ */ jsx("div", { className: "grow min-w-0 bg-card md:bg-transparent", children: /* @__PURE__ */ jsx(FlowBuilder, { readOnly: isReadOnly }) }),
48228
- !isReadOnly && /* @__PURE__ */ jsx(SidebarModule, {})
48988
+ !isReadOnly && !leaseLocked && /* @__PURE__ */ jsx(SidebarModule, {})
48229
48989
  ] }) })
48230
48990
  ] });
48231
48991
  };
48232
48992
 
48233
- 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 };