Kobo: progress tracking and kepubify #8

Merged
arne merged 2 commits from state-debug into main 2026-04-12 15:24:43 +02:00
Owner

Summary

  • Store and emit ContentSourceProgressPercent for Kobo reading progress
  • Kepubify epub files at download time for proper Kobo rendering

Test plan

  • Verify Kobo sync picks up reading progress
  • Verify downloaded epubs are kepubified

🤖 Generated with Claude Code

## Summary - Store and emit ContentSourceProgressPercent for Kobo reading progress - Kepubify epub files at download time for proper Kobo rendering ## Test plan - [ ] Verify Kobo sync picks up reading progress - [ ] Verify downloaded epubs are kepubified 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Nickel's CurrentBookmark tracks two progress values: ProgressPercent
(overall book) and ContentSourceProgressPercent (within the current
chapter file). The store was silently dropping CSPPS on every PUT
and emitting bookmarks without it on GET, because there was no
column for it — only a field declaration in internal/kobo/types.go
that nobody wrote to.

Migration 003 adds content_source_progress_percent to the
reading_state schema. The store gates the field on the same
BookmarkModified timestamp as ProgressPercent. wireReadingStateToStore
reads it from PUT bodies; storeReadingStateToWire emits it on GET.

This fix alone doesn't resolve the "reading position resets on
book reopen" symptom — the harder problem is in the next commit —
but dropping a field the device cares about was obviously wrong
regardless.
Root cause of the "reading position resets on every book reopen"
bug: the library stores plain EPUBs. Kobo's Nickel encodes resume
positions as `Location.Type=KoboSpan, Value=kobo.{paragraph}.{span}`,
where the span identifier points at a `<span class="koboSpan"
id="kobo.X.Y">` element inside the chapter's HTML. Those elements
only exist in kepubified files; plain EPUBs have nothing to encode
against, and Nickel falls back to `kobo.1.1` (first span of the
current chapter file) for every restore position. Every reopen
lands at the chapter start.

Confirmed by inspecting library files on the live server: zero
koboSpan markers anywhere under /opt/books/library, and every
reading_state row in the device's sync state has Location.Value
stuck at kobo.1.1 — even as ContentSourceProgressPercent advances,
confirming Nickel does track within-chapter progress but has no
span markers to encode it against.

Fix: run the epub through `github.com/pgaskin/kepubify/v4/kepub` on
the download handler, streaming the converted bytes to the client.
The library stays as plain EPUBs on disk (so other download paths
still work); only the Kobo download endpoint gets the kepubified
version. A few milliseconds of CPU per request, no disk cost, no
cache to invalidate.

Existing on-device position state for already-downloaded books
will still be broken because they were fetched as plain EPUBs —
recovery is per-book: unshelve → sync → reshelve → sync → fresh
kepubified download → position tracking starts working.
arne merged commit 1056b285fb into main 2026-04-12 15:24:43 +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!8
No description provided.