From 580bc993ccf127ebd4fce3ba7905e84768638bd3 Mon Sep 17 00:00:00 2001 From: ShenLong Date: Wed, 17 Jun 2026 10:56:45 +0700 Subject: [PATCH] add CLAUDE.md --- CLAUDE.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d821d62 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# 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`.