@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
@@ -1,4 +1,4 @@
1
- import { NodeEndpointConfigsDocument, NodeEndpointConfigDocument, NodeManifestDocument, fetchAllNodeEndpointConfigs } from './chunk-7PP6CRXS.js';
1
+ import { NodeEndpointConfigsDocument, NodeEndpointConfigDocument, NodeManifestDocument, fetchAllNodeEndpointConfigs } from './chunk-GQTEHWWY.js';
2
2
  import { useTr } from './chunk-UULVIT25.js';
3
3
  import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
4
4
  import { useMemo, useState, useCallback, useEffect } from 'react';
@@ -1,7 +1,7 @@
1
1
  import { REPLAY_WORKFLOW_RUN, RETRY_WORKFLOW_RUNS } from './chunk-5ERAJC2I.js';
2
- import { useFluidGridsAnalytics, throwIfResultError, emitRunStarted, openRunQuotaUpgradePrompt } from './chunk-4TGUNOQQ.js';
3
- import { useExecutionsStore } from './chunk-CY6KX4RG.js';
4
- import { ExecutionsListDocument, ExecutionDetailDocument, ExecutionStatsDocument, PauseExecutionDocument, ResumeExecutionDocument, CancelExecutionDocument, RetryNodeDocument, TriggerWorkflowDocument, TerminateExecutionDocument } from './chunk-7PP6CRXS.js';
2
+ import { useFluidGridsAnalytics, throwIfResultError, emitRunStarted, openRunQuotaUpgradePrompt } from './chunk-NAYPOEYM.js';
3
+ import { useExecutionsStore } from './chunk-6AMNEX2Y.js';
4
+ import { ExecutionsListDocument, ExecutionDetailDocument, ExecutionStatsDocument, PauseExecutionDocument, ResumeExecutionDocument, CancelExecutionDocument, RetryNodeDocument, TriggerWorkflowDocument, TerminateExecutionDocument } from './chunk-GQTEHWWY.js';
5
5
  import { useTr } from './chunk-UULVIT25.js';
6
6
  import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
7
7
  import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
@@ -75,6 +75,107 @@ function dateRangeToInput(start, end) {
75
75
  return input;
76
76
  }
77
77
 
78
+ // src/modules/executions/utils/run-parity.ts
79
+ function isRecord(value) {
80
+ return typeof value === "object" && value !== null && !Array.isArray(value);
81
+ }
82
+ function text(value) {
83
+ return typeof value === "string" && value.trim() ? value : void 0;
84
+ }
85
+ function runChainLinks(run) {
86
+ const candidates = [
87
+ ["previous", run.continuedFromRunId],
88
+ ["next", run.continuedToRunId],
89
+ ["first", run.firstRunId],
90
+ ["root", run.rootRunId === run.parentRunId ? null : run.rootRunId]
91
+ ];
92
+ const seen = /* @__PURE__ */ new Set([run.id]);
93
+ const links = [];
94
+ for (const [kind, runId] of candidates) {
95
+ if (!runId || seen.has(runId)) continue;
96
+ seen.add(runId);
97
+ links.push({ kind, runId });
98
+ }
99
+ return links;
100
+ }
101
+ function shortRunId(runId, length = 12) {
102
+ return runId.length > length ? `${runId.slice(0, length)}\u2026` : runId;
103
+ }
104
+ function handlersOf(value) {
105
+ const entries = Array.isArray(value) ? value.map((entry) => [void 0, entry]) : isRecord(value) ? Object.entries(value) : [];
106
+ const handlers = [];
107
+ for (const [key, entry] of entries) {
108
+ if (typeof entry === "string") {
109
+ handlers.push({ name: entry });
110
+ continue;
111
+ }
112
+ if (!isRecord(entry)) continue;
113
+ const name = text(entry.name) ?? key;
114
+ if (!name) continue;
115
+ const description = text(entry.description);
116
+ handlers.push(description ? { name, description } : { name });
117
+ }
118
+ return handlers;
119
+ }
120
+ function parseMetadataDefinition(definition) {
121
+ const value = isRecord(definition) ? definition : {};
122
+ return {
123
+ name: text(value.name),
124
+ signals: handlersOf(value.signals),
125
+ queries: handlersOf(value.queries),
126
+ updates: handlersOf(value.updates)
127
+ };
128
+ }
129
+ var WORKFLOW_ID_MAX_LENGTH = 256;
130
+ function withWorkflowIdFilter(input, workflowId, parityEnabled) {
131
+ const value = workflowId.trim();
132
+ if (!parityEnabled || !value) return input;
133
+ return { ...input, businessId: value.slice(0, WORKFLOW_ID_MAX_LENGTH) };
134
+ }
135
+ function statusCountFilter(input) {
136
+ const rest = { ...input };
137
+ delete rest.limit;
138
+ delete rest.offset;
139
+ delete rest.status;
140
+ return rest;
141
+ }
142
+ var STATUS_COUNT_GROUP_BY = ["STATUS" /* Status */];
143
+ function statusCountsOf(groups) {
144
+ const counts = /* @__PURE__ */ new Map();
145
+ for (const group of groups) {
146
+ const status = group.values[0] ?? group.key;
147
+ if (!status) continue;
148
+ counts.set(status, (counts.get(status) ?? 0) + group.count);
149
+ }
150
+ return [...counts.entries()].map(([status, count]) => ({ status, count })).sort((a, b) => b.count - a.count || a.status.localeCompare(b.status));
151
+ }
152
+ function overrideRequestOf(choice, version) {
153
+ if (choice === "remove") return { ok: true, override: null };
154
+ if (choice === "autoUpgrade")
155
+ return {
156
+ ok: true,
157
+ override: { behavior: "AUTO_UPGRADE" /* AutoUpgrade */ }
158
+ };
159
+ const pinned = version.trim();
160
+ if (!pinned) return { ok: false, reason: "versionRequired" };
161
+ return {
162
+ ok: true,
163
+ override: { behavior: "PINNED" /* Pinned */, version: pinned }
164
+ };
165
+ }
166
+ function callbackTone(status) {
167
+ switch (status) {
168
+ case "DELIVERED":
169
+ return "success";
170
+ case "FAILED":
171
+ return "error";
172
+ case "DELIVERING":
173
+ return "info";
174
+ default:
175
+ return "neutral";
176
+ }
177
+ }
178
+
78
179
  // src/modules/executions/utils/retry.ts
79
180
  var NON_REPLAYABLE_CONTEXT_KEYS = ["isScheduled", "workspaceId"];
80
181
  function buildRetryTriggerInput(run) {
@@ -443,7 +544,9 @@ function useExecutionActions() {
443
544
  };
444
545
  }
445
546
  var EXECUTIONS_SEARCH_DEBOUNCE_MS = 350;
446
- function useExecutionsWithStore() {
547
+ function useExecutionsWithStore({
548
+ durableParity = false
549
+ } = {}) {
447
550
  const { filter, pagination, setTotal } = useExecutionsStore();
448
551
  const [debouncedSearch, setDebouncedSearch] = useState(filter.search);
449
552
  useEffect(() => {
@@ -490,15 +593,21 @@ function useExecutionsWithStore() {
490
593
  if (filter.tagId) {
491
594
  baseInput.tagIds = [filter.tagId];
492
595
  }
493
- return baseInput;
494
- }, [filter, debouncedSearch, pagination.page, pagination.pageSize]);
596
+ return withWorkflowIdFilter(baseInput, filter.workflowId, durableParity);
597
+ }, [
598
+ filter,
599
+ debouncedSearch,
600
+ pagination.page,
601
+ pagination.pageSize,
602
+ durableParity
603
+ ]);
495
604
  const result = useExecutions(input);
496
605
  useEffect(() => {
497
606
  if (result.total !== pagination.total) {
498
607
  setTotal(result.total);
499
608
  }
500
609
  }, [result.total, pagination.total, setTotal]);
501
- return result;
610
+ return { ...result, input };
502
611
  }
503
612
  function useAutoRefresh(refetchFn, enabled = true, interval = 5e3) {
504
613
  useEffect(() => {
@@ -510,4 +619,4 @@ function useAutoRefresh(refetchFn, enabled = true, interval = 5e3) {
510
619
  }, [enabled, interval, refetchFn]);
511
620
  }
512
621
 
513
- export { useAutoRefresh, useExecution, useExecutionActions, useExecutionStats, useExecutions, useExecutionsWithStore, validateDateRange };
622
+ export { STATUS_COUNT_GROUP_BY, WORKFLOW_ID_MAX_LENGTH, callbackTone, overrideRequestOf, parseMetadataDefinition, runChainLinks, shortRunId, statusCountFilter, statusCountsOf, useAutoRefresh, useExecution, useExecutionActions, useExecutionStats, useExecutions, useExecutionsWithStore, validateDateRange };
@@ -1,18 +1,18 @@
1
- import { isGuidedTourIntent, pickGuidedTour, withoutGuidedTourIntent, isCreateIntent, CreateWorkflowRefusalDialog, decideMissingWorkflow } from './chunk-MNY2UNQH.js';
1
+ import { isGuidedTourIntent, pickGuidedTour, withoutGuidedTourIntent, isCreateIntent, CreateWorkflowRefusalDialog, decideMissingWorkflow } from './chunk-2NG5VZWI.js';
2
2
  import { registerLiveCanvasHandler } from './chunk-PIKYQE7P.js';
3
- import { useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-Z6EWB5ZG.js';
4
- import { useLazyWorkflowDefinition, useCreateWorkflow } from './chunk-LTVAUZ3M.js';
5
- import { useFlowStore, refetchStoreNodes, BILLING_PLANS_PATH, codeBuildIdOf, mergeStickyNotes, blankWorkflowCreateInput, classifyCreateRefusal, useSaveStatusStore, workflowFingerprint, registerWorkflowNodeTypes, throwIfResultError } from './chunk-4TGUNOQQ.js';
6
- import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
7
- import { WorkflowDefinitionDocument } from './chunk-7PP6CRXS.js';
3
+ import { useWorkflowReloadStore, releaseLeaseKeptForReload, useInstalledNodes, buildNodeCheck, FlowBuilderPage, buildRequiredNodeTypes } from './chunk-X2J4ZXKD.js';
4
+ import { useLazyWorkflowDefinition, useCreateWorkflow } from './chunk-G3KZOX52.js';
5
+ import { useFlowStore, refetchStoreNodes, BILLING_PLANS_PATH, codeBuildIdOf, mergeStickyNotes, rememberRevision, blankWorkflowCreateInput, classifyCreateRefusal, useSaveStatusStore, workflowFingerprint, registerWorkflowNodeTypes, throwIfResultError } from './chunk-NAYPOEYM.js';
6
+ import { getFluidGridsConfig } from './chunk-6AMNEX2Y.js';
7
+ import { WorkflowDefinitionDocument } from './chunk-GQTEHWWY.js';
8
8
  import { useTr } from './chunk-UULVIT25.js';
9
9
  import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
10
10
  import { useLocation, useNavigate, useParams } from 'react-router';
11
11
  import { toast } from 'sonner';
12
12
  import { Icon } from '@iconify/react';
13
13
  import { lazy, useRef, useEffect, useState, useCallback, useMemo, Suspense } from 'react';
14
- import { useSafeToursContext, usePlayTour } from '@burdenoff/fe-libs/shared/tours';
15
14
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+ import { useSafeToursContext, usePlayTour } from '@burdenoff/fe-libs/shared/tours';
16
16
  import { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';
17
17
  import '@xyflow/react/dist/style.css';
18
18
 
@@ -653,7 +653,11 @@ function AssistantLiveCanvasHost() {
653
653
  fetchDefinition,
654
654
  registerNodeTypes: registerWorkflowNodeTypes,
655
655
  notify,
656
- workspaceId: () => getFluidGridsConfig().workspaceId || null
656
+ workspaceId: () => getFluidGridsConfig().workspaceId || null,
657
+ // The merged canvas now holds the stored version, assistant write
658
+ // included, so the next save is based on its revision (BOFF-9725)
659
+ // rather than refused as stale over a change already on screen.
660
+ onSynced: (remote) => rememberRevision(remote.id, remote.revision)
657
661
  });
658
662
  const stopWatching = watchCanvasBase();
659
663
  const unregister = registerLiveCanvasHandler(sync.handle);
@@ -664,6 +668,48 @@ function AssistantLiveCanvasHost() {
664
668
  }, []);
665
669
  return null;
666
670
  }
671
+ function LoadProblem({
672
+ title,
673
+ description,
674
+ retryLabel,
675
+ onRetry,
676
+ backLabel,
677
+ onBack,
678
+ testId
679
+ }) {
680
+ return /* @__PURE__ */ jsxs(
681
+ "div",
682
+ {
683
+ role: "alert",
684
+ "data-testid": testId,
685
+ className: "flex h-full min-h-[60vh] w-full flex-col items-center justify-center gap-3 p-6 text-center",
686
+ children: [
687
+ /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-foreground", children: title }),
688
+ /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-foreground/60", children: description }),
689
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
690
+ /* @__PURE__ */ jsx(
691
+ "button",
692
+ {
693
+ type: "button",
694
+ className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
695
+ onClick: onRetry,
696
+ children: retryLabel
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsx(
700
+ "button",
701
+ {
702
+ type: "button",
703
+ className: "rounded-md border px-4 py-2 text-sm font-medium text-foreground",
704
+ onClick: onBack,
705
+ children: backLabel
706
+ }
707
+ )
708
+ ] })
709
+ ]
710
+ }
711
+ );
712
+ }
667
713
  var START_DELAY_MS = 800;
668
714
  function StartGuidedTour() {
669
715
  const location = useLocation();
@@ -736,11 +782,17 @@ function WorkflowItem({
736
782
  const [createRefusal, setCreateRefusal] = useState(null);
737
783
  const upgradingRef = useRef(false);
738
784
  const resetWorkflow = useFlowStore((s) => s.actions.resetWorkflow);
785
+ const reloadNonce = useWorkflowReloadStore((s) => s.nonce);
786
+ useEffect(() => () => releaseLeaseKeptForReload(), [id, version]);
787
+ const seenReloadNonceRef = useRef(reloadNonce);
788
+ const pendingReloadRef = useRef(null);
789
+ const [reloadFailed, setReloadFailed] = useState(false);
739
790
  const { getWorkflow } = useLazyWorkflowDefinition();
740
791
  const { createWorkflow } = useCreateWorkflow();
741
792
  const creatingRef = useRef(false);
742
793
  const fromSharedLoadedRef = useRef(false);
743
794
  const fetchedWorkflowRef = useRef(null);
795
+ const loadSeqRef = useRef(0);
744
796
  const { installedAppIds } = useInstalledNodes();
745
797
  const [pendingWorkflow, setPendingWorkflow] = useState(null);
746
798
  const [nodeCheck, setNodeCheck] = useState(
@@ -770,7 +822,7 @@ function WorkflowItem({
770
822
  try {
771
823
  const { getGlobalStoreClient } = await import('./global-client-KLOVTVPP.js');
772
824
  const { InstallWorkflowNodeDocument } = await import('./global-operations-D2USVOPM.js');
773
- const { getFluidGridsConfig: getFluidGridsConfig2 } = await import('./config-T7BQ7AO2.js');
825
+ const { getFluidGridsConfig: getFluidGridsConfig2 } = await import('./config-VM7FTHMZ.js');
774
826
  const storeClient = getGlobalStoreClient();
775
827
  const cfg = getFluidGridsConfig2();
776
828
  for (let i = 0; i < nodesToInstall.length; i++) {
@@ -843,11 +895,27 @@ function WorkflowItem({
843
895
  );
844
896
  useEffect(() => {
845
897
  let mounted = true;
898
+ const target = `${id ?? ""}@${version ?? ""}`;
899
+ loadSeqRef.current += 1;
900
+ const loadSeq = loadSeqRef.current;
901
+ if (reloadNonce !== seenReloadNonceRef.current) {
902
+ seenReloadNonceRef.current = reloadNonce;
903
+ pendingReloadRef.current = target;
904
+ }
905
+ const reloadPending = () => pendingReloadRef.current === target;
906
+ const fetchedCopy = () => fetchedWorkflowRef.current?.target === target ? fetchedWorkflowRef.current.workflow : null;
907
+ const recordFetched = (workflow2) => {
908
+ if (loadSeqRef.current !== loadSeq) return false;
909
+ fetchedWorkflowRef.current = { target, workflow: workflow2 };
910
+ if (pendingReloadRef.current === target) pendingReloadRef.current = null;
911
+ return true;
912
+ };
846
913
  const loadWorkflow = async () => {
847
914
  try {
848
915
  resetWorkflow();
849
916
  if (mounted) setNotFound(false);
850
917
  if (mounted) setLoadError(null);
918
+ if (mounted) setReloadFailed(false);
851
919
  if (version && mounted) {
852
920
  setIsLoading(true);
853
921
  }
@@ -904,7 +972,7 @@ function WorkflowItem({
904
972
  }
905
973
  return;
906
974
  }
907
- const freshCreate = createIntentRef.current && !creatingRef.current && !fetchedWorkflowRef.current && !loadRunId;
975
+ const freshCreate = createIntentRef.current && !creatingRef.current && !fetchedCopy() && !loadRunId;
908
976
  const result = freshCreate ? null : await getWorkflow(id, void 0, version);
909
977
  const definitionJson = result?.definitionJson;
910
978
  const defData = definitionJson?.data ?? (definitionJson?.nodes ? definitionJson : void 0);
@@ -925,7 +993,9 @@ function WorkflowItem({
925
993
  // new version (spec code-first 06 §1).
926
994
  codeBuildId: codeBuildIdOf(result?.definitionJson)
927
995
  };
928
- fetchedWorkflowRef.current = workflowState;
996
+ if (recordFetched(workflowState)) {
997
+ rememberRevision(result?.id, result?.revision);
998
+ }
929
999
  let requiredNodeTypes = [];
930
1000
  try {
931
1001
  requiredNodeTypes = await buildRequiredNodeTypes(
@@ -949,8 +1019,14 @@ function WorkflowItem({
949
1019
  return;
950
1020
  }
951
1021
  if (mounted) {
952
- if (fetchedWorkflowRef.current) {
953
- setWorkflow(fetchedWorkflowRef.current);
1022
+ if (reloadPending()) {
1023
+ setReloadFailed(true);
1024
+ releaseLeaseKeptForReload();
1025
+ return;
1026
+ }
1027
+ const earlier = fetchedCopy();
1028
+ if (earlier) {
1029
+ setWorkflow(earlier);
954
1030
  return;
955
1031
  }
956
1032
  if (loadRunId) {
@@ -999,16 +1075,18 @@ function WorkflowItem({
999
1075
  if (mounted) setCreateRefusal(classifyCreateRefusal(reported));
1000
1076
  return;
1001
1077
  }
1002
- if (mounted) {
1003
- setWorkflow({
1004
- ...newWorkflow,
1005
- id: created.workflowKey,
1006
- workflowKey: created.workflowKey,
1007
- version: created.version,
1008
- dbId: created.id,
1009
- status: created.status ?? "DRAFT"
1010
- });
1078
+ const createdWorkflow = {
1079
+ ...newWorkflow,
1080
+ id: created.workflowKey,
1081
+ workflowKey: created.workflowKey,
1082
+ version: created.version,
1083
+ dbId: created.id,
1084
+ status: created.status ?? "DRAFT"
1085
+ };
1086
+ if (recordFetched(createdWorkflow)) {
1087
+ rememberRevision(created.id, created.revision);
1011
1088
  }
1089
+ if (mounted) setWorkflow(createdWorkflow);
1012
1090
  }
1013
1091
  }
1014
1092
  } catch (error) {
@@ -1018,8 +1096,11 @@ function WorkflowItem({
1018
1096
  setLoadError(
1019
1097
  "The workflow version for this TEST run could not be loaded."
1020
1098
  );
1099
+ } else if (reloadPending()) {
1100
+ setReloadFailed(true);
1101
+ releaseLeaseKeptForReload();
1021
1102
  } else {
1022
- setWorkflow(fetchedWorkflowRef.current ?? newWorkflow);
1103
+ setWorkflow(fetchedCopy() ?? newWorkflow);
1023
1104
  }
1024
1105
  }
1025
1106
  } finally {
@@ -1042,7 +1123,8 @@ function WorkflowItem({
1042
1123
  resetWorkflow,
1043
1124
  navigate,
1044
1125
  loadRunId,
1045
- retryNonce
1126
+ retryNonce,
1127
+ reloadNonce
1046
1128
  // doNodeCheckRef is intentionally omitted — it's a ref (stable by definition)
1047
1129
  // and including doNodeCheck here caused infinite re-runs because installedAppIds
1048
1130
  // produces a new Set reference on every render.
@@ -1083,89 +1165,70 @@ function WorkflowItem({
1083
1165
  /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground/60", children: "Loading workflow..." })
1084
1166
  ] }) })
1085
1167
  );
1168
+ const retryLabel = tr(
1169
+ "fluidgrids.builder.workflowNotFound.retry",
1170
+ "Try again"
1171
+ );
1172
+ const backLabel = tr(
1173
+ "fluidgrids.builder.workflowNotFound.back",
1174
+ "Back to Workflows"
1175
+ );
1086
1176
  if (createRefusal?.kind === "failed")
1087
- return /* @__PURE__ */ jsxs(
1088
- "div",
1177
+ return /* @__PURE__ */ jsx(
1178
+ LoadProblem,
1089
1179
  {
1090
- role: "alert",
1091
- className: "flex h-full min-h-[60vh] w-full flex-col items-center justify-center gap-3 p-6 text-center",
1092
- children: [
1093
- /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-foreground", children: tr(
1094
- "fluidgrids.builder.workflowNotCreated.title",
1095
- "Workflow not created"
1096
- ) }),
1097
- /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-foreground/60", children: tr(
1098
- "fluidgrids.builder.workflowNotCreated.description",
1099
- "The new workflow could not be created, so there is nothing to edit yet."
1100
- ) }),
1101
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1102
- /* @__PURE__ */ jsx(
1103
- "button",
1104
- {
1105
- type: "button",
1106
- className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
1107
- onClick: () => {
1108
- setCreateRefusal(null);
1109
- setRetryNonce((n) => n + 1);
1110
- },
1111
- children: tr("fluidgrids.builder.workflowNotFound.retry", "Try again")
1112
- }
1113
- ),
1114
- /* @__PURE__ */ jsx(
1115
- "button",
1116
- {
1117
- type: "button",
1118
- className: "rounded-md border px-4 py-2 text-sm font-medium text-foreground",
1119
- onClick: () => navigate("../list", { replace: true }),
1120
- children: tr(
1121
- "fluidgrids.builder.workflowNotFound.back",
1122
- "Back to Workflows"
1123
- )
1124
- }
1125
- )
1126
- ] })
1127
- ]
1180
+ title: tr(
1181
+ "fluidgrids.builder.workflowNotCreated.title",
1182
+ "Workflow not created"
1183
+ ),
1184
+ description: tr(
1185
+ "fluidgrids.builder.workflowNotCreated.description",
1186
+ "The new workflow could not be created, so there is nothing to edit yet."
1187
+ ),
1188
+ retryLabel,
1189
+ onRetry: () => {
1190
+ setCreateRefusal(null);
1191
+ setRetryNonce((n) => n + 1);
1192
+ },
1193
+ backLabel,
1194
+ onBack: () => navigate("../list", { replace: true })
1128
1195
  }
1129
1196
  );
1130
1197
  if (notFound)
1131
- return /* @__PURE__ */ jsxs(
1132
- "div",
1198
+ return /* @__PURE__ */ jsx(
1199
+ LoadProblem,
1133
1200
  {
1134
- role: "alert",
1135
- className: "flex h-full min-h-[60vh] w-full flex-col items-center justify-center gap-3 p-6 text-center",
1136
- children: [
1137
- /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-foreground", children: tr(
1138
- "fluidgrids.builder.workflowNotFound.title",
1139
- "Workflow not found"
1140
- ) }),
1141
- /* @__PURE__ */ jsx("p", { className: "max-w-md text-sm text-foreground/60", children: tr(
1142
- "fluidgrids.builder.workflowNotFound.description",
1143
- "No workflow with this key exists in this workspace, or it could not be loaded. Nothing was created."
1144
- ) }),
1145
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1146
- /* @__PURE__ */ jsx(
1147
- "button",
1148
- {
1149
- type: "button",
1150
- className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
1151
- onClick: () => setRetryNonce((n) => n + 1),
1152
- children: tr("fluidgrids.builder.workflowNotFound.retry", "Try again")
1153
- }
1154
- ),
1155
- /* @__PURE__ */ jsx(
1156
- "button",
1157
- {
1158
- type: "button",
1159
- className: "rounded-md border px-4 py-2 text-sm font-medium text-foreground",
1160
- onClick: () => navigate("../list"),
1161
- children: tr(
1162
- "fluidgrids.builder.workflowNotFound.back",
1163
- "Back to Workflows"
1164
- )
1165
- }
1166
- )
1167
- ] })
1168
- ]
1201
+ title: tr(
1202
+ "fluidgrids.builder.workflowNotFound.title",
1203
+ "Workflow not found"
1204
+ ),
1205
+ description: tr(
1206
+ "fluidgrids.builder.workflowNotFound.description",
1207
+ "No workflow with this key exists in this workspace, or it could not be loaded. Nothing was created."
1208
+ ),
1209
+ retryLabel,
1210
+ onRetry: () => setRetryNonce((n) => n + 1),
1211
+ backLabel,
1212
+ onBack: () => navigate("../list")
1213
+ }
1214
+ );
1215
+ if (reloadFailed)
1216
+ return /* @__PURE__ */ jsx(
1217
+ LoadProblem,
1218
+ {
1219
+ testId: "fluidgrids-builder-reload-failed",
1220
+ title: tr(
1221
+ "fluidgrids.builder.reloadFailed.title",
1222
+ "The latest version did not load"
1223
+ ),
1224
+ description: tr(
1225
+ "fluidgrids.builder.reloadFailed.description",
1226
+ "The latest saved version of this workflow could not be loaded, so nothing older is shown in its place. Check your connection and try again."
1227
+ ),
1228
+ retryLabel,
1229
+ onRetry: () => setRetryNonce((n) => n + 1),
1230
+ backLabel,
1231
+ onBack: () => navigate("../list")
1169
1232
  }
1170
1233
  );
1171
1234
  return /* @__PURE__ */ jsxs(Fragment, { children: [