chore: upgrade file_picker to 12 and unblock flutter_secure_storage 11 #133

Merged
bjoern merged 1 commit from chore/package-upgrades-file-picker-12 into main 2026-09-11 20:15:06 +02:00
Member

What's in

file_picker 12 is stable, which clears the win32 ^5/^6 deadlock that held flutter_secure_storage at 10.x. The two cannot be split — flutter_secure_storage 11 needs win32 ^6, file_picker 11 pins win32 ^5, and pub refuses the combination:

Because file_picker 11.0.3 depends on win32 ^5.9.0 ... and because
flutter_secure_storage >=11.0.0-beta.1 depends on
flutter_secure_storage_windows ^4.2.2 which depends on win32 ^6.0.1,
file_picker ^11.0.3 is incompatible with flutter_secure_storage >=11.0.0-beta.1.
  • file_picker 11.0.3 → 12.3.0, flutter_secure_storage 10.3.1 → 11.1.0 (win32 6.4.0)
  • dio 5.11.1, html 0.15.7, very_good_analysis 11.0.0

The backup download had to be restructured. file_picker 12's saveFile writes the file itself and requires the bytes up front, and there is no save-path-only API left, so the download can no longer stream to a path chosen in advance. The flow inverts: the epic downloads the backup into memory, then opens the save dialog. A backup is the database only — images live on their own volumes — so this is metadata-sized. Nothing touches the disk until the whole body has arrived, so the "a half-finished download must not leave something that looks like a backup" property that deleteOnError gave us still holds.

That moves the save dialog out of the Backups section and next to the epic as BackupSaveDialog; the page has nothing left to ask before the download runs. The upload's open dialog stays in the page. DownloadBackupAction loses its destinationPath, and a dismissed dialog now dispatches BackupDownloadCancelledAction so the spinner clears without a notice.

Asking for bytes broke error reporting, which the tests caught. With responseType: bytes the error bodies arrive as bytes too, so the server's problem-detail JSON stopped parsing and refusals were reduced to "HTTP 400" — losing the server's own sentence about the required file-name shape. _problemDetail now decodes a bytes body, and falls back quietly when the body is not JSON at all (a proxy's HTML error page).

Other call sites: pickFiles() returns List<PlatformFile> rather than a nullable FilePickerResult, and single-file picks move to pickFile(). That last one mattered — _pickZip did result?.files.single.path, and since v12 returns an empty list on cancel, a naive port would have thrown StateError instead of returning null.

very_good_analysis 11 adds async_return_with_no_await (19 explicit awaits, semantics unchanged) and drops one_member_abstracts, so its ignore went with it.

Verification

scripts/verify.sh green — backend + app, 931 app tests.

  • Persistence / REST: downloadBackup returns the bytes with responseType: bytes; refusals keep the server's own sentence; a non-JSON error body still reports its status instead of crashing.
  • Use case / epic: download → save dialog ordering, the bytes actually reaching the dialog, cancel clearing the spinner without a notice, and a failed download never reaching the dialog at all.
  • UI: the Backups section download and cancel paths through a faked BackupSaveDialog; upload unchanged.
  • Mutation-probed (all four go red when the branch is removed): the epic's cancel branch, the reducer's cancel branch, the bytes problem-detail decoding, and its FormatException fallback.
  • Linux desktop build green, and file_picker_linux registers through the generated Dart plugin registrant (dartPluginClass, no native registration needed).

Not verified:

  • The upload_panel.dart picker ports (pickFile/pickFiles for images, ZIP and folder) are compile-checked only. FilePicker is called statically there with no seam, and the existing widget tests only assert the buttons render. Worth a manual pass on those three buttons before merge.
  • file_picker_linux 1.1.0 now drives Linux dialogs through the XDG Desktop Portal over D-Bus with no zenity fallback, so a desktop without xdg-desktop-portal running loses every file dialog. Confirmed working by hand on Fedora 44; nothing in CI exercises it. (Its PlatformFile.lengthSync() returns null on Linux, but we size files ourselves with dart:io, so that does not reach us.)
  • Windows and macOS builds were not run; the win32 6 bump is the part most likely to want a look there.

Follow-ups

All recorded in docs/BACKLOG.md:

  • build_runner held at 2.15 — 2.16 requires freezed 4, which is what unblocks the Dart 3.13 language version. Raising it adds ~520 auto-fixable unnecessary_type_name_in_constructor infos, so it wants its own PR.
  • material_ui/cupertino_ui migration is now 69 files (44 lib/, 25 test/), not the 61 recorded in #62, and the SDK design libraries are deprecated in the November 2026 stable. gpt_markdown is the only dependency still on package:flutter/material.dart (so it needs MaterialUiCompatibilityBridge); go_router 18 migrates itself and belongs in that same PR. The note in #62 that file_picker needed bridging was wrong — it has no Material imports.
  • jni stays pinned at 1.0.0: 1.0.3's dartjni.c still calls JNI_CreateJavaVM without the __ENVP_CAST.
  • The two untested areas above.

🤖 Generated with Claude Code

## What's in `file_picker` 12 is stable, which clears the win32 `^5`/`^6` deadlock that held `flutter_secure_storage` at 10.x. The two cannot be split — `flutter_secure_storage` 11 needs `win32 ^6`, `file_picker` 11 pins `win32 ^5`, and pub refuses the combination: ``` Because file_picker 11.0.3 depends on win32 ^5.9.0 ... and because flutter_secure_storage >=11.0.0-beta.1 depends on flutter_secure_storage_windows ^4.2.2 which depends on win32 ^6.0.1, file_picker ^11.0.3 is incompatible with flutter_secure_storage >=11.0.0-beta.1. ``` - `file_picker` 11.0.3 → 12.3.0, `flutter_secure_storage` 10.3.1 → 11.1.0 (win32 6.4.0) - `dio` 5.11.1, `html` 0.15.7, `very_good_analysis` 11.0.0 **The backup download had to be restructured.** `file_picker` 12's `saveFile` writes the file itself and requires the bytes up front, and there is no save-path-only API left, so the download can no longer stream to a path chosen in advance. The flow inverts: the epic downloads the backup into memory, then opens the save dialog. A backup is the database only — images live on their own volumes — so this is metadata-sized. Nothing touches the disk until the whole body has arrived, so the "a half-finished download must not leave something that looks like a backup" property that `deleteOnError` gave us still holds. That moves the save dialog out of the Backups section and next to the epic as `BackupSaveDialog`; the page has nothing left to ask before the download runs. The upload's open dialog stays in the page. `DownloadBackupAction` loses its `destinationPath`, and a dismissed dialog now dispatches `BackupDownloadCancelledAction` so the spinner clears without a notice. **Asking for bytes broke error reporting, which the tests caught.** With `responseType: bytes` the *error* bodies arrive as bytes too, so the server's problem-detail JSON stopped parsing and refusals were reduced to "HTTP 400" — losing the server's own sentence about the required file-name shape. `_problemDetail` now decodes a bytes body, and falls back quietly when the body is not JSON at all (a proxy's HTML error page). Other call sites: `pickFiles()` returns `List<PlatformFile>` rather than a nullable `FilePickerResult`, and single-file picks move to `pickFile()`. That last one mattered — `_pickZip` did `result?.files.single.path`, and since v12 returns an *empty list* on cancel, a naive port would have thrown `StateError` instead of returning null. `very_good_analysis` 11 adds `async_return_with_no_await` (19 explicit awaits, semantics unchanged) and drops `one_member_abstracts`, so its `ignore` went with it. ## Verification `scripts/verify.sh` green — backend + app, 931 app tests. - **Persistence / REST**: `downloadBackup` returns the bytes with `responseType: bytes`; refusals keep the server's own sentence; a non-JSON error body still reports its status instead of crashing. - **Use case / epic**: download → save dialog ordering, the bytes actually reaching the dialog, cancel clearing the spinner without a notice, and a failed download never reaching the dialog at all. - **UI**: the Backups section download and cancel paths through a faked `BackupSaveDialog`; upload unchanged. - **Mutation-probed** (all four go red when the branch is removed): the epic's cancel branch, the reducer's cancel branch, the bytes problem-detail decoding, and its `FormatException` fallback. - **Linux desktop build** green, and `file_picker_linux` registers through the generated Dart plugin registrant (`dartPluginClass`, no native registration needed). **Not verified:** - The `upload_panel.dart` picker ports (`pickFile`/`pickFiles` for images, ZIP and folder) are **compile-checked only**. `FilePicker` is called statically there with no seam, and the existing widget tests only assert the buttons render. Worth a manual pass on those three buttons before merge. - `file_picker_linux` 1.1.0 now drives Linux dialogs through the **XDG Desktop Portal over D-Bus with no zenity fallback**, so a desktop without `xdg-desktop-portal` running loses every file dialog. Confirmed working by hand on Fedora 44; nothing in CI exercises it. (Its `PlatformFile.lengthSync()` returns null on Linux, but we size files ourselves with `dart:io`, so that does not reach us.) - Windows and macOS builds were not run; the win32 6 bump is the part most likely to want a look there. ## Follow-ups All recorded in `docs/BACKLOG.md`: - `build_runner` held at 2.15 — 2.16 requires `freezed` 4, which is what unblocks the Dart 3.13 language version. Raising it adds ~520 auto-fixable `unnecessary_type_name_in_constructor` infos, so it wants its own PR. - `material_ui`/`cupertino_ui` migration is now **69 files** (44 `lib/`, 25 `test/`), not the 61 recorded in #62, and the SDK design libraries are deprecated in the November 2026 stable. `gpt_markdown` is the only dependency still on `package:flutter/material.dart` (so it needs `MaterialUiCompatibilityBridge`); `go_router` 18 migrates itself and belongs in that same PR. The note in #62 that `file_picker` needed bridging was wrong — it has no Material imports. - `jni` stays pinned at 1.0.0: 1.0.3's `dartjni.c` still calls `JNI_CreateJavaVM` without the `__ENVP_CAST`. - The two untested areas above. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore: upgrade file_picker to 12 and unblock flutter_secure_storage 11
All checks were successful
Flutter CI / analyze-and-test (pull_request) Successful in 4m28s
643808dc49
file_picker 12 is stable, so the win32 ^5/^6 deadlock that held
flutter_secure_storage at 10.x is gone: secure_storage 11 needs
win32 ^6, file_picker 11 pinned win32 ^5, and 12 moves to ^6. The two
have to travel together — secure_storage 11 will not resolve against
file_picker 11 — so they are one change.

file_picker 12 is breaking in a way the backup download cannot absorb
mechanically. `saveFile` now writes the file itself and requires the
bytes up front, and there is no save-path-only API left, so the
download can no longer stream to a path dio picked earlier. The flow
inverts: the epic downloads the backup into memory, then opens the
save dialog. A backup is the database only, so this is metadata-sized.
Nothing reaches the disk until the whole body has arrived, so the
truncated-file guarantee dio's `deleteOnError` gave us still holds.

That moves the save dialog out of the Backups section and next to the
epic as BackupSaveDialog — the page has nothing left to ask before the
download. The upload's open dialog stays where it was.

Asking for bytes also broke error reporting: with `responseType:
bytes` the *error* bodies arrive as bytes too, so the server's
problem-detail JSON no longer parsed and refusals lost their sentence.
_problemDetail now decodes a bytes body, and falls back quietly when
it is not JSON at all.

Also in: dio 5.11.1, html 0.15.7, very_good_analysis 11. The new
`async_return_with_no_await` lint wants an explicit await on 19
returns; `one_member_abstracts` is gone, so its ignore went with it.

build_runner stays at 2.15 — 2.16 requires freezed 4, which needs the
language version raised to 3.13. That is its own PR.

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 84.6% (33 of 39)
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/backup_save_dialog.dart 25.0% (1 of 4)
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 97.5% (39 of 40)
lib/data/models/server_models.g.dart 54.5% (67 of 123)
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/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% (90 of 90)
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 76.0% (19 of 25)
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% (118 of 118)
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 37.0% (160 of 432)
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% (285 of 287)
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 99.1% (462 of 466)
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.7% (8804 of 10651)

<!-- 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 | 84.6% (33 of 39) | | 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/backup_save_dialog.dart | 25.0% (1 of 4) | | 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 | 97.5% (39 of 40) | | lib/data/models/server_models.g.dart | 54.5% (67 of 123) | | 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/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% (90 of 90) | | 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 | 76.0% (19 of 25) | | 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% (118 of 118) | | 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 | 37.0% (160 of 432) | | 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% (285 of 287) | | 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 | 99.1% (462 of 466) | | 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.7% (8804 of 10651)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A dependency unlock where the dependency is the easy part and the API inversion is the real work — and you traced every ripple of it~ The win32 ^5/^6 deadlock explanation in the PR body is exactly the kind of provenance I adore. And you caught the _pickZip StateError trap yourselves — files.single on the empty list v12 returns on cancel. Fufu~, you know how to make a reviewer feel unnecessary~ ♡

Verdict: ✅ Looks good to me~

I verified this against the actual fetched packages, not the changelogs' promises:

  • file_picker 12.3.0 API surface confirmed from ~/.pub-cache: pickFiles → List<PlatformFile> (empty on cancel), pickFile → PlatformFile? (null on cancel), saveFile({required bytes}) → Uri?. All three ports in upload_panel.dart / server_sections.dart / backup_save_dialog.dart match the real signatures — including allowMultiple's new default of true (deprecated but intact), so multi-image picks survive the dropped parameter.
  • The download→dialog inversion is forced, and you handled it correctly. With no save-path API left, the seam moves to the epic, and the injection chain createStore(backupSaveDialog:) → createAppEpic → createServerEpics(saveDialog:) with const defaults means production wiring needed zero changes while tests get full control. That's the textbook way to invert a flow.
  • BackupSaveDialog.save handles the Uri? return precisely: isScheme('file') → toFilePath() for the notice the operator recognizes, toString() otherwise, null = cancel. Correct on all three shapes.
  • Mutation-probed all four claims myself, RED→green each: removed the epic's cancel ternary → cancel test fails; removed the reducer's BackupDownloadCancelledAction arm → the store-level cancel test fails (the reducer branch is pinned through the real store, not just in isolation); bypassed _decodedBody → the refusal test loses the server's sentence; rethrew the FormatException → the HTML-error-page test dies. None of them are tautologies~
  • The "no half-finished file that looks like a backup" property survives the restructure — nothing touches disk until the whole body arrives, and the replacement comment documents exactly that.
  • flutter_secure_storage 10→11 is safe here: v11's breaking changes are Android-cipher removals and AndroidOptions parameter deletions; this app constructs a bare FlutterSecureStorage() and only uses read/write/delete, both unchanged. And since you were on 10.3.1 (not pre-10), the v10 cipher migration already ran — no data-loss path. The v11.1.0 checkUpgradeStatus() addition is optional and irrelevant to this usage.
  • Local checks at Flutter 3.47.3: flutter analyze — No issues found; flutter test — 931/931 pass, matching your PR body's claim exactly.

💡 Little ideas (non-blocking)~

  1. app/lib/presentation/pages/settings/settings_page.dart:133 — the backupDialogs field doc still says "opens for download and upload," but the download dialog moved out of the page in this PR. You updated the sibling comment on BackupFileDialogs itself; this one drifted. Doc-only.
  2. Web, for the record: file_picker_web 3.1.0's saveFile fires the browser download and returns null — so on web a successful backup download dispatches BackupDownloadCancelledAction and shows no notice at all (the file does land in Downloads). Desktop is the verified target and the null-handling is the documented contract, so this is an upstream quirk worth a BACKLOG line at most, not a change here.

✅ What I liked~

  • The PR body's "Not verified" section is the most honest thing I've read all week — declaring the three compile-checked-only picker ports and the XDG-Portal dependency out loud instead of burying them~ ♡
  • The _problemDetail bytes-decode fix with its FormatException fallback — you didn't just fix the regression your own tests caught, you covered the proxy-HTML case too.
  • BACKLOG.md updated with the freezed/build_runner chain and the corrected migration file count. Knowledge recorded is knowledge kept~

Automated review by Jibril · 2026-09-11
CI/CD: absent for head SHA (PR just opened, no coverage bot yet) · Local checks: analyze 0 issues, 931/931 tests, 4/4 mutation probes RED-verified

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A dependency unlock where the *dependency* is the easy part and the *API inversion* is the real work — and you traced every ripple of it~ The win32 `^5`/`^6` deadlock explanation in the PR body is exactly the kind of provenance I adore. And you caught the `_pickZip` `StateError` trap yourselves — `files.single` on the empty list v12 returns on cancel. Fufu~, you know how to make a reviewer feel unnecessary~ ♡ ### Verdict: ✅ Looks good to me~ I verified this against the *actual* fetched packages, not the changelogs' promises: - **`file_picker` 12.3.0 API surface confirmed from `~/.pub-cache`**: `pickFiles` → `List<PlatformFile>` (empty on cancel), `pickFile` → `PlatformFile?` (null on cancel), `saveFile({required bytes})` → `Uri?`. All three ports in `upload_panel.dart` / `server_sections.dart` / `backup_save_dialog.dart` match the real signatures — including `allowMultiple`'s new default of `true` (deprecated but intact), so multi-image picks survive the dropped parameter. - **The download→dialog inversion is forced, and you handled it correctly.** With no save-path API left, the seam moves to the epic, and the injection chain `createStore(backupSaveDialog:) → createAppEpic → createServerEpics(saveDialog:)` with `const` defaults means production wiring needed zero changes while tests get full control. That's the textbook way to invert a flow. - **`BackupSaveDialog.save` handles the `Uri?` return precisely**: `isScheme('file') → toFilePath()` for the notice the operator recognizes, `toString()` otherwise, null = cancel. Correct on all three shapes. - **Mutation-probed all four claims myself, RED→green each**: removed the epic's cancel ternary → cancel test fails; removed the reducer's `BackupDownloadCancelledAction` arm → the store-level cancel test fails (the reducer branch is pinned through the real store, not just in isolation); bypassed `_decodedBody` → the refusal test loses the server's sentence; rethrew the `FormatException` → the HTML-error-page test dies. None of them are tautologies~ - **The "no half-finished file that looks like a backup" property survives the restructure** — nothing touches disk until the whole body arrives, and the replacement comment documents exactly that. - **`flutter_secure_storage` 10→11 is safe here**: v11's breaking changes are Android-cipher removals and `AndroidOptions` parameter deletions; this app constructs a bare `FlutterSecureStorage()` and only uses `read`/`write`/`delete`, both unchanged. And since you were on 10.3.1 (not pre-10), the v10 cipher migration already ran — no data-loss path. The v11.1.0 `checkUpgradeStatus()` addition is optional and irrelevant to this usage. - **Local checks at Flutter 3.47.3**: `flutter analyze` — No issues found; `flutter test` — **931/931 pass**, matching your PR body's claim exactly. #### 💡 Little ideas (non-blocking)~ 1. **`app/lib/presentation/pages/settings/settings_page.dart:133`** — the `backupDialogs` field doc still says "opens for download and upload," but the download dialog moved out of the page in this PR. You updated the sibling comment on `BackupFileDialogs` itself; this one drifted. Doc-only. 2. **Web, for the record**: `file_picker_web` 3.1.0's `saveFile` fires the browser download and *returns null* — so on web a successful backup download dispatches `BackupDownloadCancelledAction` and shows no notice at all (the file does land in Downloads). Desktop is the verified target and the null-handling is the documented contract, so this is an upstream quirk worth a BACKLOG line at most, not a change here. #### ✅ What I liked~ - The PR body's **"Not verified"** section is the most honest thing I've read all week — declaring the three compile-checked-only picker ports and the XDG-Portal dependency out loud instead of burying them~ ♡ - The `_problemDetail` bytes-decode fix with its `FormatException` fallback — you didn't just fix the regression your own tests caught, you covered the proxy-HTML case too. - BACKLOG.md updated with the freezed/build_runner chain and the corrected migration file count. Knowledge recorded is knowledge kept~ --- *Automated review by Jibril · 2026-09-11* *CI/CD: absent for head SHA (PR just opened, no coverage bot yet) · Local checks: analyze 0 issues, 931/931 tests, 4/4 mutation probes RED-verified*
bjoern merged commit 09e36a6a13 into main 2026-09-11 20:15:06 +02:00
bjoern deleted branch chore/package-upgrades-file-picker-12 2026-09-11 20:15:06 +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!133
No description provided.