@burdenoff/fluidgrids-fe-sdk 2026.1024.2 → 2026.1025.1

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 (36) hide show
  1. package/dist/{ExecutionDetailPage-RF5NR2ND.js → ExecutionDetailPage-EQ6HXXNL.js} +7 -7
  2. package/dist/{ExecutionsListPage-L447L5LX.js → ExecutionsListPage-ADIHJTMM.js} +7 -7
  3. package/dist/{WorkflowBuilder-AWCE62C7.js → WorkflowBuilder-BHVJKRDQ.js} +9 -9
  4. package/dist/{WorkflowsList-QFVCVGRY.js → WorkflowsList-76GI5W5U.js} +7 -7
  5. package/dist/{chunk-GCOWR7J4.js → chunk-543CP5ZX.js} +1 -1
  6. package/dist/{chunk-PMQECGSD.js → chunk-54MTXYK7.js} +3 -3
  7. package/dist/{chunk-VZQSCUD4.js → chunk-75OEGB3L.js} +3 -3
  8. package/dist/{chunk-GV76OCBP.js → chunk-7ETY5Z7E.js} +4 -4
  9. package/dist/{chunk-IZMYI6QO.js → chunk-7LJ3DYEG.js} +884 -26
  10. package/dist/{chunk-L37ZAZE4.js → chunk-AVLMQJV2.js} +2 -2
  11. package/dist/{chunk-4H7MRDNX.js → chunk-B2KYIXMD.js} +18 -5
  12. package/dist/{chunk-XFVNTWM2.js → chunk-BNA2HFGB.js} +1 -1
  13. package/dist/{chunk-XXAVXVBW.js → chunk-BP4WGVSM.js} +1 -1
  14. package/dist/{chunk-GD7CSKFD.js → chunk-FLKTTSRJ.js} +9 -1
  15. package/dist/{chunk-S5ZKDRBV.js → chunk-FOBH2IH2.js} +2 -2
  16. package/dist/{chunk-T7X3HDNX.js → chunk-FWMFZKTI.js} +1 -1
  17. package/dist/{chunk-3GHYYFBA.js → chunk-L4XUHOOH.js} +5 -5
  18. package/dist/{chunk-I2DSSBIK.js → chunk-M5WJPAZM.js} +5 -5
  19. package/dist/{chunk-QIAXIFLW.js → chunk-NOEVRDDJ.js} +2 -2
  20. package/dist/{chunk-7PC3ZBYU.js → chunk-OAZNRMRR.js} +3 -3
  21. package/dist/{chunk-7ST3XB36.js → chunk-OIB44CIF.js} +1 -1
  22. package/dist/{chunk-65DVJILQ.js → chunk-QBHWMRCT.js} +2 -2
  23. package/dist/{chunk-OQEQ3AIA.js → chunk-RYNCNNTL.js} +1 -1
  24. package/dist/{chunk-INHBJPVE.js → chunk-VBZ62DJY.js} +1 -1
  25. package/dist/{chunk-DSBU5MH4.js → chunk-YJ25AFPK.js} +1 -1
  26. package/dist/executions.js +9 -9
  27. package/dist/history.js +4 -4
  28. package/dist/index.d.ts +360 -1
  29. package/dist/index.js +31 -31
  30. package/dist/nodes.js +2 -2
  31. package/dist/observability.js +4 -4
  32. package/dist/settings.js +4 -4
  33. package/dist/templates.js +8 -8
  34. package/dist/workflow-builder.js +10 -10
  35. package/dist/workflows-list.js +7 -7
  36. package/package.json +1 -1
@@ -2,16 +2,16 @@ import { deepClone, applyTemplateInputs } from './chunk-GEMNVFPK.js';
2
2
  import { Input as Input$1 } from './chunk-IEVSFCFS.js';
3
3
  import { Dialog as Dialog$1, DialogContent as DialogContent$1, DialogHeader as DialogHeader$1, DialogTitle as DialogTitle$1, DialogDescription as DialogDescription$1, DialogFooter } from './chunk-YXMFRQYV.js';
4
4
  import { Badge as Badge$1, Button } from './chunk-BQXVT32O.js';
5
- import { QuotaExhaustedDialog } from './chunk-GCOWR7J4.js';
6
- import { EntityTagPicker } from './chunk-XFVNTWM2.js';
7
- import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-S5ZKDRBV.js';
5
+ import { QuotaExhaustedDialog } from './chunk-543CP5ZX.js';
6
+ import { EntityTagPicker } from './chunk-BNA2HFGB.js';
7
+ import { useWorkflowVersions, useRestoreWorkflowVersion, useActivateWorkflow, useDeleteWorkflow, useLazyWorkflowDefinition, useDeactivateWorkflow } from './chunk-FOBH2IH2.js';
8
8
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
9
- import { RUNS_QUOTA_NAME, throwIfResultError, useFlowStore, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, STICKY_NOTE_TYPE, fieldsFromValue, getNodeDetail, useFluidGridsAnalytics, buildOperationSeed, isNodeTrigger, buildStickyNode, fieldsFromIO, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, resolveNodeOperation, declaredOutputFields, pathToExpression, ExpressionEditor, formatParameterValue, parseParameterValue, isNodeConfigsInitialized, resolveUiSpec, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, openRunQuotaUpgradePrompt, describeOperationError, isTriggerOperation, canRenderOperationPanel, resolveOperationName, resolveInputHandles } from './chunk-T7X3HDNX.js';
9
+ import { RUNS_QUOTA_NAME, throwIfResultError, useFlowStore, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveWorkflow, SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, STICKY_NOTE_TYPE, fieldsFromValue, getNodeDetail, useFluidGridsAnalytics, buildOperationSeed, isNodeTrigger, buildStickyNode, fieldsFromIO, ConnectionSelector, structuredParameterType, nodeAccent, NodeTypeIcon, NODES_METADATA, resolveNodeOperation, declaredOutputFields, pathToExpression, ExpressionEditor, formatParameterValue, parseParameterValue, isNodeConfigsInitialized, resolveUiSpec, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, openRunQuotaUpgradePrompt, describeOperationError, isTriggerOperation, canRenderOperationPanel, resolveOperationName, resolveInputHandles } from './chunk-FWMFZKTI.js';
10
10
  import { cn } from './chunk-JL7CASN5.js';
11
11
  import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
12
12
  import { getFluidGridsConfig } from './chunk-CY6KX4RG.js';
13
13
  import { GetMyNodeInstallationsDocument, BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, InstallPurchasedNodeDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument, GetNodeInstallableVersionsDocument } from './chunk-WBH2XEWO.js';
14
- import { WorkspaceRunsQuotaDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, WorkflowGenerationTemplatesDocument, WorkflowGenerationDocument, GenerateWorkflowFromPromptDocument, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-GD7CSKFD.js';
14
+ import { WorkspaceRunsQuotaDocument, NodeSchemaIntrospectionDocument, useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, WorkflowGenerationTemplatesDocument, WorkflowGenerationDocument, GenerateWorkflowFromPromptDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, useRunFromNodeEligibilityQuery, EventRegistrationsDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument } from './chunk-FLKTTSRJ.js';
15
15
  import { useTr } from './chunk-UULVIT25.js';
16
16
  import { getFluidGridsClient, waitForFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
17
17
  import * as React3 from 'react';
@@ -19,13 +19,13 @@ import React3__default, { forwardRef, lazy, memo, createContext, useState, useRe
19
19
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
20
20
  import { gql } from '@apollo/client';
21
21
  import { cn as cn$1 } from '@burdenoff/fe-libs/utils';
22
- import { Card as Card$1, Badge, CardContent as CardContent$1, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Button as Button$1, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, TooltipProvider, DropdownMenuItem, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Popover, PopoverTrigger, PopoverContent, Tooltip, TooltipTrigger, TooltipContent, Toaster, Tabs, TabsList, TabsTrigger, TabsContent, CardHeader as CardHeader$1, CardTitle as CardTitle$1, CardDescription as CardDescription$1, ScrollArea, DropdownMenuLabel, DropdownMenuSeparator } from '@burdenoff/fe-libs/ui';
22
+ import { Card as Card$1, Badge, CardContent as CardContent$1, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Button as Button$1, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription, Label, Textarea, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, TooltipProvider, Switch, DropdownMenuItem, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Popover, PopoverTrigger, PopoverContent, Tooltip, TooltipTrigger, TooltipContent, Toaster, Tabs, TabsList, TabsTrigger, TabsContent, CardHeader as CardHeader$1, CardTitle as CardTitle$1, CardDescription as CardDescription$1, ScrollArea, DropdownMenuLabel, DropdownMenuSeparator } from '@burdenoff/fe-libs/ui';
23
23
  import { Link, useNavigate, useParams } from 'react-router';
24
24
  import { useEventBus } from '@burdenoff/fe-libs/shared/events';
25
25
  import { useQuery, useMutation } from '@apollo/client/react';
26
26
  import { gql as gql$1 } from '@apollo/client/core';
27
27
  import 'jszip';
28
- import { Sparkles, Star, Download, Eye, Loader2, History, GitCompareArrows, Play, RotateCcw, Trash2, Maximize2, Undo2, Redo2, LayoutGrid, Keyboard, Plus, GitBranch, AlertCircle, Search, RefreshCw, CheckCircle2, Package, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, Layers, X, AlertTriangle } from 'lucide-react';
28
+ import { Sparkles, Star, Download, Eye, Loader2, History, GitCompareArrows, Play, RotateCcw, Trash2, Layers, Upload, Rocket, ArrowRight, Check, X, Undo2, Maximize2, Redo2, LayoutGrid, Keyboard, Plus, GitBranch, AlertCircle, Search, RefreshCw, CheckCircle2, Package, Info, Copy, BadgeCheck, DollarSign, Type, Code, Hash, List, Braces, Workflow, PlusCircle, ChevronDown, ChevronRight, AlertTriangle } from 'lucide-react';
29
29
  import { Icon } from '@iconify/react';
30
30
  import { toast } from 'sonner';
31
31
  import { useShallow, shallow } from 'zustand/shallow';
@@ -6456,8 +6456,8 @@ var reducer = (state, action) => {
6456
6456
  if (toastId) {
6457
6457
  addToRemoveQueue(toastId);
6458
6458
  } else {
6459
- state.toasts.forEach((toast11) => {
6460
- addToRemoveQueue(toast11.id);
6459
+ state.toasts.forEach((toast12) => {
6460
+ addToRemoveQueue(toast12.id);
6461
6461
  });
6462
6462
  }
6463
6463
  return {
@@ -7833,7 +7833,7 @@ function VisualMapperContent({
7833
7833
  mapping,
7834
7834
  onMappingChange
7835
7835
  }) {
7836
- const { toast: toast11 } = useToast();
7836
+ const { toast: toast12 } = useToast();
7837
7837
  const tr = useTr();
7838
7838
  const reactFlowWrapper = useRef(null);
7839
7839
  const [selectedSourceNode, setSelectedSourceNode] = useState(null);
@@ -7853,7 +7853,7 @@ function VisualMapperContent({
7853
7853
  targetOperation
7854
7854
  );
7855
7855
  if (generatedNodes.length === 0) {
7856
- toast11({
7856
+ toast12({
7857
7857
  title: tr(
7858
7858
  "fluidgrids.builder.visualMapper.noFieldsFoundTitle",
7859
7859
  "No fields found"
@@ -7879,7 +7879,7 @@ function VisualMapperContent({
7879
7879
  targetNode,
7880
7880
  targetOperation,
7881
7881
  setNodes,
7882
- toast11,
7882
+ toast12,
7883
7883
  tr
7884
7884
  ]);
7885
7885
  useEffect(() => {
@@ -7986,7 +7986,7 @@ function VisualMapperContent({
7986
7986
  }
7987
7987
  onMappingChange(updatedMapping);
7988
7988
  if (isCompatible) {
7989
- toast11({
7989
+ toast12({
7990
7990
  title: tr(
7991
7991
  "fluidgrids.builder.visualMapper.mappingCreatedTitle",
7992
7992
  "Mapping created"
@@ -7994,7 +7994,7 @@ function VisualMapperContent({
7994
7994
  description: `Mapped ${sourcePath} (${sourceType}) to ${targetPath} (${targetType})`
7995
7995
  });
7996
7996
  } else {
7997
- toast11({
7997
+ toast12({
7998
7998
  title: tr(
7999
7999
  "fluidgrids.builder.visualMapper.typeMismatchTitle",
8000
8000
  "Type mismatch warning"
@@ -8004,7 +8004,7 @@ function VisualMapperContent({
8004
8004
  });
8005
8005
  }
8006
8006
  } else {
8007
- toast11({
8007
+ toast12({
8008
8008
  title: tr(
8009
8009
  "fluidgrids.builder.visualMapper.invalidMappingTitle",
8010
8010
  "Invalid mapping"
@@ -8017,7 +8017,7 @@ function VisualMapperContent({
8017
8017
  });
8018
8018
  }
8019
8019
  },
8020
- [nodes, mapping, onMappingChange, toast11, tr, setEdges]
8020
+ [nodes, mapping, onMappingChange, toast12, tr, setEdges]
8021
8021
  );
8022
8022
  const onEdgeDelete = useCallback(
8023
8023
  (edge) => {
@@ -8074,7 +8074,7 @@ function VisualMapperContent({
8074
8074
  const handleSaveMappingDialog = (expression, targetFieldName) => {
8075
8075
  const targetPath = targetFieldName || selectedTargetNode?.data?.path;
8076
8076
  if (!targetPath) {
8077
- toast11({
8077
+ toast12({
8078
8078
  title: tr(
8079
8079
  "fluidgrids.builder.visualMapper.noTargetFieldTitle",
8080
8080
  "No target field specified"
@@ -8106,7 +8106,7 @@ function VisualMapperContent({
8106
8106
  }
8107
8107
  onMappingChange(updatedMapping);
8108
8108
  const mappingType = detectMappingType(expression);
8109
- toast11({
8109
+ toast12({
8110
8110
  title: `${mappingType.charAt(0).toUpperCase() + mappingType.slice(1)} mapping created`,
8111
8111
  description: `${tr("fluidgrids.builder.visualMapper.appliedMappingTo", "Applied mapping to")} ${targetPath}`
8112
8112
  });
@@ -8405,7 +8405,7 @@ function MappingEditor({
8405
8405
  setEdges,
8406
8406
  edgeId
8407
8407
  }) {
8408
- const { toast: toast11 } = useToast();
8408
+ const { toast: toast12 } = useToast();
8409
8409
  const tr = useTr();
8410
8410
  const [mappingJson, setMappingJson] = useState({});
8411
8411
  const [jsonDataString, setJsonDataString] = useState(
@@ -8529,7 +8529,7 @@ function MappingEditor({
8529
8529
  const handleSaveMapping = () => {
8530
8530
  try {
8531
8531
  checkJsonValidity();
8532
- toast11({
8532
+ toast12({
8533
8533
  title: tr(
8534
8534
  "fluidgrids.builder.mappingEditor.mappingSavedTitle",
8535
8535
  "Mapping saved"
@@ -8540,7 +8540,7 @@ function MappingEditor({
8540
8540
  )
8541
8541
  });
8542
8542
  } catch (error) {
8543
- toast11({
8543
+ toast12({
8544
8544
  title: tr(
8545
8545
  "fluidgrids.builder.mappingEditor.errorSavingTitle",
8546
8546
  "Error saving mapping"
@@ -8559,7 +8559,7 @@ function MappingEditor({
8559
8559
  const parsedData = checkJsonValidity();
8560
8560
  const result = await applyMapping(parsedData, sampleInput);
8561
8561
  setPreviewOutput(result);
8562
- toast11({
8562
+ toast12({
8563
8563
  title: tr(
8564
8564
  "fluidgrids.builder.mappingEditor.mappingTestedTitle",
8565
8565
  "Mapping tested"
@@ -8571,7 +8571,7 @@ function MappingEditor({
8571
8571
  });
8572
8572
  } catch (error) {
8573
8573
  console.error("Test mapping error:", error);
8574
- toast11({
8574
+ toast12({
8575
8575
  title: tr(
8576
8576
  "fluidgrids.builder.mappingEditor.mappingTestFailedTitle",
8577
8577
  "Mapping test failed"
@@ -19726,6 +19726,862 @@ function VersionHistoryPanel({ className }) {
19726
19726
  )
19727
19727
  ] });
19728
19728
  }
19729
+ var HISTORY_LIMIT = 20;
19730
+ function messageOf(error) {
19731
+ return error instanceof Error ? error.message : String(error);
19732
+ }
19733
+ function useWorkflowEnvironments(workflowKey) {
19734
+ const [view, setView] = useState(null);
19735
+ const [history, setHistory] = useState([]);
19736
+ const [loading, setLoading] = useState(false);
19737
+ const [error, setError] = useState(null);
19738
+ const keyRef = useRef(workflowKey);
19739
+ keyRef.current = workflowKey;
19740
+ const refetch = useCallback(async () => {
19741
+ if (!workflowKey) return;
19742
+ setLoading(true);
19743
+ setError(null);
19744
+ try {
19745
+ const client = getFluidGridsClient();
19746
+ const [environments, promotions] = await Promise.all([
19747
+ client.query({
19748
+ query: WorkflowEnvironmentsDocument,
19749
+ variables: { workflowKey },
19750
+ fetchPolicy: "network-only"
19751
+ }),
19752
+ client.query({
19753
+ query: WorkflowPromotionRequestsDocument,
19754
+ variables: { workflowKey, limit: HISTORY_LIMIT },
19755
+ fetchPolicy: "network-only"
19756
+ })
19757
+ ]);
19758
+ throwIfResultError(environments);
19759
+ throwIfResultError(promotions);
19760
+ if (keyRef.current !== workflowKey) return;
19761
+ setView(environments.data?.workflowEnvironments ?? null);
19762
+ setHistory(promotions.data?.workflowPromotionRequests ?? []);
19763
+ } catch (err) {
19764
+ if (keyRef.current === workflowKey) setError(messageOf(err));
19765
+ } finally {
19766
+ if (keyRef.current === workflowKey) setLoading(false);
19767
+ }
19768
+ }, [workflowKey]);
19769
+ useEffect(() => {
19770
+ setView(null);
19771
+ setHistory([]);
19772
+ }, [workflowKey]);
19773
+ const deployToDev = useCallback(
19774
+ async (version) => {
19775
+ const result = await getFluidGridsClient().mutate({
19776
+ mutation: DeployWorkflowVersionToDevDocument,
19777
+ variables: { workflowKey, version }
19778
+ });
19779
+ throwIfResultError(result);
19780
+ await refetch();
19781
+ },
19782
+ [workflowKey, refetch]
19783
+ );
19784
+ const requestPromotion = useCallback(
19785
+ async (target, note) => {
19786
+ const result = await getFluidGridsClient().mutate({
19787
+ mutation: RequestWorkflowPromotionDocument,
19788
+ variables: {
19789
+ input: {
19790
+ workflowKey,
19791
+ toEnvironment: target,
19792
+ note: note?.trim() || void 0
19793
+ }
19794
+ }
19795
+ });
19796
+ throwIfResultError(result);
19797
+ const request = result.data?.requestWorkflowPromotion;
19798
+ if (!request) throw new Error("The promotion request returned no result");
19799
+ await refetch();
19800
+ return request;
19801
+ },
19802
+ [workflowKey, refetch]
19803
+ );
19804
+ const approve = useCallback(
19805
+ async (id, note) => {
19806
+ const result = await getFluidGridsClient().mutate({
19807
+ mutation: ApproveWorkflowPromotionDocument,
19808
+ variables: { id, note: note?.trim() || void 0 }
19809
+ });
19810
+ throwIfResultError(result);
19811
+ await refetch();
19812
+ },
19813
+ [refetch]
19814
+ );
19815
+ const reject = useCallback(
19816
+ async (id, note) => {
19817
+ const result = await getFluidGridsClient().mutate({
19818
+ mutation: RejectWorkflowPromotionDocument,
19819
+ variables: { id, note: note?.trim() || void 0 }
19820
+ });
19821
+ throwIfResultError(result);
19822
+ await refetch();
19823
+ },
19824
+ [refetch]
19825
+ );
19826
+ const cancel = useCallback(
19827
+ async (id) => {
19828
+ const result = await getFluidGridsClient().mutate({
19829
+ mutation: CancelWorkflowPromotionDocument,
19830
+ variables: { id }
19831
+ });
19832
+ throwIfResultError(result);
19833
+ await refetch();
19834
+ },
19835
+ [refetch]
19836
+ );
19837
+ const updatePolicy = useCallback(
19838
+ async (changes) => {
19839
+ const result = await getFluidGridsClient().mutate({
19840
+ mutation: UpdateWorkflowEnvironmentPolicyDocument,
19841
+ variables: { input: changes }
19842
+ });
19843
+ throwIfResultError(result);
19844
+ await refetch();
19845
+ },
19846
+ [refetch]
19847
+ );
19848
+ return {
19849
+ view,
19850
+ history,
19851
+ loading,
19852
+ error,
19853
+ refetch,
19854
+ deployToDev,
19855
+ requestPromotion,
19856
+ approve,
19857
+ reject,
19858
+ cancel,
19859
+ updatePolicy
19860
+ };
19861
+ }
19862
+
19863
+ // src/builder/lib/workflow-environments.ts
19864
+ var ENVIRONMENT_ORDER = [
19865
+ "DEV",
19866
+ "STAGING",
19867
+ "PROD"
19868
+ ];
19869
+ function promotionTargetOf(environment) {
19870
+ if (environment === "DEV") return "STAGING";
19871
+ if (environment === "STAGING") return "PROD";
19872
+ return null;
19873
+ }
19874
+ function promotionSourceOf(target) {
19875
+ return target === "STAGING" ? "DEV" : "STAGING";
19876
+ }
19877
+ function promotionNeedsApproval(policy, target) {
19878
+ return target === "PROD" ? policy.requireProdApproval : policy.requireStagingApproval;
19879
+ }
19880
+ function promotionBlocker(environments, pending, target) {
19881
+ const source = environments.find(
19882
+ (state) => state.environment === promotionSourceOf(target)
19883
+ );
19884
+ const current = environments.find((state) => state.environment === target);
19885
+ if (!source?.version || !source.workflowDefinitionId) return "sourceEmpty";
19886
+ if (!source.available) return "sourceUnavailable";
19887
+ if (current?.available && current.workflowDefinitionId === source.workflowDefinitionId) {
19888
+ return "alreadyDeployed";
19889
+ }
19890
+ if (pending.some((request) => request.toEnvironment === target)) {
19891
+ return "pending";
19892
+ }
19893
+ return null;
19894
+ }
19895
+ function orderEnvironments(environments) {
19896
+ return [...environments].sort(
19897
+ (a, b) => ENVIRONMENT_ORDER.indexOf(a.environment) - ENVIRONMENT_ORDER.indexOf(b.environment)
19898
+ );
19899
+ }
19900
+ var NOTE_MAX = 2e3;
19901
+ function EnvironmentsPanel({
19902
+ className,
19903
+ compact = false
19904
+ }) {
19905
+ const tr = useTr();
19906
+ const params = useParams();
19907
+ const workflowKey = params?.id ?? "";
19908
+ const currentVersion = params?.version ?? "";
19909
+ const setWorkflow = useFlowStore((s) => s.actions.setWorkflow);
19910
+ const openStatus = useFlowStore((s) => s.workflow.status);
19911
+ const {
19912
+ view,
19913
+ history,
19914
+ loading,
19915
+ error,
19916
+ refetch,
19917
+ deployToDev,
19918
+ requestPromotion,
19919
+ approve,
19920
+ reject,
19921
+ cancel,
19922
+ updatePolicy
19923
+ } = useWorkflowEnvironments(workflowKey);
19924
+ const [busy, setBusy] = useState(null);
19925
+ const [dialog, setDialog] = useState(null);
19926
+ const [note, setNote] = useState("");
19927
+ const envLabel = (environment) => {
19928
+ if (environment === "DEV")
19929
+ return tr("fluidgrids.builder.environments.dev", "Development");
19930
+ if (environment === "STAGING")
19931
+ return tr("fluidgrids.builder.environments.staging", "Staging");
19932
+ return tr("fluidgrids.builder.environments.prod", "Production");
19933
+ };
19934
+ const blockerText = (blocker, source) => {
19935
+ switch (blocker) {
19936
+ case "sourceEmpty":
19937
+ return `${tr("fluidgrids.builder.environments.blockedEmpty", "Nothing is deployed to")} ${envLabel(source)}`;
19938
+ case "sourceUnavailable":
19939
+ return tr(
19940
+ "fluidgrids.builder.environments.blockedDeleted",
19941
+ "The deployed version was deleted"
19942
+ );
19943
+ case "alreadyDeployed":
19944
+ return tr(
19945
+ "fluidgrids.builder.environments.blockedSame",
19946
+ "Already deployed there"
19947
+ );
19948
+ default:
19949
+ return tr(
19950
+ "fluidgrids.builder.environments.blockedPending",
19951
+ "A promotion is already waiting for approval"
19952
+ );
19953
+ }
19954
+ };
19955
+ const config = getFluidGridsConfig();
19956
+ const actorInputs = useMemo(() => {
19957
+ const ids = /* @__PURE__ */ new Set();
19958
+ for (const state of view?.environments ?? []) {
19959
+ if (state.deployedById) ids.add(state.deployedById);
19960
+ }
19961
+ for (const request of view?.pendingPromotions ?? []) {
19962
+ ids.add(request.requestedById);
19963
+ }
19964
+ for (const item of history) {
19965
+ ids.add(item.requestedById);
19966
+ if (item.reviewedById) ids.add(item.reviewedById);
19967
+ }
19968
+ return [...ids].map((actorId) => ({ actorId, actorType: "user" }));
19969
+ }, [view, history]);
19970
+ const actorProfiles = useActorProfiles(actorInputs, {
19971
+ workspaceId: config.workspaceId || void 0,
19972
+ orgId: config.organizationId,
19973
+ authToken: config.authToken
19974
+ });
19975
+ const nameOf = (actorId) => {
19976
+ if (!actorId) return null;
19977
+ return actorProfiles[actorId]?.displayName ?? buildActorFallback({ actorId, actorType: "user" }).displayName;
19978
+ };
19979
+ const environments = orderEnvironments(view?.environments ?? []);
19980
+ const pending = view?.pendingPromotions ?? [];
19981
+ const policy = view?.policy;
19982
+ const dev = environments.find((state) => state.environment === "DEV");
19983
+ const afterProdChange = (promotedVersion) => {
19984
+ if (promotedVersion === currentVersion) {
19985
+ setWorkflow({ status: "ACTIVE" });
19986
+ } else if (openStatus === "ACTIVE") {
19987
+ setWorkflow({ status: "ARCHIVED" });
19988
+ }
19989
+ };
19990
+ const run = async (key, action, errorTitle) => {
19991
+ setBusy(key);
19992
+ try {
19993
+ await action();
19994
+ } catch (err) {
19995
+ toast.error(errorTitle, {
19996
+ description: err instanceof Error ? err.message : String(err)
19997
+ });
19998
+ } finally {
19999
+ setBusy(null);
20000
+ }
20001
+ };
20002
+ const handleDeployToDev = () => run(
20003
+ "deploy",
20004
+ async () => {
20005
+ await deployToDev(currentVersion);
20006
+ toast.success(
20007
+ `${currentVersion} ${tr("fluidgrids.builder.environments.deployedToDev", "deployed to Development")}`,
20008
+ { dismissible: true }
20009
+ );
20010
+ },
20011
+ tr(
20012
+ "fluidgrids.builder.environments.deployError",
20013
+ "Failed to deploy to Development"
20014
+ )
20015
+ );
20016
+ const handleConfirmDialog = async () => {
20017
+ if (!dialog) return;
20018
+ const current = dialog;
20019
+ const text = note;
20020
+ setDialog(null);
20021
+ setNote("");
20022
+ if (current.kind === "promote") {
20023
+ await run(
20024
+ `promote:${current.target}`,
20025
+ async () => {
20026
+ const request = await requestPromotion(current.target, text);
20027
+ if (request.status === "PENDING") {
20028
+ toast.success(
20029
+ tr(
20030
+ "fluidgrids.builder.environments.requested",
20031
+ "Promotion requested"
20032
+ ),
20033
+ {
20034
+ description: `${request.version} \u2192 ${envLabel(current.target)} ${tr("fluidgrids.builder.environments.awaitingApproval", "is waiting for approval")}`,
20035
+ dismissible: true
20036
+ }
20037
+ );
20038
+ } else {
20039
+ toast.success(
20040
+ `${request.version} ${tr("fluidgrids.builder.environments.promotedTo", "promoted to")} ${envLabel(current.target)}`,
20041
+ { dismissible: true }
20042
+ );
20043
+ if (current.target === "PROD") afterProdChange(request.version);
20044
+ }
20045
+ },
20046
+ tr("fluidgrids.builder.environments.promoteError", "Promotion failed")
20047
+ );
20048
+ return;
20049
+ }
20050
+ if (current.kind === "approve") {
20051
+ await run(
20052
+ `approve:${current.id}`,
20053
+ async () => {
20054
+ await approve(current.id, text);
20055
+ toast.success(
20056
+ `${current.label} ${tr("fluidgrids.builder.environments.approved", "approved and deployed")}`,
20057
+ { dismissible: true }
20058
+ );
20059
+ if (current.target === "PROD") afterProdChange(current.version);
20060
+ },
20061
+ tr("fluidgrids.builder.environments.approveError", "Approval failed")
20062
+ );
20063
+ return;
20064
+ }
20065
+ await run(
20066
+ `reject:${current.id}`,
20067
+ async () => {
20068
+ await reject(current.id, text);
20069
+ toast.success(
20070
+ `${current.label} ${tr("fluidgrids.builder.environments.rejected", "rejected")}`,
20071
+ { dismissible: true }
20072
+ );
20073
+ },
20074
+ tr("fluidgrids.builder.environments.rejectError", "Rejection failed")
20075
+ );
20076
+ };
20077
+ const handleCancel = (id) => run(
20078
+ `cancel:${id}`,
20079
+ async () => {
20080
+ await cancel(id);
20081
+ toast.success(
20082
+ tr(
20083
+ "fluidgrids.builder.environments.canceled",
20084
+ "Promotion request withdrawn"
20085
+ ),
20086
+ { dismissible: true }
20087
+ );
20088
+ },
20089
+ tr(
20090
+ "fluidgrids.builder.environments.cancelError",
20091
+ "Could not withdraw the request"
20092
+ )
20093
+ );
20094
+ const handlePolicy = (changes) => run(
20095
+ "policy",
20096
+ async () => {
20097
+ await updatePolicy(changes);
20098
+ toast.success(
20099
+ tr(
20100
+ "fluidgrids.builder.environments.policySaved",
20101
+ "Promotion policy saved"
20102
+ ),
20103
+ { dismissible: true }
20104
+ );
20105
+ },
20106
+ tr(
20107
+ "fluidgrids.builder.environments.policyError",
20108
+ "Could not save the promotion policy"
20109
+ )
20110
+ );
20111
+ const statusVariant = (status) => {
20112
+ if (status === "APPLIED") return "default";
20113
+ if (status === "PENDING") return "secondary";
20114
+ if (status === "REJECTED") return "destructive";
20115
+ return "outline";
20116
+ };
20117
+ const dialogTitle = (() => {
20118
+ if (!dialog) return "";
20119
+ if (dialog.kind === "promote") {
20120
+ const needsApproval = policy ? promotionNeedsApproval(policy, dialog.target) : true;
20121
+ return needsApproval ? `${tr("fluidgrids.builder.environments.requestTitle", "Request promotion to")} ${envLabel(dialog.target)}` : `${tr("fluidgrids.builder.environments.promoteTitle", "Promote to")} ${envLabel(dialog.target)}`;
20122
+ }
20123
+ return dialog.kind === "approve" ? `${tr("fluidgrids.builder.environments.approveTitle", "Approve")} ${dialog.label}` : `${tr("fluidgrids.builder.environments.rejectTitle", "Reject")} ${dialog.label}`;
20124
+ })();
20125
+ const dialogBody = (() => {
20126
+ if (!dialog) return "";
20127
+ if (dialog.kind === "promote") {
20128
+ const source = dialog.target === "PROD" ? "STAGING" : "DEV";
20129
+ const version = environments.find((state) => state.environment === source)?.version ?? "";
20130
+ const needsApproval = policy ? promotionNeedsApproval(policy, dialog.target) : true;
20131
+ const base = `${version} ${tr("fluidgrids.builder.environments.moves", "moves from")} ${envLabel(source)} ${tr("fluidgrids.builder.environments.to", "to")} ${envLabel(dialog.target)}.`;
20132
+ const prodNote = dialog.target === "PROD" ? ` ${tr("fluidgrids.builder.environments.prodNote", "In Production it becomes the active version that webhooks, schedules and other triggers run.")}` : "";
20133
+ const gate = needsApproval ? ` ${tr("fluidgrids.builder.environments.gateNote", "The workspace policy holds this promotion until someone with publish rights approves it.")}` : "";
20134
+ return base + prodNote + gate;
20135
+ }
20136
+ return dialog.kind === "approve" ? tr(
20137
+ "fluidgrids.builder.environments.approveBody",
20138
+ "Approving deploys the version to its target environment now. A production promotion makes it the active version."
20139
+ ) : tr(
20140
+ "fluidgrids.builder.environments.rejectBody",
20141
+ "The version stays where it is. The requester can ask again later."
20142
+ );
20143
+ })();
20144
+ return /* @__PURE__ */ jsxs(Sheet, { onOpenChange: (open) => open && void refetch(), children: [
20145
+ /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
20146
+ Button$1,
20147
+ {
20148
+ "data-tour": "fluidgrids-builder-environments",
20149
+ variant: "outline",
20150
+ size: compact ? "icon" : "sm",
20151
+ className: cn$1(!compact && "flex gap-1", className),
20152
+ "aria-label": tr(
20153
+ "fluidgrids.builder.environments.triggerButton",
20154
+ "Environments"
20155
+ ),
20156
+ disabled: !workflowKey,
20157
+ children: [
20158
+ /* @__PURE__ */ jsx(Layers, { className: "size-4" }),
20159
+ !compact && tr("fluidgrids.builder.environments.triggerButton", "Environments")
20160
+ ]
20161
+ }
20162
+ ) }),
20163
+ /* @__PURE__ */ jsxs(
20164
+ SheetContent,
20165
+ {
20166
+ side: "right",
20167
+ className: "w-[380px] sm:w-[460px] overflow-y-auto",
20168
+ "data-testid": "fluidgrids-environments-panel",
20169
+ children: [
20170
+ /* @__PURE__ */ jsxs(SheetHeader, { children: [
20171
+ /* @__PURE__ */ jsxs(SheetTitle, { className: "flex items-center gap-2", children: [
20172
+ /* @__PURE__ */ jsx(Layers, { className: "size-5" }),
20173
+ tr("fluidgrids.builder.environments.title", "Environments")
20174
+ ] }),
20175
+ /* @__PURE__ */ jsx(SheetDescription, { children: tr(
20176
+ "fluidgrids.builder.environments.description",
20177
+ "Move a version from Development to Staging to Production. Production is the active version that automatic triggers run."
20178
+ ) })
20179
+ ] }),
20180
+ loading && !view ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center py-8 text-sm text-muted-foreground", children: [
20181
+ /* @__PURE__ */ jsx(Loader2, { className: "mr-2 size-4 animate-spin" }),
20182
+ tr("fluidgrids.builder.environments.loading", "Loading...")
20183
+ ] }) : error && !view ? /* @__PURE__ */ jsxs("div", { className: "py-6 text-sm", children: [
20184
+ /* @__PURE__ */ jsx("p", { className: "text-[var(--color-status-error-text)]", children: error }),
20185
+ /* @__PURE__ */ jsx(
20186
+ Button$1,
20187
+ {
20188
+ variant: "outline",
20189
+ size: "sm",
20190
+ className: "mt-3",
20191
+ onClick: () => void refetch(),
20192
+ children: tr("fluidgrids.builder.environments.retry", "Retry")
20193
+ }
20194
+ )
20195
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-5 pb-6", children: [
20196
+ /* @__PURE__ */ jsx("ol", { className: "space-y-2", "aria-label": "Environments", children: environments.map((state) => {
20197
+ const target = promotionTargetOf(state.environment);
20198
+ const blocker = target ? promotionBlocker(environments, pending, target) : null;
20199
+ const needsApproval = target && policy ? promotionNeedsApproval(policy, target) : false;
20200
+ const deployer = nameOf(state.deployedById);
20201
+ return /* @__PURE__ */ jsxs(
20202
+ "li",
20203
+ {
20204
+ className: "rounded-lg border border-border p-3",
20205
+ "data-testid": `fluidgrids-env-${state.environment}`,
20206
+ children: [
20207
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
20208
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: envLabel(state.environment) }),
20209
+ state.version ? /* @__PURE__ */ jsx(
20210
+ Badge,
20211
+ {
20212
+ variant: state.available ? "default" : "outline",
20213
+ className: "h-5 px-1.5 py-0 font-mono text-[10px]",
20214
+ children: state.version
20215
+ }
20216
+ ) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: tr(
20217
+ "fluidgrids.builder.environments.empty",
20218
+ "Nothing deployed"
20219
+ ) }),
20220
+ state.version === currentVersion && currentVersion !== "" && /* @__PURE__ */ jsx(
20221
+ Badge,
20222
+ {
20223
+ variant: "outline",
20224
+ className: "h-5 border-primary px-1.5 py-0 text-[10px] text-primary",
20225
+ children: tr(
20226
+ "fluidgrids.builder.environments.openVersion",
20227
+ "Open"
20228
+ )
20229
+ }
20230
+ )
20231
+ ] }) }),
20232
+ state.version && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: !state.available ? tr(
20233
+ "fluidgrids.builder.environments.deleted",
20234
+ "This version was deleted after it was deployed."
20235
+ ) : state.outOfBand ? tr(
20236
+ "fluidgrids.builder.environments.outOfBand",
20237
+ "Activated directly, not through a promotion."
20238
+ ) : [
20239
+ state.promotedFrom ? `${tr("fluidgrids.builder.environments.from", "From")} ${envLabel(state.promotedFrom)}` : null,
20240
+ deployer,
20241
+ relativeTime(state.deployedAt)
20242
+ ].filter(Boolean).join(" \xB7 ") }),
20243
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
20244
+ state.environment === "DEV" && currentVersion && /* @__PURE__ */ jsxs(
20245
+ Button$1,
20246
+ {
20247
+ variant: "outline",
20248
+ size: "sm",
20249
+ className: "h-7 gap-1 text-xs",
20250
+ disabled: busy !== null || dev?.version === currentVersion && dev.available,
20251
+ onClick: () => void handleDeployToDev(),
20252
+ children: [
20253
+ busy === "deploy" ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "size-3.5" }),
20254
+ tr(
20255
+ "fluidgrids.builder.environments.deployOpen",
20256
+ "Deploy"
20257
+ ),
20258
+ " ",
20259
+ currentVersion
20260
+ ]
20261
+ }
20262
+ ),
20263
+ target && /* @__PURE__ */ jsxs(
20264
+ Button$1,
20265
+ {
20266
+ size: "sm",
20267
+ variant: target === "PROD" ? "default" : "secondary",
20268
+ className: "h-7 gap-1 text-xs",
20269
+ disabled: busy !== null || blocker !== null,
20270
+ title: blocker ? blockerText(blocker, state.environment) : void 0,
20271
+ onClick: () => {
20272
+ setNote("");
20273
+ setDialog({ kind: "promote", target });
20274
+ },
20275
+ children: [
20276
+ busy === `promote:${target}` ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }) : target === "PROD" ? /* @__PURE__ */ jsx(Rocket, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5" }),
20277
+ needsApproval ? tr(
20278
+ "fluidgrids.builder.environments.requestTo",
20279
+ "Request"
20280
+ ) : tr(
20281
+ "fluidgrids.builder.environments.promoteTo",
20282
+ "Promote to"
20283
+ ),
20284
+ " ",
20285
+ envLabel(target)
20286
+ ]
20287
+ }
20288
+ )
20289
+ ] }),
20290
+ target && blocker && blocker !== "sourceEmpty" && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: blockerText(blocker, state.environment) })
20291
+ ]
20292
+ },
20293
+ state.environment
20294
+ );
20295
+ }) }),
20296
+ /* @__PURE__ */ jsxs("section", { "aria-labelledby": "fg-env-pending", children: [
20297
+ /* @__PURE__ */ jsx(
20298
+ "h3",
20299
+ {
20300
+ id: "fg-env-pending",
20301
+ className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
20302
+ children: tr(
20303
+ "fluidgrids.builder.environments.pendingTitle",
20304
+ "Waiting for approval"
20305
+ )
20306
+ }
20307
+ ),
20308
+ pending.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
20309
+ "fluidgrids.builder.environments.pendingEmpty",
20310
+ "No promotions are waiting."
20311
+ ) }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: pending.map((request) => {
20312
+ const label = `${request.version} \u2192 ${envLabel(request.toEnvironment)}`;
20313
+ return /* @__PURE__ */ jsxs(
20314
+ "li",
20315
+ {
20316
+ className: "rounded-lg border border-border bg-muted/30 p-3",
20317
+ "data-testid": "fluidgrids-env-pending",
20318
+ children: [
20319
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium text-foreground", children: [
20320
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: request.version }),
20321
+ " ",
20322
+ envLabel(request.fromEnvironment),
20323
+ " \u2192",
20324
+ " ",
20325
+ envLabel(request.toEnvironment)
20326
+ ] }),
20327
+ /* @__PURE__ */ jsxs("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
20328
+ nameOf(request.requestedById),
20329
+ " \xB7",
20330
+ " ",
20331
+ relativeTime(request.requestedAt)
20332
+ ] }),
20333
+ request.note && /* @__PURE__ */ jsx("p", { className: "mt-1 whitespace-pre-wrap text-xs text-foreground", children: request.note }),
20334
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
20335
+ /* @__PURE__ */ jsxs(
20336
+ Button$1,
20337
+ {
20338
+ size: "sm",
20339
+ className: "h-7 gap-1 text-xs",
20340
+ disabled: busy !== null,
20341
+ onClick: () => {
20342
+ setNote("");
20343
+ setDialog({
20344
+ kind: "approve",
20345
+ id: request.id,
20346
+ label,
20347
+ version: request.version,
20348
+ target: request.toEnvironment
20349
+ });
20350
+ },
20351
+ children: [
20352
+ /* @__PURE__ */ jsx(Check, { className: "size-3.5" }),
20353
+ tr(
20354
+ "fluidgrids.builder.environments.approve",
20355
+ "Approve"
20356
+ )
20357
+ ]
20358
+ }
20359
+ ),
20360
+ /* @__PURE__ */ jsxs(
20361
+ Button$1,
20362
+ {
20363
+ size: "sm",
20364
+ variant: "outline",
20365
+ className: "h-7 gap-1 text-xs",
20366
+ disabled: busy !== null,
20367
+ onClick: () => {
20368
+ setNote("");
20369
+ setDialog({
20370
+ kind: "reject",
20371
+ id: request.id,
20372
+ label
20373
+ });
20374
+ },
20375
+ children: [
20376
+ /* @__PURE__ */ jsx(X, { className: "size-3.5" }),
20377
+ tr(
20378
+ "fluidgrids.builder.environments.reject",
20379
+ "Reject"
20380
+ )
20381
+ ]
20382
+ }
20383
+ ),
20384
+ /* @__PURE__ */ jsxs(
20385
+ Button$1,
20386
+ {
20387
+ size: "sm",
20388
+ variant: "ghost",
20389
+ className: "h-7 gap-1 text-xs",
20390
+ disabled: busy !== null,
20391
+ onClick: () => void handleCancel(request.id),
20392
+ children: [
20393
+ busy === `cancel:${request.id}` ? /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Undo2, { className: "size-3.5" }),
20394
+ tr(
20395
+ "fluidgrids.builder.environments.withdraw",
20396
+ "Withdraw"
20397
+ )
20398
+ ]
20399
+ }
20400
+ )
20401
+ ] })
20402
+ ]
20403
+ },
20404
+ request.id
20405
+ );
20406
+ }) })
20407
+ ] }),
20408
+ policy && /* @__PURE__ */ jsxs("section", { "aria-labelledby": "fg-env-policy", children: [
20409
+ /* @__PURE__ */ jsx(
20410
+ "h3",
20411
+ {
20412
+ id: "fg-env-policy",
20413
+ className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
20414
+ children: tr(
20415
+ "fluidgrids.builder.environments.policyTitle",
20416
+ "Promotion policy (workspace)"
20417
+ )
20418
+ }
20419
+ ),
20420
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-lg border border-border p-3", children: [
20421
+ [
20422
+ [
20423
+ "requireProdApproval",
20424
+ tr(
20425
+ "fluidgrids.builder.environments.policyProd",
20426
+ "Production promotions need approval"
20427
+ )
20428
+ ],
20429
+ [
20430
+ "requireStagingApproval",
20431
+ tr(
20432
+ "fluidgrids.builder.environments.policyStaging",
20433
+ "Staging promotions need approval"
20434
+ )
20435
+ ],
20436
+ [
20437
+ "allowSelfApproval",
20438
+ tr(
20439
+ "fluidgrids.builder.environments.policySelf",
20440
+ "Requesters may approve their own promotions"
20441
+ )
20442
+ ],
20443
+ [
20444
+ "lockDirectActivation",
20445
+ tr(
20446
+ "fluidgrids.builder.environments.policyLock",
20447
+ "Change Production only through promotions"
20448
+ )
20449
+ ]
20450
+ ].map(([field, label]) => /* @__PURE__ */ jsxs(
20451
+ "div",
20452
+ {
20453
+ className: "flex items-center justify-between gap-3",
20454
+ children: [
20455
+ /* @__PURE__ */ jsx(
20456
+ Label,
20457
+ {
20458
+ htmlFor: `fg-env-policy-${field}`,
20459
+ className: "text-xs font-normal",
20460
+ children: label
20461
+ }
20462
+ ),
20463
+ /* @__PURE__ */ jsx(
20464
+ Switch,
20465
+ {
20466
+ id: `fg-env-policy-${field}`,
20467
+ checked: policy[field],
20468
+ disabled: busy !== null,
20469
+ onCheckedChange: (checked) => void handlePolicy({ [field]: checked })
20470
+ }
20471
+ )
20472
+ ]
20473
+ },
20474
+ field
20475
+ )),
20476
+ /* @__PURE__ */ jsx("p", { className: "pt-1 text-[11px] text-muted-foreground", children: tr(
20477
+ "fluidgrids.builder.environments.policyHint",
20478
+ "Only members with publish rights can approve promotions or change this policy."
20479
+ ) })
20480
+ ] })
20481
+ ] }),
20482
+ /* @__PURE__ */ jsxs("section", { "aria-labelledby": "fg-env-history", children: [
20483
+ /* @__PURE__ */ jsx(
20484
+ "h3",
20485
+ {
20486
+ id: "fg-env-history",
20487
+ className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
20488
+ children: tr(
20489
+ "fluidgrids.builder.environments.historyTitle",
20490
+ "Promotion history"
20491
+ )
20492
+ }
20493
+ ),
20494
+ history.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
20495
+ "fluidgrids.builder.environments.historyEmpty",
20496
+ "No promotions yet."
20497
+ ) }) : /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: history.map((item) => /* @__PURE__ */ jsxs(
20498
+ "li",
20499
+ {
20500
+ className: "flex items-start justify-between gap-2 text-xs",
20501
+ "data-testid": "fluidgrids-env-history",
20502
+ children: [
20503
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
20504
+ /* @__PURE__ */ jsxs("p", { className: "text-foreground", children: [
20505
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: item.version }),
20506
+ " ",
20507
+ envLabel(item.fromEnvironment),
20508
+ " \u2192",
20509
+ " ",
20510
+ envLabel(item.toEnvironment)
20511
+ ] }),
20512
+ /* @__PURE__ */ jsx("p", { className: "truncate text-muted-foreground", children: [
20513
+ nameOf(item.requestedById),
20514
+ item.reviewedById && item.reviewedById !== item.requestedById ? `${tr("fluidgrids.builder.environments.reviewedBy", "reviewed by")} ${nameOf(item.reviewedById)}` : null,
20515
+ relativeTime(item.requestedAt)
20516
+ ].filter(Boolean).join(" \xB7 ") })
20517
+ ] }),
20518
+ /* @__PURE__ */ jsx(
20519
+ Badge,
20520
+ {
20521
+ variant: statusVariant(item.status),
20522
+ className: "h-5 shrink-0 px-1.5 py-0 text-[10px]",
20523
+ children: item.autoApplied ? tr(
20524
+ "fluidgrids.builder.environments.statusAuto",
20525
+ "Applied"
20526
+ ) : item.status === "APPLIED" ? tr(
20527
+ "fluidgrids.builder.environments.statusApproved",
20528
+ "Approved"
20529
+ ) : item.status === "PENDING" ? tr(
20530
+ "fluidgrids.builder.environments.statusPending",
20531
+ "Pending"
20532
+ ) : item.status === "REJECTED" ? tr(
20533
+ "fluidgrids.builder.environments.statusRejected",
20534
+ "Rejected"
20535
+ ) : tr(
20536
+ "fluidgrids.builder.environments.statusCanceled",
20537
+ "Withdrawn"
20538
+ )
20539
+ }
20540
+ )
20541
+ ]
20542
+ },
20543
+ item.id
20544
+ )) })
20545
+ ] })
20546
+ ] })
20547
+ ]
20548
+ }
20549
+ ),
20550
+ /* @__PURE__ */ jsx(
20551
+ AlertDialog,
20552
+ {
20553
+ open: dialog !== null,
20554
+ onOpenChange: (open) => !open && setDialog(null),
20555
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { className: "z-[120]", children: [
20556
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
20557
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: dialogTitle }),
20558
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: dialogBody })
20559
+ ] }),
20560
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
20561
+ /* @__PURE__ */ jsx(Label, { htmlFor: "fg-env-note", className: "text-xs", children: tr(
20562
+ "fluidgrids.builder.environments.noteLabel",
20563
+ "Note (optional)"
20564
+ ) }),
20565
+ /* @__PURE__ */ jsx(
20566
+ Textarea,
20567
+ {
20568
+ id: "fg-env-note",
20569
+ value: note,
20570
+ maxLength: NOTE_MAX,
20571
+ rows: 3,
20572
+ onChange: (event) => setNote(event.target.value)
20573
+ }
20574
+ )
20575
+ ] }),
20576
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
20577
+ /* @__PURE__ */ jsx(AlertDialogCancel, { children: tr("fluidgrids.builder.environments.dialogCancel", "Cancel") }),
20578
+ /* @__PURE__ */ jsx(AlertDialogAction, { onClick: () => void handleConfirmDialog(), children: dialog?.kind === "reject" ? tr("fluidgrids.builder.environments.reject", "Reject") : dialog?.kind === "approve" ? tr("fluidgrids.builder.environments.approve", "Approve") : tr("fluidgrids.builder.environments.confirm", "Confirm") })
20579
+ ] })
20580
+ ] })
20581
+ }
20582
+ )
20583
+ ] });
20584
+ }
19729
20585
 
19730
20586
  // src/builder/lib/ai-draft.ts
19731
20587
  var AI_DRAFT_POLL_INTERVAL_MS = 2500;
@@ -19825,7 +20681,7 @@ function canvasHasWork(nodes) {
19825
20681
  }
19826
20682
 
19827
20683
  // src/builder/hooks/use-ai-workflow-draft.ts
19828
- function messageOf(error) {
20684
+ function messageOf2(error) {
19829
20685
  return error instanceof Error ? error.message : String(error);
19830
20686
  }
19831
20687
  function useAiWorkflowDraft() {
@@ -19882,7 +20738,7 @@ function useAiWorkflowDraft() {
19882
20738
  setIsGenerating(false);
19883
20739
  } catch (pollError) {
19884
20740
  if (run !== runRef.current) return;
19885
- setError(messageOf(pollError));
20741
+ setError(messageOf2(pollError));
19886
20742
  setIsGenerating(false);
19887
20743
  }
19888
20744
  },
@@ -19919,7 +20775,7 @@ function useAiWorkflowDraft() {
19919
20775
  );
19920
20776
  } catch (startError) {
19921
20777
  if (run !== runRef.current) return;
19922
- setError(messageOf(startError));
20778
+ setError(messageOf2(startError));
19923
20779
  setIsGenerating(false);
19924
20780
  }
19925
20781
  },
@@ -20626,6 +21482,7 @@ var FlowBuilder = ({ readOnly = false }) => {
20626
21482
  /* @__PURE__ */ jsxs("div", { className: "hidden shrink-0 lg:inline-flex items-center gap-2", children: [
20627
21483
  !isReadOnly && /* @__PURE__ */ jsx(AiDraftPanel, {}),
20628
21484
  /* @__PURE__ */ jsx(VersionHistoryPanel, {}),
21485
+ /* @__PURE__ */ jsx(EnvironmentsPanel, {}),
20629
21486
  /* @__PURE__ */ jsx(SaveFlowButton, {}),
20630
21487
  /* @__PURE__ */ jsx(ActivateWorkflowButton, {}),
20631
21488
  /* @__PURE__ */ jsx(
@@ -20647,6 +21504,7 @@ var FlowBuilder = ({ readOnly = false }) => {
20647
21504
  ] }),
20648
21505
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1 lg:hidden", children: [
20649
21506
  !isReadOnly && /* @__PURE__ */ jsx(AiDraftPanel, { compact: true }),
21507
+ /* @__PURE__ */ jsx(EnvironmentsPanel, { compact: true }),
20650
21508
  /* @__PURE__ */ jsxs(DropdownMenu, { children: [
20651
21509
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
20652
21510
  Button$1,