Commit Graph

374 Commits

Author SHA1 Message Date
Neon ff3f99e613 docs: overhaul README and update repo to bookorbit org (#9) 2026-05-12 00:54:43 -06:00
dependabot[bot] c30522d0e3 chore(deps): bump archiver from 7.0.1 to 8.0.0 (#6)
Bumps [archiver](https://github.com/archiverjs/node-archiver) from 7.0.1 to 8.0.0.
- [Release notes](https://github.com/archiverjs/node-archiver/releases)
- [Changelog](https://github.com/archiverjs/node-archiver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/archiverjs/node-archiver/compare/7.0.1...8.0.0)

---
updated-dependencies:
- dependency-name: archiver
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 00:51:03 -06:00
dependabot[bot] e1cdc16245 chore(deps): bump actions/dependency-review-action (#3)
Bumps the github-actions group with 1 update: [actions/dependency-review-action](https://github.com/actions/dependency-review-action).


Updates `actions/dependency-review-action` from 4.9.0 to 5.0.0
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/2031cfc080254a8a887f58cffee85186f0e49e48...a1d282b36b6f3519aa1f3fc636f609c47dddb294)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-12 00:46:52 -06:00
neon 6cc9867a63 fix: remove KoreaderBookSyncCard, fix missing initial library load, add mediaSrc CSP, return null from getBookProgress instead of 404 2026-05-12 05:56:38 +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 6b650981da fix(docker): rebuild kepubify with Go 1.26.3, fix read-only fs chmod
Replaced pre-compiled kepubify binaries (built with Go 1.17.8, 53
HIGH/CRITICAL CVEs) with binaries rebuilt from source using Go 1.26.3
and golang.org/x/text upgraded to v0.3.8, resolving all fixable CVEs.

Also fixed KepubifyBinaryService incorrectly falling back to downloading
the binary on every startup in read-only containers. chmod() was throwing
EROFS, and the single catch block treated it as a missing file. Split
stat and chmod into separate try/catch so only a missing-file error
triggers the download fallback.
2026-05-11 18:42:18 +00:00
neon 6e68937944 fix(kobo): replace unnest array cast with VALUES in reconcileSnapshot bulk insert 2026-05-11 16:42:42 +00:00
neon 3fd439289d fix(auth): surface rate-limit error on login and remove dropped background patterns
Show a clear too-many-attempts message on the login page when the server
throttles the request. Remove the nova and golden-ratio background options,
whose CSS definitions were already deleted.
2026-05-11 16:42:38 +00:00
neon 681f684dff fix(reader,dashboard): handle audiobook progress reliably 2026-05-11 00:08:58 -06:00
neon 4fd91b6d1f fix(client): adjust cover defaults and metadata menu/layout 2026-05-10 23:52:15 -06:00
neon 35100a0fd0 fix(demo): block reader storage mode change and font edits for demo accounts 2026-05-11 05:15:43 +00:00
neon c881f7ee97 fix(library): include coverAspectRatio in findAllForUser response 2026-05-11 04:52:42 +00:00
neon 3eed6ee695 fix(user): allow demo accounts to update UI settings via PATCH /users/me/settings 2026-05-11 04:31:39 +00:00
neon 98d21166f5 fix(book-dock): use file path as folderPath when finalizing into a book_per_file library
In book_per_file mode the scanner identifies books by the file's absolute
path (not its parent directory). The finalize service was always using
dirname(destPath) regardless of organization mode, so the scanner would
see the freshly-moved file as unknown, create a second book record, and
then steal the book_file from the first record via the onConflictDoUpdate
upsert - leaving the original book as a ghost with a stale primary_file_id.

Fix: check library.organizationMode after fetching the library and pass
destPath directly as bookFolderPath when the mode is book_per_file.

Add a dedicated test case that asserts createBookRecord receives the file
path (not its parent dir) in book_per_file mode.
2026-05-11 03:48:30 +00:00
neon 8e8b8ae303 fix(server): resolve APP_DATA_PATH to an absolute path at startup
When APP_DATA_PATH is set to a relative path (e.g. ../local/data), the
stored absolutePath in book_dock_files was relative. The file watcher
(@parcel/watcher) always reports events with fully resolved absolute
paths, so findByAbsolutePath never matched an uploaded file, causing the
watcher to ingest it again as a duplicate record.
2026-05-11 03:26:12 +00:00
neon 4a53a9250e fix(audio): replace npm ffprobe/ffmpeg installers with system binaries to fix Alpine/musl incompatibility in Docker 2026-05-11 00:43:44 +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 ea998b8bd2 fix(scanner): batch book emitting 2026-05-10 16:33:29 -06:00
neon cdb9d999d8 fix(client): proxy external images through cover proxy for CSP 2026-05-10 15:30:47 -06: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 e59608ceb3 feat(reader,ui): improve mobile reader ergonomics and touch handling
- overhaul EPUB/CBZ/PDF mobile header, footer, toolbar, sidebar, and settings interactions
- add EPUB search minimum query length enforcement with updated unit tests
- improve PDF rendering stability by drawing from an offscreen render canvas
- add app-shell level horizontal overflow/touch constraints to prevent sideways pan and double-tap zoom issues
- make statistics header controls and configure drawer responsive on mobile
2026-05-09 23:48:39 -06:00
neon 6669c4cb51 feat(ui): unify mobile controls and polish authors/series/dashboard UX
- refactor ViewHeader into reusable desktop/mobile subcomponents (desktop search, display controls, mobile menu, mobile search sheet)
- standardize mobile search/sort/filter behavior across Home, Collection, Smart Scope, Authors, and Series
- fix duplicate search clear icons by suppressing native search cancel controls where custom clear buttons are used
- move Authors and Series into contained scroll regions and tighten mobile paddings/margins for better header alignment
- fix author card count/kebab z-index overlap with sticky headers
- clamp author bio to 2 lines on mobile with show more/less toggle
- simplify Series filters by removing separate author filter and support searching series by title or author name
- update series repository query logic to include author-name matching in `q` search
- improve mobile sheet sizing for Customize Dashboard and Notifications; add compact personalized dashboard greeting
- remove sidebar trigger tooltip and prevent action-bar icon/button shrink issues
2026-05-09 21:59:25 -06:00
neon 385b4f12b1 feat: enhance metadata UX and harden backend behavior
- add local provider icon assets and a shared providerIconPath helper for metadata providers
- switch provider links in book details and quick view to use bundled SVG icons
- proxy external metadata cover URLs through /api/v1/books/cover/proxy for display while keeping same-origin/data/blob URLs untouched
- apply proxied display cover URLs in metadata diff/result UI and Book Dock surfaces
- keep raw provider cover URLs in metadata patches and add composable tests for proxy behavior
- add optional CSP_ALLOW_CLOUDFLARE_INSIGHTS env support and document it in server/root env examples and README
- wire conditional Cloudflare Insights script/connect sources into CSP directives
- remove pino HTTP customProps context injection to avoid duplicated context labels
- reduce file-write log noise by using debug level for write start and auto-skipped completions
- tune entity-manager duplicate scans by setting pg_trgm similarity_threshold per query/transaction to align with minSimilarity and prevent scan blowups
2026-05-09 18:03:12 -06:00
neon 838033717c fix(ci,books): resolve GHCR/CVE issues and clean pin/unpin fallbacks
- regenerate lockfile after package version alignment
- remove useless fallback spreads in pin and unpin column
- resolve GHCR push auth and fast-uri CVE
- patch fast-xml-builder CVE in image scan
2026-05-08 19:10:03 -06:00
neon 4e93e20bbb feat(books): add table view, metadata export, and filter builder 2026-05-01 23:59:37 -06:00
neon 93ab36a56a fix(stats): use user ratings for neglected gems 2026-04-28 13:59:29 -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 006c2c4948 fix(stats): count monthly challenge pages for books without page count 2026-04-28 12:53:33 -06:00
neon e6a18c0b50 feat(settings): add custom font upload with family grouping 2026-04-28 12:21:54 -06:00
neon de7a61888b ci: consolidate release workflow and automate release notes
- Hardened multi-arch container release flow with build-once promotion and safer image gating.
- Reworked semantic-release execution, permissions, and action pinning for more reliable releases.
- Expanded release notes transformation with contributor and artifact metadata, plus cleaner formatting.
- Fixed edge cases in notes rendering by preserving commit metadata and guarding author formatting.
- Removed redundant release notes sections to keep generated output concise and consistent.
2026-04-27 08:21:15 -06:00
neon 812efcd1be ci: fix PR token permissions and gate dependency review 2026-04-26 19:15:56 -06:00
neon 8ce315a7d0 ci: add semantic-release workflow and docs 2026-04-26 19:03:03 -06:00
neon c85379e4ff feat(auth): enforce edit restrictions on demo-restricted accounts 2026-04-25 23:34:57 -06:00
neon 1cd1291a1f chore(deps): fix pnpm lock file 2026-04-25 21:46:40 -06:00
neon 739d311e9e chore: sync dependency branch with main 2026-04-25 21:35:27 -06:00
neon 2b99684d7e chore(deps): bump production dependencies across 10 packages
Bumps the production-dependencies group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@vueuse/core](https://github.com/vueuse/vueuse/tree/HEAD/packages/core) | `14.2.1` | `14.3.0` |
| [dompurify](https://github.com/cure53/DOMPurify) | `3.4.1` | `3.4.2` |
| [pdfjs-dist](https://github.com/mozilla/pdf.js) | `5.6.205` | `5.7.284` |
| [vue](https://github.com/vuejs/core) | `3.5.32` | `3.5.33` |
| [vue-router](https://github.com/vuejs/router) | `5.0.4` | `5.0.6` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.7.1` | `5.7.2` |
| [jose](https://github.com/panva/jose) | `6.2.2` | `6.2.3` |
| [mysql2](https://github.com/sidorares/node-mysql2) | `3.22.2` | `3.22.3` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.5` | `8.0.7` |
| [zod](https://github.com/colinhacks/zod) | `4.3.6` | `4.4.1` |



Updates `@vueuse/core` from 14.2.1 to 14.3.0
- [Release notes](https://github.com/vueuse/vueuse/releases)
- [Commits](https://github.com/vueuse/vueuse/commits/v14.3.0/packages/core)

Updates `dompurify` from 3.4.1 to 3.4.2
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.1...3.4.2)

Updates `pdfjs-dist` from 5.6.205 to 5.7.284
- [Release notes](https://github.com/mozilla/pdf.js/releases)
- [Commits](https://github.com/mozilla/pdf.js/compare/v5.6.205...v5.7.284)

Updates `vue` from 3.5.32 to 3.5.33
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.5.32...v3.5.33)

Updates `vue-router` from 5.0.4 to 5.0.6
- [Release notes](https://github.com/vuejs/router/releases)
- [Commits](https://github.com/vuejs/router/compare/v5.0.4...v5.0.6)

Updates `fast-xml-parser` from 5.7.1 to 5.7.2
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.7.1...v5.7.2)

Updates `jose` from 6.2.2 to 6.2.3
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.2...v6.2.3)

Updates `mysql2` from 3.22.2 to 3.22.3
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.22.2...v3.22.3)

Updates `nodemailer` from 8.0.5 to 8.0.7
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.5...v8.0.7)

Updates `zod` from 4.3.6 to 4.4.1
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.3.6...v4.4.1)

---
updated-dependencies:
- dependency-name: "@vueuse/core"
  dependency-version: 14.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: dompurify
  dependency-version: 3.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: fast-xml-parser
  dependency-version: 5.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: jose
  dependency-version: 6.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: mysql2
  dependency-version: 3.22.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: nodemailer
  dependency-version: 8.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: pdfjs-dist
  dependency-version: 5.7.284
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: vue
  dependency-version: 3.5.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: vue-router
  dependency-version: 5.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: zod
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...
2026-04-25 21:30:37 -06:00
neon eec2b30b95 chore(deps): bump the development-dependencies group with 7 updates
Bumps the development-dependencies group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [@vue/test-utils](https://github.com/vuejs/test-utils) | `2.4.6` | `2.4.10` |
| [eslint-plugin-oxlint](https://github.com/oxc-project/eslint-plugin-oxlint) | `1.61.0` | `1.62.0` |
| [jsdom](https://github.com/jsdom/jsdom) | `29.0.2` | `29.1.1` |
| [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.61.0` | `1.62.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.9` | `8.0.10` |
| [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core) | `1.15.30` | `1.15.32` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.0` | `8.59.1` |


Updates `@vue/test-utils` from 2.4.6 to 2.4.10
- [Release notes](https://github.com/vuejs/test-utils/releases)
- [Commits](https://github.com/vuejs/test-utils/compare/v2.4.6...v2.4.10)

Updates `eslint-plugin-oxlint` from 1.61.0 to 1.62.0
- [Release notes](https://github.com/oxc-project/eslint-plugin-oxlint/releases)
- [Commits](https://github.com/oxc-project/eslint-plugin-oxlint/compare/v1.61.0...v1.62.0)

Updates `jsdom` from 29.0.2 to 29.1.1
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.0.2...v29.1.1)

Updates `oxlint` from 1.61.0 to 1.62.0
- [Release notes](https://github.com/oxc-project/oxc/releases)
- [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md)
- [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.62.0/npm/oxlint)

Updates `vite` from 8.0.9 to 8.0.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

Updates `@swc/core` from 1.15.30 to 1.15.32
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/commits/v1.15.32/packages/core)

Updates `typescript-eslint` from 8.59.0 to 8.59.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@vue/test-utils"
  dependency-version: 2.4.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint-plugin-oxlint
  dependency-version: 1.62.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: jsdom
  dependency-version: 29.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: oxlint
  dependency-version: 1.62.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: vite
  dependency-version: 8.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@swc/core"
  dependency-version: 1.15.32
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...
2026-04-25 18:40:08 -06:00
neon ac1b2cedb4 fix: harden uploads and CBR archive handling
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.
2026-04-24 08:25:38 -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 f0787914b7 style(client): standardize card and dialog corner radius
apply rounded-lg consistently across client cards, dialogs, and surface containers to keep UI shape language uniform
2026-04-21 20:20:46 -06:00
neon deb2b6d92c fix: epub identifier round-trip, metadata extraction, and misc UI bugs
- 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)
2026-04-21 17:04:07 -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 9ee9d14d94 feat(client): update book cover placeholder with vibrant lattice design 2026-04-20 05:13:32 -06:00
neon 9f658c3de7 feat(reader): improve audiobook UI with skip bubbles and custom scrubber 2026-04-20 01:55:42 -06:00
neon 8638b19a4c feat(client): enhance book cover placeholder with adaptive SVG design 2026-04-19 23:02:52 -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 d25c51fbcc docs: overhaul development, contributor, testing, and commit guidelines 2026-04-17 04:09:45 -06:00