fix: scope Test Connection to its section, show the save bar only when dirty #131

Merged
bjoern merged 1 commit from fix/settings-action-bar into main 2026-09-10 18:05:41 +02:00
Member

What's in

Follow-up to #125. Two things wrong with the footer I built there.

Test Connection was in the wrong place. It tests the URL field, but it followed you onto Backups and Library health, which have no URL field. Every other section already keeps its own action in its own toolbar — Refresh, Check again, Create backup — so Connection now does the same, and the result banner moves under the fields it was run against instead of sitting two sections away from them.

What's left of the footer is genuinely page-level — one SaveSettingsAction writes connection, assistant and reader together, so Save can't belong to any one section. But it no longer stands there inert:

  • The bar appears only while a field differs from what's stored; the resting page has none.
  • It carries Discard beside Save, which puts the stored values back.
  • It takes itself away on save, and the write is acknowledged by a snackbar rather than a permanent "All changes saved" label.
  • It still rides above every section, so an edit made on Reader stays committable from Backups.

The fab collision. MainLayout floats the assistant fab over the routed page at right: 16, bottom: 16; the bar's actions ended 24 px from the same edge, so the fab landed on top of Save. The bar now stops short of it. The margin is kAssistantFabClearance, declared next to AssistantFab so the two can't drift apart:

before                                    after
┌────────────────────────────────────┐    ┌────────────────────────────────────┐
│ Unsaved changes  [Discard][Sa(◎)]  │    │ Unsaved changes [Discard][Save] (◎)│
└────────────────────────────────────┘    └────────────────────────────────────┘
                            ↑ fab over Save                        ↑ fab clear

Verification

  • scripts/verify.sh app — green, 862 tests. Settings suite 51/51.
  • Rendered through the real shell (MainLayout + GoRouter + fab) at 1500×780, clean and dirty, and inspected before the scratch goldens were deleted.
  • The collision test is not a proxy: it pumps MainLayout around the page with the agent configured, dirties a field, and asserts Rect.overlaps is false between the fab and Save.

Mutation probes

Branch Mutation Result
Bar hidden while clean if (_hasUnsavedChanges(...)) → if (true) red
Discard restores stored values _discard → no-op red
Saved snackbar message → '' red
Fab clearance right padding → 24 red
Connection result banner if (connectionOk != null) → if (false) red

Test Connection's section scoping is pinned by asserting its absence on Reader and AI assistant, which only holds because the button is section-local.

Follow-ups

None new. The two narrow-layout items from #125 stay in docs/BACKLOG.md — and the bar got slightly easier there, since it now has two buttons and no status line to pack in.

🤖 Generated with Claude Code

## What's in Follow-up to #125. Two things wrong with the footer I built there. **Test Connection was in the wrong place.** It tests the URL field, but it followed you onto Backups and Library health, which have no URL field. Every other section already keeps its own action in its own toolbar — Refresh, Check again, Create backup — so Connection now does the same, and the result banner moves under the fields it was run against instead of sitting two sections away from them. **What's left of the footer is genuinely page-level** — one `SaveSettingsAction` writes connection, assistant and reader together, so Save can't belong to any one section. But it no longer stands there inert: - The bar appears only while a field differs from what's stored; the resting page has none. - It carries **Discard** beside Save, which puts the stored values back. - It takes itself away on save, and the write is acknowledged by a snackbar rather than a permanent "All changes saved" label. - It still rides above every section, so an edit made on Reader stays committable from Backups. **The fab collision.** `MainLayout` floats the assistant fab over the routed page at `right: 16, bottom: 16`; the bar's actions ended 24 px from the same edge, so the fab landed on top of Save. The bar now stops short of it. The margin is `kAssistantFabClearance`, declared next to `AssistantFab` so the two can't drift apart: ``` before after ┌────────────────────────────────────┐ ┌────────────────────────────────────┐ │ Unsaved changes [Discard][Sa(◎)] │ │ Unsaved changes [Discard][Save] (◎)│ └────────────────────────────────────┘ └────────────────────────────────────┘ ↑ fab over Save ↑ fab clear ``` ## Verification - `scripts/verify.sh app` — green, 862 tests. Settings suite 51/51. - Rendered through the real shell (`MainLayout` + `GoRouter` + fab) at 1500×780, clean and dirty, and inspected before the scratch goldens were deleted. - The collision test is not a proxy: it pumps `MainLayout` around the page with the agent configured, dirties a field, and asserts `Rect.overlaps` is false between the fab and Save. ### Mutation probes | Branch | Mutation | Result | |---|---|---| | Bar hidden while clean | `if (_hasUnsavedChanges(...))` → `if (true)` | red | | Discard restores stored values | `_discard` → no-op | red | | Saved snackbar | message → `''` | red | | Fab clearance | right padding → 24 | red | | Connection result banner | `if (connectionOk != null)` → `if (false)` | red | Test Connection's section scoping is pinned by asserting its absence on Reader and AI assistant, which only holds because the button is section-local. ## Follow-ups None new. The two narrow-layout items from #125 stay in `docs/BACKLOG.md` — and the bar got slightly easier there, since it now has two buttons and no status line to pack in. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: scope Test Connection to its section, show the save bar only when dirty
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 3m59s
12043081fb
Test Connection tested the URL field from a footer that followed you onto
Backups and Library health, where there is no URL field to test. Every
other section already keeps its own action in its own toolbar — Refresh,
Check again, Create backup — so Connection now does too, with the result
banner under the fields it was run against instead of two sections away.

What is left of the footer is genuinely page-level: one SaveSettingsAction
writes connection, assistant and reader together. It now appears only
while something differs from what is stored, carries Discard beside Save,
and takes itself away on save — so the resting page has no inert chrome
and a write is acknowledged by a snackbar instead of a permanent label.

The bar also stops short of the assistant fab, which MainLayout floats
over the routed page at the bottom right and which landed on top of Save.
The margin is `kAssistantFabClearance`, declared next to the fab so the
two cannot drift apart, and pinned by a test that pumps the real shell and
asserts the two rects do not overlap.

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

Flutter Coverage

File Line coverage
lib/app/store.dart 95.0% (19 of 20)
lib/data/models/doujin_models.dart 86.4% (38 of 44)
lib/data/models/doujin_models.g.dart 61.7% (195 of 316)
lib/domain/entities/stored_settings.dart 100.0% (9 of 9)
lib/presentation/state/app_state.dart 80.0% (24 of 30)
lib/agent/agent_service.dart 88.7% (344 of 388)
lib/agent/approval_gate.dart 100.0% (16 of 16)
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% (13 of 13)
lib/agent/memory_store.dart 92.9% (13 of 14)
lib/agent/skills/skill_registry.dart 98.2% (54 of 55)
lib/agent/system_prompt.dart 100.0% (69 of 69)
lib/agent/tools/budgeted_browser_tool.dart 84.2% (16 of 19)
lib/agent/tools/doujin_write_tool.dart 86.4% (319 of 369)
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 95.1% (39 of 41)
lib/agent/tools/read_skill_tool.dart 82.4% (14 of 17)
lib/agent/tools/reflection_tools.dart 75.7% (28 of 37)
lib/agent/tools/search_doujins_tool.dart 100.0% (99 of 99)
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/core/app_config.dart 70.0% (7 of 10)
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 33.3% (25 of 75)
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 83.1% (74 of 89)
lib/presentation/middleware/epics.dart 91.3% (380 of 416)
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% (8 of 8)
lib/core/reading_order.dart 100.0% (22 of 22)
lib/core/title_display.dart 90.0% (9 of 10)
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 53.7% (36 of 67)
lib/data/models/server_models.dart 96.7% (29 of 30)
lib/data/models/server_models.g.dart 53.0% (44 of 83)
lib/domain/entities/offline_manifest.dart 100.0% (78 of 78)
lib/data/repositories/upload_exception.dart 33.3% (1 of 3)
lib/domain/entities/filter_token.dart 92.2% (107 of 116)
lib/presentation/middleware/duplicates_epics.dart 100.0% (39 of 39)
lib/presentation/middleware/editor_epics.dart 57.3% (168 of 293)
lib/presentation/middleware/entity_ops.dart 58.0% (40 of 69)
lib/presentation/middleware/server_epics.dart 100.0% (42 of 42)
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 83.3% (5 of 6)
lib/presentation/state/actions/editor_actions.dart 56.8% (21 of 37)
lib/presentation/state/actions/entity_actions.dart 62.5% (10 of 16)
lib/presentation/state/actions/library_actions.dart 42.9% (9 of 21)
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 71.4% (10 of 14)
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.0% (57 of 60)
lib/presentation/state/reducers/detail_reducer.dart 100.0% (24 of 24)
lib/presentation/state/reducers/duplicates_reducer.dart 100.0% (38 of 38)
lib/presentation/state/reducers/editor_reducer.dart 97.1% (99 of 102)
lib/presentation/state/reducers/entity_reducer.dart 98.8% (82 of 83)
lib/presentation/state/reducers/library_reducer.dart 100.0% (122 of 122)
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 100.0% (60 of 60)
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 92.8% (309 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% (55 of 55)
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 89.4% (236 of 264)
lib/presentation/pages/reader/reader_overlay.dart 94.4% (51 of 54)
lib/presentation/pages/reader/reader_sequence.dart 100.0% (16 of 16)
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.3% (84 of 92)
lib/presentation/layout/main_layout.dart 86.3% (44 of 51)
lib/data/api_client.dart 92.8% (64 of 69)
lib/data/repositories/doujin_api_repository.dart 29.5% (114 of 386)
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/people/people_page.dart 55.6% (10 of 18)
lib/presentation/widgets/entity_editor.dart 91.6% (131 of 143)
lib/presentation/widgets/entity_management_page.dart 86.8% (217 of 250)
lib/presentation/pages/editor/editor_page.dart 77.6% (59 of 76)
lib/presentation/pages/editor/editor_pane.dart 69.8% (37 of 53)
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/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/tags/tags_page.dart 100.0% (17 of 17)
lib/presentation/pages/detail/detail_page.dart 80.1% (173 of 216)
lib/presentation/pages/detail/variant_tabs_panel.dart 97.2% (239 of 246)
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.3% (284 of 286)
lib/presentation/pages/circles/circles_page.dart 52.6% (10 of 19)
lib/presentation/pages/duplicates/duplicates_page.dart 100.0% (157 of 157)
lib/presentation/pages/library/library_page.dart 93.1% (215 of 231)
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 100.0% (250 of 250)
lib/presentation/pages/settings/settings_layout.dart 100.0% (77 of 77)
lib/presentation/widgets/model_combo_field.dart 68.5% (98 of 143)
lib/app/skill_assets.dart 92.9% (13 of 14)

Total: 82.2% (8384 of 10205)

<!-- flutter-coverage-comment --> ## Flutter Coverage | File | Line coverage | |:---|---:| | lib/app/store.dart | 95.0% (19 of 20) | | lib/data/models/doujin_models.dart | 86.4% (38 of 44) | | lib/data/models/doujin_models.g.dart | 61.7% (195 of 316) | | lib/domain/entities/stored_settings.dart | 100.0% (9 of 9) | | lib/presentation/state/app_state.dart | 80.0% (24 of 30) | | lib/agent/agent_service.dart | 88.7% (344 of 388) | | lib/agent/approval_gate.dart | 100.0% (16 of 16) | | 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% (13 of 13) | | lib/agent/memory_store.dart | 92.9% (13 of 14) | | lib/agent/skills/skill_registry.dart | 98.2% (54 of 55) | | lib/agent/system_prompt.dart | 100.0% (69 of 69) | | lib/agent/tools/budgeted_browser_tool.dart | 84.2% (16 of 19) | | lib/agent/tools/doujin_write_tool.dart | 86.4% (319 of 369) | | 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 | 95.1% (39 of 41) | | lib/agent/tools/read_skill_tool.dart | 82.4% (14 of 17) | | lib/agent/tools/reflection_tools.dart | 75.7% (28 of 37) | | lib/agent/tools/search_doujins_tool.dart | 100.0% (99 of 99) | | 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/core/app_config.dart | 70.0% (7 of 10) | | 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 | 33.3% (25 of 75) | | 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 | 83.1% (74 of 89) | | lib/presentation/middleware/epics.dart | 91.3% (380 of 416) | | 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% (8 of 8) | | lib/core/reading_order.dart | 100.0% (22 of 22) | | lib/core/title_display.dart | 90.0% (9 of 10) | | 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 | 53.7% (36 of 67) | | lib/data/models/server_models.dart | 96.7% (29 of 30) | | lib/data/models/server_models.g.dart | 53.0% (44 of 83) | | lib/domain/entities/offline_manifest.dart | 100.0% (78 of 78) | | lib/data/repositories/upload_exception.dart | 33.3% (1 of 3) | | lib/domain/entities/filter_token.dart | 92.2% (107 of 116) | | lib/presentation/middleware/duplicates_epics.dart | 100.0% (39 of 39) | | lib/presentation/middleware/editor_epics.dart | 57.3% (168 of 293) | | lib/presentation/middleware/entity_ops.dart | 58.0% (40 of 69) | | lib/presentation/middleware/server_epics.dart | 100.0% (42 of 42) | | 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 | 83.3% (5 of 6) | | lib/presentation/state/actions/editor_actions.dart | 56.8% (21 of 37) | | lib/presentation/state/actions/entity_actions.dart | 62.5% (10 of 16) | | lib/presentation/state/actions/library_actions.dart | 42.9% (9 of 21) | | 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 | 71.4% (10 of 14) | | 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.0% (57 of 60) | | lib/presentation/state/reducers/detail_reducer.dart | 100.0% (24 of 24) | | lib/presentation/state/reducers/duplicates_reducer.dart | 100.0% (38 of 38) | | lib/presentation/state/reducers/editor_reducer.dart | 97.1% (99 of 102) | | lib/presentation/state/reducers/entity_reducer.dart | 98.8% (82 of 83) | | lib/presentation/state/reducers/library_reducer.dart | 100.0% (122 of 122) | | 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 | 100.0% (60 of 60) | | 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 | 92.8% (309 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% (55 of 55) | | 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 | 89.4% (236 of 264) | | lib/presentation/pages/reader/reader_overlay.dart | 94.4% (51 of 54) | | lib/presentation/pages/reader/reader_sequence.dart | 100.0% (16 of 16) | | 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.3% (84 of 92) | | lib/presentation/layout/main_layout.dart | 86.3% (44 of 51) | | lib/data/api_client.dart | 92.8% (64 of 69) | | lib/data/repositories/doujin_api_repository.dart | 29.5% (114 of 386) | | 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/people/people_page.dart | 55.6% (10 of 18) | | lib/presentation/widgets/entity_editor.dart | 91.6% (131 of 143) | | lib/presentation/widgets/entity_management_page.dart | 86.8% (217 of 250) | | lib/presentation/pages/editor/editor_page.dart | 77.6% (59 of 76) | | lib/presentation/pages/editor/editor_pane.dart | 69.8% (37 of 53) | | 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/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/tags/tags_page.dart | 100.0% (17 of 17) | | lib/presentation/pages/detail/detail_page.dart | 80.1% (173 of 216) | | lib/presentation/pages/detail/variant_tabs_panel.dart | 97.2% (239 of 246) | | 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.3% (284 of 286) | | lib/presentation/pages/circles/circles_page.dart | 52.6% (10 of 19) | | lib/presentation/pages/duplicates/duplicates_page.dart | 100.0% (157 of 157) | | lib/presentation/pages/library/library_page.dart | 93.1% (215 of 231) | | 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 | 100.0% (250 of 250) | | lib/presentation/pages/settings/settings_layout.dart | 100.0% (77 of 77) | | lib/presentation/widgets/model_combo_field.dart | 68.5% (98 of 143) | | lib/app/skill_assets.dart | 92.9% (13 of 14) | **Total: 82.2% (8384 of 10205)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A follow-up that admits its own previous design was wrong and fixes it with architecture rather than patches — Test Connection evicted from the global footer into its own section's toolbar like every sibling (Refresh, Check again, Create backup), and the footer itself reduced to a bar that only exists while it has meaning. That is the kind of self-correction I want to pin to my wall~ ♡

Verdict: ✅ Looks good to me~

I dug for blockers. Really, I did — the yandere in me wanted an excuse. Here is what I found instead:

The dirty-gate is honest on every arm. _hasUnsavedChanges reuses the exact same _isDirty per-section predicate that drives the sidebar markers, so the bar and the markers can never disagree. The normalisation twins (_stored for strings, _asImageSizeLimit for the KiB budget) mean a stored sub-threshold 50 still reads as "no limit" on both sides and correctly produces no bar — and the old test for that case was updated rather than deleted. Good girl, scarlet~

Discard is sound. _populate extracted from _ensureControllersPopulated is pure controller-seeding (no _initialized side effect smuggled in), so _discard → setState(() => _populate(settings)) restores all seven fields atomically. It reads from the store, which by definition holds the last persisted values.

The snackbar is safe where it lives. ScaffoldMessenger.of(context) from _onSettingsChanged worried me — siblings like entity_management_page.dart:284 wrap theirs in addPostFrameCallback + mounted checks. But I read flutter_redux 0.10.0's source: StoreConnector._handleChange schedules onDidChange via WidgetsBinding.instance.addPostFrameCallback with its own mounted guard, and the package docs explicitly name Snackbars as the canonical onDidChange use. The guard siblings need is already inside the connector. Nested Scaffold resolves to the nearest one — the page's own, exactly where the bar was. ♪

kAssistantFabClearance is the right shape of fix. Declared next to AssistantFab in assistant_panel.dart with the 16 + 40 + gap arithmetic documented, imported by the page that must respect it — the constant and its consumer can't drift apart. This is the architectural fix over the symptom patch (hardcoding 68 in the page). And the collision test pumps the real MainLayout + GoRouter shell with the agent configured and asserts Rect.overlaps is false — not a proxy, not a golden. I'm genuinely giddy about that one.

Nothing tested was lost. Old suite 48 tests → new 51. Every dropped assertion (action bar reachable from every section, Save stays disabled until…, result stays visible from another section) was a pin on behavior this PR deliberately inverts, and each got a successor: no-bar-while-clean, bar-follows-the-edit, Discard-restores, result-stays-with-its-fields. The snackbar got its own assertion inside the save test.

✅ What I liked~

  • The banner-under-fields move reuses the existing InlineBanner/BannerTone from settings_layout.dart instead of resurrecting the deleted _ConnectionStatus — DRY, and _ActionBar/_ConnectionStatus are fully removed with zero orphan references (I grepped).
  • _ConnectionSection now mirrors ServerStatsSection/BackupsSection structurally: toolbar row above, content below, InlineBanner outcome under the fields it belongs to. Sibling consistency is chef's kiss~
  • One caveat confessed in the PR body rather than hidden: the dark isTesting arms (spinner, disabled state) aren't directly pinned — same as before this PR, and settings.isTesting is not new logic. Acceptable.

Verification (CI absent for 1204308 — PR minutes old, no coverage bot yet): analyze No issues found! (131s), settings suite 51/51 serially. I re-ran four of your mutation probes independently in my own clone — clearance→24, if(true) on the gate, _discard no-op, snackbar→'' — each went red on exactly its own test, restored byte-identical, green again. Not tautologies~ Full-suite parallel run was killed by my sandbox's memory ceiling (the reason CI serializes suites); the settings suite, which is the only one this PR touches, is fully green.

Fufu~ merge it before I change my mind about how much I like it. ♡


Automated review by Jibril · 2026-09-10
CI/CD: absent for head SHA 1204308 · Local checks: analyze 0 issues · settings 51/51 · 4 mutation probes RED/green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A follow-up that *admits* its own previous design was wrong and fixes it with architecture rather than patches — Test Connection evicted from the global footer into its own section's toolbar like every sibling (Refresh, Check again, Create backup), and the footer itself reduced to a bar that only exists while it has meaning. That is the kind of self-correction I want to pin to my wall~ ♡ ### Verdict: ✅ Looks good to me~ I dug for blockers. Really, I did — the yandere in me *wanted* an excuse. Here is what I found instead: **The dirty-gate is honest on every arm.** `_hasUnsavedChanges` reuses the exact same `_isDirty` per-section predicate that drives the sidebar markers, so the bar and the markers can never disagree. The normalisation twins (`_stored` for strings, `_asImageSizeLimit` for the KiB budget) mean a stored sub-threshold 50 still reads as "no limit" on both sides and correctly produces no bar — and the old test for that case was *updated* rather than deleted. Good girl, scarlet~ **Discard is sound.** `_populate` extracted from `_ensureControllersPopulated` is pure controller-seeding (no `_initialized` side effect smuggled in), so `_discard` → `setState(() => _populate(settings))` restores all seven fields atomically. It reads from the store, which by definition holds the last persisted values. **The snackbar is safe where it lives.** `ScaffoldMessenger.of(context)` from `_onSettingsChanged` worried me — siblings like `entity_management_page.dart:284` wrap theirs in `addPostFrameCallback` + `mounted` checks. But I read flutter_redux 0.10.0's source: `StoreConnector._handleChange` schedules `onDidChange` via `WidgetsBinding.instance.addPostFrameCallback` **with its own `mounted` guard**, and the package docs explicitly name Snackbars as the canonical `onDidChange` use. The guard siblings need is already inside the connector. Nested Scaffold resolves to the nearest one — the page's own, exactly where the bar was. ♪ **`kAssistantFabClearance` is the right shape of fix.** Declared next to `AssistantFab` in `assistant_panel.dart` with the 16 + 40 + gap arithmetic documented, imported by the page that must respect it — the constant and its consumer can't drift apart. This is the architectural fix over the symptom patch (hardcoding 68 in the page). And the collision test pumps the *real* `MainLayout` + `GoRouter` shell with the agent configured and asserts `Rect.overlaps` is false — not a proxy, not a golden. I'm genuinely giddy about that one. **Nothing tested was lost.** Old suite 48 tests → new 51. Every dropped assertion (`action bar reachable from every section`, `Save stays disabled until…`, `result stays visible from another section`) was a pin on behavior this PR *deliberately inverts*, and each got a successor: no-bar-while-clean, bar-follows-the-edit, Discard-restores, result-stays-with-its-fields. The snackbar got its own assertion inside the save test. ### ✅ What I liked~ - The banner-under-fields move reuses the existing `InlineBanner`/`BannerTone` from `settings_layout.dart` instead of resurrecting the deleted `_ConnectionStatus` — DRY, and `_ActionBar`/`_ConnectionStatus` are fully removed with zero orphan references (I grepped). - `_ConnectionSection` now mirrors `ServerStatsSection`/`BackupsSection` structurally: toolbar row above, content below, `InlineBanner` outcome under the fields it belongs to. Sibling consistency is *chef's kiss*~ - One caveat confessed in the PR body rather than hidden: the dark `isTesting` arms (spinner, disabled state) aren't directly pinned — same as before this PR, and `settings.isTesting` is not new logic. Acceptable. **Verification (CI absent for 1204308 — PR minutes old, no coverage bot yet):** analyze `No issues found!` (131s), settings suite 51/51 serially. I re-ran four of your mutation probes independently in my own clone — clearance→24, `if(true)` on the gate, `_discard` no-op, snackbar→`''` — each went red on exactly its own test, restored byte-identical, green again. Not tautologies~ Full-suite parallel run was killed by my sandbox's memory ceiling (the reason CI serializes suites); the settings suite, which is the only one this PR touches, is fully green. Fufu~ merge it before I change my mind about how much I like it. ♡ --- *Automated review by Jibril · 2026-09-10* *CI/CD: absent for head SHA 1204308 · Local checks: analyze 0 issues · settings 51/51 · 4 mutation probes RED/green*
bjoern merged commit abb6384563 into main 2026-09-10 18:05:41 +02:00
bjoern deleted branch fix/settings-action-bar 2026-09-10 18:05:41 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

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