assembleDebug, testDebugUnitTest, and recordPaparazziDebug all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bThmkmyUUdqQpy3MXFFe5
111 lines
6.8 KiB
Markdown
111 lines
6.8 KiB
Markdown
# Bookshelf — session handoff
|
|
Written 2026-09-06 by the Opus orchestrator, after a sprite restart forced a fresh session.
|
|
|
|
## Read these first, in order
|
|
1. `docs/SPEC.md` — the authoritative product/technical contract. Unchanged and still correct.
|
|
Every worker prompt must point at it. Do not restate it; do not let it drift.
|
|
2. This file — operational state, what's done, what bit us, what's next.
|
|
|
|
## Operating model (the user explicitly asked for this — keep it)
|
|
The user is on the **$20/mo Pro plan** and wants Opus used sparingly.
|
|
- **Opus = orchestrator only.** Write specs, launch workers, verify results, decide.
|
|
Do NOT write app code yourself. Do NOT read large files into Opus context.
|
|
- **Sonnet = all implementation**, via `claude -p` (NOT the Agent tool — the user asked
|
|
for `claude -p` specifically, and it keeps worker output out of the orchestrator's context).
|
|
- Read worker output via `logs/<name>.summary` / `jq -r '.result'`, never by cat-ing source.
|
|
- Quota status: user reported **62% of the 5h window consumed** at ~19:45 on 09-05.
|
|
Worker A alone cost **$2.40 / 85 turns**. Budget accordingly; prefer resuming a
|
|
session over restarting one.
|
|
|
|
### How to launch a worker
|
|
```
|
|
cd ~/bookshelf && nohup ./tasks/run-task.sh <NAME> ./tasks/<NAME>.txt >/dev/null 2>&1 &
|
|
```
|
|
`tasks/run-task.sh` is quota-aware: on a usage-limit error it sleeps `POLL` (600s) and
|
|
resumes the SAME session rather than restarting, up to `MAX_WALL` (24h), and does not
|
|
count quota waits against its 3-strike hard-failure budget. It writes:
|
|
`logs/<name>.json` (final result), `.err`, `.sid` (session id), `.state` (progress), `.summary`.
|
|
|
|
**HAZARD — do not repeat:** never edit `run-task.sh` while workers are running. Bash reads
|
|
scripts by byte offset; swapping the file mid-run makes live workers resume inside unrelated
|
|
code and can spawn duplicate `claude` processes that burn quota on finished work. If you must
|
|
change it, write a NEW file and use that for the next wave.
|
|
|
|
## Environment
|
|
- JDK 21: `~/toolchain/jdk21` (system java is 25 — too new for AGP, do not use it)
|
|
- Android SDK: `~/toolchain/android-sdk` (platforms;android-37.0, build-tools;37.0.0, platform-tools)
|
|
- **No KVM, no emulator.** Verify only via `./gradlew assembleDebug`, JVM unit tests, and
|
|
Paparazzi PNG rendering. Never claim the app was "run".
|
|
- PocketBase v0.40.2 service, **127.0.0.1:8090, deliberately NOT internet-exposed**
|
|
(no `--http-port`, so the sprite proxy can't reach it). Restart:
|
|
`sprite-env services restart pocketbase`. Logs: `/.sprite/logs/services/pocketbase.log`.
|
|
- Superuser creds: `server/.dev-credentials` (gitignored).
|
|
|
|
## STATE: what is DONE
|
|
### Wave 1A — server: COMPLETE and verified by the orchestrator (not just self-reported)
|
|
`server/` contains `setup-schema.sh` (idempotent), `create-user.sh`, `pb_hooks/main.pb.js`,
|
|
`pb_migrations/`, `deploy/` (systemd unit, Dockerfile, compose, backup.sh, README covering
|
|
Tailscale vs port-forward+Caddy), `README.md`, `.gitignore`.
|
|
|
|
Independently re-verified on 09-06 after fixing the service:
|
|
| Check | Result |
|
|
|---|---|
|
|
| anonymous LIST books/shelves/bookcases | **403 / 403 / 403** |
|
|
| anonymous self-registration | **403** |
|
|
| `/api/health` | 200 |
|
|
|
|
**`pb_hooks/main.pb.js` is INTENTIONAL, not scope drift.** PocketBase's `listRule` is a row
|
|
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.
|
|
|
|
## 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).
|
|
- **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.
|
|
|
|
## Gotchas already paid for — do not rediscover
|
|
1. **Migration filename ↔ `_migrations` desync.** Worker A renamed `1788636563_created_books.js`
|
|
to `...564...` to fix an alphabetical-replay ordering bug (`books` sorted before `shelves`,
|
|
breaking the relation). Correct for fresh instances, but the dev DB still had the old name
|
|
recorded applied, so PocketBase tried to re-create `books` and crash-looped 9 times.
|
|
Fixed via `UPDATE _migrations SET file=...`. **If you ever rename a migration, update that
|
|
table too.** DB backup: scratchpad `data.db.bak`.
|
|
2. Rule semantics: in PocketBase `""` means PUBLIC, `null` means superuser-only. Confusing these
|
|
is exactly how the library would end up world-readable.
|
|
3. `claude -p --output-format json` writes its log only at exit; a 0-byte `.json` means the
|
|
worker is still running or was killed, not that it failed.
|
|
4. System JDK is 25 and will break AGP. Workers must export `JAVA_HOME=~/toolchain/jdk21`
|
|
(run-task.sh already does).
|
|
|
|
## Verification standard (hold workers to this)
|
|
Workers self-report optimistically. Before accepting any wave:
|
|
- Re-run the security curls above yourself. The user's stated requirement is that this not be
|
|
"accessible to everyone in the world"; that check is non-negotiable and cheap.
|
|
- Require `assembleDebug` + `test` exit 0, and confirm artifacts exist on disk.
|
|
- Treat "I couldn't get Paparazzi working so I skipped screenshots" as a finding to report to
|
|
the user, not something to paper over — the user explicitly cares how this looks.
|
|
|
|
## Open questions for the user (not yet asked — deferred, not forgotten)
|
|
- Where the server will actually live (home box vs a sprite) — only affects the deploy README.
|
|
- Their two account emails, for `create-user.sh`. Not needed until the app can log in.
|