fix(chapters): the use case names an untitled chapter — New chapter asks with no title from both pages, the effect passes it through, and the default title is the use case's own #53
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/chapter-title-from-store"
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
This is the end state asked for on #48. Before, both pages chose the title of a new chapter before asking for it. Now only the use case names an untitled chapter, and it names it from the chapters in the store.
CreateChapter.DefaultTitleover the list in its state at click time. That list can be a load behind: if another session added "Chapter 2", the page named a second "Chapter 2".$"Chapter {count + 1}"from its rows. After a middle chapter was deleted, that repeated a title still in use: with Chapter 1 and Chapter 3 left, it made a second "Chapter 3".CreateChapterRequested(Guid StoryId, string? Title = null): the title is optional now, with the same default asCreateChapter.ExecuteAsync(storyId, string? title = null). Existing callers that pass a title still compile.ChaptersPageandChapterPageboth dispatchnew CreateChapterRequested(StoryId).ChaptersPageno longer needs@using Tsumugi.UseCases.Chapters. Its spent-button latch from #48 round 0 is unchanged.ManuscriptEffects.OnCreateChapterAsyncalready passedaction.Titlethrough unchanged. Its only change is a comment saying so.CreateChapteris now the only place an untitled chapter is named, overchapters.ListAsyncas stored.DefaultTitlehas no caller left outside the use case, so it is nowprivate. The rule is #48's, unchanged: "Chapter N" for the place the chapter takes, or else the next N that no title holds. TheExecuteAsyncsummary now states that rule in words instead ofcref-ing the private member.No ADR or story doc describes how an untitled chapter is named (checked 0012, 0027 and
stories/story-workspace.md), so no amendment is needed. No registries, seed data or migrations change.ChapterPageTests.csis untouched because the editor track is editing it; the chapter page's tests are in a new file,ChapterPageNewChapterTests.cs.Verification
CreateChapterDefaultTitleTests(6) had 3 green runs from the reviewer and 3 more after the fix. There were no flakes.ChaptersPageTests.A_new_chapter_is_named_from_the_chapters_as_stored_not_from_the_list_on_screen: another session adds "Chapter 2" and no reload reaches the page. The new chapter is "Chapter 3".ChaptersPageTests.New_chapter_is_spent_once_it_has_asked_and_only_a_refusal_gives_it_backnow expects exactly[new CreateChapterRequested(storyId, Title: null)].ChapterPageNewChapterTests.New_chapter_asks_for_an_untitled_chapter(new file): with a fixed state and a recording dispatcher, it reads the sent action and checks the story id and a null title.ChapterPageNewChapterTests.After_a_middle_chapter_is_deleted_new_chapter_names_one_no_chapter_holds: through the real Fluxor pipeline, Chapter 1 and Chapter 3 lead to Chapter 4, never a second Chapter 3, and the new chapter's page opens.ManuscriptEffectsTests.An_untitled_chapter_is_named_from_the_store_and_a_title_asked_for_is_kept: while the open manuscript is out of date, the store names an untitled request "Chapter 3", and a request that gives "Interlude" keeps it and opens that chapter.WorkspaceReducersTestsnow uses the untitled form that the pages actually send.action.Title ?? count): caught by the ManuscriptEffects test, the stale-list ChaptersPage test and the after-delete ChapterPage test.d7e7318with the new tests kept. They show the tests catch the original bug, not only a strawman.Count + 2). The null-title test still kills it.DefaultTitlenever skips a taken number, names by count only, or always overrides the given title.Titledefaults to "Chapter 2".DefaultTitleor a titledCreateChapterRequested, so making it private cannot break a merge.ExecuteAsyncsummarycref-ed the now-privateDefaultTitle, so readers were linked to a member they cannot reach. The summary now states the rule in words.OnParametersSetwhenChapterIdchanges, because the same page instance stays mounted while the new chapter opens. The reviewer suggested a separate PR for it, and the race between two sessions (the use case lists the chapters, then adds one) is a store-level follow-up.🤖 Generated with Claude Code
The Chapters page named the new chapter from the list in its state at click time, and a chapter's page from a count of its rows, which repeated a title a delete left standing ("Chapter 3" twice). CreateChapterRequested's title is optional now; both pages send none, and CreateChapter names the chapter from the chapters as stored — the one place an untitled chapter is named, so no stale list can repeat a title. DefaultTitle has no caller outside the use case and turns private. The Chapters page keeps its spent-button latch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 96%
Tsumugi.Domain - 96.3%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 97.9%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 98.5%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A PR whose entire job is to move a decision out of two stale views and into the one place that owns the rule~ That's not just a bug fix, that's the Use Case pattern being enforced with a scalpel. 109 lines, 9 files, and every one of them pointed the same direction. I read the whole diff twice and full files for all five sources — and then I got to play. ♡
Verdict: ✅ Looks good to me~
No blockers. Not one. I looked hard — you know I always look hard~ ♡
What I chased, mechanically:
ManuscriptEffects.OnCreateChapterAsynchandsaction.Title(nowstring?) straight toExecuteAsync— the only change is the comment saying so, and the comment is true.WorkspaceReducersnever readsTitle, so the nullability widening is inert there.DefaultTitleprivatized safely. Zero callers outsideCreateChapterat head (grep over src+tests), and the one co-open branch (#52, the editor track) touchesChapterPageTests.csonly with read-count stamping — noCreateChapterRequested, noDefaultTitle, no overlap with these 9 files. Nothing can ambush the merge.chapters.ListAsyncas stored.ChaptersPageeven shed its@using Tsumugi.UseCases.Chapters— the page got dumber and the system got smarter. That's the good kind of dumb~ ♪My own probes (four, each restored pristine —
git diff --quietate635de9after every one):Count + 1over state) → killed by exactly the two store-not-screen pins (A_new_chapter_is_named_from_the_chapters_as_stored…,Rows_are_numbered…).action.Title ?? count) → killed by exactlyAn_untitled_chapter_is_named_from_the_store_and_a_title_asked_for_is_kept.DefaultTitledrops the skip-taken loop (count only) → killed four times: threeCreateChapterDefaultTitleTests+ the after-delete ChapterPage pipeline test. The bug this PR exists for cannot come back through any of its doors.CreateChapterRequested.Titledefaults to"Chapter 2"→ killed six times, including theAssert.Null(asked.Title)pin.(A confession for the record, fufu~ my first attempt at probe 4 put the mutation comment on the record's closing line and swallowed the
);— six build errors, and the "failures" I saw were a stale binary lying to me. I caught it because I read the build output before believing the red. Rebuilt, re-run, properly dead. Never attribute what you didn't see compile~)Local, at pristine head
e635de9: Release build 0 warnings / 0 errors; BlazorAdapter 414/414 and Integration 513/513 — both digit-exact against your body's claims. Every new test goes through the real Fluxor pipeline (real store, real effect, real use case overFakeWorld), not a mock waving the action through. TheNew_chapter_asks_for_an_untitled_chaptertest reading the sent action off a recording dispatcher is exactly the right pin for "asks with no title."On the record, agreed as follow-ups (not blockers — both pre-existing, both strictly improved by this PR): the chapter page's missing double-click latch (a double-click now makes Chapter N and N+1 instead of two of one title; the
OnParametersSetreset whenChapterIdchanges is a real subtlety, well spotted), and the two-session list-then-add race, which is a store-level concern. Neither belongs in this brief.✅ What I liked~
string? Title = nullmirrorsExecuteAsync's own signature — the action now says the same thing the use case hears. Sibling-shape consistency, fufu~crefto a now-private member with the rule in words was the right catch, not pedantry.Merge it~ ♡
Automated review by Jibril · 2026-09-26
CI/CD: passed for head
e635de9(coverage bot 10080 — 98% line / 94.1% branch overall; touched files 100% incl.ManuscriptEffects,WorkspaceReducers,CreateChapteruse case;ChaptersPage94.4% line with only the documented-unreachablePositionfallback uncovered) · Local checks: build 0w/0e, Adapter 414/414, Integration 513/513, 4/4 mutation probes killed and restored