F-Droid's reviewer measured the score and move lines at 2.1:1 against the 4.5:1 normal text needs. Raising them past 14pt bold puts them under the 3:1 large-text bar instead, which is the only way ink still readable as "muted" can be conformant on a mid-tone accent: reaching 4.5:1 at the old size would have taken near-black labels on plum and slate, out-shouting the clock digits above them. Hence the 19px floor, with a comment saying so — lowering it breaks the contrast claim silently. Measuring turned up a second failure nobody had flagged: the active player's clock digits are white on the accent, and brass sat at 2.46:1 against the same 3:1 bar. Sage and brass are darkened just far enough to clear it, scaled in linear light so only lightness moves. Slate, teal and plum already passed and are untouched. Darkening further was tempting and wrong — it would have taken the headroom the muted labels need. --accent-mute was doing double duty as the delay bar's background, where it only ever agreed with the bar by accident. Splitting off --track keeps the bar pixel-identical: 234px wide, fill and unfilled segments unchanged. The delay number is right-aligned in a box exactly two digits wide. The bar and the number together now sit within a pixel of the panel's centre rather than 10px left of it, and the digit that changes every second stays put instead of sliding when the count drops out of double figures; the gap absorbs it. The settings sheet's Done button and preset chips are still white on accent at 15px, which needs 4.5:1 and gets 3.2:1. Known, and left alone: fixing them means 19px floors and visibly taller buttons. Screenshots come from tools/screenshots.py now instead of being made by hand. It seeds localStorage and lets the app render its own saved state, so scenes are reproducible. The traps are in its docstring and RELEASING.md — including one that cost an afternoon today: snap-confined Chromium cannot write into any hidden directory, and says only "Permission denied". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
6.0 KiB
Markdown
151 lines
6.0 KiB
Markdown
# Releasing a new version
|
||
|
||
The clock lives in two places: the web app in `public_html/`, which you host
|
||
yourself, and the Android app on F-Droid, which is built from a git tag.
|
||
|
||
**Everything F-Droid needs comes from a tag in this repository.** After the first
|
||
release was accepted there is nothing to do in F-Droid's own repo — their bot
|
||
watches the tags here and writes the build entry itself. Don't open a merge
|
||
request against `fdroiddata` for a new version.
|
||
|
||
Worked example below bumps 1.0 to 1.1. Substitute your own numbers.
|
||
|
||
## 1. Make the change
|
||
|
||
Both versions share one copy of the app. Edit `public_html/index.html` and the
|
||
Android build picks it up automatically — the Gradle build reads `public_html/`
|
||
directly rather than keeping a second copy, so the two can't drift.
|
||
|
||
If you changed anything visible, bump the service worker cache or installed web
|
||
users will keep the old page:
|
||
|
||
```
|
||
# public_html/sw.js
|
||
const CACHE = "bgclock-v18"; # any new value; it only has to differ
|
||
```
|
||
|
||
## 2. Bump the version
|
||
|
||
In `android/app/build.gradle.kts`:
|
||
|
||
```kotlin
|
||
versionCode = 2 // an integer, +1 every release, never reused
|
||
versionName = "1.1" // what people see
|
||
```
|
||
|
||
`versionCode` is what Android uses to decide one version is newer than another.
|
||
It must go up every single release, even for a one-character fix.
|
||
|
||
## 3. Write the changelog
|
||
|
||
A new file named after the **versionCode**, not the version name:
|
||
|
||
```
|
||
fastlane/metadata/android/en-US/changelogs/2.txt
|
||
```
|
||
|
||
Maximum 500 characters. This is what shows up in F-Droid's "What's New".
|
||
|
||
## 4. Build it and put it on your phone
|
||
|
||
```bash
|
||
export ANDROID_HOME="$HOME/Android/Sdk" # already in ~/.bashrc
|
||
cd android
|
||
./gradlew assembleDebug
|
||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||
./gradlew --stop # the daemon holds ~500 MB otherwise
|
||
```
|
||
|
||
Play a real game before tagging. Worth checking specifically: start a match,
|
||
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.
|
||
|
||
## 5. Commit, tag, push
|
||
|
||
```bash
|
||
git add -A
|
||
git commit -m "…"
|
||
git tag -a v1.1 -m "Backgammon Clock 1.1"
|
||
git push
|
||
git push origin v1.1
|
||
```
|
||
|
||
Keep tags in the `v1.1` shape for consistency, but nothing depends on it:
|
||
`UpdateCheckMode: Tags` with no pattern matches every tag whatever it's called,
|
||
and F-Droid's bot resolves whichever tag it finds to a commit hash before it
|
||
writes the build entry.
|
||
|
||
Never move or delete a tag that has been pushed — F-Droid may already have built
|
||
it. If a release is wrong, bump the version and release again. (The recipe pins
|
||
a hash, so a moved tag can't retroactively change a version they already built;
|
||
it would just leave your repo disagreeing with what's on people's phones.)
|
||
|
||
**If you ever write a build entry by hand**, its `commit:` must be a full 40-char
|
||
commit hash — never a tag, never a branch. Tags are mutable, so a tag there means
|
||
the thing F-Droid builds and signs can change under them, and they reject it on
|
||
sight. This came up on the very first submission and is easy to get wrong,
|
||
because `fdroid lint` doesn't check it under `UpdateCheckMode: Tags` — the
|
||
pipeline goes green and a human catches it days later. Get the hash with:
|
||
|
||
```bash
|
||
git rev-parse v1.1^{commit} # ^{commit}, or an annotated tag gives you the
|
||
# tag object's hash instead of the commit's
|
||
```
|
||
|
||
This doesn't come up in a normal release: the bot writes the entry, and the bot
|
||
writes hashes.
|
||
|
||
## 6. Wait
|
||
|
||
Their bot notices the new tag, adds a build entry, and the build server picks it
|
||
up on its next cycle. Expect a day or two. Nothing to do.
|
||
|
||
## Deploying the web version
|
||
|
||
Separate from all of the above, and manual: upload the six files in
|
||
`public_html/` — `index.html`, `manifest.webmanifest`, `sw.js`, `icon-192.png`,
|
||
`icon-512.png`, `icon-maskable-512.png` — to the web host. `README.md` and
|
||
`RELEASING.md` stay out of it; they live in the repo root for that reason.
|
||
|
||
## If the screenshots need redoing
|
||
|
||
```bash
|
||
python3 tools/screenshots.py
|
||
```
|
||
|
||
That rewrites all five in
|
||
`fastlane/metadata/android/en-US/images/phoneScreenshots/` at 1170×2532. They
|
||
are rendered from `public_html/index.html` with headless Chromium, not taken on
|
||
a phone, so they reproduce exactly — anything that changes the look of the app
|
||
is a reason to re-run it.
|
||
|
||
The script sets each scene by seeding `localStorage` before the app boots, so
|
||
the app renders its own saved state rather than having the DOM poked from
|
||
outside. Editing a scene means editing the `SCENES` table at the top.
|
||
|
||
Three traps, all of which fail quietly, and all of which the script already
|
||
handles — they are written down here because they cost an afternoon each:
|
||
|
||
- The capture lands whenever the virtual-time budget runs out, not when your
|
||
script finishes — so freeze the render loop (`requestAnimationFrame = () => 0`)
|
||
once the frame you want is on screen.
|
||
- CSS transitions don't advance under `--virtual-time-budget`, so a panel caught
|
||
mid-transition photographs in its *old* colour. Disable transitions in the
|
||
screenshot build.
|
||
- Snap-confined Chromium cannot write into hidden directories — not `~/.cache`,
|
||
not any dot-directory, not the private `/tmp` it is handed. It fails with a
|
||
bare "Permission denied" and no hint as to why. Both the HTML it reads and the
|
||
PNG it writes have to sit in a plainly-named directory.
|
||
|
||
## Reference
|
||
|
||
- App ID: `nl.hansdezwart.bgclock` — fixed forever; changing it makes a new app.
|
||
- F-Droid listing text: `fastlane/metadata/android/en-US/` (title ≤50 chars,
|
||
summary ≤80, description ≤4000, changelog ≤500). Only these HTML tags work in
|
||
the description: `b big blockquote br cite em i li ol small strike strong sub
|
||
sup tt u ul`.
|
||
- The original submission: https://gitlab.com/fdroid/fdroiddata/-/merge_requests/45478
|
||
- F-Droid signs the APK with their key, not yours. Reproducible builds were
|
||
declined at submission and can't be enabled later for this app ID.
|