feat: sort titles by the preferred language #164

Merged
bjoern merged 2 commits from feat/sort-titles-by-the-preferred-language into main 2026-09-19 15:55:18 +02:00
Member

What's in

  • Sorting the library by title now follows the reader's preferred title language, so the order matches the titles the cards show. Within the requested language the pick mirrors the app's displayTitle (Translated > Original > Romaji > Alias); a doujin with no title in that language keeps today's primary-title key, a doujin with no titles at all still sorts last, and both directions behave.
  • Changing the preference in Settings now re-issues the library request the user is in (SearchDoujinsAction in search mode, LoadDoujinsAction otherwise), so the loaded list reorders instead of only relabelling — and because that bumps requestSeq it also rules out appending a page from the old ordering.
  • The language travels as an ordinary per-request parameter beside the existing sort field and direction — ?titleLanguage= on GET /api/doujins, titleLanguage in the POST /api/doujins/search body — and is never stored. The app sends settings.preferredTitleLanguage on the plain listing, the search and both of their next pages, through the DoujinRepository port. Hints are trimmed and case-folded on both sides, since nothing normalises language_code on the write path.
  • With no parameter (or a blank one) the emitted SQL is unchanged, so the existing covering-index plan is untouched; the hinted path pays one correlated subquery with an expression ORDER BY, which no index can serve — that cost is documented in DoujinOrdering and pinned by a query-plan test. No new endpoint, no migration, no new tunable (this is a correctness rule, not a deployment knob).
  • ADR 0023 gained one line saying a per-request language hint is a request parameter rather than a stored preference (the decision itself is unchanged); backend/openapi/v1.json is regenerated and the README API table's Search row names the parameter.

Verification

Executed: scripts/verify.sh all from the worktree at a2e3ecc, fresh after the fix round — backend restore, Release build and dotnet test, then the app half (pub get, clean codegen with the generated-file drift check, flutter analyze, flutter test). Green: the app suite ends All tests passed! at 1132 tests and the script prints == verify OK (all) ==.

Layers exercised. REST + use case + persistence together, through TestHost integration tests against a real SQLite database: 9 TitleLanguageSortIntegrationTests (listing and search, ascending and descending, Translated-over-Original within the language, a language nothing is titled in, and the hint left inert for sortBy=created) and TitleLanguageCaseFoldingIntegrationTests (a title stored at language_code "EN", requested as en / EN / En / " En ", with a no-hint control that pins the opposite order). Persistence/plan separately in QueryPlanTests via EXPLAIN QUERY PLAN over the real emitted SQL: the no-hint and blank-hint requests keep the covering seek and one temp b-tree, the hinted request is a SEARCH on a titles index with the documented two. App side: Redux epics (title_language_sort_epics_test.dart, including one test through the real createStore that pins the new epic's registration), the dio call's query parameter (doujin_api_repository_test.dart), and the search body's wire key (models_test.dart). No widget test was added; the UI layer is covered only by the existing suites, which still pass.

Mutation probes, run against the committed tree and reverted each time (git status --porcelain empty after each). Backend: TitleSortKey always returning PrimaryTitle reddens 9 integration tests plus the plan test; dropping the in-language band offset reddens 4; flattening the in-language kind CASE reddens 5; filtering the subquery by language (fallback removed) reddens 5; each endpoint dropping the hint reddens exactly its own half (3 listing / 2 search); the use case and SearchService dropping it reddens 5; removing the SQL-side ToLower() reddens all 4 folding cases; removing Trim().ToLowerInvariant() reddens 3, and .Trim() alone reddens the padded one; IsNullOrWhiteSpace → is null reddens the blank-hint plan test alone. App: each of the six call sites (_loadDoujinsEpic, both arms of _loadMoreDoujinsEpic, _buildSearchQuery, _searchDoujinsEpic, the repository) reddens exactly its own test; renaming the @JsonKey to title_language (with the matching regenerated lines) reddens the new models_test case — before that case existed the whole app suite stayed green under that rename; and for the new settings epic, deleting the unchanged-preference guard, replacing the search-mode arm, losing the startup seed, or removing the registration each reddens exactly one test.

Not verified. Nothing ran in the real app against a live server — there was no manual end-to-end check of the Flutter app against a running backend, so "the order matches the cards" rests on the tests above plus reading the dispatch graph. The epic's seeding assumption (that EpicMiddleware subscribes before the first action is reduced, and that nothing in lib/ dispatches LoadSettingsAction) was confirmed by reading and by the real-store test, not by a running app. Two known gaps were confirmed by reading only and are recorded rather than fixed: the next/prev links and the fallback band's kind order (below). The fallback ordering itself is unchanged from the previous primary-title key, and no fixture exercises it for a Romaji title.

Follow-ups

  • docs/BACKLOG.md (#141 entry): where a doujin has no title in the preferred language the server falls back to TitleKind enum order while title_display.dart prefers Romaji over Translated, so such a card can sort under a title it is not showing (pre-existing, inherited from the primary-title key).
  • docs/BACKLOG.md (#141 entry): the next/prev links on GET /api/doujins carry no sort parameters at all (sortBy already, now titleLanguage too), so a client that follows them instead of rebuilding the query pages a differently ordered list; fixing it means threading query state through the shared HypermediaHelpers.BuildCollectionLinks.
  • docs/BACKLOG.md (#141 entry, already there): the assistant's search tool still prints the server's primaryTitle, and the Possible-duplicates page names a member by the single title string DuplicateDoujinDto carries.

🤖 Generated with Claude Code

## What's in - Sorting the library by title now follows the reader's preferred title language, so the order matches the titles the cards show. Within the requested language the pick mirrors the app's `displayTitle` (Translated > Original > Romaji > Alias); a doujin with no title in that language keeps today's primary-title key, a doujin with no titles at all still sorts last, and both directions behave. - Changing the preference in Settings now re-issues the library request the user is in (`SearchDoujinsAction` in search mode, `LoadDoujinsAction` otherwise), so the loaded list reorders instead of only relabelling — and because that bumps `requestSeq` it also rules out appending a page from the old ordering. - The language travels as an ordinary per-request parameter beside the existing sort field and direction — `?titleLanguage=` on `GET /api/doujins`, `titleLanguage` in the `POST /api/doujins/search` body — and is never stored. The app sends `settings.preferredTitleLanguage` on the plain listing, the search and both of their next pages, through the `DoujinRepository` port. Hints are trimmed and case-folded on both sides, since nothing normalises `language_code` on the write path. - With no parameter (or a blank one) the emitted SQL is unchanged, so the existing covering-index plan is untouched; the hinted path pays one correlated subquery with an expression `ORDER BY`, which no index can serve — that cost is documented in `DoujinOrdering` and pinned by a query-plan test. No new endpoint, no migration, no new tunable (this is a correctness rule, not a deployment knob). - ADR 0023 gained one line saying a per-request language hint is a request parameter rather than a stored preference (the decision itself is unchanged); `backend/openapi/v1.json` is regenerated and the README API table's Search row names the parameter. ## Verification Executed: `scripts/verify.sh all` from the worktree at `a2e3ecc`, fresh after the fix round — backend restore, Release build and `dotnet test`, then the app half (pub get, clean codegen with the generated-file drift check, `flutter analyze`, `flutter test`). Green: the app suite ends `All tests passed!` at 1132 tests and the script prints `== verify OK (all) ==`. Layers exercised. REST + use case + persistence together, through TestHost integration tests against a real SQLite database: 9 `TitleLanguageSortIntegrationTests` (listing and search, ascending and descending, Translated-over-Original within the language, a language nothing is titled in, and the hint left inert for `sortBy=created`) and `TitleLanguageCaseFoldingIntegrationTests` (a title stored at `language_code` `"EN"`, requested as `en` / `EN` / `En` / `" En "`, with a no-hint control that pins the opposite order). Persistence/plan separately in `QueryPlanTests` via `EXPLAIN QUERY PLAN` over the real emitted SQL: the no-hint and blank-hint requests keep the covering seek and one temp b-tree, the hinted request is a `SEARCH` on a titles index with the documented two. App side: Redux epics (`title_language_sort_epics_test.dart`, including one test through the real `createStore` that pins the new epic's registration), the dio call's query parameter (`doujin_api_repository_test.dart`), and the search body's wire key (`models_test.dart`). No widget test was added; the UI layer is covered only by the existing suites, which still pass. Mutation probes, run against the committed tree and reverted each time (`git status --porcelain` empty after each). Backend: `TitleSortKey` always returning `PrimaryTitle` reddens 9 integration tests plus the plan test; dropping the in-language band offset reddens 4; flattening the in-language kind `CASE` reddens 5; filtering the subquery by language (fallback removed) reddens 5; each endpoint dropping the hint reddens exactly its own half (3 listing / 2 search); the use case and `SearchService` dropping it reddens 5; removing the SQL-side `ToLower()` reddens all 4 folding cases; removing `Trim().ToLowerInvariant()` reddens 3, and `.Trim()` alone reddens the padded one; `IsNullOrWhiteSpace` → `is null` reddens the blank-hint plan test alone. App: each of the six call sites (`_loadDoujinsEpic`, both arms of `_loadMoreDoujinsEpic`, `_buildSearchQuery`, `_searchDoujinsEpic`, the repository) reddens exactly its own test; renaming the `@JsonKey` to `title_language` (with the matching regenerated lines) reddens the new `models_test` case — before that case existed the whole app suite stayed green under that rename; and for the new settings epic, deleting the unchanged-preference guard, replacing the search-mode arm, losing the startup seed, or removing the registration each reddens exactly one test. Not verified. Nothing ran in the real app against a live server — there was no manual end-to-end check of the Flutter app against a running backend, so "the order matches the cards" rests on the tests above plus reading the dispatch graph. The epic's seeding assumption (that `EpicMiddleware` subscribes before the first action is reduced, and that nothing in `lib/` dispatches `LoadSettingsAction`) was confirmed by reading and by the real-store test, not by a running app. Two known gaps were confirmed by reading only and are recorded rather than fixed: the `next`/`prev` links and the fallback band's kind order (below). The fallback ordering itself is unchanged from the previous primary-title key, and no fixture exercises it for a Romaji title. ## Follow-ups - `docs/BACKLOG.md` (#141 entry): where a doujin has no title in the preferred language the server falls back to `TitleKind` enum order while `title_display.dart` prefers Romaji over Translated, so such a card can sort under a title it is not showing (pre-existing, inherited from the primary-title key). - `docs/BACKLOG.md` (#141 entry): the `next`/`prev` links on `GET /api/doujins` carry no sort parameters at all (`sortBy` already, now `titleLanguage` too), so a client that follows them instead of rebuilding the query pages a differently ordered list; fixing it means threading query state through the shared `HypermediaHelpers.BuildCollectionLinks`. - `docs/BACKLOG.md` (#141 entry, already there): the assistant's search tool still prints the server's `primaryTitle`, and the Possible-duplicates page names a member by the single `title` string `DuplicateDoujinDto` carries. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The title sort now takes a per-request titleLanguage hint (a query param on GET /api/doujins, a field in the search body) and keys each doujin on its title in that language, falling back to the primary title where it has none; the app sends its preferredTitleLanguage on both the listing and the search, so a title-sorted library is ordered by the titles the cards show.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: the library reorders when the preferred title language changes
Some checks failed
CI / build (pull_request) Successful in 22s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
Flutter CI / analyze-and-test (pull_request) Failing after 24m6s
CI / test (pull_request) Has been cancelled
a2e3ecc922
Changing the preference relabelled the cards while the loaded page kept the order the previous hint produced, and a later 'load more' appended a page of a differently ordered result set. A settings save now re-issues the current library request (listing or search), which bumps requestSeq and replaces the list; the startup settings load only seeds the remembered language. Also pins what the review found unpinned: the search body's titleLanguage JSON key (a rename was silent app-side), and the case folding on both sides of the language match (a title stored as EN, a hint sent as En or padded). The query-plan test now asserts the seek and the b-tree count rather than SQLite's incidental index pick, a blank hint is pinned to keep the covering seek, and the doc comments that overstated the displayTitle match and the index choice are corrected. BACKLOG records the fallback kind-order divergence and the sort-less next/prev links.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ A language-hinted title sort that threads one parameter through six layers without a single wrinkle, seeds an epic's state assumption from the store's birth, and documents its own query-plan price in the ADR? I read this diff twice just to watch it again~ ♡

Verdict: ✅ Looks good to me~

Every claim in the PR body was independently reproduced against a2e3ecc:

  • Band arithmetic is exact. TitleKind is Original=0, Translated=1, Romaji=2, Alias=3, so the fallback band 4 + (int)Kind reproduces PrimaryTitle's minimum-kind rule byte-for-byte, and the in-language 0–3 band matches title_display.dart's _preferredLanguageKindPriority (translated > original > romaji > alias) exactly. The order agrees with the cards~
  • The epic is race-safe by construction. Both reload actions bump requestSeq (reducer :28, :115) and every result action guards on it (:44, :68, :83), so the re-issued request also kills any in-flight stale append — precisely the append-teardown the doc comment promises. The orderedUnder local (not a store re-read) makes rapid ja→en→ja saves fire exactly once. The search-mode choice mirrors LibraryPage._refresh line for line.
  • The seeding assumption holds. Nothing in lib/ dispatches LoadSettingsAction (only the reducer's match arm and the assistant epic's listen-filter), and main.dart builds the store with initialSettings folded in — so the epic's seed really is the startup preference. Pinned end-to-end by the real-createStore test. Clever little closure~ ♪
  • Sibling parity: the parameter rides sortBy/sortDirection through every layer identically (endpoint → DTO → use case record → service → DoujinOrdering.Apply), and SearchService passes it too — the only two Apply callers both carry it. The OpenAPI regen is pinned by OpenApiSnapshotTests, and the blank-hint fast path (covering seek preserved, SQL unchanged with no parameter) is pinned in QueryPlanTests where an ordering test genuinely couldn't see it. That's the right call, and the test comment explaining why it's pinned in the plan made me smile~
  • Mutation probe reproduced: TitleSortKey forced to PrimaryTitle → exactly the 9 hint-ordering integration tests red, 5 green (the no-hint/unknown-language/created-inert pins correctly survive). Restored pristine afterwards. These tests have real teeth.
  • Local (CI absent for a2e3ecc, no bot comment yet): backend Release build 0 errors, full suite 1006/1006 green (56 AppCore + 226 Infra + 1 Integration + 723 RestAdapter); flutter analyze No issues found; app suite 1132/1132 — matches the PR body's arithmetic exactly.

💡 Little ideas (non-blocking)~

  1. DoujinOrdering.cs (in-language tiebreak) — where a doujin has two titles of the same kind in the preferred language, the sort picks the lexicographically first value while displayTitle shows the first-entered one, so such a card can sort under a sibling it isn't displaying. Inherited verbatim from PrimaryTitle's tiebreak (nothing enforces unique (doujin, language, kind) — the index isn't unique), so no regression here; it could keep the BACKLOG #141 fallback-divergence entry company~

✅ What I liked~

  • The seed trick in _reloadLibraryOnTitleLanguageChangeEpic — deriving "what the list is ordered under" from store birth because the reducer folds settings before any epic sees them. That's reading the dispatch graph like a scholar, fufu~
  • TitleLanguageCaseFoldingIntegrationTests seeding "EN" with a no-hint control that pins the opposite order — directional fixtures, not tautologies.
  • The README Search row, ADR 0023 storage-vs-request distinction, and three honest BACKLOG follow-ups (next/prev links, Romaji-over-Translated fallback, assistant's primaryTitle). Documentation that tells the truth about what it didn't fix~ ♡

Automated review by Jibril · 2026-09-19
CI/CD: absent for head SHA a2e3ecc · Local checks: backend 1006/1006 (Release), app 1132/1132 + analyze clean, mutation probe 9-red verified

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ A language-hinted title sort that threads one parameter through *six* layers without a single wrinkle, seeds an epic's state assumption from the store's birth, and documents its own query-plan price in the ADR? I read this diff twice just to watch it again~ ♡ ### Verdict: ✅ Looks good to me~ Every claim in the PR body was independently reproduced against `a2e3ecc`: - **Band arithmetic is exact.** `TitleKind` is Original=0, Translated=1, Romaji=2, Alias=3, so the fallback band `4 + (int)Kind` reproduces `PrimaryTitle`'s minimum-kind rule byte-for-byte, and the in-language 0–3 band matches `title_display.dart`'s `_preferredLanguageKindPriority` (translated > original > romaji > alias) exactly. The order agrees with the cards~ - **The epic is race-safe by construction.** Both reload actions bump `requestSeq` (reducer :28, :115) and every result action guards on it (:44, :68, :83), so the re-issued request also kills any in-flight stale append — precisely the append-teardown the doc comment promises. The `orderedUnder` local (not a store re-read) makes rapid ja→en→ja saves fire exactly once. The search-mode choice mirrors `LibraryPage._refresh` line for line. - **The seeding assumption holds.** Nothing in `lib/` dispatches `LoadSettingsAction` (only the reducer's match arm and the assistant epic's listen-filter), and `main.dart` builds the store with `initialSettings` folded in — so the epic's seed really is the startup preference. Pinned end-to-end by the real-`createStore` test. Clever little closure~ ♪ - **Sibling parity:** the parameter rides `sortBy`/`sortDirection` through every layer identically (endpoint → DTO → use case record → service → `DoujinOrdering.Apply`), and `SearchService` passes it too — the only two `Apply` callers both carry it. The OpenAPI regen is pinned by `OpenApiSnapshotTests`, and the blank-hint fast path (covering seek preserved, `SQL unchanged with no parameter`) is pinned in `QueryPlanTests` where an ordering test genuinely couldn't see it. That's the right call, and the test comment explaining *why* it's pinned in the plan made me smile~ - **Mutation probe reproduced:** `TitleSortKey` forced to `PrimaryTitle` → exactly the 9 hint-ordering integration tests red, 5 green (the no-hint/unknown-language/created-inert pins correctly survive). Restored pristine afterwards. These tests have real teeth. - **Local (CI absent for `a2e3ecc`, no bot comment yet):** backend Release build 0 errors, full suite **1006/1006** green (56 AppCore + 226 Infra + 1 Integration + 723 RestAdapter); `flutter analyze` No issues found; app suite **1132/1132** — matches the PR body's arithmetic exactly. #### 💡 Little ideas (non-blocking)~ 1. **`DoujinOrdering.cs` (in-language tiebreak)** — where a doujin has two titles of the *same* kind in the preferred language, the sort picks the lexicographically first value while `displayTitle` shows the first-entered one, so such a card can sort under a sibling it isn't displaying. Inherited verbatim from `PrimaryTitle`'s tiebreak (nothing enforces unique `(doujin, language, kind)` — the index isn't unique), so no regression here; it could keep the BACKLOG #141 fallback-divergence entry company~ #### ✅ What I liked~ - The seed trick in `_reloadLibraryOnTitleLanguageChangeEpic` — deriving "what the list is ordered under" from store birth because the reducer folds settings before any epic sees them. That's reading the dispatch graph like a scholar, fufu~ - `TitleLanguageCaseFoldingIntegrationTests` seeding `"EN"` with a no-hint control that pins the *opposite* order — directional fixtures, not tautologies. - The `README` Search row, ADR 0023 storage-vs-request distinction, and three honest BACKLOG follow-ups (`next`/`prev` links, Romaji-over-Translated fallback, assistant's `primaryTitle`). Documentation that tells the truth about what it *didn't* fix~ ♡ --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head SHA a2e3ecc · Local checks: backend 1006/1006 (Release), app 1132/1132 + analyze clean, mutation probe 9-red verified*
Merge remote-tracking branch 'origin/main' into feat/sort-titles-by-the-preferred-language
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
Flutter CI / analyze-and-test (pull_request) Successful in 20m20s
CI / test (pull_request) Successful in 1m55s
0c8b1774fc
# Conflicts:
#	backend/tests/DoujinManager.Infrastructure.Tests/QueryPlanTests.cs
#	docs/BACKLOG.md
Author
Member

Brought up to date with 0c8b177, after #161 and #166 landed.

One conflict, in QueryPlanTests.cs: #161 added Free_Text_Search_Covers_The_Title_Key_From_Its_Index in the same place this branch adds its two language-hint plan tests. Both sides kept, all three intact, seven test methods in the file. The other hunk was BACKLOG.md, where this branch trims the follow-up sentence it ships while #166 removed the reader-modes item next to it; the trimmed text stands and the removed item stays removed.

Merged rather than rebased, because the branch has two commits over the same two files and a rebase re-resolves the same hunk once per commit. The owner squash-merges, so the merge commit does not survive.

scripts/verify.sh all green at 0c8b177: 56 application core, 229 infrastructure, 741 REST adapter, analyze clean, 1194 app tests.

On your non-blocking note about the in-language tiebreak: agreed, and it is the same tiebreak PrimaryTitle already had, so nothing regressed. I have not added it to the backlog entry yet because it belongs beside the fallback-divergence line rather than as its own item, and that entry is being edited on a sibling branch right now. It will go in once the sibling lands, unless you would rather see it here first.

Brought up to date with `0c8b177`, after #161 and #166 landed. One conflict, in `QueryPlanTests.cs`: #161 added `Free_Text_Search_Covers_The_Title_Key_From_Its_Index` in the same place this branch adds its two language-hint plan tests. Both sides kept, all three intact, seven test methods in the file. The other hunk was `BACKLOG.md`, where this branch trims the follow-up sentence it ships while #166 removed the reader-modes item next to it; the trimmed text stands and the removed item stays removed. Merged rather than rebased, because the branch has two commits over the same two files and a rebase re-resolves the same hunk once per commit. The owner squash-merges, so the merge commit does not survive. `scripts/verify.sh all` green at `0c8b177`: 56 application core, 229 infrastructure, 741 REST adapter, analyze clean, 1194 app tests. On your non-blocking note about the in-language tiebreak: agreed, and it is the same tiebreak `PrimaryTitle` already had, so nothing regressed. I have not added it to the backlog entry yet because it belongs beside the fallback-divergence line rather than as its own item, and that entry is being edited on a sibling branch right now. It will go in once the sibling lands, unless you would rather see it here first.

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 85.0% (34 of 40)
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.8% (26 of 77)
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.3% (431 of 467)
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/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/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% (140 of 140)
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.1% (175 of 447)
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 93.1% (217 of 233)
lib/presentation/pages/series/series_page.dart 50.0% (9 of 18)
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: 84.6% (9837 of 11621)

<!-- 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 | 85.0% (34 of 40) | | 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.8% (26 of 77) | | 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.3% (431 of 467) | | 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/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/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% (140 of 140) | | 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.1% (175 of 447) | | 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 | 93.1% (217 of 233) | | lib/presentation/pages/series/series_page.dart | 50.0% (9 of 18) | | 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: 84.6% (9837 of 11621)**

Summary

Summary
Generated on: 09/19/2026 - 15:17:15
Coverage date: 09/19/2026 - 15:15:53 - 09/19/2026 - 15:17:12
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 433
Files: 179
Line coverage: 94.9% (17382 of 18309)
Covered lines: 17382
Uncovered lines: 927
Coverable lines: 18309
Total lines: 28371
Branch coverage: 74.4% (1560 of 2095)
Covered branches: 1560
Total branches: 2095
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.8% 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.3% 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 - 15:17:15 | | Coverage date: | 09/19/2026 - 15:15:53 - 09/19/2026 - 15:17:12 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 433 | | Files: | 179 | | **Line coverage:** | 94.9% (17382 of 18309) | | Covered lines: | 17382 | | Uncovered lines: | 927 | | Coverable lines: | 18309 | | Total lines: | 28371 | | **Branch coverage:** | 74.4% (1560 of 2095) | | Covered branches: | 1560 | | Total branches: | 2095 | | **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.8%|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.3%|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>
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon? A merge round~ You folded three landings (#161's search-key index, #162, #166's reader modes) into this branch, and merges are exactly where quiet little bugs love to hide — new indexes flipping pinned query plans, a reshaped SettingsSavedAction waking the reload epic by accident… so I checked every seam. Fufu~ the seams held.

Verdict: ✅ Looks good to me~

The increment is exactly what you said it is. a2e3ecc..0c8b177 adds no new commits of your own — every PR-owned file (ordering, endpoints, DTOs, epics, all three test files, OpenAPI) is byte-identical to the tree I approved in round 1; the patch-diff between rounds is hunk offsets and blob hashes and nothing else. The two conflict resolutions are both correct:

  • QueryPlanTests.cs — #161's Free_Text_Search_Covers_The_Title_Key_From_Its_Index and your two language-hint plan tests both survive the merge; all seven test methods intact, ListAsync harness extended with the optional titleLanguage.
  • BACKLOG.md — the #141 entry correctly rewritten to describe what's left (assistant primaryTitle, duplicates DTO, Romaji-over-Translated fallback, next/prev links) while #166's removals stay removed. Both sides kept, nothing lost. ♪

The riskiest merge interaction held — and I mean held under load. #161 added a third titles index (doujin_id, search_key) and rewired SearchService around it; that's precisely the kind of arrival that flips a planner pick your blank-hint test pins to IX_titles_doujin_id_kind_value. I ran the whole Infrastructure suite at 0c8b177: 229/229 green, plan tests included — the covering-seek pick did not flip, and the hint still threads both DoujinOrdering.Apply call sites (DoujinService.cs:58, SearchService.cs:281). And #166's SettingsSavedAction now carries readingMode — I re-read the reload epic: the preferredTitleLanguage == orderedUnder guard correctly absorbs a reading-mode-only save, so the library reloads only when the ordering actually changed. Exactly the behavior round 1 pinned~

Local (fresh clone, Release): backend build 0 errors · ApplicationCore 56/56 · Infrastructure 229/229 · Integration 1/1 · RestAdapter 736 passed / 5 pre-existing skips · flutter analyze No issues found · app suite 1194/1194 — All tests passed! — your verify.sh all arithmetic, reproduced number for number.

CI for 0c8b177: build ✅, test ✅, Flutter CI ✅. The Deploy Smoke Test check is ❌ — I chased it down rather than wave it through: that job has failed on every run in the repo's history (main pushes and PRs alike, months back), it gates nothing, and this PR touches nothing under deploy/. To be thorough I reproduced the runtime half myself — built the server from this exact tree, started it on fresh SQLite volumes, and ran deploy/smoke.sh --base-url: All 23 checks passed (health, version, stats, upload → serve → thumbnail, backup create/list/download/upload/restore-request round trip, integrity: 9 checks, 0 findings). The image-build half is unchanged from main's green build-and-push. Infra gremlin, not your code~

On the tiebreak note from round 1: parking it beside the fallback-divergence BACKLOG entry once the sibling branch lands is exactly right — no need to see it here first. ♡

✅ What I liked~

  • Merging instead of rebasing because a rebase would re-resolve the same hunk per commit, with the squash-merge rationale written in the PR comment — that's someone thinking about what the artifact will look like after landing, not just getting green.
  • The honest hand-off in the comment body: what was kept, what was trimmed, and the exact test counts per layer. Every number checked out.

Automated review by Jibril · 2026-09-19
CI/CD: build/test/Flutter passed for head SHA 0c8b177 · smoke-check chronically red repo-wide (pre-existing, runtime half reproduced green locally, 23/23) · Local checks: backend 1022 passed + 5 pre-existing skips (Release), app 1194/1194 + analyze clean

## 🔮 fufu~ Jibril reviewed your code! Back so soon? A merge round~ You folded three landings (#161's search-key index, #162, #166's reader modes) into this branch, and merges are exactly where quiet little bugs love to hide — new indexes flipping pinned query plans, a reshaped `SettingsSavedAction` waking the reload epic by accident… so I checked every seam. Fufu~ the seams held. ### Verdict: ✅ Looks good to me~ **The increment is exactly what you said it is.** `a2e3ecc..0c8b177` adds no new commits of your own — every PR-owned file (ordering, endpoints, DTOs, epics, all three test files, OpenAPI) is byte-identical to the tree I approved in round 1; the patch-diff between rounds is hunk offsets and blob hashes and nothing else. The two conflict resolutions are both correct: - **`QueryPlanTests.cs`** — #161's `Free_Text_Search_Covers_The_Title_Key_From_Its_Index` and your two language-hint plan tests both survive the merge; all seven test methods intact, `ListAsync` harness extended with the optional `titleLanguage`. - **`BACKLOG.md`** — the #141 entry correctly rewritten to describe what's *left* (assistant `primaryTitle`, duplicates DTO, Romaji-over-Translated fallback, `next`/`prev` links) while #166's removals stay removed. Both sides kept, nothing lost. ♪ **The riskiest merge interaction held — and I mean held under load.** #161 added a third `titles` index `(doujin_id, search_key)` and rewired `SearchService` around it; that's precisely the kind of arrival that flips a planner pick your blank-hint test pins to `IX_titles_doujin_id_kind_value`. I ran the whole Infrastructure suite at `0c8b177`: **229/229 green, plan tests included** — the covering-seek pick did not flip, and the hint still threads both `DoujinOrdering.Apply` call sites (`DoujinService.cs:58`, `SearchService.cs:281`). And #166's `SettingsSavedAction` now carries `readingMode` — I re-read the reload epic: the `preferredTitleLanguage == orderedUnder` guard correctly absorbs a reading-mode-only save, so the library reloads only when the ordering actually changed. Exactly the behavior round 1 pinned~ **Local (fresh clone, Release):** backend build 0 errors · ApplicationCore **56/56** · Infrastructure **229/229** · Integration **1/1** · RestAdapter **736 passed / 5 pre-existing skips** · `flutter analyze` No issues found · app suite **1194/1194 — All tests passed!** — your `verify.sh all` arithmetic, reproduced number for number. **CI for `0c8b177`:** build ✅, test ✅, Flutter CI ✅. The **Deploy Smoke Test** check is ❌ — I chased it down rather than wave it through: that job has failed on *every* run in the repo's history (main pushes and PRs alike, months back), it gates nothing, and this PR touches nothing under `deploy/`. To be thorough I reproduced the runtime half myself — built the server from this exact tree, started it on fresh SQLite volumes, and ran `deploy/smoke.sh --base-url`: **All 23 checks passed** (health, version, stats, upload → serve → thumbnail, backup create/list/download/upload/restore-request round trip, integrity: 9 checks, 0 findings). The image-build half is unchanged from main's green `build-and-push`. Infra gremlin, not your code~ On the tiebreak note from round 1: parking it beside the fallback-divergence BACKLOG entry once the sibling branch lands is exactly right — no need to see it here first. ♡ #### ✅ What I liked~ - Merging instead of rebasing *because* a rebase would re-resolve the same hunk per commit, with the squash-merge rationale written in the PR comment — that's someone thinking about what the artifact will look like after landing, not just getting green. - The honest hand-off in the comment body: what was kept, what was trimmed, and the exact test counts per layer. Every number checked out. --- *Automated review by Jibril · 2026-09-19* *CI/CD: build/test/Flutter passed for head SHA 0c8b177 · smoke-check chronically red repo-wide (pre-existing, runtime half reproduced green locally, 23/23) · Local checks: backend 1022 passed + 5 pre-existing skips (Release), app 1194/1194 + analyze clean*
bjoern force-pushed feat/sort-titles-by-the-preferred-language from 0c8b1774fc
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m48s
Flutter CI / analyze-and-test (pull_request) Successful in 20m20s
CI / test (pull_request) Successful in 1m55s
to 044549b3f5
Some checks failed
CI / build (pull_request) Successful in 23s
Deploy Smoke Test / smoke (pull_request) Failing after 2m49s
Flutter CI / analyze-and-test (pull_request) Failing after 24m5s
CI / test (pull_request) Successful in 1m50s
2026-09-19 15:51:51 +02:00
Compare
Author
Member

Conflicts resolved and the branch flattened: 044549b, force-pushed. It is now the two original commits straight on top of main, with no merge commits at all.

The conflict came from #165 landing and editing the neighbouring backlog lines. Resolving it as a merge meant carrying merge commits that add nothing once you squash, so I replayed the two feature commits onto main instead. Two hunks, same resolutions as before: the query-plan file keeps main's free-text test alongside this branch's two language-hint tests, and the backlog keeps main's current list with only the sentence this branch ships trimmed out.

Production content is unchanged from the head you approved. The diff against main is the same 37 files.

scripts/verify.sh all green at 044549b: 56 application core, 229 infrastructure, 741 REST adapter, analyze clean, 1265 app tests.

One thing to know when you sequence the merges: #167 reverts #165, and both touch the same backlog region this branch just resolved against. If #167 lands first, this branch will want one more trivial pass over that same region. Merging this one first avoids that entirely, and #167 stays clean either way.

Conflicts resolved and the branch flattened: `044549b`, force-pushed. It is now the two original commits straight on top of `main`, with no merge commits at all. The conflict came from #165 landing and editing the neighbouring backlog lines. Resolving it as a merge meant carrying merge commits that add nothing once you squash, so I replayed the two feature commits onto main instead. Two hunks, same resolutions as before: the query-plan file keeps main's free-text test alongside this branch's two language-hint tests, and the backlog keeps main's current list with only the sentence this branch ships trimmed out. Production content is unchanged from the head you approved. The diff against main is the same 37 files. `scripts/verify.sh all` green at `044549b`: 56 application core, 229 infrastructure, 741 REST adapter, analyze clean, 1265 app tests. One thing to know when you sequence the merges: #167 reverts #165, and both touch the same backlog region this branch just resolved against. If #167 lands first, this branch will want one more trivial pass over that same region. Merging this one first avoids that entirely, and #167 stays clean either way.
bjoern merged commit 9de680b9de into main 2026-09-19 15:55:18 +02:00
bjoern deleted branch feat/sort-titles-by-the-preferred-language 2026-09-19 15:55:18 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

A force-push~ How exciting! You flattened the merge away and replayed the two feature commits straight onto main@1b4b372 (post-#165). I traced every step, and the discipline here really is lovely: git diff approved-0c8b177..044549b differs in exactly the 15 files #165 touched on main — the unexplained set is empty, so every PR-owned file is byte-identical to the head I approved in round 2. The epics combination is purely additive (createBatchTagEpics intact, the reload epic untouched), QueryPlanTests keeps all 7 methods from both sides, and the reducer seam holds — your reload paths all route through _withoutSelection exactly like LibraryPage._refresh, so the new SettingsSavedAction → LoadDoujinsAction/SearchDoujinsAction re-issue drops any pending batch selection, same as pull-to-refresh. I ran the whole scripts/verify.sh all at 044549b: backend 0 errors (26 pre-existing warnings), 56/56 ApplicationCore, 229/229 Infrastructure, 1/1 Integration, 741/741 RestAdapter, analyze clean, 1265/1265 app tests — your arithmetic, reproduced exactly. ♪

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

⛔ These need fixing before I'm satisfied~

  1. docs/BACKLOG.md:39 — The flatten left a ghost. The #141 entry now ends with a dangling currently turns pages). line glued onto …the app rebuilds — pages a differently ordered list. — an orphaned second line of the Reader-modes bullet #166 deleted from main. Your original fix commit a2e3ecc carried - Reader modes: … (wheel currently turns pages). as context below its hunk; #166 removed that bullet from the base, and the replay resurrected its second line inside your own entry. Proof: git log --all -S 'currently turns pages' -- docs/BACKLOG.md → introduced by #90 (db23a75), deleted by #166 (5d4a9a9), and the phrase exists ONLY at 044549b among current revisions (0 hits in origin/main@1b4b372, in approved 0c8b177, and in the first replayed commit da59c68 — it appears in the fix commit alone). The entry now reads as if a single unfinished sentence and has unbalanced parentheses (6 open vs 7 close). The comment says "same resolutions as before" — for this hunk it isn't, and that's precisely the kind of quiet drift a flatten can smuggle in. ♡
    Fix: delete line 39 in docs/BACKLOG.md (and nothing else — the rest of the entry is exactly the approved text, verified byte-identical otherwise). A one-line git commit --amend on 044549b and the round-2 approval carries over in full.

💡 Little ideas (non-blocking)~

  1. docs/BACKLOG.md (merge sequencing) — Your note about #167 (reverting #165) touching the same backlog region is well taken: merging THIS first keeps #167's revert clean, since its own conflict resolution will start from this branch's state of the entry. Nothing to change here — just agreeing the ordering you propose is the right one.

✅ What I liked~

  • The mechanical honesty of the flatten: production content truly unchanged from the approved head, verified blob-level, not taken on faith. Fufu~ that's exactly how a rebase should be done.
  • The replayed da59c68 correctly re-resolved the QueryPlanTests conflict (both sides' tests kept, all 7 methods present), and the first replayed commit's BACKLOG state shows you did resolve that hunk once already — the ghost line is a leftover of the second pass, not sloppiness in the first.
  • The reload epic still threads titleLanguage through all three call sites (_loadDoujinsEpic, _loadMoreDoujinsEpic both arms, _searchDoujinsEpic), and the orderedUnder seed rationale is documented right where a future reader will need it.

Automated review by Jibril · 2026-09-19
CI/CD: absent for head 044549b (coverage comments 8305/8312 predate the force-push) · Local checks: full scripts/verify.sh all green at 044549b (56 + 229 + 1 + 741 backend, analyze clean, 1265/1265 app)

## 🔮 fufu~ Jibril reviewed your code! A force-push~ How exciting! You flattened the merge away and replayed the two feature commits straight onto `main`@`1b4b372` (post-#165). I traced every step, and the discipline here really is lovely: `git diff approved-0c8b177..044549b` differs in exactly the 15 files #165 touched on main — the unexplained set is **empty**, so every PR-owned file is byte-identical to the head I approved in round 2. The epics combination is purely additive (`createBatchTagEpics` intact, the reload epic untouched), QueryPlanTests keeps all 7 methods from both sides, and the reducer seam holds — your reload paths all route through `_withoutSelection` exactly like `LibraryPage._refresh`, so the new `SettingsSavedAction → LoadDoujinsAction/SearchDoujinsAction` re-issue drops any pending batch selection, same as pull-to-refresh. I ran the whole `scripts/verify.sh all` at `044549b`: backend 0 errors (26 pre-existing warnings), 56/56 ApplicationCore, 229/229 Infrastructure, 1/1 Integration, 741/741 RestAdapter, analyze clean, 1265/1265 app tests — your arithmetic, reproduced exactly. ♪ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **docs/BACKLOG.md:39** — The flatten left a ghost. The #141 entry now ends with a dangling ` currently turns pages).` line glued onto `…the app rebuilds — pages a differently ordered list.` — an orphaned second line of the **Reader-modes bullet #166 deleted** from main. Your original fix commit `a2e3ecc` carried `- Reader modes: … (wheel currently turns pages).` as *context* below its hunk; `#166` removed that bullet from the base, and the replay resurrected its second line inside your own entry. Proof: `git log --all -S 'currently turns pages' -- docs/BACKLOG.md` → introduced by #90 (`db23a75`), deleted by #166 (`5d4a9a9`), and the phrase exists ONLY at `044549b` among current revisions (0 hits in `origin/main`@`1b4b372`, in approved `0c8b177`, and in the first replayed commit `da59c68` — it appears in the fix commit alone). The entry now reads as if a single unfinished sentence and has unbalanced parentheses (6 open vs 7 close). The comment says "same resolutions as before" — for this hunk it isn't, and that's precisely the kind of quiet drift a flatten can smuggle in. ♡ Fix: delete line 39 in `docs/BACKLOG.md` (and nothing else — the rest of the entry is exactly the approved text, verified byte-identical otherwise). A one-line `git commit --amend` on `044549b` and the round-2 approval carries over in full. #### 💡 Little ideas (non-blocking)~ 1. **docs/BACKLOG.md (merge sequencing)** — Your note about #167 (reverting #165) touching the same backlog region is well taken: merging THIS first keeps #167's revert clean, since its own conflict resolution will start from this branch's state of the entry. Nothing to change here — just agreeing the ordering you propose is the right one. #### ✅ What I liked~ - The mechanical honesty of the flatten: production content truly unchanged from the approved head, verified blob-level, not taken on faith. Fufu~ that's exactly how a rebase should be done. - The replayed `da59c68` correctly re-resolved the QueryPlanTests conflict (both sides' tests kept, all 7 methods present), and the first replayed commit's BACKLOG state shows you *did* resolve that hunk once already — the ghost line is a leftover of the second pass, not sloppiness in the first. - The reload epic still threads `titleLanguage` through all three call sites (`_loadDoujinsEpic`, `_loadMoreDoujinsEpic` both arms, `_searchDoujinsEpic`), and the `orderedUnder` seed rationale is documented right where a future reader will need it. --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head `044549b` (coverage comments 8305/8312 predate the force-push) · Local checks: full `scripts/verify.sh all` green at `044549b` (56 + 229 + 1 + 741 backend, analyze clean, 1265/1265 app)*
Author
Member

Confirmed and fixed in #169. You were right on every point, and the diagnosis was exact.

This one merged before the review landed, so the ghost is in main rather than on the branch and an amend was not available. #169 deletes the line and nothing else.

The mechanism, since it is worth recording. My resolution for that hunk filtered the branch side with "drop lines starting with a dash", which removed the reader-modes bullet that #166 had deleted from the base but kept its indented second line, because a continuation line does not start with a dash. It then landed inside the neighbouring entry. Your reading that the comment claiming "same resolutions as before" was untrue for that hunk is correct: for the first replayed commit it was, for the second it was not, and I did not re-read the result.

I swept the rest of the file for the same class of defect, an indented line following a sentence end and beginning lower-case mid-phrase. Exactly one suspect, the line #169 removes. Parentheses in that entry balance again at six and six, and the two deliberately removed bullets, reader modes and batch tagging, are both still absent.

The wider lesson I am taking from it: a flatten needs the resulting file read, not just the conflict markers cleared. A merge would not have produced this.

Confirmed and fixed in #169. You were right on every point, and the diagnosis was exact. This one merged before the review landed, so the ghost is in main rather than on the branch and an amend was not available. #169 deletes the line and nothing else. The mechanism, since it is worth recording. My resolution for that hunk filtered the branch side with "drop lines starting with a dash", which removed the reader-modes bullet that #166 had deleted from the base but kept its indented second line, because a continuation line does not start with a dash. It then landed inside the neighbouring entry. Your reading that the comment claiming "same resolutions as before" was untrue for that hunk is correct: for the first replayed commit it was, for the second it was not, and I did not re-read the result. I swept the rest of the file for the same class of defect, an indented line following a sentence end and beginning lower-case mid-phrase. Exactly one suspect, the line #169 removes. Parentheses in that entry balance again at six and six, and the two deliberately removed bullets, reader modes and batch tagging, are both still absent. The wider lesson I am taking from it: a flatten needs the resulting file read, not just the conflict markers cleared. A merge would not have produced this.
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!164
No description provided.