# WebTerm — Android client A native Android client for the WebTerm browser-terminal server, targeting functional parity with the shipped **iOS** client. See the full design in [`docs/ANDROID_CLIENT_PLAN.md`](../docs/ANDROID_CLIENT_PLAN.md) (stack §2, module architecture §3, server contract §4, task waves §5). This directory is a **Gradle multi-module** project. The module set mirrors the iOS SPM package set and inherits its rule: *dependencies only flow down; nothing points upward* (ARCHITECTURE §1). ## Build environment (SDK installed — all modules build) The Android SDK **is installed** and every module — pure Kotlin/JVM and Android-framework alike — builds and unit-tests here. AGP 9.2.1 (built-in Kotlin) + Gradle 9.6.1 build against SDK 35/36. - **Pure Kotlin/JVM (`./gradlew test`):** `:wire-protocol`, `:session-core`, `:api-client`, `:client-tls`, `:test-support`, `:transport-okhttp`. - **Android-framework (online in [`settings.gradle.kts`](settings.gradle.kts)):** `:app`, `:terminal-view`, `:host-registry`, `:client-tls-android`. Setup: `local.properties` → `sdk.dir=/usr/local/share/android-commandlinetools`; `google()` is in `pluginManagement`/`dependencyResolutionManagement`. Green gate: `./gradlew test :app:assembleDebug koverVerify`. ## Module map (mirror of the iOS SPM packages — plan §3) | iOS SPM package | Android module | Kind | Status | |------------------------|------------------------|-------------------------------|--------| | WireProtocol | `:wire-protocol` | pure Kotlin/JVM | ✅ built | | SessionCore (reducers) | `:session-core` | pure Kotlin/JVM | ✅ built | | APIClient | `:api-client` | pure Kotlin/JVM | ✅ built | | ClientTLS (pure half) | `:client-tls` | pure Kotlin/JVM | ✅ built | | TestSupport | `:test-support` | pure Kotlin/JVM (fakes) | ✅ built | | ClientTLS (fwk half) | `:client-tls-android` | Android (AndroidKeyStore/Tink)| ✅ built | | HostRegistry | `:host-registry` | Android (DataStore) | ✅ built | | SwiftTerm host view | `:terminal-view` | Android (Termux wrap) | ✅ built | | App/WebTerm | `:app` | Android app (Compose/Hilt/FCM)| ✅ built | > Not yet scaffolded: `:transport-okhttp` (OkHttp `TermTransport`/`HttpTransport` > impls, JVM) is owned by task **A7** and will be added then. The iOS > `URLSession*Transport`s consolidate into it (plan §3 framing note). ### Dependency graph (arrows = "depends on") ``` :app ┌───────────────┬───┴────┬──────────────┬───────────────┐ ▼ ▼ ▼ ▼ ▼ :terminal-view :session-core :api-client :host-registry :client-tls-android │ │ │ │ │ │ │ ▼ │ │ │ :client-tls (pure) └──────┬───────┴──────────┴──────────────┬────────────────┘ ▼ ▼ :wire-protocol ◀──────────── :transport-okhttp ▲ └──────── :test-support → test source sets only ``` `:wire-protocol` is the **frozen shared contract** (Android analogue of `src/types.ts` + WireProtocol) — `ClientMessage`/`ServerMessage`, `MessageCodec`, `Validation`, `WireConstants`, `HostEndpoint` (the single Origin/wsURL derivation), and the `TermTransport` / `HttpTransport` / `PingableTermTransport` boundary interfaces. New wire types are added **only** here (a coordination point). ## Toolchain - **Gradle** 9.6.1 (via the committed wrapper — always use `./gradlew`). - **Kotlin** 2.3.21 (matches the Kotlin embedded in Gradle 9.6.1). - **JVM toolchain** 17 (`jvmToolchain(17)` in every module). - Versions are pinned in the version catalog [`gradle/libs.versions.toml`](gradle/libs.versions.toml): kotlinx-serialization-json, kotlinx-coroutines-core/-test, JUnit5 (Jupiter), Turbine, MockK. Pure modules apply `kotlin("jvm")` + `kotlin("plugin.serialization")`, wire the `libs.bundles.unit-test` bundle into `testImplementation`, and run tests on the JUnit Platform (`tasks.test { useJUnitPlatform() }`). ## Build & test ```bash # Use the committed wrapper for everything. ./gradlew help # sanity: the build configures ./gradlew projects # lists the 5 pure modules ./gradlew build # compile all pure modules ./gradlew test # run JVM unit tests (JUnit5 + coroutines-test + Turbine + MockK) ``` > Testing target: **≥80% Kover coverage** on the pure modules (`:wire-protocol`, > `:session-core`, `:api-client`, `:client-tls` pure half). TDD, immutable data, > small focused files — same discipline as the rest of the repo. ## Android SDK setup (proven working) The pure JVM modules need only a JDK + Gradle. The **Android-framework** modules (`:app`, `:terminal-view`, `:host-registry`, `:client-tls-android` — plan AW2+) need the Android SDK. This machine is set up and the toolchain is **proven** (an AGP library module compiled against SDK 35 and produced an AAR): - **SDK location:** `/usr/local/share/android-commandlinetools` (installed via `brew install --cask android-commandlinetools`). - **Installed packages:** `platform-tools`, `platforms;android-35`, `platforms;android-36`, `build-tools;35.0.0`, `build-tools;36.0.0`. (`:app` compiles against SDK **36** — the Kotlin-2.3.21-contemporaneous androidx/Compose line refuses SDK 35; `platforms;android-37` is not fetchable here as the cmdline-tools are too old to parse the v4 repo XML.) - **`android/local.properties`** (gitignored) points Gradle at it: `sdk.dir=/usr/local/share/android-commandlinetools`. - **Shell env** (for `sdkmanager`/`adb`): `export ANDROID_HOME=/usr/local/share/android-commandlinetools`. ### Wiring an Android module (the working recipe) - Repos: `google()` is in both `pluginManagement` and `dependencyResolutionManagement` in `settings.gradle.kts` (needed to resolve AGP + androidx). - Plugin: **AGP 9.2.1** (`libs.plugins.android.library` / `.android.application`), compatible with Gradle 9.6.1. - **Gotcha:** AGP 9 has **built-in Kotlin** — apply ONLY the android plugin. Adding `org.jetbrains.kotlin.android` errors with "no longer required since AGP 9.0". - Module block: `android { namespace = "…"; compileSdk = 36; defaultConfig { minSdk = 29 } }`. (Framework modules target `compileSdk = 36`; `targetSdk` stays `35` per plan §2.) - **`:app` UI-stack version matrix** (A13, proven `:app:assembleDebug` green): AGP 9.2.1 · Kotlin 2.3.21 · Compose-compiler plugin `org.jetbrains.kotlin.plugin.compose` = 2.3.21 · Compose BOM `2025.11.01` (→ material3 1.4.0, ui/foundation 1.9.5, material3.adaptive 1.2.0, material3-adaptive-navigation-suite 1.4.0) · Hilt (dagger) 2.60.1 via KSP `2.3.9` · androidx core-ktx 1.17.0 / activity-compose 1.12.4 / lifecycle 2.10.0. Apply plugins: `android.application` + `kotlin.plugin.compose` + `ksp` + `dagger.hilt.android` (NEVER `kotlin.android`). Bump these together with `compileSdk 37` once platform 37 is installable. To add more SDK pieces later (e.g. an emulator image for instrumented tests): `sdkmanager "system-images;android-35;google_apis;arm64-v8a" "emulator"`.