komikku/docs/reading-eta.md
littlecodedragon b299a9ce81 docs(reading-eta): document unified limits, long chapters, and settings
Capture library vs reader ETA behaviour, historical duplicate caps, tunable
preferences, and code map from the reading-eta work. Link from README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 13:56:33 +02:00

6.1 KiB
Raw Blame History

Reading time estimates (ETA)

Komikku shows estimated reading time in two places:

Surface What it shows
Library Time-left badge per entry (unread chapters × average chapter duration).
Reader Series progress ETA (remaining time for the current series, including the active chapter).

Both surfaces share the same limits and defaults via ReadingEtaPreferencesReadingEtaLimits. Tunable in Settings → Reader → Time estimate tuning (SettingsReaderScreen, group getReadingEtaTuningGroup).


Very long chapters (one entry = one huge chapter)

Reading itself has no artificial page cap. The reader uses the real totalPages from the loaded chapter. If a source exposes an entire volume as a single chapter, that is one DB chapter with many pages — normal behaviour.

Limits apply only to statistics and ETA, not to loading or turning pages:

  • Sessions shorter than ~2 s or longer than the configured max trusted session are ignored for speed stats.
  • When averaging read times, each chapter sample counts at most max minutes per chapter sample (default 45 min) so an idle tab does not inflate estimates.
  • History rows with more than max pages per history sample (default 2000) are skipped for page-rate priors (cross-series hints and ms/page blending).

For very long chapters, the reader ETA path that scales by remaining pages × ms/page can still show a long ETA if your real pace is slow — that is intentional. If estimates look too short, raise the stats/session caps in settings (e.g. 180 min sample cap, 1440 min trusted session, 5000+ pages sample).


Why there used to be two different logics

Before f178a7919, library and reader each duplicated constants in their ViewModels:

Constant Library (LibraryScreenModel) Reader (ReaderViewModel)
Max trusted session 1 h 4 h
Chapters sampled 60 20

Same idea (trim outliers, cap per-chapter duration for averages), but different numbers → inconsistent badges vs reader ETA on the same manga.

The reader additionally had a page-rate path (ReadingEtaPageRateEstimator + cross-manga history); the library used chapter-duration averages only. That asymmetry is by design (library has no “current page”), but the shared caps are now unified.


Architecture (current)

flowchart TB
    Prefs[ReadingEtaPreferences]
    Limits[ReadingEtaLimits]
    Lib[LibraryScreenModel]
    Reader[ReaderViewModel]
    Cross[GetCrossMangaPageRateHistorySamples]
    Est[ReadingEtaPageRateEstimator]

    Prefs --> Limits
    Limits --> Lib
    Limits --> Reader
    Limits --> Cross
    Cross --> Reader
    Est --> Reader
    Prefs -->|limitsFlow| Lib

Key types and files

Piece Path
Defaults (code constants) domain/.../reading/ReadingEtaDefaults.kt
Effective limits snapshot domain/.../reading/ReadingEtaLimits.kt
User preferences domain/.../reading/service/ReadingEtaPreferences.kt
Page-rate blending domain/.../reading/ReadingEtaPageRateEstimator.kt
Cross-series history samples domain/.../history/interactor/GetCrossMangaPageRateHistorySamples.kt
Library ETA app/.../library/LibraryScreenModel.kt
Reader series ETA app/.../reader/ReaderViewModel.kt
Settings UI app/.../settings/screen/SettingsReaderScreen.kt

ReadingEtaPreferences is registered in PreferenceModule; GetCrossMangaPageRateHistorySamples takes it via Injekt (DomainModule).


User settings

Preference keys (defaults in parentheses):

Setting Key Default Slider range (UI)
Max minutes per chapter sample pref_reading_eta_max_stats_min 45 5180
Max trusted session length pref_reading_eta_max_trusted_session_min 240 (4 h) 601440
Chapters sampled for averages pref_reading_eta_sample_chapter_limit 60 5100
Max pages per history sample pref_reading_eta_max_pages_sample 2000 100050000 (slider 10500 × 100)

ReadingEtaLimits.fromRaw enforces:

  • Trusted session ≥ stats cap (minutes).
  • Sample chapter count ∈ [5, 200].
  • Max pages ∈ [100, 50_000].

Strings: i18n-kmk/.../base/strings.xml and de/strings.xml (pref_reading_eta_*).

Reader-only toggles (unchanged): Show series ETA, Persist series ETA (ReaderPreferences).


How estimates are computed

Library badge

  1. Load per-manga chapter read durations from history.
  2. Filter to trusted session range; cap each sample at max chapter duration for stats.
  3. Take the last N chapters (etaSampleChapterLimit), trim top/bottom 10% (when enough samples), average.
  4. Multiply by unread count (or 1 when totalChapters == 1).

Falls back to a global average across the visible library when a title has no usable samples.

Reader series ETA

Two paths; the UI uses page-based ETA when available, else chapter-based:

  1. Page-based (calculatePageBasedEtaMillis): ms/page from recent chapters in this series + optional blend with cross-manga priors (ReadingEtaPageRateEstimator). Projects remainingPages × ms/page plus whole chapters ahead using average pages/chapter.
  2. Chapter-based: average/projected chapter duration × remaining chapters, with projection from current session or progress.

Cross-manga priors: up to CROSS_MANGA_PAGE_RATE_SAMPLE_LIMIT (500) recent completed chapters from other series, preferring similar page counts (±15%, then ±30% band).


Release

Shipped in tag v1.13.6-forgejo.38 — commit feat(reading-eta): unify library and reader limits; make caps tunable.


Maintainer notes

  • When changing defaults, update ReadingEtaDefaults, ReadingEtaPreferences default getters, and the settings slider ranges if needed.
  • Keep library and reader on readingEtaPreferences.currentLimits() / limitsFlow() — do not reintroduce local MIN_TRACKED_* constants in ViewModels.
  • Page caps affect stats only; do not use them to limit UpsertHistory unless product intent changes.