mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 10:31:46 +00:00
ruler: re-arm the snap on real input only — the velocity check leaked
The arming invariant had a hole: 'snapping' clears early (the moment no word is mid-flip, which happens mid-animation), so the velocity-based re-arm read the snap's own animation tail as organic scrolling and let the ping-pong chain re-arm itself. Velocity can't distinguish a snap's motion from the user's. Re-arming now lives exclusively on unambiguous input events — wheel, touchmove, keydown, and rail thumb drags. Programmatic scrolls produce none of these, so after a heading jump or a snap the system stays disarmed until the user actually acts. Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
This commit is contained in:
1 parent
937bcefa68
commit
2cf8a65c7f
1 file changed
+19
-10
@@ -512,16 +512,12 @@ export default function HeadingFlow({
|
||||
const midIdx = phases.findIndex(
|
||||
(ph) => ph.flipP > 0.04 && ph.flipP < 0.96,
|
||||
);
|
||||
// Re-arm only on organic scroll: meaningful velocity while no
|
||||
// programmatic animation (jump or snap) is driving the page.
|
||||
if (
|
||||
!st.armed &&
|
||||
!st.snapping &&
|
||||
!st.jumping &&
|
||||
Math.abs(st.vel) > 40
|
||||
) {
|
||||
st.armed = true;
|
||||
}
|
||||
// Re-arming lives on real input events (see the listeners below) —
|
||||
// NEVER on velocity: `snapping` clears early once no word is
|
||||
// mid-flip, while the snap's scroll is still moving, so a velocity
|
||||
// check mistakes the snap's own tail for user scrolling and lets
|
||||
// snap chains re-arm themselves. Rail thumb drags count as intent.
|
||||
if (rulerZoom.dragging) st.armed = true;
|
||||
if (st.snapping) {
|
||||
if (midIdx < 0) st.snapping = false;
|
||||
} else if (
|
||||
@@ -566,8 +562,21 @@ export default function HeadingFlow({
|
||||
}
|
||||
};
|
||||
|
||||
// Organic input is the ONLY thing that re-arms the snap. Wheel, touch,
|
||||
// and keyboard are unambiguous user intent; every programmatic scroll
|
||||
// (jump, snap) produces none of these.
|
||||
const rearm = () => {
|
||||
fs.current.armed = true;
|
||||
};
|
||||
window.addEventListener("wheel", rearm, { passive: true });
|
||||
window.addEventListener("touchmove", rearm, { passive: true });
|
||||
window.addEventListener("keydown", rearm);
|
||||
|
||||
gsap.ticker.add(tick);
|
||||
return () => {
|
||||
window.removeEventListener("wheel", rearm);
|
||||
window.removeEventListener("touchmove", rearm);
|
||||
window.removeEventListener("keydown", rearm);
|
||||
gsap.ticker.remove(tick);
|
||||
};
|
||||
}, [mode, metrics, sections, geom, side, layoutVersion]);
|
||||
|
||||
Reference in new issue
Block a user