@@ -668,12 +668,7 @@ func traverseFlow(
668668 emitObjectAnnotations (obj , lines , indentStr , annotationsByTarget , flowsByOrigin , flowsByDest , activityMap )
669669 emitEnumSplitStatement (ctx , currentID , mergeID , variable , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
670670 recordSourceMap (sourceMap , currentID , startLine , len (* lines )+ headerLineCount - 1 )
671- if mergeID != "" {
672- visited [mergeID ] = true
673- for _ , flow := range flowsByOrigin [mergeID ] {
674- traverseFlow (ctx , flow .DestinationID , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
675- }
676- }
671+ continueAfterSplitJoin (ctx , mergeID , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
677672 return
678673 }
679674 if stmt != "" {
@@ -831,12 +826,7 @@ func traverseFlowUntilMerge(
831826 emitObjectAnnotations (obj , lines , indentStr , annotationsByTarget , flowsByOrigin , flowsByDest , activityMap )
832827 emitEnumSplitStatement (ctx , currentID , nestedMergeID , variable , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
833828 recordSourceMap (sourceMap , currentID , startLine , len (* lines )+ headerLineCount - 1 )
834- if nestedMergeID != "" && nestedMergeID != mergeID {
835- visited [nestedMergeID ] = true
836- for _ , flow := range flowsByOrigin [nestedMergeID ] {
837- traverseFlowUntilMerge (ctx , flow .DestinationID , mergeID , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
838- }
839- }
829+ continueAfterNestedSplitJoin (ctx , nestedMergeID , mergeID , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
840830 return
841831 }
842832 if stmt != "" {
@@ -845,10 +835,11 @@ func traverseFlowUntilMerge(
845835 }
846836
847837 trueFlow , falseFlow := findBranchFlows (flows )
838+ nestedMergeID = resolveNestedMergeID (nestedMergeID , mergeID , trueFlow , falseFlow , flowsByOrigin )
848839
849840 // Empty-then swap: negate when true branch is empty but false branch has content.
850841 // Skip when both branches go directly to merge (both empty).
851- if trueFlow != nil && falseFlow != nil && nestedMergeID != "" {
842+ if trueFlow != nil && falseFlow != nil && nestedMergeID != "" && nestedMergeID == mergeID {
852843 if trueFlow .DestinationID == nestedMergeID && falseFlow .DestinationID != nestedMergeID {
853844 stmt = negateIfCondition (stmt )
854845 trueFlow , falseFlow = falseFlow , trueFlow
@@ -1000,6 +991,63 @@ func continueAfterNestedSplitJoin(
1000991 traverseFlowUntilMerge (ctx , joinID , parentMergeID , activityMap , flowsByOrigin , flowsByDest , splitMergeMap , visited , entityNames , microflowNames , lines , indent , sourceMap , headerLineCount , annotationsByTarget )
1001992}
1002993
994+ func resolveNestedMergeID (
995+ nestedMergeID model.ID ,
996+ parentMergeID model.ID ,
997+ trueFlow * microflows.SequenceFlow ,
998+ falseFlow * microflows.SequenceFlow ,
999+ flowsByOrigin map [model.ID ][]* microflows.SequenceFlow ,
1000+ ) model.ID {
1001+ if nestedMergeID != "" && parentMergeID != "" && nestedMergeID != parentMergeID &&
1002+ canReachNode (parentMergeID , nestedMergeID , flowsByOrigin , make (map [model.ID ]bool )) {
1003+ for _ , flow := range []* microflows.SequenceFlow {trueFlow , falseFlow } {
1004+ if flow == nil {
1005+ continue
1006+ }
1007+ if flow .DestinationID == parentMergeID ||
1008+ canReachNode (flow .DestinationID , parentMergeID , flowsByOrigin , make (map [model.ID ]bool )) {
1009+ return parentMergeID
1010+ }
1011+ }
1012+ }
1013+ if nestedMergeID != "" || parentMergeID == "" {
1014+ return nestedMergeID
1015+ }
1016+ for _ , flow := range []* microflows.SequenceFlow {trueFlow , falseFlow } {
1017+ if flow == nil {
1018+ continue
1019+ }
1020+ if canReachNode (flow .DestinationID , parentMergeID , flowsByOrigin , make (map [model.ID ]bool )) {
1021+ return parentMergeID
1022+ }
1023+ }
1024+ return ""
1025+ }
1026+
1027+ func canReachNode (
1028+ currentID model.ID ,
1029+ targetID model.ID ,
1030+ flowsByOrigin map [model.ID ][]* microflows.SequenceFlow ,
1031+ visited map [model.ID ]bool ,
1032+ ) bool {
1033+ if currentID == "" {
1034+ return false
1035+ }
1036+ if currentID == targetID {
1037+ return true
1038+ }
1039+ if visited [currentID ] {
1040+ return false
1041+ }
1042+ visited [currentID ] = true
1043+ for _ , flow := range findNormalFlows (flowsByOrigin [currentID ]) {
1044+ if canReachNode (flow .DestinationID , targetID , flowsByOrigin , visited ) {
1045+ return true
1046+ }
1047+ }
1048+ return false
1049+ }
1050+
10031051// traverseLoopBody traverses activities inside a loop body.
10041052// When sourceMap is non-nil, it also records line ranges for each activity node.
10051053func traverseLoopBody (
@@ -1421,10 +1469,13 @@ func flowLooksLikeGuardContinuation(
14211469 return false
14221470 }
14231471 // Builder-generated guard continuations sit on the split's horizontal
1424- // centerline. This intentionally relies on mxcli's layout contract so a
1425- // real branch that returns to a merge below the split is not collapsed into
1426- // a guard-style continuation during describe.
1427- return dest .GetPosition ().Y == split .GetPosition ().Y
1472+ // centerline and use the builder's horizontal split→tail flow. This
1473+ // intentionally relies on mxcli's layout/anchor contract so a real false
1474+ // branch whose activities happen to be aligned with the split is not
1475+ // collapsed into a guard-style continuation during describe.
1476+ return dest .GetPosition ().Y == split .GetPosition ().Y &&
1477+ flow .OriginConnectionIndex == AnchorRight &&
1478+ flow .DestinationConnectionIndex == AnchorLeft
14281479}
14291480
14301481// findErrorHandlerFlow returns the error handler flow from an activity's outgoing flows.
0 commit comments