Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 14 additions & 47 deletions apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { BlockState } from '@/stores/workflows/workflow/types'

/** Lazy-loaded components for non-critical UI that can load after initial render */
const LazyChat = lazy(() =>
Expand Down Expand Up @@ -535,8 +536,7 @@ const WorkflowContent = React.memo(() => {
return edgesToFilter.filter((edge) => {
const sourceBlock = blocks[edge.source]
const targetBlock = blocks[edge.target]
if (!sourceBlock || !targetBlock) return false
return !isAnnotationOnlyBlock(sourceBlock.type) && !isAnnotationOnlyBlock(targetBlock.type)
return Boolean(sourceBlock && targetBlock)
})
}, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks])

Expand Down Expand Up @@ -1097,6 +1097,13 @@ const WorkflowContent = React.memo(() => {
[collaborativeBatchRemoveEdges]
)

const isAutoConnectSourceCandidate = useCallback((block: BlockState): boolean => {
if (!block.enabled) return false
if (block.type === 'response') return false
if (isAnnotationOnlyBlock(block.type)) return false
return true
}, [])

/** Finds the closest block to a position for auto-connect. */
const findClosestOutput = useCallback(
(newNodePosition: { x: number; y: number }): BlockData | null => {
Expand All @@ -1109,8 +1116,7 @@ const WorkflowContent = React.memo(() => {
position: { x: number; y: number }
distanceSquared: number
} | null>((acc, [id, block]) => {
if (!block.enabled) return acc
if (block.type === 'response') return acc
if (!isAutoConnectSourceCandidate(block)) return acc
const node = nodeIndex.get(id)
if (!node) return acc

Expand Down Expand Up @@ -1140,7 +1146,7 @@ const WorkflowContent = React.memo(() => {
position: closest.position,
}
},
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode]
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode, isAutoConnectSourceCandidate]
)

/** Determines the appropriate source handle based on block type. */
Expand Down Expand Up @@ -1208,7 +1214,8 @@ const WorkflowContent = React.memo(() => {
position: { x: number; y: number }
distanceSquared: number
} | null>((acc, block) => {
if (block.type === 'response') return acc
const blockState = blocks[block.id]
if (!blockState || !isAutoConnectSourceCandidate(blockState)) return acc
const distanceSquared =
(block.position.x - targetPosition.x) ** 2 + (block.position.y - targetPosition.y) ** 2
if (!acc || distanceSquared < acc.distanceSquared) {
Expand All @@ -1225,7 +1232,7 @@ const WorkflowContent = React.memo(() => {
}
: undefined
},
[]
[blocks, isAutoConnectSourceCandidate]
)

/**
Expand All @@ -1241,26 +1248,13 @@ const WorkflowContent = React.memo(() => {
position: { x: number; y: number },
targetBlockId: string,
options: {
blockType: string
enableTriggerMode?: boolean
targetParentId?: string | null
existingChildBlocks?: { id: string; type: string; position: { x: number; y: number } }[]
containerId?: string
}
): Edge | undefined => {
if (!autoConnectRef.current) return undefined

// Don't auto-connect starter or annotation-only blocks
if (options.blockType === 'starter' || isAnnotationOnlyBlock(options.blockType)) {
return undefined
}

// Check if target is a trigger block
const targetBlockConfig = getBlock(options.blockType)
const isTargetTrigger =
options.enableTriggerMode || targetBlockConfig?.category === 'triggers'
if (isTargetTrigger) return undefined

// Case 1: Adding block inside a container with existing children
if (options.existingChildBlocks && options.existingChildBlocks.length > 0) {
const closestBlock = findClosestBlockInSet(options.existingChildBlocks, position)
Expand Down Expand Up @@ -1368,7 +1362,6 @@ const WorkflowContent = React.memo(() => {
const name = getUniqueBlockName(baseName, blocks)

const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
blockType: data.type,
targetParentId: null,
})

Expand Down Expand Up @@ -1439,8 +1432,6 @@ const WorkflowContent = React.memo(() => {
.map((b) => ({ id: b.id, type: b.type, position: b.position }))

const autoConnectEdge = tryCreateAutoConnectEdge(relativePosition, id, {
blockType: data.type,
enableTriggerMode: data.enableTriggerMode,
targetParentId: containerInfo.loopId,
existingChildBlocks,
containerId: containerInfo.loopId,
Expand Down Expand Up @@ -1469,8 +1460,6 @@ const WorkflowContent = React.memo(() => {
if (checkTriggerConstraints(data.type)) return

const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
blockType: data.type,
enableTriggerMode: data.enableTriggerMode,
targetParentId: null,
})

Expand Down Expand Up @@ -1526,7 +1515,6 @@ const WorkflowContent = React.memo(() => {
const name = getUniqueBlockName(baseName, blocks)

const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
blockType: type,
targetParentId: null,
})

Expand Down Expand Up @@ -1562,8 +1550,6 @@ const WorkflowContent = React.memo(() => {
const name = getUniqueBlockName(baseName, blocks)

const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
blockType: type,
enableTriggerMode,
targetParentId: null,
})

Expand Down Expand Up @@ -2364,24 +2350,6 @@ const WorkflowContent = React.memo(() => {

if (!sourceNode || !targetNode) return

// Prevent connections to/from annotation-only blocks (non-executable)
if (
isAnnotationOnlyBlock(sourceNode.data?.type) ||
isAnnotationOnlyBlock(targetNode.data?.type)
) {
return
}

// Prevent incoming connections to trigger blocks (webhook, schedule, etc.)
if (targetNode.data?.config?.category === 'triggers') {
return
}

// Prevent incoming connections to starter blocks (still keep separate for backward compatibility)
if (targetNode.data?.type === 'starter') {
return
}

// Get parent information (handle container start node case)
const sourceParentId =
blocks[sourceNode.id]?.data?.parentId ||
Expand Down Expand Up @@ -2787,7 +2755,6 @@ const WorkflowContent = React.memo(() => {
.map((b) => ({ id: b.id, type: b.type, position: b.position }))

const autoConnectEdge = tryCreateAutoConnectEdge(relativePositionBefore, node.id, {
blockType: node.data?.type || '',
targetParentId: potentialParentId,
existingChildBlocks,
containerId: potentialParentId,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/workspace/providers/socket-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
})
})

useWorkflowStore.setState({
useWorkflowStore.getState().replaceWorkflowState({
blocks: workflowState.blocks || {},
edges: workflowState.edges || [],
loops: workflowState.loops || {},
Expand Down
36 changes: 30 additions & 6 deletions apps/sim/stores/workflows/workflow/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
import { getBlock } from '@/blocks'
import type { SubBlockConfig } from '@/blocks/types'
import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
import { isAnnotationOnlyBlock, normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { filterNewEdges, getUniqueBlockName, mergeSubblockState } from '@/stores/workflows/utils'
Expand Down Expand Up @@ -90,6 +91,26 @@ function resolveInitialSubblockValue(config: SubBlockConfig): unknown {
return null
}

function isValidEdge(
edge: Edge,
blocks: Record<string, { type: string; triggerMode?: boolean }>
): boolean {
const sourceBlock = blocks[edge.source]
const targetBlock = blocks[edge.target]
if (!sourceBlock || !targetBlock) return false
if (isAnnotationOnlyBlock(sourceBlock.type)) return false
if (isAnnotationOnlyBlock(targetBlock.type)) return false
if (TriggerUtils.isTriggerBlock(targetBlock)) return false
return true
}

function filterValidEdges(
edges: Edge[],
blocks: Record<string, { type: string; triggerMode?: boolean }>
): Edge[] {
return edges.filter((edge) => isValidEdge(edge, blocks))
}

const initialState = {
blocks: {},
edges: [],
Expand Down Expand Up @@ -360,8 +381,9 @@ export const useWorkflowStore = create<WorkflowStore>()(
}

if (edges && edges.length > 0) {
const validEdges = filterValidEdges(edges, newBlocks)
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
for (const edge of edges) {
for (const edge of validEdges) {
if (!existingEdgeIds.has(edge.id)) {
newEdges.push({
id: edge.id || crypto.randomUUID(),
Expand Down Expand Up @@ -495,8 +517,11 @@ export const useWorkflowStore = create<WorkflowStore>()(
},

batchAddEdges: (edges: Edge[]) => {
const blocks = get().blocks
const currentEdges = get().edges
const filtered = filterNewEdges(edges, currentEdges)

const validEdges = filterValidEdges(edges, blocks)
const filtered = filterNewEdges(validEdges, currentEdges)
const newEdges = [...currentEdges]

for (const edge of filtered) {
Expand All @@ -512,7 +537,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
})
}

const blocks = get().blocks
set({
blocks: { ...blocks },
edges: newEdges,
Expand Down Expand Up @@ -572,7 +596,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
) => {
set((state) => {
const nextBlocks = workflowState.blocks || {}
const nextEdges = workflowState.edges || []
const nextEdges = filterValidEdges(workflowState.edges || [], nextBlocks)
const nextLoops =
Object.keys(workflowState.loops || {}).length > 0
? workflowState.loops
Expand Down Expand Up @@ -1083,7 +1107,7 @@ export const useWorkflowStore = create<WorkflowStore>()(

const newState = {
blocks: deployedState.blocks,
edges: deployedState.edges,
edges: filterValidEdges(deployedState.edges ?? [], deployedState.blocks),
loops: deployedState.loops || {},
parallels: deployedState.parallels || {},
needsRedeployment: false,
Expand Down