- 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
12 KiB
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
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.docs/ux/strings.csv(keys for these screens) anddocs/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-*.- 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).
- SPEC §14.2.4 UI contract:
ProfileRepository,CategoryRepository,RecurringRepository/RecurringTemplate,DeviceRepository/Device,InviteRepository/Invite,AboutRepository/AboutState,UpdateRepository,SessionRepository(role),ConnectivityRepository,ExpenseDraft,OnlineResult. - 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.
- SPEC §6.2 (module table), §9.8 Formatting, §9.9 Online-only actions, §11.2 (row M7f), §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 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 throughMoneyFormatterandDateFormatter(§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 theNavHost; ViewModels get repositories through the:core:modelinterfaces. 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
MissingTranslationis an error in this module (§6.2, §13.4, D-017). - The states each screen must have are the ones
docs/ux/design-spec.mdlists 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)anddefaultKey: 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; aFailed("category_in_use")orFailed("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
CategoryQuickRowusesExpenseEditorOptions.frequentCategoryIdslike 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.Failedwith the §14.3.2 code andfield(for examplevalidation_failedonname), 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:fakesfor 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 fromdocs/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
- 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. 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-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:dataor:domain. - Fake scenarios outside this plan's
fakes/<feature>/directories.
10. Report
- Branch
agent-m7f, 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. - Proposed decision entries (never written to
SPEC.mdordocs/decisions.mdby this agent) orTODO(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.