diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 22fe3c8ce6..24365795b1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -99,19 +99,33 @@ const logger = createLogger('Workflow') const DEFAULT_PASTE_OFFSET = { x: 50, y: 50 } /** - * Calculates the offset to paste blocks at viewport center + * Calculates the offset to paste blocks at viewport center, or simple offset for nested blocks */ function calculatePasteOffset( clipboard: { - blocks: Record + blocks: Record< + string, + { + position: { x: number; y: number } + type: string + height?: number + data?: { parentId?: string } + } + > } | null, - viewportCenter: { x: number; y: number } + viewportCenter: { x: number; y: number }, + existingBlocks: Record = {} ): { x: number; y: number } { if (!clipboard) return DEFAULT_PASTE_OFFSET const clipboardBlocks = Object.values(clipboard.blocks) if (clipboardBlocks.length === 0) return DEFAULT_PASTE_OFFSET + const allBlocksNested = clipboardBlocks.every( + (b) => b.data?.parentId && existingBlocks[b.data.parentId] + ) + if (allBlocksNested) return DEFAULT_PASTE_OFFSET + const minX = Math.min(...clipboardBlocks.map((b) => b.position.x)) const maxX = Math.max( ...clipboardBlocks.map((b) => { @@ -307,9 +321,7 @@ const WorkflowContent = React.memo(() => { const isAutoConnectEnabled = useAutoConnect() const autoConnectRef = useRef(isAutoConnectEnabled) - useEffect(() => { - autoConnectRef.current = isAutoConnectEnabled - }, [isAutoConnectEnabled]) + autoConnectRef.current = isAutoConnectEnabled // Panel open states for context menu const isVariablesOpen = useVariablesStore((state) => state.isOpen) @@ -448,11 +460,16 @@ const WorkflowContent = React.memo(() => { ) /** Re-applies diff markers when blocks change after socket rehydration. */ - const blocksRef = useRef(blocks) + const diffBlocksRef = useRef(blocks) useEffect(() => { if (!isWorkflowReady) return - if (hasActiveDiff && isDiffReady && blocks !== blocksRef.current) { - blocksRef.current = blocks + + const blocksChanged = blocks !== diffBlocksRef.current + if (!blocksChanged) return + + diffBlocksRef.current = blocks + + if (hasActiveDiff && isDiffReady) { setTimeout(() => reapplyDiffMarkers(), 0) } }, [blocks, hasActiveDiff, isDiffReady, reapplyDiffMarkers, isWorkflowReady]) @@ -515,8 +532,7 @@ const WorkflowContent = React.memo(() => { }) }, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks]) - const { userPermissions, workspacePermissions, permissionsError } = - useWorkspacePermissionsContext() + const { userPermissions } = useWorkspacePermissionsContext() /** Returns read-only permissions when viewing snapshot, otherwise user permissions. */ const effectivePermissions = useMemo(() => { @@ -752,25 +768,6 @@ const WorkflowContent = React.memo(() => { [isErrorConnectionDrag] ) - /** Logs permission loading results for debugging. */ - useEffect(() => { - if (permissionsError) { - logger.error('Failed to load workspace permissions', { - workspaceId, - error: permissionsError, - }) - } else if (workspacePermissions) { - logger.info('Workspace permissions loaded in workflow', { - workspaceId, - userCount: workspacePermissions.total, - permissions: workspacePermissions.users.map((u) => ({ - email: u.email, - permissions: u.permissionType, - })), - }) - } - }, [workspacePermissions, permissionsError, workspaceId]) - const updateNodeParent = useCallback( (nodeId: string, newParentId: string | null, affectedEdges: any[] = []) => { const node = getNodes().find((n: any) => n.id === nodeId) @@ -1042,7 +1039,7 @@ const WorkflowContent = React.memo(() => { executePasteOperation( 'paste', - calculatePasteOffset(clipboard, getViewportCenter()), + calculatePasteOffset(clipboard, getViewportCenter(), blocks), targetContainer, flowPosition // Pass the click position so blocks are centered at where user right-clicked ) @@ -1054,6 +1051,7 @@ const WorkflowContent = React.memo(() => { screenToFlowPosition, contextMenuPosition, isPointInLoopNode, + blocks, ]) const handleContextDuplicate = useCallback(() => { @@ -1164,7 +1162,10 @@ const WorkflowContent = React.memo(() => { } else if ((event.ctrlKey || event.metaKey) && event.key === 'v') { if (effectivePermissions.canEdit && hasClipboard()) { event.preventDefault() - executePasteOperation('paste', calculatePasteOffset(clipboard, getViewportCenter())) + executePasteOperation( + 'paste', + calculatePasteOffset(clipboard, getViewportCenter(), blocks) + ) } } } @@ -1186,6 +1187,7 @@ const WorkflowContent = React.memo(() => { clipboard, getViewportCenter, executePasteOperation, + blocks, ]) /** @@ -2160,6 +2162,8 @@ const WorkflowContent = React.memo(() => { // Local state for nodes - allows smooth drag without store updates on every frame const [displayNodes, setDisplayNodes] = useState([]) + // Sync derivedNodes to displayNodes while preserving selection state + // This effect handles both normal sync and pending selection from paste/duplicate useEffect(() => { // Check for pending selection (from paste/duplicate), otherwise preserve existing selection if (pendingSelection && pendingSelection.length > 0) { @@ -2189,7 +2193,6 @@ const WorkflowContent = React.memo(() => { }, [derivedNodes, blocks, pendingSelection, clearPendingSelection]) // Phase 2: When displayNodes updates, check if pending zoom blocks are ready - // (Phase 1 is located earlier in the file where pendingZoomBlockIdsRef is defined) useEffect(() => { const pendingBlockIds = pendingZoomBlockIdsRef.current if (!pendingBlockIds || pendingBlockIds.size === 0) { @@ -2380,40 +2383,6 @@ const WorkflowContent = React.memo(() => { resizeLoopNodesWrapper() }, [derivedNodes, resizeLoopNodesWrapper, isWorkflowReady]) - /** Cleans up orphaned nodes with invalid parent references after deletion. */ - useEffect(() => { - if (!isWorkflowReady) return - - // Create a mapping of node IDs to check for missing parent references - const nodeIds = new Set(Object.keys(blocks)) - - // Check for nodes with invalid parent references and collect updates - const orphanedUpdates: Array<{ - id: string - position: { x: number; y: number } - parentId: string - }> = [] - Object.entries(blocks).forEach(([id, block]) => { - const parentId = block.data?.parentId - - // If block has a parent reference but parent no longer exists - if (parentId && !nodeIds.has(parentId)) { - logger.warn('Found orphaned node with invalid parent reference', { - nodeId: id, - missingParentId: parentId, - }) - - const absolutePosition = getNodeAbsolutePosition(id) - orphanedUpdates.push({ id, position: absolutePosition, parentId: '' }) - } - }) - - // Batch update all orphaned nodes at once - if (orphanedUpdates.length > 0) { - batchUpdateBlocksWithParent(orphanedUpdates) - } - }, [blocks, batchUpdateBlocksWithParent, getNodeAbsolutePosition, isWorkflowReady]) - /** Handles edge removal changes. */ const onEdgesChange = useCallback( (changes: any) => { diff --git a/apps/sim/stores/workflows/workflow/store.test.ts b/apps/sim/stores/workflows/workflow/store.test.ts index 1ed122c238..2590d55802 100644 --- a/apps/sim/stores/workflows/workflow/store.test.ts +++ b/apps/sim/stores/workflows/workflow/store.test.ts @@ -22,8 +22,6 @@ import { WorkflowBuilder, } from '@sim/testing' import { beforeEach, describe, expect, it } from 'vitest' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' describe('workflow store', () => { @@ -365,30 +363,6 @@ describe('workflow store', () => { }) }) - describe('duplicateBlock', () => { - it('should duplicate a block', () => { - const { addBlock, duplicateBlock } = useWorkflowStore.getState() - - addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 }) - - duplicateBlock('original') - - const { blocks } = useWorkflowStore.getState() - const blockIds = Object.keys(blocks) - - expect(blockIds.length).toBe(2) - - const duplicatedId = blockIds.find((id) => id !== 'original') - expect(duplicatedId).toBeDefined() - - if (duplicatedId) { - expect(blocks[duplicatedId].type).toBe('agent') - expect(blocks[duplicatedId].name).toContain('Original Agent') - expect(blocks[duplicatedId].position.x).not.toBe(0) - } - }) - }) - describe('batchUpdatePositions', () => { it('should update block position', () => { const { addBlock, batchUpdatePositions } = useWorkflowStore.getState() @@ -452,29 +426,6 @@ describe('workflow store', () => { expect(state.loops.loop1.forEachItems).toBe('["a", "b", "c"]') }) - it('should regenerate loops when updateLoopCollection is called', () => { - const { addBlock, updateLoopCollection } = useWorkflowStore.getState() - - addBlock( - 'loop1', - 'loop', - 'Test Loop', - { x: 0, y: 0 }, - { - loopType: 'forEach', - collection: '["item1", "item2"]', - } - ) - - updateLoopCollection('loop1', '["item1", "item2", "item3"]') - - const state = useWorkflowStore.getState() - - expect(state.blocks.loop1?.data?.collection).toBe('["item1", "item2", "item3"]') - expect(state.loops.loop1).toBeDefined() - expect(state.loops.loop1.forEachItems).toBe('["item1", "item2", "item3"]') - }) - it('should clamp loop count between 1 and 1000', () => { const { addBlock, updateLoopCount } = useWorkflowStore.getState() @@ -599,118 +550,6 @@ describe('workflow store', () => { }) }) - describe('mode switching', () => { - it('should toggle advanced mode on a block', () => { - const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState() - - addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 }) - - let state = useWorkflowStore.getState() - expect(state.blocks.agent1?.advancedMode).toBe(false) - - toggleBlockAdvancedMode('agent1') - state = useWorkflowStore.getState() - expect(state.blocks.agent1?.advancedMode).toBe(true) - - toggleBlockAdvancedMode('agent1') - state = useWorkflowStore.getState() - expect(state.blocks.agent1?.advancedMode).toBe(false) - }) - - it('should preserve systemPrompt and userPrompt when switching modes', () => { - const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState() - const { setState: setSubBlockState } = useSubBlockStore - useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' }) - addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 }) - setSubBlockState({ - workflowValues: { - 'test-workflow': { - agent1: { - systemPrompt: 'You are a helpful assistant', - userPrompt: 'Hello, how are you?', - }, - }, - }, - }) - toggleBlockAdvancedMode('agent1') - let subBlockState = useSubBlockStore.getState() - expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe( - 'You are a helpful assistant' - ) - expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe( - 'Hello, how are you?' - ) - toggleBlockAdvancedMode('agent1') - subBlockState = useSubBlockStore.getState() - expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe( - 'You are a helpful assistant' - ) - expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe( - 'Hello, how are you?' - ) - }) - - it('should preserve memories when switching from advanced to basic mode', () => { - const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState() - const { setState: setSubBlockState } = useSubBlockStore - - useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' }) - - addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 }) - - toggleBlockAdvancedMode('agent1') - - setSubBlockState({ - workflowValues: { - 'test-workflow': { - agent1: { - systemPrompt: 'You are a helpful assistant', - userPrompt: 'What did we discuss?', - memories: [ - { role: 'user', content: 'My name is John' }, - { role: 'assistant', content: 'Nice to meet you, John!' }, - ], - }, - }, - }, - }) - - toggleBlockAdvancedMode('agent1') - - const subBlockState = useSubBlockStore.getState() - expect(subBlockState.workflowValues['test-workflow'].agent1.systemPrompt).toBe( - 'You are a helpful assistant' - ) - expect(subBlockState.workflowValues['test-workflow'].agent1.userPrompt).toBe( - 'What did we discuss?' - ) - expect(subBlockState.workflowValues['test-workflow'].agent1.memories).toEqual([ - { role: 'user', content: 'My name is John' }, - { role: 'assistant', content: 'Nice to meet you, John!' }, - ]) - }) - - it('should handle mode switching when no subblock values exist', () => { - const { addBlock, toggleBlockAdvancedMode } = useWorkflowStore.getState() - - useWorkflowRegistry.setState({ activeWorkflowId: 'test-workflow' }) - - addBlock('agent1', 'agent', 'Test Agent', { x: 0, y: 0 }) - - expect(useWorkflowStore.getState().blocks.agent1?.advancedMode).toBe(false) - expect(() => toggleBlockAdvancedMode('agent1')).not.toThrow() - - const state = useWorkflowStore.getState() - expect(state.blocks.agent1?.advancedMode).toBe(true) - }) - - it('should not throw when toggling non-existent block', () => { - const { toggleBlockAdvancedMode } = useWorkflowStore.getState() - - expect(() => toggleBlockAdvancedMode('non-existent')).not.toThrow() - }) - }) - describe('workflow state management', () => { it('should work with WorkflowBuilder for complex setups', () => { const workflowState = WorkflowBuilder.linear(3).build() diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 00eeac9b88..b4d74860f7 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -2,20 +2,16 @@ import { createLogger } from '@sim/logger' import type { Edge } from 'reactflow' import { create } from 'zustand' import { devtools } from 'zustand/middleware' -import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants' +import { CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions' import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { getBlock } from '@/blocks' import type { SubBlockConfig } from '@/blocks/types' import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { - filterNewEdges, - filterValidEdges, - getUniqueBlockName, - mergeSubblockState, -} from '@/stores/workflows/utils' +import { filterNewEdges, filterValidEdges } from '@/stores/workflows/utils' import type { + BlockState, Position, SubBlockState, WorkflowState, @@ -139,30 +135,30 @@ export const useWorkflowStore = create()( ...(parentId && { parentId, extent: extent || 'parent' }), } - const newState = { - blocks: { - ...get().blocks, - [id]: { - id, - type, - name, - position, - subBlocks: {}, - outputs: {}, - enabled: blockProperties?.enabled ?? true, - horizontalHandles: blockProperties?.horizontalHandles ?? true, - advancedMode: blockProperties?.advancedMode ?? false, - triggerMode: blockProperties?.triggerMode ?? false, - height: blockProperties?.height ?? 0, - data: nodeData, - }, + const newBlocks = { + ...get().blocks, + [id]: { + id, + type, + name, + position, + subBlocks: {}, + outputs: {}, + enabled: blockProperties?.enabled ?? true, + horizontalHandles: blockProperties?.horizontalHandles ?? true, + advancedMode: blockProperties?.advancedMode ?? false, + triggerMode: blockProperties?.triggerMode ?? false, + height: blockProperties?.height ?? 0, + data: nodeData, }, - edges: [...get().edges], - loops: get().generateLoopBlocks(), - parallels: get().generateParallelBlocks(), } - set(newState) + set({ + blocks: newBlocks, + edges: [...get().edges], + loops: generateLoopBlocks(newBlocks), + parallels: generateParallelBlocks(newBlocks), + }) get().updateLastSaved() return } @@ -215,31 +211,31 @@ export const useWorkflowStore = create()( const triggerMode = blockProperties?.triggerMode ?? false const outputs = getBlockOutputs(type, subBlocks, triggerMode) - const newState = { - blocks: { - ...get().blocks, - [id]: { - id, - type, - name, - position, - subBlocks, - outputs, - enabled: blockProperties?.enabled ?? true, - horizontalHandles: blockProperties?.horizontalHandles ?? true, - advancedMode: blockProperties?.advancedMode ?? false, - triggerMode: triggerMode, - height: blockProperties?.height ?? 0, - layout: {}, - data: nodeData, - }, + const newBlocks = { + ...get().blocks, + [id]: { + id, + type, + name, + position, + subBlocks, + outputs, + enabled: blockProperties?.enabled ?? true, + horizontalHandles: blockProperties?.horizontalHandles ?? true, + advancedMode: blockProperties?.advancedMode ?? false, + triggerMode: triggerMode, + height: blockProperties?.height ?? 0, + layout: {}, + data: nodeData, }, - edges: [...get().edges], - loops: get().generateLoopBlocks(), - parallels: get().generateParallelBlocks(), } - set(newState) + set({ + blocks: newBlocks, + edges: [...get().edges], + loops: generateLoopBlocks(newBlocks), + parallels: generateParallelBlocks(newBlocks), + }) get().updateLastSaved() }, @@ -448,6 +444,41 @@ export const useWorkflowStore = create()( delete newBlocks[blockId] }) + // Clean up orphaned nodes - blocks whose parent was removed but weren't descendants + // This can happen in edge cases (e.g., data inconsistency, external modifications) + const remainingBlockIds = new Set(Object.keys(newBlocks)) + const CONTAINER_OFFSET = { + x: CONTAINER_DIMENSIONS.LEFT_PADDING, + y: CONTAINER_DIMENSIONS.HEADER_HEIGHT + CONTAINER_DIMENSIONS.TOP_PADDING, + } + + Object.entries(newBlocks).forEach(([blockId, block]) => { + const parentId = block.data?.parentId + if (parentId && !remainingBlockIds.has(parentId)) { + // Parent was removed - convert to absolute position and clear parentId + // Child positions are relative to container content area (after header + padding) + let absoluteX = block.position.x + let absoluteY = block.position.y + + // Traverse up the parent chain, adding position + container offset for each level + let currentParentId: string | undefined = parentId + while (currentParentId) { + const parent: BlockState | undefined = currentBlocks[currentParentId] + if (!parent) break + absoluteX += parent.position.x + CONTAINER_OFFSET.x + absoluteY += parent.position.y + CONTAINER_OFFSET.y + currentParentId = parent.data?.parentId + } + + const { parentId: _removed, extent: _removedExtent, ...restData } = block.data || {} + newBlocks[blockId] = { + ...block, + position: { x: absoluteX, y: absoluteY }, + data: Object.keys(restData).length > 0 ? restData : undefined, + } + } + }) + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId if (activeWorkflowId) { const subBlockStore = useSubBlockStore.getState() @@ -584,7 +615,20 @@ export const useWorkflowStore = create()( options?: { updateLastSaved?: boolean } ) => { set((state) => { - const nextBlocks = workflowState.blocks || {} + const incomingBlocks = workflowState.blocks || {} + + const nextBlocks: typeof incomingBlocks = {} + for (const [id, block] of Object.entries(incomingBlocks)) { + if (block.data?.parentId && !incomingBlocks[block.data.parentId]) { + nextBlocks[id] = { + ...block, + data: { ...block.data, parentId: undefined, extent: undefined }, + } + } else { + nextBlocks[id] = block + } + } + const nextEdges = filterValidEdges(workflowState.edges || [], nextBlocks) const nextLoops = Object.keys(workflowState.loops || {}).length > 0 @@ -635,66 +679,6 @@ export const useWorkflowStore = create()( get().updateLastSaved() }, - duplicateBlock: (id: string) => { - const block = get().blocks[id] - if (!block) return - - const newId = crypto.randomUUID() - const offsetPosition = { - x: block.position.x + DEFAULT_DUPLICATE_OFFSET.x, - y: block.position.y + DEFAULT_DUPLICATE_OFFSET.y, - } - - const newName = getUniqueBlockName(block.name, get().blocks) - - const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId - const mergedBlock = mergeSubblockState(get().blocks, activeWorkflowId || undefined, id)[id] - - const newSubBlocks = Object.entries(mergedBlock.subBlocks).reduce( - (acc, [subId, subBlock]) => ({ - ...acc, - [subId]: { - ...subBlock, - value: JSON.parse(JSON.stringify(subBlock.value)), - }, - }), - {} - ) - - const newState = { - blocks: { - ...get().blocks, - [newId]: { - ...block, - id: newId, - name: newName, - position: offsetPosition, - subBlocks: newSubBlocks, - }, - }, - edges: [...get().edges], - loops: get().generateLoopBlocks(), - parallels: get().generateParallelBlocks(), - } - - if (activeWorkflowId) { - const subBlockValues = - useSubBlockStore.getState().workflowValues[activeWorkflowId]?.[id] || {} - useSubBlockStore.setState((state) => ({ - workflowValues: { - ...state.workflowValues, - [activeWorkflowId]: { - ...state.workflowValues[activeWorkflowId], - [newId]: JSON.parse(JSON.stringify(subBlockValues)), - }, - }, - })) - } - - set(newState) - get().updateLastSaved() - }, - setBlockHandles: (id: string, horizontalHandles: boolean) => { const block = get().blocks[id] if (!block || block.horizontalHandles === horizontalHandles) return @@ -890,27 +874,10 @@ export const useWorkflowStore = create()( get().updateLastSaved() }, - setBlockTriggerMode: (id: string, triggerMode: boolean) => { - set((state) => ({ - blocks: { - ...state.blocks, - [id]: { - ...state.blocks[id], - triggerMode, - }, - }, - edges: [...state.edges], - loops: { ...state.loops }, - })) - get().updateLastSaved() - // Note: Socket.IO handles real-time sync automatically - }, - updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => { set((state) => { const block = state.blocks[id] if (!block) { - logger.warn(`Cannot update layout metrics: Block ${id} not found in workflow store`) return state } @@ -932,7 +899,6 @@ export const useWorkflowStore = create()( } }) get().updateLastSaved() - // No sync needed for layout changes, just visual }, updateLoopCount: (loopId: string, count: number) => @@ -1050,30 +1016,6 @@ export const useWorkflowStore = create()( } }), - updateLoopCollection: (loopId: string, collection: string) => { - const store = get() - const block = store.blocks[loopId] - if (!block || block.type !== 'loop') return - - const loopType = block.data?.loopType || 'for' - - if (loopType === 'while') { - store.setLoopWhileCondition(loopId, collection) - } else if (loopType === 'doWhile') { - store.setLoopDoWhileCondition(loopId, collection) - } else if (loopType === 'forEach') { - store.setLoopForEachItems(loopId, collection) - } else { - // Default to forEach-style storage for backward compatibility - store.setLoopForEachItems(loopId, collection) - } - }, - - // Function to convert UI loop blocks to execution format - generateLoopBlocks: () => { - return generateLoopBlocks(get().blocks) - }, - triggerUpdate: () => { set((state) => ({ ...state, @@ -1161,28 +1103,6 @@ export const useWorkflowStore = create()( } }, - toggleBlockAdvancedMode: (id: string) => { - const block = get().blocks[id] - if (!block) return - - const newState = { - blocks: { - ...get().blocks, - [id]: { - ...block, - advancedMode: !block.advancedMode, - }, - }, - edges: [...get().edges], - loops: { ...get().loops }, - } - - set(newState) - - get().triggerUpdate() - // Note: Socket.IO handles real-time sync automatically - }, - // Parallel block methods implementation updateParallelCount: (parallelId: string, count: number) => { const block = get().blocks[parallelId] @@ -1208,7 +1128,6 @@ export const useWorkflowStore = create()( set(newState) get().updateLastSaved() - // Note: Socket.IO handles real-time sync automatically }, updateParallelCollection: (parallelId: string, collection: string) => { @@ -1235,7 +1154,6 @@ export const useWorkflowStore = create()( set(newState) get().updateLastSaved() - // Note: Socket.IO handles real-time sync automatically }, updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => { @@ -1262,12 +1180,6 @@ export const useWorkflowStore = create()( set(newState) get().updateLastSaved() - // Note: Socket.IO handles real-time sync automatically - }, - - // Function to convert UI parallel blocks to execution format - generateParallelBlocks: () => { - return generateParallelBlocks(get().blocks) }, setDragStartPosition: (position) => { diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index f348bf0f62..f7fafb8193 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -214,7 +214,6 @@ export interface WorkflowActions { clear: () => Partial updateLastSaved: () => void setBlockEnabled: (id: string, enabled: boolean) => void - duplicateBlock: (id: string) => void setBlockHandles: (id: string, horizontalHandles: boolean) => void updateBlockName: ( id: string, @@ -225,23 +224,18 @@ export interface WorkflowActions { } setBlockAdvancedMode: (id: string, advancedMode: boolean) => void setBlockCanonicalMode: (id: string, canonicalId: string, mode: 'basic' | 'advanced') => void - setBlockTriggerMode: (id: string, triggerMode: boolean) => void updateBlockLayoutMetrics: (id: string, dimensions: { width: number; height: number }) => void triggerUpdate: () => void updateLoopCount: (loopId: string, count: number) => void updateLoopType: (loopId: string, loopType: 'for' | 'forEach' | 'while' | 'doWhile') => void - updateLoopCollection: (loopId: string, collection: string) => void setLoopForEachItems: (loopId: string, items: any) => void setLoopWhileCondition: (loopId: string, condition: string) => void setLoopDoWhileCondition: (loopId: string, condition: string) => void updateParallelCount: (parallelId: string, count: number) => void updateParallelCollection: (parallelId: string, collection: string) => void updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => void - generateLoopBlocks: () => Record - generateParallelBlocks: () => Record setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void revertToDeployedState: (deployedState: WorkflowState) => void - toggleBlockAdvancedMode: (id: string) => void setDragStartPosition: (position: DragStartPosition | null) => void getDragStartPosition: () => DragStartPosition | null getWorkflowState: () => WorkflowState