The KOReader plugin sends md5(password) as x-auth-key, which can never match the bcrypt hash of the
plaintext password. The auth guard ran a cost-12 bcrypt compare first, so every authenticated
request spent about 250 ms of CPU and stalled other traffic before the md5 fallback accepted it.
Check md5 first and keep bcrypt as the fallback for clients that send the plaintext password, so the
accepted credentials are unchanged. A plugin auth call drops from about 285 ms to 8 ms.
Bump the KOReader plugin to 2.2.0. The plugin changed after the 2.1.0 release shipped in v3.6.0
(series sorting, series selection, file browser exit, paged Shelves list) without a version bump.
Also fix three metadata-fetch tests that were failing on main since cfcd4ca52 kept single-digit
tokens in tokenizeTitleText. A volume digit in the query diluted the word overlap in scoreTitleMatch,
so an ebook candidate fell under the relevance floor and a manga outranked it. scoreTitleMatch now
drops lone digits again; symmetricTitleSimilarity keeps them to tell volumes apart.
Also align the pnpm version installed in both Dockerfiles with the packageManager field. Since
f2a7a1118 the images installed pnpm 11.22.0 while package.json pinned 11.28.9, so pnpm tried to
switch itself and failed the arm64 image build with a missing @pnpm/exe lockfile entry.
Closes#1983
Ref #2037
A Kobo sends Host without a port, and Tailscale Funnel repeats that Host in X-Forwarded-Host
without adding X-Forwarded-Port. buildBaseUrl then skipped APP_URL because forwarded headers were
present, so cover, reading state and download links lost the public port and pointed at port 443.
The Kobo never reached the server for a download or a thumbnail, and the delivery stayed offered.
Use APP_URL's origin whenever no port is given and its host matches, and, when the proxy names a
scheme, only if the scheme matches too. Explicit ports, X-Forwarded-Port and hosts or schemes
APP_URL does not share keep their previous behaviour.
Fixes#2036
Fit every library, collection and SmartScope on one screen shrank the rows with no lower bound, so a Kobo Libra with 45 shelves drew titles on top of each other. Keep rows at a readable minimum and run the shelves over further pages, repeating a section heading where it continues. Wide screens keep their two columns and turn the page once both are full, and only the shelves on the current page fetch thumbnails.
Closes#2042
Normalize stored zero values at the shared write payload boundary so every format omits an unknown page count. Align collection repository tests with the query shape left after podcast removal.
Closes#2040
Compare release and imported metadata against the requested series position so unattended fulfillment cannot file a later volume under the request. Keep unnumbered candidates eligible and leave exact ISBN matches decisive.
Fixes#2037
Compare extensionless archive entry paths first so a cover named IMG.jpg precedes page names that extend IMG with a suffix. Keep cover extraction and CBZ/CBR page delivery aligned, and invalidate old page ETags.
Closes#2035
An EPUB with Media Overlays already carries its narration, yet the Chapters tab required audio files and the Editions panel offered to request an audiobook the book already had.
List the narration sections of such an EPUB as a read-only chapter list, and count the narration as the audio half when deciding which format is missing. The chapters state gains an optional readAlong flag, so released clients keep working. Editing stays tied to audio files, because an EPUB has no chapter metadata to write.
Ref #2032
Keep the reading view as the default exit while exposing KOReader's file browser as a separate action that closes the open book safely. Bypass the BookOrbit home preference for this explicit navigation.
Closes#2013
Podcasts shipped switched off in every release and are now removed from the server,
web client and shared types. Migration 0132 drops the podcast tables and the media-type,
library-type and folder-role columns. Released iOS and Android builds read the removed
response fields as optional, so the API change stays compatible.
Achievement names, descriptions and category labels, the secret placeholder and the monthly
challenge are text the server writes and every client shows as is, so they stayed English in a
translated web page and will in the localized iOS app. The routes that return them now read
Accept-Language and answer in that language, with Vary: Accept-Language.
The translations live in the web's catalogs under serverText, so Crowdin, its checks and its
translators handle them through the existing pipeline; the server reads that subtree, and the image
copies the catalogs beside dist. English is never looked up: a request without the header, and
every client that predates it, gets the bytes it got before. A test keeps serverText in en.json
equal to the seed and the constants it mirrors. The web now sends its chosen language on every API
request, and the Accept-Language matcher moved into @bookorbit/types so the client and the server
negotiate the same way.
The iOS app is a consumer the web client does not show: it will send its own interface language.
App Store builds that predate that still send the device language, so on a non-English device they
will start getting translated achievement names inside English screens. That was accepted; the
response shape does not change.
Ref bookorbit/bookorbit-ios-support#79
Read the series id once per kept-alive instance instead of following the
shared route, so coming back no longer resets the list and strands the
scroll observer on a removed sentinel. Re-observe the sentinel whenever
the books section remounts.
Closes#1988
Send logical volume counts on each series summary, tallied from the member
grouping the ladder already builds, and read them on the cards instead of
counting the 60-slot ladder. Long series no longer stop at 60 or show as
complete after their first 60 volumes are read.
Closes#1987
Mark pull requests stale after 30 inactive days and close them seven days later. Clear the stale state when new activity arrives so active contributions remain open.
Upgrade fixable Node dependencies and rebuild the pinned kepubify binaries with Go 1.27.3 so multi-architecture image publication passes the security gate.
Add a Colour setting to Settings > Home: Accents, the default, colours each
widget's marks and draws Continue reading in its cover's deep shade; Tinted
also washes every tile in its widget's colour; Off keeps the greys. Black-and-
white screens draw as before, colour e-readers get stronger shades, and night
mode keeps each colour's hue.
Redraw every reading pet as a shaded 64 x 64 master with a colour and a
grey per palette entry, one lazily loaded module per pet, and add the
mimic, mummy, witch, skeleton, candle and alien. Paint in colour on
colour screens, keep pet colours in night mode, scale by half steps and
give pet tiles a dithered refresh so Kaleido shows their colour.
Report a linked copy whose bytes differ from the library file as positionIncompatible instead of
unmatched, so the plugin keeps the match, skips highlights without a failure and still syncs progress.
Treat a position-less place within a page as the same place and jump to the nearest page.
Suggest library books with the same title and authors for books to review, with bulk linking.
See docs/KOREADER_HASH_MATCHING.md.
Closes#1872Closes#1919
The dock's six 32px controls need 13.25rem, but the sidebar can be dragged
down to 224px, so the language and settings buttons spilled past its edge.
Below that width the buttons now shrink to 28px with a 2px gap. The spacer
that added a stray gap is replaced by a right-aligned group; the rail and
phone sheet are unchanged.
Closes#1873
A cover staged after unlocking a locked slot, then re-locked before saving,
was silently dropped. The save now leaves that slot open, writes the cover,
then locks it again, and holds the page refetch until the locks are final.
Closes#1915
Rebuild the app and read-aloud worker images on Node 26.12.1, which fixes
CVE-2026-111497 in the bundled Brotli decoder and the other October 2026
Node security issues. The app image stays on Alpine 3.23, pinned by digest.
Closes#1912
Draw dashboard thumbnails in RGB32 when the screen is RGB565. KOReader's
MuPDF scaler throws on RGB565, which crashed the first-run layout picker
and the widget previews on every reMarkable running under AppLoad.
Closes#1909
Plugins from 1.7.0 request a delivery variant, and every such download had
" - bo-<id>-<variant>" added to its name. Only copies that would share a path are
renamed now: a narrated EPUB's text-only copy and later same-format files. A new
<a|b|c> chained default also stops the default pattern naming authorless
standalone books "Standalone/ - <title>". See docs/READ_ALOUD_DELIVERY.md.
Closes#1893
Offer the one-folder-per-series layout Komga and Kavita use as a File as Book path-rule preset
rather than a new organization mode, so released iOS builds and both existing modes are unaffected.
Add a job that applies saved rules to books already in a library, and keep a run year such as
"Batman (2016)" when ComicInfo or a provider names the series without it.
Ref #1167
Ref #1918
Ref #839
Pick folders from a lazily loaded tree with find-by-name and typed paths, beside a plan showing
the books each organization mode would import, overlaps with other libraries, and folders no
library reads yet. New ManageLibraries /path routes walk folders within entry and time bounds.
Fix the scanner filing a disc-named library folder such as /mnt/disk1 under the folder above it,
and move books already filed there onto their primary file on the next scan.
Promote Viewer grants to Editor on book libraries for non-superusers who hold Edit metadata but
can edit nowhere, which is how every such account was left after the upgrade. Accounts with any
Editor or Owner role are left alone. The Users drawer now says where Edit metadata takes effect,
read from a new GET /users/:id/library-access route.
Closes#1902
Layout breakpoints use the screen width at 1x, so Interface size scales text,
icons and spacing without dropping Home columns or detail panes. Every page,
sheet and widget picks a layout that fits instead of cutting text or leaving
gaps; search becomes balanced list-row sections, Edit Home keeps hidden widgets,
stock KOReader messages become BookOrbit toasts, and the plugin is 2.1.0.
Register book-metadata-provider services by address, with no code change per
provider; Settings lists ready-made ones to install. Ranking prefers a record of
the book's own medium. LubimyCzytac moves out to its own provider and Aladin,
whose API closes, is retired; their ids stay. Adds migrations 0129 and 0130.
See docs/METADATA_PROVIDERS.md.
Ref #1405
Add GET and PUT /reading-progress and DELETE /reading-progress/:fileId: positions grouped by
kind, up to 25 files saved in one request, and an optional editedAt so the server keeps the newer
of two saves. Move the web reader and book page to them, and list reading-progress in
SERVER_FEATURES for the iOS and Android apps, which adopt the routes in the same release.
List reading-history in SERVER_FEATURES so the iOS app shows History, and its links from
Activity and a book's Timeline, only on servers that serve the routes.
Ref #1630
A text-only download of an EPUB with no narration returned 400 when the
strict inspection tripped on a structural fault such as duplicate manifest
ids, a missing resource or a chapter over 16 MiB. Serve such books as stored,
and log why inspection failed or why a text-only delivery was refused.
Closes#1888
Add a History page listing every read newest first, rereads included, as a
virtualized ledger or month cover walls, with period totals, per-read ratings,
export and a share image. Deleting a book keeps its finished reads under a
title and author snapshot (migration 0128).
Closes#1630
Add a Chapters tab to edit chapter timing and titles, look chapters up on Audible or in the ebook,
snap starts to pauses, keep a version history and write chapters into M4B, M4A and MP3 files. Add a
chapter health tool with a bulk Audible lookup queue. Editing needs Editor access to the book's
library. Chapter edits keep released iOS playback working by accepting a stale manifest revision
while the track layout is unchanged.
Ref #1120