Executes COVERAGE_PLAN.md phases 0-2 to clear the SonarQube new-code coverage gate (was 16.4%, threshold 50%). Estimated new-code coverage after this change is ~57%. 109 new tests across 19 files; full suite is 264 tests, all green. Phase 0 — coverage exclusions (sonar-project.properties): drop code a JVM unit test can't execute from the *coverage* denominator (still analysed for bugs/smells) — pure-@Composable UI the `*Screen.kt` glob missed (ui/components/**, BlockRenderer, ShardComponents), Android-framework glue (push services, Keystore-backed Encrypted* stores, Hilt di/**). Phase 1 — DTO serialization tests: AdminDto, PublicDto, WikiDto, PostDto/PageDto/ ContactDto, SsoDto, the shard board DTOs and player game-data DTOs, and the mobile-auth request bodies — decode + encode + computed helpers (isPublished/isMaintenance/ActorDto.label/ShardStatusDto.isOnline). Phase 2 — ViewModel tests: a MainDispatcherRule harness + hand-written API fakes (FakePublicApi/FakeAdminApi/FakePlayerShardApi/FakeShardStream) drive real repositories into the ViewModels. Covers the admin (dashboard/content/moderation/ support), content (news/post/page/wiki/home/contact), player (characters/ vendors/character/my-houses) and shard-board (champs/guilds/governors/houses/ hub) ViewModels — load success/error, form validation, role/status-aware feedback, and live-frame merging. To make the shard boards testable, extract a small `ShardStream` interface from `ShardStreamClient` (bound in NetworkModule) so `ShardRepository` depends on the capability, not the OkHttp client — lets a fake stream replace the perpetual SSE reconnect loop in tests. No production behaviour change. Phases 3 (repositories) and 4 (core net/auth top-up) are follow-ups; the deep-dependency auth family (Login/Account/TrustedDevices ViewModels, AuthRepository) lands with them. See docs/android/COVERAGE_PLAN.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
Runic Gateway — Android app
A native Android client for a Runic Gateway shard's public site + player self-service. It is
purely an API client of the website backend — it never talks to the link/ sidecar or the game
shard directly, and it ships none of the shard/sidecar wiring. It surfaces the same content and
player features as the website's browser client, minus every administrative/management console.
The authoritative design contract is docs/android/PLAN.md
in the RunicGateway/docs repo. The authoritative API reference is the committed OpenAPI spec at
website/server/swagger/swagger-output.json.
Status
M0 — repo scaffold. Gradle + Compose + Hilt skeleton with CI (lint + unit test + debug build). The functional Kotlin pass (M1–M4) and the design pass (M5) follow — see the plan's milestones (§9).
Stack
| Concern | Choice |
|---|---|
| Language / UI | Kotlin + Jetpack Compose (Material 3) |
| Navigation | Navigation-Compose, single-activity |
| HTTP | Retrofit + OkHttp, kotlinx.serialization |
| Async | Coroutines + Flow |
| DI | Hilt |
| Prefs / base URL | Jetpack DataStore |
| Tokens at rest | EncryptedSharedPreferences |
| Images | Coil |
| Min SDK | Android 10 (API 29) |
| Target / compile SDK | 35 |
Dependency and plugin versions are pinned in gradle/libs.versions.toml.
Build
Requires JDK 17 and the Android SDK (ANDROID_HOME / local.properties).
./gradlew assembleDebug # build a debug APK -> app/build/outputs/apk/debug/
./gradlew test # JVM unit tests
./gradlew lint # Android lint
./gradlew installDebug # install on a connected device/emulator
The app self-configures its server URL on first run (PLAN.md §3), so a single build works against any shard's website — there is no compiled-in API host.
CI
.gitea/workflows/pr-checks.yml gates PRs into main with ./gradlew lint test assembleDebug on the
org's self-hosted runner (JDK 17 + Android SDK). Debug builds are auto-signed, so the gate needs no
secrets. This pipeline is verified green end-to-end on the runner (M0). A signed release APK
attached to a Gitea release comes at M6.
The workflow carries a few runner-specific accommodations (each explained in comments in the file), because this self-hosted runner differs from a stock GitHub runner:
- JDK 17 is installed via
apt(notactions/setup-java) — the runner can't resolveapi.adoptium.net, while the Ubuntu mirrors are reachable. - SDK packages are installed explicitly via
sdkmanager, withset +o pipefailsoyesdying ofSIGPIPEdoesn't fail the step. gradlewischmod +x'd in the run step — the runner's checkout does not preserve the git executable bit, so./gradlewalone fails with "Permission denied".
Contributing
See CONTRIBUTING.md. AI-assisted contributions must be disclosed (org
policy): tick the PR box naming the tool and add a Co-Authored-By trailer to AI-authored commits.
Licensed GPL-3.0-or-later.