Files
kvitt/PLAN-M7e.md
T
jacobh2 6dce4c73a9 SPEC v0.4: M2a and M2b decisions, handoff to the real state
Records the decisions proposed in the M2a (fdf2093) and M2b (fad6fc9)
reports: D-005 OpenAPI equality normalisations and CommonRules, D-006
bp_sum_not_10000 token, D-007 per-crate no-unwrap deny, D-010 Roborazzi,
D-011 IncompleteCustomRangeException, D-012 Room 3 with M2b's entities and
1.json as a contract file, D-013 JDK 25 Robolectric flags, D-014
failOnNoDiscoveredTests (D-008, D-009 unused). Clarifications in §6, §8.2,
§8.6, §9.6, §10.3, §10.4, §11, §14.2, §14.3, §14.5, §14.7, §14.8 and
AC-11.7. PLAN-M5 builds on the existing entities; v0.4 readings added to
PLAN-M3, M5, M7b to M7f. HANDOFF rewritten: next free D-040, spec v0.5,
server schema 2, Room schema 2.

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

9.9 KiB
Raw Blame History

PLAN-M7e: Feature analytics

1. Goal

Build :feature:analytics: the analytics screen with range, granularity, measure and group-by controls and the three charts (S-11), and the drill-down list (S-12), 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-11, S-12.
  2. docs/ux/strings.csv (keys for these screens) and docs/ux/screens/S-11-*, docs/ux/screens/S-12-*.
  3. SPEC §13.2 Navigation model and §13.3 Screens (rows S-11, S-12 and the states paragraph).
  4. SPEC §14.2.4 UI contract: AnalyticsRepository, AnalyticsFilter, AnalyticsState, DrillDownState, GroupKey, SpendBucket, RangePreset, Granularity, Measure, GroupBy, LedgerRow.ExpenseRow.
  5. SPEC §4 FR-14 Analytics (AC-14.1 to AC-14.10); §14.5.4 Analytics (semantics the repository already applies); §9.8.
  6. SPEC §6.2 (module table), §9.8 Formatting, §9.9 Online-only actions, §11.2 (row M7e), §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 ledger row (S-12) comes 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).
  • Presets this_month, last_3_months, last_6_months, last_12_months, this_year, all_time, custom (start and end pickers); default last_6_months; granularity month (default) or week; measure household (default) or my_share; group by category (default) or account (AC-14.2 to AC-14.5).
  • Charts: stacked bars per bucket coloured by group with legend; category breakdown and account breakdown as horizontal bars sorted descending with amount and percent; range total above the charts; settlements never count (AC-14.6). Charts are :core:design components (M6).
  • Every bucket appears, empty ones as zero; percentages rounded half-up to one decimal and may not sum to 100 (AC-14.7, AC-14.10). Values come computed from the repository.
  • Tapping a bar or a breakdown row opens S-12 with the expenses behind that number; title names the filter ("Mat & hushåll, oktober 2026") (AC-14.8, §13.3 S-12).
  • DrillDownState.title is a String of unlocalised parts; S-12 formats its visible title itself from the arguments it passed to drillDown (the category or account, with default category names from resources, and the bucket key through DateFormatter) (§14.2.4, v0.4). AnalyticsState has no error field: the custom-range editor never applies a range with a missing start or end (the repository would treat a missing date as today) (§14.2.4, §14.5.4).
  • Archived accounts and categories are included; deleted expenses excluded (AC-14.9).

4. Owned paths

From SPEC §14.7, verbatim: android/feature/analytics/** (owner M7e), 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/analytics/ (§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/analytics/build.gradle.kts: dependencies :core:model, :core:design; :core:fakes for tests and debug only.
  • android/feature/analytics/src/main/kotlin/se/hagstedt/kvitt/feature/analytics/**: the screens of this module and their state holders.
  • android/feature/analytics/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/analytics/**: 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-11: default filter by category; by account; week granularity; my-share measure; each preset incl. custom range picker; empty "No expenses in this period"; offline (works, pending included); loading (first sync); long content (many groups, long Swedish category names, 7-digit totals).
  • S-12: bucket drill-down; category drill-down; account drill-down; empty; long content.

6. Steps

  1. Worktree. From the main checkout /Users/jhag/projects/kvitt: git worktree add .claude/worktrees/agent-m7e -b agent-m7e main, then in .claude/worktrees/agent-m7e: 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-m7e, 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:analytics; 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-7, S-8 (M7c); S-9, S-10 (M7d); 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-m7e, 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.