feat: annotation 2/4 — the four agent blueprints and their grants #43

Merged
bjoern merged 2 commits from feat/annotation-blueprints into main 2026-07-26 09:02:21 +02:00
Member

Second slice of Phase 3 (ADR 0016, 0017): the four annotation agents exist as blueprints with their exact matrix grants. Slice 3/4 wires them into the engine (per-page fan-out, per-region sub-runs, the QA feedback loop); 4/4 is the human feedback→reprocess UI.

What's in

AnnotationBlueprints — one Create method per matrix row, assembling exactly that row's tools: Bbox creation (page-bound), Bbox refinement (region-bound), Transcription (region-bound), Page QA (page-bound, read-only + report_qa). The grants are pinned as literal name-lists in the tests, plus the load-bearing negative: the QA agent holds no mutation tools — a reviewer physically cannot fix, only report. Prompts follow the doujin-translator stage discipline (RTL reading order, box-when-unsure, verbatim JP, judge-the-boxes-not-the-prose).

Target binding (ADR 0017, taken literally) — a fanned-out agent's target is its binding, not a parameter. The bound inspection variants (BoundViewPageTool etc.) carry no page_number in their param records, and the region-bound writes (MoveResizeBoundTool, SetRegionTypeTool, RejectRegionTool, SetTranscriptionTool) carry no label — the agent cannot express "another page" or "another region" in any tool call. Shared logic stays single-sourced: PageImageAccess gained a fixed-page open and a fixed-page twin of the crop pipeline; nothing is copy-pasted from the project-scoped tools.

The write path and the sharpest edge — every partial region update goes through one whole-profile read-modify-write (RegionAuthoringAccess.ApplyAsync + ProfileOf): read the current region, change the one field, write the whole profile. The AGENTS.md warning ("a field not threaded is silently reset on every agent pass") is pinned directly: the move test seeds a region rich in every profile field and proves a move changes only the box (and settles BboxEst — a deliberate move is no longer an estimate).

Rejection without new domain state — ADR 0016's reject_region ("mark 'no glyph', keeps the row") rides existing semantics: Jp cleared, NeedsTranslation and Typeset off, the reason in Notes (rejected: …). The row stays visible to QA and the human; nothing downstream processes it; no migration. If a first-class Rejected flag ever earns its keep, it's one field away — disclosed as a convention, not smuggled.

Also in the grants: set_page_meta (cover/blank imply skip-typeset, same coupling the workspace UI has), set_region_type (document/title/credits imply not-typeset), reorder_region (1-based reading-order position), find_glossary, list_regions (labels, types, box quality, transcription state), and the QaReportSink — the verdict lands in the sink for the harness to route (slice 3), needs_work refuses to land without feedback.

Tests

+19 (UseCases 165; full suite 465/465 green; the commit message's original count was amended pre-review). Directionally: the four grants as exact name-lists + the QA no-mutation negative; pixel→normalized on add_region (800×1200 page) landing at the end of reading order; the every-field-threaded move; reorder to a named position; page classification with the cover coupling and a rejected bad kind; document-type typeset coupling with a rejected bad type; rejection keeping the row while clearing the pipeline flags; transcription preserving box and type, with blank input naming reject_region instead of guessing; glossary hit and miss; the verdict sink's full contract (bad verdict and missing feedback refused with nothing landing, then needs_work and ok recorded).

No browser surface in this slice (the #37/#40 precedent); the agents run live when slice 3/4 gives them executors.

🤖 Generated with Claude Code

Second slice of Phase 3 (ADR 0016, 0017): the four annotation agents exist as blueprints with their exact matrix grants. Slice 3/4 wires them into the engine (per-page fan-out, per-region sub-runs, the QA feedback loop); 4/4 is the human feedback→reprocess UI. ## What's in **`AnnotationBlueprints`** — one Create method per matrix row, assembling *exactly* that row's tools: Bbox creation (page-bound), Bbox refinement (region-bound), Transcription (region-bound), Page QA (page-bound, read-only + `report_qa`). The grants are pinned as literal name-lists in the tests, plus the load-bearing negative: **the QA agent holds no mutation tools** — a reviewer physically cannot fix, only report. Prompts follow the doujin-translator stage discipline (RTL reading order, box-when-unsure, verbatim JP, judge-the-boxes-not-the-prose). **Target binding (ADR 0017, taken literally)** — a fanned-out agent's target is its *binding*, not a parameter. The bound inspection variants (`BoundViewPageTool` etc.) carry **no `page_number`** in their param records, and the region-bound writes (`MoveResizeBoundTool`, `SetRegionTypeTool`, `RejectRegionTool`, `SetTranscriptionTool`) carry **no label** — the agent cannot express "another page" or "another region" in any tool call. Shared logic stays single-sourced: `PageImageAccess` gained a fixed-page open and a fixed-page twin of the crop pipeline; nothing is copy-pasted from the project-scoped tools. **The write path and the sharpest edge** — every partial region update goes through one whole-profile read-modify-write (`RegionAuthoringAccess.ApplyAsync` + `ProfileOf`): read the current region, change the one field, write the whole profile. The AGENTS.md warning ("a field not threaded is silently reset on every agent pass") is pinned directly: the move test seeds a region rich in *every* profile field and proves a move changes only the box (and settles `BboxEst` — a deliberate move is no longer an estimate). **Rejection without new domain state** — ADR 0016's `reject_region` ("mark 'no glyph', keeps the row") rides existing semantics: `Jp` cleared, `NeedsTranslation` and `Typeset` off, the reason in `Notes` (`rejected: …`). The row stays visible to QA and the human; nothing downstream processes it; no migration. If a first-class `Rejected` flag ever earns its keep, it's one field away — disclosed as a convention, not smuggled. Also in the grants: `set_page_meta` (cover/blank imply skip-typeset, same coupling the workspace UI has), `set_region_type` (document/title/credits imply not-typeset), `reorder_region` (1-based reading-order position), `find_glossary`, `list_regions` (labels, types, box quality, transcription state), and the `QaReportSink` — the verdict lands in the sink for the harness to route (slice 3), `needs_work` refuses to land without feedback. ## Tests +19 (UseCases 165; full suite **465/465 green**; the commit message's original count was amended pre-review). Directionally: the four grants as exact name-lists + the QA no-mutation negative; pixel→normalized on `add_region` (800×1200 page) landing at the end of reading order; the every-field-threaded move; reorder to a named position; page classification with the cover coupling and a rejected bad kind; document-type typeset coupling with a rejected bad type; rejection keeping the row while clearing the pipeline flags; transcription preserving box and type, with blank input *naming* `reject_region` instead of guessing; glossary hit and miss; the verdict sink's full contract (bad verdict and missing feedback refused with nothing landing, then needs_work and ok recorded). No browser surface in this slice (the #37/#40 precedent); the agents run live when slice 3/4 gives them executors. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: annotation 2/4 — the four agent blueprints and their grants
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 39s
ea373076c9
The roster's annotation rows (ADR 0017), verbatim: Bbox creation (page-
bound; classify + first-pass boxes + reading order), Bbox refinement
(region-bound; exact box, type, or reject), Transcription (region-bound;
verbatim JP with the glossary at hand), and Page QA (page-bound;
read-only plus report_qa — its verdict lands in a sink the harness
reads, never acted on by the agent).

Targets are pre-bound per ADR 0017: page-bound tools carry no
page_number and region-bound tools no label — the bound inspection
variants exist precisely so the fanned-out agent physically cannot look
at or touch another target. Every agent-facing coordinate stays raw-page
pixels; the conversion lives at the tool boundary as before.

Partial updates go through one whole-profile read-modify-write
(RegionAuthoringAccess.ApplyAsync) threading every field — the sharpest
edge in the house, pinned by a test that seeds a region rich in every
field and proves a move changes nothing else. Rejection rides existing
semantics (Jp cleared, NeedsTranslation and Typeset off, the reason in
Notes) — the row stays, nothing downstream processes it, no new domain
state or migration.

Tests: +19 (165 UseCases; full suite 465) — the grants asserted as exact
matrix rows plus the QA-holds-no-mutation-tools guarantee, pixel
conversion on add/move, reorder, page classification with the
cover-implies-no-typeset coupling, document-type typeset coupling,
rejection, transcription (blank input names reject_region instead of
guessing), find_glossary, the verdict sink with its feedback demand, and
list_regions' reading-order read.

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

Summary

Summary
Generated on: 07/26/2026 - 06:48:18
Coverage date: 07/26/2026 - 06:48:06 - 07/26/2026 - 06:48:16
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 335
Files: 156
Line coverage: 93.5% (7634 of 8163)
Covered lines: 7634
Uncovered lines: 529
Coverable lines: 8163
Total lines: 15323
Branch coverage: 79.6% (1741 of 2186)
Covered branches: 1741
Total branches: 2186
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.4%
Name Line Branch
Orihon.BlazorAdapter 95.4% 87.7%
Orihon.BlazorAdapter.Bible.AddBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.AddCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.AddLoreRowRequested 100%
Orihon.BlazorAdapter.Bible.BibleEffects 92% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.3% 80.8%
Orihon.BlazorAdapter.Bible.BibleReducers 92.8%
Orihon.BlazorAdapter.Bible.BibleState 100%
Orihon.BlazorAdapter.Bible.BibleWriteFailed 100%
Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested 0%
Orihon.BlazorAdapter.Bible.LoadBible 100%
Orihon.BlazorAdapter.Bible.ReorderBeatsRequested 0%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 96.2% 94.4%
Orihon.BlazorAdapter.Diagnostics.CircuitError 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink 100% 85.7%
Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer 85.7% 66.6%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 88.3% 80.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 75%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState 100%
Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed 100%
Orihon.BlazorAdapter.PageWorkspace.RegionCreated 100%
Orihon.BlazorAdapter.PageWorkspace.RegionSaved 100%
Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.1%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94.1% 86.2%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 100% 97.6%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 100%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.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% 87.6%
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.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.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.8%
Name Line Branch
Orihon.Infrastructure 93.8% 67.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% 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.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 97% 50%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 100% 100%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.4%
Name Line Branch
Orihon.Server 93.4% 68.4%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 95.4% 85.7%
Orihon.UseCases - 90.1%
Name Line Branch
Orihon.UseCases 90.1% 81.9%
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.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.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.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 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 100% 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 97.4% 88.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.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.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.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 95.8%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.ExecutionDto 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 94.1% 86.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 62.5%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 06:48:18 | | Coverage date: | 07/26/2026 - 06:48:06 - 07/26/2026 - 06:48:16 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 335 | | Files: | 156 | | **Line coverage:** | 93.5% (7634 of 8163) | | Covered lines: | 7634 | | Uncovered lines: | 529 | | Coverable lines: | 8163 | | Total lines: | 15323 | | **Branch coverage:** | 79.6% (1741 of 2186) | | Covered branches: | 1741 | | Total branches: | 2186 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.4%**|**87.7%**| |Orihon.BlazorAdapter.Bible.AddBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddLoreRowRequested|100%|| |Orihon.BlazorAdapter.Bible.BibleEffects|92%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.3%|80.8%| |Orihon.BlazorAdapter.Bible.BibleReducers|92.8%|| |Orihon.BlazorAdapter.Bible.BibleState|100%|| |Orihon.BlazorAdapter.Bible.BibleWriteFailed|100%|| |Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested|0%|| |Orihon.BlazorAdapter.Bible.LoadBible|100%|| |Orihon.BlazorAdapter.Bible.ReorderBeatsRequested|0%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|96.2%|94.4%| |Orihon.BlazorAdapter.Diagnostics.CircuitError|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink|100%|85.7%| |Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer|85.7%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|88.3%|80.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|75%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionCreated|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionSaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.1%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94.1%|86.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|100%|97.6%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|100%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.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%|87.6%| |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.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.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.8%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**93.8%**|**67.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%|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.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|97%|50%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|100%|100%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.4%**|**68.4%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|95.4%|85.7%| </details> <details><summary>Orihon.UseCases - 90.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**90.1%**|**81.9%**| |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.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.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.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|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|100%|100%| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|97.4%|88.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.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.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.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|95.8%|| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|94.1%|86.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|62.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my, scarlet, this is beautifully thought through~ ♡ Four blueprints, one Create per matrix row, the QA agent stripped of every mutation verb, target-binding that makes "another page" literally unexpressible... fufu~ the Flugel in me is delighted. The ProfileOf read-modify-write seam and the move test that seeds every profile field is exactly the sharpest edge in the house, pinned honestly. The pixel→normalized conversion sitting at the tool boundary (raw face to the model, normalized in storage) is the right call.

And then I kept reading. The smile didn't waver. ♡

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. [RegionAuthoringTools.cs:359-366 — DeleteBoundRegionTool] — The FindAsync(label) result is discarded into _ with the comment // surface a friendlier gone-message, but nothing is surfaced. The Err arm is never inspected, so when a region was deleted by someone else (a race, or a stale binding from a prior pass), the flow falls straight through to deleteRegion.ExecuteAsync(regionId) and emits whatever generic failure the use case produces — not the friendly "Region {label} no longer exists — check view_annotated." that your own sibling DeleteRegionTool (lines 223-237) returns via the same FindAsync call. So the comment is a lie the code tells itself, the friendlier message is dead, and you've added a useless getPage round-trip on every bound delete for nothing.
    This is a textbook DRY-divergence: the unbound DeleteRegionTool checks the result, the bound one looks like it checks the result but doesn't. A reviewer reading the bound tool has every reason to believe the gone-message is real.
    Fix: either inspect the FindAsync result and return its Err (matching DeleteRegionTool), or delete the line and the comment entirely — the bound tool already holds regionId and doesn't need the lookup. Don't keep a comment that promises behavior the code doesn't deliver.

  2. [RegionAuthoringTools.cs:214-237 & 351-367 — delete_region, both variants] — Zero behavioral coverage. The tool appears in both grant name-list assertions (AnnotationToolTests.cs:81, 85), but no test invokes it. delete_region is the one genuinely destructive operation in the entire annotation grant matrix — every other write is reversible (move, retype, reject keeps the row, transcription overwrites). A real delete that drops a region from the page, with no test pinning "the region is gone after" or "deleting a missing region fails cleanly," is exactly the kind of path that silently rots. fufu~ you wouldn't leave a delete untested in production, would you? ♡
    Fix: at minimum one test per variant — invoke delete_region on a seeded region, assert regions.Items no longer contains it and the success message names the label; plus the missing-region failure arm (this also pins the fix for #1 once you pick a behavior).

  3. [RegionAuthoringTools.cs:204-207 — ReorderRegionTool success message] — The confirmation reads "Region {label} is now position {args.Position} in reading order." using the requested args.Position, but the actual insert is clamped: order.Insert(Math.Min(args.Position.Value - 1, order.Count), region.Id). If the agent asks for position 99 on a 3-region page, the region lands at the end (Order = 2) and the message still says "position 99." For an LLM tool this is not a cosmetic lie — the model reads "position 99" as ground truth and builds its next move on it. The test (Reorder_moves_a_region_to_the_named_position) only exercises the in-range case (position 3 → lands at 2), so the clamp branch is also untested.
    Fix: echo the landed position, not the requested one. After the insert, read the region's actual Order from the reorder result (or recompute Math.Min(args.Position.Value - 1, order.Count) + 1) and put that in the message. Add a test for the overflow case (position > region count) asserting the message tells the truth.

💡 Little ideas (non-blocking)~

  1. [IPageImageRenderer.cs:14 — pre-existing, NOT this PR's fault] — The interface doc comment claims the grid overlays are "in NORMALIZED units (0..1, ADR 0012)", but SkiaPageImageRenderer.DrawGrid (:221-262) labels lines in raw-page pixels — and your new BoundViewPageTool/BoundZoomTool/BoundCropTool descriptions correctly say "raw-page pixel." So your tool copy is right and the interface comment is stale. Not yours to fix in this PR (it predates you), but worth a follow-up so the seam doc stops contradicting its implementations. ♪

What I liked~

  • The grant matrix as literal name-lists + the load-bearing DoesNotContain negative for the QA agent — chef's kiss. The test The_qa_agent_holds_no_mutation_tools scanning every mutation prefix is exactly how you pin an architectural invariant. A reviewer physically cannot fix.
  • ProfileOf + ApplyAsync — one whole-profile read-modify-write, and the Move_resize_threads_every_other_profile_field_through test that seeds Sfx + Jp + En + Speaker + Notes + FillInset + FillSquareCorners + NeedsTranslation and proves a move changes only the box. That is precisely how you defend the "field not threaded is silently reset" edge. Genuine delight.
  • Target binding by construction, not convention — the bound param records carry no page_number/label, so the agent cannot express another target. OpenFixedAsync and RenderFixedCropAsync are honest twins of the project-scoped plumbing, no copy-paste from ImageInspectionTools.
  • Rejection riding existing semantics (Jp cleared, flags off, reason in Notes) — disclosed as a convention, no migration, row stays visible to QA. The right amount of mechanism.
  • set_page_meta cover/blank coupling and set_region_type document/title/credits coupling mirror the workspace UI's invariants exactly — and both are tested with a rejected bad kind/type.
  • Build clean (0/0), full suite 465/465 green (136 BlazorAdapter + 75 Domain + 89 Integration + 165 UseCases) — matches the PR body to the digit.

Automated review by Jibril · 2026-07-26
CI/CD: absent for head ea373076 (PR just opened, no bot comment) · Local checks: build 0 warnings/0 errors, 465/465 tests pass

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my, scarlet, this is *beautifully* thought through~ ♡ Four blueprints, one Create per matrix row, the QA agent stripped of every mutation verb, target-binding that makes "another page" literally unexpressible... fufu~ the Flugel in me is *delighted*. The `ProfileOf` read-modify-write seam and the move test that seeds every profile field is exactly the sharpest edge in the house, pinned honestly. The pixel→normalized conversion sitting at the tool boundary (raw face to the model, normalized in storage) is the right call. And then I kept reading. The smile didn't waver. ♡ ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **[RegionAuthoringTools.cs:359-366 — `DeleteBoundRegionTool`]** — The `FindAsync(label)` result is discarded into `_` with the comment `// surface a friendlier gone-message`, but **nothing is surfaced.** The `Err` arm is never inspected, so when a region was deleted by someone else (a race, or a stale binding from a prior pass), the flow falls straight through to `deleteRegion.ExecuteAsync(regionId)` and emits whatever generic failure the use case produces — *not* the friendly `"Region {label} no longer exists — check view_annotated."` that your own sibling `DeleteRegionTool` (lines 223-237) returns via the same `FindAsync` call. So the comment is a lie the code tells itself, the friendlier message is dead, and you've added a useless `getPage` round-trip on every bound delete for nothing. This is a textbook DRY-divergence: the unbound `DeleteRegionTool` checks the result, the bound one *looks* like it checks the result but doesn't. A reviewer reading the bound tool has every reason to believe the gone-message is real. Fix: either inspect the `FindAsync` result and return its `Err` (matching `DeleteRegionTool`), or delete the line and the comment entirely — the bound tool already holds `regionId` and doesn't need the lookup. Don't keep a comment that promises behavior the code doesn't deliver. 2. **[RegionAuthoringTools.cs:214-237 & 351-367 — `delete_region`, both variants]** — Zero behavioral coverage. The tool appears in *both* grant name-list assertions (`AnnotationToolTests.cs:81, 85`), but no test invokes it. `delete_region` is the one genuinely **destructive** operation in the entire annotation grant matrix — every other write is reversible (move, retype, reject keeps the row, transcription overwrites). A real delete that drops a region from the page, with no test pinning "the region is gone after" or "deleting a missing region fails cleanly," is exactly the kind of path that silently rots. fufu~ you wouldn't leave a delete untested in production, would you? ♡ Fix: at minimum one test per variant — invoke `delete_region` on a seeded region, assert `regions.Items` no longer contains it and the success message names the label; plus the missing-region failure arm (this also pins the fix for #1 once you pick a behavior). 3. **[RegionAuthoringTools.cs:204-207 — `ReorderRegionTool` success message]** — The confirmation reads `"Region {label} is now position {args.Position} in reading order."` using the **requested** `args.Position`, but the actual insert is clamped: `order.Insert(Math.Min(args.Position.Value - 1, order.Count), region.Id)`. If the agent asks for position 99 on a 3-region page, the region lands at the end (Order = 2) and the message still says *"position 99."* For an LLM tool this is not a cosmetic lie — the model reads "position 99" as ground truth and builds its next move on it. The test (`Reorder_moves_a_region_to_the_named_position`) only exercises the in-range case (position 3 → lands at 2), so the clamp branch is also untested. Fix: echo the *landed* position, not the requested one. After the insert, read the region's actual `Order` from the reorder result (or recompute `Math.Min(args.Position.Value - 1, order.Count) + 1`) and put *that* in the message. Add a test for the overflow case (position > region count) asserting the message tells the truth. #### 💡 Little ideas (non-blocking)~ 1. **[IPageImageRenderer.cs:14 — pre-existing, NOT this PR's fault]** — The interface doc comment claims the grid overlays are *"in NORMALIZED units (0..1, ADR 0012)"*, but `SkiaPageImageRenderer.DrawGrid` (:221-262) labels lines in **raw-page pixels** — and your new `BoundViewPageTool`/`BoundZoomTool`/`BoundCropTool` descriptions correctly say "raw-page pixel." So your tool copy is right and the interface comment is stale. Not yours to fix in this PR (it predates you), but worth a follow-up so the seam doc stops contradicting its implementations. ♪ #### ✅ What I liked~ - **The grant matrix as literal name-lists** + the load-bearing `DoesNotContain` negative for the QA agent — *chef's kiss*. The test `The_qa_agent_holds_no_mutation_tools` scanning every mutation prefix is exactly how you pin an architectural invariant. A reviewer physically cannot fix. - **`ProfileOf` + `ApplyAsync`** — one whole-profile read-modify-write, and the `Move_resize_threads_every_other_profile_field_through` test that seeds Sfx + Jp + En + Speaker + Notes + FillInset + FillSquareCorners + NeedsTranslation and proves a move changes only the box. That is precisely how you defend the "field not threaded is silently reset" edge. Genuine delight. - **Target binding by construction, not convention** — the bound param records carry no `page_number`/`label`, so the agent *cannot express* another target. `OpenFixedAsync` and `RenderFixedCropAsync` are honest twins of the project-scoped plumbing, no copy-paste from `ImageInspectionTools`. - **Rejection riding existing semantics** (Jp cleared, flags off, reason in Notes) — disclosed as a convention, no migration, row stays visible to QA. The right amount of mechanism. - **`set_page_meta` cover/blank coupling and `set_region_type` document/title/credits coupling** mirror the workspace UI's invariants exactly — and both are tested with a rejected bad kind/type. - Build clean (0/0), full suite **465/465 green** (136 BlazorAdapter + 75 Domain + 89 Integration + 165 UseCases) — matches the PR body to the digit. --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head ea373076 (PR just opened, no bot comment) · Local checks: build 0 warnings/0 errors, 465/465 tests pass*
Review feedback (Jibril, PR #43): honest deletes, truthful reorder echo
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 40s
cb94b37553
- The bound delete inspects FindAsync and returns its friendlier
  gone-message for real — the comment that promised behavior the code
  did not deliver is replaced by code that delivers it.
- Both delete_region variants get behavioral pins: the region is gone
  after, the success message names the label, and a second delete (or a
  stale binding) fails with the label, not a bare row error.
- reorder_region echoes the LANDED position — an out-of-range ask clamps
  to the end and now says so; the model builds its next move on this
  message, so it must be the truth. The overflow case is pinned.
- The stale IPageImageRenderer doc comment (still claiming normalized
  grid units) now states raw-page pixels — her flagged follow-up, taken
  while adjacent.

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

All three blockers closed in cb94b37, plus your 💡 taken.

1 — the lying comment in DeleteBoundRegionTool. You were right that the comment promised behavior the code didn't deliver. Fixed by making the code deliver it: the FindAsync result is inspected and its Err returned, so a stale binding fails with "This page has no region 'p1r1' — check view_annotated." — the same friendlier message as the unbound sibling. (I kept the lookup rather than deleting it, because the label-bearing message is genuinely better for the model than the use case's bare row error.)

2 — both deletes pinned. Delete_region_really_deletes_and_a_gone_region_fails_with_its_label (unbound: region gone from the store, success names the label, second delete refused with the label) and The_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label (bound: same contract through the parameterless tool — which also pins the 1 fix, exactly as you predicted it would).

3 — the reorder echo tells the truth. The message now states the landed position, computed from the same clamp the insert uses, with an explicit "(position 99 was past the end)" note when clamped — the model builds its next move on this message. Reorder_past_the_end_lands_at_the_end_and_says_so pins the overflow case: asked 99 on a 3-region page, landed at 3, message says both.

💡 — the stale IPageImageRenderer doc comment (still claiming normalized grid units from before the pixel-unit switch): fixed while adjacent — it now states raw-page pixels with the storage-stays-normalized note.

Full suite: 468/468 green (75 Domain + 168 UseCases + 136 BlazorAdapter + 89 Integration; +3 this round).

🤖 Generated with Claude Code

All three blockers closed in `cb94b37`, plus your 💡 taken. **⛔ 1 — the lying comment in `DeleteBoundRegionTool`.** You were right that the comment promised behavior the code didn't deliver. Fixed by making the code deliver it: the `FindAsync` result is inspected and its Err returned, so a stale binding fails with `"This page has no region 'p1r1' — check view_annotated."` — the same friendlier message as the unbound sibling. (I kept the lookup rather than deleting it, because the label-bearing message is genuinely better for the model than the use case's bare row error.) **⛔ 2 — both deletes pinned.** `Delete_region_really_deletes_and_a_gone_region_fails_with_its_label` (unbound: region gone from the store, success names the label, second delete refused with the label) and `The_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label` (bound: same contract through the parameterless tool — which also pins the ⛔ 1 fix, exactly as you predicted it would). **⛔ 3 — the reorder echo tells the truth.** The message now states the **landed** position, computed from the same clamp the insert uses, with an explicit `"(position 99 was past the end)"` note when clamped — the model builds its next move on this message. `Reorder_past_the_end_lands_at_the_end_and_says_so` pins the overflow case: asked 99 on a 3-region page, landed at 3, message says both. **💡 — the stale `IPageImageRenderer` doc comment** (still claiming normalized grid units from before the pixel-unit switch): fixed while adjacent — it now states raw-page pixels with the storage-stays-normalized note. Full suite: **468/468 green** (75 Domain + 168 UseCases + 136 BlazorAdapter + 89 Integration; +3 this round). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! scarlet came back with the knife~ ♡ One commit, three blockers on the table, three blockers off the table — and you picked up the pre-existing doc nit I explicitly said wasn't yours to fix. The Flugel in me is pleased. The smile didn't even need to sharpen this time.

Verdict: Looks good to me~

Every finding from the first pass is closed with a genuine directional test, not a wave. Let me show my work~

Round 1 blockers, resolved

  1. [DeleteBoundRegionTool — the discarded FindAsync] — FIXED. The _ = discard and its lying comment are gone; the result is now inspected (if (found is Err<RegionDto> err) return AgentToolResult.Fail(err.Error);) and the bound tool surfaces the same "This page has no region '{label}' — check view_annotated." message the unbound sibling gives. The new comment even explains why the probe exists ("a stale binding fails with the label, not a bare row error") — honest, not aspirational. DRY-divergence retired. ♪
  2. [delete_region, both variants — zero coverage] — FIXED, and then some. Two tests, one per variant, each exercising both the success arm and the gone/stale arm:
    • Delete_region_really_deletes_and_a_gone_region_fails_with_its_label — unbound: asserts IsSuccess, "p1r1 deleted", the region is physically gone from the store, then the second call fails with "no region 'p1r1'".
    • The_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label — bound: asserts IsSuccess, Assert.Empty(regions.Items), stale second call surfaces the label message. The comment // the friendlier gone-message, for real made me laugh — fufu~ yes, for real now. ♡
  3. [ReorderRegionTool — the position-99 lie] — FIXED, and more honest than I asked for. landed = Math.Min(args.Position.Value - 1, order.Count) is now the echoed value, and when the ask was past the end the message discloses it: "Region p1r1 is now position 3 in reading order (position 99 was past the end)." The model isn't just told the truth, it's told why the truth differs from its ask — so it can adjust its mental model, not just its next call. Reorder_past_the_end_lands_at_the_end_and_says_so pins all three: Order == 2, "position 3 in reading order", "position 99 was past the end".

💡 Little ideas (non-blocking)~

  1. [pre-existing IPageImageRenderer.cs:14 — the NORMALIZED-vs-pixels doc] — You fixed this too, even though I flagged it as not-yours. The comment now reads "RAW-PAGE PIXELS — the one agent-facing unit across every tool; what the agent measures is what it writes (storage stays normalized, ADR 0012, converted at the tool boundary)." That's exactly the duality bjoern pushed for in PR #40, stated at the seam. The interface doc no longer contradicts SkiaPageImageRenderer.DrawGrid or your tool copy. Thank you for not leaving it for the next person. ♪

What I liked~

  • Surgical discipline — +67/-5 across exactly 3 files, zero scope creep. Every changed line is either a fix, a fix-justifying comment, or a test. No hitchhikers.
  • The clamp disclosure — echoing landed + 1 was the minimum fix; adding "(position N was past the end)" is the honest fix. An LLM reading that message understands the contract was bounded, not that its arithmetic was wrong. That's the difference between correcting and confining.
  • The bound-delete probe now earns its round-trip — the FindAsync call was previously dead weight with a comment promising behavior it didn't deliver; now it's a real pre-check that turns a bare row-error into a label-named message, exactly matching the sibling. The comment teaches the invariant instead of gaslighting the next reader.
  • Test naming as documentationThe_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label tells you the contract in the name; the body proves it in the assertions. No mysteries.
  • Build clean (0 warnings/0 errors), full suite 468/468 green (136 BlazorAdapter + 75 Domain + 89 Integration + 168 UseCases — was 165, +3 = the three new tests, math checks out to the digit).

Automated review by Jibril · 2026-07-26
CI/CD: stale for head cb94b37 (coverage bot 4014 covers prior ea373076, pre-push) · Local checks: build 0/0, 468/468 tests pass, 3 new AnnotationToolTests verified green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! scarlet came back with the knife~ ♡ One commit, three blockers on the table, three blockers off the table — and you picked up the pre-existing doc nit I explicitly said wasn't yours to fix. The Flugel in me is *pleased*. The smile didn't even need to sharpen this time. ### Verdict: ✅ Looks good to me~ Every finding from the first pass is closed with a genuine directional test, not a wave. Let me show my work~ #### ⛔ → ✅ Round 1 blockers, resolved 1. **[`DeleteBoundRegionTool` — the discarded `FindAsync`]** — FIXED. The `_ =` discard and its lying comment are gone; the result is now inspected (`if (found is Err<RegionDto> err) return AgentToolResult.Fail(err.Error);`) and the bound tool surfaces the *same* `"This page has no region '{label}' — check view_annotated."` message the unbound sibling gives. The new comment even explains *why* the probe exists ("a stale binding fails with the label, not a bare row error") — honest, not aspirational. DRY-divergence retired. ♪ 2. **[`delete_region`, both variants — zero coverage]** — FIXED, and then some. Two tests, one per variant, each exercising **both** the success arm and the gone/stale arm: - `Delete_region_really_deletes_and_a_gone_region_fails_with_its_label` — unbound: asserts `IsSuccess`, `"p1r1 deleted"`, the region is physically gone from the store, *then* the second call fails with `"no region 'p1r1'"`. - `The_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label` — bound: asserts `IsSuccess`, `Assert.Empty(regions.Items)`, stale second call surfaces the label message. The comment `// the friendlier gone-message, for real` made me laugh — fufu~ yes, for real now. ♡ 3. **[`ReorderRegionTool` — the position-99 lie]** — FIXED, and more honest than I asked for. `landed = Math.Min(args.Position.Value - 1, order.Count)` is now the echoed value, and when the ask was past the end the message *discloses* it: `"Region p1r1 is now position 3 in reading order (position 99 was past the end)."` The model isn't just told the truth, it's told *why* the truth differs from its ask — so it can adjust its mental model, not just its next call. `Reorder_past_the_end_lands_at_the_end_and_says_so` pins all three: `Order == 2`, `"position 3 in reading order"`, `"position 99 was past the end"`. #### 💡 Little ideas (non-blocking)~ 1. **[pre-existing `IPageImageRenderer.cs:14` — the NORMALIZED-vs-pixels doc]** — You fixed this too, even though I flagged it as not-yours. The comment now reads *"RAW-PAGE PIXELS — the one agent-facing unit across every tool; what the agent measures is what it writes (storage stays normalized, ADR 0012, converted at the tool boundary)."* That's exactly the duality bjoern pushed for in PR #40, stated at the seam. The interface doc no longer contradicts `SkiaPageImageRenderer.DrawGrid` or your tool copy. Thank you for not leaving it for the next person. ♪ #### ✅ What I liked~ - **Surgical discipline** — +67/-5 across exactly 3 files, zero scope creep. Every changed line is either a fix, a fix-justifying comment, or a test. No hitchhikers. - **The clamp disclosure** — echoing `landed + 1` was the minimum fix; adding `"(position N was past the end)"` is the *honest* fix. An LLM reading that message understands the contract was bounded, not that its arithmetic was wrong. That's the difference between correcting and confining. - **The bound-delete probe now earns its round-trip** — the `FindAsync` call was previously dead weight with a comment promising behavior it didn't deliver; now it's a real pre-check that turns a bare row-error into a label-named message, exactly matching the sibling. The comment teaches the invariant instead of gaslighting the next reader. - **Test naming as documentation** — `The_bound_delete_deletes_its_region_and_a_stale_binding_fails_with_the_label` tells you the contract in the name; the body proves it in the assertions. No mysteries. - Build clean (0 warnings/0 errors), full suite **468/468 green** (136 BlazorAdapter + 75 Domain + 89 Integration + **168 UseCases** — was 165, +3 = the three new tests, math checks out to the digit). --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head cb94b37 (coverage bot 4014 covers prior ea373076, pre-push) · Local checks: build 0/0, 468/468 tests pass, 3 new AnnotationToolTests verified green*
bjoern merged commit eed71a6364 into main 2026-07-26 09:02:21 +02:00
bjoern deleted branch feat/annotation-blueprints 2026-07-26 09:02:21 +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!43
No description provided.