mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
7668f46c42bba6d9f91fd8fada2774e50b5ff876
687 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
3a095efcbb |
fix(books): let manual reading dates drive the reading lifecycle
Editing Date Started or Date Finished sent a date-only patch that the attempt layer had nothing to attach it to. On an unread book no attempt was created, so the request returned 200 with null dates and the field cleared itself. On a book being read the date landed on the open attempt as an end date with no outcome, violating reading_attempts_closed_has_outcome_chk and surfacing as a 500. Treat a date-only patch as a lifecycle statement: a start date opens an attempt and projects reading, a finish date closes one as completed and projects read, and endedOn is never written without its outcome. Clearing a finish date keeps the completion, because an outcome with no completion date is legal and is already what re-read seeding writes. Setting a finish date on an abandoned book now completes it. Reject a status that contradicts its dates with a stable error code the client can translate, and derive statusChanged from the resulting status so a date-driven transition still reaches achievements and the Kobo projection. Keep unread and want_to_read free of dates they cannot own. The attempt keeps its own, so a book marked unread no longer projects a start and finish date once a historical attempt is logged, and marking a finished book unread no longer strands its finish date on the projection. Stop the Hardcover import sending dates a status cannot carry. A want-to-read row that still holds a start date would otherwise fail its row and skip the status import entirely. Closes #1073 |
||
|
|
cbd5f56778 |
fix(metadata): stop hardcover sync colliding on reading attempt read ids
Read selection was positional: prefer any open remote read, else a cached book-level id, else the first read returned, while the primary attempt was re-derived each sync from book dates. The two could disagree, assigning one Hardcover read to two attempts and violating reading_attempts_external_uidx, which aborted the sync and persisted the failure. Treat reading_attempts.external_id as the mapping of record: an attempt keeps the read it owns, an unlinked attempt adopts only an unclaimed read and claims it locally before the remote write, and a fresh read is minted when nothing is adoptable. Never merge or delete attempts to resolve a conflict, and keep soft-deleted ids reserved so importExternalRead tombstones still hold. Also skip the link write when an attempt already owns its read, and map the aggregated attempt timestamp to a Date so change detection stops comparing a driver string against lastSyncedAt. Closes #907 |
||
|
|
9d116c3e97 |
fix(auth): surface private OIDC issuer guidance
Return a stable error code for private issuer rejections and translate it in settings so administrators can apply the required production override. Closes #1065 |
||
|
|
1e5fcedb2a |
fix(koreader): stop a rebuilt library silently dropping reading history
Deleting a library cascades away its books, files, page stats and reading sessions, and a rescan mints new rows. The device's sync state is keyed by the file's partial md5, so an unchanged file kept its cursors across that swap: the plugin asked only for events newer than a watermark earned against rows the server no longer had, and nothing recorded before the swap was ever resent. "Recheck all book matches" did not help, because it funnels back through setMatched. Treat a changed book file id or book id as a fresh sync target and keep only the local file path. Both ids matter: statistics hang off the file row while highlights, status, rating and review hang off the book row, and an existing-folder merge re-parents a file to another book without changing its id. Resending costs nothing because the page-stat dedup key makes a replay a server-side no-op. Order both hash resolvers by book file id so content that exists as two files always resolves to the same row. They previously took whichever row Postgres returned first, so they could disagree with each other and, with the reset above, move a device's sync target under it. Closes #1049 |
||
|
|
93e7c7b6b7 |
fix(metadata): stop comicvine searches timing out into no results
Every comicvine search probed up to eight candidate volumes one request at a time, each holding a one second rate limiter slot against an endpoint that answers in three to four seconds, so the fifteen second provider budget ran out before the right volume was reached and the search reported an empty result. Rank volumes on the name and issue count already carried by the volume search response, then probe them in concurrent waves, so the runs that can hold the issue are tried first and within budget. Bind the work to a soft deadline that aborts requests still in flight. Reaching it now yields the candidates already found, rather than letting a late detail lookup cross the hard timeout and take everything with it. A probe or detail lookup that fails or is throttled costs its own result only, never its siblings or the candidate it was enriching. Take the series name from a query the user actually typed instead of the stored one, or searching a comic for a different series silently returned the series it was already filed under. Strip the zero padding comic filenames add to issue numbers in the provider rather than only in the search controller, so the background refresh path benefits too. Report a provider that timed out, was throttled, or failed as its own stream event, since all three were indistinguishable from a genuinely empty result. Candidates keep the default event and an unchanged payload, so a client has to read the event name to tell the two apart. Fixes #1039 |
||
|
|
b1ba3a03e7 |
fix(koreader): stop a stale device position undoing a progress reset
Clearing a book's progress deleted the shared row and left the KOReader device row behind, so the next pull served the deleted position straight back. Clearing that row too is not enough either: an empty response reads as "this server knows nothing about your book", which is the one reply that leaves a device certain its own sidecar is the only position there is, and it pushes that back on the next sync. Record the reset as a durable fact and answer the pull with a real start position attributed to BookOrbit. Route the position by format, because KOReader's own kosync plugin feeds it straight to GotoPage or GotoXPointer with no percentage fallback and an empty value leaves the reader where it was. Stamp each delivery as current, because both clients compare that against their last page turn and default backward syncs to disabled, so the original reset time would be dropped on any pull deferred past a page turn without ever prompting. Serving a reset proves nothing, since either client can classify it as a backward sync and drop it silently, so the marker survives delivery and a device is held until its own push reports the start. Retire it once a device that took the reset reads on: the pull carries no device identity, so a marker kept alive past its purpose is answered to every device and would send that one back to the start on every sync. The bound this leaves is deliberate. A second device that never took the reset is protected only until the first reads on, and closing that needs device_id on the pull, which is a plugin change. Judge convergence on the reported position rather than the percentage, so page 1 of a hundred page comic is not mistaken for a position no short book could ever reach. Wind Kobo bookmarks back through the same paths, since a Kobo device otherwise resumes from its own bookmark and reports it back the same way. Closes #1032 |
||
|
|
d06e29a889 |
fix(stats): show manually logged progress in the reading log ring
The ring read only reading_progress, the stored reading position, which nothing writes except the web reader, Kobo sync and KOReader sync. Manual sessions record endProgress on the session row and touch nothing else, so a book whose whole history was entered by hand sat at 0% no matter how far the sessions ran, and marking it read did not help either. Surface the log's own view of progress instead of writing a reading position from a manual session: that path also drives auto read-status updates, achievement events and Hardcover and StoryGraph sync, and would store a position with no CFI for the reader to restore. Take the most recent session that recorded an endProgress rather than the highest, so a reread still reads correctly once its first session lands. Read it outside the list date and format filters, otherwise narrowing the log would drag the ring down with it. The client folds the value into the max it already computes across ebook and audiobook positions, which also lets the ring update when a session is added or deleted. Closes #1030 |
||
|
|
1199e3cdd4 |
fix(metadata): stop one book record being assembled from several books
Field rules resolve each field from whichever provider ranks first for it, and nothing ever compared candidates to one another. A provider that matched the wrong book still won every field it led on, so a single record could carry a title from one book, a cover from a second and a subtitle from a third. The stock defaults make that the ordinary case rather than an unlucky one, since cover leads with Amazon while title and subtitle lead with Goodreads. Relevance scoring is what let those wrong matches in. A candidate survived on any score above zero, and token overlap counted stopwords, so "The Girl on the Train" scored against a query for "The Hobbit" on the strength of a shared "the". Substring matching ignored word boundaries as well, which is how "Ubik" matched "Rubik Cube Solutions". Drop stopwords from the query side, require a majority of the words that remain, match whole words only, and give the surviving floor a name. Then hold every candidate to an anchor before field rules ever see it. The anchor is the edition confirmed by the book's own ISBN when there is one, otherwise the candidate the query supports most strongly, with the title rule breaking ties because that is where the reader declares which provider decides identity. Agreement admits subtitle extensions, reordered words and translated editions, and rejects on a contradicting author, which is the only signal that separates a book from a companion volume that extends its title. Reject solely on positive evidence: a candidate with no title, or with no author to compare against, keeps the benefit of the doubt, and a lone provider is never second-guessed. Rejected candidates no longer contribute their provider ids either, which would otherwise pin the wrong book for every later refresh. Normalizing titles to [a-z0-9] emptied every title written in Japanese, Chinese, Russian or Greek. That scored two providers reporting an identical title as a disagreement, which under the new anchor would drop all but one of them in those libraries, and it had already denied the relevance gate any way to score them at all. Decompose instead, strip nonspacing marks so Soseki matches Sōseki, and keep letters of any script. Two sets of fixtures described different books per provider while testing something else entirely, field-rule precedence and metadata locking, and now describe one book so that they still test what they were written for. Closes #1023 |
||
|
|
42f175f1ef |
feat(authors): add Goodreads as an author metadata source
Audnexus was the only author provider, and it returns just an ASIN, name, description and image. Goodreads carries a far richer author record, so add it as a second provider and let each field choose where it comes from. The Goodreads API was retired in 2020, so the provider scrapes: a name resolves to an author id through the WAF-free /book/auto_complete endpoint, falling back to the author search page for names that only differ by a middle initial, and the author page yields the biography, photo, birth and death dates, website, genres and influences. Two rendering quirks are worth knowing about, since both silently return the wrong value: free-text fields appear twice, truncated and whole, so the parser takes whichever copy carries the most content; and the birthplace sits in a bare text node while only the date is wrapped in a dataItem, so authors with a place but no date correctly store nothing. Preferences are now per field, mirroring the book-side FieldPreference model, with an ordered provider list and a merge strategy each. They are global, because an author row is shared across every library and cannot hold a different biography per library. Providers declare which fields they can return and both the resolver and the settings UI filter on that, so Audnexus can never be assigned to a field it has no way to fill. The single global write mode is migrated into each field's merge strategy on first read, keeping an existing install's overwrite behaviour instead of silently adopting the default. The executor now queries every listed provider and resolves each field independently, first non-empty value winning, and records which provider supplied the record so a re-run can pin the match instead of searching by name again. overwriteIfProvided skips empty values, which is what lets a matched author with an empty Audnexus biography fall through to Goodreads. |
||
|
|
eba1640df4 |
fix(kobo): acknowledge device state pushes so hub progress reaches the reader
PUT /v1/library/{id}/state answered with the stored ReadingState instead of the
RequestResult/UpdateResults envelope the device expects. The device leaves an
unacknowledged push pending and treats its own copy as authoritative, so it
re-pushes every sync and opens the book at its local bookmark no matter what the
pull path delivered. That is why a first download honours the hub position while
an already-synced book only picks up the percentage.
Three smaller defects produced the same reversion on their own:
Hub progress kept the device's previous CurrentBookmark.Location when no KoboSpan
described the new position, shipping a bookmark whose position contradicted its own
percent. Drop the Location instead; the pull path re-adds one whenever it can
convert the cfi.
Both hub-side writers stamped last_modified_kobo, priority_timestamp and the
bookmark at wall-clock time while device timestamps are stored verbatim. A Kobo
clock running ahead therefore won every conflict. Stamp strictly past whatever is
stored, mirroring what upsertState already did for the envelope.
A conversion that gave up was invisible: getRawState swallowed throws and the
converter logged reasons at debug level, so an operator could not tell a healthy
bookmark from one that silently degraded to percent-only.
Closes #749
|
||
|
|
ec4072e4be |
feat(server): add Audiobookshelf and Calibre-Web Automated migration sources
Extend the migration module beyond Booklore and Grimmory so an existing Audiobookshelf or Calibre-Web Automated library can be moved into BookOrbit without rebuilding user state by hand. Audiobookshelf supports both a live API connection and an offline backup file, while Calibre-Web Automated reads a stopped snapshot of the app and metadata databases. Match source books by ASIN in addition to ISBN, and batch the ISBN, ASIN, file hash, file path, and title/author lookups so planning a large library issues a bounded number of queries rather than one per book. Route the batched author comparison through the shared accent-insensitive predicate, which now accepts a SQL fragment so a per-row pattern column is matched the same way a bound literal is. Extract the shared source connection form into MigrationSourceFields so the modal and the settings page stay in sync as source types are added, and allow a mapping to skip a source user instead of forcing every account to be imported. Closes #986 |
||
|
|
0d9527c048 |
build(deps): upgrade chokidar, pinia, and jsdom majors and drop unused deps
Move chokidar to 5, pinia to 4, and jsdom to 30 with a matching @types/jsdom, and align packages/types on TypeScript 6 with the other workspaces. chokidar 5 is ESM-only while the server compiles to CommonJS. Node's require(ESM) support covers this, verified against a live watcher and a nest watch build rather than the mocked unit tests. Remove @fastify/formbody and @tanstack/vue-table, neither of which had a single caller, along with the stale @tanstack/vue-table entry in the Vite optimizeDeps list. Hold TypeScript at 6. typescript-eslint refuses to install alongside 7 and vue-tsc cannot drive the native checker until the stable compiler API lands in 7.1. Ignore pnpm-lock.yaml in prettier so lint-staged stops rewriting it out of pnpm's native style, which was inflating every lockfile diff to roughly 19k lines. |
||
|
|
2faa32c09d |
fix(metadata): bound provider ids and surface why a save was rejected
A provider identifier parsed out of an OPF was passed on verbatim, so a Calibre `opf:scheme="AMAZON"` holding a product URL reached a varchar(20) column as a 36-character value. It could not be stored, and the next manual save of that book was rejected for a field the user never typed into. Drop an identifier that cannot fit its column rather than truncating it: an overflowing value is not a long identifier, it is a different kind of value, and its first 20 characters are not an ASIN either. Enforce the same bound where every extractor converges, so no parser can fail a metadata write with a Postgres 22001. Keep the server's description of a rejected save instead of discarding the response body. Only that text names the field at fault, so the editor showed a bare "HTTP 400" and left the reason in the network tab. It stays untranslated, so it is preferred over the catalog message rather than replacing it, and the failure is carried as a structured value so the component still translates everything else. Tie the DTO limits to the column widths they were copied from, and guard the pair with a test, since nothing connected them when they drifted apart. Stand Node's built-in web storage down in the client test run. Node 26 defines localStorage on globalThis, jsdom shares that object, and its getter reads as undefined without --localstorage-file, which failed 229 tests across 28 unrelated suites on CI. Closes #1015 |
||
|
|
18d4d6cfc2 |
fix(server): match entity names that carry non-collapsed whitespace
Author, narrator, genre and tag names written before whitespace normalization existed can hold a non-breaking space, tab, newline or doubled space. HTML collapses all of those, so the name renders correctly in the UI while the ILIKE search predicate never matches it; unaccent() does not touch whitespace either. Re-entering an identical name by hand fixed a single book because the write path canonicalizes the row it reuses, but nothing ever ran for untouched books. Collapse stored names to the form normalizeMetadataTextSql() produces, merging rows that collide once collapsed and remapping every junction and content-filter reference before a duplicate is dropped. Case variants are left alone, since search is already case-insensitive. Close the two write paths that still produced un-collapsed names, and normalize the typed search term so a pasted non-breaking space still matches. Also carries staged client appearance work: the surface brightness picker and related controls. Closes #1014 |
||
|
|
7b43756258 |
fix(stats): track last read separately from progress row updates
Sorting by "Last Read" ordered on reading_progress.updated_at, but the KOReader sync path deliberately freezes that column so it stays a reliable "last local write" marker when resolving conflicts against device progress. Readers who sync from KOReader therefore kept the timestamp of their first ever sync of each book forever, so the smart scope sort, the jump-bucket rails and the dashboard Continue Reading list all looked unordered. Add reading_progress.last_read_at, which advances on every write unless a path opts out, and move every "last read" reader onto it. Leave updated_at to the conflict-resolution and progress-resume paths that genuinely mean "last local write". Kobo bookmark-location stamping opts out because recording which Location a bookmark maps to is bookkeeping rather than reading, while the Kobo progress write and the migration importer pass their source timestamps through so imported history is not stamped as read today. Move the ebook-versus-audiobook recency tiebreaks onto the new column as well. They compared a frozen timestamp against a live one, which selected the wrong percentage for the same reason. Swap the (user_id, updated_at) index for (user_id, last_read_at), since the ordering consumer that justified it moved. Backfill last_read_at from updated_at in the migration, as the generated DEFAULT now() would otherwise make every existing book look as though it had just been read. Fixes #1013 |
||
|
|
d7556011df |
build(deps): upgrade Node.js to 26 and pnpm to 11
Update package manager configuration to pnpm 11.22.0 and Node.js requirement to Node 26 across Dockerfile, GitHub Actions workflows, root package.json, and workspace configurations. Upgrade dependencies, update GitHub Action versions, and remove deprecated scorecard workflow. |
||
|
|
e2aeaf95ea |
fix(scanner): resolve case-insensitive path identity to prevent duplicate books
When library items are scanned or moved on case-insensitive filesystems, paths with non-standard author or folder capitalization point to the same physical disk entries. Treating previous path spellings as distinct caused scanner, upload, dock, and rename services to fail path-reuse lookups and create duplicate book records. Introduce path identity helpers to check whether paths resolve to the same underlying filesystem entry, treating matching entries as path updates rather than creating duplicate books. Closes #1012 |
||
|
|
f49541cb20 |
style(client): refine app shell actions
Move GitHub actions into the sidebar footer and tighten header grouping. |
||
|
|
0816afb2b3 |
security(kobo): rebuild kepubify with go 1.26.6
Use the patched Go standard library to address CVE-2026-39821 and CVE-2026-46600 in the bundled binaries. Keep the reported v4-dev version unchanged so existing rendition hashes remain stable. |
||
|
|
6ab4329223 |
fix(metadata): preserve authors during metadata refresh
Filter provider contributors by author role and treat empty values as absent for non-destructive merge strategies while preserving explicit overwrite clears. Fixes #1001 |
||
|
|
5d55b8aa0d |
fix(stats): evaluate achievements for updated KOReader sessions
KOReader page-stat uploads can grow an existing derived session as more page events arrive, but only newly inserted sessions reached achievement evaluation. A session that crossed a threshold by being updated showed progress past that threshold while the badge stayed locked. Carry the updated sessions out of the repository instead of a bare count and emit for them too. The oversized-upload branch emitted a backfill event that nothing listened for, so subscribe achievement evaluation to it as well; those runs are serialized per user and a burst collapses into one trailing re-run, since each backfill re-evaluates the whole catalogue. Awards from that branch stay silent: an upload that large is a device catching up, and notifying per badge would burst a toast for every historical unlock. Fixes #991 |
||
|
|
0480242a98 |
feat(koreader): retire devices without deleting their synced data
Removing a KOReader device was the only way to take a decommissioned reader off the list, and it permanently deleted that device's page-by-page reading events, per-device progress, sweep record, naming pattern and sole-source unmatched books. Derived reading sessions survived, but the raw events did not, and the plugin keeps its upload watermark on the device, so nothing ever re-uploaded them. Add retirement as a marker table rather than a device registry. Device identity is implicit, spread across five device-keyed tables and surfaced by a DISTINCT ON over device progress, so there is no row to carry a flag and nothing to backfill. Retiring records only (user, device); every byte the device synced stays where it is. Clear the marker whenever a retired device syncs again, on single-book progress, bulk sweeps, page-stat uploads and sweep completion, so reading never accrues against a device the user cannot see. Keep deleting synced data as a separate, explicit action on the retired row, and clear the marker it would otherwise leave behind. Move the confirm copy to new message keys instead of reusing the ones whose meaning changed, so the sparse target catalogs cannot ship a stale translation of a destructive dialog. Closes #996 |
||
|
|
53be54752d |
fix(library): run scans on the stored auto-scan schedule
The auto-scan cron expression was validated, persisted and shown in the UI, but nothing ever consumed it, so a library configured with a schedule never scanned and no scan job was ever recorded with the schedule trigger. Register a cron job per configured library on boot and keep it in step as libraries are created, updated and deleted. An occurrence that lands while a scan is already running is skipped rather than retried, and a failing occurrence leaves the job registered so later ones still fire. Schedules run in the server timezone, matching the cleanup, audit and statistics jobs, so a deployment that sets TZ gets the wall-clock time its users expect. Move cron validation into a single shared grammar used by both the API and the schedule editor. The two sides previously disagreed in both directions: the UI accepted step-zero and reversed ranges that the API rejected, and rejected day names the API accepted. The grammar is a strict subset of what the scheduler and the human-readable preview can each parse, so an expression the editor marks valid can always be scheduled. Closes #985 |
||
|
|
34e09fa655 |
chore(deps): bump the production-dependencies group with 41 updates (#1009)
* chore(deps): bump the production-dependencies group with 41 updates Bumps the production-dependencies group with 41 updates: | Package | From | To | | --- | --- | --- | | [@embedpdf/engines](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/engines) | `2.14.4` | `2.15.0` | | [@embedpdf/models](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/models) | `2.14.4` | `2.15.0` | | [@embedpdf/pdfium](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/pdfium) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-annotation](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-annotation) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-bookmark](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-bookmark) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-document-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-document-manager) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-history](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-history) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-interaction-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-interaction-manager) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-pan](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-pan) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-render](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-render) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-rotate](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-rotate) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-search](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-search) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-selection](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-selection) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-spread](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-spread) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-thumbnail](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-thumbnail) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-tiling](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-tiling) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-viewport](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-viewport) | `2.14.4` | `2.15.0` | | [@embedpdf/plugin-zoom](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-zoom) | `2.14.4` | `2.15.0` | | [@formatjs/icu-messageformat-parser](https://github.com/formatjs/formatjs/tree/HEAD/packages/icu-messageformat-parser) | `3.5.15` | `3.5.16` | | [@lucide/vue](https://github.com/lucide-icons/lucide/tree/HEAD/packages/vue) | `1.27.0` | `1.31.0` | | [@tanstack/vue-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/vue-virtual) | `3.13.34` | `3.13.35` | | [@tiptap/extension-link](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/extension-link) | `3.29.1` | `3.29.2` | | [@tiptap/pm](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/pm) | `3.29.1` | `3.29.2` | | [@tiptap/starter-kit](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/starter-kit) | `3.29.1` | `3.29.2` | | [@tiptap/vue-3](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/vue-3) | `3.29.1` | `3.29.2` | | [@vueuse/core](https://github.com/vueuse/vueuse/tree/HEAD/packages/core) | `14.3.0` | `14.4.0` | | [intl-messageformat](https://github.com/formatjs/formatjs) | `11.2.12` | `11.2.13` | | [reka-ui](https://github.com/unovue/reka-ui) | `2.10.1` | `2.10.3` | | [vue](https://github.com/vuejs/core) | `3.5.40` | `3.5.41` | | [@fastify/compress](https://github.com/fastify/fastify-compress) | `9.1.0` | `9.2.0` | | [@fastify/static](https://github.com/fastify/fastify-static) | `10.1.2` | `10.1.3` | | [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) | `11.1.28` | `11.1.29` | | [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core) | `11.1.28` | `11.1.29` | | [@nestjs/platform-fastify](https://github.com/nestjs/nest/tree/HEAD/packages/platform-fastify) | `11.1.28` | `11.1.29` | | [@nestjs/platform-socket.io](https://github.com/nestjs/nest/tree/HEAD/packages/platform-socket.io) | `11.1.28` | `11.1.29` | | [@nestjs/websockets](https://github.com/nestjs/nest/tree/HEAD/packages/websockets) | `11.1.28` | `11.1.29` | | [fastify](https://github.com/fastify/fastify) | `5.10.0` | `5.11.3` | | [jose](https://github.com/panva/jose) | `6.2.4` | `6.2.8` | | [mysql2](https://github.com/sidorares/node-mysql2) | `3.23.2` | `3.23.3` | | [nodemailer](https://github.com/nodemailer/nodemailer) | `9.0.3` | `9.0.5` | | [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.22.0` | `8.23.0` | Updates `@embedpdf/engines` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/engines/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/engines) Updates `@embedpdf/models` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/models/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/models) Updates `@embedpdf/pdfium` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/pdfium/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/pdfium) Updates `@embedpdf/plugin-annotation` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-annotation/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-annotation) Updates `@embedpdf/plugin-bookmark` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-bookmark/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-bookmark) Updates `@embedpdf/plugin-document-manager` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-document-manager/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-document-manager) Updates `@embedpdf/plugin-history` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-history/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-history) Updates `@embedpdf/plugin-interaction-manager` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-interaction-manager/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-interaction-manager) Updates `@embedpdf/plugin-pan` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-pan/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-pan) Updates `@embedpdf/plugin-render` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-render/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-render) Updates `@embedpdf/plugin-rotate` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-rotate/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-rotate) Updates `@embedpdf/plugin-search` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-search/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-search) Updates `@embedpdf/plugin-selection` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-selection/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-selection) Updates `@embedpdf/plugin-spread` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-spread/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-spread) Updates `@embedpdf/plugin-thumbnail` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-thumbnail/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-thumbnail) Updates `@embedpdf/plugin-tiling` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-tiling/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-tiling) Updates `@embedpdf/plugin-viewport` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-viewport/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-viewport) Updates `@embedpdf/plugin-zoom` from 2.14.4 to 2.15.0 - [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases) - [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-zoom/CHANGELOG.md) - [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-zoom) Updates `@formatjs/icu-messageformat-parser` from 3.5.15 to 3.5.16 - [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.16/packages/icu-messageformat-parser) Updates `@lucide/vue` from 1.27.0 to 1.31.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.31.0/packages/vue) Updates `@tanstack/vue-virtual` from 3.13.34 to 3.13.35 - [Release notes](https://github.com/TanStack/virtual/releases) - [Changelog](https://github.com/TanStack/virtual/blob/main/packages/vue-virtual/CHANGELOG.md) - [Commits](https://github.com/TanStack/virtual/commits/@tanstack/vue-virtual@3.13.35/packages/vue-virtual) Updates `@tiptap/extension-link` from 3.29.1 to 3.29.2 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-link/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.2/packages/extension-link) Updates `@tiptap/pm` from 3.29.1 to 3.29.2 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/pm/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.2/packages/pm) Updates `@tiptap/starter-kit` from 3.29.1 to 3.29.2 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/starter-kit/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.2/packages/starter-kit) Updates `@tiptap/vue-3` from 3.29.1 to 3.29.2 - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/main/packages/vue-3/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.29.2/packages/vue-3) Updates `@vueuse/core` from 14.3.0 to 14.4.0 - [Release notes](https://github.com/vueuse/vueuse/releases) - [Commits](https://github.com/vueuse/vueuse/commits/v14.4.0/packages/core) Updates `intl-messageformat` from 11.2.12 to 11.2.13 - [Release notes](https://github.com/formatjs/formatjs/releases) - [Commits](https://github.com/formatjs/formatjs/compare/intl-messageformat@11.2.12...intl-messageformat@11.2.13) Updates `reka-ui` from 2.10.1 to 2.10.3 - [Release notes](https://github.com/unovue/reka-ui/releases) - [Commits](https://github.com/unovue/reka-ui/compare/v2.10.1...v2.10.3) Updates `vue` from 3.5.40 to 3.5.41 - [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.40...v3.5.41) Updates `@fastify/compress` from 9.1.0 to 9.2.0 - [Release notes](https://github.com/fastify/fastify-compress/releases) - [Commits](https://github.com/fastify/fastify-compress/compare/v9.1.0...v9.2.0) Updates `@fastify/static` from 10.1.2 to 10.1.3 - [Release notes](https://github.com/fastify/fastify-static/releases) - [Commits](https://github.com/fastify/fastify-static/commits) Updates `@nestjs/common` from 11.1.28 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/common) Updates `@nestjs/core` from 11.1.28 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/core) Updates `@nestjs/platform-fastify` from 11.1.28 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/platform-fastify) Updates `@nestjs/platform-socket.io` from 11.1.28 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/platform-socket.io) Updates `@nestjs/websockets` from 11.1.28 to 11.1.29 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.29/packages/websockets) Updates `fastify` from 5.10.0 to 5.11.3 - [Release notes](https://github.com/fastify/fastify/releases) - [Commits](https://github.com/fastify/fastify/compare/v5.10.0...v5.11.3) Updates `jose` from 6.2.4 to 6.2.8 - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v6.2.4...v6.2.8) Updates `mysql2` from 3.23.2 to 3.23.3 - [Release notes](https://github.com/sidorares/node-mysql2/releases) - [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md) - [Commits](https://github.com/sidorares/node-mysql2/compare/v3.23.2...v3.23.3) Updates `nodemailer` from 9.0.3 to 9.0.5 - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v9.0.3...v9.0.5) Updates `pg` from 8.22.0 to 8.23.0 - [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg) --- updated-dependencies: - dependency-name: "@embedpdf/engines" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/models" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/pdfium" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-annotation" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-bookmark" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-document-manager" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-history" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-interaction-manager" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-pan" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-render" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-rotate" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-search" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-selection" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-spread" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-thumbnail" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-tiling" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-viewport" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@embedpdf/plugin-zoom" dependency-version: 2.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@formatjs/icu-messageformat-parser" dependency-version: 3.5.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@lucide/vue" dependency-version: 1.31.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@tanstack/vue-virtual" dependency-version: 3.13.35 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@tiptap/extension-link" dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@tiptap/pm" dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@tiptap/starter-kit" dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@tiptap/vue-3" dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@vueuse/core" dependency-version: 14.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: intl-messageformat dependency-version: 11.2.13 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: reka-ui dependency-version: 2.10.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: vue dependency-version: 3.5.41 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@fastify/compress" dependency-version: 9.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: "@fastify/static" dependency-version: 10.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/common" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/core" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/platform-fastify" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/platform-socket.io" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/websockets" dependency-version: 11.1.29 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: fastify dependency-version: 5.11.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: jose dependency-version: 6.2.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: mysql2 dependency-version: 3.23.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: nodemailer dependency-version: 9.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: pg dependency-version: 8.23.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * fix(deps): complete the embedpdf 2.15.0 bump Dependabot left @embedpdf/core and @embedpdf/plugin-scroll at 2.14.4 while moving the other 18 packages to 2.15.0. core@2.14.4 pulls models@2.14.4, so two copies of @embedpdf/models ended up in the tree; PdfTask carries a private _promise, making them structurally incompatible and failing vue-tsc. pnpm.patchedDependencies keys on an exact version, so bumping engines to 2.15.0 orphaned the patch key and silently dropped the wasmError rejection patch, which embedpdf-worker.spec.ts exists to guard. Bump the two stragglers, re-key the utils packageExtension, and rename the three patches to 2.15.0. All three patch bodies apply unchanged. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com> |
||
|
|
26b7c602f8 |
security(settings): gate Book Dock settings behind manage_book_dock
Require manage_book_dock for the settings UI and the scoped API so regular Book Dock users cannot view or modify administrative configuration. Reject every Book Dock key on the generic PATCH /app-settings/:key route as well, including book_dock_paused. That route accepted the same keys with any string value, so an operator with manage_app_settings could store an out-of-range threshold or an unknown metadata mode and bypass the validation the scoped endpoints apply. Closes #1003 |
||
|
|
2b8e1d6a79 |
fix(kobo): stop endless sync retries and cut round trips
Report success when Kobo rejects a delete for a tag it no longer has, so the device stops retrying one it can never complete. Skip the full-library reconcile while a device is still draining pages, and page fifty books at a time so a large re-announce costs a few round trips instead of hundreds. Ref #1005 |
||
|
|
0087164c2e |
feat(scanner): rebuild the Book Dock page as a uniform-row triage list
Replace the nine-column table with one list of uniform rows that reflows through a container query, so a single component serves both desktop and phone. Surface the match score, applied state and write path that the old row buried in tertiary text. Back it with a needsReview filter and attention sort over existing indexed columns, threaded through every bulk selection path so select-all cannot target the wrong rows. Keep confidence advisory: it gates automatic finalization only. BookDockSummary gains three additive counts; ../bookorbit-ios mirrors these types by hand. |
||
|
|
867295cb63 |
fix(metadata): stop iTunes returning audiobook editions for ebooks
The search params used one flag for two questions: which edition a provider should return, and whether audiobook-only providers may run. Every caller that wanted the second forced the flag true, so iTunes asked for entity=audiobook whenever Audible, AudNexus or Libro.fm was in the provider set. Split the two, and derive the media type from the book's primary file so a record holding both an ebook and an audiobook no longer searches as an audiobook in one drawer and an ebook in the other. Closes #982 |
||
|
|
7afc128bcb |
feat(cover): let users pick the default provider for cover search
Cover search always opened on DuckDuckGo, so anyone who prefers another source had to switch it by hand for every book. Store the choice per user and apply it when the drawer opens. Cache the value client-side so the drawer never waits on a request to become usable, and save it to the account rather than the device so it reaches the iOS client too. AudiobookCovers is not offered as a default because it only returns results for audiobooks. Closes #983 |
||
|
|
37c3f3acd2 |
feat(collections): keep books in the order they were added
Collections opened in title order, so a curated shelf came back rearranged. Add a collectionOrder sort field, make it the default for collection views, and offer it from a sort control that grid and list previously did not have. Back the ordering with a new collection_books.position sequence rather than added_at: added_at defaults to now(), the transaction timestamp, so every book added in one request shared it and the order fell back to books.id. Kobo collection paging had the same tie and now orders by position. Also stop useBookWindow blanking its slots and total on every query change. Clearing them collapsed the virtual list to zero height and back, which read as a flash on any sort, search, or filter change. Closes #978 |
||
|
|
ed0964a50c |
feat(library): add {library} placeholder to file naming patterns (#976)
Resolve {library} to the name of the library a book belongs to, so a
single KOReader device pattern can keep each library in its own subtree
on the device instead of merging every library into one tree.
The token is populated in every server-side pattern surface: KOReader
manifest and book detail device paths, upload, automatic and bulk
rename, book move (target library), download filenames, and BookDock
finalize.
|
||
|
|
7dcc509a54 |
feat(client): add nine interface languages and a searchable language picker
Traditional Chinese cannot reuse the `%two_letters_code%` filename because it would collide with Simplified under `zh.json`, so `crowdin.yml` maps `zh-TW` to a `zh-Hant` catalog and locale matching resolves by script subtag before falling back to the base language. Every other new locale keeps its bare two-letter ID. Twenty-five entries outgrew the old menus. The mobile submenu inherited `overflow-hidden` with no max-height, so the last nine languages were unreachable on a phone, and the list was ordered by ISO code while displaying endonyms, which reads as unsorted. Both header menus now render one LanguagePicker that searches the native name, the English name and the locale code, groups the browser's own languages as suggestions, and orders the rest with a collator on the string it actually shows. Mobile opens a sheet instead of a nested flyout, since a flyout is the wrong shape for touch regardless of height. Settings gains the Appearance language section its copy has anticipated since the keys were added. Tighten the Crowdin progress chart, which the longer language list had stretched to 810px tall. |
||
|
|
208d04d218 |
fix(opds): advertise search inline for clients that skip OpenSearch
Moon+ Reader and CrossPoint never fetch the OpenSearch description document, so they
detected no search capability and hid the search affordance entirely. Both look only for a
rel="search" link whose href already carries {searchTerms}, and they match the link type by
equality rather than by substring.
Emit that templated link alongside the existing OpenSearch pointer, keeping the pointer
first so KOReader's has_opensearch guard still wins and its behaviour is unchanged. The
type stays bare application/atom+xml and the href stays relative like every other href in
the feed, so nothing has to reconstruct an external base URL behind a reverse proxy.
Calibre-Web ships the same two-link pairing.
Add the pair to the libraries, collections, smart-scopes, authors and series feeds too.
Those carried only a self link, so search disappeared once a client descended into a
sub-folder even when it did support OpenSearch.
Closes #860
|
||
|
|
2d09c37806 |
feat(kobo): deliver Kobo store titles and fix store cover 404s
Kobo sync replaces the store API for the device but only ever answered for BookOrbit's own books, so anything owned through Kobo fell through the gaps. Library sync never queried Kobo, so Kobo Plus and purchased titles had no route to the device at all. Add an opt-in per-user store sync that pulls the device's upstream entitlements with the device's own Kobo credentials, which still reach us because initialization leaves device_auth and device_refresh pointing at Kobo, and appends them to BookOrbit's own. Both cursors ride in one composite sync token, so no server-side state is needed and tokens minted before this change keep working. The store is only consulted once BookOrbit has drained its own pages, and paging stops if Kobo's cursor fails to advance. Everything on the store path stays inside one guarantee: reading the setting, or a store that is slow, down, unauthorized or answering with a body we cannot use, costs the store page and never the BookOrbit half of the sync. Initialization also rewrites the image templates to point at BookOrbit, so the device asked us for store covers too. Those were forwarded to storeapi.kobo.com, which does not serve images, and 404d. Redirect unresolved cover ids to Kobo's image CDN instead. This is not gated on store sync, since browsing the store shows those covers whether or not the setting is on. Deliberately left alone: annotation pulls still 404 for content that is not a BookOrbit book. The device reconciles annotations by omission, so answering with an empty set would wipe its local highlights. Closes #789 |
||
|
|
80223b5e67 |
fix(koreader): accept optional KOSync document metadata (#966)
* fix(koreader): accept optional KOSync document metadata * fix(koreader): reject null progress metadata |
||
|
|
2b215141a9 |
feat(metadata): limit fetched genres per book
Keep provider metadata from creating overly granular genre catalogs while preserving unlimited behavior by default. Closes #710 |
||
|
|
e56d253dc1 |
fix(library): exclude missing books from library counts
Align library list totals with statistics by counting only present records while preserving empty libraries and user content filters. Fixes #639 |
||
|
|
9e1eb6119a |
feat(client): add configurable multi-row dashboard shelves
A shelf rendered a single horizontal row, which wastes space on wide screens and shows very few books on narrow ones. Each shelf now carries a row count of 1 to 3, set per shelf in the dashboard settings drawer. Books fill bands left to right rather than filling each column top to bottom. The cheaper grid-auto-flow approach would put the second most recent book directly under the first, which reads wrong on ordered shelves like Continue Reading and Up Next in Series. Below the sm breakpoint the row count is capped at two, since only two covers fit across and a third row costs a screen height to gain two books. A shelf fetches limit * rows books, clamped to the limit the server already accepts, so no server behaviour changes. DASHBOARD_SCROLLER_MAX_LIMIT moves into the shared types package so both sides read one value instead of drifting apart. The row count is part of the shelf key because a shelf snapshots its fetch limit during setup; without it, switching to three rows would re-flow the books fetched for one. Also widen the settings drawer, which was pinned at 384px because SheetContent's own sm:max-w-sm outranked the plain max-w override in tailwind-merge, put Shelves ahead of Widgets, and move the customize control out of the floating layer into the greeting row where it is not missed. Closes #928 |
||
|
|
7299664f64 |
feat(reader): allow smaller EPUB text sizes
Permit readers to reduce reflowable book text to 6px while keeping validation and controls aligned across shared types, server, and web. Closes #784 |
||
|
|
ca3c637271 |
fix(auth): split Book Dock access from Book Dock administration
Book Dock was a single permission that the standard preset handed to every user, so an ordinary account could pause processing, trigger a rescan, and finalize straight into the library. Keep book_dock_access for personal upload and review, and move global control behind a new manage_book_dock permission in the administration group. Finalization now also requires library_upload, since it writes into a library. Per-file reads and mutations are scoped to the uploader unless the caller can manage the dock, which closes the gap that let any Book Dock user read, edit, or discard another user's entry by id. The summary websocket now announces a change instead of broadcasting global counts, so each client refetches its own scoped totals. Closes #961 |
||
|
|
68fae9a121 |
fix(reader): route KOReader progress by format so paged books resume
KOReader reports a paged document's position as a page number and a reflowable one's as an xpointer, but applySharedProgress treated every incoming position as an xpointer and upsertReadingProgress hardcoded pageNumber to null on every write. A PDF synced from a device therefore stored a correct percentage with no page, and the web reader reopened it at page 1. The same write also destroyed a page the web reader had saved earlier, including during an unattended bulk sweep, so this lost positions that were never synced from a device at all. Carry the book file's format through hash resolution and pick the column from it. CBZ/CBR were affected identically at the data level but masked in the UI, because the comic reader estimates a page from the percentage when none is stored. Closes #954 |
||
|
|
e7039e93a0 |
feat(auth): add self-registration toggle and sign-up page
Wire the existing allow_registration setting to an admin toggle in the Users tab and a public /register page, so the documented self-registration flow is reachable from the UI instead of the API alone. Expose the flag on the public setup-status endpoint the router guard already calls, so the sign-in page can offer sign-up without an extra request. Harden the newly public path: return a conflict instead of a 500 when concurrent signups race the unique index, sanitize the logged username, and reject control, zero-width, and text-direction characters in the username and display name so a new account cannot impersonate another in the user list or audit log. Closes #952 |
||
|
|
2e8f882f4d |
fix(annotations): resolve html entities when indexing chapter text
Chapters are parsed in XML mode, which resolves only the five predefined XML entities and numeric character references. EPUB2 XHTML may use any named HTML entity through its DTD, so ` ` and its kin were indexed as literal text while kepubify wrote the decoded character. The two text indexes then never matched, which disabled structural resolution for every annotation in the chapter and failed each one with kepub_text_mismatch, even though the Kobo position was exact. Resolve the references on the raw markup before parsing, where an escaped `&nbsp;` is still distinguishable from a real reference, and read each one back through an attribute value so an unknown reference is never partially matched against a legacy entity. Rebuild canonical positions from the stored kobo_span position on position retry as well. Retry only ran the cfi backfill, which derives cfi from an xpointer and therefore had no candidate when the xpointer failed too, leaving an affected annotation pending forever with no way to recover from the UI. Route the rebuild through a registry that the Kobo module registers into, since it already imports the annotation module and the dependency cannot run the other way. Fixes #944 |
||
|
|
6769bf4b0f |
fix(auth): make account lockout recoverable and visible
The lockout was checked before the password was verified, so a correct password on a locked account returned the same generic invalid-credentials error as a wrong one. Users had no way to tell the two apart, and the natural response was to keep retrying a password that was already right. Completing a password reset did not clear the lock either, so the one recovery path the UI advertises appeared to fail for up to fifteen minutes after a successful reset. Verify the password first and report the lockout, with the remaining wait, only when the supplied password was correct. Someone guessing wrong passwords still gets the generic failure, so account existence is not leaked. Clear the lock whenever a password is reset or changed, and give admins an explicit unlock action for the case where waiting is not acceptable. |
||
|
|
9a349311cf |
feat(metadata): add hardcover edition selection and refresh pinning (#936)
* feat(metadata): add hardcover edition selection and refresh pinning Mirrors the StoryGraph edition picker for Hardcover, but backed by real GraphQL edition data instead of scraping. An edition can now be chosen either from the Hardcover settings page's new "Linked books" section or from the metadata downloader/refresh flow, and once chosen it pins future automatic metadata refreshes to that exact edition instead of silently re-deriving one by ISBN every time. book_metadata.hardcover_edition_id is shared across all users, while hardcover_book_state.hardcover_edition_id is scoped per (user, book). Cross-writing between them is one-directional and non-destructive per flow: the settings page always updates the acting user's own sync state and only back-fills the shared field if it is empty; the metadata downloader always updates the shared field and only updates the acting user's own sync state if they already have one. The propagation uses the existing achievement event bus rather than a direct module import, since HardcoverModule already depends on BookModule. * fix(metadata): address hardcover edition selection review findings Responds to CodeRabbit review on PR #936: - Surface Hardcover edition-lookup failures as errors instead of an empty list, so a provider outage cannot be mistaken for "no editions". Updates the client to throw and the UI to show a retryable error state. - Validate that a manually-picked edition id actually belongs to the book's matched Hardcover book before persisting it, rejecting mismatches with a 400 instead of trusting an unvalidated client input. - Require an exact safe-integer string before parsing a propagated hardcoverEditionId, so a value like "200abc" cannot be silently truncated into a valid-looking edition id. - Add durationMs to the edition-propagation failure log per the project's logging conventions. - Honor a pinned edition in the ISBN search() path too, not only in lookupById(), so a refresh that falls back to search still returns the previously chosen edition when it's present. * test(metadata): strengthen hardcover edition-pin fallback assertions The fallback tests for search() and lookupById() only used a single-edition fixture, so they would have passed even if the edition-pin filtering never ran. Use two-edition fixtures and assert on which edition is actually selected instead. * i18n(hardcover): use i18n keys for Linked books strings HardcoverLinkedBooks.vue hardcoded its UI text instead of using useI18n like its sibling components, breaking the ability to translate the Hardcover settings page and non-English catalogs. * fix(hardcover): harden edition selection and bound the linked-book list Validate a picked edition against the book it belongs to rather than against a capped, unordered list. Editions past the cap stay selectable, and two calls can no longer disagree about which window they saw. - Order the edition query by id and fetch one row past the cap, so truncation is detectable instead of silent. Verified against the live Hardcover API: both "The Name of the Wind" and "Pride and Prejudice" exceed the 50 cap. - Report truncation for the linked-book list and the edition list, and say so in the UI rather than presenting a partial list as complete. - Bound and order the linked-book query. It backed a settings list with the unbounded sync query; sync callers keep their previous unordered behaviour. - Throw for "not connected" and "not matched" instead of returning an empty list, which the UI could not tell apart from a genuinely empty catalogue. - Drop release_year from the edition query. Hardcover never populates it, so it was dead weight in the payload. - Add a tuned --success token. The status pill hardcoded text-green-600, which is untuned in dark mode; the light value is set for 4.5:1 on card and page. - Expose row expansion with aria-expanded and aria-controls, announce load failures through a live region, and keep the edition list open after a switch so the refreshed marker lands where the user is looking. --------- Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com> |
||
|
|
54cb7dbda0 |
fix(auth): recover live sessions when an access token expires
A tab that slept past the 15 minute access-token lifetime woke up holding a token every consumer then handed to the server independently. socket.io reconnected all seven namespaces with it, each gateway rejected the handshake and closed the socket, and because a server-initiated close reports `io server disconnect` the client never reconnects from it, so live updates stayed dead until the page was reloaded. The dashboard widgets fetch once on mount with no retry, so whatever failed in that window sat on "Failed to load" for the life of the page. Make a valid token the thing consumers ask for rather than something they hope is still good. Decode `exp` on the client and renew inside a 30 second skew before the token is used, so websocket handshakes, uploads and `api()` requests no longer discover the expiry one 401 at a time. Route every authenticated namespace through a shared factory that resolves the token per handshake and, on an auth rejection, refreshes and reconnects with bounded exponential backoff. Let the server say which rejections are worth retrying. Gateways now answer an authentication failure with an `unauthorized` event before closing, and stay silent on a permission denial, which a fresh token would not fix. Classifying that needed the gateways to stop throwing bare `Error` for both cases, so the handshake paths now throw `UnauthorizedException` or `ForbiddenException` as the project's conventions already require, and jsonwebtoken's own `TokenExpiredError` counts as an authentication failure since it is the single most common way a slept-through handshake fails. Collapse the twelve identical widget composables onto one loader that reloads a failed widget once a rejected request is followed by a successful refresh. Closes #942 |
||
|
|
d8105d4820 |
fix(kobo): keep metadata file writes off the re-download path
Embedding metadata rewrites the book file, so its hash moves even though the book itself is unchanged. Kobo reconcile reads a moved hash as new content and re-delivers the book as a NewEntitlement, which makes the device rebuild its local entry and orphan every annotation attached to the old one. Carry the post-write hash into the device snapshots that recorded the pre-write hash, so reconcile stays on the metadata-only path. The device keeps the bytes it already has and never receives the rewritten file; a content change from any other source still triggers a download. Ref #925 |
||
|
|
494b59562f |
fix(kobo): sync normalized language and ISBN metadata
Normalize heterogeneous language metadata at the Kobo boundary and include validated ISBN values so device details remain accurate. Version metadata hashes to refresh previously synchronized books. Fixes #920 |
||
|
|
27040d5dee |
fix(library): expose rename setting in scoped library list
Return fileRenameEnabled for non-superuser library responses so the bulk rename selector includes configured libraries. Closes #918 |