liseur-sync/internal
Chmouel Boudjnah 33732f14bc
Some checks are pending
build / binary (push) Waiting to run
build / container (push) Waiting to run
test / lint-openapi (push) Waiting to run
test / test (push) Waiting to run
fix: refuse a position that cannot be read instead of storing it as zero
The web reader guarded the fraction it pushes with a typeof check, and
typeof NaN is "number". JSON.stringify turns that NaN into null, and the
op handler decoded progression into a plain float64, where null becomes
the zero value and passes a 0..1 range check unharmed. So "the reader's
position is unknown" was recorded as "the reader is at the start of the
book", four times in a row, and every other device was pulled to the
first page.

Both halves are fixed, because either alone leaves the other reachable:
a client that has not been updated can still post a null, and a server
that has not been updated will still take one.

Inbound progressions are decoded through their own request struct with a
pointer field, so an absent or null value is refused rather than
defaulted. The range check is written so that NaN fails it. The response
structs keep a plain float64: they are what /v1/heads, /v1/changes and
the positions endpoint answer with, and a client reading those has never
been able to receive a null.

The NaN itself has a cause worth writing down. The paginator derives
pages from the container's own bounding rect, so a container that
measures zero makes the page count Infinity and the page index NaN, and
the fraction computed from the two is NaN for as long as that lasts —
which is why four relocate events seventeen seconds apart all carried
it, rather than one at startup. A hidden or zero-sized tab is enough.

Guarding alone would have traded a wrong position for no position: with
nothing finite to push, the reader would simply stop saving its place
and never say so. So a non-finite fraction now asks the engine to
measure again on a bounded backoff, restarts that when the tab becomes
visible, and after the retries are spent tells the reader that syncing
is paused rather than leaving them to find out later. The progress bar
holds its last good value instead of flashing back to nought.
2026-08-26 23:46:58 +02:00
..
adapter fix: harden credential enrolment 2026-08-20 06:10:51 +02:00
admin feat: scope catalog access to explicit per-user folder grants 2026-08-22 10:43:41 +02:00
api fix: refuse a position that cannot be read instead of storing it as zero 2026-08-26 23:46:58 +02:00
auth feat: Support trusted-proxy-aware client IP rate limiting 2026-08-19 22:52:26 +02:00
buildinfo feat: Add web administration panel with account-level roles 2026-08-15 20:22:05 +02:00
calibre feat(content): removing a book's file from a folder that accepts uploads 2026-08-19 08:29:55 +02:00
config feat: sync annotations across devices (ADR-0028) 2026-08-25 12:04:15 +02:00
content feat: scope catalog access to explicit per-user folder grants 2026-08-22 10:43:41 +02:00
cover feat(epub): Read a publication's cover, and make it servable 2026-08-15 20:21:56 +02:00
epub fix(epub): keep every role a contributor is credited with 2026-08-18 20:23:20 +02:00
infer feat: make work resolution atomic 2026-08-15 20:21:46 +02:00
insights feat: let the web dashboard ask about any span of reading 2026-08-24 02:11:00 +02:00
metadata feat: Make Calibre metadata authoritative during reconciliation 2026-08-17 15:41:45 +02:00
store fix: acknowledge a retried post-sweep resurrect as duplicate 2026-08-25 12:04:15 +02:00
webui fix: refuse a position that cannot be read instead of storing it as zero 2026-08-26 23:46:58 +02:00
workident feat: scope catalog access to explicit per-user folder grants 2026-08-22 10:43:41 +02:00