Sync progress both ways, by whichever side changed last #14

Merged
365DevNet merged 0 commits from refs/pull/14/head into main 2026-08-13 16:27:39 +02:00
365DevNet commented 2026-08-13 16:12:23 +02:00 (Migrated from codeberg.org)

Summary

Reading on the device stopped reaching Kavita — every sync said "Nothing to
sync". Not a regression, a scope limit: phase 2 shipped EPUB push-only and
the library moved to PDFs. Measured on the device:

Book ext device Kavita old outcome
Complete Calvin and Hobbes pdf 12/376 9/376 dropped: not EPUB
Lazy Sunday pdf 17/132 0/132 dropped: not EPUB
The Last Messiah pdf 6/24 7/24 dropped: not EPUB
7 Habits epub 3/447 2/2 read rounds to page 0

100% of the library legitimately produced "Nothing to sync", and one line of UI
could not distinguish that from "everything was silently discarded".

What changed

  • No format filter at all. Gating on extension was the bug; a longer list
    would just relocate it. Pushing needs a page fraction (npage > 0), pulling
    needs a position string this app can rewrite — neither is a property of the
    extension. CBZ/CBR will sync with no code change.
  • Latest-wins on position_ts vs lastModifiedUtc. Both are real UTC and
    agree within 30 s, verified — so no local state file is needed.
  • Both answers, not one. A disagreement has two possible resolutions and
    only the reader knows which state is real, so decide() returns both and the
    app asks with the two states as the two buttons. Forward pushes on the newer
    side still apply silently.
  • DeviceProgressWriter — the only code that writes to the device. Always
    UPDATE, profile 1 only, position substituted into the existing string
    (never synthesized), one-time DB backup before the first write.
  • Per-book report replaces "Nothing to sync".

Verified against real data

scripts/dryrun.sh (new) replays the real decision path over a copy of the
device DB and the live server, writing nothing:

AUTO   Complete C&H     device 12/376  kavita 9/376   -> push 9 -> 12
AUTO   Lazy Sunday      device 17/132  kavita 0/132   -> push 0 -> 17
ASK    7 Habits         device 3/447   kavita 2/2     -> mark finished, or push
ASK    Peter W. Zapffe  device 6/24    kavita 7/24    -> push 6, or nothing

It exists because unit tests could never have caught the original bug: the fakes
faithfully replayed what we believed.

Known limits

  • EPUB positions are never written to the deviceepubcfi cannot be
    derived from a page number. Kavita-side "finished" can still be applied, since
    that is a boolean rather than a position.
  • Reflow-mode PDF positions are not writable yet (The Last Messiah). The
    offset fields' units are unknown and a wrong guess lands the reader anywhere;
    it reports an honest skip instead. Experiment documented in the plan.

Test plan

  • 154 host tests pass, including the full decision matrix on real fixtures
  • Clean ARM cross-build
  • Dry run against the live server and a copy of the real device DB
  • On device: pull a page from Kavita, approve, confirm the reader opens at
    that page.
    Not yet run — the one check that proves the device-write path,
    and worth doing before this lands.

🤖 Generated with Claude Code

## Summary Reading on the device stopped reaching Kavita — every sync said "Nothing to sync". **Not a regression, a scope limit**: phase 2 shipped EPUB push-only and the library moved to PDFs. Measured on the device: | Book | ext | device | Kavita | old outcome | |---|---|---|---|---| | Complete Calvin and Hobbes | pdf | 12/376 | 9/376 | dropped: not EPUB | | Lazy Sunday | pdf | 17/132 | 0/132 | dropped: not EPUB | | The Last Messiah | pdf | 6/24 | 7/24 | dropped: not EPUB | | 7 Habits | epub | 3/447 | 2/2 read | rounds to page 0 | 100% of the library legitimately produced "Nothing to sync", and one line of UI could not distinguish that from "everything was silently discarded". ## What changed - **No format filter at all.** Gating on extension was the bug; a longer list would just relocate it. Pushing needs a page fraction (`npage > 0`), pulling needs a position string this app can rewrite — neither is a property of the extension. CBZ/CBR will sync with no code change. - **Latest-wins** on `position_ts` vs `lastModifiedUtc`. Both are real UTC and agree within 30 s, verified — so no local state file is needed. - **Both answers, not one.** A disagreement has two possible resolutions and only the reader knows which state is real, so `decide()` returns both and the app asks with the two states as the two buttons. Forward pushes on the newer side still apply silently. - **`DeviceProgressWriter`** — the only code that writes to the device. Always `UPDATE`, profile 1 only, position **substituted into the existing string** (never synthesized), one-time DB backup before the first write. - **Per-book report** replaces "Nothing to sync". ## Verified against real data `scripts/dryrun.sh` (new) replays the real decision path over a copy of the device DB and the live server, writing nothing: ``` AUTO Complete C&H device 12/376 kavita 9/376 -> push 9 -> 12 AUTO Lazy Sunday device 17/132 kavita 0/132 -> push 0 -> 17 ASK 7 Habits device 3/447 kavita 2/2 -> mark finished, or push ASK Peter W. Zapffe device 6/24 kavita 7/24 -> push 6, or nothing ``` It exists because unit tests could never have caught the original bug: the fakes faithfully replayed what we believed. ## Known limits - **EPUB positions are never written to the device** — `epubcfi` cannot be derived from a page number. Kavita-side "finished" can still be applied, since that is a boolean rather than a position. - **Reflow-mode PDF positions are not writable yet** (The Last Messiah). The offset fields' units are unknown and a wrong guess lands the reader anywhere; it reports an honest skip instead. Experiment documented in the plan. ## Test plan - [x] 154 host tests pass, including the full decision matrix on real fixtures - [x] Clean ARM cross-build - [x] Dry run against the live server and a copy of the real device DB - [ ] **On device: pull a page from Kavita, approve, confirm the reader opens at that page.** Not yet run — the one check that proves the device-write path, and worth doing before this lands. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
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
365DevNet/PocketBook_Kavita_sync!14
No description provided.