I had the exact same problem, and by seeing your code I might say that you have the same cause, but let's break it down.
Checking
First, let's check that my comment is relevant, and I can actually help you. Comment the remote param on your email validation set up:
"data[User][email]": {
required: true,
email: true
},
Is your problem fixed? Great, keep reading (feel free to skip to the fix section).
The problem
1. When the plugin validates, it creates a list of errors, stored into an array called "errorList".
2. Have you ever used the showErrors functionality? It's there to show all the errors, but also to target-show errors. If you want to show specific errors, or to show errors that are out of the limits of the plugin (ej.: a 60s timeout has expired), you can use that method.
3. When showing specific errors, what that method does is to add the specified error(s) to the errorList.
4. The problem is that before adding new errors that list is cleared up (I didn't write the code, but it seems that it's done in order to keep that list nice and clean, and not having two different errors of the same input).
5. Now, when the email is checked remotely we are in the same situation of a timeout. So it uses the showErrors functionality, and that means that the form is validated when click, and some seconds later (with the PHP response), the email error is shown, but clearing up the errorList. That's what is happening.
The fix
If you are not going to do explicit use of showErrors, truth is that you can comment the line where the errorList is cleared up:
showErrors: function( errors ) { if ( errors ) { // add items to error list and map $.extend( this.errorMap, errors ); //this.errorList = []; for ( var name in errors ) { ...
If you are going to do an explicit use of that method, you can try this version instead. Doesn't clear the error list, but checks that you're not adding the same error twice:
showErrors: function( errors ) { if ( errors ) { // add items to error list and map $.extend( this.errorMap, errors ); for ( var name in errors ) { var tempElem = this.findByName(name)[0]; this.errorList = jQuery.grep(this.errorList, function( error, i ) { return error.element != tempElem; }); this.errorList.push({ message: errors[name], element: tempElem }); }
Let me know if worked or you have any problem.