diff --git a/frontend/js/feed.js b/frontend/js/feed.js index 03e5937..b6d6f85 100644 --- a/frontend/js/feed.js +++ b/frontend/js/feed.js @@ -1492,15 +1492,29 @@ App.feed = App.feed || {}; if (!turnPin && !turnResolved && isTurn(settleFromShape, shape)) { pinTurn(settleFromShape); } + // A turn the device announced is not over because the viewport paused. + // It arrives in steps, and the gap between two of them is routinely + // longer than the handful of frames that stands in for "stopped" -- + // which is how the pin came off mid-turn and let the picture take an + // intermediate shape, the one thing it is here to prevent. So while + // the announcement stands the pin holds, and the frame count is left + // to end the pins nothing announced: a window dragged across the + // square, where there is no signal to go on and no turn to ruin. + const announced = Date.now() - turnSignalAt < TURN_SIGNAL_MS; // The viewport has stopped moving, so what it is now is better than // what the pin guessed it would be. Letting go here, with the realign // below in the same frame, is the one step the picture takes. - if (turnPin && settleStableFrames >= TURN_HOLD_FRAMES) { + if (turnPin && !announced && settleStableFrames >= TURN_HOLD_FRAMES) { releaseTurn(); turnResolved = true; } realignToActive(); - if (settleStableFrames < SETTLE_STABLE_FRAMES && + // The window may not conclude mid-turn either: closing it releases the + // pin on the way out, which would put the picture through the same + // intermediate shape by the other door. An announced turn therefore + // holds the window open too, and SETTLE_MAX_MS -- longer than the + // announcement -- is what still guarantees it ends. + if ((announced || settleStableFrames < SETTLE_STABLE_FRAMES) && (Date.now() - settleStartedAt) < SETTLE_MAX_MS) { resizeSettleRaf = requestAnimationFrame(settleStep); return;