Metadata management: editors for 5 entity types, harmonized model, and library filtering #42

Merged
bjoern merged 6 commits from feat/entity-management-ui into main 2026-07-01 23:28:09 +02:00
Owner

Summary

This PR turns the metadata layer into a first-class, editable feature. It adds
desktop management editors for five entity types — Tags, People, Circles,
and two new ones, Characters and Series — harmonizes all of them onto a
single shape (name + description + aliases), and makes the library search
bar filter and autocomplete by every type, including by alias.

It spans the .NET backend (entities, endpoints, search, one EF migration) and
the Flutter client (models, state, generic UI, filtering), and fixes a
pre-existing bug where the library autocomplete never had any data.

104 files changed, ~9.1k insertions. Full backend and client test suites pass;
flutter analyze is clean.


What's new (user-facing)

  • Five NavigationRail entries: Tags, People, Circles, Characters, Series —
    each a desktop master-detail editor (fixed-width searchable list on the left,
    a large inline editor on the right).
  • Full CRUD per type: create, rename, edit description, add/remove aliases,
    delete. Save fires a confirmation toast; delete and alias-removal ask for
    confirmation by name; a newly-created entity stays open in the editor.
  • HATEOAS-driven delete: the delete button only shows when the server says
    the entity is deletable; deleting an in-use entity returns 409 and shows a
    "used by N doujin(s)" dialog.
  • Consistent fields everywhere: every type is now name + description + aliases. Tags lost the confusing namespace; People/Circles lost the unused
    sortName and their notes became description.
  • Library filtering by Characters & Series, alongside the existing
    tag/person/circle filters.
  • Alias-aware autocomplete: typing an alias suggests it resolving to the
    canonical name, e.g. Tag: tg → gender bender, and commits the canonical
    name. This makes aliases usable as bilingual (EN/JP) shortcuts.
  • On the doujin detail page, tag chips are uniform with the description shown as
    a tooltip; characters and series are listed (read-only).

Backend (.NET)

Field harmonization

  • Tag: drop namespace, add description. Person/Circle: drop sortName,
    rename notesdescription.
  • Threaded through entities, EF configs, DTOs, use-case commands, services, and
    every TagDto/CircleDto construction site (metadata, doujin-detail, search).

New Character & Series entities (full Circle parity)

  • Domain entities + strongly-typed ids, join entities (DoujinCharacter,
    DoujinSeries) and configs, services + interfaces, use-cases, DTOs, hypermedia
    builders, and REST endpoints (/api/characters, /api/series) with CRUD +
    alias + pagination + search + per-item links/actions.
  • doujinCount and referential-integrity delete (409 when in use), matching
    Circles.
  • Doujin creation accepts characterIds/seriesIds; the doujin detail response
    includes assigned characters/series.
  • Library search filters by characterNames/seriesNames (mirrors
    circleNames).

Migration

  • One EF migration (HarmonizeMetadataAddCharactersSeries) that renames
    notesdescription on people/circles (data preserved), drops
    namespace/sort_name, adds description to tags, and creates characters,
    series, doujin_characters, doujin_series. Applied automatically at
    startup via the existing Database.MigrateAsync().

Client (Flutter)

Data + state

  • Tag/Person/Circle harmonized; new Character/Series freezed models
    implementing a shared EntityModel. Repository gains paginated + search reads,
    full write methods, and flat reads for the metadata cache.
  • Generic, DRY state layer: one EntityListState<T> slice, one generic
    action set + reducer, and generic epics parameterized by an EntityOps<T>
    strategy — the five types are type instantiations, not copy-paste.

UI

  • One EntityManagementPage<T> master-detail widget + one EntityEditor<T>
    (fields described declaratively per type; shared alias-chip section; Ctrl+S to
    save) + thin per-type page wrappers + NavigationRail entries and routes.

Filtering

  • FilterType gains character/series; parseFilterToken,
    buildSuggestions, SearchQuery, LibraryState, the library reducer buckets
    and the search-query builder all handle the two new types.
  • The metadata cache loads characters/series for autocomplete; the smart filter
    bar feeds them into suggestions.
  • Alias matching: the parser and suggestion builder match primary name OR
    any alias (five per-type blocks refactored into two generic EntityModel
    helpers). Alias matches render Type: alias → Canonical Name.

Bug fixes included

  1. Library autocomplete never loaded metadata. _loadMetadataEpic bailed on
    metadata.isLoading, but the reducer sets that flag true for the same
    LoadMetadataAction before the async epic runs — so it always
    short-circuited (the epic's unit test masked this with an identity reducer).
    Removed the self-defeating guard; the cache-non-empty check still prevents
    redundant reloads.
  2. Stale metadata cache. Even once loaded it never refreshed, so entities
    created afterwards never appeared in suggestions. Added a force flag to
    LoadMetadataAction and an epic that force-refreshes the cache after any
    entity create/update/delete.

Key decisions

  • Reused existing hypermedia types (HypermediaLink/HypermediaAction/
    envelopes) instead of adding new client models.
  • Per-item links/actions on collection responses so per-row delete
    eligibility is server-authoritative (list items previously had none).
  • Alias search is in-memory (aliases are a value-converted JSON column and
    aren't SQL-translatable); the no-search path keeps SQL-side pagination.
  • Generic client architecture over triplicated boilerplate — adding a type
    is a model + ops + slice + page + route.

Explicitly out of scope (future phase)

  • The doujin assignment editor — UI to add/remove a doujin's
    tags/people/circles/characters/series on the detail page — and the
    create/upload doujin flow. No type has this today; associations are populated
    by the backend create/ingest path. Filtering and read-only display work
    regardless.

Testing

  • Backend: dotnet build + dotnet test green (326 tests). New coverage:
    Character/Series CRUD, alias, search, 409-in-use, detail include, and
    search-by-name; per-item delete-action visibility.
    • Note: one pre-existing RequestLoggingMiddlewareTests case only fails under
      a local appsettings.Development.json logging override (Microsoft.AspNetCore
      Information); with the default Warning it passes. Unrelated to this PR.
  • Client: flutter analyze clean; flutter test green (220 tests). New
    coverage: master-detail interactions, save/delete/alias confirmations,
    create-stays-open, Character/Series page smokes, character/series + alias
    filter-token tests, and the metadata force-refresh path.

Verify locally

cd backend && dotnet build && dotnet test
cd app && dart run build_runner build --delete-conflicting-outputs \
        && flutter analyze && flutter test

Then run the app: all five rail entries open working editors; create a tag with
an alias (e.g. name "gender bender", alias "tg"); in the library search bar, type
tg and confirm it suggests Tag: tg → gender bender.

Reviewer notes

  • The client EntityManagementPage<T> / generic epics / entity_reducer are the
    DRY core — reviewing those once covers all five types.
  • The EF migration is generated; the one thing to eyeball is that it uses
    RenameColumn for notes→description (it does), not drop+add.
## Summary This PR turns the metadata layer into a first-class, editable feature. It adds desktop management editors for **five** entity types — Tags, People, Circles, and two new ones, **Characters** and **Series** — harmonizes all of them onto a single shape (**name + description + aliases**), and makes the library search bar **filter and autocomplete** by every type, including by **alias**. It spans the .NET backend (entities, endpoints, search, one EF migration) and the Flutter client (models, state, generic UI, filtering), and fixes a pre-existing bug where the library autocomplete never had any data. `104 files changed, ~9.1k insertions`. Full backend and client test suites pass; `flutter analyze` is clean. --- ## What's new (user-facing) - **Five NavigationRail entries**: Tags, People, Circles, Characters, Series — each a desktop master-detail editor (fixed-width searchable list on the left, a large inline editor on the right). - **Full CRUD per type**: create, rename, edit description, add/remove aliases, delete. Save fires a confirmation toast; delete and alias-removal ask for confirmation by name; a newly-created entity stays open in the editor. - **HATEOAS-driven delete**: the delete button only shows when the server says the entity is deletable; deleting an in-use entity returns 409 and shows a "used by N doujin(s)" dialog. - **Consistent fields everywhere**: every type is now `name + description + aliases`. Tags lost the confusing `namespace`; People/Circles lost the unused `sortName` and their `notes` became `description`. - **Library filtering by Characters & Series**, alongside the existing tag/person/circle filters. - **Alias-aware autocomplete**: typing an alias suggests it resolving to the canonical name, e.g. `Tag: tg → gender bender`, and commits the canonical name. This makes aliases usable as bilingual (EN/JP) shortcuts. - On the doujin detail page, tag chips are uniform with the description shown as a tooltip; characters and series are listed (read-only). --- ## Backend (.NET) ### Field harmonization - `Tag`: drop `namespace`, add `description`. `Person`/`Circle`: drop `sortName`, rename `notes` → `description`. - Threaded through entities, EF configs, DTOs, use-case commands, services, and every `TagDto`/`CircleDto` construction site (metadata, doujin-detail, search). ### New Character & Series entities (full Circle parity) - Domain entities + strongly-typed ids, join entities (`DoujinCharacter`, `DoujinSeries`) and configs, services + interfaces, use-cases, DTOs, hypermedia builders, and REST endpoints (`/api/characters`, `/api/series`) with CRUD + alias + pagination + search + per-item links/actions. - `doujinCount` and referential-integrity delete (409 when in use), matching Circles. ### Linking + search - Doujin creation accepts `characterIds`/`seriesIds`; the doujin detail response includes assigned characters/series. - Library search filters by `characterNames`/`seriesNames` (mirrors `circleNames`). ### Migration - One EF migration (`HarmonizeMetadataAddCharactersSeries`) that **renames** `notes` → `description` on people/circles (data preserved), drops `namespace`/`sort_name`, adds `description` to tags, and creates `characters`, `series`, `doujin_characters`, `doujin_series`. Applied automatically at startup via the existing `Database.MigrateAsync()`. --- ## Client (Flutter) ### Data + state - `Tag`/`Person`/`Circle` harmonized; new `Character`/`Series` freezed models implementing a shared `EntityModel`. Repository gains paginated + search reads, full write methods, and flat reads for the metadata cache. - Generic, DRY state layer: one `EntityListState<T>` slice, one generic action set + reducer, and generic epics parameterized by an `EntityOps<T>` strategy — the five types are type instantiations, not copy-paste. ### UI - One `EntityManagementPage<T>` master-detail widget + one `EntityEditor<T>` (fields described declaratively per type; shared alias-chip section; Ctrl+S to save) + thin per-type page wrappers + NavigationRail entries and routes. ### Filtering - `FilterType` gains `character`/`series`; `parseFilterToken`, `buildSuggestions`, `SearchQuery`, `LibraryState`, the library reducer buckets and the search-query builder all handle the two new types. - The metadata cache loads characters/series for autocomplete; the smart filter bar feeds them into suggestions. - **Alias matching**: the parser and suggestion builder match primary name OR any alias (five per-type blocks refactored into two generic `EntityModel` helpers). Alias matches render `Type: alias → Canonical Name`. --- ## Bug fixes included 1. **Library autocomplete never loaded metadata.** `_loadMetadataEpic` bailed on `metadata.isLoading`, but the reducer sets that flag `true` for the same `LoadMetadataAction` before the async epic runs — so it always short-circuited (the epic's unit test masked this with an identity reducer). Removed the self-defeating guard; the cache-non-empty check still prevents redundant reloads. 2. **Stale metadata cache.** Even once loaded it never refreshed, so entities created afterwards never appeared in suggestions. Added a `force` flag to `LoadMetadataAction` and an epic that force-refreshes the cache after any entity create/update/delete. --- ## Key decisions - **Reused existing hypermedia types** (`HypermediaLink`/`HypermediaAction`/ envelopes) instead of adding new client models. - **Per-item links/actions on collection responses** so per-row delete eligibility is server-authoritative (list items previously had none). - **Alias search is in-memory** (aliases are a value-converted JSON column and aren't SQL-translatable); the no-search path keeps SQL-side pagination. - **Generic client architecture** over triplicated boilerplate — adding a type is a model + ops + slice + page + route. ## Explicitly out of scope (future phase) - The **doujin assignment editor** — UI to add/remove a doujin's tags/people/circles/characters/series on the detail page — and the create/upload doujin flow. No type has this today; associations are populated by the backend create/ingest path. Filtering and read-only display work regardless. ## Testing - **Backend**: `dotnet build` + `dotnet test` green (326 tests). New coverage: Character/Series CRUD, alias, search, 409-in-use, detail include, and search-by-name; per-item delete-action visibility. - Note: one pre-existing `RequestLoggingMiddlewareTests` case only fails under a local `appsettings.Development.json` logging override (`Microsoft.AspNetCore` → `Information`); with the default `Warning` it passes. Unrelated to this PR. - **Client**: `flutter analyze` clean; `flutter test` green (220 tests). New coverage: master-detail interactions, save/delete/alias confirmations, create-stays-open, Character/Series page smokes, character/series + alias filter-token tests, and the metadata force-refresh path. ## Verify locally ```bash cd backend && dotnet build && dotnet test cd app && dart run build_runner build --delete-conflicting-outputs \ && flutter analyze && flutter test ``` Then run the app: all five rail entries open working editors; create a tag with an alias (e.g. name "gender bender", alias "tg"); in the library search bar, type `tg` and confirm it suggests `Tag: tg → gender bender`. ## Reviewer notes - The client `EntityManagementPage<T>` / generic epics / `entity_reducer` are the DRY core — reviewing those once covers all five types. - The EF migration is generated; the one thing to eyeball is that it uses `RenameColumn` for notes→description (it does), not drop+add.
Backend (.NET):
- Embed per-item HATEOAS links/actions on Tag/Person/Circle collection items
  so delete eligibility is server-authoritative per row; Include(Doujins) in
  the list queries makes per-item usage counts accurate.
- Add a `search` query param (name + aliases, case-insensitive) end-to-end
  through PaginationParams -> List*Query -> use-cases -> services. Aliases are a
  JSON column, so search falls back to in-memory filtering when a term is set.

Client (Flutter):
- Add EntityModel interface; extend Tag/Person/Circle with aliases/links/actions
  (reusing the existing HypermediaLink/HypermediaAction envelope types).
- Repository: paginated+searchable reads and full write methods; single-resource
  writes merge envelope hypermedia into the model; 409 -> EntityInUseException.
- DRY generic state layer: one EntityListState<T> + generic actions/reducer and
  generic epics parameterized by an EntityOps<T> strategy (three type
  instantiations instead of triplicated boilerplate).
- One EntityManagementPage<T> (search, infinite scroll, dense rows, HATEOAS-gated
  delete, edit dialog with alias section, delete-conflict dialog) + three thin
  pages, NavigationRail entries, and routes.

Tests: 8 new backend integration tests; 26 new client tests (reducer, epics,
page, smoke). flutter analyze clean; full suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the modal-dialog editing flow with a desktop master-detail layout:
fixed-width left column (search + New button + dense selectable list) and a
large inline editor pane (fields + alias management + Save/Delete), with Ctrl+S
to save. Newly created entities stay open in the editor instead of deselecting.

- Save always fires a confirmation toast, independent of the result.
- Delete and alias-removal now ask for confirmation by name.
- Remove the obsolete entity_edit_dialog.dart in favour of entity_editor.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reshape all metadata types onto one shape (primary name + description + aliases)
and add two new first-class types, Characters and Series, with library filtering.

Backend (.NET):
- Harmonize fields: drop tag `namespace` (add `description`); drop `sortName`
  and rename `notes`->`description` on Person/Circle. One EF migration renames
  notes->description (data preserved) and drops the removed columns.
- Add Character and Series as full Circle-parity entities: join tables,
  doujinCount, referential-integrity delete, CRUD + alias + search endpoints.
- Create-attach (CreateDoujinDto characterIds/seriesIds) + include them in the
  doujin detail DTO; search filtering by characterNames/seriesNames.

Client (Flutter):
- Harmonize Tag/Person/Circle models, repo, ops, and editors to description;
  tag chips on the detail page are uniform with the description as a tooltip.
- Add Character/Series freezed models, repo methods, ops, state slices, generic
  epics registration, thin management pages, NavigationRail entries + routes.
- Filtering: metadata cache loads characters/series; FilterType + parse +
  suggestions + SearchQuery + LibraryState + reducer buckets; read-only
  character/series sections on the doujin detail page.

Tests: 8 new backend integration tests (Character/Series CRUD, alias, search,
409-in-use, detail include, search-by-name); new client character/series page
smoke tests + character/series filter-token test; all fixtures harmonized.
Backend `dotnet test` and client `flutter analyze`/`flutter test` green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two bugs meant search suggestions were always empty:

1. `_loadMetadataEpic` bailed on `metadata.isLoading`, but the reducer sets
   that flag true for the very same `LoadMetadataAction` before the async epic
   runs — so the guard always short-circuited and metadata never loaded in a
   real store (the epic test masked this with an identity reducer). Removed the
   isLoading guard; the cache-non-empty check still prevents redundant loads.

2. Even once loaded, the cache never refreshed, so entities created afterwards
   never appeared in autocomplete. Added a `force` flag to LoadMetadataAction
   and an epic that force-refreshes the metadata cache after any entity is
   created, updated, or deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(app): match aliases in library search autocomplete
All checks were successful
CI / build (pull_request) Successful in 16s
Flutter CI / analyze-and-test (pull_request) Successful in 1m4s
CI / test (pull_request) Successful in 49s
3c75293472
Filter suggestions and the token parser now match an entity's aliases as well
as its primary name, across all five types. When a match is via an alias, the
suggestion is labelled "Tag: tg → gender bender" (alias → canonical name), and
the committed token always carries the canonical name so the backend filters
correctly. This makes aliases usable as bilingual shortcuts (e.g. an English
alias resolving to a Japanese canonical name).

Refactored the five per-type suggestion/parse blocks into two generic helpers
over EntityModel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Flutter Coverage

File Line coverage
lib/data/models/doujin_models.dart 82.5% (33 of 40)
lib/data/models/doujin_models.g.dart 39.0% (113 of 290)
lib/data/models/envelope.dart 81.2% (13 of 16)
lib/data/models/envelope.g.dart 50.7% (34 of 67)
lib/presentation/state/app_state.dart 62.5% (5 of 8)
lib/presentation/state/reducers/entity_reducer.dart 98.5% (66 of 67)
lib/core/languages.dart 100.0% (8 of 8)
lib/domain/entities/entity_model.dart 100.0% (1 of 1)
lib/domain/entities/filter_token.dart 89.5% (77 of 86)
lib/domain/entities/stored_settings.dart 100.0% (6 of 6)
lib/presentation/state/actions/detail_actions.dart 75.0% (3 of 4)
lib/presentation/state/actions/entity_actions.dart 75.0% (12 of 16)
lib/presentation/state/actions/library_actions.dart 33.3% (6 of 18)
lib/presentation/state/actions/metadata_actions.dart 66.7% (2 of 3)
lib/presentation/state/actions/settings_actions.dart 100.0% (5 of 5)
lib/app/store.dart 100.0% (10 of 10)
lib/presentation/pages/people/people_page.dart 46.7% (7 of 15)
lib/presentation/middleware/epics.dart 81.9% (203 of 248)
lib/presentation/state/reducers.dart 100.0% (6 of 6)
lib/data/models/search_query.dart 50.0% (2 of 4)
lib/data/models/search_query.g.dart 34.5% (19 of 55)
lib/data/repositories/entity_in_use_exception.dart 33.3% (1 of 3)
lib/presentation/middleware/entity_ops.dart 58.0% (40 of 69)
lib/presentation/widgets/entity_editor.dart 88.4% (122 of 138)
lib/presentation/widgets/entity_management_page.dart 80.8% (172 of 213)
lib/presentation/state/reducers/detail_reducer.dart 100.0% (11 of 11)
lib/presentation/state/reducers/library_reducer.dart 98.0% (98 of 100)
lib/presentation/state/reducers/metadata_reducer.dart 100.0% (15 of 15)
lib/presentation/state/reducers/settings_reducer.dart 100.0% (25 of 25)
lib/presentation/pages/characters/characters_page.dart 46.7% (7 of 15)
lib/presentation/pages/tags/tags_page.dart 100.0% (14 of 14)
lib/presentation/pages/detail/detail_page.dart 83.6% (209 of 250)
lib/presentation/widgets/cover_thumbnail.dart 81.8% (27 of 33)
lib/presentation/widgets/star_rating.dart 100.0% (70 of 70)
lib/core/theme.dart 96.9% (31 of 32)
lib/data/api_client.dart 88.9% (8 of 9)
lib/data/repositories/health_repository.dart 69.2% (18 of 26)
lib/app/app.dart 77.1% (27 of 35)
lib/presentation/pages/settings/settings_page.dart 100.0% (86 of 86)
lib/presentation/layout/main_layout.dart 90.5% (19 of 21)
lib/presentation/pages/circles/circles_page.dart 46.7% (7 of 15)
lib/presentation/pages/library/library_page.dart 77.4% (123 of 159)
lib/presentation/pages/series/series_page.dart 46.7% (7 of 15)
lib/presentation/pages/upload/upload_page.dart 11.1% (1 of 9)
lib/presentation/widgets/smart_filter_bar.dart 52.8% (122 of 231)
lib/data/repositories/doujin_api_repository.dart 18.4% (38 of 206)

Total: 69.6% (1929 of 2773)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/data/models/doujin_models.dart | 82.5% (33 of 40) | | lib/data/models/doujin_models.g.dart | 39.0% (113 of 290) | | lib/data/models/envelope.dart | 81.2% (13 of 16) | | lib/data/models/envelope.g.dart | 50.7% (34 of 67) | | lib/presentation/state/app_state.dart | 62.5% (5 of 8) | | lib/presentation/state/reducers/entity_reducer.dart | 98.5% (66 of 67) | | lib/core/languages.dart | 100.0% (8 of 8) | | lib/domain/entities/entity_model.dart | 100.0% (1 of 1) | | lib/domain/entities/filter_token.dart | 89.5% (77 of 86) | | lib/domain/entities/stored_settings.dart | 100.0% (6 of 6) | | lib/presentation/state/actions/detail_actions.dart | 75.0% (3 of 4) | | lib/presentation/state/actions/entity_actions.dart | 75.0% (12 of 16) | | lib/presentation/state/actions/library_actions.dart | 33.3% (6 of 18) | | lib/presentation/state/actions/metadata_actions.dart | 66.7% (2 of 3) | | lib/presentation/state/actions/settings_actions.dart | 100.0% (5 of 5) | | lib/app/store.dart | 100.0% (10 of 10) | | lib/presentation/pages/people/people_page.dart | 46.7% (7 of 15) | | lib/presentation/middleware/epics.dart | 81.9% (203 of 248) | | lib/presentation/state/reducers.dart | 100.0% (6 of 6) | | lib/data/models/search_query.dart | 50.0% (2 of 4) | | lib/data/models/search_query.g.dart | 34.5% (19 of 55) | | lib/data/repositories/entity_in_use_exception.dart | 33.3% (1 of 3) | | lib/presentation/middleware/entity_ops.dart | 58.0% (40 of 69) | | lib/presentation/widgets/entity_editor.dart | 88.4% (122 of 138) | | lib/presentation/widgets/entity_management_page.dart | 80.8% (172 of 213) | | lib/presentation/state/reducers/detail_reducer.dart | 100.0% (11 of 11) | | lib/presentation/state/reducers/library_reducer.dart | 98.0% (98 of 100) | | lib/presentation/state/reducers/metadata_reducer.dart | 100.0% (15 of 15) | | lib/presentation/state/reducers/settings_reducer.dart | 100.0% (25 of 25) | | lib/presentation/pages/characters/characters_page.dart | 46.7% (7 of 15) | | lib/presentation/pages/tags/tags_page.dart | 100.0% (14 of 14) | | lib/presentation/pages/detail/detail_page.dart | 83.6% (209 of 250) | | lib/presentation/widgets/cover_thumbnail.dart | 81.8% (27 of 33) | | lib/presentation/widgets/star_rating.dart | 100.0% (70 of 70) | | lib/core/theme.dart | 96.9% (31 of 32) | | lib/data/api_client.dart | 88.9% (8 of 9) | | lib/data/repositories/health_repository.dart | 69.2% (18 of 26) | | lib/app/app.dart | 77.1% (27 of 35) | | lib/presentation/pages/settings/settings_page.dart | 100.0% (86 of 86) | | lib/presentation/layout/main_layout.dart | 90.5% (19 of 21) | | lib/presentation/pages/circles/circles_page.dart | 46.7% (7 of 15) | | lib/presentation/pages/library/library_page.dart | 77.4% (123 of 159) | | lib/presentation/pages/series/series_page.dart | 46.7% (7 of 15) | | lib/presentation/pages/upload/upload_page.dart | 11.1% (1 of 9) | | lib/presentation/widgets/smart_filter_bar.dart | 52.8% (122 of 231) | | lib/data/repositories/doujin_api_repository.dart | 18.4% (38 of 206) | **Total: 69.6% (1929 of 2773)**

Summary

Summary
Generated on: 07/01/2026 - 21:26:20
Coverage date: 07/01/2026 - 21:25:54 - 07/01/2026 - 21:26:18
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 279
Files: 117
Line coverage: 87.7% (8270 of 9423)
Covered lines: 8270
Uncovered lines: 1153
Coverable lines: 9423
Total lines: 14308
Branch coverage: 53.2% (552 of 1037)
Covered branches: 552
Total branches: 1037
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 85%
Name Line Branch
DoujinManager.ApplicationCore 85% ****
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.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.AssignCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.AssignPersonCommand 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 0%
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.ListChaptersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCharactersQuery 100%
DoujinManager.ApplicationCore.UseCases.ListCirclesQuery 100%
DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery 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.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.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.UpdatePersonCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 0%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.Infrastructure - 93.6%
Name Line Branch
DoujinManager.Infrastructure 93.6% 68.3%
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.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.ModelBuilderExtensions 50%
DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory 100%
DoujinManager.Infrastructure.Images.SkiaSharpImageInspector 88.2% 70.9%
DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator 94.5% 66.6%
DoujinManager.Infrastructure.Services.BackupService 85.1% 66.6%
DoujinManager.Infrastructure.Services.ChapterService 54.2% 25%
DoujinManager.Infrastructure.Services.CharacterService 61.6% 22.7%
DoujinManager.Infrastructure.Services.CircleService 84.9% 45.4%
DoujinManager.Infrastructure.Services.DoujinService 71.6% 46.4%
DoujinManager.Infrastructure.Services.ImageService 92% 50%
DoujinManager.Infrastructure.Services.PageService 84.9% 65%
DoujinManager.Infrastructure.Services.PersonService 84.9% 50%
DoujinManager.Infrastructure.Services.SearchService 95.4% 95%
DoujinManager.Infrastructure.Services.SeriesService 47.9% 9%
DoujinManager.Infrastructure.Services.TagService 98.8% 90.9%
DoujinManager.Infrastructure.Services.VariantService 50.7% 16.6%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
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.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
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 92.3% 75%
DoujinManager.Infrastructure.UseCases.DeletePersonUseCase 100% 75%
DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase 70% 50%
DoujinManager.Infrastructure.UseCases.DeleteTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase 0%
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.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.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.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.UpdatePersonUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 0%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 91.4% 91.6%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 71.7% 70%
DoujinManager.RestAdapter - 84%
Name Line Branch
DoujinManager.RestAdapter 84% 69.6%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
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.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.LinkCircleDto 0%
DoujinManager.RestAdapter.Dtos.LinkPersonDto 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.UpdatePersonDto 0%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 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 84.3% 60%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.2% 50%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 84.9% 43.7%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 75%
DoujinManager.RestAdapter.Endpoints.VariantEndpoints 72.1% 37.5%
DoujinManager.RestAdapter.Envelopes.CollectionResponse`1 83.3%
DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults 0%
DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions 100%
DoujinManager.RestAdapter.Envelopes.ErrorResponse 100%
DoujinManager.RestAdapter.Envelopes.HypermediaAction 100%
DoujinManager.RestAdapter.Envelopes.HypermediaHelpers 89.4% 81.2%
DoujinManager.RestAdapter.Envelopes.Link 100%
DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter 62.5% 53.8%
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% 100%
DoujinManager.RestAdapter.RestAdapterExtensions 100% 100%
Microsoft.Extensions.Validation.Generated 76.8% 79.3%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 26%
Name Line Branch
DoujinManager.Server 26% 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: | 07/01/2026 - 21:26:20 | | Coverage date: | 07/01/2026 - 21:25:54 - 07/01/2026 - 21:26:18 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 279 | | Files: | 117 | | **Line coverage:** | 87.7% (8270 of 9423) | | Covered lines: | 8270 | | Uncovered lines: | 1153 | | Coverable lines: | 9423 | | Total lines: | 14308 | | **Branch coverage:** | 53.2% (552 of 1037) | | Covered branches: | 552 | | Total branches: | 1037 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 85%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**85%**|****| |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.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.AssignCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.AssignPersonCommand|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|0%|| |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.ListChaptersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCharactersQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListCirclesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListDoujinsQuery|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.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.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.UpdatePersonCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|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 - 93.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**93.6%**|**68.3%**| |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.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.ModelBuilderExtensions|50%|| |DoujinManager.Infrastructure.Data.StronglyTypedIdConverterFactory|100%|| |DoujinManager.Infrastructure.Images.SkiaSharpImageInspector|88.2%|70.9%| |DoujinManager.Infrastructure.Images.SkiaSharpThumbnailGenerator|94.5%|66.6%| |DoujinManager.Infrastructure.Services.BackupService|85.1%|66.6%| |DoujinManager.Infrastructure.Services.ChapterService|54.2%|25%| |DoujinManager.Infrastructure.Services.CharacterService|61.6%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|84.9%|45.4%| |DoujinManager.Infrastructure.Services.DoujinService|71.6%|46.4%| |DoujinManager.Infrastructure.Services.ImageService|92%|50%| |DoujinManager.Infrastructure.Services.PageService|84.9%|65%| |DoujinManager.Infrastructure.Services.PersonService|84.9%|50%| |DoujinManager.Infrastructure.Services.SearchService|95.4%|95%| |DoujinManager.Infrastructure.Services.SeriesService|47.9%|9%| |DoujinManager.Infrastructure.Services.TagService|98.8%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|50.7%|16.6%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|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.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |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|92.3%|75%| |DoujinManager.Infrastructure.UseCases.DeletePersonUseCase|100%|75%| |DoujinManager.Infrastructure.UseCases.DeleteSeriesUseCase|70%|50%| |DoujinManager.Infrastructure.UseCases.DeleteTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.DeleteVariantUseCase|0%|| |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.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.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.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.UpdatePersonUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|0%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|91.4%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|71.7%|70%| </details> <details><summary>DoujinManager.RestAdapter - 84%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**84%**|**69.6%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |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.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.LinkPersonDto|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.UpdatePersonDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|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|84.3%|60%| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.2%|50%| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|84.9%|43.7%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|75%| |DoujinManager.RestAdapter.Endpoints.VariantEndpoints|72.1%|37.5%| |DoujinManager.RestAdapter.Envelopes.CollectionResponse`1|83.3%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeDefaults|0%|| |DoujinManager.RestAdapter.Envelopes.EnvelopeJsonOptions|100%|| |DoujinManager.RestAdapter.Envelopes.ErrorResponse|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaAction|100%|| |DoujinManager.RestAdapter.Envelopes.HypermediaHelpers|89.4%|81.2%| |DoujinManager.RestAdapter.Envelopes.Link|100%|| |DoujinManager.RestAdapter.Envelopes.MediaTypeJsonConverter|62.5%|53.8%| |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%|100%| |DoujinManager.RestAdapter.RestAdapterExtensions|100%|100%| |Microsoft.Extensions.Validation.Generated|76.8%|79.3%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 26%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**26%**|**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! This is a wonderfully ambitious metadata spellbook — generic entity pages, alias-aware tokens, Character/Series parity... I do love seeing knowledge organized so neatly~ ♪

But fufu~ one little paging gremlin is hiding in the autocomplete cache. You wouldn't want the new metadata magic to forget everything after page 1, would you? ♡

Verdict: I can't let this pass~ ♡

These need fixing~

  1. app/lib/data/repositories/doujin_api_repository.dart:83, app/lib/presentation/middleware/epics.dart:196, backend/src/DoujinManager.RestAdapter/Endpoints/DoujinEndpoints.cs:243 — The metadata cache loaders (listTags(), listPeople(), listCircles(), and the new listCharacters() / listSeries()) call paginated endpoints without pageSize and then ignore pagination. PaginationParams defaults to PageSize = 20, so _loadMetadataEpic only caches the first 20 entities of each type. That means alias parsing/autocomplete silently misses page 2+ — exactly where a large metadata library will keep most of its precious knowledge. Please page through CollectionResponse.page.totalPages (preferably pageSize=100 until exhausted) or add a true unpaged metadata-cache endpoint.

💡 Little ideas (non-blocking)~

  1. app/lib/presentation/widgets/entity_editor.dart:133 — The editor shows Saved "..." immediately after dispatching create/update, before the async result succeeds. Cute confidence, but a bit too eager~ Consider emitting the success toast from EntityCreatedAction / EntityUpdatedAction instead, so failures don't briefly claim success before the error snackbar arrives.
  2. app/lib/domain/entities/filter_token.dart:232 — Negative-prefix suggestions now mark people/circles/characters/series/languages as negative: true, but _applyTokens only treats FilterType.excludeTag as an exclusion. This was partly pre-existing, but the new entity types inherit the same confusing red-chip-but-positive-filter behavior. Either restrict - suggestions to tags, or teach the query model real negative filters later.

Automated review by Jibril · 2026-07-01
CI/CD: passed/current for head 3c75293 (Flutter coverage #674 and backend coverage #675 posted after the head commit) · Local checks: full local checks skipped per CI policy; optional focused backend metadata/search test command exited 0, optional focused Flutter run was blocked by runner ENOSPC

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! This is a wonderfully ambitious metadata spellbook — generic entity pages, alias-aware tokens, Character/Series parity... I do love seeing knowledge organized so neatly~ ♪ But fufu~ one little paging gremlin is hiding in the autocomplete cache. You wouldn't want the new metadata magic to forget everything after page 1, would you? ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing~ 1. **app/lib/data/repositories/doujin_api_repository.dart:83, app/lib/presentation/middleware/epics.dart:196, backend/src/DoujinManager.RestAdapter/Endpoints/DoujinEndpoints.cs:243** — The metadata cache loaders (`listTags()`, `listPeople()`, `listCircles()`, and the new `listCharacters()` / `listSeries()`) call paginated endpoints without `pageSize` and then ignore pagination. `PaginationParams` defaults to `PageSize = 20`, so `_loadMetadataEpic` only caches the first 20 entities of each type. That means alias parsing/autocomplete silently misses page 2+ — exactly where a large metadata library will keep most of its precious knowledge. Please page through `CollectionResponse.page.totalPages` (preferably `pageSize=100` until exhausted) or add a true unpaged metadata-cache endpoint. #### 💡 Little ideas (non-blocking)~ 1. **app/lib/presentation/widgets/entity_editor.dart:133** — The editor shows `Saved "..."` immediately after dispatching create/update, before the async result succeeds. Cute confidence, but a bit too eager~ Consider emitting the success toast from `EntityCreatedAction` / `EntityUpdatedAction` instead, so failures don't briefly claim success before the error snackbar arrives. 2. **app/lib/domain/entities/filter_token.dart:232** — Negative-prefix suggestions now mark people/circles/characters/series/languages as `negative: true`, but `_applyTokens` only treats `FilterType.excludeTag` as an exclusion. This was partly pre-existing, but the new entity types inherit the same confusing red-chip-but-positive-filter behavior. Either restrict `-` suggestions to tags, or teach the query model real negative filters later. --- *Automated review by Jibril · 2026-07-01* *CI/CD: passed/current for head `3c75293` (Flutter coverage #674 and backend coverage #675 posted after the head commit) · Local checks: full local checks skipped per CI policy; optional focused backend metadata/search test command exited 0, optional focused Flutter run was blocked by runner ENOSPC*
fix(app): page through all metadata for autocomplete; restrict '-' to tags
All checks were successful
CI / build (pull_request) Successful in 19s
Flutter CI / analyze-and-test (pull_request) Successful in 1m6s
CI / test (pull_request) Successful in 49s
789172f2cf
Address PR review:

- BLOCKING: the metadata cache loaders (listTags/listPeople/listCircles/
  listCharacters/listSeries) hit paginated endpoints without pageSize and
  ignored pagination, so only the first 20 entities of each type were cached —
  alias parsing/autocomplete silently missed page 2+. They now walk every page
  (pageSize 100) via a shared `_listAll` helper until totalPages is exhausted.

- Restrict exclude (`-`) autocomplete suggestions to tags, since only tags
  support exclusion in the query model; previously people/circles/characters/
  series/languages could appear as red "negative" chips that actually filtered
  positively.

Tests: repository paging test (walks pages, asserts pageSize=100) and an
exclude-suggestion-restriction test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Owner

Blocking — fixed

Metadata cache truncated to page 1. The five flat loaders (listTags/listPeople/listCircles/listCharacters/listSeries) called the paginated endpoints without pageSize and ignored pagination, so only the first 20 of each type reached the autocomplete cache — page 2+ aliases/names were invisible. They now walk every page at pageSize=100 until page.totalPages is exhausted, via a shared _listAll helper. Added a repository test that serves two pages through a fake adapter and asserts both pages are fetched and concatenated with pageSize=100.

💡 Non-blocking #2 — fixed

Confusing red-but-positive - chips. Since only tags support exclusion in the query model, a leading - now yields tag-only suggestions (languages/people/circles/characters/series are suppressed for -, since they'd render as negative chips that actually filter positively). Added a test locking this.

💡 Non-blocking #1 — left as-is (deliberately), flagging for you

The eager Saved "…" toast firing on dispatch rather than on success is a valid critique — but bjoern explicitly asked earlier for "a toast independent of whether it works or not." So I kept it to honor that request; a failure still queues the error snackbar after. If you'd now prefer the reviewer's approach (emit the success toast from EntityCreatedAction/EntityUpdatedAction, so it only claims success on actual success), say the word and I'll switch it — it's a small change.

flutter analyze clean; 222 client tests pass.

⛔ Blocking — fixed Metadata cache truncated to page 1. The five flat loaders (listTags/listPeople/listCircles/listCharacters/listSeries) called the paginated endpoints without pageSize and ignored pagination, so only the first 20 of each type reached the autocomplete cache — page 2+ aliases/names were invisible. They now walk every page at pageSize=100 until page.totalPages is exhausted, via a shared _listAll helper. Added a repository test that serves two pages through a fake adapter and asserts both pages are fetched and concatenated with pageSize=100. 💡 Non-blocking #2 — fixed Confusing red-but-positive - chips. Since only tags support exclusion in the query model, a leading - now yields tag-only suggestions (languages/people/circles/characters/series are suppressed for -, since they'd render as negative chips that actually filter positively). Added a test locking this. 💡 Non-blocking #1 — left as-is (deliberately), flagging for you The eager Saved "…" toast firing on dispatch rather than on success is a valid critique — but bjoern explicitly asked earlier for "a toast independent of whether it works or not." So I kept it to honor that request; a failure still queues the error snackbar after. If you'd now prefer the reviewer's approach (emit the success toast from EntityCreatedAction/EntityUpdatedAction, so it only claims success on actual success), say the word and I'll switch it — it's a small change. flutter analyze clean; 222 client tests pass.
bjoern merged commit f2330a1f13 into main 2026-07-01 23:28:09 +02:00
bjoern deleted branch feat/entity-management-ui 2026-07-01 23:28:09 +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!42
No description provided.