Skip to content

feat(canvas): added the ability to lock blocks - #3102

Merged
waleedlatif1 merged 34 commits into
stagingfrom
feat/lock
Feb 4, 2026
Merged

feat(canvas): added the ability to lock blocks#3102
waleedlatif1 merged 34 commits into
stagingfrom
feat/lock

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • added the ability to lock blocks

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercelBot commented Feb 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
docsSkippedSkippedFeb 4, 2026 3:06am

Request Review

Resolved conflicts:
- Removed addBlock from store (now using batchAddBlocks)
- Updated lock tests to use test helper addBlock function
- Kept both staging's optimization tests and lock feature tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When duplicating a block that's inside a locked loop/parallel,
the duplicate is now placed outside the container since nothing
should be added to a locked container.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Server-side workflow duplication now sets locked: false for all blocks
- regenerateWorkflowStateIds also unlocks blocks for templates
- Client-side regenerateBlockIds already handled this (for paste/import)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@waleedlatif1
waleedlatif1 marked this pull request as ready for review February 1, 2026 02:46
@greptile-apps

greptile-appsBot commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Overview

Greptile Summary

This PR implements a comprehensive block locking feature that prevents editing, deletion, and movement of workflow blocks. The implementation includes:

  • Database schema change adding a locked boolean column to workflow_blocks
  • Frontend lock/unlock UI in block menus and action bars (admin-only)
  • Protection enforcement throughout the workflow editor:
    • Locked blocks cannot be dragged, deleted, renamed, or have edges modified
    • Blocks inside locked containers (loops/parallels) inherit protection
    • Duplicate blocks from locked containers are placed outside and unlocked
  • Server-side validation prevents lock bypass via direct API calls
  • Full collaborative editing support with real-time sync and undo/redo
  • Comprehensive test coverage for lock behavior and edge cases

The lock feature cascades to children when locking loop/parallel containers, and provides clear user notifications when operations are blocked due to lock status.

Confidence Score: 4/5

  • Safe to merge with one minor consideration about PR classification
  • The implementation is comprehensive with strong test coverage and defense-in-depth validation on both frontend and backend. The logic is consistent across components, and edge cases are well-handled. Score is 4 instead of 5 due to the PR being labeled as a "Bug fix" when it's clearly a feature addition, though this doesn't affect code quality.
  • No files require special attention - the implementation is solid across all modified files

Important Files Changed

FilenameOverview
apps/sim/stores/workflows/workflow/store.tsImplemented lock logic in store actions: batchToggleEnabled/Handles skip locked blocks, duplicateBlock handles locked containers, added setBlockLocked and batchToggleLocked
apps/sim/socket/database/operations.tsAdded server-side lock enforcement: rename, add, remove, toggle, and parent updates check for locked blocks and locked parents
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils.tsNew utility module with isBlockProtected, isEdgeProtected, and filterProtectedBlocks functions
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsxIntegrated lock protection throughout workflow UI: draggable state, edge connections, deletion with user notifications, and lock toggle handler
apps/sim/hooks/use-collaborative-workflow.tsAdded collaborative lock operations with undo/redo support, filters locked blocks from enabled/handles toggles
apps/sim/hooks/use-undo-redo.tsImplemented undo/redo for batch lock toggle, fixed toggle operations to use target state instead of negating previous states

Sequence Diagram

sequenceDiagram
participant User
participant UI as Workflow UI
participant Store as Workflow Store
participant Hook as Collaborative Hook
participant Socket as Socket Server
participant DB as Database
Note over User,DB: Lock/Unlock Block Flow
User->>UI: Right-click block(s) → Lock
UI->>Hook: collaborativeBatchToggleLocked(blockIds)
Hook->>Store: batchToggleLocked(blockIds)
Note over Store: Collect blocks + children<br/>(if loop/parallel)
Note over Store: Determine target: !firstBlock.locked
Store->>Store: Update locked state for all blocks
Hook->>Hook: Record undo/redo operation
Hook->>Socket: Emit BATCH_TOGGLE_LOCKED operation
Socket->>DB: Update workflow_blocks.locked
Socket-->>Hook: Broadcast to collaborators
Hook->>Store: Apply remote lock changes
Note over User,DB: Protection Enforcement
User->>UI: Try to delete locked block
UI->>UI: filterProtectedBlocks()
alt All blocks protected
UI->>User: Show notification: Cannot delete locked blocks
else Some protected
UI->>User: Show notification: Skipped N protected blocks
UI->>Hook: collaborativeBatchRemoveBlocks(deletableIds)
end
User->>UI: Try to drag locked block
Note over UI: draggable={!isBlockProtected(id)}
UI-->>User: Drag prevented (React Flow)
User->>UI: Try to connect to locked block
UI->>UI: isEdgeProtected(connection)
UI->>User: Show notification: Cannot connect to locked blocks
User->>UI: Try to rename locked block
UI->>Hook: collaborativeUpdateBlockName()
Hook->>Hook: Check if block.locked || parent.locked
Hook->>User: Show notification: Cannot rename locked blocks
Note over User,DB: Duplicate with Locked Parent
User->>UI: Duplicate block inside locked loop
UI->>Store: duplicateBlock(blockId)
Store->>Store: Check if parent.locked
Note over Store: Place outside container<br/>Remove parentId
Store->>Store: Create unlocked duplicate
Store->>UI: New block outside locked container
Loading

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment threadapps/sim/socket/database/operations.ts Outdated
Comment threadapps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx Outdated
- Fix toggle enabled using first toggleable block, not first block
- Delete button now checks isParentLocked
- Lock button now has disabled state
- Editor lock icon distinguishes block vs parent lock state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Editor: can't unlock block if parent container is locked
- Action bar: can't unlock block if parent container is locked
- Shows "Parent container is locked" tooltip in both cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Block Menu, Editor, Action Bar now all have identical behavior:
- Enable/Disable: disabled when locked OR parent locked
- Flip Handles: disabled when locked OR parent locked
- Delete: disabled when locked OR parent locked
- Remove from Subflow: disabled when locked OR parent locked
- Lock: always available for admins
- Unlock: disabled when parent is locked (unlock parent first)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
waleedlatif1and others added 2 commits January 31, 2026 19:05
Same pattern as lock: must enable parent container first before
enabling children inside it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added documentation for the lock/unlock block feature (admin only).
Note: Image placeholder added, pending actual screenshot.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Paste creates new blocks, doesn't modify selected ones. Changed from
disableEdit (includes lock state) to !userCanEdit (permission only),
matching the Duplicate action behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment threadapps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx Outdated
Based on audit findings, adds lock validation to multiple operations:
1. BATCH_TOGGLE_HANDLES - now skips locked/protected blocks at:
- Store layer (batchToggleHandles)
- Collaborative hook (collaborativeBatchToggleBlockHandles)
- Server socket handler
2. BATCH_ADD_BLOCKS - server now filters blocks being added to
locked parent containers
3. BATCH_UPDATE_PARENT - server now:
- Skips protected blocks (locked or inside locked container)
- Prevents moving blocks into locked containers
All validations use consistent isProtected() helper that checks both
direct lock and parent container lock.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
contextMenuBlocks already has locked and isParentLocked properties
computed in use-canvas-context-menu.ts, so there's no need to look
up blocks again via hasProtectedBlocks.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Resolved merge conflict in editor.tsx (kept lock checking logic)
- Removed unused hasProtectedBlocks function from block-protection-utils.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Defense-in-depth: although the UI disables rename for locked blocks,
the collaborative layer and server now also validate locks.
- collaborativeUpdateBlockName: checks if block is locked or inside
locked container before attempting rename
- UPDATE_NAME server handler: checks lock status and parent lock
before performing database update
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@waleedlatif1

Copy link
Copy Markdown
CollaboratorAuthor

@greptile

Defense-in-depth: adds lock checks to server-side handlers that were
previously relying only on client-side validation.
Edge operations (ADD, REMOVE, BATCH_ADD, BATCH_REMOVE):
- Check if source or target blocks are protected before modifying edges
Subblock updates:
- Check if parent block is protected before updating subblock values
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Comment threadapps/sim/socket/database/operations.ts
…ent tooltip
- Fixed edge operations to fetch parent blocks before checking lock status
- Previously, isBlockProtected checked if parent was locked, but the parent
wasn't in blocksById because only source/target blocks were fetched
- Now fetches parent blocks for all four edge operations: ADD, REMOVE,
BATCH_ADD_EDGES, BATCH_REMOVE_EDGES
- Fixed tooltip inconsistency: changed "Run previous blocks first" to
"Run upstream blocks first" in action-bar to match workflow.tsx
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment threadapps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx Outdated
- Added lock check to duplicate button in action bar to prevent
duplicating locked blocks (consistent with other edit operations)
- Removed ineffective early return in onNodeDragStart since the
`draggable` property on nodes already prevents dragging protected
blocks - the early return was misleading as it couldn't actually
stop a drag operation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed duplicate menu item to use disableEdit (which includes lock
check) instead of !userCanEdit for consistency with action bar and
other edit operations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@waleedlatif1
waleedlatif1 merged commit 4db6e55 into stagingFeb 4, 2026
11 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/lock branch February 4, 2026 03:15
royceP2 pushed a commit to arenadeveloper02/p2-sim that referenced this pull request Mar 3, 2026
* feat(canvas): added the ability to lock blocks
* unlock duplicates of locked blocks
* fix(duplicate): place duplicate outside locked container
When duplicating a block that's inside a locked loop/parallel,
the duplicate is now placed outside the container since nothing
should be added to a locked container.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(duplicate): unlock all blocks when duplicating workflow
- Server-side workflow duplication now sets locked: false for all blocks
- regenerateWorkflowStateIds also unlocks blocks for templates
- Client-side regenerateBlockIds already handled this (for paste/import)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix code block disabled state, allow unlock from editor
* fix(lock): address code review feedback
- Fix toggle enabled using first toggleable block, not first block
- Delete button now checks isParentLocked
- Lock button now has disabled state
- Editor lock icon distinguishes block vs parent lock state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): prevent unlocking blocks inside locked containers
- Editor: can't unlock block if parent container is locked
- Action bar: can't unlock block if parent container is locked
- Shows "Parent container is locked" tooltip in both cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): ensure consistent behavior across all UIs
Block Menu, Editor, Action Bar now all have identical behavior:
- Enable/Disable: disabled when locked OR parent locked
- Flip Handles: disabled when locked OR parent locked
- Delete: disabled when locked OR parent locked
- Remove from Subflow: disabled when locked OR parent locked
- Lock: always available for admins
- Unlock: disabled when parent is locked (unlock parent first)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(enable): consistent behavior - can't enable if parent disabled
Same pattern as lock: must enable parent container first before
enabling children inside it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(quick-reference): add lock block action
Added documentation for the lock/unlock block feature (admin only).
Note: Image placeholder added, pending actual screenshot.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* remove prefix square brackets in error notif
* add lock block image
* fix(block-menu): paste should not be disabled for locked selection
Paste creates new blocks, doesn't modify selected ones. Changed from
disableEdit (includes lock state) to !userCanEdit (permission only),
matching the Duplicate action behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(workflow): extract block deletion protection into shared utility
Extract duplicated block protection logic from workflow.tsx into
a reusable filterProtectedBlocks helper in utils/block-protection-utils.ts.
This ensures consistent behavior between context menu delete and
keyboard delete operations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(workflow): extend block protection utilities for edge protection
Add isEdgeProtected, filterUnprotectedEdges, and hasProtectedBlocks
utilities. Refactor workflow.tsx to use these helpers for:
- onEdgesChange edge removal filtering
- onConnect connection prevention
- onNodeDragStart drag prevention
- Keyboard edge deletion
- Block menu disableEdit calculation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): address review comments for lock feature
1. Store batchToggleEnabled now uses continue to skip locked blocks
entirely, matching database operation behavior
2. Copilot add operation now checks if parent container is locked
before adding nested nodes (defensive check for consistency)
3. Remove unused filterUnprotectedEdges function
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(copilot): add lock checks for insert and extract operations
- insert_into_subflow: Check if existing block being moved is locked
- extract_from_subflow: Check if block or parent subflow is locked
These operations now match the UI behavior where locked blocks
cannot be moved into/out of containers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): prevent duplicates inside locked containers via regenerateBlockIds
1. regenerateBlockIds now checks if existing parent is locked before
keeping the block inside it. If parent is locked, the duplicate
is placed outside (parentId cleared) instead of creating an
inconsistent state.
2. Remove unnecessary effectivePermissions.canAdmin and potentialParentId
from onNodeDragStart dependency array.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): fix toggle locked target state and draggable check
1. BATCH_TOGGLE_LOCKED now uses first block from blocksToToggle set
instead of blockIds[0], matching BATCH_TOGGLE_ENABLED pattern.
Also added early exit if blocksToToggle is empty.
2. Blocks inside locked containers are now properly non-draggable.
Changed draggable check from !block.locked to use isBlockProtected()
which checks both block lock and parent container lock.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(copilot): check parent lock in edit and delete operations
Both edit and delete operations now check if the block's parent
container is locked, not just if the block itself is locked. This
ensures consistent behavior with the UI which uses isBlockProtected
utility that checks both direct lock and parent lock.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(socket): add server-side lock validation and admin-only permissions
1. BATCH_TOGGLE_LOCKED now requires admin role - non-admin users with
write role can no longer bypass UI restriction via direct socket
messages
2. BATCH_REMOVE_BLOCKS now validates lock status server-side - filters
out protected blocks (locked or inside locked parent) before deletion
3. Remove duplicate/outdated comment in regenerateBlockIds
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test(socket): update permission test for admin-only lock toggle
batch-toggle-locked is now admin-only, so write role should be denied.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(undo-redo): use consistent target state for toggle redo
The redo logic for BATCH_TOGGLE_ENABLED and BATCH_TOGGLE_LOCKED was
incorrectly computing each block's new state as !previousStates[blockId].
However, the store's batchToggleEnabled/batchToggleLocked set ALL blocks
to the SAME target state based on the first block's previous state.
Now redo computes targetState = !previousStates[firstBlockId] and applies
it to all blocks, matching the store's behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(socket): add comprehensive lock validation across operations
Based on audit findings, adds lock validation to multiple operations:
1. BATCH_TOGGLE_HANDLES - now skips locked/protected blocks at:
- Store layer (batchToggleHandles)
- Collaborative hook (collaborativeBatchToggleBlockHandles)
- Server socket handler
2. BATCH_ADD_BLOCKS - server now filters blocks being added to
locked parent containers
3. BATCH_UPDATE_PARENT - server now:
- Skips protected blocks (locked or inside locked container)
- Prevents moving blocks into locked containers
All validations use consistent isProtected() helper that checks both
direct lock and parent container lock.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(workflow): use pre-computed lock state from contextMenuBlocks
contextMenuBlocks already has locked and isParentLocked properties
computed in use-canvas-context-menu.ts, so there's no need to look
up blocks again via hasProtectedBlocks.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): add lock validation to block rename operations
Defense-in-depth: although the UI disables rename for locked blocks,
the collaborative layer and server now also validate locks.
- collaborativeUpdateBlockName: checks if block is locked or inside
locked container before attempting rename
- UPDATE_NAME server handler: checks lock status and parent lock
before performing database update
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* added defense in depth for renaming locked blocks
* fix(socket): add server-side lock validation for edges and subblocks
Defense-in-depth: adds lock checks to server-side handlers that were
previously relying only on client-side validation.
Edge operations (ADD, REMOVE, BATCH_ADD, BATCH_REMOVE):
- Check if source or target blocks are protected before modifying edges
Subblock updates:
- Check if parent block is protected before updating subblock values
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): fetch parent blocks for edge protection checks and consistent tooltip
- Fixed edge operations to fetch parent blocks before checking lock status
- Previously, isBlockProtected checked if parent was locked, but the parent
wasn't in blocksById because only source/target blocks were fetched
- Now fetches parent blocks for all four edge operations: ADD, REMOVE,
BATCH_ADD_EDGES, BATCH_REMOVE_EDGES
- Fixed tooltip inconsistency: changed "Run previous blocks first" to
"Run upstream blocks first" in action-bar to match workflow.tsx
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* updated tooltip text for run from block
* fix(lock): add lock check to duplicate button and clean up drag handler
- Added lock check to duplicate button in action bar to prevent
duplicating locked blocks (consistent with other edit operations)
- Removed ineffective early return in onNodeDragStart since the
`draggable` property on nodes already prevents dragging protected
blocks - the early return was misleading as it couldn't actually
stop a drag operation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(lock): use disableEdit for duplicate in block menu
Changed duplicate menu item to use disableEdit (which includes lock
check) instead of !userCanEdit for consistency with action bar and
other edit operations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@waleedlatif1