diff --git a/RELEASING.md b/RELEASING.md index 8dcb7f5..42ed61a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -61,6 +61,14 @@ make some moves, force-quit from the task switcher and reopen — everything should come back, paused. That is the check that proves the app's storage is working, and it fails silently rather than loudly. +**Let the turn run at least 40 seconds before you quit.** This is the whole +check, and doing it any faster proves nothing: for the first 12 seconds of a +turn the delay absorbs everything and the reserve clock is *supposed* to sit +still, so a clock that has lost its place looks exactly like one that hasn't. +Quit well past the delay and the time must come back lower than it was at the +turn switch — and the delay counter must not be sitting at a full 12. That is +how a broken save hid from this check from 1.0 to 1.3. + ## 5. Commit, tag, push ```bash diff --git a/android/app/src/main/java/nl/hansdezwart/bgclock/MainActivity.java b/android/app/src/main/java/nl/hansdezwart/bgclock/MainActivity.java index 0305e84..7a89c5a 100644 --- a/android/app/src/main/java/nl/hansdezwart/bgclock/MainActivity.java +++ b/android/app/src/main/java/nl/hansdezwart/bgclock/MainActivity.java @@ -121,8 +121,15 @@ public class MainActivity extends Activity { } } - // forwarded so the page's visibilitychange fires — that is what calls - // saveGame() and nudges the audio context back after backgrounding + // Forwarded so the WebView stops its timers, animations and audio while the + // app is in the background, and picks them up again on return. + // + // This does NOT drive the page's visibilitychange — an earlier comment here + // claimed it did, and the saved match quietly depended on that being true. + // Document visibility follows the WebView's view and window visibility, not + // this call, so the page can be killed without ever hearing about it. The + // page no longer relies on being told: it writes the match to localStorage + // once a second while a clock is running. @Override protected void onPause() { super.onPause(); diff --git a/public_html/index.html b/public_html/index.html index e71940e..ad70ed4 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -1051,11 +1051,26 @@ if(st.phase === PAUSE && st.active < 0) st.phase = IDLE; // can't be mid-turn with no turn } - // the only hooks iOS reliably fires before killing an app + // The hooks iOS fires before killing an app. Android's WebView is the problem + // child: neither of these is guaranteed there, whatever MainActivity's + // onPause() forwarding suggests, and when they don't fire the newest save is + // the one tap() wrote — which carries held ≈ 0, because tap() sets turnAt and + // saves in the same breath. Reopening then hands back the entire turn, and + // the bug hides in plain sight: quit inside the delay window and a clock that + // correctly hasn't moved looks exactly like a clock that lost its place. document.addEventListener("visibilitychange", function(){ if(document.visibilityState === "hidden") saveGame(); }); window.addEventListener("pagehide", saveGame); + // a different signal from visibilitychange, and it does fire when Android's + // task switcher opens. saveGame() is idempotent, so an extra call is free + window.addEventListener("blur", saveGame); + + // and the one that doesn't depend on being told anything: a second is the + // most a force-quit can now cost, which is well inside the delay nobody is + // charged for. A timer rather than a hook in loop(), because rAF stops when + // the page is hidden — exactly when this matters most. + setInterval(function(){ if(st.phase === RUN) saveGame(); }, 1000); /* Reset is a hold, not a tap. It used to be two taps inside 2s, and a test user found the button, tapped it once and gave up — which is exactly what