Records D-004 and D-015 to D-025: shells and ownership, server seams (router middleware, rate_limit layer, tasks::start, AuditWriter and PushQueue traits, next_occurrence_date in kvitt-domain, M3 owns recurring generation, M4 lands after M3), OpenAPI parsed-value equality, feature convention and strings, empty fake scenario and resolveRange exception, shared ledger rows and editor fields in :core:design, no bundled font, pruning at 04:00, development infrastructure as M2c-dev built it, M2c-pi allowances, release keystore, production import procedure. Plans and prompts close their open points; HANDOFF rewritten to the real state; CLAUDE.md: implementation agents never edit SPEC.md or docs/decisions.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.6 KiB
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
docs/ux/design-spec.md: tokens, component inventory, and the sections for S-1, S-2, S-3, S-18.docs/ux/strings.csv(keys for these screens) anddocs/ux/screens/S-1-*,S-2-*,S-3-*,S-18-*.- SPEC §13.2 Navigation model and §13.3 Screens (rows S-1, S-2, S-3, S-18 and the states paragraph).
- SPEC §14.2.4 UI contract:
SessionState,SessionRepository,ProfileRepository,InviteRepository,ConnectivityRepository,OnlineResult,User. - 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).
- SPEC §6.2 (module table), §9.9 Online-only actions, §11.2 (row M7a), §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). - 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:8443in release builds) and a 12-character code shown in groups of 4, normalised per §8.3 before sending; release builds accept onlyhttps://, debug builds alsohttp://forlocalhost,10.0.2.2and private LAN addresses (AC-2.7, AC-2.8, §8.3). - Deep links
kvitt://invite?server=…&code=…andhttps://icehack.asuscomm.com:8443/i/<CODE>open S-1 with both fields filled; parsing the intent is:app's, the screen takes the values (AC-2.6). - A
partnerpairing 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.Unrecognisedshows 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/<feature>/", 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:fakesfor 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 fromdocs/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"), errorrate_limited, "Pair this phone again" (Unrecognised), invalid URL (releasehttp://), 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
- 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. 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. - 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
fakes/onboarding/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-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 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.