Skip to content

Reading progress sync feature#3511

Open
ihaddy wants to merge 2 commits intojaneczku:masterfrom
ihaddy:reading-progress-sync-feature
Open

Reading progress sync feature#3511
ihaddy wants to merge 2 commits intojaneczku:masterfrom
ihaddy:reading-progress-sync-feature

Conversation

@ihaddy
Copy link
Copy Markdown

@ihaddy ihaddy commented Dec 30, 2025

Reading Progress Sync & Continue Reading Dashboard

Summary

Hey! Long time user of the app and a software engineer, so i figured i'd contribute something back, hope you like it! Have been using it in my personal setup for a week or two ironing it out and i think with it i can finally move away from bookfusion (i was only paying for it because it had reading progress sync).

This PR adds cross-device reading progress synchronization for EPUB and PDF (it's not coded to work with other file formats yet), along with a "Continue Reading" section on the main library dashboard.

I've tested it cross-device (reading on phone, then laptop, then other computer, in various combinations and orders) and it works great so far. I also added a brief section to the dashboard to show the progress of books that you've been reading on other devices because i noticed while testing that i couldn't find a quick way to jump back into the books i was just reading from the library view.

All testing was done with the calibre-web reader client directly through the web browser (firefox/chrome on desktop/mobile) because that's what i use, so I haven't tested this with any mobile apps or downstream apps that consume from calibre-web and use their own reader clients (honestly not sure there are any but i just wanted to put this out there).

Lastly I just wanted to say that if this gets merged and needs iterative improvement i'll be happy to own this feature and continue to improve it and possibly make other contributions to the project!

What's included

Backend

  • New ReadingProgress model in ub.py with fields for location (CFI for EPUB, page number for PDF), progress percentage, and timestamp
  • GET/POST endpoints at /ajax/reading-progress/<book_id>/<format> for fetching and saving progress
  • Helper function get_continue_reading_books() that queries recent reading progress for the dashboard

EPUB Reader (epub.js)

  • Fetches server progress on load, compares timestamps with localStorage, uses whichever is newer
  • Debounced POST to server when position changes (2 second delay to avoid spamming)
  • Race condition fix: added a cooldown period after restoring from server to prevent the relocated event from immediately overwriting the restored position

PDF Reader (pdf_progress.js)

  • New module that hooks into PDF.js viewer events
  • Same timestamp-based sync logic as EPUB
  • Waits for pagesloaded event before restoring to avoid PDF.js's own initialization from overwriting our navigation

Dashboard

  • "Continue Reading" section above "Discover (Random Books)" on the index page
  • Shows up to the 6 most recently read books with progress badges
  • Links go directly to the reader (not the book detail page)

Design decisions

Simple Summary of Sync Logic cross device

No server progress (uses local), no local progress (uses server), both exist (uses newer)

Why timestamp-based "last write wins"?
Simpler than trying to do proper conflict resolution. If you read further on device A, then open device B, you want to jump to where you were on A. The timestamp handles this naturally. Edge cases exist (reading the same book on two devices simultaneously) but they're rare enough that I didn't want to over-engineer this.

Why store CFI for EPUB instead of percentage?
CFI (Canonical Fragment Identifier) in EPUBs points to a specific location in the document regardless of screen size or font settings. Percentage-based positions shift when the book reflows on different devices or different screen sizes, single pane/double pane etc. CFIs are more accurate for cross-device sync, but sometimes they're not entirely obvious to users "what part of the page" you were focused on reading was actually synced. So in general, there's no black magic to it where you if you last read sentence 5 with your eyes on page 75 it's going to somehow know that and jump you to the same exact spot with that sentence at the top of the page. It'll get you close enough to where you were reading, maybe a few sentences or a paragraph off, but it's overall very close and reliable.

In general epub CFI structure can vary by publisher or edition or copy so i tried not to be too granular with it, but i tested it with dozens of books from various sources and publishers, and it always landed me within a paragraph or few sentences of where i was, although i'm sure there will be edge cases.

Why not use the existing bookmark system?
Bookmarks are user-initiated and intentional. this is an automatic reading progress syncing that doesn't interfere with bookmarks so that users can still bookmark specific locations if they want to but automatically sync their reading progress between devices if they abruptly stop reading on one device and start on another.

A lot of users want to track their reading progress but dont want to litter their books with tons of bookmarks that they have to go back and clean out or muddy the water on important pages they bookmarked to go back and re-read or annotate later.

Why the 2-second debounce on saves?
Rapid page turns would hammer the server otherwise. Two seconds felt like a good balance - long enough to batch rapid navigation, short enough that you won't lose much if you close the tab.

Trade-offs

  • More database writes: Every reading session now writes to the server periodically. For most deployments i can't think of any feasible scenario where this wouldn't be fine (it's not a lot of data), but if there are any, the debounce interval could be changed.
  • No offline queue: If you're offline, progress just stays in localStorage until you're back online and open the book again. Didn't add a service worker or background sync - felt like overkill for v1.
  • EPUB location generation still happens: I moved position restore to happen immediately (before locations are generated), but the location generation still runs in the background for the page count display. First open of a large EPUB will still take a few seconds before you see page numbers.

Testing done many times on different devices with many different books

  • EPUB: Opened on desktop, navigated to ~50%, closed, opened on phone browser, verified it jumped to correct position with toast notification
  • PDF: Same flow, verified page number restore works
  • Continue Reading: Verified books appear after reading, ordered by most recent

Not in scope / future work

  • Kobo sync integration: Would be nice to sync with Kobo devices but that's a much bigger lift
  • CBR/CBZ support: Comic readers could use the same pattern but I didn't touch those
  • Reading statistics/history: The progress data could feed into reading stats but that's a separate feature
  • Configurable sync interval: Currently hardcoded, could be a user preference
  • Progress bar in book list: Could show progress on book covers throughout the UI, not just Continue Reading section

What i changed where

  • cps/ub.py - ReadingProgress model
  • cps/web.py - API endpoints and dashboard helper
  • cps/templates/index.html - Continue Reading section
  • cps/templates/read.html - Wire up progress URL for EPUB
  • cps/templates/readpdf.html - Wire up progress URL for PDF
  • cps/static/js/reading/epub.js - EPUB sync logic
  • cps/static/js/reading/pdf_progress.js - New file, PDF sync logic
image image

@ihaddy
Copy link
Copy Markdown
Author

ihaddy commented Jan 22, 2026

Hey all just wanted to check-in, any feedback on this? anything i can do to ease the burden of reviewing it? thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant