From 5988d0e46f83f700feaaa09cd47aea9e61cc7293 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 21 Jan 2026 02:40:58 -0800 Subject: [PATCH] fix(ring): duplicate should clear original block (#2916) * fix(ring): duplicate should clear original block * rename correctly --- apps/sim/app/api/form/[identifier]/route.ts | 4 +-- .../components/action-bar/action-bar.tsx | 4 +-- .../components/block-menu/block-menu.tsx | 12 +++++--- .../deploy-modal/components/form/form.tsx | 4 +-- .../general/components/api-info-modal.tsx | 4 +-- .../deploy-modal/components/mcp/mcp.tsx | 4 +-- .../use-accessible-reference-prefixes.ts | 4 +-- .../[workspaceId]/w/[workflowId]/workflow.tsx | 28 +++++++++++++------ .../workflow/get-block-upstream-references.ts | 4 +-- apps/sim/lib/mcp/workflow-tool-schema.ts | 4 +-- apps/sim/lib/workflows/input-format.ts | 4 +-- .../triggers/input-definition-triggers.ts | 28 +++++++++++++++++++ .../workflows/triggers/start-block-types.ts | 21 -------------- .../lib/workflows/triggers/trigger-utils.ts | 4 +-- 14 files changed, 76 insertions(+), 53 deletions(-) create mode 100644 apps/sim/lib/workflows/triggers/input-definition-triggers.ts delete mode 100644 apps/sim/lib/workflows/triggers/start-block-types.ts diff --git a/apps/sim/app/api/form/[identifier]/route.ts b/apps/sim/app/api/form/[identifier]/route.ts index e75dd236c..a4ad31eef 100644 --- a/apps/sim/app/api/form/[identifier]/route.ts +++ b/apps/sim/app/api/form/[identifier]/route.ts @@ -11,7 +11,7 @@ import { preprocessExecution } from '@/lib/execution/preprocessing' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { normalizeInputFormatValue } from '@/lib/workflows/input-format' import { createStreamingResponse } from '@/lib/workflows/streaming/streaming' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { setFormAuthCookie, validateFormAuth } from '@/app/api/form/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -36,7 +36,7 @@ async function getWorkflowInputSchema(workflowId: string): Promise { .from(workflowBlocks) .where(eq(workflowBlocks.workflowId, workflowId)) - const startBlock = blocks.find((block) => isValidStartBlockType(block.type)) + const startBlock = blocks.find((block) => isInputDefinitionTrigger(block.type)) if (!startBlock) { return [] diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx index 59b3e5b09..e2fa63da7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar.tsx @@ -2,7 +2,7 @@ import { memo, useCallback } from 'react' import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react' import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -90,7 +90,7 @@ export const ActionBar = memo( const userPermissions = useUserPermissionsContext() - const isStartBlock = isValidStartBlockType(blockType) + const isStartBlock = isInputDefinitionTrigger(blockType) const isResponseBlock = blockType === 'response' const isNoteBlock = blockType === 'note' const isSubflowBlock = blockType === 'loop' || blockType === 'parallel' diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx index 5d6af16f2..c3a4d2ea8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx @@ -8,7 +8,7 @@ import { PopoverDivider, PopoverItem, } from '@/components/emcn' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { TriggerUtils } from '@/lib/workflows/triggers/triggers' /** * Block information for context menu actions @@ -74,12 +74,16 @@ export function BlockMenu({ const allEnabled = selectedBlocks.every((b) => b.enabled) const allDisabled = selectedBlocks.every((b) => !b.enabled) - const hasStarterBlock = selectedBlocks.some((b) => isValidStartBlockType(b.type)) + const hasSingletonBlock = selectedBlocks.some( + (b) => + TriggerUtils.requiresSingleInstance(b.type) || TriggerUtils.isSingleInstanceBlockType(b.type) + ) + const hasTriggerBlock = selectedBlocks.some((b) => TriggerUtils.isTriggerBlock(b)) const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note') const isSubflow = isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel') - const canRemoveFromSubflow = showRemoveFromSubflow && !hasStarterBlock + const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock const getToggleEnabledLabel = () => { if (allEnabled) return 'Disable' @@ -127,7 +131,7 @@ export function BlockMenu({ Paste ⌘V - {!hasStarterBlock && ( + {!hasSingletonBlock && ( { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/form/form.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/form/form.tsx index 3af80885b..d6f6f3e90 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/form/form.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/form/form.tsx @@ -17,7 +17,7 @@ import { Skeleton } from '@/components/ui' import { isDev } from '@/lib/core/config/feature-flags' import { cn } from '@/lib/core/utils/cn' import { getBaseUrl, getEmailDomain } from '@/lib/core/utils/urls' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { type FieldConfig, useCreateForm, @@ -147,7 +147,7 @@ export function FormDeploy({ useEffect(() => { const blocks = Object.values(useWorkflowStore.getState().blocks) - const startBlock = blocks.find((b) => isValidStartBlockType(b.type)) + const startBlock = blocks.find((b) => isInputDefinitionTrigger(b.type)) if (startBlock) { const inputFormat = useSubBlockStore.getState().getValue(startBlock.id, 'inputFormat') diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx index 8f0cb5163..002d094f6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/components/api-info-modal.tsx @@ -14,7 +14,7 @@ import { Textarea, } from '@/components/emcn' import { normalizeInputFormatValue } from '@/lib/workflows/input-format' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import type { InputFormatField } from '@/lib/workflows/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' @@ -52,7 +52,7 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro for (const [blockId, block] of Object.entries(blocks)) { if (!block || typeof block !== 'object') continue const blockType = (block as { type?: string }).type - if (blockType && isValidStartBlockType(blockType)) { + if (blockType && isInputDefinitionTrigger(blockType)) { return blockId } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx index 75c7bb326..c18727a5b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx @@ -15,7 +15,7 @@ import { import { Skeleton } from '@/components/ui' import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema' import { normalizeInputFormatValue } from '@/lib/workflows/input-format' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import type { InputFormatField } from '@/lib/workflows/types' import { useAddWorkflowMcpTool, @@ -107,7 +107,7 @@ export function McpDeploy({ for (const [blockId, block] of Object.entries(blocks)) { if (!block || typeof block !== 'object') continue const blockType = (block as { type?: string }).type - if (blockType && isValidStartBlockType(blockType)) { + if (blockType && isInputDefinitionTrigger(blockType)) { return blockId } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes.ts index a30d56502..f89971fab 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react' import { useShallow } from 'zustand/react/shallow' import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator' import { SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/sanitization/references' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { normalizeName } from '@/executor/constants' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { Loop, Parallel } from '@/stores/workflows/workflow/types' @@ -27,7 +27,7 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set(ancestorIds) accessibleIds.add(blockId) - const starterBlock = Object.values(blocks).find((block) => isValidStartBlockType(block.type)) + const starterBlock = Object.values(blocks).find((block) => isInputDefinitionTrigger(block.type)) if (starterBlock && ancestorIds.includes(starterBlock.id)) { accessibleIds.add(starterBlock.id) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 0072136a3..a1089e0a7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -180,6 +180,21 @@ function mapEdgesByNode(edges: Edge[], nodeIds: Set): Map 1 && currentBlockId) { + clearCurrentBlock() + } +} + /** Custom node types for ReactFlow. */ const nodeTypes: NodeTypes = { workflowBlock: WorkflowBlock, @@ -2075,7 +2090,10 @@ const WorkflowContent = React.memo(() => { ...node, selected: pendingSet.has(node.id), })) - setDisplayNodes(resolveParentChildSelectionConflicts(withSelection, blocks)) + const resolved = resolveParentChildSelectionConflicts(withSelection, blocks) + setDisplayNodes(resolved) + const selectedIds = resolved.filter((node) => node.selected).map((node) => node.id) + syncPanelWithSelection(selectedIds) return } @@ -2175,13 +2193,7 @@ const WorkflowContent = React.memo(() => { }) const selectedIds = selectedIdsRef.current as string[] | null if (selectedIds !== null) { - const { currentBlockId, clearCurrentBlock, setCurrentBlockId } = - usePanelEditorStore.getState() - if (selectedIds.length === 1 && selectedIds[0] !== currentBlockId) { - setCurrentBlockId(selectedIds[0]) - } else if (selectedIds.length === 0 && currentBlockId) { - clearCurrentBlock() - } + syncPanelWithSelection(selectedIds) } }, [blocks] diff --git a/apps/sim/lib/copilot/tools/client/workflow/get-block-upstream-references.ts b/apps/sim/lib/copilot/tools/client/workflow/get-block-upstream-references.ts index a0b03e923..f02c9958c 100644 --- a/apps/sim/lib/copilot/tools/client/workflow/get-block-upstream-references.ts +++ b/apps/sim/lib/copilot/tools/client/workflow/get-block-upstream-references.ts @@ -17,7 +17,7 @@ import { type GetBlockUpstreamReferencesResultType, } from '@/lib/copilot/tools/shared/schemas' import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { Loop, Parallel } from '@/stores/workflows/workflow/types' @@ -141,7 +141,7 @@ export class GetBlockUpstreamReferencesClientTool extends BaseClientTool { const accessibleIds = new Set(ancestorIds) accessibleIds.add(blockId) - const starterBlock = Object.values(blocks).find((b) => isValidStartBlockType(b.type)) + const starterBlock = Object.values(blocks).find((b) => isInputDefinitionTrigger(b.type)) if (starterBlock && ancestorIds.includes(starterBlock.id)) { accessibleIds.add(starterBlock.id) } diff --git a/apps/sim/lib/mcp/workflow-tool-schema.ts b/apps/sim/lib/mcp/workflow-tool-schema.ts index 3e7555c3f..7af927ff1 100644 --- a/apps/sim/lib/mcp/workflow-tool-schema.ts +++ b/apps/sim/lib/mcp/workflow-tool-schema.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { normalizeInputFormatValue } from '@/lib/workflows/input-format' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import type { InputFormatField } from '@/lib/workflows/types' import type { McpToolSchema } from './types' @@ -217,7 +217,7 @@ export function extractInputFormatFromBlocks( const blockObj = block as Record const blockType = blockObj.type as string - if (isValidStartBlockType(blockType)) { + if (isInputDefinitionTrigger(blockType)) { // Try to get inputFormat from subBlocks.inputFormat.value const subBlocks = blockObj.subBlocks as Record | undefined const subBlockValue = subBlocks?.inputFormat?.value diff --git a/apps/sim/lib/workflows/input-format.ts b/apps/sim/lib/workflows/input-format.ts index 4c77c1d41..56455a2e8 100644 --- a/apps/sim/lib/workflows/input-format.ts +++ b/apps/sim/lib/workflows/input-format.ts @@ -1,4 +1,4 @@ -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import type { InputFormatField } from '@/lib/workflows/types' /** @@ -25,7 +25,7 @@ export function extractInputFieldsFromBlocks( // Find trigger block const triggerEntry = Object.entries(blocks).find(([, block]) => { const b = block as Record - return typeof b.type === 'string' && isValidStartBlockType(b.type) + return typeof b.type === 'string' && isInputDefinitionTrigger(b.type) }) if (!triggerEntry) return [] diff --git a/apps/sim/lib/workflows/triggers/input-definition-triggers.ts b/apps/sim/lib/workflows/triggers/input-definition-triggers.ts new file mode 100644 index 000000000..7c9fb5f53 --- /dev/null +++ b/apps/sim/lib/workflows/triggers/input-definition-triggers.ts @@ -0,0 +1,28 @@ +/** + * Trigger types that define workflow input parameters (inputFormat). + * These are triggers where users can configure input schema for the workflow. + * + * This module is kept lightweight with no dependencies to avoid circular imports. + * + * Note: External triggers like webhook/schedule are NOT included here because + * they receive input from external event payloads, not user-defined inputFormat. + */ +export const INPUT_DEFINITION_TRIGGER_TYPES = [ + 'starter', + 'start', + 'start_trigger', + 'api_trigger', + 'input_trigger', +] as const + +export type InputDefinitionTriggerType = (typeof INPUT_DEFINITION_TRIGGER_TYPES)[number] + +/** + * Check if a block type is a trigger that defines workflow input parameters. + * Used to find blocks that have inputFormat subblock for workflow input schema. + */ +export function isInputDefinitionTrigger( + blockType: string +): blockType is InputDefinitionTriggerType { + return INPUT_DEFINITION_TRIGGER_TYPES.includes(blockType as InputDefinitionTriggerType) +} diff --git a/apps/sim/lib/workflows/triggers/start-block-types.ts b/apps/sim/lib/workflows/triggers/start-block-types.ts deleted file mode 100644 index 5bf389f91..000000000 --- a/apps/sim/lib/workflows/triggers/start-block-types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Valid start block types that can trigger a workflow - * This module is kept lightweight with no dependencies to avoid circular imports - */ -export const VALID_START_BLOCK_TYPES = [ - 'starter', - 'start', - 'start_trigger', - 'api', - 'api_trigger', - 'input_trigger', -] as const - -export type ValidStartBlockType = (typeof VALID_START_BLOCK_TYPES)[number] - -/** - * Check if a block type is a valid start block type - */ -export function isValidStartBlockType(blockType: string): blockType is ValidStartBlockType { - return VALID_START_BLOCK_TYPES.includes(blockType as ValidStartBlockType) -} diff --git a/apps/sim/lib/workflows/triggers/trigger-utils.ts b/apps/sim/lib/workflows/triggers/trigger-utils.ts index de68af8a7..276e28ce8 100644 --- a/apps/sim/lib/workflows/triggers/trigger-utils.ts +++ b/apps/sim/lib/workflows/triggers/trigger-utils.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types' +import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { type StartBlockCandidate, StartBlockPath, @@ -22,7 +22,7 @@ export function hasValidStartBlockInState(state: WorkflowState | null | undefine const startBlock = Object.values(state.blocks).find((block: BlockState) => { const blockType = block?.type - return isValidStartBlockType(blockType) + return isInputDefinitionTrigger(blockType) }) return !!startBlock