399 Commits

Author SHA1 Message Date
neon 936b907352 feat(series): add stack display mode for series thumbnails
Introduces a new 'stack' cover mode for series cards, rendering the covers
as a stacked pile. This mode has also been made the default for series card
display preferences. Settings UI is updated to allow users to toggle this
mode alongside existing mosaic and single-cover options.

Closes #223
2026-06-03 17:56:20 +00:00
neon efa300cecc fix(metadata): restore RanobeDB file metadata and links #207 #210
Preserve RanobeDB IDs through write-back, file import, and load-from-file so saved metadata survives round trips.

Expose saved RanobeDB sources in the book detail surfaces with the provider icon and database link.

Closes #207
Closes #210
2026-06-03 16:40:09 +00:00
Chris Mansell 06657a56a5 feat(books): write metadata directly to audiobook files (#219)
- Extend file write pipeline to write metadata fields directly into audiobook files.
- Map metadata fields to standard audio tag mappings and run ffmpeg to embed tags and cover art for M4B, M4A, MP3, and FLAC files.
- Update UI in edit metadata tab to show file write status and highlight written fields.

Handle edge cases in metadata extraction, file write-back status checks, and comic XML building. This ensures narrators are not incorrectly populated when they match the author/artist, preserves audibleId during chapter extraction, prevents whitespace issue numbers from breaking the XML, and improves the UI to clearly show why write-back is disabled.
2026-06-03 10:03:52 -06:00
neon 73795767da fix(series): use numeric ID for routing to support long titles
Transition from title-based to ID-based routing for series to prevent
404 errors when a series has an excessively long title. This introduces
numeric IDs for series, backed by schema updates, and updates the
frontend router to use seriesId instead of seriesName.

Closes #198
2026-06-02 18:53:14 +00:00
Chris Mansell d06e65a42b feat(audio): embed cover image in audiobook files (#197)
* feat(audio): embed cover image in audiobook files

- Add audio cover embedder
- Implement format writers for M4B, M4A, MP3, and FLAC using ffmpeg
- Add library settings for audio file writes

Closes #145

* fix(library): disable audio file write by default and add ffmpeg timeout

Change the default value of file_write_audio_enabled from true to false in the database schema and migrations. Automatically disable audio writing in the UI if cover writing is toggled off. Add a 60-second timeout to the ffmpeg child process to prevent hanging when embedding covers.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-06-02 12:40:27 -06:00
neon 2bdc88e2cc feat(dashboard): add up next in series to continue reading
Closes #162
2026-06-01 16:48:21 +00:00
Chris Mansell 53a08788bc feat(metadata): add RanobeDB as metadata provider (#185)
* feat(metadata): add RanobeDB metadata provider

Introduces a new metadata provider to fetch book metadata from RanobeDB.

- Adds `ranobedb.client` to interact with the RanobeDB API.
- Implements `ranobedb.mapper` to map RanobeDB data to the internal metadata schema.
  - Maps native Japanese titles to subtitles.
  - Uses the original language as a fallback for the language field.
  - Parses specific release dates to determine the original publication year.
- Registers RanobeDB as an available provider in the preferences UI.
- Adds `ranobedb_id` to the metadata database schema.
- Removes the `bookId` override when manually performing a metadata search in the UI.

Closes #114
2026-05-31 22:56:40 -06:00
neon 16e01f6b01 feat(settings): add option to show titles under grid cards (#181)
Add a user preference to configure the label displayed underneath book
and series cards in grid view. The option allows users to select
between hiding labels completely, showing book titles, or showing series
titles.

Closes #159
2026-05-31 19:37:28 -06:00
neon 7a1b0120b8 feat(series): add selectable cover modes for collapsed series cards
Allow readers to choose which representative series cover appears in collapsed grid view so browsing can prioritize first, latest, or first unread volumes.

Closes #160
2026-05-31 15:10:00 +00:00
Chris Mansell 26b3c8ba18 feat(settings): add adaptive cover display preferences (#169)
Allow users to choose how cover artwork is fitted so mixed aspect ratios render cleanly in cards, lists, carousels, and widgets.

Persist display preferences through shared types and user-preferences endpoints so these settings stay consistent across sessions and devices.

Closes #152
2026-05-30 18:57:29 -06:00
neon a1fe471e83 feat(settings): add theme persistence across devices
Introduce a user-preferences module and database schema to sync theme
settings (theme, accent, radius, background, brightness) across devices.
Users can now enable theme sync to persist their customization choices.

Closes #146
2026-05-30 17:27:08 +00:00
Chris Mansell 100334773c feat(metadata): add bulk metadata edit dialog (#164)
Add a dialog to batch-edit metadata for multiple selected books.

Support add/remove/replace/clear modes for list fields, with clear as an explicit
frontend-only mode.

Fix scalar input border rendering, year field validation/coercion, tags replace
optimistic updates, and submit error handling.

Closes #150
2026-05-30 11:21:27 -06:00
neon 6ffce7d7c8 feat(books): add manual write-and-rename action in metadata editor
allow users to trigger file write-back and rename from the edit metadata tab as a single explicit action.

return write and rename outcomes with library setting flags so the UI can show clear result and warning states.

Closes #143
2026-05-30 05:16:33 +00:00
neon 9b50b0ebb7 fix(cover): retain audiobook square covers in discovery carousels
Align recommendation data and rendering with per-book media format semantics.
Expose audiobook state from recommendation queries and render audiobook cards as square while preserving portrait and spine treatment for non-audiobooks.

Closes #149
2026-05-30 03:58:49 +00:00
neon 54814201b8 feat(metadata): add provider connection testing and token validation
Add UI and backend support for testing metadata provider credentials
before saving. Include parsing and validation for Bearer tokens,
display test results in the settings panel, and execute a test
connection specifically against the Hardcover provider.

Closes #127
2026-05-29 03:34:56 +00:00
Chris Mansell e659111e4e feat(library): add bulk rename tool and e2e coverage (#124)
* feat(library): add bulk rename based on file naming settings

Provides a way to rename all books in a library according to the user's
configured file naming template without requiring manual metadata edits.
Includes a streaming endpoint to report progress back to the frontend.

Closes #121

* test(library): add e2e tests for bulk file rename

Introduce a comprehensive E2E test suite for the bulk rename functionality to verify behavior across different library modes (book per file, book per folder) and handle edge cases like naming collisions or special characters.

Additionally fix a TypeScript compilation error in BulkRenameView.vue by replacing `replaceAll` with a regex-based `replace` for broader target compatibility.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-05-28 09:27:03 -06:00
neon 3a72ece26d fix(settings): gate integration tabs by integration permissions
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.
2026-05-26 19:52:32 +00:00
neon 1479989f74 feat(settings): add cross-platform path sanitization option
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
2026-05-25 05:17:17 +00:00
Chris Mansell 956a7a5e9a feat(users): add per-user tag/genre content restrictions (#83)
* 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
2026-05-22 11:01:11 -06:00
neon 75d9161962 feat(library): rename files on metadata update (#76)
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
2026-05-21 11:15:22 -06:00
neon 40bdd661a5 feat(books): add started/finished reading date support
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
2026-05-21 03:39:13 +00:00
neon 0b9027c275 feat(book-dock): expose Book Dock folder as a configurable drop zone (#64)
* 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.
2026-05-20 13:00:56 -06:00
Chris Mansell 418314352e feat: hardcover sync for per user reading status progress and rating (#53)
* feat(server): sync reading progress with Hardcover

* fix(server): update Hardcover sync pending state

* test(server): cover Hardcover admin and repository
2026-05-19 10:49:15 -06:00
neon 8abde907d8 feat(reader): add footer modes, wake lock, keyboard shortcuts, and go-to input
- 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
2026-05-18 22:02:49 -06:00
neon 25117e3d0b fix(download): enforce real file extension in pattern resolver 2026-05-18 17:46:20 -06:00
neon fabd1747d3 fix: add per-org upload pattern defaults for book_per_folder mode (#33)
* fix(settings): add per-org upload defaults for book_per_folder mode

* ci: add maintainer-gated PR GHCR image publish

---------

Signed-off-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-05-18 17:12:13 -06:00
neon 69f69febb2 feat(book): add highlights tab with filters, export, and pagination 2026-05-18 03:52:17 +00:00
neon 9f15d0955a feat(reading-log): implement end-to-end reading log feature 2026-05-17 01:42:41 -06:00
neon eb9b714ecb feat: add full-stack achievements system
- add achievement schema, migrations, evaluators, APIs, and backfill support on the server
- add achievements UI, composables, toast utilities, and tests on the client
- add shared achievement/auth/notification type updates
2026-05-16 15:50:42 +00:00
neon d1d0b7b2c1 feat(reader): add dictionary lookup to epub reader selection popup 2026-05-14 05:46:43 +00:00
neon f9c1e29911 feat: implement version UI and short-sha CI versioning 2026-05-13 22:24:46 +00:00
neon e3d82b53e5 fix(security): resolve 100 CodeQL alerts - sanitizeLogValue helper, kobo-proxy SSRF guard, duckduckgo URL parsing, dismiss false positives 2026-05-12 21:46:55 +00:00
neon 6a78df6597 feat(koreader): add KOReader sync with kosync protocol, device progress tracking, and reading position bridge 2026-05-12 03:36:00 +00:00
neon 31b3460658 feat(kobo): restore read-status thresholds, remove two-way progress sync, fix LAN dev sync, and add fallback fraction positioning for epub/cbz/pdf readers 2026-05-11 21:23:11 +00:00
neon 57d91ebea1 feat(cover,metadata): ffprobe extraction, cover placeholders, grid refresh, and audiobook search
- Replace music-metadata and mediainfo with @ffprobe-installer for all audio metadata extraction; remove mediainfo dependency
- Show BookCoverPlaceholder in all missing-cover locations: Similar/Series/Author Books carousel, cover editor panel, metadata search result cards, and metadata diff panel
- Extend BookRecommendation and SeriesBookRecommendation shared types with hasCover and authors; update recommendation and book repositories to supply them via parallel queries
- Fix cover refresh not updating in grid: BookCoverCard now merges refreshed BookDetail into the local BookCard and emits update:book; VirtualBookGrid forwards the event; Home, Series, Author, and Collection views wire the handler so covers update without a page reload
- Add audiobook covers checkbox to the Online Search drawer so users can toggle between square and portrait cover searches; result thumbnails and loading skeletons adapt their aspect ratio to match the selection
2026-05-11 00:06:05 +00:00
neon ce72cfd988 feat(entity-manager): pre-computed duplicate detection for large datasets
Replace the live self-join scan (O(N^2), times out at 100K+ authors) with a
background-computed candidates table served instantly at read time.

Key changes:
- Add entity_duplicate_candidates and entity_duplicate_scan_status tables
- Background job batches 1K authors at a time (~58s total for 200K authors)
- Scan endpoint reads from the indexed table (<10ms regardless of scale)
- Candidates are invalidated after merge/delete, not recomputed from scratch
- Add GET /duplicates/status and POST /duplicates/refresh endpoints
- Frontend updates clusters locally after merge/dismiss (no re-scan)
- Status bar shows last computed time and recompute button with progress
- Author browse in entity manager now shows all authors, not just those with books
2026-05-10 08:12:26 +00:00
neon 4e93e20bbb feat(books): add table view, metadata export, and filter builder 2026-05-01 23:59:37 -06:00
neon a41ce07834 feat(stats): add Speed trait and 4 new archetypes to Reading DNA widget 2026-04-28 13:50:26 -06:00
neon e6a18c0b50 feat(settings): add custom font upload with family grouping 2026-04-28 12:21:54 -06:00
neon c85379e4ff feat(auth): enforce edit restrictions on demo-restricted accounts 2026-04-25 23:34:57 -06:00
neon 739d311e9e chore: sync dependency branch with main 2026-04-25 21:35:27 -06:00
neon 46208fc82d feat(stats): add dashboard with stats, challenges, and streak widgets 2026-04-22 15:55:14 -06:00
neon cbb814827e feat(books): add discover, author, and series recommendation rows 2026-04-21 23:42:35 -06:00
neon cfc3672a6c refactor: rename book-bucket to book-dock and lens to smart-scope 2026-04-20 22:57:27 -06:00
neon ce6a439062 feat(server): add unified entity manager with duplicate detection
- Remove author insights and duplicate suggestions panels, composables, DTOs, and all backend support
- Add genre and tag duplicate detection, merge, and dismiss with server-side paginated scan results
- Consolidate per-entity-type tools into a single entity-manager module backed by the Strategy pattern
- Support browse, merge, rename, delete, split, and dismiss across all entity types (author, genre, tag, narrator, publisher, language, series)
- Replace old tools module with unified entity-manager controller, service, and repository; add 85+ unit tests
2026-04-20 19:10:47 -06:00
neon f2ede06ea2 feat(scanner): add incremental dir-mtime scanning with concurrency fix
Core performance overhaul - new library_dir_scan_state schema table persists per-directory
mtimes so unchanged directories are skipped entirely on subsequent scans, reducing filesystem
stat() calls from O(all-files) to O(changed-dirs) for large libraries.

Incremental scan (walk.ts / scanner.service.ts):
- New library_dir_scan_state table (bigint mtime, FK to library_folders ON DELETE CASCADE)
- findBookCandidates / findLooseFileCandidates now accept knownDirMtimes and return WalkResult
  (candidates + skippedDirs + unchangedDirs + dirMtimes) instead of bare BookCandidate[]
- Per-dir mtime skip with 3 safety rules: never skip disc dirs, never skip dirs with disc
  subdirs, never skip non-root dirs whose parent mtime changed (guards stem-named merging)
- Root directory now also skippable - parent-mtime check conditionally skipped for root since
  root-level files are never stem-merged (explicitly excluded in buildBookCandidates)
- Settings hash invalidation: SHA-256 of {allowedFormats, formatPriority, excludePatterns,
  organizationMode} forces full scan when settings change or on first scan after server restart
- isInUnchangedDir guard prevents false book-missing marks for skipped dirs
- Math.round() on stat().mtimeMs before storing - fixes bigint rejection of macOS fractional
  milliseconds (e.g. 1776138191093.9556) in PostgreSQL

Walk improvements:
- Bounded concurrency: subdirs processed in chunks of 50 to avoid EMFILE on deep trees
- format + role cached on FileStat to avoid re-classifying the same file twice
- statFilesIntoAcc helper extracted to deduplicate parallel stat logic
- skippedDirs set propagated up from permission-denied dirs

ScanJobStore:
- acquireStartLock / releaseStartLock prevent concurrent scan starts for the same library
- markPendingRescan / consumePendingRescan queue a follow-up scan if one arrives while running

waitForStability:
- Accepts optional knownMtimeMs to skip a redundant stat() call when mtime is already known

File event processor (Issue 25, 11, 20):
- Own-book restore: check file's own book before searching all missing books, fixing false
  book-moved results when an editor saves with a new inode (atomic-save pattern)
- Settings cache per library avoids repeated DB lookups during reconcile of many missing books
- Partial move guard: verify old file is gone before reassigning inode (multi-file books)

Live notifications (scanner.service.ts / file-watcher.service.ts):
- scanBookFolder now emits scan:books:added via existing WebSocket infrastructure so the
  frontend grid updates live when the file watcher adds a book (all 3 code paths covered)
- bufferWatcherNotification() with 30s debounce fires one ScanCompleted notification per
  library summarising watcher-driven additions and removals ('Novels updated - 3 added')
- Removals wired in FileWatcherService.process() and reconcile() on book-missing events
- findLibraryName() added to ScannerRepository for notification title lookup
2026-04-17 17:18:14 -06:00
neon 97207cd66f feat: implement scoped and mobile search in ViewHeader 2026-04-15 22:20:40 -06:00
neon cbaa3b0f1b feat(series): add series collapse with per-library preferences 2026-04-15 18:25:34 -06:00
neon 485bea5e5b feat: implement series browser with card deck visual style
- Add series browser page featuring browse and detail views
- Implement card deck cover design with horizontal spread and square aspect ratio
- Fix series-related bugs in decoding, state management, and data types
- Optimize responsive grid layout for series items
2026-04-15 08:55:25 -06:00
neon 40d829a8a9 feat(notifications): add notification center with real-time updates
- add notifications DB schema, repository, service, gateway, and controller
- add shared notification types, categories, and preference models
- add NotificationAccess permission with authorization matrix probe
- add frontend composable with Socket.IO subscription and REST API
- add NotificationPopover, NotificationItem, and NotificationPreferences components
- integrate notification producers in scanner, metadata fetch, book bucket, author enrichment, email, migration, and file write-back services
- add 30-day auto-cleanup via existing CleanupService cron
- add 29 backend unit tests for notification service and controller
2026-04-15 03:15:05 -06:00