Commit Graph

334 Commits

Author SHA1 Message Date
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 a35e222cb2 feat(types): expose the book file id on audiobook manifest assets
Clients that download an audiobook get its files from the book detail and its playback order from
the manifest, and had to pair the two by position. The two lists are ordered differently, so an
index pairing can attach the wrong file to an asset. Add the book_files id to each manifest asset
so the pairing can be made by id.

The field is additive and the server always sends it. The manifest revision is hashed from file
and chapter data, not from the response, so existing revisions do not change and clients holding
one do not see a 412 from this deploy.

The iOS app consumes this field to pair downloaded files with manifest assets; the web client
does not read it yet, so a grep of the client alone will show it as unused.
2026-09-28 22:38:47 +00:00
neon 55110d5b44 feat(settings): add per-user Requests visibility toggle
Let users hide request entry points and notifications while existing requests continue running. Keep the setting optional so older clients retain their current behavior.

Ref #1476
2026-09-26 18:46:10 +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 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 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 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 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 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 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 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 3bb33b615c feat(book-requests): add per-indexer seed goals
Add configurable seed ratio and seed time goals to request indexers, supporting both static goals and tracker-advertised ratio/seed-time limits via torznab attributes. Enforce indexer-specific seed policy during request fulfillment and display active seed targets in the request seed panel.

Closes #1360
2026-09-13 17:42:12 +00:00
neon 8f1215d331 feat(book-requests): add indexer plugin updates and release profiles
Add an in-app update service and UI controls for installed indexer plugins, verifying releases and fetching updates securely. Introduce configurable release profiles for fine-tuning score weights, format preferences, and rejection criteria in book request automation.
2026-09-13 15:04:20 +00:00
neon fcdd72a384 feat(book-requests): add newznab and nzbget support
Support Usenet indexers via the built-in Newznab adapter and Usenet downloads via NZBGet. Generalize active download tracking from torrent client hashes to protocol-agnostic client keys, add NZB release scoring and file handling, and expose Newznab indexer and NZBGet client options in settings and grab flows.

Closes #1312
2026-09-13 06:47:06 +00:00
neon ceda7428b0 feat(reader): add dedicated audiobook playback API 2026-09-11 05:41:29 +00:00
SergeiAK 73ac0bb722 feat(authors): collapse series on author pages (#1271)
Reduce large author listings to one card per series while preserving book counts, scoped visibility, and row-based pagination. Compute both totals in the collapsed query so deep pages remain consistent without a second author-wide scan.

Closes #301

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-09-10 12:39:14 -06:00
neon d9ea13ba45 feat: add BookOrbit attribution requirements
Define AGPLv3 additional terms and expose attribution, source, and license details through unobtrusive About surfaces.
2026-09-10 18:03:11 +00:00
bobberb 17aa7425f8 feat(library): per-library added_at source (imported/file_modified/file_created) (#1239)
* feat(scanner): set book added_at from earliest on-disk file mtime

A book's "date added" now reflects when its content files first landed on
disk (the earliest mtime across its content-role files) instead of the
moment BookOrbit imported it. Cover/metadata/supplement sidecars are
excluded. When no content file has a usable mtime, addedAt is left unset so
the DB defaultNow() fallback (import time) still applies. Only set at
initial book creation; re-scan/restore paths do not touch it.

* feat(library): add per-library added_at source with recompute

Add an addedAtSource setting to libraries so "date added" can follow the
book's files on disk rather than the moment the scanner happened to see
them. The setting takes imported (unchanged default), file_modified, or
file_created, guarded by a check constraint on the column.

Expose a recompute action so the chosen source can be applied to books
that are already imported. file_modified runs as a single set-based
update over the stored mtimes; file_created has to stat the files, since
birthtime is not persisted, so it batches those reads.

Leave every existing library on imported so behavior is unchanged unless
a user opts in.

* feat(library): add added_at source control to the library editor

Surface the new addedAtSource setting in the library editor as a radio
group, alongside a button that triggers the recompute for the library
being edited.

Keep the recompute out of the save path so changing the setting stays
cheap: the setting alone only affects books imported from then on, and
applying it to existing books is an explicit choice.

* i18n(library): translate added_at source strings (de/nl/pt/sl)

* fix(library): correct create payload, recompute floor and failure logging

The library creator posts its whole form, so omitting addedAtSource from
CreateLibraryDto made the global whitelist reject every create request
with a 400. Declare the field, validate it against the same source list
as the update DTO, and persist it so the initial scan uses the requested
source rather than silently falling back to imported.

Apply the scanner's usable-time floor to the mtime recompute query too.
It previously excluded only NULL, so a filesystem reporting epoch 0 would
rewrite added_at to 1970 instead of leaving the import time in place. The
floor now lives in library.constants rather than being spelled out twice.

Wrap the recompute in try/catch so a database or stat failure emits the
[fail] log the logging conventions require instead of propagating with
only a [start] line recorded.

* fix(library): gate recompute on the saved source and label the radios

The recompute button stayed enabled while the selected source differed
from the stored one. Because the request carries no source, clicking it
recomputed against the old stored value while the UI showed the new
selection. A hint already said to save first; disable the control as well
so the mismatch is not actionable.

Give the source list radiogroup semantics with aria-checked so screen
readers can tell which option is active, since selection was conveyed by
styling alone.

* test(library): assert the create DTO under the real validation options

The previous comment claimed the case exercised whitelist and
forbidNonWhitelisted, but the shared helper calls validate without them,
so it only proved @IsIn rejects an unknown value.

Drop the misleading comment and add a case that validates with the same
options main.ts gives ValidationPipe, so an undeclared addedAtSource
fails the way it would in a real request.

* docs(library): remove redundant date-added documentation

---------

Co-authored-by: Bob Bobber <user@example.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-09-10 10:43:47 -06:00
neon c2f9eea839 chore: add AGPL attribution notice
Preserve project credit in conveyed source distributions and include the legal files in runtime images.
2026-09-10 13:32:29 +00:00
neon 33c7260f18 fix(auth): explain untrusted OIDC certificate failures
Classify private trust-chain failures with a stable error code and direct administrators to a mounted CA bundle without weakening TLS verification.

Fixes #1346
2026-09-10 11:47:56 +00:00
neon b9609b198e fix(annotations): preserve source highlight timestamps
Use KOReader highlight times for annotation history while retaining ingestion times for diagnostics. Export all stored location representations and backfill existing rows during sync.

Fixes #1300
2026-09-08 16:52:36 +00:00
neon 9165dbed1d feat(auth): add SSO-only authentication mode
Require an enabled OIDC-linked superuser before disabling password flows. Track session authentication methods and fail closed across the API, web client, WebSockets, and administrative mutations.

Closes #1002
2026-09-02 05:04:18 +00:00
neon adab5c8783 fix(scanner): handle legacy Book Dock series indexes
Normalize numeric JSON snapshots at the Book Dock boundary and reject malformed runtime values without aborting filing.

Closes #1241
2026-09-02 00:58:39 +00:00
neon e3779c1d7e feat(metadata): restrict autofill to stored provider ids
Prevent automatic metadata refreshes from searching alternate identities when a book already has trusted provider IDs. Preserve discovery and manual search behavior, and avoid dirtying descriptions on empty refreshes.

Closes #1222
2026-09-01 20:55:36 +00:00
wmTJc9IK0Q 04ec495c25 feat(reader): add PDF highlights and annotations on web only (#1213)
* feat(reader): add PDF highlights and annotations

Persist and render PDF highlights through the same annotation system as
EPUB. The annotation_positions table already supported a 'pdf' format, so
this wires the web create path, response, and reader UI to it.

Backend:
- create-annotation DTO accepts a pdf position (page + segment rects) as an
  alternative to a cfi; exactly one is required
- repository createPdf() persists a format='pdf' position (geometry in pos0,
  pageno in extras); selectWithCfi exposes the pdf geometry and status
- response DTO parses the pdf position and derives positionStatus

Frontend (pdf-v4 reader):
- usePdfAnnotations REST store and usePdfHighlights orchestration render
  persisted highlights via the EmbedPDF annotation plugin (autoCommit off,
  DB stays source of truth)
- selection popup (colour/style/note/copy/delete), note dialog, and a
  Highlights sidebar tab with search/colour/notes filters, navigate, delete
- NoteDialog relocated to reader/shared for reuse by both readers

Shared types gain AnnotationRect/AnnotationPdfPosition and an optional pdf
field on AnnotationItem.

* fix(reader): preserve input on failed PDF annotation save; convert mixed PDF+CFI for KOReader

- usePdfHighlights: createFromSelection/restyleExisting now return success;
  applyHighlight and saveNote only clear the selection, dismiss the popup, and
  close the note dialog on success. On failure the mark, popup, and typed note
  are preserved so the user can retry without losing input.
- koreader annotation exchange: the PDF push-down guard now skips only PDF-only
  annotations (no EPUB anchor). A mixed PDF+CFI annotation still converts its CFI
  to xpointer instead of being dropped.
- Tests for both paths.

* fix(reader): label PDF selection buttons and dedupe multi-page highlight retries

- PdfSelectionPopup: add aria-labels to the copy, highlight, note, and delete
  icon-only buttons so screen readers can identify them.
- usePdfHighlights: createFromSelection keeps only the pages that failed, so a
  retry after a partial multi-page failure never duplicates already-saved pages.
- Test for the multi-page partial-failure retry path.

* fix(reader): harden PDF annotation store, validation, and create logging

- usePdfAnnotations: catch rejected api() calls (session expiry / network) so
  load() sets loadError and mutations return null/false instead of rejecting.
- create-annotation DTO: require positive rect width/height so degenerate PDF
  geometry is rejected; tests for zero/negative dimensions.
- annotation.service: add [annotation.create] start/end/fail lifecycle logs with
  IDs, durationMs, and sanitized error for the multi-step create.

* test(reader): cover PDF annotations in the hub and add missing docstrings

Adds a real-DB e2e proving a PDF highlight persists as a pdf position and surfaces in the library-wide Annotations hub with the exact fields the page deep link needs (jumpFileFormat=pdf, cfi=null, pageno). Adds a hub-service unit test for the pdf row mapping. Documents the PDF render helpers, the annotation deep-link builder, and the createPdf repository method.

* fix(annotations): make PDF highlights reliable across reloads

Reconcile persisted highlights after EmbedPDF finishes loading its annotation state. Keep file-scoped pagination bounded and prevent PDF-only positions from starving device sync.

Ref #1195

* fix(annotations): keep pdf annotations stable during reload

Use authenticated native pagination fetches and discard responses that overlap successful mutations. Align shared dialog styling and annotation tests with project conventions.

Ref #1195

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-09-01 09:16:01 -06:00
neon 2912429ca6 feat(reader): add epub typography controls
Preserve publisher typography by default while allowing explicit spacing and indentation overrides across reader clients.

Ref #1206
2026-09-01 00:15:28 +00:00
neon 53cfe16b04 feat(reader): add epub paragraph spacing
Preserve publisher spacing at the minimum while allowing synchronized
format defaults and per-book overrides.

Closes #1206
2026-08-31 23:04:55 +00:00
neon fc6e6f03d5 feat(book-requests): add automated request fulfillment
Let users request books, route approvals, search configured sources, and import fulfilled releases through the Book Dock. Include scoped permissions, automation, download-client integrations, plugin support, requester visibility, and one consolidated schema migration.

Closes #248
Ref #1115
2026-08-31 16:52:43 +00:00
neon 0f5429131a feat(smart-scope): filter by community review count
Allow provider-aware review count criteria so rating thresholds can be qualified by community review volume.

Closes #1041
2026-08-31 04:54:00 +00:00
neon 5a668b0015 feat(collections): add public collections with shared read access
Add an isPublic flag to collections allowing users to share collections across the instance. Public collections are readable by all authenticated users while write, edit, reorder, and delete operations remain restricted to the owner.
2026-08-28 16:13:28 +00:00
neon 1dd39bf0eb feat(books): add primary file size filter rule
Add fileSize as a filterable rule field resolved against the book's primary file size in bytes. In the filter builder UI, allow operators to input sizes in megabytes with automatic serialization to bytes for queries.
2026-08-28 15:32:28 +00:00
neon a51122237d feat(library): rebuild the bulk rename tool
Redesign the bulk rename UI with change-type grouping, inline character diffs, and tree previews. Add two-mode selection for large libraries, virtualize the queue, and move search to the server. Optimize execution by pausing the library watcher instead of tearing it down, and improve SSE progress reporting and run error handling.

Share one path-resolution routine between the preview and the rename so the preview cannot offer a rename the executor refuses: multi-track audiobooks whose parts collapse onto one filename, and renumbering that would overwrite the book's own files.
2026-08-28 04:11:06 +00:00
neon ddb296cade fix(books): show read status on the book detail discovery shelf
The series, author and similar endpoints never selected a read status, so
every cover in the detail shelf rendered bare. Attach the requesting user's
status in one bulk lookup per lane, and draw the badge in BookCarousel using
the same icons and placement as the library grid, plus an sr-only label.
BookRepository has no user in scope, so it now returns UnscopedBookRecommendation
and the type system forces the status on where the user exists.

Also adds the untracked scanner overlap repro for issue 1094.

Closes #1163
2026-08-27 21:56:17 +00:00
neon 7ae49c093c fix(types): trim trailing dots and spaces from resolved path segments
Token values are sanitized before modifiers run, so {authors:sort} rewrote
"J.K. Rowling" into a folder named "Rowling, J.K." that Windows and SMB reject,
serving a mangled 8.3 alias instead. Normalize each segment after assembly rather
than reusing sanitizePathSegment, whose reserved-name guard re-fires on a second
pass and would turn "NUL.txt_" into "NUL.txt__".

Closes #1160
2026-08-27 16:54:07 +00:00
neon c1794b677c feat(series): rebuild the list as a card and a list view sharing one volume track
Both views are toggled from the header and read from the same ladder: one
segment per volume the series should have, coloured read, reading, in library
or missing. Gaps were previously only on the series detail page, so the list
could never say a volume was absent; a Gaps filter now decides that in SQL,
mirroring the existing gap finder branch for branch so the two surfaces can
never disagree.

The card's fanned covers are laid out from cached percentages against an
aspect-ratio box, so nothing is measured at runtime and hover spreads the fan
through one custom property instead of per-cover reactive state.

See docs/COMMIT_GUIDELINES.md for the API additions to SeriesSummary.
2026-08-27 04:11:10 +00:00
neon b41675c608 feat(authors): rebuild the page as an index and a gallery on one view toggle
The two layouts are the viewMode the app already persists, so search, filters
and sort carry across them. Only 19% of a library's authors have a portrait,
so the gallery leans on monograms and the index treats the name as the content;
a sort name now prints only when it says something the display name does not.
Jump buckets, a cover fallback and two filters come from the server so the A-Z
rail and the chips cover the whole result set rather than the loaded pages.

Reconciles authors.has_photo against the image store on boot: the list resolves
portraits from disk while the filter queried the column, so "no portrait" was
over-reporting by 64.
2026-08-27 04:04:18 +00:00
neon 95239ba701 feat(annotations): rebuild the hub and the highlights tab as one reading surface
Both pages now share the stream, entry and detail components, so a highlight
reads the same wherever you meet it. Entries split three ways - date rail,
quote at a real measure, source margin - and the library hub gains an overview
endpoint so its side rail does not recompute six aggregates per scroll.
Notes and anchor-review are server filters now; filtering the loaded window
made the counts and the pager disagree with each other.
2026-08-26 22:34:11 +00:00
neon 75ccb8dd1a feat(book-detail): rebuild the highlights tab as a single-screen ledger
Replace the paginated card list with a three-column ledger: a marks rail, a
grouped stream that loads on scroll, an index that regroups by chapter, colour
or day, and a chapter position band. Add chapterBreakdown and activity to the
annotation stats so those panels describe the whole book without loading every
row, and move the metadata score colour onto a shared oklch ramp.
2026-08-26 17:04:48 +00:00
neon 4c42e0c73f fix(books): reshuffle random sort per visit instead of per day
Seed the random sort field from a per-view randomSeed on BookQuery rather
than a day counter, so a reload or a scope change draws a new order while
every page of one listing stays coherent. Callers that omit the seed keep
the daily fallback. Adds a shuffle button to the library, smart scope, and
collection toolbars when the active sort is random.

Closes #1153
2026-08-26 15:25:46 +00:00
neon 095113a594 feat(book-detail): rebuild the reading log as a single-screen ledger
Vitals, the session ledger and attempts/records fill the pane height, with
the progress band across the row beneath, matching the details tab.

- attempt boundaries and reading stalls are rows inside the ledger, in date
  order only, so sorting by length or change still works
- the band toggles between the progress curve and a trace of the stretch of
  the book each session covered
- one empty state for a book with no log, in place of four empty panels
- drops the twelve-month heatmap, which was 357 empty cells on a real book

Stats gain longestSessionSeconds, longestSessionAt and backtrackCount, and
sessions gain attemptId; the client cannot derive either from one page.
2026-08-26 07:28:41 +00:00
neon c383ad16e1 feat(naming): add a narrators token to file naming patterns
Multiple audiobook editions of one title resolved to the same target path, so
enabling file rename merged or skipped every narrator but the first. Add
{narrators}, plus a :max3 modifier that yields nothing above three values, and
judge an optional group by what its placeholders render so the fallback literal
stays in the user's own pattern. The five duplicate token builders collapse into
one shared helper, which also gives audio uploads the metadata tokens they never
had. Build the shared types package before server lint, since type-aware rules
degrade to unknown on a clean checkout without it.

Refs #1108
2026-08-25 19:03:37 +00:00
neon a25362244c feat(notifications): add per-category levels and collapse repeat notifications
Choose Off, Problems or All per category instead of a single on/off switch, and
stop announcing scheduled scans that changed nothing. Repeat occurrences now
collapse into one row with a count, so a bulk run no longer emits one
notification per book. Category and severity move into a shared registry that
the client reads instead of keeping its own copies.

Closes #1141
2026-08-25 17:37:45 +00:00
neon 7d3ff9997d feat(library): add a Missing Resources cleanup tool
Remove database records for books whose files are gone, clear cover
references with no file on disk, and delete cover folders left behind by
deleted books. Each category is reviewed in a paginated list and
re-verified against disk before anything is removed.

Closes #312
2026-08-25 05:18:31 +00:00
neon 63d4dc3bb4 feat(reader): continue a comic series from the last page
Finishing an issue offered no way onward, so the next one had to be opened from the library.
Resolve the next readable book server-side with one keyset query, offer it on the last page, and
let an opt-in setting cross over on a further page turn. The reader route now remounts on a param
change, without which it would keep rendering the book it was mounted with.

Also cast the compared value in compareSeriesIndexSql: as a bare parameter Postgres could not type
it inside the null test and rejected the statement.

Closes #998
2026-08-25 04:26:31 +00:00
neon 6702d5b729 feat(reader): jump to reading session checkpoints
Expose each session's originating file so the reader can restore a validated historical progress position without losing exact highlight navigation.

Closes #1045
2026-08-25 01:20:12 +00:00
neon 8c854dada8 feat(metadata): merge fetched genres with existing values
Preserve curated genres while appending unique values returned by metadata
providers, and expose merge versus replace behavior in the web interface.

Closes #1125
2026-08-25 00:41:12 +00:00
neon b264ef1132 fix(series): preserve exact series index labels
Treat series positions as validated string labels so values such as 5.10 retain their exact representation across storage, metadata providers, file formats, and clients.

Closes #1119
2026-08-24 21:40:41 +00:00
neon 5ec34aef23 feat(settings): redesign users, field rules, file naming and libraries
Rebuilds the users roster, metadata field rules, file naming and metadata
score panels, and replaces the libraries list with an expandable ledger.
Libraries now batch their counts and last scan into one request instead of a
per-library fan-out, and surface scan history from scan_jobs on demand.
2026-08-23 23:57:17 +00:00
neon a3bc499028 feat(library): filter dashboards by selected libraries
Let each user choose which accessible libraries feed dashboard shelves and reading widgets while preserving access checks and Smart Scope filters.

Closes #1113
2026-08-23 15:15:18 +00:00
neon 7668f46c42 feat(reader): let readers pick the font style for body text
Read variable-font axes and named instances so readers can select real weight and style variants instead of synthesized faces. Preserve the axis default, accept the full CSS weight range, and keep saved choices stable while font catalogs load.

Require the new settings fields for complete EPUB defaults, keep per-book patches partial, and widen the persisted font-weight constraints to match CSS.

Closes #533
2026-08-22 06:16:56 +00:00