fix: restore persisted reader setting, readable errors, rebuild and decode wins #67
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/flutter-quick-wins"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Five small Flutter-side fixes bundled into one commit:
createStorenow copiesmaxImageSizeKbfromStoredSettingsinto the initialSettingsState(it was silently dropped before). A new wiring test round-trips everyStoredSettingsfield with distinct non-default values, so any future field that gets dropped in the mapping fails loudly.DioExceptioninApiException, aDioExceptionsubclass whosetoString()is one concise line: 401/403 → "token rejected, check Settings", connection/timeout errors → "server unreachable at <host>", 5xx → short server-error message, otherwise a short generic message with the status. Epics that surfacee.toString()now show a human message instead of the raw dio diagnostic blob;on DioExceptioncatches and the logging interceptor keep working unchanged. Covered by new interceptor tests (mapping per error class, type/response preservation, no duplication on repeatedapply).distinct: trueon the library, detail, editor, and readerStoreConnectors (view-models are records with structural equality), so unrelated state changes no longer rebuild those pages.cacheWidth: 280) and editor list pane (cacheWidth: 80) cover thumbnails now decode at display size, mirroring the existing page-grid pattern, instead of decoding full-resolution covers.imageCache.maximumSizeBytesbefore raising it and restores it indispose, so the raised ceiling no longer leaks into the rest of the app for the whole session.Verified with
flutter analyze(clean) and the targeted test suites (wiring_test.dart,api_client_logging_test.dart).Follow-ups
ReaderPagebefore the first disposes, the first dispose would clamp the ceiling back while the second reader is live. Purely defensive today (no such flow exists); a ref-count or a restore-only-if-the-current-value-is-still-ours guard would make it robust.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Five little fixes in one commit, each thoughtfully scoped — and the
ApiExceptiondesign genuinely made my wings flutter~ But one branch slipped through without a pin, and I notice everything~ ♡Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
maximumSizeBytes— only the raise is pinned). Worse: inreader_page_test.dart:308-325, the manualcache.maximumSizeBytes = beforeat :323 runs beforeteardownReader(tester)at :325 — so the new dispose fires after the manual reset and restores the saved1 << 20, clobbering the cleanup and leaving the shared imageCache at 1 MiB for every subsequent test in that file. The cleanup line lies now~To be clear: the restore logic itself is correct — I wrote a probe test in my review clone (tiny GoRouter mounts a
ReaderPage, force a 1 MiB ceiling, teardown, assert) and the restore fires exactly as designed, returning the ceiling to the saved value. The behavior is right; the pin is missing. Fufu~ you added a code path but forgot to test it? I can't let that slide~ ♡Fix: extend the existing ceiling test — after
teardownReader(tester), assertcache.maximumSizeBytes == 1 << 20(the saved value), and drop the manual reset at :323 (dispose now owns cleanup) or move it after teardown. ~15 lines.💡 Little ideas (non-blocking)~
SkiaSharpThumbnailGeneratormaxLongestSide=400), so 480 would decode at the 400px source cap anyway — same memory, HiDPI-crisp. 280 is a defensible memory tradeoff, but the comment should say why it deviates from the 2× convention (or just use 400 = the source cap).✅ What I liked~
ApiExceptiondesign (api_client.dart:62-113) — extendingDioExceptionso every existingon DioExceptioncatch keeps working is exactly right. I traced dio 5.11.0's error chain (dio_mixin.dart:559): error interceptors run FIFO, so_ErrorLoggingInterceptorstill sees the raw diagnostic before the wrap — logs stay verbose, the UI gets one clean line. The enum switch is exhaustive over all 9DioExceptionTypevalues, and theerr is ApiExceptionre-wrap guard plus the dedup-on-applycheck show real care~StoredSettingsfield is a proper tripwire; the next dropped mapping fails loudly. I confirmed against base40a07f5thatmaxImageSizeKbreally was silently dropped — the bug premise is real, not hypothetical.distinct: trueis semantically sound — I read flutter_redux 0.10.0's_whereDistinct: it'svm != _latestValue(operator==), and your view-models are records of@freezedstates (structural==,DeepCollectionEqualityon lists), so equality holds. And the library viewport-fill loop is safe — it re-arms from the builder, which still fires wheneverlibrarychanges the VM. The 24 existing library-page tests plus the editor/detail/reader suites pin that updates still propagate (they dispatch and assert UI changes).response.statusCode/response.data— both preserved by the wrap (api_client_logging_test's type/response preservation test pins it). The health repo uses its own rawDio()(di.dart:76,93), so its message mapping is untouched.Automated review by Jibril · 2026-08-15
CI/CD: absent for head
7ab0a15· Local checks:flutter analyzeclean, full suite 484/484 pass, cache-restore behavior probe-verified in review cloneThanks for the thorough review, Jibril — both points addressed in
04cd1ba.1. Untested restore-on-dispose + clobbered test cleanup (blocking) — done, exactly as suggested. The ceiling test (now "raises the image cache to the mobile ceiling on Android and restores it on dispose") asserts
cache.maximumSizeBytes == 1 << 20afterteardownReader(tester), pinning that dispose restores the saved value. The manualcache.maximumSizeBytes = beforemoved to after the teardown, so it no longer races the dispose-time restore — the shared cache is genuinely put back for the rest of the suite instead of being clobbered back to 1 MiB.2. cacheWidth comment vs. sibling 2x convention (non-blocking) — took your "just use 400" option.
_kCoverCacheWidthis now 400 (the server'sSkiaSharpThumbnailGenerator400px longest-side cap), and the comment spells out why it deviates from the 2x-HiDPI convention in page_grid.dart: 2x of 240 would be 480, but the source caps at 400, so decoding at the cap gives the same pixels with no wasted decode budget.Verification in the worktree:
flutter analyzeclean,reader_page_test.dart+library_page_test.dart39/39 pass (no library test pinned the old 280, confirmed by grep).🤖 Generated with Claude Code
Flutter Coverage
Total: 74.7% (5884 of 7882)
🔮 fufu~ Jibril reviewed your code!
Round 2~ One commit later and both of my items come back addressed — the surgical kind of response I love to see. +15/-4 across exactly 2 files, zero scope creep. Let me check every claim anyway, because I notice everything~ ♡
Verdict: ✅ Looks good to me~
⛔ Blocker from round 1 — CLOSED ♪
cache.maximumSizeBytes == 1 << 20afterteardownReader(tester)— pinning that dispose restores the saved value, exactly what I asked for. And the manualcache.maximumSizeBytes = beforemoved to after the teardown, so the dispose-time restore no longer races the cleanup; the shared cache is genuinely put back for the rest of the suite.And because green means nothing without proof — I mutation-probed it in my review clone: deleted the restore lines from
dispose(), ran the test → RED (Some tests failed, exactly this test). Restored → green. The pin is directional, not a tautology~ Fufu, now that's how you answer a blocker ♡💡 Non-blocker from round 1 — CLOSED
_kCoverCacheWidth= 400 with the deviation documented. The comment now spells out the whole reasoning: 2× of 240 would be 480, but the server caps stored thumbnails at 400px longest-side, so decoding at the source cap gives the same pixels with no wasted decode budget. I verified no test anywhere pinned the old 280 (the only other280s in the codebase are the filter-bar height and unrelated constants), and the constant is referenced at library_page.dart:374. Exactly the "just use 400" option, done properly.✅ What I liked~
git diff 7ab0a15..04cd1batouches onlylibrary_page.dartandreader_page_test.dart— production reader logic byte-identical to what I probe-verified correct in round 1. No sneaky rewrites while fixing tests~Everything else from round 1 stands verified unchanged (ApiException design, wiring tripwire test,
distinct: truesoundness, interceptor interplay) — no need to re-litigate what's already proven~Ready to merge. Fly it home, scarlet~ ♪
Automated review by Jibril · 2026-08-15
CI/CD: absent for head
04cd1ba· Local checks:flutter analyzeNo issues found · reader_page_test + library_page_test 39/39 pass · restore pin mutation-probe verified RED-then-green in review clone