Skip to content

Commit 94dd2e1

Browse files
authored
improvement(workflow): narrow zustand selectors and optimize log tree builds (#4378)
* improvement(workflow): narrow zustand selectors and optimize log tree builds - Add useIsCurrentWorkflowExecuting selector; swap broad useCurrentWorkflowExecution in action-bar/chat - Use useShallow for execution-state subset in useWorkflowExecution - Drop useCallback-wrapped object selectors in diff-controls and use-block-state (use primitive selectors) - Stabilize EMPTY_SUBBLOCK_VALUES constant for empty-workflow selector results in api-info-modal, mcp, tag-dropdown - O(n) Map lookups in buildEntryTree (was O(n^2) filter scans) - Skip expanded-paths reset when structured-output data is content-stable - Extract ReactFlow constants out of workflow.tsx into workflow-constants.ts * fix(workflow): seed structured-output JSON ref and centralize EMPTY_SUBBLOCK_VALUES - Use null sentinel for prevDataJsonRef and lazily stringify prevDataRef on first compare so the optimization holds on the first stream refresh after mount - Export EMPTY_SUBBLOCK_VALUES from the subblock store; remove three duplicated module constants * chore(ci): bump api-validation route baseline to 717 PR #4373 added apps/sim/app/api/table/[tableId]/export/route.ts but didn't update the audit baseline, so unrelated PRs fail check:api-validation:strict. Bump baseline to match current route count.
1 parent a7a941f commit 94dd2e1

16 files changed

Lines changed: 144 additions & 85 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/provide
77
import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
88
import { validateTriggerPaste } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
99
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
10-
import { useCurrentWorkflowExecution, useExecutionStore } from '@/stores/execution'
10+
import { useExecutionStore, useIsCurrentWorkflowExecuting } from '@/stores/execution'
1111
import { useNotificationStore } from '@/stores/notifications'
1212
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
1313
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -114,7 +114,7 @@ export const ActionBar = memo(
114114
)
115115

116116
const { activeWorkflowId } = useWorkflowRegistry()
117-
const { isExecuting } = useCurrentWorkflowExecution()
117+
const isExecuting = useIsCurrentWorkflowExecuting()
118118
const getLastExecutionSnapshot = useExecutionStore((s) => s.getLastExecutionSnapshot)
119119
const userPermissions = useUserPermissionsContext()
120120
const edges = useWorkflowStore((state) => state.edges)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowI
5555
import type { BlockLog, ExecutionResult } from '@/executor/types'
5656
import { useChatStore } from '@/stores/chat/store'
5757
import { getChatPosition } from '@/stores/chat/utils'
58-
import { useCurrentWorkflowExecution } from '@/stores/execution'
58+
import { useIsCurrentWorkflowExecuting } from '@/stores/execution'
5959
import { useOperationQueue } from '@/stores/operation-queue/store'
6060
import { useTerminalConsoleStore, useWorkflowConsoleEntries } from '@/stores/terminal'
6161
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -269,7 +269,7 @@ export function Chat() {
269269
const entries = useWorkflowConsoleEntries(
270270
hasConsoleHydrated && typeof activeWorkflowId === 'string' ? activeWorkflowId : undefined
271271
)
272-
const { isExecuting } = useCurrentWorkflowExecution()
272+
const isExecuting = useIsCurrentWorkflowExecuting()
273273
const { handleRunWorkflow, handleCancelExecution } = useWorkflowExecution()
274274
const { data: session } = useSession()
275275
const { addToQueue } = useOperationQueue()

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls/diff-controls.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,12 @@ const NOTIFICATION_WIDTH = 240
1212
const NOTIFICATION_GAP = 16
1313

1414
export const DiffControls = memo(function DiffControls() {
15-
const { isDiffReady, hasActiveDiff, acceptChanges, rejectChanges } = useWorkflowDiffStore(
16-
useCallback(
17-
(state) => ({
18-
isDiffReady: state.isDiffReady,
19-
hasActiveDiff: state.hasActiveDiff,
20-
acceptChanges: state.acceptChanges,
21-
rejectChanges: state.rejectChanges,
22-
}),
23-
[]
24-
)
25-
)
15+
const isDiffReady = useWorkflowDiffStore((state) => state.isDiffReady)
16+
const hasActiveDiff = useWorkflowDiffStore((state) => state.hasActiveDiff)
17+
const acceptChanges = useWorkflowDiffStore((state) => state.acceptChanges)
18+
const rejectChanges = useWorkflowDiffStore((state) => state.rejectChanges)
2619

27-
const { activeWorkflowId } = useWorkflowRegistry(
28-
useCallback((state) => ({ activeWorkflowId: state.activeWorkflowId }), [])
29-
)
20+
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
3021

3122
const allNotifications = useNotificationStore((state) => state.notifications)
3223
const hasVisibleNotifications = useMemo(() => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { useDeploymentInfo, useUpdatePublicApi } from '@/hooks/queries/deploymen
2323
import { useUpdateWorkflow, useWorkflowMap } from '@/hooks/queries/workflows'
2424
import { usePermissionConfig } from '@/hooks/use-permission-config'
2525
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
26-
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
26+
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
2727
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
2828

2929
type NormalizedField = InputFormatField & { name: string }
@@ -38,8 +38,8 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro
3838
const { workspaceId } = useParams<{ workspaceId: string }>()
3939
const blocks = useWorkflowStore((state) => state.blocks)
4040
const setValue = useSubBlockStore((state) => state.setValue)
41-
const subBlockValues = useSubBlockStore((state) =>
42-
workflowId ? (state.workflowValues[workflowId] ?? {}) : {}
41+
const subBlockValues = useSubBlockStore(
42+
(state) => (workflowId ? state.workflowValues[workflowId] : undefined) ?? EMPTY_SUBBLOCK_VALUES
4343
)
4444

4545
const { data: workflows = {} } = useWorkflowMap(workspaceId)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
type WorkflowMcpServer,
2828
type WorkflowMcpTool,
2929
} from '@/hooks/queries/workflow-mcp-servers'
30-
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
30+
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
3131
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3232

3333
const logger = createLogger('McpToolDeploy')
@@ -120,8 +120,8 @@ export function McpDeploy({
120120
return null
121121
}, [blocks])
122122

123-
const subBlockValues = useSubBlockStore((state) =>
124-
workflowId ? (state.workflowValues[workflowId] ?? {}) : {}
123+
const subBlockValues = useSubBlockStore(
124+
(state) => (workflowId ? state.workflowValues[workflowId] : undefined) ?? EMPTY_SUBBLOCK_VALUES
125125
)
126126

127127
const inputFormat = useMemo((): NormalizedField[] => {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { normalizeName } from '@/executor/constants'
3333
import { useVariablesStore } from '@/stores/variables/store'
3434
import type { Variable } from '@/stores/variables/types'
3535
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
36-
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
36+
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
3737
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3838
import type { BlockState } from '@/stores/workflows/workflow/types'
3939

@@ -980,8 +980,8 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
980980
return new Set<string>(rawAccessiblePrefixes)
981981
}, [rawAccessiblePrefixes])
982982

983-
const workflowSubBlockValues = useSubBlockStore((state) =>
984-
workflowId ? (state.workflowValues[workflowId] ?? {}) : {}
983+
const workflowSubBlockValues = useSubBlockStore(
984+
(state) => (workflowId ? state.workflowValues[workflowId] : undefined) ?? EMPTY_SUBBLOCK_VALUES
985985
)
986986

987987
const getMergedSubBlocks = useCallback(

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/components/output-panel/components/structured-output.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ export const StructuredOutput = memo(function StructuredOutput({
682682
computeInitialPaths(data, isError)
683683
)
684684
const prevDataRef = useRef(data)
685+
const prevDataJsonRef = useRef<string | null>(null)
685686
const prevIsErrorRef = useRef(isError)
686687
const internalRef = useRef<HTMLDivElement>(null)
687688
const listRef = useListRef(null)
@@ -712,10 +713,22 @@ export const StructuredOutput = memo(function StructuredOutput({
712713

713714
// Reset expanded paths when data changes
714715
useEffect(() => {
715-
if (prevDataRef.current !== data || prevIsErrorRef.current !== isError) {
716+
if (prevIsErrorRef.current !== isError) {
716717
prevDataRef.current = data
717718
prevIsErrorRef.current = isError
719+
prevDataJsonRef.current = JSON.stringify(data)
718720
setExpandedPaths(computeInitialPaths(data, isError))
721+
return
722+
}
723+
724+
if (prevDataRef.current !== data) {
725+
const newJson = JSON.stringify(data)
726+
const prevJson = prevDataJsonRef.current ?? JSON.stringify(prevDataRef.current)
727+
if (prevJson !== newJson) {
728+
setExpandedPaths(computeInitialPaths(data, isError))
729+
}
730+
prevDataJsonRef.current = newJson
731+
prevDataRef.current = data
719732
}
720733
}, [data, isError])
721734

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/terminal/utils.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -366,14 +366,23 @@ export function buildEntryTree(entries: ConsoleEntry[], idPrefix = ''): EntryNod
366366
}
367367
}
368368

369+
const nestedByContainerId = new Map<string, ConsoleEntry[]>()
370+
for (const e of nestedIterationEntries) {
371+
const parent = e.parentIterations?.[0]
372+
if (!parent) continue
373+
const list = nestedByContainerId.get(parent.iterationContainerId)
374+
if (list) {
375+
list.push(e)
376+
} else {
377+
nestedByContainerId.set(parent.iterationContainerId, [e])
378+
}
379+
}
380+
369381
const subflowNodes: EntryNode[] = []
370382
for (const subflowGroup of subflowGroups.values()) {
371383
const { iterationType, iterationContainerId, groups: iterationGroups } = subflowGroup
372384

373-
const nestedForThisSubflow = nestedIterationEntries.filter((e) => {
374-
const parent = e.parentIterations?.[0]
375-
return parent && parent.iterationContainerId === iterationContainerId
376-
})
385+
const nestedForThisSubflow = nestedByContainerId.get(iterationContainerId) ?? []
377386

378387
const allDirectBlocks = iterationGroups.flatMap((g) => g.blocks)
379388
const allRelevantBlocks = [...allDirectBlocks, ...nestedForThisSubflow]
@@ -406,12 +415,21 @@ export function buildEntryTree(entries: ConsoleEntry[], idPrefix = ''): EntryNod
406415
iterationContainerId,
407416
}
408417

418+
const nestedByIteration = new Map<number, ConsoleEntry[]>()
419+
for (const e of nestedForThisSubflow) {
420+
const iterNum = e.parentIterations?.[0]?.iterationCurrent
421+
if (iterNum === undefined) continue
422+
const list = nestedByIteration.get(iterNum)
423+
if (list) {
424+
list.push(e)
425+
} else {
426+
nestedByIteration.set(iterNum, [e])
427+
}
428+
}
429+
409430
const iterationNodes: EntryNode[] = iterationGroups
410431
.map((iterGroup): EntryNode | null => {
411-
const matchingNestedEntries = nestedForThisSubflow.filter((e) => {
412-
const parent = e.parentIterations?.[0]
413-
return parent?.iterationCurrent === iterGroup.iterationCurrent
414-
})
432+
const matchingNestedEntries = nestedByIteration.get(iterGroup.iterationCurrent) ?? []
415433

416434
const strippedNestedEntries: ConsoleEntry[] = matchingNestedEntries.map((e) => ({
417435
...e,

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/hooks/use-block-state.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useCallback } from 'react'
21
import type { DiffStatus } from '@/lib/workflows/diff/types'
32
import { hasDiffStatus } from '@/lib/workflows/diff/types'
43
import { useIsBlockActive } from '@/stores/execution'
@@ -54,15 +53,8 @@ export function useBlockState(
5453
: undefined
5554

5655
// Get diff-related data
57-
const { diffAnalysis, isShowingDiff } = useWorkflowDiffStore(
58-
useCallback(
59-
(state) => ({
60-
diffAnalysis: state.diffAnalysis,
61-
isShowingDiff: state.isShowingDiff,
62-
}),
63-
[]
64-
)
65-
)
56+
const diffAnalysis = useWorkflowDiffStore((state) => state.diffAnalysis)
57+
const isShowingDiff = useWorkflowDiffStore((state) => state.isShowingDiff)
6658

6759
const isDeletedBlock = !isShowingDiff && diffAnalysis?.deleted_blocks?.includes(blockId)
6860

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { subscriptionKeys } from '@/hooks/queries/subscription'
3838
import { getWorkflows } from '@/hooks/queries/utils/workflow-cache'
3939
import { isExecutionStreamHttpError, useExecutionStream } from '@/hooks/use-execution-stream'
4040
import { WorkflowValidationError } from '@/serializer'
41-
import { useCurrentWorkflowExecution, useExecutionStore } from '@/stores/execution'
41+
import { defaultWorkflowExecutionState, useExecutionStore } from '@/stores/execution'
4242
import { useNotificationStore } from '@/stores/notifications'
4343
import {
4444
clearExecutionPointer,
@@ -136,8 +136,20 @@ export function useWorkflowExecution() {
136136
variables: s.variables,
137137
}))
138138
)
139-
const { isExecuting, isDebugging, pendingBlocks, executor, debugContext } =
140-
useCurrentWorkflowExecution()
139+
const { isExecuting, isDebugging, pendingBlocks, executor, debugContext } = useExecutionStore(
140+
useShallow((state) => {
141+
const exec = activeWorkflowId
142+
? (state.workflowExecutions.get(activeWorkflowId) ?? defaultWorkflowExecutionState)
143+
: defaultWorkflowExecutionState
144+
return {
145+
isExecuting: exec.isExecuting,
146+
isDebugging: exec.isDebugging,
147+
pendingBlocks: exec.pendingBlocks,
148+
executor: exec.executor,
149+
debugContext: exec.debugContext,
150+
}
151+
})
152+
)
141153
const setCurrentExecutionId = useExecutionStore((s) => s.setCurrentExecutionId)
142154
const getCurrentExecutionId = useExecutionStore((s) => s.getCurrentExecutionId)
143155
const rawSetIsExecuting = useExecutionStore((s) => s.setIsExecuting)

0 commit comments

Comments
 (0)