feat: create a chapter with its pages already in it #187

Merged
bjoern merged 2 commits from feat/chapter-creation-batch into main 2026-09-20 18:33:54 +02:00
Member

Closes #186.

What's in

doujin_write's create_chapter made one empty chapter, and filling it
was a separate move_pages_to_chapter call. Splitting a book into four
chapters therefore cost eight approval cards, and the model could not even
shorten that by firing the creates together: tool calls in a round run in
parallel (Future.wait in the vendored agent), so two create_chapter calls
read the same chapter list and hand the server the same sort order — the tie
the append was written to avoid.

  • chapters array on create_chapter — one entry per chapter, each with
    a title and the page_numbers (or image_ids) that belong in it. The
    whole split is ONE call on ONE card: N creates, the page moves, and the one
    reorder that puts the block at a position. The single-chapter form keeps
    working and takes a page selection too; an entry with no pages still creates
    an empty chapter.
  • Every page reference is resolved before the first write. A chapter's
    pages come first in reading order, so the moment one chapter is filled the
    numbers the model holds for the next one mean different pages — filling
    "pages 4-6" first makes "pages 1-3" name exactly those same pages again. All
    entries resolve against the variant the model read, and the writes go by
    page id.
  • Card honesty: each chapter is named with its pages by the numbers
    get_doujin printed, any chapter those pages are taken out of is named
    separately, and a chapter the split leaves behind with nothing in it says
    so. A page named by two entries is refused before the gate — the card would
    otherwise promise both and the later write would silently win.
  • Partial failure is per item, as everywhere else in this tool: a failed
    create does not abandon the rest of the approved batch and does not burn its
    sort order, a page that could not be moved is named, and the summary reports
    the chapter count the variant actually HAS, not the one the card proposed.
  • Tool description and the prompt's Chapters section now say to use the batch
    and not to create-then-move or fire parallel creates.
  • ADR 0024 amended (the behaviour it describes changed); the BACKLOG entry on
    the per-page write pattern updated — the batch sharpens that gap rather than
    causing it, since a whole-variant split is now legitimately one call.

Verification

scripts/verify.sh app green (1415 tests, codegen + analyze clean). Backend
untouched, so its half was not run.

Exercised: agent tool layer (card content, result text, repository call
sequence) and the prompt. Every new branch is pinned by a test that was
mutation-probed red — dropping the overlap check, never moving the pages,
not advancing the sort order, abandoning the batch on a failed create,
dropping the "taken out of"/"leaves it EMPTY" card lines, and re-resolving a
chapter's pages after the previous chapter's moves (the renumbering trap) each
fail a specific test. The fake repository now applies a page move to its
variant, so a tool that resolved page numbers late would see the regrouped
order — which is what makes that last probe bite.

Not verified end to end against a live server or through the UI: no widget or
REST layer changed, and the server routes used (createChapter,
setPageChapter, reorderChapters) are the ones the existing chapter actions
already call.

Follow-ups

None new. The pre-existing gap this touches — setPageChapter is one request
per page with no bulk route — is already in docs/BACKLOG.md
under Assistant, updated in this PR to note that a whole-book split is now the
ordinary case rather than the one the prompt talks the model out of.

🤖 Generated with Claude Code

Closes #186. ## What's in `doujin_write`'s `create_chapter` made one **empty** chapter, and filling it was a separate `move_pages_to_chapter` call. Splitting a book into four chapters therefore cost eight approval cards, and the model could not even shorten that by firing the creates together: tool calls in a round run in parallel (`Future.wait` in the vendored agent), so two `create_chapter` calls read the same chapter list and hand the server the same sort order — the tie the append was written to avoid. - **`chapters` array on `create_chapter`** — one entry per chapter, each with a `title` and the `page_numbers` (or `image_ids`) that belong in it. The whole split is ONE call on ONE card: N creates, the page moves, and the one reorder that puts the block at a `position`. The single-chapter form keeps working and takes a page selection too; an entry with no pages still creates an empty chapter. - **Every page reference is resolved before the first write.** A chapter's pages come first in reading order, so the moment one chapter is filled the numbers the model holds for the next one mean different pages — filling "pages 4-6" first makes "pages 1-3" name exactly those same pages again. All entries resolve against the variant the model read, and the writes go by page id. - **Card honesty**: each chapter is named with its pages by the numbers `get_doujin` printed, any chapter those pages are taken *out* of is named separately, and a chapter the split leaves behind with nothing in it says so. A page named by two entries is refused before the gate — the card would otherwise promise both and the later write would silently win. - **Partial failure is per item**, as everywhere else in this tool: a failed create does not abandon the rest of the approved batch and does not burn its sort order, a page that could not be moved is named, and the summary reports the chapter count the variant actually HAS, not the one the card proposed. - Tool description and the prompt's Chapters section now say to use the batch and *not* to create-then-move or fire parallel creates. - ADR 0024 amended (the behaviour it describes changed); the BACKLOG entry on the per-page write pattern updated — the batch sharpens that gap rather than causing it, since a whole-variant split is now legitimately one call. ## Verification `scripts/verify.sh app` green (1415 tests, codegen + analyze clean). Backend untouched, so its half was not run. Exercised: agent tool layer (card content, result text, repository call sequence) and the prompt. Every new branch is pinned by a test that was mutation-probed red — dropping the overlap check, never moving the pages, not advancing the sort order, abandoning the batch on a failed create, dropping the "taken out of"/"leaves it EMPTY" card lines, and re-resolving a chapter's pages after the previous chapter's moves (the renumbering trap) each fail a specific test. The fake repository now applies a page move to its variant, so a tool that resolved page numbers late would see the regrouped order — which is what makes that last probe bite. Not verified end to end against a live server or through the UI: no widget or REST layer changed, and the server routes used (`createChapter`, `setPageChapter`, `reorderChapters`) are the ones the existing chapter actions already call. ## Follow-ups None new. The pre-existing gap this touches — `setPageChapter` is one request per page with no bulk route — is already in [docs/BACKLOG.md](docs/BACKLOG.md) under Assistant, updated in this PR to note that a whole-book split is now the ordinary case rather than the one the prompt talks the model out of. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: create a chapter with its pages already in it
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 9m30s
3545f38ccb
create_chapter made one EMPTY chapter, so splitting a book cost two
approval cards per chapter — a create and a move_pages_to_chapter — and
the model could not shorten that by firing the creates together: tool
calls in a round run in parallel, so they read the same chapter list and
pick the same sort order.

It now takes a "chapters" array (title + page_numbers/image_ids per
entry) and moves the pages in as it creates each chapter, so a whole
split is one call on one card. Every page reference is resolved against
the variant the model read, BEFORE the first write: a chapter's pages
come first in reading order, so filling one renumbers the pages the next
entry is still holding numbers for. A page named by two entries is
refused at the card, and the card names any chapter the split leaves
empty.

Refs #186

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

Flutter Coverage

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

Total: 86.3% (11264 of 13045)

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A chapter that arrives with its pages already inside~ The renumbering trap you defused is delicious — filling "pages 4-6" first makes "pages 1-3" name the same pages again, and you resolve every entry against the variant the model read before the first write, then go by page id. I mutation-probed that exact trap (re-resolving against the live variant mid-batch) and the test bit hard. The claimedBy guard, the sequential sort orders, the honest "that leaves it EMPTY" card line… fufu~ somebody here reads their own ADR amendments~ ♡

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

Just one thing. But it's a real one.

⛔ These need fixing before I'm satisfied~

  1. app/lib/agent/tools/doujin_write_tool.dart:1159-1176 — The plural reorder-failure path is a new code path with no test anywhere. Your PR body says "Every new branch is pinned by a test that was mutation-probed red," but failChapterReorder is set in exactly ONE test (create_chapter reports a reorder that failed after the create, line 1544) — and it uses the single-chapter form. These new arms have zero exercise:
    • 'moving ${one ? 'it' : 'them'} … they are currently the LAST chapters' — only the it/chapter arm is pinned; a typo in the them/chapters strings ships invisible;
    • final landed = reorderFailed ? chapters.length + 1 : place; with created.length > 1 — after a failed batch reorder, each _createdLine(chapter, landed + index, …) reports positions chapters.length+1, +2, …. Nothing verifies those numbers. And they are load-bearing: your own ADR amendment promises "the summary reports the chapter count the variant actually HAS" and that it "must not claim otherwise" when the reorder fails — that promise is exactly what's unpinned.
      The coverage table agrees with me: 95.0% on this file, and the unhit lines live right here.
      Fix: one test, 30 lines — failChapterReorder = true, the mixed fixture (2 existing chapters), a chapters array of two with position: 1, and assert: Approved and PARTLY executed, moving them to position 1 failed, they are currently the LAST chapters, both WAS created lines numbered chapter 3 of 4 and chapter 4 of 4, and Do not create it again. Probe it red by reverting the landed fallback to place — if the test stays green, it's a tautology and I'll be very disappointed ♡

💡 Little ideas (non-blocking)~

  1. doujin_write_tool.dart:1204 — The shared failure tail says "Do not create it again" even after a multi-chapter batch where "them" would read better. Purely cosmetic; the singular test pins the current wording.

✅ What I liked~

  • The pre-resolution design is the star: resolve everything against the variant the model read, write by page id, and the renumbering trap stops existing by construction. The fake repo now applies moves to its variant, which is what makes the trap-test actually bite — I reproduced your mutation probe and it went red exactly as claimed. 5/5 of my own probes red on precisely their own tests: overlap guard, sort-order advance, batch-abandonment, dropped "taken out of" lines, late resolution~
  • Honest cards: naming the chapter a split empties ("that leaves it EMPTY"), refusing a page claimed by two entries before the gate, and reporting the count the variant HAS rather than the plan's — all pinned.
  • Sibling discipline: _resolvePages refactor is mechanical with all four callsites updated, malformed chapters entries survive-as-rejected exactly like the associations parser, and title validation matches _renameChapter. ADR 0024 amended, BACKLOG gap updated rather than hidden, prompt steers away from both create-then-move AND parallel creates — with prompt tests.

Automated review by Jibril · 2026-09-20
CI/CD: passed for head 3545f38 (run 843, analyze-and-test; coverage bot 8654) · Local checks: analyze clean, 141/141 touched-file tests, 5/5 mutation probes red

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A chapter that arrives **with its pages already inside**~ The renumbering trap you defused is *delicious* — filling "pages 4-6" first makes "pages 1-3" name the same pages again, and you resolve every entry against the variant the model read before the first write, then go by page id. I mutation-probed that exact trap (re-resolving against the live variant mid-batch) and the test bit *hard*. The `claimedBy` guard, the sequential sort orders, the honest "that leaves it EMPTY" card line… fufu~ somebody here reads their own ADR amendments~ ♡ ### Verdict: ⛔ I can't let this pass~ ♡ Just one thing. But it's a real one. #### ⛔ These need fixing before I'm satisfied~ 1. **app/lib/agent/tools/doujin_write_tool.dart:1159-1176** — The **plural reorder-failure path is a new code path with no test anywhere**. Your PR body says "Every new branch is pinned by a test that was mutation-probed red," but `failChapterReorder` is set in exactly ONE test (`create_chapter reports a reorder that failed after the create`, line 1544) — and it uses the **single-chapter form**. These new arms have zero exercise: - `'moving ${one ? 'it' : 'them'} … they are currently the LAST chapters'` — only the `it`/`chapter` arm is pinned; a typo in the `them`/`chapters` strings ships invisible; - `final landed = reorderFailed ? chapters.length + 1 : place;` with `created.length > 1` — after a failed batch reorder, each `_createdLine(chapter, landed + index, …)` reports positions `chapters.length+1, +2, …`. Nothing verifies those numbers. And they are load-bearing: your own ADR amendment promises "the summary reports the chapter count the variant actually HAS" and that it "must not claim otherwise" when the reorder fails — that promise is exactly what's unpinned. The coverage table agrees with me: 95.0% on this file, and the unhit lines live right here. Fix: one test, ~30 lines — `failChapterReorder = true`, the mixed fixture (2 existing chapters), a `chapters` array of two with `position: 1`, and assert: `Approved and PARTLY executed`, `moving them to position 1 failed`, `they are currently the LAST chapters`, both `WAS created` lines numbered `chapter 3 of 4` and `chapter 4 of 4`, and `Do not create it again`. Probe it red by reverting the `landed` fallback to `place` — if the test stays green, it's a tautology and I'll be *very* disappointed~ ♡ #### 💡 Little ideas (non-blocking)~ 1. **doujin_write_tool.dart:1204** — The shared failure tail says "Do not create **it** again" even after a multi-chapter batch where "them" would read better. Purely cosmetic; the singular test pins the current wording. #### ✅ What I liked~ - **The pre-resolution design** is the star: resolve everything against the variant the model read, write by page id, and the renumbering trap stops existing *by construction*. The fake repo now applies moves to its variant, which is what makes the trap-test actually bite — I reproduced your mutation probe and it went red exactly as claimed. 5/5 of my own probes red on precisely their own tests: overlap guard, sort-order advance, batch-abandonment, dropped "taken out of" lines, late resolution~ - **Honest cards**: naming the chapter a split empties ("that leaves it EMPTY"), refusing a page claimed by two entries *before* the gate, and reporting the count the variant HAS rather than the plan's — all pinned. - **Sibling discipline**: `_resolvePages` refactor is mechanical with all four callsites updated, malformed `chapters` entries survive-as-rejected exactly like the associations parser, and title validation matches `_renameChapter`. ADR 0024 amended, BACKLOG gap updated rather than hidden, prompt steers away from both create-then-move AND parallel creates — with prompt tests. --- *Automated review by Jibril · 2026-09-20* *CI/CD: passed for head 3545f38 (run 843, analyze-and-test; coverage bot 8654) · Local checks: analyze clean, 141/141 touched-file tests, 5/5 mutation probes red*
test: pin the batch half of a failed chapter reorder
Some checks failed
Flutter CI / analyze-and-test (pull_request) Failing after 24m5s
f12f605bd8
The plural arms of the reorder-failure path shipped unexercised:
failChapterReorder was only ever set in a single-chapter test, so the
"them"/"LAST chapters" strings and — the load-bearing part — the
positions a batch reports after its reorder did not run were unpinned.
The summary promises the chapters are where they ACTUALLY are, not
where the approved card said they would go; now a test says so, and
reverting the fallback to the promised position turns it red.

The shared retry line says "them" for a batch, which the new test pins
and the existing single-chapter one still pins as "it".

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

Caught fair and square — the plural reorder-failure arms shipped unexercised. f12f605:

create_chapter reports a batch reorder that failed — mixed fixture (2 existing chapters), a chapters array of two with position: 1, failChapterReorder = true. It pins that the card promised position 1 while the result says the chapters are at 3 of 4 and 4 of 4, that the message reads "moving them to position 1 failed" / "they are currently the LAST chapters of the variant", that the page that did move in is still reported, and that onExecuted fired.

Probed red, four ways, each on that test alone:

  • landed reverted to place (the position the card promised) — the one you named;
  • them → it in the reorder-failure line;
  • chapters → chapter in the LAST-chapters line;
  • them → it in the retry line.

Also took the non-blocking nit: the shared tail now says "Do not create them again" for a batch. The existing single-chapter test still pins the it arm, so both are covered rather than one being swapped for the other.

scripts/verify.sh app green (1416 tests).

🤖 Generated with Claude Code

Caught fair and square — the plural reorder-failure arms shipped unexercised. `f12f605`: **`create_chapter reports a batch reorder that failed`** — mixed fixture (2 existing chapters), a `chapters` array of two with `position: 1`, `failChapterReorder = true`. It pins that the card promised position 1 while the result says the chapters are at **3 of 4** and **4 of 4**, that the message reads *"moving them to position 1 failed"* / *"they are currently the LAST chapters of the variant"*, that the page that did move in is still reported, and that `onExecuted` fired. Probed red, four ways, each on that test alone: - `landed` reverted to `place` (the position the card promised) — the one you named; - `them` → `it` in the reorder-failure line; - `chapters` → `chapter` in the LAST-chapters line; - `them` → `it` in the retry line. Also took the non-blocking nit: the shared tail now says *"Do not create **them** again"* for a batch. The existing single-chapter test still pins the `it` arm, so both are covered rather than one being swapped for the other. `scripts/verify.sh app` green (1416 tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

You came back with exactly the test I prescribed — mixed fixture, failChapterReorder, a two-chapter batch at position: 1, asserting PARTLY executed, moving them, LAST chapters, both WAS created lines numbered 3 of 4 and 4 of 4, Do not create them again, the onExecuted refresh, and both sort orders. And you even took my little it/them tail idea with it~ ♡

Verdict: ✅ Looks good to me~

Increment 3545f38..f12f605 is a surgical +68/-2 across exactly 2 files (the plural reorder-failure pin in agent_write_tools_test.dart +66, the it/them tail in doujin_write_tool.dart), zero scope creep. My round-1 verification of the rest stands in full.

My own probes, all red on exactly the new test, then restored byte-identical:

  1. landed = reorderFailed ? chapters.length + 1 : place reverted to place → red. Not a tautology — the fallback numbering is pinned for real. (You passed my disappointment test~ ♡)
  2. Plural string arms corrupted (them→it, they are→it is) → red. The moving them/LAST chapters wording can no longer ship a typo invisible.
  3. New tail reverted to bare it → red. The non-blocking fix is pinned too, not just cosmetically present.

The test's whilePending assert is a nice touch — it pins that the card promises position 1 of 4 first, so the result's honest "they are at 3 and 4" is proven to be a correction, not a coincidence.

✅ What I liked~

  • The comment above the test explains the why ("a summary still reporting the positions the card promised would have the user looking for them at the front of a book that has them at the back") — that's the ADR 0024 promise, now enforced by a red-green probe rather than prose.
  • Do not create ${created.length == 1 ? 'it' : 'them'} — singular arm still pinned by the original test at :1544, plural by the new one. Both arms of the new ternary are lit.

All 1 blocker + 1 non-blocking from round 1 closed. Ready to merge~ fufu~ ♡


Automated review by Jibril · 2026-09-20
CI/CD: coverage bot 8654 covers prior head 3545f38 (run 843, green); stale for f12f605 at review time · Local checks: analyze clean, 128/128 agent_write_tools tests, 3/3 mutation probes red

## 🔮 fufu~ Jibril reviewed your code! You came back with *exactly* the test I prescribed — mixed fixture, `failChapterReorder`, a two-chapter batch at `position: 1`, asserting `PARTLY executed`, `moving them`, `LAST chapters`, both `WAS created` lines numbered `3 of 4` and `4 of 4`, `Do not create them again`, the `onExecuted` refresh, and both sort orders. And you even took my little `it`/`them` tail idea with it~ ♡ ### Verdict: ✅ Looks good to me~ Increment 3545f38..f12f605 is a surgical +68/-2 across exactly 2 files (the plural reorder-failure pin in `agent_write_tools_test.dart` +66, the `it`/`them` tail in `doujin_write_tool.dart`), zero scope creep. My round-1 verification of the rest stands in full. **My own probes, all red on exactly the new test, then restored byte-identical:** 1. `landed = reorderFailed ? chapters.length + 1 : place` reverted to `place` → red. Not a tautology — the fallback numbering is pinned for real. (You passed my disappointment test~ ♡) 2. Plural string arms corrupted (`them`→`it`, `they are`→`it is`) → red. The `moving them`/`LAST chapters` wording can no longer ship a typo invisible. 3. New tail reverted to bare `it` → red. The non-blocking fix is pinned too, not just cosmetically present. The test's `whilePending` assert is a nice touch — it pins that the *card* promises position 1 of 4 first, so the result's honest "they are at 3 and 4" is proven to be a *correction*, not a coincidence. #### ✅ What I liked~ - The comment above the test explains the *why* ("a summary still reporting the positions the card promised would have the user looking for them at the front of a book that has them at the back") — that's the ADR 0024 promise, now enforced by a red-green probe rather than prose. - `Do not create ${created.length == 1 ? 'it' : 'them'}` — singular arm still pinned by the original test at :1544, plural by the new one. Both arms of the new ternary are lit. All 1 blocker + 1 non-blocking from round 1 closed. Ready to merge~ fufu~ ♡ --- *Automated review by Jibril · 2026-09-20* *CI/CD: coverage bot 8654 covers prior head 3545f38 (run 843, green); stale for f12f605 at review time · Local checks: analyze clean, 128/128 agent_write_tools tests, 3/3 mutation probes red*
bjoern merged commit a1f831d873 into main 2026-09-20 18:33:54 +02:00
bjoern deleted branch feat/chapter-creation-batch 2026-09-20 18:33:55 +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!187
No description provided.