Files
jacobh2 e6768121d6 SPEC v0.9: M12b and M12c outcomes (D-055 to D-058), U2 values, UX-9 (D-059, M12d)
- D-055 build-time copy of the keyword table, D-056 parseCategoryKeywords,
  D-057 enum declaration orders, D-058 E-7 example shows server_version
  15 and 16 and its test compares them again.
- D-051 fulfilled: §9.7 hex values from docs/ux/design-spec.md §2.3;
  design-spec.md binding, strings.csv the source of string resources.
- D-059: ExpenseEditorOptions.frequentCategoryIds and
  CategoryRepository.deletable; new milestone M12d with PLAN-M12d and
  prompts/M12d.md; M7a-f depend on M6 and M12d.
- suggestCategory readings (null when other is archived), §11.2 landed
  marks and the 2026-10-18 dry run, dev stack on schema 2, PLAN-M12b
  SQLite 3.51.x, design-spec §6 notes in PLAN-M6/M7c/M7f; HANDOFF rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
2026-10-18 08:34:45 +02:00

97 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PLAN-M7f: Feature settings
## 1. Goal
Build `:feature:settings`: the settings hub (S-13), profile (S-14), categories (S-15), recurring expenses (S-19), devices (S-20), invites (S-21) and about (S-22), against the `:core:model` interfaces and `:core:fakes`. Every state of every screen 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-13, S-14, S-15, S-19, S-20, S-21, S-22.
2. `docs/ux/strings.csv` (keys for these screens) and `docs/ux/screens/S-13-*`, `docs/ux/screens/S-14-*`, `docs/ux/screens/S-15-*`, `docs/ux/screens/S-19-*`, `docs/ux/screens/S-20-*`, `docs/ux/screens/S-21-*`, `docs/ux/screens/S-22-*`.
3. SPEC §13.2 Navigation model and §13.3 Screens (rows S-13, S-14, S-15, S-19, S-20, S-21, S-22 and the states paragraph).
4. SPEC §14.2.4 UI contract: `ProfileRepository`, `CategoryRepository`, `RecurringRepository`/`RecurringTemplate`, `DeviceRepository`/`Device`, `InviteRepository`/`Invite`, `AboutRepository`/`AboutState`, `UpdateRepository`, `SessionRepository` (role), `ConnectivityRepository`, `ExpenseDraft`, `OnlineResult`.
5. SPEC §4 FR-2 AC-2.1, AC-2.2, AC-2.11; FR-3 Devices and revocation (AC-3.1, AC-3.2); FR-4 Profiles (AC-4.1 to AC-4.5); FR-7 Categories (AC-7.1 to AC-7.7); FR-9 Recurring expenses (AC-9.1, AC-9.7, AC-9.8); FR-16 (check for update); §8.3 (code display), §8.4 Authorization rules, §9.6, §9.7, §9.9.
6. SPEC §6.2 (module table), §9.8 Formatting, §9.9 Online-only actions, §11.2 (row M7f), §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-19) 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).
- Hub rows: Profile, Categories, Recurring expenses, Devices, Invite partner or Pair a device (admin only), Sync status (opens S-16 via `:app`), About; own and partner's avatar and name (§13.3 S-13, §8.4).
- Profile: own name and picture only, from the Android photo picker, online only (AC-4.2, AC-4.3, §9.9).
- Categories: list with icon, colour, name and an archived section; editor with "Reset name" for defaults, the 33 icons and 14 colour swatches of §9.7 (D-054); reorder by drag; delete blocked with the reason (`category_in_use`, `category_is_default`), archive offered instead; all online only (FR-7, §13.3 S-15).
- Category editor delete (D-059, SPEC §14.2.4 "Changes in v0.9"): "Ta bort kategori" is disabled with its reason before any tap, from `CategoryRepository.deletable(id)` and `defaultKey`: default → `settings_category_delete_blocked_default_short`; in use and archived → `settings_category_delete_blocked_in_use_archived`; in use and active → `settings_category_delete_blocked_in_use`; a `Failed("category_in_use")` or `Failed("category_is_default")` after a tap still shows the same reason inline. M12d must have landed (§11.2).
- Recurring editor S-19 has **no category suggestion** (§9.11 CS-1, design-spec §6 point 3): its default category is the AC-8.6-style default (last category of the account, else Övrigt); its `CategoryQuickRow` uses `ExpenseEditorOptions.frequentCategoryIds` like S-7 (D-059).
- Design-spec open points (`docs/ux/design-spec.md` §6) carried here: tabular figures as M6 delivers them (checked by M6 on the S26); Material 3 Expressive components only as M6 provides them, with M6's stable fallbacks.
- Recurring: list with description, amount, account, "Every month on day 25", next date, paused badge; editor with the expense fields of S-7 except date, plus day of month, first month (default per AC-9.8, up to 12 months back), optional last month, pause switch; online only (FR-9, §13.3 S-19).
- Every settings write is online: a refused value comes back as `OnlineResult.Failed` with the §14.3.2 code and `field` (for example `validation_failed` on `name`), shown inline at that field (§9.6, §14.2.4, §14.3.2).
- Devices: rows per AC-3.1 ("This device" marked); revoke with confirmation; the current device cannot be revoked; admin sees all, member own (AC-3.1, AC-3.2).
- Invites (admin): "Invite partner" only before the partner joins, "Pair another device" for either user; a created invite shows the code in groups of 4, the link, expiry and an Android share action; pending invites with cancel (AC-2.1, AC-2.2, AC-2.11, §13.3 S-21).
- About: version name and code, server URL, server version from `/health` (null offline), open-source licences, "Check for update" (§13.3 S-22).
## 4. Owned paths
From SPEC §14.7, verbatim: `android/feature/settings/**` (owner M7f), 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/settings/` (§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/settings/build.gradle.kts`: dependencies `:core:model`, `:core:design`; `:core:fakes` for tests and debug only.
- `android/feature/settings/src/main/kotlin/se/hagstedt/kvitt/feature/settings/**`: the screens of this module and their state holders.
- `android/feature/settings/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/settings/**`: 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-13**: admin; member; partner not yet joined; offline; long content (long names).
- **S-14**: with picture; initials only; upload in progress; error (`image_invalid`, `payload_too_large`, network); "Needs connection".
- **S-15**: list with archived section; editor for a default category (Reset name) and a custom one; icon grid; colour swatches; reorder in progress; delete not allowed with each reason; error; "Needs connection"; long content.
- **S-19**: list with active and paused templates; empty; editor new and edit (each split mode); start period in the past (backfill); end period set; error `validation_failed`; "Needs connection"; long content.
- **S-20**: admin with both users' devices incl. a revoked one; member with own devices; revoke confirmation; error `cannot_revoke_current_device`; "Needs connection"; loading.
- **S-21**: admin before partner joins; after partner joined (device invites only); created invite with code, link, expiry, share; pending list with cancel; error `household_full`; "Needs connection"; empty.
- **S-22**: online with server version; offline (server version unknown); check-for-update result states.
## 6. Steps
1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m7f -b agent-m7f main`, then in `.claude/worktrees/agent-m7f`: `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-m7f`, `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:settings`; 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-11, S-12 (M7e).
- 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-m7f`, 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.