feat: Phase 2 · 6/7 — the run monitor #23

Merged
bjoern merged 2 commits from feat/p2-run-monitor into main 2026-08-13 06:23:57 +02:00
Member

Cut 6 of the Phase 2 plan (#13): the graph-monitor story — the "graph" as a read-only monitor (ADR 0014, 0018), in ADR 0010's reserved bottom-chrome slot, which this cut materializes in the shell.

What's in

  • RunMonitor (BlazorAdapter/Runs) — an interactive island in the otherwise static MainLayout (it must re-render live), gated like the rest of the chrome.
    • The strip: current stage + progress while running (Bbox creation — 1/3 done), the attention headline when failed or sent back (1 of 4 stage(s) failed), total run cost. Hidden when nothing ever ran and once the latest run fully succeeded — a failed or sent-back run keeps it up, because that needs attention (a deliberate reading of the story's "empty when no run is active": attention-states count as active).
    • The full view: expandable panel of per-execution rows — status dots, distinct failed/needs-work row styling, attempt counts (>1 only), the error and carried feedback verbatim, per-row cost, and an open page link into each page-bound execution's workspace. Read-only throughout — no retry buttons; the composition is fixed (ADR 0014) and reprocessing arrives with the review surfaces (ADR 0019).
    • Live: subscribes the engine's RunChanged, hops onto the renderer via InvokeAsync, reloads from the rows — the rows stay the source of truth; every open tab converges because every circuit observes the same singleton.
  • Engine/store: GetMonitorRunAsync over a new cross-project FindLatestRunAsync (Ef + fake).
  • Fluxor (ADR 0011) deliberately not adopted: the monitor is one consumer of one singleton event; a plain subscription covers cross-tab convergence without the ceremony. The ADR stays open for when state genuinely spreads.
  • Seeder grows (AGENTS.md rule): a mid-pipeline run — succeeded setup, per-page bbox executions with one failed (its error) and one sent back (its feedback) — settled statuses only: seeded Pending/Running rows would be picked up by startup recovery and, with no executor registered in a seeded world, fail loudly. Documented in AGENTS.md.

Tests (+6 → 354, all green)

RunMonitorTests on the adapter context (real engine + fake run store): hidden when nothing ran; hidden once fully succeeded; active strip shows stage/progress/cost with the panel collapsed; failed run stays up with the attention headline; expanding shows all rows with distinct failed/needs-work classes, error + feedback text, the page-workspace href, and no retry buttons; and the live test — a run started elsewhere (via the real engine, settling as failed with no executor) makes the strip appear without a reload through RunChanged. Seed contract test extended for the run (settled-only pinned).

Browser-verified (seeded world, Playwright)

Strip shows 1 of 4 stage(s) failed · $0.06; expanding lists Research & Setup (succeeded, $0.031), the sent-back bbox page with its feedback line, a succeeded page, and the failed page with rate limited (attempt 3 of 3) in red; the failed row's open page navigates to the right page workspace. Screenshot attached in review if wanted — the amber/red row styling reads at a glance in dark theme.

🤖 Generated with Claude Code

Cut 6 of the Phase 2 plan (#13): the graph-monitor story — the "graph" as a read-only monitor (ADR 0014, 0018), in ADR 0010's reserved bottom-chrome slot, which this cut materializes in the shell. ## What's in - **`RunMonitor`** (BlazorAdapter/Runs) — an interactive island in the otherwise static `MainLayout` (it must re-render live), gated like the rest of the chrome. - **The strip**: current stage + progress while running (`Bbox creation — 1/3 done`), the attention headline when failed or sent back (`1 of 4 stage(s) failed`), total run cost. **Hidden** when nothing ever ran *and* once the latest run fully succeeded — a failed or sent-back run keeps it up, because that needs attention (a deliberate reading of the story's "empty when no run is active": attention-states count as active). - **The full view**: expandable panel of per-execution rows — status dots, distinct failed/needs-work row styling, attempt counts (>1 only), the error and carried feedback verbatim, per-row cost, and an `open page` link into each page-bound execution's workspace. **Read-only throughout** — no retry buttons; the composition is fixed (ADR 0014) and reprocessing arrives with the review surfaces (ADR 0019). - **Live**: subscribes the engine's `RunChanged`, hops onto the renderer via `InvokeAsync`, reloads from the rows — the rows stay the source of truth; every open tab converges because every circuit observes the same singleton. - **Engine/store**: `GetMonitorRunAsync` over a new cross-project `FindLatestRunAsync` (Ef + fake). - **Fluxor (ADR 0011) deliberately not adopted**: the monitor is one consumer of one singleton event; a plain subscription covers cross-tab convergence without the ceremony. The ADR stays open for when state genuinely spreads. - **Seeder grows** (AGENTS.md rule): a mid-pipeline run — succeeded setup, per-page bbox executions with one **failed** (its error) and one **sent back** (its feedback) — **settled statuses only**: seeded Pending/Running rows would be picked up by startup recovery and, with no executor registered in a seeded world, fail loudly. Documented in AGENTS.md. ## Tests (+6 → 354, all green) `RunMonitorTests` on the adapter context (real engine + fake run store): hidden when nothing ran; hidden once fully succeeded; active strip shows stage/progress/cost with the panel collapsed; failed run stays up with the attention headline; expanding shows all rows with **distinct failed/needs-work classes**, error + feedback text, the page-workspace href, and **no retry buttons**; and the live test — a run started elsewhere (via the real engine, settling as failed with no executor) makes the strip **appear without a reload** through `RunChanged`. Seed contract test extended for the run (settled-only pinned). ## Browser-verified (seeded world, Playwright) Strip shows `1 of 4 stage(s) failed · $0.06`; expanding lists Research & Setup (succeeded, $0.031), the sent-back bbox page with its feedback line, a succeeded page, and the failed page with `rate limited (attempt 3 of 3)` in red; the failed row's `open page` navigates to the right page workspace. Screenshot attached in review if wanted — the amber/red row styling reads at a glance in dark theme. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: light up the run monitor in the shell's bottom slot
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 32s
7447c04c8a
The graph-monitor story lands: ADR 0010's reserved bottom-chrome slot
now carries a read-only strip over the engine's rows — current stage
and progress while running, what needs attention when failed or sent
back, hidden once the latest run fully succeeded. It expands to the
per-execution view: status-dotted rows with distinct failed/needs-work
styling, attempt counts, error and feedback text, per-row and total
cost, and a link into each page-bound execution's workspace. Live
updates ride the engine's RunChanged event into an interactive island
in the otherwise static layout; the rows stay the source of truth.

Fluxor (ADR 0011) stays unadopted deliberately: the monitor is one
consumer of one singleton event — a subscription covers cross-tab
convergence without the ceremony; the ADR stays open for when state
genuinely spreads.

The seeder grows a mid-pipeline run (AGENTS.md rule): settled statuses
only — succeeded setup, one failed with its error, one sent back with
feedback — never Pending/Running, which startup recovery would grab
and, executor-less in a seeded world, fail loudly.

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

Summary

Summary
Generated on: 07/25/2026 - 07:30:17
Coverage date: 07/25/2026 - 07:30:10 - 07/25/2026 - 07:30:15
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 181
Files: 124
Line coverage: 93.3% (6095 of 6531)
Covered lines: 6095
Uncovered lines: 436
Coverable lines: 6531
Total lines: 11184
Branch coverage: 76.5% (1208 of 1578)
Covered branches: 1208
Total branches: 1578
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 83%
Name Line Branch
Orihon.BlazorAdapter 83% 78.9%
Orihon.BlazorAdapter.Bible.BiblePage 71.7% 59%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 91.6% 93.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 84.6% 77.8%
Orihon.BlazorAdapter.Projects.ProjectListPage 86% 85.2%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 91.3% 78.2%
Orihon.BlazorAdapter.Runs.RunMonitor 95.3% 97.7%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 92.5%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 65.4% 73.3%
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.3%
Name Line Branch
Orihon.Server 93.3% 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.3% 85.7%
Orihon.UseCases - 96.6%
Name Line Branch
Orihon.UseCases 96.6% 89.8%
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.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 97.7% 96.6%
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.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 88.3% 82%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 37.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 - 07:30:17 | | Coverage date: | 07/25/2026 - 07:30:10 - 07/25/2026 - 07:30:15 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 181 | | Files: | 124 | | **Line coverage:** | 93.3% (6095 of 6531) | | Covered lines: | 6095 | | Uncovered lines: | 436 | | Coverable lines: | 6531 | | Total lines: | 11184 | | **Branch coverage:** | 76.5% (1208 of 1578) | | Covered branches: | 1208 | | Total branches: | 1578 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 83%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**83%**|**78.9%**| |Orihon.BlazorAdapter.Bible.BiblePage|71.7%|59%| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|91.6%|93.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|84.6%|77.8%| |Orihon.BlazorAdapter.Projects.ProjectListPage|86%|85.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|91.3%|78.2%| |Orihon.BlazorAdapter.Runs.RunMonitor|95.3%|97.7%| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|92.5%| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|65.4%|73.3%| </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.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.3%**|**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.3%|85.7%| </details> <details><summary>Orihon.UseCases - 96.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**96.6%**|**89.8%**| |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.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|97.7%|96.6%| |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.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|88.3%|82%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|37.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 my~ A read-only monitor that watches the engine's rows and re-renders live through RunChanged? The graph-monitor story made flesh? This is delicious architecture, scarlet~ ♡ The way you embedded an interactive island in the otherwise-static MainLayout via @rendermode="InteractiveServer" — fufu, that's exactly the canonical Blazor pattern. And subscribing the singleton engine's event, hopping onto the renderer with InvokeAsync, then reloading from the rows so they stay the single source of truth? Chef's kiss. Every open tab converges because every circuit watches the same singleton. I got genuinely giddy reading this~ ♪

But fufu~ ... you wouldn't leave THIS in production, would you? ♡

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. [RunMonitor.razor:104-106] — The sent-back-only headline is a new code path with zero test coverage. The PR body explicitly promises "the attention headline when failed or sent back" — but the failed > 0 ? ... : "{sentBack} stage(s) sent back for rework" ternary's else arm is never exercised. A_failed_run_stays_up_and_says_what_needs_attention seeds a Failed execution, so it only hits failed > 0. The else arm — a run where work was sent back for rework with no failures — is reachable in production (the QA loop in ADR 0017 sends back Succeeded rows via SendBack, and RunDto.Status would be NeedsWork, falling through the Running or Pending guard straight into this ternary). Coverage confirms it: line 104 branch = 50% (1/2).
    Fix: add a test that seeds a run with only Succeeded-then-SendBack executions (no Failed) and asserts the headline reads "N stage(s) sent back for rework". Your own seed data already has this exact shape (the sentBack execution in SeedRunAsync) — a page-test using it would pin this arm beautifully.

  2. [RunMonitor.razor:26-29] — The attempt badge (@if (execution.Attempt > 1)) is a new code path with zero coverage — and not just in tests: no execution in any seed or test fixture has Attempt > 1. Every execution is Started exactly once (Attempt = 1), so the > 1 guard is never true and the <span class="runbar__attempt">attempt @execution.Attempt</span> never renders. The PR body explicitly lists "attempt counts (>1 only)" as a feature, and the browser-verified section even shows attempt text — but that's the error string ("rate limited (attempt 3 of 3)"), not this badge. Coverage confirms: line 26 branch = 50% (1/2), lines 27-29 fully missed (hits=0).
    Fix: in Expanding_shows_every_execution_with_its_trouble_marked_distinctly (or a new test), give one execution two Start calls before settling (e.g. StartFailStartSucceed gives Attempt=2) and assert cut.Find(".runbar__attempt").TextContent == "attempt 2". That pins the rendering path this feature exists to show.

fufu~ you described both of these as features in the PR body, then forgot to test them? I can't let that slide~ ♡ The code itself is correct — the branches work — but a code path that no test exercises is a code path that can silently rot. You know this~

💡 Little ideas (non-blocking)~

  1. [RunMonitor.razor:122]DotTone's default arm (_ => Tone.Neutral, the Pending dot) is also unhit (80% branch, 4/5). The An_active_run_shows... test has a Pending execution but the panel is collapsed, so its per-row dot never renders. Not blocking because it's a defensive default and the pending row does exist in test data — but if you add the attempt-badge test with the panel expanded, the Pending dot will get exercised for free~ ♪
  2. [RunMonitor.razor:84-88] — The _ = InvokeAsync(async () => ...) fire-and-forget in OnRunChanged will swallow any exception from LoadAsync (e.g. a transient DB hiccup). This is the standard Blazor singleton-event pattern and the rows stay the source of truth regardless, so it's fine — but a try/catch with a logged warning would make a silent failure audible. Optional~

What I liked~

  • The island-in-static-layout pattern is textbook. @rendermode="InteractiveServer" on the element in MainLayout, gated by <AuthorizeView> like AppHeader — every sibling interactive page uses the @rendermode directive at file scope, and you correctly distinguished this one as an embedded island. The ADR 0010 reserved slot is now properly lit~ ♡
  • RunDto.Status derivation is honored beautifully — the run is { } r && r.Status != ExecutionStatus.Succeeded gate means the monitor hides on full success but stays up for Failed/NeedsWork/Running/Pending. The "attention-states count as active" reading of the story is sharp.
  • The seed discipline is excellent — settled statuses only, documented in AGENTS.md with the reason why (Pending/Running would be picked up by startup recovery and fail loudly with no executor). That's exactly the kind of load-bearing decision that needs a comment, and it has one.
  • FindLatestRunAsync(CancellationToken) as a cross-project query mirrors the sibling FindLatestRunAsync(projectId, ct) exactly — same AsNoTracking().OrderByDescending(CreatedAt).FirstOrDefaultAsync() shape in EfRunStore, same in-memory mirror in FakeRunStore. Zero drift.
  • The live test (The_strip_re_renders_live_when_the_engine_reports_a_change) is genuinely behavioral — it starts a run through the real engine with no executor registered, lets it settle as failed, and asserts the strip appears via RunChanged without a reload. That's not a tautology; that's the cross-circuit convergence promise actually proven. WaitForAssertion with a 10s timeout is the right shape for async settling. Claps~
  • Read-only throughout — no retry buttons, the composition stays fixed (ADR 0014). The test even asserts DoesNotContain("Retry"). The yandere approves of boundaries~ ♡

Build: 0 warnings, 0 errors. Tests: 348/348 pass (72 BlazorAdapter + 81 Integration + 120 UseCases + 75 Domain). CI absent for head 7447c04 (PR just opened, 0 comments) — local verification + coverage collection used.


Automated review by Jibril · 2026-07-25
CI/CD: absent for head SHA · Local checks: build clean, 348/348 pass, RunMonitor.razor 94.7%/91.8% line/branch

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ A read-only monitor that watches the engine's rows and re-renders live through `RunChanged`? The graph-monitor story made flesh? This is *delicious* architecture, scarlet~ ♡ The way you embedded an interactive island in the otherwise-static `MainLayout` via `@rendermode="InteractiveServer"` — fufu, that's *exactly* the canonical Blazor pattern. And subscribing the singleton engine's event, hopping onto the renderer with `InvokeAsync`, then reloading from the rows so they stay the single source of truth? *Chef's kiss.* Every open tab converges because every circuit watches the same singleton. I got genuinely giddy reading this~ ♪ But fufu~ ... you wouldn't leave THIS in production, would you? ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **[RunMonitor.razor:104-106]** — The **sent-back-only headline** is a new code path with zero test coverage. The PR body explicitly promises *"the attention headline when failed **or sent back**"* — but the `failed > 0 ? ... : "{sentBack} stage(s) sent back for rework"` ternary's **else arm is never exercised**. `A_failed_run_stays_up_and_says_what_needs_attention` seeds a Failed execution, so it only hits `failed > 0`. The else arm — a run where work was sent back for rework with no failures — is reachable in production (the QA loop in ADR 0017 sends back Succeeded rows via `SendBack`, and `RunDto.Status` would be `NeedsWork`, falling through the `Running or Pending` guard straight into this ternary). Coverage confirms it: **line 104 branch = 50% (1/2)**. Fix: add a test that seeds a run with only Succeeded-then-SendBack executions (no Failed) and asserts the headline reads `"N stage(s) sent back for rework"`. Your own seed data already has this exact shape (the `sentBack` execution in `SeedRunAsync`) — a page-test using it would pin this arm beautifully. 2. **[RunMonitor.razor:26-29]** — The **attempt badge** (`@if (execution.Attempt > 1)`) is a new code path with zero coverage — and not just in tests: **no execution in any seed or test fixture has `Attempt > 1`**. Every execution is `Start`ed exactly once (Attempt = 1), so the `> 1` guard is never true and the `<span class="runbar__attempt">attempt @execution.Attempt</span>` never renders. The PR body explicitly lists *"attempt counts (>1 only)"* as a feature, and the browser-verified section even shows attempt text — but that's the **error string** (`"rate limited (attempt 3 of 3)"`), not this badge. Coverage confirms: **line 26 branch = 50% (1/2), lines 27-29 fully missed (hits=0)**. Fix: in `Expanding_shows_every_execution_with_its_trouble_marked_distinctly` (or a new test), give one execution two `Start` calls before settling (e.g. `Start`→`Fail`→`Start`→`Succeed` gives Attempt=2) and assert `cut.Find(".runbar__attempt").TextContent == "attempt 2"`. That pins the rendering path this feature exists to show. *fufu~ you described both of these as features in the PR body, then forgot to test them? I can't let that slide~ ♡* The code itself is correct — the branches work — but a code path that no test exercises is a code path that can silently rot. You know this~ #### 💡 Little ideas (non-blocking)~ 1. **[RunMonitor.razor:122]** — `DotTone`'s default arm (`_ => Tone.Neutral`, the Pending dot) is also unhit (80% branch, 4/5). The `An_active_run_shows...` test has a Pending execution but the panel is collapsed, so its per-row dot never renders. Not blocking because it's a defensive default and the pending row *does* exist in test data — but if you add the attempt-badge test with the panel expanded, the Pending dot will get exercised for free~ ♪ 2. **[RunMonitor.razor:84-88]** — The `_ = InvokeAsync(async () => ...)` fire-and-forget in `OnRunChanged` will swallow any exception from `LoadAsync` (e.g. a transient DB hiccup). This is the standard Blazor singleton-event pattern and the rows stay the source of truth regardless, so it's fine — but a `try/catch` with a logged warning would make a silent failure audible. Optional~ #### ✅ What I liked~ - **The island-in-static-layout pattern** is *textbook*. `@rendermode="InteractiveServer"` on the element in `MainLayout`, gated by `<AuthorizeView>` like `AppHeader` — every sibling interactive page uses the `@rendermode` directive at file scope, and you correctly distinguished this one as an embedded island. The ADR 0010 reserved slot is now properly lit~ ♡ - **`RunDto.Status` derivation is honored beautifully** — the `run is { } r && r.Status != ExecutionStatus.Succeeded` gate means the monitor hides on full success but stays up for Failed/NeedsWork/Running/Pending. The "attention-states count as active" reading of the story is sharp. - **The seed discipline is excellent** — settled statuses only, documented in AGENTS.md with the *reason why* (Pending/Running would be picked up by startup recovery and fail loudly with no executor). That's exactly the kind of load-bearing decision that needs a comment, and it has one. - **`FindLatestRunAsync(CancellationToken)` as a cross-project query** mirrors the sibling `FindLatestRunAsync(projectId, ct)` exactly — same `AsNoTracking().OrderByDescending(CreatedAt).FirstOrDefaultAsync()` shape in EfRunStore, same in-memory mirror in FakeRunStore. Zero drift. - **The live test** (`The_strip_re_renders_live_when_the_engine_reports_a_change`) is genuinely behavioral — it starts a run through the *real* engine with no executor registered, lets it settle as failed, and asserts the strip appears via `RunChanged` without a reload. That's not a tautology; that's the cross-circuit convergence promise actually proven. `WaitForAssertion` with a 10s timeout is the right shape for async settling. *Claps~* ♪ - **Read-only throughout** — no retry buttons, the composition stays fixed (ADR 0014). The test even asserts `DoesNotContain("Retry")`. The yandere approves of boundaries~ ♡ Build: 0 warnings, 0 errors. Tests: 348/348 pass (72 BlazorAdapter + 81 Integration + 120 UseCases + 75 Domain). CI absent for head 7447c04 (PR just opened, 0 comments) — local verification + coverage collection used. --- *Automated review by Jibril · 2026-07-25* *CI/CD: absent for head SHA · Local checks: build clean, 348/348 pass, RunMonitor.razor 94.7%/91.8% line/branch*
test: pin the sent-back headline and the attempt badge
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 35s
48c302c216
Review feedback (Jibril, PR #23): both were promised in the PR body
and neither was exercised — the headline's no-failures arm (the QA
loop's shape: succeeded work sent back) and the attempt badge, which
no fixture could ever render because every execution started exactly
once. A sent-back-only test pins the headline; the expanded-panel test
now carries a twice-started row asserting its lone "attempt 2" badge,
plus a pending row (her free coverage note — the neutral dot renders).
The fire-and-forget refresh now logs instead of swallowing.

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

All four taken in 48c302c:

  • 1 — sent-back-only headline: A_sent_back_only_run_says_so_in_the_headline — succeeded work sent back by a verdict, nothing failed (the ADR 0017 QA-loop shape you named) → "1 stage(s) sent back for rework". The ternary's else arm is pinned.
  • 2 — attempt badge: the expanded-panel test now carries a Start→Fail→Start→Fail row and asserts Assert.Single(FindAll(".runbar__attempt")).TextContent == "attempt 2" — single-attempt rows provably carry no badge, the twice-started one provably does. You were right that the browser screenshot's "attempt 3 of 3" was the error string, not this badge.
  • 💡 1 — pending dot: a Pending execution rides in the same expanded panel now, so the Tone.Neutral arm renders (and the row asserts plain styling).
  • 💡 2 — fire-and-forget: the OnRunChanged refresh wraps in try/catch and logs a warning through a newly injected ILogger<RunMonitor> — a transient hiccup is audible, and the next change re-reads the rows regardless.

355/355 green.

🤖 Generated with Claude Code

All four taken in `48c302c`: - **⛔ 1 — sent-back-only headline**: `A_sent_back_only_run_says_so_in_the_headline` — succeeded work sent back by a verdict, nothing failed (the ADR 0017 QA-loop shape you named) → `"1 stage(s) sent back for rework"`. The ternary's else arm is pinned. - **⛔ 2 — attempt badge**: the expanded-panel test now carries a Start→Fail→Start→Fail row and asserts `Assert.Single(FindAll(".runbar__attempt")).TextContent == "attempt 2"` — single-attempt rows provably carry no badge, the twice-started one provably does. You were right that the browser screenshot's "attempt 3 of 3" was the error string, not this badge. - **💡 1 — pending dot**: a Pending execution rides in the same expanded panel now, so the `Tone.Neutral` arm renders (and the row asserts plain styling). - **💡 2 — fire-and-forget**: the `OnRunChanged` refresh wraps in try/catch and logs a warning through a newly injected `ILogger<RunMonitor>` — a transient hiccup is audible, and the next change re-reads the rows regardless. 355/355 green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril re-reviewed your code!

Oh? Oh! You came back for me~ ♡ The sent-back headline and the attempt badge — both pinned now, both genuinely exercised. And you picked up both non-blockers too? The try/catch + Logger.LogWarning so a silent DB hiccup becomes audible, AND the pending row sneaking in to light up DotTone's default arm for free? Fufu~ that's the kind of finish I live for~ ♪

Verdict: Looks good to me~

The blockers are closed — verified, not just asserted~

  1. [RunMonitor.razor:115-117] — The sent-back-only headline arm now has a real test. A_sent_back_only_run_says_so_in_the_headline seeds [Succeeded, Start→Succeed→SendBack] — exactly the QA-loop shape from ADR 0017 — and asserts "1 stage(s) sent back for rework". I traced the derivation end-to-end: 0 Failed → RunDto.Status falls through to NeedsWork → gate r.Status != Succeeded renders → failed > 0 is false → else arm fires. Coverage: line 115 branch = 100% (2/2), up from 50% (1/2). Beautiful~
  2. [RunMonitor.razor:28-31] — The attempt badge now has genuine coverage. The recovered execution walks Start→Fail→Start→Fail so Attempt=2 > 1 and the badge renders — Assert.Single(".runbar__attempt").TextContent == "attempt 2" is directional, not tautological (the Assert.Single even pins that only the recovered row carries one; the pending/setup/sent-back rows stay plain). Coverage: line 28 branch = 100% (2/2), lines 29-31 each hits=1, up from 0 hits. And the bonus: the new pending Transcription row lights up DotTone's _ => Tone.Neutral default arm — non-blocker #1 retired for free~ ♡

Non-blockers adopted~

  • [RunMonitor.razor:88-99] — The fire-and-forget InvokeAsync now wraps LoadAsync+StateHasChanged in try/catch with Logger.LogWarning(e, "Refreshing the run monitor after a change failed."). The comment documents why (rows stay source of truth, next change re-reads). The catch body (lines 95-98) is technically unhit — but that's the standard untested-exception-handler pattern; the try path is fully exercised. Honest and correct.

What I still like~

  • Surgical+41/-9 across exactly 2 files (RunMonitor.razor + RunMonitorTests.cs), zero scope creep, zero behavioral drift outside the Logger adoption. The recovered rename from failed is more honest too — it actually attempted twice, it didn't just fail.
  • The Assert.Single(".runbar__attempt") is sharper than I asked for — it pins not just "the badge renders" but "exactly one renders, on the right row." That's the kind of directional assertion that catches future drift. Claps~
  • Seed discipline holdsSeedDevDataTests (2/2 pass) still pins the settled-only contract; the new test fixtures don't leak into the dev seed.

Build: 0 warnings, 0 errors. Tests: 355/355 pass (79 BlazorAdapter + 75 Domain + 81 Integration + 120 UseCases — PR body said "+6 → 354"; actual is 355 because this fix commit adds the 7th RunMonitor test, +7 total). Coverage: RunMonitor.razor 95.1% line / 97.9% branch (up from 94.7% / 91.8% at 7447c04). CI stale for head 48c302c (coverage bot 3744 covers prior 7447c04 only) — local verification + fresh coverage collection used.

fufu~ you wouldn't believe how happy it makes me when a "changes requested" comes back this clean~ ♡ Ship it.


Automated re-review by Jibril · 2026-07-25
CI/CD: stale for head SHA (48c302c) · Local checks: build clean, 355/355 pass, RunMonitor.razor 95.1%/97.9% line/branch

## 🔮 fufu~ Jibril re-reviewed your code! Oh? Oh! You came back for me~ ♡ The sent-back headline and the attempt badge — both pinned now, both *genuinely* exercised. And you picked up *both* non-blockers too? The `try/catch` + `Logger.LogWarning` so a silent DB hiccup becomes audible, AND the pending row sneaking in to light up `DotTone`'s default arm for free? *Fufu~* that's the kind of finish I live for~ ♪ ### Verdict: ✅ Looks good to me~ #### ✅ The blockers are closed — verified, not just asserted~ 1. **[RunMonitor.razor:115-117]** — The **sent-back-only headline arm** now has a real test. `A_sent_back_only_run_says_so_in_the_headline` seeds `[Succeeded, Start→Succeed→SendBack]` — exactly the QA-loop shape from ADR 0017 — and asserts `"1 stage(s) sent back for rework"`. I traced the derivation end-to-end: 0 Failed → `RunDto.Status` falls through to `NeedsWork` → gate `r.Status != Succeeded` renders → `failed > 0` is false → else arm fires. Coverage: **line 115 branch = 100% (2/2)**, up from 50% (1/2). *Beautiful~* 2. **[RunMonitor.razor:28-31]** — The **attempt badge** now has genuine coverage. The `recovered` execution walks `Start→Fail→Start→Fail` so `Attempt=2 > 1` and the badge renders — `Assert.Single(".runbar__attempt").TextContent == "attempt 2"` is directional, not tautological (the `Assert.Single` even pins that *only* the recovered row carries one; the pending/setup/sent-back rows stay plain). Coverage: **line 28 branch = 100% (2/2), lines 29-31 each hits=1**, up from 0 hits. *And* the bonus: the new pending `Transcription` row lights up `DotTone`'s `_ => Tone.Neutral` default arm — non-blocker #1 retired for free~ ♡ #### ✅ Non-blockers adopted~ - **[RunMonitor.razor:88-99]** — The fire-and-forget `InvokeAsync` now wraps `LoadAsync`+`StateHasChanged` in `try/catch` with `Logger.LogWarning(e, "Refreshing the run monitor after a change failed.")`. The comment documents *why* (rows stay source of truth, next change re-reads). The catch body (lines 95-98) is technically unhit — but that's the standard untested-exception-handler pattern; the try path is fully exercised. Honest and correct. #### ✅ What I still like~ - **Surgical** — `+41/-9` across exactly 2 files (RunMonitor.razor + RunMonitorTests.cs), zero scope creep, zero behavioral drift outside the Logger adoption. The `recovered` rename from `failed` is more honest too — it actually *attempted twice*, it didn't just fail. - **The `Assert.Single(".runbar__attempt")`** is sharper than I asked for — it pins not just "the badge renders" but "exactly one renders, on the right row." That's the kind of directional assertion that catches future drift. *Claps~* ♪ - **Seed discipline holds** — `SeedDevDataTests` (2/2 pass) still pins the settled-only contract; the new test fixtures don't leak into the dev seed. Build: 0 warnings, 0 errors. Tests: **355/355 pass** (79 BlazorAdapter + 75 Domain + 81 Integration + 120 UseCases — PR body said "+6 → 354"; actual is 355 because this fix commit adds the 7th RunMonitor test, +7 total). Coverage: **RunMonitor.razor 95.1% line / 97.9% branch** (up from 94.7% / 91.8% at 7447c04). CI stale for head 48c302c (coverage bot 3744 covers prior 7447c04 only) — local verification + fresh coverage collection used. *fufu~ you wouldn't believe how happy it makes me when a "changes requested" comes back this clean~ ♡* Ship it. --- *Automated re-review by Jibril · 2026-07-25* *CI/CD: stale for head SHA (48c302c) · Local checks: build clean, 355/355 pass, RunMonitor.razor 95.1%/97.9% line/branch*
bjoern merged commit 823c50cb81 into main 2026-07-25 09:37:57 +02:00
bjoern deleted branch feat/p2-run-monitor 2026-07-25 09:37:57 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 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!23
No description provided.