On old hardware (e.g. AMD Turion/Phenom, Intel Core 2), sharp's musl binary
crashes with SIGILL during NestJS module init. Combined with bufferLogs: true
and restart: unless-stopped, this produces a silent infinite restart loop -
only the migration success message is ever visible.
Add check_cpu_sse42 to entrypoint.sh: greps /proc/cpuinfo for sse4_2, exits
with a clear error if absent, no-op when /proc/cpuinfo is unavailable. Also
add a System Requirements section to README documenting the SSE4.2 minimum.
Closes#116
Prevent unauthorized users from seeing integrations they cannot use and keep settings routing consistent with API authorization behavior. Add hardcover_sync and enforce it server-side so tab visibility and endpoint access stay aligned.
Validate DATABASE_URL with pg's connection-string parser so unix-socket DSNs with empty authority hosts pass while malformed/non-postgres values are rejected.
Closes#106
* fix(scanner): clamp precision-unsafe inodes to avoid lossy matching
Clamp inode values above Number.MAX_SAFE_INTEGER to 0 before scanner, upload,
and file-event matching paths consume them.
Add regression tests for Unraid-style inodes so precision-unsafe values
cannot collapse file detection during scans.
Lower server coverage thresholds from 85 to 80 as requested for this branch.
Closes#84
* fix(scanner): reuse existing book for same-library folder renames
Keep the original book id when a folder path changes so scan does not create a duplicate entry with no files. Use inode first and hash fallback for precision-unsafe inode values while keeping book_per_file behavior unchanged.
Book titles and author names can contain characters that are invalid on Windows,
SMB/NFS shares, or cloud sync platforms (e.g., `:`, `?`, `*`, `"`). Without
sanitization these characters appear verbatim in generated file and folder names,
which causes failures on Windows filesystems, broken NFS/SMB mounts, and sync issues.
Add a toggleable app setting that, when enabled, sanitizes illegal filesystem
characters in all pattern-resolved paths (upload, rename, book-dock finalize, and
download). Each token value is sanitized before substitution: invalid chars are
replaced with `_`, trailing dots and spaces are stripped, and Windows reserved names
(CON, AUX, NUL, COM1-COM9, LPT1-LPT9) get a suffix to avoid conflicts. The setting
defaults to off to preserve existing behavior.
Closes#103
document cross-reader sync limitations in the KOReader settings panel.
set clear expectations for direction-specific accuracy so users understand why resume location can differ between KOReader and the web reader.
treat the series-position badge as a non-open interactive target and
exclude those clicks from card-open handling.
add regression tests for hover hit-testing and badge-click behavior.
type vitest mock functions in the series detail spec to satisfy
the repository pre-push lint checks.
Closes#99
Handle add-to-collection actions in the series detail view so the collection sheet opens with the selected book id. Add regression tests for grid and table actions plus sheet close state reset.
Closes#102
Extracts the in-memory caching logic previously duplicated across
UserStatisticsService and StatisticsService into a new shared
StatsCache class. This standardizes cache keys, improves invalidation
by scope (such as userId), and makes the dashboard widget and
statistics caching more robust.
The previous SQL query used a LATERAL join and complex aggregations that caused series collapsing to fail. This refactors the query to use a unified base_rows CTE and explicit window functions for fetching series covers, ensuring that series are correctly grouped and displayed.
Closes#87
The backend previously only checked for new releases from GitHub
during application bootstrap. If the server stayed up for a long time,
the UI would never be notified of new releases.
The AppInfoService now re-checks GitHub when the frontend requests
app info, using a 10-minute cache TTL to balance freshness with
API rate limits.
The previous hardcoded AUTHOR_MAP was incomplete and caused missing authors
in the generated release notes. The config now attempts to fetch actual
GitHub usernames associated with commit SHAs using the GitHub API,
falling back to local git log author names if the API request fails.
Use a single semantic-release config to avoid diverging behavior, and update the release notes context logic to map authors with full commit hashes and stable issue URLs.
Add books.id ASC tiebreaker to all 7 OPDS_SORT_MAP sort orders so
pagination is deterministic even when books share identical addedAt
timestamps (e.g. after a bulk library scan). Without a tiebreaker,
PostgreSQL returns equal-valued rows in an undefined order, causing
books to appear on multiple pages or not at all.
Make catalog feed IDs context-specific: filtered views (by library,
collection, smartScope, author, series, or search query) now produce
distinct URNs instead of all sharing urn:bookorbit:catalog. This
aligns with the OPDS spec requirement that <id> uniquely identify
the logical feed.
Remove the dead idQuery.$dynamic() block from paginatedBookQuery
that was built and conditionally mutated but never awaited.
Closes#93
support toggling membership in the collection sheet so users can remove books without leaving the details and quick-view flow.
cover add/remove paths plus access edge cases with frontend and backend tests.
Closes#85
Run recipient fetch on initial open state so dialogs mounted with open=true load data immediately. Add dialog and recipient composable unit tests to lock in the regression behavior and error paths.
Closes#89
improve header discoverability with quick access to documentation and
community support actions. keep the github prompt in a dismissible
popover to avoid adding noise to the main navigation.
* feat(users): tag/genre content filters for per-user access control
Add granular per-user content filtering by tags and genres, allowing admins to set include/exclude filter rules that are enforced across all book-serving surfaces.
- Implement DB schema, repository, and SQL helpers for content filters
- Wire enforcement across core domains (Books, Collections, OPDS, Kobo sync)
- Add REST endpoints for managing filter rules
- Redesign UserFormDrawer to support configuring content restrictions
- Add read-only Restrictions tab in account settings and user list badges
- Add comprehensive unit tests and fix existing fixtures
Closes#68
* test(client): update account tab expectations for restrictions
Use each group's displayed tier description instead of the next tier description so titles and copy stay consistent across all tiered cards. Refresh achievement seed names for books, pages, hours, long-book, and library ladders, and adjust books_finished_3 to a 35-book threshold to match the new progression.
Enable author image upload and removal from the author detail edit panel.
Persist author photos with generated thumbnails and enforce server-side validation plus access-scoped API routes.
Cover the new controller and service behavior with author module tests.
Prevent enrichment from applying unrelated author records when provider search returns low-signal names.
Rank multiple candidates and require strong name compatibility before accepting a match.
Treat lookup failures as provider failures instead of silently falling back to raw search hits.
* fix(server): deduplicate migration user-state upsert batches
Prevent migration failures when source data contains duplicate user-state rows for the same target keys. Keep the latest row per upsert key and prefer richer reading progress signals on timestamp ties, then cover the behavior with focused unit and e2e tests.
Closes#71
* ci: raise commitlint body line limit to 500 chars
Allow longer commit body lines so convention checks do not fail on detailed but still concise commit explanations in this repository.
Introduce status badges for library capabilities such as file write, file rename, and watching. Add cronstrue to format the auto-scan cron expression into human-readable text. Increase the layout max-width for the library settings page to accommodate the new cards.
Add file rename feature that renames physical book files on disk when
naming-relevant metadata fields are updated (title, authors, series,
seriesIndex, publishedYear).
Closes#69
Extract the achievements page out of /statistics?tab=achievements into its
own /achievements route. Add a page header with a Trophy icon and subtitle
to match the app visual style (consistent with BookDockView pattern).
Add a beforeEnter redirect on the statistics route so ?tab=achievements links
continue to work. Update AppHeader navigation and active state checks to use
the new route name. Remove the achievements tab conditional from StatisticsPage.
Add per-user startedAt and finishedAt filtering, sorting, and timezone-safe date normalization.
Surface compact read-date editing in Details and return canonical date-only status payloads.
Preserve manual dates across status updates and reject null status patches.
Closes#66
Unify cover rendering with a reusable surface wrapper so cover styling stays consistent across grid, list, table, and dashboard views.
Expose spine overlay and shadow strength controls in appearance settings and fit spine overlays to cropped cover frames to avoid visual bleed.
Add tests for the cover surface component and aspect-ratio fitting helpers.
closes#62
* feat(docker): expose Book Dock folder as a configurable drop zone
Add support for an external book drop folder that users can bind-mount
into the container via the BOOK_DROP_HOST_PATH env var. Files copied or
moved into this folder are automatically picked up and ingested by the
existing Book Dock file watcher.
- docker-compose.yml: add BOOK_DROP_HOST_PATH volume override so the
book-dock path is separately bind-mountable
- .env.example: document BOOK_DROP_HOST_PATH with an example NAS path
- AppInfoResponse: add bookDockPath so the UI can show the
container-internal path admins need to bind-mount
- AppInfoService: compute and return bookDockPath from storage config
- BookDockSettings: display drop folder path with BOOK_DROP_HOST_PATH hint
- useAppInfo: expose bookDockPath ref from the app-info API response
- README: document the optional drop folder in the quickstart section
When BOOK_DROP_HOST_PATH is unset, the volume defaults to
./data/app/book-dock, preserving full backward compatibility.
No watcher code changes required - the watcher already watches
the correct container-internal path.
Closes#58
* fix(book-dock): resolve symlink paths so rescan stays idempotent
BookDockWatcherService and BookDockIngestService were building
bookDockPath with path.join(), which preserves symlinks. `@parcel/watcher`
resolves symlinks internally when reporting events (using realpath), so the
absolutePath values stored by watcher events and the paths computed by
walkAndIngest during rescan never matched. This caused findByAbsolutePath
to always return undefined on rescan, creating duplicate DB records for
every file already tracked via a watcher event.
Fix: both services now call `realpath(bookDockPath)` in
onApplicationBootstrap (after mkdir), updating the stored path to the
canonical real path that the watcher also uses. Rescan is now idempotent
regardless of symlinks in the data directory.
Verified by E2E test: 3 consecutive rescans produce zero new records.
* fix(books): switch progress reset to per-file controls
Remove the book-level reset action and endpoint, and keep reset scoped to each file row to avoid accidental bulk clears.
Add tests for per-file reset behavior and progress formatting edge cases while keeping migration import filters aligned with the new progress expectations.
closes#57
* ci: increase commitlint body line limit to 250
Allow longer explanatory commit bodies so CI accepts existing project commit patterns without forcing manual hard wraps at 100 characters.
Reduce the settings navigation bar from 17 flat tabs to 10 by grouping
related settings into subtab containers, following the existing ?tab=
query-param pattern used by Reader and Metadata.
New groups:
- Integrations (/settings/integrations): Kobo, KOReader, Hardcover
- Admin (/settings/admin): Users, OIDC/SSO (permission-filtered)
- System (/settings/system): File Naming, Book Dock, Maintenance, Audit Log (permission-filtered)
- Account (/settings/account): Profile, Notifications (merged from standalone tabs)
All old routes redirect to their new grouped equivalents for backward
compatibility. Admin and System containers filter available tabs based on
user permissions. Demo-restricted accounts hide the Notifications tab.
New files:
- IntegrationsAllSettings.vue, AdminAllSettings.vue, SystemAllSettings.vue, AccountAllSettings.vue
- lib/integrations-tabs.ts, lib/admin-tabs.ts, lib/system-tabs.ts, lib/account-tabs.ts
- Unit tests for all new containers and tab-lib files (SettingsHeader included)
Closes#55
Block Google Books from being enabled without credentials because unauthenticated API calls now fail quota checks. Normalize legacy enabled configs without keys to disabled.
Use BigInt stat reads for upload and file-event paths to preserve 64-bit inode values. Clamp inodes that exceed PostgreSQL bigint range to 0 before persistence and inode lookups, and add regression coverage for insert, upsert, restore, and move-detection paths.
Fixes#48
- Add tap-to-cycle footer modes (pages, time/session, chapter) along with elapsed reading time and chapter progress on the slider
- Prevent screen dimming during reading using the Wake Lock API
- Add keyboard shortcuts (t/s/b/f/Esc/?/Home/End) with an integrated help modal
- Add go-to-location support (page number or percentage) and a resume-reading toast on book open
* fix(scanner): clamp oversized MergerFS inodes before storage
MergerFS generates synthetic unsigned 64-bit inodes by hashing file paths.
Values above 2^63-1 overflow PostgreSQL's signed bigint column, crashing
the scanner with 'value out of range for type bigint'.
Switch all file stat() calls to stat(path, { bigint: true }) to read
inodes as precise BigInt values instead of lossy JS numbers. Introduce
clampIno(ino: bigint): number which returns 0 for any inode exceeding
PostgreSQL's bigint max - 0 is the existing sentinel meaning 'rename
detection disabled', so no other code needs to change.
The fix covers all four code paths that write ino to a FileStat:
statFilesIntoAcc and buildSingleBookCandidate in walk.ts, plus the two
targeted-scan paths in scanner.service.ts.
Fixes#19
* ci: add maintainer-gated PR GHCR image publish
---------
Signed-off-by: neon <18559658+neonsolstice@users.noreply.github.com>
archiver v8 switched to pure ESM with no default export and a
completely different API, breaking every zip export request with
TypeError: (0 , _archiver.default) is not a function. The NestJS
SWC build produces CJS output, which cannot import the new ESM-only
package at runtime.
Downgrade to ~7.0.1 (the last CJS release) and add a Dependabot
ignore rule for archiver major bumps so this cannot recur silently.
Fixes#31