Commit Graph

13 Commits

Author SHA1 Message Date
neon 80244ea4e2 feat(koreader): rebuild the plugin interface and fix four-way annotation sync
Squash of feat/koreader-widget-board. KOReader plugin 2.0.0 gets the widget-board dashboard, redesigned
book details, menus, lists and dialogs, Decor page and reading companions. Highlights, notes and bookmarks
now stay in step across web, Kobo, KOReader and the phones (defects D-00 to D-20, migration 0121), and
reading progress and narration hand off cleanly between devices.
2026-10-20 16:05:27 -06:00
neon b189bf2edd fix(reading): file late Kobo sessions into the read they finished
When a Kobo syncs after finishing a book, its finished state closes the
reading attempt before the offline sessions arrive, and those sessions
opened a second attempt, marking the book as reread or read twice.
Activity dated within the finished read now joins it, activity from
before it began joins nothing, and an undated start never widens the
range. Update the reading-attempts E2E Kobo test to use a real device
that holds the book.

Fixes #1224
Fixes #1182
2026-10-17 02:55:13 +00:00
neon 2cbd76bc7f fix(stats): preserve historical KOReader completion dates
Use the source finish day when importing completed KOReader books so
completion timelines reflect reading history instead of the sync date.
Preserve newer manual edits and retain fallback behavior for unusable dates.

Closes #1783
2026-10-14 18:14:25 +00:00
neon b86502fcd6 fix(metadata): stop Hardcover sync following a stale cached match
A cached Hardcover match outranked a corrected Hardcover ID or ISBN, so progress kept going to the
wrong book. Record the identifiers each link came from and re-check it when they change, honour the
metadata edition on a match, and release the old book's read links so they are never reused.

Closes #1488
2026-10-12 16:35:26 +00:00
neon e6ea5bd77e fix(stats): invalidate reading caches after committed changes
Refresh dashboard and statistics caches immediately after reading history,
projection, reset, import, and shared deletion changes. Coalesce bulk
notifications while keeping committed changes visible to concurrent reads,
and isolate listener failures from successful writes.

Skip unchanged Hardcover attempt updates and protect cache generations
against stale in-flight loads. Cover date edits, projection-only resumes,
rollback, bulk failures, user scope, and cache races with regression tests.

Ref #1695
2026-10-07 01:28:10 +00:00
neon 826584e5da feat(annotations): add bounded dashboard highlights route
Return a user-scoped highlights shelf while preserving the existing single-highlight endpoint. Align calendar dashboard figures with each reader's timezone and completed attempts.
2026-09-28 23:50:27 +00:00
neon 9afa3a2f70 fix(stats): count completed attempts in the activity goal
The activity overview derived completions from reading sessions that reached 99 percent, while the
dashboard's reading-goal widget counted completed reading attempts. The two disagreed on iOS, where
Home reads the widget and the Annual Goal card reads the overview: a book marked read by hand, one
finished on Kobo or KOReader, an audiobook whose last session stops short of the end, and a re-read
collapsed into its first finish were all invisible to the session heuristic.

Commit 3de365a0 already made completed attempts canonical for the web timeline, goal trajectory and
completion latency. The activity-overview route arrived later with its own session-based query and
reintroduced the divergence on the one surface the web never calls.

Drop the timezone conversion with it. endedOn is a calendar date rather than an instant, so it has
no zone to convert and AT TIME ZONE would only shift buckets across year boundaries.

Fixes #1474
2026-09-21 14:55:47 +00:00
neon 79b02e5b35 test(books): cover same-day reading completion
Protect equal manual start and finish dates through the client and status API.

Closes #1097
2026-08-31 02:15:34 +00:00
neon 3de365a0c9 fix(stats): count reading attempts in completion analytics
Use completed reading attempts as the canonical source for timelines, goals, and completion latency. Preserve user and library scoping while excluding deleted, skimmed, and undated attempts.

Fixes #1132
2026-08-24 23:43:26 +00:00
neon 3a095efcbb fix(books): let manual reading dates drive the reading lifecycle
Editing Date Started or Date Finished sent a date-only patch that the attempt layer had nothing to
attach it to. On an unread book no attempt was created, so the request returned 200 with null dates
and the field cleared itself. On a book being read the date landed on the open attempt as an end
date with no outcome, violating reading_attempts_closed_has_outcome_chk and surfacing as a 500.

Treat a date-only patch as a lifecycle statement: a start date opens an attempt and projects
reading, a finish date closes one as completed and projects read, and endedOn is never written
without its outcome. Clearing a finish date keeps the completion, because an outcome with no
completion date is legal and is already what re-read seeding writes. Setting a finish date on an
abandoned book now completes it. Reject a status that contradicts its dates with a stable error
code the client can translate, and derive statusChanged from the resulting status so a date-driven
transition still reaches achievements and the Kobo projection.

Keep unread and want_to_read free of dates they cannot own. The attempt keeps its own, so a book
marked unread no longer projects a start and finish date once a historical attempt is logged, and
marking a finished book unread no longer strands its finish date on the projection.

Stop the Hardcover import sending dates a status cannot carry. A want-to-read row that still holds
a start date would otherwise fail its row and skip the status import entirely.

Closes #1073
2026-08-21 19:01:54 +00:00
neon cbd5f56778 fix(metadata): stop hardcover sync colliding on reading attempt read ids
Read selection was positional: prefer any open remote read, else a cached book-level id, else the
first read returned, while the primary attempt was re-derived each sync from book dates. The two
could disagree, assigning one Hardcover read to two attempts and violating
reading_attempts_external_uidx, which aborted the sync and persisted the failure.

Treat reading_attempts.external_id as the mapping of record: an attempt keeps the read it owns, an
unlinked attempt adopts only an unclaimed read and claims it locally before the remote write, and a
fresh read is minted when nothing is adoptable. Never merge or delete attempts to resolve a
conflict, and keep soft-deleted ids reserved so importExternalRead tombstones still hold.

Also skip the link write when an attempt already owns its read, and map the aggregated attempt
timestamp to a Date so change detection stops comparing a driver string against lastSyncedAt.

Closes #907
2026-08-21 18:18:29 +00:00
neon c70ff593ca fix(kobo): prevent reading state sync feedback loop (#657)
Exclude the originating device from reading-state fan-out and ignore stale or identical state echoes so paginated syncs can advance. Preserve pending new entitlements while propagating genuine changes to other devices.

Closes #654
2026-07-12 23:10:43 -06:00
neon f68a3ca98e feat(stats): add reading attempt history
Track repeat reads as distinct attempts while preserving existing status flows, device integrations, goals, sessions, and legacy data.

Closes #652
2026-07-12 17:41:35 +00:00