fix(hydration): duplicate overlay after idle + subblocks race condition (#1246)

* fix(hydration): duplicate overlay after idle + subblocks race condition

* remove random timeout

* re-use correct helper

* remove redundant check

* add check

* remove third init func
This commit is contained in:
Vikhyath Mondreti
2025-09-04 16:18:35 -07:00
committed by GitHub
parent afc1632830
commit 0f7dfe084a
4 changed files with 29 additions and 95 deletions

View File

@@ -887,14 +887,10 @@ const WorkflowContent = React.memo(() => {
// 2. The workflow exists in the registry
// 3. Workflows are not currently loading
if (hasActiveWorkflow && hasWorkflowInRegistry && isNotLoading) {
// Add a small delay to ensure blocks state has settled
const timeoutId = setTimeout(() => {
setIsWorkflowReady(true)
}, 100)
return () => clearTimeout(timeoutId)
setIsWorkflowReady(true)
} else {
setIsWorkflowReady(false)
}
setIsWorkflowReady(false)
}, [activeWorkflowId, params.workflowId, workflows, isLoading])
// Init workflow

View File

@@ -376,38 +376,24 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
)
})
// Merge workflow store with server state (do not drop optimistic local state)
const existing = useWorkflowStore.getState()
const mergedBlocks = {
...(existing.blocks || {}),
...(workflowState.blocks || {}),
}
const edgeById = new Map<string, any>()
;(existing.edges || []).forEach((e: any) => edgeById.set(e.id, e))
;(workflowState.edges || []).forEach((e: any) => edgeById.set(e.id, e))
const mergedEdges = Array.from(edgeById.values())
// Replace local workflow store with authoritative server state
useWorkflowStore.setState({
blocks: mergedBlocks,
edges: mergedEdges,
loops: workflowState.loops || existing.loops || {},
parallels: workflowState.parallels || existing.parallels || {},
lastSaved: workflowState.lastSaved || existing.lastSaved || Date.now(),
isDeployed: workflowState.isDeployed ?? existing.isDeployed ?? false,
deployedAt: workflowState.deployedAt || existing.deployedAt,
deploymentStatuses:
workflowState.deploymentStatuses || existing.deploymentStatuses || {},
hasActiveWebhook:
workflowState.hasActiveWebhook ?? existing.hasActiveWebhook ?? false,
blocks: workflowState.blocks || {},
edges: workflowState.edges || [],
loops: workflowState.loops || {},
parallels: workflowState.parallels || {},
lastSaved: workflowState.lastSaved || Date.now(),
isDeployed: workflowState.isDeployed ?? false,
deployedAt: workflowState.deployedAt,
deploymentStatuses: workflowState.deploymentStatuses || {},
hasActiveWebhook: workflowState.hasActiveWebhook ?? false,
})
// Merge subblock store values per workflow
// Replace subblock store values for this workflow
useSubBlockStore.setState((state: any) => ({
workflowValues: {
...state.workflowValues,
[data.workflowId]: {
...(state.workflowValues?.[data.workflowId] || {}),
...subblockValues,
},
[data.workflowId]: subblockValues,
},
}))
@@ -518,36 +504,24 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
})
})
const existing = useWorkflowStore.getState()
const mergedBlocks = {
...(existing.blocks || {}),
...(workflowState.blocks || {}),
}
const edgeById = new Map<string, any>()
;(existing.edges || []).forEach((e: any) => edgeById.set(e.id, e))
;(workflowState.edges || []).forEach((e: any) => edgeById.set(e.id, e))
const mergedEdges = Array.from(edgeById.values())
// Replace local workflow store with authoritative server state
useWorkflowStore.setState({
blocks: mergedBlocks,
edges: mergedEdges,
loops: workflowState.loops || existing.loops || {},
parallels: workflowState.parallels || existing.parallels || {},
lastSaved: workflowState.lastSaved || existing.lastSaved || Date.now(),
isDeployed: workflowState.isDeployed ?? existing.isDeployed ?? false,
deployedAt: workflowState.deployedAt || existing.deployedAt,
deploymentStatuses:
workflowState.deploymentStatuses || existing.deploymentStatuses || {},
hasActiveWebhook:
workflowState.hasActiveWebhook ?? existing.hasActiveWebhook ?? false,
blocks: workflowState.blocks || {},
edges: workflowState.edges || [],
loops: workflowState.loops || {},
parallels: workflowState.parallels || {},
lastSaved: workflowState.lastSaved || Date.now(),
isDeployed: workflowState.isDeployed ?? false,
deployedAt: workflowState.deployedAt,
deploymentStatuses: workflowState.deploymentStatuses || {},
hasActiveWebhook: workflowState.hasActiveWebhook ?? false,
})
// Replace subblock store values for this workflow
useSubBlockStore.setState((state: any) => ({
workflowValues: {
...state.workflowValues,
[workflowData.id]: {
...(state.workflowValues?.[workflowData.id] || {}),
...subblockValues,
},
[workflowData.id]: subblockValues,
},
}))

View File

@@ -124,27 +124,6 @@ async function fetchWorkflowsFromDB(workspaceId?: string): Promise<void> {
}
}
// Initialize subblock values
const subblockValues: Record<string, Record<string, any>> = {}
if (state?.blocks) {
Object.entries(state.blocks).forEach(([blockId, block]) => {
const blockState = block as BlockState
subblockValues[blockId] = {}
Object.entries(blockState.subBlocks || {}).forEach(([subblockId, subblock]) => {
subblockValues[blockId][subblockId] = subblock.value
})
})
}
// Update subblock store
useSubBlockStore.setState((state) => ({
workflowValues: {
...state.workflowValues,
[id]: subblockValues,
},
}))
if (variables && typeof variables === 'object') {
useVariablesStore.setState((state) => {
const withoutWorkflow = Object.fromEntries(
@@ -506,22 +485,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
},
}
// Extract and update subblock values
const subblockValues: Record<string, Record<string, any>> = {}
Object.entries(workflowState.blocks).forEach(([blockId, block]) => {
const blockState = block as any
subblockValues[blockId] = {}
Object.entries(blockState.subBlocks || {}).forEach(([subblockId, subblock]) => {
subblockValues[blockId][subblockId] = (subblock as any).value
})
})
useSubBlockStore.setState((state) => ({
workflowValues: {
...state.workflowValues,
[id]: subblockValues,
},
}))
// Subblock values will be initialized by initializeFromWorkflow below
} else {
// If no state in DB, use empty state - server should have created start block
workflowState = {

View File

@@ -103,7 +103,7 @@ export const useSubBlockStore = create<SubBlockStore>()(
const values: Record<string, Record<string, any>> = {}
Object.entries(blocks).forEach(([blockId, block]) => {
values[blockId] = {}
Object.entries(block.subBlocks).forEach(([subBlockId, subBlock]) => {
Object.entries(block.subBlocks || {}).forEach(([subBlockId, subBlock]) => {
values[blockId][subBlockId] = (subBlock as SubBlockConfig).value
})
})