Files
kvitt/android
jacobh2 0e1d99df93 M7b: feature home and account (S-4, S-5, S-6, S-16, S-17)
:feature:home: Home with balance hero, sync pill, accounts, archived section,
empty and first-sync states, update banner and "Tillåt installation" dialog;
sync status with pending rows, rejections of every §14.4.6 reason and the
clock-skew banner. :feature:account: account detail with header, ledger by
month, overflow menu, archive/unarchive/delete and their errors; account
editor with name, percent fields (two decimals), slider, presets, preview,
name_taken/validation errors and offline state. Fake scenarios in
fakes/home and fakes/account; full-screen Roborazzi goldens for every state
in sv/en × light/dark (200 % for long states); ViewModel unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 10:49:55 +02:00
..

Kvitt Android project

One Gradle project, modules as in SPEC §6.2 and §14.7. Build only through tools/gradle-serial.sh with PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH (CLAUDE.md › Build and verify). The gate is tools/gradle-serial.sh check: compile, unit tests (including every vector of contracts/test-vectors/), lint (MissingTranslation and ExtraTranslation are errors), the architecture check and the screenshot tests.

Modules

Module Plugin (build-logic) Notes
:core:model kvitt.jvm.library UI contract (§14.2.4), domain types model.money (§14.5.1), analytics types model.analytics (§14.5.4)
:domain kvitt.jvm.library §14.5 functions; VectorTest runs every vector file, copied by :domain:copyTestVectors
:core:design kvitt.android.library + kvitt.android.compose KvittTheme, tokens, shared components, Canvas charts, formatters and shared strings (M6, 3080804; SPEC §6.2); M6 owns the module
:core:fakes kvitt.android.library FakeRepositories(scenario), FakeScenarios.empty; scenarios of feature X go in fakes/<x>/
:data kvitt.android.library + KSP + Room 3 Room entities and KvittDatabase v1, schema in data/schemas/; M5 owns the module
:adapter:fcm kvitt.android.library shell; M8 owns it
:feature:* kvitt.android.feature shells with one entry composable; M7a to M7f own them
:app kvitt.android.application + compose + hilt + Roborazzi Application, MainActivity, goldens in app/src/test/screenshots/

Architecture check

architectureCheck runs in every module's check (build-logic/.../ArchitectureRules.kt holds the rules as data). It fails on a project dependency outside the §6.2 table, on :core:fakes in any configuration that is not a test or debug configuration, and, in :core:model and :domain, on an Android plugin, on a library outside org.jetbrains.kotlin*, and on any import or fully qualified reference outside kotlin.*, kotlinx.* and se.hagstedt.kvitt.* in src/main. A new module must be added to ArchitectureRules.allowed.

Feature convention

  • Each feature module exposes one composable entry point per screen. By default it obtains its ViewModel with hiltViewModel() (from androidx.hilt:hilt-lifecycle-viewmodel-compose) and takes navigation as plain lambdas, for example:

    @Composable
    fun AccountDetailScreen(
        onOpenExpense: (ExpenseId) -> Unit,
        onAddExpense: (AccountId) -> Unit,
        onBack: () -> Unit,
        modifier: Modifier = Modifier,
        viewModel: AccountDetailViewModel = hiltViewModel(),
    )
    

    A stateless overload that takes the *State and event lambdas is what previews and screenshot tests render.

  • ViewModels are @HiltViewModel classes that receive repositories through the interfaces of :core:model only. :app binds those interfaces to :data (release) or :core:fakes (debug and tests) with Hilt (M8).

  • Features never reference another feature, the NavHost or :data/:domain. :app owns the NavHost and wires the lambdas.

  • Strings live in the feature's own src/main/res/values/strings.xml and values-sv/strings.xml, keys prefixed with the feature name; shared strings in :core:design; :app holds only the app name.

  • Previews and screenshot tests use FakeRepositories(scenario) from :core:fakes (debugImplementation and testImplementation, added by the feature plugin). Fakes never reach a release build.

Screenshot tests

Roborazzi on Robolectric. roborazzi.test.verify=true in gradle.properties makes every unit-test run compare with the committed goldens. Re-record an intended change with tools/gradle-serial.sh recordRoborazziDebug and open every changed PNG before committing.