fix: the preferred title language reaches the last four places #178

Merged
bjoern merged 5 commits from fix/the-preferred-title-language-reaches-the-last-four-places into main 2026-09-20 08:05:46 +02:00
Member

What's in

  • The assistant names works in your language. search_doujins now renders each hit through the shared summaryTitle/displayTitle rule, and asks the server to sort by the same language it prints (titleLanguage on the SearchQuery) — before this the tool could list "ordered by title asc" and then show names in a different order than it sorted. The preference travels AgentService.configure → _settings → the per-turn tool set, and _configureEpic now forwards preferredTitleLanguage so it survives a Settings save (it did not: saving any setting silently reset the assistant to the server's primary title for the rest of the session).
  • The Possible-duplicates page names members in your language, everywhere it names one. DuplicateDoujinReference/DuplicateDoujinDto carry the full titles list on both duplicate views, the app model gained titles, and one memberTitle helper feeds the chip, the delete tooltip, the destructive-delete confirmation and the merge dialog's survivor chip.
  • The two fallback bands agree. The app's _kindPriority moves onto the TitleKind order (Original > Translated > Romaji > Alias), so a card can no longer sort under a title it is not showing. This is a user-visible relabel with no migration and no announcement: a doujin with a romaji and a translated title and no original now reads under its translation on the library card, the detail AppBar, the editor list and the reader overlay from the next launch, where the app used to show the romaji.
  • Paging links keep the ordering. HypermediaHelpers.BuildCollectionLinks takes optional query state and GET /api/doujins threads sortBy/sortDirection/titleLanguage into self/next/prev (canonical enum spellings, blanks dropped, values percent-encoded), so following next no longer re-sorts the list.
  • ADR 0022 gains a consequence recording that display precedence now follows the TitleKind enum order on both sides, that the romaji elevation is about the sort key only, and what the relabel above costs. No new endpoint, no new tunable, no migration; backend/openapi/v1.json regenerated for the titles field.

Why the app moved and not the server, since the brief allowed either: DoujinOrdering.PrimaryTitleKey stays OrderBy(Kind).ThenBy(SortKey), a covering seek on IX_titles_doujin_id_kind_sort_key. Moving the server would have ordered the unhinted arm on a CASE expression no index can serve, turning the default title sort from 1 temp b-tree into 3 — the plan shape QueryPlanTests pins. No backend ordering behaviour changed, so #164's and #175's tests stay untouched and still meaningful.

Verification

What this gate ran: scripts/verify.sh all, fresh, in the branch worktree at ca228ed, on a clean tree. Result == verify OK (all) ==. Backend 1,234 tests, 0 failed (RestAdapter 870, Infrastructure 249, ApplicationCore 114, IntegrationTests 1); app flutter analyze "No issues found!" and 1,328 tests passed (was 1,326 on main — the two additions are the store-wired epic test and the sort-language test). No mutation probe was run in this gate; the probes below were run in the implementation and probing rounds at the commits named, and are reported as those rounds measured them.

Layers exercised: REST endpoints via TestHost integration (CollectionSortLinkIntegrationTests, DuplicateDetectionIntegrationTests, TitleFallbackAgreementIntegrationTests), the hypermedia helper as a unit (HypermediaHelpersTests), Infrastructure/EF over SQLite for the title load and ordering, and on the app side the wire models (models_test.dart), the pure display rule (title_display_test.dart), the agent tool, a store-wired Redux epic with a real AgentService, and the duplicates page as a widget test.

Probes that went red (each reverting one line and failing exactly the test claimed): the epic dropping preferredTitleLanguage → only a language saved in Settings survives the save and reaches the next turn; the tool dropping titleLanguage → only asks the server to sort by the same language it renders (the negative half was probed too — forcing ?? 'en' reddens the same test's isNull assertion); _confirmDelete back to doujin.title ?? 'Untitled' → only names a member by the preferred title language, everywhere it names one; _$DuplicateDoujinFromJson with titles: const [] → only deserializes members, their titles, count, sample and links from the wire. The last two were green against all 1,326 tests before this round — the confirmation dialog and the JSON seam were genuinely unpinned and a claim to the contrary in the implementation report was wrong. Also red: reverting _kindPriority (2 tests, including the cross-suite agreement fixture); reversing either server band (TitleFallbackAgreementIntegrationTests, 2 tests); weakening the new link-suffix skip from IsNullOrWhiteSpace to is null (2 tests); dropping each of sortBy/sortDirection/titleLanguage from the links, and removing the encoding (CollectionSortLinkIntegrationTests). Two round-1 findings were fixed rather than shipped: the sort-link fixture was reseeded (e08277c) because a dropped sortBy was invisible while the seeding order happened to agree with the title order, and the duplicate title load gained an explicit ORDER BY (47ed3f4) because "Original, else the first row" resolved to whatever index SQLite chose.

One probe survived, disclosed: deleting .OrderBy(t => t.Kind).ThenBy(t => t.SortKey) outright from PrimaryTitleHelper.LoadAsync leaves DuplicateDetectionIntegrationTests 18/18 green — SQLite walks IX_titles_doujin_id_kind_sort_key and returns the same rows. The behaviour is pinned from both sides (reversing the order reddens 2 tests; reading the last row instead of the first reddens 2); what no black-box fixture against this engine can separate is whether the order comes from the term or from the plan. It is kept as defence-in-depth against an index change. This gap is recorded in the code comment at ImageUseCases.cs and here — deliberately not in docs/BACKLOG.md, because it is a guard rather than an untested branch, the same class #175 documented for its three ORDER BY tiebreaks. If you would rather it were tracked, say so and it goes in.

Not verified / read only: nothing was run against a live server or the real Flutter app — every user-visible claim above rests on the tests listed and on reading the dispatch graph. In particular, that the tool's titleLanguage reaches the server for real (doujin_api_repository.dart → generated toJson → SearchEndpoints → SearchService) was confirmed by reading those four files, not by executing them; the app-side tests stop at a fake repository. The ADR 0022 consequence and the BACKLOG trim are documentation and are unprobed by construction. No README change (no named sentence moved, and titles is a DTO detail already in the regenerated OpenAPI snapshot); no deploy/DEPLOYMENT.md §2 or docker-compose.yml change, as all four gaps are correctness rules rather than tunables.

Follow-ups

  • POST /api/doujins/search answers next/prev as bare links with no page and no ordering, so following one re-runs page 1; a POST cannot carry the window in the URL, so the fix is a body template in the action or a GET search route. (docs/BACKLOG.md)
  • GET /api/tags, /api/people, /api/circles, /api/characters and /api/series bind search and then build their paging links without it, so following next on a filtered list pages the unfiltered one — now a one-argument fix each with the new optional parameter, left out because MetadataEndpoints.cs belongs to a sibling branch. (docs/BACKLOG.md)

🤖 Generated with Claude Code

## What's in - **The assistant names works in your language.** `search_doujins` now renders each hit through the shared `summaryTitle`/`displayTitle` rule, and asks the server to sort by the same language it prints (`titleLanguage` on the `SearchQuery`) — before this the tool could list "ordered by title asc" and then show names in a different order than it sorted. The preference travels `AgentService.configure` → `_settings` → the per-turn tool set, and `_configureEpic` now forwards `preferredTitleLanguage` so it survives a Settings save (it did not: saving any setting silently reset the assistant to the server's primary title for the rest of the session). - **The Possible-duplicates page names members in your language, everywhere it names one.** `DuplicateDoujinReference`/`DuplicateDoujinDto` carry the full `titles` list on both duplicate views, the app model gained `titles`, and one `memberTitle` helper feeds the chip, the delete tooltip, the destructive-delete confirmation and the merge dialog's survivor chip. - **The two fallback bands agree.** The app's `_kindPriority` moves onto the `TitleKind` order (Original > Translated > Romaji > Alias), so a card can no longer sort under a title it is not showing. **This is a user-visible relabel with no migration and no announcement:** a doujin with a romaji and a translated title and *no* original now reads under its translation on the library card, the detail AppBar, the editor list and the reader overlay from the next launch, where the app used to show the romaji. - **Paging links keep the ordering.** `HypermediaHelpers.BuildCollectionLinks` takes optional query state and `GET /api/doujins` threads `sortBy`/`sortDirection`/`titleLanguage` into self/next/prev (canonical enum spellings, blanks dropped, values percent-encoded), so following `next` no longer re-sorts the list. - **ADR 0022 gains a consequence** recording that *display* precedence now follows the `TitleKind` enum order on both sides, that the romaji elevation is about the sort **key** only, and what the relabel above costs. No new endpoint, no new tunable, no migration; `backend/openapi/v1.json` regenerated for the `titles` field. Why the app moved and not the server, since the brief allowed either: `DoujinOrdering.PrimaryTitleKey` stays `OrderBy(Kind).ThenBy(SortKey)`, a covering seek on `IX_titles_doujin_id_kind_sort_key`. Moving the server would have ordered the unhinted arm on a `CASE` expression no index can serve, turning the default title sort from 1 temp b-tree into 3 — the plan shape `QueryPlanTests` pins. No backend ordering behaviour changed, so #164's and #175's tests stay untouched and still meaningful. ## Verification **What this gate ran:** `scripts/verify.sh all`, fresh, in the branch worktree at `ca228ed`, on a clean tree. Result `== verify OK (all) ==`. Backend 1,234 tests, 0 failed (RestAdapter 870, Infrastructure 249, ApplicationCore 114, IntegrationTests 1); app `flutter analyze` "No issues found!" and 1,328 tests passed (was 1,326 on main — the two additions are the store-wired epic test and the sort-language test). No mutation probe was run in this gate; the probes below were run in the implementation and probing rounds at the commits named, and are reported as those rounds measured them. **Layers exercised:** REST endpoints via TestHost integration (`CollectionSortLinkIntegrationTests`, `DuplicateDetectionIntegrationTests`, `TitleFallbackAgreementIntegrationTests`), the hypermedia helper as a unit (`HypermediaHelpersTests`), Infrastructure/EF over SQLite for the title load and ordering, and on the app side the wire models (`models_test.dart`), the pure display rule (`title_display_test.dart`), the agent tool, a store-wired Redux epic with a real `AgentService`, and the duplicates page as a widget test. **Probes that went red** (each reverting one line and failing exactly the test claimed): the epic dropping `preferredTitleLanguage` → only `a language saved in Settings survives the save and reaches the next turn`; the tool dropping `titleLanguage` → only `asks the server to sort by the same language it renders` (the negative half was probed too — forcing `?? 'en'` reddens the same test's `isNull` assertion); `_confirmDelete` back to `doujin.title ?? 'Untitled'` → only `names a member by the preferred title language, everywhere it names one`; `_$DuplicateDoujinFromJson` with `titles: const []` → only `deserializes members, their titles, count, sample and links from the wire`. **The last two were green against all 1,326 tests before this round** — the confirmation dialog and the JSON seam were genuinely unpinned and a claim to the contrary in the implementation report was wrong. Also red: reverting `_kindPriority` (2 tests, including the cross-suite agreement fixture); reversing either server band (`TitleFallbackAgreementIntegrationTests`, 2 tests); weakening the new link-suffix skip from `IsNullOrWhiteSpace` to `is null` (2 tests); dropping each of `sortBy`/`sortDirection`/`titleLanguage` from the links, and removing the encoding (`CollectionSortLinkIntegrationTests`). Two round-1 findings were fixed rather than shipped: the sort-link fixture was reseeded (`e08277c`) because a dropped `sortBy` was invisible while the seeding order happened to agree with the title order, and the duplicate title load gained an explicit `ORDER BY` (`47ed3f4`) because "Original, else the first row" resolved to whatever index SQLite chose. **One probe survived, disclosed:** deleting `.OrderBy(t => t.Kind).ThenBy(t => t.SortKey)` outright from `PrimaryTitleHelper.LoadAsync` leaves `DuplicateDetectionIntegrationTests` 18/18 green — SQLite walks `IX_titles_doujin_id_kind_sort_key` and returns the same rows. The *behaviour* is pinned from both sides (reversing the order reddens 2 tests; reading the last row instead of the first reddens 2); what no black-box fixture against this engine can separate is whether the order comes from the term or from the plan. It is kept as defence-in-depth against an index change. **This gap is recorded in the code comment at `ImageUseCases.cs` and here — deliberately not in `docs/BACKLOG.md`**, because it is a guard rather than an untested branch, the same class #175 documented for its three `ORDER BY` tiebreaks. If you would rather it were tracked, say so and it goes in. **Not verified / read only:** nothing was run against a live server or the real Flutter app — every user-visible claim above rests on the tests listed and on reading the dispatch graph. In particular, that the tool's `titleLanguage` reaches the server for real (`doujin_api_repository.dart` → generated `toJson` → `SearchEndpoints` → `SearchService`) was confirmed by reading those four files, not by executing them; the app-side tests stop at a fake repository. The ADR 0022 consequence and the BACKLOG trim are documentation and are unprobed by construction. No README change (no named sentence moved, and `titles` is a DTO detail already in the regenerated OpenAPI snapshot); no `deploy/DEPLOYMENT.md` §2 or `docker-compose.yml` change, as all four gaps are correctness rules rather than tunables. ## Follow-ups - `POST /api/doujins/search` answers `next`/`prev` as bare links with no page and no ordering, so following one re-runs page 1; a POST cannot carry the window in the URL, so the fix is a body template in the action or a GET search route. (docs/BACKLOG.md) - `GET /api/tags`, `/api/people`, `/api/circles`, `/api/characters` and `/api/series` bind `search` and then build their paging links without it, so following `next` on a filtered list pages the unfiltered one — now a one-argument fix each with the new optional parameter, left out because `MetadataEndpoints.cs` belongs to a sibling branch. (docs/BACKLOG.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes the four #141 gaps the title sort left behind.

- search_doujins renders the preferred-language title: the setting travels through AgentService.configure and into the per-turn tool set, so agent/ still reads no Redux and imports no Flutter.

- DuplicateDoujinDto carries titles like the summary does, and the Possible-duplicates page names a member (chip, delete prompt, merge dialog) with the shared display rule.

- The fallback band agrees on both sides: title_display.dart moved onto the TitleKind order (Original > Translated > Romaji > Alias) the titles index already carries, instead of moving the server off its covering seek.

- The next/prev links on GET /api/doujins repeat sortBy, sortDirection and titleLanguage, so following one pages the list the client is reading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first probe round showed Following_Next_Keeps_The_Ordering surviving a dropped sortBy: with sortDirection=asc still in the link the fallback is created-ascending, which the old creation order (A, B, C) made agree with the English title order. Creating them C, B, A pulls the two apart, so the one assertion now covers all three parameters as its doc comment claims.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first probe round showed Primary() returning rows[0] unconditionally leaving every suite green: the title load had no ORDER BY, so "the Original kind, else the first row" was pinned by nothing and, for a doujin with no Original, resolved to whatever the planner handed back. The load now orders by kind then sort key — the same precedence DoujinOrdering.PrimaryTitleKey sorts on and title_display.dart shows — Primary() reads the first row, and a test names a member that has only an alias and a translation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: the preferred title language survives a settings save
Some checks failed
CI / build (pull_request) Successful in 26s
Deploy Smoke Test / smoke (pull_request) Failing after 2m47s
Flutter CI / analyze-and-test (pull_request) Failing after 8m50s
CI / test (pull_request) Successful in 2m14s
ca228ed530
Settings -> Save rebuilt the assistant's StoredSettings field by field and left preferredTitleLanguage out, and configure() replaces rather than merges, so the first save of the session nulled it: search_doujins printed the server's primaryTitle from then on, while a cold start looked right. The epic forwards it, and a store-wired test dispatches SettingsSavedAction through the real AgentService and asserts the rebuilt tool renders the English title.

The tool also asks the server to SORT by the language it renders. It set sortBy/sortDirection but no titleLanguage, so 'ordered by title asc' listed rows keyed on the language-agnostic primary title while the lines printed translations - gap 3's defect on the one path this branch touched.

Tests: the duplicates page's delete CONFIRMATION is now pinned as well as its tooltip (a separate call site, and the only fixture that reached it carried no titles); the DuplicateDoujin wire fixture in models_test carries a titles array, so dropping the field from fromJson goes red. BACKLOG: the anchor is trimmed to what is deferred, and names the five metadata collections that still drop search from next/prev. ADR 0022 records that display precedence follows the TitleKind order on both sides, and what that relabels.

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

Summary

Summary
Generated on: 09/20/2026 - 01:07:13
Coverage date: 09/20/2026 - 01:05:32 - 09/20/2026 - 01:07:10
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 453
Files: 187
Line coverage: 96.7% (20310 of 20999)
Covered lines: 20310
Uncovered lines: 689
Coverable lines: 20999
Total lines: 32534
Branch coverage: 78.8% (1946 of 2469)
Covered branches: 1946
Total branches: 2469
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96.6%
Name Line Branch
DoujinManager.ApplicationCore 96.6% 94.7%
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 87.5%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupContent 100%
DoujinManager.ApplicationCore.Services.BackupDirectory 100%
DoujinManager.ApplicationCore.Services.BackupFailedException 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.BackupSchedule 100% 100%
DoujinManager.ApplicationCore.Services.BackupScheduleStatus 100%
DoujinManager.ApplicationCore.Services.BackupStaging 100%
DoujinManager.ApplicationCore.Services.BackupVerification 100% 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.LikePattern 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchIndex 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.SortKey 100% 90.9%
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.DoujinMergeReport 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeResult 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.IntegrityRepairResult 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairs 96.1% 95%
DoujinManager.ApplicationCore.UseCases.IntegrityReport 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeRoleConflict 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand 100%
DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand 100%
DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 97.5%
Name Line Branch
DoujinManager.Infrastructure 97.5% 86.7%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex 96.5%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey 99.1%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey 98.6%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.SearchKeyBackfill 100% 100%
DoujinManager.Infrastructure.Data.SqliteDatabaseFile 100%
DoujinManager.Infrastructure.Data.SqliteMaintenance 78.2% 62.5%
DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor 100% 75%
DoujinManager.Infrastructure.Data.SqliteRuntimeOptions 100% 100%
DoujinManager.Infrastructure.Data.StartupMigration 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 90.1% 83.8%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupExpectations 100%
DoujinManager.Infrastructure.Services.BackupExpectationsReader 84.7% 75%
DoujinManager.Infrastructure.Services.BackupInspection 100%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 71.6% 73%
DoujinManager.Infrastructure.Services.BackupRestoreService 93.6% 88%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 92.3% 83.3%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 92% 66.6%
DoujinManager.Infrastructure.Services.CircleService 100% 72.2%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 100% 77.5%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 77.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 72.2%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96% 78.9%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 97.2% 88.4%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 87.5%
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.3% 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.IntegrityQueries 83.3% 50%
DoujinManager.Infrastructure.UseCases.ListBackupsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase 100% 98.4%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase 96.1% 80.7%
DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase 100%
DoujinManager.RestAdapter - 99%
Name Line Branch
DoujinManager.RestAdapter 99% 81%
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.IntegrityRepairResultDto 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.MergeDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.MergeReportDto 100%
DoujinManager.RestAdapter.Dtos.MergeResultDto 100%
DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderChaptersDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.RestoreRequestDto 100%
DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto 100% 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99.2%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 98.1% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.2% 50%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 97.9% 93.3%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 88.8% 87.5%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 99.8% 94.2%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 64.8%
Name Line Branch
DoujinManager.Server 64.8% 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/20/2026 - 01:07:13 | | Coverage date: | 09/20/2026 - 01:05:32 - 09/20/2026 - 01:07:10 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 453 | | Files: | 187 | | **Line coverage:** | 96.7% (20310 of 20999) | | Covered lines: | 20310 | | Uncovered lines: | 689 | | Coverable lines: | 20999 | | Total lines: | 32534 | | **Branch coverage:** | 78.8% (1946 of 2469) | | Covered branches: | 1946 | | Total branches: | 2469 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96.6%**|**94.7%**| |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|87.5%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupContent|100%|| |DoujinManager.ApplicationCore.Services.BackupDirectory|100%|| |DoujinManager.ApplicationCore.Services.BackupFailedException|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.BackupSchedule|100%|100%| |DoujinManager.ApplicationCore.Services.BackupScheduleStatus|100%|| |DoujinManager.ApplicationCore.Services.BackupStaging|100%|| |DoujinManager.ApplicationCore.Services.BackupVerification|100%|100%| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.LikePattern|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchIndex|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.SortKey|100%|90.9%| |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.DoujinMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeResult|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.IntegrityRepairResult|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairs|96.1%|95%| |DoujinManager.ApplicationCore.UseCases.IntegrityReport|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeRoleConflict|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 97.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**97.5%**|**86.7%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex|96.5%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey|99.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey|98.6%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.SearchKeyBackfill|100%|100%| |DoujinManager.Infrastructure.Data.SqliteDatabaseFile|100%|| |DoujinManager.Infrastructure.Data.SqliteMaintenance|78.2%|62.5%| |DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor|100%|75%| |DoujinManager.Infrastructure.Data.SqliteRuntimeOptions|100%|100%| |DoujinManager.Infrastructure.Data.StartupMigration|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|90.1%|83.8%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupExpectations|100%|| |DoujinManager.Infrastructure.Services.BackupExpectationsReader|84.7%|75%| |DoujinManager.Infrastructure.Services.BackupInspection|100%|| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|71.6%|73%| |DoujinManager.Infrastructure.Services.BackupRestoreService|93.6%|88%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|92.3%|83.3%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|92%|66.6%| |DoujinManager.Infrastructure.Services.CircleService|100%|72.2%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|100%|77.5%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|77.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|72.2%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96%|78.9%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|97.2%|88.4%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|87.5%| |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.3%|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.IntegrityQueries|83.3%|50%| |DoujinManager.Infrastructure.UseCases.ListBackupsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase|100%|98.4%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase|96.1%|80.7%| |DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| |DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase|100%|| </details> <details><summary>DoujinManager.RestAdapter - 99%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**99%**|**81%**| |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.IntegrityRepairResultDto|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.MergeDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderChaptersDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.RestoreRequestDto|100%|| |DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto|100%|100%| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99.2%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|98.1%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.2%|50%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|97.9%|93.3%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|88.8%|87.5%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|99.8%|94.2%| |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.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**64.8%**|**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!

Oh? Oh my~ A PR that arrives with its own honest disclosure section — "One probe survived, disclosed" — and a verification section that separates what ran from what was only read. You know how to talk to me, scarlet ♡ Four surfaces, one shared rule, and the app moved instead of the server for a stated index-shaped reason. I read all 1,357 diff lines, the full changed files, traced every sibling, and ran my own probe batteries on both sides. Settle in~

Verdict: ✅ Looks good to me~

🔬 What I verified myself (CI was absent for ca228ed — 0 comments pre-review, so local was the only gate)

  • The wire seam the PR body only read (titleLanguage: app → server) — I traced it end to end: SearchQuery.titleLanguage (search_query.dart:39/139) → doujin_api_repository.dart:50 body → SearchEndpoints.cs:106 → SearchUseCases.cs:66 → SearchService.cs:295 into DoujinOrdering.Apply(..., query.TitleLanguage). Every hop exists; the chain is real.
  • The fallback-band agreement is not just claimed, it's cross-pinned: TitleFallbackAgreementIntegrationTests (backend, where the row LANDS) shares its fixture verbatim with title_display_test.dart's "the fallback band agrees with the server sort" (where R is CALLED). TitleKind confirmed Original=0, Translated=1, Romaji=2, Alias=3 — the new _kindPriority is the enum order, exactly what IX_titles_doujin_id_kind_sort_key carries. Reverting the server instead would have broken QueryPlanTests' covering-seek pin — the "why the app moved" paragraph checks out.
  • _configureEpic completeness: StoredSettings has exactly 7 fields; the epic passes all 7. The replace-not-merge doc comment on configure is the correct structural lesson from #141.
  • 6 independent mutation probes, each red on exactly its own tests, restored byte-identical (clone pristine, git status clean):
    1. _kindPriority reverted to romaji-first → 3 red (incl. the cross-suite agreement fixture)
    2. epic drops preferredTitleLanguage → exactly the Settings-survival store-wired test red
    3. tool drops titleLanguage → exactly the sort-language test red
    4. memberTitle made to ignore titles → exactly the everywhere-it-names-one widget test red
    5. link query drops sortBy → 4 red in CollectionSortLinkIntegrationTests
    6. LoadAsync order REVERSED → exactly Duplicate_Members_Carry_Every_Title + A_Member_Without_An_Original red — which also independently reproduces your disclosed survivor: dropping the ORDER BY outright stayed green (SQLite hands back index order), so the term is defence-in-depth and the behaviour is pinned from both sides. Your disclosure is accurate, and I agree with the reasoning for keeping it out of BACKLOG (guard, not untested branch — the #175 class).
  • Local suites at ca228ed: backend build 0 errors (25 warnings, all pre-existing families: NU1903 OpenApi, EF1002, CS8602 SearchService); TitleFallback+CollectionSortLink+HypermediaHelpers 40/40; DuplicateDetection 18/18; Infrastructure 249/249; full RestAdapter 865 passed / 0 failed / 5 skipped (skips = the known #112 uid-0 SetUnixFileMode rows); app flutter analyze "No issues found!", touched suites 119/119, full suite 1328/1328 — matching your verify.sh numbers exactly.
  • Full transparency, because I demand it of you~: my first two full RestAdapter runs showed 2 failures — the same two DuplicateDetection tests. Forensics: the test project's bin still held my own probe-6-mutated Infrastructure.dll (different MD5 than src's, older timestamp; classic mutate-src-rebuild-src-only trap). After rebuilding the test project: 865/0/5. The failures were my artifact, not your code — mechanically proven, not assumed ♡

✅ What I liked~

  • CollectionSortLinkIntegrationTests is textbook directional fixture design. Seeding created-order C,B,A so that English order, primary order, and creation order are three different sequences means one page-3 assertion covers all three single-parameter losses. And Links_Omit_Parameters_The_Request_Did_Not_Send — refusing to freeze sortBy=Created into every follower's URLs — is the kind of hypermedia foresight that makes me genuinely giddy. Percent-encoding titleLanguage because it's free-form client text, tested with an injected en&sortBy=rating? Wonderful~
  • PrimaryTitleHelper growing ToReference/Primary shared by both duplicate views — the per-hash and per-doujin-set endpoints can no longer drift apart on naming. And MapToTitleDto going internal with a documented reason instead of a blind duplicate: yes.
  • The _FakeRepo gaining listDoujins so the store-wired epic test can't fail on an unrelated library re-fetch — test infrastructure chosen with care.
  • The BACKLOG rewrite turns a stale "#141 gaps" blob into two sharply-scoped survivors (POST-search links; metadata search links off MetadataEndpoints.cs), each with its fix shape named. And the ADR 0022 consequence paragraph records the relabel cost honestly — "a doujin with a romaji and a translated title and no original is now displayed under its translation" is exactly the sentence that paragraph exists for.
  • summaryTitle's three-layer fallback (displayTitle → primaryTitle → 'Untitled') keeps older-server compatibility unpinned-but-tested in both directions (models_test's d2 with no titles, and the widget test's Untitled member).

💡 Little ideas (non-blocking)~

  1. PrimaryTitleHelper.Primary vs MapToSummary — the endpoint's MapToSummary still picks Original ?? First while the helper picks lowest-kind (Original else Translated else Romaji…). For a doujin with no Original, /api/doujins rows and duplicate members can disagree on title. Your ADR consequence paragraph says the display rule is the enum order "on both sides" — the summary path is arguably the third side. Only worth touching if that divergence ever bites; it's consistent with titles being on the wire for clients that care.
  2. The editor_reducer test comment now says "pins displayTitle rather than list order" — lovely, but the fixture swap (romaji seeded first) means the test would also pass if the pane rendered primaryTitle from a server computing lowest-kind. Fine today because the app computes it locally; just noting the pin is on the value, not the rule's location.

Merge it, scarlet. The last four places now speak the same language as everything else — and the one probe that survived is disclosed, documented in code, and pinned from both directions. Nothing about that smile wavers~ ♡-mozilla


Automated review by Jibril · 2026-09-20
CI/CD: absent for head ca228ed (PR opened 02:23, no coverage bot yet) · Local checks: backend 865/870 (5 known uid-0 skips) + Infrastructure 249/249 + app 1328/1328, analyze clean, 6 mutation probes red→restored→green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ A PR that arrives with its own honest disclosure section — "One probe survived, disclosed" — and a verification section that separates what ran from what was only read. You know how to talk to me, scarlet ♡ Four surfaces, one shared rule, and the app moved instead of the server *for a stated index-shaped reason*. I read all 1,357 diff lines, the full changed files, traced every sibling, and ran my own probe batteries on both sides. Settle in~ ### Verdict: ✅ Looks good to me~ #### 🔬 What I verified myself (CI was absent for `ca228ed` — 0 comments pre-review, so local was the only gate) - **The wire seam the PR body only *read*** (`titleLanguage`: app → server) — I traced it end to end: `SearchQuery.titleLanguage` (search_query.dart:39/139) → `doujin_api_repository.dart:50` body → `SearchEndpoints.cs:106` → `SearchUseCases.cs:66` → `SearchService.cs:295` into `DoujinOrdering.Apply(..., query.TitleLanguage)`. Every hop exists; the chain is real. - **The fallback-band agreement is not just claimed, it's cross-pinned**: `TitleFallbackAgreementIntegrationTests` (backend, where the row LANDS) shares its fixture verbatim with `title_display_test.dart`'s "the fallback band agrees with the server sort" (where R is CALLED). `TitleKind` confirmed `Original=0, Translated=1, Romaji=2, Alias=3` — the new `_kindPriority` is the enum order, exactly what `IX_titles_doujin_id_kind_sort_key` carries. Reverting the server instead would have broken `QueryPlanTests`' covering-seek pin — the "why the app moved" paragraph checks out. - **`_configureEpic` completeness**: `StoredSettings` has exactly 7 fields; the epic passes all 7. The replace-not-merge doc comment on `configure` is the correct structural lesson from #141. - **6 independent mutation probes, each red on exactly its own tests, restored byte-identical (clone pristine, `git status` clean):** 1. `_kindPriority` reverted to romaji-first → 3 red (incl. the cross-suite agreement fixture) 2. epic drops `preferredTitleLanguage` → exactly the Settings-survival store-wired test red 3. tool drops `titleLanguage` → exactly the sort-language test red 4. `memberTitle` made to ignore `titles` → exactly the everywhere-it-names-one widget test red 5. link query drops `sortBy` → 4 red in `CollectionSortLinkIntegrationTests` 6. `LoadAsync` order REVERSED → exactly `Duplicate_Members_Carry_Every_Title` + `A_Member_Without_An_Original` red — which also independently reproduces your disclosed survivor: dropping the `ORDER BY` *outright* stayed green (SQLite hands back index order), so the term is defence-in-depth and the behaviour is pinned from both sides. Your disclosure is accurate, and I agree with the reasoning for keeping it out of BACKLOG (guard, not untested branch — the #175 class). - **Local suites at `ca228ed`:** backend build 0 errors (25 warnings, all pre-existing families: NU1903 OpenApi, EF1002, CS8602 SearchService); TitleFallback+CollectionSortLink+HypermediaHelpers 40/40; DuplicateDetection 18/18; Infrastructure 249/249; full RestAdapter **865 passed / 0 failed / 5 skipped** (skips = the known #112 uid-0 SetUnixFileMode rows); app `flutter analyze` "No issues found!", touched suites 119/119, full suite **1328/1328** — matching your verify.sh numbers exactly. - **Full transparency, because I demand it of you~**: my first two full RestAdapter runs showed 2 failures — the same two DuplicateDetection tests. Forensics: the test project's bin still held my *own probe-6-mutated* `Infrastructure.dll` (different MD5 than src's, older timestamp; classic mutate-src-rebuild-src-only trap). After rebuilding the test project: 865/0/5. The failures were my artifact, not your code — mechanically proven, not assumed ♡ #### ✅ What I liked~ - **`CollectionSortLinkIntegrationTests` is textbook directional fixture design.** Seeding created-order C,B,A so that English order, primary order, and creation order are *three different sequences* means one page-3 assertion covers all three single-parameter losses. And `Links_Omit_Parameters_The_Request_Did_Not_Send` — refusing to freeze `sortBy=Created` into every follower's URLs — is the kind of hypermedia foresight that makes me genuinely giddy. Percent-encoding `titleLanguage` because it's free-form client text, *tested* with an injected `en&sortBy=rating`? Wonderful~ - **`PrimaryTitleHelper` growing `ToReference`/`Primary` shared by both duplicate views** — the per-hash and per-doujin-set endpoints can no longer drift apart on naming. And `MapToTitleDto` going `internal` with a documented reason instead of a blind duplicate: yes. - **The `_FakeRepo` gaining `listDoujins` so the store-wired epic test can't fail on an unrelated library re-fetch** — test infrastructure chosen with care. - **The BACKLOG rewrite** turns a stale "#141 gaps" blob into two sharply-scoped survivors (POST-search links; metadata `search` links off `MetadataEndpoints.cs`), each with its fix shape named. And the ADR 0022 consequence paragraph records the relabel cost honestly — "a doujin with a romaji and a translated title and no original is now displayed under its translation" is exactly the sentence that paragraph exists for. - **`summaryTitle`'s three-layer fallback** (`displayTitle` → `primaryTitle` → `'Untitled'`) keeps older-server compatibility unpinned-but-tested in both directions (models_test's `d2` with no `titles`, and the widget test's Untitled member). #### 💡 Little ideas (non-blocking)~ 1. **`PrimaryTitleHelper.Primary` vs `MapToSummary`** — the endpoint's `MapToSummary` still picks `Original ?? First` while the helper picks lowest-kind (Original else Translated else Romaji…). For a doujin with no Original, `/api/doujins` rows and duplicate members can disagree on `title`. Your ADR consequence paragraph says the display rule is the enum order "on both sides" — the summary path is arguably the third side. Only worth touching if that divergence ever bites; it's consistent with `titles` being on the wire for clients that care. 2. The editor_reducer test comment now says "pins `displayTitle` rather than list order" — lovely, but the fixture swap (romaji seeded first) means the test would also pass if the pane rendered `primaryTitle` from a server computing lowest-kind. Fine today because the app computes it locally; just noting the pin is on the value, not the rule's location. Merge it, scarlet. The last four places now speak the same language as everything else — and the one probe that survived is disclosed, documented in code, and pinned from both directions. Nothing about that smile wavers~ ♡-mozilla --- *Automated review by Jibril · 2026-09-20* *CI/CD: absent for head `ca228ed` (PR opened 02:23, no coverage bot yet) · Local checks: backend 865/870 (5 known uid-0 skips) + Infrastructure 249/249 + app 1328/1328, analyze clean, 6 mutation probes red→restored→green*
bjoern merged commit 72a2a47bc1 into main 2026-09-20 08:05:46 +02:00
bjoern deleted branch fix/the-preferred-title-language-reaches-the-last-four-places 2026-09-20 08:05:46 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!178
No description provided.