df34de1c38
* fix(sync): upload book files when Upload Book Files is enabled after first sync (#4856) Incremental sync decided what to push purely from `isLocalNewer` (`book.updatedAt` vs the shared index). A book's config/cover change over time, but its FILE is immutable per hash and only needs uploading once. After a first sync with "Upload Book Files" off, toggling it on never bumped `book.updatedAt`, so the book was skipped and its file never reached the remote. Record which book FILES are already on the remote in library.json (`uploadedHashes`) and split the push decision: config/cover stay gated on the incremental "changed locally" cursor, while a file is (re)uploaded only when syncBooks is on and its hash isn't recorded yet. This keeps an incremental "Sync now" O(changed) — once a file is recorded, later syncs skip it with no per-book HEAD probe, so large libraries don't pay an O(library) cost on every sync. Full Sync bypasses the record as an escape hatch for out-of-band drift. The record is additive and optional, so an old client that rewrites the index just drops it and the next new-client sync re-verifies each file once and re-records it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sync): propagate WebDAV book deletions to peers and the server (#4860) A book deleted on one device only tombstoned itself in library.json — the deletion never reached other devices or the server: - peers kept the book: the reconcile pass skipped deletedAt entries, so a tombstone never removed the local copy; - the server kept the files: the per-hash directory was never GC'd; - the tombstone could vanish entirely: a device that had never seen the book rebuilt the index purely from its own library, dropping the tombstone and silently reviving the book for everyone. Fixes all three in engine.syncLibrary: - apply a peer's tombstone locally (LocalStore.deleteBookLocally removes the app-managed copy and persists the tombstone), with edit-wins-over-delete LWW so a book still being read isn't yanked; - GC the remote per-hash directory of tombstoned books, scoped to the dirs the discovery scan saw so removed dirs are never re-DELETEd; - union remote-only entries (chiefly tombstones) into the re-pushed index so a deletion can't be dropped by a device that never had the book. Books tombstoned mid-run are excluded from the push pass via the merged state so a just-deleted book isn't re-uploaded right before it is GC'd. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>