Skip to content

Commit dcaf3e9

Browse files
v0.6.73: zustand v5 migration fix
2 parents 07b8f1b + 94f60e7 commit dcaf3e9

25 files changed

Lines changed: 646 additions & 241 deletions

File tree

.github/workflows/test-build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ jobs:
109109
- name: API contract boundary audit
110110
run: bun run check:api-validation:strict
111111

112+
- name: Zustand v5 selector audit
113+
run: bun run check:zustand-v5
114+
112115
- name: Verify realtime prune graph
113116
run: bun run check:realtime-prune
114117

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { memo, useCallback } from 'react'
22
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Lock, LogOut, Unlock } from 'lucide-react'
3+
import { useShallow } from 'zustand/react/shallow'
34
import { Button, Copy, PlayOutline, Tooltip, Trash2 } from '@/components/emcn'
45
import { cn } from '@/lib/core/utils/cn'
56
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
@@ -51,7 +52,7 @@ export const ActionBar = memo(
5152
collaborativeBatchToggleBlockHandles,
5253
collaborativeBatchToggleLocked,
5354
} = useCollaborativeWorkflow()
54-
const { setPendingSelection } = useWorkflowRegistry()
55+
const setPendingSelection = useWorkflowRegistry((state) => state.setPendingSelection)
5556
const { handleRunFromBlock } = useWorkflowExecution()
5657

5758
const addNotification = useNotificationStore((s) => s.addNotification)
@@ -94,26 +95,28 @@ export const ActionBar = memo(
9495
isParentLocked,
9596
isParentDisabled,
9697
} = useWorkflowStore(
97-
useCallback(
98-
(state) => {
99-
const block = state.blocks[blockId]
100-
const parentId = block?.data?.parentId
101-
const parentBlock = parentId ? state.blocks[parentId] : undefined
102-
return {
103-
isEnabled: block?.enabled ?? true,
104-
horizontalHandles: block?.horizontalHandles ?? false,
105-
parentId,
106-
parentType: parentBlock?.type,
107-
isLocked: block?.locked ?? false,
108-
isParentLocked: parentBlock?.locked ?? false,
109-
isParentDisabled: parentBlock ? !parentBlock.enabled : false,
110-
}
111-
},
112-
[blockId]
98+
useShallow(
99+
useCallback(
100+
(state) => {
101+
const block = state.blocks[blockId]
102+
const parentId = block?.data?.parentId
103+
const parentBlock = parentId ? state.blocks[parentId] : undefined
104+
return {
105+
isEnabled: block?.enabled ?? true,
106+
horizontalHandles: block?.horizontalHandles ?? false,
107+
parentId,
108+
parentType: parentBlock?.type,
109+
isLocked: block?.locked ?? false,
110+
isParentLocked: parentBlock?.locked ?? false,
111+
isParentDisabled: parentBlock ? !parentBlock.enabled : false,
112+
}
113+
},
114+
[blockId]
115+
)
113116
)
114117
)
115118

116-
const { activeWorkflowId } = useWorkflowRegistry()
119+
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
117120
const isExecuting = useIsCurrentWorkflowExecuting()
118121
const getLastExecutionSnapshot = useExecutionStore((s) => s.getLastExecutionSnapshot)
119122
const userPermissions = useUserPermissionsContext()

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/connection-blocks/connection-blocks.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { ConnectedBlock } from '@/app/workspace/[workspaceId]/w/[workflowId
1414
import { useBlockOutputFields } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-output-fields'
1515
import { getBlock } from '@/blocks/registry'
1616
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
17-
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
17+
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
1818
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1919

2020
const logger = createLogger('ConnectionBlocks')
@@ -148,7 +148,7 @@ export function ConnectionBlocks({ connections, currentBlockId }: ConnectionBloc
148148

149149
const workflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
150150
const workflowSubBlockValues = useSubBlockStore((state) =>
151-
workflowId ? (state.workflowValues[workflowId] ?? {}) : {}
151+
workflowId ? (state.workflowValues[workflowId] ?? EMPTY_SUBBLOCK_VALUES) : EMPTY_SUBBLOCK_VALUES
152152
)
153153

154154
const getMergedSubBlocks = useCallback(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function CredentialSelector({
5454
const [showOAuthModal, setShowOAuthModal] = useState(false)
5555
const [editingValue, setEditingValue] = useState('')
5656
const [isEditing, setIsEditing] = useState(false)
57-
const { activeWorkflowId } = useWorkflowRegistry()
57+
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
5858
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)
5959

6060
const requiredScopes = subBlock.requiredScopes || []

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export function FileUpload({
160160

161161
const fileInputRef = useRef<HTMLInputElement>(null)
162162

163-
const { activeWorkflowId } = useWorkflowRegistry()
163+
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
164164
const params = useParams()
165165
const workspaceId = params?.workspaceId as string
166166

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2+
import { isEqual } from 'es-toolkit'
23
import { RepeatIcon, SplitIcon } from 'lucide-react'
34
import { useShallow } from 'zustand/react/shallow'
5+
import { useStoreWithEqualityFn } from 'zustand/traditional'
46
import {
57
Popover,
68
PopoverAnchor,
@@ -37,6 +39,8 @@ import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subb
3739
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3840
import type { BlockState } from '@/stores/workflows/workflow/types'
3941

42+
const EMPTY_VARIABLES: Variable[] = []
43+
4044
/**
4145
* Context for sharing nested navigation state between components.
4246
* This enables unlimited nesting depth with a single back button.
@@ -997,8 +1001,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
9971001
[blocks, workflowSubBlockValues]
9981002
)
9991003

1000-
const getVariablesByWorkflowId = useVariablesStore((state) => state.getVariablesByWorkflowId)
1001-
const workflowVariables = workflowId ? getVariablesByWorkflowId(workflowId) : []
1004+
const workflowVariables = useStoreWithEqualityFn(
1005+
useVariablesStore,
1006+
useCallback(
1007+
(state) =>
1008+
workflowId
1009+
? Object.values(state.variables).filter((variable) => variable.workflowId === workflowId)
1010+
: EMPTY_VARIABLES,
1011+
[workflowId]
1012+
),
1013+
isEqual
1014+
)
10021015

10031016
const searchTerm = useMemo(
10041017
() => getTagSearchTerm(inputValue, cursorPosition),

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/search-replace/workflow-search-replace.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { ChevronDown, ChevronRight, ChevronUp, X } from 'lucide-react'
55
import { useParams } from 'next/navigation'
6+
import { useShallow } from 'zustand/react/shallow'
67
import { Button, Input } from '@/components/emcn'
78
import { cn } from '@/lib/core/utils/cn'
89
import { getWorkflowSearchDependentClears } from '@/lib/workflows/search-replace/dependencies'
@@ -126,7 +127,21 @@ export function WorkflowSearchReplace() {
126127
setQuery,
127128
setReplacement,
128129
setActiveMatchId,
129-
} = useWorkflowSearchReplaceStore()
130+
} = useWorkflowSearchReplaceStore(
131+
useShallow((state) => ({
132+
isOpen: state.isOpen,
133+
query: state.query,
134+
replacement: state.replacement,
135+
activeMatchId: state.activeMatchId,
136+
position: state.position,
137+
close: state.close,
138+
open: state.open,
139+
setPosition: state.setPosition,
140+
setQuery: state.setQuery,
141+
setReplacement: state.setReplacement,
142+
setActiveMatchId: state.setActiveMatchId,
143+
}))
144+
)
130145
const { data: workspaceCredentials } = useWorkspaceCredentials({ workspaceId, enabled: isOpen })
131146

132147
useRegisterGlobalCommands([

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

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useCallback } from 'react'
2+
import { useShallow } from 'zustand/react/shallow'
23
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
34
import type { WorkflowBlockProps } from '../types'
45

@@ -43,18 +44,20 @@ export function useBlockProperties(
4344
storeBlockAdvancedMode,
4445
storeBlockTriggerMode,
4546
} = useWorkflowStore(
46-
useCallback(
47-
(state) => {
48-
const block = state.blocks[blockId]
49-
return {
50-
storeHorizontalHandles: block?.horizontalHandles ?? true,
51-
storeBlockHeight: block?.height ?? 0,
52-
storeBlockLayout: block?.layout,
53-
storeBlockAdvancedMode: block?.advancedMode ?? false,
54-
storeBlockTriggerMode: block?.triggerMode ?? false,
55-
}
56-
},
57-
[blockId]
47+
useShallow(
48+
useCallback(
49+
(state) => {
50+
const block = state.blocks[blockId]
51+
return {
52+
storeHorizontalHandles: block?.horizontalHandles ?? true,
53+
storeBlockHeight: block?.height ?? 0,
54+
storeBlockLayout: block?.layout,
55+
storeBlockAdvancedMode: block?.advancedMode ?? false,
56+
storeBlockTriggerMode: block?.triggerMode ?? false,
57+
}
58+
},
59+
[blockId]
60+
)
5861
)
5962
)
6063

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-folder-expand.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useCallback } from 'react'
22
import { useFolderStore } from '@/stores/folders/store'
33

4+
const toggleFolderExpanded = useFolderStore.getState().toggleExpanded
5+
const setFolderExpanded = useFolderStore.getState().setExpanded
6+
47
interface UseFolderExpandProps {
58
folderId: string
69
}
@@ -13,22 +16,22 @@ interface UseFolderExpandProps {
1316
* @returns Expansion state and event handlers
1417
*/
1518
export function useFolderExpand({ folderId }: UseFolderExpandProps) {
16-
const { expandedFolders, toggleExpanded, setExpanded } = useFolderStore()
19+
const expandedFolders = useFolderStore((state) => state.expandedFolders)
1720
const isExpanded = expandedFolders.has(folderId)
1821

1922
/**
2023
* Toggle folder expansion state
2124
*/
2225
const handleToggleExpanded = useCallback(() => {
23-
toggleExpanded(folderId)
24-
}, [folderId, toggleExpanded])
26+
toggleFolderExpanded(folderId)
27+
}, [folderId])
2528

2629
/**
2730
* Expand the folder (useful when creating items inside)
2831
*/
2932
const expandFolder = useCallback(() => {
30-
setExpanded(folderId, true)
31-
}, [folderId, setExpanded])
33+
setFolderExpanded(folderId, true)
34+
}, [folderId])
3235

3336
/**
3437
* Handle keyboard navigation (Enter/Space)

apps/sim/components/emcn/components/badge/badge.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type * as React from 'react'
1+
import { forwardRef, type HTMLAttributes } from 'react'
22
import { cva, type VariantProps } from 'class-variance-authority'
33
import { cn } from '@/lib/core/utils/cn'
44

@@ -72,7 +72,7 @@ const ICON_SIZES: Record<string, string> = {
7272
}
7373

7474
export interface BadgeProps
75-
extends React.HTMLAttributes<HTMLDivElement>,
75+
extends HTMLAttributes<HTMLDivElement>,
7676
VariantProps<typeof badgeVariants> {
7777
/** Displays a dot indicator before content (only for color variants) */
7878
dot?: boolean
@@ -92,27 +92,23 @@ export interface BadgeProps
9292
* Status color variants can display a dot indicator via the `dot` prop.
9393
* All variants support an optional `icon` prop for leading icons.
9494
*/
95-
function Badge({
96-
className,
97-
variant,
98-
size,
99-
dot = false,
100-
icon: Icon,
101-
children,
102-
...props
103-
}: BadgeProps) {
95+
const Badge = forwardRef<HTMLDivElement, BadgeProps>(function Badge(
96+
{ className, variant, size, dot = false, icon: Icon, children, ...props },
97+
ref
98+
) {
10499
const isStatusVariant = STATUS_VARIANTS.includes(variant as (typeof STATUS_VARIANTS)[number])
105100
const effectiveSize = size ?? 'md'
106101

107102
return (
108-
<div className={cn(badgeVariants({ variant, size }), className)} {...props}>
103+
<div ref={ref} className={cn(badgeVariants({ variant, size }), className)} {...props}>
109104
{isStatusVariant && dot && (
110105
<div className={cn('rounded-xs bg-current', DOT_SIZES[effectiveSize])} />
111106
)}
112107
{Icon && <Icon className={ICON_SIZES[effectiveSize]} />}
113108
{children}
114109
</div>
115110
)
116-
}
111+
})
112+
Badge.displayName = 'Badge'
117113

118114
export { Badge, badgeVariants }

0 commit comments

Comments
 (0)