diff --git a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-card.tsx b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-card.tsx index fce1eff9df..3d3a05329d 100644 --- a/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-card.tsx +++ b/apps/sim/app/w/[id]/components/control-bar/components/deployment-controls/components/deployed-workflow-card.tsx @@ -1,11 +1,14 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useMemo } from 'react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader } from '@/components/ui/card' import { cn } from '@/lib/utils' +import { createLogger } from '@/lib/logs/console-logger' import { WorkflowPreview } from '@/app/w/components/workflow-preview/workflow-preview' +const logger = createLogger('DeployedWorkflowCard') + interface DeployedWorkflowCardProps { currentWorkflowState?: { blocks: Record @@ -29,6 +32,72 @@ export function DeployedWorkflowCard({ const workflowToShow = showingDeployed ? deployedWorkflowState : currentWorkflowState console.log('workflowToShow', workflowToShow) + // Add detailed logging for debugging + useEffect(() => { + if (workflowToShow) { + // Log basic stats + const blockCount = Object.keys(workflowToShow.blocks || {}).length; + const blocksWithSubBlocks = Object.values(workflowToShow.blocks || {}) + .filter(block => block.subBlocks && Object.keys(block.subBlocks).length > 0); + + logger.info(`[WORKFLOW-STATE] ${showingDeployed ? 'Deployed' : 'Current'} workflow with ${blockCount} blocks`, { + type: showingDeployed ? 'deployed' : 'current', + blockCount, + blocksWithSubBlocksCount: blocksWithSubBlocks.length, + // Log a sample of a block with subblocks if any exist + sampleBlock: blocksWithSubBlocks.length > 0 + ? { + id: blocksWithSubBlocks[0].id, + type: blocksWithSubBlocks[0].type, + subBlocksCount: Object.keys(blocksWithSubBlocks[0].subBlocks || {}).length, + subBlocksSample: Object.entries(blocksWithSubBlocks[0].subBlocks || {}).slice(0, 2) + } + : null + }); + + // For deep debug, log each block's subblocks (limited data for readability) + Object.entries(workflowToShow.blocks || {}).forEach(([blockId, block]) => { + if (block.subBlocks && Object.keys(block.subBlocks).length > 0) { + logger.info(`[BLOCK-SUBBLOCKS] ${showingDeployed ? 'Deployed' : 'Current'} block ${blockId}`, { + blockId, + blockType: block.type, + subBlocksCount: Object.keys(block.subBlocks).length, + // Just log IDs to avoid huge logs, but include a couple of values as examples + subBlockIds: Object.keys(block.subBlocks), + sampleValues: Object.entries(block.subBlocks).slice(0, 2).map(([id, value]) => ({ id, value })) + }); + } + }); + } + }, [workflowToShow, showingDeployed]); + + // Create sanitized workflow state + const sanitizedWorkflowState = useMemo(() => { + if (!workflowToShow) return null; + + // Filter out invalid blocks and make deep clone to avoid reference issues + return { + blocks: Object.fromEntries( + Object.entries(workflowToShow.blocks || {}) + .filter(([_, block]) => block && block.type) // Filter out invalid blocks + .map(([id, block]) => { + // Deep clone the block to avoid any reference sharing + const clonedBlock = JSON.parse(JSON.stringify(block)); + return [id, clonedBlock]; + }) + ), + edges: workflowToShow.edges ? JSON.parse(JSON.stringify(workflowToShow.edges)) : [], + loops: workflowToShow.loops ? JSON.parse(JSON.stringify(workflowToShow.loops)) : {} + }; + }, [workflowToShow]); + + // Generate a unique key for the workflow preview + const previewKey = useMemo(() => { + return `${showingDeployed ? 'deployed' : 'current'}-preview-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; + }, [showingDeployed]); + + {console.log('sanitizedWorkflowState', sanitizedWorkflowState)} + return ( {/* Workflow preview with fixed height */} -
- {workflowToShow ? ( +
+ {sanitizedWorkflowState ? ( { + if (!currentWorkflowState) return undefined; + + return { + blocks: Object.fromEntries( + Object.entries(currentWorkflowState.blocks || {}) + .filter(([_, block]) => block && block.type) + .map(([id, block]) => { + // Deep clone the block to avoid any reference sharing + return [id, JSON.parse(JSON.stringify(block))]; + }) + ), + edges: currentWorkflowState.edges ? [...currentWorkflowState.edges] : [], + loops: currentWorkflowState.loops ? {...currentWorkflowState.loops} : {} + }; + }, [currentWorkflowState]); + + const sanitizedDeployedState = useMemo(() => { + if (!deployedWorkflowState) return { + blocks: {}, + edges: [], + loops: {} + }; + + return { + blocks: Object.fromEntries( + Object.entries(deployedWorkflowState.blocks || {}) + .filter(([_, block]) => block && block.type) + .map(([id, block]) => { + // Deep clone the block to avoid any reference sharing + return [id, JSON.parse(JSON.stringify(block))]; + }) + ), + edges: deployedWorkflowState.edges ? [...deployedWorkflowState.edges] : [], + loops: deployedWorkflowState.loops ? {...deployedWorkflowState.loops} : {} + }; + }, [deployedWorkflowState]); const handleRevert = () => { <<<<<<< HEAD @@ -107,8 +146,8 @@ export function DeployedWorkflowModal({
) : ( )} diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx index 8f802cd24c..033a71ebcb 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/dropdown.tsx @@ -6,8 +6,11 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' +import { createLogger } from '@/lib/logs/console-logger' import { useSubBlockValue } from '../hooks/use-sub-block-value' +const logger = createLogger('Dropdown') + interface DropdownProps { options: | Array @@ -15,12 +18,32 @@ interface DropdownProps { defaultValue?: string blockId: string subBlockId: string + isPreview?: boolean + value?: string } -export function Dropdown({ options, defaultValue, blockId, subBlockId }: DropdownProps) { - const [value, setValue] = useSubBlockValue(blockId, subBlockId, true) +export function Dropdown({ + options, + defaultValue, + blockId, + subBlockId, + isPreview = false, + value: propValue +}: DropdownProps) { + const [value, setValue] = useSubBlockValue(blockId, subBlockId, true, isPreview, propValue) const [storeInitialized, setStoreInitialized] = useState(false) + // Log when in preview mode to verify it's working + useEffect(() => { + if (isPreview) { + logger.info(`[PREVIEW] Dropdown for ${blockId}:${subBlockId}`, { + isPreview, + propValue, + value + }); + } + }, [isPreview, propValue, value, blockId, subBlockId]); + // Evaluate options if it's a function const evaluatedOptions = useMemo(() => { return typeof options === 'function' ? options() : options diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx index bd66e8d5da..8353c85bf2 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/long-input.tsx @@ -19,6 +19,8 @@ interface LongInputProps { isConnecting: boolean config: SubBlockConfig rows?: number + isPreview?: boolean + value?: string } // Constants @@ -33,8 +35,10 @@ export function LongInput({ isConnecting, config, rows, + isPreview = false, + value: propValue, }: LongInputProps) { - const [value, setValue] = useSubBlockValue(blockId, subBlockId) + const [value, setValue] = useSubBlockValue(blockId, subBlockId, false, isPreview, propValue) const [showEnvVars, setShowEnvVars] = useState(false) const [showTags, setShowTags] = useState(false) const [searchTerm, setSearchTerm] = useState('') @@ -44,6 +48,17 @@ export function LongInput({ const [activeSourceBlockId, setActiveSourceBlockId] = useState(null) const containerRef = useRef(null) + // Log when in preview mode to verify it's working + useEffect(() => { + if (isPreview) { + logger.info(`[PREVIEW] LongInput for ${blockId}:${subBlockId}`, { + isPreview, + propValue, + value + }); + } + }, [isPreview, propValue, value, blockId, subBlockId]); + // Calculate initial height based on rows prop with reasonable defaults const getInitialHeight = () => { // Use provided rows or default, then convert to pixels with a minimum diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx index 70203237db..513346410a 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/short-input.tsx @@ -20,6 +20,7 @@ interface ShortInputProps { config: SubBlockConfig value?: string onChange?: (value: string) => void + isPreview?: boolean } export function ShortInput({ @@ -31,11 +32,18 @@ export function ShortInput({ config, value: propValue, onChange, + isPreview = false, }: ShortInputProps) { const [isFocused, setIsFocused] = useState(false) const [showEnvVars, setShowEnvVars] = useState(false) const [showTags, setShowTags] = useState(false) - const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) + const [storeValue, setStoreValue] = useSubBlockValue( + blockId, + subBlockId, + false, // No workflow update needed + isPreview, + propValue + ) const [searchTerm, setSearchTerm] = useState('') const [cursorPosition, setCursorPosition] = useState(0) const inputRef = useRef(null) @@ -45,8 +53,10 @@ export function ShortInput({ // Get ReactFlow instance for zoom control const reactFlowInstance = useReactFlow() - // Use either controlled or uncontrolled value - const value = propValue !== undefined ? propValue : storeValue + // Use either controlled or uncontrolled value, prioritizing the direct value if in preview mode + const value = isPreview && propValue !== undefined + ? propValue + : (propValue !== undefined ? propValue : storeValue) // Check if this input is API key related const isApiKeyField = useMemo(() => { diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts index 5f28744276..7d230d9a52 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts @@ -1,10 +1,17 @@ import { useCallback, useEffect, useRef } from 'react' import { isEqual } from 'lodash' +<<<<<<< HEAD import { getProviderFromModel } from '@/providers/utils' +======= +import { createLogger } from '@/lib/logs/console-logger' +>>>>>>> 6f129dfc (fix: subblock rerender fixed) import { useGeneralStore } from '@/stores/settings/general/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +// Add logger for diagnostic logging +const logger = createLogger('useSubBlockValue') + /** * Helper to handle API key auto-fill for provider-based blocks * Used for agent, router, evaluator, and any other blocks that use LLM providers @@ -148,12 +155,20 @@ function storeApiKeyValue( * @param blockId The ID of the block containing the sub-block * @param subBlockId The ID of the sub-block * @param triggerWorkflowUpdate Whether to trigger a workflow update when the value changes + * @param isPreview Whether this is being used in preview mode + * @param directValue The direct value to use when in preview mode * @returns A tuple containing the current value and a setter function */ export function useSubBlockValue( blockId: string, subBlockId: string, +<<<<<<< HEAD triggerWorkflowUpdate = false +======= + triggerWorkflowUpdate: boolean = false, + isPreview: boolean = false, + directValue?: T +>>>>>>> 6f129dfc (fix: subblock rerender fixed) ): readonly [T | null, (value: T) => void] { const blockType = useWorkflowStore( useCallback((state) => state.blocks?.[blockId]?.type, [blockId]) @@ -171,12 +186,124 @@ export function useSubBlockValue( // Previous model reference for detecting model changes const prevModelRef = useRef(null) + + // Ref to track if we're in preview mode and have direct values + const previewDataRef = useRef<{ + isInPreview: boolean, + directValue: T | null + }>({ + isInPreview: isPreview, + directValue: directValue as T || null + }) // Get value from subblock store - always call this hook unconditionally const storeValue = useSubBlockStore( useCallback((state) => state.getValue(blockId, subBlockId), [blockId, subBlockId]) ) + // Directly update preview data when props change + useEffect(() => { + if (isPreview && directValue !== undefined) { + previewDataRef.current = { + isInPreview: true, + directValue: directValue as T || null + }; + valueRef.current = directValue as T || null; + + logger.info(`[PREVIEW-DIRECT] Using direct value for ${blockId}:${subBlockId}`, { + directValue, + isPreview + }); + } + }, [isPreview, directValue, blockId, subBlockId]); + + // Check for preview mode first and get direct subblock values if available + useEffect(() => { + // Skip DOM-based detection if already in preview mode with direct values + if (isPreview && directValue !== undefined) return; + + try { + // Try to find if this component is within a preview parent + const parentBlock = document.querySelector(`[data-id="${blockId}"]`); + if (parentBlock) { + const isPreviewContext = parentBlock.closest('.preview-mode') != null; + + // Get direct subblock values from parent's data props if in preview mode + if (isPreviewContext) { + const dataProps = parentBlock.getAttribute('data-props'); + if (dataProps) { + const parsedProps = JSON.parse(dataProps); + const directValue = parsedProps?.data?.subBlockValues?.[subBlockId]; + + // If we have direct values in preview mode, use them + if (directValue !== undefined && directValue !== null) { + // Save the values in our ref + previewDataRef.current = { + isInPreview: true, + directValue: directValue + }; + + // Update valueRef directly to use the preview value + valueRef.current = directValue; + + logger.info(`[PREVIEW-DOM] Using direct subblock value for ${blockId}:${subBlockId}`, { + directValue, + storeValue + }); + } + } + } else { + // Reset preview flag if we're no longer in preview mode + previewDataRef.current.isInPreview = false; + } + } + } catch (e) { + // Ignore errors in preview detection + } + }, [blockId, subBlockId, isPreview, directValue, storeValue]); + + // Add logging to trace where values are coming from + useEffect(() => { + // Skip if we've already determined we're in preview mode + if (previewDataRef.current.isInPreview) return; + + // Check if we're in preview context + let isPreviewContext = false; + let directSubBlockValue = null; + + try { + // Try to find if this component is within a preview parent + const parentBlock = document.querySelector(`[data-id="${blockId}"]`); + if (parentBlock) { + isPreviewContext = parentBlock.closest('.preview-mode') != null; + + // Try to find the parent data to see if subBlockValues was passed directly + const dataProps = parentBlock.getAttribute('data-props'); + if (dataProps) { + const parsedProps = JSON.parse(dataProps); + directSubBlockValue = parsedProps?.data?.subBlockValues?.[subBlockId]; + } + } + + logger.info(`[DATA-TRACE] SubBlock value source for ${blockId}:${subBlockId}`, { + blockId, + subBlockId, + storeValueExists: storeValue !== undefined, + initialValueExists: initialValue !== null, + isPreviewContext, + hasDirectSubBlockValue: directSubBlockValue !== undefined && directSubBlockValue !== null, + valueFromStore: storeValue, + valueFromInitial: initialValue, + valueFromDirect: directSubBlockValue, + actuallyUsing: previewDataRef.current.isInPreview ? 'directValue' : + (storeValue !== undefined ? 'globalStore' : + (initialValue !== null ? 'initialValue' : 'null')), + }); + } catch (e) { + // Ignore errors - this is just diagnostic logging + } + }, [blockId, subBlockId, storeValue, initialValue]); + // Check if this is an API key field that could be auto-filled const isApiKey = subBlockId === 'apiKey' || (subBlockId?.toLowerCase().includes('apikey') ?? false) @@ -196,10 +323,46 @@ export function useSubBlockValue( // Compute the modelValue based on block type const modelValue = isProviderBasedBlock ? (modelSubBlockValue as string) : null - // Hook to set a value in the subblock store - const setValue = useCallback( + // Initialize valueRef on first render + useEffect(() => { + // If we're in preview mode with direct values, use those + if (previewDataRef.current.isInPreview) { + valueRef.current = previewDataRef.current.directValue; + } else { + // Otherwise use the store value or initial value + valueRef.current = storeValue !== undefined ? storeValue : initialValue; + } + }, []) + + // Update the ref if the store value changes + // This ensures we're always working with the latest value + useEffect(() => { + // Skip updates from global store if we're using preview values + if (previewDataRef.current.isInPreview) return; + + // Use deep comparison for objects to prevent unnecessary updates + if (!isEqual(valueRef.current, storeValue)) { + valueRef.current = storeValue !== undefined ? storeValue : initialValue + } + }, [storeValue, initialValue]) + + // Create a preview-aware setValue function + const setValueWithPreview = useCallback( (newValue: T) => { - // Use deep comparison to avoid unnecessary updates for complex objects + // If we're in preview mode, just update the local valueRef for display + // but don't update the global store + if (previewDataRef.current.isInPreview) { + // Only update if the value has changed + if (!isEqual(valueRef.current, newValue)) { + valueRef.current = newValue; + // Update the ref as well + previewDataRef.current.directValue = newValue; + } + // Return early without updating global state + return; + } + + // For non-preview mode, use the normal setValue logic if (!isEqual(valueRef.current, newValue)) { valueRef.current = newValue @@ -228,11 +391,6 @@ export function useSubBlockValue( [blockId, subBlockId, blockType, isApiKey, storeValue, triggerWorkflowUpdate, modelValue] ) - // Initialize valueRef on first render - useEffect(() => { - valueRef.current = storeValue !== undefined ? storeValue : initialValue - }, []) - // When component mounts, check for existing API key in toolParamsStore useEffect(() => { // Skip autofill if the feature is disabled in settings @@ -288,14 +446,5 @@ export function useSubBlockValue( isProviderBasedBlock, ]) - // Update the ref if the store value changes - // This ensures we're always working with the latest value - useEffect(() => { - // Use deep comparison for objects to prevent unnecessary updates - if (!isEqual(valueRef.current, storeValue)) { - valueRef.current = storeValue !== undefined ? storeValue : initialValue - } - }, [storeValue, initialValue]) - - return [valueRef.current as T | null, setValue] as const + return [valueRef.current as T | null, setValueWithPreview] as const } diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx index faf013deef..5c84fa013c 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx @@ -1,6 +1,12 @@ +import { useEffect } from 'react' import { Info } from 'lucide-react' import { Label } from '@/components/ui/label' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +<<<<<<< HEAD +======= +import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import { createLogger } from '@/lib/logs/console-logger' +>>>>>>> 6f129dfc (fix: subblock rerender fixed) import { getBlock } from '@/blocks/index' import type { SubBlockConfig } from '@/blocks/types' import { useSubBlockStore } from '@/stores/workflows/subblock/store' @@ -28,13 +34,36 @@ import { TimeInput } from './components/time-input' import { ToolInput } from './components/tool-input/tool-input' import { WebhookConfig } from './components/webhook/webhook' +// Add logger +const logger = createLogger('SubBlock') + interface SubBlockProps { blockId: string config: SubBlockConfig isConnecting: boolean + isPreview?: boolean + previewValue?: any } -export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { +export function SubBlock({ + blockId, + config, + isConnecting, + isPreview = false, + previewValue = undefined +}: SubBlockProps) { + // Add debugging logs to trace parent context + useEffect(() => { + logger.info(`[TRACE] SubBlock ${config.id} for block ${blockId}`, { + blockId, + subBlockId: config.id, + subBlockTitle: config.title, + isPreview: isPreview, + previewValue: previewValue, + usingGlobalStore: !isPreview + }); + }, [blockId, config.id, config.title, isPreview, previewValue]); + const handleMouseDown = (e: React.MouseEvent) => { e.stopPropagation() } @@ -50,6 +79,9 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { } const renderInput = () => { + // Get the subblock value from the config if available + const directValue = isPreview ? previewValue : undefined; + switch (config.type) { case 'short-input': return ( @@ -60,6 +92,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { password={config.password} isConnecting={isConnecting} config={config} + isPreview={isPreview} + value={directValue} /> ) case 'long-input': @@ -71,6 +105,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { isConnecting={isConnecting} rows={config.rows} config={config} + isPreview={isPreview} + value={directValue} /> ) case 'dropdown': @@ -80,6 +116,8 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { blockId={blockId} subBlockId={config.id} options={config.options as string[]} + isPreview={isPreview} + value={directValue} />
) diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index a8f720f123..83c30bc9e0 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -8,6 +8,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { parseCronToHumanReadable } from '@/lib/schedules/utils' import { cn, formatDateTime, validateName } from '@/lib/utils' import type { BlockConfig, SubBlockConfig } from '@/blocks/types' +import { createLogger } from '@/lib/logs/console-logger' import { useExecutionStore } from '@/stores/execution/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { mergeSubblockState } from '@/stores/workflows/utils' @@ -16,18 +17,38 @@ import { ActionBar } from './components/action-bar/action-bar' import { ConnectionBlocks } from './components/connection-blocks/connection-blocks' import { SubBlock } from './components/sub-block/sub-block' +// Add logger for diagnostic logging +const logger = createLogger('WorkflowBlock') + interface WorkflowBlockProps { type: string config: BlockConfig name: string isActive?: boolean isPending?: boolean + isPreview?: boolean + isReadOnly?: boolean + subBlockValues?: Record + blockState?: any } // Combine both interfaces into a single component export function WorkflowBlock({ id, data }: NodeProps) { const { type, config, name, isActive: dataIsActive, isPending } = data + // Add logging for debug purposes + useEffect(() => { + if (data.isPreview === true) { + logger.info(`[PREVIEW-RENDER] Block ${id} rendering in preview mode`, { + blockId: id, + blockType: type, + subBlockValues: data.subBlockValues ? Object.keys(data.subBlockValues) : [], + hasDirectSubBlocks: Boolean(data.subBlockValues && Object.keys(data.subBlockValues).length > 0), + isReadOnly: data.isReadOnly, + }); + } + }, [id, type, data.isPreview, data.subBlockValues, data.isReadOnly]); + // State management const [isConnecting, setIsConnecting] = useState(false) const [isEditing, setIsEditing] = useState(false) @@ -256,11 +277,15 @@ export function WorkflowBlock({ id, data }: NodeProps) { let currentRow: SubBlockConfig[] = [] let currentRowWidth = 0 - // Get merged state for this block + // Get merged state for this block - use direct props if in preview mode const blocks = useWorkflowStore.getState().blocks const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId || undefined - const mergedState = mergeSubblockState(blocks, activeWorkflowId, blockId)[blockId] - const isAdvancedMode = useWorkflowStore.getState().blocks[blockId]?.advancedMode ?? false + const isAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false) + + // If in preview mode with direct subBlockValues, use those instead of global state + const mergedState = data.isPreview && data.subBlockValues + ? { [blockId]: { subBlocks: data.subBlockValues } } + : mergeSubblockState(blocks, activeWorkflowId, blockId) // Filter visible blocks and those that meet their conditions const visibleSubBlocks = subBlocks.filter((block) => { @@ -276,9 +301,9 @@ export function WorkflowBlock({ id, data }: NodeProps) { if (!block.condition) return true // Get the values of the fields this block depends on from merged state - const fieldValue = mergedState?.subBlocks[block.condition.field]?.value + const fieldValue = mergedState?.[blockId]?.subBlocks[block.condition.field]?.value const andFieldValue = block.condition.and - ? mergedState?.subBlocks[block.condition.and.field]?.value + ? mergedState?.[blockId]?.subBlocks[block.condition.and.field]?.value : undefined // Check if the condition value is an array @@ -398,6 +423,15 @@ export function WorkflowBlock({ id, data }: NodeProps) { isPending && 'ring-2 ring-amber-500', 'z-[20]' )} + data-id={id} + data-props={data.isPreview ? JSON.stringify({ + isPreview: data.isPreview, + isReadOnly: data.isReadOnly, + blockType: type, + data: { + subBlockValues: data.subBlockValues + } + }) : undefined} > {/* Show debug indicator for pending blocks */} {isPending && ( @@ -703,7 +737,14 @@ export function WorkflowBlock({ id, data }: NodeProps) { key={`${id}-${rowIndex}-${blockIndex}`} className={cn('space-y-1', subBlock.layout === 'half' ? 'flex-1' : 'w-full')} > - + ))} diff --git a/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx b/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx index 9483f1fb5b..1e5f3d7cbd 100644 --- a/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx +++ b/apps/sim/app/w/components/workflow-preview/workflow-preview.tsx @@ -26,7 +26,11 @@ import { WorkflowEdge } from '@/app/w/[id]/components/workflow-edge/workflow-edg // import { LoopLabel } from '@/app/w/[id]/components/workflow-loop/components/loop-label/loop-label' // import { createLoopNode } from '@/app/w/[id]/components/workflow-loop/workflow-loop' import { getBlock } from '@/blocks' +<<<<<<< HEAD import type { SubBlockConfig } from '@/blocks/types' +======= +import { cn } from '@/lib/utils' +>>>>>>> 6f129dfc (fix: subblock rerender fixed) const logger = createLogger('WorkflowPreview') @@ -81,6 +85,14 @@ export function WorkflowPreview({ defaultPosition, defaultZoom, }: WorkflowPreviewProps) { + // Use effect to log the workflow state once outside of useMemo + useEffect(() => { + logger.info('WorkflowPreview received new state', { + blockCount: Object.keys(workflowState?.blocks || {}).length, + withSubBlocks: Object.values(workflowState?.blocks || {}).filter(b => b.subBlocks && Object.keys(b.subBlocks).length > 0).length, + }); + }, [workflowState]); + // Transform blocks and loops into ReactFlow nodes const nodes: Node[] = useMemo(() => { const nodeArray: Node[] = [] @@ -102,12 +114,20 @@ export function WorkflowPreview({ // Add block nodes using the same approach as workflow.tsx Object.entries(workflowState.blocks).forEach(([blockId, block]) => { + if (!block || !block.type) { + logger.warn(`Skipping invalid block: ${blockId}`); + return; + } + const blockConfig = getBlock(block.type) if (!blockConfig) { logger.error(`No configuration found for block type: ${block.type}`, { blockId }) - return + return; } + // Create a deep clone of subBlocks to avoid any references to the original state + const subBlocksClone = block.subBlocks ? JSON.parse(JSON.stringify(block.subBlocks)) : {}; + nodeArray.push({ id: blockId, type: 'workflowBlock', @@ -120,9 +140,10 @@ export function WorkflowPreview({ blockState: block, isReadOnly: true, // Set read-only mode for preview isPreview: true, // Indicate this is a preview - subBlockValues: block.subBlocks || {}, // Use empty object as fallback + subBlockValues: subBlocksClone, // Use the deep clone to avoid reference issues }, }) +<<<<<<< HEAD // Add children of this block if it's a loop if (block.type === 'loop') { @@ -156,6 +177,12 @@ export function WorkflowPreview({ }) }) } +======= + logger.info(`Preview node created: ${blockId}`, { + blockType: block.type, + hasSubBlocks: block.subBlocks && Object.keys(block.subBlocks).length > 0 + }); +>>>>>>> 6f129dfc (fix: subblock rerender fixed) }) return nodeArray @@ -179,7 +206,7 @@ export function WorkflowPreview({ return ( -
+