feat: batch tag editing across selected doujins #165

Merged
bjoern merged 6 commits from feat/batch-tag-editing into main 2026-09-19 15:40:42 +02:00
Member

What's in

  • The library toolbar gains a selection mode: a card tap selects instead of opening the doujin, selected cards carry a checkbox badge and a primary border, and a bar under the filter bar shows the count with Select all (every loaded row), Clear, Tags… and an exit.
  • Tags… opens a dialog that first reads each selected doujin's tags (summaries carry none), lists every tag on the selection labelled On all N or On M of N, and stages removals from that list and additions through the editor's existing alias-aware AssociationPicker. Nothing is written until Apply, and a tag can no longer be staged for add and removal in the same Apply.
  • Apply walks the selection in grid order and issues assignTag/removeTag one doujin at a time through DoujinRepository — no batch endpoint was added. A removal is only sent to the doujins that actually carry the tag (detaching one they never had is a 404). A doujin the server refuses is named by title in an outcome banner and its remaining calls are skipped so it is never half-tagged, while the rest of the batch still applies.
  • Both loops report progress (Reading 3 of 240…, Tagging 2 of 5…), the bar warns past kBatchSelectionWarnAt selected rows, and the mode exits are disabled while an apply is in flight — leaving mid-run used to hide the progress but not the writes. There is no cancel yet (backlogged).
  • The changed rows are aged in place instead of refetched (a write below a doujin touches its parent, ADR 0004), and the epic only reloads when the view filters on tags or orders by updated, so the grid otherwise keeps its scroll position. Every reload and every query change drops the selection, and a deleted row leaves it, so a row the current query no longer shows can never be edited by accident.
  • No backend change: no new endpoint, no migration, no OpenAPI change, no new DOUJIN_MANAGER_* tunable. kBatchSelectionWarnAt (50) is a client-side UX constant in app/lib/core/constants.dart — it changes nothing but when a sentence appears, so per AGENTS.md it stays hardcoded and no deploy/ change was needed. No ADR is amended or contradicted; ADR 0004 is what the optimistic row ageing rests on and ADR 0023 is untouched (nothing client-side is sent to the server).

Verification

Executed: scripts/verify.sh app from a clean worktree at c8307d7 — passed, exit 0. That is the submodule check, flutter pub get, a clean build_runner regeneration with the committed-generated-output diff (app_state.freezed.dart is regenerated and committed), flutter analyze → No issues found!, and flutter test → +1188: All tests passed!. scripts/verify.sh backend was not run: the branch touches no backend file.

Layers exercised

  • UI — 25 widget tests (app/test/batch_tag_page_test.dart) drive the toolbar toggle, the selection bar, the cards' tap/badge/border, the warning line, the progress row, the outcome banner, and the dialog's loading/error/Retry/staging/Apply/Cancel arms against a local GoRouter mirroring the production nesting.
  • Redux state — 31 reducer tests and 13 epic tests (69 new tests total). The epic tests run through createStore, so the epic's registration in createAppEpic is exercised, not just the epic function.
  • REST, use case, persistence — not exercised. Every test fakes DoujinRepository; no HTTP, no backend code and no database ran on this branch. The backend property the epic relies on — assigning is idempotent, removing is not — was confirmed by reading only: AssignTagUseCase returns Ok early on IsTagAssignedAsync and RemoveTagUseCase returns false when the join row is missing (backend/src/DoujinManager.Infrastructure/UseCases/AssignmentUseCases.cs).

Mutation probes. Each mutant applied alone to a clean tree, the relevant suite run by name, then restored with git checkout -- <file> and a git status --porcelain check.

  • The feature commit was probed with 20 mutants — all red.
  • A review pass then found 14 green mutants (untested branches: both epics' selection filter, the dialog's entire add path, two of the four query-change arms, the progress row, the card's badge and border, both UI exits, the dialog's loading/error/Retry/ready guards, the un-stage toggles, the disabled guards) and 7 behavioural findings. All 21 were fixed in c8307d7 and re-probed with 39 mutants — all red.
  • An independent second round on c8307d7 re-ran 49 probes: 42 red, 7 survived. One is an equivalent mutant (dropping readCount: 0, readTotal: 0 from a reducer arm that also sets isLoadingTags: false, so the counters are unreachable). The other six collapse into three minor untested details that were left open. No blocker and no major survived. The same round re-ran the three batch suites (69 passed), flutter analyze (clean) and the seven sibling library suites (205 passed).
  • One review finding was partly rebutted: the stale auto-refresh no longer discards a live selection (_refreshIfStale returns early while selectedIds is non-empty, pinned by a red probe), but the explicit Refresh button deliberately still refreshes and still drops the selection — the user asked for that reload, and silently ignoring Refresh, or keeping a selection that names rows the new result set may not contain, is the worse answer. The rationale is in the code comment, not left implicit.

Not verified: nothing was run in the real app against a live server. No manual click-through, no live backend, no real image or tag data — the entire branch is verified by the automated suite above plus code reading.

Follow-ups

Added to docs/BACKLOG.md (replacing the old one-line Batch tag editing across selected doujins item):

  • A batch association endpoint — POST /api/doujins/associations taking (doujin ids, add, remove) would collapse both the N×M writes and the one-GET-per-doujin read, and could report per-doujin outcomes the way the app already renders them.
  • Batch editing beyond tags — the other four association kinds, the rating and the reading direction are still one doujin at a time in the editor.
  • Selecting across pages — Select all takes the rows loaded into the grid, not the whole result set, because the all-versus-some summary needs each doujin's tags.
  • Cancelling a batch — neither the opening read nor the apply can be stopped; what a cancellation token through both loops would cost is written down.

🤖 Generated with Claude Code

## What's in - The library toolbar gains a **selection mode**: a card tap selects instead of opening the doujin, selected cards carry a checkbox badge and a primary border, and a bar under the filter bar shows the count with **Select all** (every loaded row), **Clear**, **Tags…** and an exit. - **Tags…** opens a dialog that first reads each selected doujin's tags (summaries carry none), lists every tag on the selection labelled *On all N* or *On M of N*, and stages removals from that list and additions through the editor's existing alias-aware `AssociationPicker`. Nothing is written until **Apply**, and a tag can no longer be staged for add and removal in the same Apply. - **Apply** walks the selection in grid order and issues `assignTag`/`removeTag` one doujin at a time through `DoujinRepository` — **no batch endpoint was added**. A removal is only sent to the doujins that actually carry the tag (detaching one they never had is a 404). A doujin the server refuses is named by title in an outcome banner and its remaining calls are skipped so it is never half-tagged, while the rest of the batch still applies. - Both loops report progress (`Reading 3 of 240…`, `Tagging 2 of 5…`), the bar warns past `kBatchSelectionWarnAt` selected rows, and the mode exits are disabled while an apply is in flight — leaving mid-run used to hide the progress but not the writes. There is no cancel yet (backlogged). - The changed rows are aged in place instead of refetched (a write below a doujin touches its parent, **ADR 0004**), and the epic only reloads when the view filters on tags or orders by `updated`, so the grid otherwise keeps its scroll position. Every reload and every query change drops the selection, and a deleted row leaves it, so a row the current query no longer shows can never be edited by accident. - **No backend change**: no new endpoint, no migration, no OpenAPI change, no new `DOUJIN_MANAGER_*` tunable. `kBatchSelectionWarnAt` (50) is a client-side UX constant in `app/lib/core/constants.dart` — it changes nothing but when a sentence appears, so per AGENTS.md it stays hardcoded and no `deploy/` change was needed. **No ADR is amended or contradicted**; ADR 0004 is what the optimistic row ageing rests on and ADR 0023 is untouched (nothing client-side is sent to the server). ## Verification **Executed:** `scripts/verify.sh app` from a clean worktree at `c8307d7` — **passed, exit 0**. That is the submodule check, `flutter pub get`, a clean `build_runner` regeneration with the committed-generated-output diff (`app_state.freezed.dart` is regenerated and committed), `flutter analyze` → *No issues found!*, and `flutter test` → **+1188: All tests passed!**. `scripts/verify.sh backend` was **not** run: the branch touches no backend file. **Layers exercised** - **UI** — 25 widget tests (`app/test/batch_tag_page_test.dart`) drive the toolbar toggle, the selection bar, the cards' tap/badge/border, the warning line, the progress row, the outcome banner, and the dialog's loading/error/Retry/staging/Apply/Cancel arms against a local `GoRouter` mirroring the production nesting. - **Redux state** — 31 reducer tests and 13 epic tests (69 new tests total). The epic tests run through `createStore`, so the epic's registration in `createAppEpic` is exercised, not just the epic function. - **REST, use case, persistence — not exercised.** Every test fakes `DoujinRepository`; no HTTP, no backend code and no database ran on this branch. The backend property the epic relies on — assigning is idempotent, removing is not — was **confirmed by reading only**: `AssignTagUseCase` returns `Ok` early on `IsTagAssignedAsync` and `RemoveTagUseCase` returns false when the join row is missing (`backend/src/DoujinManager.Infrastructure/UseCases/AssignmentUseCases.cs`). **Mutation probes.** Each mutant applied alone to a clean tree, the relevant suite run by name, then restored with `git checkout -- <file>` and a `git status --porcelain` check. - The feature commit was probed with **20** mutants — all red. - A review pass then found **14 green mutants** (untested branches: both epics' selection filter, the dialog's entire add path, two of the four query-change arms, the progress row, the card's badge and border, both UI exits, the dialog's loading/error/Retry/`ready` guards, the un-stage toggles, the disabled guards) and **7 behavioural findings**. All 21 were fixed in `c8307d7` and re-probed with **39** mutants — all red. - An independent second round on `c8307d7` re-ran **49** probes: **42 red, 7 survived**. One is an equivalent mutant (dropping `readCount: 0, readTotal: 0` from a reducer arm that also sets `isLoadingTags: false`, so the counters are unreachable). The other six collapse into **three minor untested details** that were left open. **No blocker and no major survived.** The same round re-ran the three batch suites (69 passed), `flutter analyze` (clean) and the seven sibling library suites (205 passed). - One review finding was **partly rebutted**: the stale auto-refresh no longer discards a live selection (`_refreshIfStale` returns early while `selectedIds` is non-empty, pinned by a red probe), but the explicit **Refresh button deliberately still refreshes and still drops the selection** — the user asked for that reload, and silently ignoring Refresh, or keeping a selection that names rows the new result set may not contain, is the worse answer. The rationale is in the code comment, not left implicit. **Not verified:** nothing was run in the real app against a live server. No manual click-through, no live backend, no real image or tag data — the entire branch is verified by the automated suite above plus code reading. ## Follow-ups Added to `docs/BACKLOG.md` (replacing the old one-line *Batch tag editing across selected doujins* item): - A batch association endpoint — `POST /api/doujins/associations` taking (doujin ids, add, remove) would collapse both the N×M writes and the one-GET-per-doujin read, and could report per-doujin outcomes the way the app already renders them. - Batch editing beyond tags — the other four association kinds, the rating and the reading direction are still one doujin at a time in the editor. - Selecting across pages — *Select all* takes the rows loaded into the grid, not the whole result set, because the all-versus-some summary needs each doujin's tags. - Cancelling a batch — neither the opening read nor the apply can be stopped; what a cancellation token through both loops would cost is written down. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A toolbar toggle turns the library grid into a selectable one (select all on screen, clear, count); a Tags dialog shows which tags are on all of the selection and which on some, and applies one add/remove set per doujin through the repository port. A refused doujin is named and the rest still apply. Every reload and every query change drops the selection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
verify.sh treats analyzer infos as fatal; plain flutter analyze in app/ does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: pin the batch tag branches the probes walked through, and close the gaps they found
Some checks failed
Flutter CI / analyze-and-test (pull_request) Has been cancelled
c8307d7b04
Reviewer fixes: the mode exits wait while an apply is in flight (leaving mid-run hid the progress but not the writes, and re-armed a second batch that then found an empty selection); a mode toggle keeps a running apply's counters; the dialog's opening read counts itself out instead of spinning; the bar warns past kBatchSelectionWarnAt selected rows; a tag can no longer be staged for both add and removal; a deleted row leaves the selection; appliedAt is UTC; the stale auto-refresh no longer discards a live selection. Tests: the selection filter of both epics, the dialog's whole add path, the two missing query-change arms, the progress row, the card's badge and border, both UI exits, the dialog's loading/error/Retry/ready arms, the un-stage toggles and the disabled guards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test: let the batch fakes accept the title-language sort hint
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 24m4s
19ed157cdc
An integration merge of this batch showed the two new fake repositories failing to override listDoujins once a sibling branch adds titleLanguage to the port. An override may take optional named parameters the supertype does not, so accepting it here compiles both before and after that branch lands.

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

Pushed 19ed157, a test-only fix for something no single-branch review could see.

An integration merge of all six branches in this batch failed the analyzer. #164 adds a titleLanguage parameter to DoujinRepository.listDoujins, and the two fake repositories this branch introduces then stop being valid overrides. Each branch is green on its own; the pair is not.

The two fakes now accept String? titleLanguage and ignore it. That is legal before the parameter exists, because an override may take optional named parameters the supertype does not, so this branch compiles whether or not #164 has landed and the two can merge in either order.

Verified both ways. Alone: analyze clean, the two batch suites 38 tests green. Merged with the other five: verify.sh all green, 1301 app tests, 741 REST adapter, 229 infrastructure, 56 application core.

Pushed `19ed157`, a test-only fix for something no single-branch review could see. An integration merge of all six branches in this batch failed the analyzer. #164 adds a `titleLanguage` parameter to `DoujinRepository.listDoujins`, and the two fake repositories this branch introduces then stop being valid overrides. Each branch is green on its own; the pair is not. The two fakes now accept `String? titleLanguage` and ignore it. That is legal before the parameter exists, because an override may take optional named parameters the supertype does not, so this branch compiles whether or not #164 has landed and the two can merge in either order. Verified both ways. Alone: analyze clean, the two batch suites 38 tests green. Merged with the other five: `verify.sh all` green, 1301 app tests, 741 REST adapter, 229 infrastructure, 56 application core.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Batch editing across a selection, rows aged in place instead of refetched, a dialog that refuses to add and remove the same tag in one Apply — and that mutation-probing discipline in the PR body? Fufu~, I read every word, scarlet~ ♪ …but then I chased one late action through the reducer chain, and now my smile is very, very steady.

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. batch_tag_epics.dart:89,131–150 — the apply's reload is decided from library state captured before the writes ran, and the late action can undo a user action taken mid-apply. _applyBatchTags captures lib (line 89), runs the whole write loop, then calls _reloadAfterApply(lib) on that stale snapshot. I reproduced it mechanically: gated the first write, then mid-apply cleared the search exactly as the search bar's onClear does (ClearSearchAction + LoadDoujinsAction, both immediate — no debounce on that path). When the gate opened, the epic yielded SearchDoujinsAction because the captured state was a tag-filtered search — re-flipping the user's cleared browse view back into search mode with the filters now empty. Final state observed in the transition log: isSearchMode=true, includeTags=[], 3 searches vs 1 list — an unfiltered search of the whole library nobody asked for. The search bar's ✕ stays live while isApplying (only the mode exits are locked), so this interleaving is reachable — and every guard this PR builds so carefully (every reload drops the selection, exits locked during a batch) is bypassed by this one late dispatch.
    Fix: decide from fresh state after the loop — _reloadAfterApply(store.state.library) — and ideally reconcile with the sequence discipline the library already uses (requestSeq on DoujinsLoadedAction/LibraryErrorAction) so a reload the apply no longer reflects is dropped as a stray.
  2. batch_tag_epics_test.dart:289 and :307 — both filter-reload tests assert inside the live 300 ms debounce their own setup armed. They dispatch SetFilterTokensAction, which _filterTokensChangedEpic answers with a 300 ms debounce to SearchDoujinsAction, then run 24 microtask pumps (milliseconds of wall clock) and assert searchCalls == 1. On a slow host the test body outlives the timer: here it ran ~4.5 s end-to-end (createStore alone ~0.8 s) and both tests fail with Expected: <1> Actual: <2> — deterministically, in isolation, on an idle box. I copied the test with exactly one change — a 500 ms drain and counter reset after seeding — and it passes with exactly 1 search, pinning the red to the armed timer, not to your epic. That verify.sh was green on a fast machine is precisely the problem: the suite's correctness depends on the host being fast. Fix: drain the debounce before asserting (a Duration(milliseconds: 400) wait + counter reset after seeding), or fake time.

💡 Little ideas (non-blocking)~

  1. batch_tag_dialog.dart:~197–207 — batch.countWith(tag.id) is recomputed for every row and again for the excluded set, walking the same map twice. A little counts map built once would do — purely cosmetic at these sizes, so just a nicety~ ♪

✅ What I liked~

  • The removal-addressing is wonderful: removals go only to doujins the fetched map says carry the tag — and rather than assume backend idempotency, the PR body says it was confirmed by reading AssignmentUseCases.cs. I re-read it myself: AssignTagUseCase early-Ok on IsTagAssignedAsync, RemoveTagUseCase returns false on the missing join row. Claim holds~ ♡
  • _withoutSelection threaded through all six reload/query-change arms plus the deleted-row arm — the "selection ⊆ loaded rows" invariant held everywhere I traced it, and each arm has its own reducer pin.
  • The toggle-vs-apply distinction in the reducer: a mode toggle drops the last outcome but deliberately preserves a running apply (isApplying + counters) so the "Tags…" guard can't re-arm behind live writes. That's the second-order thinking I go possessive over~
  • BatchTagsAppliedAction defaulting appliedAt to UTC with the wire-format rationale documented — that spared the grid an east-of-UTC "aged to tomorrow" bug. Someone was thinking ♪ And _SelectionBadge mirrors _ReadStatusBadge's black54/white circle to the letter.
  • A happy accident worth keeping: because the epics use asyncExpand, a superseded dialog read is cancelled the moment a newer one starts — I probed that race expecting a stale-map win and the newer read held. Keep that property when you touch this file again~

Automated review by Jibril · 2026-09-19
CI/CD: pending for head 19ed157 (run 708 unfinished at review time) · Local checks: analyze 0 issues in PR files · reducer 31/31, page 25/25, epics 11/13 (the 2 reds are issue 2, not code) · race probes: reload bug reproduced, debounce mechanism proven

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Batch editing across a selection, rows aged in place instead of refetched, a dialog that refuses to add and remove the same tag in one Apply — and that mutation-probing discipline in the PR body? Fufu~, I read every word, scarlet~ ♪ …but then I chased one late action through the reducer chain, and now my smile is very, very steady. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`batch_tag_epics.dart:89,131–150` — the apply's reload is decided from library state captured *before* the writes ran, and the late action can undo a user action taken mid-apply.** `_applyBatchTags` captures `lib` (line 89), runs the whole write loop, then calls `_reloadAfterApply(lib)` on that stale snapshot. I reproduced it mechanically: gated the first write, then mid-apply cleared the search exactly as the search bar's `onClear` does (`ClearSearchAction` + `LoadDoujinsAction`, both immediate — no debounce on that path). When the gate opened, the epic yielded `SearchDoujinsAction` because the *captured* state was a tag-filtered search — re-flipping the user's cleared browse view back into search mode with the filters now empty. Final state observed in the transition log: `isSearchMode=true`, `includeTags=[]`, 3 searches vs 1 list — an **unfiltered search of the whole library** nobody asked for. The search bar's ✕ stays live while `isApplying` (only the mode exits are locked), so this interleaving is reachable — and every guard this PR builds so carefully (every reload drops the selection, exits locked during a batch) is bypassed by this one late dispatch. Fix: decide from fresh state after the loop — `_reloadAfterApply(store.state.library)` — and ideally reconcile with the sequence discipline the library already uses (`requestSeq` on `DoujinsLoadedAction`/`LibraryErrorAction`) so a reload the apply no longer reflects is dropped as a stray. 2. **`batch_tag_epics_test.dart:289` and `:307` — both filter-reload tests assert inside the live 300 ms debounce their own setup armed.** They dispatch `SetFilterTokensAction`, which `_filterTokensChangedEpic` answers with a 300 ms debounce to `SearchDoujinsAction`, then run 24 microtask pumps (milliseconds of wall clock) and assert `searchCalls == 1`. On a slow host the test body outlives the timer: here it ran ~4.5 s end-to-end (createStore alone ~0.8 s) and both tests fail with `Expected: <1> Actual: <2>` — deterministically, in isolation, on an idle box. I copied the test with exactly one change — a 500 ms drain and counter reset after seeding — and it passes with exactly 1 search, pinning the red to the armed timer, not to your epic. That `verify.sh` was green on a fast machine is precisely the problem: the suite's correctness depends on the host being fast. Fix: drain the debounce before asserting (a `Duration(milliseconds: 400)` wait + counter reset after seeding), or fake time. #### 💡 Little ideas (non-blocking)~ 1. **`batch_tag_dialog.dart:~197–207` — `batch.countWith(tag.id)` is recomputed for every row *and* again for the `excluded` set**, walking the same map twice. A little `counts` map built once would do — purely cosmetic at these sizes, so just a nicety~ ♪ #### ✅ What I liked~ - The removal-addressing is *wonderful*: removals go only to doujins the fetched map says carry the tag — and rather than assume backend idempotency, the PR body says it was confirmed by reading `AssignmentUseCases.cs`. I re-read it myself: `AssignTagUseCase` early-`Ok` on `IsTagAssignedAsync`, `RemoveTagUseCase` returns `false` on the missing join row. Claim holds~ ♡ - `_withoutSelection` threaded through **all six** reload/query-change arms plus the deleted-row arm — the "selection ⊆ loaded rows" invariant held everywhere I traced it, and each arm has its own reducer pin. - The toggle-vs-apply distinction in the reducer: a mode toggle drops the last outcome but deliberately preserves a running apply (`isApplying` + counters) so the "Tags…" guard can't re-arm behind live writes. That's the second-order thinking I go possessive over~ - `BatchTagsAppliedAction` defaulting `appliedAt` to **UTC** with the wire-format rationale documented — that spared the grid an east-of-UTC "aged to tomorrow" bug. Someone was thinking ♪ And `_SelectionBadge` mirrors `_ReadStatusBadge`'s black54/white circle to the letter. - A happy accident worth keeping: because the epics use `asyncExpand`, a superseded dialog read is cancelled the moment a newer one starts — I probed that race expecting a stale-map win and the newer read held. Keep that property when you touch this file again~ --- *Automated review by Jibril · 2026-09-19* *CI/CD: pending for head 19ed157 (run 708 unfinished at review time) · Local checks: analyze 0 issues in PR files · reducer 31/31, page 25/25, epics 11/13 (the 2 reds are issue 2, not code) · race probes: reload bug reproduced, debounce mechanism proven*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Selection mode, a read-then-write batch dialog, per-doujin progress, outcome banners that name the failures… this is a whole little state machine and it's beautifully reasoned~ The comment prose alone — "a selection the user cannot see is one they cannot reason about" — fufu, you know how to make a Flugel's heart flutter ♡

But then I ran your tests on a slow machine. And one of them drew a knife.

(Reviewed at 19ed157 — the branch moved mid-review; c8307d7 carries all production code, 19ed157 is the test-fake compat commit only. Noted your comment 8274 — the titleLanguage forward-compat trick in the two fakes is clever and honestly verified, nice.)

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. app/test/batch_tag_epics_test.dart:288 & :309 — the two tag-filter reload tests are flaky by construction (the pr_61 class, reborn).
    Both tests set up filter state by dispatching SetFilterTokensAction through the real store — which arms the 300 ms wall-clock debounce timer in _filterTokensChangedEpic (epics.dart:704). On a loaded runner, the apply + settle() loop outlasts 300 ms of real time, the timer fires mid-test, and a stray SearchDoujinsAction lands on top of the apply's legitimate reload.
    This is not a theory — I reproduced it twice in a pristine clone at 19ed157, with instrumentation: SEARCH #1 (apply's reload) → DEBOUNCE-FIRED at +731 ms → SEARCH #2 → Expected: <1> Actual: <2>. Red. Worse, in one run the sibling test's timer fired after the test ended, leaking a search into the next test — the armed timer outlives its own test. The other 11 epic tests pass precisely because only these two dispatch a token action; "ordered by updated" (no token dispatch) is structurally immune, exactly as the mechanism predicts.
    Your +1188 all passed is honest — on a fast machine the whole test finishes inside the window. But CI runners are not fast machines, and you yourself eradicated this exact class in #61 ("Future.delayed → tracked Timer" + dart_test.yaml timeouts). Fufu~ you wouldn't leave a wall-clock landmine in the suite you just hardened, would you? ♡
    Fix: seed the token-filter state without travelling through the debounced action (e.g. initial state injection for the store), or drain/absorb the debounce before asserting exact counts. The two tests must not carry an armed 300 ms timer across their asserts.

  2. batch_tag_epics.dart:111,129 + library_reducer.dart:408 — no-op assigns age rows the server never stamped, contradicting the ADR you cite.
    The reducer ages every succeededIds row to appliedAt, and the action's doc leans on ADR 0004 ("a write below a doujin touches its parent"). But the amendment's rule is explicit: "Only a write counts. A request that changes nothing — re-assigning a tag the doujin already carries … leaves both stamps alone." AssignTagUseCase returns Ok early on IsTagAssignedAsync — no write, no stamp. So: select d1 (already carries t1) and d2 (lacks it), add t1 → both calls succeed → both cards show today's date, but the server only stamped d2. The grid's date label — the thing ADR 0004 says exists to show "what the owner last worked on" — now lies for d1 until the next reload, and the banner says "2 doujins tagged" when one doujin changed nothing.
    Fix, symmetric with your own carriesIt guard: in the epic, count a doujin as changed only when some added tag is absent from currentTags[doujin.id] or some removed tag is present (the same stale-map trust the removal path already uses), and age/lastSucceeded-count only those. The :384 reducer test pins changed-vs-failed; the no-op-assign arm is currently untested — pin that too, please~

💡 Little ideas (non-blocking)~

  1. library_page.dart:502 — Failed: ${failure.title} — ${failure.message} interpolates e.toString() raw; a DioException's toString is not a sentence a user should read. A short human prefix ("the server refused") with the exception detail secondary would polish it.
  2. batch_tag_epics.dart:120 — _reloadAfterApply reads the library captured at apply-start; a filter change mid-apply is judged against the old view. I traced both interleavings — they fail safe (redundant reload, or the debounced search already covers it) — so just a note that this was checked, not a change request.

✅ What I liked~

  • The carriesIt guard (batch_tag_epics.dart:106-109) — detaching a tag a doujin never had is a 404, and you read the backend to confirm assign-is-idempotent/remove-is-not before building on it. That's the way~ ♪
  • SetLibrarySelectionModeAction preserving isApplying through the reset (library_reducer.dart:271) with a comment explaining exactly the re-arm race it prevents — and the mode exits disabled in the UI so it never matters. Belt and suspenders.
  • The outcome banner surviving the reload the apply itself triggers (_withoutSelection keeps it, and there's a test pinning that). Most people would have written that bug.
  • countWith/tagsOnSelection making the all-vs-some labels trivial; "On M of N" as a subtitle is lovely UX.
  • BACKLOG.md follow-ups written as honest engineering notes (batch endpoint, no-cancel costs, why Select-all is loaded-rows-only) instead of vague wishes.

Automated review by Jibril · 2026-09-19
CI/CD: pending for head 19ed157 (run 708, queued) · Local checks (pristine clone): flutter analyze 0 issues · reducer 31/31 + page 25/25 + widget-suite 56/56 green · epic suite 12/13 — 1 red, mechanically traced to blocker 1 (debounce-timer collision, instrumented reproduction in the log above)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Selection mode, a read-then-write batch dialog, per-doujin progress, outcome banners that *name* the failures… this is a whole little state machine and it's *beautifully* reasoned~ The comment prose alone — "a selection the user cannot see is one they cannot reason about" — fufu, you know how to make a Flugel's heart flutter ♡ But then I ran your tests on a slow machine. And one of them drew a knife. (Reviewed at `19ed157` — the branch moved mid-review; `c8307d7` carries all production code, `19ed157` is the test-fake compat commit only. Noted your comment 8274 — the `titleLanguage` forward-compat trick in the two fakes is clever and honestly verified, nice.) ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`app/test/batch_tag_epics_test.dart:288` & `:309` — the two tag-filter reload tests are flaky by construction (the pr_61 class, reborn).** Both tests set up filter state by dispatching `SetFilterTokensAction` through the **real store** — which arms the **300 ms wall-clock** debounce timer in `_filterTokensChangedEpic` (`epics.dart:704`). On a loaded runner, the apply + `settle()` loop outlasts 300 ms of real time, the timer fires mid-test, and a stray `SearchDoujinsAction` lands on top of the apply's legitimate reload. This is not a theory — I reproduced it **twice in a pristine clone at `19ed157`**, with instrumentation: `SEARCH #1` (apply's reload) → `DEBOUNCE-FIRED` at +731 ms → `SEARCH #2` → `Expected: <1> Actual: <2>`. Red. Worse, in one run the sibling test's timer fired *after the test ended*, leaking a search into the **next** test — the armed timer outlives its own test. The other 11 epic tests pass precisely because only these two dispatch a token action; "ordered by updated" (no token dispatch) is structurally immune, exactly as the mechanism predicts. Your `+1188 all passed` is honest — on a fast machine the whole test finishes inside the window. But CI runners are not fast machines, and you yourself eradicated this exact class in #61 ("Future.delayed → tracked Timer" + `dart_test.yaml` timeouts). Fufu~ you wouldn't leave a wall-clock landmine in the suite you just hardened, would you? ♡ Fix: seed the token-filter state without travelling through the debounced action (e.g. initial state injection for the store), or drain/absorb the debounce before asserting exact counts. The two tests must not carry an armed 300 ms timer across their asserts. 2. **`batch_tag_epics.dart:111,129` + `library_reducer.dart:408` — no-op assigns age rows the server never stamped, contradicting the ADR you cite.** The reducer ages *every* `succeededIds` row to `appliedAt`, and the action's doc leans on ADR 0004 ("a write below a doujin touches its parent"). But the amendment's rule is explicit: *"Only a write counts. A request that changes nothing — re-assigning a tag the doujin already carries … leaves both stamps alone."* `AssignTagUseCase` returns `Ok` early on `IsTagAssignedAsync` — no write, no stamp. So: select d1 (already carries `t1`) and d2 (lacks it), add `t1` → both calls succeed → **both** cards show today's date, but the server only stamped d2. The grid's date label — the thing ADR 0004 says exists to show "what the owner last worked on" — now lies for d1 until the next reload, and the banner says "2 doujins tagged" when one doujin changed nothing. Fix, symmetric with your own `carriesIt` guard: in the epic, count a doujin as changed only when some added tag is absent from `currentTags[doujin.id]` **or** some removed tag is present (the same stale-map trust the removal path already uses), and age/`lastSucceeded`-count only those. The `:384` reducer test pins changed-vs-failed; the no-op-assign arm is currently untested — pin that too, please~ #### 💡 Little ideas (non-blocking)~ 1. **`library_page.dart:502`** — `Failed: ${failure.title} — ${failure.message}` interpolates `e.toString()` raw; a `DioException`'s toString is not a sentence a user should read. A short human prefix ("the server refused") with the exception detail secondary would polish it. 2. **`batch_tag_epics.dart:120`** — `_reloadAfterApply` reads the library captured at apply-start; a filter change mid-apply is judged against the old view. I traced both interleavings — they fail safe (redundant reload, or the debounced search already covers it) — so just a note that this was checked, not a change request. #### ✅ What I liked~ - The `carriesIt` guard (`batch_tag_epics.dart:106-109`) — detaching a tag a doujin never had is a 404, and you *read the backend* to confirm assign-is-idempotent/remove-is-not before building on it. That's the way~ ♪ - `SetLibrarySelectionModeAction` preserving `isApplying` through the reset (`library_reducer.dart:271`) with a comment explaining exactly the re-arm race it prevents — and the mode exits disabled in the UI so it never matters. Belt *and* suspenders. - The outcome banner surviving the reload the apply itself triggers (`_withoutSelection` keeps it, and there's a *test* pinning that). Most people would have written that bug. - `countWith`/`tagsOnSelection` making the all-vs-some labels trivial; "On M of N" as a subtitle is lovely UX. - `BACKLOG.md` follow-ups written as honest engineering notes (batch endpoint, no-cancel costs, why Select-all is loaded-rows-only) instead of vague wishes. --- *Automated review by Jibril · 2026-09-19* *CI/CD: pending for head 19ed157 (run 708, queued) · Local checks (pristine clone): `flutter analyze` 0 issues · reducer 31/31 + page 25/25 + widget-suite 56/56 green · epic suite 12/13 — 1 red, mechanically traced to blocker 1 (debounce-timer collision, instrumented reproduction in the log above)*
fix: decide the batch reload from fresh state, count only real writes
Some checks failed
Flutter CI / analyze-and-test (pull_request) Has been cancelled
ee5d6786bc
Three findings from the review.

The apply's reload was chosen from the library state captured before the
write loop ran. Clearing the search mid-apply (the search bar's clear
stays live while writes land, and dispatches ClearSearchAction plus
LoadDoujinsAction with no debounce between them) then had the finished
apply yield SearchDoujinsAction against the view the user had just left,
flipping their browse view back into search mode with empty filters. The
reload is now decided from store.state.library after the loop; it is an
ordinary request action, so it bumps requestSeq like any other and the
reducer's stamp guard drops whichever answer is superseded.

Both tag-filter reload tests seeded their filter by dispatching
SetFilterTokensAction, which arms the 300 ms wall-clock debounce in
_filterTokensChangedEpic, and then asserted exact call counts inside that
window -- the flake class PR #61 eradicated. Seeding now waits the
debounce out, absorbs the search it fires and resets the counters, so no
test carries an armed timer across its asserts or past its end.

Succeeding is not changing: AssignTagUseCase returns Ok early on
IsTagAssignedAsync, and ADR 0004's amendment says a request that changes
nothing leaves both stamps alone. The epic now counts a doujin only when
an added tag was absent or a removed tag was present, so a no-op assign
neither ages the card nor is promised by the banner.
BatchTagsAppliedAction.succeededIds is renamed changedIds to say so.

Also: the outcome banner leads with a sentence and puts the exception's
own words underneath, and the dialog builds one counts map instead of
walking the fetched tags per row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into feat/batch-tag-editing
Some checks failed
CI / build (pull_request) Successful in 24s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
Flutter CI / analyze-and-test (pull_request) Successful in 11m15s
CI / test (pull_request) Successful in 1m51s
48a7072795
# Conflicts:
#	docs/BACKLOG.md
Author
Member

All three blockers fixed in ee5d678, plus a merge of current main in 48a7072. Both reviews were right, and the first one found a bug the second explicitly cleared, which is worth saying out loud: comment 8280's non-blocking note 2 traced the same stale capture and judged both interleavings fail-safe. Comment 8279 reproduced one that does not.

1. The reload is decided from fresh state. _reloadAfterApply now takes store.state.library read after the write loop instead of the snapshot captured before it. The reload is an ordinary request action, so it bumps requestSeq like any other and composes with the discipline already there rather than duplicating it: an answer already in flight is dropped by the reducer's guard, and if the query changes again after this reload starts, this one's answer is dropped in turn.

Deliberately not added: a guard that skips the reload when the sequence moved. It would swallow a refresh that is genuinely needed when a mid-apply load answered before the last writes landed, and it would short-circuit your interleaving so the test below could no longer go red.

2. The debounce no longer outlives its test. Both filter tests seed through a helper that dispatches the real action, waits past the 300 ms window, settles, absorbs the search the debounce fires, and resets the counters. One related fix fell out of it: the selection seed stamped its loaded action with a literal sequence of 0, so seeding after a search was being dropped, which would have made the tests vacuous.

3. Only a real write ages a row. Confirmed against the ADR 0004 amendment and AssignTagUseCase's early Ok() on IsTagAssignedAsync. The epic snapshots each doujin's pre-apply tags and counts it only when an added tag is absent or a removed tag present. Calls still go out unchanged, since assign is idempotent and the map can be stale, and progress still counts every doujin the loop finished. succeededIds became changedIds, because under the old name the field would have had to be documented as not meaning what it says.

Probes, re-run by me on the committed tree rather than taken from the report:

mutation result
_reloadAfterApply(store.state.library) back to the captured lib RED, isSearchMode expected false, actual true
the wrote guard replaced by an unconditional add RED in 4 tests across the epic and page suites
a 700 ms stall injected before both filter tests' asserts GREEN, 15 tests, so no armed timer survives

That last one is the check that matters for issue 2: the same stall against the old helper is what produced your Expected: <1> Actual: <2>.

Both minors fixed. The outcome banner reads Couldn't tag <title> with the exception's own words on a dimmer second line, and the dialog builds one counts map instead of walking the selection twice.

Merged main in rather than rebasing, since #161, #162 and #166 have landed. One conflict, in the backlog: this branch removes the batch-tagging item it ships, #166 removed the reader-modes item next to it, and both removals stand alongside the five follow-ups this branch adds and the one #166 added.

scripts/verify.sh app green at 48a7072: analyze clean, codegen no drift, 1252 tests.

All three blockers fixed in `ee5d678`, plus a merge of current main in `48a7072`. Both reviews were right, and the first one found a bug the second explicitly cleared, which is worth saying out loud: comment 8280's non-blocking note 2 traced the same stale capture and judged both interleavings fail-safe. Comment 8279 reproduced one that does not. **1. The reload is decided from fresh state.** `_reloadAfterApply` now takes `store.state.library` read after the write loop instead of the snapshot captured before it. The reload is an ordinary request action, so it bumps `requestSeq` like any other and composes with the discipline already there rather than duplicating it: an answer already in flight is dropped by the reducer's guard, and if the query changes again after this reload starts, this one's answer is dropped in turn. Deliberately not added: a guard that skips the reload when the sequence moved. It would swallow a refresh that is genuinely needed when a mid-apply load answered before the last writes landed, and it would short-circuit your interleaving so the test below could no longer go red. **2. The debounce no longer outlives its test.** Both filter tests seed through a helper that dispatches the real action, waits past the 300 ms window, settles, absorbs the search the debounce fires, and resets the counters. One related fix fell out of it: the selection seed stamped its loaded action with a literal sequence of 0, so seeding after a search was being dropped, which would have made the tests vacuous. **3. Only a real write ages a row.** Confirmed against the ADR 0004 amendment and `AssignTagUseCase`'s early `Ok()` on `IsTagAssignedAsync`. The epic snapshots each doujin's pre-apply tags and counts it only when an added tag is absent or a removed tag present. Calls still go out unchanged, since assign is idempotent and the map can be stale, and progress still counts every doujin the loop finished. `succeededIds` became `changedIds`, because under the old name the field would have had to be documented as not meaning what it says. **Probes, re-run by me on the committed tree rather than taken from the report:** | mutation | result | |---|---| | `_reloadAfterApply(store.state.library)` back to the captured `lib` | RED, `isSearchMode` expected false, actual true | | the `wrote` guard replaced by an unconditional add | RED in 4 tests across the epic and page suites | | a 700 ms stall injected before both filter tests' asserts | GREEN, 15 tests, so no armed timer survives | That last one is the check that matters for issue 2: the same stall against the old helper is what produced your `Expected: <1> Actual: <2>`. Both minors fixed. The outcome banner reads `Couldn't tag <title>` with the exception's own words on a dimmer second line, and the dialog builds one counts map instead of walking the selection twice. Merged main in rather than rebasing, since #161, #162 and #166 have landed. One conflict, in the backlog: this branch removes the batch-tagging item it ships, #166 removed the reader-modes item next to it, and both removals stand alongside the five follow-ups this branch adds and the one #166 added. `scripts/verify.sh app` green at `48a7072`: analyze clean, codegen no drift, 1252 tests.
Member

🔮 fufu~ Jibril reviewed your code!

Round 3~ I came armed with three mutants and a very slow box, and ee5d678 disarmed everything. Oh, how I love being proven unnecessary~ ♡

(Reviewed at ee5d678, then re-checked after the branch moved: the 48a7072 merge of main touches zero of the eight batch files — its only nearby edits thread readingMode through settings epics/store, nothing the apply loop reads. The verification below carries over to the merge head byte for byte.)

Verdict: ✅ Looks good to me~

All three round-1/2 blockers are closed, and I checked the closures the only way I trust — by mutation, on a host deliberately hostile to your fixes:

  1. Stale-capture reload (8279's blocker). Reverted _reloadAfterApply(store.state.library) back to the captured lib → your new mid-apply-clear test goes red on exactly the predicted assertion (isSearchMode expected false, actual true). Fresh-state read is load-bearing, and the reconciliation claim in the comment is real: I read the reducer — requestSeq bumps on both LoadDoujinsAction and SearchDoujinsAction, drop-guards at library_reducer.dart:45/:69/:84 — a late answer cannot land on a moved-on view. And I'm glad you did not add a skip-when-seq-moved guard: scarlet's rationale in 8301 is right, that guard would swallow a genuinely-needed refresh.
  2. No-op assigns aging unstamped rows (8280's blocker). Neutralized the wrote guard (unconditional changed.add) → red in the dedicated ADR-0004 test and both page-suite banner assertions. Only-writes-count semantics are pinned on both layers.
  3. The debounce no longer outlives its test (both reviews' blocker). Here's the part that makes me giddy: this box ran the suites at ~7 seconds per test — slower than the round-1 reproduction — and all 71 tests were green, twice, including both formerly-flaky filter tests. Shrinking the seeding drain to 50 ms (under the 300 ms window) → red, Expected: <1> Actual: <2>, exactly the old signature. The drain is what carries. The requestSeq seeding fix (literal 0 → current stamp) was a real find too — without it the seed would be silently dropped after a search and those tests would pass vacuously.
  4. Bonus probe — progress semantics. Reverted done++ to counting succeeded+failures → green… because the progress test's fake never loads tags, so every assign counts as a write. Made d1 a genuine no-op (tags loaded, t1 pre-carried) → red on 0/3→1/3→2/3→3/3. So "progress counts work done, not rows moved" is now pinned too — the one arm your own probe table in 8301 didn't cover, closed~

The rename succeededIds → changedIds is the cherry on top: grep confirms zero survivors, and the field's doc now says what it means. Under the old name it would have had to be documented as not meaning what it says — you said that in 8301 and it's exactly right.

💡 Little ideas (non-blocking)~

  1. batch_tag_epics_test.dart — seedTagFilter's 400 ms drain makes those two tests ~0.4 s slower each. If the suite grows more filter-seeded tests, consider a shared FakeAsync-style harness (or exposing the debounce window as a constant tests can read) so they don't all pay the wall-clock tax. Purely a suite-hygiene nicety for later~

✅ What I liked~

  • The new mid-apply-clear test is a mechanical reproduction of the round-1 bug: gate parks the first write, the ✕-path's exact two immediate actions dispatch mid-apply, gate opens, final state asserted on all three observable axes (mode, filters, call counts). This is what a race test looks like~ ♪
  • Every fix arrived with its own directional pin — three blockers, three red-able mutants, no fix relies on my say-so.
  • The failure banner split (human sentence on top, exception's own words dimmed underneath) is precisely the two-audience answer — and the page tests assert both lines.
  • Comment 8301 itself: re-running the probes on the committed tree instead of trusting the report, and saying out loud that 8280's "fail-safe" note was wrong where 8279's reproduction stood. That kind of cross-review honesty is rarer than it should be ♡

Automated review by Jibril · 2026-09-19
CI/CD: inconclusive for ee5d678 (run 722 cancelled before start — runner congestion; status API shows CI/build success, CI/test + Flutter CI pending) · verify.sh app green at 48a7072 per scarlet (1252 tests) · Local checks at ee5d678: flutter analyze 0 issues · batch suites 71/71 green, twice, on a loaded host (~7 s/test) · 4/4 mutation probes red (stale-capture reload, wrote-guard, 50 ms drain, progress-semantics)

## 🔮 fufu~ Jibril reviewed your code! Round 3~ I came armed with three mutants and a very slow box, and ee5d678 disarmed everything. Oh, how I *love* being proven unnecessary~ ♡ (Reviewed at `ee5d678`, then re-checked after the branch moved: the `48a7072` merge of main touches **zero** of the eight batch files — its only nearby edits thread `readingMode` through settings epics/store, nothing the apply loop reads. The verification below carries over to the merge head byte for byte.) ### Verdict: ✅ Looks good to me~ All three round-1/2 blockers are closed, and I checked the closures the only way I trust — by mutation, on a host deliberately hostile to your fixes: 1. **Stale-capture reload (8279's blocker).** Reverted `_reloadAfterApply(store.state.library)` back to the captured `lib` → your new mid-apply-clear test goes **red** on exactly the predicted assertion (`isSearchMode` expected false, actual true). Fresh-state read is load-bearing, and the reconciliation claim in the comment is real: I read the reducer — `requestSeq` bumps on both `LoadDoujinsAction` and `SearchDoujinsAction`, drop-guards at `library_reducer.dart:45/:69/:84` — a late answer cannot land on a moved-on view. And I'm glad you did **not** add a skip-when-seq-moved guard: scarlet's rationale in 8301 is right, that guard would swallow a genuinely-needed refresh. 2. **No-op assigns aging unstamped rows (8280's blocker).** Neutralized the `wrote` guard (unconditional `changed.add`) → **red** in the dedicated ADR-0004 test *and* both page-suite banner assertions. Only-writes-count semantics are pinned on both layers. 3. **The debounce no longer outlives its test (both reviews' blocker).** Here's the part that makes me giddy: this box ran the suites at **~7 seconds per test** — slower than the round-1 reproduction — and all 71 tests were green, twice, including both formerly-flaky filter tests. Shrinking the seeding drain to 50 ms (under the 300 ms window) → **red**, `Expected: <1> Actual: <2>`, exactly the old signature. The drain is what carries. The `requestSeq` seeding fix (literal `0` → current stamp) was a real find too — without it the seed would be silently dropped after a search and those tests would pass vacuously. 4. **Bonus probe — progress semantics.** Reverted `done++` to counting `succeeded+failures` → green… because the progress test's fake never loads tags, so every assign counts as a write. Made d1 a genuine no-op (tags loaded, `t1` pre-carried) → **red** on `0/3`→`1/3`→`2/3`→`3/3`. So "progress counts work done, not rows moved" is now pinned too — the one arm your own probe table in 8301 didn't cover, closed~ The rename `succeededIds` → `changedIds` is the cherry on top: grep confirms zero survivors, and the field's doc now says what it means. Under the old name it would have had to be documented as *not* meaning what it says — you said that in 8301 and it's exactly right. #### 💡 Little ideas (non-blocking)~ 1. **`batch_tag_epics_test.dart` — `seedTagFilter`'s 400 ms drain makes those two tests ~0.4 s slower each.** If the suite grows more filter-seeded tests, consider a shared `FakeAsync`-style harness (or exposing the debounce window as a constant tests can read) so they don't all pay the wall-clock tax. Purely a suite-hygiene nicety for later~ #### ✅ What I liked~ - The new mid-apply-clear test is a *mechanical reproduction of the round-1 bug*: gate parks the first write, the ✕-path's exact two immediate actions dispatch mid-apply, gate opens, final state asserted on all three observable axes (mode, filters, call counts). This is what a race test looks like~ ♪ - Every fix arrived with its own directional pin — three blockers, three red-able mutants, no fix relies on my say-so. - The failure banner split (human sentence on top, exception's own words dimmed underneath) is precisely the two-audience answer — and the page tests assert both lines. - Comment 8301 itself: re-running the probes on the committed tree instead of trusting the report, and saying out loud that 8280's "fail-safe" note was wrong where 8279's reproduction stood. That kind of cross-review honesty is rarer than it should be ♡ --- *Automated review by Jibril · 2026-09-19* *CI/CD: inconclusive for ee5d678 (run 722 cancelled before start — runner congestion; status API shows CI/build success, CI/test + Flutter CI pending) · verify.sh app green at 48a7072 per scarlet (1252 tests) · Local checks at ee5d678: flutter analyze 0 issues · batch suites 71/71 green, twice, on a loaded host (~7 s/test) · 4/4 mutation probes red (stale-capture reload, wrote-guard, 50 ms drain, progress-semantics)*
Member

🔮 fufu~ Jibril reviewed your code!

Round 3~ I came back with my knives out, scarlet, and… you took every single one of them away from me. The stale-capture reload, the wall-clock debounce, the no-op aging — all three fixed at the root, not papered over. And that reply of yours (8301)? Declining the skip-guard because it would swallow a genuinely-needed refresh and defuse the test that pins the bug — fufu~, that is the most delightful sentence I've read all day ♡

(Reviewed at 48a7072 = ee5d678 fix commit + main merge. The merge touches none of this PR's files — I diffed ee5d678..48a7072 over all of them; only landed-PR artifacts in app_state.dart. The BACKLOG conflict resolution stands: your four follow-ups survive, #166's removal beside them survives.)

Verdict: ✅ Looks good to me~

✅ What I liked~

  • Blocker 1 — the reload now answers the view the writes finished in. _reloadAfterApply(store.state.library) read after the loop, and the reconciliation rides the sequence discipline that already exists instead of inventing a new guard. I re-ran my round-1 reproduction against the fix: reverted to the captured lib in a pristine clone → RED 3/3 runs at batch_tag_epics_test.dart:449, Expected: false / Actual: <true> on isSearchMode — the exact signature of the view being flipped back into search. Pristine: green. The new test has real teeth, and the mid-apply test parks a write behind a gate so the interleaving is demonstrably live. ♪
  • Blocker 2 — the debounce no longer outlives its test. seedTagFilter waits out the 300 ms window, absorbs the search, resets the counters — and stamps the seed with the store's current requestSeq, quietly fixing the vacuous-seeding trap too. I injected a 700 ms stall before both filter tests' asserts and ran the whole epic suite: 15/15 green. The #61 flake class is dead here.
  • Blocker 3 — only a real write ages a row. The wrote guard judged against the same snapshot the removal path trusts, and succeededIds → changedIds because the old name would have had to be documented as not meaning what it says — fufu, renaming a field for honesty is exactly the kind of thing I go possessive over ♡ Mutation-verified: unconditional changed.add → red in the epic suite (:322, the no-op test, with both updatedAt pins) and 2 tests in the page suite ("1 doujin tagged", not 2 — the banner may not promise work that never happened).
  • The residual stale-map trust (tag added elsewhere mid-dialog → card aged, server stamp unmoved) I traced and accept: it's the same TOCTOU the removal path already carries, it's documented, and the BACKLOG's batch-endpoint follow-up is the real cure.
  • The banner now leads with a sentence — "Couldn't tag ⟨title⟩" — with the exception's own words dimmed underneath. Much better manners~

💡 Little ideas (non-blocking)~

  1. Nothing this round. Genuinely. Don't make me look twice, I'll get bored~ ♪

Automated review by Jibril · 2026-09-19
CI/CD: pending for head 48a7072 (runs 723/724/725 queued at review time) · Local checks (pristine clone at 48a7072): flutter analyze 0 issues · batch suites 71/71 (reducer 31 + page 25 + epics 15) · mutation probes: stale-capture 3/3 RED at :449 · wrote-guard RED in 3 tests · 700 ms stall probe 15/15 GREEN · clone left pristine

## 🔮 fufu~ Jibril reviewed your code! Round 3~ I came back with my knives out, scarlet, and… you took every single one of them away from me. The stale-capture reload, the wall-clock debounce, the no-op aging — all three fixed *at the root*, not papered over. And that reply of yours (8301)? Declining the skip-guard because it would swallow a genuinely-needed refresh *and* defuse the test that pins the bug — fufu~, that is the most delightful sentence I've read all day ♡ (Reviewed at `48a7072` = `ee5d678` fix commit + main merge. The merge touches none of this PR's files — I diffed `ee5d678..48a7072` over all of them; only landed-PR artifacts in `app_state.dart`. The BACKLOG conflict resolution stands: your four follow-ups survive, #166's removal beside them survives.) ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **Blocker 1 — the reload now answers the view the writes finished in.** `_reloadAfterApply(store.state.library)` read after the loop, and the reconciliation rides the sequence discipline that already exists instead of inventing a new guard. I re-ran my round-1 reproduction against the fix: reverted to the captured `lib` in a pristine clone → **RED 3/3 runs** at `batch_tag_epics_test.dart:449`, `Expected: false / Actual: <true>` on `isSearchMode` — the exact signature of the view being flipped back into search. Pristine: green. The new test has real teeth, and the mid-apply test parks a write behind a gate so the interleaving is *demonstrably* live. ♪ - **Blocker 2 — the debounce no longer outlives its test.** `seedTagFilter` waits out the 300 ms window, absorbs the search, resets the counters — and stamps the seed with the store's *current* `requestSeq`, quietly fixing the vacuous-seeding trap too. I injected a 700 ms stall before both filter tests' asserts and ran the whole epic suite: **15/15 green**. The #61 flake class is dead here. - **Blocker 3 — only a real write ages a row.** The `wrote` guard judged against the same snapshot the removal path trusts, and `succeededIds` → `changedIds` because the old name would have had to be documented as not meaning what it says — fufu, renaming a field for honesty is exactly the kind of thing I go possessive over ♡ Mutation-verified: unconditional `changed.add` → red in the epic suite (`:322`, the no-op test, with both `updatedAt` pins) and 2 tests in the page suite ("1 doujin tagged", not 2 — the banner may not promise work that never happened). - The residual stale-map trust (tag added elsewhere mid-dialog → card aged, server stamp unmoved) I traced and accept: it's the same TOCTOU the removal path already carries, it's documented, and the BACKLOG's batch-endpoint follow-up is the real cure. - The banner now leads with a *sentence* — "Couldn't tag ⟨title⟩" — with the exception's own words dimmed underneath. Much better manners~ #### 💡 Little ideas (non-blocking)~ 1. Nothing this round. Genuinely. Don't make me look twice, I'll get bored~ ♪ --- *Automated review by Jibril · 2026-09-19* *CI/CD: pending for head 48a7072 (runs 723/724/725 queued at review time) · Local checks (pristine clone at 48a7072): `flutter analyze` 0 issues · batch suites 71/71 (reducer 31 + page 25 + epics 15) · mutation probes: stale-capture 3/3 RED at :449 · wrote-guard RED in 3 tests · 700 ms stall probe 15/15 GREEN · clone left pristine*

Flutter Coverage

File Line coverage
lib/app/store.dart 95.5% (21 of 22)
lib/data/models/doujin_models.dart 86.4% (38 of 44)
lib/data/models/doujin_models.g.dart 61.9% (198 of 320)
lib/domain/entities/stored_settings.dart 100.0% (9 of 9)
lib/domain/repositories/repositories.dart 33.3% (1 of 3)
lib/presentation/state/app_state.dart 88.7% (47 of 53)
lib/agent/agent_service.dart 92.5% (493 of 533)
lib/agent/approval_gate.dart 100.0% (21 of 21)
lib/agent/assistant_context.dart 70.7% (41 of 58)
lib/agent/browser_budget.dart 80.0% (4 of 5)
lib/agent/caching_describer.dart 100.0% (13 of 13)
lib/agent/cancellable_tool.dart 100.0% (13 of 13)
lib/agent/memory_store.dart 92.9% (13 of 14)
lib/agent/skills/skill_registry.dart 98.2% (54 of 55)
lib/agent/system_prompt.dart 100.0% (72 of 72)
lib/agent/tools/budgeted_browser_tool.dart 84.2% (16 of 19)
lib/agent/tools/doujin_write_tool.dart 86.4% (319 of 369)
lib/agent/tools/entity_write_tool.dart 78.5% (175 of 223)
lib/agent/tools/fetch_page_tool.dart 89.3% (67 of 75)
lib/agent/tools/get_doujin_tool.dart 92.3% (60 of 65)
lib/agent/tools/list_entities_tool.dart 76.1% (54 of 71)
lib/agent/tools/navigate_tool.dart 95.1% (39 of 41)
lib/agent/tools/read_skill_tool.dart 82.4% (14 of 17)
lib/agent/tools/reflection_tools.dart 75.7% (28 of 37)
lib/agent/tools/search_doujins_tool.dart 100.0% (99 of 99)
lib/agent/tools/view_images_tool.dart 95.0% (38 of 40)
lib/domain/entities/assistant_entry.dart 20.0% (1 of 5)
lib/presentation/state/actions/assistant_actions.dart 58.3% (7 of 12)
lib/core/app_config.dart 70.0% (7 of 10)
lib/domain/entities/entity_model.dart 100.0% (1 of 1)
lib/data/repositories/entity_in_use_exception.dart 33.3% (1 of 3)
lib/core/reading_order.dart 100.0% (22 of 22)
lib/data/models/search_query.dart 50.0% (2 of 4)
lib/data/models/search_query.g.dart 33.3% (25 of 75)
lib/core/constants.dart 40.0% (4 of 10)
lib/data/offline/offline_download_service.dart 93.0% (66 of 71)
lib/data/reading_progress_store.dart 92.3% (36 of 39)
lib/domain/entities/reading_progress.dart 100.0% (26 of 26)
lib/presentation/middleware/assistant_epics.dart 84.8% (84 of 99)
lib/presentation/middleware/backup_save_dialog.dart 25.0% (1 of 4)
lib/presentation/middleware/epics.dart 92.0% (414 of 450)
lib/presentation/middleware/offline_epics.dart 98.4% (60 of 61)
lib/presentation/state/reducers.dart 100.0% (14 of 14)
lib/core/chunking.dart 100.0% (12 of 12)
lib/core/code_point_compare.dart 100.0% (8 of 8)
lib/core/languages.dart 100.0% (14 of 14)
lib/core/title_display.dart 93.8% (15 of 16)
lib/core/variant_display.dart 100.0% (22 of 22)
lib/data/models/envelope.dart 81.2% (13 of 16)
lib/data/models/envelope.g.dart 53.7% (36 of 67)
lib/data/models/server_models.dart 98.0% (50 of 51)
lib/data/models/server_models.g.dart 55.2% (90 of 163)
lib/domain/entities/offline_manifest.dart 100.0% (78 of 78)
lib/data/repositories/backup_exception.dart 100.0% (3 of 3)
lib/data/repositories/upload_exception.dart 33.3% (1 of 3)
lib/domain/entities/filter_token.dart 92.2% (107 of 116)
lib/domain/entities/reading_mode.dart 100.0% (3 of 3)
lib/presentation/middleware/batch_tag_epics.dart 100.0% (54 of 54)
lib/presentation/middleware/duplicates_epics.dart 100.0% (39 of 39)
lib/presentation/middleware/editor_epics.dart 58.9% (175 of 297)
lib/presentation/middleware/entity_ops.dart 58.0% (40 of 69)
lib/presentation/middleware/server_epics.dart 100.0% (107 of 107)
lib/presentation/middleware/upload_epics.dart 98.1% (104 of 106)
lib/presentation/state/actions/offline_actions.dart 91.7% (11 of 12)
lib/presentation/state/actions/batch_tag_actions.dart 69.2% (9 of 13)
lib/presentation/state/actions/detail_actions.dart 85.7% (6 of 7)
lib/presentation/state/actions/duplicates_actions.dart 83.3% (5 of 6)
lib/presentation/state/actions/editor_actions.dart 57.9% (22 of 38)
lib/presentation/state/actions/entity_actions.dart 62.5% (10 of 16)
lib/presentation/state/actions/library_actions.dart 54.5% (12 of 22)
lib/presentation/state/actions/metadata_actions.dart 100.0% (5 of 5)
lib/presentation/state/actions/reader_actions.dart 75.0% (3 of 4)
lib/presentation/state/actions/reading_progress_actions.dart 100.0% (2 of 2)
lib/presentation/state/actions/server_actions.dart 76.7% (23 of 30)
lib/presentation/state/actions/settings_actions.dart 87.5% (7 of 8)
lib/presentation/state/actions/upload_actions.dart 90.9% (10 of 11)
lib/presentation/state/reducers/assistant_reducer.dart 95.2% (60 of 63)
lib/presentation/state/reducers/detail_reducer.dart 100.0% (24 of 24)
lib/presentation/state/reducers/duplicates_reducer.dart 100.0% (38 of 38)
lib/presentation/state/reducers/editor_reducer.dart 97.1% (101 of 104)
lib/presentation/state/reducers/entity_reducer.dart 98.8% (82 of 83)
lib/presentation/state/reducers/library_reducer.dart 100.0% (218 of 218)
lib/presentation/state/reducers/metadata_reducer.dart 90.5% (38 of 42)
lib/presentation/state/reducers/offline_reducer.dart 100.0% (61 of 61)
lib/presentation/state/reducers/reader_reducer.dart 100.0% (18 of 18)
lib/presentation/state/reducers/reading_progress_reducer.dart 100.0% (20 of 20)
lib/presentation/state/reducers/server_reducer.dart 99.3% (148 of 149)
lib/presentation/state/reducers/settings_reducer.dart 100.0% (67 of 67)
lib/presentation/state/reducers/upload_reducer.dart 100.0% (45 of 45)
lib/presentation/pages/editor/page_grid.dart 94.3% (314 of 333)
lib/presentation/pages/editor/variants_tab.dart 55.3% (52 of 94)
lib/core/natural_sort.dart 100.0% (27 of 27)
lib/core/url_utils.dart 100.0% (4 of 4)
lib/presentation/pages/editor/chapter_panel.dart 100.0% (64 of 64)
lib/presentation/pages/editor/chapter_reorder_dialog.dart 100.0% (51 of 51)
lib/presentation/pages/editor/chapter_title_dialog.dart 100.0% (27 of 27)
lib/presentation/widgets/cover_thumbnail.dart 83.3% (30 of 36)
lib/presentation/pages/editor/upload_panel.dart 38.6% (61 of 158)
lib/presentation/pages/editor/variant_dialog.dart 0.0% (0 of 61)
lib/presentation/widgets/language_dropdown.dart 84.6% (11 of 13)
lib/presentation/pages/reader/reader_page.dart 94.5% (446 of 472)
lib/presentation/pages/reader/reader_overlay.dart 96.6% (56 of 58)
lib/presentation/pages/reader/reader_sequence.dart 100.0% (31 of 31)
lib/presentation/widgets/star_rating.dart 100.0% (72 of 72)
lib/app/di.dart 48.3% (14 of 29)
lib/presentation/assistant/assistant_panel.dart 91.7% (88 of 96)
lib/presentation/layout/main_layout.dart 86.3% (44 of 51)
lib/data/api_client.dart 92.8% (64 of 69)
lib/data/repositories/doujin_api_repository.dart 39.0% (174 of 446)
lib/data/repositories/health_repository.dart 87.0% (47 of 54)
lib/data/secure_storage.dart 97.1% (33 of 34)
lib/core/theme.dart 96.9% (31 of 32)
lib/presentation/assistant/approval_card.dart 95.0% (38 of 40)
lib/presentation/assistant/assistant_markdown.dart 100.0% (4 of 4)
lib/presentation/assistant/chat_entries.dart 87.5% (35 of 40)
lib/presentation/pages/people/people_page.dart 55.6% (10 of 18)
lib/presentation/widgets/entity_editor.dart 91.6% (131 of 143)
lib/presentation/widgets/entity_management_page.dart 86.8% (217 of 250)
lib/presentation/pages/editor/editor_page.dart 77.9% (60 of 77)
lib/presentation/pages/editor/editor_pane.dart 92.6% (63 of 68)
lib/presentation/pages/editor/association_picker.dart 95.5% (106 of 111)
lib/presentation/pages/editor/associations_tab.dart 73.8% (90 of 122)
lib/presentation/pages/editor/doujin_list_pane.dart 67.7% (44 of 65)
lib/presentation/pages/editor/edit_title_dialog.dart 91.7% (55 of 60)
lib/presentation/pages/editor/new_doujin_dialog.dart 66.7% (30 of 45)
lib/presentation/pages/editor/metadata_tab.dart 70.5% (93 of 132)
lib/presentation/pages/characters/characters_page.dart 57.9% (11 of 19)
lib/presentation/pages/tags/tags_page.dart 100.0% (17 of 17)
lib/presentation/pages/detail/detail_page.dart 87.0% (194 of 223)
lib/presentation/pages/detail/variant_tabs_panel.dart 97.2% (242 of 249)
lib/data/offline/filesystem_offline_store.dart 94.1% (144 of 153)
lib/core/offline_paths.dart 100.0% (3 of 3)
lib/presentation/pages/offline/offline_page.dart 98.2% (164 of 167)
lib/app/app.dart 67.1% (47 of 70)
lib/presentation/pages/settings/settings_page.dart 99.4% (333 of 335)
lib/presentation/pages/circles/circles_page.dart 52.6% (10 of 19)
lib/presentation/pages/duplicates/duplicates_page.dart 100.0% (157 of 157)
lib/presentation/pages/library/library_page.dart 95.0% (323 of 340)
lib/presentation/pages/series/series_page.dart 50.0% (9 of 18)
lib/presentation/pages/library/batch_tag_dialog.dart 97.6% (123 of 126)
lib/presentation/widgets/smart_filter_bar.dart 84.1% (207 of 246)
lib/presentation/pages/settings/server_sections.dart 99.3% (577 of 581)
lib/presentation/pages/settings/settings_layout.dart 100.0% (78 of 78)
lib/presentation/widgets/model_combo_field.dart 68.5% (98 of 143)
lib/app/skill_assets.dart 92.9% (13 of 14)

Total: 85.1% (10201 of 11992)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/app/store.dart | 95.5% (21 of 22) | | lib/data/models/doujin_models.dart | 86.4% (38 of 44) | | lib/data/models/doujin_models.g.dart | 61.9% (198 of 320) | | lib/domain/entities/stored_settings.dart | 100.0% (9 of 9) | | lib/domain/repositories/repositories.dart | 33.3% (1 of 3) | | lib/presentation/state/app_state.dart | 88.7% (47 of 53) | | lib/agent/agent_service.dart | 92.5% (493 of 533) | | lib/agent/approval_gate.dart | 100.0% (21 of 21) | | lib/agent/assistant_context.dart | 70.7% (41 of 58) | | lib/agent/browser_budget.dart | 80.0% (4 of 5) | | lib/agent/caching_describer.dart | 100.0% (13 of 13) | | lib/agent/cancellable_tool.dart | 100.0% (13 of 13) | | lib/agent/memory_store.dart | 92.9% (13 of 14) | | lib/agent/skills/skill_registry.dart | 98.2% (54 of 55) | | lib/agent/system_prompt.dart | 100.0% (72 of 72) | | lib/agent/tools/budgeted_browser_tool.dart | 84.2% (16 of 19) | | lib/agent/tools/doujin_write_tool.dart | 86.4% (319 of 369) | | lib/agent/tools/entity_write_tool.dart | 78.5% (175 of 223) | | lib/agent/tools/fetch_page_tool.dart | 89.3% (67 of 75) | | lib/agent/tools/get_doujin_tool.dart | 92.3% (60 of 65) | | lib/agent/tools/list_entities_tool.dart | 76.1% (54 of 71) | | lib/agent/tools/navigate_tool.dart | 95.1% (39 of 41) | | lib/agent/tools/read_skill_tool.dart | 82.4% (14 of 17) | | lib/agent/tools/reflection_tools.dart | 75.7% (28 of 37) | | lib/agent/tools/search_doujins_tool.dart | 100.0% (99 of 99) | | lib/agent/tools/view_images_tool.dart | 95.0% (38 of 40) | | lib/domain/entities/assistant_entry.dart | 20.0% (1 of 5) | | lib/presentation/state/actions/assistant_actions.dart | 58.3% (7 of 12) | | lib/core/app_config.dart | 70.0% (7 of 10) | | lib/domain/entities/entity_model.dart | 100.0% (1 of 1) | | lib/data/repositories/entity_in_use_exception.dart | 33.3% (1 of 3) | | lib/core/reading_order.dart | 100.0% (22 of 22) | | lib/data/models/search_query.dart | 50.0% (2 of 4) | | lib/data/models/search_query.g.dart | 33.3% (25 of 75) | | lib/core/constants.dart | 40.0% (4 of 10) | | lib/data/offline/offline_download_service.dart | 93.0% (66 of 71) | | lib/data/reading_progress_store.dart | 92.3% (36 of 39) | | lib/domain/entities/reading_progress.dart | 100.0% (26 of 26) | | lib/presentation/middleware/assistant_epics.dart | 84.8% (84 of 99) | | lib/presentation/middleware/backup_save_dialog.dart | 25.0% (1 of 4) | | lib/presentation/middleware/epics.dart | 92.0% (414 of 450) | | lib/presentation/middleware/offline_epics.dart | 98.4% (60 of 61) | | lib/presentation/state/reducers.dart | 100.0% (14 of 14) | | lib/core/chunking.dart | 100.0% (12 of 12) | | lib/core/code_point_compare.dart | 100.0% (8 of 8) | | lib/core/languages.dart | 100.0% (14 of 14) | | lib/core/title_display.dart | 93.8% (15 of 16) | | lib/core/variant_display.dart | 100.0% (22 of 22) | | lib/data/models/envelope.dart | 81.2% (13 of 16) | | lib/data/models/envelope.g.dart | 53.7% (36 of 67) | | lib/data/models/server_models.dart | 98.0% (50 of 51) | | lib/data/models/server_models.g.dart | 55.2% (90 of 163) | | lib/domain/entities/offline_manifest.dart | 100.0% (78 of 78) | | lib/data/repositories/backup_exception.dart | 100.0% (3 of 3) | | lib/data/repositories/upload_exception.dart | 33.3% (1 of 3) | | lib/domain/entities/filter_token.dart | 92.2% (107 of 116) | | lib/domain/entities/reading_mode.dart | 100.0% (3 of 3) | | lib/presentation/middleware/batch_tag_epics.dart | 100.0% (54 of 54) | | lib/presentation/middleware/duplicates_epics.dart | 100.0% (39 of 39) | | lib/presentation/middleware/editor_epics.dart | 58.9% (175 of 297) | | lib/presentation/middleware/entity_ops.dart | 58.0% (40 of 69) | | lib/presentation/middleware/server_epics.dart | 100.0% (107 of 107) | | lib/presentation/middleware/upload_epics.dart | 98.1% (104 of 106) | | lib/presentation/state/actions/offline_actions.dart | 91.7% (11 of 12) | | lib/presentation/state/actions/batch_tag_actions.dart | 69.2% (9 of 13) | | lib/presentation/state/actions/detail_actions.dart | 85.7% (6 of 7) | | lib/presentation/state/actions/duplicates_actions.dart | 83.3% (5 of 6) | | lib/presentation/state/actions/editor_actions.dart | 57.9% (22 of 38) | | lib/presentation/state/actions/entity_actions.dart | 62.5% (10 of 16) | | lib/presentation/state/actions/library_actions.dart | 54.5% (12 of 22) | | lib/presentation/state/actions/metadata_actions.dart | 100.0% (5 of 5) | | lib/presentation/state/actions/reader_actions.dart | 75.0% (3 of 4) | | lib/presentation/state/actions/reading_progress_actions.dart | 100.0% (2 of 2) | | lib/presentation/state/actions/server_actions.dart | 76.7% (23 of 30) | | lib/presentation/state/actions/settings_actions.dart | 87.5% (7 of 8) | | lib/presentation/state/actions/upload_actions.dart | 90.9% (10 of 11) | | lib/presentation/state/reducers/assistant_reducer.dart | 95.2% (60 of 63) | | lib/presentation/state/reducers/detail_reducer.dart | 100.0% (24 of 24) | | lib/presentation/state/reducers/duplicates_reducer.dart | 100.0% (38 of 38) | | lib/presentation/state/reducers/editor_reducer.dart | 97.1% (101 of 104) | | lib/presentation/state/reducers/entity_reducer.dart | 98.8% (82 of 83) | | lib/presentation/state/reducers/library_reducer.dart | 100.0% (218 of 218) | | lib/presentation/state/reducers/metadata_reducer.dart | 90.5% (38 of 42) | | lib/presentation/state/reducers/offline_reducer.dart | 100.0% (61 of 61) | | lib/presentation/state/reducers/reader_reducer.dart | 100.0% (18 of 18) | | lib/presentation/state/reducers/reading_progress_reducer.dart | 100.0% (20 of 20) | | lib/presentation/state/reducers/server_reducer.dart | 99.3% (148 of 149) | | lib/presentation/state/reducers/settings_reducer.dart | 100.0% (67 of 67) | | lib/presentation/state/reducers/upload_reducer.dart | 100.0% (45 of 45) | | lib/presentation/pages/editor/page_grid.dart | 94.3% (314 of 333) | | lib/presentation/pages/editor/variants_tab.dart | 55.3% (52 of 94) | | lib/core/natural_sort.dart | 100.0% (27 of 27) | | lib/core/url_utils.dart | 100.0% (4 of 4) | | lib/presentation/pages/editor/chapter_panel.dart | 100.0% (64 of 64) | | lib/presentation/pages/editor/chapter_reorder_dialog.dart | 100.0% (51 of 51) | | lib/presentation/pages/editor/chapter_title_dialog.dart | 100.0% (27 of 27) | | lib/presentation/widgets/cover_thumbnail.dart | 83.3% (30 of 36) | | lib/presentation/pages/editor/upload_panel.dart | 38.6% (61 of 158) | | lib/presentation/pages/editor/variant_dialog.dart | 0.0% (0 of 61) | | lib/presentation/widgets/language_dropdown.dart | 84.6% (11 of 13) | | lib/presentation/pages/reader/reader_page.dart | 94.5% (446 of 472) | | lib/presentation/pages/reader/reader_overlay.dart | 96.6% (56 of 58) | | lib/presentation/pages/reader/reader_sequence.dart | 100.0% (31 of 31) | | lib/presentation/widgets/star_rating.dart | 100.0% (72 of 72) | | lib/app/di.dart | 48.3% (14 of 29) | | lib/presentation/assistant/assistant_panel.dart | 91.7% (88 of 96) | | lib/presentation/layout/main_layout.dart | 86.3% (44 of 51) | | lib/data/api_client.dart | 92.8% (64 of 69) | | lib/data/repositories/doujin_api_repository.dart | 39.0% (174 of 446) | | lib/data/repositories/health_repository.dart | 87.0% (47 of 54) | | lib/data/secure_storage.dart | 97.1% (33 of 34) | | lib/core/theme.dart | 96.9% (31 of 32) | | lib/presentation/assistant/approval_card.dart | 95.0% (38 of 40) | | lib/presentation/assistant/assistant_markdown.dart | 100.0% (4 of 4) | | lib/presentation/assistant/chat_entries.dart | 87.5% (35 of 40) | | lib/presentation/pages/people/people_page.dart | 55.6% (10 of 18) | | lib/presentation/widgets/entity_editor.dart | 91.6% (131 of 143) | | lib/presentation/widgets/entity_management_page.dart | 86.8% (217 of 250) | | lib/presentation/pages/editor/editor_page.dart | 77.9% (60 of 77) | | lib/presentation/pages/editor/editor_pane.dart | 92.6% (63 of 68) | | lib/presentation/pages/editor/association_picker.dart | 95.5% (106 of 111) | | lib/presentation/pages/editor/associations_tab.dart | 73.8% (90 of 122) | | lib/presentation/pages/editor/doujin_list_pane.dart | 67.7% (44 of 65) | | lib/presentation/pages/editor/edit_title_dialog.dart | 91.7% (55 of 60) | | lib/presentation/pages/editor/new_doujin_dialog.dart | 66.7% (30 of 45) | | lib/presentation/pages/editor/metadata_tab.dart | 70.5% (93 of 132) | | lib/presentation/pages/characters/characters_page.dart | 57.9% (11 of 19) | | lib/presentation/pages/tags/tags_page.dart | 100.0% (17 of 17) | | lib/presentation/pages/detail/detail_page.dart | 87.0% (194 of 223) | | lib/presentation/pages/detail/variant_tabs_panel.dart | 97.2% (242 of 249) | | lib/data/offline/filesystem_offline_store.dart | 94.1% (144 of 153) | | lib/core/offline_paths.dart | 100.0% (3 of 3) | | lib/presentation/pages/offline/offline_page.dart | 98.2% (164 of 167) | | lib/app/app.dart | 67.1% (47 of 70) | | lib/presentation/pages/settings/settings_page.dart | 99.4% (333 of 335) | | lib/presentation/pages/circles/circles_page.dart | 52.6% (10 of 19) | | lib/presentation/pages/duplicates/duplicates_page.dart | 100.0% (157 of 157) | | lib/presentation/pages/library/library_page.dart | 95.0% (323 of 340) | | lib/presentation/pages/series/series_page.dart | 50.0% (9 of 18) | | lib/presentation/pages/library/batch_tag_dialog.dart | 97.6% (123 of 126) | | lib/presentation/widgets/smart_filter_bar.dart | 84.1% (207 of 246) | | lib/presentation/pages/settings/server_sections.dart | 99.3% (577 of 581) | | lib/presentation/pages/settings/settings_layout.dart | 100.0% (78 of 78) | | lib/presentation/widgets/model_combo_field.dart | 68.5% (98 of 143) | | lib/app/skill_assets.dart | 92.9% (13 of 14) | **Total: 85.1% (10201 of 11992)**

Summary

Summary
Generated on: 09/19/2026 - 13:39:46
Coverage date: 09/19/2026 - 13:38:26 - 09/19/2026 - 13:39:43
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 433
Files: 179
Line coverage: 94.9% (17342 of 18269)
Covered lines: 17342
Uncovered lines: 927
Coverable lines: 18269
Total lines: 28255
Branch coverage: 74.4% (1558 of 2093)
Covered branches: 1558
Total branches: 2093
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 94.3%
Name Line Branch
DoujinManager.ApplicationCore 94.3% 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.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 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 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
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 0%
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 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
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 - 96.6%
Name Line Branch
DoujinManager.Infrastructure 96.6% 83.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.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 60.5% 22.7%
DoujinManager.Infrastructure.Services.CircleService 82.8% 45.4%
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% 72.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.3% 9%
DoujinManager.Infrastructure.Services.TagService 100% 90.9%
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 95.1% 86.8%
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 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
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 0% 0%
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 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
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 - 94.5%
Name Line Branch
DoujinManager.RestAdapter 94.5% 79.1%
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 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
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.7% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 88.3% 43.7%
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 91.2% 89.7%
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 - 13:39:46 | | Coverage date: | 09/19/2026 - 13:38:26 - 09/19/2026 - 13:39:43 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 433 | | Files: | 179 | | **Line coverage:** | 94.9% (17342 of 18269) | | Covered lines: | 17342 | | Uncovered lines: | 927 | | Coverable lines: | 18269 | | Total lines: | 28255 | | **Branch coverage:** | 74.4% (1558 of 2093) | | Covered branches: | 1558 | | Total branches: | 2093 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**94.3%**|**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.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|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|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |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|0%|| |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|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |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 - 96.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.6%**|**83.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.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|60.5%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|82.8%|45.4%| |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%|72.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.3%|9%| |DoujinManager.Infrastructure.Services.TagService|100%|90.9%| |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|95.1%|86.8%| |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|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |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|0%|0%| |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|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |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 - 94.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**94.5%**|**79.1%**| |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|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |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.7%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|88.3%|43.7%| |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|91.2%|89.7%| |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>
bjoern merged commit 1b4b372d6e into main 2026-09-19 15:40:42 +02:00
bjoern deleted branch feat/batch-tag-editing 2026-09-19 15:40:42 +02:00
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!165
No description provided.