Book scan: camera/ISBN capture → reader-affinity → optional insight (Android)#71
Merged
Conversation
- Run lookup() on Dispatchers.IO; the OpenLibrary client does a blocking OkHttp call without hopping dispatchers, so the VM must. Correct the KDoc that wrongly claimed it hopped internally. - Replace the 401 dead-spinner (re-emitting Working) with a recoverable ScanUiState.ReauthRequired state after firing onReauth(). - Guard every post-suspension state write with a per-scan generation check and rethrow CancellationException, so a slow/superseded scan can never clobber a newer scan's result (matches LibraryStatsViewModel's pattern). Add regression tests: 401 -> ReauthRequired (not stuck Working), stale scan does not clobber a newer result, and lookup runs off the main thread.
…ful no-server affinity
This was referenced May 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Track B of the book-scan feature — the Android client for the deterministic affinity endpoint shipped in #68. Device-validated on a real phone (camera decode confirmed working).
What it does
Catalog tab → scan FAB →
ScanScreen: live CameraX preview + ZXing EAN-13 decode, with an always-available manual ISBN field + permission flow. On an ISBN → on-device OpenLibrary metadata lookup →POST /library/v1/affinity→ result screen showing score + band + reasons (or "already in your library" / "not enough history" / "affinity unavailable"), plus a "Get full insight" button that reuses the existingInsightSection.Layers
:core:metadataIsbn(ISBN-10/13 canonicalization, mirrors the server) ·:data:opdsOpenLibraryClient(plain OkHttp, no account auth) ·:data:libraryLibraryClient.affinity()+ DTOs (account-authed) ·:appScanViewModel(state machine) +ScanScreen/ScanResultScreen+ nav/Catalog FAB/DI.quire_serverstays metadata-only.Correctness
Built and hardened with multi-agent adversarial passes (implement → independent reviewers told to refute → fix). Real bugs caught + fixed before they ever shipped: camera rotation (portrait-held horizontal barcode) + YUV row-stride, a
rotateCounterClockwise()call that throws in ZXing 3.5.3, single-argdecode()wiping hints, a401dead-spinner, a cancellation/stale-result race, and the no-server case now degrades to metadata-only instead of a red error.Testing
Unit tests for ISBN, OpenLibrary parsing (MockWebServer), affinity client, and the
ScanViewModelstate machine. Full:apptest + lint + assembleDebug green. The camera analyzer itself isn't unit-tested (verified on-device); a Compose UI test could be added now thatmainships a Compose test harness.Known follow-ups (non-blocking)
Requires the affinity endpoint (#68, merged) deployed to the server the client targets.