6dce4c73a9
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>
87 lines
9.9 KiB
Markdown
87 lines
9.9 KiB
Markdown
# 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.
|