Commit Graph

1185 Commits

Author SHA1 Message Date
neon 5a108bbd5e fix(client): paginate Book Dock navigation
Add a reusable pagination control and cover Book Dock navigation with focused tests. Align the client PDF dependency and lockfile for clean CI verification.
2026-09-26 01:17:55 +00:00
dependabot[bot] 0f2a679339 chore(deps): bump the production-dependencies group with 27 updates (#1542)
Bumps the production-dependencies group with 27 updates:

| Package | From | To |
| --- | --- | --- |
| [@embedpdf/engines](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/engines) | `2.15.0` | `2.15.1` |
| [@embedpdf/models](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/models) | `2.15.0` | `2.15.1` |
| [@embedpdf/pdfium](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/pdfium) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-annotation](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-annotation) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-bookmark](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-bookmark) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-document-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-document-manager) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-history](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-history) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-interaction-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-interaction-manager) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-pan](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-pan) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-render](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-render) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-rotate](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-rotate) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-search](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-search) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-selection](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-selection) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-spread](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-spread) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-thumbnail](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-thumbnail) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-tiling](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-tiling) | `2.15.0` | `2.15.1` |
| [@embedpdf/plugin-viewport](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-viewport) | `2.15.0` | `2.15.1` |
| [@formatjs/icu-messageformat-parser](https://github.com/formatjs/formatjs/tree/HEAD/packages/icu-messageformat-parser) | `3.5.18` | `3.5.20` |
| [@lucide/vue](https://github.com/lucide-icons/lucide/tree/HEAD/packages/vue) | `1.46.0` | `1.47.0` |
| [cronstrue](https://github.com/bradymholt/cRonstrue) | `3.26.0` | `3.27.0` |
| [reka-ui](https://github.com/unovue/reka-ui) | `2.10.4` | `2.10.5` |
| [vue](https://github.com/vuejs/core) | `3.5.42` | `3.5.43` |
| [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n) | `11.4.10` | `11.4.12` |
| [@fastify/static](https://github.com/fastify/fastify-static) | `10.1.3` | `10.1.4` |
| [@nestjs/throttler](https://github.com/nestjs/throttler) | `6.5.0` | `6.7.0` |
| [drizzle-orm](https://github.com/drizzle-team/drizzle-orm) | `0.45.2` | `0.45.3` |
| [fastify](https://github.com/fastify/fastify) | `5.12.4` | `5.12.5` |


Updates `@embedpdf/engines` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/engines)

Updates `@embedpdf/models` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/models)

Updates `@embedpdf/pdfium` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/pdfium)

Updates `@embedpdf/plugin-annotation` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-annotation)

Updates `@embedpdf/plugin-bookmark` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-bookmark)

Updates `@embedpdf/plugin-document-manager` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-document-manager)

Updates `@embedpdf/plugin-history` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-history)

Updates `@embedpdf/plugin-interaction-manager` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-interaction-manager)

Updates `@embedpdf/plugin-pan` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-pan)

Updates `@embedpdf/plugin-render` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-render)

Updates `@embedpdf/plugin-rotate` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-rotate)

Updates `@embedpdf/plugin-search` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-search)

Updates `@embedpdf/plugin-selection` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-selection)

Updates `@embedpdf/plugin-spread` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-spread)

Updates `@embedpdf/plugin-thumbnail` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-thumbnail)

Updates `@embedpdf/plugin-tiling` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-tiling)

Updates `@embedpdf/plugin-viewport` from 2.15.0 to 2.15.1
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.1/packages/plugin-viewport)

Updates `@formatjs/icu-messageformat-parser` from 3.5.18 to 3.5.20
- [Release notes](https://github.com/formatjs/formatjs/releases)
- [Changelog](https://github.com/formatjs/formatjs/blob/main/packages/icu-messageformat-parser/CHANGELOG.md)
- [Commits](https://github.com/formatjs/formatjs/commits/@formatjs/icu-messageformat-parser@3.5.20/packages/icu-messageformat-parser)

Updates `@lucide/vue` from 1.46.0 to 1.47.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.47.0/packages/vue)

Updates `cronstrue` from 3.26.0 to 3.27.0
- [Release notes](https://github.com/bradymholt/cRonstrue/releases)
- [Changelog](https://github.com/bradymholt/cRonstrue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bradymholt/cRonstrue/compare/v3.26.0...v3.27.0)

Updates `reka-ui` from 2.10.4 to 2.10.5
- [Release notes](https://github.com/unovue/reka-ui/releases)
- [Commits](https://github.com/unovue/reka-ui/compare/v2.10.4...v2.10.5)

Updates `vue` from 3.5.42 to 3.5.43
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.42...v3.5.43)

Updates `vue-i18n` from 11.4.10 to 11.4.12
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/v11.4.12/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v11.4.12/packages/vue-i18n)

Updates `@fastify/static` from 10.1.3 to 10.1.4
- [Release notes](https://github.com/fastify/fastify-static/releases)
- [Commits](https://github.com/fastify/fastify-static/compare/v10.1.3...v10.1.4)

Updates `@nestjs/throttler` from 6.5.0 to 6.7.0
- [Release notes](https://github.com/nestjs/throttler/releases)
- [Changelog](https://github.com/nestjs/throttler/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nestjs/throttler/compare/v6.5.0...v6.7.0)

Updates `drizzle-orm` from 0.45.2 to 0.45.3
- [Release notes](https://github.com/drizzle-team/drizzle-orm/releases)
- [Commits](https://github.com/drizzle-team/drizzle-orm/compare/0.45.2...0.45.3)

Updates `fastify` from 5.12.4 to 5.12.5
- [Release notes](https://github.com/fastify/fastify/releases)
- [Commits](https://github.com/fastify/fastify/compare/v5.12.4...v5.12.5)

---
updated-dependencies:
- dependency-name: "@embedpdf/engines"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/models"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/pdfium"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-annotation"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-bookmark"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-document-manager"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-history"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-interaction-manager"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-pan"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-render"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-rotate"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-search"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-selection"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-spread"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-thumbnail"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-tiling"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@embedpdf/plugin-viewport"
  dependency-version: 2.15.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@formatjs/icu-messageformat-parser"
  dependency-version: 3.5.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@lucide/vue"
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: cronstrue
  dependency-version: 3.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: reka-ui
  dependency-version: 2.10.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: vue
  dependency-version: 3.5.43
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: vue-i18n
  dependency-version: 11.4.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@fastify/static"
  dependency-version: 10.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: "@nestjs/throttler"
  dependency-version: 6.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: drizzle-orm
  dependency-version: 0.45.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: fastify
  dependency-version: 5.12.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-25 18:38:43 -06:00
dependabot[bot] afd118815e chore(deps): bump codecov/codecov-action (#1541)
Bumps the github-actions group with 1 update in the / directory: [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `codecov/codecov-action` from 7.1.0 to 7.1.1
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/0b35c9ecc4f0529d0eb674914510c22f85b196b4...303a32d7a59b442fa8d48b6a1cc6825c09c847a5)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  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-09-25 18:38:02 -06:00
neon fa36cb2edd fix(users): use public site for admin password links
Use the browser origin when APP_URL points to a local address so setup links work through a public proxy. Preserve public canonical URLs and HTTPS.

Closes #1548
2026-09-26 00:28:33 +00:00
neon 4b90dc7ddd fix(books): re-rank the primary file when an import joins an existing book
A file filed into a book that already existed was added without re-ranking, so the
book kept whichever file created it: an ebook that arrived first stayed primary in a
library that ranks audiobooks first. Imports and uploads now re-rank with the
library's priority, keep the current primary on a tie, and a rolled-back finalize
restores the primary it replaced.
2026-09-24 22:05:23 +00:00
neon 218f8d5dbd fix(books): list a book's formats from one ranked, read-along aware source
Every format chip, edition row and format menu now builds from shared helpers: content files
only, a read-along EPUB kept apart from a plain one, in the library's format priority, which
gains a read-along entry. Labels, colours and the chip each have one source, and cards, OPDS,
email, Kobo and the dashboard rank files the same way. Book detail file order is unchanged
because iOS builds its track queue from it.
2026-09-24 21:05:24 +00:00
neon 480904d250 fix(settings): hide the built-in type picker when setting up a plugin
The plugin Set up form reused the new-source editor, which always showed
an Indexer type select listing only torznab and newznab. Changing it
saved a torznab feed under the plugin's name, which is how a MyAnonaMouse
feed ended up pointed at the tracker's website.
2026-09-24 20:53:05 +00:00
neon da5d0bb405 fix(reader): start read along at the selected narrated sentence
"Read from here" and the page start resolved the range to the nearest element with any id and
matched SMIL entries against that id. Storyteller keeps the book's inline elements inside its
sentence spans with their ids intact (Kobo's kobo.N.N, calibre's calibre_link-N), so in those
books the nearest id named no entry and narration restarted at the top of the chapter, while
playback kept highlighting the same sentences correctly.

Start at the first SMIL entry whose element overlaps the range instead, looking each entry up the
way foliate resolves the highlight. Foliate already walks entries in reading order through the
start filter, so the vendored engine is unchanged.

Fixes #1528
2026-09-24 13:15:24 +00:00
neon 0d8648feb2 fix(kobo): scope plugin update gate to requesting device
Evaluate identified KOReader clients independently so a stale sibling device does not suppress safe updates. Keep the conservative account-wide gate for legacy or unusable client identities.

Fixes #1519
2026-09-24 12:50:56 +00:00
neon e28bd67fa3 feat(cover): give books with both media separate ebook and audiobook covers
Keep one cover slot per medium and show the one each surface needs: the
library's shape for the face, square art on audio screens, the ebook slot
for Kobo and OPDS. Legacy covers convert on upgrade or on first read, and a
write that names no slot keeps working for released iOS builds. Also read
the dev client origin from one constant.
2026-09-23 19:55:25 +00:00
neon 4b86ff73cf build: move local development off port 3000
Run the dev API on 6262 and Vite on 6263 with strictPort; production
defaults stay on 3000. Define the dev client origin once and read the
typed appConfig instead of repeating its fallback in every gateway.
2026-09-23 18:18:51 +00:00
neon 1dd29c742f fix(auth): keep signed-out reset links on their page
Load read-aloud voices only once playback starts, so the app-wide player
no longer fires a 401 on public pages. Let the session-failure handler
clear auth without leaving a public route, which also keeps magic links
and the sign-in redirect intact.

Fixes #1503
v3.1.0
2026-09-23 13:02:00 +00:00
neon 3c032e0e3c fix(reader): sync progress between an EPUB and its read-along copy
Carry positions between sibling EPUBs even without an audiobook, and keep
narration markers on the read-along file only. Point the KOReader book panel
and hold release at the copy devices sync, and label EPUB-only sync in the
read-aloud panel.

Fixes #1508
2026-09-23 12:30:01 +00:00
neon 94eca198c3 fix(stats): route KOReader hash collisions safely
Track colliding KOReader statistics rows independently and require verified explicit targets before upload. Keep unresolved history pending with a visible recovery path.

Fixes #1457
2026-09-23 03:27:52 +00:00
neon 27cfdc2028 fix(client): keep confirmation dialogs above sheets
Raise shared confirmation layers above sheet overlays so mouse input reaches privilege-change prompts.

Fixes #1502
2026-09-23 01:40:28 +00:00
neon bc10ebcc33 feat(stats): record android as a reading source
Accept android as a reading-session source from clients and report it as
its own statistics bucket, labelled Android app, beside the iOS app and
Apple Watch. A generated migration adds it to the reading_sessions check
constraint, achievements count Android reading toward Full Orbit and Two
Worlds, and the web charts draw it in a new --pill-android token at hue 95.
2026-09-23 01:34:19 +00:00
neon 4a4b62bbef fix(stats): repair stale KOReader file identities
Recompute the open file digest and recover previously verified matches without guessing between duplicate files. Preserve stranded statistics across lifecycle retries and advertise plugin version 1.5.3 so installed devices can update.

Fixes #1497
2026-09-23 01:15:20 +00:00
neon 759ec730d9 fix(scanner): repair incomplete audiobook durations
Repair legacy null durations during ordinary and incremental scans. Derive completion only after every asset has a measured duration.

Fixes #1498
2026-09-23 00:05:32 +00:00
neon c25a756ed7 fix(reader): skip zero-length overlay clips
Keep zero-width SMIL entries from capturing every later timeline position.

Fixes #1492
2026-09-22 15:34:39 +00:00
neon 09ea8bdd5f fix(server): prevent broad fuzzy search matches
Restore containment as the search eligibility rule so common trigrams
cannot admit unrelated books, authors, or series.

Fixes #1490
2026-09-22 14:49:48 +00:00
neon 5609b38901 fix(metadata): populate Hardcover genres for identified books
Request cached tags for ISBN and stored-ID GraphQL lookups. Normalize the Genre bucket so identified books retain the same genre metadata as free-text search results.

Fixes #1422
2026-09-22 11:56:54 +00:00
neon 6fa4d68728 fix(reader): prevent duplicate media overlay playback
Wait for the requested SMIL fragment to resolve before falling back, and invalidate stale audio loads so overlapping starts cannot orphan playback.

Fixes #1477
2026-09-22 11:38:28 +00:00
neon 12cf724c39 fix(books): align progress bar color with read status
Use the authoritative read status so cards completed at a library's configured threshold no longer appear in progress.

Fixes #1465
2026-09-22 11:11:24 +00:00
neon 926f5de225 fix(reader): preserve narration highlights across themes
Use the EPUB media-overlay active class and a theme-aware highlight variable so forced page backgrounds cannot hide the narrated sentence.

Fixes #1479
2026-09-22 10:52:32 +00:00
neon 304ccd5640 fix(reader): preserve iOS text selection adjustment
Keep pagination and the selection popup idle until native selection gestures finish, including cancelled touches.

Cover paginated and scrolled flows while retaining ordinary swipe snapping.

Closes #1487
2026-09-22 10:19:38 +00:00
neon a4809512d8 fix(reader): decode character references in read-aloud text
Block text was unescaped from a six-entry table, so only &amp;, &lt;, &gt;, &quot;,
&apos; and &nbsp; ever became characters. Every numeric reference survived into the
text handed to the TTS provider, and an EPUB that writes its apostrophes as &#39; had
them read out as "hash thirty-nine" while the page showed a normal apostrophe. Which
apostrophes broke depended only on how the book was serialized: &apos; was in the
table, &#39; was not, and both appear in the same sentence often enough.

Parse each block instead of stripping tags and unescaping by hand. The parser decodes
whatever the reader's own DOM decodes, which is the property that matters here because
the narration is read against the page, and it decodes in one pass, so a book that
quotes &amp;#39; is still narrated as &#39;.

Only the text conversion changed. Block boundaries still come from the same split on
block-element opening tags, so block indices stay aligned with Foliate's highlight
blocks and audio cannot drift a paragraph away from the highlight.

Do not route this through htmlToPlainText. That helper strips tag-shaped text a second
time after decoding, which is right for the scraped metadata it was written for but
silences book content: a code sample written &lt;div&gt;hi&lt;/div&gt; would narrate as
"hi", and "5 &lt; 6" would lose its operator. Tests cover both.

Fixes #1486
2026-09-22 09:51:10 +00:00
neon 970b152529 fix(cover): rebuild book thumbnails that went missing beside a cover (#1482)
A cover refresh skipped thumbnail generation whenever the stored cover
source was custom, so re-extract reported success while /thumbnail kept
404ing. Generate the missing thumbnail from the custom cover, and repair
any other gap lazily on the first thumbnail request.

Fixes #1475
2026-09-21 14:15:14 -06:00
neon 3bdd25e968 test(client): type the recorded request bodies
The new assertions read `apiMock.mock.calls[0][1].body` straight off an `unknown`, which vitest
runs happily and `vue-tsc` rejects. Read them through one typed helper instead.
2026-09-21 17:29:36 +00:00
neon d87312a8ca fix(client): send only the reader settings that changed
Every settings write from the reader PUT the whole object: the defaults write sent all twenty-one
EPUB fields and the per-book write sent the entire accumulated delta. PUT replaces the stored row,
so a write from this client threw away whatever another one had set since the page loaded.

That is a real conflict now rather than a theoretical one. The iOS reader owns the look fields and
keeps the layout fields on the device, so it writes a small subset and leaves the rest alone. A
full snapshot from a web tab reverted its look edits silently, and there was nothing either side
could do about it: last writer won the whole row.

Move the three writes to the PATCH routes, which merge field by field on the server, so two
clients editing different fields no longer race. An empty patch is dropped rather than sent,
because the server rejects an empty body.

Reset still deletes, on both routes: removing a customization is a whole-row operation and means
the same thing to every client.
2026-09-21 17:24:30 +00:00
neon a06b111046 feat(reader): merge reader settings field by field over PATCH
PUT reader/defaults/:formatGroup demands the full settings object, so a client has to send every
field of a group to change one of them. That forces a client to own fields it does not model and to
echo back values it never shows, and it turns two clients editing different fields into a
last-writer-wins race over the whole row.

Add PATCH for both defaults and per-book preferences. The defaults patch merges over the stored
row with the built-in defaults folded in underneath, so the row stays a complete object for the web
client's sanitiser while a partial caller leaves no holes. The per-book patch takes set and unset
and resolves both in one statement, so a concurrent patch cannot lose a field to a read-modify-write,
and deletes the row once its last key goes, which is the same state as never having been customized.
Nulls are never stripped: a null fontFamily means "use the book's own font", not an absent key.

Keep only the keys the caller actually sent. Zod applies .default() while parsing a partial, so a
cbx patch that never mentioned spreadGap came back carrying one; the existing per-book PUT had the
same latent bug and now shares the fix, which stops a touched comic from being pinned to the
default gap.

iOS is the only caller of the new routes. It keeps the layout fields on the device and syncs the
look fields, which is exactly the split a partial write makes expressible. The web client does not
call them and still PUTs full snapshots.
2026-09-21 15:31:53 +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 6be648b48a ci(release): treat breaking changes as a major release
Custom releaseRules short-circuit semantic-release's default rules, so a
feat carrying a BREAKING CHANGE footer resolved to minor. v2.11.0 should
have been v3.0.0.
v3.0.0
2026-09-21 07:18:29 +00:00
neon 67ef7f29a5 fix(client): localize the read-aloud interface
Replace the hardcoded English in the TTS and narration components with
message keys, label every icon-only control, and add settings.common.nav.tts,
which the settings sidebar rendered as a raw key.

Stop requiring PODCAST_ENCRYPTION_KEY while APP_FEATURES.podcasts is off, and
skip podcast routes and permissions in the authorization matrix for the same
reason.

Pass the direct-download settle() helper the SETTLE_MS budget the file already
defines: vi.waitFor's one-second default cannot cover six DNS-resolved redirect
hops on a cold CI resolver.
2026-09-21 06:36:24 +00:00
neon 7c7e23806e docs: link the creator workflow walkthrough from the readme
Add a short section pointing at bookorbit.app/my-workflow, the read-along
EPUB3 setup used day to day across iPhone, Apple Watch, KOReader, and the
web reader.
2026-09-21 03:47:44 +00:00
neon 6e1cbbf3e0 chore(client): fix tooltip provider lookup and desktop middle-tap
Both failures broke Tests (Client) on main. reka-ui tooltip parts inject a provider
context that only App.vue supplies, so any spec mounting a subtree threw; the tooltip
primitive now stands in its own provider when none is in scope. The foliate click
handler's final else had no isMobile guard, letting a desktop middle-zone tap toggle
the reader chrome. Also record the v3.0.0 server floor beside the App Store badge.
2026-09-21 03:15:58 +00:00
neon 2759893bf6 fix(books): record reading dates on the reader local calendar day
Reading attempt date keys came from toISOString(), which is UTC's day, so an evening finish west
of Greenwich filed tomorrow while the reading session it came from filed today. Callers now pass
the instant and UserBookStatusService resolves the reader timezone itself, because device syncs
carry no request user and a caller that forgot would silently fall back to UTC.

Closes #1458
2026-09-21 02:39:51 +00:00
neon b057e5e9d1 feat(client): link the iOS app from the sidebar footer
Add an App Store link and an Android placeholder beside the existing
project links, and regroup the footer so support and client links sit
on opposite sides of the version label. Size the Android mark larger
than the Apple one because its head fills only 56% of its viewBox.
2026-09-21 02:24:32 +00:00
neon 89af341b37 fix(server): authenticate against qbittorrent 5.2 and newer
qBittorrent 5.2 renamed its session cookie to QBT_SID_<WebUI port> and removed the setting that
overrode the name, so reading only SID left the session empty and every call answered 403. Match
both names, and report a 401 login as rejected credentials now that 5.2 answers a bad password
that way instead of with "Fails.".

Fixes #1306
2026-09-21 01:00:45 +00:00
neon 33952322f2 feat(settings): add Prowlarr and SABnzbd support (#1416)
* feat(settings): add SABnzbd download client

Support API-key authentication, NZB uploads, queue monitoring, reconciliation, and completed-download imports through SABnzbd.

Expose the client in request settings and validate its required category and credentials.

Ref #1312

* feat(settings): add Prowlarr indexer management

Manage synchronized torrent and Usenet sources through a shared Prowlarr connection.

Ref #1312

* fix(settings): correct SABnzbd adoption and managed search retries

Address four defects found in review of the Prowlarr and SABnzbd work.

SABnzbd keeps a failed job in its history until somebody clears it, and BookOrbit removes a client
download only on an explicit action. Because the client key is a stable digest, re-grabbing the same
release adopted that dead history entry, never uploaded the NZB, and failed instantly and forever.
Adopt only an entry that is not already failed; a queue entry still wins, so a fresh upload reports
correctly.

The category-expansion retry reused the title-and-author query, making it strictly narrower on text
than the title-only attempt that had just returned nothing. A release matching only a title-only
search with expanded categories was therefore unreachable. Drop the author there so each rung of the
ladder is genuinely broader than the last.

Changing the download client type through the create form's select bypassed the picker's handler, so
a username typed for qBittorrent was still sent when SABnzbd was chosen. The server stores it, the
API-key form hides it, and it can no longer be edited away. Watch the credential kind instead, so
both controls clear it.

Finally, a swallowed managed-release refresh failure left nothing behind at all; give it its own
fail log while still reporting the original refusal to the caller.

* fix(requests): cast managed search health flags to boolean

The bulk search-health update bound each case branch as an untyped
parameter, so Postgres could not resolve it against the boolean column.
Cast every branch explicitly and assert the cast in the repository test.

* fix(requests): close review gaps in Prowlarr and SABnzbd support

Inventory the eight indexer-manager routes so the authorization matrix
probes them, resolve a managed indexer without reading its manager's
whole source list, keep an unreadable credential out of the test-failed
mapping, translate manager error codes, and show per-source search
health. Documents Prowlarr connections in docs/BOOK_REQUESTS.md.

* i18n(client): keep the retired sources empty-state key

Dropping bodyOneWay from en.json forced pruning it from five Crowdin
target catalogs, which the localization policy check rejects outright.
Leave the now-unused key in place; nothing validates unreferenced
English source keys, so Crowdin can retire it on its own schedule.

* db: regenerate the SABnzbd and Prowlarr migration onto main

Rebasing collided with main's own 0091 and 0092, which left this branch's
two migrations on disk with no journal entry referencing them. Drop both
and regenerate a single migration on main's lineage; the three affected
tables are schema-identical to the pre-rebase snapshot.
2026-09-20 18:38:36 -06:00
neon 2859dd6950 chore: tighten README branding spacing
Keep the logo and project name in one heading block to avoid GitHub default inter-block margin.
2026-09-20 23:55:29 +00:00
neon c239cfa2dd feat(books): rebuild duplicate books as a ledger
Sort groups by reclaimable space, pre-pick a keeper with a rule that states
its reasoning, and expand any row into a field-by-field comparison that shows
only what differs. Persist "not duplicates" so a judgement survives a rescan,
and review every copy before deleting. Reading progress no longer renders 0%.
2026-09-20 20:11:34 +00:00
neon 0007a9d84f fix(docker): support mounted files for every app secret
PODCAST_ENCRYPTION_KEY is required in production but was absent from the
entrypoint file_env list, leaving it as the only secret with no _FILE path.
Test every supported name rather than spot-checking a single one.
Move secrets-file and OIDC hardening detail to bookorbit.app/installation.
2026-09-20 18:35:15 +00:00
neon 6e608e79a4 fix(client): show the platform's own shortcut modifier
Lift the bulk rename empty state out of the vertical centre of the
tools pane so it sits near the top instead of floating in the void.

Replace the hardcoded Cmd in the settings search hint with a shared
useModifierKey composable, so Windows and Linux see Ctrl+K. The
translated hint now takes the combo as a placeholder.
2026-09-20 17:49:01 +00:00
neon 2da27cb5c2 fix(settings): stop cropping the sidebar search placeholder
Reserve the clear button's 32px of right padding only while it renders,
which leaves the placeholder 124px instead of 102px at the default
sidebar width. Add text-ellipsis so longer translations end in an
ellipsis rather than a sliced glyph, and hide the native WebKit search
cancel button that doubled up with our own.
2026-09-20 17:36:19 +00:00
neon 06dcb6df6c docs: showcase official iPhone and Apple Watch apps 2026-09-20 15:30:24 +00:00
neon 58d90b3ab9 docs: add BookOrbit icon to README 2026-09-20 14:13:34 +00:00
neon d3f3382bfa feat(reader): synchronize Storyteller reading progress
Keep audiobook, EPUB, Kobo, and KOReader positions coherent while serving each client the safest suitable book variant.
2026-09-20 14:12:18 +00:00
neon 4dabacb2b3 fix(migration): verify BookLore v2.4.0 compatibility 2026-09-19 16:52:25 +00:00
neon e0d3d42150 feat: add read-aloud and media playback support
Add EPUB narration, shared media controls, and native device session integration.

Apply the generated migrations and update all first-party clients alongside the server.

BREAKING CHANGE: introduce incompatible database and API contracts for media playback
2026-09-19 02:27:05 +00:00
neon c292d6ccd2 fix(metadata): honor configured Amazon domain in book links
Use the sanitized provider-link setting so book detail URLs target the selected marketplace.

Fixes #1423
2026-09-18 16:32:18 +00:00