Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ jobs:
- name: API contract boundary audit
run: bun run check:api-validation:strict

- name: Zustand v5 selector audit
run: bun run check:zustand-v5

- name: Verify realtime prune graph
run: bun run check:realtime-prune

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { memo, useCallback } from 'react'
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Lock, LogOut, Unlock } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import { Button, Copy, PlayOutline, Tooltip, Trash2 } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
Expand Down Expand Up @@ -51,7 +52,7 @@ export const ActionBar = memo(
collaborativeBatchToggleBlockHandles,
collaborativeBatchToggleLocked,
} = useCollaborativeWorkflow()
const { setPendingSelection } = useWorkflowRegistry()
const setPendingSelection = useWorkflowRegistry((state) => state.setPendingSelection)
const { handleRunFromBlock } = useWorkflowExecution()

const addNotification = useNotificationStore((s) => s.addNotification)
Expand Down Expand Up @@ -94,26 +95,28 @@ export const ActionBar = memo(
isParentLocked,
isParentDisabled,
} = useWorkflowStore(
useCallback(
(state) => {
const block = state.blocks[blockId]
const parentId = block?.data?.parentId
const parentBlock = parentId ? state.blocks[parentId] : undefined
return {
isEnabled: block?.enabled ?? true,
horizontalHandles: block?.horizontalHandles ?? false,
parentId,
parentType: parentBlock?.type,
isLocked: block?.locked ?? false,
isParentLocked: parentBlock?.locked ?? false,
isParentDisabled: parentBlock ? !parentBlock.enabled : false,
}
},
[blockId]
useShallow(
useCallback(
(state) => {
const block = state.blocks[blockId]
const parentId = block?.data?.parentId
const parentBlock = parentId ? state.blocks[parentId] : undefined
return {
isEnabled: block?.enabled ?? true,
horizontalHandles: block?.horizontalHandles ?? false,
parentId,
parentType: parentBlock?.type,
isLocked: block?.locked ?? false,
isParentLocked: parentBlock?.locked ?? false,
isParentDisabled: parentBlock ? !parentBlock.enabled : false,
}
},
[blockId]
)
)
)

const { activeWorkflowId } = useWorkflowRegistry()
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const isExecuting = useIsCurrentWorkflowExecuting()
const getLastExecutionSnapshot = useExecutionStore((s) => s.getLastExecutionSnapshot)
const userPermissions = useUserPermissionsContext()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { ConnectedBlock } from '@/app/workspace/[workspaceId]/w/[workflowId
import { useBlockOutputFields } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-output-fields'
import { getBlock } from '@/blocks/registry'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'

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

const workflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const workflowSubBlockValues = useSubBlockStore((state) =>
workflowId ? (state.workflowValues[workflowId] ?? {}) : {}
workflowId ? (state.workflowValues[workflowId] ?? EMPTY_SUBBLOCK_VALUES) : EMPTY_SUBBLOCK_VALUES
)

const getMergedSubBlocks = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function CredentialSelector({
const [showOAuthModal, setShowOAuthModal] = useState(false)
const [editingValue, setEditingValue] = useState('')
const [isEditing, setIsEditing] = useState(false)
const { activeWorkflowId } = useWorkflowRegistry()
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)

const requiredScopes = subBlock.requiredScopes || []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export function FileUpload({

const fileInputRef = useRef<HTMLInputElement>(null)

const { activeWorkflowId } = useWorkflowRegistry()
const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
const params = useParams()
const workspaceId = params?.workspaceId as string

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isEqual } from 'es-toolkit'
import { RepeatIcon, SplitIcon } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
import { useStoreWithEqualityFn } from 'zustand/traditional'
import {
Popover,
PopoverAnchor,
Expand Down Expand Up @@ -37,6 +39,8 @@ import { EMPTY_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subb
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { BlockState } from '@/stores/workflows/workflow/types'

const EMPTY_VARIABLES: Variable[] = []

/**
* Context for sharing nested navigation state between components.
* This enables unlimited nesting depth with a single back button.
Expand Down Expand Up @@ -997,8 +1001,17 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
[blocks, workflowSubBlockValues]
)

const getVariablesByWorkflowId = useVariablesStore((state) => state.getVariablesByWorkflowId)
const workflowVariables = workflowId ? getVariablesByWorkflowId(workflowId) : []
const workflowVariables = useStoreWithEqualityFn(
useVariablesStore,
useCallback(
(state) =>
workflowId
? Object.values(state.variables).filter((variable) => variable.workflowId === workflowId)
: EMPTY_VARIABLES,
[workflowId]
),
isEqual
)

const searchTerm = useMemo(
() => getTagSearchTerm(inputValue, cursorPosition),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { ChevronDown, ChevronRight, ChevronUp, X } from 'lucide-react'
import { useParams } from 'next/navigation'
import { useShallow } from 'zustand/react/shallow'
import { Button, Input } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { getWorkflowSearchDependentClears } from '@/lib/workflows/search-replace/dependencies'
Expand Down Expand Up @@ -126,7 +127,21 @@ export function WorkflowSearchReplace() {
setQuery,
setReplacement,
setActiveMatchId,
} = useWorkflowSearchReplaceStore()
} = useWorkflowSearchReplaceStore(
useShallow((state) => ({
isOpen: state.isOpen,
query: state.query,
replacement: state.replacement,
activeMatchId: state.activeMatchId,
position: state.position,
close: state.close,
open: state.open,
setPosition: state.setPosition,
setQuery: state.setQuery,
setReplacement: state.setReplacement,
setActiveMatchId: state.setActiveMatchId,
}))
)
const { data: workspaceCredentials } = useWorkspaceCredentials({ workspaceId, enabled: isOpen })

useRegisterGlobalCommands([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { WorkflowBlockProps } from '../types'

Expand Down Expand Up @@ -43,18 +44,20 @@ export function useBlockProperties(
storeBlockAdvancedMode,
storeBlockTriggerMode,
} = useWorkflowStore(
useCallback(
(state) => {
const block = state.blocks[blockId]
return {
storeHorizontalHandles: block?.horizontalHandles ?? true,
storeBlockHeight: block?.height ?? 0,
storeBlockLayout: block?.layout,
storeBlockAdvancedMode: block?.advancedMode ?? false,
storeBlockTriggerMode: block?.triggerMode ?? false,
}
},
[blockId]
useShallow(
useCallback(
(state) => {
const block = state.blocks[blockId]
return {
storeHorizontalHandles: block?.horizontalHandles ?? true,
storeBlockHeight: block?.height ?? 0,
storeBlockLayout: block?.layout,
storeBlockAdvancedMode: block?.advancedMode ?? false,
storeBlockTriggerMode: block?.triggerMode ?? false,
}
},
[blockId]
)
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useCallback } from 'react'
import { useFolderStore } from '@/stores/folders/store'

const toggleFolderExpanded = useFolderStore.getState().toggleExpanded
const setFolderExpanded = useFolderStore.getState().setExpanded

interface UseFolderExpandProps {
folderId: string
}
Expand All @@ -13,22 +16,22 @@ interface UseFolderExpandProps {
* @returns Expansion state and event handlers
*/
export function useFolderExpand({ folderId }: UseFolderExpandProps) {
const { expandedFolders, toggleExpanded, setExpanded } = useFolderStore()
const expandedFolders = useFolderStore((state) => state.expandedFolders)
const isExpanded = expandedFolders.has(folderId)

/**
* Toggle folder expansion state
*/
const handleToggleExpanded = useCallback(() => {
toggleExpanded(folderId)
}, [folderId, toggleExpanded])
toggleFolderExpanded(folderId)
}, [folderId])

/**
* Expand the folder (useful when creating items inside)
*/
const expandFolder = useCallback(() => {
setExpanded(folderId, true)
}, [folderId, setExpanded])
setFolderExpanded(folderId, true)
}, [folderId])

/**
* Handle keyboard navigation (Enter/Space)
Expand Down
22 changes: 9 additions & 13 deletions apps/sim/components/emcn/components/badge/badge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type * as React from 'react'
import { forwardRef, type HTMLAttributes } from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'

Expand Down Expand Up @@ -72,7 +72,7 @@ const ICON_SIZES: Record<string, string> = {
}

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {
/** Displays a dot indicator before content (only for color variants) */
dot?: boolean
Expand All @@ -92,27 +92,23 @@ export interface BadgeProps
* Status color variants can display a dot indicator via the `dot` prop.
* All variants support an optional `icon` prop for leading icons.
*/
function Badge({
className,
variant,
size,
dot = false,
icon: Icon,
children,
...props
}: BadgeProps) {
const Badge = forwardRef<HTMLDivElement, BadgeProps>(function Badge(
{ className, variant, size, dot = false, icon: Icon, children, ...props },
ref
) {
const isStatusVariant = STATUS_VARIANTS.includes(variant as (typeof STATUS_VARIANTS)[number])
const effectiveSize = size ?? 'md'

return (
<div className={cn(badgeVariants({ variant, size }), className)} {...props}>
<div ref={ref} className={cn(badgeVariants({ variant, size }), className)} {...props}>
{isStatusVariant && dot && (
<div className={cn('rounded-xs bg-current', DOT_SIZES[effectiveSize])} />
)}
{Icon && <Icon className={ICON_SIZES[effectiveSize]} />}
{children}
</div>
)
}
})
Badge.displayName = 'Badge'

export { Badge, badgeVariants }
5 changes: 3 additions & 2 deletions apps/sim/hooks/use-reactive-conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibilit
import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility'
import type { SubBlockConfig } from '@/blocks/types'
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { EMPTY_BLOCK_SUBBLOCK_VALUES, useSubBlockStore } from '@/stores/workflows/subblock/store'

/**
* Evaluates reactive conditions for subblocks. Always calls the same hooks
Expand All @@ -27,7 +27,8 @@ export function useReactiveConditions(
useCallback(
(state) => {
if (!reactiveCond || !activeWorkflowId) return ''
const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {}
const blockValues =
state.workflowValues[activeWorkflowId]?.[blockId] ?? EMPTY_BLOCK_SUBBLOCK_VALUES
for (const field of reactiveCond.watchFields) {
const val = resolveDependencyValue(
field,
Expand Down
Loading
Loading