Problem
The Stats page and Goals page use different logic for counting completed books, leading to potential count mismatches when orphaned books (books removed from Calibre but preserved in Tome) have reading sessions.
Stats Page
Uses sessionRepository methods that INNER JOIN books and filter orphaned = false OR IS NULL:
countByStatus() — total books read, currently reading count
countCompletedByYear() — books read this year
countCompletedByYearMonth() — books read this month
Result: Orphaned books are excluded from all counts.
Goals Page
Uses readingGoalRepository methods that query reading_sessions directly with no book join:
getBooksCompletedInYear() — yearly goal progress
getBooksCompletedByMonth() — monthly breakdown
getBooksByCompletionYear() — joins books for display but does not filter orphans
Result: Orphaned books are included in all counts.
Inconsistency
If a user completes a book and it later becomes orphaned (removed from Calibre), the Stats page will show a lower count than the Goals page for the same time period.
Current Impact
Low — all 34 orphaned books in the current database have to-read status with no completed sessions, so the counts match today. This becomes a real issue if a user completes a book and then removes it from their Calibre library.
Considerations
- The constitution principle of preserving reading history suggests orphans should be counted (the reading happened)
- However, this is a product-level decision with trade-offs in both directions
- Page read counts (progress repository) already include orphans everywhere since they query
progress_logs directly
- Book lists (currently reading, read-next) should likely continue excluding orphans regardless, since those are actionable views
Relevant Code
lib/repositories/session.repository.ts — countByStatus(), countCompletedByYear(), countCompletedByYearMonth()
lib/repositories/reading-goals.repository.ts — getBooksCompletedInYear(), getBooksCompletedByMonth(), getBooksByCompletionYear()
lib/services/reading-stats.service.ts — getOverview() (Stats page)
lib/services/reading-goals.service.ts — Goals page service
Related
Part of the #349 investigation. Identified during code review of PR #353.
Problem
The Stats page and Goals page use different logic for counting completed books, leading to potential count mismatches when orphaned books (books removed from Calibre but preserved in Tome) have reading sessions.
Stats Page
Uses
sessionRepositorymethods thatINNER JOIN booksand filterorphaned = false OR IS NULL:countByStatus()— total books read, currently reading countcountCompletedByYear()— books read this yearcountCompletedByYearMonth()— books read this monthResult: Orphaned books are excluded from all counts.
Goals Page
Uses
readingGoalRepositorymethods that queryreading_sessionsdirectly with no book join:getBooksCompletedInYear()— yearly goal progressgetBooksCompletedByMonth()— monthly breakdowngetBooksByCompletionYear()— joins books for display but does not filter orphansResult: Orphaned books are included in all counts.
Inconsistency
If a user completes a book and it later becomes orphaned (removed from Calibre), the Stats page will show a lower count than the Goals page for the same time period.
Current Impact
Low — all 34 orphaned books in the current database have
to-readstatus with no completed sessions, so the counts match today. This becomes a real issue if a user completes a book and then removes it from their Calibre library.Considerations
progress_logsdirectlyRelevant Code
lib/repositories/session.repository.ts—countByStatus(),countCompletedByYear(),countCompletedByYearMonth()lib/repositories/reading-goals.repository.ts—getBooksCompletedInYear(),getBooksCompletedByMonth(),getBooksByCompletionYear()lib/services/reading-stats.service.ts—getOverview()(Stats page)lib/services/reading-goals.service.ts— Goals page serviceRelated
Part of the #349 investigation. Identified during code review of PR #353.