From ce1fe94556b4789b13ab9bda14a86298860797cc Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Tue, 1 Sep 2026 23:55:21 -0700 Subject: [PATCH 1/5] fix(chat-deploy): restore output picker interactions --- .../output-select/output-select.test.tsx | 92 +++++++++++-------- .../output-select/output-select.tsx | 62 ++++++++++--- .../components/tool-input/tool-input.tsx | 52 ++++++----- 3 files changed, 132 insertions(+), 74 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx index 296da0f8df2..26781e9ed1b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx @@ -9,13 +9,8 @@ const { outputMenuState } = vi.hoisted(() => ({ outputMenuState: { includeNestedWorkflow: true }, })) -vi.mock('@sim/emcn', () => ({ - cn: (...values: unknown[]) => values.flat().filter(Boolean).join(' '), - Combobox: ({ - groups, - multiSelectValues = [], - onMultiSelectChange, - }: { +vi.mock('@sim/emcn', () => { + interface MockComboboxProps { groups: Array<{ section?: string sectionElement?: ReactNode @@ -29,7 +24,13 @@ vi.mock('@sim/emcn', () => ({ }> multiSelectValues?: string[] onMultiSelectChange?: (values: string[]) => void - }) => ( + } + + const MockCombobox = ({ + groups, + multiSelectValues = [], + onMultiSelectChange, + }: MockComboboxProps) => (
{groups.map((group, groupIndex) => (
@@ -59,31 +60,18 @@ vi.mock('@sim/emcn', () => ({
))}
- ), - ChipCombobox: ({ - groups, - multiSelectValues, - onMultiSelectChange, - }: { - groups: Array<{ section?: string; items: Array<{ label: string; value: string }> }> - multiSelectValues?: string[] - onMultiSelectChange?: (values: string[]) => void - }) => ( -
- {groups.flatMap((group) => - group.items.map((option) => ( - - )) - )} -
- ), -})) + ) + + return { + cn: (...values: unknown[]) => values.flat().filter(Boolean).join(' '), + Combobox: MockCombobox, + ChipCombobox: (props: MockComboboxProps) => ( +
+ +
+ ), + } +}) vi.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })) @@ -186,7 +174,8 @@ function outputSelect( workflowId: string, selectedOutputs: string[], onOutputSelect: (outputIds: string[]) => void, - valueMode: 'id' | 'label' | 'public' = 'id' + valueMode: 'id' | 'label' | 'public' = 'id', + size: 'sm' | 'md' = 'sm' ) { return ( ) } @@ -201,14 +191,15 @@ function outputSelect( function renderOutputSelect( selectedOutputs: string[], onOutputSelect = vi.fn(), - valueMode: 'id' | 'label' | 'public' = 'id' + valueMode: 'id' | 'label' | 'public' = 'id', + size: 'sm' | 'md' = 'sm' ) { ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true container = document.createElement('div') document.body.appendChild(container) root = createRoot(container) act(() => { - root?.render(outputSelect('root', selectedOutputs, onOutputSelect, valueMode)) + root?.render(outputSelect('root', selectedOutputs, onOutputSelect, valueMode, size)) }) return onOutputSelect } @@ -263,6 +254,35 @@ describe('OutputSelect nested workflow menu', () => { expect(onOutputSelect).toHaveBeenCalledWith(['child-workflow.agent_answer']) }) + it('keeps every selected output at the top and deselects nested outputs from there', () => { + const onOutputSelect = renderOutputSelect(['child-workflow.agent_answer']) + + const sections = [...document.querySelectorAll('[data-section]')] + expect(sections[0]?.textContent).toBe('Selected') + expect(document.body.textContent).toContain('Research / Writer / answer') + + clickOption('Research / Writer / answer') + expect(onOutputSelect).toHaveBeenCalledWith([]) + }) + + it('preserves existing selections when choosing a nested output', () => { + const onOutputSelect = renderOutputSelect(['summary_content']) + + clickOption('Outputs') + clickOption('answer') + + expect(onOutputSelect).toHaveBeenCalledWith(['summary_content', 'child-workflow.agent_answer']) + }) + + it('selects outputs from the medium chat deployment picker', () => { + const onOutputSelect = renderOutputSelect([], vi.fn(), 'id', 'md') + + expect(document.querySelector('[data-chip-combobox]')).not.toBeNull() + clickOption('content') + + expect(onOutputSelect).toHaveBeenCalledWith(['summary_content']) + }) + it('emits public dot selectors for trigger authoring', () => { const onOutputSelect = renderOutputSelect([], vi.fn(), 'public') diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx index 0f6bbe63d09..d6bc5d2fef3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx @@ -250,9 +250,17 @@ function OutputSelectMenu({ const [menuPath, setMenuPath] = useState([]) const activeMenuNode = resolveOutputMenuNode(outputMenu, menuPath) - const validOutputCount = selectedOutputs.filter((val) => - workflowOutputs.some((output) => output.id === val || output.label === val) - ).length + const selectedOutputOptions = selectedOutputs + .map((selectedValue) => + workflowOutputs.find( + (output) => + output.id === selectedValue || + output.label === selectedValue || + getOutputValue(output, valueMode) === selectedValue + ) + ) + .filter((output): output is WorkflowOutputOption => output !== undefined) + const validOutputCount = selectedOutputOptions.length let selectedDisplayText = placeholder if (validOutputCount === 1) { selectedDisplayText = '1 output' @@ -260,13 +268,26 @@ function OutputSelectMenu({ selectedDisplayText = `${validOutputCount} outputs` } - const normalizedSelectedValues = selectedOutputs - .map((val) => { - const output = workflowOutputs.find((item) => item.id === val || item.label === val) - if (!output) return null - return getOutputValue(output, valueMode) - }) - .filter((value): value is string => value !== null) + const normalizedSelectedValues = selectedOutputOptions.map((output) => + getOutputValue(output, valueMode) + ) + const selectedValueSet = new Set(normalizedSelectedValues) + + const outputOption = (output: WorkflowOutputOption, label = output.path): ComboboxOption => { + const value = getOutputValue(output, valueMode) + return { + label, + value, + onSelect: () => { + onOutputSelect( + selectedValueSet.has(value) + ? normalizedSelectedValues.filter((selectedValue) => selectedValue !== value) + : [...normalizedSelectedValues, value] + ) + }, + keepOpen: true, + } + } const folderOption = (node: WorkflowOutputMenuNode): ComboboxOption => ({ label: 'Outputs', @@ -288,15 +309,14 @@ function OutputSelectMenu({ ), items: [ - ...node.outputs.map((output) => ({ - label: output.path, - value: getOutputValue(output, valueMode), - })), + ...node.outputs + .filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) + .map((output) => outputOption(output)), ...(node.children.length > 0 ? [folderOption(node)] : []), ], }) - const comboboxGroups: ComboboxOptionGroup[] = activeMenuNode + const menuGroups: ComboboxOptionGroup[] = activeMenuNode ? [ { section: activeMenuNode.blockName, @@ -313,6 +333,18 @@ function OutputSelectMenu({ ...activeMenuNode.children.map(outputGroup), ] : outputMenu.map(outputGroup) + const selectedGroup: ComboboxOptionGroup[] = + selectedOutputOptions.length > 0 + ? [ + { + section: 'Selected', + items: selectedOutputOptions.map((output) => + outputOption(output, `${output.groupLabel} / ${output.path}`) + ), + }, + ] + : [] + const comboboxGroups = [...selectedGroup, ...menuGroups] const Trigger = size === 'md' ? ChipCombobox : Combobox return ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 496958e1d22..ba1f3c868c9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -1252,29 +1252,6 @@ export const ToolInput = memo(function ToolInput({ ) : undefined, }) - if (supportsAdvancedMcpServer) { - actionItems.push({ - label: 'MCP Server (Advanced)', - value: 'action-mcp-server-advanced', - icon: Server, - onSelect: () => { - setStoreValue([ - ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), - { - type: MCP_SERVER_ADVANCED_TOOL_TYPE, - params: { serverId: '' }, - isExpanded: true, - usageControl: 'auto', - }, - ]) - setOpen(false) - }, - disabled: isPreview || disabled || mcpUnsupported, - suffixElement: mcpUnsupported ? ( - - ) : undefined, - }) - } } if (actionItems.length > 0) { groups.push({ items: actionItems }) @@ -1412,6 +1389,35 @@ export const ToolInput = memo(function ToolInput({ }) } + if (!permissionConfig.disableMcpTools && supportsAdvancedMcpServer) { + groups.push({ + section: 'Advanced', + items: [ + { + label: 'MCP Server (Advanced)', + value: 'action-mcp-server-advanced', + icon: Server, + onSelect: () => { + setStoreValue([ + ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), + { + type: MCP_SERVER_ADVANCED_TOOL_TYPE, + params: { serverId: '' }, + isExpanded: true, + usageControl: 'auto', + }, + ]) + setOpen(false) + }, + disabled: isPreview || disabled || mcpUnsupported, + suffixElement: mcpUnsupported ? ( + + ) : undefined, + }, + ], + }) + } + return groups }, [ open, From f3c4d678e6e6748be116603dcd3dd1d6193be9ac Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 2 Sep 2026 00:56:33 -0700 Subject: [PATCH 2/5] fix(pickers): restore modal interactions and surface workflows --- .../output-select/output-select.dom.test.tsx | 118 ++++++++++++++++++ .../components/tool-input/tool-input.tsx | 67 +++++----- .../emcn/src/components/popover/popover.tsx | 2 +- 3 files changed, 152 insertions(+), 35 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx new file mode 100644 index 00000000000..17c844252ab --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx @@ -0,0 +1,118 @@ +/** + * @vitest-environment jsdom + */ +import { act, useState } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' + +vi.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })) + +vi.mock('@/blocks/block-tile', () => ({ + BlockTile: ({ blockType }: { blockType: string }) => , +})) + +vi.mock('@/hooks/queries/workflows', () => ({ useWorkflowStates: () => new Map() })) + +vi.mock('@/stores/workflow-diff/store', () => ({ + useWorkflowDiffStore: (selector: (state: object) => unknown) => + selector({ + isShowingDiff: false, + isDiffReady: false, + hasActiveDiff: false, + baselineWorkflow: null, + }), +})) + +vi.mock('@/stores/workflows/subblock/store', () => ({ + useSubBlockStore: (selector: (state: object) => unknown) => + selector({ workflowValues: { root: {} } }), +})) + +vi.mock('@/stores/workflows/workflow/store', () => ({ + useWorkflowStore: (selector: (state: object) => unknown) => selector({ blocks: {}, edges: [] }), +})) + +vi.mock('@/lib/workflows/streaming/nested-output-options', () => { + const rootOutput = { + id: 'summary_content', + label: 'Summarizer.content', + blockId: 'summary', + blockName: 'Summarizer', + blockType: 'agent', + groupKey: 'summary', + groupLabel: 'Summarizer', + path: 'content', + menuPath: [], + } + + return { + collectReferencedWorkflowIds: () => [], + buildWorkflowOutputOptions: () => [rootOutput], + buildWorkflowOutputMenu: () => [ + { + blockId: 'summary', + blockName: 'Summarizer', + blockType: 'agent', + outputs: [rootOutput], + children: [], + }, + ], + } +}) + +import { OutputSelect } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select' + +let root: Root | null = null +let container: HTMLDivElement | null = null + +function renderPicker() { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + + function Picker() { + const [selectedOutputs, setSelectedOutputs] = useState([]) + return ( + + ) + } + + act(() => root?.render()) +} + +afterEach(() => { + if (root) act(() => root?.unmount()) + container?.remove() + root = null + container = null +}) + +describe('OutputSelect DOM interaction', () => { + it('selects an output through the real medium combobox', () => { + renderPicker() + + const trigger = document.querySelector('[role="combobox"]') + if (!trigger) throw new Error('Output picker trigger did not render') + act(() => trigger.click()) + + const option = [...document.querySelectorAll('[role="option"]')].find( + (candidate) => candidate.textContent === 'content' + ) + if (!option) throw new Error('Output option did not render') + const floatingSurface = option.closest('[data-native-surface-overlay]') + if (!floatingSurface) throw new Error('Output picker floating surface did not render') + + expect(floatingSurface.className).toContain('pointer-events-auto') + + act(() => option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) + + expect(trigger.textContent).toContain('1 output') + expect(document.body.textContent).toContain('Selected') + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index ba1f3c868c9..56ffb2e0689 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -1257,6 +1257,39 @@ export const ToolInput = memo(function ToolInput({ groups.push({ items: actionItems }) } + if (availableWorkflows.length > 0) { + groups.push({ + section: 'Workflows', + items: availableWorkflows.map((workflow) => { + const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id) + return { + label: workflow.name, + value: `workflow-${workflow.id}`, + iconElement: createToolIcon('#6366F1', WorkflowIcon), + onSelect: () => { + if (alreadySelected) return + const newTool: StoredTool = { + type: 'workflow_input', + title: 'Workflow', + toolId: 'workflow_executor', + params: { + workflowId: workflow.id, + }, + isExpanded: true, + usageControl: 'auto', + } + setStoreValue([ + ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), + newTool, + ]) + setOpen(false) + }, + disabled: isPreview || disabled || alreadySelected, + } + }), + }) + } + if (!permissionConfig.disableCustomTools && !customUnsupported && customTools.length > 0) { groups.push({ section: 'Custom Tools', @@ -1355,40 +1388,6 @@ export const ToolInput = memo(function ToolInput({ }) } - // Workflows section - shows available workflows that can be executed as tools - if (availableWorkflows.length > 0) { - groups.push({ - section: 'Workflows', - items: availableWorkflows.map((workflow) => { - const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id) - return { - label: workflow.name, - value: `workflow-${workflow.id}`, - iconElement: createToolIcon('#6366F1', WorkflowIcon), - onSelect: () => { - if (alreadySelected) return - const newTool: StoredTool = { - type: 'workflow_input', - title: 'Workflow', - toolId: 'workflow_executor', - params: { - workflowId: workflow.id, - }, - isExpanded: true, - usageControl: 'auto', - } - setStoreValue([ - ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), - newTool, - ]) - setOpen(false) - }, - disabled: isPreview || disabled || alreadySelected, - } - }), - }) - } - if (!permissionConfig.disableMcpTools && supportsAdvancedMcpServer) { groups.push({ section: 'Advanced', diff --git a/packages/emcn/src/components/popover/popover.tsx b/packages/emcn/src/components/popover/popover.tsx index 8efe883c9d4..4afa7c41a48 100644 --- a/packages/emcn/src/components/popover/popover.tsx +++ b/packages/emcn/src/components/popover/popover.tsx @@ -601,7 +601,7 @@ const PopoverContent = React.forwardRef< {...restProps} data-native-surface-overlay='' className={cn( - 'z-[var(--z-popover)] flex flex-col outline-none', + 'pointer-events-auto z-[var(--z-popover)] flex flex-col outline-none', showArrow ? 'overflow-visible' : 'overflow-auto', STYLES.colorScheme[colorScheme].content, STYLES.content, From 06521ce434dd49e8f757c8cd92610c2a083ec235 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 2 Sep 2026 01:21:42 -0700 Subject: [PATCH 3/5] fix(chat-deploy): surface child workflow outputs --- .../output-select/output-select.dom.test.tsx | 125 ++++++++++++------ .../output-select/output-select.test.tsx | 53 +++++++- .../output-select/output-select.tsx | 31 ++--- .../components/tool-input/tool-input.tsx | 66 ++++----- 4 files changed, 177 insertions(+), 98 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx index 17c844252ab..ee3a6050a32 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx @@ -5,13 +5,80 @@ import { act, useState } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' +const { workflowFixture } = vi.hoisted(() => ({ + workflowFixture: { + rootBlocks: { + invoke: { + id: 'invoke', + type: 'workflow_input', + name: 'invokeChild', + position: { x: 0, y: 0 }, + subBlocks: { + workflowId: { + id: 'workflowId', + type: 'workflow-selector', + value: 'child-workflow', + }, + }, + outputs: {}, + enabled: true, + data: { canonicalModes: { workflowId: 'basic' } }, + }, + }, + childState: { + blocks: { + agent: { + id: 'agent', + type: 'agent', + name: 'researchAgent', + position: { x: 0, y: 0 }, + subBlocks: {}, + outputs: {}, + enabled: true, + }, + }, + edges: [], + }, + }, +})) + vi.mock('zustand/react/shallow', () => ({ useShallow: (selector: unknown) => selector })) vi.mock('@/blocks/block-tile', () => ({ BlockTile: ({ blockType }: { blockType: string }) => , })) -vi.mock('@/hooks/queries/workflows', () => ({ useWorkflowStates: () => new Map() })) +vi.mock('@/lib/workflows/blocks/flatten-outputs', () => ({ + flattenWorkflowOutputs: (blocks: Iterable<{ id: string; name: string; type: string }>) => + [...blocks].flatMap((block) => { + if (block.type === 'workflow_input') { + return ['success', 'childWorkflowName', 'childWorkflowId', 'result', 'error'].map( + (path) => ({ + blockId: block.id, + blockName: block.name, + blockType: block.type, + path, + }) + ) + } + if (block.type === 'agent') { + return [ + { blockId: block.id, blockName: block.name, blockType: block.type, path: 'content' }, + ] + } + return [] + }), +})) + +vi.mock('@/hooks/queries/workflows', () => ({ + useWorkflowStates: (workflowIds: string[]) => + new Map( + workflowIds.map((workflowId) => [ + workflowId, + workflowId === 'child-workflow' ? workflowFixture.childState : null, + ]) + ), +})) vi.mock('@/stores/workflow-diff/store', () => ({ useWorkflowDiffStore: (selector: (state: object) => unknown) => @@ -25,41 +92,14 @@ vi.mock('@/stores/workflow-diff/store', () => ({ vi.mock('@/stores/workflows/subblock/store', () => ({ useSubBlockStore: (selector: (state: object) => unknown) => - selector({ workflowValues: { root: {} } }), + selector({ workflowValues: { root: { invoke: { workflowId: 'child-workflow' } } } }), })) vi.mock('@/stores/workflows/workflow/store', () => ({ - useWorkflowStore: (selector: (state: object) => unknown) => selector({ blocks: {}, edges: [] }), + useWorkflowStore: (selector: (state: object) => unknown) => + selector({ blocks: workflowFixture.rootBlocks, edges: [] }), })) -vi.mock('@/lib/workflows/streaming/nested-output-options', () => { - const rootOutput = { - id: 'summary_content', - label: 'Summarizer.content', - blockId: 'summary', - blockName: 'Summarizer', - blockType: 'agent', - groupKey: 'summary', - groupLabel: 'Summarizer', - path: 'content', - menuPath: [], - } - - return { - collectReferencedWorkflowIds: () => [], - buildWorkflowOutputOptions: () => [rootOutput], - buildWorkflowOutputMenu: () => [ - { - blockId: 'summary', - blockName: 'Summarizer', - blockType: 'agent', - outputs: [rootOutput], - children: [], - }, - ], - } -}) - import { OutputSelect } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select' let root: Root | null = null @@ -94,25 +134,36 @@ afterEach(() => { }) describe('OutputSelect DOM interaction', () => { - it('selects an output through the real medium combobox', () => { + it('drills into and selects a child workflow output through the real medium combobox', () => { renderPicker() const trigger = document.querySelector('[role="combobox"]') if (!trigger) throw new Error('Output picker trigger did not render') act(() => trigger.click()) - const option = [...document.querySelectorAll('[role="option"]')].find( - (candidate) => candidate.textContent === 'content' + expect(document.body.textContent).toContain('invokeChild') + const rootOptions = [...document.querySelectorAll('[role="option"]')] + const folderOption = rootOptions.find((candidate) => candidate.textContent === 'Outputs') + if (!folderOption) throw new Error('Child workflow folder did not render') + expect(rootOptions.indexOf(folderOption)).toBeLessThan( + rootOptions.findIndex((candidate) => candidate.textContent === 'result') ) - if (!option) throw new Error('Output option did not render') - const floatingSurface = option.closest('[data-native-surface-overlay]') + const floatingSurface = folderOption.closest('[data-native-surface-overlay]') if (!floatingSurface) throw new Error('Output picker floating surface did not render') expect(floatingSurface.className).toContain('pointer-events-auto') + act(() => folderOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) + + expect(document.body.textContent).toContain('researchAgent') + const outputOption = [...document.querySelectorAll('[role="option"]')].find( + (candidate) => candidate.textContent === 'content' + ) + if (!outputOption) throw new Error('Child workflow output did not render') - act(() => option.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) + act(() => outputOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) expect(trigger.textContent).toContain('1 output') expect(document.body.textContent).toContain('Selected') + expect(document.body.textContent).toContain('invokeChild / researchAgent / content') }) }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx index 26781e9ed1b..6b1a91e8767 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx @@ -6,7 +6,14 @@ import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { outputMenuState } = vi.hoisted(() => ({ - outputMenuState: { includeNestedWorkflow: true }, + outputMenuState: { + includeNestedWorkflow: true, + workflowBlocks: {} as Record, + workflowValues: { root: {} } as Record>>, + capturedRootState: null as { + blocks: Record }> + } | null, + }, })) vi.mock('@sim/emcn', () => { @@ -93,11 +100,12 @@ vi.mock('@/stores/workflow-diff/store', () => ({ vi.mock('@/stores/workflows/subblock/store', () => ({ useSubBlockStore: (selector: (state: object) => unknown) => - selector({ workflowValues: { root: {} } }), + selector({ workflowValues: outputMenuState.workflowValues }), })) vi.mock('@/stores/workflows/workflow/store', () => ({ - useWorkflowStore: (selector: (state: object) => unknown) => selector({ blocks: {}, edges: [] }), + useWorkflowStore: (selector: (state: object) => unknown) => + selector({ blocks: outputMenuState.workflowBlocks, edges: [] }), })) vi.mock('@/lib/workflows/streaming/nested-output-options', () => { @@ -127,8 +135,14 @@ vi.mock('@/lib/workflows/streaming/nested-output-options', () => { return { collectReferencedWorkflowIds: () => [], - buildWorkflowOutputOptions: () => - outputMenuState.includeNestedWorkflow ? [rootOutput, nestedOutput] : [rootOutput], + buildWorkflowOutputOptions: (input: { + rootState: { + blocks: Record }> + } + }) => { + outputMenuState.capturedRootState = input.rootState + return outputMenuState.includeNestedWorkflow ? [rootOutput, nestedOutput] : [rootOutput] + }, buildWorkflowOutputMenu: () => { const rootNode = { blockId: 'summary', @@ -168,6 +182,9 @@ let container: HTMLDivElement | null = null beforeEach(() => { outputMenuState.includeNestedWorkflow = true + outputMenuState.workflowBlocks = {} + outputMenuState.workflowValues = { root: {} } + outputMenuState.capturedRootState = null }) function outputSelect( @@ -246,6 +263,32 @@ describe('OutputSelect nested workflow menu', () => { expect(document.body.textContent).not.toContain('Summarizer') }) + it('preserves a persisted workflow target when the editor value map is sparse', () => { + outputMenuState.workflowBlocks = { + invoke: { + id: 'invoke', + type: 'workflow_input', + name: 'Research', + position: { x: 0, y: 0 }, + subBlocks: { + workflowId: { + id: 'workflowId', + type: 'workflow-selector', + value: 'child-workflow', + }, + }, + outputs: {}, + enabled: true, + }, + } + + renderOutputSelect([]) + + expect(outputMenuState.capturedRootState?.blocks.invoke.subBlocks.workflowId.value).toBe( + 'child-workflow' + ) + }) + it('keeps workflow-scoped values when toggling nested outputs', () => { const onOutputSelect = renderOutputSelect([]) clickOption('Outputs') diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx index d6bc5d2fef3..950968431f5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx @@ -9,6 +9,7 @@ import { cn, } from '@sim/emcn' import { ArrowLeft, ChevronRight } from '@sim/emcn/icons' +import { mergeSubblockStateWithValues } from '@sim/workflow-persistence/subblocks' import type { BlockState, WorkflowState } from '@sim/workflow-types/workflow' import { useShallow } from 'zustand/react/shallow' import { @@ -153,30 +154,14 @@ function OutputSelectContent({ if (!workflowId || !workflowBlocks || typeof workflowBlocks !== 'object') { return { blocks: {}, edges: [] } } - const blockArray = Object.values(workflowBlocks) as BlockState[] - - const mergedBlocks = blockArray.map((block): BlockState => { - const rawSubBlockValues = - shouldUseBaseline && baselineWorkflow - ? baselineWorkflow.blocks?.[block.id]?.subBlocks - : subBlockValues?.[block.id] - const subBlocks: Record = {} - if (rawSubBlockValues && typeof rawSubBlockValues === 'object') { - for (const [key, val] of Object.entries(rawSubBlockValues)) { - subBlocks[key] = - val && typeof val === 'object' && 'value' in (val as object) - ? (val as { value: unknown }) - : { value: val } - } - } - return { - ...block, - subBlocks, - } as BlockState - }) + const blockMap = workflowBlocks as Record + const mergedBlocks = mergeSubblockStateWithValues( + blockMap, + shouldUseBaseline ? {} : (subBlockValues ?? {}) + ) return { - blocks: Object.fromEntries(mergedBlocks.map((block) => [block.id, block])), + blocks: mergedBlocks, edges: workflowEdges, } }, [ @@ -309,10 +294,10 @@ function OutputSelectMenu({ ), items: [ + ...(node.children.length > 0 ? [folderOption(node)] : []), ...node.outputs .filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) .map((output) => outputOption(output)), - ...(node.children.length > 0 ? [folderOption(node)] : []), ], }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 56ffb2e0689..0c274a92a18 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -1257,39 +1257,6 @@ export const ToolInput = memo(function ToolInput({ groups.push({ items: actionItems }) } - if (availableWorkflows.length > 0) { - groups.push({ - section: 'Workflows', - items: availableWorkflows.map((workflow) => { - const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id) - return { - label: workflow.name, - value: `workflow-${workflow.id}`, - iconElement: createToolIcon('#6366F1', WorkflowIcon), - onSelect: () => { - if (alreadySelected) return - const newTool: StoredTool = { - type: 'workflow_input', - title: 'Workflow', - toolId: 'workflow_executor', - params: { - workflowId: workflow.id, - }, - isExpanded: true, - usageControl: 'auto', - } - setStoreValue([ - ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), - newTool, - ]) - setOpen(false) - }, - disabled: isPreview || disabled || alreadySelected, - } - }), - }) - } - if (!permissionConfig.disableCustomTools && !customUnsupported && customTools.length > 0) { groups.push({ section: 'Custom Tools', @@ -1388,6 +1355,39 @@ export const ToolInput = memo(function ToolInput({ }) } + if (availableWorkflows.length > 0) { + groups.push({ + section: 'Workflows', + items: availableWorkflows.map((workflow) => { + const alreadySelected = isWorkflowAlreadySelected(selectedTools, workflow.id) + return { + label: workflow.name, + value: `workflow-${workflow.id}`, + iconElement: createToolIcon('#6366F1', WorkflowIcon), + onSelect: () => { + if (alreadySelected) return + const newTool: StoredTool = { + type: 'workflow_input', + title: 'Workflow', + toolId: 'workflow_executor', + params: { + workflowId: workflow.id, + }, + isExpanded: true, + usageControl: 'auto', + } + setStoreValue([ + ...selectedTools.map((tool) => ({ ...tool, isExpanded: false })), + newTool, + ]) + setOpen(false) + }, + disabled: isPreview || disabled || alreadySelected, + } + }), + }) + } + if (!permissionConfig.disableMcpTools && supportsAdvancedMcpServer) { groups.push({ section: 'Advanced', From 4d98153359b02b89c13ae394f25db849e9d08344 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 2 Sep 2026 01:29:35 -0700 Subject: [PATCH 4/5] fix(chat-deploy): distinguish subworkflow output navigation --- .../output-select/output-select.dom.test.tsx | 36 ++++++++-- .../output-select/output-select.test.tsx | 17 ++--- .../output-select/output-select.tsx | 66 +++++++++++-------- 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx index ee3a6050a32..0adc6fac820 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx @@ -134,6 +134,26 @@ afterEach(() => { }) describe('OutputSelect DOM interaction', () => { + it('selects a workflow block field without entering its child workflow', () => { + renderPicker() + + const trigger = document.querySelector('[role="combobox"]') + if (!trigger) throw new Error('Output picker trigger did not render') + act(() => trigger.click()) + + const resultOption = [...document.querySelectorAll('[role="option"]')].find( + (candidate) => candidate.textContent === 'result' + ) + if (!resultOption) throw new Error('Workflow block result output did not render') + + act(() => resultOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) + + expect(trigger.textContent).toContain('1 output') + expect(document.body.textContent).toContain('Selected') + expect(document.body.textContent).toContain('invokeChild.result') + expect(document.body.textContent).not.toContain('researchAgent') + }) + it('drills into and selects a child workflow output through the real medium combobox', () => { renderPicker() @@ -143,16 +163,20 @@ describe('OutputSelect DOM interaction', () => { expect(document.body.textContent).toContain('invokeChild') const rootOptions = [...document.querySelectorAll('[role="option"]')] - const folderOption = rootOptions.find((candidate) => candidate.textContent === 'Outputs') - if (!folderOption) throw new Error('Child workflow folder did not render') - expect(rootOptions.indexOf(folderOption)).toBeLessThan( + expect(document.body.textContent).toContain('Subworkflows') + const subworkflowOption = rootOptions.find( + (candidate) => candidate.textContent === 'invokeChild' + ) + if (!subworkflowOption) throw new Error('Child workflow navigation did not render') + expect(rootOptions.indexOf(subworkflowOption)).toBeLessThan( rootOptions.findIndex((candidate) => candidate.textContent === 'result') ) - const floatingSurface = folderOption.closest('[data-native-surface-overlay]') + + const floatingSurface = subworkflowOption.closest('[data-native-surface-overlay]') if (!floatingSurface) throw new Error('Output picker floating surface did not render') expect(floatingSurface.className).toContain('pointer-events-auto') - act(() => folderOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) + act(() => subworkflowOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) expect(document.body.textContent).toContain('researchAgent') const outputOption = [...document.querySelectorAll('[role="option"]')].find( @@ -164,6 +188,6 @@ describe('OutputSelect DOM interaction', () => { expect(trigger.textContent).toContain('1 output') expect(document.body.textContent).toContain('Selected') - expect(document.body.textContent).toContain('invokeChild / researchAgent / content') + expect(document.body.textContent).toContain('invokeChild / researchAgent.content') }) }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx index 6b1a91e8767..61a1d5f9282 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx @@ -256,7 +256,8 @@ describe('OutputSelect nested workflow menu', () => { expect(document.body.textContent).toContain('Research') expect(document.body.textContent).not.toContain('Writer') - clickOption('Outputs') + expect([...document.querySelectorAll('[data-section]')][0]?.textContent).toBe('Subworkflows') + clickOption('Research') expect(document.body.textContent).toContain('Back') expect(document.body.textContent).toContain('Writer') expect(document.body.textContent).toContain('answer') @@ -291,7 +292,7 @@ describe('OutputSelect nested workflow menu', () => { it('keeps workflow-scoped values when toggling nested outputs', () => { const onOutputSelect = renderOutputSelect([]) - clickOption('Outputs') + clickOption('Research') clickOption('answer') expect(onOutputSelect).toHaveBeenCalledWith(['child-workflow.agent_answer']) @@ -302,16 +303,16 @@ describe('OutputSelect nested workflow menu', () => { const sections = [...document.querySelectorAll('[data-section]')] expect(sections[0]?.textContent).toBe('Selected') - expect(document.body.textContent).toContain('Research / Writer / answer') + expect(document.body.textContent).toContain('Research / Writer.answer') - clickOption('Research / Writer / answer') + clickOption('Research / Writer.answer') expect(onOutputSelect).toHaveBeenCalledWith([]) }) it('preserves existing selections when choosing a nested output', () => { const onOutputSelect = renderOutputSelect(['summary_content']) - clickOption('Outputs') + clickOption('Research') clickOption('answer') expect(onOutputSelect).toHaveBeenCalledWith(['summary_content', 'child-workflow.agent_answer']) @@ -332,14 +333,14 @@ describe('OutputSelect nested workflow menu', () => { clickOption('content') expect(onOutputSelect).toHaveBeenCalledWith(['summarizer.content']) - clickOption('Outputs') + clickOption('Research') clickOption('answer') expect(onOutputSelect).toHaveBeenCalledWith(['child-workflow.writer.answer']) }) it('returns to the root menu when the owning workflow changes', () => { const onOutputSelect = renderOutputSelect([]) - clickOption('Outputs') + clickOption('Research') rerenderOutputSelect('replacement', [], onOutputSelect) @@ -349,7 +350,7 @@ describe('OutputSelect nested workflow menu', () => { it('returns to the root menu when a workflow edit invalidates the active path', () => { const onOutputSelect = renderOutputSelect([]) - clickOption('Outputs') + clickOption('Research') outputMenuState.includeNestedWorkflow = false rerenderOutputSelect('root', [], onOutputSelect) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx index 950968431f5..34626e7f4fa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx @@ -274,9 +274,9 @@ function OutputSelectMenu({ } } - const folderOption = (node: WorkflowOutputMenuNode): ComboboxOption => ({ - label: 'Outputs', - value: `folder:${node.blockId}`, + const subworkflowOption = (node: WorkflowOutputMenuNode): ComboboxOption => ({ + label: node.blockName, + value: `subworkflow:${node.blockId}`, suffixElement: , onSelect: () => setMenuPath((currentPath) => [...currentPath, node.blockId]), keepOpen: true, @@ -293,38 +293,50 @@ function OutputSelectMenu({ {node.blockName} ), - items: [ - ...(node.children.length > 0 ? [folderOption(node)] : []), - ...node.outputs - .filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) - .map((output) => outputOption(output)), - ], + items: node.outputs + .filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) + .map((output) => outputOption(output)), }) - const menuGroups: ComboboxOptionGroup[] = activeMenuNode - ? [ - { - section: activeMenuNode.blockName, - items: [ - { - label: 'Back', - value: `back:${activeMenuNode.blockId}`, - iconElement: , - onSelect: () => setMenuPath((currentPath) => currentPath.slice(0, -1)), - keepOpen: true, - }, - ], - }, - ...activeMenuNode.children.map(outputGroup), - ] - : outputMenu.map(outputGroup) + const menuNodes = activeMenuNode ? activeMenuNode.children : outputMenu + const subworkflowNodes = menuNodes.filter((node) => node.children.length > 0) + const availableOutputNodes = menuNodes.filter((node) => + node.outputs.some((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) + ) + const menuGroups: ComboboxOptionGroup[] = [ + ...(activeMenuNode + ? [ + { + section: activeMenuNode.blockName, + items: [ + { + label: 'Back', + value: `back:${activeMenuNode.blockId}`, + iconElement: , + onSelect: () => setMenuPath((currentPath) => currentPath.slice(0, -1)), + keepOpen: true, + }, + ], + }, + ] + : []), + ...(subworkflowNodes.length > 0 + ? [ + { + section: 'Subworkflows', + items: subworkflowNodes.map(subworkflowOption), + }, + ] + : []), + ...availableOutputNodes.map(outputGroup), + ] const selectedGroup: ComboboxOptionGroup[] = selectedOutputOptions.length > 0 ? [ { section: 'Selected', items: selectedOutputOptions.map((output) => - outputOption(output, `${output.groupLabel} / ${output.path}`) + outputOption(output, `${output.groupLabel}.${output.path}`) ), }, ] From fe7d06a06b0aa7fafcf23b3609fd0788ccccb4f3 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 2 Sep 2026 01:31:57 -0700 Subject: [PATCH 5/5] fix(chat-deploy): keep selected outputs in place --- .../output-select/output-select.dom.test.tsx | 14 +++++++---- .../output-select/output-select.test.tsx | 11 +++++---- .../output-select/output-select.tsx | 24 ++++--------------- 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx index 0adc6fac820..3c5a2927162 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.dom.test.tsx @@ -149,8 +149,11 @@ describe('OutputSelect DOM interaction', () => { act(() => resultOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) expect(trigger.textContent).toContain('1 output') - expect(document.body.textContent).toContain('Selected') - expect(document.body.textContent).toContain('invokeChild.result') + expect(document.body.textContent).not.toContain('Selected') + const selectedResultOption = [ + ...document.querySelectorAll('[role="option"]'), + ].find((candidate) => candidate.textContent === 'result') + expect(selectedResultOption?.getAttribute('aria-selected')).toBe('true') expect(document.body.textContent).not.toContain('researchAgent') }) @@ -187,7 +190,10 @@ describe('OutputSelect DOM interaction', () => { act(() => outputOption.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))) expect(trigger.textContent).toContain('1 output') - expect(document.body.textContent).toContain('Selected') - expect(document.body.textContent).toContain('invokeChild / researchAgent.content') + expect(document.body.textContent).not.toContain('Selected') + const selectedOutputOption = [ + ...document.querySelectorAll('[role="option"]'), + ].find((candidate) => candidate.textContent === 'content') + expect(selectedOutputOption?.getAttribute('aria-selected')).toBe('true') }) }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx index 61a1d5f9282..a03e0964d04 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.test.tsx @@ -298,14 +298,15 @@ describe('OutputSelect nested workflow menu', () => { expect(onOutputSelect).toHaveBeenCalledWith(['child-workflow.agent_answer']) }) - it('keeps every selected output at the top and deselects nested outputs from there', () => { + it('keeps a nested selection in its subworkflow and deselects it there', () => { const onOutputSelect = renderOutputSelect(['child-workflow.agent_answer']) - const sections = [...document.querySelectorAll('[data-section]')] - expect(sections[0]?.textContent).toBe('Selected') - expect(document.body.textContent).toContain('Research / Writer.answer') + expect(document.body.textContent).not.toContain('Selected') + expect(document.body.textContent).not.toContain('Writer') - clickOption('Research / Writer.answer') + clickOption('Research') + expect(document.body.textContent).toContain('Writer') + clickOption('answer') expect(onOutputSelect).toHaveBeenCalledWith([]) }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx index 34626e7f4fa..ab54ceee5b1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/components/output-select/output-select.tsx @@ -293,16 +293,12 @@ function OutputSelectMenu({ {node.blockName} ), - items: node.outputs - .filter((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) - .map((output) => outputOption(output)), + items: node.outputs.map((output) => outputOption(output)), }) const menuNodes = activeMenuNode ? activeMenuNode.children : outputMenu const subworkflowNodes = menuNodes.filter((node) => node.children.length > 0) - const availableOutputNodes = menuNodes.filter((node) => - node.outputs.some((output) => !selectedValueSet.has(getOutputValue(output, valueMode))) - ) + const outputNodes = menuNodes.filter((node) => node.outputs.length > 0) const menuGroups: ComboboxOptionGroup[] = [ ...(activeMenuNode ? [ @@ -328,27 +324,15 @@ function OutputSelectMenu({ }, ] : []), - ...availableOutputNodes.map(outputGroup), + ...outputNodes.map(outputGroup), ] - const selectedGroup: ComboboxOptionGroup[] = - selectedOutputOptions.length > 0 - ? [ - { - section: 'Selected', - items: selectedOutputOptions.map((output) => - outputOption(output, `${output.groupLabel}.${output.path}`) - ), - }, - ] - : [] - const comboboxGroups = [...selectedGroup, ...menuGroups] const Trigger = size === 'md' ? ChipCombobox : Combobox return (