Version cover URLs, and document firmware-update failure modes #27

Merged
arne merged 1 commit from cover-cache-busting into main 2026-10-09 11:45:03 +02:00
Owner

Follow-up to #26, prompted by a cover that still looked unchanged after
that shipped.

Versioned cover URLs

no-cache prevents future staleness but cannot cure a cover already
cached under the old max-age=86400: a copy that is fresh by contract
is served without a request, so the client never learns the policy
changed. The entry stays poisoned for the rest of its 24 hours — and the
cover being tested was exactly that one, which made a working fix look
broken.

The book and edit pages now emit /cover/{uuid}?v=<cover mtime>, so a
replaced cover is a different URL that no cached entry can shadow. A
versioned URL is served immutable for a year; an unversioned one — a
bookmark, or a cover that cannot be stat'd — keeps revalidating. A book
with no cover emits no ?v= at all.

This is the option-2 fix from the original diagnosis, and it is the one
that does not depend on client revalidation behaviour. Worth noting the
cover markup is a CSS background: url(...), which browsers are
noticeably more willing to serve from cache than an <img>.

Firmware-update failure modes

docs/kobo-api.md documented all 20 endpoints and five failure modes,
but not the one hazard certain to arrive eventually. Two sections added:

  • Firmware updates reset the redirect. The entire redirect is one
    key, api_endpoint. An update or factory reset can restore it to
    storeapi.kobo.com, after which the device syncs against the real
    Kobo store and nothing fails — no error, no log line, the shelf just
    stops changing. devices.last_seen_at is the canary: frozen across a
    sync means the device is no longer talking to this server. Re-applying
    the device URL is the whole fix; the token is unchanged.
  • Firmware drift. Every wire shape here came from 4.45 on a Libra
    Colour. The catch-all is what keeps drift from becoming a hard "sync
    failed", so expect a degraded feature and silence rather than errors.

Also recorded that pointing api_endpoint at a self-hosted server does
not stop the device updating its own firmware — observed in
practice, including an update being required before sync would proceed.

Deliberately not included

internal/kobo/cover.go:21 still sends public, max-age=86400 on an
equally stable URL, so a replaced cover can lag on the device by up to a
day. The device URL carries no version, and changing device-facing cache
behaviour deserves its own decision rather than riding along here.

Testing

go vet clean, full suite green. Added: the book page references the
exact versioned URL for an uploaded cover, a coverless book emits no
?v=, and CoverGet's policy is immutable with a version and
no-cache without.

🤖 Generated with Claude Code

Follow-up to #26, prompted by a cover that still looked unchanged after that shipped. ## Versioned cover URLs `no-cache` prevents *future* staleness but cannot cure a cover already cached under the old `max-age=86400`: a copy that is fresh by contract is served without a request, so the client never learns the policy changed. The entry stays poisoned for the rest of its 24 hours — and the cover being tested was exactly that one, which made a working fix look broken. The book and edit pages now emit `/cover/{uuid}?v=<cover mtime>`, so a replaced cover is a different URL that no cached entry can shadow. A versioned URL is served `immutable` for a year; an unversioned one — a bookmark, or a cover that cannot be stat'd — keeps revalidating. A book with no cover emits no `?v=` at all. This is the option-2 fix from the original diagnosis, and it is the one that does not depend on client revalidation behaviour. Worth noting the cover markup is a CSS `background: url(...)`, which browsers are noticeably more willing to serve from cache than an `<img>`. ## Firmware-update failure modes `docs/kobo-api.md` documented all 20 endpoints and five failure modes, but not the one hazard certain to arrive eventually. Two sections added: - **Firmware updates reset the redirect.** The entire redirect is one key, `api_endpoint`. An update or factory reset can restore it to `storeapi.kobo.com`, after which the device syncs against the real Kobo store and *nothing fails* — no error, no log line, the shelf just stops changing. `devices.last_seen_at` is the canary: frozen across a sync means the device is no longer talking to this server. Re-applying the device URL is the whole fix; the token is unchanged. - **Firmware drift.** Every wire shape here came from 4.45 on a Libra Colour. The catch-all is what keeps drift from becoming a hard "sync failed", so expect a degraded feature and silence rather than errors. Also recorded that pointing `api_endpoint` at a self-hosted server does **not** stop the device updating its own firmware — observed in practice, including an update being required before sync would proceed. ## Deliberately not included `internal/kobo/cover.go:21` still sends `public, max-age=86400` on an equally stable URL, so a replaced cover can lag on the device by up to a day. The device URL carries no version, and changing device-facing cache behaviour deserves its own decision rather than riding along here. ## Testing `go vet` clean, full suite green. Added: the book page references the exact versioned URL for an uploaded cover, a coverless book emits no `?v=`, and `CoverGet`'s policy is `immutable` with a version and `no-cache` without. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
no-cache fixed future staleness but could not cure a cover already
cached under the old max-age=86400: a fresh-by-contract copy is used
without a request, so the client never learns the policy changed. The
cover being tested at the time was exactly the poisoned one, which made
a working fix look broken.

Emit /cover/{uuid}?v=<cover mtime> from the book and edit pages, so a
replaced cover is a new URL no cached entry can shadow. A versioned URL
is then served immutable for a year; an unversioned one — a bookmark, or
a cover that cannot be stat'd — keeps revalidating.

The Kobo cover handler keeps its own public, max-age=86400 on an equally
stable URL, so a replaced cover can lag on the device by a day. Left
alone: the device URL carries no version and that is device-facing
behaviour worth changing deliberately.

Also document two firmware failure modes in docs/kobo-api.md, which
covered every endpoint and five failure modes but not the one hazard
guaranteed to arrive eventually: an update or factory reset restoring
api_endpoint to storeapi.kobo.com, which fails completely silently
because the device then syncs against the real store, with
devices.last_seen_at as the canary; and protocol drift, where the
catch-all turns a would-be "sync failed" into a degraded feature.
Pointing api_endpoint at a self-hosted server does not stop the device
updating its own firmware.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
arne merged commit a695de9d1d into main 2026-10-09 11:45:03 +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!27
No description provided.