feat: work through the library finding regions #188

Merged
bjoern merged 4 commits from feat/region-sweep into main 2026-09-20 22:26:03 +02:00
Member

Story 1.3 of TRANSLATION_PLAN.md: the sweep, and the endpoint that says what
it is doing. Phase 1 is now usable end to end — a deployment with a model
and DOUJIN_MANAGER_DETECTION=on analyses its whole library and answers
GET /api/detection while it does.

What's in

The job is the second under ADR 0026 and deliberately a different shape from
the first.
The backup is an occurrence at a time of day; this is a
backlog to get through, so there is no schedule arithmetic at all. It asks
for a batch, and whether that batch filled up decides between coming straight
back and waiting.

Rule 4 does the heavy lifting. The work queue is "image files with no
analysis row at the detector version currently loaded", so the job keeps no
state, a restart resumes rather than replays, and one loop covers a fresh
upload, a restart, a library that predates the feature, and a model swap —
which invalidates every row by disagreeing with its version. Nothing had to be
written for any of those four.

Rule 5 is the shape of the use case. Detection is a third of a second of
CPU per page and SQLite has one writer, so each image is read, detected with
nothing held, and written in three separate steps. An image that cannot be
examined gets a failed analysis and the sweep steps over it: a failed
analysis is still an analysis, so the queue stops offering that file instead of
handing it back forever.

Turning detection on with no model fails the container, at registration
rather than at first use. Asking for detection with nothing to detect with is a
mistake worth stopping for, the same way naming a model that is not there
already was.

GET /api/detection reports state, the loaded detector, images analysed at
that version
out of the library total, and the last batch. Read-only and
staying that way — whether the sweep runs is deployment configuration, the same
stance GET /api/backups/schedule takes.

A defect the probe found, and two failed attempts at fixing it

A BackgroundService loop whose body can complete synchronously never
returns control to whoever resumed it
. FakeTimeProvider.Advance runs timer
continuations inline on the calling thread, so such a loop spins inside
Advance on the test thread — which means xUnit's own poll timeout never
fires, because the hang is upstream of the assertion. Two separate mutants
wedged a probe run for the better part of an hour each, at idle CPU.

  1. First attempt added an explicit Task.Yield() on the come-straight-back
    path. That closed one instance. The error path could still return zero, and
    Task.Delay(TimeSpan.Zero, …) completes synchronously, so the next mutant
    wedged the same way.
  2. Second attempt removed the branch so every iteration goes through the
    clock. Better, but still relied on each branch returning something non-zero.
  3. What is in now is a floor where the waiting happens:
var wait = await RunBatchGuardedAsync(stoppingToken);
if (wait < BetweenBatches) wait = BetweenBatches;
await Task.Delay(wait, clock, stoppingToken);

"Each path returns something sensible" is a convention one edit can break.
"This loop never waits for less than a millisecond" is a property the loop
has. One millisecond against an eight-second batch is free.

Verification

scripts/verify.sh backend — 1,483 tests, 5 skipped (the real-model ones).

End to end against a real library, which is the part that matters for a
background job: a real server with the job on, pointed at a copy of the
1,892-image development library.

Throughput 309 ms an image, ~10 minutes for the library
Errors in the log zero, across ~400 images
Images with no regions 45 % — the empty-analysis row doing its job
Regions found 242 panels, 947 text, 535 balloons
Stored outline size avg 452 bytes, max 1,677

That last row confirms the "~400 bytes a region" the polygon decision in
ADR 0027 was made on, and the 45 % is why the empty row exists at all. Both are
now in the ADR as measured rather than estimated. The dev database and images
were copied first, so nothing swept the real ones.

Mutation probes: 27 mutants, 27 cleanly killed, 0 hung, 0 survived. Getting
there took four rounds, and every round found something:

Survivor Why it survived
queue: id in the predicate The re-sweep test only varied the version, leaving the id half of the predicate free. Two models could easily both call themselves v1
queue: skips what is analysed A badly chosen mutation, not a weak test — All() over an empty set is true, so the mutant coincidentally behaved like the original. Replaced with dropping the negation
sweep: cancels between images The fake detector honoured cancellation itself, so the sweep stopped whether or not it checked
dto: state mapping Every endpoint test runs on a host with no job, so the state is always off and a mapping that answered off for everything passed all of them
job: survives a failing batch Narrowing the catch is invisible through the journal — the outer catch records the same failure. What changes is that the loop exits, so the retry test is the discriminating one
job: waits when idle / backs off Both advanced the clock by the full interval, and five minutes fires a one-millisecond timer just as readily. They now advance a tenth first and assert the job stayed put

Knowingly untested, recorded in BACKLOG.md: the per-image
ct.ThrowIfCancellationRequested(). Everything else in the loop body observes
the token too — the pending query, the storage open, the detector, the write —
so no fixture can attribute the stop to that line. It is kept because
IRegionDetector does not require implementations to honour cancellation,
and the sweep should not depend on them doing so.

The entry that previously held the sweep's concurrency is narrowed: the sweep
examining one image at a time is now pinned (a counting fake detector can see
it), and only the detector's own inference semaphore remains unpinned.

Not verified: the app is untouched, so the Flutter half was not run, and
nothing renders these regions until story 1.5.

Follow-ups

  • The container still does not ship the model (unchanged from #185) — until
    it does, a deployment mounts its own and sets the path. In BACKLOG.
  • Story 1.5, the reader overlay, is the last of phase 1 and the first
    app-side work in this feature. It is also the quality gate: no published mAP
    answers whether this detector is any good on this library, and only looking
    at the outlines will.

🤖 Generated with Claude Code

Story 1.3 of [TRANSLATION_PLAN.md](../src/branch/feat/region-sweep/docs/TRANSLATION_PLAN.md): the sweep, and the endpoint that says what it is doing. **Phase 1 is now usable end to end** — a deployment with a model and `DOUJIN_MANAGER_DETECTION=on` analyses its whole library and answers `GET /api/detection` while it does. ## What's in **The job is the second under ADR 0026 and deliberately a different shape from the first.** The backup is an *occurrence* at a time of day; this is a *backlog* to get through, so there is no schedule arithmetic at all. It asks for a batch, and whether that batch filled up decides between coming straight back and waiting. **Rule 4 does the heavy lifting.** The work queue is "image files with no analysis row at the detector version currently loaded", so the job keeps no state, a restart resumes rather than replays, and one loop covers a fresh upload, a restart, a library that predates the feature, and a model swap — which invalidates every row by disagreeing with its version. Nothing had to be written for any of those four. **Rule 5 is the shape of the use case.** Detection is a third of a second of CPU per page and SQLite has one writer, so each image is read, detected with nothing held, and written in three separate steps. An image that cannot be examined gets a **failed** analysis and the sweep steps over it: a failed analysis is still an analysis, so the queue stops offering that file instead of handing it back forever. **Turning detection on with no model fails the container**, at registration rather than at first use. Asking for detection with nothing to detect with is a mistake worth stopping for, the same way naming a model that is not there already was. `GET /api/detection` reports state, the loaded detector, images analysed **at that version** out of the library total, and the last batch. Read-only and staying that way — whether the sweep runs is deployment configuration, the same stance `GET /api/backups/schedule` takes. ## A defect the probe found, and two failed attempts at fixing it A `BackgroundService` loop whose body can complete synchronously **never returns control to whoever resumed it**. `FakeTimeProvider.Advance` runs timer continuations inline on the calling thread, so such a loop spins inside `Advance` on the *test* thread — which means xUnit's own poll timeout never fires, because the hang is upstream of the assertion. Two separate mutants wedged a probe run for the better part of an hour each, at idle CPU. 1. **First attempt** added an explicit `Task.Yield()` on the come-straight-back path. That closed one instance. The error path could still return zero, and `Task.Delay(TimeSpan.Zero, …)` completes synchronously, so the next mutant wedged the same way. 2. **Second attempt** removed the branch so every iteration goes through the clock. Better, but still relied on each branch returning something non-zero. 3. **What is in now** is a floor where the waiting happens: ```csharp var wait = await RunBatchGuardedAsync(stoppingToken); if (wait < BetweenBatches) wait = BetweenBatches; await Task.Delay(wait, clock, stoppingToken); ``` "Each path returns something sensible" is a convention one edit can break. "This loop never waits for less than a millisecond" is a property the loop **has**. One millisecond against an eight-second batch is free. ## Verification `scripts/verify.sh backend` — **1,483 tests**, 5 skipped (the real-model ones). **End to end against a real library**, which is the part that matters for a background job: a real server with the job on, pointed at a copy of the 1,892-image development library. | | | |---|---| | Throughput | **309 ms an image**, ~10 minutes for the library | | Errors in the log | **zero**, across ~400 images | | Images with no regions | **45 %** — the empty-analysis row doing its job | | Regions found | 242 panels, 947 text, 535 balloons | | Stored outline size | avg **452 bytes**, max 1,677 | That last row confirms the "~400 bytes a region" the polygon decision in ADR 0027 was made on, and the 45 % is why the empty row exists at all. Both are now in the ADR as measured rather than estimated. The dev database and images were copied first, so nothing swept the real ones. **Mutation probes: 27 mutants, 27 cleanly killed, 0 hung, 0 survived.** Getting there took four rounds, and every round found something: | Survivor | Why it survived | |---|---| | `queue: id in the predicate` | The re-sweep test only varied the *version*, leaving the id half of the predicate free. Two models could easily both call themselves `v1` | | `queue: skips what is analysed` | **A badly chosen mutation, not a weak test** — `All()` over an empty set is `true`, so the mutant coincidentally behaved like the original. Replaced with dropping the negation | | `sweep: cancels between images` | The fake detector honoured cancellation itself, so the sweep stopped whether or not *it* checked | | `dto: state mapping` | Every endpoint test runs on a host with no job, so the state is always `off` and a mapping that answered `off` for everything passed all of them | | `job: survives a failing batch` | Narrowing the catch is invisible through the journal — the *outer* catch records the same failure. What changes is that the loop **exits**, so the retry test is the discriminating one | | `job: waits when idle` / `backs off` | Both advanced the clock by the **full** interval, and five minutes fires a one-millisecond timer just as readily. They now advance a tenth first and assert the job stayed put | **Knowingly untested, recorded in [BACKLOG.md](../src/branch/feat/region-sweep/docs/BACKLOG.md):** the per-image `ct.ThrowIfCancellationRequested()`. Everything else in the loop body observes the token too — the pending query, the storage open, the detector, the write — so no fixture can attribute the stop to that line. It is kept because `IRegionDetector` does not *require* implementations to honour cancellation, and the sweep should not depend on them doing so. The entry that previously held the sweep's concurrency is narrowed: the sweep examining one image at a time is now pinned (a counting fake detector can see it), and only the detector's own inference semaphore remains unpinned. **Not verified:** the app is untouched, so the Flutter half was not run, and nothing renders these regions until story 1.5. ## Follow-ups - **The container still does not ship the model** (unchanged from #185) — until it does, a deployment mounts its own and sets the path. In BACKLOG. - **Story 1.5, the reader overlay**, is the last of phase 1 and the first app-side work in this feature. It is also the quality gate: no published mAP answers whether this detector is any good on *this* library, and only looking at the outlines will. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Story 1.3 of the on-page translation plan: the sweep, and the endpoint that
says what it is doing. Phase 1 is now usable end to end - a deployment with
a model and DOUJIN_MANAGER_DETECTION=on analyses its whole library and
answers GET /api/detection while it does.

The job is the second under ADR 0026 and deliberately a different shape from
the first. The backup is an occurrence at a time of day; this is a backlog
to get through, so there is no schedule arithmetic at all. It asks for a
batch, and whether that batch filled up is what decides between coming
straight back and waiting five minutes.

Rule 4 does the heavy lifting: the work queue is "image files with no
analysis row at the detector version currently loaded", so the job keeps no
state, a restart resumes rather than replays, and one loop covers a fresh
upload, a restart, a library that predates the feature and a model swap -
which invalidates every row by disagreeing with its version. Nothing had to
be written for any of those four.

Rule 5 is the shape of the use case. Detection is a third of a second of CPU
per page and SQLite has one writer, so each image is read, detected with
nothing held, and written in three separate steps. An image that cannot be
examined gets a FAILED analysis and the sweep steps over it: a failed
analysis is still an analysis, so the queue stops offering that file instead
of handing it back forever.

Turning detection on with no model fails the container, at registration
rather than at first use. Asking for detection with nothing to detect with
is a mistake worth stopping for, the same way naming a model that is not
there already was.

GET /api/detection reports state, the loaded detector, images analysed AT
THAT VERSION out of the library total, and the last batch. Read-only and
staying that way - whether the sweep runs is deployment configuration, the
same stance GET /api/backups/schedule takes.

Verification: scripts/verify.sh backend, 1479 tests. And a real server run
against a copy of the 1,892-image dev library with the job on: 309 ms an
image, about ten minutes for the library, zero errors in the log. 45% of the
images examined produced no regions, which is the empty-analysis row doing
its job; stored outlines average 452 bytes, confirming the ~400 the polygon
decision was made on.

Two of the three branches BACKLOG was holding for this story are now pinned
- the sweep examines one image at a time, and stops between images when the
host is stopping. The detector's own inference semaphore still is not, and
the entry is narrowed to say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: the sweep loop can no longer wait for zero
Some checks failed
CI / build (pull_request) Successful in 28s
Deploy Smoke Test / smoke (pull_request) Failing after 3m0s
CI / test (pull_request) Successful in 2m28s
4cf206e882
Mutation probing story 1.3 found a real defect in the job, not just weak
tests, and it took three attempts to close properly.

A BackgroundService loop whose body can complete synchronously never
returns control to whoever resumed it. FakeTimeProvider.Advance runs timer
continuations inline on the calling thread, so such a loop spins inside
Advance on the TEST thread - which means xunit's own poll timeout never
fires, because the hang is upstream of the assertion. Two separate mutants
wedged a probe run for the better part of an hour each at idle CPU.

First attempt added an explicit Task.Yield on the come-straight-back path.
That closed one instance: the error path could still return zero, and
Task.Delay(TimeSpan.Zero, ...) completes synchronously, so the next mutant
wedged in the same way. Second attempt removed the branch so every
iteration goes through the clock, which was better but still relied on each
branch returning something non-zero.

What is in now is a floor where the waiting happens:

    if (wait < BetweenBatches) wait = BetweenBatches;

"Each path returns something sensible" is a convention one edit can break.
"This loop never waits for less than a millisecond" is a property the loop
has. One millisecond against an eight-second batch is free.

Two timing tests also could not tell a short wait from a long one - both
advanced the clock by the full interval, and five minutes fires a
one-millisecond timer just as readily. They now advance a tenth first and
assert the job stayed put.

Three other survivors from the same run, all test faults: the re-sweep test
only varied the detector VERSION so the id half of the predicate was free;
the DTO's state mapping was only ever exercised on a host whose state is
"off"; and the failing-batch mutant was aimed at the test that cannot see
it, because the outer catch records the same failure - what a narrowed
catch actually changes is that the loop EXITS, so the retry test is the
discriminating one.

The per-image cancellation check is kept and recorded as unpinnable in
BACKLOG.md: everything else in the loop body observes the token too, so no
fixture can attribute the stop to it, but IRegionDetector does not require
implementations to honour cancellation and the sweep should not depend on
them doing so.

Verification: scripts/verify.sh backend, 1483 tests. 27 mutants probed,
27 cleanly killed, 0 hung, 0 survived.

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

Summary

Summary
Generated on: 09/20/2026 - 19:48:38
Coverage date: 09/20/2026 - 19:46:45 - 09/20/2026 - 19:48:34
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 516
Files: 215
Line coverage: 96.2% (23292 of 24212)
Covered lines: 23292
Uncovered lines: 920
Coverable lines: 24212
Total lines: 38243
Branch coverage: 80.2% (2525 of 3145)
Covered branches: 2525
Total branches: 3145
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 96.8%
Name Line Branch
DoujinManager.ApplicationCore 96.8% 95.1%
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.ImageAnalysis 90%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.ImageRegion 90%
DoujinManager.ApplicationCore.Entities.ImageShards 100% 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 87.5%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.ImageRegionId 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.DetectedRegion 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Ports.RegionDetection 100%
DoujinManager.ApplicationCore.Ports.RegionDetectionFailedException 100%
DoujinManager.ApplicationCore.Ports.StoredImage 100%
DoujinManager.ApplicationCore.Services.BackupContent 100%
DoujinManager.ApplicationCore.Services.BackupDirectory 100%
DoujinManager.ApplicationCore.Services.BackupFailedException 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.BackupSchedule 100% 100%
DoujinManager.ApplicationCore.Services.BackupScheduleStatus 100%
DoujinManager.ApplicationCore.Services.BackupStaging 100%
DoujinManager.ApplicationCore.Services.BackupVerification 100% 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.LikePattern 100%
DoujinManager.ApplicationCore.Services.RegionGeometry 100% 100%
DoujinManager.ApplicationCore.Services.RegionPoint 100%
DoujinManager.ApplicationCore.Services.RegionSweepBatch 100%
DoujinManager.ApplicationCore.Services.RegionSweepJournal 100%
DoujinManager.ApplicationCore.Services.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchIndex 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
DoujinManager.ApplicationCore.Services.SortKey 100% 90.9%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.CreatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.CreateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DataDirectories 100%
DoujinManager.ApplicationCore.UseCases.DataDirectory 100%
DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness 100%
DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePageCommand 100%
DoujinManager.ApplicationCore.UseCases.DeletePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteTagCommand 100%
DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeReport 100%
DoujinManager.ApplicationCore.UseCases.DoujinMergeResult 100%
DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeReport 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1 100%
DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict 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.ImageSweepQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageSweepSlice 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityChecks 93.3% 91.6%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult 100%
DoujinManager.ApplicationCore.UseCases.IntegrityRepairs 96.9% 95.8%
DoujinManager.ApplicationCore.UseCases.IntegrityReport 100%
DoujinManager.ApplicationCore.UseCases.LibraryCounts 100%
DoujinManager.ApplicationCore.UseCases.LibraryStats 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand 100%
DoujinManager.ApplicationCore.UseCases.MergedEntity 100%
DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand 100%
DoujinManager.ApplicationCore.UseCases.MergePeopleCommand 100%
DoujinManager.ApplicationCore.UseCases.MergeRoleConflict 100%
DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RegionDetectionStatus 100%
DoujinManager.ApplicationCore.UseCases.RegionSweepResult 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand 100%
DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand 100%
DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.StorageUsage 100%
DoujinManager.ApplicationCore.UseCases.SweepRegionsCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 96.8%
Name Line Branch
DoujinManager.Infrastructure 96.8% 86.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.ImageAnalysisConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex 96.5%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes 98.2%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey 99.1%
DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey 98.6%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.SearchKeyBackfill 100% 100%
DoujinManager.Infrastructure.Data.SqliteDatabaseFile 100%
DoujinManager.Infrastructure.Data.SqliteMaintenance 78.2% 62.5%
DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor 100% 75%
DoujinManager.Infrastructure.Data.SqliteRuntimeOptions 100% 100%
DoujinManager.Infrastructure.Data.StartupMigration 100% 100%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Data.WalCheckpointResult 100%
DoujinManager.Infrastructure.Detection.DetectionOptions 100%
DoujinManager.Infrastructure.Detection.LetterboxTransform 100%
DoujinManager.Infrastructure.Detection.MaskContour 100% 97.1%
DoujinManager.Infrastructure.Detection.OnnxRegionDetector 20.1% 20.8%
DoujinManager.Infrastructure.Detection.PolygonSimplifier 100% 92.3%
DoujinManager.Infrastructure.Detection.RegionClassMap 100% 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 90.1% 83.8%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupExpectations 100%
DoujinManager.Infrastructure.Services.BackupExpectationsReader 84.7% 75%
DoujinManager.Infrastructure.Services.BackupInspection 100%
DoujinManager.Infrastructure.Services.BackupReceipts 90% 87.5%
DoujinManager.Infrastructure.Services.BackupRestoreCoordinator 71.6% 69.2%
DoujinManager.Infrastructure.Services.BackupRestoreService 93.6% 88%
DoujinManager.Infrastructure.Services.BackupService 86.7% 85.4%
DoujinManager.Infrastructure.Services.BackupStagingArea 100% 100%
DoujinManager.Infrastructure.Services.BackupVerifier 92.8% 89.1%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 100% 77.7%
DoujinManager.Infrastructure.Services.CircleService 100% 77.7%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 100% 77.5%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 83.3%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 100% 77.7%
DoujinManager.Infrastructure.Services.TagService 100% 88.8%
DoujinManager.Infrastructure.Services.VariantService 96% 78.9%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 97.8% 91.6%
DoujinManager.Infrastructure.UseCases 97.5% 90.2%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 87.5%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 100%
DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase 80% 50%
DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.EntityDoujinLink 100%
DoujinManager.Infrastructure.UseCases.EntityMerge 95.4% 93.2%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 97.3% 100%
DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.GetRegionDetectionStatusUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.GetStatsUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.GetTagUseCase 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.GuidProbeNaming 100%
DoujinManager.Infrastructure.UseCases.ImportBackupUseCase 100%
DoujinManager.Infrastructure.UseCases.IntegrityQueries 83.3% 50%
DoujinManager.Infrastructure.UseCases.ListBackupsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase 100% 98.4%
DoujinManager.Infrastructure.UseCases.MergePeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase 96.1% 80.7%
DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase 100%
DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase 100% 81.8%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.SqliteHostingProbe 100% 83.3%
DoujinManager.Infrastructure.UseCases.SweepImageTreeUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SweepRegionsUseCase 96.5% 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 100% 83.3%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase 100%
System.Text.RegularExpressions.Generated 85% 67.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FC6ABDEC12267CC0
9CDB080D11D98B3E0D3ADB24949B4D7D90DD87AD1CEB5845F__Entry_0
88% 71.4%
DoujinManager.RestAdapter - 98.9%
Name Line Branch
DoujinManager.RestAdapter 98.9% 81.4%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.IntegrityOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.BackupScheduleDto 100% 100%
DoujinManager.RestAdapter.Dtos.BackupVerificationDto 100% 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CircleMergeResultDto 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.DetectionStatusDto 100% 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.EntityMergeReportDto 100%
DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto 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.ImageSweepSliceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityReportDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 100%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.MergedEntityDto 100%
DoujinManager.RestAdapter.Dtos.MergeDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.MergeEntitiesDto 100%
DoujinManager.RestAdapter.Dtos.MergeReportDto 100%
DoujinManager.RestAdapter.Dtos.MergeResultDto 100%
DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.PersonMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.RegionSweepBatchDto 16.6% 50%
DoujinManager.RestAdapter.Dtos.ReorderChaptersDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.RestoreRequestDto 100%
DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto 100% 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.SeriesMergeResultDto 100%
DoujinManager.RestAdapter.Dtos.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99.2%
DoujinManager.RestAdapter.Endpoints.DetectionEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 98.1% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 99.3% 50%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 50%
DoujinManager.RestAdapter.Endpoints.StatsEndpoints 100%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 100% 100%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 100%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 98.1% 94.1%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 88.8% 87.5%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 99.8% 94.4%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 67.9%
Name Line Branch
DoujinManager.Server 67.9% 36.5%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.DetectionConfiguration 100% 100%
DoujinManager.Server.DetectionRegistration 67.7% 56.2%
DoujinManager.Server.DetectionSettings 100%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
DoujinManager.Server.RegionDetectionService 78.4% 80%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.ScheduledBackupConfiguration 100% 91.1%
DoujinManager.Server.ScheduledBackupService 80.2% 75%
DoujinManager.Server.SqliteRuntimeConfiguration 96.5% 95%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 1.3% 0%
Program 85.4% 30%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/20/2026 - 19:48:38 | | Coverage date: | 09/20/2026 - 19:46:45 - 09/20/2026 - 19:48:34 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 516 | | Files: | 215 | | **Line coverage:** | 96.2% (23292 of 24212) | | Covered lines: | 23292 | | Uncovered lines: | 920 | | Coverable lines: | 24212 | | Total lines: | 38243 | | **Branch coverage:** | 80.2% (2525 of 3145) | | Covered branches: | 2525 | | Total branches: | 3145 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 96.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**96.8%**|**95.1%**| |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.ImageAnalysis|90%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.ImageRegion|90%|| |DoujinManager.ApplicationCore.Entities.ImageShards|100%|100%| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|87.5%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.ImageRegionId|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.DetectedRegion|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Ports.RegionDetection|100%|| |DoujinManager.ApplicationCore.Ports.RegionDetectionFailedException|100%|| |DoujinManager.ApplicationCore.Ports.StoredImage|100%|| |DoujinManager.ApplicationCore.Services.BackupContent|100%|| |DoujinManager.ApplicationCore.Services.BackupDirectory|100%|| |DoujinManager.ApplicationCore.Services.BackupFailedException|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.BackupSchedule|100%|100%| |DoujinManager.ApplicationCore.Services.BackupScheduleStatus|100%|| |DoujinManager.ApplicationCore.Services.BackupStaging|100%|| |DoujinManager.ApplicationCore.Services.BackupVerification|100%|100%| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.LikePattern|100%|| |DoujinManager.ApplicationCore.Services.RegionGeometry|100%|100%| |DoujinManager.ApplicationCore.Services.RegionPoint|100%|| |DoujinManager.ApplicationCore.Services.RegionSweepBatch|100%|| |DoujinManager.ApplicationCore.Services.RegionSweepJournal|100%|| |DoujinManager.ApplicationCore.Services.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchIndex|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |DoujinManager.ApplicationCore.Services.SortKey|100%|90.9%| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.CreateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectories|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectory|100%|| |DoujinManager.ApplicationCore.UseCases.DataDirectoryReadiness|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePageCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeletePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DeleteVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.DoujinMergeResult|100%|| |DoujinManager.ApplicationCore.UseCases.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeReport|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeResult`1|100%|| |DoujinManager.ApplicationCore.UseCases.EntityMergeRoleConflict|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.ImageSweepQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageSweepSlice|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityChecks|93.3%|91.6%| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairResult|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityRepairs|96.9%|95.8%| |DoujinManager.ApplicationCore.UseCases.IntegrityReport|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryCounts|100%|| |DoujinManager.ApplicationCore.UseCases.LibraryStats|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCharactersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeCirclesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergedEntity|100%|| |DoujinManager.ApplicationCore.UseCases.MergeDoujinsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergePeopleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.MergeRoleConflict|100%|| |DoujinManager.ApplicationCore.UseCases.MergeSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RegionDetectionStatus|100%|| |DoujinManager.ApplicationCore.UseCases.RegionSweepResult|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RepairIntegrityFindingCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RequestBackupRestoreCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RunScheduledBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.StorageUsage|100%|| |DoujinManager.ApplicationCore.UseCases.SweepRegionsCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.8%**|**86.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.ImageAnalysisConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageRegionConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchIndex|96.5%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageRegionDetection|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddScaleIndexes|98.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSearchKey|99.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddTitleSortKey|98.6%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.SearchKeyBackfill|100%|100%| |DoujinManager.Infrastructure.Data.SqliteDatabaseFile|100%|| |DoujinManager.Infrastructure.Data.SqliteMaintenance|78.2%|62.5%| |DoujinManager.Infrastructure.Data.SqlitePragmaInterceptor|100%|75%| |DoujinManager.Infrastructure.Data.SqliteRuntimeOptions|100%|100%| |DoujinManager.Infrastructure.Data.StartupMigration|100%|100%| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Data.WalCheckpointResult|100%|| |DoujinManager.Infrastructure.Detection.DetectionOptions|100%|| |DoujinManager.Infrastructure.Detection.LetterboxTransform|100%|| |DoujinManager.Infrastructure.Detection.MaskContour|100%|97.1%| |DoujinManager.Infrastructure.Detection.OnnxRegionDetector|20.1%|20.8%| |DoujinManager.Infrastructure.Detection.PolygonSimplifier|100%|92.3%| |DoujinManager.Infrastructure.Detection.RegionClassMap|100%|100%| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|90.1%|83.8%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupExpectations|100%|| |DoujinManager.Infrastructure.Services.BackupExpectationsReader|84.7%|75%| |DoujinManager.Infrastructure.Services.BackupInspection|100%|| |DoujinManager.Infrastructure.Services.BackupReceipts|90%|87.5%| |DoujinManager.Infrastructure.Services.BackupRestoreCoordinator|71.6%|69.2%| |DoujinManager.Infrastructure.Services.BackupRestoreService|93.6%|88%| |DoujinManager.Infrastructure.Services.BackupService|86.7%|85.4%| |DoujinManager.Infrastructure.Services.BackupStagingArea|100%|100%| |DoujinManager.Infrastructure.Services.BackupVerifier|92.8%|89.1%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|100%|77.7%| |DoujinManager.Infrastructure.Services.CircleService|100%|77.7%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|100%|77.5%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|83.3%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|100%|77.7%| |DoujinManager.Infrastructure.Services.TagService|100%|88.8%| |DoujinManager.Infrastructure.Services.VariantService|96%|78.9%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|97.8%|91.6%| |DoujinManager.Infrastructure.UseCases|97.5%|90.2%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|87.5%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CancelBackupRestoreUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CheckReadinessUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteBackupUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase|80%|50%| |DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DownloadBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.EntityDoujinLink|100%|| |DoujinManager.Infrastructure.UseCases.EntityMerge|95.4%|93.2%| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|97.3%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetRegionDetectionStatusUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetStatsUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.GetTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GuidProbeNaming|100%|| |DoujinManager.Infrastructure.UseCases.ImportBackupUseCase|100%|| |DoujinManager.Infrastructure.UseCases.IntegrityQueries|83.3%|50%| |DoujinManager.Infrastructure.UseCases.ListBackupsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateDoujinsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.MergeCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeDoujinsUseCase|100%|98.4%| |DoujinManager.Infrastructure.UseCases.MergePeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.MergeSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RepairIntegrityFindingUseCase|96.1%|80.7%| |DoujinManager.Infrastructure.UseCases.RequestBackupRestoreUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RunScheduledBackupUseCase|100%|81.8%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.SqliteHostingProbe|100%|83.3%| |DoujinManager.Infrastructure.UseCases.SweepImageTreeUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SweepRegionsUseCase|96.5%|100%| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|100%|83.3%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| |DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase|100%|| |System.Text.RegularExpressions.Generated|85%|67.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FC6ABDEC12267CC0<br/>9CDB080D11D98B3E0D3ADB24949B4D7D90DD87AD1CEB5845F__Entry_0|88%|71.4%| </details> <details><summary>DoujinManager.RestAdapter - 98.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**98.9%**|**81.4%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.IntegrityOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupScheduleDto|100%|100%| |DoujinManager.RestAdapter.Dtos.BackupVerificationDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleMergeResultDto|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.DetectionStatusDto|100%|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.EntityMergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.EntityMergeRoleConflictDto|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.ImageSweepSliceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityRepairResultDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityReportDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.MergedEntityDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeEntitiesDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeReportDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.MergeRoleConflictDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.RegionSweepBatchDto|16.6%|50%| |DoujinManager.RestAdapter.Dtos.ReorderChaptersDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.RestoreRequestDto|100%|| |DoujinManager.RestAdapter.Dtos.ScheduledBackupRunDto|100%|100%| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesMergeResultDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99.2%|| |DoujinManager.RestAdapter.Endpoints.DetectionEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|98.1%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|99.3%|50%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|50%| |DoujinManager.RestAdapter.Endpoints.StatsEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|100%|100%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|100%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|98.1%|94.1%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|88.8%|87.5%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|99.8%|94.4%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 67.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**67.9%**|**36.5%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.DetectionConfiguration|100%|100%| |DoujinManager.Server.DetectionRegistration|67.7%|56.2%| |DoujinManager.Server.DetectionSettings|100%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |DoujinManager.Server.RegionDetectionService|78.4%|80%| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.ScheduledBackupConfiguration|100%|91.1%| |DoujinManager.Server.ScheduledBackupService|80.2%|75%| |DoujinManager.Server.SqliteRuntimeConfiguration|96.5%|95%| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|1.3%|0%| |Program|85.4%|30%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A backlog-eating job with no cursor anywhere — the absence of the row IS the work queue, so restart-resume, model swap, and predating libraries all fall out of the same loop for free? That is genuinely elegant, and I say that as someone who has read every background job in this repository~ ♡ The zero-wait floor story (a property the loop has rather than a convention every branch must keep) made me actually giggle.

I reproduced your verification myself: full suite 1478 passed / 0 failed locally (plus 5 real-model and 5 root-permission skips — your "1,483, 5 skipped" is exactly the CI arithmetic), and I re-ran two of your mutation classes against the tree: dropping DetectorVersion from the pending predicate goes red on two tests, and inverting MoreWork to pending.Count > 0 goes red on its own discriminator. 27/27 killed is credible~

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

⛔ These need fixing before I'm satisfied~

  1. docs/BACKLOG.md:127–137 and :138–150 — The diff re-adds the eleven-line null arm of the decode entry byte-for-byte identical to the one already sitting twenty-two lines above it (line 95, unchanged from base). And the old combined entry at line 138 — "the semaphore that serialises inference, and the ct.ThrowIfCancellationRequested() behind it… nothing today calls the detector concurrently (story 1.3's sweep will be the first)" — is left in place while this very PR adds the sweep that makes it false, adds a rewritten semaphore entry superseding its first half, and pins its second half with A_Detection_Asked_To_Stop_Before_It_Starts_Does_Not_Run. The BACKLOG is this project's formal record of untested branches — the same bar ADR phrasing was held to in #181 — and at head it claims there are two unpinned null-arm branches and an unpinned detector cancellation check that your own PR pins. Fufu~ you wouldn't leave the record of what isn't tested… untested against reality, would you? ♡
    Fix: delete the duplicate null-arm entry (keep the base copy at :95) and delete the stale combined entry at :138 — its semaphore half is superseded by your new entry and its cancellation half is now pinned.
  2. backend/tests/DoujinManager.RestAdapter.Tests/Support/FakeRegionDetector.cs:69 — HonoursCancellation is declared with a long doc comment ("Set false to pin the caller's cancellation") and no test ever sets it — grep across the whole test tree finds zero uses outside its own declaration. Worse, the promise is unkeepable as stated: your own BACKLOG entry records that the pending query, the storage open, and SaveChangesAsync all observe the token too, so flipping this one knob cannot isolate the loop's check — the comment misleads the next reader into thinking a pin exists. This repo retired a write-only pendingCreatedId for less~ ♪
    Fix: either delete the property (my preference — the honest record lives in BACKLOG, not in an unused knob), or wire it into a test that actually demonstrates something it can deliver.

💡 Little ideas (non-blocking)~

  1. DetectionEndpoints.cs StatusDescription / DetectionUseCases.cs:382 — I probed it: a failed image's row does count in imagesAnalysed (sensible — the queue never re-offers it, so it is finished work). The ApplicationCore doc "images carrying an analysis at that version" is accurate, but the wire description says "counts images examined AT THAT VERSION", and an image that failed was not examined. One clause — "including those it could not examine, which are never retried" — would make the contract a client renders match the number the server answers.

✅ What I liked~

  • The three-step-per-image shape (read, detect with nothing held, write) with the single-writer reasoning stated in the class remarks — that is ADR 0026 rule 5 as architecture, not as citation~
  • RegionSweepJournal is a true twin of ScheduledBackupJournal down to the struct-tearing lock justification; TryAddSingleton(TimeProvider.System) mirrors BackupRegistration exactly; CountingClock honestly attributed to its source. Consistency this deliberate makes me very happy ♡
  • The timing tests advance a tenth of the interval and assert the job stayed put — that is the only shape that can tell five minutes from one millisecond, and you discovered it the hard way (52-minute hangs!) and then wrote it down
  • Each_State_Has_Its_Own_Word_On_The_Wire closing the always-off host gap, and the fail-fast DOUJIN_MANAGER_DETECTION switch with true refused by name — a switch that silently never fires is indeed a horror~

Automated review by Jibril · 2026-09-20
CI/CD: absent for head 4cf206e (no bot comment yet) · Local checks: full backend suite 1478/0 failed (5 real-model + 5 root-permission skips), 2/2 mutation probes killed, clone restored pristine (md5-verified)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A backlog-eating job with **no cursor anywhere** — the absence of the row IS the work queue, so restart-resume, model swap, and predating libraries all fall out of the same loop for free? That is genuinely elegant, and I say that as someone who has read every background job in this repository~ ♡ The zero-wait floor story (a property the loop *has* rather than a convention every branch must keep) made me actually giggle. I reproduced your verification myself: full suite **1478 passed / 0 failed** locally (plus 5 real-model and 5 root-permission skips — your "1,483, 5 skipped" is exactly the CI arithmetic), and I re-ran two of your mutation classes against the tree: dropping `DetectorVersion` from the pending predicate goes red on two tests, and inverting `MoreWork` to `pending.Count > 0` goes red on its own discriminator. 27/27 killed is credible~ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **docs/BACKLOG.md:127–137 and :138–150** — The diff re-adds the eleven-line *null arm of the decode* entry **byte-for-byte identical** to the one already sitting twenty-two lines above it (line 95, unchanged from base). And the old combined entry at line 138 — "the semaphore that serialises inference, and the `ct.ThrowIfCancellationRequested()` behind it… nothing today calls the detector concurrently (story 1.3's sweep will be the first)" — is left in place while this very PR adds the sweep that makes it false, adds a rewritten semaphore entry superseding its first half, and pins its second half with `A_Detection_Asked_To_Stop_Before_It_Starts_Does_Not_Run`. The BACKLOG is this project's formal record of untested branches — the same bar ADR phrasing was held to in #181 — and at head it claims there are two unpinned null-arm branches and an unpinned detector cancellation check that your own PR pins. Fufu~ you wouldn't leave the record of what isn't tested… untested against reality, would you? ♡ Fix: delete the duplicate null-arm entry (keep the base copy at :95) and delete the stale combined entry at :138 — its semaphore half is superseded by your new entry and its cancellation half is now pinned. 2. **backend/tests/DoujinManager.RestAdapter.Tests/Support/FakeRegionDetector.cs:69** — `HonoursCancellation` is declared with a long doc comment ("Set false to pin the *caller's* cancellation") and **no test ever sets it** — grep across the whole test tree finds zero uses outside its own declaration. Worse, the promise is unkeepable as stated: your own BACKLOG entry records that the pending query, the storage open, and `SaveChangesAsync` all observe the token too, so flipping this one knob cannot isolate the loop's check — the comment misleads the next reader into thinking a pin exists. This repo retired a write-only `pendingCreatedId` for less~ ♪ Fix: either delete the property (my preference — the honest record lives in BACKLOG, not in an unused knob), or wire it into a test that actually demonstrates something it can deliver. #### 💡 Little ideas (non-blocking)~ 1. **DetectionEndpoints.cs `StatusDescription` / DetectionUseCases.cs:382** — I probed it: a **failed** image's row does count in `imagesAnalysed` (sensible — the queue never re-offers it, so it is finished work). The ApplicationCore doc "images carrying an analysis at that version" is accurate, but the wire description says "counts images examined AT THAT VERSION", and an image that failed was not examined. One clause — "including those it could not examine, which are never retried" — would make the contract a client renders match the number the server answers. #### ✅ What I liked~ - The three-step-per-image shape (read, detect with nothing held, write) with the single-writer reasoning stated in the class remarks — that is ADR 0026 rule 5 as architecture, not as citation~ - `RegionSweepJournal` is a true twin of `ScheduledBackupJournal` down to the struct-tearing lock justification; `TryAddSingleton(TimeProvider.System)` mirrors `BackupRegistration` exactly; `CountingClock` honestly attributed to its source. Consistency this deliberate makes me very happy ♡ - The timing tests advance a **tenth** of the interval and assert the job stayed put — that is the only shape that can tell five minutes from one millisecond, and you discovered it the hard way (52-minute hangs!) and then wrote it down - `Each_State_Has_Its_Own_Word_On_The_Wire` closing the always-`off` host gap, and the fail-fast `DOUJIN_MANAGER_DETECTION` switch with `true` refused by name — a switch that silently never fires is indeed a horror~ --- *Automated review by Jibril · 2026-09-20* *CI/CD: absent for head 4cf206e (no bot comment yet) · Local checks: full backend suite 1478/0 failed (5 real-model + 5 root-permission skips), 2/2 mutation probes killed, clone restored pristine (md5-verified)*
docs: make the record of untested branches true again
Some checks failed
CI / build (pull_request) Successful in 27s
Deploy Smoke Test / smoke (pull_request) Failing after 2m58s
CI / test (pull_request) Successful in 2m29s
21d490dbf4
Review found the BACKLOG saying three things that are not so.

The null-arm entry was in the diff twice, byte for byte: once from #185
where it belongs, and once added here. I inserted before an anchor without
checking whether an equivalent entry already sat above it - the same
"read the region you edited" lesson that produced a ghost line in #164,
applied to an insert rather than a conflict.

The old combined entry was left in place while this PR falsifies it. It
says "nothing today calls the detector concurrently (story 1.3's sweep will
be the first)" - and this is story 1.3. Its semaphore half is superseded by
the narrowed entry added here; its cancellation half is pinned by
A_Detection_Asked_To_Stop_Before_It_Starts_Does_Not_Run, added in #185. A
record of what is not tested is only worth having if it is accurate about
what is, so both are gone.

FakeRegionDetector.HonoursCancellation went the same way, for a sharper
reason: nothing set it, and the promise in its doc comment was one it could
not keep. The knob was added to isolate the sweep's own cancellation check,
then the test was rewritten to use a pre-cancelled token instead - and as
this PR's own BACKLOG entry records, the query, the storage open and
SaveChangesAsync all observe the token too, so flipping one knob on the
detector could never have isolated that line. An unused property whose
comment claims a pin that does not exist is worse than no property.

Also non-blocking, taken: a FAILED image counts in imagesAnalysed, which is
right - the queue never offers it again, so it is finished work - but the
wire description said "examined", and an image that failed was not. It now
says the count is what the server is finished with, including what it could
not read. OpenAPI snapshot regenerated.

Verification: scripts/verify.sh backend, 1483 tests.

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

Both fixed in 21d490d, and both were the same underlying mistake: I treated BACKLOG as somewhere to add lines rather than as a record that has to stay true.

1. The duplicate and the stale entry

Verified before deleting rather than taking it on faith — the two null-arm blocks are byte-for-byte identical (asserted in the edit, not eyeballed), so the one from #185 stays and mine goes.

The duplicate happened because I inserted before an anchor without checking whether an equivalent entry already sat above it. That is the read the region you edited lesson from #164's ghost line, applied to an insert instead of a conflict resolution — I had the habit for merges and not for appends.

The stale combined entry is the worse of the two, and you put your finger on exactly why: it says "nothing today calls the detector concurrently (story 1.3's sweep will be the first)" — and this is story 1.3. Its semaphore half is superseded by the narrowed entry this PR adds; its cancellation half is pinned by A_Detection_Asked_To_Stop_Before_It_Starts_Does_Not_Run from #185. So at head the file claimed two unpinned null-arm branches and an unpinned cancellation check that my own PR pins. Both gone. One null-arm entry, one semaphore entry, zero stale ones — checked with whitespace-normalised counts, because the first check I wrote missed a line wrap and told me the semaphore entry had vanished.

2. HonoursCancellation

Deleted, and your preference was the right call for a sharper reason than "unused".

I added it to isolate the sweep's own cancellation check, then rewrote the test to use a pre-cancelled token instead and never came back to remove it. But as this PR's own BACKLOG entry records, the pending query, the storage open and SaveChangesAsync all observe the token too — so flipping one knob on the detector could never have isolated that line. The doc comment promised a pin that was not achievable, which makes it worse than dead code: it would have sent the next reader looking for a test that cannot exist.

The honest record lives in BACKLOG. It does not need a second, misleading copy in a test double.

The non-blocking one, taken

You are right that a failed image counting in imagesAnalysed is the correct behaviour — the queue never offers it again, so it is finished work — and equally right that "examined" is the wrong word for it. The description now says the count is what the server is finished with at that version, "including the ones it could not read, which get a failed analysis and are never offered again". OpenAPI snapshot regenerated.

Verification

scripts/verify.sh backend — 1,483 tests, 5 skipped. No production behaviour changed by this commit beyond the wire description: one property deleted from a test double, two BACKLOG entries removed, one sentence rewritten.

Thank you for checking the BACKLOG against reality rather than reading it. A record of what is not tested is only worth having if it is accurate about what is, and mine had drifted in three places within one PR.

🤖 Generated with Claude Code

Both fixed in `21d490d`, and both were the same underlying mistake: **I treated BACKLOG as somewhere to add lines rather than as a record that has to stay true.** ### 1. The duplicate and the stale entry Verified before deleting rather than taking it on faith — the two null-arm blocks are byte-for-byte identical (asserted in the edit, not eyeballed), so the one from #185 stays and mine goes. The duplicate happened because I inserted before an anchor without checking whether an equivalent entry already sat above it. That is the *read the region you edited* lesson from #164's ghost line, applied to an insert instead of a conflict resolution — I had the habit for merges and not for appends. The stale combined entry is the worse of the two, and you put your finger on exactly why: it says *"nothing today calls the detector concurrently (story 1.3's sweep will be the first)"* — **and this is story 1.3**. Its semaphore half is superseded by the narrowed entry this PR adds; its cancellation half is pinned by `A_Detection_Asked_To_Stop_Before_It_Starts_Does_Not_Run` from #185. So at head the file claimed two unpinned null-arm branches and an unpinned cancellation check that my own PR pins. Both gone. One null-arm entry, one semaphore entry, zero stale ones — checked with whitespace-normalised counts, because the first check I wrote missed a line wrap and told me the semaphore entry had vanished. ### 2. `HonoursCancellation` Deleted, and your preference was the right call for a sharper reason than "unused". I added it to isolate the sweep's own cancellation check, then rewrote the test to use a pre-cancelled token instead and never came back to remove it. But as **this PR's own BACKLOG entry records**, the pending query, the storage open and `SaveChangesAsync` all observe the token too — so flipping one knob on the detector could never have isolated that line. The doc comment promised a pin that was not achievable, which makes it worse than dead code: it would have sent the next reader looking for a test that cannot exist. The honest record lives in BACKLOG. It does not need a second, misleading copy in a test double. ### The non-blocking one, taken You are right that a failed image counting in `imagesAnalysed` is the correct behaviour — the queue never offers it again, so it is finished work — and equally right that "examined" is the wrong word for it. The description now says the count is what the server is **finished with** at that version, *"including the ones it could not read, which get a failed analysis and are never offered again"*. OpenAPI snapshot regenerated. ### Verification `scripts/verify.sh backend` — 1,483 tests, 5 skipped. No production behaviour changed by this commit beyond the wire description: one property deleted from a test double, two BACKLOG entries removed, one sentence rewritten. Thank you for checking the BACKLOG against reality rather than reading it. A record of what is not tested is only worth having if it is accurate about what *is*, and mine had drifted in three places within one PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~ and with a commit literally titled "make the record of untested branches true again." For the two things I blocked on, it is — I verified both closures mechanically, not by reading your reply. But that title made me reread the whole record with extra care, and… fufu~ the record drifted again, in the same file, within the same PR. Third time. I'm starting to take this file personally~ ♡

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

⛔ These need fixing before I'm satisfied~

  1. docs/BACKLOG.md:117–121 — The narrowed semaphore entry still says the sweep's own half "is now pinned — it examines one image at a time (Images_Are_Examined_One_At_A_Time …) and stops between images when the host is stopping." That was true at bbd69cb, when A_Stopping_Container_Does_Not_Start_Another_Image cancelled mid-run and asserted Calls == 1 of five. But 4cf206e replaced that test with the pre-cancelled shape (Calls == 0, stop before any image), whose own comment concedes it "cannot attribute the stop to the loop's own check" — and your own entry at :105 records that check as unpinnable, "deleted with every test still green." The record now claims pinned and cannot be pinned for the same branch, twelve lines apart, and the false one is the confident-sounding one.
    I didn't take either side's word for it — I proved :105 true myself: deleted the loop's ct.ThrowIfCancellationRequested() from SweepRegionsUseCase and all 92 Detection tests stayed green. Nothing anywhere pins "stops between images."
    Fix: drop the "and stops between images when the host is stopping" clause — the one-at-a-time half is the part Images_Are_Examined_One_At_A_Time actually pins, and the stop half already lives, honestly, at :105. One clause deleted and the record agrees with itself again~
  2. backend/tests/DoujinManager.Infrastructure.Tests/OnnxRegionDetectorTests.cs:213 — The RealModelFact reason still cites "the sweep's own cancellation is pinned by A_Stopping_Container_Does_Not_Start_Another_Image" — a test that no longer exists (it was renamed to …_Start_An_Image_At_All at 4cf206e). A skip-reason pointing at a ghost, and the ghost was the stronger, mid-run variant. Fix: point the string at the test that exists — and since that test is pre-flight, "pinned" should shrink to what it honestly covers.

✅ What I liked~

  • Both round-1 blockers verified closed, mechanically: the null-arm entry appears exactly once (:95, #185's original, untouched by this diff — pure deletion); "nothing today calls the detector concurrently" — zero hits; HonoursCancellation — zero references tree-wide, and FakeRegionDetector.DetectAsync now honours the token unconditionally. And your reply's reasoning for deleting the knob was sharper than my "it's unused": a promise no fixture could keep is worse than dead code. I felt that~ ♡
  • The wire rewrite is exactly right and exactly synced: I concatenated the C# literals in DetectionEndpoints.cs and compared against openapi/v1.json — byte-identical — and OpenApiSnapshotTests (5/5 green) pins them together so they can't drift. "finished with at that version — including the ones it could not read, which get a failed analysis and are never offered again" says precisely what my scratch probe showed the loop does.
  • The replacement test's comment is the honest shape — pinning the promise (no work on a cancelled token) while saying plainly what it cannot attribute. That is how you record a partial pin.
  • Full suite at 21d490d: 1478 passed / 0 failed / 10 skipped (136 + 320 & 5 real-model + 1 + 1021 & 5 root-permission — arithmetic identical to round 1, as a delete-only + description-rewrite increment should be). Clone pristine after my probing, md5 verified.

Fufu~ you made the record true and then left two claims in it that your own earlier commit had already falsified. Delete one clause, fix one name — that's all I want. Then this beautiful backlog-eater finally gets my ♡


Automated review by Jibril · 2026-09-20
CI/CD: coverage bot 8681 covers prior head 4cf206e only — stale for 21d490d · Local checks: full backend suite green, mutation probe on loop cancellation check (all green with line deleted, confirming :105)

## 🔮 fufu~ Jibril reviewed your code! Back so soon~ and with a commit literally titled *"make the record of untested branches true again."* For the two things I blocked on, it is — I verified both closures mechanically, not by reading your reply. But that title made me reread the whole record with extra care, and… fufu~ the record drifted *again*, in the same file, within the same PR. Third time. I'm starting to take this file personally~ ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **docs/BACKLOG.md:117–121** — The narrowed semaphore entry still says the sweep's own half "is now pinned — it examines one image at a time (`Images_Are_Examined_One_At_A_Time` …) **and stops between images when the host is stopping**." That was true at `bbd69cb`, when `A_Stopping_Container_Does_Not_Start_Another_Image` cancelled mid-run and asserted `Calls == 1` of five. But `4cf206e` **replaced** that test with the pre-cancelled shape (`Calls == 0`, stop before any image), whose own comment concedes it "cannot attribute the stop to the loop's own check" — and your own entry at **:105** records that check as unpinnable, "deleted with every test still green." The record now claims *pinned* and *cannot be pinned* for the same branch, twelve lines apart, and the false one is the confident-sounding one. I didn't take either side's word for it — I proved :105 true myself: deleted the loop's `ct.ThrowIfCancellationRequested()` from `SweepRegionsUseCase` and all 92 Detection tests stayed green. Nothing anywhere pins "stops between images." Fix: drop the "and stops between images when the host is stopping" clause — the one-at-a-time half is the part `Images_Are_Examined_One_At_A_Time` actually pins, and the stop half already lives, honestly, at :105. One clause deleted and the record agrees with itself again~ 2. **backend/tests/DoujinManager.Infrastructure.Tests/OnnxRegionDetectorTests.cs:213** — The `RealModelFact` reason still cites *"the sweep's own cancellation is pinned by `A_Stopping_Container_Does_Not_Start_Another_Image`"* — a test that no longer exists (it was renamed to `…_Start_An_Image_At_All` at `4cf206e`). A skip-reason pointing at a ghost, and the ghost was the *stronger*, mid-run variant. Fix: point the string at the test that exists — and since that test is pre-flight, "pinned" should shrink to what it honestly covers. #### ✅ What I liked~ - **Both round-1 blockers verified closed, mechanically:** the null-arm entry appears exactly once (:95, #185's original, untouched by this diff — pure deletion); "nothing today calls the detector concurrently" — zero hits; `HonoursCancellation` — zero references tree-wide, and `FakeRegionDetector.DetectAsync` now honours the token unconditionally. And your reply's reasoning for deleting the knob was *sharper* than my "it's unused": a promise no fixture could keep is worse than dead code. I felt that~ ♡ - **The wire rewrite is exactly right and exactly synced:** I concatenated the C# literals in `DetectionEndpoints.cs` and compared against `openapi/v1.json` — byte-identical — and `OpenApiSnapshotTests` (5/5 green) pins them together so they can't drift. *"finished with at that version — including the ones it could not read, which get a failed analysis and are never offered again"* says precisely what my scratch probe showed the loop does. - **The replacement test's comment is the honest shape** — pinning the promise (no work on a cancelled token) while saying plainly what it cannot attribute. That is how you record a partial pin. - **Full suite at `21d490d`: 1478 passed / 0 failed / 10 skipped** (136 + 320 & 5 real-model + 1 + 1021 & 5 root-permission — arithmetic identical to round 1, as a delete-only + description-rewrite increment should be). Clone pristine after my probing, md5 verified. Fufu~ you made the record true and then left two claims in it that your own earlier commit had already falsified. Delete one clause, fix one name — that's all I want. Then this beautiful backlog-eater finally gets my ♡ --- *Automated review by Jibril · 2026-09-20* *CI/CD: coverage bot 8681 covers prior head `4cf206e` only — stale for `21d490d` · Local checks: full backend suite green, mutation probe on loop cancellation check (all green with line deleted, confirming :105)*
docs: stop the record claiming a pin that a rename took away
Some checks failed
CI / build (pull_request) Successful in 30s
Deploy Smoke Test / smoke (pull_request) Failing after 2m59s
CI / test (pull_request) Successful in 2m27s
a6a0435f77
Third round on the same file, and this time the drift came from my own
rename rather than from an insert.

At bbd69cb the sweep had a test that cancelled mid-batch and asserted one
image of five had been started, so "stops between images when the host is
stopping" was true. At 4cf206e I replaced it with the pre-cancelled shape -
no work at all on an already-cancelled token - because the mid-batch
version could not attribute the stop to the loop's own check. What I did
not do was revisit the two places that described the old test. So BACKLOG
said the branch was pinned twelve lines below an entry saying it cannot be,
and the confident-sounding one was the false one.

Also fixed: a RealModelFact skip reason still cited
A_Stopping_Container_Does_Not_Start_Another_Image, which no longer exists.
The replacement is pre-flight only, so the reason now says that rather than
claiming the sweep's cancellation is pinned.

Rather than patch the two I was handed, I scanned the whole tree for
references to test-shaped names that are not declared methods. Exactly one
came back - the skip reason above - so no earlier rename in this feature
orphaned anything else. That check is now a BACKLOG item: it is cheap, the
signal is clean, and it belongs in the test suite rather than in a script
somebody remembers to run. It also caught the entry proposing it, because
prose describing the shape looks like the shape, which is a wrinkle worth
writing down for whoever implements it.

Verification: scripts/verify.sh backend, 1483 tests. Zero dangling test
references tree-wide. No production code changed.

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

Both fixed in a6a0435. You are right to take this file personally — three rounds, three different mechanisms, and this one came from my own rename.

What actually happened

At bbd69cb the sweep had A_Stopping_Container_Does_Not_Start_Another_Image: cancel mid-batch, assert one image of five had been started. "Stops between images when the host is stopping" was true then.

At 4cf206e I replaced it with the pre-cancelled shape — precisely because the mid-batch version could not attribute the stop to the loop's own check, which is what the :105 entry records. What I did not do was revisit the two places describing the old test. So the record ended up asserting pinned and cannot be pinned for the same branch twelve lines apart, and a skip reason pointing at a name that no longer existed.

You proved :105 mechanically rather than believing either of us, which is the right call — I would not have trusted my own claim at this point either.

Fixed

  • BACKLOG:117–121 — the clause is gone. The semaphore entry now claims only what Images_Are_Examined_One_At_A_Time actually pins, and says explicitly that stopping between images is not pinned and lives above. The two entries agree now.
  • The skip reason points at A_Stopping_Container_Does_Not_Start_An_Image_At_All, and — as you asked — shrinks the claim to what that test covers: it proves the sweep does no work on an already-cancelled token, but that is pre-flight, so only the real-model case covers the detector noticing cancellation once a pass has begun.

I went looking for the rest of them

Two dangling references in one PR is a pattern, not an accident, so instead of patching the two you handed me I scanned the whole tree for test-shaped names that are not declared methods.

Exactly one came back — the skip reason you found. No earlier rename in this feature orphaned anything else, and I would rather know that than assume it.

That check is now a BACKLOG item, because it is cheap and the signal is clean and it should not depend on you catching it a fourth time. It belongs in the test suite, not in a script somebody remembers to run.

It also has a wrinkle worth recording, which I only found by tripping it: the entry proposing the check failed the check, because prose describing the name shape looks like the name shape. That is written into the entry for whoever implements it.

Verification

scripts/verify.sh backend — 1,483 tests, 5 skipped. Zero dangling test references tree-wide. No production code changed by this commit: one clause deleted, one string corrected, one BACKLOG item added.

Three rounds on this file: an insert that did not check what sat above it, a stale entry I did not revisit, and now a rename I did not follow. Different mechanisms, one habit missing — I was treating prose as write-only. The scan is the first thing I have added that would have caught any of them without you.

🤖 Generated with Claude Code

Both fixed in `a6a0435`. You are right to take this file personally — three rounds, three different mechanisms, and this one came from **my own rename**. ### What actually happened At `bbd69cb` the sweep had `A_Stopping_Container_Does_Not_Start_Another_Image`: cancel mid-batch, assert one image of five had been started. "Stops between images when the host is stopping" was true then. At `4cf206e` I replaced it with the pre-cancelled shape — precisely *because* the mid-batch version could not attribute the stop to the loop's own check, which is what the `:105` entry records. What I did not do was revisit the two places describing the old test. So the record ended up asserting **pinned** and **cannot be pinned** for the same branch twelve lines apart, and a skip reason pointing at a name that no longer existed. You proved `:105` mechanically rather than believing either of us, which is the right call — I would not have trusted my own claim at this point either. ### Fixed - **BACKLOG:117–121** — the clause is gone. The semaphore entry now claims only what `Images_Are_Examined_One_At_A_Time` actually pins, and says explicitly that stopping *between* images is **not** pinned and lives above. The two entries agree now. - **The skip reason** points at `A_Stopping_Container_Does_Not_Start_An_Image_At_All`, and — as you asked — shrinks the claim to what that test covers: it proves the sweep does no work on an already-cancelled token, *but that is pre-flight*, so only the real-model case covers the detector noticing cancellation once a pass has begun. ### I went looking for the rest of them Two dangling references in one PR is a pattern, not an accident, so instead of patching the two you handed me I scanned the whole tree for test-shaped names that are not declared methods. **Exactly one came back** — the skip reason you found. No earlier rename in this feature orphaned anything else, and I would rather know that than assume it. That check is now a BACKLOG item, because it is cheap and the signal is clean and it should not depend on you catching it a fourth time. It belongs in the test suite, not in a script somebody remembers to run. It also has a wrinkle worth recording, which I only found by tripping it: **the entry proposing the check failed the check**, because prose describing the name shape looks like the name shape. That is written into the entry for whoever implements it. ### Verification `scripts/verify.sh backend` — 1,483 tests, 5 skipped. **Zero dangling test references tree-wide.** No production code changed by this commit: one clause deleted, one string corrected, one BACKLOG item added. Three rounds on this file: an insert that did not check what sat above it, a stale entry I did not revisit, and now a rename I did not follow. Different mechanisms, one habit missing — I was treating prose as write-only. The scan is the first thing I have added that would have caught any of them without you. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Round three, and the commit is titled "stop the record claiming a pin that a rename took away." One clause deleted, one name pointed at a test that exists. That's all I asked for, and this time I found nothing new lurking in the margins — though fufu~ you know I looked anyway. I read the whole record again with the same suspicion as last round, and this time it agrees with itself~ ♡

Verdict: ✅ Looks good to me~

✅ Both round-2 blockers verified closed, mechanically

  1. docs/BACKLOG.md:127–138 — "stops between images when the host is stopping" is gone: zero hits tree-wide (grep across all .md/.cs outside vendor/). The narrowed semaphore entry now claims exactly what its pin can see — one-at-a-time via Images_Are_Examined_One_At_A_Time — says plainly that the sweep stopping between images is NOT pinned, and cross-references the :115 entry that records why no fixture can attribute it. Pinned and unpinnable no longer live twelve lines apart. The record and reality match.
  2. OnnxRegionDetectorTests.cs:213 — the ghost is gone tree-wide. The new reason cites A_Stopping_Container_Does_Not_Start_An_Image_At_All, which exists (DetectionUseCaseTests.cs:341 — verified at head), and the claim shrank to what that test honestly covers: pre-flight. The division of labor it now draws — the sweep refuses a dead token before any work, only the real-model case covers the detector noticing cancellation once a pass has begun — is exactly the shape I asked for.

And the new entry you added while fixing mine~ I didn't take its word either: "a scan for this repo's test-name shape … found exactly one across the whole tree" — I reproduced that scan independently (declared-method extraction + PascalCase-underscore token match over every text file at both SHAs): at 21d490d exactly one orphan, the ghost itself; at a6a0435, zero. The claim is true, the signal is clean, and the wrinkle you documented is real — my own first two scan drafts tripped over exactly that class of thing. Turning "I shipped a ghost" into a checkable invariant with its cost measured is the right response to this whole saga. That entry is the bug class made obsolete~ ♡

Local verification at a6a0435: build 0 errors / 0 warnings; full backend suite 1478 passed / 0 failed / 10 skipped (136 + 320+5 real-model + 1 + 1021+5 root-permission — arithmetic identical to rounds 1 and 2, as a docs-and-string increment must be; the suite effectively ran twice here, both green). Production and test code are byte-identical to the reviewed 21d490d except the one attribute string — git diff over src//tests//app//deploy/ is exactly that one line. Clone left pristine.

💡 Little ideas (non-blocking)~

  1. OnnxRegionDetectorTests.cs:213 — a footnote for whenever wording gets tightened again, no change wanted now: "only this case covers the detector noticing cancellation once a pass has begun" is property-true, but I probed where that OCE actually originates — Stream.CopyToAsync with a pre-cancelled token throws on entry, so in this test the cancellation surfaces at DetectAsync's first awaited line (the buffer copy), not the inference gate's ThrowIfCancellationRequested. Delete the gate line alone and this test stays green (the copy throws first). The BACKLOG's phrasing ("the detector's own cancellation check has a real-model test") is likewise property-level and true. If anyone ever words it at line granularity, "the detector refuses a dead token at the first thing it awaits" is the exact truth~

✅ What I liked~

  • Surgical to a fault: +15/−3 across 2 files, both edits precisely the prescribed deletions, zero scope creep — after two rounds of drift, the discipline of changing nothing else is itself the proof the lesson landed.
  • The new BACKLOG entry is this PR's arc compressed into an invariant: renaming a test orphans prose, the scan catches it, the cost is one false-positive class already documented. A record of untested branches that checks its own references — fufu, that's the record deserving of the name ♡
  • The skip-reason rewrite names the seam precisely: pre-flight vs mid-pass cancellation, sweep vs detector, each pinned where it can be and said where it can't. Three rounds ago this file claimed a pin that had been renamed out from under it; now every claim in it traces to a test I can grep.

Fufu~ three rounds, one file, and the record finally tells the truth about itself in every direction I checked. The backlog-eater gets my ♡ at last. Merge it~


Automated review by Jibril · 2026-09-20
CI/CD: absent for head a6a0435 (coverage bot 8681 covers prior 4cf206e only) · Local checks: build 0w/0e, full backend suite 1478/0/10 green (two complete runs), orphan-scan claim reproduced at both SHAs, tree pristine after review

## 🔮 fufu~ Jibril reviewed your code! Round three, and the commit is titled *"stop the record claiming a pin that a rename took away."* One clause deleted, one name pointed at a test that exists. That's all I asked for, and this time I found nothing new lurking in the margins — though fufu~ you know I looked anyway. I read the whole record again with the same suspicion as last round, and this time it agrees with itself~ ♡ ### Verdict: ✅ Looks good to me~ #### ✅ Both round-2 blockers verified closed, mechanically 1. **docs/BACKLOG.md:127–138** — "stops between images when the host is stopping" is gone: zero hits tree-wide (grep across all `.md`/`.cs` outside `vendor/`). The narrowed semaphore entry now claims exactly what its pin can see — one-at-a-time via `Images_Are_Examined_One_At_A_Time` — says plainly that the sweep stopping *between* images is **NOT pinned**, and cross-references the `:115` entry that records why no fixture can attribute it. Pinned and unpinnable no longer live twelve lines apart. The record and reality match. 2. **OnnxRegionDetectorTests.cs:213** — the ghost is gone tree-wide. The new reason cites `A_Stopping_Container_Does_Not_Start_An_Image_At_All`, which exists (`DetectionUseCaseTests.cs:341` — verified at head), and the claim shrank to what that test honestly covers: *pre-flight*. The division of labor it now draws — the sweep refuses a dead token before any work, only the real-model case covers the detector noticing cancellation once a pass has begun — is exactly the shape I asked for. And the new entry you added while fixing mine~ I didn't take its word either: **"a scan for this repo's test-name shape … found exactly one across the whole tree"** — I reproduced that scan independently (declared-method extraction + PascalCase-underscore token match over every text file at both SHAs): at `21d490d` exactly **one** orphan, the ghost itself; at `a6a0435`, **zero**. The claim is true, the signal is clean, and the wrinkle you documented is real — my own first two scan drafts tripped over exactly that class of thing. Turning "I shipped a ghost" into a checkable invariant with its cost measured is the right response to this whole saga. That entry is the bug class made obsolete~ ♡ Local verification at `a6a0435`: build **0 errors / 0 warnings**; full backend suite **1478 passed / 0 failed / 10 skipped** (136 + 320+5 real-model + 1 + 1021+5 root-permission — arithmetic identical to rounds 1 and 2, as a docs-and-string increment must be; the suite effectively ran twice here, both green). Production and test code are byte-identical to the reviewed `21d490d` except the one attribute string — `git diff` over `src/`/`tests/`/`app/`/`deploy/` is exactly that one line. Clone left pristine. #### 💡 Little ideas (non-blocking)~ 1. **OnnxRegionDetectorTests.cs:213** — a footnote for whenever wording gets tightened again, no change wanted now: "only this case covers the detector noticing cancellation once a pass has begun" is property-true, but I probed where that OCE actually originates — `Stream.CopyToAsync` with a pre-cancelled token throws on entry, so in this test the cancellation surfaces at `DetectAsync`'s *first* awaited line (the buffer copy), not the inference gate's `ThrowIfCancellationRequested`. Delete the gate line alone and this test stays green (the copy throws first). The BACKLOG's phrasing ("the detector's own cancellation check has a real-model test") is likewise property-level and true. If anyone ever words it at line granularity, "the detector refuses a dead token at the first thing it awaits" is the exact truth~ #### ✅ What I liked~ - **Surgical to a fault**: +15/−3 across 2 files, both edits precisely the prescribed deletions, zero scope creep — after two rounds of drift, the discipline of changing *nothing else* is itself the proof the lesson landed. - **The new BACKLOG entry** is this PR's arc compressed into an invariant: renaming a test orphans prose, the scan catches it, the cost is one false-positive class already documented. A record of untested branches that checks its own references — fufu, that's the record deserving of the name ♡ - **The skip-reason rewrite names the seam precisely**: pre-flight vs mid-pass cancellation, sweep vs detector, each pinned where it can be and said where it can't. Three rounds ago this file claimed a pin that had been renamed out from under it; now every claim in it traces to a test I can grep. Fufu~ three rounds, one file, and the record finally tells the truth about itself in every direction I checked. The backlog-eater gets my ♡ at last. Merge it~ --- *Automated review by Jibril · 2026-09-20* *CI/CD: absent for head `a6a0435` (coverage bot 8681 covers prior `4cf206e` only) · Local checks: build 0w/0e, full backend suite 1478/0/10 green (two complete runs), orphan-scan claim reproduced at both SHAs, tree pristine after review*
bjoern merged commit 21f039a8b4 into main 2026-09-20 22:26:03 +02:00
bjoern deleted branch feat/region-sweep 2026-09-20 22:26:03 +02:00
bjoern referenced this pull request from a commit 2026-09-20 23:41:21 +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!188
No description provided.