Both wave-4 workers hit the 5h session limit ~11 minutes in and were then lost to a sprite suspend. This commit preserves the work that landed before that, independently verified green (assembleDebug + testDebugUnitTest, 94 tests). F1-livesync: - LiveSyncTest + server/live-sync-test.sh: end-to-end exercise against a real PocketBase (auth, push with client ids, pull, tombstones, cover round-trip). Gated behind LIVE_SYNC=1 so the normal test task stays green with no server. - Fix: BookDto.authors must be nullable. PocketBase serializes an unset `json` field as literal null (unlike text/number, which come back ""/0), so decoding any real response with empty authors threw. Found by the live test; no fake had ever reproduced it. - Fix: cover upload derived its media type from the filename instead of hardcoding image/jpeg. F2-release: - ScreenFixtures + LibraryScreenPaparazziTest: library populated and empty, light and dark (4 PNGs). Still owed by wave 4: screenshots for the other five screens, release keystore + signed APK, top-level README. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016mTs3kQXQsQwonXpEq7aEw
Bookshelf server
PocketBase backend for the Bookshelf app — see ../docs/SPEC.md for the
authoritative data model and API rules. This directory contains everything
needed to provision and run it.
Layout
server/
pocketbase PocketBase v0.40.2 binary (gitignored — platform-specific)
pb_data/ SQLite databases + uploaded files (gitignored — runtime state)
pb_migrations/ Schema history, auto-captured — a fresh instance replays
these on first boot and ends up with the full schema
pb_hooks/ JS hook that closes a PocketBase quirk (see below)
setup-schema.sh Idempotent schema/rules provisioning (curl + jq only)
create-user.sh Superuser-driven account creation (no public sign-up)
deploy/ systemd unit, Docker, backups, remote-access guidance
Quickstart (local dev)
PocketBase must already be running (see deploy/README.md for how to run
it as a proper service; for a quick local check you can just run the binary
directly: ./pocketbase serve).
# 1. Provision the schema + API rules (safe to re-run).
./setup-schema.sh http://127.0.0.1:8090 <superuser-email> <superuser-password>
# 2. Create app accounts — there is no self-registration.
./create-user.sh you@example.com "a strong password" "Your Name"
Both scripts also read PB_URL/PB_EMAIL/PB_PASS from the environment,
or fall back to .dev-credentials (gitignored, dev-instance-only — see
.dev-credentials in this directory if present) if no args are given.
Schema summary
Three collections — bookcases, shelves, books — each requiring
authentication for every action (list/view/create/update/delete). Deletion
in the app is always a soft deleted flag (tombstone), never an actual
record delete, so sync can propagate it — see SyncEngine in the Android
app and docs/SPEC.md's Sync design section.
The built-in users collection is locked down: createRule = null means
only a superuser can create an account (via create-user.sh); regular
users can view any user (needed to resolve the added_by relation) and
update only their own record.
The pb_hooks quirk
PocketBase's declarative list/search rule acts as a row-level SQL filter,
not a hard gate: an unauthenticated request against a collection whose
listRule requires auth still gets 200 OK with an empty result,
rather than an error — because the rule can't be cleanly separated into
"deny the whole request" vs. "just don't return matching rows" (see
pocketbase/pocketbase#6492).
No private data ever leaks this way, but a 200 is still the wrong signal
for "you're not allowed here." pb_hooks/main.pb.js adds a small
onRecordsListRequest hook that turns that into a 403 for the three app
collections. It's plain JS, auto-loaded by the stock pocketbase binary —
no recompilation, no framework — so it ships and deploys exactly like
pb_migrations/.
Verifying a deployment
# Anonymous requests must be rejected (4xx) for all three collections:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8090/api/collections/books/records
# -> 403
# Self-registration must be rejected:
curl -s -X POST http://127.0.0.1:8090/api/collections/users/records \
-H 'Content-Type: application/json' -d '{"email":"x@x.com","password":"password123","passwordConfirm":"password123"}'
# -> 403 "Only superusers can perform this action."
See deploy/README.md for running the server long-term, reaching it from
outside your home network, and backups.