Skip to content

Commit abd5b70

Browse files
waleedlatif1claude
andcommitted
fix(deploy): use client-side comparison for editor header, remove server polling
The lastSaved-based server polling was triggering API calls on every local store mutation (before socket persistence), wasting requests and checking stale DB state. Revert the editor header to pure client-side hasWorkflowChanged comparison — zero network during editing, instant badge updates. Child workflow badges still use server-side useDeploymentInfo (they don't have Zustand state). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 250668f commit abd5b70

File tree

2 files changed

+7
-83
lines changed

2 files changed

+7
-83
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
useDeployment,
1010
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks'
1111
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow'
12-
import { useDeployedWorkflowState, useDeploymentInfo } from '@/hooks/queries/deployments'
12+
import { useDeployedWorkflowState } from '@/hooks/queries/deployments'
1313
import type { WorkspaceUserPermissions } from '@/hooks/use-user-permissions'
1414
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1515

@@ -33,12 +33,6 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP
3333
)
3434
const isDeployed = deploymentStatus?.isDeployed || false
3535

36-
// Server-side deployment info (authoritative source for needsRedeployment)
37-
const { data: deploymentInfoData, isLoading: isLoadingDeploymentInfo } = useDeploymentInfo(
38-
activeWorkflowId,
39-
{ enabled: isDeployed && !isRegistryLoading }
40-
)
41-
4236
const isDeployedStateEnabled = Boolean(activeWorkflowId) && isDeployed && !isRegistryLoading
4337
const { data: deployedStateData, isLoading: isLoadingDeployedState } = useDeployedWorkflowState(
4438
activeWorkflowId,
@@ -50,8 +44,6 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP
5044
workflowId: activeWorkflowId,
5145
deployedState,
5246
isLoadingDeployedState,
53-
serverNeedsRedeployment: deploymentInfoData?.needsRedeployment,
54-
isServerLoading: isLoadingDeploymentInfo,
5547
})
5648

5749
const { isDeploying, handleDeployClick } = useDeployment({
Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import { useEffect, useMemo, useRef } from 'react'
2-
import { useQueryClient } from '@tanstack/react-query'
1+
import { useMemo } from 'react'
32
import { hasWorkflowChanged } from '@/lib/workflows/comparison'
43
import { mergeSubblockStateWithValues } from '@/lib/workflows/subblocks'
5-
import { deploymentKeys } from '@/hooks/queries/deployments'
64
import { useVariablesStore } from '@/stores/panel/variables/store'
75
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
86
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -12,34 +10,23 @@ interface UseChangeDetectionProps {
1210
workflowId: string | null
1311
deployedState: WorkflowState | null
1412
isLoadingDeployedState: boolean
15-
serverNeedsRedeployment: boolean | undefined
16-
isServerLoading: boolean
1713
}
1814

1915
/**
2016
* Detects meaningful changes between current workflow state and deployed state.
21-
*
22-
* Uses the server-side needsRedeployment (from useDeploymentInfo) as the
23-
* authoritative signal. The server compares the persisted DB state to the
24-
* deployed version state, which avoids false positives from client-side
25-
* representation differences.
26-
*
27-
* When the workflow store is updated (e.g. after auto-save), the deployment
28-
* info query is invalidated so the server can recheck for changes.
17+
* Performs comparison entirely on the client using hasWorkflowChanged — no API
18+
* calls needed. The deployed state snapshot is fetched once via React Query and
19+
* refreshed after deploy/undeploy/version-activate mutations.
2920
*/
3021
export function useChangeDetection({
3122
workflowId,
3223
deployedState,
3324
isLoadingDeployedState,
34-
serverNeedsRedeployment,
35-
isServerLoading,
3625
}: UseChangeDetectionProps) {
37-
const queryClient = useQueryClient()
3826
const blocks = useWorkflowStore((state) => state.blocks)
3927
const edges = useWorkflowStore((state) => state.edges)
4028
const loops = useWorkflowStore((state) => state.loops)
4129
const parallels = useWorkflowStore((state) => state.parallels)
42-
const lastSaved = useWorkflowStore((state) => state.lastSaved)
4330
const subBlockValues = useSubBlockStore((state) =>
4431
workflowId ? state.workflowValues[workflowId] : null
4532
)
@@ -55,50 +42,8 @@ export function useChangeDetection({
5542
return vars
5643
}, [workflowId, allVariables])
5744

58-
// Tracks the lastSaved timestamp at mount to distinguish real saves from initial hydration.
59-
const initialLastSavedRef = useRef<number | undefined>(undefined)
60-
const workflowIdRef = useRef(workflowId)
61-
62-
// Must run before the lastSaved effect to prevent stale-ref invalidation on workflow switch.
63-
useEffect(() => {
64-
workflowIdRef.current = workflowId
65-
initialLastSavedRef.current = undefined
66-
}, [workflowId])
67-
68-
useEffect(() => {
69-
if (lastSaved !== undefined && initialLastSavedRef.current === undefined) {
70-
initialLastSavedRef.current = lastSaved
71-
return
72-
}
73-
74-
if (
75-
lastSaved === undefined ||
76-
initialLastSavedRef.current === undefined ||
77-
lastSaved === initialLastSavedRef.current ||
78-
!workflowId
79-
) {
80-
return
81-
}
82-
83-
initialLastSavedRef.current = lastSaved
84-
85-
const capturedWorkflowId = workflowId
86-
const timer = setTimeout(() => {
87-
if (workflowIdRef.current !== capturedWorkflowId) return
88-
queryClient.invalidateQueries({
89-
queryKey: deploymentKeys.info(capturedWorkflowId),
90-
})
91-
}, 500)
92-
93-
return () => clearTimeout(timer)
94-
}, [lastSaved, workflowId, queryClient])
95-
96-
// Skip expensive state merge when server result is available (the common path).
97-
// Only build currentState for the client-side fallback comparison.
98-
const needsClientFallback = serverNeedsRedeployment === undefined && !isServerLoading
99-
10045
const currentState = useMemo((): WorkflowState | null => {
101-
if (!needsClientFallback || !workflowId || !deployedState) return null
46+
if (!workflowId || !deployedState) return null
10247

10348
const mergedBlocks = mergeSubblockStateWithValues(blocks, subBlockValues ?? {})
10449

@@ -110,7 +55,6 @@ export function useChangeDetection({
11055
variables: workflowVariables,
11156
} as WorkflowState & { variables: Record<string, any> }
11257
}, [
113-
needsClientFallback,
11458
workflowId,
11559
deployedState,
11660
blocks,
@@ -122,21 +66,9 @@ export function useChangeDetection({
12266
])
12367

12468
const changeDetected = useMemo(() => {
125-
if (isServerLoading) return false
126-
127-
if (serverNeedsRedeployment !== undefined) {
128-
return serverNeedsRedeployment
129-
}
130-
13169
if (!currentState || !deployedState || isLoadingDeployedState) return false
13270
return hasWorkflowChanged(currentState, deployedState)
133-
}, [
134-
currentState,
135-
deployedState,
136-
isLoadingDeployedState,
137-
serverNeedsRedeployment,
138-
isServerLoading,
139-
])
71+
}, [currentState, deployedState, isLoadingDeployedState])
14072

14173
return { changeDetected }
14274
}

0 commit comments

Comments
 (0)