From b0ff330b0fea0c0a0638807ba9e58d55d6def5ff Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 15 Oct 2026 11:38:50 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20remember=20ratings=20in=205-star=20space?= =?UTF-8?q?=20=E2=80=94=20odd=20server=20ratings=20no=20longer=20drift=20u?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-detection compared a derived 10-scale value against a remembered raw 10-scale value, but the summary can only hold whole stars: a pulled server 9 (4.5 stars) applied as 5 stars, remembered 9, and the next close-push derived 10 ≠ 9 and silently rounded the server rating up. Every odd rating (9/7/5/3/1) drifted one half-star on the first close after a pull. The remembered state is now the star count actually written (5-space); the POST fires only when the star count itself changed, converting at the boundary. koreader's display is an array-index lookup (a fractional rating would render nil), so whole stars are the only safe memory. getMetaSyncState normalizes any 10-space value an older build wrote. Verified: s → stars → 2s → stars is stable for all s in 1..10. --- main.lua | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/main.lua b/main.lua index 85cfe0e..5faf4e1 100644 --- a/main.lua +++ b/main.lua @@ -1980,6 +1980,11 @@ function Bookhoard:getMetaSyncState(book_key) if type(state) ~= "table" then state = {} self.settings.meta_sync_state[book_key] = state + elseif type(state.rating) == "number" and state.rating > 5 then + -- Settings written by the first bridge build remembered ratings + -- in 10-space; normalize once. 10-space memory cannot survive + -- the round-trip (odd values would re-round at every push). + state.rating = rating10to5(state.rating) end return state end @@ -2007,20 +2012,24 @@ function Bookhoard:pullBookMeta(interactive) if self.settings.sync_ratings then local ok, result = api:getRating(ref) if ok and result and not result.error then - local server10 = result.rating - local server5 = rating10to5(server10) + -- Compare in 5-STAR SPACE. The summary cannot represent odd + -- server values (9 = 4.5 stars), so remembering the raw + -- 10-scale would make every pulled odd rating look "changed" + -- at push time (9 remembered → 5★ local → push derives 10) + -- and silently round it up. Remember the star count we + -- actually wrote; conversion to 10 happens only at the POST. + local server5 = rating10to5(result.rating) local local5 = summary.rating or 0 - local local10 = rating5to10(local5) or 0 - local remembered10 = remembered.rating or 0 - if server10 ~= nil and server10 ~= local10 and remembered10 == local10 then + local remembered5 = remembered.rating or 0 + if server5 ~= local5 and remembered5 == local5 then -- Server moved, koreader didn't → apply. summary.rating = server5 self:writeSummary(summary) - remembered.rating = server10 + remembered.rating = server5 table.insert(applied, T(_("rating %1/5"), server5)) - elseif server10 ~= nil and server10 == local10 and remembered10 ~= local10 then + elseif server5 == local5 and remembered5 ~= local5 then -- Values agree but our memory is stale; just re-sync memory. - remembered.rating = server10 + remembered.rating = server5 end end end @@ -2068,12 +2077,15 @@ function Bookhoard:pushBookMeta(interactive) local api = self:getAPI() if self.settings.sync_ratings then - local local10 = rating5to10(summary.rating) or 0 - local remembered10 = remembered.rating or 0 - if local10 ~= remembered10 then - local ok, result = api:setRating(ref, local10) + -- 5-star space comparison (see the pull): the POST only fires + -- when the star count itself changed; conversion happens at the + -- boundary (nil/0 → server clears). + local local5 = summary.rating or 0 + local remembered5 = remembered.rating or 0 + if local5 ~= remembered5 then + local ok, result = api:setRating(ref, rating5to10(local5) or 0) if ok and result and not result.error then - remembered.rating = local10 + remembered.rating = local5 table.insert(pushed, _("rating")) else errors = errors + 1