Offer content changes to the device, not just shelf changes #31

Merged
arne merged 1 commit from sync-content-changes into main 2026-10-09 12:48:15 +02:00
Owner

The backfill rewrote three epubs and the Kobo did not re-download. The
file_hash assumption was right; the code path that acts on it was never
reached.

The bug

LibrarySync reuses a drained session when the shelf "still looks the
same", and that was decided by setsEqual over book IDs:

sessionSet := ...   // book IDs only
if setsEqual(sessionSet, shelfSet) {
    sessionID = tokenIn.SessionID   // reuse -> no new snapshot -> no diff
}

A changed book keeps its place on the shelf. So the session was reused,
no snapshot was built, and ComputeDelta — which does compare
FileHash and MetaRev — never ran.

Observed live: the session created at 10:03 was still current at 10:45,
holding file_hash 95f478e4 for a book whose row said 32f39663. It
would have stayed that way indefinitely.

Scope is wider than covers. Any edit — retitle, author, description,
series — was invisible to the device unless some book was added or
removed from the shelf.

The fix

A drained session matches the shelf only when it holds the same books
and the same (file_hash, meta_rev) for each. Membership is not
evidence of being up to date.

Testing

Three new sync tests, driving real sync conversations with tokens:

  • a file rewrite (what a cover change does) is offered as a
    ChangedEntitlement
  • a metadata edit is offered, with the new title in the envelope
  • an unchanged shelf still settles across repeated polls, so a device
    does not resync for ever

updated_at is second-granularity, so the metadata test ages the row
rather than sleeping; a metadata-only edit inside the same second as the
snapshot is genuinely indistinguishable. Cover and file rewrites change
file_hash, so they are detected regardless.

🤖 Generated with Claude Code

The backfill rewrote three epubs and the Kobo did not re-download. The file_hash assumption was right; the code path that acts on it was never reached. ## The bug `LibrarySync` reuses a drained session when the shelf "still looks the same", and that was decided by `setsEqual` over book IDs: ```go sessionSet := ... // book IDs only if setsEqual(sessionSet, shelfSet) { sessionID = tokenIn.SessionID // reuse -> no new snapshot -> no diff } ``` A changed book keeps its place on the shelf. So the session was reused, no snapshot was built, and `ComputeDelta` — which *does* compare `FileHash` and `MetaRev` — never ran. Observed live: the session created at 10:03 was still current at 10:45, holding `file_hash 95f478e4` for a book whose row said `32f39663`. It would have stayed that way indefinitely. **Scope is wider than covers.** Any edit — retitle, author, description, series — was invisible to the device unless some book was added or removed from the shelf. ## The fix A drained session matches the shelf only when it holds the same books *and* the same `(file_hash, meta_rev)` for each. Membership is not evidence of being up to date. ## Testing Three new sync tests, driving real sync conversations with tokens: - a file rewrite (what a cover change does) is offered as a `ChangedEntitlement` - a metadata edit is offered, with the new title in the envelope - an unchanged shelf still settles across repeated polls, so a device does not resync for ever `updated_at` is second-granularity, so the metadata test ages the row rather than sleeping; a metadata-only edit inside the same second as the snapshot is genuinely indistinguishable. Cover and file rewrites change `file_hash`, so they are detected regardless. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A cover rewrite, a retitle, any edit at all: the device was never told.
Only adding or removing a book reached it.

LibrarySync reuses a drained session when the shelf still looks the
same, and "the same" was decided by setsEqual on book IDs alone. A
changed book keeps its place on the shelf, so the session was reused,
no new snapshot was built, and ComputeDelta — which does compare
FileHash and MetaRev — never got the chance to run. The live session
from this morning still carried file_hash 95f478e4 for a book that had
moved to 32f39663, and would have forever.

Compare fingerprints instead: a drained session matches the shelf only
when it holds the same books AND the same (file_hash, meta_rev) for
each. Membership is not evidence of being up to date.

Note updated_at is second-granularity, so a metadata-only edit within
the same second as the snapshot is not distinguishable. Cover and file
rewrites change file_hash too, so they are detected regardless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
arne merged commit 8761413696 into main 2026-10-09 12:48:15 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
arne/books!31
No description provided.