Some checks failed
End-to-end pairing flow: 1. SetupScreen renders a CameraX preview on the left, a manual-entry form on the right (dev fallback). Camera permission is requested in-place — no accompanist dep. 2. QrScannerView uses ML Kit's barcode scanner (QR format only), single-shot fires the decoded JSON to the ViewModel and stops analysing. 3. SetupViewModel.pairFromQr decodes via Moshi, persists the pairing in DataStore, then verifies by hitting /api/v1/store/loyalty/program through the AuthInterceptor (which now sees the new url + token). On 200 it warms the staff PIN and category caches and emits Success; on failure it rolls back via DeviceConfigRepository.resetDevice() so the user is back at a clean Setup with an error. 4. The NavHost watches is_device_set_up and forwards to PIN once Success fires. The DataStore key was aligned to "is_device_set_up" so this reactive switch keeps working. Backend: the QR payload generated by POST /merchants/loyalty/devices now includes store_id and store_name in addition to api_url, store_code and auth_token, so the tablet doesn't have to resolve them later via a separate call. Old QRs (which only had three fields) won't decode — the merchant has to revoke and re-pair, which is the same flow they'd run anyway after losing a tablet. Files: - ui/scanner/QrScannerView.kt (new) — CameraX + ML Kit composable - ui/setup/SetupViewModel.kt (rewrite) — pair flow + state machine - ui/setup/SetupScreen.kt (rewrite) — two-pane layout, status overlay - data/model/ApiModels.kt — SetupPayload model - data/repository/DeviceConfigRepository.kt — IS_SET_UP key alignment - app/modules/loyalty/services/terminal_device_service.py — richer QR payload Verified by ./gradlew assembleDebug — clean build, all warnings address in this commit (LocalLifecycleOwner moved to lifecycle.compose, OptIn on ExperimentalGetImage removed since it's no longer @RequiresOptIn). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>