@@ -507,7 +507,37 @@ async function handleBlocksOperationTx(
507507 } )
508508
509509 if ( blocks && blocks . length > 0 ) {
510- const blockValues = blocks . map ( ( block : Record < string , unknown > ) => {
510+ // Fetch existing blocks to check for locked parents
511+ const existingBlocks = await tx
512+ . select ( { id : workflowBlocks . id , locked : workflowBlocks . locked } )
513+ . from ( workflowBlocks )
514+ . where ( eq ( workflowBlocks . workflowId , workflowId ) )
515+
516+ type ExistingBlockRecord = ( typeof existingBlocks ) [ number ]
517+ const lockedParentIds = new Set (
518+ existingBlocks
519+ . filter ( ( b : ExistingBlockRecord ) => b . locked )
520+ . map ( ( b : ExistingBlockRecord ) => b . id )
521+ )
522+
523+ // Filter out blocks being added to locked parents
524+ const allowedBlocks = ( blocks as Array < Record < string , unknown > > ) . filter ( ( block ) => {
525+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
526+ | string
527+ | undefined
528+ if ( parentId && lockedParentIds . has ( parentId ) ) {
529+ logger . info ( `Skipping block ${ block . id } - parent ${ parentId } is locked` )
530+ return false
531+ }
532+ return true
533+ } )
534+
535+ if ( allowedBlocks . length === 0 ) {
536+ logger . info ( 'All blocks filtered out due to locked parents, skipping add' )
537+ break
538+ }
539+
540+ const blockValues = allowedBlocks . map ( ( block : Record < string , unknown > ) => {
511541 const blockId = block . id as string
512542 const mergedSubBlocks = mergeSubBlockValues (
513543 block . subBlocks as Record < string , unknown > ,
@@ -538,7 +568,7 @@ async function handleBlocksOperationTx(
538568 // Create subflow entries for loop/parallel blocks (skip if already in payload)
539569 const loopIds = new Set ( loops ? Object . keys ( loops ) : [ ] )
540570 const parallelIds = new Set ( parallels ? Object . keys ( parallels ) : [ ] )
541- for ( const block of blocks ) {
571+ for ( const block of allowedBlocks ) {
542572 const blockId = block . id as string
543573 if ( block . type === 'loop' && ! loopIds . has ( blockId ) ) {
544574 await tx . insert ( workflowSubflows ) . values ( {
@@ -567,7 +597,7 @@ async function handleBlocksOperationTx(
567597
568598 // Update parent subflow node lists
569599 const parentIds = new Set < string > ( )
570- for ( const block of blocks ) {
600+ for ( const block of allowedBlocks ) {
571601 const parentId = ( block . data as Record < string , unknown > ) ?. parentId as string | undefined
572602 if ( parentId ) {
573603 parentIds . add ( parentId )
@@ -838,22 +868,53 @@ async function handleBlocksOperationTx(
838868
839869 logger . info ( `Batch toggling handles for ${ blockIds . length } blocks in workflow ${ workflowId } ` )
840870
841- const blocks = await tx
842- . select ( { id : workflowBlocks . id , horizontalHandles : workflowBlocks . horizontalHandles } )
871+ // Fetch all blocks to check lock status and filter out protected blocks
872+ const allBlocks = await tx
873+ . select ( {
874+ id : workflowBlocks . id ,
875+ horizontalHandles : workflowBlocks . horizontalHandles ,
876+ locked : workflowBlocks . locked ,
877+ data : workflowBlocks . data ,
878+ } )
843879 . from ( workflowBlocks )
844- . where ( and ( eq ( workflowBlocks . workflowId , workflowId ) , inArray ( workflowBlocks . id , blockIds ) ) )
880+ . where ( eq ( workflowBlocks . workflowId , workflowId ) )
845881
846- for ( const block of blocks ) {
882+ type HandleBlockRecord = ( typeof allBlocks ) [ number ]
883+ const blocksById : Record < string , HandleBlockRecord > = Object . fromEntries (
884+ allBlocks . map ( ( b : HandleBlockRecord ) => [ b . id , b ] )
885+ )
886+
887+ // Helper to check if a block is protected (locked or inside locked parent)
888+ const isProtected = ( blockId : string ) : boolean => {
889+ const block = blocksById [ blockId ]
890+ if ( ! block ) return false
891+ if ( block . locked ) return true
892+ const parentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
893+ | string
894+ | undefined
895+ if ( parentId && blocksById [ parentId ] ?. locked ) return true
896+ return false
897+ }
898+
899+ // Filter to only toggle handles on unprotected blocks
900+ const blocksToToggle = blockIds . filter ( ( id ) => blocksById [ id ] && ! isProtected ( id ) )
901+ if ( blocksToToggle . length === 0 ) {
902+ logger . info ( 'All requested blocks are protected, skipping handles toggle' )
903+ break
904+ }
905+
906+ for ( const blockId of blocksToToggle ) {
907+ const block = blocksById [ blockId ]
847908 await tx
848909 . update ( workflowBlocks )
849910 . set ( {
850911 horizontalHandles : ! block . horizontalHandles ,
851912 updatedAt : new Date ( ) ,
852913 } )
853- . where ( and ( eq ( workflowBlocks . id , block . id ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
914+ . where ( and ( eq ( workflowBlocks . id , blockId ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
854915 }
855916
856- logger . debug ( `Batch toggled handles for ${ blocks . length } blocks` )
917+ logger . debug ( `Batch toggled handles for ${ blocksToToggle . length } blocks` )
857918 break
858919 }
859920
@@ -930,19 +991,54 @@ async function handleBlocksOperationTx(
930991
931992 logger . info ( `Batch updating parent for ${ updates . length } blocks in workflow ${ workflowId } ` )
932993
994+ // Fetch all blocks to check lock status
995+ const allBlocks = await tx
996+ . select ( {
997+ id : workflowBlocks . id ,
998+ locked : workflowBlocks . locked ,
999+ data : workflowBlocks . data ,
1000+ } )
1001+ . from ( workflowBlocks )
1002+ . where ( eq ( workflowBlocks . workflowId , workflowId ) )
1003+
1004+ type ParentBlockRecord = ( typeof allBlocks ) [ number ]
1005+ const blocksById : Record < string , ParentBlockRecord > = Object . fromEntries (
1006+ allBlocks . map ( ( b : ParentBlockRecord ) => [ b . id , b ] )
1007+ )
1008+
1009+ // Helper to check if a block is protected (locked or inside locked parent)
1010+ const isProtected = ( blockId : string ) : boolean => {
1011+ const block = blocksById [ blockId ]
1012+ if ( ! block ) return false
1013+ if ( block . locked ) return true
1014+ const currentParentId = ( block . data as Record < string , unknown > | null ) ?. parentId as
1015+ | string
1016+ | undefined
1017+ if ( currentParentId && blocksById [ currentParentId ] ?. locked ) return true
1018+ return false
1019+ }
1020+
9331021 for ( const update of updates ) {
9341022 const { id, parentId, position } = update
9351023 if ( ! id ) continue
9361024
1025+ // Skip protected blocks (locked or inside locked container)
1026+ if ( isProtected ( id ) ) {
1027+ logger . info ( `Skipping block ${ id } parent update - block is protected` )
1028+ continue
1029+ }
1030+
1031+ // Skip if trying to move into a locked container
1032+ if ( parentId && blocksById [ parentId ] ?. locked ) {
1033+ logger . info ( `Skipping block ${ id } parent update - target parent ${ parentId } is locked` )
1034+ continue
1035+ }
1036+
9371037 // Fetch current parent to update subflow node lists
938- const [ existing ] = await tx
939- . select ( {
940- id : workflowBlocks . id ,
941- parentId : sql < string | null > `${ workflowBlocks . data } ->>'parentId'` ,
942- } )
943- . from ( workflowBlocks )
944- . where ( and ( eq ( workflowBlocks . id , id ) , eq ( workflowBlocks . workflowId , workflowId ) ) )
945- . limit ( 1 )
1038+ const existing = blocksById [ id ]
1039+ const existingParentId = ( existing ?. data as Record < string , unknown > | null ) ?. parentId as
1040+ | string
1041+ | undefined
9461042
9471043 if ( ! existing ) {
9481044 logger . warn ( `Block ${ id } not found for batch-update-parent` )
@@ -987,8 +1083,8 @@ async function handleBlocksOperationTx(
9871083 await updateSubflowNodeList ( tx , workflowId , parentId )
9881084 }
9891085 // If the block had a previous parent, update that parent's node list as well
990- if ( existing ?. parentId && existing . parentId !== parentId ) {
991- await updateSubflowNodeList ( tx , workflowId , existing . parentId )
1086+ if ( existingParentId && existingParentId !== parentId ) {
1087+ await updateSubflowNodeList ( tx , workflowId , existingParentId )
9921088 }
9931089 }
9941090
0 commit comments