# PLAN-M7a: Feature onboarding ## 1. Goal Build `:feature:onboarding`: pairing (S-1), the partner profile step (S-2), waiting for the partner (S-3) and device removed (S-18), 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-1, S-2, S-3, S-18. 2. `docs/ux/strings.csv` (keys for these screens) and `docs/ux/screens/S-1-*`, `S-2-*`, `S-3-*`, `S-18-*`. 3. SPEC §13.2 Navigation model and §13.3 Screens (rows S-1, S-2, S-3, S-18 and the states paragraph). 4. SPEC §14.2.4 UI contract: `SessionState`, `SessionRepository`, `ProfileRepository`, `InviteRepository`, `ConnectivityRepository`, `OnlineResult`, `User`. 5. SPEC §4 FR-2 Invites and pairing (AC-2.6, AC-2.7, AC-2.8, AC-2.9, AC-2.10), FR-3 AC-3.3, FR-4 AC-4.3, AC-4.5, FR-5 AC-5.1; §8.3 Invite codes (display and normalisation); §14.4.8 (the "Pair this phone again" case). 6. SPEC §6.2 (module table), §9.9 Online-only actions, §11.2 (row M7a), §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//` (§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). - 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). - Pairing accepts a server URL (prefilled `https://icehack.asuscomm.com:8443` in release builds) and a 12-character code shown in groups of 4, normalised per §8.3 before sending; release builds accept only `https://`, debug builds also `http://` for `localhost`, `10.0.2.2` and private LAN addresses (AC-2.7, AC-2.8, §8.3). - Deep links `kvitt://invite?server=…&code=…` and `https://icehack.asuscomm.com:8443/i/` open S-1 with both fields filled; parsing the intent is `:app`'s, the screen takes the values (AC-2.6). - A `partner` pairing continues to S-2 (display name prefilled, optional picture from the photo picker, "You are joining ") before Home (AC-2.10, AC-4.3). - S-3: until the partner joins, the admin sees "Waiting for your partner" with "Invite partner" (to S-21) and Settings (AC-5.1, §13.3). - S-18 after `device_revoked`: explanation that access was removed and local data deleted; "Pair again" goes to S-1 (AC-3.3, §13.3). - `SessionState.Unrecognised` shows S-1 with "Pair this phone again" (§14.4.8). ## 4. Owned paths From SPEC §14.7, verbatim: `android/feature/onboarding/**` (owner M7a), and from the `android/core/fakes/**` row: "M7x add files only under `fakes//`", here `android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/onboarding/` (§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/onboarding/build.gradle.kts`: dependencies `:core:model`, `:core:design`; `:core:fakes` for tests and debug only. - `android/feature/onboarding/src/main/kotlin/se/hagstedt/kvitt/feature/onboarding/**`: S-1, S-2, S-3, S-18 screens and their state holders. - `android/feature/onboarding/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/onboarding/**`: a scenario per state below. - Screenshot tests and committed goldens for every screen × state × light/dark × sv/en. - Unit tests for state holders (code normalisation display, URL 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-1 Pairing**: loaded (empty fields; release prefill), prefilled from a deep link, pairing in progress, error `invite_invalid`, error network ("Could not reach the server"), error `rate_limited`, "Pair this phone again" (Unrecognised), invalid URL (release `http://`), offline, long content n/a unless the design spec says otherwise. - **S-2 Partner profile step**: loaded with prefilled name and initials avatar, with picture chosen, upload in progress, error (failed upload), offline ("Needs connection" for the picture), long Swedish name. - **S-3 Waiting for partner**: with a pending invite's expiry, without a pending invite, offline, error. - **S-18 Device removed**: loaded (single state), plus any state the design spec adds. ## 6. Steps 1. Worktree. From the main checkout `/Users/jhag/projects/kvitt`: `git worktree add .claude/worktrees/agent-m7a -b agent-m7a main`, then in `.claude/worktrees/agent-m7a`: `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. 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 `fakes/onboarding/` 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-m7a`, `git rebase main`, rerun the gate. Never merge, push or remove the worktree. ## 7. Gate From SPEC §11.2, verbatim: "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:onboarding`; 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-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); S-13 to S-15, S-19 to S-22 (M7f). - Intent parsing, navigation graph, Hilt, the notification-permission prompt after pairing (AC-2.9), the revoke wipe (M8); the real `SessionRepository` (M5). - Editing `:core:design` (requests go to the orchestrator) or `:core:model`. - Fake scenarios outside `fakes/onboarding/`. ## 10. Report - Branch `agent-m7a`, 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.