feat(release): m6 release mechanics — signed APK, R8, version guard, icons #11

Merged
whitlocktech merged 1 commits from feat/m6-release into main 2026-07-20 09:17:24 +00:00
Member

M6 — Polish & first release (release mechanics)

Release-hardening pass per docs/android/PLAN.md §9 (M6), §10, §12. No architecture, data-flow, endpoint, or DTO changes — the app stays a pure API client. Biometric app-lock is descoped (out of v1 unless later requested; tokens are already encrypted at rest).

App icons (default brand assets)

  • New gateway-medallion launcher icon set (all densities, adaptive fg/bg, round, Play Store icon) plus an "RG" notification icon staged for M7 push.
  • The Image Asset wizard kept its default green-grid adaptive background behind the transparent medallion. Replaced with the deep-indigo brand fill (@color/ic_launcher_background #1B1033), and recomposited the legacy square/round webps + the 512 Play icon over indigo so the whole set is coherent (the green never ships).
  • Restored the SPDX headers the wizard stripped; removed the orphaned placeholder foreground vector. No <monochrome> layer — the full-colour medallion has no clean single-tone silhouette, so themed-icon mode falls back to the standard icon rather than a tinted blob.

Version-mismatch guard (§3)

  • The first-run connect probe now refuses a Runic Gateway backend whose API version this build can't speak (e.g. a future v2) with a clear "app is out of date" message, instead of mis-rendering. Lenient on a blank api (older backend). Decision logic extracted to a pure ConnectionRepository.evaluateVersion() with unit tests.

Release build hardening (§7, §12)

  • R8 full-mode minify + resource shrink for release: ~31 MB debug → 4.2 MB signed release. Keep-rules for kotlinx.serialization serializers + our wire DTOs, Retrofit service interfaces, and a -dontwarn for Tink's compile-only Error Prone annotations (from EncryptedSharedPreferences).
  • Release signingConfig reads keystore material from a gitignored keystore.properties or env vars; absent → unsigned (debug builds + the PR gate are unaffected, so contributors without the keystore still build). The keystore never lives in the repo.
  • versionName is the committed source of truth (bumped by the release engine); versionCode is derived from it (major*10000+minor*100+patch, monotonic). Both stay -P-overridable for local builds (§10).
  • release.yml is link/'s language-agnostic release engine adapted for Android. On every push to main it: derives the next version from conventional-commit subjects since the last v* tag (feat!/BREAKING → major, feat → minor, fix/perf → patch; nothing releasable → no release), generates a grouped changelog, bumps build.gradle.kts, builds the signed APK (keystore from a base64 Gitea secret), then commits the bump [skip ci], tags vX.Y.Z, and creates the Gitea release with notes + APK + SHA256SUMS.
  • Uses REGISTRY_USER/REGISTRY_TOKEN (write:repository) to push the bump + create the release, exactly like link/; main must allow that account to push. Mirrors pr-checks.yml's self-hosted-runner handling (apt JDK 17, explicit sdkmanager, in-step chmod +x gradlew).

Verification

  • Built locally (JDK 21): :app:testDebugUnitTest + :app:lintDebug + :app:assembleRelease green. The release APK is signed (verified v2 scheme, cert matches the generated upload keystore) and version overrides flow into the manifest. The release-engine shell logic (version extract, bump, versionCode derive, the two build.gradle.kts seds) was validated against the real file.
  • ⚠️ Before release.yml can publish, add repo Actions secrets: REGISTRY_USER, REGISTRY_TOKEN (write:repository), ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD — and allow that account to push to main. Handed off separately.
  • ⚠️ Note: with this engine, the next release-worthy merge to main auto-cuts a release (the first run, with no prior v* tag, ships the committed 0.1.0). The on-device visual QA item carried over from M5 still applies before shipping publicly.

AI disclosure

AI-assisted (Claude Code). AI-authored commits carry Co-Authored-By: Claude <noreply@anthropic.com>.

## M6 — Polish & first release (release mechanics) Release-hardening pass per `docs/android/PLAN.md` §9 (M6), §10, §12. **No architecture, data-flow, endpoint, or DTO changes** — the app stays a pure API client. Biometric app-lock is **descoped** (out of v1 unless later requested; tokens are already encrypted at rest). ### App icons (default brand assets) - New gateway-medallion launcher icon set (all densities, adaptive fg/bg, round, Play Store icon) plus an "RG" notification icon staged for M7 push. - The Image Asset wizard kept its **default green-grid** adaptive background behind the transparent medallion. Replaced with the deep-indigo brand fill (`@color/ic_launcher_background` `#1B1033`), and recomposited the legacy square/round webps + the 512 Play icon over indigo so the whole set is coherent (the green never ships). - Restored the SPDX headers the wizard stripped; removed the orphaned placeholder foreground vector. **No `<monochrome>` layer** — the full-colour medallion has no clean single-tone silhouette, so themed-icon mode falls back to the standard icon rather than a tinted blob. ### Version-mismatch guard (§3) - The first-run connect probe now refuses a Runic Gateway backend whose API version this build can't speak (e.g. a future `v2`) with a clear "app is out of date" message, instead of mis-rendering. Lenient on a blank `api` (older backend). Decision logic extracted to a pure `ConnectionRepository.evaluateVersion()` with unit tests. ### Release build hardening (§7, §12) - **R8 full-mode minify + resource shrink** for release: **~31 MB debug → 4.2 MB signed release.** Keep-rules for kotlinx.serialization serializers + our wire DTOs, Retrofit service interfaces, and a `-dontwarn` for Tink's compile-only Error Prone annotations (from `EncryptedSharedPreferences`). - Release **`signingConfig`** reads keystore material from a gitignored `keystore.properties` or env vars; absent → unsigned (debug builds + the PR gate are unaffected, so contributors without the keystore still build). **The keystore never lives in the repo.** - `versionName` is the committed source of truth (bumped by the release engine); `versionCode` is derived from it (`major*10000+minor*100+patch`, monotonic). Both stay `-P`-overridable for local builds (§10). ### CI — auto-release engine (mirrors `link/`) - **`release.yml`** is `link/`'s language-agnostic release engine adapted for Android. On every push to `main` it: derives the next version from **conventional-commit** subjects since the last `v*` tag (`feat!`/BREAKING → major, `feat` → minor, `fix`/`perf` → patch; nothing releasable → **no release**), generates a grouped **changelog**, bumps `build.gradle.kts`, builds the **signed** APK (keystore from a base64 Gitea secret), then commits the bump `[skip ci]`, **tags `vX.Y.Z`**, and creates the **Gitea release** with notes + APK + `SHA256SUMS`. - Uses `REGISTRY_USER`/`REGISTRY_TOKEN` (`write:repository`) to push the bump + create the release, exactly like `link/`; `main` must allow that account to push. Mirrors `pr-checks.yml`'s self-hosted-runner handling (apt JDK 17, explicit `sdkmanager`, in-step `chmod +x gradlew`). ### Verification - Built locally (JDK 21): `:app:testDebugUnitTest` + `:app:lintDebug` + `:app:assembleRelease` green. The release APK is **signed** (verified v2 scheme, cert matches the generated upload keystore) and version overrides flow into the manifest. The release-engine shell logic (version extract, bump, `versionCode` derive, the two `build.gradle.kts` seds) was validated against the real file. - ⚠️ Before `release.yml` can publish, add repo Actions secrets: `REGISTRY_USER`, `REGISTRY_TOKEN` (write:repository), `ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD` — and allow that account to push to `main`. Handed off separately. - ⚠️ **Note:** with this engine, the **next release-worthy merge to `main` auto-cuts a release** (the first run, with no prior `v*` tag, ships the committed `0.1.0`). The on-device visual QA item carried over from M5 still applies before shipping publicly. ### AI disclosure AI-assisted (Claude Code). AI-authored commits carry `Co-Authored-By: Claude <noreply@anthropic.com>`.
wtclaude added 1 commit 2026-07-20 08:35:52 +00:00
feat(release): m6 release mechanics — signed APK, R8, version guard, icons
All checks were successful
PR Checks / android-build (pull_request) Successful in 9m52s
0df862a6af
Release-hardening pass (PLAN.md §9 M6, §10, §12). No architecture, data-flow,
or endpoint changes; the app remains a pure API client.

App icons (default brand assets):
- New gateway-medallion launcher icon set (all densities, adaptive fg/bg, round,
  Play Store icon) + an RG notification icon staged for M7 push.
- Replace the Image Asset wizard's default green-grid adaptive background with the
  deep-indigo brand fill (@color/ic_launcher_background #1B1033); recomposite the
  legacy square/round webps and the 512 Play icon over indigo so the whole set is
  coherent (the green never shipped). Restore the SPDX headers the wizard stripped;
  drop the orphaned placeholder foreground vector. No <monochrome> layer — the
  full-colour medallion has no clean silhouette, so themed mode falls back to the
  standard icon rather than a tinted blob.

Version-mismatch guard (§3):
- The connect probe now refuses a Runic Gateway backend whose API version this
  build can't speak (e.g. a future v2) with a clear "app out of date" message,
  instead of mis-rendering; lenient on a blank api (older backend). Decision logic
  extracted to a pure ConnectionRepository.evaluateVersion() with unit tests.

Release build hardening (§7, §12):
- Enable R8 full-mode minify + resource shrink for release (~31 MB debug -> 4.2 MB
  signed release). ProGuard keep-rules for kotlinx.serialization serializers + our
  wire DTOs, Retrofit service interfaces, and a -dontwarn for Tink's compile-only
  Error Prone annotations (EncryptedSharedPreferences).
- Release signingConfig reads keystore material from a gitignored keystore.properties
  or env vars; absent -> unsigned (debug + PR gate unaffected). Keystore never in repo.
- versionName/versionCode overridable via -P so the release tag + CI run number
  drive them (§10).

CI:
- release.yml: on a `v*` tag, build a SIGNED release APK (keystore from a base64
  Gitea secret) and attach it + SHA256SUMS to a Gitea release; workflow_dispatch is
  a signing dry run. Mirrors pr-checks.yml's self-hosted-runner handling (apt JDK 17,
  explicit sdkmanager, in-step chmod +x gradlew).

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech approved these changes 2026-07-20 08:39:24 +00:00
whitlocktech merged commit de79bf547c into main 2026-07-20 09:17:24 +00:00
whitlocktech deleted branch feat/m6-release 2026-07-20 09:17:25 +00:00
Sign in to join this conversation.
No description provided.