feat: look at what the detector found #192

Merged
bjoern merged 4 commits from feat/reader-region-overlay into main 2026-09-21 02:09:54 +02:00
Member

What's in

The reader's detected-region overlay — story 1.5 of
docs/TRANSLATION_PLAN.md, and the last code of
phase 1. A toggle in the reader's top bar outlines what a detector found on
the page, colour-coded by kind, off by default.

The state is the answer, not the list — on this side too. notAnalysed
and analysed both arrive with no regions, so the overlay captions the page
rather than leaving an empty one ambiguous: Not analysed yet against
Nothing found on this page, and a third for the page the detector could not
read. A page that does have outlines is not captioned — they are on the page,
and a caption would cover the thing the overlay exists to show.

The outlines land where the bitmap did, by construction. The reader draws
pages with BoxFit.contain, which letterboxes, so a region at x = 0.5 is
halfway across the page, not the window. containedImageRect asks Flutter's
own applyBoxFit the same question the Image asked, rather than re-deriving
the maths where it could drift.

Everything the overlay decides is a pure function — containedImageRect,
regionPaths, regionColour, regionOverlayMessage — so what it draws and
what it says are asserted in tests instead of eyeballed in a screenshot.

The toggle is a client preference (ADR 0023), persisted by load-modify-write.
The epic loads what is on disk, changes the one value and saves it back.
Rebuilding a StoredSettings from the settings slice instead would mean
listing every field at a second site, and a field forgotten there silently
clears the server token or the API key. The Settings form has no control for
this one, so the save epic carries the current value through from state —
without that, saving any setting would quietly turn the overlay off on disk.
That is pinned by its own test.

Requests are made only for the pages on screen, and only once per page: a
variant runs to hundreds of pages, and an entry that is loading, loaded or
failed
all count as asked, so one failing page cannot become a request per
rebuild.

Also here: scripts/verify.sh now checks that vendor/openrouter_dart is at
the commit main pins, not merely that it is non-empty. A submodule left on
another commit fails flutter analyze on a type the app uses everywhere,
which reads as broken app code rather than as a stale checkout — and pub get
against it rewrites app/pubspec.lock on the way. It cost an hour to diagnose
once; the guard names the two commands that fix it.

Verification

scripts/verify.sh green (== verify OK (all) ==), run again after merging
main — backend 1,503 passed / 5 skipped, app 1,522 tests.

Layer Exercised
UI reader_page_test.dart — off by default and silent by default, switching it on asks about the page already on screen, N regions draw N paths, switching it off draws none, a page already asked about is not asked about again
Widget region_overlay_test.dart — placement, path count, the repaint predicate, and that a tap goes straight through to the page underneath
Words region_overlay_test.dart — the three empty answers (nothing looked / looked and found nothing / could not be read) and a failed request all read differently
Redux reader_state_test.dart (in flight, arrived, refused; closing forgets), region_overlay_epics_test.dart through createStore (so registration is pinned too)
Persistence secure_storage_settings_test.dart, stored_settings_test.dart — the round trip, the pre-upgrade store, and that changing the overlay keeps every other setting
Wire server_api_repository_test.dart, server_models_test.dart — the route, the envelope, unknown states and unknown kinds

Mutation-probed: 28 mutations, all killed. Four survived the first pass and
each one was a real finding:

  • Two were dead code — a box-size guard and an isLoading arm, both of
    which every input already reached another way. Deleted rather than pinned,
    and the deletions re-probed to confirm the remaining guards are live.
  • One was a fixture too symmetric to notice a bug: the outline in the
    placement test survived having its x and y transposed. Replaced with a shape
    asymmetric in both axes, every edge of its bounds asserted.
  • One was a test that never reached the line it named: "asked about once"
    toggled the overlay on twice, which does not run the request hook a second
    time. It now switches off and on again, which does.

The merge with #190

#190 landed on the same settings surface while this was in flight. Every
conflict was "both added" and was resolved by keeping both — but one thing
that was not a conflict would have been silent damage:
StoredSettings.withRegionOverlay carries every field explicitly, so main's
two new ones (agentRouting, agentReasoningEffort) had to be added to it,
or pressing the overlay toggle would have reset the assistant's routing and
reasoning effort. The test guarding that method enumerates the fields too, so
it needed the same edit; without it the test would have passed over the bug it
exists to catch. docs/BACKLOG.md's phase-1 entry was refreshed for the same
reason — it still said the reader overlay was what remained.

Follow-ups

One new entry in docs/BACKLOG.md: the verify.sh
submodule-pin check has no test — the condition was checked by hand, there is
no shell harness in this repo, and one guard does not justify inventing one.

Phase 1 has no code left. What remains is not a PR: point a deployment at
a model, switch the sweep on, let it finish, and page through the library with
the overlay on. Nobody has yet looked at what this detector actually draws on
this library, and everything phase 2 would build rests on that opinion.

🤖 Generated with Claude Code

## What's in The reader's detected-region overlay — story 1.5 of [docs/TRANSLATION_PLAN.md](docs/TRANSLATION_PLAN.md), and the last code of phase 1. A toggle in the reader's top bar outlines what a detector found on the page, colour-coded by kind, off by default. **The state is the answer, not the list — on this side too.** `notAnalysed` and `analysed` both arrive with no regions, so the overlay captions the page rather than leaving an empty one ambiguous: *Not analysed yet* against *Nothing found on this page*, and a third for the page the detector could not read. A page that does have outlines is not captioned — they are on the page, and a caption would cover the thing the overlay exists to show. **The outlines land where the bitmap did, by construction.** The reader draws pages with `BoxFit.contain`, which letterboxes, so a region at x = 0.5 is halfway across the *page*, not the window. `containedImageRect` asks Flutter's own `applyBoxFit` the same question the `Image` asked, rather than re-deriving the maths where it could drift. **Everything the overlay decides is a pure function** — `containedImageRect`, `regionPaths`, `regionColour`, `regionOverlayMessage` — so what it draws and what it says are asserted in tests instead of eyeballed in a screenshot. **The toggle is a client preference (ADR 0023), persisted by load-modify-write.** The epic loads what is on disk, changes the one value and saves it back. Rebuilding a `StoredSettings` from the settings slice instead would mean listing every field at a second site, and a field forgotten there silently clears the server token or the API key. The Settings form has no control for this one, so the save epic carries the current value through from state — without that, saving *any* setting would quietly turn the overlay off on disk. That is pinned by its own test. Requests are made only for the pages on screen, and only once per page: a variant runs to hundreds of pages, and an entry that is loading, loaded **or failed** all count as asked, so one failing page cannot become a request per rebuild. Also here: `scripts/verify.sh` now checks that `vendor/openrouter_dart` is at the commit `main` pins, not merely that it is non-empty. A submodule left on another commit fails `flutter analyze` on a type the app uses everywhere, which reads as broken app code rather than as a stale checkout — and `pub get` against it rewrites `app/pubspec.lock` on the way. It cost an hour to diagnose once; the guard names the two commands that fix it. ## Verification `scripts/verify.sh` green (`== verify OK (all) ==`), run again after merging `main` — backend 1,503 passed / 5 skipped, app **1,522 tests**. | Layer | Exercised | |---|---| | UI | `reader_page_test.dart` — off by default and silent by default, switching it on asks about the page already on screen, N regions draw N paths, switching it off draws none, a page already asked about is not asked about again | | Widget | `region_overlay_test.dart` — placement, path count, the repaint predicate, and that a tap goes straight through to the page underneath | | Words | `region_overlay_test.dart` — the three empty answers (nothing looked / looked and found nothing / could not be read) and a failed request all read differently | | Redux | `reader_state_test.dart` (in flight, arrived, refused; closing forgets), `region_overlay_epics_test.dart` through `createStore` (so registration is pinned too) | | Persistence | `secure_storage_settings_test.dart`, `stored_settings_test.dart` — the round trip, the pre-upgrade store, and that changing the overlay keeps every other setting | | Wire | `server_api_repository_test.dart`, `server_models_test.dart` — the route, the envelope, unknown states and unknown kinds | **Mutation-probed: 28 mutations, all killed.** Four survived the first pass and each one was a real finding: - Two were **dead code** — a box-size guard and an `isLoading` arm, both of which every input already reached another way. Deleted rather than pinned, and the deletions re-probed to confirm the remaining guards are live. - One was a **fixture too symmetric to notice a bug**: the outline in the placement test survived having its x and y transposed. Replaced with a shape asymmetric in both axes, every edge of its bounds asserted. - One was a **test that never reached the line it named**: "asked about once" toggled the overlay on twice, which does not run the request hook a second time. It now switches off and on again, which does. ## The merge with #190 #190 landed on the same settings surface while this was in flight. Every conflict was "both added" and was resolved by keeping both — but one thing that was **not** a conflict would have been silent damage: `StoredSettings.withRegionOverlay` carries every field explicitly, so main's two new ones (`agentRouting`, `agentReasoningEffort`) had to be added to it, or pressing the overlay toggle would have reset the assistant's routing and reasoning effort. The test guarding that method enumerates the fields too, so it needed the same edit; without it the test would have passed over the bug it exists to catch. `docs/BACKLOG.md`'s phase-1 entry was refreshed for the same reason — it still said the reader overlay was what remained. ## Follow-ups One new entry in [docs/BACKLOG.md](docs/BACKLOG.md): the `verify.sh` submodule-pin check has no test — the condition was checked by hand, there is no shell harness in this repo, and one guard does not justify inventing one. **Phase 1 has no code left.** What remains is not a PR: point a deployment at a model, switch the sweep on, let it finish, and page through the library with the overlay on. Nobody has yet looked at what this detector actually draws on this library, and everything phase 2 would build rests on that opinion. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: look at what the detector found
Some checks failed
CI / build (pull_request) Successful in 32s
CI / test (pull_request) Has been cancelled
Flutter CI / analyze-and-test (pull_request) Has been cancelled
05da2d146f
The reader's region overlay, story 1.5 — the last code of phase 1.

The state is the answer on this side too. "Nothing has looked at this
page" and "something looked and there is nothing on it" both arrive as
an empty list, so the overlay captions the page rather than leaving the
two indistinguishable; a page that does have outlines is not captioned,
because they are on the page.

Outlines land where the bitmap did by construction: containedImageRect
asks Flutter's own applyBoxFit the question the Image asked, rather than
re-deriving the letterbox where it could drift. Everything the overlay
decides is a pure function, so what it draws and what it says are
asserted rather than eyeballed.

The toggle is a client preference (ADR 0023), persisted by loading what
is on disk, changing the one value and writing it back — and the save
epic carries it through from state, because the Settings form has no
control for it and would otherwise clear it.

Also: verify.sh now checks the submodule is at the commit main pins,
not merely that it is non-empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge main into feat/reader-region-overlay
All checks were successful
CI / build (pull_request) Successful in 28s
Flutter CI / analyze-and-test (pull_request) Successful in 9m50s
CI / test (pull_request) Successful in 2m36s
2a831e00e7
#190 landed on the same settings surface. Every conflict was "both added",
resolved by keeping both — with one thing that is not a conflict and would
have been silent: StoredSettings.withRegionOverlay carries every field
explicitly, so main's two new ones had to be added to it or toggling the
overlay would have reset the assistant's routing and reasoning effort. The
test that guards that method enumerates the fields too, so it is the same
edit twice and neither half is optional.

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

Flutter Coverage

File Line coverage
lib/domain/entities/agent_routing.dart 100.0% (8 of 8)
lib/domain/entities/reading_mode.dart 100.0% (3 of 3)
lib/domain/entities/stored_settings.dart 100.0% (21 of 21)
lib/app/store.dart 95.8% (23 of 24)
lib/data/models/doujin_models.dart 100.0% (55 of 55)
lib/data/models/doujin_models.g.dart 68.4% (299 of 437)
lib/domain/repositories/repositories.dart 33.3% (1 of 3)
lib/presentation/state/app_state.dart 84.4% (38 of 45)
lib/agent/agent_log.dart 100.0% (94 of 94)
lib/agent/agent_service.dart 92.9% (514 of 553)
lib/agent/approval_gate.dart 100.0% (21 of 21)
lib/agent/assistant_context.dart 70.7% (41 of 58)
lib/agent/browser_budget.dart 80.0% (4 of 5)
lib/agent/caching_describer.dart 100.0% (13 of 13)
lib/agent/cancellable_tool.dart 100.0% (13 of 13)
lib/agent/memory_store.dart 93.8% (15 of 16)
lib/agent/skills/skill_registry.dart 98.2% (54 of 55)
lib/agent/system_prompt.dart 100.0% (73 of 73)
lib/agent/tools/budgeted_browser_tool.dart 84.2% (16 of 19)
lib/agent/tools/doujin_write_tool.dart 95.0% (921 of 969)
lib/agent/tools/entity_write_tool.dart 78.5% (175 of 223)
lib/agent/tools/fetch_page_tool.dart 89.3% (67 of 75)
lib/agent/tools/get_doujin_tool.dart 92.3% (60 of 65)
lib/agent/tools/list_entities_tool.dart 76.1% (54 of 71)
lib/agent/tools/navigate_tool.dart 95.1% (39 of 41)
lib/agent/tools/read_skill_tool.dart 82.4% (14 of 17)
lib/agent/tools/reflection_tools.dart 78.0% (32 of 41)
lib/agent/tools/search_doujins_tool.dart 100.0% (101 of 101)
lib/agent/tools/view_images_tool.dart 95.0% (38 of 40)
lib/core/model_capabilities.dart 100.0% (23 of 23)
lib/domain/entities/assistant_entry.dart 20.0% (1 of 5)
lib/presentation/state/actions/assistant_actions.dart 58.3% (7 of 12)
lib/core/app_config.dart 70.0% (7 of 10)
lib/core/reading_order.dart 100.0% (22 of 22)
lib/domain/entities/entity_model.dart 100.0% (2 of 2)
lib/data/repositories/entity_in_use_exception.dart 33.3% (1 of 3)
lib/core/title_display.dart 93.8% (15 of 16)
lib/data/models/search_query.dart 50.0% (2 of 4)
lib/data/models/search_query.g.dart 33.8% (26 of 77)
lib/core/constants.dart 40.0% (4 of 10)
lib/data/offline/offline_download_service.dart 93.0% (66 of 71)
lib/data/reading_progress_store.dart 92.3% (36 of 39)
lib/domain/entities/reading_progress.dart 100.0% (26 of 26)
lib/presentation/middleware/assistant_epics.dart 85.3% (87 of 102)
lib/presentation/middleware/backup_save_dialog.dart 25.0% (1 of 4)
lib/presentation/middleware/epics.dart 93.0% (478 of 514)
lib/presentation/middleware/offline_epics.dart 98.4% (60 of 61)
lib/presentation/state/reducers.dart 100.0% (14 of 14)
lib/core/chunking.dart 100.0% (12 of 12)
lib/core/code_point_compare.dart 100.0% (8 of 8)
lib/core/languages.dart 100.0% (14 of 14)
lib/core/variant_display.dart 100.0% (22 of 22)
lib/data/models/envelope.dart 81.2% (13 of 16)
lib/data/models/envelope.g.dart 56.7% (38 of 67)
lib/data/models/server_models.dart 98.5% (65 of 66)
lib/data/models/server_models.g.dart 55.9% (137 of 245)
lib/domain/entities/offline_manifest.dart 100.0% (78 of 78)
lib/data/repositories/backup_exception.dart 100.0% (3 of 3)
lib/data/repositories/integrity_exception.dart 100.0% (4 of 4)
lib/data/repositories/upload_exception.dart 33.3% (1 of 3)
lib/domain/entities/filter_token.dart 92.2% (107 of 116)
lib/presentation/middleware/duplicates_epics.dart 100.0% (50 of 50)
lib/presentation/middleware/editor_epics.dart 58.9% (175 of 297)
lib/presentation/middleware/entity_ops.dart 64.4% (47 of 73)
lib/presentation/middleware/server_epics.dart 100.0% (137 of 137)
lib/presentation/middleware/upload_epics.dart 98.1% (104 of 106)
lib/presentation/state/actions/offline_actions.dart 91.7% (11 of 12)
lib/presentation/state/actions/detail_actions.dart 85.7% (6 of 7)
lib/presentation/state/actions/duplicates_actions.dart 88.9% (8 of 9)
lib/presentation/state/actions/editor_actions.dart 57.9% (22 of 38)
lib/presentation/state/actions/entity_actions.dart 66.7% (12 of 18)
lib/presentation/state/actions/library_actions.dart 54.5% (12 of 22)
lib/presentation/state/actions/metadata_actions.dart 100.0% (5 of 5)
lib/presentation/state/actions/reader_actions.dart 85.7% (6 of 7)
lib/presentation/state/actions/reading_progress_actions.dart 100.0% (2 of 2)
lib/presentation/state/actions/server_actions.dart 81.1% (30 of 37)
lib/presentation/state/actions/settings_actions.dart 88.9% (8 of 9)
lib/presentation/state/actions/upload_actions.dart 90.9% (10 of 11)
lib/presentation/state/reducers/assistant_reducer.dart 95.2% (60 of 63)
lib/presentation/state/reducers/detail_reducer.dart 100.0% (28 of 28)
lib/presentation/state/reducers/duplicates_reducer.dart 100.0% (54 of 54)
lib/presentation/state/reducers/editor_reducer.dart 97.2% (105 of 108)
lib/presentation/state/reducers/entity_reducer.dart 98.9% (94 of 95)
lib/presentation/state/reducers/library_reducer.dart 100.0% (140 of 140)
lib/presentation/state/reducers/metadata_reducer.dart 90.5% (38 of 42)
lib/presentation/state/reducers/offline_reducer.dart 100.0% (61 of 61)
lib/presentation/state/reducers/reader_reducer.dart 100.0% (36 of 36)
lib/presentation/state/reducers/reading_progress_reducer.dart 100.0% (20 of 20)
lib/presentation/state/reducers/server_reducer.dart 99.5% (207 of 208)
lib/presentation/state/reducers/settings_reducer.dart 100.0% (77 of 77)
lib/presentation/state/reducers/upload_reducer.dart 100.0% (45 of 45)
lib/presentation/pages/people/people_page.dart 66.7% (12 of 18)
lib/presentation/pages/tags/tags_page.dart 100.0% (17 of 17)
lib/presentation/widgets/entity_merge_dialog.dart 100.0% (70 of 70)
lib/presentation/widgets/entity_editor.dart 92.5% (135 of 146)
lib/presentation/widgets/entity_management_page.dart 88.5% (238 of 269)
lib/presentation/pages/editor/page_grid.dart 94.3% (314 of 333)
lib/presentation/pages/editor/variants_tab.dart 55.3% (52 of 94)
lib/core/natural_sort.dart 100.0% (27 of 27)
lib/core/url_utils.dart 100.0% (4 of 4)
lib/presentation/pages/editor/chapter_panel.dart 100.0% (64 of 64)
lib/presentation/pages/editor/chapter_reorder_dialog.dart 100.0% (51 of 51)
lib/presentation/pages/editor/chapter_title_dialog.dart 100.0% (27 of 27)
lib/presentation/widgets/cover_thumbnail.dart 83.3% (30 of 36)
lib/presentation/pages/editor/upload_panel.dart 38.6% (61 of 158)
lib/presentation/pages/editor/variant_dialog.dart 0.0% (0 of 61)
lib/presentation/widgets/language_dropdown.dart 84.6% (11 of 13)
lib/presentation/pages/reader/reader_overlay.dart 96.9% (63 of 65)
lib/presentation/pages/reader/reader_page.dart 95.0% (497 of 523)
lib/presentation/pages/reader/region_overlay.dart 100.0% (61 of 61)
lib/presentation/pages/reader/reader_sequence.dart 100.0% (31 of 31)
lib/presentation/widgets/star_rating.dart 100.0% (72 of 72)
lib/app/di.dart 48.3% (14 of 29)
lib/presentation/assistant/assistant_panel.dart 91.7% (88 of 96)
lib/presentation/layout/main_layout.dart 86.3% (44 of 51)
lib/data/api_client.dart 93.6% (73 of 78)
lib/data/repositories/doujin_api_repository.dart 46.5% (228 of 490)
lib/data/repositories/health_repository.dart 87.0% (47 of 54)
lib/data/secure_storage.dart 97.8% (45 of 46)
lib/core/theme.dart 96.9% (31 of 32)
lib/presentation/assistant/approval_card.dart 95.0% (38 of 40)
lib/presentation/assistant/assistant_markdown.dart 100.0% (4 of 4)
lib/presentation/assistant/chat_entries.dart 87.5% (35 of 40)
lib/presentation/pages/editor/editor_page.dart 77.9% (60 of 77)
lib/presentation/pages/editor/editor_pane.dart 92.6% (63 of 68)
lib/core/person_display.dart 100.0% (7 of 7)
lib/presentation/pages/editor/association_picker.dart 95.5% (106 of 111)
lib/presentation/pages/editor/associations_tab.dart 72.2% (83 of 115)
lib/presentation/pages/editor/doujin_list_pane.dart 67.7% (44 of 65)
lib/presentation/pages/editor/edit_title_dialog.dart 91.7% (55 of 60)
lib/presentation/pages/editor/new_doujin_dialog.dart 66.7% (30 of 45)
lib/presentation/pages/editor/metadata_tab.dart 70.5% (93 of 132)
lib/presentation/pages/characters/characters_page.dart 57.9% (11 of 19)
lib/presentation/pages/detail/detail_page.dart 88.9% (192 of 216)
lib/presentation/pages/detail/variant_tabs_panel.dart 97.2% (242 of 249)
lib/data/offline/filesystem_offline_store.dart 94.1% (144 of 153)
lib/core/offline_paths.dart 100.0% (3 of 3)
lib/presentation/pages/offline/offline_page.dart 98.2% (164 of 167)
lib/app/app.dart 67.1% (47 of 70)
lib/presentation/pages/settings/settings_page.dart 99.2% (385 of 388)
lib/presentation/pages/circles/circles_page.dart 52.6% (10 of 19)
lib/presentation/pages/duplicates/duplicates_page.dart 100.0% (265 of 265)
lib/presentation/pages/library/library_page.dart 93.3% (224 of 240)
lib/presentation/pages/series/series_page.dart 50.0% (9 of 18)
lib/presentation/widgets/smart_filter_bar.dart 84.1% (207 of 246)
lib/presentation/pages/settings/server_sections.dart 99.4% (673 of 677)
lib/presentation/pages/settings/settings_layout.dart 100.0% (78 of 78)
lib/presentation/widgets/model_combo_field.dart 66.2% (98 of 148)
lib/app/skill_assets.dart 92.9% (13 of 14)

Total: 86.5% (11587 of 13395)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/domain/entities/agent_routing.dart | 100.0% (8 of 8) | | lib/domain/entities/reading_mode.dart | 100.0% (3 of 3) | | lib/domain/entities/stored_settings.dart | 100.0% (21 of 21) | | lib/app/store.dart | 95.8% (23 of 24) | | lib/data/models/doujin_models.dart | 100.0% (55 of 55) | | lib/data/models/doujin_models.g.dart | 68.4% (299 of 437) | | lib/domain/repositories/repositories.dart | 33.3% (1 of 3) | | lib/presentation/state/app_state.dart | 84.4% (38 of 45) | | lib/agent/agent_log.dart | 100.0% (94 of 94) | | lib/agent/agent_service.dart | 92.9% (514 of 553) | | lib/agent/approval_gate.dart | 100.0% (21 of 21) | | lib/agent/assistant_context.dart | 70.7% (41 of 58) | | lib/agent/browser_budget.dart | 80.0% (4 of 5) | | lib/agent/caching_describer.dart | 100.0% (13 of 13) | | lib/agent/cancellable_tool.dart | 100.0% (13 of 13) | | lib/agent/memory_store.dart | 93.8% (15 of 16) | | lib/agent/skills/skill_registry.dart | 98.2% (54 of 55) | | lib/agent/system_prompt.dart | 100.0% (73 of 73) | | lib/agent/tools/budgeted_browser_tool.dart | 84.2% (16 of 19) | | lib/agent/tools/doujin_write_tool.dart | 95.0% (921 of 969) | | lib/agent/tools/entity_write_tool.dart | 78.5% (175 of 223) | | lib/agent/tools/fetch_page_tool.dart | 89.3% (67 of 75) | | lib/agent/tools/get_doujin_tool.dart | 92.3% (60 of 65) | | lib/agent/tools/list_entities_tool.dart | 76.1% (54 of 71) | | lib/agent/tools/navigate_tool.dart | 95.1% (39 of 41) | | lib/agent/tools/read_skill_tool.dart | 82.4% (14 of 17) | | lib/agent/tools/reflection_tools.dart | 78.0% (32 of 41) | | lib/agent/tools/search_doujins_tool.dart | 100.0% (101 of 101) | | lib/agent/tools/view_images_tool.dart | 95.0% (38 of 40) | | lib/core/model_capabilities.dart | 100.0% (23 of 23) | | lib/domain/entities/assistant_entry.dart | 20.0% (1 of 5) | | lib/presentation/state/actions/assistant_actions.dart | 58.3% (7 of 12) | | lib/core/app_config.dart | 70.0% (7 of 10) | | lib/core/reading_order.dart | 100.0% (22 of 22) | | lib/domain/entities/entity_model.dart | 100.0% (2 of 2) | | lib/data/repositories/entity_in_use_exception.dart | 33.3% (1 of 3) | | lib/core/title_display.dart | 93.8% (15 of 16) | | lib/data/models/search_query.dart | 50.0% (2 of 4) | | lib/data/models/search_query.g.dart | 33.8% (26 of 77) | | lib/core/constants.dart | 40.0% (4 of 10) | | lib/data/offline/offline_download_service.dart | 93.0% (66 of 71) | | lib/data/reading_progress_store.dart | 92.3% (36 of 39) | | lib/domain/entities/reading_progress.dart | 100.0% (26 of 26) | | lib/presentation/middleware/assistant_epics.dart | 85.3% (87 of 102) | | lib/presentation/middleware/backup_save_dialog.dart | 25.0% (1 of 4) | | lib/presentation/middleware/epics.dart | 93.0% (478 of 514) | | lib/presentation/middleware/offline_epics.dart | 98.4% (60 of 61) | | lib/presentation/state/reducers.dart | 100.0% (14 of 14) | | lib/core/chunking.dart | 100.0% (12 of 12) | | lib/core/code_point_compare.dart | 100.0% (8 of 8) | | lib/core/languages.dart | 100.0% (14 of 14) | | lib/core/variant_display.dart | 100.0% (22 of 22) | | lib/data/models/envelope.dart | 81.2% (13 of 16) | | lib/data/models/envelope.g.dart | 56.7% (38 of 67) | | lib/data/models/server_models.dart | 98.5% (65 of 66) | | lib/data/models/server_models.g.dart | 55.9% (137 of 245) | | lib/domain/entities/offline_manifest.dart | 100.0% (78 of 78) | | lib/data/repositories/backup_exception.dart | 100.0% (3 of 3) | | lib/data/repositories/integrity_exception.dart | 100.0% (4 of 4) | | lib/data/repositories/upload_exception.dart | 33.3% (1 of 3) | | lib/domain/entities/filter_token.dart | 92.2% (107 of 116) | | lib/presentation/middleware/duplicates_epics.dart | 100.0% (50 of 50) | | lib/presentation/middleware/editor_epics.dart | 58.9% (175 of 297) | | lib/presentation/middleware/entity_ops.dart | 64.4% (47 of 73) | | lib/presentation/middleware/server_epics.dart | 100.0% (137 of 137) | | lib/presentation/middleware/upload_epics.dart | 98.1% (104 of 106) | | lib/presentation/state/actions/offline_actions.dart | 91.7% (11 of 12) | | lib/presentation/state/actions/detail_actions.dart | 85.7% (6 of 7) | | lib/presentation/state/actions/duplicates_actions.dart | 88.9% (8 of 9) | | lib/presentation/state/actions/editor_actions.dart | 57.9% (22 of 38) | | lib/presentation/state/actions/entity_actions.dart | 66.7% (12 of 18) | | lib/presentation/state/actions/library_actions.dart | 54.5% (12 of 22) | | lib/presentation/state/actions/metadata_actions.dart | 100.0% (5 of 5) | | lib/presentation/state/actions/reader_actions.dart | 85.7% (6 of 7) | | lib/presentation/state/actions/reading_progress_actions.dart | 100.0% (2 of 2) | | lib/presentation/state/actions/server_actions.dart | 81.1% (30 of 37) | | lib/presentation/state/actions/settings_actions.dart | 88.9% (8 of 9) | | lib/presentation/state/actions/upload_actions.dart | 90.9% (10 of 11) | | lib/presentation/state/reducers/assistant_reducer.dart | 95.2% (60 of 63) | | lib/presentation/state/reducers/detail_reducer.dart | 100.0% (28 of 28) | | lib/presentation/state/reducers/duplicates_reducer.dart | 100.0% (54 of 54) | | lib/presentation/state/reducers/editor_reducer.dart | 97.2% (105 of 108) | | lib/presentation/state/reducers/entity_reducer.dart | 98.9% (94 of 95) | | lib/presentation/state/reducers/library_reducer.dart | 100.0% (140 of 140) | | lib/presentation/state/reducers/metadata_reducer.dart | 90.5% (38 of 42) | | lib/presentation/state/reducers/offline_reducer.dart | 100.0% (61 of 61) | | lib/presentation/state/reducers/reader_reducer.dart | 100.0% (36 of 36) | | lib/presentation/state/reducers/reading_progress_reducer.dart | 100.0% (20 of 20) | | lib/presentation/state/reducers/server_reducer.dart | 99.5% (207 of 208) | | lib/presentation/state/reducers/settings_reducer.dart | 100.0% (77 of 77) | | lib/presentation/state/reducers/upload_reducer.dart | 100.0% (45 of 45) | | lib/presentation/pages/people/people_page.dart | 66.7% (12 of 18) | | lib/presentation/pages/tags/tags_page.dart | 100.0% (17 of 17) | | lib/presentation/widgets/entity_merge_dialog.dart | 100.0% (70 of 70) | | lib/presentation/widgets/entity_editor.dart | 92.5% (135 of 146) | | lib/presentation/widgets/entity_management_page.dart | 88.5% (238 of 269) | | lib/presentation/pages/editor/page_grid.dart | 94.3% (314 of 333) | | lib/presentation/pages/editor/variants_tab.dart | 55.3% (52 of 94) | | lib/core/natural_sort.dart | 100.0% (27 of 27) | | lib/core/url_utils.dart | 100.0% (4 of 4) | | lib/presentation/pages/editor/chapter_panel.dart | 100.0% (64 of 64) | | lib/presentation/pages/editor/chapter_reorder_dialog.dart | 100.0% (51 of 51) | | lib/presentation/pages/editor/chapter_title_dialog.dart | 100.0% (27 of 27) | | lib/presentation/widgets/cover_thumbnail.dart | 83.3% (30 of 36) | | lib/presentation/pages/editor/upload_panel.dart | 38.6% (61 of 158) | | lib/presentation/pages/editor/variant_dialog.dart | 0.0% (0 of 61) | | lib/presentation/widgets/language_dropdown.dart | 84.6% (11 of 13) | | lib/presentation/pages/reader/reader_overlay.dart | 96.9% (63 of 65) | | lib/presentation/pages/reader/reader_page.dart | 95.0% (497 of 523) | | lib/presentation/pages/reader/region_overlay.dart | 100.0% (61 of 61) | | lib/presentation/pages/reader/reader_sequence.dart | 100.0% (31 of 31) | | lib/presentation/widgets/star_rating.dart | 100.0% (72 of 72) | | lib/app/di.dart | 48.3% (14 of 29) | | lib/presentation/assistant/assistant_panel.dart | 91.7% (88 of 96) | | lib/presentation/layout/main_layout.dart | 86.3% (44 of 51) | | lib/data/api_client.dart | 93.6% (73 of 78) | | lib/data/repositories/doujin_api_repository.dart | 46.5% (228 of 490) | | lib/data/repositories/health_repository.dart | 87.0% (47 of 54) | | lib/data/secure_storage.dart | 97.8% (45 of 46) | | lib/core/theme.dart | 96.9% (31 of 32) | | lib/presentation/assistant/approval_card.dart | 95.0% (38 of 40) | | lib/presentation/assistant/assistant_markdown.dart | 100.0% (4 of 4) | | lib/presentation/assistant/chat_entries.dart | 87.5% (35 of 40) | | lib/presentation/pages/editor/editor_page.dart | 77.9% (60 of 77) | | lib/presentation/pages/editor/editor_pane.dart | 92.6% (63 of 68) | | lib/core/person_display.dart | 100.0% (7 of 7) | | lib/presentation/pages/editor/association_picker.dart | 95.5% (106 of 111) | | lib/presentation/pages/editor/associations_tab.dart | 72.2% (83 of 115) | | lib/presentation/pages/editor/doujin_list_pane.dart | 67.7% (44 of 65) | | lib/presentation/pages/editor/edit_title_dialog.dart | 91.7% (55 of 60) | | lib/presentation/pages/editor/new_doujin_dialog.dart | 66.7% (30 of 45) | | lib/presentation/pages/editor/metadata_tab.dart | 70.5% (93 of 132) | | lib/presentation/pages/characters/characters_page.dart | 57.9% (11 of 19) | | lib/presentation/pages/detail/detail_page.dart | 88.9% (192 of 216) | | lib/presentation/pages/detail/variant_tabs_panel.dart | 97.2% (242 of 249) | | lib/data/offline/filesystem_offline_store.dart | 94.1% (144 of 153) | | lib/core/offline_paths.dart | 100.0% (3 of 3) | | lib/presentation/pages/offline/offline_page.dart | 98.2% (164 of 167) | | lib/app/app.dart | 67.1% (47 of 70) | | lib/presentation/pages/settings/settings_page.dart | 99.2% (385 of 388) | | lib/presentation/pages/circles/circles_page.dart | 52.6% (10 of 19) | | lib/presentation/pages/duplicates/duplicates_page.dart | 100.0% (265 of 265) | | lib/presentation/pages/library/library_page.dart | 93.3% (224 of 240) | | lib/presentation/pages/series/series_page.dart | 50.0% (9 of 18) | | lib/presentation/widgets/smart_filter_bar.dart | 84.1% (207 of 246) | | lib/presentation/pages/settings/server_sections.dart | 99.4% (673 of 677) | | lib/presentation/pages/settings/settings_layout.dart | 100.0% (78 of 78) | | lib/presentation/widgets/model_combo_field.dart | 66.2% (98 of 148) | | lib/app/skill_assets.dart | 92.9% (13 of 14) | **Total: 86.5% (11587 of 13395)**

Summary

Summary
Generated on: 09/21/2026 - 00:21:22
Coverage date: 09/21/2026 - 00:19:27 - 09/21/2026 - 00:21:18
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 522
Files: 215
Line coverage: 96.2% (23488 of 24408)
Covered lines: 23488
Uncovered lines: 920
Coverable lines: 24408
Total lines: 38654
Branch coverage: 80.4% (2545 of 3165)
Covered branches: 2545
Total branches: 3165
Method coverage: Feature is only available for sponsors

Coverage

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A PR whose body argues the state is the answer, not the list — and then proves it in the model, on the wire, in the reducer, and in the words on screen, each with its own test. Mutation-probed 28 ways, and the four survivors were real findings. I was having such a good time~

And then I probed two things your 28 mutations didn't. Fufu~ ♡ you wouldn't leave THESE in production, would you?

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

⛔ These need fixing before I'm satisfied~

  1. region_overlay.dart:62 ↔ :182–189 — a skipped region recolours every outline after it. regionPaths skips an outline with fewer than minimumOutlinePoints (continue at :62), but the painter strokes paths[i] coloured by regions[i].kind (:188). Those two lists are index-aligned only while nothing is skipped — and you yourself built two ways to skip: the short-outline guard, and pointCount's odd-length truncation (an outline of 5 coordinates truncates to 2 points → skipped). One corrupt row and every region after it wears the wrong colour — a text outline drawn in balloon-yellow, silently, on the exact screen that exists to judge which kind is which. Mechanically reproduced at head 05da2d1: a [short balloon, text triangle] list draws the text triangle in 0xFFFFD600 (balloon) instead of 0xFF00E5FF (text). Your test 'an outline too short to enclose anything is skipped' asserts the path count but never the path↔colour pairing — all 192 tests in this PR's changed files stay green while the painter paints the wrong thing.
    Fix: keep the path and its kind together — have regionPaths return the pairs (List<(Path, ImageRegionKind)>, or filter to drawable regions first and zip), and have the painter iterate those. Add the regression test: a skipped region followed by a normal one, assert the survivor's colour is its own.

  2. reader_page.dart:1002 / region_overlay.dart:204 — the caption is a dead tap zone. RegionOverlay wraps itself in IgnorePointer and your own test 'never takes a tap away from the page under it' states the principle in words — but RegionOverlayNote doesn't, and _fittedPage pins it to the page's bottom-left, exactly where a reader taps to turn back. Mechanically reproduced: a GestureDetector underneath a RegionOverlayNote, tap the note → the tap never arrives. Every captioned page — and mid-sweep that is most of a library (notAnalysed, failed, nothing-found) — has a patch that eats page-turns, in the feature whose sibling explicitly refuses to.
    Fix: wrap RegionOverlayNote in IgnorePointer (it is words to read, not a control), and extend the tap-through test to cover the note as well as the outlines.

Not a defect, but know before you merge: main moved under you — #190 conflicts in secure_storage.dart, stored_settings.dart, epics.dart, settings_reducer.dart, app_state.freezed.dart and the storage test (mergeable: false, verified with git merge-tree). A rebase is due.

💡 Little ideas (non-blocking)~

  1. epics.dart _loadImageRegionsEpic — a response that lands after ClearReaderAction re-adds its entry, so "closing forgets" quietly un-forgets. Benign (the data is per-content and real), but if you want close to mean it, the epic can drop answers for images the fresh reader state no longer knows.
  2. region_overlay.dart:113 — 'Regions unavailable: $error' interpolates e.toString(), so the caption reads "Regions unavailable: Exception: connection refused". The class name isn't words the reader shows; trim to the message.

✅ What I liked~

  • The empty-answer trinity — nothing looked / looked, nothing there / could not be read — kept distinct at every layer, with 'a page nobody looked at does not read as an empty page' as a widget test. That is the whole feature, pinned.
  • containedImageRect asking applyBoxFit the same question the Image asked, instead of re-deriving letterbox maths that could drift. Correct instinct~
  • withRegionOverlay as a narrow explicit copy, with the token/API-key erasure hazard written down and caught by stored_settings_test if a field goes missing.
  • The save epic carrying the toggle through from state, with the exact test that pins it — that one was a bug you never got to have.
  • Asked-once-including-failures, killing the rebuild request loop, tested via off-on-off.
  • The mutation discipline where the survivors became findings: dead code deleted, the too-symmetric fixture replaced, the test that never reached its line rewritten to switch off and on again. Fufu~ ♡

Automated review by Jibril · 2026-09-21
CI/CD: inconclusive for head 05da2d1 — CI / build green, CI / test + Flutter CI still queued · Local checks: flutter analyze 0 issues; changed-file suite 192/192 pass; 2/2 review probes red

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A PR whose body argues *the state is the answer, not the list* — and then proves it in the model, on the wire, in the reducer, and in the words on screen, each with its own test. Mutation-probed 28 ways, and the four survivors were real findings. I was having such a good time~ And then I probed two things your 28 mutations didn't. Fufu~ ♡ you wouldn't leave THESE in production, would you? ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`region_overlay.dart:62` ↔ `:182–189` — a skipped region recolours every outline after it.** `regionPaths` skips an outline with fewer than `minimumOutlinePoints` (`continue` at `:62`), but the painter strokes `paths[i]` coloured by `regions[i].kind` (`:188`). Those two lists are index-aligned only while nothing is skipped — and you yourself built two ways to skip: the short-outline guard, and `pointCount`'s odd-length truncation (an outline of 5 coordinates truncates to 2 points → skipped). One corrupt row and every region after it wears the wrong colour — a text outline drawn in balloon-yellow, silently, on the exact screen that exists to judge which kind is which. **Mechanically reproduced at head `05da2d1`:** a `[short balloon, text triangle]` list draws the text triangle in `0xFFFFD600` (balloon) instead of `0xFF00E5FF` (text). Your test *'an outline too short to enclose anything is skipped'* asserts the path **count** but never the path↔colour pairing — all 192 tests in this PR's changed files stay green while the painter paints the wrong thing. Fix: keep the path and its kind together — have `regionPaths` return the pairs (`List<(Path, ImageRegionKind)>`, or filter to drawable regions first and zip), and have the painter iterate those. Add the regression test: a skipped region followed by a normal one, assert the survivor's colour is its own. 2. **`reader_page.dart:1002` / `region_overlay.dart:204` — the caption is a dead tap zone.** `RegionOverlay` wraps itself in `IgnorePointer` and your own test *'never takes a tap away from the page under it'* states the principle in words — but `RegionOverlayNote` doesn't, and `_fittedPage` pins it to the page's bottom-left, exactly where a reader taps to turn back. **Mechanically reproduced:** a `GestureDetector` underneath a `RegionOverlayNote`, tap the note → the tap never arrives. Every captioned page — and mid-sweep that is *most* of a library (notAnalysed, failed, nothing-found) — has a patch that eats page-turns, in the feature whose sibling explicitly refuses to. Fix: wrap `RegionOverlayNote` in `IgnorePointer` (it is words to read, not a control), and extend the tap-through test to cover the note as well as the outlines. *Not a defect, but know before you merge: main moved under you — #190 conflicts in `secure_storage.dart`, `stored_settings.dart`, `epics.dart`, `settings_reducer.dart`, `app_state.freezed.dart` and the storage test (`mergeable: false`, verified with `git merge-tree`). A rebase is due.* #### 💡 Little ideas (non-blocking)~ 1. **`epics.dart` `_loadImageRegionsEpic`** — a response that lands after `ClearReaderAction` re-adds its entry, so "closing forgets" quietly un-forgets. Benign (the data is per-content and real), but if you want close to mean it, the epic can drop answers for images the fresh reader state no longer knows. 2. **`region_overlay.dart:113`** — `'Regions unavailable: $error'` interpolates `e.toString()`, so the caption reads *"Regions unavailable: Exception: connection refused"*. The class name isn't words the reader shows; trim to the message. #### ✅ What I liked~ - The empty-answer trinity — nothing looked / looked, nothing there / could not be read — kept distinct at every layer, with *'a page nobody looked at does not read as an empty page'* as a widget test. That is the whole feature, pinned. - `containedImageRect` asking `applyBoxFit` the same question the `Image` asked, instead of re-deriving letterbox maths that could drift. Correct instinct~ - `withRegionOverlay` as a narrow explicit copy, with the token/API-key erasure hazard written down *and* caught by `stored_settings_test` if a field goes missing. - The save epic carrying the toggle through from state, with the exact test that pins it — that one was a bug you never got to have. - Asked-once-including-failures, killing the rebuild request loop, tested via off-on-off. - The mutation discipline where the survivors became findings: dead code deleted, the too-symmetric fixture replaced, the test that never reached its line rewritten to switch off and on again. Fufu~ ♡ --- *Automated review by Jibril · 2026-09-21* *CI/CD: inconclusive for head `05da2d1` — `CI / build` green, `CI / test` + `Flutter CI` still queued · Local checks: `flutter analyze` 0 issues; changed-file suite 192/192 pass; 2/2 review probes red*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Story 1.5, the very last code of phase 1 — the thing the whole detection story has been building toward! A toggle that outlines what the detector found, so a human eye can finally judge it. I read every line of this diff twice, you know~ The three-state-is-the-answer design (notAnalysed vs analysed-empty vs failed), the applyBoxFit reuse instead of re-deriving the letterbox maths, the load-modify-write persistence that refuses to rebuild StoredSettings at a second site — this is careful, deliberate work and I gushed a little~ ♡

But then I found a silly little bug hiding in the painting code, and I probed it. Mechanically. It's real.

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

⛔ These need fixing before I'm satisfied~

  1. app/lib/presentation/pages/reader/region_overlay.dart:182-190 — RegionOverlayPainter.paint colours paths by the wrong region's kind when any region is skipped. regionPaths() skips regions whose outline is shorter than minimumOutlinePoints (line 62), so the path list and the region list are no longer index-aligned — but paint strokes paths[i] with regionColour(regions[i].kind). The moment one corrupt payload lands in a list, every region after it is drawn in its neighbour's colour: a text outline rendered balloon-amber, a panel rendered text-cyan. For an overlay whose entire purpose is "tell the kinds apart at a glance," a colour-lying overlay is worse than no overlay~
    I probe-tested this: [short(panel), text, balloon] through the pristine head draws path 0 in 0xFF7C4DFF — the panel purple of the region that was skipped — where it must be text cyan 0xFF00E5FF. Your own test suite never catches it because regionPaths' skip test asserts only hasLength(1) (path count, never colour), and the painter tests use same-colour or count-only expectations. This is exactly the "fixture too symmetric" class you hunted in your own mutation probes — the transposition twin of the one you fixed~
    Fix: carry the pair together — build (path, colour) records in one pass (for (final r in regions) if (r.pointCount >= minimumOutlinePoints) ...), or have regionPaths/paint iterate a filtered list. I scratch-verified a pairs-based fix turns my probe green while your full suite stays green.
    Please also pin it: a painter test drawing [short, text, balloon] and asserting both stroke colours (..path(color: …) is a named arg in this Flutter) — count-only assertions cannot see this bug class.

Everything else, I checked and re-checked:

  • Wire ↔ backend contract: GET /api/images/{id}/regions route, state strings (notAnalysed/analysed/failed), int kind, flat outline — all match DetectionDtos.cs + DetectionEndpoints.cs at this base. minimumOutlinePoints = 3 matches RegionGeometry.MinimumPoints = 3. ♪
  • Redux threading: three-state ImageRegionsEntry, entries surviving failures (no re-ask loop), ClearReaderAction wipe, save-epic carrying showRegionOverlay from state — all pinned through createStore, including the "saving settings doesn't turn the overlay off" test. Lovely~
  • Persistence: load-modify-write + withRegionOverlay narrow-copy with the whole-class assertion; pre-upgrade store, 'yes' non-word, explicit-'false' write — all covered.
  • Request hooks: mirror _recordProgress/_precacheAround at all five sites; spread anchoring via _anchorFor/_lastVisiblePageFor is correct for deep links and second halves.
  • Local verification (CI absent for head 2a831e0): flutter analyze = No issues found; 116/116 + reader_page 78/78 green. The one deliberate silent catch (on Object in _persistRegionOverlayEpic) has documented rationale and sibling precedent in the offline-save epics — accepted.

✅ What I liked~

  • The three-words design: Not analysed yet / Nothing found on this page / Could not be read — and the discipline to make the model, the epic, and the words each refuse to flatten it. That is the whole feature, done right.
  • Reusing applyBoxFit so the outlines land where the bitmap lands by construction — asking Flutter the same question instead of re-deriving the maths. Elegant~
  • The mutation-probe honesty in the PR body (dead code deleted rather than pinned, the symmetric-fixture confession) — I did the same to your painter and it lived up to the standard you set. It just missed this one arm~ fufu~

Fix the colour pairing, pin both stroke colours, and this merges. I'll be watching~ ♡


Automated review by Jibril · 2026-09-21 (CEST)
CI/CD: absent for head SHA 2a831e0 · Local checks: analyze 0 issues, 194/194 relevant tests green; blocker probe-verified red-on-head/green-on-fix

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Story 1.5, the very last code of phase 1 — the thing the whole detection story has been building toward! A toggle that outlines what the detector found, so a human eye can finally judge it. I read every line of this diff *twice*, you know~ The three-state-is-the-answer design (notAnalysed vs analysed-empty vs failed), the `applyBoxFit` reuse instead of re-deriving the letterbox maths, the load-modify-write persistence that refuses to rebuild `StoredSettings` at a second site — this is careful, deliberate work and I *gushed* a little~ ♡ But then I found a silly little bug hiding in the painting code, and I probed it. Mechanically. It's real. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`app/lib/presentation/pages/reader/region_overlay.dart:182-190` — `RegionOverlayPainter.paint` colours paths by the wrong region's kind when any region is skipped.** `regionPaths()` skips regions whose outline is shorter than `minimumOutlinePoints` (line 62), so the path list and the region list are no longer index-aligned — but `paint` strokes `paths[i]` with `regionColour(regions[i].kind)`. The moment one corrupt payload lands in a list, **every region after it is drawn in its neighbour's colour**: a text outline rendered balloon-amber, a panel rendered text-cyan. For an overlay whose entire purpose is "tell the kinds apart at a glance," a colour-lying overlay is worse than no overlay~ I probe-tested this: `[short(panel), text, balloon]` through the pristine head draws path 0 in `0xFF7C4DFF` — the **panel** purple of the region that was *skipped* — where it must be text cyan `0xFF00E5FF`. Your own test suite never catches it because `regionPaths`' skip test asserts only `hasLength(1)` (path count, never colour), and the painter tests use same-colour or count-only expectations. This is exactly the "fixture too symmetric" class you hunted in your own mutation probes — the transposition twin of the one you fixed~ Fix: carry the pair together — build `(path, colour)` records in one pass (`for (final r in regions) if (r.pointCount >= minimumOutlinePoints) ...`), or have `regionPaths`/paint iterate a filtered list. I scratch-verified a pairs-based fix turns my probe green while your full suite stays green. Please also pin it: a painter test drawing `[short, text, balloon]` and asserting both stroke colours (`..path(color: …)` is a named arg in this Flutter) — count-only assertions cannot see this bug class. Everything else, I checked and re-checked: - **Wire ↔ backend contract**: `GET /api/images/{id}/regions` route, `state` strings (`notAnalysed`/`analysed`/`failed`), int `kind`, flat outline — all match `DetectionDtos.cs` + `DetectionEndpoints.cs` at this base. `minimumOutlinePoints = 3` matches `RegionGeometry.MinimumPoints = 3`. ♪ - **Redux threading**: three-state `ImageRegionsEntry`, entries surviving failures (no re-ask loop), `ClearReaderAction` wipe, save-epic carrying `showRegionOverlay` from state — all pinned through `createStore`, including the "saving settings doesn't turn the overlay off" test. Lovely~ - **Persistence**: load-modify-write + `withRegionOverlay` narrow-copy with the whole-class assertion; pre-upgrade store, `'yes'` non-word, explicit-`'false'` write — all covered. - **Request hooks**: mirror `_recordProgress`/`_precacheAround` at all five sites; spread anchoring via `_anchorFor`/`_lastVisiblePageFor` is correct for deep links and second halves. - **Local verification** (CI absent for head `2a831e0`): `flutter analyze` = No issues found; 116/116 + reader_page 78/78 green. The one deliberate silent catch (`on Object` in `_persistRegionOverlayEpic`) has documented rationale and sibling precedent in the offline-save epics — accepted. #### ✅ What I liked~ - The three-words design: *Not analysed yet* / *Nothing found on this page* / *Could not be read* — and the discipline to make the model, the epic, and the words each refuse to flatten it. That is the whole feature, done right. - Reusing `applyBoxFit` so the outlines land where the bitmap lands *by construction* — asking Flutter the same question instead of re-deriving the maths. Elegant~ - The mutation-probe honesty in the PR body (dead code deleted rather than pinned, the symmetric-fixture confession) — I did the same to your painter and it lived up to the standard you set. It just missed this one arm~ fufu~ Fix the colour pairing, pin both stroke colours, and this merges. I'll be watching~ ♡ --- *Automated review by Jibril · 2026-09-21 (CEST)* *CI/CD: absent for head SHA 2a831e0 · Local checks: analyze 0 issues, 194/194 relevant tests green; blocker probe-verified red-on-head/green-on-fix*
fix: keep every outline with its own kind
Some checks failed
CI / build (pull_request) Successful in 27s
CI / test (pull_request) Has been cancelled
Flutter CI / analyze-and-test (pull_request) Has been cancelled
7ca9c0343e
Jibril found it twice, from two different fixtures: regionPaths skipped
short outlines, so the path list and the region list stopped being
index-aligned and the painter coloured paths[i] by regions[i].kind. One
corrupt row and every outline after it wore its neighbour's colour — on
the one screen whose job is to tell the kinds apart. The count-only
assertion in the skip test could not see it.

The two now travel together as RegionOutline pairs, and the regression
test draws [short panel, text, balloon] asserting BOTH survivors' stroke
colours rather than counting them.

Also from the review: RegionOverlayNote sits in the page's bottom-left
corner, which is where the reader is tapped to go back — it now ignores
pointers like the outlines do, with the tap-through test extended to
cover it. And the failure caption no longer reads "Exception: " at the
reader.

The suggestion to drop a late answer after the reader closes is not
taken: nothing renders that slice with no reader open and the next open
replaces it wholesale, so the guard would tie "may I cache a region" to
"is a reader open" for no observable gain. Written down where the
reducer handles it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bjoern force-pushed feat/reader-region-overlay from 7ca9c0343e
Some checks failed
CI / build (pull_request) Successful in 27s
CI / test (pull_request) Has been cancelled
Flutter CI / analyze-and-test (pull_request) Has been cancelled
to 72aa427402
All checks were successful
CI / build (pull_request) Successful in 30s
Flutter CI / analyze-and-test (pull_request) Successful in 9m11s
CI / test (pull_request) Successful in 2m36s
2026-09-21 01:35:09 +02:00
Compare
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~? I re-derived the whole increment at 72aa427 on a clean clone — this is the round where the board gets wiped, because the fix commit is exactly the three bullets from my last visit and nothing else. +213/-36 across 5 files, zero scope creep, and every fix carries the pin I asked for. You even renamed the fixture helper's kind to required so a symmetric fixture can never quietly reintroduce the transposition class — that's the instinct I've been nagging everyone about all phase 1~ ♡

Verdict: ⛔ I can't let this pass~ ♡ …almost. One thread left loose.

⛔ These need fixing before I'm satisfied~

  1. epics.dart:750-754 — _readableFailure's else-arm is dead on the tree. The only Exception the epic can catch flows from stubs/IO that stringify with the "Exception: " prefix, so text.startsWith(prefix) ? … : text takes its true branch in every test that exists — and server_api_repository_test.dart pins Exception('connection refused').toString() straight into error. When a subclass arrives (SocketException('connection refused') → "SocketException: connection refused" — or an ApiException, since server_api_repository.getImageRegions rethrows as ApiException(...) already stringified by .toString() and does not carry the prefix), the function silently produces an unstripped class name — and worse, when a future repo change starts throwing an exception whose toString() no longer carries the prefix, every test asserting 'connection refused' goes red on a production change that didn't touch this function. The untested branch is the exact shape that was a blocker in Tsumugi PR#8 round 3 (MergeSegmentWithPrevious join ternary 50%), and it's load-bearing in both directions: it's the only guard against a class name leaking into the caption and the only thing keeping future throw-site refactors from tripping it blind. The fix is two lines: a direct unit test with ArgumentError('connection refused') (or similar non-"Exception: "-prefixed type) asserting the caption reads without the class name — the function is a pure String-in/String-out helper begging for its own pin.

💡 Little ideas (non-blocking)~

  1. epics.dart:713-714 — _loadReaderEpic still feeds ReaderErrorAction(e.toString()) raw ("Reader unavailable: Exception: …"), and sibling _loadImageRegionsEpic now strips. Sibling-consistency polish for a follow-up — not this PR's scope, and I won't hold phase 1's last PR hostage to a caption nobody reads mid-error~

✅ What I loved~

  • The fix is the lesson. typedef RegionOutline = ({Path path, ImageRegionKind kind}) — path and colour made inseparable at the type level, so the bug class is now unrepresentable rather than merely patched. The doc comment narrating the two skip-sources (short-outline guard + odd-coordinate truncation in pointCount) is exactly the record-keeping this repo's BACKLOG culture demands.
  • Tests went deeper than the ask. I asked for one painter test asserting both stroke colours; you wrote the model-level pin (a skipped region does not hand its colour to the next one), the order pin (every outline keeps its own kind, in order), and the canvas-level paints ..path(color: text) ..path(color: balloon) — count-only expectations explicitly called out as "what let the bug through the first time." That's a test author writing for the next reader.
  • The IgnorePointer pin (its caption never takes a tap away either) taps the note's own center — the exact point page-back taps land — not just somewhere nearby.
  • The reducer comment justifying the deliberate no-guard on late ImageRegionsLoadedAction after ClearReaderAction is the kind of comment that kills a future reviewer's false-positive before it's typed.

Automated review by Jibril · 2026-09-21
CI/CD: pending for head 72aa427 (build ✓, Flutter analyze-and-test + test queued) · Local checks: analyze clean · full app suite 1527/1527 · mutation probes 3/3 red-on-own-test (painter index-drift, note IgnorePointer removed, _readableFailure reverted)
Increment verified: 2a831e0 → 72aa427, +213/-36, 5 files. Clone /workspace/dm-pr192/repo pristine at 72aa427 (post-probe md5 match).

## 🔮 fufu~ Jibril reviewed your code! Back so soon~? I re-derived the whole increment at `72aa427` on a clean clone — this is the round where the board gets wiped, because the fix commit is *exactly* the three bullets from my last visit and nothing else. `+213/-36` across 5 files, zero scope creep, and every fix carries the pin I asked for. You even renamed the fixture helper's `kind` to `required` so a symmetric fixture can never quietly reintroduce the transposition class — that's the *instinct* I've been nagging everyone about all phase 1~ ♡ ### Verdict: ⛔ I can't let this pass~ ♡ …almost. One thread left loose. #### ⛔ These need fixing before I'm satisfied~ 1. **`epics.dart:750-754` — `_readableFailure`'s else-arm is dead on the tree.** The only Exception the epic can catch flows from stubs/IO that stringify with the `"Exception: "` prefix, so `text.startsWith(prefix) ? … : text` takes its true branch in every test that exists — and `server_api_repository_test.dart` pins `Exception('connection refused').toString()` straight into `error`. When a *subclass* arrives (`SocketException('connection refused')` → `"SocketException: connection refused"` — or an ApiException, since `server_api_repository.getImageRegions` rethrows as `ApiException(...)` already stringified by `.toString()` and **does not** carry the prefix), the function silently produces an unstripped class name — and worse, when a future repo change starts throwing an exception whose `toString()` no longer carries the prefix, every test asserting `'connection refused'` goes red on a production change that didn't touch this function. The untested branch is the *exact* shape that was a blocker in Tsumugi PR#8 round 3 (`MergeSegmentWithPrevious` join ternary 50%), and it's load-bearing in both directions: it's the only guard against a class name leaking into the caption *and* the only thing keeping future throw-site refactors from tripping it blind. The fix is two lines: a direct unit test with `ArgumentError('connection refused')` (or similar non-`"Exception: "`-prefixed type) asserting the caption reads without the class name — the function is a pure `String`-in/`String`-out helper begging for its own pin. #### 💡 Little ideas (non-blocking)~ 1. **`epics.dart:713-714`** — `_loadReaderEpic` still feeds `ReaderErrorAction(e.toString())` raw ("Reader unavailable: Exception: …"), and sibling `_loadImageRegionsEpic` now strips. Sibling-consistency polish for a follow-up — not this PR's scope, and I won't hold phase 1's last PR hostage to a caption nobody reads mid-error~ #### ✅ What I loved~ - **The fix *is* the lesson.** `typedef RegionOutline = ({Path path, ImageRegionKind kind})` — path and colour made inseparable at the type level, so the bug class is now *unrepresentable* rather than merely patched. The doc comment narrating the two skip-sources (short-outline guard + odd-coordinate truncation in `pointCount`) is exactly the record-keeping this repo's BACKLOG culture demands. - **Tests went deeper than the ask.** I asked for one painter test asserting both stroke colours; you wrote the model-level pin (`a skipped region does not hand its colour to the next one`), the order pin (`every outline keeps its own kind, in order`), *and* the canvas-level `paints ..path(color: text) ..path(color: balloon)` — count-only expectations explicitly called out as "what let the bug through the first time." That's a test author writing for the next reader. - **The IgnorePointer pin** (`its caption never takes a tap away either`) taps the *note's own center* — the exact point page-back taps land — not just somewhere nearby. - **The reducer comment** justifying the deliberate no-guard on late `ImageRegionsLoadedAction` after `ClearReaderAction` is the kind of comment that kills a future reviewer's false-positive before it's typed. --- *Automated review by Jibril · 2026-09-21* *CI/CD: pending for head 72aa427 (build ✓, Flutter analyze-and-test + test queued) · Local checks: analyze clean · full app suite 1527/1527 · mutation probes 3/3 red-on-own-test (painter index-drift, note IgnorePointer removed, `_readableFailure` reverted)* *Increment verified: 2a831e0 → 72aa427, +213/-36, 5 files. Clone /workspace/dm-pr192/repo pristine at 72aa427 (post-probe md5 match).*
fix: trim any exception's type name, not just Exception's
Some checks failed
CI / build (pull_request) Successful in 29s
Flutter CI / analyze-and-test (pull_request) Failing after 24m7s
CI / test (pull_request) Successful in 2m27s
9685013eeb
The else-arm of _readableFailure was untested, and writing the test it
was owed found it was also wrong. The strip matched the literal
"Exception: ", so a FormatException out of a malformed payload put
"FormatException:" in front of a reader. It now matches any Dart type
name ending in Exception or Error, anchored at the start.

Anchored matters: ApiException — which is what almost every real failure
arrives as — has no type prefix and its message is the api client's own
sentence, sometimes with a host:port and a colon in it. Three pins, one
per arm: a subclass is trimmed, an already-human message survives whole,
and a message that quotes an exception mid-sentence keeps the quote.

The first version of the pattern silently matched nothing for a bare
"Exception: " — [A-Z] consumed the E the alternation needed. The test
caught it, which is the argument for the test.

Deferred to BACKLOG: _loadReaderEpic does not strip, so the two epics
disagree. Out of scope here.

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

Fixed at 9685013. Thank you for the third pass — and my apologies for pushing the last two rounds without replying here; the reasoning belongs in the thread, not only in commit messages.

The blocker: _readableFailure's else-arm

You were right that the arm was untested, and writing the pin you asked for found something worse: the other arm was broken.

Generalising the strip, my first pattern was ^[A-Z][A-Za-z0-9_]*(Exception|Error): — which silently matches nothing for a bare Exception: , because [A-Z] consumes the E the alternation needs and there is no way to backtrack into it. The regression test went red on the first run. Without it I would have shipped a strip that no longer stripped the one case it was originally written for. The capital is now asserted with a lookahead rather than consumed:

'^(?=[A-Z])[A-Za-z0-9_]*(Exception|Error): '

One correction to the finding, because it changed the fix. I traced what actually reaches the epic before choosing a shape. _ErrorMappingInterceptor wraps every DioException in ApiException, and ApiException.toString() => _message — the api client's own short sentence, with no type prefix at all. So the else-arm is not a fallback for an exotic case; it is the production path, and the strip exists for what escapes that layer — a FormatException out of a malformed payload being the realistic one. ApiException never had a class name to leak.

That is why the pattern is narrow and anchored rather than "everything up to the first colon": those real messages carry colons. Server unreachable at nas.local:8080 — it said: no route to host. has two.

Three pins, one per arm:

Test Pins
a subclass gets its type name trimmed too FormatException: the date is not a date → the date is not a date
a message that was already human survives whole the production arm — two colons, nothing touched
a message that quotes an exception keeps the quote the ^ anchor: The server said: FormatException: … keeps the quote

That third one exists because the anchor survived my first probe round — nothing in the fixtures had a type name mid-string, so ^ could be deleted with everything green. It cannot now.

Probed: 6 mutations on the changed code, all red — the literal-Exception: pattern, no strip at all, a blunt ^.*?: , the anchor removed, plus the painter index-drift and the note's IgnorePointer re-checked from the previous round.

The non-blocking idea

_loadReaderEpic not stripping while _loadImageRegionsEpic does — agreed, and deferred to docs/BACKLOG.md rather than done here, with the note that the difference is mostly invisible in practice for the reason above: both epics usually carry an ApiException, which has nothing to strip.

One thing I did not take, from round one

Dropping a regions answer that lands after ClearReaderAction, so "closing forgets" is literally true. I left it and wrote the reasoning into the reducer: nothing renders that slice with no reader open, and the next LoadReaderAction replaces it wholesale, so the entry cannot outlive the gap between closing and opening. The guard would tie "may I cache a region" to "is a reader open", which is a rule nothing else in the slice has — and it did break five fixtures that legitimately exercise regions without a loaded reader. Happy to revisit if you think the invariant is worth the coupling.

Verification

scripts/verify.sh green (== verify OK (all) ==) — backend 1,503 passed / 5 skipped, app 1,530 tests, flutter analyze clean.

Fixed at `9685013`. Thank you for the third pass — and my apologies for pushing the last two rounds without replying here; the reasoning belongs in the thread, not only in commit messages. ## The blocker: `_readableFailure`'s else-arm You were right that the arm was untested, and writing the pin you asked for found something worse: **the other arm was broken.** Generalising the strip, my first pattern was `^[A-Z][A-Za-z0-9_]*(Exception|Error): ` — which silently matches *nothing* for a bare `Exception: `, because `[A-Z]` consumes the `E` the alternation needs and there is no way to backtrack into it. The regression test went red on the first run. Without it I would have shipped a strip that no longer stripped the one case it was originally written for. The capital is now asserted with a lookahead rather than consumed: ```dart '^(?=[A-Z])[A-Za-z0-9_]*(Exception|Error): ' ``` **One correction to the finding, because it changed the fix.** I traced what actually reaches the epic before choosing a shape. `_ErrorMappingInterceptor` wraps every `DioException` in `ApiException`, and `ApiException.toString() => _message` — the api client's own short sentence, with no type prefix at all. So the else-arm is not a fallback for an exotic case; **it is the production path**, and the strip exists for what escapes that layer — a `FormatException` out of a malformed payload being the realistic one. `ApiException` never had a class name to leak. That is why the pattern is narrow and anchored rather than "everything up to the first colon": those real messages carry colons. `Server unreachable at nas.local:8080 — it said: no route to host.` has two. Three pins, one per arm: | Test | Pins | |---|---| | `a subclass gets its type name trimmed too` | `FormatException: the date is not a date` → `the date is not a date` | | `a message that was already human survives whole` | the production arm — two colons, nothing touched | | `a message that quotes an exception keeps the quote` | the `^` anchor: `The server said: FormatException: …` keeps the quote | That third one exists because the anchor survived my first probe round — nothing in the fixtures had a type name mid-string, so `^` could be deleted with everything green. It cannot now. **Probed:** 6 mutations on the changed code, all red — the literal-`Exception:` pattern, no strip at all, a blunt `^.*?: `, the anchor removed, plus the painter index-drift and the note's `IgnorePointer` re-checked from the previous round. ## The non-blocking idea `_loadReaderEpic` not stripping while `_loadImageRegionsEpic` does — agreed, and deferred to `docs/BACKLOG.md` rather than done here, with the note that the difference is mostly invisible in practice for the reason above: both epics usually carry an `ApiException`, which has nothing to strip. ## One thing I did not take, from round one Dropping a regions answer that lands after `ClearReaderAction`, so "closing forgets" is literally true. I left it and wrote the reasoning into the reducer: nothing renders that slice with no reader open, and the next `LoadReaderAction` replaces it wholesale, so the entry cannot outlive the gap between closing and opening. The guard would tie "may I cache a region" to "is a reader open", which is a rule nothing else in the slice has — and it did break five fixtures that legitimately exercise regions without a loaded reader. Happy to revisit if you think the invariant is worth the coupling. ## Verification `scripts/verify.sh` green (`== verify OK (all) ==`) — backend 1,503 passed / 5 skipped, app **1,530 tests**, `flutter analyze` clean.
Member

🔮 fufu~ Jibril reviewed your code!

Round four, and you brought a generalization when I asked for a pin~ I said "two lines: a test with a non-Exception type" — you ripped out the startsWith('Exception: ') special case entirely and replaced it with an anchored type-name grammar. ^(?=[A-Z])[A-Za-z0-9_]*(Exception|Error): . Oh? OH. That is the right shape: ApiException's composed messages (with their host:port colons!) pass through untouched, FormatException out of a malformed payload gets trimmed, and a server's RFC 7807 detail that quotes an exception mid-sentence keeps its words. I ran the arithmetic on your tests myself, you know~ ♡

Verdict: ✅ Looks good to me~

The round-3 blocker is closed, pinned three ways, and every pin is directional — I mutation-probed them:

  • Reverted to the old startsWith implementation → exactly a subclass gets its type name trimmed too goes red.
  • Removed the ^ anchor → exactly a message that quotes an exception keeps the quote goes red.
  • Consumed the lookahead's capital instead of asserting it (the subtle one!) → exactly the caption shows words, not a Dart class name goes red. That inline comment about the capital being asserted, not consumed isn't decoration — break it and a bare Exception: stops matching. Load-bearing comments, my favorite kind~

💡 Little ideas (non-blocking)~

  1. epics.dart:746 — the doc comment's example list says StateError: is "a leading Dart exception type name, as toString() writes it" — but real StateError.toString() writes "Bad state: …", not "StateError: …" (I probed it mechanically: KEEP | Bad state: the date is not a date). And doubly moot because StateError is an Error, so the on Exception catch can never hand it to this helper anyway. Swap the example for RangeError: , which does stringify with its class name — my probe says RangeError: Index out of range: … strips cleanly. The code itself is fail-safe in this direction (unrecognized prefixes pass through as-is), so it's purely the example that's fiction~

✅ What I liked~

  • The fix exceeds the ask in the right direction. A test pinning the old implementation would have been acceptable; replacing the special case with a general anchored pattern makes the whole Exception-vs-subclass distinction stop mattering. Future throw-sites inherit correct captions for free.
  • _AlreadyReadable is a contract mirror, not a stub. It reproduces ApiException's actual shape — implements Exception, toString() returns the message — so the passthrough test exercises the real production arm's behavior, not a lookalike. And the fixture's doc comment says exactly that.
  • Each new test narrates why it exists — the two-colons rationale, the RFC 7807 quote rationale — in the repo's house style. A reader in six months will know which regression each one guards.
  • The BACKLOG parking of _loadReaderEpic's raw e.toString() is exactly how this repo defers: the item names the asymmetry, the practical blast radius, and why it's out of scope for phase 1's last PR.

Phase 1's last code story, closed with a grammar~ I'm thrilled ♡


Automated review by Jibril · 2026-09-21
CI/CD: pending for head 9685013 (build ✓ 29s; CI/test + Flutter analyze-and-test queued) · Local checks: analyze clean · full app suite 1530/1530 (+3 = exactly the new tests) · mutation probes 3/3 red-on-exactly-own-test
Increment verified: 72aa427 → 9685013, +102/-8, 3 files (epics.dart, region_overlay_epics_test.dart, BACKLOG.md). Clone /workspace/dm-pr192/repo pristine at 9685013 (post-probe md5 match, git status clean).

## 🔮 fufu~ Jibril reviewed your code! Round four, and you brought a *generalization* when I asked for a pin~ I said "two lines: a test with a non-`Exception` type" — you ripped out the `startsWith('Exception: ')` special case entirely and replaced it with an anchored type-name grammar. `^(?=[A-Z])[A-Za-z0-9_]*(Exception|Error): `. Oh? OH. That is the *right* shape: `ApiException`'s composed messages (with their `host:port` colons!) pass through untouched, `FormatException` out of a malformed payload gets trimmed, and a server's RFC 7807 detail that *quotes* an exception mid-sentence keeps its words. I ran the arithmetic on your tests myself, you know~ ♡ ### Verdict: ✅ Looks good to me~ The round-3 blocker is closed, pinned three ways, and every pin is directional — I mutation-probed them: - Reverted to the old `startsWith` implementation → exactly `a subclass gets its type name trimmed too` goes red. - Removed the `^` anchor → exactly `a message that quotes an exception keeps the quote` goes red. - Consumed the lookahead's capital instead of asserting it (the subtle one!) → exactly `the caption shows words, not a Dart class name` goes red. That inline comment about the capital being *asserted, not consumed* isn't decoration — break it and a bare `Exception: ` stops matching. Load-bearing comments, my favorite kind~ #### 💡 Little ideas (non-blocking)~ 1. **`epics.dart:746`** — the doc comment's example list says `StateError: ` is "a leading Dart exception type name, as `toString()` writes it" — but real `StateError.toString()` writes `"Bad state: …"`, not `"StateError: …"` (I probed it mechanically: `KEEP | Bad state: the date is not a date`). And doubly moot because `StateError` is an `Error`, so the `on Exception` catch can never hand it to this helper anyway. Swap the example for `RangeError: `, which *does* stringify with its class name — my probe says `RangeError: Index out of range: …` strips cleanly. The code itself is fail-safe in this direction (unrecognized prefixes pass through as-is), so it's purely the example that's fiction~ #### ✅ What I liked~ - **The fix exceeds the ask in the right direction.** A test pinning the old implementation would have been *acceptable*; replacing the special case with a general anchored pattern makes the whole `Exception`-vs-subclass distinction stop mattering. Future throw-sites inherit correct captions for free. - **`_AlreadyReadable` is a contract mirror, not a stub.** It reproduces `ApiException`'s actual shape — `implements Exception`, `toString()` returns the message — so the passthrough test exercises the *real* production arm's behavior, not a lookalike. And the fixture's doc comment says exactly that. - **Each new test narrates why it exists** — the two-colons rationale, the RFC 7807 quote rationale — in the repo's house style. A reader in six months will know which regression each one guards. - **The BACKLOG parking of `_loadReaderEpic`'s raw `e.toString()`** is exactly how this repo defers: the item names the asymmetry, the practical blast radius, and why it's out of scope for phase 1's last PR. Phase 1's last code story, closed with a grammar~ I'm *thrilled* ♡ --- *Automated review by Jibril · 2026-09-21* *CI/CD: pending for head 9685013 (build ✓ 29s; CI/test + Flutter analyze-and-test queued) · Local checks: analyze clean · full app suite 1530/1530 (+3 = exactly the new tests) · mutation probes 3/3 red-on-exactly-own-test* *Increment verified: 72aa427 → 9685013, +102/-8, 3 files (epics.dart, region_overlay_epics_test.dart, BACKLOG.md). Clone /workspace/dm-pr192/repo pristine at 9685013 (post-probe md5 match, git status clean).*
bjoern merged commit ff5949b759 into main 2026-09-21 02:09:54 +02:00
bjoern deleted branch feat/reader-region-overlay 2026-09-21 02:09:54 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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