4.8 KiB
4.8 KiB
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
./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 cataloggradle/libs.versions.toml— add/upgrade libraries there, not inline inbuild.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/*Implimplement domain interfaces;source/remoteis Retrofit (ApiService+dto/);source/localis Room (HeimdallDatabase,CredentialDao,entity/+ mappers) plus DataStore;preferences/andvault/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 asStateFlowviastateIn.navigation/—Screenis a@Serializable sealed class; routes are type-safe Compose Navigation destinations wired inHeimdallNavGraph.
Key cross-cutting flows:
- Network: repositories extend
BaseRepositoryand wrap calls insafeApiCall { ... }, which converts a RetrofitResponseintoApiResult.Success/ApiResult.Error. Error bodies are parsed byErrorBodyParser.toApiError(). JSON config lives incommon/GlobalConfig.json. Base URL is hardcoded indi/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 viaLocalDataSource. Never store the token in plaintext. - Vault locking:
VaultSessionManager(impl indata/vault) holds an in-memoryLocked/Unlocked(expiredAt)StateFlow.HeimdallApplicationobservesProcessLifecycleOwnerand callsrefreshSession()on app foreground, re-locking after the timeout. ViewModels readsessionStateto gate access. (Timeout is currently 5 seconds —TIMEOUT_DURATIONinVaultSessionManagerImpl.) - Autofill:
HeimdallAutofillService(registered in the manifest, BIND_AUTOFILL_SERVICE) receives anAssistStructure;StructureParserclassifies username/password fields using a layered heuristic (autofillHints → inputType → htmlInfo → text/hint keyword matching). The fill values inbuildFillResponseare currently hardcoded placeholders, not yet wired to the vault. - First launch / onboarding:
MainViewModelreadsPreferenceDataSource("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.startKoinloads only the manualappModule(di/AppModule.kt), which uses the DSL (single<Impl>() bind Interface::class,viewModel<...>()).- Other files use Koin annotations (
@Module,@SingleinNetworkModule/DatabaseModule,@KoinViewModel,@Singleton) which require the KSP-generated module to be included to take effect. These are not currently passed tostartKoin.
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) usekotlinx.serialization(@Serializable); domain models are separate types — map between them in the repository. - Validation lives in
common/ValidationHelper.