From b299a9ce8163d1692526d72413c81dd7fd38ab47 Mon Sep 17 00:00:00 2001 From: littlecodedragon <71352143+AugenAugenAugen@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:56:33 +0200 Subject: [PATCH] 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 --- README.md | 2 +- docs/reading-eta.md | 135 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 docs/reading-eta.md diff --git a/README.md b/README.md index aaab159fd..5f541d0e9 100755 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ A free and open source manga reader which is based off TachiyomiSY & Mihon/Tachi - Long-click to add/remove single entry to/from library, everywhere. - Docking Read/Resume button to left/right. - In-app progress banner shows Library syncing / Backup restoring / Library updating progress. -- `Series ETA` / reading time estimate in the reader, with optional persistence across sessions. +- `Series ETA` / reading time estimate in the reader, with optional persistence across sessions; library time-left badges use the same tuning. See [docs/reading-eta.md](docs/reading-eta.md). - Auto-install app update. - Configurable interval to refresh entries from downloaded storage. - Forked from SY so everything from SY. diff --git a/docs/reading-eta.md b/docs/reading-eta.md new file mode 100644 index 000000000..3195c3c8a --- /dev/null +++ b/docs/reading-eta.md @@ -0,0 +1,135 @@ +# 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 `ReadingEtaPreferences` → `ReadingEtaLimits`. 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) + +```mermaid +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 | 5–180 | +| Max trusted session length | `pref_reading_eta_max_trusted_session_min` | 240 (4 h) | 60–1440 | +| Chapters sampled for averages | `pref_reading_eta_sample_chapter_limit` | 60 | 5–100 | +| Max pages per history sample | `pref_reading_eta_max_pages_sample` | 2000 | 1000–50000 (slider 10–500 × 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.