M-2: route every SharedPreferences write (Settings/History/Subs/Resume) through
one PrefsWriter — a per-store single-thread dispatcher — so the on-disk apply()
order matches the in-memory CAS order. Previously a Main-thread toggle and an
IO-thread import could land apply() out of order, and ResumePositions detached
ordering entirely via a fresh globalScope.launch per write; a stale value could
then win the next cold-start load. Each write reads the live StateFlow so disk
converges to the latest in-memory state regardless of enqueue order.
L-14: Settings storage-usage sampling (File.length() x4 + Coil diskCache.size)
moved off the composition/Main thread into a LaunchedEffect on Dispatchers.IO.
L-2 / L-4..L-8 / L-15 / L-16: dead code + stale comments from the vc=85 SB/RYD
to Rust migration. Http.kt trimmed to STRAW_USER_AGENT; reconciled the
network_security_config / feed.rs / SubscriptionFeedViewModel / net.rs / CI
comments with reality; recencyScore overflow-guarded; ci/Dockerfile now
pre-installs build-tools 36 (AGP 9.2.1's actual floor, was auto-fetched).
Verified: headless compileDebugKotlin green on the straw-build image.
Gradle build is green (uses JAVA_HOME directly), but the signer-verify
step calls apksigner — a shell wrapper that needs 'java' on PATH. The
straw-build image sets JAVA_HOME without adding its bin to PATH for run
steps, so apksigner died with 'exec: java: not found'. Export it.
The runner's default shell for run: steps is dash, which errors
'Illegal option -o pipefail' the moment a step runs 'set -euo pipefail'
(the clone step, plus the pre-existing Verify + publish steps). Set
defaults.run.shell: bash for the job; the straw-build image ships bash.
The build-and-publish job runs in the straw-build container, which ships
the Android + Rust toolchain but NOT node. actions/checkout@v4 is a Node
action, so it died with 'exec: "node": not found' before any source was
checked out — every build run since the workflow landed was red for this,
not the registry-pull theory.
- Replace both actions/checkout@v4 steps with a plain 'git clone' (git is
in the image, both repos are public). Also sidesteps the runner's flaky
data.forgejo.org action fetch. strawcore stays a sibling of straw for
the rust/strawcore path dependency.
- Pick apksigner from whatever build-tools the image actually ships (36),
not the hardcoded 34.0.0 that doesn't exist in it.
Build + publish prereqs verified present: docker CLI in image, runner
docker_host=automount + --group-add, and the STRAW_SIGNING_KEYSTORE_B64 /
STRAW_FDROID_RACKHAM_KEY secrets are set.
Build the Straw APK in CI from a dedicated, ephemeral build container
(git.sulkta.com/sulkta-infra/straw-build — Android SDK/NDK + Rust +
cargo-ndk, see ci/Dockerfile) instead of the persistent crafting-table.
The runner spins the container up per job and tears it down after.
On push to main (after the build passes + the signer fingerprint is
verified against the canonical key) it publishes to fdroid.sulkta.com:
APK into the Lucy repo + index re-sign via the host docker socket, then
the signed repo streamed to Rackham web168 over a scoped forced-command
deploy key. Keystore + deploy key are Forgejo repo secrets.
Build steps run under `ionice -c3 nice` so they can't I/O-starve the live
DBs on Lucy.