Files
kvitt/android/README.md
T
jacobh2 f4dc10c7ae SPEC v0.11: M7 wave and M6b outcomes (D-069 to D-080), HANDOFF, prompts
Records the readings of M7a to M7f (b9b061a, 6ac711d, 082b8dc, 31760f9,
1e42f76, a54eb7c) and the M6b design-system fix pass (3a1bd14): S-11/S-12
captions, titles and states; navigation wiring and result snackbars (§13.2);
new template day default (AC-9.8); manual update check (AC-16.6); FieldError
field names and settlement editing (AC-11.6); quickRowCategories; two-line
category labels; long day ranges (§9.8); amounts open with two decimals
(§9.6); string keys (strings.csv 578 rows); "Synka nu" online only (§9.9).
§11 marks M7a-M7f and M6b landed, M8 and M7g started; §11.3 lists contract
requests for a later contract pass (M12e). Docs only.

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

61 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`; fixes M6b, `3a1bd14`; SPEC §6.2); M6 owns the module |
| `:core:fakes` | `kvitt.android.library` | `FakeRepositories(scenario)`, `FakeScenarios.empty`; scenarios of feature X go in `fakes/<x>/` (added by M7a to M7f) |
| `: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` | screens built by M7a to M7f (landed 2026-10-18), goldens in `feature/<x>/src/test/screenshots/` (full screen 411 × 891 dp, xhdpi); top-level screens draw their own `KvittNavigationBar` (SPEC §13.2, D-071); M7g does the cleanups after M6b |
| `:app` | `kvitt.android.application` + compose + hilt + Roborazzi | Application, MainActivity, goldens in `app/src/test/screenshots/`; release binds `:data` (`DataBindingsModule`, `e6ad858`), debug binds the fakes in `src/debug/.../DebugFakesModule.kt` (`72f21b2`) until M8 deletes it; M8 owns the module |
## 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:
```kotlin
@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` with Hilt (release since `e6ad858`, debug too once M8 deletes the temporary debug fakes module); tests and previews use `:core:fakes`.
* 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.