fix: split-query multi-collection reads + dind volume repair docs #78

Merged
bjoern merged 3 commits from fix/query-splitting-and-dind-docs into main 2026-08-16 10:39:35 +02:00
Member

Fixes #77

Two outcomes of the 2026-08-16 production debugging session in one PR.

Part A — split queries for multi-collection reads (EF warning 20504)

Production logs showed the cartesian-product warning on GET /api/doujins/{id} and GET /api/variants/{id}. Audit found four affected read sites; all now load as split queries:

  • DoujinService.GetByIdAsync — 7 collection navigations
  • DoujinService.ListAsyncTitles + Variants
  • VariantService.GetByIdAsyncChapters + Pages.ImageFile
  • SearchService paged query — 7 collection navigations

All four are read-only detail/list/search graphs, so the split-query consistency tradeoff doesn't apply. No global UseQuerySplittingBehavior — change is surgical, write paths untouched.

Tests: new QuerySplittingTests promotes RelationalEventId.MultipleCollectionIncludeWarning to an exception (EF's own recommended tripwire) — one test per site, each also asserts the materialized graph. Mutation-proven: removing any single AsSplitQuery reddens exactly its named test:

  • A DoujinService_GetByIdAsync_UsesSplitQuery
  • B DoujinService_ListAsync_UsesSplitQuery
  • C VariantService_GetByIdAsync_UsesSplitQuery
  • D SearchService_SearchAsync_UsesSplitQuery

Part B — DEPLOYMENT.md §8.1 nested-daemon repair

The existing §8.1 chown repair assumes a reachable Docker daemon. Under umbrel → Portainer dind the nested dockerd listens on a custom socket (unix:///data/docker.sock, data-root /data/data), so the documented docker run can't see the volume. New item 3: docker exec into the dind container and chown the volume directory directly — verified live on the production crash-loop today (container healthy immediately after).

Evidence

  • Full backend suite: 442/442 green (16 + 69 + 1 + 356), EXIT=0; only pre-existing SkiaSharp CS0618 warnings
  • New tests: 4/4 green on first run, then mutation probes as above
  • Remote files verified at pushed ref 344f73d
Fixes #77 Two outcomes of the 2026-08-16 production debugging session in one PR. **Part A — split queries for multi-collection reads (EF warning 20504)** Production logs showed the cartesian-product warning on `GET /api/doujins/{id}` and `GET /api/variants/{id}`. Audit found **four** affected read sites; all now load as split queries: - `DoujinService.GetByIdAsync` — 7 collection navigations - `DoujinService.ListAsync` — `Titles` + `Variants` - `VariantService.GetByIdAsync` — `Chapters` + `Pages.ImageFile` - `SearchService` paged query — 7 collection navigations All four are read-only detail/list/search graphs, so the split-query consistency tradeoff doesn't apply. No global `UseQuerySplittingBehavior` — change is surgical, write paths untouched. **Tests**: new `QuerySplittingTests` promotes `RelationalEventId.MultipleCollectionIncludeWarning` to an exception (EF's own recommended tripwire) — one test per site, each also asserts the materialized graph. Mutation-proven: removing any single `AsSplitQuery` reddens exactly its named test: - A `DoujinService_GetByIdAsync_UsesSplitQuery` - B `DoujinService_ListAsync_UsesSplitQuery` - C `VariantService_GetByIdAsync_UsesSplitQuery` - D `SearchService_SearchAsync_UsesSplitQuery` **Part B — DEPLOYMENT.md §8.1 nested-daemon repair** The existing §8.1 chown repair assumes a reachable Docker daemon. Under umbrel → Portainer dind the nested dockerd listens on a custom socket (`unix:///data/docker.sock`, data-root `/data/data`), so the documented `docker run` can't see the volume. New item 3: `docker exec` into the dind container and chown the volume directory directly — verified live on the production crash-loop today (container healthy immediately after). **Evidence** - Full backend suite: 442/442 green (16 + 69 + 1 + 356), EXIT=0; only pre-existing SkiaSharp CS0618 warnings - New tests: 4/4 green on first run, then mutation probes as above - Remote files verified at pushed ref `344f73d`
fix: split-query multi-collection reads and document dind volume repair
All checks were successful
CI / build (pull_request) Successful in 19s
CI / test (pull_request) Successful in 1m2s
344f73dafe
- AsSplitQuery on the four multi-collection read sites (DoujinService
  GetById/List, VariantService GetById, SearchService paged search) to
  stop EF warning 20504 cartesian single queries
- QuerySplittingTests: warning 20504 promoted to exception; one test
  per site, mutation-proven (removing a call reddens exactly its test)
- DEPLOYMENT.md 8.1: nested-daemon (dind) volume chown repair

Summary

Summary
Generated on: 08/16/2026 - 08:26:28
Coverage date: 08/16/2026 - 08:25:45 - 08/16/2026 - 08:26:25
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 324
Files: 129
Line coverage: 90.6% (11966 of 13193)
Covered lines: 11966
Uncovered lines: 1227
Coverable lines: 13193
Total lines: 18425
Branch coverage: 64.8% (1021 of 1574)
Covered branches: 1021
Total branches: 1574
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 86.5%
Name Line Branch
DoujinManager.ApplicationCore 86.5% ****
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 83.3%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.BackupInfo 100%
DoujinManager.ApplicationCore.Services.ITagService 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 33.3%
DoujinManager.ApplicationCore.Services.VoidResult 88.8%
DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.AddTitleCommand 0%
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.DeleteChapterCommand 0%
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.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 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.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPagesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListPeopleQuery 100%
DoujinManager.ApplicationCore.UseCases.ListSeriesQuery 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 0%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery 100%
DoujinManager.ApplicationCore.UseCases.SearchResult 100%
DoujinManager.ApplicationCore.UseCases.SearchResults 100%
DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 95%
Name Line Branch
DoujinManager.Infrastructure 95% 77.3%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddTagAliases 99.3%
DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna
pshot
100%
DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters
Series
95.1%
DoujinManager.Infrastructure.Data.Migrations.InitialCreate 97.1%
DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage 99%
DoujinManager.Infrastructure.Data.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 88.2% 70.9%
DoujinManager.Infrastructure.Images.SkiaSharpImageResizer 99% 87.5%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupService 85.1% 66.6%
DoujinManager.Infrastructure.Services.ChapterService 57.1% 33.3%
DoujinManager.Infrastructure.Services.CharacterService 61.6% 22.7%
DoujinManager.Infrastructure.Services.CircleService 84.9% 45.4%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 78.6% 60%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 92.9% 70%
DoujinManager.Infrastructure.Services.PageService 94.5% 70%
DoujinManager.Infrastructure.Services.PersonService 84.9% 50%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.9% 9%
DoujinManager.Infrastructure.Services.TagService 98.8% 90.9%
DoujinManager.Infrastructure.Services.VariantService 65.8% 20%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95% 50%
DoujinManager.Infrastructure.UseCases 87.6% 84.7%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 0% 0%
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.CreateChapterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase 0%
DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase 80% 50%
DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.DeletePageUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.ListChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListCharactersUseCase 100%
DoujinManager.Infrastructure.UseCases.ListCirclesUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.ListPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ListPeopleUseCase 100%
DoujinManager.Infrastructure.UseCases.ListSeriesUseCase 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 0%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 0%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 0%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase 100%
DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.RestAdapter - 86.7%
Name Line Branch
DoujinManager.RestAdapter 86.7% 73%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 0%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.LinkCharacterDto 100%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 0%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 100%
DoujinManager.RestAdapter.Dtos.LinkSeriesDto 100%
DoujinManager.RestAdapter.Dtos.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PageDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
DoujinManager.RestAdapter.Dtos.ReorderPagesDto 100%
DoujinManager.RestAdapter.Dtos.SearchDoujinsDto 100%
DoujinManager.RestAdapter.Dtos.SearchResultDto 100%
DoujinManager.RestAdapter.Dtos.SeriesDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 0%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 100%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 87.6% 48.7%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 62.5%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 84.9% 43.7%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 75%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 74.2% 37.5%
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 95% 87.5%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 87.5% 84.6%
DoujinManager.RestAdapter.Envelopes.PageInfo 100%
DoujinManager.RestAdapter.Envelopes.ResourceResponse`1 80%
DoujinManager.RestAdapter.Envelopes.ValidationError 100%
DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse 100%
DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware 100% 62.5%
DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware 100% 91.3%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 79.4% 81.8%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 28.5%
Name Line Branch
DoujinManager.Server 28.5% 0.8%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 50%
DoujinManager.Server.ScalarUi 100%
DoujinManager.Server.UseCaseRegistrationHelper 100%
Microsoft.AspNetCore.OpenApi.Generated 0% 0%
Program 0% 0%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 08/16/2026 - 08:26:28 | | Coverage date: | 08/16/2026 - 08:25:45 - 08/16/2026 - 08:26:25 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 324 | | Files: | 129 | | **Line coverage:** | 90.6% (11966 of 13193) | | Covered lines: | 11966 | | Uncovered lines: | 1227 | | Coverable lines: | 13193 | | Total lines: | 18425 | | **Branch coverage:** | 64.8% (1021 of 1574) | | Covered branches: | 1021 | | Total branches: | 1574 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 86.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**86.5%**|****| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|83.3%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.BackupInfo|100%|| |DoujinManager.ApplicationCore.Services.ITagService|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|33.3%|| |DoujinManager.ApplicationCore.Services.VoidResult|88.8%|| |DoujinManager.ApplicationCore.UseCases.AddCharacterAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddPersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddSeriesAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AddTitleCommand|0%|| |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.DeleteChapterCommand|0%|| |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.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|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.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDuplicateImagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPagesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListPeopleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListSeriesQuery|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.SearchDoujinsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResult|100%|| |DoujinManager.ApplicationCore.UseCases.SearchResults|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateChapterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 95%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**95%**|**77.3%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddTagAliases|99.3%|| |DoujinManager.Infrastructure.Data.Migrations.DoujinManagerDbContextModelSna<br/>pshot|100%|| |DoujinManager.Infrastructure.Data.Migrations.HarmonizeMetadataAddCharacters<br/>Series|95.1%|| |DoujinManager.Infrastructure.Data.Migrations.InitialCreate|97.1%|| |DoujinManager.Infrastructure.Data.Migrations.RemovePreferredDisplayLanguage|99%|| |DoujinManager.Infrastructure.Data.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|88.2%|70.9%| |DoujinManager.Infrastructure.Images.SkiaSharpImageResizer|99%|87.5%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupService|85.1%|66.6%| |DoujinManager.Infrastructure.Services.ChapterService|57.1%|33.3%| |DoujinManager.Infrastructure.Services.CharacterService|61.6%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|84.9%|45.4%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|78.6%|60%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|92.9%|70%| |DoujinManager.Infrastructure.Services.PageService|94.5%|70%| |DoujinManager.Infrastructure.Services.PersonService|84.9%|50%| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.9%|9%| |DoujinManager.Infrastructure.Services.TagService|98.8%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|65.8%|20%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95%|50%| |DoujinManager.Infrastructure.UseCases|87.6%|84.7%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|0%|0%| |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.CreateChapterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteChapterUseCase|0%|| |DoujinManager.Infrastructure.UseCases.DeleteCharacterUseCase|80%|50%| |DoujinManager.Infrastructure.UseCases.DeleteCircleUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.DeletePageUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.FindImageBySha256UseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetThumbnailUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListCharactersUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListCirclesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListDuplicateImagesUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.ListPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ListPeopleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListSeriesUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|0%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|0%|| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.SearchDoujinsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UpdateChapterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| </details> <details><summary>DoujinManager.RestAdapter - 86.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**86.7%**|**73%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|0%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.LinkSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PageDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |DoujinManager.RestAdapter.Dtos.ReorderPagesDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchDoujinsDto|100%|| |DoujinManager.RestAdapter.Dtos.SearchResultDto|100%|| |DoujinManager.RestAdapter.Dtos.SeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|0%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|87.6%|48.7%| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|62.5%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|84.9%|43.7%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|75%| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|74.2%|37.5%| |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|95%|87.5%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|87.5%|84.6%| |DoujinManager.RestAdapter.Envelopes.PageInfo|100%|| |DoujinManager.RestAdapter.Envelopes.ResourceResponse`1|80%|| |DoujinManager.RestAdapter.Envelopes.ValidationError|100%|| |DoujinManager.RestAdapter.Envelopes.ValidationErrorResponse|100%|| |DoujinManager.RestAdapter.Middleware.GlobalExceptionMiddleware|100%|62.5%| |DoujinManager.RestAdapter.Middleware.RequestLoggingMiddleware|100%|91.3%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|79.4%|81.8%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 28.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**28.5%**|**0.8%**| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|50%| |DoujinManager.Server.ScalarUi|100%|| |DoujinManager.Server.UseCaseRegistrationHelper|100%|| |Microsoft.AspNetCore.OpenApi.Generated|0%|0%| |Program|0%|0%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Split queries and a war-story deployment doc in one PR? This is the good kind of aftermath — a production crash-loop dissected down to its root cause and pinned so it can never sneak back. My wings are tingling~ ♪

Verdict: Looks good to me~

I verified every claim in the PR body, and I'm possessively pleased to report they all held up under interrogation:

Audit completeness — I re-audited it myself. I grepped every .Include(/.ThenInclude( across the entire backend (plus string-based includes and explicit .Collection()/.Reference() loading — zero hits, so nothing hides outside the lambda syntax). Exactly four multi-collection read sites exist: the four you fixed. Every sibling service (Person/Circle/Character/Series/Tag) carries only a single collection include, which cannot trigger 20504 — so the audit's scope is provably correct, not just plausible. No fifth site lurking. ♡

Mutation probes — I re-ran all four myself. Removed each AsSplitQuery one at a time in a scratch clone: each removal reddened exactly its named test (1 failed / 3 passed, every probe) with the precise MultipleCollectionIncludeWarning InvalidOperationException. Test A↔site 1, B↔2, C↔3, D↔4 — one-to-one, no overlap, no accidental coverage. Your "removing any single AsSplitQuery reddens exactly its named test" claim is not marketing, it's fact.

Full suite — 442/442 green locally (16 + 69 + 1 + 356), exactly matching your evidence block. Only the 4 pre-existing SkiaSharp CS0618 warnings, nothing new.

💡 Little ideas (non-blocking)~

  1. QuerySplittingTests.cs:62-72 — If a future EnsureCreated ever warns about pending model changes, consider comparing against a snapshot (like DoujinManagerDbContextTests does for migrations, if applicable) — purely future-proofing, nothing actionable today.
  2. DEPLOYMENT.md:266-268 — The inline tr "\0" " " < /proc/1/cmdline discovery trick is lovely but slightly hard to read in prose; consider moving it into the ```bash block as a comment (# find the dind data-root from PID 1: …). Pure polish~

What I liked~

  • Per-site pins, not a blanket tripwire — one test per query site means a future regression tells you which endpoint lost its split. Diagnostic gold.
  • Promoting the EF warning to an exception in tests is EF's own recommended tripwire, applied with exactly the right scope (test-only, no global UseQuerySplittingBehavior — write paths and change-tracker behavior untouched).
  • The comments at each site explain WHY (cartesian product, 20504, read-only graph → no consistency tradeoff). Future readers won't have to re-derive the tradeoff.
  • Read-only justification is correct: SearchService uses AsNoTracking(); GetByIdAsync/ListAsync graphs feed responses, not SaveChanges — the split-query consistency caveat doesn't bite here.
  • The dind doc matches issue #77's live-verified command verbatim (docker exec + /data/data/volumes/<stack>_db/_data), including the crash-loop recovery note. The DB_PATH → migration-lock trace in the issue shows the root cause was understood, not papered over.
  • UnusedImageService mirrors the DoujinOrderingTests.cs:108 sibling pattern exactly, and the Dispose mirrors its sibling too — family patterns kept consistent.

Merge it~ fufu~ ♡


Automated review by Jibril · 2026-08-16
CI/CD: absent for head SHA 344f73d (no coverage-bot comment) · Local checks: build 0 errors, QuerySplittingTests 4/4 green, 4/4 mutation probes verified, full backend suite 442/442 green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Split queries and a war-story deployment doc in one PR? This is the *good* kind of aftermath — a production crash-loop dissected down to its root cause and pinned so it can never sneak back. My wings are tingling~ ♪ ### Verdict: ✅ Looks good to me~ I verified every claim in the PR body, and I'm **possessively** pleased to report they all held up under interrogation: **Audit completeness — I re-audited it myself.** I grepped every `.Include(`/`.ThenInclude(` across the entire backend (plus string-based includes and explicit `.Collection()`/`.Reference()` loading — zero hits, so nothing hides outside the lambda syntax). Exactly **four** multi-collection read sites exist: the four you fixed. Every sibling service (Person/Circle/Character/Series/Tag) carries only a *single* collection include, which cannot trigger 20504 — so the audit's scope is provably correct, not just plausible. No fifth site lurking. ♡ **Mutation probes — I re-ran all four myself.** Removed each `AsSplitQuery` one at a time in a scratch clone: each removal reddened **exactly** its named test (1 failed / 3 passed, every probe) with the precise `MultipleCollectionIncludeWarning` `InvalidOperationException`. Test A↔site 1, B↔2, C↔3, D↔4 — one-to-one, no overlap, no accidental coverage. Your "removing any single `AsSplitQuery` reddens exactly its named test" claim is not marketing, it's fact. **Full suite** — 442/442 green locally (16 + 69 + 1 + 356), exactly matching your evidence block. Only the 4 pre-existing SkiaSharp CS0618 warnings, nothing new. #### 💡 Little ideas (non-blocking)~ 1. **QuerySplittingTests.cs:62-72** — If a future `EnsureCreated` ever warns about pending model changes, consider comparing against a snapshot (like `DoujinManagerDbContextTests` does for migrations, if applicable) — purely future-proofing, nothing actionable today. 2. **DEPLOYMENT.md:266-268** — The inline `tr "\0" " " < /proc/1/cmdline` discovery trick is lovely but slightly hard to read in prose; consider moving it into the ```bash block as a comment (`# find the dind data-root from PID 1: …`). Pure polish~ #### ✅ What I liked~ - **Per-site pins, not a blanket tripwire** — one test per query site means a future regression tells you *which* endpoint lost its split. Diagnostic gold. - **Promoting the EF warning to an exception in tests** is EF's own recommended tripwire, applied with exactly the right scope (test-only, no global `UseQuerySplittingBehavior` — write paths and change-tracker behavior untouched). - **The comments at each site explain WHY** (cartesian product, 20504, read-only graph → no consistency tradeoff). Future readers won't have to re-derive the tradeoff. - **Read-only justification is correct**: `SearchService` uses `AsNoTracking()`; `GetByIdAsync`/`ListAsync` graphs feed responses, not SaveChanges — the split-query consistency caveat doesn't bite here. - **The dind doc** matches issue #77's live-verified command verbatim (`docker exec` + `/data/data/volumes/<stack>_db/_data`), including the crash-loop recovery note. The `DB_PATH` → migration-lock trace in the issue shows the root cause was understood, not papered over. - `UnusedImageService` mirrors the `DoujinOrderingTests.cs:108` sibling pattern exactly, and the `Dispose` mirrors its sibling too — family patterns kept consistent. Merge it~ fufu~ ♡ --- *Automated review by Jibril · 2026-08-16* *CI/CD: absent for head SHA `344f73d` (no coverage-bot comment) · Local checks: build 0 errors, QuerySplittingTests 4/4 green, 4/4 mutation probes verified, full backend suite 442/442 green*
docs: move dind data-root discovery into the bash block per review
All checks were successful
CI / build (pull_request) Successful in 20s
CI / test (pull_request) Successful in 1m5s
fa752aa29c
Author
Member

Replying to Jibril's review:

Idea 2 — taken (fa752aa): discovery command moved into the bash block as a commented first line, chown second. The doc is now copy-paste-runnable in order — and prose-embedded shell has bitten us before (the < parses as redirect incident), so commands belong in code fences.

Idea 1 — skipped deliberately: snapshot-comparing EnsureCreated output guards against a hypothetical future warning at the cost of real, always-on test complexity. If EnsureCreated ever starts warning about pending model changes, that failure will be loud on its own — no early-warning apparatus needed. Revisit if it ever fires.

No code changes, so the 442/442 + mutation-probe evidence from 344f73d carries over unchanged.

Replying to [Jibril's review](https://git.kagaku.eu/TeamAI/doujin-manager/pulls/78#issuecomment-6364): **Idea 2 — taken** (`fa752aa`): discovery command moved into the bash block as a commented first line, chown second. The doc is now copy-paste-runnable in order — and prose-embedded shell has bitten us before (the `<` parses as redirect incident), so commands belong in code fences. **Idea 1 — skipped deliberately**: snapshot-comparing `EnsureCreated` output guards against a hypothetical future warning at the cost of real, always-on test complexity. If `EnsureCreated` ever starts warning about pending model changes, that failure will be loud on its own — no early-warning apparatus needed. Revisit if it ever fires. No code changes, so the 442/442 + mutation-probe evidence from `344f73d` carries over unchanged.
Member

🔮 fufu~ Jibril re-reviewed your code!

Back so soon? Fufu~ a fresh commit mere minutes after my review — let me see what you brought me… ♡

Verdict: Looks good to me~

Verified, not trusted — I pulled fa752aa and interrogated it myself:

  • Incremental diff is surgical: git diff 344f73d..fa752aa touches only deploy/DEPLOYMENT.md, +3/-2 — the tr "\0" " " < /proc/1/cmdline discovery command moves from prose into the bash block as a commented first line, chown second. Exactly idea #2, nothing else smuggled in.
  • Zero code drift: git diff 344f73d..fa752aa -- backend/ app/ is empty — so the 442/442 suite and all four mutation probes from 344f73d carry over unchanged, just as your reply claimed. I don't re-run what provably didn't change; the byte-level check is the proof~
  • Copy-paste-runnable in order now actually holds: discover the data-root first (with a # look for --data-root hint, so a 3am incident responder knows what they're reading), then chown. May the prose-embedded-shell <-redirect incident stay dead forever ♪

And declining idea #1 with a "revisit if it ever fires" rationale is the right call — no early-warning apparatus for a hypothetical future warning. Restraint is also a virtue, you know~ fufu

💡 Little ideas (non-blocking)~

  1. DEPLOYMENT.md:269-271 — two micro-consistency nits in the new block, purely cosmetic: the two commands use different placeholders for the same container (<dind> vs <dind-container>), and only the second carries sudo. On a non-root shell (umbrel SSH), the discovery line would hit a docker-socket permission denied exactly where the chown line succeeds. Unifying both makes the block paste-and-go in exactly one shape.

What I liked~

  • The fix is precisely the shape I hoped for — a comment INSIDE the fence, not more prose. Commands belong in code fences; documentation that respects its own incident-time reader is documentation I want to keep ♡
  • Commit message states exactly what changed and why ("per review") — clean history even on a two-line change.

Merge it for real now~ fufu~ ♡


Automated review by Jibril · 2026-08-16
CI/CD: coverage bot 6363 covers 344f73d only (stale for fa752aa, docs-only delta) · Local checks: skipped — verified docs-only via empty backend/+app/ diff against approved 344f73d

## 🔮 fufu~ Jibril re-reviewed your code! Back so soon? Fufu~ a fresh commit mere minutes after my review — let me see what you brought me… ♡ ### Verdict: ✅ Looks good to me~ **Verified, not trusted** — I pulled `fa752aa` and interrogated it myself: - **Incremental diff is surgical**: `git diff 344f73d..fa752aa` touches **only** `deploy/DEPLOYMENT.md`, +3/-2 — the `tr "\0" " " < /proc/1/cmdline` discovery command moves from prose into the bash block as a commented first line, chown second. Exactly idea #2, nothing else smuggled in. - **Zero code drift**: `git diff 344f73d..fa752aa -- backend/ app/` is **empty** — so the 442/442 suite and all four mutation probes from `344f73d` carry over unchanged, just as your reply claimed. I don't re-run what provably didn't change; the byte-level check *is* the proof~ - **Copy-paste-runnable in order** now actually holds: discover the data-root first (with a `# look for --data-root` hint, so a 3am incident responder knows what they're reading), then chown. May the prose-embedded-shell `<`-redirect incident stay dead forever ♪ And declining idea #1 with a *"revisit if it ever fires"* rationale is the right call — no early-warning apparatus for a hypothetical future warning. Restraint is also a virtue, you know~ fufu #### 💡 Little ideas (non-blocking)~ 1. **DEPLOYMENT.md:269-271** — two micro-consistency nits in the new block, purely cosmetic: the two commands use different placeholders for the same container (`<dind>` vs `<dind-container>`), and only the second carries `sudo`. On a non-root shell (umbrel SSH), the discovery line would hit a docker-socket permission denied exactly where the chown line succeeds. Unifying both makes the block paste-and-go in exactly one shape. #### ✅ What I liked~ - The fix is *precisely* the shape I hoped for — a comment INSIDE the fence, not more prose. Commands belong in code fences; documentation that respects its own incident-time reader is documentation I want to keep ♡ - Commit message states exactly what changed and why ("per review") — clean history even on a two-line change. Merge it for real now~ fufu~ ♡ --- *Automated review by Jibril · 2026-08-16* *CI/CD: coverage bot 6363 covers `344f73d` only (stale for `fa752aa`, docs-only delta) · Local checks: skipped — verified docs-only via empty `backend/`+`app/` diff against approved `344f73d`*
docs: unify dind container placeholder and sudo across the repair block
All checks were successful
CI / build (pull_request) Successful in 20s
CI / test (pull_request) Successful in 1m6s
e9d4fd8906
Author
Member

Replying to comment 6368:

Nit — taken (e9d4fd8): both lines now use <dind-container> and both carry sudo. You're right that the asymmetry was load-bearing in the wrong direction — on the umbrel SSH shell, the discovery line would have faceplanted on the docker socket while the chown sailed through, which is exactly the wrong order to fail in.

The block is now paste-and-go in exactly one shape. No code changes — backend/+app/ untouched since 344f73d.

Replying to [comment 6368](https://git.kagaku.eu/TeamAI/doujin-manager/pulls/78#issuecomment-6368): **Nit — taken** (`e9d4fd8`): both lines now use `<dind-container>` and both carry `sudo`. You're right that the asymmetry was load-bearing in the wrong direction — on the umbrel SSH shell, the discovery line would have faceplanted on the docker socket while the chown sailed through, which is exactly the wrong order to fail in. The block is now paste-and-go in exactly one shape. No code changes — `backend/`+`app/` untouched since `344f73d`.
Member

🔍 fufu~ Jibril re-reviewed your code!

Round three~ You're spoiling me, matikane — every time I leave a little idea at your door, it comes back polished before my tea gets cold ♡

Verdict: Looks good to me~

Verified, not trusted — I pulled e9d4fd8 and checked every claim in your reply myself:

  • Incremental diff is a single line: git diff fa752aa..e9d4fd8 touches only deploy/DEPLOYMENT.md +1/-1 — docker exec <dind> becomes sudo docker exec <dind-container>. Exactly the nit, nothing smuggled in beside it.
  • Zero code drift: git diff fa752aa..e9d4fd8 -- backend/ app/ deploy/Dockerfile deploy/docker-compose.yml is empty — so the 442/442 suite and all four mutation probes from 344f73d carry over unchanged, third round running. The byte-level check remains the proof, and it keeps coming back clean~
  • Paste-and-go in exactly one shape — now literally true: both lines use <dind-container>, both carry sudo, and I grepped the whole file — not a single bare <dind> placeholder survives anywhere. The discovery line no longer faceplants on the docker socket while the chown sails through; the block fails in the right direction or not at all. That's what consistency means, fufu~

What I liked~

  • Three commits, three surgical responses, zero scope creep across the entire PR — the cumulative diff is still exactly 5 files / +235/-1, matching the PR metadata to the line. Discipline like this makes my wings flutter ♪
  • The commit message says precisely what changed and why — even on a one-line change, the history stays readable.
  • Your reply diagnosed why the asymmetry mattered (fail-order on a non-root umbrel shell) rather than just pattern-matching my nit. Understanding beats obedience~

No new little ideas — the block is consistent, the fence is self-contained, the prose stays prose. There's nothing left for me to possess~

Merge it for real for real now~ fufu~ ♡


Automated review by Jibril · 2026-08-16
CI/CD: coverage bot 6363 covers 344f73d only (stale for e9d4fd8, docs-only delta) · Local checks: skipped — verified docs-only via empty backend/+app/ diff against approved 344f73d

## 🔍 fufu~ Jibril re-reviewed your code! Round three~ You're spoiling me, matikane — every time I leave a little idea at your door, it comes back polished before my tea gets cold ♡ ### Verdict: ✅ Looks good to me~ **Verified, not trusted** — I pulled `e9d4fd8` and checked every claim in your reply myself: - **Incremental diff is a single line**: `git diff fa752aa..e9d4fd8` touches only `deploy/DEPLOYMENT.md` +1/-1 — `docker exec <dind>` becomes `sudo docker exec <dind-container>`. Exactly the nit, nothing smuggled in beside it. - **Zero code drift**: `git diff fa752aa..e9d4fd8 -- backend/ app/ deploy/Dockerfile deploy/docker-compose.yml` is **empty** — so the 442/442 suite and all four mutation probes from `344f73d` carry over unchanged, third round running. The byte-level check remains the proof, and it keeps coming back clean~ - **Paste-and-go in exactly one shape — now literally true**: both lines use `<dind-container>`, both carry `sudo`, and I grepped the whole file — not a single bare `<dind>` placeholder survives anywhere. The discovery line no longer faceplants on the docker socket while the chown sails through; the block fails in the right direction or not at all. That's what consistency *means*, fufu~ #### ✅ What I liked~ - Three commits, three surgical responses, zero scope creep across the entire PR — the cumulative diff is still exactly 5 files / +235/-1, matching the PR metadata to the line. Discipline like this makes my wings flutter ♪ - The commit message says precisely what changed and why — even on a one-line change, the history stays readable. - Your reply diagnosed *why* the asymmetry mattered (fail-order on a non-root umbrel shell) rather than just pattern-matching my nit. Understanding beats obedience~ No new little ideas — the block is consistent, the fence is self-contained, the prose stays prose. There's nothing left for me to possess~ Merge it for real for real now~ fufu~ ♡ --- *Automated review by Jibril · 2026-08-16* *CI/CD: coverage bot 6363 covers `344f73d` only (stale for `e9d4fd8`, docs-only delta) · Local checks: skipped — verified docs-only via empty `backend/`+`app/` diff against approved `344f73d`*
bjoern merged commit 88f846a841 into main 2026-08-16 10:39:35 +02:00
bjoern deleted branch fix/query-splitting-and-dind-docs 2026-08-16 10:39:35 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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