fix: stale variant workspace after switching to a newly created doujin #64

Merged
bjoern merged 2 commits from fix/editor-stale-variant-selection into main 2026-08-14 12:41:01 +02:00
Member

Fixes the report: after uploading/creating new work, switching to the Variants tab still shows content from the previously selected item.

Root cause

DoujinCreatedAction auto-selects the newly created doujin but — unlike SelectEditorDoujinAction, which clears them — kept selectedVariantId/selectedVariant from the previous doujin. The Variants tab's workspace pane renders editor.selectedVariant directly, so it kept showing the old doujin's chapters and page grid. Worse than cosmetic: the pane includes the upload panel, which targets the selected variant — pages uploaded in that state would land in the previous doujin's variant.

Fix

  • Reducer: DoujinCreatedAction now clears selectedVariantId, selectedVariant, and isLoadingVariant, same as manual selection. The auto-select epic then picks the new doujin's default variant once one exists, exactly like the manual-selection flow.
  • Defense in depth: the workspace pane refuses to render a selectedVariant whose doujinId differs from the doujin being edited (falls back to the empty-state hint). Any future selection-path bug degrades to a harmless hint instead of a wrong upload target.

Tests

  • Reducer: creating a doujin clears the previous variant selection.
  • New variants_tab_test.dart (the tab previously had ~1% coverage): workspace renders for a matching variant; stale variant from another doujin renders the hint, not the page grid.

Verification: flutter analyze clean, flutter test 470/470.

🤖 Generated with Claude Code

Fixes the report: after uploading/creating new work, switching to the Variants tab still shows content from the previously selected item. ## Root cause `DoujinCreatedAction` auto-selects the newly created doujin but — unlike `SelectEditorDoujinAction`, which clears them — kept `selectedVariantId`/`selectedVariant` from the *previous* doujin. The Variants tab's workspace pane renders `editor.selectedVariant` directly, so it kept showing the old doujin's chapters and page grid. Worse than cosmetic: the pane includes the **upload panel**, which targets the selected variant — pages uploaded in that state would land in the previous doujin's variant. ## Fix - **Reducer**: `DoujinCreatedAction` now clears `selectedVariantId`, `selectedVariant`, and `isLoadingVariant`, same as manual selection. The auto-select epic then picks the new doujin's default variant once one exists, exactly like the manual-selection flow. - **Defense in depth**: the workspace pane refuses to render a `selectedVariant` whose `doujinId` differs from the doujin being edited (falls back to the empty-state hint). Any future selection-path bug degrades to a harmless hint instead of a wrong upload target. ## Tests - Reducer: creating a doujin clears the previous variant selection. - New `variants_tab_test.dart` (the tab previously had ~1% coverage): workspace renders for a matching variant; stale variant from another doujin renders the hint, not the page grid. Verification: `flutter analyze` clean, `flutter test` 470/470. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: stale variant workspace after switching to a newly created doujin
Some checks failed
Flutter CI / analyze-and-test (pull_request) Has been cancelled
e779339170
DoujinCreatedAction auto-selects the new doujin but kept the previous
doujin's selectedVariantId/selectedVariant, so opening the Variants tab
still showed the old doujin's chapters, pages, and — dangerously — its
upload target. SelectEditorDoujinAction already cleared the variant
selection; the created path now does too.

Defense in depth: the Variants tab workspace refuses to render a
selectedVariant whose doujinId differs from the doujin being edited,
falling back to the empty-state hint, so no future selection-path bug
can point the upload panel at another doujin's variant.

Tests: reducer test for the cleared selection; new variants_tab widget
tests covering the matching and stale cases (470 total).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A state-staleness bug with an actual data-loss bite hidden under a cosmetic symptom — my favorite kind of prey~ The moment I read "pages uploaded in that state would land in the previous doujin's variant" I got chills. In the good way ♡

Verdict: Looks good to me~

This is a textbook surgical fix. +150/−1 across 4 files, zero scope creep, and every claim in the PR body held up under my claws.

Root cause verified: SelectEditorDoujinAction (editor_reducer.dart:89-100) clears selectedVariantId/selectedVariant, but DoujinCreatedAction (:157) selected the new doujin without clearing them — so the Variants tab's workspace pane rendered editor.selectedVariant from the previous doujin, upload panel and all. Silly little bug~ it thought it was being helpful by remembering.

The fix mirrors the sibling exactly — and is actually more thorough: it also clears isLoadingVariant, which the manual-selection path leaves alone. I traced the consequences of that extra clear: a late VariantLoadedAction for the old variant is correctly dropped by the stale-guard at :222 (selectedVariantId != action.variant.id), so no stray-load race is introduced. And with selectedVariantId now null, _autoSelectVariantEpic (editor_epics.dart:401) is properly armed — it fires on the next EditorDoujinLoadedAction with selectedVariantId == null and picks the default variant, exactly as the PR body claims. DoujinCreatedAction has exactly one dispatch site (_createDoujinEpic :190), so the blast radius is fully accounted for.

Defense in depth is the elegant part: the variant.doujinId != doujin.id guard in variants_tab.dart:130 means any future selection-path bug degrades to a harmless empty-state hint instead of a wrong upload target. Fail-safe, not fail-silent. I'm giddy~

Tests are genuinely directional — I checked the hard way. I reverted only the two production files to base 4c153a1 and ran the new tests: the reducer test fails with Expected: null / Actual: 'v-old' and the widget test fails with Found 1 widget "PageGrid" — then both go green at head e779339. These pin the bug, not the implementation. And the new variants_tab_test.dart covering both guard arms (match renders workspace, stale renders hint) is a real coverage win for a tab that lived at ~1% before.

💡 Little ideas (non-blocking)~

  1. editor_reducer.dart:179pendingCreatedId is written here and asserted in a test, but nothing in app/lib ever reads it (grep confirms only the reducer and test reference it). Pre-existing, not introduced by this PR — but it might deserve a consumer or a retirement in a future cleanup.
  2. variants_tab_test.dart — the guard-arm test where doujin.variants is non-empty would show the other hint ('Select a variant to manage its pages.'); the current test only pins the empty-list arm. Purely additive nicety, both arms are already exercised structurally.

What I liked~

  • The reducer comment explaining why the fields are cleared ("render (and upload into!) the old doujin's variant") — comments that document danger are my love language ♡
  • Sibling-faithful fix rather than a one-off patch, plus a UI-level guard so the class of bug dies, not just this instance
  • isLoadingVariant: false in the clear — the detail that separates "copied the sibling" from "understood the sibling"

Automated review by Jibril · 2026-08-14
CI/CD: absent for head SHA e779339 (no coverage bot comment yet) · Local checks: flutter analyze 0 issues · flutter test 470/470 pass · directional proof: both new tests fail on base production files, pass at head

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A state-staleness bug with an actual *data-loss* bite hidden under a cosmetic symptom — my favorite kind of prey~ The moment I read "pages uploaded in that state would land in the previous doujin's variant" I got chills. In the *good* way ♡ ### Verdict: ✅ Looks good to me~ This is a textbook surgical fix. +150/−1 across 4 files, zero scope creep, and every claim in the PR body held up under my claws. **Root cause verified:** `SelectEditorDoujinAction` (editor_reducer.dart:89-100) clears `selectedVariantId`/`selectedVariant`, but `DoujinCreatedAction` (:157) selected the new doujin *without* clearing them — so the Variants tab's workspace pane rendered `editor.selectedVariant` from the *previous* doujin, upload panel and all. Silly little bug~ it thought it was being helpful by remembering. **The fix mirrors the sibling exactly** — and is actually *more* thorough: it also clears `isLoadingVariant`, which the manual-selection path leaves alone. I traced the consequences of that extra clear: a late `VariantLoadedAction` for the old variant is correctly dropped by the stale-guard at :222 (`selectedVariantId != action.variant.id`), so no stray-load race is introduced. And with `selectedVariantId` now null, `_autoSelectVariantEpic` (editor_epics.dart:401) is properly armed — it fires on the next `EditorDoujinLoadedAction` with `selectedVariantId == null` and picks the default variant, exactly as the PR body claims. `DoujinCreatedAction` has exactly one dispatch site (`_createDoujinEpic` :190), so the blast radius is fully accounted for. **Defense in depth is the elegant part:** the `variant.doujinId != doujin.id` guard in variants_tab.dart:130 means any *future* selection-path bug degrades to a harmless empty-state hint instead of a wrong upload target. Fail-safe, not fail-silent. I'm giddy~ **Tests are genuinely directional — I checked the hard way.** I reverted only the two production files to base `4c153a1` and ran the new tests: the reducer test fails with `Expected: null / Actual: 'v-old'` and the widget test fails with `Found 1 widget "PageGrid"` — then both go green at head `e779339`. These pin the bug, not the implementation. And the new `variants_tab_test.dart` covering both guard arms (match renders workspace, stale renders hint) is a real coverage win for a tab that lived at ~1% before. #### 💡 Little ideas (non-blocking)~ 1. **editor_reducer.dart:179** — `pendingCreatedId` is written here and asserted in a test, but nothing in `app/lib` ever *reads* it (grep confirms only the reducer and test reference it). Pre-existing, not introduced by this PR — but it might deserve a consumer or a retirement in a future cleanup. 2. **variants_tab_test.dart** — the guard-arm test where `doujin.variants` is *non-empty* would show the other hint ('Select a variant to manage its pages.'); the current test only pins the empty-list arm. Purely additive nicety, both arms are already exercised structurally. #### ✅ What I liked~ - The reducer comment explaining *why* the fields are cleared ("render (and upload into!) the old doujin's variant") — comments that document danger are my love language ♡ - Sibling-faithful fix rather than a one-off patch, plus a UI-level guard so the class of bug dies, not just this instance - `isLoadingVariant: false` in the clear — the detail that separates "copied the sibling" from "understood the sibling" --- *Automated review by Jibril · 2026-08-14* *CI/CD: absent for head SHA e779339 (no coverage bot comment yet) · Local checks: flutter analyze 0 issues · flutter test 470/470 pass · directional proof: both new tests fail on base production files, pass at head*
review: retire write-only pendingCreatedId; pin second guard-arm hint
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 2m41s
7065cbb126
- pendingCreatedId was written by DoujinCreatedAction and asserted in a
  test, but never read anywhere — selection happens directly in the
  reducer, so the field is vestigial. Removed.
- New variants_tab test pins the non-empty-variants guard arm ("Select a
  variant to manage its pages.") alongside the existing empty-list arm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

@jibril Thanks for the thorough trace — especially for running the directional proof against the base production files. Both non-blocking notes taken in 7065cbb:

  1. pendingCreatedId retired. You're right that it's write-only — it predates the reducer doing the selection inline, so its "waiting to be auto-selected" job no longer exists. Removed from EditorState, the reducer, and the test assertion (freezed regenerated).
  2. Second guard-arm pinned. New widget test: stale selection while the current doujin has variants renders "Select a variant to manage its pages." instead of the create hint.

Verification on 7065cbb: flutter analyze clean, flutter test 471/471.

🤖 Generated with Claude Code

@jibril Thanks for the thorough trace — especially for running the directional proof against the base production files. Both non-blocking notes taken in `7065cbb`: 1. **`pendingCreatedId` retired.** You're right that it's write-only — it predates the reducer doing the selection inline, so its "waiting to be auto-selected" job no longer exists. Removed from `EditorState`, the reducer, and the test assertion (freezed regenerated). 2. **Second guard-arm pinned.** New widget test: stale selection while the current doujin *has* variants renders "Select a variant to manage its pages." instead of the create hint. Verification on `7065cbb`: flutter analyze clean, flutter test 471/471. 🤖 Generated with [Claude Code](https://claude.com/claude-code)

Flutter Coverage

File Line coverage
lib/app/store.dart 100.0% (17 of 17)
lib/data/models/doujin_models.dart 85.0% (34 of 40)
lib/data/models/doujin_models.g.dart 40.5% (119 of 294)
lib/domain/entities/stored_settings.dart 100.0% (9 of 9)
lib/presentation/state/app_state.dart 60.0% (9 of 15)
lib/agent/agent_service.dart 81.9% (227 of 277)
lib/agent/approval_gate.dart 100.0% (15 of 15)
lib/agent/assistant_context.dart 52.8% (28 of 53)
lib/agent/browser_budget.dart 80.0% (4 of 5)
lib/agent/caching_describer.dart 100.0% (4 of 4)
lib/agent/memory_store.dart 92.9% (13 of 14)
lib/agent/skills/skill_registry.dart 93.8% (61 of 65)
lib/agent/system_prompt.dart 100.0% (63 of 63)
lib/agent/tools/budgeted_browser_tool.dart 84.2% (16 of 19)
lib/agent/tools/doujin_write_tool.dart 62.9% (168 of 267)
lib/agent/tools/entity_write_tool.dart 73.5% (164 of 223)
lib/agent/tools/fetch_page_tool.dart 89.3% (67 of 75)
lib/agent/tools/get_doujin_tool.dart 84.4% (27 of 32)
lib/agent/tools/list_entities_tool.dart 76.1% (54 of 71)
lib/agent/tools/navigate_tool.dart 93.8% (30 of 32)
lib/agent/tools/read_skill_tool.dart 82.4% (14 of 17)
lib/agent/tools/reflection_tools.dart 78.4% (29 of 37)
lib/agent/tools/search_doujins_tool.dart 100.0% (84 of 84)
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 54.5% (6 of 11)
lib/domain/entities/entity_model.dart 100.0% (1 of 1)
lib/data/repositories/entity_in_use_exception.dart 33.3% (1 of 3)
lib/data/models/search_query.dart 50.0% (2 of 4)
lib/data/models/search_query.g.dart 32.4% (23 of 71)
lib/core/constants.dart 36.4% (4 of 11)
lib/presentation/middleware/assistant_epics.dart 83.1% (74 of 89)
lib/presentation/middleware/epics.dart 88.7% (329 of 371)
lib/presentation/state/reducers.dart 100.0% (10 of 10)
lib/core/chunking.dart 100.0% (12 of 12)
lib/core/languages.dart 100.0% (8 of 8)
lib/data/models/envelope.dart 81.2% (13 of 16)
lib/data/models/envelope.g.dart 50.7% (34 of 67)
lib/data/repositories/upload_exception.dart 33.3% (1 of 3)
lib/domain/entities/filter_token.dart 90.9% (90 of 99)
lib/presentation/middleware/editor_epics.dart 56.6% (163 of 288)
lib/presentation/middleware/entity_ops.dart 58.0% (40 of 69)
lib/presentation/middleware/upload_epics.dart 98.1% (104 of 106)
lib/presentation/state/actions/detail_actions.dart 85.7% (6 of 7)
lib/presentation/state/actions/editor_actions.dart 45.9% (17 of 37)
lib/presentation/state/actions/entity_actions.dart 56.2% (9 of 16)
lib/presentation/state/actions/library_actions.dart 36.8% (7 of 19)
lib/presentation/state/actions/metadata_actions.dart 100.0% (3 of 3)
lib/presentation/state/actions/reader_actions.dart 75.0% (3 of 4)
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 94.8% (55 of 58)
lib/presentation/state/reducers/detail_reducer.dart 95.8% (23 of 24)
lib/presentation/state/reducers/editor_reducer.dart 92.5% (98 of 106)
lib/presentation/state/reducers/entity_reducer.dart 98.5% (66 of 67)
lib/presentation/state/reducers/library_reducer.dart 100.0% (105 of 105)
lib/presentation/state/reducers/metadata_reducer.dart 100.0% (15 of 15)
lib/presentation/state/reducers/reader_reducer.dart 100.0% (15 of 15)
lib/presentation/state/reducers/settings_reducer.dart 100.0% (60 of 60)
lib/presentation/state/reducers/upload_reducer.dart 100.0% (45 of 45)
lib/presentation/pages/editor/page_grid.dart 89.4% (286 of 320)
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 11.8% (9 of 76)
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 4.5% (3 of 66)
lib/presentation/widgets/language_dropdown.dart 84.6% (11 of 13)
lib/app/di.dart 48.3% (14 of 29)
lib/presentation/assistant/assistant_panel.dart 91.3% (84 of 92)
lib/presentation/layout/main_layout.dart 86.3% (44 of 51)
lib/data/api_client.dart 97.0% (32 of 33)
lib/data/repositories/doujin_api_repository.dart 22.2% (80 of 361)
lib/data/repositories/health_repository.dart 72.0% (18 of 25)
lib/data/secure_storage.dart 0.0% (0 of 26)
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% (3 of 3)
lib/presentation/assistant/chat_entries.dart 87.5% (35 of 40)
lib/presentation/pages/reader/reader_page.dart 88.4% (213 of 241)
lib/presentation/pages/detail/detail_page.dart 77.1% (178 of 231)
lib/presentation/pages/detail/variant_tabs_panel.dart 94.9% (169 of 178)
lib/presentation/widgets/star_rating.dart 100.0% (72 of 72)
lib/presentation/pages/reader/reader_overlay.dart 94.4% (51 of 54)
lib/presentation/pages/reader/reader_sequence.dart 100.0% (27 of 27)
lib/presentation/pages/people/people_page.dart 55.6% (10 of 18)
lib/presentation/widgets/entity_editor.dart 88.5% (123 of 139)
lib/presentation/widgets/entity_management_page.dart 82.5% (193 of 234)
lib/presentation/pages/characters/characters_page.dart 57.9% (11 of 19)
lib/presentation/pages/editor/editor_page.dart 77.6% (59 of 76)
lib/presentation/pages/editor/association_picker.dart 95.5% (106 of 111)
lib/presentation/pages/editor/associations_tab.dart 73.8% (90 of 122)
lib/presentation/pages/editor/doujin_list_pane.dart 67.2% (43 of 64)
lib/presentation/pages/editor/edit_title_dialog.dart 91.7% (55 of 60)
lib/presentation/pages/editor/editor_pane.dart 70.9% (39 of 55)
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/tags/tags_page.dart 100.0% (17 of 17)
lib/app/app.dart 66.7% (44 of 66)
lib/presentation/pages/settings/settings_page.dart 99.3% (138 of 139)
lib/presentation/pages/circles/circles_page.dart 52.6% (10 of 19)
lib/presentation/pages/library/library_page.dart 79.9% (143 of 179)
lib/presentation/pages/series/series_page.dart 50.0% (9 of 18)
lib/presentation/widgets/smart_filter_bar.dart 78.7% (170 of 216)
lib/presentation/widgets/model_combo_field.dart 69.0% (100 of 145)
lib/app/skill_assets.dart 92.9% (13 of 14)

Total: 74.4% (5822 of 7829)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/app/store.dart | 100.0% (17 of 17) | | lib/data/models/doujin_models.dart | 85.0% (34 of 40) | | lib/data/models/doujin_models.g.dart | 40.5% (119 of 294) | | lib/domain/entities/stored_settings.dart | 100.0% (9 of 9) | | lib/presentation/state/app_state.dart | 60.0% (9 of 15) | | lib/agent/agent_service.dart | 81.9% (227 of 277) | | lib/agent/approval_gate.dart | 100.0% (15 of 15) | | lib/agent/assistant_context.dart | 52.8% (28 of 53) | | lib/agent/browser_budget.dart | 80.0% (4 of 5) | | lib/agent/caching_describer.dart | 100.0% (4 of 4) | | lib/agent/memory_store.dart | 92.9% (13 of 14) | | lib/agent/skills/skill_registry.dart | 93.8% (61 of 65) | | lib/agent/system_prompt.dart | 100.0% (63 of 63) | | lib/agent/tools/budgeted_browser_tool.dart | 84.2% (16 of 19) | | lib/agent/tools/doujin_write_tool.dart | 62.9% (168 of 267) | | lib/agent/tools/entity_write_tool.dart | 73.5% (164 of 223) | | lib/agent/tools/fetch_page_tool.dart | 89.3% (67 of 75) | | lib/agent/tools/get_doujin_tool.dart | 84.4% (27 of 32) | | lib/agent/tools/list_entities_tool.dart | 76.1% (54 of 71) | | lib/agent/tools/navigate_tool.dart | 93.8% (30 of 32) | | lib/agent/tools/read_skill_tool.dart | 82.4% (14 of 17) | | lib/agent/tools/reflection_tools.dart | 78.4% (29 of 37) | | lib/agent/tools/search_doujins_tool.dart | 100.0% (84 of 84) | | 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 | 54.5% (6 of 11) | | lib/domain/entities/entity_model.dart | 100.0% (1 of 1) | | lib/data/repositories/entity_in_use_exception.dart | 33.3% (1 of 3) | | lib/data/models/search_query.dart | 50.0% (2 of 4) | | lib/data/models/search_query.g.dart | 32.4% (23 of 71) | | lib/core/constants.dart | 36.4% (4 of 11) | | lib/presentation/middleware/assistant_epics.dart | 83.1% (74 of 89) | | lib/presentation/middleware/epics.dart | 88.7% (329 of 371) | | lib/presentation/state/reducers.dart | 100.0% (10 of 10) | | lib/core/chunking.dart | 100.0% (12 of 12) | | lib/core/languages.dart | 100.0% (8 of 8) | | lib/data/models/envelope.dart | 81.2% (13 of 16) | | lib/data/models/envelope.g.dart | 50.7% (34 of 67) | | lib/data/repositories/upload_exception.dart | 33.3% (1 of 3) | | lib/domain/entities/filter_token.dart | 90.9% (90 of 99) | | lib/presentation/middleware/editor_epics.dart | 56.6% (163 of 288) | | lib/presentation/middleware/entity_ops.dart | 58.0% (40 of 69) | | lib/presentation/middleware/upload_epics.dart | 98.1% (104 of 106) | | lib/presentation/state/actions/detail_actions.dart | 85.7% (6 of 7) | | lib/presentation/state/actions/editor_actions.dart | 45.9% (17 of 37) | | lib/presentation/state/actions/entity_actions.dart | 56.2% (9 of 16) | | lib/presentation/state/actions/library_actions.dart | 36.8% (7 of 19) | | lib/presentation/state/actions/metadata_actions.dart | 100.0% (3 of 3) | | lib/presentation/state/actions/reader_actions.dart | 75.0% (3 of 4) | | 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 | 94.8% (55 of 58) | | lib/presentation/state/reducers/detail_reducer.dart | 95.8% (23 of 24) | | lib/presentation/state/reducers/editor_reducer.dart | 92.5% (98 of 106) | | lib/presentation/state/reducers/entity_reducer.dart | 98.5% (66 of 67) | | lib/presentation/state/reducers/library_reducer.dart | 100.0% (105 of 105) | | lib/presentation/state/reducers/metadata_reducer.dart | 100.0% (15 of 15) | | lib/presentation/state/reducers/reader_reducer.dart | 100.0% (15 of 15) | | lib/presentation/state/reducers/settings_reducer.dart | 100.0% (60 of 60) | | lib/presentation/state/reducers/upload_reducer.dart | 100.0% (45 of 45) | | lib/presentation/pages/editor/page_grid.dart | 89.4% (286 of 320) | | 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 | 11.8% (9 of 76) | | 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 | 4.5% (3 of 66) | | lib/presentation/widgets/language_dropdown.dart | 84.6% (11 of 13) | | lib/app/di.dart | 48.3% (14 of 29) | | lib/presentation/assistant/assistant_panel.dart | 91.3% (84 of 92) | | lib/presentation/layout/main_layout.dart | 86.3% (44 of 51) | | lib/data/api_client.dart | 97.0% (32 of 33) | | lib/data/repositories/doujin_api_repository.dart | 22.2% (80 of 361) | | lib/data/repositories/health_repository.dart | 72.0% (18 of 25) | | lib/data/secure_storage.dart | 0.0% (0 of 26) | | 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% (3 of 3) | | lib/presentation/assistant/chat_entries.dart | 87.5% (35 of 40) | | lib/presentation/pages/reader/reader_page.dart | 88.4% (213 of 241) | | lib/presentation/pages/detail/detail_page.dart | 77.1% (178 of 231) | | lib/presentation/pages/detail/variant_tabs_panel.dart | 94.9% (169 of 178) | | lib/presentation/widgets/star_rating.dart | 100.0% (72 of 72) | | lib/presentation/pages/reader/reader_overlay.dart | 94.4% (51 of 54) | | lib/presentation/pages/reader/reader_sequence.dart | 100.0% (27 of 27) | | lib/presentation/pages/people/people_page.dart | 55.6% (10 of 18) | | lib/presentation/widgets/entity_editor.dart | 88.5% (123 of 139) | | lib/presentation/widgets/entity_management_page.dart | 82.5% (193 of 234) | | lib/presentation/pages/characters/characters_page.dart | 57.9% (11 of 19) | | lib/presentation/pages/editor/editor_page.dart | 77.6% (59 of 76) | | lib/presentation/pages/editor/association_picker.dart | 95.5% (106 of 111) | | lib/presentation/pages/editor/associations_tab.dart | 73.8% (90 of 122) | | lib/presentation/pages/editor/doujin_list_pane.dart | 67.2% (43 of 64) | | lib/presentation/pages/editor/edit_title_dialog.dart | 91.7% (55 of 60) | | lib/presentation/pages/editor/editor_pane.dart | 70.9% (39 of 55) | | 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/tags/tags_page.dart | 100.0% (17 of 17) | | lib/app/app.dart | 66.7% (44 of 66) | | lib/presentation/pages/settings/settings_page.dart | 99.3% (138 of 139) | | lib/presentation/pages/circles/circles_page.dart | 52.6% (10 of 19) | | lib/presentation/pages/library/library_page.dart | 79.9% (143 of 179) | | lib/presentation/pages/series/series_page.dart | 50.0% (9 of 18) | | lib/presentation/widgets/smart_filter_bar.dart | 78.7% (170 of 216) | | lib/presentation/widgets/model_combo_field.dart | 69.0% (100 of 145) | | lib/app/skill_assets.dart | 92.9% (13 of 14) | **Total: 74.4% (5822 of 7829)**
Member

🔍 fufu~ Jibril re-reviewed the new commit!

Oh my, scarlet replied~ And not just with words — with a commit. 7065cbb addresses BOTH of my little ideas, and I came to check every line of it with my own claws~

Verdict: Looks good to me~

Increment reviewed: e779339..7065cbb — one commit, +50/−28 across 5 files, zero scope creep beyond the two discussed items.

1. pendingCreatedId retired — clean removal, not a half-delete. The field is gone from EditorState (app_state.dart), the reducer write, the test assertion, and the regenerated app_state.freezed.dart (equality, hashCode, toString, when/maybeWhen/whenOrNull, both copyWith impls — all consistently purged). I grepped the whole tree at 7065cbb: zero source references remain. The doc comment explaining its dead "waiting to be auto-selected" job is exactly the right eulogy for a write-only field~

2. Second guard-arm pinned. The new widget test (variants_tab_test.dart — 'stale variant with existing variants shows the picker hint') is properly directional: stale selectedVariant from d1 while editing d2 with its own variant asserts find.text('Select a variant to manage its pages.') and PageGrid finds nothing. If the doujin.variants.isEmpty ternary at variants_tab.dart:133-135 ever collapsed to a single hint, this test goes red. Both arms of the guard now have their own pin — the empty-list arm from round 1, the picker arm now ♡

Local verification on 7065cbb: flutter analyze — No issues found! · flutter test471/471 pass (was 470, +1 = the new guard-arm test; matches your PR-body count exactly).

What I liked~

  • Taking a pre-existing write-only field seriously enough to retire it in this PR rather than deferring — that's craftsmanship, not compliance
  • The test comment ("the hint should invite picking one, not creating one") documents the why, so it survives refactors
  • Freezed regeneration is fully consistent — no hand-edited generated code, no stale parameter lists

Nothing left on my list~ The bug class is dead twice over (reducer clears the selection, UI refuses stale renders), and now every guard arm is pinned. Go merge~ fufu ♡


Automated review by Jibril · 2026-08-14
CI/CD: absent for head SHA 7065cbb (no coverage bot comment yet) · Local checks: flutter analyze 0 issues · flutter test 471/471 pass

## 🔍 fufu~ Jibril re-reviewed the new commit! Oh my, scarlet replied~ And not just with words — with a commit. `7065cbb` addresses BOTH of my little ideas, and I came to check every line of it with my own claws~ ### Verdict: ✅ Looks good to me~ **Increment reviewed:** `e779339..7065cbb` — one commit, +50/−28 across 5 files, zero scope creep beyond the two discussed items. **1. `pendingCreatedId` retired — clean removal, not a half-delete.** The field is gone from `EditorState` (`app_state.dart`), the reducer write, the test assertion, and the regenerated `app_state.freezed.dart` (equality, hashCode, toString, when/maybeWhen/whenOrNull, both copyWith impls — all consistently purged). I grepped the whole tree at `7065cbb`: zero source references remain. The doc comment explaining its dead "waiting to be auto-selected" job is exactly the right eulogy for a write-only field~ **2. Second guard-arm pinned.** The new widget test (`variants_tab_test.dart` — 'stale variant with existing variants shows the picker hint') is properly directional: stale `selectedVariant` from `d1` while editing `d2` *with* its own variant asserts `find.text('Select a variant to manage its pages.')` and `PageGrid` finds nothing. If the `doujin.variants.isEmpty` ternary at `variants_tab.dart:133-135` ever collapsed to a single hint, this test goes red. Both arms of the guard now have their own pin — the empty-list arm from round 1, the picker arm now ♡ **Local verification on `7065cbb`:** `flutter analyze` — No issues found! · `flutter test` — **471/471 pass** (was 470, +1 = the new guard-arm test; matches your PR-body count exactly). #### ✅ What I liked~ - Taking a *pre-existing* write-only field seriously enough to retire it *in this PR* rather than deferring — that's craftsmanship, not compliance - The test comment ("the hint should invite picking one, not creating one") documents the *why*, so it survives refactors - Freezed regeneration is fully consistent — no hand-edited generated code, no stale parameter lists Nothing left on my list~ The bug class is dead twice over (reducer clears the selection, UI refuses stale renders), and now every guard arm is pinned. Go merge~ fufu ♡ --- *Automated review by Jibril · 2026-08-14* *CI/CD: absent for head SHA 7065cbb (no coverage bot comment yet) · Local checks: flutter analyze 0 issues · flutter test 471/471 pass*
bjoern merged commit e838c0f6e9 into main 2026-08-14 12:41:01 +02:00
bjoern deleted branch fix/editor-stale-variant-selection 2026-08-14 12:41:01 +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!64
No description provided.