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
+45 -21
View File
@@ -58,28 +58,52 @@ Independently re-verified on 09-06 after fixing the service:
filter, so anonymous LIST would otherwise return `200 []` instead of an error. The hook forces
403. Keep it; it is why the table above passes. It is auto-loaded by the stock binary.
### Wave 1B — Android scaffold + design system: COMPLETE, verified by the orchestrator
The pre-restart worker had gotten much further than the last handoff recorded. On 09-06 the
orchestrator found everything on disk (theme, 7 shared components, MainActivity,
BookshelfApplication, Paparazzi test, all 8 Literata TTFs) and only THREE compile errors,
all the same class of trivial import bug — fixed directly by the orchestrator rather than
spending a worker session on two-line edits:
- `import androidx.compose.foundation.layout.weight` (x2: BookshelfScaffold.kt, the Paparazzi
test) — that resolves to the *internal* `RowColumnParentData.weight`. `weight` is a
ColumnScope/RowScope member; it needs NO import. Delete the line.
- SyncStatusBar.kt was missing `import androidx.compose.runtime.getValue`, so `val x by
transition.animateFloat(...)` had no delegate.
| Check | Result |
|---|---|
| `./gradlew assembleDebug` | **exit 0** — app-debug.apk, 47MB |
| `./gradlew testDebugUnitTest` | **exit 0** |
| `./gradlew recordPaparazziDebug` | **exit 0** — 10 PNGs, light+dark |
Snapshots: `app/app/src/test/snapshots/images/`. The orchestrator eyeballed scaffold-light:
warm paper ground, Literata serif title, thin gold hairline rule. Matches the design language.
### Repo is now a git repo
`git init` + baseline commit `8bcd9f7` at the 1B-green point. This is deliberate: it lets the
orchestrator verify a wave with `git diff --stat` / `git log` instead of reading source files
into Opus context, and gives a rollback that isn't a whole-sprite checkpoint restore.
Root `.gitignore` covers build outputs, `server/pb_data`, `.dev-credentials`, worker logs.
## STATE: what is IN FLIGHT
### Wave 2 — C (data layer) + D (metadata/scanning): LAUNCHED 09-06 ~01:59Z, running in parallel
Prompts: `tasks/C-data.txt`, `tasks/D-metadata.txt`. Sessions:
C-data=c2b92ca5-55d7-49b2-8a89-dc36e3ba4c9f, D-metadata=9a2c0de8-e475-4a43-9302-bc66889ee2bd
Two coordination devices were put in place before launch; keep them for wave 3:
1. **`tasks/gw` — a `flock`-serialized gradle wrapper.** Both workers share ONE Gradle project
dir; concurrent `./gradlew` runs clobber each other's outputs. Both prompts forbid
`./gradlew` and require `tasks/gw`. Reuse this for every future parallel wave.
2. **Disjoint file ownership, stated as a hard boundary in each prompt.** C owns data.local,
data.remote, data.repo, data.prefs, AppContainer, BookshelfApplication. D owns data.metadata
and ui.scan plumbing. NEITHER may touch `app/build.gradle.kts` or `libs.versions.toml` —
the orchestrator confirmed every wave-2 dependency is ALREADY declared and wired, so there
is no legitimate reason for a worker to edit a build file. D must not wire MetadataRepository
into AppContainer (C owns it); D reports the one-line snippet instead, to be applied later.
## STATE: what is NOT done
### Wave 1B — Android scaffold + design system: INCOMPLETE (killed mid-run by the restart)
Present: gradle wrapper, `gradle/libs.versions.toml`, `app/build.gradle.kts`,
`AndroidManifest.xml`, `proguard-rules.pro`, Literata OFL license.
Missing/unverified: ui/theme (Color/Type/Theme), the shared component set, MainActivity,
Paparazzi setup, and **any evidence the build compiles**.
**Its session SURVIVED and is resumable — prefer this over a restart (saves quota):**
`claude -p --model sonnet --permission-mode bypassPermissions --output-format json \`
` --add-dir ~/bookshelf --resume 6823e72a-69c1-486e-ae5a-18abab84529b`
with a "continue where you left off, don't restart" prompt. (Worker A's session, for
reference, is `5e3bd183-252c-4224-99b5-91779761ccbc`.)
First thing the resumed worker must do: get `./gradlew assembleDebug` GREEN. Everything
downstream is blocked on it.
### Waves 2-4 — not started. Prompts not yet written.
- **Wave 2 (parallel, after 1B is green):**
- C — data layer: Room entities/DAOs/DB, PocketBase Retrofit client + auth interceptor,
`SyncEngine` (push-then-pull, LWW, tombstones, client-generated 15-char ids), SettingsStore.
- D — metadata + scanning: Open Library + Google Books merge, ISBN-13 checksum validation,
CameraX + ML Kit continuous scanning.
- **Wave 3 (after C+D):** E — the six screens (setup, library, detail, scan, locations, settings).
### Waves 3-4 — not started. Prompts not yet written.
- **Wave 3 (after C+D land and are verified):** E — the six screens (setup, library, detail, scan, locations, settings).
- **Wave 4:** F — Paparazzi screenshots for the user to judge the look, release keystore +
signed APK, top-level README, end-to-end sync test against the live PocketBase.