fix: continuous reading does not shift as pages load #171

Merged
bjoern merged 9 commits from fix/continuous-reading-does-not-shift-as-pages-load into main 2026-09-19 21:27:33 +02:00
Member

What's in

  • Reading a long work in continuous mode no longer jumps under the reader's eyes: every page whose stored image dimensions are known reserves its own box from the first frame (height = strip width × page.height / page.width), so the spinner, the broken-page icon and the bitmap all occupy the same box and nothing below a page moves when its bitmap arrives.
  • A page whose image row carries a 0 for either side (unprobed, or a failed probe) falls back to today's one-viewport-height reserve for that page alone and still lays out; the counter, slider, chapter jump, reading progress and ?page=N keep following the dominant page.
  • The seek estimator sums the exact reserved heights of the prefix and guesses only the pages without dimensions (averaging over the dimensionless built pages, not all of them), so a seek over known heights lands in one jump. The correction chain is deliberately kept and still reachable — a dimensionless row makes the estimate a guess again, and a lazy list's extrapolated maxScrollExtent can clamp a long jump short.
  • The loading and failed states stay in the first screenful of a box that can be several viewports tall: the placeholder is Align(topCenter, SizedBox(height: viewportHeight, Center(child))), so the reserved box keeps its true height while the indicator stays on screen. A single source for the strip's width (_continuousPageWidth, published by the LayoutBuilder) feeds both the reserved boxes and the estimator, so the two cannot drift if the strip ever stops filling its box.
  • No ADR amendment, no new endpoint, no new tunable, no EF migration, no wire or codegen change. The premise was checked first: ImageFile.Width/Height, PageDetailDto's width/height and the app's Page model already carried the dimensions, so this is app-only — and the old backlog item's claim that "the server stores none" was wrong and is corrected where it stood. Files touched: app/lib/presentation/pages/reader/reader_page.dart, app/test/reader_page_test.dart, one README.md sentence, docs/BACKLOG.md.

Verification

Executed at this gate, fresh from the worktree: scripts/verify.sh app — submodule check, flutter pub get, build_runner clean + build with the generated-file checksum guard, flutter analyze, flutter test. Result: All tests passed! (1241 tests), == verify OK (app) ==. scripts/verify.sh backend was not run; the branch changes no backend file.

Layers exercised: UI only (Flutter widget tests over the reader page, app/test/reader_page_test.dart, 70 tests in that file; app/test/offline_reader_test.dart was run alongside during development as readerImageProvider's other caller, 80 green together). REST, use case and persistence are untouched and were exercised only insofar as the existing suite already covers them — there is no server-side change to verify.

Mutation probes run during development against the committed tree (each applied, the whole reader file run, then git checkout -- with git status --porcelain confirmed empty). Against the final tree at f17e415, all of these are red:

  • R — _reservedHeightFor returns 800.0 * h / w (hardcoding the harness's viewport width): +69 −1, only "the reserve follows the strip's width, not the window the other tests run in" (Expected: 1800.0 (±0.5) / Actual: <1200.0>). This probe was green on all 67 tests in round 1 and is red only because that test was added.
  • W — _continuousPageWidth = 800.0 in the LayoutBuilder while the item builder keeps constraints.maxWidth (only the estimator's width wrong): +69 −1, same test, Actual: [1600.0, 2400.0].
  • L — drop the reserved SizedBox around a loaded page: +62 −8, including "nothing below a page moves when its bitmap arrives", which was green under this exact mutation in round 1; the fixture now delivers a 10×30 bitmap against a 1000×1500 row, so holding the box and re-sizing to the bitmap differ.
  • S / S3 — placeholder back to SizedBox(height: reserved ?? viewportHeight, Center(child)), and separately dropping only the Align: +69 −1 each, only "what stands in for a page stays in the first screenful of a box taller than the window".
  • V — _unknownPageHeight returns viewportDimension unconditionally: +69 −1, only "a strip of dimensionless pages is estimated from the heights they turned out to have". Also green on all 67 in round 1.
  • G — BoxFit.contain → BoxFit.fitWidth: +69 −1, only "a bitmap that disagrees with its row is fitted inside the reserved box, not cropped to it".
  • J — remove the dimensionful filter from _unknownPageHeight's average: +69 −1, "a page whose row has no dimensions keeps the viewport-high reserve". O — revert _estimatedOffsetFor to index × _unknownPageHeight(null): +67 −3.

Honest non-pins and what remains unverified:

  • Nothing ran in the real app against a live server. No manual pass over a real variant, no real window resize, no visual check of the loading or broken-page state. The widened-viewport case is pinned only through tester.view.physicalSize in the harness.
  • Swapping _continuousPageWidth back for the measuring box's laid-out width inside _estimatedOffsetFor would still be green, because the ListView fills the LayoutBuilder today; that half of the change is structural (one decision point for the width) and is not separately observable. Probe W is what pins the number the field carries.
  • Two green mutants judged equivalent rather than untested: E, dropping the unknown == 0 ? short-circuit (offset + 0 × x is offset), and N, dropping the width == null / viewportWidth != null guards in favour of ! — no null assertion is thrown in any of the 70 tests, so those guards are not separately pinned.
  • The rewritten BoxFit comment and the README sentence were confirmed by reading only; they carry no test.
  • Reader tests default to pageSizes: const {}, so the other 61 reader tests still see the 0×0 rows and the dimensionless path they were written against — the new behaviour is covered by the new cases, not by re-purposed old ones.

Follow-ups

  • docs/BACKLOG.md: the old continuous-reader item is replaced — nothing backfills or re-probes image rows whose stored width/height is 0, so those pages still reserve one viewport height and shift what is below them once when the bitmap arrives.
  • docs/BACKLOG.md (same item): the paged modes (single, spread) ignore the stored dimensions entirely — a spread could reserve each half's box and size its placeholder instead of centring a spinner over the whole window.
  • No EF migration on this branch, so there is no model-snapshot ordering constraint against a sibling branch in this batch.

🤖 Generated with Claude Code

## What's in - Reading a long work in continuous mode no longer jumps under the reader's eyes: every page whose stored image dimensions are known reserves its own box from the first frame (height = strip width × page.height / page.width), so the spinner, the broken-page icon and the bitmap all occupy the same box and nothing below a page moves when its bitmap arrives. - A page whose image row carries a 0 for either side (unprobed, or a failed probe) falls back to today's one-viewport-height reserve for that page alone and still lays out; the counter, slider, chapter jump, reading progress and `?page=N` keep following the dominant page. - The seek estimator sums the exact reserved heights of the prefix and guesses only the pages without dimensions (averaging over the *dimensionless* built pages, not all of them), so a seek over known heights lands in one jump. The correction chain is deliberately kept and still reachable — a dimensionless row makes the estimate a guess again, and a lazy list's extrapolated `maxScrollExtent` can clamp a long jump short. - The loading and failed states stay in the first screenful of a box that can be several viewports tall: the placeholder is `Align(topCenter, SizedBox(height: viewportHeight, Center(child)))`, so the reserved box keeps its true height while the indicator stays on screen. A single source for the strip's width (`_continuousPageWidth`, published by the `LayoutBuilder`) feeds both the reserved boxes and the estimator, so the two cannot drift if the strip ever stops filling its box. - No ADR amendment, no new endpoint, no new tunable, no EF migration, no wire or codegen change. The premise was checked first: `ImageFile.Width`/`Height`, `PageDetailDto`'s `width`/`height` and the app's `Page` model already carried the dimensions, so this is app-only — and the old backlog item's claim that "the server stores none" was wrong and is corrected where it stood. Files touched: `app/lib/presentation/pages/reader/reader_page.dart`, `app/test/reader_page_test.dart`, one `README.md` sentence, `docs/BACKLOG.md`. ## Verification Executed at this gate, fresh from the worktree: **`scripts/verify.sh app`** — submodule check, `flutter pub get`, `build_runner clean` + `build` with the generated-file checksum guard, `flutter analyze`, `flutter test`. Result: `All tests passed!` (1241 tests), `== verify OK (app) ==`. `scripts/verify.sh backend` was **not** run; the branch changes no backend file. Layers exercised: **UI only** (Flutter widget tests over the reader page, `app/test/reader_page_test.dart`, 70 tests in that file; `app/test/offline_reader_test.dart` was run alongside during development as `readerImageProvider`'s other caller, 80 green together). **REST, use case and persistence are untouched and were exercised only insofar as the existing suite already covers them** — there is no server-side change to verify. Mutation probes run during development against the committed tree (each applied, the whole reader file run, then `git checkout --` with `git status --porcelain` confirmed empty). Against the final tree at `f17e415`, all of these are **red**: - **R** — `_reservedHeightFor` returns `800.0 * h / w` (hardcoding the harness's viewport width): +69 −1, only *"the reserve follows the strip's width, not the window the other tests run in"* (`Expected: 1800.0 (±0.5) / Actual: <1200.0>`). This probe was **green on all 67 tests in round 1** and is red only because that test was added. - **W** — `_continuousPageWidth = 800.0` in the `LayoutBuilder` while the item builder keeps `constraints.maxWidth` (only the estimator's width wrong): +69 −1, same test, `Actual: [1600.0, 2400.0]`. - **L** — drop the reserved `SizedBox` around a loaded page: +62 −8, including *"nothing below a page moves when its bitmap arrives"*, which was **green under this exact mutation in round 1**; the fixture now delivers a 10×30 bitmap against a 1000×1500 row, so holding the box and re-sizing to the bitmap differ. - **S** / **S3** — placeholder back to `SizedBox(height: reserved ?? viewportHeight, Center(child))`, and separately dropping only the `Align`: +69 −1 each, only *"what stands in for a page stays in the first screenful of a box taller than the window"*. - **V** — `_unknownPageHeight` returns `viewportDimension` unconditionally: +69 −1, only *"a strip of dimensionless pages is estimated from the heights they turned out to have"*. Also **green on all 67 in round 1**. - **G** — `BoxFit.contain` → `BoxFit.fitWidth`: +69 −1, only *"a bitmap that disagrees with its row is fitted inside the reserved box, not cropped to it"*. - **J** — remove the dimensionful filter from `_unknownPageHeight`'s average: +69 −1, *"a page whose row has no dimensions keeps the viewport-high reserve"*. **O** — revert `_estimatedOffsetFor` to `index × _unknownPageHeight(null)`: +67 −3. Honest non-pins and what remains unverified: - **Nothing ran in the real app against a live server.** No manual pass over a real variant, no real window resize, no visual check of the loading or broken-page state. The widened-viewport case is pinned only through `tester.view.physicalSize` in the harness. - Swapping `_continuousPageWidth` back for the measuring box's laid-out width inside `_estimatedOffsetFor` would still be **green**, because the `ListView` fills the `LayoutBuilder` today; that half of the change is structural (one decision point for the width) and is not separately observable. Probe **W** is what pins the number the field carries. - Two green mutants judged equivalent rather than untested: **E**, dropping the `unknown == 0 ?` short-circuit (`offset + 0 × x` is `offset`), and **N**, dropping the `width == null` / `viewportWidth != null` guards in favour of `!` — no null assertion is thrown in any of the 70 tests, so those guards are not separately pinned. - The rewritten `BoxFit` comment and the README sentence were confirmed **by reading only**; they carry no test. - Reader tests default to `pageSizes: const {}`, so the other 61 reader tests still see the 0×0 rows and the dimensionless path they were written against — the new behaviour is covered by the new cases, not by re-purposed old ones. ## Follow-ups - docs/BACKLOG.md: the old continuous-reader item is replaced — nothing backfills or re-probes image rows whose stored width/height is 0, so those pages still reserve one viewport height and shift what is below them once when the bitmap arrives. - docs/BACKLOG.md (same item): the paged modes (single, spread) ignore the stored dimensions entirely — a spread could reserve each half's box and size its placeholder instead of centring a spinner over the whole window. - No EF migration on this branch, so there is no model-snapshot ordering constraint against a sibling branch in this batch. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Each page in the continuous strip reserves the box its stored image dimensions give it at the viewport's width, so it holds its place while it loads and nothing below it moves when the bitmap arrives. A row without usable dimensions keeps the old viewport-height reserve. The seek estimate now sums the known boxes of the prefix and guesses only the rest; the correction chain stays, for dimensionless rows and for the lazy list's extrapolated maxScrollExtent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records every offset the scroll position visits during a seek, so the first jump's value is observable: with every height in the prefix known it is the answer, and nothing corrects it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A bitmap that disagrees with the dimensions its row stores is contained in the reserved box rather than cropped to its width.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Says plainly that such a page shifts what is below it once, when its bitmap arrives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reserved box now scales with the width the pages are built at in a test that changes it, the bitmap that arrives disagrees with its row so holding the box is distinguishable from taking the bitmap's size, the spinner and the broken-page icon stand in the first screenful of a box taller than the window, the fit ternary's unobservable arm is gone, and the seek estimate reads the width the pages were built at rather than the measuring box's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The strip's page doc now says the spinner and the broken-page icon stand in the reserved box's first screenful, and the new width test's name uses double quotes (analyze treats the escape info as fatal).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: drop the clamped half of the placeholder height
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 24m4s
f17e415ee6
The reserved box already tightens the placeholder, so asking for one screenful says all of it; the outer SizedBox keeps the page's true height.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A layout fix that starts by checking whether its premise is even true — and catches the old backlog item lying about the server storing no dimensions~ ♡ You read PageMapper.ToDto before writing a line of UI code. That's the kind of scholarship that makes my wings flutter!

Verdict: ✅ Looks good to me~

I traced every claim independently, and everything held:

  • The premise is real. ImageFile.Width/Height → PageMapper.ToDto (img?.Width ?? 0, null-tolerant) → PageDetailDto → the client's Page model. The <= 0 guard in _reservedHeightFor honors exactly the 0-means-unknown convention the mapper establishes. And since _sequence.pages comes from the variant detail regardless of image source, offline readers reserve boxes too — no online/offline divergence.
  • The estimator is honest about what it knows. Exact prefix over dimensioned rows, dimensionless-only average for the rest (excluding dimensioned pages from the guess so a 3000px page can't poison it), viewportDimension fallback inherited from the old code. The reserved box and the estimate read the same width from the same field — they cannot drift.
  • Both placeholder arms are correct. Align(topCenter, …) shrink-wraps to the viewport-high box on the dimensionless arm (old behavior byte-for-byte) and centers in the first screenful of a multi-viewport reserved box on the other. The paged modes' BoxFit.contain sibling parity is respected, and the fitWidth→contain switch is right: fitWidth would crop a stale row's bitmap.
  • The seek chain interplay is preserved. _pendingContinuousSeek still gates _onContinuousScroll, _jumpContinuousTo still clamps to the extrapolated maxScrollExtent, and the correction chain stays armed for exactly the two documented cases that still need it.
  • Tests are directional, not decorative. I re-ran your two meanest mutants myself at f17e415: L (drop the reserved SizedBox) → 8 tests red including "nothing below a page moves when its bitmap arrives"; R (hardcode 800.0 in _reservedHeightFor) → exactly one test red, Expected: 1800.0 / Actual: <1200.0>. Fresh repo per test via setUp, image cache cleared per test — no pollution of the 61 dimensionless-path tests that still pin the old behavior.

💡 Little ideas (non-blocking)~

  1. reader_page.dart:785 — the counted == 0 → viewportDimension fallback in _unknownPageHeight is the one arm no test pins (a seek with unknown pages in the prefix but none built yet). It's inherited verbatim from the old estimator and provably harmless, so I'm only noting it for your honest-non-pins ledger~
  2. reader_page.dart:762 — _estimatedOffsetFor is O(index) per call and the chain can call it up to 8 times per seek. Trivial at any realistic page count; a prefix sum would only matter in the tens of thousands. Forget I said anything unless you ever get there ♪

✅ What I liked~

  • The BACKLOG correction ("the server stores none" was wrong, fixed where it stood) — documenting why the old belief was false, not just deleting it.
  • The PR body's honest non-pins section (equivalent mutants E/N, the structural-not-observable width swap) — that's the mark of someone who probes their own tests.
  • jumpToChapter2 as a shared helper for the whole new group instead of a fifteenth copy-pasted tap sequence. DRY~

(Note: Forgejo reported mergeable: false at fetch time — stale; git merge-tree shows zero conflict markers and main has only moved a BACKLOG.md line since the merge base.)


Automated review by Jibril · 2026-09-19
CI/CD: absent for head SHA f17e415 (no bot comment yet) · Local checks: analyze clean, codegen guard passed, full suite 1241/1241 green, mutation probes L/R red-on-target

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A layout fix that starts by checking whether its premise is even true — and catches the old backlog item *lying* about the server storing no dimensions~ ♡ You read `PageMapper.ToDto` before writing a line of UI code. That's the kind of scholarship that makes my wings flutter! ### Verdict: ✅ Looks good to me~ I traced every claim independently, and everything held: - **The premise is real.** `ImageFile.Width/Height` → `PageMapper.ToDto` (`img?.Width ?? 0`, null-tolerant) → `PageDetailDto` → the client's `Page` model. The `<= 0` guard in `_reservedHeightFor` honors exactly the 0-means-unknown convention the mapper establishes. And since `_sequence.pages` comes from the variant detail regardless of image source, offline readers reserve boxes too — no online/offline divergence. - **The estimator is honest about what it knows.** Exact prefix over dimensioned rows, dimensionless-only average for the rest (excluding dimensioned pages from the guess so a 3000px page can't poison it), `viewportDimension` fallback inherited from the old code. The reserved box and the estimate read the *same* width from the same field — they cannot drift. - **Both placeholder arms are correct.** `Align(topCenter, …)` shrink-wraps to the viewport-high box on the dimensionless arm (old behavior byte-for-byte) and centers in the first screenful of a multi-viewport reserved box on the other. The paged modes' `BoxFit.contain` sibling parity is respected, and the fitWidth→contain switch is right: fitWidth would crop a stale row's bitmap. - **The seek chain interplay is preserved.** `_pendingContinuousSeek` still gates `_onContinuousScroll`, `_jumpContinuousTo` still clamps to the extrapolated `maxScrollExtent`, and the correction chain stays armed for exactly the two documented cases that still need it. - **Tests are directional, not decorative.** I re-ran your two meanest mutants myself at f17e415: **L** (drop the reserved `SizedBox`) → 8 tests red including *"nothing below a page moves when its bitmap arrives"*; **R** (hardcode `800.0` in `_reservedHeightFor`) → exactly one test red, `Expected: 1800.0 / Actual: <1200.0>`. Fresh `repo` per test via `setUp`, image cache cleared per test — no pollution of the 61 dimensionless-path tests that still pin the old behavior. #### 💡 Little ideas (non-blocking)~ 1. **reader_page.dart:785** — the `counted == 0 → viewportDimension` fallback in `_unknownPageHeight` is the one arm no test pins (a seek with unknown pages in the prefix but none built yet). It's inherited verbatim from the old estimator and provably harmless, so I'm only noting it for your honest-non-pins ledger~ 2. **reader_page.dart:762** — `_estimatedOffsetFor` is O(index) per call and the chain can call it up to 8 times per seek. Trivial at any realistic page count; a prefix sum would only matter in the tens of thousands. Forget I said anything unless you ever get there ♪ #### ✅ What I liked~ - The BACKLOG correction (`"the server stores none"` was wrong, fixed where it stood) — documenting *why* the old belief was false, not just deleting it. - The PR body's honest non-pins section (equivalent mutants E/N, the structural-not-observable width swap) — that's the mark of someone who probes their own tests. - `jumpToChapter2` as a shared helper for the whole new group instead of a fifteenth copy-pasted tap sequence. DRY~ *(Note: Forgejo reported `mergeable: false` at fetch time — stale; `git merge-tree` shows zero conflict markers and main has only moved a BACKLOG.md line since the merge base.)* --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head SHA f17e415 (no bot comment yet) · Local checks: analyze clean, codegen guard passed, full suite 1241/1241 green, mutation probes L/R red-on-target*
docs/BACKLOG.md conflicted on the Continuous reader item: main (#169) dropped the orphaned 'currently turns pages).' line that had been stranded inside the #141 entry, while this branch rewrote the neighbouring bullet. Resolved by item — main's deletion of the orphan, this branch's rewritten Continuous reader entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test: pin the guess for a page nothing has measured yet
Some checks failed
CI / build (pull_request) Successful in 25s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
Flutter CI / analyze-and-test (pull_request) Failing after 24m5s
CI / test (pull_request) Successful in 2m4s
f72f98e0ff
The review's one open arm: _unknownPageHeight falls back to the viewport's height when the list has built no dimensionless page to average, and every test in the file runs in the harness default 800x600 window — where that height and a hardcoded 600 are the same number. The new test seeks across an unmeasured page in a 900-high window, so the two part ways: the estimate is 1200 + 900 in one jump, and a 600 in the fallback's place is walked back by the correction chain inside the same frame.

Also defers the O(index) _estimatedOffsetFor prefix walk into the Continuous reader backlog item, per the review's second note.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Member

Updated to f72f98e: your first idea is now pinned, the second is in the backlog, and the branch is merged with main so it is mergeable again.

The counted == 0 arm. You said it was the one arm no test pins, and that was half right in a way worth stating. The arm is reached: mutating it to 0.0 already reddened the existing dimensionless-reserve test. What nothing pinned was its value, because every test in the file runs in the harness default 800 by 600 window, where a viewport height and a hardcoded 600 are the same number. The new test a page in the prefix that nothing has measured is worth this window's height, not the harness default runs in an 800 by 900 window with a dimensionless page far enough down the strip that nothing has measured it at seek time, and requires the seek to land in one jump.

The probe that matters is the hardcode, not the zero. I re-ran it myself on the committed tree: replacing the fallback with a literal 600 fails exactly one test of 71, and the failure shows the first jump landing 300 short and the correction chain walking the rest back inside the same frame. Every one of the 70 pre-existing tests stays green under that mutation, which is the gap the new test closes.

The O(index) prefix walk is deferred, as you suggested. It is on the branch's backlog item with the shape of the fix, a prefix sum, and the condition that would make it worth doing.

The merge. One conflict, in the backlog, and it came from your own catch: #169 deleted the orphaned line that my earlier rebase had stranded at the end of the preferred-title-language entry, and that line sat immediately above the continuous-reader bullet this branch rewrites, so git put both edits in one hunk. Resolved by item rather than by line, which is the lesson from #169, and checked by diffing against both parents and sweeping the file for orphaned continuation lines. None remain. The merge is its own commit so the resolution is reviewable separately.

Correction to the PR body, which is now stale and I am leaving in place rather than rewriting history: it says 1241 tests and 70 in the reader file, and it lists an honest-non-pins bullet about this very arm. Read those as 1270 and 71, and treat that bullet as closed by this commit. scripts/verify.sh app green at f72f98e.

Updated to `f72f98e`: your first idea is now pinned, the second is in the backlog, and the branch is merged with main so it is mergeable again. **The `counted == 0` arm.** You said it was the one arm no test pins, and that was half right in a way worth stating. The arm *is* reached: mutating it to `0.0` already reddened the existing dimensionless-reserve test. What nothing pinned was its **value**, because every test in the file runs in the harness default 800 by 600 window, where a viewport height and a hardcoded 600 are the same number. The new test `a page in the prefix that nothing has measured is worth this window's height, not the harness default` runs in an 800 by 900 window with a dimensionless page far enough down the strip that nothing has measured it at seek time, and requires the seek to land in one jump. The probe that matters is the hardcode, not the zero. I re-ran it myself on the committed tree: replacing the fallback with a literal 600 fails exactly one test of 71, and the failure shows the first jump landing 300 short and the correction chain walking the rest back inside the same frame. Every one of the 70 pre-existing tests stays green under that mutation, which is the gap the new test closes. **The O(index) prefix walk** is deferred, as you suggested. It is on the branch's backlog item with the shape of the fix, a prefix sum, and the condition that would make it worth doing. **The merge.** One conflict, in the backlog, and it came from your own catch: #169 deleted the orphaned line that my earlier rebase had stranded at the end of the preferred-title-language entry, and that line sat immediately above the continuous-reader bullet this branch rewrites, so git put both edits in one hunk. Resolved by item rather than by line, which is the lesson from #169, and checked by diffing against both parents and sweeping the file for orphaned continuation lines. None remain. The merge is its own commit so the resolution is reviewable separately. **Correction to the PR body**, which is now stale and I am leaving in place rather than rewriting history: it says 1241 tests and 70 in the reader file, and it lists an honest-non-pins bullet about this very arm. Read those as 1270 and 71, and treat that bullet as closed by this commit. `scripts/verify.sh app` green at `f72f98e`.
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~? My round-1 approval was for f17e415, and now the branch carries a merge of main plus one small commit — so I re-read every line of the delta, fufu~ ♡

Verdict: ✅ Looks good to me~

What actually changed since the approval: c4e41c5 is an honest merge of origin/main (3fbae75, = #170 + #172, both already reviewed on their own PRs; the BACKLOG conflict resolved item-by-item exactly as the merge message narrates — main's orphan-line deletion kept, your rewritten Continuous-reader entry kept), and f72f98e is a test-only commit: reader_page.dart is byte-identical to the approved head (git diff f17e415..f72f98e -- app/lib/presentation/pages/reader/reader_page.dart is empty — verified, not assumed).

The one open arm is now pinned, and pinned well~ My round-1 note said the counted == 0 → viewportDimension fallback in _unknownPageHeight was the only arm no test witnessed. You didn't just add an assertion — you noticed the reason it was unwitnessed (every test runs in 800×600, where viewport-height and hardcoded 600 are indistinguishable) and made the window itself the discriminator: 900-high viewport, p2 dimensionless and parked past the cache extent, so at estimate time nothing has measured it. 1200 + 900 = 2100 in one jump — and visitedOffsets records every notification, so the "not walked back by the correction chain" claim is genuinely assertable, not just a final-frame check. I love a test that argues with its own harness ♪

Mutual closure of the ledger: the O(index) prefix-walk note also landed where it belongs — BACKLOG.md's Continuous-reader entry now records the running-prefix-sum alternative with the "tens of thousands of pages" trigger and (#171) attribution, per the remove-when-ships deferral convention.

My own hands, at f72f98e: reader suite 71/71 (+1 = exactly the new pin), offline-reader 10/10, analyze clean on both touched files. And because a claim isn't verified until the mutant dies: I mutated the fallback arm to a hardcoded 600.0 myself → exactly the new test RED (Expected/Actual on the one-jump assertion), all 70 siblings green → restored byte-identical (git status --porcelain empty) → green again. Directional, not decorative~

✅ What I liked~

  • The test comment teaches the trap it closes: "every other test runs where that height and a hardcoded 600 are the same number." Future readers won't re-open this arm by accident.
  • jumpToChapter2's 8×16ms pumps let the correction chain settle without pumpAndSettle — no timer-hang regression of the #61 class.
  • Zero production drift between rounds. A re-review where the diff is only more certainty is the best kind ♡

(CI: still no bot comment for f72f98e; local verification used — full details above.)


Automated review by Jibril · 2026-09-19
CI/CD: absent for head SHA f72f98e · Local checks: analyze clean · reader 71/71 · offline-reader 10/10 · mutation probe (fallback→600) red-on-target, restored green

## 🔮 fufu~ Jibril reviewed your code! Back so soon~? My round-1 approval was for `f17e415`, and now the branch carries a merge of main plus one small commit — so I re-read every line of the delta, fufu~ ♡ ### Verdict: ✅ Looks good to me~ **What actually changed since the approval:** `c4e41c5` is an honest merge of origin/main (`3fbae75`, = #170 + #172, both already reviewed on their own PRs; the BACKLOG conflict resolved item-by-item exactly as the merge message narrates — main's orphan-line deletion kept, your rewritten Continuous-reader entry kept), and `f72f98e` is a **test-only** commit: `reader_page.dart` is byte-identical to the approved head (`git diff f17e415..f72f98e -- app/lib/presentation/pages/reader/reader_page.dart` is empty — verified, not assumed). **The one open arm is now pinned, and pinned well~** My round-1 note said the `counted == 0 → viewportDimension` fallback in `_unknownPageHeight` was the only arm no test witnessed. You didn't just add an assertion — you noticed the *reason* it was unwitnessed (every test runs in 800×600, where viewport-height and hardcoded 600 are indistinguishable) and made the window itself the discriminator: 900-high viewport, p2 dimensionless and parked past the cache extent, so at estimate time nothing has measured it. `1200 + 900 = 2100` in **one** jump — and `visitedOffsets` records *every* notification, so the "not walked back by the correction chain" claim is genuinely assertable, not just a final-frame check. I love a test that argues with its own harness ♪ **Mutual closure of the ledger:** the O(index) prefix-walk note also landed where it belongs — BACKLOG.md's Continuous-reader entry now records the running-prefix-sum alternative with the "tens of thousands of pages" trigger and `(#171)` attribution, per the remove-when-ships deferral convention. **My own hands, at `f72f98e`:** reader suite **71/71** (+1 = exactly the new pin), offline-reader 10/10, analyze clean on both touched files. And because a claim isn't verified until the mutant dies: I mutated the fallback arm to a hardcoded `600.0` myself → **exactly the new test RED** (`Expected`/`Actual` on the one-jump assertion), all 70 siblings green → restored byte-identical (`git status --porcelain` empty) → green again. Directional, not decorative~ #### ✅ What I liked~ - The test comment teaches the *trap* it closes: "every other test runs where that height and a hardcoded 600 are the same number." Future readers won't re-open this arm by accident. - `jumpToChapter2`'s 8×16ms pumps let the correction chain settle without `pumpAndSettle` — no timer-hang regression of the #61 class. - Zero production drift between rounds. A re-review where the diff is only *more certainty* is the best kind ♡ *(CI: still no bot comment for `f72f98e`; local verification used — full details above.)* --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head SHA f72f98e · Local checks: analyze clean · reader 71/71 · offline-reader 10/10 · mutation probe (fallback→600) red-on-target, restored green*
bjoern merged commit a62eb7dd7b into main 2026-09-19 21:27:33 +02:00
bjoern deleted branch fix/continuous-reading-does-not-shift-as-pages-load 2026-09-19 21:27:33 +02:00

Summary

Summary
Generated on: 09/19/2026 - 21:38:04
Coverage date: 09/19/2026 - 21:36:27 - 09/19/2026 - 21:38:00
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 436
Files: 183
Line coverage: 96.5% (18674 of 19332)
Covered lines: 18674
Uncovered lines: 658
Coverable lines: 19332
Total lines: 29869
Branch coverage: 76.8% (1617 of 2103)
Covered branches: 1617
Total branches: 2103
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 95.8%
Name Line Branch
DoujinManager.ApplicationCore 95.8% 100%
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 85.7%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupContent 100%
DoujinManager.ApplicationCore.Services.BackupDirectory 100%
DoujinManager.ApplicationCore.Services.BackupFailedException 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.BackupSchedule 100% 100%
DoujinManager.ApplicationCore.Services.BackupScheduleStatus 100%
DoujinManager.ApplicationCore.Services.BackupStaging 100%
DoujinManager.ApplicationCore.Services.BackupVerification 100% 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.LikePattern 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchIndex 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DataDirectories 100%
DoujinManager.ApplicationCore.UseCases.DataDirectory 100%
DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness 100%
DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteTagCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 100%
DoujinManager.ApplicationCore.UseCases.GetCharacterQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCircleQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetPersonQuery 100%
DoujinManager.ApplicationCore.UseCases.GetSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.GetTagQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageCoverReference 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
DoujinManager.ApplicationCore.UseCases.IntegrityReport 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand 100%
DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 97.5%
Name Line Branch
DoujinManager.Infrastructure 97.5% 86.3%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex 96.5%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey 99.1%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.SearchKeyBackfill 100% 100%
DoujinManager.Infrastructure.Data.SqliteDatabaseFile 100%
DoujinManager.Infrastructure.Data.SqliteMaintenance 78.2% 62.5%
DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor 100% 75%
DoujinManager.Infrastructure.Data.SqliteRuntimeOptions 100% 100%
DoujinManager.Infrastructure.Data.StartupMigration 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 90.1% 83.8%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupExpectations 100%
DoujinManager.Infrastructure.Services.BackupExpectationsReader 84.7% 75%
DoujinManager.Infrastructure.Services.BackupInspection 100%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 71.6% 73%
DoujinManager.Infrastructure.Services.BackupRestoreService 93.6% 88%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 92.3% 83.3%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 92% 66.6%
DoujinManager.Infrastructure.Services.CircleService 100% 72.2%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 99.4% 76.3%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 77.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 72.2%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 97.5% 88.2%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 100%
DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase 80% 50%
DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 97.1% 100%
DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.GetStatsUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.GetTagUseCase 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.GuidProbeNaming 100%
DoujinManager.Infrastructure.UseCases.ImportBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.ListBackupsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase 100%
DoujinManager.RestAdapter - 98.9%
Name Line Branch
DoujinManager.RestAdapter 98.9% 80.6%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.BackupScheduleDto 100% 100%
DoujinManager.RestAdapter.Dtos.BackupVerificationDto 100% 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.HealthDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityReportDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderChaptersDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.RestoreRequestDto 100%
DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto 100% 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99.2%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 97.8% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.2% 50%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 98.1% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 88.8% 87.5%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 99.8% 94.1%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 64.7%
Name Line Branch
DoujinManager.Server 64.7% 27.6%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.ScheduledBackupConfiguration 100% 91.1%
DoujinManager.Server.ScheduledBackupService 80.2% 75%
DoujinManager.Server.SqliteRuntimeConfiguration 96.5% 95%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 87.7% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/19/2026 - 21:38:04 | | Coverage date: | 09/19/2026 - 21:36:27 - 09/19/2026 - 21:38:00 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 436 | | Files: | 183 | | **Line coverage:** | 96.5% (18674 of 19332) | | Covered lines: | 18674 | | Uncovered lines: | 658 | | Coverable lines: | 19332 | | Total lines: | 29869 | | **Branch coverage:** | 76.8% (1617 of 2103) | | Covered branches: | 1617 | | Total branches: | 2103 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 95.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**95.8%**|**100%**| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|85.7%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupContent|100%|| |DoujinManager.ApplicationCore.Services.BackupDirectory|100%|| |DoujinManager.ApplicationCore.Services.BackupFailedException|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.BackupSchedule|100%|100%| |DoujinManager.ApplicationCore.Services.BackupScheduleStatus|100%|| |DoujinManager.ApplicationCore.Services.BackupStaging|100%|| |DoujinManager.ApplicationCore.Services.BackupVerification|100%|100%| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.LikePattern|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchIndex|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectories|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectory|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|100%|| |DoujinManager.ApplicationCore.UseCases.GetCharacterQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCircleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetPersonQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetTagQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageCoverReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityReport|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 97.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**97.5%**|**86.3%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex|96.5%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey|99.1%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.SearchKeyBackfill|100%|100%| |DoujinManager.Infrastructure.Data.SqliteDatabaseFile|100%|| |DoujinManager.Infrastructure.Data.SqliteMaintenance|78.2%|62.5%| |DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor|100%|75%| |DoujinManager.Infrastructure.Data.SqliteRuntimeOptions|100%|100%| |DoujinManager.Infrastructure.Data.StartupMigration|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|90.1%|83.8%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupExpectations|100%|| |DoujinManager.Infrastructure.Services.BackupExpectationsReader|84.7%|75%| |DoujinManager.Infrastructure.Services.BackupInspection|100%|| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|71.6%|73%| |DoujinManager.Infrastructure.Services.BackupRestoreService|93.6%|88%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|92.3%|83.3%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|92%|66.6%| |DoujinManager.Infrastructure.Services.CircleService|100%|72.2%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|99.4%|76.3%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|77.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|72.2%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|97.5%|88.2%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase|80%|50%| |DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|97.1%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetStatsUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.GetTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GuidProbeNaming|100%|| |DoujinManager.Infrastructure.UseCases.ImportBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListBackupsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| |DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase|100%|| </details> <details><summary>DoujinManager.RestAdapter - 98.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**98.9%**|**80.6%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupScheduleDto|100%|100%| |DoujinManager.RestAdapter.Dtos.BackupVerificationDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.HealthDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityReportDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderChaptersDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.RestoreRequestDto|100%|| |DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto|100%|100%| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99.2%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|97.8%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.2%|50%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|98.1%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|88.8%|87.5%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|99.8%|94.1%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 64.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**64.7%**|**27.6%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.ScheduledBackupConfiguration|100%|91.1%| |DoujinManager.Server.ScheduledBackupService|80.2%|75%| |DoujinManager.Server.SqliteRuntimeConfiguration|96.5%|95%| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|87.7%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/doujin-manager!171
No description provided.