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>
9.5 KiB
PLAN-M7c: Feature expense
1. Goal
Build :feature:expense: the full-screen expense editor (S-7) and the expense detail with its timeline (S-8), against the :core:model interfaces and :core:fakes. Every state of both screens is screenshot-tested in Swedish and English, light and dark.
2. Read first
docs/ux/design-spec.md: tokens, component inventory, and the sections for S-7, S-8.docs/ux/strings.csv(keys for these screens) anddocs/ux/screens/S-7-*,docs/ux/screens/S-8-*.- SPEC §13.2 Navigation model and §13.3 Screens (rows S-7, S-8 and the states paragraph).
- SPEC §14.2.4 UI contract:
ExpenseRepository,ExpenseDraft,ExpenseEditorOptions,ExpenseDetailState,TimelineLine,FieldChange,Share,SaveResult,FieldError,SplitMode,Account,Category,User. - SPEC §4 FR-8 Expenses (AC-8.1 to AC-8.11); FR-12 AC-12.4, AC-12.5; §9.1 Splits; §9.6 (limits, amount input parsing); §9.10 (exact-mode edit); §14.5.3 error tokens.
- SPEC §6.2 (module table), §9.8 Formatting, §9.9 Online-only actions, §11.2 (row M7c), §11.3 (note M7x), §14.7.
- Files:
android/core/model/**,android/core/design/**(read only),android/core/fakes/**,android/build-logic/**,CLAUDE.md(BUILD AND VERIFY: Screenshots; ARCHITECTURE RULES; ANTI-PATTERNS).
3. Already established
- Features depend only on
:core:model,:core:designand:core:fakes;:core:fakesonly in test, screenshot-test and debug-preview configurations, never in a release build. Features never depend on:dataor:domain(§6.2, §11.3, CLAUDE.md Layering). - Screens consume the
*Stateclasses through the repository interfaces of §14.2.4; no field may be added except through a contract change (§14.2.4, §14.8). - Fake scenarios go only in
android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/<feature>/(§11.3, §14.7). - Strings live in the module's
src/main/res/values/strings.xmlandvalues-sv/strings.xmlwith keys fromdocs/ux/strings.csv; no string outside that file without a decisions entry (§13.4, §14.7, AC-19.1). - No hard-coded colours, sizes or strings; tokens and shared components come from
:core:design, which features may not edit (CLAUDE.md, §14.7). Money and dates go throughMoneyFormatterandDateFormatter(§9.8). - Online-only actions are disabled with "Needs connection" when offline (§9.9); a REST failure shows "Could not reach the server".
- Navigation graph, deep links and Hilt bindings are in
:app(M8); a feature exposes its screens with navigation callbacks in the form M2b's feature convention fixed (§11.3). - Layout and visuals follow
docs/ux/design-spec.md; TalkBack labels as it specifies; 48 dp targets, 200 % font scale reflow (§13.1). - No placeholder text in previews or screenshots; goldens are committed; open every changed PNG before committing (CLAUDE.md).
- The add flow needs at most: open app, tap add, type amount, type description, tap save; account, category, payer and split are defaulted (§13.1, AC-8.6).
- Editor is a full screen, not a bottom sheet (§13.2). Amount input accepts digits, one decimal separator (
,or.), at most two decimals, no sign, converted to öre by string parsing, never floating point (§9.6). - Defaults: account = last used on this phone if active else oldest active; category = last used in that account if active else
other; split modedefault; payer = current user; date = today in the phone's zone (AC-8.3, AC-8.4, AC-8.6). Defaults come fromExpenseEditorOptions. - Split modes
default("Account default 60/40"),ratio,exact(type one share, the other is amount minus it, read-only; a share larger than the amount blocks saving inline),full(one bearer); changing account indefaultre-copies the ratio; live preview fromExpenseRepository.preview(AC-8.5, AC-8.7, AC-8.8, §13.3 S-7). - Validation errors come back as
SaveResult.Invalidwith §14.5.3 tokens orrequired; deleting asks for confirmation (AC-8.9). - Detail shows amount, description, category, account, date, payer, shares and mode, notes, "Recurring: " when generated, and the timeline: newest last, one line per entry, field names in the UI language, ids resolved, pending line "Your change is waiting to sync"; a deleted state when opened from a deletion notification (AC-8.11, AC-12.4, AC-12.5, §13.3 S-8).
4. Owned paths
From SPEC §14.7, verbatim:
android/feature/expense/**(owner M7c), and from theandroid/core/fakes/**row: "M7x add files only underfakes/<feature>/", hereandroid/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/expense/(§11.3). Catalog entries and module includes may be added (§14.7,android/gradle/**row).Edit nothing outside these paths; a needed change elsewhere is a contract request to the orchestrator.
5. Deliverables
android/feature/expense/build.gradle.kts: dependencies:core:model,:core:design;:core:fakesfor tests and debug only.android/feature/expense/src/main/kotlin/se/hagstedt/kvitt/feature/expense/**: the screens of this module and their state holders.android/feature/expense/src/main/res/values/strings.xml,values-sv/strings.xml: keys fromdocs/ux/strings.csv.android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/expense/**: a scenario per state below.- Screenshot tests and committed goldens for every screen × state × light/dark × sv/en.
- Unit tests for the state holders (input handling, enablement rules, error mapping).
Screens and states. Required states for every screen below (SPEC §13.3): loaded, empty, offline (with pending changes), loading (first sync only), error (a failed online action), and for lists long content (long Swedish names, 7-digit amounts), each where it applies. A state that
docs/ux/design-spec.mdmarks not applicable for a screen is skipped and named in the report. Each state is screenshot-tested in light and dark, Swedish and English. Screen-specific states from §13.3 "Must show" are added per screen:- S-7: new (defaults applied); edit mode with Delete; each split mode
default,ratio,exact,full(on payer and on the other user); exact share larger than the amount (inline error, save blocked); missing description and missing amount errors; notes collapsed and expanded; account picker open; category grid open; date picker; delete confirmation; offline (saving still allowed); long content (long description, 7-digit amount, long account and category names). - S-8: loaded in each split mode; recurring; with notes; timeline with create, change (amount, account, split) and pending lines; deleted; pending; offline; long content (long timeline).
6. Steps
- Worktree. From the main checkout
/Users/jhag/projects/kvitt:git worktree add .claude/worktrees/agent-m7c -b agent-m7c main, then in.claude/worktrees/agent-m7c:git reset --hard main. Copyandroid/local.propertiesfrom the main checkout (addsdk.dir=/opt/homebrew/share/android-commandlinetoolsif missing; never overwrite or commit). Runtools/install-git-hooks.sh. - Read §2 in order. Confirm M6 has landed (
android/core/design/has components onmain); if not, stop and report. If a component the design spec assigns to:core:designis missing, file a contract request; do not build it inside the feature. - Run
python3 tools/dependency-report/latest.py; record the lines. No new library is expected; any one added is at its latest stable version from official docs; record it. - Write the fake scenarios under the feature's
fakes/<feature>/directory for every state listed in §5. - Write the state holders and screens, one screen at a time, with strings from
docs/ux/strings.csvin both languages. - Write the screenshot tests; record goldens; open every PNG and compare with
docs/ux/screens/. - Run the gate (§7). Commit on
agent-m7c,git rebase main, rerun the gate. Never merge, push or remove the worktree.
7. Gate
From SPEC §11.2, verbatim: "as M7a", that is "Android gate with screenshot tests of every state, sv and en, light and dark". Command, from
android/:PATH=/opt/homebrew/opt/openjdk@25/bin:$PATH ../tools/gradle-serial.sh check. Green means:BUILD SUCCESSFULin this session; screenshot verification passes for:feature:expense; the report lists, per screen, every state × light/dark × sv/en golden; unit tests 0 failures in fresh XML.8. HUMAN steps
From SPEC §11.2, verbatim: "none".
9. Out of scope
- Other screens: S-1, S-2, S-3, S-18 (M7a); S-4, S-5, S-6, S-16, S-17 (M7b); S-9, S-10 (M7d); S-11, S-12 (M7e); S-13, S-14, S-15, S-19, S-20, S-21, S-22 (M7f).
- Navigation graph, deep links, Hilt bindings, notifications (M8); real repositories (M5).
- Editing
:core:design(requests go to the orchestrator) or:core:model; any dependency on:dataor:domain. - Fake scenarios outside this plan's
fakes/<feature>/directories.
10. Report
- Branch
agent-m7c, head commit hash after rebasing onmain. - Gate output: BUILD line, actionable tasks line, screenshot task result, test counts from fresh XML.
- Screens × states table with golden file names; states skipped as not applicable.
- Versions added (if any) with sources; dependency-report lines.
- Strings used that are missing from
strings.csv(should be none) and design-spec mismatches. - Numbering used or
TODO(spec)occurrences. - Contract requests (
:core:design,:core:model), 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.