diff --git a/apps/sim/serializer/index.test.ts b/apps/sim/serializer/index.test.ts index c9fc5b8c9c..7a252583eb 100644 --- a/apps/sim/serializer/index.test.ts +++ b/apps/sim/serializer/index.test.ts @@ -991,6 +991,46 @@ describe('Serializer', () => { expect(slackBlock?.config.params.unknownField).toBeUndefined() }) + it.concurrent( + 'should include conditional fields when condition field has default value but null stored value', + () => { + const serializer = new Serializer() + + // This simulates the cursor block scenario where: + // - operation has a default value function: () => 'cursor_launch_agent' + // - branchName has condition: { field: 'operation', value: 'cursor_launch_agent' } + // - But operation's stored value is null (user never changed the dropdown) + // We'll use slack block which has similar conditional field behavior + const slackBlockWithNullChannel: any = { + id: 'slack-1', + type: 'slack', + name: 'Slack', + position: { x: 0, y: 0 }, + advancedMode: false, + subBlocks: { + channel: { value: 'general' }, // This simulates the 'operation' dropdown + text: { value: 'Hello world' }, // This has a condition on channel + // manualChannel would be null as it's in advanced mode + }, + outputs: {}, + enabled: true, + } + + const serialized = serializer.serializeWorkflow( + { 'slack-1': slackBlockWithNullChannel }, + [], + {} + ) + + const slackBlock = serialized.blocks.find((b) => b.id === 'slack-1') + expect(slackBlock).toBeDefined() + + // Both fields should be included since channel (the condition field) has a value + expect(slackBlock?.config.params.channel).toBe('general') + expect(slackBlock?.config.params.text).toBe('Hello world') + } + ) + it.concurrent( 'should preserve legacy agent fields (systemPrompt, userPrompt, memories) for backward compatibility', () => { diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts index 6500f7fb22..9d74941e57 100644 --- a/apps/sim/serializer/index.ts +++ b/apps/sim/serializer/index.ts @@ -400,6 +400,24 @@ export class Serializer { allValues[id] = subBlock.value }) + // Apply default values to allValues before condition evaluation + // This ensures that fields with default value functions (like dropdowns) + // have their defaults applied when evaluating conditions for other fields + blockConfig.subBlocks.forEach((subBlockConfig) => { + const id = subBlockConfig.id + if ( + (allValues[id] === null || allValues[id] === undefined) && + subBlockConfig.value && + shouldIncludeField(subBlockConfig, isAdvancedMode) + ) { + try { + allValues[id] = subBlockConfig.value(allValues) + } catch { + // If the default value function fails, keep the original value + } + } + }) + // Second pass: filter by mode and conditions Object.entries(block.subBlocks).forEach(([id, subBlock]) => { const matchingConfigs = blockConfig.subBlocks.filter((config) => config.id === id)