Fetch requests now await existing promises for in-flight blocks instead of re-triggering and starving the load when crossing block boundaries. The navigation window entries are also preserved during identical structural updates to avoid infinite watcher loops.
Closes#325
The Library, Series, and Authors filter panels were rendered inside the
scrolling <main> element, so opening a panel after scrolling down placed
it above the visible viewport and it appeared to do nothing until the
page was scrolled back to the top. Render each panel as a sibling of
<main> so it stays anchored regardless of scroll position. A height cap
is intentionally avoided because the filter typeaheads use absolutely
positioned dropdowns that an overflow container would clip.
Add structural regression tests for the Series and Authors views that
assert the panel renders outside the scroll container.
Fixes#326
When a metadata provider returns a page count of 0 (e.g. unknown), the frontend would submit 0, causing a validation error (HTTP 400). This change normalizes 0 to null on both the client (before sending) and server (during validation).
Closes#329
Removes the `assertManualUpdateAllowedForLockTransition` guard that incorrectly rejected the legitimate UI flow of unlocking a field, editing its value, and re-locking it before saving.
Closes#328
* feat(metadata): add LubimyCzytac metadata provider
- Add LubimyCzytac scraper and provider implementation
- Add lubimyczytacId field across types, schema, extractors, and metadata service
- Register lubimyczytac provider in module, pipeline, repository, and config service
- Add provider icon, color, link, and UI support in book detail views
- Add migration for lubimyczytac_id column
Closes#304
* fix(books): keep cover card read button opening reader when thumbnail click prefers details
The overlay primary button should always open the reader regardless of
the thumbnailClickAction setting. That setting only controls card click
behaviour.
* fix(books): add explicit read button to hover overlay when thumbnail click prefers details
When thumbnailClickAction is 'details', a dedicated Read button now
appears in the overlay top row so users can still open the reader
directly without changing their preference.
The book dock metadata service now routes audio formats through the audio extractor to extract chapters, duration, narrators, and other embedded tags. This ensures that audiobook metadata is correctly mapped and persisted during the import process.
Closes#322
Mobile keyboards, such as on Android, may not show a functioning
Enter key by default in this input. Adding enterkeyhint="enter"
allows users to properly submit chips like authors in the
metadata editor.
Closes#310
The `orientation: 'any'` setting in the manifest forces the PWA to rotate even if the device is locked. Removing it restores native behavior.
Closes#309
The previous 'already here' message was confusing when immediately adding an item to a collection, making it seem like the action didn't just occur. The confirmation has been updated to clarify that the item was just added.
Closes#306
The standalone BulkUpdateTagsDialog and its related state have been
removed from the table shell and views. In its place, a new
handleEditIndividually action was added, allowing users to select
multiple books and open the first one in the detail view for sequential
editing. The SelectionActionBar was also updated to support this action
and display query-scoped selection states.
Modify book navigation composables to work with sparse subsets
by pairing loaded book IDs with their absolute position in the
full listing. This ensures books reached via the jump rail
maintain their true position even when windowed.
Closes#319
When the user preference for thumbnail clicks is set to 'details', the
primary overlay button on book cards now routes to the book details
page and displays a BookText icon instead of the reader icon.
Also replaced the ExternalLink icon with BookText across dropdown
menus and settings for better visual consistency.
Delay spine layer rendering for blurred-fit covers until the image ratio is known so it does not overflow the artwork. Enable spine overlays on series stack cards, series detail headers, and carousels where they were previously disabled. Apply data attributes to spine layers so standalone artwork elements render properly without surface ancestors.
Closes#294
The default upload file naming patterns for single file and folder per book previously relied exclusively on the title. If multiple files lacked a title, they would map to the same path. This changes the default patterns to fall back to the original filename if the title is missing, preventing files without metadata from clumping.
Closes#308
Simplify the loading state for book covers by removing the complex BookCoverPlaceholder in favor of a neutral pulsing skeleton. This ensures a cleaner visual experience while the actual cover image is being loaded.
Add server-backed jump buckets so large library, collection, and smart-scope listings can jump directly to sorted sections without loading every row up front.
Closes#163
Update the action buttons in the Edit Metadata tab to hide their text
labels on small screens. The buttons are kept horizontally scrollable on
mobile but wrap to the next line on desktop viewports. This ensures the
Save button and other actions are easily accessible without consuming
excessive vertical space on mobile devices.
Closes#292
Keyboard events bubbling up from within a shadow DOM (such as the PDF
viewer's embedded find bar) report the custom element host as the event
target rather than the inner input field. This caused the reader's
global shortcuts to incorrectly trigger while typing.
Resolved by checking e.composedPath() to determine the true origin
of the event.
Closes#291
The PDF reader lacked an exit button, trapping users. Since the native PDF viewer header is packed with tools and difficult to safely inject into, a floating action button (FAB) is added to the bottom left of the screen.
Closes#290
The details page now includes a 'Send via Email' button in the more options menu if the user has the email_send permission. Selecting it opens the SendBookDialog.
Closes#289
Extract DB deletion logic into OidcCleanupService as a pure method
returning counts. Move the @Cron trigger, logging, and error handling
into a new OidcCleanupJob, matching the AuditCleanupJob pattern.
Sidecar OPF files can link to a sibling cover image, but the extractor
always passed null for the cover buffer, silently dropping the image.
Parse coverHref from three sources in priority order:
1. EPUB2 <guide><reference type="cover" href="..."/>
2. EPUB3 <manifest><item properties="cover-image" href="..."/>
3. Calibre <meta name="cover" content="id"/> resolved via manifest
Resolve the href relative to the OPF file directory and read the image
file. Fall back to null cover if the file is missing or unreadable,
so a broken cover link does not fail the import.
Closes#273
The SSRF guard was throwing a generic 'URL host is not allowed' with no
indication that the block was due to a private or local IP, or how to
resolve it. Users with a LAN-hosted OIDC provider had no way to diagnose
the failure.
Introduce PrivateAddressException extends BadRequestException in ssrf.utils
so callers can distinguish private-address blocks from other validation
errors. The OIDC connection test catches it and re-throws with a message
that names OIDC_ALLOW_LOCAL_ISSUERS=true as the fix.
Closes#264
The debug log in invalidateSnapshot did not follow the project logging
convention ([event] [phase] key=value ... - message) and logged all
reset book IDs, which could produce unbounded single-line output for
large libraries.
Fix the event name, add the [end] phase marker, add durationMs, drop
the bookIds field, and remove the no-snapshot early-return log (noisy
no-op path).
* fix(kobo): enhance kobodeviceservice to manage device snapshots
Refactor KoboDeviceService to include a syncService for invalidating user snapshots when devices are created or revoked. Update tests to verify snapshot invalidation behavior and ensure proper service instantiation. This change improves the synchronization process for user devices in the Kobo ecosystem.
* test(kobo): update device revocation tests for snapshot invalidation
Enhance the test cases for the KoboDeviceService's revokeDevice method to verify that the syncService's invalidateSnapshot is called correctly and that database deletion methods are invoked the expected number of times. This ensures accurate testing of device revocation behavior and snapshot management.
---------
Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
* fix(series): keep startup alive when series backfill fails
The series id backfill ran inside SeriesIdentityService.onModuleInit
without any error handling, so any failure rejected the lifecycle hook
and aborted NestApplication bootstrap, leaving the app unable to start.
On databases where the book_series unique arbiter index is missing or
unusable, the backfill INSERT ... ON CONFLICT throws and bricks startup.
Wrap the backfill in a guard so failures are logged and startup
continues, and surface the underlying driver error (Drizzle hides the
real cause behind its 'Failed query' wrapper) so the root DB issue is
diagnosable from logs.
Closes#270
* fix(series): resolve series backfill timeout on large libraries
The NodePg pool has statement_timeout: 30_000 ms, which is the right
guard for API queries but too aggressive for the one-time startup
backfill. On libraries with 100k+ books the bulk UPDATE against
book_metadata hits the limit before completing, leaving series_id
NULL for all books and repeating the timeout on every subsequent
restart.
Two changes:
1. Wrap the backfill in a transaction and issue SET LOCAL
statement_timeout = 0 at the start. SET LOCAL scopes the change to
this transaction only, so the pool timeout applies to every other
query unchanged. The transaction also makes all three backfill
statements atomic: previously a successful INSERT followed by a
timed-out UPDATE left book_series populated but series_id still
NULL everywhere.
2. Add a functional B-tree index on lower(btrim(series_name)) in
book_metadata. The backfill UPDATE joins on that expression; without
an index the planner falls back to a full sequential scan and hash
join on every startup. With the index, subsequent startups use a
nested-loop index scan that short-circuits immediately when no rows
need updating.
Measured on 250k books: first migration completes without timeout;
subsequent startups finish the no-op check in ~150ms.
Closes#270
- Adds rename and delete file capabilities to Book Details > Files tab
- Automatically promotes a fallback file to primary upon deletion
- Refactors path update logic to correctly apply db folder path updates
- Ensures file type format badges in header search use global format colors
- Replaces legacy file property overrides with rename-only DTO
Closes#214
The scanner disc directory pattern previously recognized cd, disc, disk, part, and side, but not pt. This caused Pt subfolders to be treated as separate books instead of being merged into the parent audiobook. This change adds pt as an accepted disc/part folder prefix.
Closes#262
Ensure all files associated with a book are renamed or moved when the
primary file is renamed. Previously, the rename service only computed
new target paths for the primary file, abandoning companion files (e.g.,
covers, sidecars) or failing to rename them properly.
Compute new target paths for all content and secondary files, manage
batch rollbacks on failure, and handle internal naming collisions. Clean
up unused single-file rename repository and service methods.
Closes#265
* feat(kobo): handle ratebook events in koboanalyticsservice
This commit adds the BookModule to the KoboModule imports, allowing for better integration with book-related functionalities. Additionally, the KoboAnalyticsService has been updated to handle 'RateBook' events, enabling the processing of user ratings through the new bookService. Tests have been added to ensure the correct handling of these events and their interactions with the reading session service.
* test(kobo): fill ratebook testing gaps
---------
Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
prevent focusout events on the sheet container from bubbling up to the document to bypass the focus scope trap. this allows the icon picker search box to retain focus when opened from the collection sheet. also increase the tooltip z-index to appear above the teleported panel.
Closes#280
Remove invalid XML 1.0 characters from book metadata. These characters can cause lenient OPDS clients like KOReader to incorrectly parse the feed and shift entry boundaries onto the wrong acquisition link.
Closes#260
The kepubify binaries contained golang.org/x/text v0.3.6 which
triggers CVE-2021-38561 and CVE-2022-32149 in Trivy. This commit
updates the module to the latest version and rebuilds the binaries.
The previous kepubify binaries were built with an older Go version
that triggered Trivy's CVE-2026-42504 vulnerability in the standard
library. This updates the binaries to the latest Go version to resolve
the CI pipeline failure.
The metadata fetch pipeline now gathers and returns detailed diagnostics
about provider selection, candidate results, and the reason for empty
resolved fields (e.g. no active providers, throttled providers, or no
candidates). These diagnostics are logged to improve observability and
make debugging metadata fetch failures significantly easier.
Use paired Kobo open and leave analytics events to derive progress deltas and active reading time.
Format negative reading-log deltas without an extra plus sign.
KOReader progress updates were updating the database but failing to
emit the progress changed event, causing Hardcover auto-sync to be
skipped. This change adds the missing event emission to the KOReader
service so that external syncs trigger correctly, and patches a query
filter issue in the Hardcover repository.
Closes#258
Add an error event listener to the source stream when appending entries during zip patching to prevent unhandled ENOENT errors from crashing the Node process.
Closes#226
The placeholder property now correctly uses the provided value or falls
back to instructional text based on whether items exist. The placeholder
text opacity is also reduced.
Closes#251
Add Kobo as a first-class metadata source so books can keep Kobo IDs across fetch, edit, import, scoring, and file-write flows. Provide the Python cloudscraper setup needed for production fetches while keeping local setup explicit.
Closes#247
* feat(kobo): integrate analytics service into kobodevicecontroller
- added analytics service to the kobodevicecontroller to handle analytics events.
- updated the analyticsevent method to process incoming events and handle errors gracefully.
- enhanced unit tests to cover new analytics functionality, including successful ingestion and error handling scenarios.
- updated module imports to include the new analytics service and resolver.
* test(architecture): add kobo analytics resolver service to architecture boundaries tests
* refactor(kobo): enhance analytics handling and improve tests
- Updated KoboDeviceController to include analyticsService in constructor and added test for handling non-Error rejections.
- Enhanced KoboAnalyticsResolverService tests to cover scenarios where book rows do not exist and when primary file IDs are set without corresponding EPUB files.
- Modified KoboAnalyticsService to integrate bookIdentityService for resolving volume IDs and added tests for various edge cases in event ingestion, including handling invalid timestamps and logging warnings for non-Error exceptions.
These changes improve the robustness of analytics processing and ensure comprehensive test coverage for new functionalities.
* fix(kobo): enhance leavecontent event validation and update tests
- Improved validation logic in KoboAnalyticsService for LeaveContent events to ensure volume ID is a non-empty string.
---------
Signed-off-by: Logan Selley <37816781+Logan-Selley@users.noreply.github.com>
Co-authored-by: Logan-Selley <logan@selleytech.com>
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
* feat(kobo): support two-way progress sync
Implement two-way page-level reading progress synchronization between Kobo devices, KOReader, and BookOrbit. This ensures readers can switch between physical devices and the web app without losing their exact page-level reading position.
Closes#249
* fix(settings): restore thumbnail click preference
Restore the main branch thumbnail-click setting after the Kobo branch accidentally dropped it during the rebase. Keep the Kobo architecture allowlist aligned with the new identity service.
* fix(kobo): harden two-way progress sync fallbacks
Preserve Kobo bookmark metadata and tolerate null auth payloads/settings failures.
Cover review-reported regressions with focused tests.
Introduce a new display preference to control what happens when clicking
a book cover thumbnail. Users can now choose to open the book details
page instead of launching the reader directly.
Closes#240