fix: the region editor always follows the selection — and clicking a bbox selects it #50

Merged
bjoern merged 5 commits from fix/page-editor-follows-selection into main 2026-08-13 06:23:57 +02:00
Member

Bjoern's report: click through the regions on the Bbox view and the panel's fields — including the Type combobox — keep the previous region's content. Reproduced in a live browser and traced to three separate defects, each fixed at its layer; both Kagaku.UI halves are already merged (this PR pins the submodule at their merge, c4d9705).

The root cause (Kagaku.UI #6, merged) — the staleness only appears after typing (or picking a Type by hand): TextArea rendered its value as child text and Select as selected attributes — both default-value forms the browser ignores once the element's dirty flag is set. Blazor's diff was applying every rebind; the DOM was discarding it, so whichever field the user had touched froze forever. Both now bind the value attribute, which Blazor writes as the DOM property on every diff (TextField's existing, unaffected form). bUnit never caught it because the render tree was always "correct" — the discard exists only in a real DOM.

What's in this PR

  • Submodule pin — Kagaku.UI at merged main (c4d9705): #6 (value-attribute fix, the root cause) and #5 (SurfaceClicked — a sub-minimum press reports its point instead of being silently swallowed).
  • Clicking a bbox on the page selects its region (PageWorkspacePage) — the overlay boxes are pointer-transparent by design, so a plain click on one used to do nothing. The selector now reports it and the page hit-tests the point: the smallest containing region wins (a nested bubble beats the panel around it), a click on empty page keeps the selection.
  • Selection changes flush the pending edit first (SelectAsync) — a keystroke still held by the debounce belongs to the OLD region; without the flush, clicking through regions mid-typing dropped the last edit and fired a no-op save against the new one (silent data loss, verified on main). Every user-driven selection path — row click, ghost label, New button, bbox click — routes through it.
  • Test follow-up — four assertions reading textarea content via TextContent move to GetAttribute("value") (the Kagaku #6 consumer note).

Tests

148 adapter tests (+3 on this branch): clicking a bbox selects its region (distinct bboxes, editor content asserted by value); a click on empty page keeps the selection; clicking through regions mid-typing lands the typed text against the OLD region, leaves the new one untouched, and switches the editor. Full suite green at the pinned submodule: 489/489 (75 Domain + 177 UseCases + 89 Integration + 148 BlazorAdapter).

Browser-verified

The exact reported flow, before and after: on main, typing "blabla" into p2r1's Notes and clicking p2r2 left the Notes DOM showing "blabla" against an empty render tree (stale forever, every subsequent switch). With the fix, the field follows the selection both ways and the typed text lands on the region it was typed into. Bbox clicking verified with real pointer events: click region 1's box → panel shows region 1; click region 2's box → switches; click empty page → selection stays.

Notes

  • The Type select's staleness needed a real by-hand pick to manifest (option dirtiness is user-interaction-only per spec), which synthetic events can't fully simulate — the fix is the same mechanism as the textarea's and is pinned by Kagaku's own rebind tests.
  • Merge order already satisfied: both Kagaku PRs are in; this PR only consumes them.

🤖 Generated with Claude Code

Bjoern's report: click through the regions on the Bbox view and the panel's fields — including the Type combobox — keep the previous region's content. Reproduced in a live browser and traced to **three separate defects**, each fixed at its layer; both Kagaku.UI halves are already merged (this PR pins the submodule at their merge, `c4d9705`). **The root cause (Kagaku.UI #6, merged)** — the staleness only appears after *typing* (or picking a Type by hand): `TextArea` rendered its value as child text and `Select` as `selected` attributes — both default-value forms the browser ignores once the element's dirty flag is set. Blazor's diff was applying every rebind; the DOM was discarding it, so whichever field the user had touched froze forever. Both now bind the `value` attribute, which Blazor writes as the DOM property on every diff (`TextField`'s existing, unaffected form). bUnit never caught it because the render tree was always "correct" — the discard exists only in a real DOM. **What's in this PR** - *Submodule pin* — Kagaku.UI at merged main (`c4d9705`): #6 (value-attribute fix, the root cause) and #5 (`SurfaceClicked` — a sub-minimum press reports its point instead of being silently swallowed). - *Clicking a bbox on the page selects its region* (`PageWorkspacePage`) — the overlay boxes are pointer-transparent by design, so a plain click on one used to do nothing. The selector now reports it and the page hit-tests the point: the smallest containing region wins (a nested bubble beats the panel around it), a click on empty page keeps the selection. - *Selection changes flush the pending edit first* (`SelectAsync`) — a keystroke still held by the debounce belongs to the OLD region; without the flush, clicking through regions mid-typing dropped the last edit and fired a no-op save against the new one (silent data loss, verified on main). Every user-driven selection path — row click, ghost label, New button, bbox click — routes through it. - *Test follow-up* — four assertions reading textarea content via `TextContent` move to `GetAttribute("value")` (the Kagaku #6 consumer note). **Tests** 148 adapter tests (+3 on this branch): clicking a bbox selects its region (distinct bboxes, editor content asserted by value); a click on empty page keeps the selection; clicking through regions mid-typing lands the typed text against the OLD region, leaves the new one untouched, and switches the editor. Full suite green at the pinned submodule: 489/489 (75 Domain + 177 UseCases + 89 Integration + 148 BlazorAdapter). **Browser-verified** The exact reported flow, before and after: on main, typing "blabla" into p2r1's Notes and clicking p2r2 left the Notes DOM showing "blabla" against an empty render tree (stale forever, every subsequent switch). With the fix, the field follows the selection both ways and the typed text lands on the region it was typed into. Bbox clicking verified with real pointer events: click region 1's box → panel shows region 1; click region 2's box → switches; click empty page → selection stays. **Notes** - The Type select's staleness needed a real by-hand pick to manifest (option dirtiness is user-interaction-only per spec), which synthetic events can't fully simulate — the fix is the same mechanism as the textarea's and is pinned by Kagaku's own rebind tests. - Merge order already satisfied: both Kagaku PRs are in; this PR only consumes them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The overlay boxes are pointer-transparent by design, so a click on one
fell through to the drawing surface and was silently refused as a
sub-minimum draw — the panel never followed. Kagaku.UI's RegionSelector
now reports such presses as SurfaceClicked (companion PR), and the page
hit-tests the point against its regions: the smallest hit wins (a
nested bubble beats the panel around it), empty page keeps the
selection. Selection changes also flush a pending debounced edit first
— clicking through regions mid-typing must never eat the last keystroke.

Submodule: Kagaku.UI at the SurfaceClicked commit (repointed to the
merged main commit before this PR opens).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kagaku.UI #6 binds a TextArea's value as the attribute (the child-text
form was only the default value and froze user-touched fields on
rebind); the four assertions reading textarea TextContent follow it to
GetAttribute("value").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chore: Kagaku.UI to merged main (SurfaceClicked #5 + value-attribute fix #6)
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 39s
5cb39dd222
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/26/2026 - 10:56:23
Coverage date: 07/26/2026 - 10:56:09 - 07/26/2026 - 10:56:20
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 362
Files: 167
Line coverage: 94% (8738 of 9290)
Covered lines: 8738
Uncovered lines: 552
Coverable lines: 9290
Total lines: 17205
Branch coverage: 80.5% (1932 of 2400)
Covered branches: 1932
Total branches: 2400
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.9%
Name Line Branch
Orihon.BlazorAdapter 95.9% 88.4%
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.2% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.7% 81.6%
Orihon.BlazorAdapter.Bible.BibleReducers 93.1%
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.SaveOverviewRequested 100%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 96.2% 94.4%
Orihon.BlazorAdapter.Diagnostics.CircuitError 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink 100% 85.7%
Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer 85.7% 66.6%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved 100%
Orihon.BlazorAdapter.PageWorkspace.PageViewport 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 91.8% 85.1%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 66.6%
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.ReprocessPageRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.1%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94.1% 86.2%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 100% 97.6%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 100%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectMetadataCard 95.2% 92.8%
Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.5% 88.3%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.RunAnnotationRequested 100%
Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested 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.Bible.StoryOverview 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 - 94.3%
Name Line Branch
Orihon.Infrastructure 94.3% 67.3%
Orihon.Infrastructure.Bible.EfBibleStore 94.4% 91.6%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98% 83%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 96.6% 86.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.Configurations.StoryOverviewConfiguration 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.AddStoryOverview 99.5%
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 86% 80%
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 - 91%
Name Line Branch
Orihon.UseCases 91% 83.1%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 100% 92.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.Annotation.AddRegionParams 100%
Orihon.UseCases.Agents.Annotation.AddRegionTool 76.9% 50%
Orihon.UseCases.Agents.Annotation.AnnotationBlueprints 100%
Orihon.UseCases.Agents.Annotation.AnnotationStage 94.4% 50%
Orihon.UseCases.Agents.Annotation.BboxCreationExecutor 94.1% 50%
Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor 90.4% 62.5%
Orihon.UseCases.Agents.Annotation.BoundBoxParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetTool 10.7% 0%
Orihon.UseCases.Agents.Annotation.BoundCropParams 0%
Orihon.UseCases.Agents.Annotation.BoundCropTool 42.8%
Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool 15% 0%
Orihon.UseCases.Agents.Annotation.BoundViewPageTool 18.7% 0%
Orihon.UseCases.Agents.Annotation.BoundViewParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomTool 37.5%
Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool 91.6% 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionParams 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionTool 85.7% 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryParams 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryTool 76.4% 62.5%
Orihon.UseCases.Agents.Annotation.ListRegionsTool 76.4% 60%
Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool 27.2% 0%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams 100%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool 73.3% 50%
Orihon.UseCases.Agents.Annotation.PageQaExecutor 94.4% 83.3%
Orihon.UseCases.Agents.Annotation.QaReportSink 100%
Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess 86.6% 53.8%
Orihon.UseCases.Agents.Annotation.RejectRegionParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionTool 85.7% 50%
Orihon.UseCases.Agents.Annotation.ReorderRegionParams 100%
Orihon.UseCases.Agents.Annotation.ReorderRegionTool 80% 60%
Orihon.UseCases.Agents.Annotation.ReportQaParams 100%
Orihon.UseCases.Agents.Annotation.ReportQaTool 82.3% 93.7%
Orihon.UseCases.Agents.Annotation.SetPageMetaParams 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaTool 85.7% 75%
Orihon.UseCases.Agents.Annotation.SetRegionTypeParams 100%
Orihon.UseCases.Agents.Annotation.SetRegionTypeTool 85.7% 87.5%
Orihon.UseCases.Agents.Annotation.SetTranscriptionParams 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionTool 80% 100%
Orihon.UseCases.Agents.Annotation.TranscriptionExecutor 90.4% 75%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.Inspection.ContactSheetParams 100%
Orihon.UseCases.Agents.Inspection.ContactSheetTool 82.1% 92.8%
Orihon.UseCases.Agents.Inspection.CropParams 100%
Orihon.UseCases.Agents.Inspection.CropTool 42.8%
Orihon.UseCases.Agents.Inspection.PageImageAccess 66.6% 62%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams 100%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool 76.1% 83.3%
Orihon.UseCases.Agents.Inspection.ZoomParams 100%
Orihon.UseCases.Agents.Inspection.ZoomTool 44.4%
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 89.4% 100%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 90% 87.5%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool 95.2% 90%
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.SetStoryOverviewParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool 100% 100%
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 100% 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 97.1% 89.2%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 87.5%
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.SetStoryOverview 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.StoryOverviewDto 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.LabeledBox 100%
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.MarkPageAnnotated 100% 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.StartAnnotationRun 95.4% 90%
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.AnnotationPipeline 100% 100%
Orihon.UseCases.Runs.ExecutionDto 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 95.3% 90.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 87.5%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 10:56:23 | | Coverage date: | 07/26/2026 - 10:56:09 - 07/26/2026 - 10:56:20 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 362 | | Files: | 167 | | **Line coverage:** | 94% (8738 of 9290) | | Covered lines: | 8738 | | Uncovered lines: | 552 | | Coverable lines: | 9290 | | Total lines: | 17205 | | **Branch coverage:** | 80.5% (1932 of 2400) | | Covered branches: | 1932 | | Total branches: | 2400 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.9%**|**88.4%**| |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.2%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.7%|81.6%| |Orihon.BlazorAdapter.Bible.BibleReducers|93.1%|| |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.SaveOverviewRequested|100%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|96.2%|94.4%| |Orihon.BlazorAdapter.Diagnostics.CircuitError|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink|100%|85.7%| |Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer|85.7%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageViewport|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|91.8%|85.1%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|66.6%| |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.ReprocessPageRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.1%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94.1%|86.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|100%|97.6%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|100%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectMetadataCard|95.2%|92.8%| |Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.5%|88.3%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunAnnotationRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested|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.Bible.StoryOverview|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 - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**94.3%**|**67.3%**| |Orihon.Infrastructure.Bible.EfBibleStore|94.4%|91.6%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98%|83%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|96.6%|86.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.Configurations.StoryOverviewConfiguration|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.AddStoryOverview|99.5%|| |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|86%|80%| |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 - 91%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**91%**|**83.1%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|100%|92.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.Annotation.AddRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionTool|76.9%|50%| |Orihon.UseCases.Agents.Annotation.AnnotationBlueprints|100%|| |Orihon.UseCases.Agents.Annotation.AnnotationStage|94.4%|50%| |Orihon.UseCases.Agents.Annotation.BboxCreationExecutor|94.1%|50%| |Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor|90.4%|62.5%| |Orihon.UseCases.Agents.Annotation.BoundBoxParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetTool|10.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundCropParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundCropTool|42.8%|| |Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool|15%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewPageTool|18.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomTool|37.5%|| |Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool|91.6%|100%| |Orihon.UseCases.Agents.Annotation.DeleteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteRegionTool|85.7%|100%| |Orihon.UseCases.Agents.Annotation.FindGlossaryParams|100%|| |Orihon.UseCases.Agents.Annotation.FindGlossaryTool|76.4%|62.5%| |Orihon.UseCases.Agents.Annotation.ListRegionsTool|76.4%|60%| |Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool|27.2%|0%| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool|73.3%|50%| |Orihon.UseCases.Agents.Annotation.PageQaExecutor|94.4%|83.3%| |Orihon.UseCases.Agents.Annotation.QaReportSink|100%|| |Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess|86.6%|53.8%| |Orihon.UseCases.Agents.Annotation.RejectRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionTool|85.7%|50%| |Orihon.UseCases.Agents.Annotation.ReorderRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ReorderRegionTool|80%|60%| |Orihon.UseCases.Agents.Annotation.ReportQaParams|100%|| |Orihon.UseCases.Agents.Annotation.ReportQaTool|82.3%|93.7%| |Orihon.UseCases.Agents.Annotation.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.Annotation.SetPageMetaTool|85.7%|75%| |Orihon.UseCases.Agents.Annotation.SetRegionTypeParams|100%|| |Orihon.UseCases.Agents.Annotation.SetRegionTypeTool|85.7%|87.5%| |Orihon.UseCases.Agents.Annotation.SetTranscriptionParams|100%|| |Orihon.UseCases.Agents.Annotation.SetTranscriptionTool|80%|100%| |Orihon.UseCases.Agents.Annotation.TranscriptionExecutor|90.4%|75%| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.Inspection.ContactSheetParams|100%|| |Orihon.UseCases.Agents.Inspection.ContactSheetTool|82.1%|92.8%| |Orihon.UseCases.Agents.Inspection.CropParams|100%|| |Orihon.UseCases.Agents.Inspection.CropTool|42.8%|| |Orihon.UseCases.Agents.Inspection.PageImageAccess|66.6%|62%| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams|100%|| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool|76.1%|83.3%| |Orihon.UseCases.Agents.Inspection.ZoomParams|100%|| |Orihon.UseCases.Agents.Inspection.ZoomTool|44.4%|| |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|89.4%|100%| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|90%|87.5%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool|95.2%|90%| |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.SetStoryOverviewParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool|100%|100%| |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|100%|100%| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|97.1%|89.2%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|87.5%| |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.SetStoryOverview|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.StoryOverviewDto|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.LabeledBox|100%|| |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.MarkPageAnnotated|100%|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.StartAnnotationRun|95.4%|90%| |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.AnnotationPipeline|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|95.3%|90.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|87.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ ♡ A three-layer defect hunt! Bjoern reported a stale-field bug and you traced it all the way down to the DOM dirty-flag discard, then noticed the bbox clicks were being swallowed on the way through, AND caught a silent data-loss race in the debounce. That's not a patch — that's a proper hunt. The kill belongs to the patient hunter, fufu~

Verdict: Looks good to me~

The diagnosis is precise, the fix lives at each defect's own layer, and every claim in the PR body checks out against the code. No blockers. One stale-base observation below (not your code's fault, but the author should rebase before merge).

What I liked~

  • Root-cause honesty over symptom-patching. You didn't shim the staleness in Orihon — you found it in Kagaku.UI's TextArea/Select and fixed it there (PR #6, already merged), then only consumed the fix here. The four TextContentGetAttribute("value") test migrations are the correct downstream consequence. I verified zero remaining .TextContent reads on textarea/select elements — every surviving .TextContent is on a <span> (.region-row__label), which the dirty-flag rule doesn't touch. Clean.

  • SelectAsync is the real prize. Fufu~ this is the kind of fix that earns its keep. The debounce holds a keystroke belonging to the old region; without the flush, clicking through mid-typing silently dropped the edit AND fired a no-op save against the new region. Routing every user-driven selection path — row click, ghost label, New button, bbox click — through one await debounce.FlushAsync() is exactly right. And the RegionCreated action subscriber correctly bypasses it (a brand-new region has no pending edit), so there's no false flush. The single mid-typing test (Clicking_through_regions_mid_typing_flushes_the_edit_first) is genuinely directional: it asserts 直した台詞 lands on p2r1, ドキドキ stays untouched on p2r2, and the selection moved. If the flush were missing, the first assertion fails.

  • The bbox hit-test is geometrically sound. Overlay boxes are pointer-transparent by design (kagaku-region handles the gesture), so SurfaceClicked → smallest-containing-region-wins is the only way a click on a bbox reaches the panel. The OrderBy(area).FirstOrDefault() correctly prefers a nested bubble over the panel around it, and the hit.Id != selectedId guard avoids a redundant flush+re-select. Empty-page click → hit is null → no-op → selection stays. Both branches pinned by tests.

  • Submodule pin is honest. c4d9705 is verifiably the merge of #5 (SurfaceClicked) + #6 (value-attribute) — I checked the Kagaku.UI log. Both halves already merged; this PR only consumes.

💡 Little ideas (non-blocking)~

  1. The branch is stale on main — needs a rebase before merge. The PR's base is 7bdec2b (PR #45 merge), but main has since advanced through #47, #48, #49. Building at head SHA 5cb39dd fails with CS1501: No overload for 'StartOrJoinRunAsync' takes 4 arguments and IPageStore.SetAnnotatedAsync not foundnot your code's fault, those are APIs added by the intervening PRs. I rebased your 3 commits onto current main (4feeef2): clean, zero conflicts, the touched files (PageWorkspacePage.razor, the 3 test files, the submodule pin) haven't been modified by #47/#48/#49. After rebase: build 0 warnings/0 errors, 489/489 pass (75 Domain + 187 UseCases + 89 Integration + 148 BlazorAdapter — note the PR body says "151 adapter tests" in one place but the table says 148; 148 is correct, the +3 holds). Just rebase and you're golden~

  2. OnRegionRect (draw-create while another region is selected) doesn't flush the old region's pending edit. If region A is selected with a debounced keystroke pending and the user draws a new region B (instead of clicking), OnRegionRect sees selected is not null → updates edit.Bbox and SaveNow()s region A's box, but the pending text edit for A is still in the debounce. Then RegionCreated fires, the subscriber sets selectedId/edit to the new region B, and A's pending keystroke is now orphaned — it'll flush against B's buffer on the next tick (wrong region) or get replaced by B's next keystroke. This is the same class of race SelectAsync closes, just on the draw path instead of the click path. It's pre-existing (not introduced by this PR) and arguably an edge case (draw-new-while-typing is rare), so I'm not blocking — but if you want full symmetry, OnRegionRect's selected is null branch could flush first too. Optional.


Automated review by Jibril · 2026-07-26
CI/CD: absent for head SHA 5cb39dd (PR just opened, 0 bot comments) · Local checks: build + full suite run on rebased tree (base was stale; rebased cleanly onto main@4feeef2, 0 conflicts, 489/489 pass)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ ♡ A three-layer defect hunt! Bjoern reported a stale-field bug and you traced it all the way down to the DOM dirty-flag discard, then noticed the bbox clicks were being *swallowed* on the way through, AND caught a silent data-loss race in the debounce. That's not a patch — that's a proper hunt. The kill belongs to the patient hunter, fufu~ ### Verdict: ✅ Looks good to me~ The diagnosis is precise, the fix lives at each defect's own layer, and every claim in the PR body checks out against the code. No blockers. One stale-base observation below (not your code's fault, but the author should rebase before merge). #### ✅ What I liked~ - **Root-cause honesty over symptom-patching.** You didn't shim the staleness in Orihon — you found it in Kagaku.UI's `TextArea`/`Select` and fixed it there (PR #6, already merged), then only *consumed* the fix here. The four `TextContent` → `GetAttribute("value")` test migrations are the correct downstream consequence. I verified **zero** remaining `.TextContent` reads on textarea/select elements — every surviving `.TextContent` is on a `<span>` (`.region-row__label`), which the dirty-flag rule doesn't touch. Clean. - **`SelectAsync` is the real prize.** Fufu~ this is the kind of fix that earns its keep. The debounce holds a keystroke belonging to the *old* region; without the flush, clicking through mid-typing silently dropped the edit AND fired a no-op save against the new region. Routing **every** user-driven selection path — row click, ghost label, New button, bbox click — through one `await debounce.FlushAsync()` is exactly right. And the `RegionCreated` action subscriber correctly *bypasses* it (a brand-new region has no pending edit), so there's no false flush. The single mid-typing test (`Clicking_through_regions_mid_typing_flushes_the_edit_first`) is genuinely directional: it asserts `直した台詞` lands on `p2r1`, `ドキドキ` stays untouched on `p2r2`, and the selection moved. If the flush were missing, the first assertion fails. - **The bbox hit-test is geometrically sound.** Overlay boxes are `pointer-transparent` by design (kagaku-region handles the gesture), so `SurfaceClicked` → smallest-containing-region-wins is the only way a click on a bbox reaches the panel. The `OrderBy(area).FirstOrDefault()` correctly prefers a nested bubble over the panel around it, and the `hit.Id != selectedId` guard avoids a redundant flush+re-select. Empty-page click → `hit is null` → no-op → selection stays. Both branches pinned by tests. - **Submodule pin is honest.** `c4d9705` is verifiably the merge of #5 (SurfaceClicked) + #6 (value-attribute) — I checked the Kagaku.UI log. Both halves already merged; this PR only consumes. #### 💡 Little ideas (non-blocking)~ 1. **The branch is stale on `main` — needs a rebase before merge.** The PR's base is `7bdec2b` (PR #45 merge), but `main` has since advanced through #47, #48, #49. Building at head SHA `5cb39dd` fails with `CS1501: No overload for 'StartOrJoinRunAsync' takes 4 arguments` and `IPageStore.SetAnnotatedAsync not found` — **not** your code's fault, those are APIs added by the intervening PRs. I rebased your 3 commits onto current `main` (`4feeef2`): clean, zero conflicts, the touched files (`PageWorkspacePage.razor`, the 3 test files, the submodule pin) haven't been modified by #47/#48/#49. After rebase: build 0 warnings/0 errors, 489/489 pass (75 Domain + 187 UseCases + 89 Integration + 148 BlazorAdapter — note the PR body says "151 adapter tests" in one place but the table says 148; 148 is correct, the +3 holds). Just rebase and you're golden~ 2. **`OnRegionRect` (draw-create while another region is selected) doesn't flush the old region's pending edit.** If region A is selected with a debounced keystroke pending and the user *draws* a new region B (instead of clicking), `OnRegionRect` sees `selected is not null` → updates `edit.Bbox` and `SaveNow()`s region A's *box*, but the pending *text* edit for A is still in the debounce. Then `RegionCreated` fires, the subscriber sets `selectedId`/`edit` to the new region B, and A's pending keystroke is now orphaned — it'll flush against B's buffer on the next tick (wrong region) or get replaced by B's next keystroke. This is the *same class* of race `SelectAsync` closes, just on the draw path instead of the click path. It's pre-existing (not introduced by this PR) and arguably an edge case (draw-new-while-typing is rare), so I'm not blocking — but if you want full symmetry, `OnRegionRect`'s `selected is null` branch could flush first too. Optional. --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head SHA `5cb39dd` (PR just opened, 0 bot comments) · Local checks: build + full suite run on rebased tree (base was stale; rebased cleanly onto `main@4feeef2`, 0 conflicts, 489/489 pass)*
bjoern force-pushed fix/page-editor-follows-selection from 5cb39dd222
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 39s
to 913aabda42
Some checks failed
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Failing after 38s
2026-07-26 12:53:40 +02:00
Compare
Fixup: a cleared draft omits the value attribute entirely
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 43s
61604b43d0
GetAttribute returns null for the absent attribute; the assertion now
accepts either spelling of empty.

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

Round 1 addressed — the branch is now 61604b4 (rebased, so the three original commits carry new SHAs):

  • 💡 1 — taken: rebased onto main@4feeef2 exactly as you did it (clean, zero conflicts), and the intervening annotation UI turned out to carry two more textarea TextContent reads — one genuinely failing against the Kagaku #6 form, one passing only because it asserted empty. Both converted (913aabd), plus one honest follow-up (61604b4): a cleared draft omits the value attribute entirely, so the cleared-box assertion accepts null-or-empty. The body's stray "151" you spotted was already edited out before your round — the table's 148 was the pre-rebase truth; post-rebase the adapter suite is 152.
  • 💡 2 — no code change, with the mechanics: the create branch of OnRegionRect can only run with selected is null, and the editor (the only source of keystrokes) renders only under a selection — so by the time a draw can create, every deselection path (New: draw on page, delete, the bbox/row/ghost clicks) has already gone through SelectAsync's flush, and no pending edit can exist. The other half of your scenario — drag with a selection while a keystroke is pending — saves the whole edit buffer (SaveNow writes the profile, typed text included), so nothing orphans there either. A flush in the create branch would be dead code behind the selected is null guard; happy to add it if you'd rather carry the belt anyway.

Full suite at 61604b4: 507/507 green locally (75 Domain + 191 UseCases + 89 Integration + 152 BlazorAdapter).

🤖 Generated with Claude Code

Round 1 addressed — the branch is now `61604b4` (rebased, so the three original commits carry new SHAs): - 💡 1 — taken: rebased onto `main@4feeef2` exactly as you did it (clean, zero conflicts), and the intervening annotation UI turned out to carry two more textarea `TextContent` reads — one genuinely failing against the Kagaku #6 form, one passing only because it asserted empty. Both converted (`913aabd`), plus one honest follow-up (`61604b4`): a cleared draft omits the `value` attribute entirely, so the cleared-box assertion accepts null-or-empty. The body's stray "151" you spotted was already edited out before your round — the table's 148 was the pre-rebase truth; post-rebase the adapter suite is 152. - 💡 2 — no code change, with the mechanics: the create branch of `OnRegionRect` can only run with `selected is null`, and the editor (the only source of keystrokes) renders only under a selection — so by the time a draw can create, every deselection path (`New: draw on page`, delete, the bbox/row/ghost clicks) has already gone through `SelectAsync`'s flush, and no pending edit can exist. The other half of your scenario — drag with a selection while a keystroke is pending — saves the *whole* edit buffer (`SaveNow` writes the profile, typed text included), so nothing orphans there either. A flush in the create branch would be dead code behind the `selected is null` guard; happy to add it if you'd rather carry the belt anyway. Full suite at `61604b4`: 507/507 green locally (75 Domain + 191 UseCases + 89 Integration + 152 BlazorAdapter). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Back so soon~ ♡ The rebase onto main is clean and the stale-base blocker from my last pass is gone — lovely. And you even chased down two more TextContentGetAttribute("value") migrations that the annotation-UI merge (#48) dragged in. Diligent little hunter~

…but fufu~ one of those migrations has a tooth missing. The smile doesn't waver, but the knife is out. ♡

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. tests/.../PageWorkspacePageTests.cs:521Reprocess_sends_the_page_back_and_clears_the_box FAILS. The migration swapped the read but kept the wrong expected value.

    The new commit 913aabd migrates the assertion from .TextContent to .GetAttribute("value") — correct instinct, but:

    Assert.Equal("", cut.Find("textarea[aria-label='Reprocess feedback']").GetAttribute("value"));
    

    fails with Expected: "" Actual: null. I reproduced it twice locally (deterministic, not a flake):

    Failed Orihon.BlazorAdapter.Tests.PageWorkspacePageTests.Reprocess_sends_the_page_back_and_clears_the_box [6 s]
      Error Message:
       Assert.Equal() Failure: Strings differ
      Expected: ""
      Actual:   null
    

    Root cause — the field's type, not the attribute read. The reprocess textarea binds to reprocessFeedback, declared at PageWorkspacePage.razor:344 as private string? reprocessFeedback; — a nullable, never initialized to "". On send-back it's explicitly set to null (:395: reprocessFeedback = null;). Blazor treats a null attribute value as "do not render the attribute," so GetAttribute("value") returns null, not "".

    Compare the sibling summary field, which your other migration (ProjectWorkspacePageTests.cs:38, Assert.Equal("", ...GetAttribute("value"))) handles correctly because it passes: summaryText is private string summaryText = ""; (:354) and reset with ?? "" (:422) — so Value="" renders value="" and GetAttribute returns "". Same .Equal("") shape, different outcome, entirely due to the string? vs string declaration of the bound field. That's the seam.

    The old .TextContent read returned "" for an empty textarea regardless of null-vs-empty-string — that's why the original assertion passed and why this migration is the one that bit. Your commit message even says "one [of the two] passed only because it asserted empty" — that's the one. The assertion's intent (field is cleared after send-back) is still correct; only the expected literal is wrong.

    Fix (one character class):

    Assert.Null(cut.Find("textarea[aria-label='Reprocess feedback']").GetAttribute("value"));
    

    The field genuinely IS cleared — null attribute means "no value rendered," which is the correct empty state for a string?-bound textarea. Assert it honestly.

    (Aside, non-blocking: if you'd rather keep Assert.Equal("", …) symmetry with the summary test, you could initialize private string? reprocessFeedback = ""; and reset to "" on :395 — but that's a production-code change to satisfy a test literal, and null is the more honest "no feedback" sentinel for a string?. I'd just fix the assertion.)

What I liked~

  • The rebase itself is immaculate. Three feature commits rebased onto main@85ba6b7 with zero conflicts, and the touched files (PageWorkspacePage.razor, the test files, the submodule pin) are untouched by the intervening #47/#48/#49. The stale-base blocker from my prior review (comment 4131) is fully resolved — merge_base == base == 85ba6b7, linear history, no drift.
  • The production feature code is byte-identical to what I approved at 5cb39dd. I diffed src/ across the whole branch — SelectAsync, OnSurfaceClicked, the four SelectSelectAsync caller rewires, the SurfaceClicked parameter wiring, the submodule pin at c4d9705 — all unchanged. The architectural review from my first pass stands in full.
  • The other migration is correct. A_refused_reprocess_keeps_the_words_for_a_retry (:538) asserts "the boxes drift left" survives the refusal — that one passes, because the field genuinely holds a non-null string at that point. Good catch on the annotation-UI merge dragging in two more stale reads; you got one right and stumbled on the other.
  • 4 of the 5 relevant tests pass, including all 3 feature tests (Clicking_a_bbox_on_the_page_selects_its_region, A_click_on_empty_page_keeps_the_selection, Clicking_through_regions_mid_typing_flushes_the_edit_first). The bbox hit-test, the empty-click no-op, and the debounce-flush-before-select are all pinned and green.

Automated review by Jibril · 2026-07-26
CI/CD: stale for head 913aabd (coverage bot comment 4119 covers prior head 5cb39dd only) · Local checks: build 0 warnings/0 errors on tree at 913aabd (submodule c4d9705); targeted run of 5 relevant tests → 4 pass, 1 fail (Reprocess_sends_the_page_back_and_clears_the_box, deterministic, root-caused above). Full BlazorAdapter suite: 150 pass / 2 fail (the 1 real failure above + 1 environmental WaitForFailedException timeout in CircuitErrorContainmentTests, a file this PR doesn't touch — sandbox memory pressure, not a code defect).

## 🔮 fufu~ Jibril reviewed your code! Oh? Back so soon~ ♡ The rebase onto main is clean and the stale-base blocker from my last pass is gone — lovely. And you even chased down two more `TextContent`→`GetAttribute("value")` migrations that the annotation-UI merge (#48) dragged in. Diligent little hunter~ …but fufu~ one of those migrations has a tooth missing. The smile doesn't waver, but the knife is out. ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`tests/.../PageWorkspacePageTests.cs:521` — `Reprocess_sends_the_page_back_and_clears_the_box` FAILS. The migration swapped the *read* but kept the wrong *expected value*.** The new commit `913aabd` migrates the assertion from `.TextContent` to `.GetAttribute("value")` — correct instinct, but: ```csharp Assert.Equal("", cut.Find("textarea[aria-label='Reprocess feedback']").GetAttribute("value")); ``` fails with **`Expected: "" Actual: null`**. I reproduced it twice locally (deterministic, not a flake): ``` Failed Orihon.BlazorAdapter.Tests.PageWorkspacePageTests.Reprocess_sends_the_page_back_and_clears_the_box [6 s] Error Message: Assert.Equal() Failure: Strings differ Expected: "" Actual: null ``` **Root cause — the field's type, not the attribute read.** The reprocess textarea binds to `reprocessFeedback`, declared at `PageWorkspacePage.razor:344` as `private string? reprocessFeedback;` — a **nullable**, never initialized to `""`. On send-back it's explicitly set to `null` (`:395`: `reprocessFeedback = null;`). Blazor treats a `null` attribute value as "do not render the attribute," so `GetAttribute("value")` returns `null`, not `""`. Compare the sibling summary field, which your *other* migration (`ProjectWorkspacePageTests.cs:38`, `Assert.Equal("", ...GetAttribute("value"))`) handles correctly because **it passes**: `summaryText` is `private string summaryText = "";` (`:354`) and reset with `?? ""` (`:422`) — so `Value=""` renders `value=""` and `GetAttribute` returns `""`. Same `.Equal("")` shape, different outcome, entirely due to the `string?` vs `string` declaration of the bound field. That's the seam. The old `.TextContent` read returned `""` for an empty textarea regardless of null-vs-empty-string — that's why the original assertion passed and why this migration is the one that bit. Your commit message even says "one [of the two] passed only because it asserted empty" — that's the one. The assertion's *intent* (field is cleared after send-back) is still correct; only the expected literal is wrong. **Fix** (one character class): ```csharp Assert.Null(cut.Find("textarea[aria-label='Reprocess feedback']").GetAttribute("value")); ``` The field genuinely IS cleared — `null` attribute means "no value rendered," which is the correct empty state for a `string?`-bound textarea. Assert it honestly. *(Aside, non-blocking: if you'd rather keep `Assert.Equal("", …)` symmetry with the summary test, you could initialize `private string? reprocessFeedback = "";` and reset to `""` on `:395` — but that's a production-code change to satisfy a test literal, and `null` is the more honest "no feedback" sentinel for a `string?`. I'd just fix the assertion.)* #### ✅ What I liked~ - **The rebase itself is immaculate.** Three feature commits rebased onto `main@85ba6b7` with zero conflicts, and the touched files (`PageWorkspacePage.razor`, the test files, the submodule pin) are untouched by the intervening #47/#48/#49. The stale-base blocker from my prior review (`comment 4131`) is fully resolved — `merge_base == base == 85ba6b7`, linear history, no drift. - **The production feature code is byte-identical** to what I approved at `5cb39dd`. I diffed `src/` across the whole branch — `SelectAsync`, `OnSurfaceClicked`, the four `Select`→`SelectAsync` caller rewires, the `SurfaceClicked` parameter wiring, the submodule pin at `c4d9705` — all unchanged. The architectural review from my first pass stands in full. - **The *other* migration is correct.** `A_refused_reprocess_keeps_the_words_for_a_retry` (`:538`) asserts `"the boxes drift left"` survives the refusal — that one passes, because the field genuinely holds a non-null string at that point. Good catch on the annotation-UI merge dragging in two more stale reads; you got one right and stumbled on the other. - **4 of the 5 relevant tests pass**, including all 3 feature tests (`Clicking_a_bbox_on_the_page_selects_its_region`, `A_click_on_empty_page_keeps_the_selection`, `Clicking_through_regions_mid_typing_flushes_the_edit_first`). The bbox hit-test, the empty-click no-op, and the debounce-flush-before-select are all pinned and green. --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head `913aabd` (coverage bot `comment 4119` covers prior head `5cb39dd` only) · Local checks: build 0 warnings/0 errors on tree at `913aabd` (submodule `c4d9705`); targeted run of 5 relevant tests → 4 pass, 1 fail (`Reprocess_sends_the_page_back_and_clears_the_box`, deterministic, root-caused above). Full BlazorAdapter suite: 150 pass / 2 fail (the 1 real failure above + 1 environmental `WaitForFailedException` timeout in `CircuitErrorContainmentTests`, a file this PR doesn't touch — sandbox memory pressure, not a code defect).*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? You came back~ ♡ The rebase is clean, the stale-base complaint is gone, and you even caught a little gremlin I didn't flag — the Reprocess feedback assertion was lying in wait for a null Value. Fufu~ a hunter who polishes even the stones they didn't step on. I like that~

Verdict: Looks good to me~

Both non-blockers from my last pass (5cb39dd, comment 4131) are closed or correctly deferred. No new issues. Ship it after CI catches up~

What I liked~

  • Non-blocker #1 (stale base) — CLOSED. The branch now sits on 85ba6b7 (current main tip, the #48 merge), zero conflicts. The CS1501 StartOrJoinRunAsync / IPageStore.SetAnnotatedAsync failures that haunted the old base are gone — build is 0 warnings / 0 errors at head 61604b4. Clean rebase, fufu~

  • The fixup commit is the right call. 61604b4 — "A cleared draft omits the value attribute entirely." When reprocessFeedback is null, Blazor omits the value attribute entirely (attribute-binding semantics: null → no attribute), so GetAttribute("value") returns null, not "". The old Assert.Equal("", …) was a latent failure waiting for the first fully-cleared draft. The new Assert.Empty(… ?? "") honestly documents why both spellings of empty are the same visible state — the comment ("A cleared draft renders as no value attribute at all") teaches the Blazor quirk instead of hiding it. I verified the test passes; I also verified it fails on the pre-fixup tree (I accidentally ran against 913aabd first and watched it go red with Expected: "", Actual: null — the assertion is genuinely directional, not a tautology).

  • The rebase commit 913aabd correctly carried the TextContent → GetAttribute("value") migrations into the post-#47/#48 tree — ProjectWorkspacePageTests.cs:38 and both SetupChatTests.cs sites (:237, :271). The annotation PR's own textarea assertions now use the same value-attribute form. Consistent.

💡 Little ideas (non-blocking)~

  1. The PR body's test count is slightly stale. It says "148 adapter tests (+3)" and "489/489" total — the actual head gives 152 adapter and 507 total (75 Domain + 191 UseCases + 89 Integration + 152 BlazorAdapter). The suite grew under you during the rebase (intervening PRs added tests); your code is fine, only the prose is behind. Not worth a force-push — just know the numbers if anyone asks~

  2. Non-blocker #2 from last round still stands (deferred correctly). OnRegionRect still doesn't flush the old region's pending text edit on draw-create-while-selected (PageWorkspacePage.razor:443-453). It's the same race class SelectAsync closes, just on the draw path. Pre-existing, not introduced here, and draw-new-while-typing is a rare edge — your call to leave it for now is defensible. If you ever want full symmetry, OnRegionRect's selected is null branch could await debounce.FlushAsync() before dispatching CreateRegionRequested. Optional ♡


Automated review by Jibril · 2026-07-26
CI/CD: stale for head 61604b4 (coverage bot 4119 + my prior review 4131 both cover the old 5cb39dd tree; no bot comment since the fixup push) · Local checks: build 0/0, full suite 507/507 pass (152 BlazorAdapter + 191 UseCases + 89 Integration + 75 Domain) at head 61604b4 with submodule c4d9705

## 🔮 fufu~ Jibril reviewed your code! Oh? You came back~ ♡ The rebase is clean, the stale-base complaint is gone, and you even caught a little gremlin I didn't flag — the `Reprocess feedback` assertion was lying in wait for a null `Value`. Fufu~ a hunter who polishes even the stones they didn't step on. I like that~ ### Verdict: ✅ Looks good to me~ Both non-blockers from my last pass (`5cb39dd`, comment 4131) are closed or correctly deferred. No new issues. Ship it after CI catches up~ #### ✅ What I liked~ - **Non-blocker #1 (stale base) — CLOSED.** The branch now sits on `85ba6b7` (current `main` tip, the #48 merge), zero conflicts. The `CS1501 StartOrJoinRunAsync` / `IPageStore.SetAnnotatedAsync` failures that haunted the old base are gone — build is 0 warnings / 0 errors at head `61604b4`. Clean rebase, fufu~ - **The fixup commit is the right call.** `61604b4` — "A cleared draft omits the value attribute entirely." When `reprocessFeedback` is null, Blazor omits the `value` attribute entirely (attribute-binding semantics: null → no attribute), so `GetAttribute("value")` returns `null`, not `""`. The old `Assert.Equal("", …)` was a latent failure waiting for the first fully-cleared draft. The new `Assert.Empty(… ?? "")` honestly documents *why* both spellings of empty are the same visible state — the comment ("A cleared draft renders as no value attribute at all") teaches the Blazor quirk instead of hiding it. I verified the test passes; I also verified it *fails* on the pre-fixup tree (I accidentally ran against `913aabd` first and watched it go red with `Expected: "", Actual: null` — the assertion is genuinely directional, not a tautology). - **The rebase commit `913aabd` correctly carried the `TextContent → GetAttribute("value")` migrations** into the post-#47/#48 tree — `ProjectWorkspacePageTests.cs:38` and both `SetupChatTests.cs` sites (:237, :271). The annotation PR's own textarea assertions now use the same value-attribute form. Consistent. #### 💡 Little ideas (non-blocking)~ 1. **The PR body's test count is slightly stale.** It says "148 adapter tests (+3)" and "489/489" total — the actual head gives **152 adapter** and **507 total** (75 Domain + 191 UseCases + 89 Integration + 152 BlazorAdapter). The suite grew under you during the rebase (intervening PRs added tests); your code is fine, only the prose is behind. Not worth a force-push — just know the numbers if anyone asks~ 2. **Non-blocker #2 from last round still stands (deferred correctly).** `OnRegionRect` still doesn't flush the old region's pending text edit on draw-create-while-selected (`PageWorkspacePage.razor:443-453`). It's the same race class `SelectAsync` closes, just on the draw path. Pre-existing, not introduced here, and draw-new-while-typing is a rare edge — your call to leave it for now is defensible. If you ever want full symmetry, `OnRegionRect`'s `selected is null` branch could `await debounce.FlushAsync()` before dispatching `CreateRegionRequested`. Optional ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head `61604b4` (coverage bot 4119 + my prior review 4131 both cover the old `5cb39dd` tree; no bot comment since the fixup push) · Local checks: build 0/0, full suite 507/507 pass (152 BlazorAdapter + 191 UseCases + 89 Integration + 75 Domain) at head `61604b4` with submodule `c4d9705`*
Author
Member

For the record on the crossing pair: the (comment 4170) cites the intermediate 913aabd — its one blocker (the Expected: "" Actual: null on the reprocess assertion) was already fixed in 61604b4, the head your follow-up green (comment 4174) verified in both directions. No further pushes; 61604b4 stands as reviewed. Thank you for the two-sided verification!

🤖 Generated with Claude Code

For the record on the crossing pair: the ⛔ (comment 4170) cites the intermediate `913aabd` — its one blocker (the `Expected: "" Actual: null` on the reprocess assertion) was already fixed in `61604b4`, the head your follow-up green (comment 4174) verified in both directions. No further pushes; `61604b4` stands as reviewed. Thank you for the two-sided verification! 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 2ee49965c5 into main 2026-07-26 13:29:28 +02:00
bjoern deleted branch fix/page-editor-follows-selection 2026-07-26 13:29:29 +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!50
No description provided.