Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
b7e377e
v0.5.91: docs i18n, turborepo upgrade
waleedlatif1 Feb 16, 2026
da46a38
v0.5.92: shortlinks, copilot scrolling stickiness, pagination
waleedlatif1 Feb 17, 2026
fdca736
v0.5.93: NextJS config changes, MCP and Blocks whitelisting, copilot …
waleedlatif1 Feb 18, 2026
15ace5e
v0.5.94: vercel integration, folder insertion, migrated tracking redi…
waleedlatif1 Feb 19, 2026
67aa4bb
v0.5.95: gemini 3.1 pro, cloudflare, dataverse, revenuecat, redis, up…
waleedlatif1 Feb 20, 2026
34d92fa
v0.5.96: sim oauth provider, slack ephemeral message tool and blockki…
waleedlatif1 Feb 21, 2026
115f04e
v0.5.97: oidc discovery for copilot mcp
waleedlatif1 Feb 21, 2026
0d86ea0
v0.5.98: change detection improvements, rate limit and code execution…
waleedlatif1 Feb 22, 2026
af59234
v0.5.99: local dev improvements, live workflow logs in terminal
waleedlatif1 Feb 23, 2026
67f8a68
v0.5.100: multiple credentials, 40% speedup, gong, attio, audit log i…
waleedlatif1 Feb 25, 2026
4fd0989
v0.5.101: circular dependency mitigation, confluence enhancements, go…
waleedlatif1 Feb 26, 2026
198e2c2
feat(executor): support nested loop DAG construction and edge wiring
abram0v1ch Mar 1, 2026
7ecbe5d
feat(executor): add nested loop iteration context and named loop vari…
abram0v1ch Mar 1, 2026
acfd6d6
feat(terminal): propagate parent iteration context through SSE events…
abram0v1ch Mar 1, 2026
c140523
feat(canvas): allow nesting subflow containers and prevent cycles
abram0v1ch Mar 1, 2026
968ed58
feat(agent): add MCP server discovery mode for agent tool input (#3353)
waleedlatif1 Feb 26, 2026
86736d0
improvement(tests): speed up unit tests by eliminating vi.resetModule…
waleedlatif1 Feb 26, 2026
b87563b
feat(databricks): add Databricks integration with 8 tools (#3361)
waleedlatif1 Feb 27, 2026
a94e634
feat(luma): add Luma integration for event and guest management (#3364)
waleedlatif1 Feb 27, 2026
1f3110f
feat(gamma): add gamma integration for AI-powered content generation …
waleedlatif1 Feb 27, 2026
740aee6
feat(greenhouse): add greenhouse integration for managing candidates,…
waleedlatif1 Feb 27, 2026
9f677f3
feat(ashby): add ashby integration for candidate, job, and applicatio…
waleedlatif1 Feb 27, 2026
a90eb31
improvement(oauth): reordered oauth modal (#3368)
waleedlatif1 Feb 27, 2026
665e7cc
feat(loops): add Loops email platform integration (#3359)
waleedlatif1 Feb 27, 2026
39c9c80
feat(resend): expand integration with contacts, domains, and enhanced…
waleedlatif1 Feb 27, 2026
6e17cc4
improvement(blocks): update luma styling and linkup field modes (#3370)
waleedlatif1 Feb 27, 2026
c00c5f1
feat(x): add 28 new X API v2 tool integrations and expand OAuth scope…
waleedlatif1 Feb 27, 2026
c3c3649
improvement(docs): audit and standardize tool description sections, u…
waleedlatif1 Feb 27, 2026
6647241
improvement(x): align OAuth scopes, add scope descriptions, and set o…
waleedlatif1 Feb 27, 2026
9265b9c
improvement(ci): add sticky disk caches and bump runner for faster bu…
waleedlatif1 Feb 27, 2026
a1cb4c0
improvement(selectors): make selectorKeys declarative (#3374)
icecrasher321 Feb 27, 2026
d9cc375
improvement(selectors): consolidate selector input logic (#3375)
icecrasher321 Feb 27, 2026
a2a7d1a
feat(google-contacts): add google contacts integration (#3340)
waleedlatif1 Feb 27, 2026
828475c
improvement(mcp): add all MCP server tools individually instead of as…
waleedlatif1 Feb 27, 2026
eac3c5b
fix(sse): fix memory leaks in SSE stream cleanup and add memory telem…
waleedlatif1 Feb 28, 2026
80adb61
improvement(ashby): validate ashby integration and update skill files…
waleedlatif1 Feb 28, 2026
ce29e60
improvement(luma): expand host response fields and harden event ID in…
waleedlatif1 Feb 28, 2026
43a86dd
improvement(resend): add error handling, authMode, and naming consist…
waleedlatif1 Feb 28, 2026
1a51664
fix(chat-deploy): fix launch chat popup and auth persistence, clean u…
waleedlatif1 Feb 28, 2026
8d2ae12
improvement(loops): validate loops integration and update skill files…
waleedlatif1 Feb 28, 2026
9fa8d3c
fix(monitoring): set MemoryTelemetry logger to INFO level for product…
waleedlatif1 Feb 28, 2026
0eb3f73
feat(integrations): add amplitude, google pagespeed insights, and pag…
waleedlatif1 Mar 1, 2026
d16784e
feat(docs): add API reference with OpenAPI spec and auto-generated en…
waleedlatif1 Mar 2, 2026
367ccf6
fix(icons): fix pagerduty icon (#3392)
waleedlatif1 Mar 2, 2026
7ff85a4
improvement(executor): audit and harden nested loop/parallel implemen…
waleedlatif1 Mar 2, 2026
45ec8b2
improvement(executor): audit and harden nested loop/parallel implemen…
waleedlatif1 Mar 3, 2026
55d8099
improvement(executor): audit fixes for nested subflow implementation
waleedlatif1 Mar 3, 2026
ecfb48f
finished
waleedlatif1 Mar 3, 2026
bfbad9c
improvement(airtable): added more tools (#3396)
waleedlatif1 Mar 2, 2026
ea51d8c
fix(layout): polyfill crypto.randomUUID for non-secure HTTP contexts …
waleedlatif1 Mar 2, 2026
35634a4
feat(integrations): add dub.co integration (#3400)
waleedlatif1 Mar 2, 2026
310687a
fix(memory): fix O(n²) string concatenation and unconsumed fetch resp…
waleedlatif1 Mar 2, 2026
fd2e15b
chore(careers): remove careers page, redirect to Ashby jobs portal (#…
waleedlatif1 Mar 2, 2026
7b89b20
feat(integrations): add google meet integration (#3403)
waleedlatif1 Mar 3, 2026
a54324e
ack comments
waleedlatif1 Mar 3, 2026
2e61711
fix(terminal): deduplicate nested container entries in buildEntryTree
waleedlatif1 Mar 3, 2026
15f3d9c
improvement(executor): clean up nested subflow implementation
waleedlatif1 Mar 3, 2026
2b97791
fix(test): update parallel resolver test to use distribution instead …
waleedlatif1 Mar 3, 2026
9205786
fix(executor): skip loop back-edges in parallel boundary detection an…
waleedlatif1 Mar 3, 2026
f972409
fix(executor): clean up cloned loop scopes in deleteParallelScopeAndC…
waleedlatif1 Mar 3, 2026
837e7c8
fix(executor): remove dead fallbacks, fix nested loop boundary detect…
waleedlatif1 Mar 3, 2026
0386055
leftover
waleedlatif1 Mar 3, 2026
6af3d53
upgrade turborepo
waleedlatif1 Mar 3, 2026
ab07a72
update stagehand icon
waleedlatif1 Mar 3, 2026
3bac0dd
fix(tag-dropdown): show contextual loop/parallel tags for deeply nest…
waleedlatif1 Mar 3, 2026
8093739
testing
waleedlatif1 Mar 3, 2026
f8de34b
fixed dedicated logs
waleedlatif1 Mar 3, 2026
ea4fcdc
fix
waleedlatif1 Mar 4, 2026
27ed713
fix(subflows): enable nested subflow interaction and execution highli…
waleedlatif1 Mar 4, 2026
d714149
Merge branch 'staging' into feat/loop_nesting
waleedlatif1 Mar 4, 2026
2a6b631
fix(preview): add cycle guard to recursive subflow status derivation
waleedlatif1 Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(subflows): enable nested subflow interaction and execution highli…
…ghting

Remove !important z-index overrides that prevented nested subflows from
being grabbed/dragged independently. Z-index is now managed by ReactFlow's
elevateNodesOnSelect and per-node zIndex: depth props. Also adds execution
status highlighting for nested subflows in both canvas and snapshot preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Loading branch information
waleedlatif1 and claude committed Mar 4, 2026
commit 27ed713ad285035e1e55f6149cb7615693654061
10 changes: 1 addition & 9 deletions apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -833,15 +833,7 @@ input[type="search"]::-ms-clear {
animation: growShrink 1.5s infinite ease-in-out;
}

/* Subflow node z-index and drag-over styles */
.workflow-container .react-flow__node-subflowNode {
z-index: -1 !important;
}

.workflow-container .react-flow__node-subflowNode:has([data-subflow-selected="true"]) {
z-index: 10 !important;
}

/* Subflow node drag-over styles */
.loop-node-drag-over,
.parallel-node-drag-over {
box-shadow: 0 0 0 1.75px var(--brand-secondary) !important;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1240,8 +1240,12 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({

const parallelBlockGroups: BlockTagGroup[] = []
const ancestorParallelIds = new Set<string>()
const visitedParallelTargets = new Set<string>()

const findAncestorParallels = (targetId: string) => {
if (visitedParallelTargets.has(targetId)) return
visitedParallelTargets.add(targetId)

for (const [parallelId, parallel] of Object.entries(parallels || {})) {
if (parallel.nodes.includes(targetId) && !ancestorParallelIds.has(parallelId)) {
ancestorParallelIds.add(parallelId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type DiffStatus, hasDiffStatus } from '@/lib/workflows/diff/types'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { ActionBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/action-bar/action-bar'
import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
import { useLastRunPath } from '@/stores/execution'
import { usePanelEditorStore } from '@/stores/panel'

/**
Expand All @@ -23,6 +24,8 @@ export interface SubflowNodeData {
isPreviewSelected?: boolean
kind: 'loop' | 'parallel'
name?: string
/** Execution status passed by preview/snapshot views */
executionStatus?: 'success' | 'error' | 'not-executed'
}

/**
Expand Down Expand Up @@ -56,6 +59,15 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf

const isPreviewSelected = data?.isPreviewSelected || false

const lastRunPath = useLastRunPath()
const executionStatus = data.executionStatus
const runPathStatus: 'success' | 'error' | undefined =
executionStatus === 'success' || executionStatus === 'error'
? executionStatus
: isPreview
? undefined
: lastRunPath.get(id)

/**
* Calculate the nesting level of this subflow node based on its parent hierarchy.
* Used to apply appropriate styling for nested containers.
Expand Down Expand Up @@ -105,32 +117,57 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
* Determine the ring styling based on subflow state priority:
* 1. Focused (selected in editor), selected (shift-click/box), or preview selected - blue ring
* 2. Diff status (version comparison) - green/orange ring
* 3. Run path status (execution result) - green/red ring
*/
const isSelected = !isPreview && selected
const hasRing =
isFocused || isSelected || isPreviewSelected || diffStatus === 'new' || diffStatus === 'edited'
const ringStyles = cn(
hasRing && 'ring-[1.75px]',
(isFocused || isSelected || isPreviewSelected) && 'ring-[var(--brand-secondary)]',
diffStatus === 'new' && 'ring-[var(--brand-tertiary-2)]',
diffStatus === 'edited' && 'ring-[var(--warning)]'
)
isFocused ||
isSelected ||
isPreviewSelected ||
diffStatus === 'new' ||
diffStatus === 'edited' ||
!!runPathStatus
/**
* Compute the outline color for the subflow ring.
* Uses CSS outline instead of box-shadow ring because in ReactFlow v11,
* child nodes are DOM children of parent nodes and paint over the parent's
* internal ring overlay. Outline renders on the element's own compositing
* layer, so it stays visible above nested child nodes.
*/
const outlineColor = hasRing
? isFocused || isSelected || isPreviewSelected
? 'var(--brand-secondary)'
: diffStatus === 'new'
? 'var(--brand-tertiary-2)'
: diffStatus === 'edited'
? 'var(--warning)'
: runPathStatus === 'success'
? executionStatus
? 'var(--brand-tertiary-2)'
: 'var(--border-success)'
: runPathStatus === 'error'
? 'var(--text-error)'
: undefined
: undefined

return (
<div className='group relative'>
<div
ref={blockRef}
onClick={() => setCurrentBlockId(id)}
className={cn(
'workflow-drag-handle relative cursor-grab select-none rounded-[8px] border border-[var(--border-1)] [&:active]:cursor-grabbing',
'transition-block-bg transition-ring'
'relative select-none rounded-[8px] border border-[var(--border-1)]',
'transition-block-bg'
)}
style={{
width: data.width || 500,
height: data.height || 300,
position: 'relative',
overflow: 'visible',
pointerEvents: isPreview ? 'none' : 'all',
pointerEvents: 'none',
...(outlineColor && {
outline: `1.75px solid ${outlineColor}`,
outlineOffset: '-1px',
}),
}}
data-node-id={id}
data-type='subflowNode'
Expand All @@ -141,11 +178,13 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
<ActionBar blockId={id} blockType={data.kind} disabled={!userPermissions.canEdit} />
)}

{/* Header Section */}
{/* Header Section — only interactive area for dragging */}
<div
onClick={() => setCurrentBlockId(id)}
className={cn(
'flex items-center justify-between rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px]'
'workflow-drag-handle flex cursor-grab items-center justify-between rounded-t-[8px] border-[var(--border)] border-b bg-[var(--surface-2)] py-[8px] pr-[12px] pl-[8px] [&:active]:cursor-grabbing'
)}
style={{ pointerEvents: 'auto' }}
>
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
<div
Expand Down Expand Up @@ -182,7 +221,7 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
data-dragarea='true'
style={{
position: 'relative',
pointerEvents: isPreview ? 'none' : 'auto',
pointerEvents: 'none',
}}
>
{/* Subflow Start */}
Expand Down Expand Up @@ -232,12 +271,6 @@ export const SubflowNodeComponent = memo(({ data, id, selected }: NodeProps<Subf
}}
id={endHandleId}
/>

{hasRing && (
<div
className={cn('pointer-events-none absolute inset-0 z-40 rounded-[8px]', ringStyles)}
/>
)}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
} from '@/executor/types'
import { hasExecutionResult } from '@/executor/utils/errors'
import { coerceValue } from '@/executor/utils/start-block'
import { stripCloneSuffixes } from '@/executor/utils/subflow-utils'
import { subscriptionKeys } from '@/hooks/queries/subscription'
import { useExecutionStream } from '@/hooks/use-execution-stream'
import { WorkflowValidationError } from '@/serializer'
Expand Down Expand Up @@ -486,14 +487,23 @@ export function useWorkflowExecution() {
if (isStaleExecution()) return
updateActiveBlocks(data.blockId, false)
if (workflowId) setBlockRunStatus(workflowId, data.blockId, 'success')

executedBlockIds.add(data.blockId)
accumulatedBlockStates.set(data.blockId, {
output: data.output,
executed: true,
executionTime: data.durationMs,
})

// For nested containers, the SSE blockId may be a cloned ID (e.g. P1__obranch-0).
// Also record the original workflow-level ID so the canvas can highlight it.
if (isContainerBlockType(data.blockType)) {
const originalId = stripCloneSuffixes(data.blockId)
if (originalId !== data.blockId) {
executedBlockIds.add(originalId)
if (workflowId) setBlockRunStatus(workflowId, originalId, 'success')
}
}

if (isContainerBlockType(data.blockType) && !data.iterationContainerId) {
Comment thread
waleedlatif1 marked this conversation as resolved.
return
}
Expand Down Expand Up @@ -525,6 +535,15 @@ export function useWorkflowExecution() {
executionTime: data.durationMs || 0,
})

// For nested containers, also record the original workflow-level ID
if (isContainerBlockType(data.blockType)) {
const originalId = stripCloneSuffixes(data.blockId)
if (originalId !== data.blockId) {
executedBlockIds.add(originalId)
if (workflowId) setBlockRunStatus(workflowId, originalId, 'error')
}
}

accumulatedBlockLogs.push(
createBlockLogEntry(data, { success: false, output: {}, error: data.error })
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const defaultEdgeOptions = { type: 'custom' }
const reactFlowStyles = [
'bg-[var(--bg)]',
'[&_.react-flow__edges]:!z-0',
'[&_.react-flow__node]:!z-[21]',
'[&_.react-flow__node]:z-[21]',
'[&_.react-flow__handle]:!z-[30]',
'[&_.react-flow__edge-labels]:!z-[60]',
'[&_.react-flow__pane]:!bg-[var(--bg)]',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,21 +289,32 @@ export function PreviewWorkflow({
return map
}, [executedBlocks])

/** Derives subflow status from children. Error takes precedence. */
/** Derives subflow status from children. Recursively checks nested subflows. Error takes precedence. */
const getSubflowExecutionStatus = useMemo(() => {
return (subflowId: string): ExecutionStatus | undefined => {
const derive = (subflowId: string): ExecutionStatus | undefined => {
const childIds = subflowChildrenMap.get(subflowId)
if (!childIds?.length) return undefined

const executedChildren = childIds
.map((id) => blockExecutionMap.get(id))
.filter((status): status is { status: string } => Boolean(status))
const childStatuses: string[] = []
for (const childId of childIds) {
const direct = blockExecutionMap.get(childId)
if (direct) {
childStatuses.push(direct.status)
} else {
const childBlock = workflowState.blocks?.[childId]
if (childBlock?.type === 'loop' || childBlock?.type === 'parallel') {
const nested = derive(childId)
if (nested) childStatuses.push(nested)
}
}
}

if (executedChildren.length === 0) return undefined
if (executedChildren.some((s) => s.status === 'error')) return 'error'
if (childStatuses.length === 0) return undefined
if (childStatuses.some((s) => s === 'error')) return 'error'
return 'success'
}
}, [subflowChildrenMap, blockExecutionMap])
return derive
Comment thread
waleedlatif1 marked this conversation as resolved.
}, [subflowChildrenMap, blockExecutionMap, workflowState.blocks])

/** Gets block status. Subflows derive status from children. */
const getBlockExecutionStatus = useMemo(() => {
Expand Down
18 changes: 11 additions & 7 deletions apps/sim/lib/logs/execution/trace-spans/trace-spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import type { ToolCall, TraceSpan } from '@/lib/logs/types'
import { isWorkflowBlockType, stripCustomToolPrefix } from '@/executor/constants'
import type { ExecutionResult } from '@/executor/types'
import { stripCloneSuffixes } from '@/executor/utils/subflow-utils'

const logger = createLogger('TraceSpans')

Expand Down Expand Up @@ -569,28 +570,31 @@ interface ContainerNameCounters {

/**
* Resolves a container name from normal (non-iteration) spans or assigns a sequential number.
* Strips clone suffixes so all clones of the same container share one name/number.
*/
function resolveContainerName(
containerId: string,
containerType: 'parallel' | 'loop',
normalSpans: TraceSpan[],
counters: ContainerNameCounters
): string {
const originalId = stripCloneSuffixes(containerId)

const matchingBlock = normalSpans.find(
(s) => s.blockId === containerId && s.type === containerType
(s) => s.blockId === originalId && s.type === containerType
)
if (matchingBlock?.name) return matchingBlock.name

if (containerType === 'parallel') {
if (!counters.parallelNumbers.has(containerId)) {
counters.parallelNumbers.set(containerId, counters.parallelCounter++)
if (!counters.parallelNumbers.has(originalId)) {
counters.parallelNumbers.set(originalId, counters.parallelCounter++)
}
return `Parallel ${counters.parallelNumbers.get(containerId)}`
return `Parallel ${counters.parallelNumbers.get(originalId)}`
}
if (!counters.loopNumbers.has(containerId)) {
counters.loopNumbers.set(containerId, counters.loopCounter++)
if (!counters.loopNumbers.has(originalId)) {
counters.loopNumbers.set(originalId, counters.loopCounter++)
}
return `Loop ${counters.loopNumbers.get(containerId)}`
return `Loop ${counters.loopNumbers.get(originalId)}`
}

/**
Expand Down