# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Heimdall is an Android password manager (Jetpack Compose, Kotlin) that also acts as a system **Autofill service**. Users register/login against a remote backend, then store and retrieve credentials locally in an encrypted vault. ## Build & Test ```bash ./gradlew assembleDebug # build debug APK ./gradlew installDebug # build + install on connected device/emulator ./gradlew test # JVM unit tests (src/test) ./gradlew connectedAndroidTest # instrumented tests on device (src/androidTest) ./gradlew lint # Android lint # single unit test class / method: ./gradlew test --tests "com.longnh15.heimdall.SomeTest" ./gradlew test --tests "com.longnh15.heimdall.SomeTest.someMethod" ``` - Single-module project (`:app`). Dependencies are managed via the version catalog `gradle/libs.versions.toml` — add/upgrade libraries there, not inline in `build.gradle.kts`. - minSdk 26, targetSdk/compileSdk 37, Java 11. ## Architecture Clean-architecture-style layering under `app/src/main/java/com/longnh15/heimdall/`: - **`domain/`** — interfaces + models, no Android/framework deps. `repository/` holds repo interfaces, `model/` holds domain types (`ApiResult`, `AuthInfo`, `Credential`), `vault/` holds the session abstraction. - **`data/`** — implementations. `repository/*Impl` implement domain interfaces; `source/remote` is Retrofit (`ApiService` + `dto/`); `source/local` is Room (`HeimdallDatabase`, `CredentialDao`, `entity/` + mappers) plus DataStore; `preferences/` and `vault/` hold other impls. - **`ui/`** — Compose screens grouped by feature (`auth/login`, `auth/register`, `onboarding`, `vault`, `vault/edit`). Each feature follows MVVM: `*Screen` (Compose) + `*ViewModel` + `*State` + `*Event` (user intents). State is exposed as `StateFlow` via `stateIn`. - **`navigation/`** — `Screen` is a `@Serializable sealed class`; routes are type-safe Compose Navigation destinations wired in `HeimdallNavGraph`. Key cross-cutting flows: - **Network**: repositories extend `BaseRepository` and wrap calls in `safeApiCall { ... }`, which converts a Retrofit `Response` into `ApiResult.Success`/`ApiResult.Error`. Error bodies are parsed by `ErrorBodyParser.toApiError()`. JSON config lives in `common/GlobalConfig.json`. Base URL is hardcoded in `di/NetworkModule`. - **Auth tokens**: on login, the token is encrypted with `common/TokenCrypto` (AES/GCM key in the Android Keystore, IV prepended) and persisted in the `"terces"` DataStore via `LocalDataSource`. Never store the token in plaintext. - **Vault locking**: `VaultSessionManager` (impl in `data/vault`) holds an in-memory `Locked`/`Unlocked(expiredAt)` `StateFlow`. `HeimdallApplication` observes `ProcessLifecycleOwner` and calls `refreshSession()` on app foreground, re-locking after the timeout. ViewModels read `sessionState` to gate access. (Timeout is currently 5 seconds — `TIMEOUT_DURATION` in `VaultSessionManagerImpl`.) - **Autofill**: `HeimdallAutofillService` (registered in the manifest, BIND_AUTOFILL_SERVICE) receives an `AssistStructure`; `StructureParser` classifies username/password fields using a layered heuristic (autofillHints → inputType → htmlInfo → text/hint keyword matching). The fill values in `buildFillResponse` are currently **hardcoded placeholders**, not yet wired to the vault. - **First launch / onboarding**: `MainViewModel` reads `PreferenceDataSource` (`"settings"` DataStore) to decide the nav start destination (Onboarding vs Login); the splash screen is held until that flag resolves to avoid a flicker. ## Dependency Injection (Koin) — note the inconsistency DI is **mid-migration between two Koin styles**, and only one is actually loaded: - `HeimdallApplication.startKoin` loads **only** the manual `appModule` (`di/AppModule.kt`), which uses the DSL (`single() bind Interface::class`, `viewModel<...>()`). - Other files use **Koin annotations** (`@Module`, `@Single` in `NetworkModule`/`DatabaseModule`, `@KoinViewModel`, `@Singleton`) which require the KSP-generated module to be included to take effect. These are **not currently passed to `startKoin`**. When adding a dependency, prefer registering it in `appModule` so it is actually wired, or explicitly include the annotation-generated modules in `startKoin`. Don't assume an `@Single` annotation alone makes a class injectable here. ## Conventions - Room schemas are exported to `app/schemas/`; bump the DB version and commit the new schema JSON when changing entities. - DTOs (`data/source/remote/dto`) use `kotlinx.serialization` (`@Serializable`); domain models are separate types — map between them in the repository. - Validation lives in `common/ValidationHelper`.