Detail page redesign: variant tabs, page thumbnails, and user rating #45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/detail-page-variant-tabs"
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?
Summary
Makes the Detail page visually rich instead of a flat variant-card list: variants are now
tabs with the default one preselected, each showing its page thumbnails immediately —
and the left panel's star rating is now the actual user rating feature (ADR 0023),
editable in place. Thumbnails are wired as the future reader entry point.
App
default variant auto-selected when the doujin loads — same default-first logic as the
editor. Switching tabs fetches that variant's detail; a stale-load guard keeps fast tab
switches correct.
and chapter counts), then read-only page thumbnails:
mirroring the editor's grid.
place for the reader phase.
button when rated. Rating updates refresh the doujin from the PUT response without
losing the selected variant tab (the detail reducer now preserves selection on reloads,
and errors no longer wipe the on-screen doujin).
Backend
clearRatingflag onPUT /api/doujins/{id}(mirrorsclearCoverImage): withpartial-update semantics,
rating: nullmeans "leave unchanged", so clearing needed anexplicit switch. No schema change.
Verification
flutter analyzeclean — new coverage for tab rendering +default auto-select, tab switching with chapter collapse, and rating clear with the
variant selection surviving the refresh.
Flutter Coverage
Total: 68.0% (3496 of 5141)
Summary
Summary
Coverage
DoujinManager.ApplicationCore - 85.2%
DoujinManager.Infrastructure - 94.3%
on
pshot
Series
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
DoujinManager.RestAdapter - 85.1%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 27.1%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A detail page that finally breathes — variants as tabs, thumbnails blooming into view, and stars you can actually press~ ♪ I do love watching a flat list grow a spine. And you brought the backend along politely:
clearRatingwalks the exact same path as its older siblingclearCoverImage, so I didn't even have to raise an eyebrow. Fufu~ let me tell you what I found while I was… thoroughly enjoying myself.Verdict: ✅ Looks good to me~
The dangerous corners are all handled, and I looked closely — I promise ♡
detail_reducer.dart):if (state.detail.selectedVariantId != action.variant.id) return state;— a fast tab-switch can't paint the wrong variant's pages. That's the exact race I went hunting for. Denied~ ♡detail_reducer.dart):DoujinDetailLoadedActionnowcopyWiths instead of rebuildingDetailState, and_autoSelectDetailVariantEpiconly fires whenselectedVariantId == null. So a rating PUT round-trip won't yank you back to the default tab. Lovely.detail_reducer.dart):DetailErrorActionkeeps the on-screen doujin and just surfaces the message. Much kinder than the oldDetailState(error: …)nuke.detail_page.dart→epics.dart): clear dispatchesSetDoujinRatingAction(doujinId: …)withrating: null, and the epic mapsclearRating: action.rating == null. Set vs. clear vs. leave-alone all distinct — and the backend'sif (rating is not null) … else if (clearRating) …respects the documented "rating wins over clearRating" precedence. Verified against the newUpdate_Rating_Set_And_Clearintegration test~variant_tabs_panel.dart):joinServerUrl(serverUrl, ApiPaths.thumbnail(...))— no more hardcoded/api/thumbnails/{id}strings, no more trailing-slash double-slash. This quietly fixes the exact thing I fussed about back on #35 and #37. I noticed. I always notice. ♡CI is green for your exact head
22bdc83— build (19s), test (53s), and Flutter analyze-and-test (1m21s) all success, posted right after the commit. So I trusted CI and kept my hands off a local rebuild. Static scan on the added lines: spotless — no secrets, injection, eval/exec, or unsafe deserialization.💡 Little ideas (non-blocking)~
variant_tabs_panel.dart(_openReader(models.Page page)) — thepageparameter is unused; the placeholder just shows a snackbar. Harmless, but a stray unused param is exactly the kind of loose thread I'd tidy before the reader phase actually consumes it.// ignore: unused elementor just_openReader()for now~variant_tabs_panel.dart(_VariantPagesView._sectionsgetter) — you re-sort pages + chapters and rebuild the grouping on everybuild(). Totally fine at doujin page counts, but if a monster 800-page variant ever wanders in, caching_sections(recompute only whenwidget.variantchanges) would keep scroll buttery. A whisper, not a demand.variant_tabs_panel.dart(didUpdateWidget↔TabBar.onTap) — both the tap handler and the store-syncanimateTocan drive the controller. They converge (store is source of truth), so no bug — just noting the two-master setup in case a future edit forgets which one wins.Everything I'd call blocking is absent. Ship it when you're ready — and do come back with the reader phase soon. I'll be waiting. ♡
Automated review by Jibril · 2026-07-02
CI/CD: passed for head
22bdc83c(build + test + Flutter analyze-and-test all success) · Local checks: skipped per CI-evidence policy