Files
kvitt/PLAN-M2b.md
T
jacobh2 5e1be38364 Plans and start prompts for every milestone; SPEC v0.2
PLAN-<id>.md for U1, U2, M1, M2a, M2b, M2c-dev, M2c-pi, M3, M4, M5, M6,
M7a-M7f, M8, M9, M10, M11, each with the eleven fixed sections, and a
start prompt per plan in prompts/ plus prompts/README.md (dependency
order and parallel waves).

SPEC v0.2: owner answers D-001 (development-first, M2c split into
M2c-dev and M2c-pi, Gitea registry), D-002 (local backups deleted once
on Drive), D-003 (Splitwise CSV format, English and Swedish variants,
category mapping), OQ-1, OQ-2, OQ-4 answered; M1's readings of
ambiguous contract points stated explicitly in §14.3 to §14.5.
HANDOFF §2 lists the plans.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:05:34 +02:00

98 lines
12 KiB
Markdown

# PLAN-M2b: Android skeleton and `:domain`
## 1. Goal
Create the Gradle project in `android/` with every module of §6.2, the version catalog, the architecture check, the `:core:model` UI contract and domain types, a `:domain` that passes every shared test vector including analytics, empty fakes, and an `:app` that launches to an empty themed screen. After it lands, M5 (data), M6 (design) and M7a to M7f (features) build against the same interfaces in parallel.
## 2. Read first
1. SPEC §6.2 Android app (stack table, module graph, the paragraph on the architecture check).
2. SPEC §14.2.4 UI contract: types and repository interfaces in `:core:model`.
3. SPEC §14.5 Domain rules as pure functions (14.5.1 Types, 14.5.2 Functions, 14.5.3 Error tokens, 14.5.4 Analytics, 14.5.5 Test-vector format).
4. SPEC §9.1 Splits, §9.3 Balances, §9.4 Settlement allocation proposal, §9.6 Limits and validation.
5. SPEC §4 FR-14 Analytics (AC-14.2, AC-14.7, AC-14.10) and FR-19 Localisation and formatting.
6. SPEC §14.2 Room entities (only to know what `:data` will hold; M5 writes it).
7. SPEC §11.2 (row M2b), §11.3 (notes M2b, M7x, M8), §14.7 Module layout and ownership map, §14.8.
8. Files: `contracts/test-vectors/*.json`, `android/gradle.properties`, `android/gradle/gradle-daemon-jvm.properties`, `tools/gradle-serial.sh`, `tools/dependency-report/latest.py`, `tools/git-hooks/pre-commit`, `CLAUDE.md` (BUILD AND VERIFY, ARCHITECTURE RULES, ANTI-PATTERNS, THE PHONE).
## 3. Already established
- Kotlin 2.x latest stable; Compose with Material 3; `minSdk` 36; `targetSdk` and `compileSdk` the latest stable API; Room with KSP; Hilt; Ktor client (OkHttp engine) with kotlinx.serialization; WorkManager; kotlinx-datetime; Coil; Vico or Canvas charts (decided by M6); FCM only in `:adapter:fcm`; version catalog `android/gradle/libs.versions.toml`; Gradle daemon on JDK 25 (pinned, existing files); bytecode target Java 17 (§6.2).
- Screenshot tests use Roborazzi or AGP Compose Preview Screenshot Testing, whichever is stable at implementation time, recorded in decisions (§6.2).
- Modules and allowed edges exactly as the §6.2 table; features never depend on `:data` or `:domain`; `:core:fakes` only for tests, screenshot tests and debug previews (§6.2, CLAUDE.md Layering).
- The architecture check is a Gradle task in `check`, owned by M2b; it fails on any other dependency edge and on `java.time`, `java.io` or `android.*` imports in `:core:model` and `:domain` (§6.2).
- `:core:model` package `se.hagstedt.kvitt.model` holds the §14.2.4 types and interfaces verbatim; domain input/output types in `se.hagstedt.kvitt.model.money` (§14.5.1); `:domain` package `se.hagstedt.kvitt.domain` with the §14.5.2 functions and §14.5.4 analytics (§14.5).
- Wire value of every enum = lowercase name; icon tokens are Strings from `CategoryIcons.ALL`, the 30 tokens of §9.7 in that order (§14.2.4).
- `:domain` passes every vector file in `contracts/test-vectors/`, including `spend_series.json` and `resolve_range.json` (Kotlin only) (§14.5, §11.2).
- Strings live in `values/strings.xml` (English fallback) and `values-sv/strings.xml`; a lint or test in the gate fails on a key missing from either file (AC-19.1).
- Application ids: release `se.hagstedt.kvitt`, debug `se.hagstedt.kvitt.debug`, instrumented tests `se.hagstedt.kvitt.<module>.test` (CLAUDE.md THE PHONE).
- No mocking libraries, no `!!`, no `@Suppress`, no lint-baseline additions (CLAUDE.md).
- M1 has landed (`c6df2ab`); its readings are explicit in SPEC v0.2: domain functions receive only non-deleted rows; analytics vector keys per §14.5.5; `bp_out_of_range` means above 10000; `resolveRange` edge cases per §14.5.4 (how `CUSTOM` with a null date signals `validation_failed` is for M2b to fix and report).
- `:core:model/**` and `:domain/**` become contract files once landed (§14.8).
## 4. Owned paths
From SPEC §14.7, verbatim:
- `android/settings.gradle.kts`, `android/build.gradle.kts`, `android/gradle.properties`, `android/gradle/**`, `android/build-logic/**` (owner M2b; after landing: "later plans may add catalog entries and module includes (rebase resolves)").
- `android/core/model/**`, `android/domain/**` (owner M2b; after landing: "contract-change plans only").
- `android/core/fakes/**` (owner M2b; after landing: "M7x add files only under `fakes/<feature>/`").
- `android/app/**` up to and including "the M2b shell" (the row `android/adapter/fcm/**`, `android/app/**` (after the M2b shell) | M8).
Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
Open points the orchestrator must settle in the start prompt's notes (stop and ask if they are not settled): (a) "every module of §6.2" means build files in `android/core/design/`, `android/data/`, `android/adapter/fcm/` and `android/feature/*/`, which §14.7 gives to M6, M5, M8 and M7x: whether M2b creates them as empty module shells that ownership then passes on; (b) where the theme of the "empty themed screen" lives, given `:core:design` belongs to M6; (c) which module holds the "string resource files for both languages" M2b creates; (d) which screenshot-test tool, and whether M2b wires it into `check` (needs a decision id); (e) whether feature modules use Hilt (`hiltViewModel`) or receive repositories as parameters, and how cross-feature navigation is expressed, since the feature convention plugin in `build-logic` fixes it for M7a to M7f; (f) the package of the §14.5.4 analytics types (`:core:model` money package or `:domain`); (g) what an "empty scenario" is for repositories whose state has required fields (for example `HomeState.me`).
## 5. Deliverables
- `android/settings.gradle.kts`, `android/build.gradle.kts`: the project with every module of §6.2.
- `android/gradle/libs.versions.toml`: every library and plugin of §6.2 with the versions chosen.
- `android/gradle/wrapper/` (`gradle-wrapper.properties`, `gradle-wrapper.jar`) and `android/gradlew`, `android/gradlew.bat`.
- `android/gradle.properties`, `android/gradle/gradle-daemon-jvm.properties`: the existing JDK 25 pin and flags kept, merged with what the project needs.
- `android/build-logic/**`: convention plugins (pure JVM module, Android library, feature module, application) and the architecture check task wired into `check`.
- `android/core/model/**`: §14.2.4 and §14.5.1 types and interfaces, verbatim, plus unit tests for enum wire values and `CategoryIcons.ALL`.
- `android/domain/**`: §14.5.2 functions and §14.5.4 analytics; a vector test runner reading `../contracts/test-vectors/*.json` for all seven files.
- `android/core/fakes/**`: one fake per repository interface of §14.2.4 driven by a scenario type, with an empty scenario each; fakes compute derived values through `:domain`.
- `android/app/**`: `Application` class, `MainActivity` showing an empty themed screen, debug `applicationIdSuffix` `.debug`, manifest.
- Module shells for `:core:design`, `:data`, `:adapter:fcm`, `:feature:{onboarding,home,account,expense,settle,analytics,settings}` (per open point a).
- `values/strings.xml` and `values-sv/strings.xml` with the app name (per open point c), and the missing-translation check (Android lint `MissingTranslation` as an error, or a test) in `check`.
- Screenshot-test tooling wired into `check` (per open point d) with one screenshot of the empty screen in light and dark.
- `docs/decisions.md` entry for the screenshot tool only if the start prompt grants a decision id and the path (the file is not owned by M2b; otherwise put the text in the report as a contract request).
## 6. Steps
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m2b -b agent-m2b main`, then in `.claude/worktrees/agent-m2b`: `git reset --hard main`. Copy `android/local.properties` from the main checkout if it exists (`cp /Users/jhag/projects/kvitt/android/local.properties android/`); if it does not, create it with the single line `sdk.dir=/opt/homebrew/share/android-commandlinetools`. Never overwrite an existing one, never commit it. Run `tools/install-git-hooks.sh`.
2. Read §2 in order. Confirm `contracts/test-vectors/` exists on `main`; if not, stop and report.
3. Versions ("latest stable" in §6.2): resolve from official sources and record each with its source: Kotlin (kotlinlang.org releases), Android Gradle Plugin and the latest stable `compileSdk`/`targetSdk` API level (developer.android.com release notes), Gradle wrapper (gradle.org releases; must support JDK 25 daemon and the AGP chosen), Compose BOM, Material 3 (developer.android.com/jetpack/androidx/releases), KSP matching the Kotlin version, Room, Hilt, Ktor, kotlinx.serialization, kotlinx.coroutines, kotlinx-datetime, WorkManager, DataStore, Coil, Roborazzi or the AGP screenshot plugin. Check whether the AGP Compose Preview Screenshot Testing plugin is stable; report the finding for open point (d).
4. Generate the Gradle wrapper with the chosen version; keep `android/gradle.properties` and `android/gradle/gradle-daemon-jvm.properties` content (JDK 25 pin, auto-download off).
5. Write `build-logic` convention plugins and the architecture check (allowed edges per the §6.2 table; forbidden imports `java.time`, `java.io`, `android.*` in `:core:model` and `:domain`); add a failing-case test of the check if `build-logic` supports it.
6. Write `:core:model` exactly from §14.2.4 and §14.5.1 (no added fields). Write `:domain` test-first against all seven vector files, then implement §14.5.2 and §14.5.4.
7. Write `:core:fakes` (one fake per interface, scenario-driven, empty scenario each) and the module shells.
8. Write `:app`: themed empty screen, string resources in both languages, missing-translation check, screenshot tooling with one golden in light and dark; open the PNGs before committing (CLAUDE.md Screenshots).
9. Run `python3 tools/dependency-report/latest.py` and record its lines.
10. Run the gate (§7). If the owner's phone is attached and the start prompt names this plan as the one on the phone: `PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH tools/gradle-serial.sh installDebug`, after `/opt/homebrew/share/android-commandlinetools/platform-tools/adb shell pm list packages | grep se.hagstedt.kvitt` to confirm the ids.
11. Commit on `agent-m2b`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree.
## 7. Gate
From SPEC §11.2, verbatim: "Android gate".
Command, from `android/`: `PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ../tools/gradle-serial.sh check`.
Green means: `BUILD SUCCESSFUL` in this session; the architecture check, `:core:model` and `:domain` unit tests (all seven vector files), lint with the missing-translation rule, and the screenshot test all ran (task names listed in the report); test counts from fresh XML under `build/test-results/`.
## 8. HUMAN steps
From SPEC §11.2, verbatim: "Plug the phone in once to confirm `installDebug` works".
Where in the flow: after the gate is green (step 10). The orchestrator relays the step; the agent runs the package check and `installDebug` itself and reports the result. If the phone is not attached, report the step as outstanding.
## 9. Out of scope
- Room, DAOs, Ktor client, sync engine, token store, workers, real repositories, APK updater (M5).
- Theme tokens, typography, shared components, charts, `MoneyFormatter`, `DateFormatter` (M6).
- Any feature screen (M7a to M7f) and fake scenarios beyond the empty one (M7x add theirs).
- Navigation graph, Hilt bindings to `:data`, deep links, FCM, notifications, network security config, release signing (M8).
- Server work (M2a and later), `infra/` (M2c-dev).
- Adding fields to §14.2.4 types (contract change).
## 10. Report
- Branch `agent-m2b`, head commit hash after rebasing on `main`.
- Gate output: BUILD line, actionable tasks line, the names of the architecture-check, lint and screenshot tasks that ran, unit test counts from fresh XML (per module).
- Versions chosen (Kotlin, AGP, Gradle, compile/target SDK, every catalog entry) with the official source; dependency-report lines.
- Screenshot tool chosen and why (and the decisions text if no decision id was granted).
- How each open point (a) to (g) was resolved, as given in the start prompt.
- `installDebug` result on the phone, or "outstanding".
- Numbering used (decision ids, spec version) or `TODO(spec)` occurrences.
- Contract requests, open questions, spec gaps (with section numbers).
## 11. Numbering
Decision ids, spec version and schema version are assigned by the orchestrator in the start prompt; use `TODO(spec)` if none was given.