From 7033288bc4686f6b71300d6c651a3e7d3f8e1ed8 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 27 Oct 2026 22:26:20 +0000 Subject: [PATCH] Hold the slides for the whole turn, not until it first pauses The slides are held at the size a turn is heading for so the picture steps once, straight into the shape it keeps, instead of being re-letterboxed through every shape the viewport passes on its way round. The hold came off once the height had gone six frames unchanged, which stood in for "the viewport has stopped moving". It does not: a turn arrives in steps, and the gap between two of them is routinely longer than six frames -- on a phone and in a loaded tab alike. So the pin came off mid-turn, the slides took an intermediate shape, and the picture jumped twice on the way round. That is the stutter the pin exists to remove, and TURN_HOLD_FRAMES was meant to be "long enough that a viewport animating round in steps is not mistaken for one that has arrived" -- six frames is not. The device does say when a turn is underway, so believe it: while the announcement stands the pin holds, and the frame count is left to end the pins nothing announced, where there is no signal to go on and no turn to ruin. The realign window has to stop concluding mid-turn too, since closing it releases the pin by the other door; SETTLE_MAX_MS outlasts the announcement and still guarantees both of them end. The cost is that an announced turn keeps the old size until the announcement lapses rather than letting go the moment the viewport looks still. That buys the one step. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MPZFnNdHbPGDTqQUNiE4ZN --- frontend/js/feed.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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;