Skip to content

Commit 8429040

Browse files
fix(notes): ghost edges (#2970)
* fix(notes): ghost edges * fix deployed state fallback * fallback * remove UI level checks * annotation missing from autoconnect source check
1 parent 8574e6c commit 8429040

File tree

3 files changed

+45
-54
lines changed

3 files changed

+45
-54
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
8181
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
8282
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
8383
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
84+
import type { BlockState } from '@/stores/workflows/workflow/types'
8485

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

@@ -1097,6 +1097,13 @@ const WorkflowContent = React.memo(() => {
10971097
[collaborativeBatchRemoveEdges]
10981098
)
10991099

1100+
const isAutoConnectSourceCandidate = useCallback((block: BlockState): boolean => {
1101+
if (!block.enabled) return false
1102+
if (block.type === 'response') return false
1103+
if (isAnnotationOnlyBlock(block.type)) return false
1104+
return true
1105+
}, [])
1106+
11001107
/** Finds the closest block to a position for auto-connect. */
11011108
const findClosestOutput = useCallback(
11021109
(newNodePosition: { x: number; y: number }): BlockData | null => {
@@ -1109,8 +1116,7 @@ const WorkflowContent = React.memo(() => {
11091116
position: { x: number; y: number }
11101117
distanceSquared: number
11111118
} | null>((acc, [id, block]) => {
1112-
if (!block.enabled) return acc
1113-
if (block.type === 'response') return acc
1119+
if (!isAutoConnectSourceCandidate(block)) return acc
11141120
const node = nodeIndex.get(id)
11151121
if (!node) return acc
11161122

@@ -1140,7 +1146,7 @@ const WorkflowContent = React.memo(() => {
11401146
position: closest.position,
11411147
}
11421148
},
1143-
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode]
1149+
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode, isAutoConnectSourceCandidate]
11441150
)
11451151

11461152
/** Determines the appropriate source handle based on block type. */
@@ -1208,7 +1214,8 @@ const WorkflowContent = React.memo(() => {
12081214
position: { x: number; y: number }
12091215
distanceSquared: number
12101216
} | null>((acc, block) => {
1211-
if (block.type === 'response') return acc
1217+
const blockState = blocks[block.id]
1218+
if (!blockState || !isAutoConnectSourceCandidate(blockState)) return acc
12121219
const distanceSquared =
12131220
(block.position.x - targetPosition.x) ** 2 + (block.position.y - targetPosition.y) ** 2
12141221
if (!acc || distanceSquared < acc.distanceSquared) {
@@ -1225,7 +1232,7 @@ const WorkflowContent = React.memo(() => {
12251232
}
12261233
: undefined
12271234
},
1228-
[]
1235+
[blocks, isAutoConnectSourceCandidate]
12291236
)
12301237

12311238
/**
@@ -1241,26 +1248,13 @@ const WorkflowContent = React.memo(() => {
12411248
position: { x: number; y: number },
12421249
targetBlockId: string,
12431250
options: {
1244-
blockType: string
1245-
enableTriggerMode?: boolean
12461251
targetParentId?: string | null
12471252
existingChildBlocks?: { id: string; type: string; position: { x: number; y: number } }[]
12481253
containerId?: string
12491254
}
12501255
): Edge | undefined => {
12511256
if (!autoConnectRef.current) return undefined
12521257

1253-
// Don't auto-connect starter or annotation-only blocks
1254-
if (options.blockType === 'starter' || isAnnotationOnlyBlock(options.blockType)) {
1255-
return undefined
1256-
}
1257-
1258-
// Check if target is a trigger block
1259-
const targetBlockConfig = getBlock(options.blockType)
1260-
const isTargetTrigger =
1261-
options.enableTriggerMode || targetBlockConfig?.category === 'triggers'
1262-
if (isTargetTrigger) return undefined
1263-
12641258
// Case 1: Adding block inside a container with existing children
12651259
if (options.existingChildBlocks && options.existingChildBlocks.length > 0) {
12661260
const closestBlock = findClosestBlockInSet(options.existingChildBlocks, position)
@@ -1368,7 +1362,6 @@ const WorkflowContent = React.memo(() => {
13681362
const name = getUniqueBlockName(baseName, blocks)
13691363

13701364
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
1371-
blockType: data.type,
13721365
targetParentId: null,
13731366
})
13741367

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

14411434
const autoConnectEdge = tryCreateAutoConnectEdge(relativePosition, id, {
1442-
blockType: data.type,
1443-
enableTriggerMode: data.enableTriggerMode,
14441435
targetParentId: containerInfo.loopId,
14451436
existingChildBlocks,
14461437
containerId: containerInfo.loopId,
@@ -1469,8 +1460,6 @@ const WorkflowContent = React.memo(() => {
14691460
if (checkTriggerConstraints(data.type)) return
14701461

14711462
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
1472-
blockType: data.type,
1473-
enableTriggerMode: data.enableTriggerMode,
14741463
targetParentId: null,
14751464
})
14761465

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

15281517
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
1529-
blockType: type,
15301518
targetParentId: null,
15311519
})
15321520

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

15641552
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
1565-
blockType: type,
1566-
enableTriggerMode,
15671553
targetParentId: null,
15681554
})
15691555

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

23652351
if (!sourceNode || !targetNode) return
23662352

2367-
// Prevent connections to/from annotation-only blocks (non-executable)
2368-
if (
2369-
isAnnotationOnlyBlock(sourceNode.data?.type) ||
2370-
isAnnotationOnlyBlock(targetNode.data?.type)
2371-
) {
2372-
return
2373-
}
2374-
2375-
// Prevent incoming connections to trigger blocks (webhook, schedule, etc.)
2376-
if (targetNode.data?.config?.category === 'triggers') {
2377-
return
2378-
}
2379-
2380-
// Prevent incoming connections to starter blocks (still keep separate for backward compatibility)
2381-
if (targetNode.data?.type === 'starter') {
2382-
return
2383-
}
2384-
23852353
// Get parent information (handle container start node case)
23862354
const sourceParentId =
23872355
blocks[sourceNode.id]?.data?.parentId ||
@@ -2787,7 +2755,6 @@ const WorkflowContent = React.memo(() => {
27872755
.map((b) => ({ id: b.id, type: b.type, position: b.position }))
27882756

27892757
const autoConnectEdge = tryCreateAutoConnectEdge(relativePositionBefore, node.id, {
2790-
blockType: node.data?.type || '',
27912758
targetParentId: potentialParentId,
27922759
existingChildBlocks,
27932760
containerId: potentialParentId,

apps/sim/app/workspace/providers/socket-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
352352
})
353353
})
354354

355-
useWorkflowStore.setState({
355+
useWorkflowStore.getState().replaceWorkflowState({
356356
blocks: workflowState.blocks || {},
357357
edges: workflowState.edges || [],
358358
loops: workflowState.loops || {},

apps/sim/stores/workflows/workflow/store.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { create } from 'zustand'
44
import { devtools } from 'zustand/middleware'
55
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
66
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
7+
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
78
import { getBlock } from '@/blocks'
89
import type { SubBlockConfig } from '@/blocks/types'
9-
import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
10+
import { isAnnotationOnlyBlock, normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
1011
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1112
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1213
import { filterNewEdges, getUniqueBlockName, mergeSubblockState } from '@/stores/workflows/utils'
@@ -90,6 +91,26 @@ function resolveInitialSubblockValue(config: SubBlockConfig): unknown {
9091
return null
9192
}
9293

94+
function isValidEdge(
95+
edge: Edge,
96+
blocks: Record<string, { type: string; triggerMode?: boolean }>
97+
): boolean {
98+
const sourceBlock = blocks[edge.source]
99+
const targetBlock = blocks[edge.target]
100+
if (!sourceBlock || !targetBlock) return false
101+
if (isAnnotationOnlyBlock(sourceBlock.type)) return false
102+
if (isAnnotationOnlyBlock(targetBlock.type)) return false
103+
if (TriggerUtils.isTriggerBlock(targetBlock)) return false
104+
return true
105+
}
106+
107+
function filterValidEdges(
108+
edges: Edge[],
109+
blocks: Record<string, { type: string; triggerMode?: boolean }>
110+
): Edge[] {
111+
return edges.filter((edge) => isValidEdge(edge, blocks))
112+
}
113+
93114
const initialState = {
94115
blocks: {},
95116
edges: [],
@@ -360,8 +381,9 @@ export const useWorkflowStore = create<WorkflowStore>()(
360381
}
361382

362383
if (edges && edges.length > 0) {
384+
const validEdges = filterValidEdges(edges, newBlocks)
363385
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
364-
for (const edge of edges) {
386+
for (const edge of validEdges) {
365387
if (!existingEdgeIds.has(edge.id)) {
366388
newEdges.push({
367389
id: edge.id || crypto.randomUUID(),
@@ -495,8 +517,11 @@ export const useWorkflowStore = create<WorkflowStore>()(
495517
},
496518

497519
batchAddEdges: (edges: Edge[]) => {
520+
const blocks = get().blocks
498521
const currentEdges = get().edges
499-
const filtered = filterNewEdges(edges, currentEdges)
522+
523+
const validEdges = filterValidEdges(edges, blocks)
524+
const filtered = filterNewEdges(validEdges, currentEdges)
500525
const newEdges = [...currentEdges]
501526

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

515-
const blocks = get().blocks
516540
set({
517541
blocks: { ...blocks },
518542
edges: newEdges,
@@ -572,7 +596,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
572596
) => {
573597
set((state) => {
574598
const nextBlocks = workflowState.blocks || {}
575-
const nextEdges = workflowState.edges || []
599+
const nextEdges = filterValidEdges(workflowState.edges || [], nextBlocks)
576600
const nextLoops =
577601
Object.keys(workflowState.loops || {}).length > 0
578602
? workflowState.loops
@@ -1083,7 +1107,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
10831107

10841108
const newState = {
10851109
blocks: deployedState.blocks,
1086-
edges: deployedState.edges,
1110+
edges: filterValidEdges(deployedState.edges ?? [], deployedState.blocks),
10871111
loops: deployedState.loops || {},
10881112
parallels: deployedState.parallels || {},
10891113
needsRedeployment: false,

0 commit comments

Comments
 (0)