matrix-android/AGENTS.md

177 lines
8 KiB
Markdown

# 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=<token>`. The app intercepts this URI via an `<intent-filter>` 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<SyncState>`.
### 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