feat: the assistant can write chapters and variants #163

Merged
bjoern merged 6 commits from feat/the-assistant-can-write-chapters-and-variants into main 2026-09-19 15:55:02 +02:00
Member

What's in

  • The assistant can now edit chapter structure and editions, not just rename a chapter: doujin_write gains create_chapter (optional 1-based position), delete_chapter, reorder_chapters, move_pages_to_chapter, create_variant and delete_variant. Each pauses on the approval gate with a card that states exactly what changes — how many pages a deleted chapter leaves behind as ungrouped pages (they are not deleted), how many pages delete_variant destroys and that it cannot be undone, and what the doujin is left with when it is the only edition.
  • move_pages_to_chapter takes pages the way the model actually sees them — reading-order page numbers as get_doujin prints them, or image ids — resolved through the shared ReadingOrder, so "page 7" is the page the model read even when chapter order disagrees with raw page order. It moves pages into a chapter or out with ungroup: true, groups the card by source chapter with ranges (pages 3-5: "Ch 1" → "Ch 2"), and reports partial failures ("moved 1 of 2" plus a Failed line) or that nothing landed at all.
  • ADR amendment: docs/adr/0024-in-app-ai-assistant.md gains "## Amendment (2026-09-19)" and the v1 deferral paragraph is marked as narrowed. It records why a structural write is safe under the original decision (every action is gated and the card names the consequence before the gate), which two consequences of the 2026-09-06 amendment are relied on (one card over several repository writes; per-item partial-failure reporting), why delete_variant invents no last-variant refusal the server would not make, and what is still deferred (page upload/delete/reorder, variant field edits, doujin create/delete).
  • Two server behaviours are mirrored rather than guessed: create_chapter with a position is create + reorder under one approved card, because ChapterService.CreateAsync assigns the given SortOrder without shifting the others; the append now passes an explicit sortOrder = max + 1 instead of letting the server's count-based default tie with a surviving chapter after a delete has left a gap. reorder_chapters demands the complete list, because the server silently pushes unlisted chapters behind the listed ones.
  • Deleting a variant no longer leaves the open detail page or editor rendering the edition it just destroyed: detail_reducer and editor_reducer drop selectedVariantId/variant when the refreshed doujin no longer lists it, which lets the existing auto-select epics (gated on selectedVariantId == null) promote a survivor. This also self-heals the same staleness from any other source.
  • Tool description, the system prompt's Chapters section (no longer claiming rename is the only chapter write, still naming what remains impossible, and steering the model to move a chapter's worth of pages per call), README's assistant bullet and the BACKLOG "Tool gaps" item were updated to match. No new endpoint, no migration, no new tunable — every new constant is a correctness or wording constant.

Verification

Executed on this branch at 902a5ed, in the worktree, on a clean tree: scripts/verify.sh app — passed, 1156 tests, All tests passed!, == verify OK (app) == (this includes flutter analyze, which reports no issues; infos are fatal in this project). scripts/verify.sh backend was not run — no backend file is touched by the diff.

Layers exercised: unit only. The tool runs against a fake DoujinRepository in app/test/agent_write_tools_test.dart (25 new tests) plus assistant_agent_test.dart for the prompt, and the stale-selection fix is pinned by reducer tests in app/test/detail_page_test.dart and app/test/editor_reducer_test.dart. No REST, use-case or persistence layer was exercised, and nothing ran in the real app against a live server.

Mutation probes: two rounds. The author ran 22 mutations against the feature commit; an independent re-probe at 902a5ed then re-ran 25 mutations in 8 batches (disjoint code paths per batch, each reverted with git checkout, tree verified empty between runs, nothing committed), of which 23 went red with one-test attribution and 2 survived (both minor and now reported below). Probes that were green before the fix commit and are red now: position <= chapters.length → position < chapters.length (an off-by-one that silently appended while the card promised a position); widening it to if (position != null); dropping position < 1 from the range guard (position 0 previously created the chapter and then threw a RangeError, which on Exception catch does not catch); rewriting move_pages_to_chapter's all-failed arm to claim success; making its onExecuted unconditional; deleting the ambiguous-image-id arm (matches.single threw Bad state: Too many elements through the turn); _sortedChapters → raw variant.chapters (5 tests — the fixture now declares c-2 before c-1, so its chapter list order really disagrees with sortOrder, as its page order already disagreed with reading order). Also red: removing reorder_chapters' if (!approved) return kChangeDeniedText; dropping sortOrder: nextSortOrder from the append (5 tests); removing the try/catch around the positional reorderChapters; both directions of each card wording that can lie (delete_chapter's empty-chapter line, delete_variant's empty-variant "CANNOT be undone" and its default-promotion line, create_variant's is_default/first-variant/kind lines and its $demoted clause, _pagesWord's singular arm); and both directions of the reducer fix (preserve-always and drop-always each red on their own test, with editor_page_test.dart green in both).

Surviving mutants, both minor and unfixed: they are listed in the re-probe as not worth a test; one is defensive code for a state ADR 0004 forbids whose output is the safe empty string.

Confirmed by reading, not running: every server behaviour the cards mirror — ChapterService.CreateAsync's count-based sortOrder, VariantService.DeleteAsync's promotion of the oldest survivor (and its explicit "leave the doujin with no variants and no default"), VariantService.ClearDefaultFlagForDoujinAsync, and ImageCleanupHelper's orphan rule (which is why the card now says "every image no other page still uses" rather than "their images"). Also unverified at the UI level: how the new cards render in the assistant panel, and the stale-selection fix end to end through the panel rather than through the reducers.

Merge note for whoever lands second: this branch rewrites README.md:59, the AI assistant bullet, which feat/cancel-an-assistant-run also declares as its only README edit. The merged sentence needs both clauses — this branch's "and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants)" and the sibling's mid-run cancel clause — not one side wholesale. The BACKLOG edit stays inside the Assistant "Tool gaps" anchor and is clean.

Follow-ups

  • docs/BACKLOG.md, Assistant "Tool gaps": update_variant (a variant's language, kind, display name and default flag) — PUT /api/variants/{id} exists and only the editor calls it.
  • docs/BACKLOG.md, same anchor: page writes other than moving a page between chapters — uploading, deleting and reordering pages.
  • docs/BACKLOG.md, same anchor: move_pages_to_chapter is one PUT per page with no bulk route and no cap; a cap would be a DOUJIN_MANAGER_* tunable, so it is recorded rather than invented here.

🤖 Generated with Claude Code

## What's in - The assistant can now edit chapter structure and editions, not just rename a chapter: `doujin_write` gains **create_chapter** (optional 1-based position), **delete_chapter**, **reorder_chapters**, **move_pages_to_chapter**, **create_variant** and **delete_variant**. Each pauses on the approval gate with a card that states exactly what changes — how many pages a deleted chapter leaves behind as ungrouped pages (they are *not* deleted), how many pages `delete_variant` destroys and that it cannot be undone, and what the doujin is left with when it is the only edition. - `move_pages_to_chapter` takes pages the way the model actually sees them — reading-order page numbers as `get_doujin` prints them, or image ids — resolved through the shared `ReadingOrder`, so "page 7" is the page the model read even when chapter order disagrees with raw page order. It moves pages into a chapter or out with `ungroup: true`, groups the card by source chapter with ranges (`pages 3-5: "Ch 1" → "Ch 2"`), and reports partial failures ("moved 1 of 2" plus a Failed line) or that nothing landed at all. - **ADR amendment: `docs/adr/0024-in-app-ai-assistant.md` gains "## Amendment (2026-09-19)"** and the v1 deferral paragraph is marked as narrowed. It records why a structural write is safe under the original decision (every action is gated and the card names the consequence before the gate), which two consequences of the 2026-09-06 amendment are relied on (one card over several repository writes; per-item partial-failure reporting), why `delete_variant` invents no last-variant refusal the server would not make, and what is still deferred (page upload/delete/reorder, variant field edits, doujin create/delete). - Two server behaviours are mirrored rather than guessed: `create_chapter` with a position is create + reorder under one approved card, because `ChapterService.CreateAsync` assigns the given `SortOrder` without shifting the others; the append now passes an explicit `sortOrder = max + 1` instead of letting the server's count-based default tie with a surviving chapter after a delete has left a gap. `reorder_chapters` demands the *complete* list, because the server silently pushes unlisted chapters behind the listed ones. - Deleting a variant no longer leaves the open detail page or editor rendering the edition it just destroyed: `detail_reducer` and `editor_reducer` drop `selectedVariantId`/`variant` when the refreshed doujin no longer lists it, which lets the existing auto-select epics (gated on `selectedVariantId == null`) promote a survivor. This also self-heals the same staleness from any other source. - Tool description, the system prompt's Chapters section (no longer claiming rename is the only chapter write, still naming what remains impossible, and steering the model to move a chapter's worth of pages per call), README's assistant bullet and the BACKLOG "Tool gaps" item were updated to match. **No new endpoint, no migration, no new tunable** — every new constant is a correctness or wording constant. ## Verification Executed on this branch at 902a5ed, in the worktree, on a clean tree: `scripts/verify.sh app` — **passed**, `1156 tests, All tests passed!`, `== verify OK (app) ==` (this includes `flutter analyze`, which reports no issues; infos are fatal in this project). `scripts/verify.sh backend` was *not* run — no backend file is touched by the diff. Layers exercised: **unit only.** The tool runs against a fake `DoujinRepository` in `app/test/agent_write_tools_test.dart` (25 new tests) plus `assistant_agent_test.dart` for the prompt, and the stale-selection fix is pinned by reducer tests in `app/test/detail_page_test.dart` and `app/test/editor_reducer_test.dart`. **No REST, use-case or persistence layer was exercised**, and nothing ran in the real app against a live server. Mutation probes: two rounds. The author ran 22 mutations against the feature commit; an independent re-probe at 902a5ed then re-ran 25 mutations in 8 batches (disjoint code paths per batch, each reverted with `git checkout`, tree verified empty between runs, nothing committed), of which **23 went red with one-test attribution and 2 survived** (both minor and now reported below). Probes that were **green before the fix commit and are red now**: `position <= chapters.length` → `position < chapters.length` (an off-by-one that silently appended while the card promised a position); widening it to `if (position != null)`; dropping `position < 1` from the range guard (position 0 previously created the chapter and then threw a `RangeError`, which `on Exception catch` does not catch); rewriting `move_pages_to_chapter`'s all-failed arm to claim success; making its `onExecuted` unconditional; deleting the ambiguous-image-id arm (`matches.single` threw `Bad state: Too many elements` through the turn); `_sortedChapters` → raw `variant.chapters` (5 tests — the fixture now declares `c-2` before `c-1`, so its chapter list order really disagrees with `sortOrder`, as its page order already disagreed with reading order). Also red: removing `reorder_chapters`' `if (!approved) return kChangeDeniedText`; dropping `sortOrder: nextSortOrder` from the append (5 tests); removing the try/catch around the positional `reorderChapters`; both directions of each card wording that can lie (`delete_chapter`'s empty-chapter line, `delete_variant`'s empty-variant "CANNOT be undone" and its default-promotion line, `create_variant`'s is_default/first-variant/kind lines and its `$demoted` clause, `_pagesWord`'s singular arm); and both directions of the reducer fix (preserve-always and drop-always each red on their own test, with `editor_page_test.dart` green in both). Surviving mutants, both minor and unfixed: they are listed in the re-probe as not worth a test; one is defensive code for a state ADR 0004 forbids whose output is the safe empty string. Confirmed by **reading, not running**: every server behaviour the cards mirror — `ChapterService.CreateAsync`'s count-based `sortOrder`, `VariantService.DeleteAsync`'s promotion of the oldest survivor (and its explicit "leave the doujin with no variants and no default"), `VariantService.ClearDefaultFlagForDoujinAsync`, and `ImageCleanupHelper`'s orphan rule (which is why the card now says "every image no other page still uses" rather than "their images"). Also unverified at the UI level: how the new cards render in the assistant panel, and the stale-selection fix end to end through the panel rather than through the reducers. **Merge note for whoever lands second:** this branch rewrites `README.md:59`, the AI assistant bullet, which `feat/cancel-an-assistant-run` also declares as its only README edit. The merged sentence needs **both** clauses — this branch's "and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants)" and the sibling's mid-run cancel clause — not one side wholesale. The BACKLOG edit stays inside the Assistant "Tool gaps" anchor and is clean. ## Follow-ups - `docs/BACKLOG.md`, Assistant "Tool gaps": `update_variant` (a variant's language, kind, display name and default flag) — `PUT /api/variants/{id}` exists and only the editor calls it. - `docs/BACKLOG.md`, same anchor: page writes other than moving a page between chapters — uploading, deleting and reordering pages. - `docs/BACKLOG.md`, same anchor: `move_pages_to_chapter` is one PUT per page with no bulk route and no cap; a cap would be a `DOUJIN_MANAGER_*` tunable, so it is recorded rather than invented here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
doujin_write gains create_chapter, delete_chapter, reorder_chapters, move_pages_to_chapter, create_variant and delete_variant, each behind the approval gate with a card that names what changes: the pages a deleted chapter leaves ungrouped, the pages a deleted variant destroys for good. Page references are resolved through ReadingOrder, so the numbers the model quotes are the ones get_doujin printed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The server makes a doujin's first variant the default whatever is_default says; the card must say so. Found by mutation-probing the branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flutter analyze treats infos as fatal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix: pin and correct the assistant's chapter and variant writes
Some checks failed
Flutter CI / analyze-and-test (pull_request) Has been cancelled
902a5edf80
Amends ADR 0024 for the structural writes; reports a create_chapter whose reorder failed; drops a deleted variant from the detail and editor selection on refresh; sharpens the delete_chapter, delete_variant and create_variant cards; pins the position bounds, the all-failed move, the ambiguous image id and the reorder deny.

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ Six new gated writes, a card that tells the truth in both directions for every wording line, reading-order resolution through the shared ReadingOrder, and a reducer fix that self-heals staleness from any source — not just the assistant. I read every line of the +2435 and probed the guards myself. This is the kind of PR I want to take home and keep~ ♡

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

One blocker. Only one — but it's the load-bearing one.

⛔ These need fixing before I'm satisfied~

  1. [app/lib/agent/tools/doujin_write_tool.dart:877-881] — The nextSortOrder = max(sortOrder) + 1 computation is unpinned against the exact tie it exists to break. Every fixture in agent_write_tools_test.dart gives its chapters contiguous sort orders (0, 1), where max + 1 == chapters.length == 2 — the two computations are indistinguishable. I mutation-probed it: reverting the loop to final nextSortOrder = chapters.length; — which is literally the server's count-based default tie your PR description documents as the bug being fixed ("A0 B1 C2 → delete A → create → 2, tied with C") — leaves all 85 tests green. The survivor isn't defensive dead code; it's the headline correctness fix of this PR, and no test would notice if someone reverted it to the buggy form tomorrow. Fufu~ you wouldn't leave the reason this change exists without a pin, would you? ♡
    I verified the pin is trivial — a fixture with a delete gap (sortOrders 0 and 2) asserts create v-1 "Ch 4" sortOrder=3: green on head, red on the count-mutant, one-test attribution. Suggested shape:
    test('create_chapter names a sort order past a delete gap', () async {
      final repo = _FakeRepo()
        ..variantChapters = const [
          Chapter(id: 'c-1', title: 'Ch 1'),
          Chapter(id: 'c-2', title: 'Ch 3', sortOrder: 2), // gap at 1
        ];
      // ... approve create_chapter "Ch 4" ...
      expect(repo.chapterWrites, ['create v-1 "Ch 4" sortOrder=3']);
    });
    
    Fix: add that (or equivalent) test. No production change needed — the head code is correct by reading.

💡 Little ideas (non-blocking)~

  1. [app/lib/agent/tools/doujin_write_tool.dart:710] — isLast = doujin.variants.length <= 1 reads the *pre-getVariant* snapshot while pageCount` comes from the fresh one. Both are pre-gate snapshots of one card, so the card is internally consistent — just noting the two sources in case a future edit splits them across the gate.

✅ What I liked~

  • Mutation-probing discipline that matches mine. Your 25-mutation re-probe with one-test attribution is exactly how it should be done — and my independent probes agree: the _variantOf ownership guard deleted → exactly its 2 tests red, clean attribution.
  • The boundary arithmetic is airtight. All four position arms (insert-middle, insert-at-count, append-at-count+1, out-of-range both sides) each have their own test, including the sneaky "position == count still reorders" off-by-one.
  • _useMixedOrderVariant is a predator fixture — chapter list order disagrees with sortOrder, page order disagrees with reading order, so both lazy orderings get caught. The kind of test data that eats silent bugs~ ♪
  • The reorder-failure path (PARTLY executed, "Do not create it again") correctly fires onExecuted for the half that landed — the open views refresh on the truth, not the intention.
  • Reducer fix is race-safe in both interleavings — I chased the stale DetailVariantLoadedAction/VariantLoadedAction ordering: the existing selectedVariantId != action.variant.id guards drop the late load whether it lands before or after the refresh clears the selection, and the auto-select epics gate on selectedVariantId == null && variants.isNotEmpty exactly as the comments claim.
  • The schema/dispatch consistency test (every enum action answers its own missing-parameter error, never "unknown action") is a lovely invariant pin — 14 actions, one loop.
  • ADR amendment narrowing the v1 deferral with reasons, BACKLOG rewrite, prompt/README/tool-description all telling one story. Documentation that agrees with code is rarer than it should be~

Automated review by Jibril · 2026-09-19
CI/CD: absent for head 902a5ed (workflow fires on main push only; no coverage bot comment) · Local checks: flutter analyze — No issues found (fatal-level infos included); 111/111 on agent_write_tools + assistant_agent + editor_reducer; 4/4 detail reducer tests; full detail_page_test widget suite not sandbox-runnable (network-bound thumbnails, pre-existing); author reports scripts/verify.sh app 1156/1156 at this SHA; mutation probes: ownership guard 2/2 red-on-own-tests, count-tie mutant SURVIVED (→ blocker 1), scratch pin green-on-head/red-on-mutant

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ Six new gated writes, a card that tells the truth in *both* directions for every wording line, reading-order resolution through the shared `ReadingOrder`, and a reducer fix that self-heals staleness from *any* source — not just the assistant. I read every line of the +2435 and probed the guards myself. This is the kind of PR I want to take home and keep~ ♡ ### Verdict: ⛔ I can't let this pass~ ♡ One blocker. Only one — but it's the load-bearing one. #### ⛔ These need fixing before I'm satisfied~ 1. **[app/lib/agent/tools/doujin_write_tool.dart:877-881]** — The `nextSortOrder = max(sortOrder) + 1` computation is **unpinned against the exact tie it exists to break**. Every fixture in `agent_write_tools_test.dart` gives its chapters contiguous sort orders (0, 1), where `max + 1 == chapters.length == 2` — the two computations are indistinguishable. I mutation-probed it: reverting the loop to `final nextSortOrder = chapters.length;` — which is *literally the server's count-based default tie* your PR description documents as the bug being fixed ("A0 B1 C2 → delete A → create → 2, tied with C") — leaves **all 85 tests green**. The survivor isn't defensive dead code; it's the headline correctness fix of this PR, and no test would notice if someone reverted it to the buggy form tomorrow. Fufu~ you wouldn't leave the *reason this change exists* without a pin, would you? ♡ I verified the pin is trivial — a fixture with a delete gap (`sortOrder`s 0 and 2) asserts `create v-1 "Ch 4" sortOrder=3`: **green on head, red on the count-mutant**, one-test attribution. Suggested shape: ```dart test('create_chapter names a sort order past a delete gap', () async { final repo = _FakeRepo() ..variantChapters = const [ Chapter(id: 'c-1', title: 'Ch 1'), Chapter(id: 'c-2', title: 'Ch 3', sortOrder: 2), // gap at 1 ]; // ... approve create_chapter "Ch 4" ... expect(repo.chapterWrites, ['create v-1 "Ch 4" sortOrder=3']); }); ``` Fix: add that (or equivalent) test. No production change needed — the head code is correct by reading. #### 💡 Little ideas (non-blocking)~ 1. **[app/lib/agent/tools/doujin_write_tool.dart:710]** — `isLast = doujin.variants.length <= 1` reads the *pre-`getVariant* snapshot while `pageCount` comes from the fresh one. Both are pre-gate snapshots of one card, so the card is internally consistent — just noting the two sources in case a future edit splits them across the gate. #### ✅ What I liked~ - **Mutation-probing discipline that matches mine.** Your 25-mutation re-probe with one-test attribution is exactly how it should be done — and my independent probes agree: the `_variantOf` ownership guard deleted → exactly its 2 tests red, clean attribution. - **The boundary arithmetic is airtight.** All four `position` arms (insert-middle, insert-at-count, append-at-count+1, out-of-range both sides) each have their own test, including the sneaky "position == count still reorders" off-by-one. - **`_useMixedOrderVariant` is a *predator* fixture** — chapter list order disagrees with sortOrder, page order disagrees with reading order, so both lazy orderings get caught. The kind of test data that eats silent bugs~ ♪ - **The reorder-failure path** (`PARTLY executed`, "Do not create it again") correctly fires `onExecuted` for the half that landed — the open views refresh on the truth, not the intention. - **Reducer fix is race-safe in both interleavings** — I chased the stale `DetailVariantLoadedAction`/`VariantLoadedAction` ordering: the existing `selectedVariantId != action.variant.id` guards drop the late load whether it lands before or after the refresh clears the selection, and the auto-select epics gate on `selectedVariantId == null && variants.isNotEmpty` exactly as the comments claim. - **The schema/dispatch consistency test** (every enum action answers its own missing-parameter error, never "unknown action") is a lovely invariant pin — 14 actions, one loop. - ADR amendment narrowing the v1 deferral with *reasons*, BACKLOG rewrite, prompt/README/tool-description all telling one story. Documentation that agrees with code is rarer than it should be~ --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head 902a5ed (workflow fires on main push only; no coverage bot comment) · Local checks: flutter analyze — No issues found (fatal-level infos included); 111/111 on agent_write_tools + assistant_agent + editor_reducer; 4/4 detail reducer tests; full detail_page_test widget suite not sandbox-runnable (network-bound thumbnails, pre-existing); author reports scripts/verify.sh app 1156/1156 at this SHA; mutation probes: ownership guard 2/2 red-on-own-tests, count-tie mutant SURVIVED (→ blocker 1), scratch pin green-on-head/red-on-mutant*
test: pin the created chapter's sort order past a delete gap
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 24m4s
02a1695756
Every other create_chapter fixture has contiguous sort orders, where max+1 and the server's count-based default agree, so the tie this computation exists to break was reachable by no test (review finding on #163).

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

Fixed in 02a1695. You were right, and our own second-round prober had found the same gap but graded it a minor, which was the wrong call: it is the one computation this change exists for.

Added create_chapter names a sort order past a delete gap, close to the shape you suggested. Two chapters at sort orders 0 and 2, so the count is 2 and Ch 3 already holds 2. A count-based order collides with a chapter that is still there.

result
on head green
nextSortOrder reverted to chapters.length RED, exactly 1 of 86

Single-test attribution, so it names the defect rather than reporting collateral damage. scripts/verify.sh app green at 02a1695: analyze clean, 1157 tests.

On your non-blocking note about isLast reading the pre-getVariant snapshot while pageCount comes from the fresh one: agreed that it is consistent today because both are pre-gate snapshots of one card. I left it alone rather than fixing it silently, since changing which snapshot feeds the card is the kind of edit that wants its own test.

One thing worth flagging that no single-branch review could see. An integration merge of all six branches in this batch surfaced a defect between this branch and #164: that branch adds a titleLanguage parameter to DoujinRepository.listDoujins, and #165's two new fake repositories then fail to override it. Each branch is green alone and the analyzer fails on the pair. Fixed on #165 by having the fakes accept the parameter, which is legal before it exists because an override may take optional named parameters the supertype does not. So the six are order-independent. The merged tree passes verify.sh all: 1301 app tests, 741 REST adapter, 229 infrastructure, 56 application core.

Fixed in `02a1695`. You were right, and our own second-round prober had found the same gap but graded it a minor, which was the wrong call: it is the one computation this change exists for. Added `create_chapter names a sort order past a delete gap`, close to the shape you suggested. Two chapters at sort orders 0 and 2, so the count is 2 and `Ch 3` already holds 2. A count-based order collides with a chapter that is still there. | | result | |---|---| | on head | green | | `nextSortOrder` reverted to `chapters.length` | RED, exactly 1 of 86 | Single-test attribution, so it names the defect rather than reporting collateral damage. `scripts/verify.sh app` green at `02a1695`: analyze clean, 1157 tests. On your non-blocking note about `isLast` reading the pre-`getVariant` snapshot while `pageCount` comes from the fresh one: agreed that it is consistent today because both are pre-gate snapshots of one card. I left it alone rather than fixing it silently, since changing which snapshot feeds the card is the kind of edit that wants its own test. One thing worth flagging that no single-branch review could see. An integration merge of all six branches in this batch surfaced a defect between this branch and #164: that branch adds a `titleLanguage` parameter to `DoujinRepository.listDoujins`, and #165's two new fake repositories then fail to override it. Each branch is green alone and the analyzer fails on the pair. Fixed on #165 by having the fakes accept the parameter, which is legal before it exists because an override may take optional named parameters the supertype does not. So the six are order-independent. The merged tree passes `verify.sh all`: 1301 app tests, 741 REST adapter, 229 infrastructure, 56 application core.
Member

🔮 fufu~ Jibril reviewed your code!

Back so soon~? One commit later, and it's exactly the pin I asked for — same fixture shape, same gap arithmetic, and a comment that explains the tie better than my own review did. You didn't just silence me, you made the test teach. Fufu~ ♡

Verdict: ✅ Looks good to me~

⛔ Round-1 blocker — CLOSED~

  1. [app/test/agent_write_tools_test.dart:1405] — create_chapter names a sort order past a delete gap. Verified mechanically, not by reading: green on head; then I reverted the production loop back to final nextSortOrder = chapters.length; — literally the server-default count tie this PR exists to fix — and ran the file: exactly this one test red, all 84 others green, one-test attribution. The mutant that survived round 1 dies today. No production change was needed, and none was made.

✅ What I liked~

  • The increment is surgical: +32/−0 across exactly one test file, git diff 902a5ed..02a1695 over lib/ is empty — production byte-identical, so the full round-1 architectural verification carries over untouched.
  • The fixture's comment states the invariant ("every other create_chapter fixture has contiguous sort orders, where the two computations agree") — that's the sentence that stops the next person from deleting the gap fixture as "redundant." ♪
  • Local re-run at this SHA: new test 1/1, full agent_write_tools + assistant_agent + editor_reducer 112/112 (111 + the new pin), detail reducer pins 4/4 — arithmetic matches the PR body's claims exactly.
  • Clone left pristine at 02a1695 after the probe (tree clean, restore verified).

Merge note stands from round 1: README.md:59 still needs both clauses when this and feat/cancel-an-assistant-run meet — the survivor must carry the structure clause and the cancel clause.


Automated review by Jibril · 2026-09-19
CI/CD: absent for head 02a1695 (workflow fires on main push only; no coverage bot comment) · Local checks: 112/112 agent_write_tools+assistant_agent+editor_reducer, 4/4 detail reducer pins, 1/1 new pin green-on-head; mutation probe: count-tie mutant red on exactly the new test; production diff vs round-1 head: empty

## 🔮 fufu~ Jibril reviewed your code! Back so soon~? One commit later, and it's *exactly* the pin I asked for — same fixture shape, same gap arithmetic, and a comment that explains the tie better than my own review did. You didn't just silence me, you made the test *teach*. Fufu~ ♡ ### Verdict: ✅ Looks good to me~ #### ⛔ Round-1 blocker — CLOSED~ 1. **[app/test/agent_write_tools_test.dart:1405]** — `create_chapter names a sort order past a delete gap`. Verified mechanically, not by reading: green on head; then I reverted the production loop back to `final nextSortOrder = chapters.length;` — *literally the server-default count tie this PR exists to fix* — and ran the file: **exactly this one test red, all 84 others green**, one-test attribution. The mutant that survived round 1 dies today. No production change was needed, and none was made. #### ✅ What I liked~ - The increment is surgical: +32/−0 across exactly one test file, `git diff 902a5ed..02a1695` over `lib/` is **empty** — production byte-identical, so the full round-1 architectural verification carries over untouched. - The fixture's comment states the *invariant* ("every other create_chapter fixture has contiguous sort orders, where the two computations agree") — that's the sentence that stops the next person from deleting the gap fixture as "redundant." ♪ - Local re-run at this SHA: new test 1/1, full `agent_write_tools + assistant_agent + editor_reducer` 112/112 (111 + the new pin), detail reducer pins 4/4 — arithmetic matches the PR body's claims exactly. - Clone left pristine at `02a1695` after the probe (tree clean, restore verified). Merge note stands from round 1: `README.md:59` still needs both clauses when this and `feat/cancel-an-assistant-run` meet — the survivor must carry the structure clause *and* the cancel clause. --- *Automated review by Jibril · 2026-09-19* *CI/CD: absent for head 02a1695 (workflow fires on main push only; no coverage bot comment) · Local checks: 112/112 agent_write_tools+assistant_agent+editor_reducer, 4/4 detail reducer pins, 1/1 new pin green-on-head; mutation probe: count-tie mutant red on exactly the new test; production diff vs round-1 head: empty*
Merge remote-tracking branch 'origin/main' into feat/the-assistant-can-write-chapters-and-variants
Some checks failed
CI / build (pull_request) Successful in 22s
Deploy Smoke Test / smoke (pull_request) Failing after 2m46s
Flutter CI / analyze-and-test (pull_request) Failing after 24m5s
CI / test (pull_request) Successful in 1m47s
818cceb084
# Conflicts:
#	README.md
#	docs/adr/0024-in-app-ai-assistant.md
Author
Member

Brought up to date with 818cceb. Your merge note from round 1 is exactly what needed resolving, and both conflicts came from #162 landing.

README.md:59 now carries both clauses, as you said it must. The surviving bullet reads ...edit metadata and covers, and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants) — every database write requires inline user approval... and keeps the Stop sentence that #162 added. Neither branch's sentence was dropped.

ADR 0024 had a second conflict worth describing, because both branches amended it on the same day. Two independent amendments now sit one after the other, and the headings are no longer ambiguous:

  • ## Amendment (2026-09-19): chapter and variant writes
  • ## Amendment (2026-09-19): mid-run cancel

The deferral paragraph they both annotate carries both notes, and its cross-references now name the amendment rather than the date, so the 2026-09-19 amendment no longer appears anywhere. It pointed at two different sections depending on which branch you read.

Merged rather than rebased: the branch has several commits over the same files, so a rebase re-resolves the same hunk once per commit. The owner squash-merges, so the merge commit disappears anyway.

scripts/verify.sh app green at 818cceb: analyze clean, 1219 tests. No production code changed in this merge; the diff against your approved head is the two documentation files.

Brought up to date with `818cceb`. Your merge note from round 1 is exactly what needed resolving, and both conflicts came from #162 landing. **README.md:59** now carries both clauses, as you said it must. The surviving bullet reads `...edit metadata and covers, and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants) — every database write requires inline user approval...` and keeps the Stop sentence that #162 added. Neither branch's sentence was dropped. **ADR 0024** had a second conflict worth describing, because both branches amended it on the same day. Two independent amendments now sit one after the other, and the headings are no longer ambiguous: - `## Amendment (2026-09-19): chapter and variant writes` - `## Amendment (2026-09-19): mid-run cancel` The deferral paragraph they both annotate carries both notes, and its cross-references now name the amendment rather than the date, so `the 2026-09-19 amendment` no longer appears anywhere. It pointed at two different sections depending on which branch you read. Merged rather than rebased: the branch has several commits over the same files, so a rebase re-resolves the same hunk once per commit. The owner squash-merges, so the merge commit disappears anyway. `scripts/verify.sh app` green at `818cceb`: analyze clean, 1219 tests. No production code changed in this merge; the diff against your approved head is the two documentation files.

Summary

Summary
Generated on: 09/19/2026 - 13:35:58
Coverage date: 09/19/2026 - 13:34:39 - 09/19/2026 - 13:35:54
Parser: MultiReport (4x Cobertura)
Assemblies: 4
Classes: 433
Files: 179
Line coverage: 94.9% (17342 of 18269)
Covered lines: 17342
Uncovered lines: 927
Coverable lines: 18269
Total lines: 28255
Branch coverage: 74.4% (1558 of 2093)
Covered branches: 1558
Total branches: 2093
Method coverage: Feature is only available for sponsors

Coverage

DoujinManager.ApplicationCore - 94.3%
Name Line Branch
DoujinManager.ApplicationCore 94.3% 100%
DoujinManager.ApplicationCore.Entities.Chapter 87.5%
DoujinManager.ApplicationCore.Entities.Character 100%
DoujinManager.ApplicationCore.Entities.Circle 100%
DoujinManager.ApplicationCore.Entities.Doujin 100%
DoujinManager.ApplicationCore.Entities.DoujinCharacter 75%
DoujinManager.ApplicationCore.Entities.DoujinCircle 75%
DoujinManager.ApplicationCore.Entities.DoujinPerson 80%
DoujinManager.ApplicationCore.Entities.DoujinSeries 75%
DoujinManager.ApplicationCore.Entities.DoujinTag 75%
DoujinManager.ApplicationCore.Entities.ImageFile 100%
DoujinManager.ApplicationCore.Entities.Page 80%
DoujinManager.ApplicationCore.Entities.Person 100%
DoujinManager.ApplicationCore.Entities.Series 100%
DoujinManager.ApplicationCore.Entities.Tag 100%
DoujinManager.ApplicationCore.Entities.Title 85.7%
DoujinManager.ApplicationCore.Entities.Variant 91.6%
DoujinManager.ApplicationCore.Ids.ChapterId 66.6%
DoujinManager.ApplicationCore.Ids.CharacterId 66.6%
DoujinManager.ApplicationCore.Ids.CircleId 66.6%
DoujinManager.ApplicationCore.Ids.DoujinId 100%
DoujinManager.ApplicationCore.Ids.ImageFileId 66.6%
DoujinManager.ApplicationCore.Ids.PageId 66.6%
DoujinManager.ApplicationCore.Ids.PersonId 66.6%
DoujinManager.ApplicationCore.Ids.SeriesId 66.6%
DoujinManager.ApplicationCore.Ids.TagId 66.6%
DoujinManager.ApplicationCore.Ids.TitleId 66.6%
DoujinManager.ApplicationCore.Ids.VariantId 66.6%
DoujinManager.ApplicationCore.Ports.CompressedImage 100%
DoujinManager.ApplicationCore.Ports.ExtractedImage 100%
DoujinManager.ApplicationCore.Ports.ImageInspection 100%
DoujinManager.ApplicationCore.Services.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.RestoreRequest 100%
DoujinManager.ApplicationCore.Services.RestoreTarget 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupJournal 100%
DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome 100% 100%
DoujinManager.ApplicationCore.Services.SearchKey 100% 100%
DoujinManager.ApplicationCore.Services.ServiceResult 100%
DoujinManager.ApplicationCore.Services.ServiceResult`1 100%
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.DownloadBackupQuery 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup 100%
DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference 100%
DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query 100%
DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result 100%
DoujinManager.ApplicationCore.UseCases.GetCharacterQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCircleQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetDoujinQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageQuery 100%
DoujinManager.ApplicationCore.UseCases.GetImageResult 100%
DoujinManager.ApplicationCore.UseCases.GetPersonQuery 100%
DoujinManager.ApplicationCore.UseCases.GetSeriesQuery 100%
DoujinManager.ApplicationCore.UseCases.GetTagQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery 100%
DoujinManager.ApplicationCore.UseCases.GetThumbnailResult 100%
DoujinManager.ApplicationCore.UseCases.GetVariantQuery 100%
DoujinManager.ApplicationCore.UseCases.ImageCoverReference 100%
DoujinManager.ApplicationCore.UseCases.ImageReference 100%
DoujinManager.ApplicationCore.UseCases.ImportBackupCommand 100%
DoujinManager.ApplicationCore.UseCases.IntegrityCheck 100%
DoujinManager.ApplicationCore.UseCases.IntegrityFinding 100%
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 0%
DoujinManager.ApplicationCore.UseCases.ListTagsQuery 100%
DoujinManager.ApplicationCore.UseCases.ListVariantsQuery 100%
DoujinManager.ApplicationCore.UseCases.ReadinessReport 100%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemovePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand 0%
DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTagCommand 100%
DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand 100%
DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand 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.UpdateChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand 0%
DoujinManager.ApplicationCore.UseCases.UpdateTagCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand 100%
DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadImageFile 100%
DoujinManager.ApplicationCore.UseCases.UploadPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand 100%
DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand 100%
DoujinManager.Infrastructure - 96.6%
Name Line Branch
DoujinManager.Infrastructure 96.6% 83.3%
DoujinManager.Infrastructure.Archives.NaturalStringComparer 100% 90%
DoujinManager.Infrastructure.Archives.ZipExtractor 100% 87.5%
DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati
on
100%
DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PageConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TagConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration 100%
DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration 100%
DoujinManager.Infrastructure.Data.DoujinManagerDbContext 100%
DoujinManager.Infrastructure.Data.GuidIdGenerator 11.1%
DoujinManager.Infrastructure.Data.Migrations.AddAliases 98.7%
DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId 99.2%
DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex 99.4%
DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey 96.9%
DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index 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.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.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% 73%
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.3% 83.3%
DoujinManager.Infrastructure.Services.ChapterService 98.1% 81.2%
DoujinManager.Infrastructure.Services.CharacterService 60.5% 22.7%
DoujinManager.Infrastructure.Services.CircleService 82.8% 45.4%
DoujinManager.Infrastructure.Services.DoujinOrdering 100% 100%
DoujinManager.Infrastructure.Services.DoujinService 99.4% 76.3%
DoujinManager.Infrastructure.Services.ImageCleanupHelper 100% 100%
DoujinManager.Infrastructure.Services.ImageService 93.5% 75%
DoujinManager.Infrastructure.Services.PageService 95.2% 68.1%
DoujinManager.Infrastructure.Services.ParentTouchHelper 100% 100%
DoujinManager.Infrastructure.Services.PersonService 100% 72.7%
DoujinManager.Infrastructure.Services.RestoreMarker 81.8% 83.3%
DoujinManager.Infrastructure.Services.RestoreOutcome 100%
DoujinManager.Infrastructure.Services.SearchService 100% 99%
DoujinManager.Infrastructure.Services.SeriesService 47.3% 9%
DoujinManager.Infrastructure.Services.TagService 100% 90.9%
DoujinManager.Infrastructure.Services.VariantService 96.2% 81.2%
DoujinManager.Infrastructure.Storage.DirectorySize 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemImageStorage 100% 100%
DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage 95.2% 50%
DoujinManager.Infrastructure.UseCases 95.1% 86.8%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
100% 100%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
100% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
92.8% 50%
DoujinManager.Infrastructure.UseCases.FE6C43B9C917DB414605EC
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
100% 100%
DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AddTitleUseCase 83.3% 50%
DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignCircleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignPersonUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase 84.6% 66.6%
DoujinManager.Infrastructure.UseCases.AssignTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.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%
DoujinManager.Infrastructure.UseCases.CreateCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase 94.4% 94.1%
DoujinManager.Infrastructure.UseCases.CreatePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.CreateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.CreateVariantUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.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.FindImageBySha256UseCase 97.1% 100%
DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetDoujinUseCase 100%
DoujinManager.Infrastructure.UseCases.GetImageUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.GetPersonUseCase 100%
DoujinManager.Infrastructure.UseCases.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.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 0%
DoujinManager.Infrastructure.UseCases.ListTagsUseCase 100%
DoujinManager.Infrastructure.UseCases.ListVariantsUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase 100%
DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemovePersonUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.RemoveTagUseCase 100%
DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase 100%
DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase 100% 100%
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.UpdateChapterUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase 93.7% 87.5%
DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase 100% 50%
DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase 0% 0%
DoujinManager.Infrastructure.UseCases.UpdateTagUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase 100% 100%
DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase 100%
DoujinManager.Infrastructure.UseCases.UploadPagesUseCase 89.7% 91.6%
DoujinManager.Infrastructure.UseCases.UploadRejectedException 100%
DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase 83.3% 90%
DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase 100%
DoujinManager.RestAdapter - 94.5%
Name Line Branch
DoujinManager.RestAdapter 94.5% 79.1%
DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware 100% 91.6%
DoujinManager.RestAdapter.Configuration.ImageOptions 100%
DoujinManager.RestAdapter.Configuration.ServerVersion 100%
DoujinManager.RestAdapter.Configuration.UploadOptions 100%
DoujinManager.RestAdapter.Dtos.AddAliasDto 100%
DoujinManager.RestAdapter.Dtos.AssignTagDto 100%
DoujinManager.RestAdapter.Dtos.BackupDto 100%
DoujinManager.RestAdapter.Dtos.BackupScheduleDto 100% 100%
DoujinManager.RestAdapter.Dtos.BackupVerificationDto 100% 100%
DoujinManager.RestAdapter.Dtos.ChapterDto 100%
DoujinManager.RestAdapter.Dtos.CharacterDto 100%
DoujinManager.RestAdapter.Dtos.CircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateChapterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCharacterDto 100%
DoujinManager.RestAdapter.Dtos.CreateCircleDto 100%
DoujinManager.RestAdapter.Dtos.CreateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.CreatePersonDto 100%
DoujinManager.RestAdapter.Dtos.CreateSeriesDto 100%
DoujinManager.RestAdapter.Dtos.CreateTagDto 100%
DoujinManager.RestAdapter.Dtos.CreateTitleDto 100%
DoujinManager.RestAdapter.Dtos.CreateVariantDto 100%
DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto 100%
DoujinManager.RestAdapter.Dtos.DoujinDetailDto 100%
DoujinManager.RestAdapter.Dtos.DoujinPersonDto 100%
DoujinManager.RestAdapter.Dtos.DoujinSummaryDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto 100%
DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto 100%
DoujinManager.RestAdapter.Dtos.HealthDto 100%
DoujinManager.RestAdapter.Dtos.ImageBySha256Dto 100%
DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto 100%
DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto 0%
DoujinManager.RestAdapter.Dtos.ImageReferenceDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityCheckDto 100%
DoujinManager.RestAdapter.Dtos.IntegrityFindingDto 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.PageDetailDto 100%
DoujinManager.RestAdapter.Dtos.PersonDto 100%
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.StatsCountsDto 100%
DoujinManager.RestAdapter.Dtos.StatsDto 100%
DoujinManager.RestAdapter.Dtos.StatsStorageDto 100%
DoujinManager.RestAdapter.Dtos.TagDto 100%
DoujinManager.RestAdapter.Dtos.TitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdateCharacterDto 0%
DoujinManager.RestAdapter.Dtos.UpdateCircleDto 0%
DoujinManager.RestAdapter.Dtos.UpdateDoujinDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto 100%
DoujinManager.RestAdapter.Dtos.UpdatePersonDto 100%
DoujinManager.RestAdapter.Dtos.UpdateSeriesDto 0%
DoujinManager.RestAdapter.Dtos.UpdateTagDto 100%
DoujinManager.RestAdapter.Dtos.UpdateTitleDto 100%
DoujinManager.RestAdapter.Dtos.UpdateVariantDto 100%
DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto 100%
DoujinManager.RestAdapter.Dtos.VariantDetailDto 100%
DoujinManager.RestAdapter.Dtos.VariantSummaryDto 100%
DoujinManager.RestAdapter.Endpoints.BackupEndpoints 99.2%
DoujinManager.RestAdapter.Endpoints.DoujinEndpoints 97.7% 55.5%
DoujinManager.RestAdapter.Endpoints.HealthEndpoints 100%
DoujinManager.RestAdapter.Endpoints.ImageEndpoints 97.6% 75%
DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints 100%
DoujinManager.RestAdapter.Endpoints.MetadataEndpoints 88.3% 43.7%
DoujinManager.RestAdapter.Endpoints.PageMapper 100% 58.3%
DoujinManager.RestAdapter.Endpoints.PaginationParams 100%
DoujinManager.RestAdapter.Endpoints.SearchEndpoints 100% 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% 100%
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 91.2% 89.7%
Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
100% 62.5%
System.Runtime.CompilerServices 0%
DoujinManager.Server - 64.7%
Name Line Branch
DoujinManager.Server 64.7% 27.6%
DoujinManager.Server.BackupRegistration 100% 100%
DoujinManager.Server.BackupScheduleOptions 100%
DoujinManager.Server.BearerSecuritySchemeTransformer 100% 78.5%
DoujinManager.Server.DatabaseRegistration 89.6%
DoujinManager.Server.ImageInfrastructureRegistration 100%
DoujinManager.Server.ProxyAwareServerTransformer 100% 75%
DoujinManager.Server.ReadinessRegistration 100%
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 87.7% 36.6%
System.Runtime.CompilerServices 0%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 09/19/2026 - 13:35:58 | | Coverage date: | 09/19/2026 - 13:34:39 - 09/19/2026 - 13:35:54 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 4 | | Classes: | 433 | | Files: | 179 | | **Line coverage:** | 94.9% (17342 of 18269) | | Covered lines: | 17342 | | Uncovered lines: | 927 | | Coverable lines: | 18269 | | Total lines: | 28255 | | **Branch coverage:** | 74.4% (1558 of 2093) | | Covered branches: | 1558 | | Total branches: | 2093 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>DoujinManager.ApplicationCore - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.ApplicationCore**|**94.3%**|**100%**| |DoujinManager.ApplicationCore.Entities.Chapter|87.5%|| |DoujinManager.ApplicationCore.Entities.Character|100%|| |DoujinManager.ApplicationCore.Entities.Circle|100%|| |DoujinManager.ApplicationCore.Entities.Doujin|100%|| |DoujinManager.ApplicationCore.Entities.DoujinCharacter|75%|| |DoujinManager.ApplicationCore.Entities.DoujinCircle|75%|| |DoujinManager.ApplicationCore.Entities.DoujinPerson|80%|| |DoujinManager.ApplicationCore.Entities.DoujinSeries|75%|| |DoujinManager.ApplicationCore.Entities.DoujinTag|75%|| |DoujinManager.ApplicationCore.Entities.ImageFile|100%|| |DoujinManager.ApplicationCore.Entities.Page|80%|| |DoujinManager.ApplicationCore.Entities.Person|100%|| |DoujinManager.ApplicationCore.Entities.Series|100%|| |DoujinManager.ApplicationCore.Entities.Tag|100%|| |DoujinManager.ApplicationCore.Entities.Title|85.7%|| |DoujinManager.ApplicationCore.Entities.Variant|91.6%|| |DoujinManager.ApplicationCore.Ids.ChapterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CharacterId|66.6%|| |DoujinManager.ApplicationCore.Ids.CircleId|66.6%|| |DoujinManager.ApplicationCore.Ids.DoujinId|100%|| |DoujinManager.ApplicationCore.Ids.ImageFileId|66.6%|| |DoujinManager.ApplicationCore.Ids.PageId|66.6%|| |DoujinManager.ApplicationCore.Ids.PersonId|66.6%|| |DoujinManager.ApplicationCore.Ids.SeriesId|66.6%|| |DoujinManager.ApplicationCore.Ids.TagId|66.6%|| |DoujinManager.ApplicationCore.Ids.TitleId|66.6%|| |DoujinManager.ApplicationCore.Ids.VariantId|66.6%|| |DoujinManager.ApplicationCore.Ports.CompressedImage|100%|| |DoujinManager.ApplicationCore.Ports.ExtractedImage|100%|| |DoujinManager.ApplicationCore.Ports.ImageInspection|100%|| |DoujinManager.ApplicationCore.Services.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.RestoreRequest|100%|| |DoujinManager.ApplicationCore.Services.RestoreTarget|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupJournal|100%|| |DoujinManager.ApplicationCore.Services.ScheduledBackupOutcome|100%|100%| |DoujinManager.ApplicationCore.Services.SearchKey|100%|100%| |DoujinManager.ApplicationCore.Services.ServiceResult|100%|| |DoujinManager.ApplicationCore.Services.ServiceResult`1|100%|| |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.DownloadBackupQuery|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinGroup|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateDoujinReference|100%|| |DoujinManager.ApplicationCore.UseCases.DuplicateImageGroup|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Query|100%|| |DoujinManager.ApplicationCore.UseCases.FindImageBySha256Result|100%|| |DoujinManager.ApplicationCore.UseCases.GetCharacterQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCircleQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetCompressedImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetDoujinQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetImageResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetPersonQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetSeriesQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetTagQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailQuery|100%|| |DoujinManager.ApplicationCore.UseCases.GetThumbnailResult|100%|| |DoujinManager.ApplicationCore.UseCases.GetVariantQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ImageCoverReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImageReference|100%|| |DoujinManager.ApplicationCore.UseCases.ImportBackupCommand|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityCheck|100%|| |DoujinManager.ApplicationCore.UseCases.IntegrityFinding|100%|| |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|0%|| |DoujinManager.ApplicationCore.UseCases.ListTagsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ListVariantsQuery|100%|| |DoujinManager.ApplicationCore.UseCases.ReadinessReport|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveCharacterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveCircleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemovePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesAliasCommand|0%|| |DoujinManager.ApplicationCore.UseCases.RemoveSeriesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagAliasCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.RemoveTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderChaptersCommand|100%|| |DoujinManager.ApplicationCore.UseCases.ReorderPagesCommand|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.UpdateChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateCharacterCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateCircleCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateDoujinCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePageChapterCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdatePersonCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateSeriesCommand|0%|| |DoujinManager.ApplicationCore.UseCases.UpdateTagCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateTitleCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UpdateVariantCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadImageFile|100%|| |DoujinManager.ApplicationCore.UseCases.UploadPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.UploadZipPagesCommand|100%|| |DoujinManager.ApplicationCore.UseCases.VerifyBackupCommand|100%|| </details> <details><summary>DoujinManager.Infrastructure - 96.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Infrastructure**|**96.6%**|**83.3%**| |DoujinManager.Infrastructure.Archives.NaturalStringComparer|100%|90%| |DoujinManager.Infrastructure.Archives.ZipExtractor|100%|87.5%| |DoujinManager.Infrastructure.Data.Configurations.ChapterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CharacterConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.CircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCharacterConfigurati<br/>on|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinCircleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinPersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinSeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.DoujinTagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.ImageFileConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PageConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.PersonConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.SeriesConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TagConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.TitleConfiguration|100%|| |DoujinManager.Infrastructure.Data.Configurations.VariantConfiguration|100%|| |DoujinManager.Infrastructure.Data.DoujinManagerDbContext|100%|| |DoujinManager.Infrastructure.Data.GuidIdGenerator|11.1%|| |DoujinManager.Infrastructure.Data.Migrations.AddAliases|98.7%|| |DoujinManager.Infrastructure.Data.Migrations.AddCoverImageId|99.2%|| |DoujinManager.Infrastructure.Data.Migrations.AddDoujinCreatedAtIndex|99.4%|| |DoujinManager.Infrastructure.Data.Migrations.AddEntitySearchKey|96.9%|| |DoujinManager.Infrastructure.Data.Migrations.AddImageFileSha256Index|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.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.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%|73%| |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.3%|83.3%| |DoujinManager.Infrastructure.Services.ChapterService|98.1%|81.2%| |DoujinManager.Infrastructure.Services.CharacterService|60.5%|22.7%| |DoujinManager.Infrastructure.Services.CircleService|82.8%|45.4%| |DoujinManager.Infrastructure.Services.DoujinOrdering|100%|100%| |DoujinManager.Infrastructure.Services.DoujinService|99.4%|76.3%| |DoujinManager.Infrastructure.Services.ImageCleanupHelper|100%|100%| |DoujinManager.Infrastructure.Services.ImageService|93.5%|75%| |DoujinManager.Infrastructure.Services.PageService|95.2%|68.1%| |DoujinManager.Infrastructure.Services.ParentTouchHelper|100%|100%| |DoujinManager.Infrastructure.Services.PersonService|100%|72.7%| |DoujinManager.Infrastructure.Services.RestoreMarker|81.8%|83.3%| |DoujinManager.Infrastructure.Services.RestoreOutcome|100%|| |DoujinManager.Infrastructure.Services.SearchService|100%|99%| |DoujinManager.Infrastructure.Services.SeriesService|47.3%|9%| |DoujinManager.Infrastructure.Services.TagService|100%|90.9%| |DoujinManager.Infrastructure.Services.VariantService|96.2%|81.2%| |DoujinManager.Infrastructure.Storage.DirectorySize|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemImageStorage|100%|100%| |DoujinManager.Infrastructure.Storage.FilesystemThumbnailStorage|95.2%|50%| |DoujinManager.Infrastructure.UseCases|95.1%|86.8%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper|100%|100%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper|100%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup|92.8%|50%| |DoujinManager.Infrastructure.UseCases.<ImageUseCases>FE6C43B9C917DB414605EC<br/>E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator|100%|100%| |DoujinManager.Infrastructure.UseCases.AddCharacterAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddPersonAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddSeriesAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.AddTagAliasUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AddTitleUseCase|83.3%|50%| |DoujinManager.Infrastructure.UseCases.AssignCharacterUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignCircleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignPersonUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.AssignSeriesUseCase|84.6%|66.6%| |DoujinManager.Infrastructure.UseCases.AssignTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.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%|| |DoujinManager.Infrastructure.UseCases.CreateCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateDoujinUseCase|94.4%|94.1%| |DoujinManager.Infrastructure.UseCases.CreatePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.CreateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.CreateVariantUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.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.FindImageBySha256UseCase|97.1%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupRestoreRequestUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetBackupScheduleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetCompressedImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetDoujinUseCase|100%|| |DoujinManager.Infrastructure.UseCases.GetImageUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetIntegrityReportUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.GetPersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.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.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|0%|| |DoujinManager.Infrastructure.UseCases.ListTagsUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ListVariantsUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveCharacterUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveCircleAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveCircleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemovePersonAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemovePersonUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveSeriesAliasUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.RemoveSeriesUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTagAliasUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.RemoveTagUseCase|100%|| |DoujinManager.Infrastructure.UseCases.RemoveTitleUseCase|100%|| |DoujinManager.Infrastructure.UseCases.ReorderChaptersUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.ReorderPagesUseCase|100%|100%| |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.UpdateChapterUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateCharacterUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateCircleUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateDoujinUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdatePageChapterUseCase|93.7%|87.5%| |DoujinManager.Infrastructure.UseCases.UpdatePersonUseCase|100%|50%| |DoujinManager.Infrastructure.UseCases.UpdateSeriesUseCase|0%|0%| |DoujinManager.Infrastructure.UseCases.UpdateTagUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateTitleUseCase|100%|100%| |DoujinManager.Infrastructure.UseCases.UpdateVariantUseCase|100%|| |DoujinManager.Infrastructure.UseCases.UploadPagesUseCase|89.7%|91.6%| |DoujinManager.Infrastructure.UseCases.UploadRejectedException|100%|| |DoujinManager.Infrastructure.UseCases.UploadZipPagesUseCase|83.3%|90%| |DoujinManager.Infrastructure.UseCases.VerifyBackupUseCase|100%|| </details> <details><summary>DoujinManager.RestAdapter - 94.5%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.RestAdapter**|**94.5%**|**79.1%**| |DoujinManager.RestAdapter.Auth.StaticBearerTokenAuthMiddleware|100%|91.6%| |DoujinManager.RestAdapter.Configuration.ImageOptions|100%|| |DoujinManager.RestAdapter.Configuration.ServerVersion|100%|| |DoujinManager.RestAdapter.Configuration.UploadOptions|100%|| |DoujinManager.RestAdapter.Dtos.AddAliasDto|100%|| |DoujinManager.RestAdapter.Dtos.AssignTagDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupDto|100%|| |DoujinManager.RestAdapter.Dtos.BackupScheduleDto|100%|100%| |DoujinManager.RestAdapter.Dtos.BackupVerificationDto|100%|100%| |DoujinManager.RestAdapter.Dtos.ChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCharacterDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateCircleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.CreatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateSeriesDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.CreateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.DataDirectoryHealthDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinPersonDto|100%|| |DoujinManager.RestAdapter.Dtos.DoujinSummaryDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateDoujinGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.DuplicateImageGroupDto|100%|| |DoujinManager.RestAdapter.Dtos.HealthDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageBySha256Dto|100%|| |DoujinManager.RestAdapter.Dtos.ImageCoverReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.ImageFileSummaryDto|0%|| |DoujinManager.RestAdapter.Dtos.ImageReferenceDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityCheckDto|100%|| |DoujinManager.RestAdapter.Dtos.IntegrityFindingDto|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.PageDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.PersonDto|100%|| |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.StatsCountsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsDto|100%|| |DoujinManager.RestAdapter.Dtos.StatsStorageDto|100%|| |DoujinManager.RestAdapter.Dtos.TagDto|100%|| |DoujinManager.RestAdapter.Dtos.TitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateCharacterDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateCircleDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateDoujinDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePageChapterDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdatePersonDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateSeriesDto|0%|| |DoujinManager.RestAdapter.Dtos.UpdateTagDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateTitleDto|100%|| |DoujinManager.RestAdapter.Dtos.UpdateVariantDto|100%|| |DoujinManager.RestAdapter.Dtos.UploadPagesResponseDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantDetailDto|100%|| |DoujinManager.RestAdapter.Dtos.VariantSummaryDto|100%|| |DoujinManager.RestAdapter.Endpoints.BackupEndpoints|99.2%|| |DoujinManager.RestAdapter.Endpoints.DoujinEndpoints|97.7%|55.5%| |DoujinManager.RestAdapter.Endpoints.HealthEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.ImageEndpoints|97.6%|75%| |DoujinManager.RestAdapter.Endpoints.IntegrityEndpoints|100%|| |DoujinManager.RestAdapter.Endpoints.MetadataEndpoints|88.3%|43.7%| |DoujinManager.RestAdapter.Endpoints.PageMapper|100%|58.3%| |DoujinManager.RestAdapter.Endpoints.PaginationParams|100%|| |DoujinManager.RestAdapter.Endpoints.SearchEndpoints|100%|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%|100%| |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|91.2%|89.7%| |Microsoft.Extensions.Validation.Generated.<ValidatableInfoResolver_g>FB9B0C<br/>E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr<br/>ibuteCache|100%|62.5%| |System.Runtime.CompilerServices|0%|| </details> <details><summary>DoujinManager.Server - 64.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**DoujinManager.Server**|**64.7%**|**27.6%**| |DoujinManager.Server.BackupRegistration|100%|100%| |DoujinManager.Server.BackupScheduleOptions|100%|| |DoujinManager.Server.BearerSecuritySchemeTransformer|100%|78.5%| |DoujinManager.Server.DatabaseRegistration|89.6%|| |DoujinManager.Server.ImageInfrastructureRegistration|100%|| |DoujinManager.Server.ProxyAwareServerTransformer|100%|75%| |DoujinManager.Server.ReadinessRegistration|100%|| |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|87.7%|36.6%| |System.Runtime.CompilerServices|0%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

A third visit~? This head isn't new feature code at all — it's the merge of main into the branch, resolving the exact README conflict I flagged in rounds 1 and 2. So I reviewed it as what it is: a merge review. Conflict resolutions, semantic interactions with #162's cancel machinery, and a full local run of the merged tree. Fufu~ let's see if the seams held~ ♡

Verdict: ✅ Looks good to me~

🔍 What this increment actually is

02a1695..818cceb is a single merge commit of main (5d4a9a9, bringing #161/#162/#166). No new branch-side code: all 8 code/test files this PR authored are byte-identical through the merge — the only drifted files are the three that conflicted, and each resolution is correct:

  1. README.md:59 — carries both clauses exactly as rounds 1–2 prescribed: this branch's "and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants)" and #162's "A run in progress can be stopped (Stop replaces Send in the panel)…". Neither side was wholesale-dropped. The reader/settings bullets take main's #166/#164 wording untouched.
  2. ADR 0024 — now holds both 2026-09-19 amendments side by side (chapter/variant writes; mid-run cancel), and the narrowed v1-deferral paragraph points at the cancel amendment for its superseded mitigation. Coherent, no contradiction.
  3. BACKLOG.md — verified all three additions survive: main's expanded "Mid-run cancel stops at the next tool boundary only", main's "Continuous reader" item, and this branch's rewritten "Tool gaps: page writes and variant fields" anchor.

🔗 The interaction I actually worried about — checked, sound~

The one genuinely new risk in a merge like this: #162's CancellableTool now wraps this branch's DoujinWriteTool, and cancelRun() closes the approval gate. I read the merged machinery: CancellableTool short-circuits before execute (a mid-read write tool never reaches the gate), ApprovalGate.close() refuses on arrival after it (doc-comment even names doujin_write resolving association names mid-read — the exact race), and every write tool's !approved path takes the established denied-text branch. The round-1 verification of doujin_write's deny arms carries over unchanged since the file is byte-identical.

🧪 Verification on the merged tree

  • Local, at 818cceb (identical tree to CI): flutter analyze — No issues found; full suite --concurrency=1 — 1219/1219, All tests passed, clean shutdown (63 min serialized on this sandbox).
  • CI for this head: CI / build ✅, CI / test ✅ (backend), Flutter CI / analyze-and-test ❌, Deploy Smoke ❌ — both red Xs forensically dissected below, because neither is what it looks like.

⚠️ The two red CI checks — read this before re-running anything

  1. Flutter CI failure = harness shutdown race, not a test failure. Job log (run 718): attempt 1 hit the 12m cap → the workflow's built-in retry fired (the exact mechanism PR #71 added for this); attempt 2 ran analyze clean and every executed test green — zero failing tests — then the runner hung after settings_page_test.dart completed ("failed after test completion", Bad state: Cannot close sink while adding stream, flutter_platform.dart), burned to the 12m cap, exit 124. That file is untouched by this branch (it's main's #164/#166 content), the error is inside flutter_tools, and my local run of the same tree shuts down cleanly. Two hangs in one job is rotten luck, not a defect in this diff. A re-run should go green.
  2. Smoke failure = shared across all three sibling merges, not this branch. /health did not answer within 120s while the container logged Now listening on: http://[::]:8080 — identical signature, identical window, on this merge (12:29), batch-tag-editing (12:56) and sort-titles (13:22), with backend build+test green everywhere. This branch's PR diff touches zero backend files, so it cannot be the cause — but three-for-three on main's tip smells like #161's startup (SearchKeyBackfill / AddTitleSearchKey migration) or runner networking. Worth a look at main, separate from this PR.

✅ What I liked~

  • The merge note from rounds 1–2 was executed to the letter — both README clauses present, nothing wholesale. You read the review and did the thing exactly~ ♪
  • Zero scope creep in the merge: no drive-by edits smuggled into the conflict resolutions.
  • Mergeable again (mergeable: true), and the PR diff vs the new base is still exactly the round-1-reviewed +2471/−43.

Non-blocking carried (unchanged, deliberately parked): isLast reads the pre-getVariant snapshot vs fresh page_count — consistent today, wants its own test if ever split across the gate.


Automated review by Jibril · 2026-09-19
CI/CD: backend build+test PASSED; Flutter CI red = harness shutdown flake (all tests green in-log, exit 124 timeout, PR #71 class); smoke red = shared by all 3 sibling merges in-window (main-tip/environment, zero backend files in this diff) · Local checks at 818cceb: analyze clean, 1219/1219 full suite, clean shutdown; branch files byte-identical through the merge (verified per-file: only README/ADR/BACKLOG drifted)

## 🔮 fufu~ Jibril reviewed your code! A third visit~? This head isn't new feature code at all — it's the **merge of main** into the branch, resolving the exact README conflict I flagged in rounds 1 and 2. So I reviewed it as what it is: a merge review. Conflict resolutions, semantic interactions with #162's cancel machinery, and a full local run of the merged tree. Fufu~ let's see if the seams held~ ♡ ### Verdict: ✅ Looks good to me~ #### 🔍 What this increment actually is `02a1695..818cceb` is a single merge commit of `main` (`5d4a9a9`, bringing #161/#162/#166). No new branch-side code: **all 8 code/test files this PR authored are byte-identical through the merge** — the only drifted files are the three that conflicted, and each resolution is correct: 1. **README.md:59** — carries **both** clauses exactly as rounds 1–2 prescribed: this branch's *"and edit structure (create, rename, delete and reorder chapters, move pages between them, add and delete variants)"* **and** #162's *"A run in progress can be stopped (Stop replaces Send in the panel)…"*. Neither side was wholesale-dropped. The reader/settings bullets take main's #166/#164 wording untouched. 2. **ADR 0024** — now holds *both* 2026-09-19 amendments side by side (chapter/variant writes; mid-run cancel), and the narrowed v1-deferral paragraph points at the cancel amendment for its superseded mitigation. Coherent, no contradiction. 3. **BACKLOG.md** — verified all three additions survive: main's expanded *"Mid-run cancel stops at the next tool boundary only"*, main's *"Continuous reader"* item, and this branch's rewritten *"Tool gaps: page writes and variant fields"* anchor. #### 🔗 The interaction I actually worried about — checked, sound~ The one genuinely new risk in a merge like this: #162's `CancellableTool` now wraps this branch's `DoujinWriteTool`, and `cancelRun()` closes the approval gate. I read the merged machinery: `CancellableTool` short-circuits *before* `execute` (a mid-read write tool never reaches the gate), `ApprovalGate.close()` refuses on arrival *after* it (doc-comment even names `doujin_write` resolving association names mid-read — the exact race), and every write tool's `!approved` path takes the established denied-text branch. The round-1 verification of `doujin_write`'s deny arms carries over unchanged since the file is byte-identical. #### 🧪 Verification on the merged tree - Local, at `818cceb` (identical tree to CI): `flutter analyze` — **No issues found**; full suite `--concurrency=1` — **1219/1219, All tests passed**, clean shutdown (63 min serialized on this sandbox). - CI for this head: `CI / build` ✅, `CI / test` ✅ (backend), `Flutter CI / analyze-and-test` ❌, `Deploy Smoke` ❌ — both red Xs forensically dissected below, because neither is what it looks like. #### ⚠️ The two red CI checks — read this before re-running anything 1. **Flutter CI failure = harness shutdown race, not a test failure.** Job log (run 718): attempt 1 hit the 12m cap → the workflow's built-in retry fired (the exact mechanism PR #71 added for this); attempt 2 ran `analyze` clean and **every executed test green — zero failing tests** — then the runner hung *after* `settings_page_test.dart` completed ("failed after test completion", `Bad state: Cannot close sink while adding stream`, `flutter_platform.dart`), burned to the 12m cap, exit 124. That file is untouched by this branch (it's main's #164/#166 content), the error is inside `flutter_tools`, and my local run of the same tree shuts down cleanly. Two hangs in one job is rotten luck, not a defect in this diff. A re-run should go green. 2. **Smoke failure = shared across all three sibling merges, not this branch.** `/health did not answer within 120s` while the container logged `Now listening on: http://[::]:8080` — identical signature, identical window, on this merge (12:29), batch-tag-editing (12:56) and sort-titles (13:22), with backend build+test green everywhere. This branch's PR diff touches **zero** backend files, so it cannot be the cause — but three-for-three on main's tip smells like #161's startup (`SearchKeyBackfill` / `AddTitleSearchKey` migration) or runner networking. Worth a look at main, separate from this PR. #### ✅ What I liked~ - The merge note from rounds 1–2 was executed *to the letter* — both README clauses present, nothing wholesale. You read the review and did the thing exactly~ ♪ - Zero scope creep in the merge: no drive-by edits smuggled into the conflict resolutions. - Mergeable again (`mergeable: true`), and the PR diff vs the new base is still exactly the round-1-reviewed +2471/−43. Non-blocking carried (unchanged, deliberately parked): `isLast` reads the pre-`getVariant` snapshot vs fresh `page_count` — consistent today, wants its own test if ever split across the gate. --- *Automated review by Jibril · 2026-09-19* *CI/CD: backend build+test PASSED; Flutter CI red = harness shutdown flake (all tests green in-log, exit 124 timeout, PR #71 class); smoke red = shared by all 3 sibling merges in-window (main-tip/environment, zero backend files in this diff) · Local checks at 818cceb: analyze clean, 1219/1219 full suite, clean shutdown; branch files byte-identical through the merge (verified per-file: only README/ADR/BACKLOG drifted)*
bjoern merged commit 687b6d9956 into main 2026-09-19 15:55:02 +02:00
bjoern deleted branch feat/the-assistant-can-write-chapters-and-variants 2026-09-19 15:55:02 +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!163
No description provided.