M4: Pairing, onboarding and the app shell (ENG-253) #16

Merged demi.dev
targeting main from stoopkidddd/eng-253-m4-pairing-onboarding-and-the-app-shell
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-253-m4-pairing-onboarding-and-the-app-shell

Linear: https://linear.app/engineereddev/issue/ENG-253

What's in this PR (all under apps/android/):

  • Theme: BrandColors.kt mapped to Material 3 light/dark (dynamic colour off) plus DESIGN.md typography; brand launcher and notification icons.
  • Navigation (ui/PneumoNavHost.kt): Pair → Confirm → Onboarding → main shell with bottom nav (Home, Devices, Settings). The start destination comes from SecureStore and DataStore state.
  • Pair: CameraX + ZXing core scanner (no Play Services), camera rationale and denied state, and "Paste link instead".
  • Deep link pneumo://pair → Confirm. Confirm shows the host (warns on non-HTTPS outside debug), claims, checks keyCheck against k, and saves MK + token. Mismatch follows §7.5 (delete own device, wipe); 410 → "This code has expired…".
  • Onboarding: notifications (API 33+) and the battery-optimization exemption, both skippable with a warning, shown once.
  • Placeholder Home/Devices/Settings; Settings Sign out (DELETE own device, wipe SecureStore, back to Pair).
  • AppContainer manual DI; PairingRepository, SessionPreferences, PairingViewModel.
  • A debug-only network security config allows cleartext to 10.0.2.2/localhost; release keeps usesCleartextTraffic="false".
  • 36 new Roborazzi baselines, recorded on purpose (light + dark): Pair (idle, camera denied, paste, invalid), Confirm (https, non-https, claiming, retryable error, error), Onboarding (notifications, battery, done, warning), Shell (home, devices, settings, settings busy, sign-out error). The obsolete scaffold screen and its goldens were removed.

Verify (lead, rebased on 555a737):

  • mise run check:android → exit 0 (Spotless, detekt, Lint "no errors or warnings", JVM tests, Roborazzi verify)
  • mise run e2e:android → exit 0; forced rerun pixel6Api34DebugAndroidTest --rerun → BUILD SUCCESSFUL, "Running tests on devices: pixel6Api34"
  • testDebugUnitTest --rerun → 108 tests, 0 failures
  • Implementer's end-to-end check against a local server: deep-link pairing reached Home ("Paired with 10.0.2.2:8080"); a relaunch kept the session; sign-out removed the device on the server.

Deviations:

  • A narrowly scoped @SuppressLint("BatteryLife") on the spec-required battery-exemption helper. Lint flags the Play-policy API; Pneumo is sideloaded with no FCM. No project-wide disable or baseline entry.
  • The managed (ATD) image has no Settings/SystemUI, so the battery request exercised the "settings unavailable" warning path there. The real dialog and physical-camera scanning need a regular emulator or phone (M5 acceptance).

🤖 Generated with Claude Code

Commits 0