Wave 2: data layer (C) + metadata/scanning (D)

Room entities/DAOs/DB, PocketBase Retrofit client + auth interceptor, SyncEngine
(push-then-pull, LWW, tombstones, client-generated ids), SettingsStore, AppContainer.
Open Library + Google Books merge, ISBN validation, CameraX + ML Kit scanner plumbing.

Verified by orchestrator: assembleDebug exit 0; testDebugUnitTest exit 0, 68 tests,
0 failures, 0 errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bThmkmyUUdqQpy3MXFFe5
This commit is contained in:
2026-09-06 03:24:04 +00:00
parent 6c17e42037
commit d1a73a1193
61 changed files with 3104 additions and 26 deletions
+1
View File
@@ -0,0 +1 @@
c2b92ca5-55d7-49b2-8a89-dc36e3ba4c9f
+11
View File
@@ -0,0 +1,11 @@
=== C-data attempts=1 quota_waits=0 hard_fails=0 ===
cost=$2.0208028 turns=65 err=?
--- result (tail) ---
`BookshelfApplication.kt`
- Tests: `IdGeneratorTest`, `PbDateFormatTest`, `BookDaoTest`, `LocationDaoTest` (Robolectric, prove `deleted=0` filtering), `SyncEngineTest` (15 tests: LWW pure-function cases, push ordering, syncState transitions, 404/409, pull conflict resolution), `FakePocketBaseApi` (hand-written fake, no mocking lib available)
**Gaps / deviations, reported honestly:**
1. **Cover pipeline is best-effort, not fully spec-complete.** I implemented download-on-create (`BookRepository`, plain OkHttp GET to `coverSourceUrl` → app-private file) and multipart upload-on-sync (`SyncEngine.uploadCoverIfNeeded`), building the served `coverUrl` from PocketBase's `/api/files/...` convention. This is untested against a *real* PocketBase server (only via fakes) — I did not have time/scope to run an end-to-end live-server cover round-trip. Logic is sound but should get a real-server smoke test in a later wave.
2. **No Room foreign-key constraints** between books→shelves→bookcases (only indices). Deliberate: enforcing FK would make sync ordering brittle across paginated pulls; push/pull order (bookcases→shelves→books) already handles the realistic cases. Documented in `SyncEngine`'s class doc.
3. **Full library search/sort/filter surface is minimal** — `BookDao` exposes `observeAll`, `observeAllByRecentlyAdded`, `observeByShelf`, `search(query)`; I did not build every SPEC-listed sort/filter combination since that's UI-driven (wave 3/Worker E territory) and risked overengineering ahead of real requirements. Easy to extend.
4. Did not touch `app/build.gradle.kts` / `libs.versions.toml` per instructions — all needed deps (room+ksp, retrofit+kotlinx-serialization converter, okhttp, datastore, coroutines-test, robolectric) were already present and sufficient.
+1
View File
@@ -0,0 +1 @@
9a2c0de8-e475-4a43-9302-bc66889ee2bd
+4
View File
@@ -0,0 +1,4 @@
=== D-metadata attempts=1 quota_waits=0 hard_fails=0 ===
cost=$0.2020432 turns=6
--- result ---
Exit code 0, 0 failures (68 tests, all passing). Fix: rewrote the two `ScannerControllerTest` cases to `backgroundScope.launch { scanResults.toList(...) }` + `runCurrent()` before emitting, so the collector subscribes before `onBarcodeScanned` fires (replay=0 semantics kept unchanged in production code).