From 6907c8873651e2a936ee982c2e9e915c77d0dfe9 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 31 Jan 2026 17:33:02 -0800 Subject: [PATCH] unlock duplicates of locked blocks --- .../[workspaceId]/w/[workflowId]/workflow.tsx | 7 +++--- apps/sim/stores/workflows/utils.test.ts | 22 +++++++++---------- apps/sim/stores/workflows/utils.ts | 3 ++- .../stores/workflows/workflow/store.test.ts | 8 +++---- apps/sim/stores/workflows/workflow/store.ts | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index 1148f966b..649271070 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -2980,11 +2980,12 @@ const WorkflowContent = React.memo(() => { // Don't process parent changes if the node hasn't actually changed parent or is being moved within same parent if (potentialParentId === dragStartParentId) return - // Prevent moving blocks out of locked containers - if (dragStartParentId && blocks[dragStartParentId]?.locked) { + // Prevent moving locked blocks out of locked containers + // Unlocked blocks (e.g., duplicates) can be moved out freely + if (dragStartParentId && blocks[dragStartParentId]?.locked && blocks[node.id]?.locked) { addNotification({ level: 'info', - message: 'Cannot move blocks out of locked containers', + message: 'Cannot move locked blocks out of locked containers', workflowId: activeWorkflowId || undefined, }) setPotentialParentId(dragStartParentId) // Reset to original parent diff --git a/apps/sim/stores/workflows/utils.test.ts b/apps/sim/stores/workflows/utils.test.ts index ef5936342..4f2fc5165 100644 --- a/apps/sim/stores/workflows/utils.test.ts +++ b/apps/sim/stores/workflows/utils.test.ts @@ -433,7 +433,7 @@ describe('regenerateBlockIds', () => { expect(duplicatedBlock.data?.parentId).toBe(loopId) }) - it('should preserve locked state when pasting a locked block', () => { + it('should unlock pasted block when source is locked', () => { const blockId = 'block-1' const blocksToCopy = { @@ -459,11 +459,12 @@ describe('regenerateBlockIds', () => { const newBlocks = Object.values(result.blocks) expect(newBlocks).toHaveLength(1) + // Pasted blocks are always unlocked so users can edit them const pastedBlock = newBlocks[0] - expect(pastedBlock.locked).toBe(true) + expect(pastedBlock.locked).toBe(false) }) - it('should preserve unlocked state when pasting an unlocked block', () => { + it('should keep pasted block unlocked when source is unlocked', () => { const blockId = 'block-1' const blocksToCopy = { @@ -493,20 +494,20 @@ describe('regenerateBlockIds', () => { expect(pastedBlock.locked).toBe(false) }) - it('should preserve mixed locked states when pasting multiple blocks', () => { + it('should unlock all pasted blocks regardless of source locked state', () => { const lockedId = 'locked-1' const unlockedId = 'unlocked-1' const blocksToCopy = { [lockedId]: createAgentBlock({ id: lockedId, - name: 'Locked Agent', + name: 'Originally Locked Agent', position: { x: 100, y: 50 }, locked: true, }), [unlockedId]: createFunctionBlock({ id: unlockedId, - name: 'Unlocked Function', + name: 'Originally Unlocked Function', position: { x: 200, y: 50 }, locked: false, }), @@ -526,10 +527,9 @@ describe('regenerateBlockIds', () => { const newBlocks = Object.values(result.blocks) expect(newBlocks).toHaveLength(2) - const lockedBlock = newBlocks.find((b) => b.name.includes('Locked')) - const unlockedBlock = newBlocks.find((b) => b.name.includes('Unlocked')) - - expect(lockedBlock?.locked).toBe(true) - expect(unlockedBlock?.locked).toBe(false) + // All pasted blocks should be unlocked so users can edit them + for (const block of newBlocks) { + expect(block.locked).toBe(false) + } }) }) diff --git a/apps/sim/stores/workflows/utils.ts b/apps/sim/stores/workflows/utils.ts index 2657c4b6c..b1d3805fa 100644 --- a/apps/sim/stores/workflows/utils.ts +++ b/apps/sim/stores/workflows/utils.ts @@ -482,7 +482,8 @@ export function regenerateBlockIds( position: newPosition, // Temporarily keep data as-is, we'll fix parentId in second pass data: block.data ? { ...block.data } : block.data, - // locked state is preserved via spread (same as Figma) + // Duplicated blocks are always unlocked so users can edit them + locked: false, } newBlocks[newId] = newBlock diff --git a/apps/sim/stores/workflows/workflow/store.test.ts b/apps/sim/stores/workflows/workflow/store.test.ts index ed32623f1..106dc15a1 100644 --- a/apps/sim/stores/workflows/workflow/store.test.ts +++ b/apps/sim/stores/workflows/workflow/store.test.ts @@ -888,7 +888,7 @@ describe('workflow store', () => { }) describe('duplicateBlock with locked', () => { - it('should preserve locked state when duplicating a locked block', () => { + it('should unlock duplicate when duplicating a locked block', () => { const { addBlock, setBlockLocked, duplicateBlock } = useWorkflowStore.getState() addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 }) @@ -909,12 +909,12 @@ describe('workflow store', () => { if (duplicatedId) { // Original should still be locked expect(blocks.original.locked).toBe(true) - // Duplicate should also be locked (preserves state like Figma) - expect(blocks[duplicatedId].locked).toBe(true) + // Duplicate should be unlocked so users can edit it + expect(blocks[duplicatedId].locked).toBe(false) } }) - it('should preserve unlocked state when duplicating an unlocked block', () => { + it('should create unlocked duplicate when duplicating an unlocked block', () => { const { addBlock, duplicateBlock } = useWorkflowStore.getState() addBlock('original', 'agent', 'Original Agent', { x: 0, y: 0 }) diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 4235996ba..3d11dc3f0 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -709,7 +709,7 @@ export const useWorkflowStore = create()( name: newName, position: offsetPosition, subBlocks: newSubBlocks, - // locked state is preserved via spread (same as Figma) + locked: false, }, }, edges: [...get().edges],