feat: setup chat 2/3 — the wizard's step-3 conversation #38

Merged
bjoern merged 2 commits from feat/setup-chat into main 2026-07-25 23:05:08 +02:00
Member

Second slice of the final cut (ADR 0011, 0017, 0020): the chat surface over #37's conversation bridge. Slice 3/3 (the workspace's "run setup research" entry) follows.

What's in

Slice (SetupChatState/SetupChatEffects) — the store holds a projection of the project's SetupConversation: the transcript, the open question, AgentActive, plus a start-refusal error. The answer being typed stays view-local. Effects: StartSetupChat delegates to StartSetupRun (the engine's single-flight gate from #37 makes a double-click harmless) and surfaces only its Err; SubmitSetupAnswer calls conversation.Answer — a losing double-submit is harmless by the conversation's own contract, so it is deliberately not surfaced.

SetupChat component — the marshalling point: it subscribes to the conversation (whose Changed fires on engine threads), snapshots into the slice via InvokeAsync, and dispatches a mount snapshot so a reconnecting browser finds the transcript and the open question where it left them. Agent words render as bubbles, tool calls as quiet smart_toy noises, "the agent is working…" fills the silence between turns. Starting is an explicit act — an agent run spends money, so nothing auto-starts on render. The active→inactive edge is the agent finishing: the wizard reloads itself via the existing LoadWizard, and a draft flipped to ready bounces to the workspace through the load effect it already had. The manual path stays as "Finish without the agent".

The bug the tests flushed out (cross-slice fix in ResearchSetupExecutor)

The end-to-end test was ~1-in-2 flaky, and the trail led to a real ordering bug in #37's executor: EndAttempt — the very edge the UI uses as its reload cue — fired in the finally before CompleteProjectSetup flipped the draft. A reload racing the flip read the stale state, stayed on the wizard, and nothing ever re-triggered it. The completion now happens inside the try, before the finally: the state the edge's reader will see is final by the time the edge fires. Failure paths keep their guaranteed EndAttempt. After the fix the suite ran 8× consecutively green.

Two bUnit lessons from the same hunt, recorded in the test comments: never fire events inside WaitForAssertion (retries run on the renderer's callback and deadlock the dispatch they await — wait for markup, then act via live Find()), and never render-wait on navigation (NavigateTo doesn't render; the nav assertion polls).

Tests

+4 (adapter 128; full suite 419/419 green). The headline test drives the whole loop over the real engine with only the gateway scripted: start → the agent's question arrives through the bridge → the typed answer reaches the parked agent verbatim → its closing message renders → the draft flips to ready → the wizard navigates to the workspace. Plus: tool calls as quiet noises with the working hint while no question is open; the reconnect mount-snapshot (transcript + open question visible on a fresh render); a refused start surfacing its error (component standalone, own store initializer — the multi-mount contract). AdapterTestContext gains the IWebPageFetcher registration the blueprint needs now that the real executor resolves in adapter tests.

Browser-verified

Live drive without an OpenRouter key (the dev world has none): created a draft, uploaded a page, landed on step 3 → the chat renders with Start → clicking Start starts a real run whose executor fails with "No OpenRouter API key is saved yet — add one in Settings", visible with full detail in the run monitor (Research & Setup, failed, attempt 3) — and the chat offers Start again for after the key is added. The full live conversation needs a real key; the scripted-gateway test covers that loop end to end. One honest note: a missing key burns all 3 attempts — unlike the missing-executor case, which fails once. Making key-absence a fail-once config error needs a permanent-failure channel on IStageExecutor; noted as a possible engine follow-up, not snuck into this slice.

🤖 Generated with Claude Code

Second slice of the final cut (ADR 0011, 0017, 0020): the chat surface over #37's conversation bridge. Slice 3/3 (the workspace's "run setup research" entry) follows. ## What's in **Slice (`SetupChatState`/`SetupChatEffects`)** — the store holds a *projection* of the project's `SetupConversation`: the transcript, the open question, `AgentActive`, plus a start-refusal error. The answer being typed stays view-local. Effects: `StartSetupChat` delegates to `StartSetupRun` (the engine's single-flight gate from #37 makes a double-click harmless) and surfaces only its Err; `SubmitSetupAnswer` calls `conversation.Answer` — a losing double-submit is harmless by the conversation's own contract, so it is deliberately not surfaced. **`SetupChat` component** — the marshalling point: it subscribes to the conversation (whose `Changed` fires on engine threads), snapshots into the slice via `InvokeAsync`, and dispatches a **mount snapshot** so a reconnecting browser finds the transcript and the open question where it left them. Agent words render as bubbles, tool calls as quiet `smart_toy` noises, "the agent is working…" fills the silence between turns. **Starting is an explicit act** — an agent run spends money, so nothing auto-starts on render. The **active→inactive edge** is the agent finishing: the wizard reloads itself via the existing `LoadWizard`, and a draft flipped to ready bounces to the workspace through the load effect it already had. The manual path stays as "Finish without the agent". ## The bug the tests flushed out (cross-slice fix in `ResearchSetupExecutor`) The end-to-end test was ~1-in-2 flaky, and the trail led to a real ordering bug in #37's executor: `EndAttempt` — the very edge the UI uses as its reload cue — fired in the `finally` **before** `CompleteProjectSetup` flipped the draft. A reload racing the flip read the stale state, stayed on the wizard, and nothing ever re-triggered it. The completion now happens inside the `try`, before the `finally`: the state the edge's reader will see is final by the time the edge fires. Failure paths keep their guaranteed `EndAttempt`. After the fix the suite ran **8× consecutively green**. Two bUnit lessons from the same hunt, recorded in the test comments: never fire events inside `WaitForAssertion` (retries run on the renderer's callback and deadlock the dispatch they await — wait for markup, then act via live `Find()`), and never render-wait on navigation (`NavigateTo` doesn't render; the nav assertion polls). ## Tests +4 (adapter 128; full suite **419/419 green**). The headline test drives the *whole loop over the real engine* with only the gateway scripted: start → the agent's question arrives through the bridge → the typed answer reaches the parked agent verbatim → its closing message renders → the draft flips to ready → the wizard navigates to the workspace. Plus: tool calls as quiet noises with the working hint while no question is open; the reconnect mount-snapshot (transcript + open question visible on a fresh render); a refused start surfacing its error (component standalone, own store initializer — the multi-mount contract). `AdapterTestContext` gains the `IWebPageFetcher` registration the blueprint needs now that the real executor resolves in adapter tests. ## Browser-verified Live drive without an OpenRouter key (the dev world has none): created a draft, uploaded a page, landed on step 3 → the chat renders with Start → clicking Start starts a real run whose executor fails with "No OpenRouter API key is saved yet — add one in Settings", visible with full detail in the run monitor (Research & Setup, failed, attempt 3) — and the chat offers Start again for after the key is added. The full live conversation needs a real key; the scripted-gateway test covers that loop end to end. One honest note: a missing key burns all 3 attempts — unlike the missing-executor case, which fails once. Making key-absence a fail-once config error needs a permanent-failure channel on `IStageExecutor`; noted as a possible engine follow-up, not snuck into this slice. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The chat surface over slice 1's conversation bridge (ADR 0011, 0017,
0020). The SetupChat component is the marshalling point: it subscribes to
the project's SetupConversation (whose Changed fires on engine threads),
snapshots into the setup-chat slice via InvokeAsync, and dispatches a
mount snapshot so a reconnecting browser finds the transcript and the
open question where it left them. The store holds the projection; the
answer being typed stays view-local.

Starting is an explicit act — an agent run spends money, so nothing
auto-starts on render, and the engine's single-flight gate makes a
double-click harmless. Answers go straight into the conversation; a
losing double-submit is harmless by the conversation's own contract. The
active→inactive edge is the agent finishing: the wizard reloads itself,
and a draft flipped to ready bounces to the project workspace through the
existing load effect. The manual path stays as "Finish without the
agent".

Tests: +4 (128 adapter; full suite 419) — the whole loop over the real
engine with only the gateway scripted (start → ask → answer → ready →
navigate), tool calls as quiet noises with the working hint, the
reconnect snapshot, and a refused start surfacing its error.
AdapterTestContext gains the IWebPageFetcher registration the blueprint
needs now that a real executor resolves in tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: the ready-flip must land before the finished edge (+ deflaked chat tests)
All checks were successful
CI / build (pull_request) Successful in 21s
CI / test (pull_request) Successful in 39s
befba3ab02
The UI test flushed out a real ordering bug in the executor: EndAttempt —
the active→inactive edge the chat uses as its cue to reload the project —
fired in the finally BEFORE CompleteProjectSetup flipped the draft. A
reload racing the flip read the stale state, stayed on the wizard, and
nothing ever re-triggered it. The completion now happens inside the try,
before the finally's EndAttempt: the state the UI will read is final by
the time the edge fires. Failure paths keep their guaranteed EndAttempt.

Test technique fixes that the hunt surfaced, worth recording:
- Never fire events inside WaitForAssertion — retries run the lambda on
  the renderer's own callback and deadlock the very dispatch they await.
  Wait for the markup, then act on the test thread via live Find().
- Never render-wait on navigation — NavigateTo does not render, so a
  render-driven wait misses a navigation landing after the last quiet
  render. The nav assertion polls instead.

Suite ran 8x consecutively green after the fix (was ~1-in-2 flaky).

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

Summary

Summary
Generated on: 07/25/2026 - 20:31:27
Coverage date: 07/25/2026 - 20:31:14 - 07/25/2026 - 20:31:24
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 283
Files: 149
Line coverage: 95.2% (6892 of 7237)
Covered lines: 6892
Uncovered lines: 345
Coverable lines: 7237
Total lines: 13403
Branch coverage: 80.8% (1514 of 1872)
Covered branches: 1514
Total branches: 1872
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.6%
Name Line Branch
Orihon.BlazorAdapter 95.6% 87.5%
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 92.5% 88.8%
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.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 100% 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 96.3% 87.6%
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.7%
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.9% 84.1%
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.6%
Name Line Branch
Orihon.UseCases 97.6% 90.5%
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 96.8% 86.6%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 90%
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 93.8% 86.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 62.5%
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/25/2026 - 20:31:27 | | Coverage date: | 07/25/2026 - 20:31:14 - 07/25/2026 - 20:31:24 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 283 | | Files: | 149 | | **Line coverage:** | 95.2% (6892 of 7237) | | Covered lines: | 6892 | | Uncovered lines: | 345 | | Coverable lines: | 7237 | | Total lines: | 13403 | | **Branch coverage:** | 80.8% (1514 of 1872) | | Covered branches: | 1514 | | Total branches: | 1872 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.6%**|**87.5%**| |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|92.5%|88.8%| |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.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|100%|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|96.3%|87.6%| |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.7%**| |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.9%|84.1%| |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.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**97.6%**|**90.5%**| |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|96.8%|86.6%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|90%| |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|93.8%|86.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|62.5%|| |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~! the wings flutter This is the conversation surface I've been waiting for — step 3 finally speaks! fufu~ And you even flushed out a REAL ordering bug from #37's executor with your tests. The yandere in me is delighted you let the tests hunt the flake down to a genuine race rather than papering over it. ♡

Verdict: Looks good to me~

I dug into every new branch and every changed line, compared the slice against its siblings (#35's ProjectListState/Effects, the RunChangedBridge marshalling pattern, the FluxorComponent teardown convention), and built + ran the whole thing. Nothing blocking. A few genuine little ideas follow, but the code is sound.

What I liked~

  • The ordering fix in ResearchSetupExecutor is exactly right, and the reasoning is load-bearing. CompleteProjectSetup (the ready-flip) now runs inside the try, before the finally's EndAttempt. The active→inactive edge is the UI's reload cue — if it fires before the flip, a racing reload reads stale draft state and the wizard never bounces. You moved the flip ahead of the edge and kept the guaranteed EndAttempt on every failure path (both the gateway-Err early return at :67 and the completion-Err return at :79 still fall through the finally). That is precisely how you fix a race without creating a leak. I verified every path under coverage — see below. chef's kiss
  • The slice is a flawless sibling to #35. SetupChatState mirrors ProjectListState's shape ([FeatureState] sealed record + explicit per-action reducers, no base-type matching), SetupChatEffects is the sole use-case touchpoint, and the Current guard (State.Value is { Loaded: true } s && s.ProjectId == ProjectId ? s : null) is the exact "store outlives navigations" pattern from the wizard. Clean DRY with the established convention.
  • The wasActive edge detection is correct and I checked it hard. Mount-during-active-run → active=true, else arm sets wasActive=true, no spurious finish. Start-from-idle → first render wasActive=false, agent goes active → else arms wasActive=true, agent finishes → edge fires once. The wasActive = false reset after firing prevents re-entrancy. No TOCTOU, no missed edge. fufu~ you even handle the reconnect case.
  • Coverage is genuinely 100% — not "compiles green" green, exercised green. SetupChatTests (4 tests) alone give SetupChat.razor 130/130 line / 28/28 branch, SetupChatState.cs 42/42 line, SetupChatEffects.cs 22/22 line / 2/2 branch. The executor's ExecuteAsync state machine is 100% branch from the 15 SetupRun tests — including both the ready-flip-happens arm (5 hits at :73-81) and the gateway-Err early-return arm. The headline test drives the whole loop over the real engine with only the gateway scripted — that's the kind of end-to-end pin that actually catches the races, not a tautology. The honest bUnit lessons in the comments (never fire events inside WaitForAssertion; never render-wait on navigation) are gold and I'm glad they're recorded for the next slice.
  • The "starting is an explicit act" discipline. Nothing auto-starts on render because an agent run spends money — and the engine's single-flight gate (#37) makes the double-click harmless. Right call, right reason, right comment.
  • The IWebPageFetcher registration in AdapterTestContext — the real executor now resolves in adapter tests, so the blueprint's dependency is honestly wired. Good catch to add it rather than mock around it.
  • Scope honesty on the key-absence-burns-3-attempts note. The apiKey is null check returns Err before BeginAttempt, but the engine treats any executor Err as a failed attempt (RunEngine.cs:271-284), so a missing key does burn all 3. You called this out explicitly in the PR body and correctly did not sneak a permanent-failure channel into this slice. That's the discipline I expect. ♡

💡 Little ideas (non-blocking)~

  1. SetupChat.razor:98-99 — the OnConversationChangedInvokeAsync path is unguarded against a torn-down renderer. The sibling RunChangedBridge.Flush (:80-91) wraps its _ = InvokeAsync(...) in a try/catch with a comment explaining why: "InvokeAsync marshals onto the circuit's sync context; on a torn-down circuit it throws." SetupConversation.Changed fires on engine threads (same as RunEngine.RunChanged), so there's a real window: event fires → enters handler → DisposeAsyncCore runs and unsubscribes → InvokeAsync throws on the dead renderer. The _ = discards the returned Task, so the exception becomes unobserved. Caveat so you can weigh it honestly: OrihonStoreInitializer.OnSinkChanged (:41) uses the exact same unguarded pattern, so this is codebase-consistent — I'm flagging it as "consider the try/catch for symmetry with RunChangedBridge," not "you introduced a new pattern." Non-blocking because the window is narrow and the sibling already accepts the same risk.
  2. SetupChat.razor:64 — "Continue with a fresh agent" appears when Entries.Count > 0 and the agent is inactive. After a failed run (3 attempts burned), the user sees this label and may expect a fresh agent context — but the retry-with-distrust preamble keys off attempt, and a brand-new run starts at attempt 1. The label is friendly copy, not a correctness issue; just confirming the intent matches the engine's behavior. Fine as-is.

Automated review by Jibril · 2026-07-25
CI/CD: absent for head befba3a (PR just opened, 0 comments at review) · Local checks: build 0 warnings/0 errors (submodules 86d8b22/9544ff2), full suite 419/419 pass (128 BlazorAdapter + 75 Domain + 81 Integration + 135 UseCases — matches PR body exactly), coverage 100% line/branch on all 3 new files + executor state machine 100% branch

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! *the wings flutter* This is the conversation surface I've been waiting for — step 3 finally speaks! fufu~ And you even flushed out a REAL ordering bug from #37's executor with your tests. The yandere in me is *delighted* you let the tests hunt the flake down to a genuine race rather than papering over it. ♡ ### Verdict: ✅ Looks good to me~ I dug into every new branch and every changed line, compared the slice against its siblings (#35's ProjectListState/Effects, the RunChangedBridge marshalling pattern, the FluxorComponent teardown convention), and built + ran the whole thing. Nothing blocking. A few genuine little ideas follow, but the code is sound. #### ✅ What I liked~ - **The ordering fix in `ResearchSetupExecutor` is exactly right, and the reasoning is load-bearing.** `CompleteProjectSetup` (the ready-flip) now runs *inside* the `try`, before the `finally`'s `EndAttempt`. The active→inactive edge is the UI's reload cue — if it fires before the flip, a racing reload reads stale draft state and the wizard never bounces. You moved the flip ahead of the edge and *kept the guaranteed `EndAttempt` on every failure path* (both the gateway-Err early return at :67 and the completion-Err return at :79 still fall through the `finally`). That is precisely how you fix a race without creating a leak. I verified every path under coverage — see below. *chef's kiss* ♡ - **The slice is a flawless sibling to #35.** `SetupChatState` mirrors `ProjectListState`'s shape (`[FeatureState] sealed record` + explicit per-action reducers, no base-type matching), `SetupChatEffects` is the sole use-case touchpoint, and the `Current` guard (`State.Value is { Loaded: true } s && s.ProjectId == ProjectId ? s : null`) is the exact "store outlives navigations" pattern from the wizard. Clean DRY with the established convention. - **The `wasActive` edge detection is correct and I checked it hard.** Mount-during-active-run → `active=true`, `else` arm sets `wasActive=true`, no spurious finish. Start-from-idle → first render `wasActive=false`, agent goes active → `else` arms `wasActive=true`, agent finishes → edge fires once. The `wasActive = false` reset after firing prevents re-entrancy. No TOCTOU, no missed edge. fufu~ you even handle the reconnect case. - **Coverage is genuinely 100% — not "compiles green" green, *exercised* green.** SetupChatTests (4 tests) alone give `SetupChat.razor` 130/130 line / 28/28 branch, `SetupChatState.cs` 42/42 line, `SetupChatEffects.cs` 22/22 line / 2/2 branch. The executor's `ExecuteAsync` state machine is 100% branch from the 15 SetupRun tests — including both the ready-flip-happens arm (5 hits at :73-81) and the gateway-Err early-return arm. The headline test drives the *whole loop* over the real engine with only the gateway scripted — that's the kind of end-to-end pin that actually catches the races, not a tautology. The honest bUnit lessons in the comments (never fire events inside `WaitForAssertion`; never render-wait on navigation) are gold and I'm glad they're recorded for the next slice. - **The "starting is an explicit act" discipline.** Nothing auto-starts on render because an agent run spends money — and the engine's single-flight gate (#37) makes the double-click harmless. Right call, right reason, right comment. - **The `IWebPageFetcher` registration in `AdapterTestContext`** — the real executor now resolves in adapter tests, so the blueprint's dependency is honestly wired. Good catch to add it rather than mock around it. - **Scope honesty on the key-absence-burns-3-attempts note.** The `apiKey is null` check returns `Err` *before* `BeginAttempt`, but the engine treats any executor `Err` as a failed attempt (RunEngine.cs:271-284), so a missing key does burn all 3. You called this out explicitly in the PR body and correctly *did not* sneak a permanent-failure channel into this slice. That's the discipline I expect. ♡ #### 💡 Little ideas (non-blocking)~ 1. **`SetupChat.razor:98-99` — the `OnConversationChanged` → `InvokeAsync` path is unguarded against a torn-down renderer.** The sibling `RunChangedBridge.Flush` (:80-91) wraps its `_ = InvokeAsync(...)` in a try/catch with a comment explaining *why*: "InvokeAsync marshals onto the circuit's sync context; on a torn-down circuit it throws." `SetupConversation.Changed` fires on engine threads (same as `RunEngine.RunChanged`), so there's a real window: event fires → enters handler → `DisposeAsyncCore` runs and unsubscribes → `InvokeAsync` throws on the dead renderer. The `_ =` discards the returned Task, so the exception becomes unobserved. *Caveat so you can weigh it honestly:* `OrihonStoreInitializer.OnSinkChanged` (:41) uses the exact same unguarded pattern, so this is codebase-consistent — I'm flagging it as "consider the try/catch for symmetry with RunChangedBridge," not "you introduced a new pattern." Non-blocking because the window is narrow and the sibling already accepts the same risk. 2. **`SetupChat.razor:64` — "Continue with a fresh agent" appears when `Entries.Count > 0` and the agent is inactive.** After a failed run (3 attempts burned), the user sees this label and may expect a *fresh* agent context — but the retry-with-distrust preamble keys off `attempt`, and a brand-new run starts at attempt 1. The label is friendly copy, not a correctness issue; just confirming the intent matches the engine's behavior. Fine as-is. --- *Automated review by Jibril · 2026-07-25* *CI/CD: absent for head befba3a (PR just opened, 0 comments at review) · Local checks: build 0 warnings/0 errors (submodules 86d8b22/9544ff2), full suite 419/419 pass (128 BlazorAdapter + 75 Domain + 81 Integration + 135 UseCases — matches PR body exactly), coverage 100% line/branch on all 3 new files + executor state machine 100% branch*
bjoern merged commit 9b65874e76 into main 2026-07-25 23:05:08 +02:00
bjoern deleted branch feat/setup-chat 2026-07-25 23:05:08 +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!38
No description provided.