Handle existing upload folder paths without creating duplicate books, tolerate partial RAR 1.5 archive reads, and clean up related production defaults and dashboard type safety.
- Switch EPUB provider identifiers to opf:scheme style (Calibre-compatible)
and teach the OPF parser to read both opf:scheme and legacy urn: formats
- Extract all provider IDs, rating, pageCount, and tags from EPUB on scan
- Validate publication years (1000-2200) across all metadata providers
- Guard SmartScope service against null filter to prevent runtime crash
- Replace window.confirm() in EditCollectionDialog with inline confirm state
- Fix useLiveScanBooks to apply book updates, not just prepend new arrivals
- Add toast feedback and fix route name on SmartScope/Collection delete
- Streamline onboarding tour order (left-to-right, no page jumps)
- 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
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
- 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
- install driver.js and add 9-step tour covering libraries, lenses,
collections, search, statistics, appearance, upload, and book bucket
- first step context-aware: points to welcome card on first run, falls
back to settings nav when libraries already exist
- tour state persisted in user.settings JSONB, survives across devices
- skip on mobile; any dismissal (skip or done) marks tour complete
- theme the tour popover with app design tokens (card, primary, radius)
- add "Take the tour again" button in Settings > Account (desktop only)
- implement OIDC provider and identity repositories
- allow linking/unlinking multiple OIDC identities to a single user
- update login UI to display and handle multiple providers
- add audit events for OIDC login and user provisioning
- improve callback error messages and state validation
- add OIDC provider schema and initial migration
- improve responsive layouts and controls across dashboard, home, lens, collection, authors, and book detail surfaces
- modernize settings and metadata provider UIs, including the floating dashboard settings action and cleaner section composition
- add synced or per-view cover sizing and spacing behavior, plus expanded mobile filter and sort control patterns
- extend backend filtering with library rules and increase hardcover ID limits across schema, DTO validation, and migration metadata