+
Select a block to see its full configuration
)}
diff --git a/apps/docs/content/docs/en/workflows/how-it-runs.mdx b/apps/docs/content/docs/en/workflows/how-it-runs.mdx
index 2a808c2cde3..9bc42f226da 100644
--- a/apps/docs/content/docs/en/workflows/how-it-runs.mdx
+++ b/apps/docs/content/docs/en/workflows/how-it-runs.mdx
@@ -6,7 +6,13 @@ pageType: concept
import { Callout } from 'fumadocs-ui/components/callout'
import { Card, Cards } from 'fumadocs-ui/components/card'
-import { Image } from '@/components/ui/image'
+import {
+ COMBINATION_WORKFLOW,
+ CONCURRENCY_WORKFLOW,
+ ERROR_PATH_WORKFLOW,
+ ROUTING_WORKFLOW,
+ WorkflowPreview,
+} from '@/components/workflow-preview'
When you run a workflow, Sim works out the order from the [connections](/workflows/connections): a block runs as soon as the blocks it depends on have finished.
@@ -14,7 +20,7 @@ When you run a workflow, Sim works out the order from the [connections](/workflo
Multiple blocks in a workflow can be executing at the same time. A block starts the moment its dependencies finish, and it waits on nothing else.
-
+
Here the Customer Support and Deep Researcher agents each depend only on Start, so neither waits for the other.
@@ -22,7 +28,7 @@ Here the Customer Support and Deep Researcher agents each depend only on Start,
When several blocks feed into one, that block waits for every feeder that is going to run, then runs once with each of their outputs available to read. A feeder on a branch that wasn't taken doesn't hold it up. You don't merge the outputs yourself.
-
+
The Function block here runs after both agents complete, with both of their outputs ready.
@@ -30,7 +36,7 @@ The Function block here runs after both agents complete, with both of their outp
A workflow can split. A [Condition](/workflows/blocks/condition) block branches on an explicit rule; a [Router](/workflows/blocks/router) block lets a model choose the path. Only the branch that is taken runs. A block on a branch that didn't run produces no output, which is why a [connection tag](/workflows/connections) pointing at it comes back empty.
-
+
To repeat work, a [Loop](/workflows/blocks/loop) block runs its inner blocks over a list, a count, or while a condition holds, and a [Parallel](/workflows/blocks/parallel) block runs them for several items at once.
@@ -42,7 +48,7 @@ A workflow can call other workflows, through a Workflow block, an MCP tool, or a
A block that errors fails the run: blocks already running finish, and nothing new starts. To handle the failure instead, connect the block's **error port** — the run follows the [error path](/workflows/connections) and continues.
-
+
Here `throwError` fails, so the run leaves through its red error port to `handleError`; `handleSuccess` on the normal path never runs.
diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts
index 1993869f285..a322697fbd4 100644
--- a/apps/docs/next.config.ts
+++ b/apps/docs/next.config.ts
@@ -5,7 +5,7 @@ const withMDX = createMDX()
const config: NextConfig = {
reactStrictMode: true,
- transpilePackages: ['@sim/emcn'],
+ transpilePackages: ['@sim/emcn', '@sim/workflow-renderer'],
images: {
unoptimized: true,
},
diff --git a/apps/docs/package.json b/apps/docs/package.json
index e1bf0b4ebb6..9f8d622f6cc 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -19,6 +19,7 @@
"@ai-sdk/react": "2.0.205",
"@sim/db": "workspace:*",
"@sim/emcn": "workspace:*",
+ "@sim/workflow-renderer": "workspace:*",
"@vercel/og": "^0.6.5",
"ai": "5.0.203",
"class-variance-authority": "^0.7.1",
@@ -34,6 +35,7 @@
"postgres": "^3.4.5",
"react": "19.2.4",
"react-dom": "19.2.4",
+ "remark-breaks": "^4.0.0",
"shiki": "4.0.0",
"streamdown": "2.5.0",
"tailwind-merge": "^3.0.2",
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
index 0976cdde23d..6c7976327e5 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
@@ -617,7 +617,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({
*/
const conditionRows = useMemo(() => {
if (type !== 'condition') return [] as { id: string; title: string; value: string }[]
- return getConditionRows(id, topologySubBlocks.conditions?.value)
+ return getConditionRows(id, topologySubBlocks.conditions?.value).map((cond) => ({
+ ...cond,
+ value: getDisplayValue(cond.value),
+ }))
}, [type, topologySubBlocks, id])
/**
@@ -627,7 +630,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({
*/
const routerRows = useMemo(() => {
if (type !== 'router_v2') return [] as { id: string; value: string }[]
- return getRouterRows(id, topologySubBlocks.routes?.value)
+ return getRouterRows(id, topologySubBlocks.routes?.value).map((route) => ({
+ ...route,
+ value: getDisplayValue(route.value),
+ }))
}, [type, topologySubBlocks, id])
/**
@@ -699,29 +705,10 @@ export const WorkflowBlock = memo(function WorkflowBlock({
const webhookProviderName = webhookProvider ? getProviderName(webhookProvider) : undefined
- const rows = (
- <>
- {type === 'condition' ? (
- conditionRows.map((cond) => (
-
- ))
- ) : type === 'router_v2' ? (
- <>
-
- {routerRows.map((route, index) => (
-
- ))}
- >
- ) : (
- subBlockRows.map((row, rowIndex) =>
+ const rows =
+ type === 'condition' || type === 'router_v2' ? null : (
+ <>
+ {subBlockRows.map((row, rowIndex) =>
row.flatMap((subBlock) => {
const rawValue = subBlockState[subBlock.id]?.value
if (subBlock.type === 'mcp-dynamic-args') {
@@ -761,11 +748,9 @@ export const WorkflowBlock = memo(function WorkflowBlock({
/>,
]
})
- )
- )}
- {shouldShowDefaultHandles &&
}
- >
- )
+ )}
+ >
+ )
return (
=0.479.0",
"react": "^19",
"reactflow": "^11.11.4",
diff --git a/packages/workflow-renderer/package.json b/packages/workflow-renderer/package.json
index 65274c95eb0..64f08ccb7f7 100644
--- a/packages/workflow-renderer/package.json
+++ b/packages/workflow-renderer/package.json
@@ -26,6 +26,7 @@
"format:check": "biome format ."
},
"peerDependencies": {
+ "@sim/emcn": "workspace:*",
"lucide-react": ">=0.479.0",
"react": "^19",
"reactflow": "^11.11.4",
@@ -33,6 +34,7 @@
"streamdown": ">=2.5.0"
},
"devDependencies": {
+ "@sim/emcn": "workspace:*",
"@sim/tsconfig": "workspace:*",
"@types/react": "^19",
"lucide-react": "^0.479.0",
diff --git a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx
index e56254322bb..4d636b0bae9 100644
--- a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx
+++ b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx
@@ -3,6 +3,7 @@ import { Badge, cn, handleKeyboardActivation, Tooltip } from '@sim/emcn'
import { Handle, Position } from 'reactflow'
import { HANDLE_POSITIONS } from '../dimensions'
import type { BlockRunStatus } from '../types'
+import { SubBlockRowView } from './sub-block-row-view'
/**
* Reusable styles and positioning for Handle components.
@@ -62,32 +63,34 @@ export interface WorkflowBlockViewProps {
hasContentBelowHeader: boolean
conditionRows: { id: string; title: string; value: string }[]
routerRows: { id: string; value: string }[]
+ /** Router 'Context' summary-row value (router_v2 only). */
+ routerContextValue?: string
/** Connection-cycle guard; reads fresh edge state on every call. */
wouldCreateConnectionCycle: (source: string, target: string) => boolean
- /** Child-workflow deploy badge state. */
- isWorkflowSelector: boolean
+ /** Child-workflow deploy badge state — editor-only; omit in read-only contexts. */
+ isWorkflowSelector?: boolean
childWorkflowId?: string
- childIsDeployed: boolean | null
- childNeedsRedeploy: boolean
- isDeploying: boolean
- canAdmin: boolean
- onDeployChild: () => void
+ childIsDeployed?: boolean | null
+ childNeedsRedeploy?: boolean
+ isDeploying?: boolean
+ canAdmin?: boolean
+ onDeployChild?: () => void
- /** Schedule badge state. */
- shouldShowScheduleBadge: boolean
- scheduleIsDisabled: boolean
- onReactivateSchedule: () => void
+ /** Schedule badge state — editor-only; omit in read-only contexts. */
+ shouldShowScheduleBadge?: boolean
+ scheduleIsDisabled?: boolean
+ onReactivateSchedule?: () => void
- /** Webhook badge state. */
- showWebhookIndicator: boolean
+ /** Webhook badge state — editor-only; omit in read-only contexts. */
+ showWebhookIndicator?: boolean
webhookProvider?: string
webhookPath?: string
webhookProviderName?: string
- isWebhookConfigured: boolean
- isWebhookDisabled: boolean
+ isWebhookConfigured?: boolean
+ isWebhookDisabled?: boolean
webhookId?: string
- onReactivateWebhook: () => void
+ onReactivateWebhook?: () => void
/** Selects this block in the editor panel. */
onSelect: () => void
@@ -95,7 +98,11 @@ export interface WorkflowBlockViewProps {
contentRef?: Ref
/** Editor-only action bar; omit in read-only / preview contexts. */
actionBar?: ReactNode
- /** Collapsed subblock summary rows, built by the container. */
+ /**
+ * Non-branch collapsed subblock summary rows, built by the container.
+ * Condition/router/error rows are rendered by the view itself from
+ * conditionRows/routerRows.
+ */
rows: ReactNode
}
@@ -121,6 +128,7 @@ export function WorkflowBlockView({
hasContentBelowHeader,
conditionRows,
routerRows,
+ routerContextValue,
wouldCreateConnectionCycle,
isWorkflowSelector,
childWorkflowId,
@@ -221,7 +229,7 @@ export function WorkflowBlockView({
dot
onClick={(e) => {
e.stopPropagation()
- onDeployChild()
+ onDeployChild?.()
}}
>
{isDeploying ? 'Deploying...' : !childIsDeployed ? 'undeployed' : 'redeploy'}
@@ -250,7 +258,7 @@ export function WorkflowBlockView({
dot
onClick={(e) => {
e.stopPropagation()
- onReactivateSchedule()
+ onReactivateSchedule?.()
}}
>
disabled
@@ -293,7 +301,7 @@ export function WorkflowBlockView({
dot
onClick={(e) => {
e.stopPropagation()
- onReactivateWebhook()
+ onReactivateWebhook?.()
}}
>
disabled
@@ -315,7 +323,29 @@ export function WorkflowBlockView({