You are Worker D on the Bookshelf project (~/bookshelf). Implement BOOK METADATA LOOKUP + BARCODE SCANNING. FIRST, READ THESE — they are the contract, follow them exactly, do not invent alternative names or restate them back to me: ~/bookshelf/docs/SPEC.md (authoritative product/technical spec — see the "Book metadata lookup" and "Barcode scanning" sections especially) ~/bookshelf/docs/HANDOFF.md (operational state and gotchas already paid for) ## Your scope — these packages ONLY, under app/app/src/main/java/org/modg/bookshelf/ data.metadata - IsbnUtils: ISBN-13 checksum validation, ISBN-10 -> 13 conversion, normalization (strip hyphens/spaces, handle trailing X) - OpenLibraryClient and GoogleBooksClient (Retrofit or OkHttp + kotlinx- serialization; endpoints are in SPEC) - BookMetadata (source-agnostic result model) and MetadataMerger implementing SPEC's merge rule: prefer whichever has a title, fill blanks from the other, return null if both miss - MetadataRepository: the single entry point, `suspend fun lookup(isbn): BookMetadata?` ui.scan — SCANNER PLUMBING ONLY, no finished screen (wave 3 builds the screen): - a CameraX ImageAnalysis analyzer wrapping ML Kit BarcodeScanning (EAN_13, EAN_8, UPC_A), validating the ISBN-13 checksum before emitting, debouncing repeat reads of the same code - a small stateful holder exposing scan results as a Flow, plus torch toggle and camera-permission-denied states ## HARD BOUNDARIES — you share this repo with Worker C, running right now - DO NOT create or edit anything under `data.local`, `data.remote`, `data.repo`, `data.prefs`, `AppContainer`, or `BookshelfApplication.kt`. Those are Worker C's. Touching them WILL cause a conflict. - DO NOT edit any `ui.theme` or `ui.components` file — wave 1B finished those and they are verified green. Reuse them; do not modify them. - DO NOT edit `app/build.gradle.kts` or `gradle/libs.versions.toml`. Everything you need (camerax core/camera2/lifecycle/view, mlkit barcode-scanning, retrofit, kotlinx-serialization, okhttp, coroutines-test, robolectric) is ALREADY declared and wired. If you think something is missing, DO NOT add it — report it instead. - Your MetadataRepository must be a plain class with an explicit constructor (e.g. taking OkHttpClient/Json). Do NOT wire it into AppContainer — Worker C owns that file. In your final report, give the exact one-line wiring snippet needed. ## Build/verify — CRITICAL Never run `./gradlew` directly; a second worker builds concurrently and you will corrupt each other's build. ALWAYS build with the serialized wrapper: ~/bookshelf/tasks/gw assembleDebug ~/bookshelf/tasks/gw testDebugUnitTest It takes the lock and may block until the other worker's build finishes. That is expected — wait for it, do not bypass it. ## Definition of done — all must actually pass, verified by you, not assumed 1. `~/bookshelf/tasks/gw assembleDebug` exits 0. 2. `~/bookshelf/tasks/gw testDebugUnitTest` exits 0. 3. Real unit tests with real assertions (SPEC: "Do not write assertion-free tests"): - ISBN-13 checksum: known-valid and known-invalid ISBNs, ISBN-10 conversion, hyphen/space handling - MetadataMerger: OL-only, GB-only, both, neither(-> null), and blank-filling - client JSON parsing against CHECKED-IN SAMPLE JSON FIXTURES, not live network. Tests must pass offline with no network access. 4. Network code must never be called on the main thread and must fail soft (return null / empty) rather than throw on timeout or malformed JSON. ## Report back (keep it short — it is read by a token-constrained orchestrator) - exact pass/fail of the two gradle commands above - files created, one line each - the one-line AppContainer wiring snippet for MetadataRepository - anything in SPEC.md you could NOT satisfy, and why. Do not paper over gaps: a truthfully reported gap is worth more than a false green.