Files
kvitt/PLAN-M7c.md
T
jacobh2 76ee957f41 SPEC v0.3: orchestrator resolutions of the plans' open points
Records D-004 and D-015 to D-025: shells and ownership, server seams
(router middleware, rate_limit layer, tasks::start, AuditWriter and PushQueue
traits, next_occurrence_date in kvitt-domain, M3 owns recurring generation,
M4 lands after M3), OpenAPI parsed-value equality, feature convention and
strings, empty fake scenario and resolveRange exception, shared ledger rows and
editor fields in :core:design, no bundled font, pruning at 04:00, development
infrastructure as M2c-dev built it, M2c-pi allowances, release keystore,
production import procedure. Plans and prompts close their open points;
HANDOFF rewritten to the real state; CLAUDE.md: implementation agents never
edit SPEC.md or docs/decisions.md.

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

10 KiB
Raw Blame History

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

  1. docs/ux/design-spec.md: tokens, component inventory, and the sections for S-7, S-8.
  2. docs/ux/strings.csv (keys for these screens) and docs/ux/screens/S-7-*, docs/ux/screens/S-8-*.
  3. SPEC §13.2 Navigation model and §13.3 Screens (rows S-7, S-8 and the states paragraph).
  4. SPEC §14.2.4 UI contract: ExpenseRepository, ExpenseDraft, ExpenseEditorOptions, ExpenseDetailState, TimelineLine, FieldChange, Share, SaveResult, FieldError, SplitMode, Account, Category, User.
  5. 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.
  6. SPEC §6.2 (module table), §9.8 Formatting, §9.9 Online-only actions, §11.2 (row M7c), §11.3 (note M7x), §14.7.
  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:design and :core:fakes; :core:fakes only in test, screenshot-test and debug-preview configurations, never in a release build. Features never depend on :data or :domain (§6.2, §11.3, CLAUDE.md Layering).
  • Screens consume the *State classes 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.xml and values-sv/strings.xml with keys from docs/ux/strings.csv; no string outside that file unless the report proposes it as a decision entry for the orchestrator (§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 through MoneyFormatter and DateFormatter (§9.8).
  • Online-only actions are disabled with "Needs connection" when offline (§9.9); a REST failure shows "Could not reach the server".
  • Feature convention (§6.2, D-017): one composable entry point per screen, taking a hiltViewModel()-provided ViewModel and its navigation as plain lambdas; the feature never references another feature or the NavHost; ViewModels get repositories through the :core:model interfaces. Navigation graph, deep links and Hilt bindings are in :app (M8).
  • M2b created this module's shell with one empty entry composable (D-004); this plan owns it and replaces it with the real entry points.
  • Keys are prefixed by the feature name; lint MissingTranslation is an error in this module (§6.2, §13.4, D-017).
  • The states each screen must have are the ones docs/ux/design-spec.md lists for it (§13.3).
  • Shared UI (D-019): the expense editor fields (S-7) come from :core:design, built by M6; never copy or re-implement it here.
  • 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 mode default; payer = current user; date = today in the phone's zone (AC-8.3, AC-8.4, AC-8.6). Defaults come from ExpenseEditorOptions.
  • 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 in default re-copies the ratio; live preview from ExpenseRepository.preview (AC-8.5, AC-8.7, AC-8.8, §13.3 S-7).
  • Validation errors come back as SaveResult.Invalid with §14.5.3 tokens or required; 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 the android/core/fakes/** row: "M7x add files only under fakes/<feature>/", here android/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:fakes for 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 from docs/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.md marks 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

    1. 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. Copy android/local.properties from the main checkout (add sdk.dir=/opt/homebrew/share/android-commandlinetools if missing; never overwrite or commit). Run tools/install-git-hooks.sh.
    2. Read §2 in order. Confirm M6 has landed (android/core/design/ has components on main); if not, stop and report. If a component the design spec assigns to :core:design is missing, file a contract request; do not build it inside the feature.
    3. 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.
    4. Write the fake scenarios under the feature's fakes/<feature>/ directory for every state listed in §5.
    5. Write the state holders and screens, one screen at a time, with strings from docs/ux/strings.csv in both languages.
    6. Write the screenshot tests; record goldens; open every PNG and compare with docs/ux/screens/.
    7. 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 SUCCESSFUL in 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 :data or :domain.
    • Fake scenarios outside this plan's fakes/<feature>/ directories.

    10. Report

    • Branch agent-m7c, head commit hash after rebasing on main.
    • 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.
    • Proposed decision entries (never written to SPEC.md or docs/decisions.md by this agent) 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.