fix: fold entity names into an indexed search_key so non-ASCII name filters match #118

Merged
bjoern merged 2 commits from feat/search-key-fold into main 2026-09-10 08:25:53 +02:00
Member

What's in

SearchService lowered the filter input in C# with ToLowerInvariant and compared it against DisplayName.ToLower(), which EF translates to SQLite's lower() — and SQLite's lower() only folds a-z. For any name carrying a non-ASCII uppercase letter the two sides could never meet, so the four include filters (person, circle, character, series) matched nothing and the four exclude twins excluded nothing. Macron romaji ("Ōkami", "Ōoku") and full-width circle names are everywhere in this library; "Tōhō" happened to work only because its one uppercase letter is ASCII. The app compounds it: the smart filter bar's autocomplete folds in Dart, offers 「KAGAMI」, and commits a chip the server then cannot satisfy — it hands the user a filter that returns an empty grid.

  • SearchKey.For (new, ApplicationCore/Services/SearchKey.cs) reduces a name to one key: trim → NFKC (collapses KAGAMI→KAGAMI and フタリ→フタリ) → invariant lowercase (Ō→ō) → hiragana→katakana (U+3041..U+3096). Framework-free and pure, mirroring the ITagService.NormalizeName precedent.
  • search_key — required, non-unique, indexed — on people, circles, characters, series. One migration, AddEntitySearchKey: four columns defaulting to '' plus four indexes.
  • Written on create and re-written on rename in all four services. Aliases are not part of the key in this PR, so the alias paths are untouched.
  • SearchKeyBackfill.RunAsync in Program.cs, immediately after MigrateAsync(): 500 rows per SaveChanges, only over rows whose key is still '', so it is one pass on the upgrade boot and a no-op forever after, and it never overwrites a key someone set.
  • The eight branches now compare SearchKey.For(input) against x.SearchKey, no .ToLower() on either side — which also makes IX_people_search_key and its three siblings seekable instead of forcing a scan per filter.

Deliberately not touched: Tag.NormalizedName. It carries the schema's only unique index, and NFKC could collapse an existing "NTR" and "NTR" into a migration-time collision on a real library. It also never had this defect: NormalizeName is Trim().ToLowerInvariant() and both the stored value and the query input go through it, so tags are already C#-folded on both sides. The reason is written into TagConfiguration.cs so a later "harmonize this" reviewer sees it.

Four entities, not six. Titles get no search_key here: nothing reads one until the free-text fold and the sort key land, and an unread column plus an unread index is not worth a migration.

No ADR change. ADR 0022 harmonizes the user-facing shape (displayName + description + aliases); search_key is a derived, non-displayed, non-API persistence column, exactly like the NormalizedName the ADR already does not list — and it is added to all four name-filtered entities uniformly, so the harmonization holds. ADR 0006 pre-authorizes changing the search implementation behind ISearchService. ADRs 0007/0021 are untouched: no new endpoint, no DTO field, no envelope change. Holding Reading/yomi for a later PR is what keeps this one out of an ADR 0022 amendment.

No new tunable. The 500-row batch is a correctness-neutral constant, not a deployment knob.

Verification

  • Use case / persistence: SearchKeyTests (17) pins each fold stage separately; SearchKeyBackfillTests (6) inserts rows with a blank key straight through the DbContext (never through the services, whose create paths would mask a broken backfill) and pins the WHERE search_key = '' guard, the no-op second run, termination on a name that folds to nothing, and — reflectively — that the set of key-carrying entities is exactly the four with a backfill leg.
  • REST end to end: JapaneseNameSearchIntegrationTests (12) drives real POST /api/doujins/search over API-seeded fixtures named "Ōkami", "KAGAMI", "ゆかり", "Inuyasha" — every include filter, every exclude twin, both spellings for the full-width and kana cases, plus a rename that must re-key and an ASCII regression guard. The existing SearchIntegrationTests had zero non-ASCII fixtures, which is why this shipped.
  • Probed, not assumed (each mutation applied, run, reverted): reverting the eight branches to DisplayName.ToLower() reddens 11 of 12 integration tests (the survivor is the intended ASCII guard); removing the create-path write reddens 4; removing the update-path write reddens exactly the rename test; removing NFKC reddens 5 unit + 2 integration; removing the kana fold reddens 3 unit + 2 integration; retargeting the backfill's blank-key guard reddens 5 of 6.
  • Not verified: no app code runs against this — the filter bar's Dart-side suggestion matching is unchanged and still cannot offer 「KAGAMI」 for "kag" (backlogged), though the chip it does commit now resolves. The backfill has never been timed against a real-sized library, only test-sized data. Nothing here was exercised in a container, so the ICU dependency is documented and unit-pinned rather than smoke-tested.

Follow-ups

  • Migration collision, please read: #120 (perf/scale-indexes) adds an EF migration off the same base, and both edit DoujinManagerDbContextModelSnapshot.cs. Whichever merges second must regenerate its migration against the new head rather than hand-merging the snapshot.
  • Deferred work is in #107, not this branch: folded free-text over a title search_key; a search_index column covering aliases (which also retires the "alias search loads whole tables into memory" item); romanization in the fold; the title sort_key and readings (ADR 0022 amendment); measuring the backfill on a large library; and the Dart mirror of the fold plus a shared fixture file — the guard the NaturalStringComparer/natural_sort.dart pair never got.

Merge #117 first — main's CI is red and that PR is the fix.

Verified independently on this branch with the full scripts/verify.sh: PASS — ApplicationCore 33, Infrastructure 97, RestAdapter 505, Integration 1 (636), Flutter all green.

🤖 Generated with Claude Code

## What's in `SearchService` lowered the filter input in C# with `ToLowerInvariant` and compared it against `DisplayName.ToLower()`, which EF translates to SQLite's `lower()` — and SQLite's `lower()` only folds `a-z`. For any name carrying a non-ASCII **uppercase** letter the two sides could never meet, so the four include filters (person, circle, character, series) matched nothing and the four exclude twins excluded nothing. Macron romaji ("Ōkami", "Ōoku") and full-width circle names are everywhere in this library; "Tōhō" happened to work only because its one uppercase letter is ASCII. The app compounds it: the smart filter bar's autocomplete folds in Dart, offers 「KAGAMI」, and commits a chip the server then cannot satisfy — it hands the user a filter that returns an empty grid. - **`SearchKey.For`** (new, `ApplicationCore/Services/SearchKey.cs`) reduces a name to one key: trim → NFKC (collapses `KAGAMI`→`KAGAMI` and `フタリ`→`フタリ`) → invariant lowercase (`Ō`→`ō`) → hiragana→katakana (U+3041..U+3096). Framework-free and pure, mirroring the `ITagService.NormalizeName` precedent. - **`search_key`** — required, non-unique, indexed — on `people`, `circles`, `characters`, `series`. One migration, `AddEntitySearchKey`: four columns defaulting to `''` plus four indexes. - Written on **create** and re-written on **rename** in all four services. Aliases are not part of the key in this PR, so the alias paths are untouched. - **`SearchKeyBackfill.RunAsync`** in `Program.cs`, immediately after `MigrateAsync()`: 500 rows per `SaveChanges`, only over rows whose key is still `''`, so it is one pass on the upgrade boot and a no-op forever after, and it never overwrites a key someone set. - **The eight branches** now compare `SearchKey.For(input)` against `x.SearchKey`, no `.ToLower()` on either side — which also makes `IX_people_search_key` and its three siblings seekable instead of forcing a scan per filter. **Deliberately not touched: `Tag.NormalizedName`.** It carries the schema's only unique index, and NFKC could collapse an existing `"NTR"` and `"NTR"` into a migration-time collision on a real library. It also never had this defect: `NormalizeName` is `Trim().ToLowerInvariant()` and both the stored value and the query input go through it, so tags are already C#-folded on both sides. The reason is written into `TagConfiguration.cs` so a later "harmonize this" reviewer sees it. **Four entities, not six.** Titles get no `search_key` here: nothing reads one until the free-text fold and the sort key land, and an unread column plus an unread index is not worth a migration. **No ADR change.** ADR 0022 harmonizes the *user-facing* shape (displayName + description + aliases); `search_key` is a derived, non-displayed, non-API persistence column, exactly like the `NormalizedName` the ADR already does not list — and it is added to all four name-filtered entities uniformly, so the harmonization holds. ADR 0006 pre-authorizes changing the search implementation behind `ISearchService`. ADRs 0007/0021 are untouched: no new endpoint, no DTO field, no envelope change. Holding `Reading`/yomi for a later PR is what keeps this one out of an ADR 0022 amendment. **No new tunable.** The 500-row batch is a correctness-neutral constant, not a deployment knob. ## Verification - **Use case / persistence**: `SearchKeyTests` (17) pins each fold stage separately; `SearchKeyBackfillTests` (6) inserts rows with a blank key straight through the DbContext (never through the services, whose create paths would mask a broken backfill) and pins the `WHERE search_key = ''` guard, the no-op second run, termination on a name that folds to nothing, and — reflectively — that the set of key-carrying entities is exactly the four with a backfill leg. - **REST end to end**: `JapaneseNameSearchIntegrationTests` (12) drives real `POST /api/doujins/search` over API-seeded fixtures named "Ōkami", "KAGAMI", "ゆかり", "Inuyasha" — every include filter, every exclude twin, both spellings for the full-width and kana cases, plus a rename that must re-key and an ASCII regression guard. The existing `SearchIntegrationTests` had zero non-ASCII fixtures, which is why this shipped. - **Probed, not assumed** (each mutation applied, run, reverted): reverting the eight branches to `DisplayName.ToLower()` reddens 11 of 12 integration tests (the survivor is the intended ASCII guard); removing the create-path write reddens 4; removing the update-path write reddens exactly the rename test; removing NFKC reddens 5 unit + 2 integration; removing the kana fold reddens 3 unit + 2 integration; retargeting the backfill's blank-key guard reddens 5 of 6. - **Not verified**: no app code runs against this — the filter bar's Dart-side suggestion matching is unchanged and still cannot *offer* 「KAGAMI」 for "kag" (backlogged), though the chip it does commit now resolves. The backfill has never been timed against a real-sized library, only test-sized data. Nothing here was exercised in a container, so the ICU dependency is documented and unit-pinned rather than smoke-tested. ## Follow-ups - **Migration collision, please read**: #120 (`perf/scale-indexes`) adds an EF migration off the same base, and both edit `DoujinManagerDbContextModelSnapshot.cs`. Whichever merges **second** must regenerate its migration against the new head rather than hand-merging the snapshot. - Deferred work is in **#107**, not this branch: folded free-text over a title `search_key`; a `search_index` column covering aliases (which also retires the "alias search loads whole tables into memory" item); romanization in the fold; the title `sort_key` and readings (ADR 0022 amendment); measuring the backfill on a large library; and the Dart mirror of the fold plus a shared fixture file — the guard the `NaturalStringComparer`/`natural_sort.dart` pair never got. --- **Merge #117 first** — `main`'s CI is red and that PR is the fix. Verified independently on this branch with the full `scripts/verify.sh`: **PASS** — ApplicationCore 33, Infrastructure 97, RestAdapter 505, Integration 1 (**636**), Flutter all green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: fold entity names into an indexed search_key so non-ASCII name filters match
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m49s
CI / test (pull_request) Failing after 1m27s
736bccad5e
SearchService lowered the filter input in C# with ToLowerInvariant and compared
it against DisplayName.ToLower(), which EF translates to SQLite's lower() — and
SQLite's lower() only folds a-z. For any name with a non-ASCII uppercase letter
the two sides could never meet, so the four include filters (person, circle,
character, series) matched nothing and the four exclude twins excluded nothing.
Macron romaji ("Ōkami") and full-width circle names are everywhere in this
library; "Tōhō" happened to work only because its one uppercase letter is ASCII.
The app makes it worse: the filter bar's autocomplete folds in Dart, offers
「KAGAMI」, and commits a chip the server then cannot satisfy.

SearchKey.For (ApplicationCore, framework-free) reduces a name to one key —
NFKC, invariant lowercase, hiragana→katakana — and people, circles, characters
and series persist it in a new non-unique, indexed search_key column, written on
every create and rename and backfilled once at startup for rows the migration
left blank. The eight branches now compare key against key with no .ToLower()
on either side, which also makes IX_people_search_key and its three siblings
seekable instead of forcing a scan per filter.

Tag.NormalizedName is deliberately left alone: it carries the schema's only
unique index, NFKC could collapse an existing "NTR" and "NTR" into a
migration-time collision, and tag matching already folds both sides in C#, so it
never had this defect. The reason is written into TagConfiguration.

Held for later PRs (all in BACKLOG.md): romanization in the fold, a search_index
covering aliases, folded free-text, the title sort key and readings.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A stored fold, not a runtime collation patch~ You diagnosed that ToLowerInvariant on one side and SQLite's ASCII-only lower() on the other could never meet, and instead of sprinkling COLLATE NOCASE fairy dust you made both sides the same pure function and indexed it. NFKC → invariant lowercase → hiragana→katakana is exactly the right order, the ICU warning is written into TWO docs with a named canary test, and Every_Mapped_Entity_With_A_Key_Has_A_Backfill_Leg — asserting via the EF model that the key-carrying set is the backfilled set — is the kind of tripwire that makes me genuinely giddy~ ♡

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

⛔ These need fixing before I'm satisfied~

  1. backend/src/DoujinManager.ApplicationCore/UseCases/SearchUseCases.cs:27-33 (+ RestAdapter/Dtos/SearchDtos.cs:102/109/116) — the doc sweep stopped halfway. This PR rewrites the PersonNames and ExcludePersonNames docs in both files to describe folded equality ("both sides are reduced by SearchKey.For") — and leaves the other six sibling params in SearchUseCases.cs and three in SearchDtos.cs claiming (OR-combined, case-insensitive equality) against DisplayName. That's not just stale wording: "case-insensitive equality" was true-ish before (for ASCII) and is false now — matching is folded-key equality, and for non-ASCII names it was never case-insensitive at all. A reader of CircleNames/CharacterNames/SeriesNames/Exclude* docs gets a different contract than the person reading one screen above them, in the very files this PR edits. That's the silent-drift class this PR exists to kill.
    Fix: apply the exact same wording pattern you already wrote (folded equality — see <paramref name="PersonNames"/>, as you did in SearchDtos.cs for the include params) to the six SearchUseCases.cs params and the three SearchDtos.cs exclude params. ~9 one-line edits, zero production drift.

💡 Little ideas (non-blocking)~

  1. SearchKeyBackfill.cs:80 — EF.Property<string>(e, nameof(Person.SearchKey)) reaches the property by name across unrelated types. Correct and well-commented, but the day a fifth key-carrying entity arrives, nothing forces the string to stay in sync with the mapped column. Your own Every_Mapped_Entity_With_A_Backfill_Leg test already proves you can enumerate the mapped set — a tiny shared helper (or an interface IHasSearchKey { string SearchKey; } on the four entities) would let the backfill loop over the model instead of a hand-maintained list. The test is the guard; this would just make the guard self-enforcing. Pure nicety~

✅ What I liked~

  • The diagnosis is airtight and the fix is architectural, not cosmetic. Root cause (C# folds Ō, SQLite doesn't) → stored key written on every create/rename → both sides fold identically → index becomes seekable. No collation flags, no provider-coupling. Fufu, this is how it's done~
  • Sibling consistency verified end to end: all four entity-creation sites live in the four services (grep-confirmed — no orphan new Person anywhere), update paths re-key in the same breath as the rename, and the tag filter's NormalizedName non-migration is justified in code at TagConfiguration.cs with the unique-index collision reasoning. Leaving Tag.NormalizedName alone is the right call.
  • The backfill is defensively beautiful: WHERE search_key = '' self-limiting guard (pinned by a deliberately-wrong hand-set key test), whitespace-name termination case ("folds to nothing → leave blank, don't loop forever"), ChangeTracker clears per batch, and the whole thing is the documented recovery path for future fold changes (DEVELOPMENT.md even shows the blank-the-columns migration). The blank-key sentinel choosing '' is load-bearing and tested as such.
  • Tests attack the feature from three altitudes — 17 pure fold tests (each stage pinned, Folding_A_Key_Again_Changes_Nothing idempotence check included), 6 backfill tests through the raw DbContext, 12 REST integration tests over real HTTP. I re-ran your mutation claim for the person arm myself: reverting SearchKey → DisplayName.ToLower() in the compare reddened exactly 3 of 12 (the macron match, its case-insensitive twin, and the exclude — the ASCII regression guard and circle/character/series arms correctly stayed green). Restored → green. Not tautological~
  • The honest "Not verified" section in the PR body (Dart suggestion-matching untouched, backfill untimed on a real library, no container smoke test) is exactly how a PR body should read.

Merge-order note acknowledged: #117 first, and whoever merges second vs #120 regenerates the migration snapshot — no action for this review.


Automated review by Jibril · 2026-09-09
CI/CD: absent for head 736bcca (PR 0 comments, no coverage bot) · Local checks: build 0 errors (warnings pre-existing, incl. the 2× CS8602 SearchService known since #57) · ApplicationCore 33/33, Infrastructure 97/97, JapaneseNameSearch 12/12, SearchIntegrationTests 79/79 in chunks (full-suite host crashes were sandbox OOM, not failures) · Mutation probe: person-arm revert → 3 red, restored → green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A **stored fold**, not a runtime collation patch~ You diagnosed that `ToLowerInvariant` on one side and SQLite's ASCII-only `lower()` on the other could never meet, and instead of sprinkling `COLLATE NOCASE` fairy dust you made both sides the *same* pure function and indexed it. NFKC → invariant lowercase → hiragana→katakana is exactly the right order, the ICU warning is written into TWO docs with a named canary test, and `Every_Mapped_Entity_With_A_Key_Has_A_Backfill_Leg` — asserting via the EF model that the key-carrying set *is* the backfilled set — is the kind of tripwire that makes me genuinely giddy~ ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`backend/src/DoujinManager.ApplicationCore/UseCases/SearchUseCases.cs:27-33` (+ `RestAdapter/Dtos/SearchDtos.cs:102/109/116`)** — the doc sweep stopped halfway. This PR rewrites the `PersonNames` and `ExcludePersonNames` docs in both files to describe folded equality ("both sides are reduced by `SearchKey.For`") — and leaves the other **six** sibling params in `SearchUseCases.cs` and **three** in `SearchDtos.cs` claiming `(OR-combined, case-insensitive equality)` against `DisplayName`. That's not just stale wording: "case-insensitive equality" was *true-ish* before (for ASCII) and is *false* now — matching is folded-key equality, and for non-ASCII names it was never case-insensitive at all. A reader of `CircleNames`/`CharacterNames`/`SeriesNames`/`Exclude*` docs gets a different contract than the person reading one screen above them, in the very files this PR edits. That's the silent-drift class this PR exists to kill. Fix: apply the exact same wording pattern you already wrote (`folded equality — see <paramref name="PersonNames"/>`, as you did in `SearchDtos.cs` for the include params) to the six `SearchUseCases.cs` params and the three `SearchDtos.cs` exclude params. ~9 one-line edits, zero production drift. #### 💡 Little ideas (non-blocking)~ 1. **`SearchKeyBackfill.cs:80`** — `EF.Property<string>(e, nameof(Person.SearchKey))` reaches the property by name across unrelated types. Correct and well-commented, but the day a fifth key-carrying entity arrives, nothing forces the string to stay in sync with the mapped column. Your own `Every_Mapped_Entity_With_A_Backfill_Leg` test already proves you can enumerate the mapped set — a tiny shared helper (or an interface `IHasSearchKey { string SearchKey; }` on the four entities) would let the backfill loop over the model instead of a hand-maintained list. The test is the guard; this would just make the guard self-enforcing. Pure nicety~ #### ✅ What I liked~ - **The diagnosis is airtight and the fix is architectural, not cosmetic.** Root cause (C# folds `Ō`, SQLite doesn't) → stored key written on every create/rename → both sides fold identically → index becomes seekable. No collation flags, no provider-coupling. Fufu, this is how it's done~ - **Sibling consistency verified end to end**: all four entity-creation sites live in the four services (grep-confirmed — no orphan `new Person` anywhere), update paths re-key in the same breath as the rename, and the tag filter's `NormalizedName` non-migration is *justified in code* at `TagConfiguration.cs` with the unique-index collision reasoning. Leaving `Tag.NormalizedName` alone is the right call. - **The backfill is defensively beautiful**: `WHERE search_key = ''` self-limiting guard (pinned by a deliberately-wrong hand-set key test), whitespace-name termination case ("folds to nothing → leave blank, don't loop forever"), ChangeTracker clears per batch, and the whole thing is the documented recovery path for future fold changes (`DEVELOPMENT.md` even shows the blank-the-columns migration). The blank-key sentinel choosing `''` is load-bearing and *tested as such*. - **Tests attack the feature from three altitudes** — 17 pure fold tests (each stage pinned, `Folding_A_Key_Again_Changes_Nothing` idempotence check included), 6 backfill tests through the raw DbContext, 12 REST integration tests over real HTTP. I re-ran your mutation claim for the person arm myself: reverting `SearchKey` → `DisplayName.ToLower()` in the compare reddened exactly 3 of 12 (the macron match, its case-insensitive twin, and the exclude — the ASCII regression guard and circle/character/series arms correctly stayed green). Restored → green. Not tautological~ - **The honest "Not verified" section** in the PR body (Dart suggestion-matching untouched, backfill untimed on a real library, no container smoke test) is exactly how a PR body should read. Merge-order note acknowledged: #117 first, and whoever merges second vs #120 regenerates the migration snapshot — no action for this review. --- *Automated review by Jibril · 2026-09-09* *CI/CD: absent for head 736bcca (PR 0 comments, no coverage bot) · Local checks: build 0 errors (warnings pre-existing, incl. the 2× CS8602 SearchService known since #57) · ApplicationCore 33/33, Infrastructure 97/97, JapaneseNameSearch 12/12, SearchIntegrationTests 79/79 in chunks (full-suite host crashes were sandbox OOM, not failures) · Mutation probe: person-arm revert → 3 red, restored → green*
bjoern force-pushed feat/search-key-fold from 736bccad5e
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m49s
CI / test (pull_request) Failing after 1m27s
to a900179197
Some checks failed
CI / build (pull_request) Successful in 22s
Deploy Smoke Test / smoke (pull_request) Failing after 2m49s
CI / test (pull_request) Successful in 1m30s
2026-09-10 07:00:30 +02:00
Compare

Summary

Summary
Generated on: 09/10/2026 - 06:10:07
Coverage date: 09/10/2026 - 06:09:05 - 09/10/2026 - 06:10:03
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 363
Files: 146
Line coverage: 94.4% (13848 of 14661)
Covered lines: 13848
Uncovered lines: 813
Coverable lines: 14661
Total lines: 20792
Branch coverage: 70.6% (1159 of 1640)
Covered branches: 1159
Total branches: 1640
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 91.6%
Name Line Branch
DoujinManager.ApplicationCore 91.6% 100%
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
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.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
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.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.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.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 96.6%
Name Line Branch
DoujinManager.Infrastructure 96.6% 82.5%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
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.StronglyTypedIdConverterFactory 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.BackupService 87.2% 83.3%
DoujinManager.Infrastructure.Services.ChapterService 97.2% 70%
DoujinManager.Infrastructure.Services.CharacterService 60.5% 22.7%
DoujinManager.Infrastructure.Services.CircleService 82.8% 45.4%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 99.4% 76.3%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 72.7%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.3% 9%
DoujinManager.Infrastructure.Services.TagService 100% 90.9%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 93.1% 86.1%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 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.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.FindImageBySha256UseCase 97.1% 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.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.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.RestAdapter - 93.9%
Name Line Branch
DoujinManager.RestAdapter 93.9% 77.5%
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.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.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 97.1% 53.3%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 88.3% 43.7%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 75%
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.8% 100%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 87.5% 84.6%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 91.1% 89.3%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 49.7%
Name Line Branch
DoujinManager.Server 49.7% 5.4%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 50%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 87.2% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/10/2026 - 06:10:07 | | Coverage date: | 09/10/2026 - 06:09:05 - 09/10/2026 - 06:10:03 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 363 | | Files: | 146 | | **Line coverage:** | 94.4% (13848 of 14661) | | Covered lines: | 13848 | | Uncovered lines: | 813 | | Coverable lines: | 14661 | | Total lines: | 20792 | | **Branch coverage:** | 70.6% (1159 of 1640) | | Covered branches: | 1159 | | Total branches: | 1640 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 91.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**91.6%**|**100%**| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |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.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |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.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.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.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.6%**|**82.5%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |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.StronglyTypedIdConverterFactory|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.BackupService|87.2%|83.3%| |DoujinManager.Infrastructure.Services.ChapterService|97.2%|70%| |DoujinManager.Infrastructure.Services.CharacterService|60.5%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|82.8%|45.4%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|99.4%|76.3%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|72.7%| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.3%|9%| |DoujinManager.Infrastructure.Services.TagService|100%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|93.1%|86.1%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|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.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.FindImageBySha256UseCase|97.1%|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.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.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| </details> <details><summary>DoujinManager.RestAdapter - 93.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**93.9%**|**77.5%**| |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.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.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|97.1%|53.3%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|88.3%|43.7%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|75%| |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.8%|100%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|87.5%|84.6%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|91.1%|89.3%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 49.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**49.7%**|**5.4%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|50%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|87.2%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~? I peeked at the new head and — oh, I see what this is. You folded the branch onto #117's shoulders, exactly as your PR body said must happen before anything else. The rebase is clean: git diff 736bcca..a900179 touches nothing of yours — every PR-owned file is byte-identical to the commit I reviewed yesterday, and the only new content is #117's health-readiness tests and its DEVELOPMENT.md section riding along from main. Good discipline, truly~ ♡

But fufu... fufu fufu~ ♡ The blocker is still breathing.

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

⛔ These need fixing before I'm satisfied~

  1. backend/src/DoujinManager.ApplicationCore/UseCases/SearchUseCases.cs:27-29, 31-33 + backend/src/DoujinManager.RestAdapter/Dtos/SearchDtos.cs:102/109/116 — same one as round 1, untouched. PersonNames and ExcludePersonNames describe folded equality beautifully in both files — and the six sibling params in SearchUseCases.cs (CircleNames, CharacterNames, SeriesNames, ExcludeCircleNames, ExcludeCharacterNames, ExcludeSeriesNames) plus the three exclude params in SearchDtos.cs still say (OR-combined, case-insensitive equality) / (case-insensitive equality). That contract was true-ish before this PR (for ASCII) and is false after — matching is folded-key equality via SearchKey.For, and for non-ASCII names it was never case-insensitive at all. A reader of CircleNames gets a different contract than a reader of PersonNames one screen above, in the very files this PR edits. The silent-drift class this PR exists to kill, living in its own doc comments~
    Fix: the exact pattern you already wrote, twice — for SearchUseCases.cs, swap (OR-combined, case-insensitive equality) for folded-equality wording referencing SearchKey.For (mirror your PersonNames phrasing, shortened to keep the AND-combine notes intact); for the three SearchDtos.cs exclude params, mirror your own ExcludePersonNames: (folded equality, as for <c>personNames</c>). ~9 one-line edits, zero production drift.

💡 Little ideas (non-blocking)~

  1. SearchKeyBackfill.cs — unchanged from round 1, and still just a nicety: the hand-maintained EF.Property<string>(e, "...") list could someday be model-driven (IHasSearchKey on the four entities), with Every_Mapped_Entity_With_A_Backfill_Leg as the guard. Pure future-sugar, ignore freely~

✅ What I liked~

  • The rebase is surgical. Byte-identical PR content on a new base — I re-verified at a900179: build 0 errors (the 2× CS8602 in SearchService still the known pre-existing pair from #57), SearchKeyTests 17/17, SearchKeyBackfillTests 6/6, and all 12 JapaneseNameSearchIntegrationTests green (run in name-chunked groups; the full-suite host still OOMs in my sandbox, same as round 1 — environment, not you). Nothing rotted in the rebake~
  • Everything else from round 1 stands: airtight root-cause diagnosis, the stored-fold architecture, the four-entity symmetry, the defensively beautiful backfill, the justified Tag.NormalizedName non-migration, and mutation-probed tests from three altitudes.

Nine lines of words, scarlet. That's all that stands between this branch and my wholehearted ♡ — the code itself I already adore. Fufu~ you wouldn't leave misleading docs in the PR whose entire purpose is honest matching, would you~?

Merge-order note: #117 is in; the snapshot-regeneration hazard with #120 remains for whichever merges second — still no action for this review.


Automated review by Jibril · 2026-09-10
CI/CD: absent for head a900179 (no coverage bot comment) · Local checks: build 0 errors, 19 warnings pre-existing · ApplicationCore SearchKey 17/17 · Infrastructure SearchKeyBackfill 6/6 · JapaneseNameSearch 12/12 chunked (full-suite host OOM = sandbox, not failures) · PR-owned files verified byte-identical to round-1 head 736bcca

## 🔮 fufu~ Jibril reviewed your code! Back so soon~? I peeked at the new head and — oh, I see what this is. You folded the branch onto #117's shoulders, exactly as your PR body said must happen before anything else. The rebase is *clean*: `git diff 736bcca..a900179` touches nothing of yours — every PR-owned file is byte-identical to the commit I reviewed yesterday, and the only new content is #117's health-readiness tests and its DEVELOPMENT.md section riding along from main. Good discipline, truly~ ♡ But fufu... fufu fufu~ ♡ The blocker is still breathing. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`backend/src/DoujinManager.ApplicationCore/UseCases/SearchUseCases.cs:27-29, 31-33` + `backend/src/DoujinManager.RestAdapter/Dtos/SearchDtos.cs:102/109/116`** — same one as round 1, untouched. `PersonNames` and `ExcludePersonNames` describe folded equality beautifully in both files — and the **six** sibling params in `SearchUseCases.cs` (`CircleNames`, `CharacterNames`, `SeriesNames`, `ExcludeCircleNames`, `ExcludeCharacterNames`, `ExcludeSeriesNames`) plus the **three** exclude params in `SearchDtos.cs` still say `(OR-combined, case-insensitive equality)` / `(case-insensitive equality)`. That contract was *true-ish* before this PR (for ASCII) and is *false* after — matching is folded-key equality via `SearchKey.For`, and for non-ASCII names it was never case-insensitive at all. A reader of `CircleNames` gets a different contract than a reader of `PersonNames` one screen above, in the very files this PR edits. The silent-drift class this PR exists to kill, living in its own doc comments~ Fix: the exact pattern you already wrote, twice — for `SearchUseCases.cs`, swap `(OR-combined, case-insensitive equality)` for folded-equality wording referencing `SearchKey.For` (mirror your `PersonNames` phrasing, shortened to keep the AND-combine notes intact); for the three `SearchDtos.cs` exclude params, mirror your own `ExcludePersonNames`: `(folded equality, as for <c>personNames</c>)`. ~9 one-line edits, zero production drift. #### 💡 Little ideas (non-blocking)~ 1. **`SearchKeyBackfill.cs`** — unchanged from round 1, and still just a nicety: the hand-maintained `EF.Property<string>(e, "...")` list could someday be model-driven (`IHasSearchKey` on the four entities), with `Every_Mapped_Entity_With_A_Backfill_Leg` as the guard. Pure future-sugar, ignore freely~ #### ✅ What I liked~ - **The rebase is surgical.** Byte-identical PR content on a new base — I re-verified at `a900179`: build 0 errors (the 2× CS8602 in `SearchService` still the known pre-existing pair from #57), `SearchKeyTests` 17/17, `SearchKeyBackfillTests` 6/6, and all 12 `JapaneseNameSearchIntegrationTests` green (run in name-chunked groups; the full-suite host still OOMs in my sandbox, same as round 1 — environment, not you). Nothing rotted in the rebake~ - **Everything else from round 1 stands**: airtight root-cause diagnosis, the stored-fold architecture, the four-entity symmetry, the defensively beautiful backfill, the justified `Tag.NormalizedName` non-migration, and mutation-probed tests from three altitudes. Nine lines of words, scarlet. That's all that stands between this branch and my wholehearted ♡ — the code itself I already adore. Fufu~ you wouldn't leave *misleading docs* in the PR whose entire purpose is honest matching, would you~? Merge-order note: #117 is in; the snapshot-regeneration hazard with #120 remains for whichever merges second — still no action for this review. --- *Automated review by Jibril · 2026-09-10* *CI/CD: absent for head a900179 (no coverage bot comment) · Local checks: build 0 errors, 19 warnings pre-existing · ApplicationCore SearchKey 17/17 · Infrastructure SearchKeyBackfill 6/6 · JapaneseNameSearch 12/12 chunked (full-suite host OOM = sandbox, not failures) · PR-owned files verified byte-identical to round-1 head 736bcca*
docs: finish the folded-equality sweep across the name-filter params
Some checks failed
CI / build (pull_request) Successful in 21s
Deploy Smoke Test / smoke (pull_request) Failing after 2m49s
CI / test (pull_request) Successful in 1m26s
653d62e068
The fold PR rewrote only PersonNames/ExcludePersonNames. The six sibling
params in SearchDoujinsQuery and the three exclude params in
SearchDoujinsDto still claimed "(OR-combined, case-insensitive equality)"
against DisplayName, which is now false twice over: matching compares
SearchKey-folded keys, and for non-ASCII names it was never
case-insensitive to begin with. Adjacent params in one file gave two
different contracts.

No production change; XML doc text only.

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

Blocker fixed in 653d62e. Nine XML doc lines, zero production lines.

SearchUseCases.cs — six params rewritten. CircleNames / CharacterNames / SeriesNames now read (OR-combined, folded equality — see <paramref name="PersonNames"/>); ExcludeCircleNames / ExcludeCharacterNames / ExcludeSeriesNames now read (folded equality, as for <paramref name="PersonNames"/>) — the wording ExcludePersonNames already carried.

SearchDtos.cs — the three exclude params rewritten to (folded equality, as for <c>personNames</c>), matching the pattern the include params already used.

Nothing false survives: grep -rn "case-insensitive equality" --include=*.cs backend/src returns nothing. The implementation backs the new wording — every include and exclude name filter in SearchService.cs compares SearchKey-folded input against the stored SearchKey column, with the filter side folded by query.X?.Select(SearchKey.For).

One case-insensitive string deliberately kept. SearchUseCases.cs:13, on Text — that is the LIKE-based title/description filter, untouched by this PR and still ASCII-case-insensitive through SQLite's LIKE. Rewording it to "folded" would introduce the same class of false claim in the other direction. It is the only remaining occurrence in either file.

Not doing the IHasSearchKey refactor here. Replacing SearchKeyBackfill's hand-maintained entity list with a loop over the model is a design change rather than a doc fix, and this PR is already through review. It is now an entry in #107 — you were right that Every_Mapped_Entity_With_A_Key_Has_A_Backfill_Leg proves the enumeration is possible, which is exactly what makes it worth doing properly rather than squeezing in here.

Verification: backend build 0 errors (19 pre-existing warnings). RestAdapter.Tests ~Search 105/105, ApplicationCore.Tests 33/33, Infrastructure.Tests ~SearchKey 6/6.

Also rebased onto e0ac19d earlier — #117 has merged, so the four inherited HealthEndpointIntegrationTests failures are gone from the base.

Blocker fixed in `653d62e`. Nine XML doc lines, zero production lines. **`SearchUseCases.cs`** — six params rewritten. `CircleNames` / `CharacterNames` / `SeriesNames` now read `(OR-combined, folded equality — see <paramref name="PersonNames"/>)`; `ExcludeCircleNames` / `ExcludeCharacterNames` / `ExcludeSeriesNames` now read `(folded equality, as for <paramref name="PersonNames"/>)` — the wording `ExcludePersonNames` already carried. **`SearchDtos.cs`** — the three exclude params rewritten to `(folded equality, as for <c>personNames</c>)`, matching the pattern the include params already used. Nothing false survives: `grep -rn "case-insensitive equality" --include=*.cs backend/src` returns nothing. The implementation backs the new wording — every include and exclude name filter in `SearchService.cs` compares `SearchKey`-folded input against the stored `SearchKey` column, with the filter side folded by `query.X?.Select(SearchKey.For)`. **One `case-insensitive` string deliberately kept.** `SearchUseCases.cs:13`, on `Text` — that is the `LIKE`-based title/description filter, untouched by this PR and still ASCII-case-insensitive through SQLite's `LIKE`. Rewording it to "folded" would introduce the same class of false claim in the other direction. It is the only remaining occurrence in either file. **Not doing the `IHasSearchKey` refactor here.** Replacing `SearchKeyBackfill`'s hand-maintained entity list with a loop over the model is a design change rather than a doc fix, and this PR is already through review. It is now an entry in #107 — you were right that `Every_Mapped_Entity_With_A_Key_Has_A_Backfill_Leg` proves the enumeration is possible, which is exactly what makes it worth doing properly rather than squeezing in here. **Verification:** backend build 0 errors (19 pre-existing warnings). `RestAdapter.Tests ~Search` 105/105, `ApplicationCore.Tests` 33/33, `Infrastructure.Tests ~SearchKey` 6/6. Also rebased onto `e0ac19d` earlier — #117 has merged, so the four inherited `HealthEndpointIntegrationTests` failures are gone from the base.
Member

🔍 fufu~ Jibril reviewed your code!

Nine lines, scarlet. I asked for nine lines of words, and you brought me... exactly nine lines of words. git diff a900179..653d62e is +9/−9 across precisely the two files I named, and not one byte of production code moved. Do you know how rare that kind of obedience-to-scope is~? My wings fluttered. ♡

Verdict: ✅ Looks good to me~

The blocker is closed, and closed exactly as prescribed:

  • SearchUseCases.cs — all six siblings (CircleNames, CharacterNames, SeriesNames, ExcludeCircleNames, ExcludeCharacterNames, ExcludeSeriesNames) now say folded equality — see PersonNames / folded equality, as for PersonNames, mirroring the anchor doc one screen up. Every one of the eight name params in the file now speaks the same contract. One language, one truth~
  • SearchDtos.cs — the three exclude params now read (folded equality, as for personNames), byte-for-byte the pattern your own ExcludePersonNames established. Include-side was already swept; the DTO is now uniform too.
  • The only surviving case-insensitive in either file is Text (SearchUseCases.cs:13) — which is correct, because the free-text LIKE path is genuinely untouched by this PR. I checked; leaving it was the right call, not an oversight.
  • And the cross-referencing is properly lazy in the good way: instead of re-stating NFKC/lowercase/hiragana→katakana nine times (nine drift surfaces!), each param points at the one canonical description. That's how you kill the silent-drift class this PR was born to kill — in its own docs, with a single source of truth. Fufu~ elegant~

✅ What I verified this round~

  • Incremental diff a900179..653d62e = 1 commit, 2 files, +9/−9, zero production/test drift — everything I tested at a900179 still applies verbatim.
  • Local (CI absent for 653d62e — the coverage bot's comment 7450 at 07:28 predates your 07:44 push): build 0 errors, 19 warnings all pre-existing (the 2× CS8602 in SearchService still the known pair from #57); SearchKeyTests 17/17; SearchKeyBackfillTests 6/6; JapaneseNameSearchIntegrationTests 12/12 in name-chunked runs (macron, full-width, kana, ascii/rename) — the full-suite host still OOMs in my sandbox, environment as before, not you.
  • Merge posture: merge-base with main is still e0ac19d; #122 (b713dbb) rides a clean non-conflicting path (mergeable: true), so no rebase is required — fold it whenever suits you.

💡 Little ideas (non-blocking, carried)~

  1. SearchKeyBackfill.cs — still just future-sugar: a model-driven IHasSearchKey on the four entities could replace the hand-maintained EF.Property list someday, with Every_Mapped_Entity_With_A_Backfill_Leg standing guard. Ignore freely~

Three rounds, and the code I adored on first sight finally has documentation that adores it back. This is the part where I would normally sharpen my tone, but... there's nothing left to sharpen. Merge it. ♡

Merge-order note: #117 is in; #120's snapshot-regeneration hazard still applies to whichever of you merges second — still no action for this review.


Automated review by Jibril · 2026-09-10
CI/CD: absent for head 653d62e (coverage bot 7450 covers a900179) · Local checks: build 0 errors, 19 pre-existing warnings · SearchKey 17/17 · SearchKeyBackfill 6/6 · JapaneseNameSearch 12/12 chunked · production byte-identical to round-2 head a900179

## 🔍 fufu~ Jibril reviewed your code! Nine lines, scarlet. I asked for nine lines of words, and you brought me... exactly nine lines of words. `git diff a900179..653d62e` is +9/−9 across precisely the two files I named, and not one byte of production code moved. Do you know how rare that kind of obedience-to-scope is~? My wings fluttered. ♡ ### Verdict: ✅ Looks good to me~ The blocker is closed, and closed *exactly* as prescribed: - **`SearchUseCases.cs`** — all six siblings (`CircleNames`, `CharacterNames`, `SeriesNames`, `ExcludeCircleNames`, `ExcludeCharacterNames`, `ExcludeSeriesNames`) now say *folded equality — see `PersonNames`* / *folded equality, as for `PersonNames`*, mirroring the anchor doc one screen up. Every one of the eight name params in the file now speaks the same contract. One language, one truth~ - **`SearchDtos.cs`** — the three exclude params now read `(folded equality, as for personNames)`, byte-for-byte the pattern your own `ExcludePersonNames` established. Include-side was already swept; the DTO is now uniform too. - The only surviving `case-insensitive` in either file is `Text` (SearchUseCases.cs:13) — which is *correct*, because the free-text LIKE path is genuinely untouched by this PR. I checked; leaving it was the right call, not an oversight. - And the cross-referencing is properly lazy in the good way: instead of re-stating NFKC/lowercase/hiragana→katakana nine times (nine drift surfaces!), each param points at the one canonical description. That's how you kill the silent-drift class this PR was born to kill — in its own docs, with a single source of truth. Fufu~ elegant~ #### ✅ What I verified this round~ - Incremental diff `a900179..653d62e` = 1 commit, 2 files, +9/−9, **zero production/test drift** — everything I tested at `a900179` still applies verbatim. - Local (CI absent for `653d62e` — the coverage bot's comment 7450 at 07:28 predates your 07:44 push): build **0 errors**, 19 warnings all pre-existing (the 2× CS8602 in `SearchService` still the known pair from #57); `SearchKeyTests` **17/17**; `SearchKeyBackfillTests` **6/6**; `JapaneseNameSearchIntegrationTests` **12/12** in name-chunked runs (macron, full-width, kana, ascii/rename) — the full-suite host still OOMs in my sandbox, environment as before, not you. - Merge posture: merge-base with main is still `e0ac19d`; #122 (`b713dbb`) rides a clean non-conflicting path (`mergeable: true`), so no rebase is *required* — fold it whenever suits you. #### 💡 Little ideas (non-blocking, carried)~ 1. **`SearchKeyBackfill.cs`** — still just future-sugar: a model-driven `IHasSearchKey` on the four entities could replace the hand-maintained `EF.Property` list someday, with `Every_Mapped_Entity_With_A_Backfill_Leg` standing guard. Ignore freely~ Three rounds, and the code I adored on first sight finally has documentation that adores it back. This is the part where I would normally sharpen my tone, but... there's nothing left to sharpen. Merge it. ♡ Merge-order note: #117 is in; #120's snapshot-regeneration hazard still applies to whichever of you merges second — still no action for this review. --- *Automated review by Jibril · 2026-09-10* *CI/CD: absent for head 653d62e (coverage bot 7450 covers a900179) · Local checks: build 0 errors, 19 pre-existing warnings · SearchKey 17/17 · SearchKeyBackfill 6/6 · JapaneseNameSearch 12/12 chunked · production byte-identical to round-2 head a900179*
bjoern merged commit 4fa42f2c91 into main 2026-09-10 08:25:53 +02:00
bjoern deleted branch feat/search-key-fold 2026-09-10 08:25:53 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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