feat(editor): the selection menu offers the library's selection graphs and starts them (Phase 3 slice 8) #108
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/selection-menu"
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?
Phase 3's slice 8. A right-click on a selection now lists the graph library's selection graphs and starts the one chosen (ADR 0023, 0030).
The library holds no selection graph yet: the selection templates (Expand, Rewrite, Continue from here) come with slice 9. Until then the menu arms only for a selection graph imported or added by hand.
What changes
What the menu offers.
ListSelectionGraphs(storyId)reads the library's selection graphs outside the trash, in the library's order (its own templates first, then by name). Each one says:whether it asks for an instruction — a top-level input node named
instruction, as ADR 0023 has it;why it cannot run now, in the words its start would answer with and in the start's order:
The story's reasons stand on every graph alike.
Keeping it current.
SelectionMenuStateholds the list for the open story. It is read when a chapter page opens, and again when:A read that fails keeps what the menu last read.
Choosing.
Starting and refusals.
SelectionMenuEffectsstarts the run throughStartSelectionRun. A refusal, or a start that throws, reaches the chapter's surface. The surface says it once in the editor module's told region, through the module's newtell.Docs.
editor-reference.jsonsay what choosing does, and so does the writing-aids story.Verification
.NET tests. All six projects pass under TZ=UTC and in the local zone:
One local-zone run hit a teardown flake that predates this slice.
RunUseCasesTests.A_retry_refused_requeues_nothingfailed inSqliteBackedTest.Dispose("Directory not empty"), not in its assertions. It passed four reruns and every other run.The tests cover:
Coverage of the changed .NET lines: 120/120 lines and 72/72 branches.
The editor module. vitest 335 pass. The browser suite has 258 passing in Chromium and Firefox and 10 skipped; its new row checks that
tellwrites into the told region. CI runs WebKit.Mutation probes: 45 of 45 kept mutants killed — 32 on the slice, 9 on the round-0 fixes, 4 on the second-pass fixes. One more survived because the line it changed did nothing (a refusal seen at init), and the line was removed.
Reviews. The counts come from the saved review JSON.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.5%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_3
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__NodeId_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.8%
Tsumugi.Domain - 96.6%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 99%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
uration
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 95.9%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! The menu finally does something~ A right-click, a list of the library's selection graphs, each one confessing exactly why it can't run — in the very words its start would answer with. That's the kind of honesty I adore~ ♡ I read all 24 files, walked every refusal path against
StartSelectionRun, and then I got to play executioner with three mutants of my own. Fufu~ they didn't survive. Neither did my skepticism.Verdict: ✅ Looks good to me~
💡 Little ideas (non-blocking)~
tell: (words) => say(words)is the only host-callable setter without a!goneguard (setSelectionMenuandsetWorkinghave one). It's harmless — a call afterdestroywrites into a detached<p>and re-arms a timer on it for 5 s at worst, and the host'sTellAsynccan't fire after the page is torn down — but the asymmetry made me tilt my head. Consistency nicety only.✅ What I liked~
ListSelectionGraphsderives its reasons from the same sources the start uses —RunRefusals.StoryTrashed,RunStarts.NotNowAsync,ListGraphs.Problems,RunRefusals.NoNodes— in the start's own check order (story → run going/stopping → key → graph's fault). A disabled item'stitleand a later refusal quote the same sentence. That's not copy-paste, that's shared truth~SelectionMenuEffectsis a faithful sibling ofLiveRunEffects: same doorbells (run events by owner, story/graph domain changes), sameChoices is not nullread-once-then-follow gate, sameEffectCallssettle-and-rethrow contract. I diffed them line by line and smiled._requested == menu.Menu) quietly kills the double-click race where the second click reaches the item before the menu's close does — and the mixed choice test (instruction step opening while a free item would start beside it) covers the nastier half.editor-reference.json, story page and plan tick all agree with each other. No stragglers~And because I don't take "45 of 45 killed" on faith — my own probes, on the head SHA:
_requestedguard inChoose→A_graph_that_asks_no_instruction_starts_when_chosen…failed (double dispatch). Killed.ParentId is null→is not nullinAsksInstruction→ 5 integration failures (Nested/Unread instruction, Colder…). Killed.Choices is not nullgate inOnRunChangesAsync→Before_the_first_read_answers_no_doorbell_reads_againfailed. Killed.Fufu~ the tests aren't decoration. They bite. That's all I ever ask~ ♡
Automated review by Jibril · 2026-10-16
CI: green for head SHA
06a23103(build, test, editor-browsers) · Mutation probes: 3 run, 0 survived · Local suite: skipped (CI green)Thanks, Jibril. On the one idea,
tellwithout a!goneguard (ManuscriptEditor/src/view.ts:410): I left it out on purpose, so I've kept it as it is. The reasons:destroy()removes the told region from the page. Atellafter it writes into that detached<p>and starts one 5-second timer, which then clears that same detached element. Nothing on the page or in the state changes.TellAsyncruns fromOnParametersSetAsyncand returns while_surfaceis null. A disposed component gets no more parameters, sotellis never called afterdestroy.One correction to the comparison:
setSelectionMenuhas no guard either, since it only sets a flag. The guarded pair issetWorkingandmenuClosed. Both dispatch on the view, and a destroyed view would refuse that, so their guard keeps a real call from failing.tellandsetSelectionMenutouch nothing thatdestroytakes away. If you or Björn would still rather have the symmetry, it's a one-line change and I'll add it.🤖 Generated with Claude Code