Files
kvitt/PLAN-K2.md
T
jacobh2 07df30d04d SPEC v0.38: K1's report recorded (D-181 to D-188), K2 v0.39 and K3 v0.40
K1 landed 671c00d (card purchases on the server, schema 4, E-36 to E-44). Decisions: D-181
history counts today (6 windows for 180 days, revises CP-3 and CP-7); D-182 the server's CS-4
first token; D-183 Enable Banking errors to CP-14; D-184 callback (expired state stored as
expired, revises CP-2; atomic claim; session without accounts revoked); D-185 fetch details;
D-186 PSU headers; D-187 E-41, E-43, E-44 details, CardAttempt.failure a plain string; D-188
placement and the secrets/cards compose mount. D-189, D-190 unused.

SPEC: §9.12 CP-2 to CP-5, CP-7, CP-8, CP-14; §10.2; §11.1, §11.2 K1 row with the landing
gates, §11.3; §14.1.3; §14.3.3; §14.3.4 (E-1 schema_version 4, E-37, E-39, E-41, E-43, E-44);
§14.3.5; §14.7; change log. PLAN-K2, PLAN-K3 and prompts/README.md name v0.39 and v0.40.
HANDOFF: K1 landed, K2 and K3 running, the Android gate red until K2, K1 owner steps 1 to 4
pending, next free D-212 and v0.39.

Pre-commit hook bypassed (--no-verify): docs only; its Android fast tests fail only in
:domain VectorFilesTest, red on main by design until K2 lands (as for 671c00d).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-24 16:50:04 +02:00

11 KiB
Raw Blame History

PLAN-K2: Card purchases, Android

1. Goal

Card purchases in the Android app (FR-22, SPEC §9.12, D-169, D-170, D-172, D-173): the Kortköp tab as the second bottom destination, Anslut kort in Settings with the SJ Prio artwork, the BankID connect flow in a Custom Tab, the purchase sheet with "Skapa utgift", suggested matches and "Ignorera", "Från kortköp" on account detail, the card line on expense detail, offline-capable link changes, Room version 2. A UI-contract change (:core:model is a contract file, §14.8) with data layer, fakes, features and goldens.

2. Read first

  1. SPEC §9.12 (CP-1 to CP-14, with K0's answers in v0.36), FR-22, §9.9, §9.11 (the suggestion on the prefilled description), §13.2 (four destinations), §13.3 rows S-5, S-7, S-8, S-13, S-23 to S-27 and the "Card purchase strings" table, §14.2 (§14.2.1 card entities, §14.2.3 version 2, §14.2.4 "Changes in v0.35"), §14.3.3 and §14.3.4 E-36 to E-44, §14.4.1, §14.5.8, §6.2 (module table, layering, feature convention), §14.7, §14.8 (compatibility); D-169, D-170, D-172, D-173, D-177 to D-180 (K0's results and the artwork).
  2. K1's contracts/openapi.yaml operations E-36 to E-44, contracts/examples/E-36-* to E-44-*, contracts/test-vectors/match_card_purchase.json (on main once K1 lands; until then the spec).
  3. docs/ux/design-spec.md §2 (tokens), §3.4, §3.5 (ledger rows), §3.6 (banners), §3.11 (scaffolding), S-4, S-5, S-8, S-13, S-16 (for list and banner patterns); docs/ux/strings.csv.
  4. Files: android/core/model/**, android/domain/**, android/data/** (KvittDatabase, sync engine, KvittApi, DataBindingsModule), android/core/fakes/**, android/core/design/** (KvittNavigationBar, KvittDestination), android/feature/{account,expense,settings}/**, android/app/** (nav/KvittApp.kt, the manifest's intent filters), android/README.md.
  5. CLAUDE.md (Android gate, layering, anti-patterns, screenshots, the phone, personal data).

3. Already established

  • The UI contract of §14.2.4 "Changes in v0.35" is binding (names and members); additions need a decision, proposed in the report.
  • New module :feature:cards (android/feature/cards/, package se.hagstedt.kvitt.feature.cards), included in android/settings.gradle.kts, following the feature convention (D-017): one entry composable per screen, navigation as lambdas, ViewModels on :core:model interfaces only, strings prefixed cards_.
  • :core:design: KvittDestination.Cards between Home and Analytics, icon credit_card (Material Symbols, outlined like the others), label nav_cards; every top-level screen's bar shows four items, so the S-3, S-4, S-11, S-13 goldens change (re-record and open each).
  • Card artwork (D-173, D-180): the owner's picture is committed at android/feature/cards/src/main/res/drawable-nodpi/card_sj_prio_mastercard.png (1313 × 829, cropped to the card, aspect 1.584; transparent only at its rounded corners and in the semicircular notch on the right edge, as on the physical card). CardArt(imageKey, name, modifier) in :feature:cards maps sj_prio_mastercard to R.drawable.card_sj_prio_mastercard from the first commit and draws it at its own aspect ratio inside the card box (ContentScale.Fit), without clipping or a background, so its own corners and notch show; content description the card's name. For any unknown key it draws the fallback card: aspect ratio 1.586:1, corner radius 16 dp, fill the green category token's light value in both themes, the card name in white (contrast 4.95:1), no other text. The goldens use the committed picture; one golden covers the fallback (a fake catalogue entry with an unknown image key). The picture shows the SJ and Mastercard logos; the owner cleared that use (2026-10-24, the "Licences first" step). Do not edit or re-encode the PNG.
  • Custom Tab: androidx.browser (catalog entry, latest stable at implementation time, documented usage confirmed from the official docs). The deep link kvitt://cards/connected?attempt=<id> is an intent filter on MainActivity (exported already); :app routes it.
  • Room version 2 with MIGRATION_1_2 and its test (§14.2.3), the exported 2.json committed. The revoke wipe includes the card tables.
  • The card cycle (§14.2.4 :data) runs after every household sync cycle and after refresh; pending link changes go out after the household push (CP-9).
  • matchCardPurchase in :domain passes match_card_purchase.json in the vector test.
  • No card content in any log, debug builds included; no notification for card purchases (D-171).
  • Strings: the keys of §13.3's table are added to docs/ux/strings.csv (en and sv) by this plan, with any TalkBack keys it needs (listed in the report); then to the modules' strings.xml.

4. Owned paths

From SPEC §14.7 (row K2): android/feature/cards/** (new), and as a contract-change plan android/core/model/**, android/domain/**, android/data/** (with android/data/schemas/**), android/core/fakes/**; android/core/design/** (the destination and nav_cards only), android/feature/account/** (S-5's action), android/feature/expense/** (S-7's prefill and link, S-8's line), android/feature/settings/** (S-13's row), android/app/** (navigation, deep link, bindings), android/settings.gradle.kts, android/gradle/libs.versions.toml, docs/ux/strings.csv (the card keys). Nothing in server/**, contracts/**, web/**, SPEC.md, docs/decisions.md.

5. Deliverables

  • :core:model model/Cards.kt and the members of §14.2.4; ContractTest updated.
  • :domain matchCardPurchase with its vector test.
  • :data: DTOs for E-36 to E-44 with round-trip tests over K1's examples; CardRepositoryImpl; the card cycle with MockEngine tests (paging, tombstones, pending rows skipped on pull, the E-44 outcomes of §14.2.1, a 404 from E-40 as "no card support", cursor_ahead); prefill (CP-9) tests; the local unlink when an expense is deleted locally; Room 2 and the migration test.
  • :core:fakes: FakeCardRepository and fakes/cards/ scenarios for every state of S-23 to S-27 (§14.2.4 list).
  • :feature:cards: S-23 (with the sheet and the second sheet "Välj en annan utgift"), S-24, S-25 (polling E-38 every 2 seconds while open and resumed, the Custom Tab, the return link), S-26 (with the disconnect dialog), S-27; screenshot tests of every state in the D-098 variants (sv light, sv dark, en light, plus _sv_light_200 for the long states); state-holder unit tests.
  • :feature:expense: S-7 with the optional cardPurchaseId argument (prefill, then link after a successful save; a foreign-currency purchase leaves the amount empty with the S-23 note), S-8's line "Från kortköp: , " with "Ta bort koppling"; goldens for the new states.
  • :feature:account: S-5's overflow item "Från kortköp" while hasConnection; :feature:settings: S-13's row "Anslut kort" with its subtitle; goldens re-recorded where they change.
  • :app: the fourth destination, routes for S-23 to S-27, the S-7 argument, the deep link, the binding in DataBindingsModule.

6. Steps

  1. Worktree: from /Users/jhag/projects/kvitt, git worktree add .claude/worktrees/agent-k2 -b agent-k2 main; in it git reset --hard main; copy android/local.properties and android/app/google-services.json from the main checkout (never commit them); tools/install-git-hooks.sh.
  2. Confirm SPEC v0.36 (K0's answers) is on main; if not, stop and report. K2 may start before K1 lands, on the spec and fakes; before its gate it rebases on K1 (DTO tests need K1's examples, the vectors file is K1's). If K1 has not landed when everything else is done, stop and report rather than inventing examples.
  3. python3 tools/dependency-report/latest.py; record the lines.
  4. Model, domain, data (Room 2 first, with its test), fakes, :feature:cards, the edits in the other features and :app, strings, goldens (open every changed PNG).
  5. Phone (only if the brief names this agent for it; one agent on the phone at a time): confirm the ids (adb shell pm list packages | grep se.hagstedt.kvitt), installDebug of the debug app against the development stack for the screens and offline behaviour; the real BankID flow needs the release app against the Pi with K1 deployed (§8). Never clear the real app's data.
  6. Gate (§7). Commit on agent-k2, git rebase main, rerun the gate. Never merge, push or remove the worktree.

7. Gate

The Android gate, from android/: PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ../tools/gradle-serial.sh check. Green: BUILD SUCCESSFUL in this session, test counts from fresh XML, screenshot verification passing.

8. HUMAN steps (relayed by the orchestrator in chat)

  1. After K1 is deployed on the Pi and the K2 release APK is installed (Auto Blocker off for the update, back on afterwards): Inställningar › Anslut kort › SJ Prio Mastercard › 30 dagar › "Anslut med BankID"; BankID opens, sign; back in Kvitt "Kortet är anslutet" and the Kortköp tab lists your purchases (yes/no each).
  2. Kortköp › a purchase › "Skapa utgift" › "Spara": the expense is in its account and the purchase is under "Kopplade" (yes/no).
  3. On a purchase your partner already entered: the row shows "Koppla till …"; tap it: under "Kopplade" (yes/no).
  4. Flight mode on: ignore a purchase, then flight mode off: it stays ignored after a sync (yes/no).
  5. An account › menu › "Från kortköp": a purchase › "Spara": the expense is in that account (yes/no).
  6. Inställningar › Anslut kort › SJ Prio Mastercard › "Koppla från" › "Koppla från": the Kortköp tab shows "Anslut ditt kort" and the linked purchases are kept under "Kopplade" after reconnecting (yes/no).
  7. On the partner's phone (after she connects her card, PLAN-K1 §8 step 5): Kortköp shows only her purchases, and none of yours anywhere (yes/no).

9. Out of scope

  • The server, the contract files, the web (K1, K3). A card notification (OQ-7), bulk add (OQ-8), automatic linking (D-171).
  • Changes to docs/ux/design-spec.md (the report proposes the S-23 to S-27 design-spec sections for a docs agent).

10. Report

  • Branch agent-k2, head commit after rebasing; gate lines; tests; goldens added and re-recorded (names).
  • Proposed decision entries and spec text for anything beyond §14.2.4 (numbered from the brief); the strings added to strings.csv (keys); proposed design-spec sections for S-23 to S-27.
  • Dependency-report lines; HUMAN steps outstanding worded for relay; open questions with SPEC section numbers.

11. Numbering

Room version 2; decisions from the start prompt (TODO(spec) if none was given). Proposed spec text goes into SPEC v0.39 (v0.36 recorded K0's results, v0.38 K1's report; K3's goes into v0.40).