8 Commits
Author SHA1 Message Date
hansdezwartandClaude Opus 5 7f1b246679 Release 1.4
versionCode 5. public_html/ changed, so the service worker cache goes to
bgclock-v20.

Confirmed on device: a turn left running well past the delay, force-quit and
reopened, now comes back with the time it had actually spent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 15:07:26 +02:00
hansdezwartandClaude Opus 5 3a7eb8c612 Save the match every second instead of waiting to be told
A force-quit deep into a turn came back at the clock's last-turn-switch value,
losing the whole interrupted turn. The tell was the delay counter returning at
a full 12: the newest saved state was the one tap() writes, and tap() sets
turnAt and calls saveGame() in the same breath, so it carries held ~ 0.
Restoring it reproduces the moment of the turn switch exactly.

The two triggers meant to save at quit time — visibilitychange and pagehide —
do not fire in Android's WebView. MainActivity's onPause() forwards to
web.onPause() under a comment claiming that is what makes visibilitychange
fire. It isn't: document visibility follows the WebView's view and window
visibility, not that call. The forwarding is still right for stopping timers
and audio, but the saved match should never have depended on it.

This is not new. It has almost certainly been broken since 1.0, and the release
check could not catch it, because for the first 12 seconds of a turn the delay
absorbs everything and render() never touches st.reserve. A clock that has lost
its place is indistinguishable from one that is correctly sitting still, so
every force-quit test that quit soon after a turn change passed. RELEASING.md
now says to let the turn run 40 seconds first, which is the only version of
that check worth running.

So the page stops waiting to be told. A timer writes the match once a second
while a clock is running — a timer rather than a hook in loop(), because
requestAnimationFrame stops when the page is hidden, which is exactly when this
matters. blur joins the listeners too: a different signal, and one that does
fire when the task switcher opens. At most a second can now be lost, which is
well inside the delay nobody is charged for anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 15:07:18 +02:00
hansdezwartandClaude Opus 5 bf3f565dd0 Release 1.3
versionCode 4. public_html/ changed, so the service worker cache goes to
bgclock-v19 — without it an installed web user would keep serving the old page
from cache indefinitely.

The five store screenshots are untouched: the ring only exists while a finger
is on the button, and none of them shows that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 14:51:14 +02:00
hansdezwartandClaude Opus 5 b31087223a Release 1.2
versionCode 3. public_html/ changed this time, so the service worker cache is
bumped to bgclock-v18 — without it an installed web user would keep serving
the old page from cache indefinitely.

Nothing to do in fdroiddata. The recipe landed with the 1.0 submission and
carries UpdateCheckMode: Tags and AutoUpdateMode: Version, so their bot finds
this tag, resolves it to a commit hash and writes the build entry itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:41:48 +02:00
hansdezwartandClaude Opus 5 ac144f2eb1 Release 1.1
versionCode 2. The one user-visible change since 1.0 is the navigation bar
staying on screen; public_html/ is untouched, so the service worker cache
keeps its name and no web user sees a different page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 11:13:55 +02:00
hansdezwartandClaude Opus 5 862b029953 Keep the navigation bar; hide only the status bar
hideSystemBars() asked for systemBars(), which is the union of the two, so
the way out of the app was an invisible swipe you had to remember.
statusBars() leaves the navigation bar where it is: still fullscreen at the
top, but leaving is now an affordance you can see. On three-button
navigation it also owns the bottom strip outright, so those taps never
reach the panel at all — the lift-not-press rule still carries the gesture
case, where the pill is only drawn over the app.

The pre-Android-11 branch loses HIDE_NAVIGATION, LAYOUT_HIDE_NAVIGATION and
IMMERSIVE_STICKY along with it; immersive only governs a navigation bar
that is hidden, and this one isn't.

The layout needed nothing: .panel--bottom .inner already pads by
max(14px, env(safe-area-inset-bottom)), so the readout lifts clear of the
bar by itself. Confirmed on device.

Not tagged. This rides along with whatever else goes into 1.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:07:24 +02:00
hansdezwartandClaude Opus 5 e4a53a847f Don't pin buildToolsVersion
fdroidserver forces its own build-tools version into app builds, but that
rewrite only matches files ending .gradle — not .gradle.kts. So the pin
would have survived onto their buildserver, and if it could not get
build-tools 36.0.0 the build would fail rather than fall back. AGP picks
its own default instead. Clean rebuild is unaffected: still builds, still
zero permissions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:20:40 +02:00
hansdezwartandClaude Opus 5 9f2081a0c9 Add the Android app: a WebView around public_html
A single Activity holding one WebView, in Java rather than Kotlin — for
~120 lines of setup Kotlin would only add its Gradle plugin, its stdlib
in the APK and a third version to keep aligned with Gradle and AGP. One
dependency: androidx.webkit 1.16.0.

The assets are served from https://appassets.androidplatform.net through
WebViewAssetLoader rather than file://. That is the part that matters: an
https origin is a secure context, and localStorage — which holds every
setting and the match in progress — is blocked on file://, so a saved
game would silently vanish. Confirmed on device: force-quit mid-match and
everything comes back.

public_html stays the only copy of the web app. A task syncs it into the
assets at build time, so the browser version and the app version cannot
drift; the APK's index.html hashes identical to the working tree. sw.js
is left out on purpose — it is cache-first, and a service-worker cache
outlives an app upgrade, so it would serve a stale index.html with no way
to clear it. The APK is the cache now.

Three web APIs don't exist in a WebView, so the Activity supplies them:
FLAG_KEEP_SCREEN_ON for navigator.wakeLock, native immersive mode for
requestFullscreen(), and LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES so the
panels' env(safe-area-inset-*) has something to report. textZoom is
pinned to 100: the layout is built on clamp() and vw, and a large system
font would scale the text inside it.

The manifest declares no permissions whatsoever.

Wiring note: AGP 9 refuses a Provider in sourceSets.assets.srcDir, since
it can't tell generated from static directories. The sync is a task with
a DirectoryProperty output, attached via androidComponents.onVariants,
which is the supported path and carries the task dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:10:38 +02:00