# AGENTS.md — matrix-android Progress log and architecture notes for AI agents working on this project. --- ## Status | Component | Status | |---|---| | Build infrastructure (Gradle, version catalog) | done | | NixOS CLI dev shell / Android SDK toolchain | done | | Matrix networking (Retrofit API + Session) | done | | SSO / password login flow | done | | Room list screen | done | | Chat screen (send + receive) | done | | E2EE via local Olm wrapper | done | | Device verification (SAS) | done | | Terminal theme (green on black, monospace) | done | | Launcher icon (vector adaptive) | done | | README | done | --- ## Design decisions ### No heavy Matrix SDK Instead of importing the full `matrix-android-sdk2` (used by Element), we call the Matrix REST API directly with Retrofit. This keeps the APK small and the code easy to follow. The trade-off is no E2E encryption support. ### Local Olm build instead of Maven-native `olm-sdk` The upstream `org.matrix.android:olm-sdk` Maven artifact shipped a `libolm.so` that was only 4 KB ELF-aligned on arm64, which triggered Android's 16 KB page-size compatibility warning on modern devices. The app now vendors: - `app/libs/olm-sdk-3.2.16.jar` - `app/src/main/jniLibs/*/libolm.so` Those JNI binaries were rebuilt locally from the Matrix `olm` Android wrapper with linker flags that produce 16 KB-aligned `LOAD` segments on arm64. ### Nix-first CLI builds The repo includes a `flake.nix` dev shell for NixOS. It provisions: - JDK 17 - Gradle - Android SDK platforms 31 and 35 - Build Tools 30.0.2, 34.0.0, and 35.0.0 - Android NDK The shell rewrites `local.properties` on entry so CLI builds use the Nix-provided SDK path. ### SSO via Custom Tabs Login opens the homeserver's `/login/sso/redirect` endpoint in a Chrome Custom Tab. After auth the homeserver redirects to `matrixandroid://sso?loginToken=`. The app intercepts this URI via an `` in `AndroidManifest.xml` and exchanges the token for an access token. ### Single-activity Compose navigation `MainActivity` → `AppNavigation` hosts all three screens (Login, Rooms, Chat) inside a single `NavHost`. No fragments. ### MatrixSession singleton `MatrixSession` is a process-scoped singleton (companion object). It holds the Retrofit client, persists the access token in `SharedPreferences`, and runs the `/sync` long-polling loop in a coroutine scope. All screens observe `syncState: StateFlow`. ### Timeline is event-cache driven `MatrixSession` now keeps a per-room in-memory `RoomEvent` cache and rebuilds the visible `Message` list from those events. This allows: - local filtering/search over loaded events - message edits (`m.replace`) - reactions (`m.annotation`) - incremental history pagination - retrying decryption after keys arrive from another device ### Sync filter Initial and incremental syncs use a compact filter: - Timeline limited to 50 events per room - State events use lazy member loading - Presence and account data stripped This prevents the first sync from being huge on accounts with many rooms. --- ## Known issues / future work - **Olm is still legacy crypto**: The app uses a locally rebuilt Olm wrapper. Matrix has deprecated libolm in favor of vodozemac. A future migration should replace the current JNI/JAR setup. - **SAS verification edge case**: A handshake-ordering fix is currently in progress. The known symptom was that self-verification could fail depending on which device confirmed matching emojis first. - **Push notifications**: Not implemented. The app only receives messages while open. - **History is partial**: Pagination exists only while the room exposes a `prev_batch` token and only for message history. There is still no durable local database. - **Image/file messages**: Silently dropped (only `m.text` is rendered). - **Rich message support is partial**: Reactions and edits are handled for loaded events, but there is no reaction picker, no redactions, and no full relation aggregation from the homeserver. - **Search is in-memory only**: Chat search only scans events already loaded into memory for the current room. - **Error handling**: Basic inline send/load errors are shown, but there is still no global offline state, retry queue, or structured error UX. --- ## File map ``` matrix-android/ ├── settings.gradle.kts ├── build.gradle.kts ├── gradle/ │ ├── libs.versions.toml ← version catalog │ └── wrapper/ │ └── gradle-wrapper.properties ├── app/ │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src/main/ │ ├── AndroidManifest.xml │ ├── java/com/ltadeu6/matrix/ │ │ ├── MainActivity.kt │ │ ├── navigation/AppNavigation.kt │ │ ├── auth/LoginScreen.kt │ │ ├── auth/LoginViewModel.kt │ │ ├── verification/VerificationScreen.kt │ │ ├── rooms/RoomListScreen.kt │ │ ├── chat/ChatScreen.kt │ │ ├── chat/ChatViewModel.kt │ │ ├── matrix/MatrixApi.kt │ │ ├── matrix/MatrixModels.kt │ │ ├── matrix/MatrixSession.kt │ │ ├── matrix/CryptoService.kt │ │ ├── matrix/VerificationService.kt │ │ └── ui/theme/Theme.kt │ └── res/ │ ├── drawable/ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26/ic_launcher.xml │ ├── mipmap-anydpi-v26/ic_launcher_round.xml │ └── values/{strings,colors,themes}.xml ├── flake.nix ├── flake.lock ├── gradlew ├── gradlew.bat ├── app/libs/olm-sdk-3.2.16.jar ├── app/src/main/jniLibs/ ├── README.md └── AGENTS.md ``` --- ## Session 1 — initial build (2026-05-14) - Inspected `matrix-message` terminal app for UI reference: - `app.py`: curses chat with `< ` prefix for incoming, `> ` for outgoing - `font.py`: big pixel ASCII font for incoming banners - Color scheme: `COLOR_GREEN` on black - Created full Android project from scratch (no fork) - Chose Retrofit + kotlinx.serialization over matrix-android-sdk2 for simplicity - SSO flow: Custom Tabs → homeserver → `matrixandroid://sso` deep link → token exchange - Compose UI with monospace font, #00FF41 green, #0A0A0A background - All screens text-only, no images/icons/avatars ## Session 2 — NixOS CLI + 16 KB Android compatibility (2026-05-16) - Added `flake.nix` + `flake.lock` for NixOS CLI builds - Generated and committed Gradle wrapper files - Discovered Android 16 KB compatibility warning on Pixel 7a - Confirmed APK ZIP alignment was already correct; root cause was Maven `libolm.so` being ELF-aligned to 4 KB - Cloned upstream Matrix `olm` Android wrapper, patched NDK build flags for 16 KB page size, and rebuilt `libolm.so` - Worked around old upstream Android wrapper assumptions: - required platform 31 + build-tools 30.0.2 - required NDK path/layout adjustments - required one small C++ compatibility fix in `olm::List` for modern clang/NDK - Replaced `implementation(libs.olm.sdk)` with: - local `app/libs/olm-sdk-3.2.16.jar` - local `app/src/main/jniLibs/*/libolm.so` - Validated final APK on-device: - `libolm.so` inside the APK has `LOAD Align = 0x4000` - APK installed and launched over ADB on the Pixel 7a ## Session 3 — verification and chat UX expansion (2026-05-16, in progress) - Created checkpoint commit `0ede945` before feature work - Began fixing SAS verification handshake ordering - Added event-cache-backed message reconstruction in `MatrixSession` - Added local support for: - in-room search over loaded messages - message edits via `m.replace` - reactions via `m.annotation` - older-message pagination via `/rooms/{roomId}/messages` - room-level encryption/history indicators - inline chat error feedback - Added first-pass DM name resolution fallback via joined members lookup - This session is still dirty in the worktree; verify behavior before treating these changes as final