fix(subflows): recurse into all descendants for lock, enable, and protection checks#3412
fix(subflows): recurse into all descendants for lock, enable, and protection checks#3412waleedlatif1 wants to merge 5 commits intostagingfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@greptile |
|
@cursor review |
|
Skipping Bugbot: Bugbot is disabled for this repository. Visit the Bugbot dashboard to update your settings. |
Greptile SummaryThis PR fixes a class of bugs where lock/enable propagation and protection checks only inspected the immediate parent of a block rather than the full ancestor chain, causing operations to silently bypass locks in deeply nested subflows. It consolidates all duplicated Key changes:
Core logic fixes are sound and well-tested. However, two unintentional side effects in Confidence Score: 4/5
Last reviewed commit: 8a8a9d5 |
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/subflow-node.tsx
Show resolved
Hide resolved
…p code - Add canvasReadyRef to skip container dimension recalculation during ReactFlow init — position changes from extent clamping fired before block heights are measured, causing containers to resize on page load - Resolve globals.css merge conflict, remove global z-index overrides (handled via ReactFlow zIndex prop instead) - Clean up subflow-node: hoist static helpers to module scope, remove unused ref, fix nested ternary readability, rename outlineColor→ringColor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…le-toggle The enable-toggle for descendants was checking only direct `locked` status instead of walking the full ancestor chain via `isBlockProtected`. This meant a block nested 2+ levels inside a locked subflow could still be toggled. Also added TSDoc clarifying why boxShadow works for subflow ring indicators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR SummaryMedium Risk Overview Updates both the client stores/collab workflow and the socket DB operation handlers to use the same recursive protection/descendant logic, preventing locked/disabled state inconsistencies after persistence/refresh. Improves nested subflow UX: container bodies are now click-to-select, selection rings use Written by Cursor Bugbot for commit 8a8a9d5. Configure here. |
The canvasReadyRef gating in onNodesChange didn't fully fix the container resize-on-load issue. Reverting to address properly later. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Leftover from merge conflict resolution — not part of this PR's changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| const isParentLocked = parentId ? (blocks[parentId]?.locked ?? false) : false | ||
| const isLocked = (currentBlock?.locked ?? false) || isParentLocked | ||
| const isLocked = currentBlockId ? isBlockProtected(currentBlockId, blocks) : false | ||
| const isAncestorLocked = isLocked && !(currentBlock?.locked ?? false) |
There was a problem hiding this comment.
Incorrect isAncestorLocked when block and ancestor both locked
Medium Severity
isAncestorLocked is derived as isLocked && !(currentBlock?.locked ?? false), but isBlockProtected short-circuits on block.locked before ever checking ancestors. When a block is both self-locked and has a locked ancestor, isAncestorLocked evaluates to false instead of true. This causes the editor to incorrectly show an "Unlock" button (with tooltip "Unlock block") instead of a lock icon with the "Ancestor container is locked" message. Unlocking the block won't actually make it editable since the ancestor remains locked, which is confusing.
Additional Locations (1)
| edgesFocusable={true} | ||
| edgesUpdatable={effectivePermissions.canEdit} | ||
| className={`workflow-container h-full transition-opacity duration-150 ${reactFlowStyles} ${isCanvasReady ? 'opacity-100' : 'opacity-0'} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`} | ||
| className={`workflow-container h-full bg-[var(--bg)] ${reactFlowStyles} ${isCanvasReady ? 'opacity-100' : 'opacity-0'} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`} |
There was a problem hiding this comment.
The transition-opacity duration-150 class was removed in this change. The opacity is still toggled based on isCanvasReady, but without the transition class, the canvas will snap from invisible to fully visible instantly instead of fading in smoothly.
| className={`workflow-container h-full bg-[var(--bg)] ${reactFlowStyles} ${isCanvasReady ? 'opacity-100' : 'opacity-0'} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`} | |
| className={`workflow-container h-full bg-[var(--bg)] transition-opacity duration-150 ${reactFlowStyles} ${isCanvasReady ? 'opacity-100' : 'opacity-0'} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`} |
| onlyRenderVisibleElements={false} | ||
| deleteKeyCode={null} | ||
| elevateNodesOnSelect={true} | ||
| elevateNodesOnSelect={false} |
There was a problem hiding this comment.
Setting elevateNodesOnSelect={false} was necessary to prevent containers from painting over their children (fixed by manual zIndex: 1000 on child nodes). However, this manual elevation only applies to blocks with a parentId, so top-level blocks at the canvas root will never be elevated when selected.
Before this change, selecting overlapping top-level workflow blocks would bring the selected block visually to the front. After this change, overlapping top-level blocks retain their natural DOM stacking order regardless of selection, making it impossible to interact with a block positioned beneath another.
One approach to preserve elevation for top-level selected nodes without re-introducing the container-covers-children problem would be to explicitly handle elevation via the style property on root nodes (e.g., bump their z-index when they match the selected node ID).


Summary
isBlockProtectedandfindAllDescendantNodesutilities to eliminate 7+ duplicate inline closuresType of Change
Testing
Tested manually with 3-level nesting (Parallel > Parallel > Parallel > Function). All existing tests pass (158/158).
Checklist