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>
This commit is contained in:
littlecodedragon 2026-06-30 13:56:33 +02:00
parent f178a79195
commit b299a9ce81
2 changed files with 136 additions and 1 deletions

View file

@ -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. - Long-click to add/remove single entry to/from library, everywhere.
- Docking Read/Resume button to left/right. - Docking Read/Resume button to left/right.
- In-app progress banner shows Library syncing / Backup restoring / Library updating progress. - 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. - Auto-install app update.
- Configurable interval to refresh entries from downloaded storage. - Configurable interval to refresh entries from downloaded storage.
- Forked from SY so everything from SY. - Forked from SY so everything from SY.

135
docs/reading-eta.md Normal file
View file

@ -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 | 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.