Commit Graph

1377 Commits

Author SHA1 Message Date
neon c59dd0118f i18n(client): sync translations from Crowdin (#1844)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
v3.5.0
2026-10-20 17:07:14 -06:00
neon cecbdc44ce security(docker): require patched libexpat version
Prevent cached image layers from retaining the vulnerable Alpine package.
2026-10-20 22:33:08 +00:00
neon a41d3da63e fix(email): use shared default provider as fallback
Preserve explicit and per-user provider precedence while allowing multi-user installations to use a configured shared default deterministically.

Closes #1387
2026-10-20 16:05:33 -06:00
neon 2b8917afb6 feat(authors): allow correcting metadata matches
Let users edit author facts, choose and pin a provider record, and keep later refreshes tied to that record. Preserve compatibility for older clients through additive API fields and a discoverable server feature.

Keep server typechecking deterministic by breaking circular Drizzle inference and correcting the nullability mismatches it previously hid.

Closes #1464
2026-10-20 16:05:32 -06:00
neon 0d695a7ea0 feat(metadata): expand book request discovery
Allow request searches to browse broader provider result sets, rerun queries by exact author, and submit unmatched titles for approval. Advertise the optional limit so older clients retain their existing contract.

Closes #1363
2026-10-20 16:05:32 -06:00
neon 2be79af880 fix(stats): read session archetype hours in the user's time zone
Convert started_at to the user's configured time zone before taking the
hour, minute and weekday, and include the zone in the cache key so a
changed setting is picked up.

Closes #716
2026-10-20 16:05:32 -06:00
neon cc6bac696b fix(book-requests): tell a plugin indexer when its connection changes
Add an optional forget(indexerId) to the plugin contract and forward the
host's existing invalidation to it, so a plugin holding a live session
drops it when the credential, base URL or private-address setting changes.
A plugin that throws or rejects in forget is logged and never fails the save.

Fixes #1325
2026-10-20 16:05:32 -06:00
neon b95a285c1b feat(koreader): hide audiobook-only books from the KOReader plugin
Add a per-login setting, on by default, that leaves books whose content files are all audio out
of the plugin catalog, its counts and bulk downloads, and drops a library holding nothing else.
The plugin detail page also names the first file the device can open as the main file.

Closes #1345
2026-10-20 16:05:32 -06:00
neon a728bf293b feat(book-requests): link releases to their source page and hold imports for review
Torznab and Newznab read the feed's details page, plugins may send infoUrl, and the
search drops any link that could carry a credential. A grab can ask to be held for
review whatever its score, and the held review shows the file's own edition facts,
its cover and the release page. Torznab guids are now digested like Newznab's.

Refs #1438
2026-10-20 16:05:31 -06:00
neon 3c87418416 feat(client): let users hide and reorder sidebar rows and groups
Add a Customize sidebar mode, opened from any section menu or Cmd+K, that hides rows and groups
and reorders them by drag or keyboard, with reset and undo. The layout saves to a new
sidebarLayout setting; a hidden Book Dock or Requests row returns while work is waiting.
Also line up the Rows shown menu labels, which sat 3px apart.

Closes #1434
2026-10-20 16:05:31 -06:00
neon 568614265f fix(books): list series stacks before standalones when sorting by series
With series collapsed, the series sort used the same key as title, so stacks
and standalone books interleaved alphabetically. Order stacks by series name
first, then standalones by title, in both directions. The jump rail now only
letters series, matching the flat view.

Fixes #1456
2026-10-20 16:05:31 -06:00
neon 192c205299 fix(email): pick the only recipient when none is starred
Creating a recipient never makes it the default, so a single-Kindle setup
opened the send sheet with nobody ticked and Send disabled. Tick the lone
recipient in that case; a starred default still wins.

Ref #1443
2026-10-20 16:05:30 -06:00
neon 2e454cc846 fix(koreader): rank synced books by when the device read them
Stamp reading_progress.last_read_at with the device's reading time instead of the
server's write time. A sweep pushes a whole shelf at once, so sorting by last read
came out in processing order. Keep the later of the stored and pushed times, so a
late or offline push never sinks a newer read from another client.

Fixes #1433
2026-10-20 16:05:30 -06:00
neon 98415b90cc fix(dashboard): keep abandoned books out of up next in series
Offer a series' next volume only when its read status is unset, unread or want to read, on the
shelf and in the is up next filter. Abandoned, on-hold and in-progress volumes at 0% no longer
show, and an abandoned volume ends its series there instead of surfacing the one after it.

Fixes #1649
2026-10-20 16:05:30 -06:00
neon 8762c4e628 fix(book-requests): show the book's cover on a request made from its page
Asking for a book's missing format sends no cover, so the request list, drawer and release
picker drew a blank placeholder. Fall back to the cover of the book the request joins or
became, in the requested format's slot when it has one. The request's own art still wins.
2026-10-20 16:05:30 -06:00
neon 435a60288d fix(books): show read-aloud sync as a compact pill only when it applies
Show the sync control only for a read-along EPUB with audio files or another
EPUB copy, so lone read-along EPUBs and plain audiobooks no longer show an
Unavailable card. Fold the card into a pill in the format row whose two-track
meter shows the state, with the details and toggle in a popover. Your Reading
now lists three recent sessions.

Fixes #1845
2026-10-20 16:05:29 -06:00
neon 58f258a5fc fix(reader): reopen a PDF at its saved page instead of the first
The PDF reader restored the saved page with a smooth scroll, which Chrome
starts about a second after it is asked. With Fit page, Fit width or
Automatic zoom, the first viewport resize refits the page from the
current offset with an instant scroll, and a classic scrollbar appearing
as the document loads is such a resize. The refit cancelled the restore
before it moved, so the reader stayed on page 1 while the toolbar showed
the saved page, and the next scroll saved page 1 or 2 over the position.

Restore the opening page with an instant scroll, so the refit starts from
the saved page and keeps it. Page turns still scroll smoothly.

Fixes #1857
2026-10-20 16:05:29 -06:00
neon a5c4dfac16 fix(series): stop counting unreleased and extra books as missing
Hardcover's books_count included novellas, split editions, box sets and announced books, so
series named books as missing that are not out yet. Count only released main books, clear the
inflated Hardcover totals once on boot, and stop a series row saved without a total from wiping
that series' total.

Fixes #1861
2026-10-20 16:05:29 -06:00
neon 1214152bd5 fix(series): group books by media from content files only
Count only primary and content files when profiling a book's media, so a
side image, cover or .nfo in an audiobook folder no longer adds it to the
Books group on the series page.

Fixes #1862
2026-10-20 16:05:29 -06:00
neon 5878c96254 fix(books): fit series card covers to the grid instead of cropping them
First volume, Latest volume and First unread series cards drew their cover
with a cropping img, so a square audiobook cover in a 2:3 grid lost a third
of its width. They now render through BookCoverArtwork like single tiles and
follow the cover display setting, while the grid keeps one tile shape.

Fixes #1863
2026-10-20 16:05:29 -06:00
neon e0ce6d3348 chore(annotations): add KOReader highlight drawer
Keep long dashboard highlights readable while preserving quick access to book details.
2026-10-20 16:05:28 -06:00
neon c01022aeb2 chore(client): default new users to Nostalgia and teal
Give users without stored appearance choices the Nostalgia style and teal accent while preserving saved selections and BookOrbit styling on standalone pages.
2026-10-20 16:05:28 -06:00
neon 1a2ffb68c0 fix(koreader): offer a further place before a fresh copy pushes its own
A device that had not weighed its open book against BookOrbit since
opening dated its place by its last page turn. One turn from a fresh
download's cover made another device's further place look older, so
Sync now pushed the cover over it without asking, and the phone then
reopened the book at its title page.

Until the open book's place has been compared with BookOrbit's (a pull
or push went through, or Jump or Stay was answered), a further BookOrbit
place now also counts as newer, so Sync now and pulls offer Jump instead
of overwriting it. Once compared, page-turn times decide as before, and a
reset stamped after the last page turn still prompts.

Ref #1806
2026-10-20 16:05:28 -06:00
neon 6440281e65 fix(books): count played read-along narration in continue listening
Continue Listening listed only books with audiobook progress, so a book
whose only audio is a read-along EPUB never appeared there, on the web,
on the phone or in the car, however much of its narration was heard.

The shelf now also takes read-along EPUBs whose narration progress was
saved by playing it, which is when its seconds are set. Reading on any
device carries the narration marker along and clears the seconds, so a
book only read stays in Continue Reading. A book with both an audiobook
and narration under way is listed once, at its latest listening.

The web and the iOS Home shelf show these as ordinary book cards. iOS
CarPlay builds before this change leave them out of Continue, which
there lists only audiobooks; the current build plays their narration.

Ref #1847
2026-10-20 16:05:28 -06:00
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 7202a59afc fix(auth): wait for an unreachable server instead of signing out
A page load during a server restart read the failed session refresh as
signed out and stranded the reader on /login. Boot now holds the first
navigation behind a reconnect screen and retries until the server
answers, and api() keeps the session when a refresh gets a 5xx or no
connection.
2026-10-20 16:05:27 -06:00
neon ce118e3890 i18n(client): sync translations from Crowdin (#1843)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-16 23:47:46 -06:00
neon c1b4627e0d fix(kobo): take the Kobo's port from APP_URL when Host omits it
A Kobo sends Host without a port, so cover, reading state and download
links got the port the server listens on. Behind a published mapping
such as 3010:3000 that is the internal port, which the Kobo cannot
reach. Use APP_URL's origin when it names the same host; other hosts,
explicit ports and forwarded headers behave as before.

Fixes #1281
2026-10-17 05:19:20 +00:00
neon 7f172fd8fb fix(kobo): convert chapters listed by percent-encoded hrefs
kepubify looks a manifest href up exactly as written, so a chapter stored
as "chapter one.xhtml" but listed as "chapter%20one.xhtml" shipped with no
KoboSpans and every synced position snapped back to the chapter start.
Convert a copy with those hrefs decoded and restore the book's spelling
afterwards; only affected books get a new cache key. Position conversion
now matches and loads such chapters too.

Fixes #1136
2026-10-17 05:01:43 +00:00
neon 06cfc2a074 fix(kobo): hand the Kobo a sync token in a shape it recognises
Kobos on firmware 4.45 were reported failing every sync on BookOrbit's
prefixed PX tokens and passing on Calibre-Web's. Send Calibre-Web's base64
JSON token, or Kobo's own cursor verbatim when store titles carry one. The
token never located the snapshot, and PX and PX2 tokens still decode.

Fixes #1175
Fixes #435
2026-10-17 04:23:23 +00:00
neon edfa20e19f fix(kobo): deliver a changed cover to a Kobo that already has the book
A Kobo downloads a cover only for a new entitlement, so a changed cover
sent as changed metadata left the book with a text-only cover until the
home screen fetched a small tile. Record the cover id on each device
snapshot row and re-announce the book when it moves; rows from before
the change learn it silently. Serve tile-size requests at a full-screen
size so the sleep screen never stretches a tile.
2026-10-17 04:03:34 +00:00
neon 2dca4bc85c fix(kobo): send Kobo covers at the device's screen size
A Kobo asks for each cover at its screen size but always got the
400x600 grid thumbnail, so sleep screens and full-screen covers were
blurry. Scale the full cover to fit the requested box instead, behind a
new Full-size covers setting that is on by default. Drop the stale
UserStorage entries from the authorization matrix gap baseline.

Fixes #924
2026-10-17 03:34:18 +00:00
neon 51b53d4581 fix(kobo): relay Kobo notebooks to Kobo when store titles are on
A Kobo syncs its notebooks through Kobo's reading services, which
BookOrbit answered with an empty listing, so notebooks never synced.
When Include Kobo store titles is on, relay every UserStorage call to
readingservices.kobo.com; otherwise keep the empty listing.

Fixes #1787
2026-10-17 03:05:40 +00: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 058cd6e7e4 fix(kobo): sync Kobo bookmarks as bookmarks and keep annotation sync converging
A Kobo dogear was stored as an empty yellow highlight. Land it as a
BookOrbit bookmark linked to the device's id, serve it back as a dogear,
sync deletions both ways, and move dogears already stored as highlights.
Report only books on that Kobo from checkforchanges: a re-registered
device was told about books it never had, so real changes were fetched
outside its sync and never applied. Serve a known annotation at its last
location instead of failing the whole pull when this copy cannot place it.

Fixes #1374
2026-10-17 02:42:42 +00:00
neon 5397825003 fix(kobo): open a newly downloaded book at its saved position
A book's first announcement went to the Kobo before it was downloaded,
when no Kobo location could be computed, so it carried a bare percentage
and the book opened at its start. Compute the location from the copy
being offered for bookmarks sent to the device; positions it reports
still wait for the transfer.
2026-10-17 01:20:03 +00:00
neon ff8759b9a0 fix(kobo): keep a Kobo's progress on the file its copy was built from
When a book's primary file changed, the Kobo kept the copy built from the
old file but its positions no longer converted, so it was sent a bare
percentage, reset to the start, and that reset was saved over real
progress. Sync each device against its copy's source file instead, which
BookOrbit's sibling sync carries to the other EPUBs, and serve the new
primary when the device downloads the book again.

Fixes #1786
2026-10-17 01:02:57 +00:00
neon 60ad70ff5c fix(kobo): trust a confirmed replacement for positions and annotations
A replacement stayed ambiguous after the user confirmed it, so every
position the Kobo sent was discarded and annotation pulls failed with 409.
Confirming now makes it the device's copy and moves the stored bookmark
onto it. It still cannot drag saved progress backwards, and that is
judged by position, since its percentages run on a different scale.

Ref #1786
2026-10-17 00:45:21 +00:00
neon 27edb5e25c perf(kobo): stop re-hashing a delivery's source on every Kobo sync
Every position conversion resolved the delivered artifact by hashing the
whole source file, so a multi-gigabyte read-along outlasted the Kobo's 30 s
request timeout. Remember each source's hash by its file signature and
hash again only after the file changes. Reusing the stamped Kobo location
on device syncs (defect 3) is left out: that stamp is not per device.

Ref #1786
2026-10-17 00:19:14 +00:00
neon abb44aa47a fix(kobo): refuse a replacement with the bytes the device already has
A replacement byte-identical to the device's copy was never downloaded, so
it stayed offered and every bookmark the device pushed was discarded.
Keep the current delivery instead, and tell the user in the replacement
panel that the device already has this copy.

Ref #1786
2026-10-17 00:10:32 +00:00
neon 5051ec0913 fix(kobo): open a read-along book at its narrated sentence on the Kobo
A read-along save can hold the sentence being narrated without a text
position, and the Kobo was then sent a bare percentage, so it opened the
book at its start. Turn the narration marker into a text position and a
Kobo location when the saved progress has no cfi.

Ref #1786
2026-10-16 23:59:51 +00:00
neon dd004a3fa7 fix(kobo): stop a replacement with no earlier copy from blocking delivery
A replacement prepared before the device ever received the book had no
earlier delivery to fall back to, so every sync failed the book. It now
stops standing for the book and the book gets an ordinary first offer.
Cancelling a replacement the device has not fetched now clears it, hands
the book back to the earlier delivery, and re-announces it. Update the
download E2E suite for the stale-offer replacement from #1816.

Ref #1786
2026-10-16 23:47:40 +00:00
neon d02b4fcb8a fix(kobo): stop store sync looping when Kobo's cursor is too large to carry
A store cursor over the carry limit was dropped while the device was still
told to continue, so the store restarted at its first page forever. Stop
store paging for that sync instead, and carry Kobo's cursor verbatim in a
compact PX2 token so the limit can rise to 7,000 characters. Tokens in the
old format still decode.

Fixes #1679
2026-10-16 23:34:28 +00:00
neon e070460876 fix(kobo): replace a stale delivery instead of failing its download forever
When a book's file changed after it was offered to a Kobo, every download
returned 409 because the offer could no longer be rebuilt. A device download
now retires that offer and serves a fresh one from the current file, and the
device bookmark adopts the copy it downloaded. Log the conflict code when a
sync defers a delivery. Also drop a dev-only relocate log from the reader.

Fixes #1816
2026-10-16 23:34:28 +00:00
François-Xavier Payet 7f12e8cd6d fix(kobo): reconcile counter estimates on their counter interval (#1796)
fix(kobo): reconcile counter estimates on their counter interval (#1796)

Stop counting offline Kobo reading twice. Record when each counter
estimate's interval began, split measured sessions across intervals by
wall-clock overlap, and subtract measured seconds instead of dropping the
estimate, so both arrival orders converge.

Closes #1667

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-10-16 17:33:05 -06:00
neon d0da80e751 i18n(client): sync translations from Crowdin (#1755)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-16 16:35:14 -06:00
dependabot[bot] 60eb830a77 chore(deps): bump the github-actions group across 1 directory with 3 updates (#1828)
Bumps the github-actions group with 3 updates in the / directory: [EnricoMi/publish-unit-test-result-action](https://github.com/enricomi/publish-unit-test-result-action), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `EnricoMi/publish-unit-test-result-action` from 2.24.0 to 2.25.0
- [Release notes](https://github.com/enricomi/publish-unit-test-result-action/releases)
- [Commits](https://github.com/enricomi/publish-unit-test-result-action/compare/d0a4676d0e0b938bc201470d88276b7c74c712b3...7622c5bf3d112a72ab4fa3951c2aa5723d8ab92d)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: EnricoMi/publish-unit-test-result-action
  dependency-version: 2.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-16 16:34:46 -06:00
Dennis 428aefe8d1 feat(scanner): regex metadata extraction (#1669)
feat(metadata): extract metadata from file paths with regex rules (#1669)

Let libraries define ordered regex rules that read title, authors, series,
series number and dates from each book's path relative to its library folder.
Run rules in a bounded worker pool, honour source precedence and field locks,
and apply them on scans, uploads and watcher moves. Also retry unprobed M4B
tag writes with the mp4 muxer.

Closes #1351

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-10-16 16:24:11 -06:00
neon fd10f89586 fix(books): stop the details column from scrolling sideways
Clip the hidden genre measuring strip so its full single-line width no
longer widens the scrollable middle column, and let the rating row and
reading activity stats wrap in narrow columns.
2026-10-16 20:24:11 +00:00
neon a72179ad4d feat(auth): rebuild the sign-in pages as a bookplate on an endpaper
Put every auth page on a shared Ex Libris plate with signature-line fields, a show-password toggle and a Caps Lock hint.
Show sign-in messages outside the password form, with Retry, a reset link on lockout and a reset confirmation.
Add language and endpaper pickers (eight repeats) to the auth toolbar, replacing the background and corner controls.
2026-10-16 20:11:55 +00:00