diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx index cac127c00..bd9569248 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx @@ -112,19 +112,13 @@ export const ActionBar = memo( const isInsideSubflow = parentId && (parentType === 'loop' || parentType === 'parallel') const snapshot = activeWorkflowId ? getLastExecutionSnapshot(activeWorkflowId) : null - const hasExecutionSnapshot = !!snapshot - const dependenciesSatisfied = (() => { - if (!snapshot) return false - const incomingEdges = edges.filter((edge) => edge.target === blockId) - if (incomingEdges.length === 0) return true - return incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source)) - })() + const incomingEdges = edges.filter((edge) => edge.target === blockId) + const isTriggerBlock = incomingEdges.length === 0 + const dependenciesSatisfied = + isTriggerBlock || + (snapshot && incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source))) const canRunFromBlock = - hasExecutionSnapshot && - dependenciesSatisfied && - !isNoteBlock && - !isInsideSubflow && - !isExecuting + dependenciesSatisfied && !isNoteBlock && !isInsideSubflow && !isExecuting const handleRunFromBlockClick = useCallback(() => { if (!activeWorkflowId || !canRunFromBlock) return @@ -176,9 +170,8 @@ export const ActionBar = memo( {(() => { if (disabled) return getTooltipMessage('Run from this block') if (isExecuting) return 'Execution in progress' - if (!hasExecutionSnapshot) return 'Run workflow first' - if (!dependenciesSatisfied) return 'Run upstream blocks first' if (isInsideSubflow) return 'Cannot run from inside subflow' + if (!dependenciesSatisfied) return 'Run upstream blocks first' return 'Run from this block' })()} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index c52785294..983896fe2 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -1435,16 +1435,18 @@ export function useWorkflowExecution() { const handleRunFromBlock = useCallback( async (blockId: string, workflowId: string) => { const snapshot = getLastExecutionSnapshot(workflowId) - if (!snapshot) { + const workflowEdges = useWorkflowStore.getState().edges + const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId) + const isTriggerBlock = incomingEdges.length === 0 + + if (!snapshot && !isTriggerBlock) { logger.error('No execution snapshot available for run-from-block', { workflowId, blockId }) return } - const workflowEdges = useWorkflowStore.getState().edges - const incomingEdges = workflowEdges.filter((edge) => edge.target === blockId) const dependenciesSatisfied = - incomingEdges.length === 0 || - incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source)) + isTriggerBlock || + (snapshot && incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source))) if (!dependenciesSatisfied) { logger.error('Upstream dependencies not satisfied for run-from-block', { @@ -1454,10 +1456,20 @@ export function useWorkflowExecution() { return } + // For trigger blocks with no snapshot, create an empty one + const effectiveSnapshot: SerializableExecutionState = snapshot || { + blockStates: {}, + executedBlocks: [], + blockLogs: [], + decisions: { router: {}, condition: {} }, + completedLoops: [], + activeExecutionPath: [], + } + logger.info('Starting run-from-block execution', { workflowId, startBlockId: blockId, - snapshotExecutedBlocks: snapshot.executedBlocks.length, + isTriggerBlock, }) setIsExecuting(true) @@ -1471,7 +1483,7 @@ export function useWorkflowExecution() { await executionStream.executeFromBlock({ workflowId, startBlockId: blockId, - sourceSnapshot: snapshot, + sourceSnapshot: effectiveSnapshot, callbacks: { onExecutionStarted: (data) => { logger.info('Run-from-block execution started:', data) @@ -1579,21 +1591,23 @@ export function useWorkflowExecution() { onExecutionCompleted: (data) => { if (data.success) { - const mergedBlockStates: Record = { ...snapshot.blockStates } + const mergedBlockStates: Record = { + ...effectiveSnapshot.blockStates, + } for (const [bId, state] of accumulatedBlockStates) { mergedBlockStates[bId] = state } const mergedExecutedBlocks = new Set([ - ...snapshot.executedBlocks, + ...effectiveSnapshot.executedBlocks, ...executedBlockIds, ]) const updatedSnapshot: SerializableExecutionState = { - ...snapshot, + ...effectiveSnapshot, blockStates: mergedBlockStates, executedBlocks: Array.from(mergedExecutedBlocks), - blockLogs: [...snapshot.blockLogs, ...accumulatedBlockLogs], + blockLogs: [...effectiveSnapshot.blockLogs, ...accumulatedBlockLogs], activeExecutionPath: Array.from(mergedExecutedBlocks), } setLastExecutionSnapshot(workflowId, updatedSnapshot) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 1a51f0a4a..33b10a393 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -1012,18 +1012,17 @@ const WorkflowContent = React.memo(() => { } const block = contextMenuBlocks[0] const snapshot = getLastExecutionSnapshot(workflowIdParam) - if (!snapshot) return { canRun: false, reason: 'Run workflow first' } - const incomingEdges = edges.filter((edge) => edge.target === block.id) + const isTriggerBlock = incomingEdges.length === 0 const dependenciesSatisfied = - incomingEdges.length === 0 || - incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source)) + isTriggerBlock || + (snapshot && incomingEdges.every((edge) => snapshot.executedBlocks.includes(edge.source))) const isNoteBlock = block.type === 'note' const isInsideSubflow = block.parentId && (block.parentType === 'loop' || block.parentType === 'parallel') - if (!dependenciesSatisfied) return { canRun: false, reason: 'Run upstream blocks first' } if (isInsideSubflow) return { canRun: false, reason: 'Cannot run from inside subflow' } + if (!dependenciesSatisfied) return { canRun: false, reason: 'Run upstream blocks first' } if (isNoteBlock) return { canRun: false, reason: undefined } if (isExecuting) return { canRun: false, reason: undefined }