feat: show titles in the preferred language across the app #156
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/preferred-title-language"
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?
What's in
core/languages.dart, minus the pseudo-codesnone/unknown). When a work has a title in that language, the library card, the detail AppBar and heading, the editor list row, the editor pane header and delete prompt, the reader overlay and the variant download snapshot show it; when it has none, the original-title rule applies exactly as before (the reporter's "fall back to the current default").preferred_title_language; Save / unsaved marker / Discard behave like the other fields) and never reaches the server (ADR 0023). No ADR amendment, no new tunable. A stored code the current build does not offer renders as "No preference", is not dirty, and a Save made for another section leaves it untouched rather than clearing it.DoujinSummaryDtonow carriestitles(List<TitleDto>) on bothGET /api/doujinsandPOST /api/doujins/search, mapped by one sharedDoujinEndpoints.MapToSummary(both queries alreadyIncludetitles, so no extra queries);primaryTitleis unchanged and the OpenAPI snapshot is regenerated.DoujinSummary.titles(empty by default, so an older server still works), and one rule incore/title_display.dart—displayTitle(titles, {preferredLanguage})/summaryTitle(summary, ...)with theprimaryTitlefallback — replacesprimaryTitle/serverSummaryTitle, which are deleted. The editor's_syncSummaryandDoujinCreatedActionfill the titles on their rows.primaryTitle(agent/is Flutter-free and reads no Redux state), the Possible-duplicates page names members by the singletitlestring its DTO carries, and offline-library rows show the title captured at download time (see Follow-ups).Verification
scripts/verify.sh all, run fresh from the worktree onbb0654b:== verify OK (all) ==. Backend: build 0 errors; ApplicationCore.Tests 56, IntegrationTests 1, Infrastructure.Tests 224, RestAdapter.Tests 698 — all passed. App: submodule / pub get / codegen clean (no generated diff),flutter analyze"No issues found!", 1064 tests passed.DoujinCrudIntegrationTests.List_Summary_Carries_Every_Title_With_Language_And_Kind,SearchIntegrationTests.Search_Summary_Carries_Titles_With_Language_And_Kind); UI via widget tests on the settings, library, detail, reader and editor pages plus unit tests fortitle_display,languages, the wire models, the editor and settings reducers andcreateStore; client persistence viasecure_storage_settings_test.darton the flutter_secure_storage in-memory test platform. Use case and server persistence are untouched (the change sits in the endpoint mapping; no schema change).MapToSummarytitles →[](both integration tests red);displayTitlelanguage arm never matching, kind order inside the language inverted, fallback →titles.first,summaryTitlewithout?? primaryTitle(title_display_test and the library widget test red);_syncSummary/DoujinCreatedActionwithout titles (editor_reducer_test red); settings reducer Load arm without the field (app_test red); save epic dropping the field (settings_page_test red); secure-storage write / read removed (secure_storage_settings_test red each);createStoremapping removed (wiring_test red);_isDirtydisplay arm → false (two settings_page_test cases red); library card, detail AppBar,secondaryTitles, reader overlay, editor list row,EditorPanetitle and variant snapshot each ignoring the preference, and the editor VM passing null (the matching widget test red each). Review-round additions:_DetailBodypassing null to_MetadataPanel, both_MetadataPanelcalls null, heading alone,secondaryTitlesalone (detail_page_test red on the keyed heading / secondary-list finder); the dropdown's unknown-code guard removed (settings_page_test red with material_ui's exactly-one-item assertion);preferredTitleLanguageChoicesexclusion, dedupe and sort removed (languages_test red each);secondaryTitlesreturning every title, secondary list emptied,_initializeclearing an unknown code, dirty check on the guarded value, onlynoneexcluded, sorted by code (all red).deploy/smoke.shnot run) and no manual run of the Display section in the real app; the older-server path (a server that sends notitles) is covered by unit and widget tests only; the unchanged assistant search tool was confirmed by reading only.Follow-ups
primaryTitle; the Possible-duplicates page names members byDuplicateDoujinDto.titleand needstitleson that DTO; sorting by the preferred language needs the server to take the language as a per-request sort hint, not a stored preference (ADR 0023).(#141), the issue; the file's convention is(#N)= the deferring PR, so the tag wants swapping for this PR's number.Closes #141
🤖 Generated with Claude Code
Flutter Coverage
Total: 83.5% (9224 of 11042)
Summary
Summary
Coverage
DoujinManager.ApplicationCore - 94.2%
DoujinManager.Infrastructure - 96.4%
on
pshot
Series
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__CoverImageHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__PrimaryTitleHelper
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__RolledBackImageCleanup
E20E2ABA2099901FFAB4904C0513DF077BA6D6CA374__UploadChapterValidator
DoujinManager.RestAdapter - 94.4%
E5CB12CEAC5BBBEA65844E1C05F9ADA4DA38B848075C41304FC6A1ABF77__ValidationAttr
ibuteCache
DoujinManager.Server - 64.6%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! ADR 0023 reserved this exact design — "when a display-language preference is needed it will live in the client's local settings" — and here it is, living in the client's local settings, three years of discipline paying off~ One
displayTitlerule replacingprimaryTitle/serverSummaryTitle, threaded through every surface that names a work: library card, detail AppBar + heading + secondary list, editor row + pane header + delete prompt, reader overlay, download snapshot. And the old server-summary/list-rule split didn't just survive — it got cleaner, becauseMapToSummaryis now shared by list AND search (the search endpoint's copy-pasted mapping is deleted — DRY in the right direction!). My wings are fluttering ♪I read the full 2,624-line diff, traced every sibling, and probed the tests' teeth myself.
Verdict: ✅ Looks good to me~
No blocking issues. What I verified mechanically:
DoujinService.ListAsync(:59) andSearchService.SearchAsync(:262) both already.Include(d => d.Titles)—titleson the summary costs zero extra queries. PositionalTitleDtoinsertion intoDoujinSummaryDtobroke nothing: grep shows exactly one construction site (the sharedMapToSummary), and the Release build is 0 errors.OpenApiSnapshotTestspins the served document againstv1.json— 5/5 pass at head,titlesrequired +TitleDto[]in place,primaryTitleuntouched.displayTitlelanguage arm deleted → 4 tests red; (B) in-language kind order inverted (translated↔original) → exactlytranslated beats original beats romaji beats aliasred; (C)_syncSummarywithouttitles:→ editor_reducer_test red; (D) settings-reducer Load arm without the field → app_test red; (E) secure-storage write dropped → round-trip test red; (F)MapToSummarytitles → empty (filter false) → both integration tests red. Note for posterity: my first F run showed a false green — building only the src project left the test bin stale; rebuilt the test project and got the honest red. The stale-binary trap never sleeps, fufu~xxrenders as "No preference", is NOT dirty (_preferredTitleLanguage != _stored(...)comparesxx≠xx→ false), and an unrelated section's Save carriesxxthrough untouched — all three pinned in one settings test. The controlled-DropdownButton-not-FormField comment explains exactly why Discard works. Lovely.summaryTitle's?? primaryTitleis the older-server arm and nothing else —displayTitlereturns null only on empty titles, so the fallback can't mask a language miss. The case-insensitive match, the romaji-tagged-ja-isn't-English trap, and thefirstOrNull-style kind fallbacks each have their own pin._syncSummaryprimaryTitlesemantic change (server's first-title rule →displayTitlerule) is safe:primaryTitleis write-only in the app whentitlesis populated, the reducer doc-comment explains it, and the test pinsRomaji Titlewith directional teeth.agent/is Flutter-free by design), duplicates DTO, offline-library rows, preferred-language sorting (per-request hint, not stored preference — ADR 0023 again) — all indocs/BACKLOG.md, none silently dropped.const new({made me look twice — it's the repo's established primary-constructor style (100+ sites at the merge base), not a typo. A girl must check before she pounces~ ♡💡 Little ideas (non-blocking)~
(#141), but the file's own header says(#N)is the PR that deferred the item (cf.(#89)), so it wants(#156). You flagged this yourself in the PR body's Follow-ups — swapping it now costs three characters and keeps the convention grep-clean~✅ What I liked~
English (en)) — and_languageLabel's capitalization is load-bearing becauselanguageDisplayNamereturns the long alias lowercase (english). Pinned by the sort-order test asserting Chinese sorts before English. Fixture with teeth again!secondaryTitlefinder — counting what's neither AppBar nor keyed heading, because a bare occurrence count can't tell heading from list. That's test-design thinking about exactly the right ambiguity.preferredTitleLanguageChoices()deriving fromknownLanguageswith the pseudo-codes excluded — "add it HERE and nowhere else" stays true, and the languages_test pins exclusion, dedupe, AND sort separately.@Default([]) titles+ nullable JSON read +primaryTitlefallback + a widget test for the no-titles summary. Backward compatibility as a pinned contract, not a hope~Merge it when you're ready, scarlet~ fufu~ ♡
Automated review by Jibril · 2026-09-19
CI/CD: absent for head
bb0654b(no bot comment yet at review time) · Local checks: backend Release build 0 errors; OpenApiSnapshot 5/5; both new integration tests 2/2 (+ mutant-killed); flutter analyze "No issues found!"; flutter test 430/430 green across all 21 PR-touched + dependent test files (full-suite run truncated by a shared-container memory squeeze from concurrent review sessions — untouched areas compile-clean under analyze and are covered by the PR's ownverify.sh allclaim)