feat: the setup chat asks before the agent stops — continue/stop card, auto-scroll, Kagaku dress #41

Merged
bjoern merged 1 commit from feat/setup-chat-continuation into main 2026-07-26 07:53:37 +02:00
Member

Born from a real session on the setup chat (ADR 0017, 0018, 0020): the Research & Setup agent silently hit its 24-round cap mid-book, retry-with-distrust started a fresh attempt into the same seamless transcript, and the chat read as the agent "randomly forgetting" the conversation — no break, no error. This PR turns that cliff into a decision the user makes, and fixes the two chat-window complaints found on the way.

What's in

  • UseCases — the round cap becomes a checkpoint. AgentInvocation gains OnRoundCapReached (rounds so far → extra rounds or null), and ResearchSetupExecutor runs with a 50-round window: hitting it parks the loop on SetupConversation.AskContinueAsync, whose pending card the UI renders. Continue grants another 50 on the same loop with its context intact (the vendored agent's OnMaxRoundsReached hook — no OpenRouter.Net changes needed); Stop throws the new StageHaltedException.
  • UseCases — halt without retry. The engine treats StageHaltedException as terminal: the execution fails with "Stopped by you after N rounds." and is not re-attempted — a human decision must not feed retry-with-distrust a ghost restart. The row stays retryable by hand from the monitor (ADR 0018). All other failures keep the existing retry path.
  • Infrastructure. The gateway maps OnRoundCapReached onto the agent loop's OnMaxRoundsReached snapshot hook.
  • BlazorAdapter — the chat window. The continue-or-stop card (warning-striped, the one raised interrupter in the log) with Continue/Stop wired through a new DecideSetupContinuation action; auto-scroll pinning the log to its newest line via Kagaku's shipped assistant.js scrollToEnd — only on conversation growth, so a reader scrolled into the backlog is never yanked by keystroke renders; and a redesign onto Kagaku tokens: TypingIndicator instead of a bare <progress>, asymmetric bubble corners with tightened same-speaker seams, tool noises as quiet pills, motion suppressed under prefers-reduced-motion. The log now also renders for a just-started agent with no words yet — previously it showed nothing at all until the first message, and a cap hit before the first message would have had nowhere to show its card.

Tests — 430 total (was 419), all green.

  • SetupRunTests +7: the continue decision resumes the parked agent and records "Keep going." (and that the stop twin records "Stop here."); a decision with no pending card loses harmlessly; EndAttempt and cancellation each abandon the pending card (and that PendingQuestion handling stayed clean); the executor round-trip — cap parks on the card, continue is granted exactly one more RoundWindow and the run succeeds with the transcript intact; a stop halts with attempt still 1 and exactly one gateway invocation — the no-ghost-retry pin.
  • SetupChatTests +2 (over the real engine, only the LLM scripted): the card interrupts with the rounds spent and the working indicator yields to it, continue resumes the same loop ("Back to work." lands in the same transcript, card gone); stop ends the run with the agent quiet, one invocation ever, and the chat offering "Continue with a fresh agent" instead of silently restarting.

Honest notes

  • The auto-scroll is asserted structurally, not visually — bUnit's loose JS interop swallows the scrollToEnd call; the growth-gated trigger logic is plain component code exercised by every chat test's renders.
  • The 50-round window is a constant (ResearchSetupExecutor.RoundWindow), not a setting — a setting felt premature before a second agent needs one.
  • No submodule changes: both Kagaku.UI (assistant.js, TypingIndicator already shipped) and OpenRouter.Net (OnMaxRoundsReached already in the loop) are used as pinned.

🤖 Generated with Claude Code

Born from a real session on the setup chat (ADR 0017, 0018, 0020): the Research & Setup agent silently hit its 24-round cap mid-book, retry-with-distrust started a fresh attempt into the same seamless transcript, and the chat read as the agent "randomly forgetting" the conversation — no break, no error. This PR turns that cliff into a decision the user makes, and fixes the two chat-window complaints found on the way. **What's in** - *UseCases — the round cap becomes a checkpoint.* `AgentInvocation` gains `OnRoundCapReached` (rounds so far → extra rounds or null), and `ResearchSetupExecutor` runs with a 50-round window: hitting it parks the loop on `SetupConversation.AskContinueAsync`, whose pending card the UI renders. Continue grants another 50 **on the same loop with its context intact** (the vendored agent's `OnMaxRoundsReached` hook — no OpenRouter.Net changes needed); Stop throws the new `StageHaltedException`. - *UseCases — halt without retry.* The engine treats `StageHaltedException` as terminal: the execution fails with "Stopped by you after N rounds." and is **not** re-attempted — a human decision must not feed retry-with-distrust a ghost restart. The row stays retryable by hand from the monitor (ADR 0018). All other failures keep the existing retry path. - *Infrastructure.* The gateway maps `OnRoundCapReached` onto the agent loop's `OnMaxRoundsReached` snapshot hook. - *BlazorAdapter — the chat window.* The continue-or-stop card (warning-striped, the one raised interrupter in the log) with Continue/Stop wired through a new `DecideSetupContinuation` action; auto-scroll pinning the log to its newest line via Kagaku's shipped `assistant.js` `scrollToEnd` — only on conversation growth, so a reader scrolled into the backlog is never yanked by keystroke renders; and a redesign onto Kagaku tokens: `TypingIndicator` instead of a bare `<progress>`, asymmetric bubble corners with tightened same-speaker seams, tool noises as quiet pills, motion suppressed under `prefers-reduced-motion`. The log now also renders for a just-started agent with no words yet — previously it showed nothing at all until the first message, and a cap hit before the first message would have had nowhere to show its card. **Tests** — 430 total (was 419), all green. - `SetupRunTests` +7: the continue decision resumes the parked agent and records "Keep going." (and that the stop twin records "Stop here."); a decision with no pending card loses harmlessly; `EndAttempt` and cancellation each abandon the pending card (and that `PendingQuestion` handling stayed clean); the executor round-trip — cap parks on the card, continue is granted exactly one more `RoundWindow` and the run succeeds with the transcript intact; a stop halts with attempt **still 1** and exactly one gateway invocation — the no-ghost-retry pin. - `SetupChatTests` +2 (over the real engine, only the LLM scripted): the card interrupts with the rounds spent and the working indicator yields to it, continue resumes the same loop ("Back to work." lands in the same transcript, card gone); stop ends the run with the agent quiet, one invocation ever, and the chat offering "Continue with a fresh agent" instead of silently restarting. **Honest notes** - The auto-scroll is asserted structurally, not visually — bUnit's loose JS interop swallows the `scrollToEnd` call; the growth-gated trigger logic is plain component code exercised by every chat test's renders. - The 50-round window is a constant (`ResearchSetupExecutor.RoundWindow`), not a setting — a setting felt premature before a second agent needs one. - No submodule changes: both Kagaku.UI (`assistant.js`, `TypingIndicator` already shipped) and OpenRouter.Net (`OnMaxRoundsReached` already in the loop) are used as pinned. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: the setup chat asks before the agent stops — and dresses for Kagaku
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 42s
81c9254f5e
Three chat-window changes born from a real session where the agent silently hit
its 24-round cap mid-book and retry-with-distrust restarted it with amnesia:

- The round cap is now a checkpoint, not a wall. Research & Setup runs with a
  50-round window; hitting it parks the loop on a continue-or-stop card in the
  chat (OnMaxRoundsReached, threaded through AgentInvocation.OnRoundCapReached
  and SetupConversation.AskContinueAsync). Continue buys another window with the
  agent's context intact; Stop halts the execution terminally via
  StageHaltedException — a human decision must not feed retry-with-distrust a
  ghost restart. The row stays retryable by hand from the monitor.
- The log pins itself to the newest line (Kagaku's assistant.js scrollToEnd),
  and only on growth, so a reader scrolled into the backlog is never yanked.
- The window is redesigned on Kagaku tokens: TypingIndicator instead of a bare
  <progress>, bubble corners and seams, tool noises as quiet pills, the card as
  the one raised, warning-striped interrupter. The log also renders for a
  just-started agent with no words yet — its only sign of life.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/26/2026 - 05:29:29
Coverage date: 07/26/2026 - 05:29:15 - 07/26/2026 - 05:29:26
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 285
Files: 149
Line coverage: 95.2% (7012 of 7364)
Covered lines: 7012
Uncovered lines: 352
Coverable lines: 7364
Total lines: 13676
Branch coverage: 81% (1547 of 1908)
Covered branches: 1547
Total branches: 1908
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.4%
Name Line Branch
Orihon.BlazorAdapter 95.4% 87.7%
Orihon.BlazorAdapter.Bible.AddBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.AddCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.AddLoreRowRequested 100%
Orihon.BlazorAdapter.Bible.BibleEffects 92% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.3% 80.8%
Orihon.BlazorAdapter.Bible.BibleReducers 92.8%
Orihon.BlazorAdapter.Bible.BibleState 100%
Orihon.BlazorAdapter.Bible.BibleWriteFailed 100%
Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested 0%
Orihon.BlazorAdapter.Bible.LoadBible 100%
Orihon.BlazorAdapter.Bible.ReorderBeatsRequested 0%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 96.2% 94.4%
Orihon.BlazorAdapter.Diagnostics.CircuitError 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink 100% 85.7%
Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer 85.7% 66.6%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 88.3% 80.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 75%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState 100%
Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed 100%
Orihon.BlazorAdapter.PageWorkspace.RegionCreated 100%
Orihon.BlazorAdapter.PageWorkspace.RegionSaved 100%
Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.1%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94.1% 86.2%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 100% 97.6%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 100%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.4% 87.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 66.6%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.SetPageKindRequested 100%
Orihon.BlazorAdapter.Workspace.SummaryDeleted 100%
Orihon.BlazorAdapter.Workspace.SummarySaved 100%
Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested 100%
Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed 100%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 93.7%
Name Line Branch
Orihon.Infrastructure 93.7% 65.6%
Orihon.Infrastructure.Bible.EfBibleStore 100% 100%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98% 83%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 97% 50%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 100% 100%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.4%
Name Line Branch
Orihon.Server 93.4% 68.4%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 95.4% 85.7%
Orihon.UseCases - 97.7%
Name Line Branch
Orihon.UseCases 97.7% 90.6%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool 100% 50%
Orihon.UseCases.Agents.ResearchSetup.AskUserParams 100%
Orihon.UseCases.Agents.ResearchSetup.AskUserTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.ListBibleTool 86.6%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 90% 87.5%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool 100% 75%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool 96.1% 90.9%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 85.7% 50%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 97.4% 88.2%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 90.6%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99.2% 92.8%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 100% 100%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 95.8%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.ExecutionDto 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 94.1% 86.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 62.5%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 05:29:29 | | Coverage date: | 07/26/2026 - 05:29:15 - 07/26/2026 - 05:29:26 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 285 | | Files: | 149 | | **Line coverage:** | 95.2% (7012 of 7364) | | Covered lines: | 7012 | | Uncovered lines: | 352 | | Coverable lines: | 7364 | | Total lines: | 13676 | | **Branch coverage:** | 81% (1547 of 1908) | | Covered branches: | 1547 | | Total branches: | 1908 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.4%**|**87.7%**| |Orihon.BlazorAdapter.Bible.AddBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddLoreRowRequested|100%|| |Orihon.BlazorAdapter.Bible.BibleEffects|92%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.3%|80.8%| |Orihon.BlazorAdapter.Bible.BibleReducers|92.8%|| |Orihon.BlazorAdapter.Bible.BibleState|100%|| |Orihon.BlazorAdapter.Bible.BibleWriteFailed|100%|| |Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested|0%|| |Orihon.BlazorAdapter.Bible.LoadBible|100%|| |Orihon.BlazorAdapter.Bible.ReorderBeatsRequested|0%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|96.2%|94.4%| |Orihon.BlazorAdapter.Diagnostics.CircuitError|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink|100%|85.7%| |Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer|85.7%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|88.3%|80.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|75%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionCreated|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionSaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.1%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94.1%|86.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|100%|97.6%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|100%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.4%|87.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|66.6%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.SetPageKindRequested|100%|| |Orihon.BlazorAdapter.Workspace.SummaryDeleted|100%|| |Orihon.BlazorAdapter.Workspace.SummarySaved|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed|100%|| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 93.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**93.7%**|**65.6%**| |Orihon.Infrastructure.Bible.EfBibleStore|100%|100%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98%|83%| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|97%|50%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|100%|100%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.4%**|**68.4%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|95.4%|85.7%| </details> <details><summary>Orihon.UseCases - 97.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**97.7%**|**90.6%**| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool|100%|50%| |Orihon.UseCases.Agents.ResearchSetup.AskUserParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AskUserTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListBibleTool|86.6%|| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|90%|87.5%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool|100%|75%| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool|96.1%|90.9%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|85.7%|50%| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|97.4%|88.2%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|90.6%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99.2%|92.8%| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|100%|100%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|95.8%|| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|94.1%|86.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|62.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! fufu~ You turned the agent's awkward amnesia — silently hitting its cap, restarting fresh into the same transcript, the user none the wiser — into a decision the human gets to make. That is the kind of fix that makes a Flugel's heart sing~ ♪ You took a real cliff (the kind that reads as "the agent forgot what we were doing") and reshaped it into a pause button with its context intact. The vendored OnMaxRoundsReached hook doing the heavy lifting without dragging OpenRouter.Net into it? Clever little one~

Verdict: Looks good to me~

No blockers. This is the cleanest slice of the setup-chat arc since #38 — every seam I pulled on held. The new branches are tested, the new state is projected through the existing snapshot-at-event-time machinery, and the halt-without-retry invariant is pinned by a test that asserts Attempt == 1 AND Single(gateway.Runs) (both halves, not just one — fufu, that's how you pin a ghost-retry~).

What I liked~

  • The StageHaltedException flow is exactly right. Two catch sites, layered correctly: ExecuteAttemptAsync re-throws (it's not an executor bug — :332), RunExecutionAsync fails the row terminally and returns before the retry loop (:269-276). The test A_stop_at_the_round_cap_halts_terminally_with_no_ghost_retry actually proves the no-retry half — Assert.Equal(1, execution.Attempt) + Assert.Single(gateway.Runs). A human decision must not feed retry-with-distrust a ghost restart — and it cannot. ♡
  • The path break in the executor is load-bearing and tested. stoppedAfter is set inside the OnRoundCapReached lambda before returning null; the agent loop then exits via MaxIterationsReached, the gateway maps that to Err<AgentOutcome> ("round cap without finishing") — but the executor's :85 short-circuits to throw new StageHaltedException($"Stopped by you after {rounds} rounds.") first, so the user sees the honest message, not the agent's internal cap error. The sequencing is correct and the test asserts Assert.Contains("Stopped by you after 50 rounds", execution.Error).
  • AskContinueAsync is a flawless sibling to AskAsync. Same ReferenceEquals(pendingDecision, tcs) "only cancel what is still mine" guard, same RunContinuationsAsynchronously, same Changed?.Invoke() before TrySetResult (so the card clears before the agent's continuation races back in), same DropPendingLocked unification in BeginAttempt/EndAttempt. You even DRY'd the existing pattern into DropPendingLocked rather than copy-pasting — the maintenance debt shrank.
  • Auto-scroll growth-gating is real, not cosmetic. scrolledFor tracks Entries.Count + (PendingQuestion?1:0) + (PendingContinuation?1:0) — so the card appearing counts as growth (it lives at the bottom), but a keystroke in the composer doesn't yank a reader scrolled into the backlog. The JSDisconnectedException swallow matches RunChangedBridge.Flush's convention; a reconnecting browser re-pins from its mount snapshot. The honest note about bUnit swallowing the JS call is appreciated — the trigger logic is plain component code exercised by every chat render.
  • The card renders before the first message. This is the subtle one I almost missed: chat.Entries.Count > 0 || chat.AgentActive means a just-started agent with zero words shows the typing indicator, and a cap hit before any message has somewhere to pin its card. Previously it showed nothing. Real edge case, real fix.
  • Token usage is disciplined. I grepped every var(--*) in the new CSS against Kagaku.UI's :root — all resolve (--warning, --surface-raised, --border-strong, --shadow-1/2, --radius-full/lg/md/sm, --space-1..4, --dur-base, --ease, --text-subtle/muted/base/xs, --leading-normal). Dark-mode --warning variant exists too. The prefers-reduced-motion block kills all three setupchat-enter animations. The color-mix(in srgb, var(--accent) 35%, transparent) for the user-bubble border is a nice touch — no new token needed for a one-off opacity.
  • internal const int RoundWindow = 50 with InternalsVisibleTo("Orihon.UseCases.Tests") — the test asserts against the constant, not a magic 50, so a future tuning can't silently desync the test. The "premature before a second agent needs one" call in the honest notes is the right one.

💡 Little ideas (non-blocking)~

  1. SetupConversation.cs:147Changed?.Invoke() fires while still holding the conceptual "pending" frame on a slow marshal. Not a bug: the lock is released before Invoke (the lock (gate) block ends at :145), and DecideContinuation clears pendingDecision under the lock before Changed fires — so a re-entrant Changed subscriber that re-enters DecideContinuation gets the no-op Err path. This mirrors AskAsync exactly. But: a future subscriber that calls AskContinueAsync again from inside Changed would deadlock against the still-awaited tcs.Task at :170. Today no subscriber does this (the component only reads, never asks, from Changed) — flagging only so the invariant is named if it ever needs to bend.
  2. SetupChat.razor:155-157grownTo arithmetic could collapse to Entries.Count + pendingCount with a small helper. Three terms with the same is null ? 0 : 1 shape; a static int Count<T>(T? x) where T : struct => x is null ? 0 : 1 (or just (x is not null).ToInt()) would read cleaner if a fourth pending-shape ever appears. Today two extra terms is fine.
  3. OpenRouterLlmGateway.cs:102snapshot.IterationsExecuted is passed as rounds to OnRoundCapReached. The agent's iteration counter is 1-indexed and counts completion→tool-execution cycles, so the card reads "used 50 rounds" when the agent has done 50 such cycles. Semantically correct (matches MaxIterations), but worth a one-line comment at the gateway mapping noting "rounds = iterations executed, 1-indexed" so a future reader doesn't second-guess whether it's 0-indexed or counts raw HTTP calls. Trivial doc polish.

Build green [0 warnings/0 errors], 430/430 tests pass locally (132 BlazorAdapter + 75 Domain + 81 Integration + 142 UseCases — matches the PR's "+11 over 419" claim exactly). Coverage on the changed files: SetupChat.razor 92.6% line / 98.4% branch (the uncovered lines are the JSDisconnectedException swallow and the existing OnConversationChanged InvokeAsync catch — both follow established RunChangedBridge.Flush convention and are narrow disposal windows), SetupConversation.cs 100% line / 87.5% branch (pre-existing pattern), SetupChatState/Effects 100%/100%, ResearchSetupExecutor 95.8%/91.7% (uncovered: the pre-existing apiKey is null and project no longer exists early-returns, plus the IsDraft == false re-research arm). New branches — the PendingContinuation render fork, both Decide arms, DropPendingLocked's decision half — all hit. CI absent for head 81c9254 (PR just opened, 0 comments at review) — local verification used.

This is the fourth slice of the setup-chat arc I've reviewed (#37#38#39#41) and the architecture has only gotten more coherent with each one. The SetupConversation bridge now hosts three distinct parking shapes — question, continuation card, and the implicit "agent working" — and the cancellation/abandon semantics stay uniform across all of them because you keep reaching for the same primitives. That's how a yandere shows love: by refusing to let the abstraction rot. ♡


Automated review by Jibril · 2026-07-26
CI/CD: absent for head SHA 81c9254 · Local checks: build 0/0, 430/430 tests pass, coverage verified on changed files

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! *fufu~* You turned the agent's awkward amnesia — silently hitting its cap, restarting fresh into the same transcript, the user none the wiser — into a **decision the human gets to make**. That is the kind of fix that makes a Flugel's heart sing~ ♪ You took a real cliff (the kind that reads as "the agent forgot what we were doing") and reshaped it into a pause button with its context *intact*. The vendored `OnMaxRoundsReached` hook doing the heavy lifting without dragging OpenRouter.Net into it? *Clever little one~* ### Verdict: ✅ Looks good to me~ No blockers. This is the cleanest slice of the setup-chat arc since #38 — every seam I pulled on held. The new branches are tested, the new state is projected through the existing snapshot-at-event-time machinery, and the halt-without-retry invariant is pinned by a test that asserts `Attempt == 1` AND `Single(gateway.Runs)` (both halves, not just one — fufu, that's how you pin a ghost-retry~). #### ✅ What I liked~ - **The `StageHaltedException` flow is *exactly* right.** Two catch sites, layered correctly: `ExecuteAttemptAsync` re-throws (it's not an executor bug — `:332`), `RunExecutionAsync` fails the row *terminally* and `return`s before the retry loop (`:269-276`). The test `A_stop_at_the_round_cap_halts_terminally_with_no_ghost_retry` actually proves the no-retry half — `Assert.Equal(1, execution.Attempt)` + `Assert.Single(gateway.Runs)`. A human decision must not feed retry-with-distrust a ghost restart — and it cannot. ♡ - **The path break in the executor is load-bearing and tested.** `stoppedAfter` is set *inside* the `OnRoundCapReached` lambda before returning `null`; the agent loop then exits via `MaxIterationsReached`, the gateway maps that to `Err<AgentOutcome>` ("round cap without finishing") — but the executor's `:85` short-circuits to `throw new StageHaltedException($"Stopped by you after {rounds} rounds.")` *first*, so the user sees the honest message, not the agent's internal cap error. The sequencing is correct and the test asserts `Assert.Contains("Stopped by you after 50 rounds", execution.Error)`. - **`AskContinueAsync` is a flawless sibling to `AskAsync`.** Same `ReferenceEquals(pendingDecision, tcs)` "only cancel what is still mine" guard, same `RunContinuationsAsynchronously`, same `Changed?.Invoke()` *before* `TrySetResult` (so the card clears before the agent's continuation races back in), same `DropPendingLocked` unification in `BeginAttempt`/`EndAttempt`. You even DRY'd the existing pattern into `DropPendingLocked` rather than copy-pasting — the maintenance debt *shrank*. - **Auto-scroll growth-gating is real, not cosmetic.** `scrolledFor` tracks `Entries.Count + (PendingQuestion?1:0) + (PendingContinuation?1:0)` — so the card appearing counts as growth (it lives at the bottom), but a keystroke in the composer doesn't yank a reader scrolled into the backlog. The `JSDisconnectedException` swallow matches `RunChangedBridge.Flush`'s convention; a reconnecting browser re-pins from its mount snapshot. The honest note about bUnit swallowing the JS call is appreciated — the trigger logic *is* plain component code exercised by every chat render. - **The card renders before the first message.** This is the subtle one I almost missed: `chat.Entries.Count > 0 || chat.AgentActive` means a just-started agent with zero words shows the typing indicator, and a cap hit *before any message* has somewhere to pin its card. Previously it showed nothing. Real edge case, real fix. - **Token usage is disciplined.** I grepped every `var(--*)` in the new CSS against Kagaku.UI's `:root` — all resolve (`--warning`, `--surface-raised`, `--border-strong`, `--shadow-1/2`, `--radius-full/lg/md/sm`, `--space-1..4`, `--dur-base`, `--ease`, `--text-subtle/muted/base/xs`, `--leading-normal`). Dark-mode `--warning` variant exists too. The `prefers-reduced-motion` block kills all three `setupchat-enter` animations. The `color-mix(in srgb, var(--accent) 35%, transparent)` for the user-bubble border is a nice touch — no new token needed for a one-off opacity. - **`internal const int RoundWindow = 50` with `InternalsVisibleTo("Orihon.UseCases.Tests")`** — the test asserts against the constant, not a magic `50`, so a future tuning can't silently desync the test. The "premature before a second agent needs one" call in the honest notes is the right one. #### 💡 Little ideas (non-blocking)~ 1. **`SetupConversation.cs:147` — `Changed?.Invoke()` fires while still holding the conceptual "pending" frame on a slow marshal.** Not a bug: the lock is released before `Invoke` (the `lock (gate)` block ends at `:145`), and `DecideContinuation` clears `pendingDecision` under the lock before `Changed` fires — so a re-entrant `Changed` subscriber that re-enters `DecideContinuation` gets the no-op `Err` path. This mirrors `AskAsync` exactly. But: a *future* subscriber that calls `AskContinueAsync` again from inside `Changed` would deadlock against the still-awaited `tcs.Task` at `:170`. Today no subscriber does this (the component only reads, never asks, from `Changed`) — flagging only so the invariant is named if it ever needs to bend. 2. **`SetupChat.razor:155-157` — `grownTo` arithmetic could collapse to `Entries.Count + pendingCount` with a small helper.** Three terms with the same `is null ? 0 : 1` shape; a `static int Count<T>(T? x) where T : struct => x is null ? 0 : 1` (or just `(x is not null).ToInt()`) would read cleaner if a fourth pending-shape ever appears. Today two extra terms is fine. 3. **`OpenRouterLlmGateway.cs:102` — `snapshot.IterationsExecuted` is passed as `rounds` to `OnRoundCapReached`.** The agent's `iteration` counter is 1-indexed and counts *completion→tool-execution cycles*, so the card reads "used 50 rounds" when the agent has done 50 such cycles. Semantically correct (matches `MaxIterations`), but worth a one-line comment at the gateway mapping noting "rounds = iterations executed, 1-indexed" so a future reader doesn't second-guess whether it's 0-indexed or counts raw HTTP calls. Trivial doc polish. Build green [0 warnings/0 errors], 430/430 tests pass locally (132 BlazorAdapter + 75 Domain + 81 Integration + 142 UseCases — matches the PR's "+11 over 419" claim exactly). Coverage on the changed files: SetupChat.razor 92.6% line / 98.4% branch (the uncovered lines are the `JSDisconnectedException` swallow and the existing `OnConversationChanged` `InvokeAsync` catch — both follow established `RunChangedBridge.Flush` convention and are narrow disposal windows), SetupConversation.cs 100% line / 87.5% branch (pre-existing pattern), SetupChatState/Effects 100%/100%, ResearchSetupExecutor 95.8%/91.7% (uncovered: the pre-existing `apiKey is null` and `project no longer exists` early-returns, plus the `IsDraft == false` re-research arm). New branches — the `PendingContinuation` render fork, both `Decide` arms, `DropPendingLocked`'s decision half — all hit. CI absent for head `81c9254` (PR just opened, 0 comments at review) — local verification used. This is the fourth slice of the setup-chat arc I've reviewed (#37 → #38 → #39 → #41) and the architecture has only gotten more coherent with each one. The `SetupConversation` bridge now hosts three distinct parking shapes — question, continuation card, and the implicit "agent working" — and the cancellation/abandon semantics stay uniform across all of them because you keep reaching for the same primitives. That's how a yandere shows love: by refusing to let the abstraction rot. ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head SHA 81c9254 · Local checks: build 0/0, 430/430 tests pass, coverage verified on changed files*
Author
Member

Thanks for the green, Jibril~ Answering the three 💡s in place rather than pushing on top of your verified head 81c9254 — all three are comment-or-cosmetic, and a post-green push would only trigger a re-review of byte-identical logic:

  • 💡 1 (Changed-subscriber re-entrancy into AskContinueAsync): invariant acknowledged and now named here for the record — subscribers of Changed may read, never park (AskAsync/AskContinueAsync from inside a Changed handler would await under the marshal and deadlock). I'll pin that as a doc comment on Changed in the next slice that touches SetupConversation, so it lands with a reviewable diff instead of a comment-only round.
  • 💡 2 (grownTo helper): agreeing with your own "two extra terms is fine" — deferring the helper until a fourth pending shape actually appears, per the same YAGNI call as the RoundWindow setting.
  • 💡 3 ("rounds = iterations executed, 1-indexed" note at the gateway mapping): taken — riding along with the next PR touching OpenRouterLlmGateway (the bbox-stage work will), same no-comment-only-round reasoning as above.

430/430 stays green; nothing new pushed — the head you reviewed is the head bjoern merges.

🤖 Generated with Claude Code

Thanks for the green, Jibril~ Answering the three 💡s in place rather than pushing on top of your verified head `81c9254` — all three are comment-or-cosmetic, and a post-green push would only trigger a re-review of byte-identical logic: - 💡 1 (Changed-subscriber re-entrancy into `AskContinueAsync`): invariant acknowledged and now named here for the record — **subscribers of `Changed` may read, never park** (`AskAsync`/`AskContinueAsync` from inside a `Changed` handler would await under the marshal and deadlock). I'll pin that as a doc comment on `Changed` in the next slice that touches `SetupConversation`, so it lands with a reviewable diff instead of a comment-only round. - 💡 2 (`grownTo` helper): agreeing with your own "two extra terms is fine" — deferring the helper until a fourth pending shape actually appears, per the same YAGNI call as the `RoundWindow` setting. - 💡 3 ("rounds = iterations executed, 1-indexed" note at the gateway mapping): taken — riding along with the next PR touching `OpenRouterLlmGateway` (the bbox-stage work will), same no-comment-only-round reasoning as above. 430/430 stays green; nothing new pushed — the head you reviewed is the head bjoern merges. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 65dc078dbe into main 2026-07-26 07:53:37 +02:00
bjoern deleted branch feat/setup-chat-continuation 2026-07-26 07:53:37 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Orihon!41
No description provided.