diff --git a/apps/sim/app/api/workflows/[id]/deploy/route.ts b/apps/sim/app/api/workflows/[id]/deploy/route.ts index ed7b57c0e7..9ebbce6764 100644 --- a/apps/sim/app/api/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/workflows/[id]/deploy/route.ts @@ -60,11 +60,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const { loadWorkflowFromNormalizedTables } = await import('@/lib/workflows/persistence/utils') const normalizedData = await loadWorkflowFromNormalizedTables(id) if (normalizedData) { + const [workflowRecord] = await db + .select({ variables: workflow.variables }) + .from(workflow) + .where(eq(workflow.id, id)) + .limit(1) + const currentState = { blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, parallels: normalizedData.parallels, + variables: workflowRecord?.variables || {}, } const { hasWorkflowChanged } = await import('@/lib/workflows/comparison') needsRedeployment = hasWorkflowChanged(currentState as any, active.state as any) diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index e17dfc1df2..443424c858 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -318,6 +318,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: loops: Record parallels: Record deploymentVersionId?: string + variables?: Record } | null = null let processedInput = input @@ -327,6 +328,11 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: : await loadDeployedWorkflowState(workflowId) if (workflowData) { + const deployedVariables = + !shouldUseDraftState && 'variables' in workflowData + ? (workflowData as any).variables + : undefined + cachedWorkflowData = { blocks: workflowData.blocks, edges: workflowData.edges, @@ -336,6 +342,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: !shouldUseDraftState && 'deploymentVersionId' in workflowData ? (workflowData.deploymentVersionId as string) : undefined, + variables: deployedVariables, } const serializedWorkflow = new Serializer().serializeWorkflow( @@ -405,11 +412,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowStateOverride: effectiveWorkflowStateOverride, } + const executionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {} + const snapshot = new ExecutionSnapshot( metadata, workflow, processedInput, - workflow.variables || {}, + executionVariables, selectedOutputs ) @@ -471,6 +480,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: selectedOutputs, cachedWorkflowData?.blocks || {} ) + const streamVariables = cachedWorkflowData?.variables ?? (workflow as any).variables + const stream = await createStreamingResponse({ requestId, workflow: { @@ -478,7 +489,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: userId: actorUserId, workspaceId, isDeployed: workflow.isDeployed, - variables: (workflow as any).variables, + variables: streamVariables, }, input: processedInput, executingUserId: actorUserId, @@ -675,11 +686,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: workflowStateOverride: effectiveWorkflowStateOverride, } + const sseExecutionVariables = cachedWorkflowData?.variables ?? workflow.variables ?? {} + const snapshot = new ExecutionSnapshot( metadata, workflow, processedInput, - workflow.variables || {}, + sseExecutionVariables, selectedOutputs ) diff --git a/apps/sim/app/api/workflows/[id]/status/route.ts b/apps/sim/app/api/workflows/[id]/status/route.ts index b2525b6d5e..62262981e0 100644 --- a/apps/sim/app/api/workflows/[id]/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/status/route.ts @@ -1,4 +1,4 @@ -import { db, workflowDeploymentVersion } from '@sim/db' +import { db, workflow, workflowDeploymentVersion } from '@sim/db' import { and, desc, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { generateRequestId } from '@/lib/core/utils/request' @@ -22,17 +22,12 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return createErrorResponse(validation.error.message, validation.error.status) } - // Check if the workflow has meaningful changes that would require redeployment let needsRedeployment = false if (validation.workflow.isDeployed) { - // Get current state from normalized tables (same logic as deployment API) - // Load current state from normalized tables using centralized helper const normalizedData = await loadWorkflowFromNormalizedTables(id) if (!normalizedData) { - // Workflow exists but has no blocks in normalized tables (empty workflow or not migrated) - // This is valid state - return success with no redeployment needed return createSuccessResponse({ isDeployed: validation.workflow.isDeployed, deployedAt: validation.workflow.deployedAt, @@ -41,11 +36,18 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ }) } + const [workflowRecord] = await db + .select({ variables: workflow.variables }) + .from(workflow) + .where(eq(workflow.id, id)) + .limit(1) + const currentState = { blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, parallels: normalizedData.parallels, + variables: workflowRecord?.variables || {}, lastSaved: Date.now(), } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx index 31c7645c90..8425bc68b5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx @@ -35,7 +35,6 @@ interface DeployModalProps { workflowId: string | null isDeployed: boolean needsRedeployment: boolean - setNeedsRedeployment: (value: boolean) => void deployedState: WorkflowState isLoadingDeployedState: boolean refetchDeployedState: () => Promise @@ -58,7 +57,6 @@ export function DeployModal({ workflowId, isDeployed: isDeployedProp, needsRedeployment, - setNeedsRedeployment, deployedState, isLoadingDeployedState, refetchDeployedState, @@ -229,7 +227,6 @@ export function DeployModal({ setDeploymentStatus(workflowId, isDeployedStatus, deployedAtTime, apiKeyLabel) - setNeedsRedeployment(false) if (workflowId) { useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false) } @@ -453,7 +450,6 @@ export function DeployModal({ getApiKeyLabel(apiKey) ) - setNeedsRedeployment(false) if (workflowId) { useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx index 7c2253351b..f00b070ea2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx @@ -45,8 +45,7 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP isRegistryLoading, }) - // Detect changes between current and deployed state - const { changeDetected, setChangeDetected } = useChangeDetection({ + const { changeDetected } = useChangeDetection({ workflowId: activeWorkflowId, deployedState, isLoadingDeployedState, @@ -136,7 +135,6 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP workflowId={activeWorkflowId} isDeployed={isDeployed} needsRedeployment={changeDetected} - setNeedsRedeployment={setChangeDetected} deployedState={deployedState!} isLoadingDeployedState={isLoadingDeployedState} refetchDeployedState={refetchWithErrorHandling} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-change-detection.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-change-detection.ts index 1fcf76325b..869c096ce7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-change-detection.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks/use-change-detection.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react' import { hasWorkflowChanged } from '@/lib/workflows/comparison' import { useDebounce } from '@/hooks/use-debounce' +import { useVariablesStore } from '@/stores/panel/variables/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { WorkflowState } from '@/stores/workflows/workflow/types' @@ -27,8 +28,18 @@ export function useChangeDetection({ const subBlockValues = useSubBlockStore((state) => workflowId ? state.workflowValues[workflowId] : null ) + const allVariables = useVariablesStore((state) => state.variables) + const workflowVariables = useMemo(() => { + if (!workflowId) return {} + const vars: Record = {} + for (const [id, variable] of Object.entries(allVariables)) { + if (variable.workflowId === workflowId) { + vars[id] = variable + } + } + return vars + }, [workflowId, allVariables]) - // Build current state with subblock values merged into blocks const currentState = useMemo((): WorkflowState | null => { if (!workflowId) return null @@ -37,12 +48,10 @@ export function useChangeDetection({ const blockSubValues = subBlockValues?.[blockId] || {} const subBlocks: Record = {} - // Merge subblock values into the block's subBlocks structure for (const [subId, value] of Object.entries(blockSubValues)) { subBlocks[subId] = { value } } - // Also include existing subBlocks from the block itself if (block.subBlocks) { for (const [subId, subBlock] of Object.entries(block.subBlocks)) { if (!subBlocks[subId]) { @@ -64,10 +73,10 @@ export function useChangeDetection({ edges, loops, parallels, - } - }, [workflowId, blocks, edges, loops, parallels, subBlockValues]) + variables: workflowVariables, + } as WorkflowState & { variables: Record } + }, [workflowId, blocks, edges, loops, parallels, subBlockValues, workflowVariables]) - // Compute change detection with debouncing for performance const rawChangeDetected = useMemo(() => { if (!currentState || !deployedState || isLoadingDeployedState) { return false @@ -75,13 +84,7 @@ export function useChangeDetection({ return hasWorkflowChanged(currentState, deployedState) }, [currentState, deployedState, isLoadingDeployedState]) - // Debounce to avoid UI flicker during rapid edits const changeDetected = useDebounce(rawChangeDetected, 300) - const setChangeDetected = () => { - // No-op: change detection is now computed, not stateful - // Kept for API compatibility - } - - return { changeDetected, setChangeDetected } + return { changeDetected } } diff --git a/apps/sim/lib/workflows/comparison/compare.test.ts b/apps/sim/lib/workflows/comparison/compare.test.ts index 2eeacfbcc2..0c2543c499 100644 --- a/apps/sim/lib/workflows/comparison/compare.test.ts +++ b/apps/sim/lib/workflows/comparison/compare.test.ts @@ -2075,4 +2075,437 @@ describe('hasWorkflowChanged', () => { expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) }) }) + + describe('Variable Changes', () => { + it.concurrent('should detect added variables', () => { + const deployedState = { + ...createWorkflowState({}), + variables: {}, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should detect removed variables', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: {}, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should detect variable value changes', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'world' }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should detect variable type changes', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: '123' }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'number', value: 123 }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should detect variable name changes', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'oldName', type: 'string', value: 'hello' }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'newName', type: 'string', value: 'hello' }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should not detect change for identical variables', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + var2: { id: 'var2', name: 'count', type: 'number', value: 42 }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + var2: { id: 'var2', name: 'count', type: 'number', value: 42 }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(false) + }) + + it.concurrent('should not detect change for empty variables on both sides', () => { + const deployedState = { + ...createWorkflowState({}), + variables: {}, + } + + const currentState = { + ...createWorkflowState({}), + variables: {}, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(false) + }) + + it.concurrent('should not detect change for undefined vs empty object variables', () => { + const deployedState = { + ...createWorkflowState({}), + variables: undefined, + } + + const currentState = { + ...createWorkflowState({}), + variables: {}, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(false) + }) + + it.concurrent('should handle complex variable values (objects)', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'config', type: 'object', value: { key: 'value1' } }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'config', type: 'object', value: { key: 'value2' } }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should handle complex variable values (arrays)', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'items', type: 'array', value: [1, 2, 3] }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'items', type: 'array', value: [1, 2, 4] }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(true) + }) + + it.concurrent('should not detect change when variable key order differs', () => { + const deployedState = { + ...createWorkflowState({}), + variables: { + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + var2: { id: 'var2', name: 'count', type: 'number', value: 42 }, + }, + } + + const currentState = { + ...createWorkflowState({}), + variables: { + var2: { id: 'var2', name: 'count', type: 'number', value: 42 }, + var1: { id: 'var1', name: 'myVar', type: 'string', value: 'hello' }, + }, + } + + expect(hasWorkflowChanged(currentState as any, deployedState as any)).toBe(false) + }) + }) + + describe('Trigger Runtime Metadata (Should Not Trigger Change)', () => { + it.concurrent('should not detect change when webhookId differs', () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: null }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: 'wh_123456' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + }) + + it.concurrent('should not detect change when triggerPath differs', () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + triggerPath: { value: '' }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + triggerPath: { value: '/api/webhooks/abc123' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + }) + + it.concurrent('should not detect change when testUrl differs', () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + testUrl: { value: null }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + testUrl: { value: 'https://test.example.com/webhook' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + }) + + it.concurrent('should not detect change when testUrlExpiresAt differs', () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + testUrlExpiresAt: { value: null }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + testUrlExpiresAt: { value: '2025-12-31T23:59:59Z' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + }) + + it.concurrent('should not detect change when all runtime metadata differs', () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: null }, + triggerPath: { value: '' }, + testUrl: { value: null }, + testUrlExpiresAt: { value: null }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: 'wh_123456' }, + triggerPath: { value: '/api/webhooks/abc123' }, + testUrl: { value: 'https://test.example.com/webhook' }, + testUrlExpiresAt: { value: '2025-12-31T23:59:59Z' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + }) + + it.concurrent( + 'should detect change when triggerConfig differs but runtime metadata also differs', + () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: null }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'pull_request' } }, + webhookId: { value: 'wh_123456' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(true) + } + ) + + it.concurrent( + 'should not detect change when runtime metadata is added to current state', + () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: 'wh_123456' }, + triggerPath: { value: '/api/webhooks/abc123' }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + } + ) + + it.concurrent( + 'should not detect change when runtime metadata is removed from current state', + () => { + const deployedState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + webhookId: { value: 'wh_old123' }, + triggerPath: { value: '/api/webhooks/old' }, + }, + }), + }, + }) + + const currentState = createWorkflowState({ + blocks: { + block1: createBlock('block1', { + type: 'starter', + subBlocks: { + triggerConfig: { value: { event: 'push' } }, + }, + }), + }, + }) + + expect(hasWorkflowChanged(currentState, deployedState)).toBe(false) + } + ) + }) }) diff --git a/apps/sim/lib/workflows/comparison/compare.ts b/apps/sim/lib/workflows/comparison/compare.ts index e6efb75791..40957962b7 100644 --- a/apps/sim/lib/workflows/comparison/compare.ts +++ b/apps/sim/lib/workflows/comparison/compare.ts @@ -1,4 +1,5 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types' +import { TRIGGER_RUNTIME_SUBBLOCK_IDS } from '@/triggers/constants' import { normalizedStringify, normalizeEdge, @@ -100,10 +101,12 @@ export function hasWorkflowChanged( subBlocks: undefined, } - // Get all subBlock IDs from both states + // Get all subBlock IDs from both states, excluding runtime metadata const allSubBlockIds = [ ...new Set([...Object.keys(currentSubBlocks), ...Object.keys(deployedSubBlocks)]), - ].sort() + ] + .filter((id) => !TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(id)) + .sort() // Normalize and compare each subBlock for (const subBlockId of allSubBlockIds) { @@ -224,5 +227,16 @@ export function hasWorkflowChanged( } } + // 6. Compare variables + const currentVariables = (currentState as any).variables || {} + const deployedVariables = (deployedState as any).variables || {} + + const normalizedCurrentVars = normalizeValue(currentVariables) + const normalizedDeployedVars = normalizeValue(deployedVariables) + + if (normalizedStringify(normalizedCurrentVars) !== normalizedStringify(normalizedDeployedVars)) { + return true + } + return false } diff --git a/apps/sim/lib/workflows/persistence/utils.ts b/apps/sim/lib/workflows/persistence/utils.ts index e24c0708ac..e08caa70df 100644 --- a/apps/sim/lib/workflows/persistence/utils.ts +++ b/apps/sim/lib/workflows/persistence/utils.ts @@ -42,6 +42,7 @@ export interface NormalizedWorkflowData { export interface DeployedWorkflowData extends NormalizedWorkflowData { deploymentVersionId: string + variables?: Record } export async function blockExistsInDeployment( @@ -94,13 +95,14 @@ export async function loadDeployedWorkflowState(workflowId: string): Promise } return { blocks: state.blocks || {}, edges: state.edges || [], loops: state.loops || {}, parallels: state.parallels || {}, + variables: state.variables || {}, isFromNormalizedTables: false, deploymentVersionId: active.id, }