Fix crash on pull: AnnotationsModified payload must be a table

Event:new("AnnotationsModified", nil) delivered a nil payload to
every handler; ReaderThumbnail:resetCachedPagesForBookmarks does
#annotations on it and crashed KOReader the moment a pull applied
annotations (native KOReader always dispatches this event with a
table payload).

applyServerAnnotations now collects per-changed-item payloads and
dispatches them after applying, mirroring the native add/update/
remove dispatch shapes exactly: {entry, index_modified=index} for
adds (index from annotation:addItem), {bm} for content updates, and
{removed, index_modified=-index} for tombstone removals. This keeps
ReaderView's page-level highlight-box cache invalidation AND its
cached box-index shifting consistent, so mid-session pulls both
paint immediately and keep tap-to-edit associations correct.

Harness-verified: every dispatched payload is a table (explicit
crash-regression assertion simulating ReaderThumbnail's handler),
per-item index_modified values are correct, and all prior behaviors
(color flow, echo suppression, dedup round-trip) still hold.
This commit is contained in:
2026-08-20 08:57:59 -04:00
parent eec956e681
commit 4ea3966e37
+18 -4
View File
@@ -1232,6 +1232,10 @@ function Bookhoard:applyServerAnnotations(annotations)
if not model then return end
local changed = false
local notify_events = {}
local function notify(ev)
notify_events[#notify_events + 1] = ev
end
local has_pages = self.ui.document.info.has_pages
local function findLocal(server_entry)
@@ -1321,6 +1325,7 @@ function Bookhoard:applyServerAnnotations(annotations)
if cur_text ~= srv_text or cur_note ~= srv_notes then
setFields(bm, srv_text, srv_notes)
if server_entry.color and server_entry.color ~= "" then bm.color = server_entry.color end
notify({ bm })
changed = true
end
elseif model == "v2" then
@@ -1359,7 +1364,8 @@ function Bookhoard:applyServerAnnotations(annotations)
else
if srv_text ~= "" then entry.note = srv_text end -- bookmark label
end
self.ui.annotation:addItem(entry)
local index = self.ui.annotation:addItem(entry)
notify({ entry, index_modified = index })
changed = true
else
local entry = {
@@ -1435,6 +1441,7 @@ function Bookhoard:applyServerAnnotations(annotations)
local cur_text = bm and (getFields(bm)) or ""
if idx and cur_text ~= "" then
table.remove(entries, idx)
notify({ bm, index_modified = -idx })
changed = true
end
end
@@ -1444,7 +1451,9 @@ function Bookhoard:applyServerAnnotations(annotations)
for _, del in ipairs(annotations.deleted_bookmarks) do
local idx = findLocalByTombstone(del, false)
if idx then
local removed = entries[idx]
table.remove(entries, idx)
notify({ removed, index_modified = -idx })
changed = true
end
end
@@ -1462,9 +1471,14 @@ function Bookhoard:applyServerAnnotations(annotations)
-- ReaderView caches each rendered page's highlight boxes and only
-- invalidates them on AnnotationsModified — without this, applied
-- annotations don't paint until restart despite the repaint.
-- A non-table payload clears the whole cache (per-item payloads
-- only remove the pages of the listed items).
self.ui:handleEvent(Event:new("AnnotationsModified", nil))
-- Payloads MUST be tables: ReaderThumbnail iterates them, and a
-- nil payload crashed KOReader (#nil) on pull. Per-item payloads
-- with index_modified mirror the native add/update/remove
-- dispatches exactly (page-level cache invalidation + cached box
-- index shifting).
for _, ev in ipairs(notify_events) do
self.ui:handleEvent(Event:new("AnnotationsModified", ev))
end
if self.ui.dialog then
UIManager:setDirty(self.ui.dialog, "ui")
end