Ensure author enrichment progress notifications can be dismissed and resolve stuck status indicators that persist on screen after completion or matching failures.
Closes#678
Use the cleaned file stem as the search title when searching for book dock metadata if no title is present in embedded or selected metadata.
Closes#655
* build(deps): synchronize i18n lockfile
Record the locale dependencies so frozen installs can resolve the client workspace.
* feat(stats): expose privacy-aware reading insights
- Give administrators permission-gated access to bounded account activity.
- Let readers opt into auditable shared reading insights.
Ref #501
* feat(i18n): add vue-i18n foundation with language switcher and Dutch locale
Introduce multi-language support for the client using vue-i18n and
@intlify/unplugin-vue-i18n. Locale is detected from a stored preference
or the browser language and persisted client-first, with optional server
sync riding the existing theme-sync mechanism (new locale category on the
generic user_preferences store, no migration needed).
Adds a language selector in Appearance > Behavior settings and seeds
en/nl message catalogs. Extraction of existing hardcoded strings follows
in subsequent commits.
* feat(i18n): add full en/nl message catalogs and test/build wiring
Populate the English and Dutch message catalogs (~4300 keys) covering the
whole UI, register a vitest setup that installs vue-i18n globally so
component tests resolve real messages, and allow inline HTML in messages
(strictMessage/escapeHtml off) for notes rendered via v-html / <i18n-t>.
* feat(i18n): localize settings and book UI strings
Replace hardcoded strings with t() calls across the settings and book
feature areas (including book detail, table, metadata edit, reading log,
highlights). Label collections captured at setup are wrapped in computed()
so they react to locale changes.
* feat(i18n): localize remaining feature UI strings
Replace hardcoded strings with t() across reader, email, library, tools,
migration, dashboard, auth, hardcover, author, series, book-dock,
book-metadata-fetch, annotations, admin, statistics, collection,
smart-scope, audit, whats-new, achievements, notifications, custom-icons,
scanner and metadata-score. Reactive label maps use computed().
* feat(i18n): localize shared components and view wrappers
Replace hardcoded strings with t() across shared components (app header,
sidebar, view header, pickers, selection bar) and top-level views.
* feat(i18n): localize settings tab bars and browser page titles
Make settings tab labels reactive via t() in the consuming components and
slim the *-tabs.ts helpers to ids/order/permission only. Route the browser
page titles in the router through the app i18n instance so document.title
follows the active locale. Adds settings.<area>.tabs.* and titles.* keys.
* feat(i18n): localize KOReader hash-linking and reader sidebar pin
Localize the strings from the KOReader manual hash-linking feature and the
reader sidebar pin/close controls that were merged from main during the
rebase, adding the corresponding en/nl keys.
* feat(i18n): localize upstream features merged during rebase onto main
Add en/nl catalog keys for the new upstream functionality pulled in by the
rebase (Kobo activity log, KOReader dismiss/remove-device and hash-link
modal, default library access, upload-size settings) and update a few
existing keys whose wording changed upstream (dynamic upload-size hints,
write-and-rename phrasing).
* feat(i18n): switch language picker to a dropdown
Replace the segmented language buttons with a native select, which scales
better as more locales are added.
* feat(i18n): add German and Slovenian translations
Register de and sl in the supported locales and ship full de.json and
sl.json catalogs (4616 keys each, structural parity with en.json).
* fix(client): harden locale switching and translation coverage
Keep locale catalogs structurally aligned, localize rebased UI changes, and use locale-aware formatting. Move language selection into the header, persist authenticated preferences, and prevent stale locale loads from winning.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Retrieve the book file format when querying the annotation hub, and dynamically determine the router parameters for navigation. This prevents hardcoding the epub format and correctly routes fb2 and other non-epub format annotations to their respective readers.
Closes#634
Introduce a metadata provider for Libro.fm to fetch audiobook details, offering an alternative to Audible and iTunes. Add server-side mapping, fetch controller endpoints, validation DTOs, and client-side UI configurations for user metadata preferences.
Closes#636
Make long PDF reading sessions easier to navigate across desktop and mobile. Keep document loading recoverable when EmbedPDF initializes or tears down out of order, without enabling document-editing capabilities.
Introduce user-specific settings to toggle achievements. Avoid evaluation
and awarding of achievements on the backend when disabled. Render a toggle
in the Account Settings interface on the frontend.
Closes#614
Provide a series-collapse toggle in the Smart Scopes view to reduce clutter from large series. Use the existing series collapse preferences logic to allow users to toggle this setting per Smart Scope, maintaining consistency with Library and Collection views.
Closes#609
Provide a new endpoint and user interface to reset the reading state of a book. Delete all related reading sessions, progress, audiobook progress, and KOReader progress. Clear lifecycle dates when setting the book status back to unread or want to read, preventing the book from persisting on the continue reading shelf.
Closes#606
Migrate the snapshot and snapshot books tables to be per-device instead
of per-user. This resolves a sync race condition where only the first
registered Kobo device for a user receives the library delta, leaving the
others empty.
Closes#314
- Add a persisted publication date while continuing to derive publication years.
- Thread full dates through metadata import, editing, filters, sorting, write-back, and sync payloads.
- Cover provider parsing, DTO validation, jump buckets, and date formatting with tests.
Closes#596
Provide an action in the Book Dock finalize dialog to discard files that
are identified as duplicate candidates. This saves users from having to close
the finalize window, re-select the duplicates in the main queue, and manually
discard them.
Closes#591
Introduce a control mechanism to pause and resume the Book Dock import task.
Suspend resource-intensive background file ingestion and metadata finalization
to avoid system overhead or database locks during large-scale operations.
Closes#593
* feat(storygraph): add StoryGraph sync integration
Sync reading status and progress from BookOrbit to StoryGraph using
session-cookie authentication. Includes book matching with manual
rematch and edition selection, per-user sync toggles, a throttled
request queue, a storygraph_sync permission, and a StoryGraph
settings page.
Closes#274
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vi6e8ZJRAGMmRSmSj9sWXR
* fix(storygraph): address automated review findings
- clear stale cookie validation badge when inputs change
- reject saves with only one cookie value instead of silently dropping it
- handle linked-books load failures with a retryable error state so post-action
reloads cannot misattribute failures to the action
- add a 30s request timeout so a hung StoryGraph response cannot stall a user's
sync queue indefinitely
- reserve throttle slots synchronously so concurrent requests serialize
- reject empty cookie strings that would overwrite stored credentials
- report the retry response status when the rereading fallback fails
- add component specs for the connection card and linked books, plus regression
tests for the throttle, settings, and retry-status fixes
* test(storygraph): fully restore setTimeout spies between queue tests
* feat(storygraph): add sync-scope option
Adds the book sync scope selector (all eligible books vs selected books
only) and the per-book StoryGraph sync control on the book details page,
mirroring the Hardcover integration's controls. All books with a syncable
status are now eligible — the previous connected-at cutoff that skipped
books finished before connecting is removed in favor of explicit scope
and per-book selection.
* fix(storygraph): stream live sync progress and surface failed books
The manual sync progress stream emitted the same mutated status object,
so distinctUntilChanged dropped every update after the first and the bar
never moved. Progress snapshots are now immutable and track processed
books (synced + skipped + failed), so the bar advances for every book.
Adds live synced/skipped/failed counters, a persistent last-run summary
from a terminal completed/cancelled event, and a failed-books list under
Manual sync backed by GET /storygraph/sync/failures. Details-page sync
state now refetches when the global sync scope changes.
(cherry picked from commit 075eeaa0c8678d4ebbba5dcce7aa15df42d64f51)
* feat(storygraph): show distinguishing identifiers in the editions list
Edition rows previously showed only format, pages, and language, making
identical-looking editions impossible to tell apart. The editions parser
now also scrapes ISBN, publisher, publication date, and cover image when
present, and each row shows the edition title, those identifiers, a cover
thumbnail, and the StoryGraph edition id linking to the edition page for
verification before switching.
(cherry picked from commit 09ec111613e9be149b556f64eb242cd3d82b296c)
* fix(storygraph): auto-refresh linked books after sync and add unmatched hint
The Linked books list only loaded on mount, so a book linked during a manual
sync run stayed on 'Not linked yet' until a manual page refresh. It now
silently reloads when a run completes (no spinner flash, keeps expanded rows),
guarded by a request id so overlapping reloads can't resolve out of order.
Books StoryGraph couldn't match automatically now show a short hint pointing
at 'Try auto-match' or pasting the StoryGraph URL.
(cherry picked from commit 1d0cfa1e06f3e211258620f7ad5e61373df7a310)
* feat(storygraph): compact edition link and wrap identifiers on mobile
The editions list showed the raw StoryGraph edition UUID as a long
monospace hyperlink, which was hard to read and cluttered the row. Replace
it with a small icon-only 'Open on StoryGraph' button (aria-labelled) next
to 'Use this', and let the publisher/date/ISBN line wrap instead of
truncating so the ISBN is no longer cut off on narrow/mobile screens.
(cherry picked from commit cdf4d64063534adeea9daafad11cd58a0cbc9a00)
* fix(storygraph): re-sync books re-added after being marked unread, clarify labels
A book synced while 'read' then marked 'unread' left its synced markers in
place, so re-marking it read compared against a stale lastSyncedStatus and was
silently skipped (shown as Linked but never re-pushed) until 'Try auto-match'.
On the unread transition the synced markers are now cleared while keeping the
match, so re-adding the book re-syncs.
Also present match methods with real casing (ISBN/Title/Cached/Manual) and
clarify that the manual-link field takes a StoryGraph URL or book ID (the code
after /books/), not an ISBN.
(cherry picked from commit 771e7e4fe793afbc30800717ae1c017ca307e5e1)
* refactor(storygraph): compare skippedBooks explicitly in sync-status dedup
The active-sync stream's equality check omitted skippedBooks, relying on
processedBooks (synced+skipped+failed) to capture skip-only changes. That is
correct but non-obvious; comparing every counter explicitly makes the intent
clear. No behavior change.
(cherry picked from commit f267088939f0d2493caadab8eb90f24ab4b28c0b)
* fix(storygraph): encode manually-entered book id in the lookup path
The manual-link id can be pasted directly, so a stray ?, #, or / would change
which /books/ URL is requested. Encode it before interpolating; a bad id now
404s cleanly instead of being stored.
* style(storygraph): fix prettier formatting and remove em dashes
The CI Lint (Client) job runs prettier --check, which flagged
StorygraphLinkedBooks.vue and StorygraphSyncProgress.vue. Reflow both
to prettier style and replace em dashes in UI strings and comments with
regular punctuation per project convention.
* fix(storygraph): harden sync access and batching
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
* feat(types): add readwise_sync permission
* feat(types): add readwise settings types
* feat(db): add readwise_user_settings table
* feat(readwise): add constants
* feat(readwise): add throttle queue
* feat(readwise): add REST client
* feat(readwise): add repository
* feat(readwise): add settings service
* feat(readwise): add sync service with watermark
* refactor(readwise): drop unused logger in settings service
* feat(readwise): add debounced scheduler
* feat(readwise): add event listener
* feat(readwise): add controller and dtos
* feat(readwise): wire module into app
* test(readwise): add readwise_sync to authorization matrix
* feat(annotation): emit annotation.created on device sync ingest
* feat(client): add readwise api client
* feat(client): add useReadwiseSettings composable
* feat(client): add Readwise settings page
* feat(client): add Readwise settings route
* feat(client): add Readwise to settings nav
* fix(readwise): avoid typeof this in settings type annotation
Production build (nest build → tsc via tsconfig.build.json) rejects
`Parameters<typeof this.repo.upsertSettings>[1]` with TS2304. Reference
the repository class type directly so the prod image compiles.
* fix(client): add Readwise sync to admin permission list
The admin UserFormDrawer renders a hardcoded permission list, not an
iteration of PERMISSION_LABELS, so readwise_sync was ungrantable via UI.
Add it to the Devices & Access group and the Standard preset, mirroring
Hardcover sync.
* feat(readwise): send book cover to Readwise via OpenLibrary ISBN
Populate the Readwise highlight image_url with the OpenLibrary cover URL
built from the book's isbn13 (falling back to isbn10). Uses ?default=false
so a missing cover yields 404 (no image) rather than a blank placeholder.
Books without an ISBN send no image_url. BookOrbit's own cover endpoint is
auth-gated and stores no public source URL, so a public third-party cover
URL is the zero-infra way to give Readwise an image.
* test(readwise): add type parameters to vi.fn mocks
oxlint (vitest/require-mock-type-parameters) fails on untyped vi.fn()
in the settings page test; the pre-commit hook only runs eslint, so
this surfaced under full lint:check.
* docs(readme): add Readwise Sync to features list
* fix(readwise): surface settings fetch errors in the UI
The settings page destructured error from useReadwiseSettings but never
rendered it. A failed fetch on mount left the user with an empty default
form and no indication that loading failed. Render an error banner when
error is set, and cover it with component tests.
* test(readwise): cover settings page token test, save, and visibility toggle
Adds component tests for the Test-connection flow (empty/valid/invalid
token), Save (success clears the field, failure surfaces an error toast),
and the show/hide token toggle — the interactive handlers Codecov flagged
as uncovered in ReadwiseSettings.vue.
* test(readwise): match settings buttons by exact label in test helper
Use an exact text match instead of substring matching in the testButton
helper so it can never return the wrong button if labels overlap.
* fix(readwise): harden highlight sync
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: Oscar Nogueira Neto <oscarnogueira@gmail.com>
Allow smart scopes to opt into Kobo sync while preserving collection sync behavior. Generate the current Drizzle migration and keep smart-scope matching aligned with the normal book query path.
Fixes#202
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: Jane Doe <janedoe@users.noreply.github.com>
Introduce a LIBRARY_BROWSE_ROOT environment variable (defaulting to the
current directory) to restrict directories users can browse and select when
setting up library paths. Add a PathPolicyService on the backend to enforce
path containment checks securely, and update the frontend FolderPickerModal
to prevent navigating above the configured browse root.
Closes#567
Truncate long path segments generated during upload pattern resolution and
download filename generation to prevent ENAMETOOLONG errors. Keep the file
extension and suffix of the segment, and use a deterministic hash of the
original segment to ensure unique truncations.
Closes#549
Add support for user-scoped personal notes (reviews) and allow clearing book ratings to null,
providing full sync capabilities for these actions in the KOReader plugin. Modularize the
KOReader plugin codebase into dedicated mixin files to improve maintainability and expand
the catalog detail view with related sections (series, author, similar books).
Closes#552
Add configuration settings and backend support to define default library access permissions for newly created users. Newly registered users automatically receive these default library access permissions upon creation. Also align current users page controls and magic link options in the admin tab settings UI, adding styled callout cards for empty states.
Closes#486
Records success and failure events for Kobo device syncs in the database, including operation duration, event types, and error details. Surfaces the recent sync history in the Kobo Settings UI for users to monitor their device activity.
Introduces the koreader_unmatched_book_devices schema to track unmatched
books per device. Adds API endpoints and UI in settings for dismissing
unmatched books and completely removing a KOReader device.
Closes#504
When KOReader syncs progress or annotations, it identifies books by a document
hash. BookOrbit normally resolves this automatically, but some books cannot be
matched. This adds a tracking table for unmatched hashes and a UI to manually
link them to existing library books.
Closes#458
During an automatic metadata refresh, the fetched Hardcover edition ID was not being copied to the update payload. This ensures the value is persisted to the database alongside the other metadata fields.
Closes#440
Adds a fixedLayoutSpread preference to EPUB reader settings to allow forcing single-page display. This is particularly useful for image-based EPUBs like comics and manga.
Closes#487
Reading source is often less relevant for day-to-day analytics than
book format or reading pace. Shift the source distribution chart to the
end of the user dashboard and change the default breakdown dimension
from source to format to prioritize more useful metrics. Include a
migration to update existing users who have not customized their chart
order.
The group rule validator now accepts an optional `provider` field for
`communityRating` rules. This enables smart scopes to filter books based
on community ratings from specific providers (e.g., Goodreads, Amazon)
rather than just the aggregate rating.
The validator ensures that the provider field is only applied to community
rating rules and rejects unsupported providers.
Closes#477
Adds support for displaying custom metadata as columns in the book
table view. The frontend now fetches active fields and dynamically
renders the corresponding cells, backed by book card API updates.
Closes#465
Previously, the explicit metadata search modal for a book was restricted
to only the providers configured in the library's field rules. This
prevented users from manually searching a specific provider if it
was not part of their default automated fetch pipeline.
This change allows all globally enabled providers to be used during
a manual metadata search, while still defaulting the selected UI
checkboxes to the providers specified in the field rules.
The KOReader plugin previously matched series by name, which caused
issues for series with identical names or special characters. This
updates the plugin and backend to use the exact series ID for
navigation and filtering, ensuring all series load reliably. Also
bumps the KOReader plugin version to 1.0.1.
Closes#466
Add full-stack support for uploading, managing, and using custom SVG icons for libraries and smart scopes. Includes database schema, server module for storage and sanitization, and client UI under Appearance settings.
Add the KOReader catalog surfaces, plugin package metadata, and device progress handling needed for the plugin workflow. Keep progress status updates non-blocking so secondary status failures do not reject successful sync writes.
Users rely on community feedback when evaluating books in their library. This change
introduces aggregated community ratings and rating counts from external metadata
providers, allowing users to see popular opinion directly within the book details
without needing to leave the application.
Closes#414
Adds hardcoverEditionId to the database schema and shared types, enabling it to be stored as first-class metadata. Updates the file metadata parsers and writers (EPUB, PDF, CBZ) to read and embed this ID, and exposes it in the frontend metadata editor.
Closes#420
Allow users to define new custom fields for books beyond the standard
metadata properties. This provides flexibility for cataloging arbitrary
data like reading formats, editions, or personal ratings.
Closes#419
Both AddToCollection and SendBook previously only operated on the explicitly provided list of book IDs. This prevented them from working correctly when a user selected all items across multiple pages in the UI. They now inherit from BulkSelectionDto and resolve queries to apply to all matching books.
Closes#416
Introduce a per-book sync toggle in the book detail view that lets
users include or exclude individual books from Hardcover sync.
Add a book sync scope setting with two modes:
- all_eligible: sync all books unless explicitly excluded (default)
- selected_only: sync only books the user explicitly includes
Extract sync eligibility logic into hardcover-sync-policy.ts for
testability. The toggle handles unread books correctly: sync is skipped
while the book is unread, but the user's inclusion intent is stored so
sync activates automatically once reading starts.
Closes#412
Add two per-file override flags to the Book Dock finalize flow:
- skipDuplicateCheck: bypasses ISBN/title+author duplicate detection for
a specific file. In book_per_folder mode the file naturally lands in
the same folder as the existing book, so createBookRecord auto-attaches
it as a second format on the same record.
- targetFileName: lets the user rename the file at the destination to
resolve a same-path collision. The extension is always appended from
the file format; if the user includes the extension it is stripped
first to avoid doubling. Setting targetFileName also implicitly skips
the duplicate check. A directory-traversal guard rejects any name
whose resolved path escapes the intended destination folder.
Frontend: results panel shows an "Import anyway" button for isDuplicate
rows and an inline rename input for file-exists-at-target rows. Both
retry calls forward defaultLibraryId/defaultFolderId so files that rely
on dialog-level defaults get the right destination. Concurrent double-
click is guarded. reimportingIds uses reactive(Set) so spinner and
disabled states update correctly.
Closes#213
Ensure the settings UI saves inherit toggles immediately and guards eligible-count previews against stale async responses. Align library override DTO validation with the shared partial override contract, and record manual library runs even when nothing queues.
Extract and map multiple series memberships from AudNexus (primary, secondary, and fallback series) to a new seriesMemberships array. Update Goodreads mapper tests to verify correct extraction of the first series.
Closes#383
Add a preview-and-review workflow that matches Hardcover books by cached IDs, ISBNs, slugs, and title/author confidence before updating BookOrbit read status.
Import reading progress only when requested and safe, preserve existing local progress, and keep status imports resilient when progress writes fail.
Closes#394
Emit transfer events when watcher reconciliation moves a book between libraries. Keep file-as-book and folder-as-book watcher paths consistent across Docker, direct runs, and delayed copy-delete moves.
Closes#386
Expose zero-book first-class metadata entities in browse so admins can review orphaned rows before deleting them.
Keep inline metadata values out of empty filtering because they only exist through book metadata rows.
Closes#370
Update cover mutation paths to bump the book timestamp used for cover cache busting, and carry per-book collapsed-series cover versions so child covers do not reuse a representative book timestamp.
Introduces a new feature to fetch, parse, and display app release notes
and update highlights directly within the UI.
- Backend: Adds the `release-notes` module to fetch GitHub releases
and parse markdown highlight sections.
- Frontend: Adds the `WhatsNew` dialog and view, including custom
markdown rendering and an image lightbox for update media.
- Tools: Adds validation scripts (`whats-new-lint`, `whats-new-preview`)
to ensure release content parses correctly before publication.