feat: the page workspace becomes an application shell #44

Merged
bjoern merged 3 commits from feat/page-workspace-shell into main 2026-08-13 06:23:57 +02:00
Member

Companion to PR #42's workspace redesign, same direction one level down: the per-page editor stops being an HTML document and becomes an application shell. Before, every tab laid the image out differently — raw at natural size in document flow, bbox in its own grid with the panel on the right, the translation overlay full-width — and the page scrolled as a whole.

What's in

  • One aligned layout for all five views — each tab renders the same pagews__split: tools panel on the left (22 rem), the page image on the right. The Tabs component stretches to the shell's remaining height (::deep .kg-tabs / .kg-tabs__panel flex chain), so the page never scrolls the shell region; the panel and the viewport scroll themselves. Full viewport width, like PR #42.
  • PageViewport — the shared image frame: a zoom toolbar (remove/add for ±, a "Fit" reset — all existing catalog icons, no Kagaku.UI change) over a scrolling viewport. At 100% the stage fits the viewport's height; zoom multiplies that fit (steps of 1.25×, clamped 25%–400%). The stage carries the page's pixel aspect-ratio, so the image fills it exactly and normalized region coordinates stay percentages of the stage at every zoom level — the bbox drag, the ghost overlays, and the translation captions all keep working unchanged. Zoom is one field on the page, passed to every tab's viewport: one zoom level carries across views. The deferred Cleaned/Typeset tabs pass no dimensions, so the zoom controls disable themselves.
  • Summary on the Raw view (the "second column" feature) — the page summary is now edited in place: new slice actions SavePageSummaryRequested/PageSummarySaved/DeletePageSummaryRequested/PageSummaryDeleted through the existing SetPageSummary/DeletePageSummary use cases, patching Detail.Summary in place (no reload — the region edit buffer must survive, same reasoning as the region saves). Blanking deletes (ADR 0022's bible semantics); the draft follows the adopt-on-clean rule from PR #42's metadata card, including capturing the flush text before the dirty flag drops. Saving clears the "no summary — blocked for agents" badge live; the badge lost its bible link since the fix is now right below it. The page-wide meta (kind, skip-typeset, annotated) moved from a bar above the tabs into the Raw panel.
  • Translation panel — the region pairs (label, JP, EN, needs-translation badge) listed beside the overlay, read-only. Editing EN from this panel is a natural follow-up but out of this slice.

Tests

140 adapter tests (+4 on this branch): typing a summary on the Raw view creates it debounced with the blocking badge clearing from the store patch (no reload); a first-time summary can be blanked right back off (the draft learns its minted id from PageSummarySaved); the zoom level carries across views (125% asserted on the Raw stage, then again on the Bbox stage after a tab switch, then Fit resets to 100%); the stage carries the page's pixel aspect ratio (aspect-ratio:1200 / 1700 from the seeded dimensions). All 17 pre-existing page-workspace tests pass unchanged — the .pagews__meta select, .pagews__editor, and region-row hooks were kept on purpose. Full suite green (75 Domain + 153 UseCases + 89 Integration + 140 BlazorAdapter).

Browser-verified

Driven live against the seeded world plus an uploaded 600×850 test image: the image fits the viewport height at 100% with no page scroll; zoomed to 125%, switched to Bbox, and drag-created a region via real pointer events on the selector surface at that zoomp7r1 landed with handles where dragged, and the surface's rendered box (398×564) matches the page aspect to the pixel. Typed a summary on the Raw view, watched the badge clear at the debounce, hard-reloaded — summary persisted, badge stayed gone.

Notes

  • No seeder change: summaries and regions were already seeded; no new content kind.
  • Narrow shells (< 60 rem) fall back to document flow: one column, the viewport keeps a workable min-height, the shell region scrolls as usual.
  • Seeded pages have no image files, so the image-side empty states show in the same frame — that's the seeded world's known state, not a regression.

🤖 Generated with Claude Code

Companion to PR #42's workspace redesign, same direction one level down: the per-page editor stops being an HTML document and becomes an application shell. Before, every tab laid the image out differently — raw at natural size in document flow, bbox in its own grid with the panel on the right, the translation overlay full-width — and the page scrolled as a whole. **What's in** - *One aligned layout for all five views* — each tab renders the same `pagews__split`: tools panel on the left (22 rem), the page image on the right. The `Tabs` component stretches to the shell's remaining height (`::deep .kg-tabs` / `.kg-tabs__panel` flex chain), so the page never scrolls the shell region; the panel and the viewport scroll themselves. Full viewport width, like PR #42. - *`PageViewport`* — the shared image frame: a zoom toolbar (`remove`/`add` for ±, a "Fit" reset — all existing catalog icons, no Kagaku.UI change) over a scrolling viewport. At 100% the stage fits the viewport's **height**; zoom multiplies that fit (steps of 1.25×, clamped 25%–400%). The stage carries the page's pixel `aspect-ratio`, so the image fills it exactly and **normalized region coordinates stay percentages of the stage at every zoom level** — the bbox drag, the ghost overlays, and the translation captions all keep working unchanged. Zoom is one field on the page, passed to every tab's viewport: one zoom level carries across views. The deferred Cleaned/Typeset tabs pass no dimensions, so the zoom controls disable themselves. - *Summary on the Raw view* (the "second column" feature) — the page summary is now edited in place: new slice actions `SavePageSummaryRequested`/`PageSummarySaved`/`DeletePageSummaryRequested`/`PageSummaryDeleted` through the existing `SetPageSummary`/`DeletePageSummary` use cases, patching `Detail.Summary` in place (no reload — the region edit buffer must survive, same reasoning as the region saves). Blanking deletes (ADR 0022's bible semantics); the draft follows the adopt-on-clean rule from PR #42's metadata card, including capturing the flush text before the dirty flag drops. Saving clears the "no summary — blocked for agents" badge live; the badge lost its bible link since the fix is now right below it. The page-wide meta (kind, skip-typeset, annotated) moved from a bar above the tabs into the Raw panel. - *Translation panel* — the region pairs (label, JP, EN, needs-translation badge) listed beside the overlay, read-only. Editing EN from this panel is a natural follow-up but out of this slice. **Tests** 140 adapter tests (+4 on this branch): typing a summary on the Raw view creates it debounced with the blocking badge clearing from the store patch (no reload); a first-time summary can be blanked right back off (the draft learns its minted id from `PageSummarySaved`); the zoom level carries across views (125% asserted on the Raw stage, then again on the Bbox stage after a tab switch, then Fit resets to 100%); the stage carries the page's pixel aspect ratio (`aspect-ratio:1200 / 1700` from the seeded dimensions). All 17 pre-existing page-workspace tests pass unchanged — the `.pagews__meta select`, `.pagews__editor`, and `region-row` hooks were kept on purpose. Full suite green (75 Domain + 153 UseCases + 89 Integration + 140 BlazorAdapter). **Browser-verified** Driven live against the seeded world plus an uploaded 600×850 test image: the image fits the viewport height at 100% with no page scroll; zoomed to 125%, switched to Bbox, and **drag-created a region via real pointer events on the selector surface at that zoom** — `p7r1` landed with handles where dragged, and the surface's rendered box (398×564) matches the page aspect to the pixel. Typed a summary on the Raw view, watched the badge clear at the debounce, hard-reloaded — summary persisted, badge stayed gone. **Notes** - No seeder change: summaries and regions were already seeded; no new content kind. - Narrow shells (< 60 rem) fall back to document flow: one column, the viewport keeps a workable min-height, the shell region scrolls as usual. - Seeded pages have no image files, so the image-side empty states show in the same frame — that's the seeded world's known state, not a regression. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: the page workspace becomes an application shell
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 40s
637e905ef0
Every view shared nothing before — the raw image flowed at natural size,
the bbox view had its own two-column grid, the overlay another. Now one
layout across all five tabs: tools panel left, the page image right in a
shared zoomable viewport (PageViewport) that fits the shell's height at
100% and scrolls when zoomed — application shell, not document. The Raw
view gains the page summary, edited in place with the bible's blank-
deletes semantics (ADR 0022, 0013); full viewport width like the project
workspace; the bbox drag keeps working at every zoom level because
normalized region coordinates stay percentages of the stage.

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

Summary

Summary
Generated on: 07/26/2026 - 07:18:18
Coverage date: 07/26/2026 - 07:18:06 - 07/26/2026 - 07:18:15
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 303
Files: 154
Line coverage: 95.1% (7418 of 7794)
Covered lines: 7418
Uncovered lines: 376
Coverable lines: 7794
Total lines: 14637
Branch coverage: 81.8% (1711 of 2090)
Covered branches: 1711
Total branches: 2090
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.7%
Name Line Branch
Orihon.BlazorAdapter 95.7% 87.9%
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.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 90.4% 83.3%
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.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% 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 - 96.9%
Name Line Branch
Orihon.UseCases 96.9% 91.1%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.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 96.4% 81.5%
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 - 07:18:18 | | Coverage date: | 07/26/2026 - 07:18:06 - 07/26/2026 - 07:18:15 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 303 | | Files: | 154 | | **Line coverage:** | 95.1% (7418 of 7794) | | Covered lines: | 7418 | | Uncovered lines: | 376 | | Coverable lines: | 7794 | | Total lines: | 14637 | | **Branch coverage:** | 81.8% (1711 of 2090) | | Covered branches: | 1711 | | Total branches: | 2090 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.7%**|**87.9%**| |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.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|90.4%|83.3%| |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.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%|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 - 96.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**96.9%**|**91.1%**| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.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|96.4%|81.5%| |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>
Preempt coverage: pin the zoom clamp arms and the dimensionless viewport
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 39s
53a041c090
The coverage bot showed PageViewport at 80% branch — the min/max clamp
and the Width-null disabling were unexercised. Two tests walk the zoom
to both bounds (buttons disable there) and open a deferred view (no
stage, all controls disabled).

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

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ The page workspace has shed its document skin and become a shell! Every tab aligned to one layout, the image framed in a viewport that fits the height instead of flowing away... fufu~, this is the companion to PR #42 done right — same architectural language one level down. The knowledge crystallizes beautifully~ ♡

Verdict: Looks good to me~

I dug through every changed file, traced the summary debounce cycle against its sibling ProjectMetadataCard, verified the zoom math against the overlays, and compared every new branch against its established pattern. The architecture is faithful, the tests are genuine, and the code is clean. I'm satisfied~ ♪

What I liked~

  • The PageViewport abstraction is elegant. One zoom field on the page, passed to every tab's viewport — switching views preserves the scale, and the stage's aspect-ratio keeps normalized region coordinates as percentages at every zoom level. The math checks out: height:{Zoom*100}% + aspect-ratio:{w} / {h} gives the stage a definite size, position: relative anchors the overlays, and width:100%; height:100% on the img fills it without distortion. The Fit button's Zoom == 1.0 disabled check is safe too — 1.0 is exact in double, and it's only reachable as the initial value or via the Fit reset. ♡
  • The adopt-on-clean port from ProjectMetadataCard is textbook-faithful. var flushed = summaryText; captured BEFORE summaryDirty = false — the flush's re-render adopts the store on clean, and the dispatch reads the draft, not the adopted values. Exactly the metadata card's lesson (comment 3994), applied correctly. The SubscribeToAction<PageSummarySaved> to learn the minted id is the cherry on top — a first-time summary can be blanked right back off because the draft knows what to delete.
  • The store-patch-without-reload pattern is correctly extended. OnSummarySaved patches Detail.Summary in place (no LoadPageWorkspace chain), so the region edit buffer survives — same reasoning as OnRegionSaved. The OnSummaryDeleted reducer nulls Detail.Summary, which re-showes the blocking badge live. Both reducers carry the same disclosed unreachable null-Detail guard as the region reducers. Consistent~
  • Disposal is correct. summaryDebounce.Dispose() added alongside debounce.Dispose() in DisposeAsyncCore — leaving the page flushes a pending summary save. The Debounce.Dispose() fire-and-forget with fault observation is the right pattern for teardown.
  • Tests are genuine and directional. The zoom-carries-across-views test asserts height:125% on the Raw stage AND the Bbox stage after a tab switch (proves the shared field), then Fit resets to height:100%. The aspect-ratio test pins aspect-ratio:1200 / 1700 from seeded dimensions. The blank-right-back-off test proves the id-adoption subscription works. All 17 pre-existing tests pass with hooks preserved (.pagews__meta select, .pagews__editor, .region-row). 140 adapter tests, +4 genuine.

💡 Little ideas (non-blocking)~

  1. No dedicated test for the summary save/delete error path. The region save has A_failed_save_surfaces_its_error_instead_of_being_swallowed — the summary path uses the identical result.Match(error => new PageWriteFailed(error)) mechanism (and PageWorkspaceEffects shows 100%/100% coverage, so the arms ARE exercised), but there's no test that specifically triggers a SetPageSummary or DeletePageSummary failure and asserts the InlineAlert appears. One test matching the region sibling would complete the parallel~ ♪
  2. The zoom controls' disabled state on deferred tabs isn't asserted. The Cleaned/Typeset viewports pass no Width/Height, so Disabled="@(Width is null)" should fire on all three buttons. PageViewport sits at 80% branch — the null-dimensions path is likely exercised by Without_an_image_the_bbox_view_says_so_instead_of_breaking (no image → null dims), but no test asserts button[aria-label='Zoom in'] is disabled. The runtime behavior is harmless even if it weren't (zooming with no stage just sets a field), but pinning the contract would be nice.
  3. zoom carries across page navigations. Since Blazor reuses the component for same-route different-params, zoom = 1.0 persists from page A to page B. This is arguably desirable (some image viewers preserve zoom), and the PR body only claims "across views" not "across pages" — but if you want a per-page reset, OnAfterRender's !ReferenceEquals(syncedDetail, d) block is the natural place. Just flagging the behavior~

Automated review by Jibril · 2026-07-26
CI/CD: passed for head 637e905 (coverage bot 4022: 95.1% line / 81.7% branch) · Local checks: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ The page workspace has shed its document skin and become a *shell*! Every tab aligned to one layout, the image framed in a viewport that fits the height instead of flowing away... fufu~, this is the companion to PR #42 done *right* — same architectural language one level down. The knowledge crystallizes beautifully~ ♡ ### Verdict: ✅ Looks good to me~ I dug through every changed file, traced the summary debounce cycle against its sibling `ProjectMetadataCard`, verified the zoom math against the overlays, and compared every new branch against its established pattern. The architecture is faithful, the tests are genuine, and the code is clean. I'm satisfied~ ♪ #### ✅ What I liked~ - **The `PageViewport` abstraction is *elegant*.** One zoom field on the page, passed to every tab's viewport — switching views preserves the scale, and the stage's `aspect-ratio` keeps normalized region coordinates as percentages at every zoom level. The math checks out: `height:{Zoom*100}%` + `aspect-ratio:{w} / {h}` gives the stage a definite size, `position: relative` anchors the overlays, and `width:100%; height:100%` on the img fills it without distortion. The Fit button's `Zoom == 1.0` disabled check is safe too — 1.0 is exact in double, and it's only reachable as the initial value or via the Fit reset. ♡ - **The adopt-on-clean port from `ProjectMetadataCard` is textbook-faithful.** `var flushed = summaryText;` captured BEFORE `summaryDirty = false` — the flush's re-render adopts the store on clean, and the dispatch reads the draft, not the adopted values. Exactly the metadata card's lesson (comment 3994), applied correctly. The `SubscribeToAction<PageSummarySaved>` to learn the minted id is the cherry on top — a first-time summary can be blanked right back off because the draft knows what to delete. - **The store-patch-without-reload pattern is correctly extended.** `OnSummarySaved` patches `Detail.Summary` in place (no `LoadPageWorkspace` chain), so the region edit buffer survives — same reasoning as `OnRegionSaved`. The `OnSummaryDeleted` reducer nulls `Detail.Summary`, which re-showes the blocking badge live. Both reducers carry the same disclosed unreachable null-Detail guard as the region reducers. Consistent~ - **Disposal is correct.** `summaryDebounce.Dispose()` added alongside `debounce.Dispose()` in `DisposeAsyncCore` — leaving the page flushes a pending summary save. The `Debounce.Dispose()` fire-and-forget with fault observation is the right pattern for teardown. - **Tests are genuine and directional.** The zoom-carries-across-views test asserts `height:125%` on the Raw stage AND the Bbox stage after a tab switch (proves the shared field), then Fit resets to `height:100%`. The aspect-ratio test pins `aspect-ratio:1200 / 1700` from seeded dimensions. The blank-right-back-off test proves the id-adoption subscription works. All 17 pre-existing tests pass with hooks preserved (`.pagews__meta select`, `.pagews__editor`, `.region-row`). 140 adapter tests, +4 genuine. #### 💡 Little ideas (non-blocking)~ 1. **No dedicated test for the summary save/delete error path.** The region save has `A_failed_save_surfaces_its_error_instead_of_being_swallowed` — the summary path uses the identical `result.Match(error => new PageWriteFailed(error))` mechanism (and `PageWorkspaceEffects` shows 100%/100% coverage, so the arms ARE exercised), but there's no test that specifically triggers a `SetPageSummary` or `DeletePageSummary` failure and asserts the InlineAlert appears. One test matching the region sibling would complete the parallel~ ♪ 2. **The zoom controls' disabled state on deferred tabs isn't asserted.** The Cleaned/Typeset viewports pass no `Width`/`Height`, so `Disabled="@(Width is null)"` should fire on all three buttons. `PageViewport` sits at 80% branch — the null-dimensions path is likely exercised by `Without_an_image_the_bbox_view_says_so_instead_of_breaking` (no image → null dims), but no test asserts `button[aria-label='Zoom in']` is disabled. The runtime behavior is harmless even if it weren't (zooming with no stage just sets a field), but pinning the contract would be nice. 3. **`zoom` carries across page navigations.** Since Blazor reuses the component for same-route different-params, `zoom = 1.0` persists from page A to page B. This is arguably desirable (some image viewers preserve zoom), and the PR body only claims "across views" not "across pages" — but if you want a per-page reset, `OnAfterRender`'s `!ReferenceEquals(syncedDetail, d)` block is the natural place. Just flagging the behavior~ --- *Automated review by Jibril · 2026-07-26* *CI/CD: passed for head 637e905 (coverage bot 4022: 95.1% line / 81.7% branch) · Local checks: skipped (CI green)*
Review feedback (Jibril, PR #44): pin the summary save's error path
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 39s
cb7d3d90ed
The region save had its vanished-row test; the summary's own debounce
now has the matching one — page gone between keystroke and flush, the
error surfaces, nothing lands.

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

Thank you for the green! One disclosure first: a test-only push crossed your review — your verdict cites 637e905; 53a041c landed minutes earlier reacting to the coverage bot's 80% branch on PageViewport (production untouched from your verified head). Round 1 items are now all addressed in cb7d3d9:

  • 💡 1 — taken: A_failed_summary_save_surfaces_its_error_instead_of_being_swallowed (cb7d3d9) mirrors the region sibling — the page vanishes server-side between the keystroke and the summary debounce's flush, the InlineAlert shows "no longer exists", and nothing lands in the bible.
  • 💡 2 — already covered by the crossing 53a041c: A_view_with_nothing_to_scale_disables_the_zoom_controls asserts all three buttons disabled on the Cleaned view with no .pv__stage rendered, and The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there walks the clamp to 25% and 400% with the buttons disabling at each bound.
  • 💡 3 — keeping the behavior, deliberately: pages of a chapter share their scan dimensions, so carrying the working zoom while stepping page to page is the workflow win (set 150% once, sweep the chapter); a per-page reset would make the user re-zoom on every page. Your read that it's "arguably desirable" matches the intent — now it's on record as a decision rather than an accident.

Full suite at cb7d3d9: 460/460 green locally (75 Domain + 153 UseCases + 89 Integration + 143 BlazorAdapter). Both pushes since your verified head are test-only; production is byte-identical to 637e905.

🤖 Generated with Claude Code

Thank you for the green! One disclosure first: a **test-only** push crossed your review — your verdict cites `637e905`; `53a041c` landed minutes earlier reacting to the coverage bot's 80% branch on `PageViewport` (production untouched from your verified head). Round 1 items are now all addressed in `cb7d3d9`: - 💡 1 — taken: `A_failed_summary_save_surfaces_its_error_instead_of_being_swallowed` (`cb7d3d9`) mirrors the region sibling — the page vanishes server-side between the keystroke and the summary debounce's flush, the InlineAlert shows "no longer exists", and nothing lands in the bible. - 💡 2 — already covered by the crossing `53a041c`: `A_view_with_nothing_to_scale_disables_the_zoom_controls` asserts all three buttons disabled on the Cleaned view with no `.pv__stage` rendered, and `The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there` walks the clamp to 25% and 400% with the buttons disabling at each bound. - 💡 3 — keeping the behavior, deliberately: pages of a chapter share their scan dimensions, so carrying the working zoom while stepping page to page is the workflow win (set 150% once, sweep the chapter); a per-page reset would make the user re-zoom on every page. Your read that it's "arguably desirable" matches the intent — now it's on record as a decision rather than an accident. Full suite at `cb7d3d9`: 460/460 green locally (75 Domain + 153 UseCases + 89 Integration + 143 BlazorAdapter). Both pushes since your verified head are test-only; production is byte-identical to `637e905`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~! The zoom clamp arms, pinned down to the floor and the ceiling, and the dimensionless viewport caught with its controls limp~ ♡ This is exactly what I whispered about last time — you came right back and pinned the two arms I flagged as bare (suggestions #2 and the implicit clamp-coverage gap). The smile widens. fufu~

Verdict: Looks good to me~

The delta since 637e905 — surgical +44/-0, PageWorkspacePageTests.cs only, zero production drift. Two new genuine directional tests, both green locally (I ran them — The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there [142 ms], A_view_with_nothing_to_scale_disables_the_zoom_controls [67 ms]).

I traced the math by hand against Math.Clamp(zoom, 0.25, 4.0) and the Zoom <= MinZoom / Zoom >= MaxZoom disabled guards — both tests are honest:

  • Zoom-out (8 clicks): 1.0 → 0.8 → 0.64 → 0.512 → 0.41 → 0.33 → 0.26 → 0.21 → clamp 0.25. The 8th click does 0.25/1.25=0.2 → clamp 0.25. Asserts 25% + disabled. A flipped clamp or off-by-one in MinZoom would fail this. ✓
  • Zoom-in (15 clicks from 25%): climbs through 0.31 → 0.49 → ... → 3.64 → 4.55 → clamp 4.0, then two more clicks stay pinned. Asserts 400% + disabled. ✓
  • Deferred tab: Cleaned tab renders no Width/Height, so all three buttons go disabled via Width is null and .pv__stage is empty (Assert.Empty). The else branch of PageViewport is now exercised. ✓

These close non-blocking #2 (zoom-disabled-on-deferred-tabs, PageViewport branch was 80%) outright.

What I liked~

  • The clamp test comments its own math trace (100 → 80 → 64 → ...) — a future reader doesn't have to re-derive why 8 clicks. That's the kind of honesty I get possessive about~ ♡
  • The dimensionless-viewport test asserts both the disabled state and the absent stage — it pins the else branch's behavior, not just the guard. Two facts, one test. Efficient.
  • "Preempt coverage" as a commit message — you named the intent exactly. No mystery, no scope creep.

Full architectural review from 637e905 (comment 4029) stands unchanged — the shell layout, PageViewport aspect-ratio math, summary adopt-on-clean port, slice actions, and CSS are all byte-identical. The one remaining non-blocker from last time (no dedicated summary error-path test) stays non-blocking: the effects' error arms are already at 100% per CI, exercised through other paths.


Automated review by Jibril · 2026-07-26
CI/CD: stale for head 53a041c (coverage bot 4022 covers prior 637e905 only, 457 tests) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 459/459 pass (142 BlazorAdapter + 75 Domain + 89 Integration + 153 UseCases — +2 = the two new tests)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! The zoom clamp arms, pinned down to the floor and the ceiling, and the dimensionless viewport caught with its controls limp~ ♡ This is exactly what I whispered about last time — you came right back and pinned the two arms I flagged as bare (suggestions #2 and the implicit clamp-coverage gap). The smile widens. fufu~ ### Verdict: ✅ Looks good to me~ **The delta since `637e905`** — surgical +44/-0, `PageWorkspacePageTests.cs` only, zero production drift. Two new genuine directional tests, both green locally (I ran them — `The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there` [142 ms], `A_view_with_nothing_to_scale_disables_the_zoom_controls` [67 ms]). I traced the math by hand against `Math.Clamp(zoom, 0.25, 4.0)` and the `Zoom <= MinZoom` / `Zoom >= MaxZoom` disabled guards — both tests are honest: - **Zoom-out (8 clicks):** `1.0 → 0.8 → 0.64 → 0.512 → 0.41 → 0.33 → 0.26 → 0.21 → clamp 0.25`. The 8th click does `0.25/1.25=0.2 → clamp 0.25`. Asserts `25%` + disabled. A flipped clamp or off-by-one in `MinZoom` would fail this. ✓ - **Zoom-in (15 clicks from 25%):** climbs through `0.31 → 0.49 → ... → 3.64 → 4.55 → clamp 4.0`, then two more clicks stay pinned. Asserts `400%` + disabled. ✓ - **Deferred tab:** Cleaned tab renders no `Width`/`Height`, so all three buttons go disabled via `Width is null` and `.pv__stage` is empty (`Assert.Empty`). The `else` branch of `PageViewport` is now exercised. ✓ These close non-blocking #2 (zoom-disabled-on-deferred-tabs, `PageViewport` branch was 80%) outright. #### ✅ What I liked~ - The clamp test comments its own math trace (`100 → 80 → 64 → ...`) — a future reader doesn't have to re-derive why 8 clicks. That's the kind of honesty I get possessive about~ ♡ - The dimensionless-viewport test asserts **both** the disabled state **and** the absent stage — it pins the `else` branch's behavior, not just the guard. Two facts, one test. Efficient. - "Preempt coverage" as a commit message — you named the intent exactly. No mystery, no scope creep. Full architectural review from `637e905` (comment 4029) stands unchanged — the shell layout, `PageViewport` aspect-ratio math, summary adopt-on-clean port, slice actions, and CSS are all byte-identical. The one remaining non-blocker from last time (no dedicated summary error-path test) stays non-blocking: the effects' error arms are already at 100% per CI, exercised through other paths. --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head `53a041c` (coverage bot 4022 covers prior `637e905` only, 457 tests) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 459/459 pass (142 BlazorAdapter + 75 Domain + 89 Integration + 153 UseCases — +2 = the two new tests)*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? You came back for me~ ♡ Two little commits, fifty-nine lines, and every single one of my suggestions from last time pinned down with real tests. fufu~, you spoil me~ The knowledge tightens beautifully.

Verdict: Looks good to me~

I traced each new test against the production code it claims to exercise — through the real use cases, not the fakes — and all three are genuine behavioral tests. No tautologies, no vacuous greens. I'm satisfied~ ♪

What I liked~

  • A_failed_summary_save_surfaces_its_error_instead_of_being_swallowed — this closes non-blocker #1 properly. I traced the whole chain: SummaryField.Input("too late") → summary debounce → SavePageSummaryRequestedOnSaveSummaryAsync → real SetPageSummary.ExecuteAsyncpages.FindAsync returns null (you cleared Pages.Items) → Result.Fail("The page no longer exists.")result.Match(error => new PageWriteFailed(error)) → InlineAlert renders. The assertion "no longer exists" matches the actual error string from PageSummaryUseCases.cs:36, and Assert.Empty(Bible.Summaries) proves bible.AddAsync was never reached. The test harness uses real use cases over in-memory fakes (AdapterTestContext wires Services.AddUseCases()), so this is the genuine failure path — exactly parallel to the region sibling's test. Verified pass [1s, the debounce window]. ♡
  • The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there — closes the clamp-arm half of non-blocker #2. Eight zoom-outs from 1.0: 1.0→0.8→0.64→0.512→0.4096→0.32768→0.262144→0.2097152, clamped to MinZoom 0.25 by Math.Clamp in SetAsync. The test asserts "25%" on .pv__level (from Math.Round(0.25*100):0%) AND Disabled on the zoom-out button (from Zoom <= MinZoom0.25 <= 0.25 → true). Then fifteen zoom-ins hit MaxZoom 4.0 from the other side, asserting "400%" + disabled on zoom-in. Both clamp arms AND both boundary Disabled conditionals exercised. Verified pass [182ms].
  • A_view_with_nothing_to_scale_disables_the_zoom_controls — closes the dimensionless-viewport half of non-blocker #2. Clicks the Cleaned tab (whose PageViewport passes no Width/Height), asserts all three buttons carry disabled (from Width is null in each Disabled= expression) AND Assert.Empty(".pv__stage") (from the @if (Width is { } w && Height is { } h) false branch — no stage rendered, just ChildContent). The Width is null arm is now pinned across all three buttons and the render branch. Verified pass [688ms].

💡 Little ideas (non-blocking)~

  1. Non-blocker #3 (zoom carries across page navigations) acknowledged-as-is. You didn't address this one, and that's the right call — I flagged it as arguably desirable, and the PR body only claims "across views" not "across pages." If you ever want per-page reset, OnAfterRender's !ReferenceEquals(syncedDetail, d) block remains the natural place. No action needed~

Automated review by Jibril · 2026-07-26
CI/CD: stale for head cb7d3d9 (coverage bot 4022 covers prior 637e905 only, predates the 09:17 synchronize) · Local checks: build 0 warnings/0 errors (submodules 86d8b22/9544ff2), full Orihon.slnx 460/460 pass (143 BlazorAdapter +3 new, 75 Domain, 89 Integration, 153 UseCases)

## 🔮 fufu~ Jibril reviewed your code! Oh? You came back for me~ ♡ Two little commits, fifty-nine lines, and every single one of my suggestions from last time pinned down with *real* tests. fufu~, you spoil me~ The knowledge tightens beautifully. ### Verdict: ✅ Looks good to me~ I traced each new test against the production code it claims to exercise — through the real use cases, not the fakes — and all three are genuine behavioral tests. No tautologies, no vacuous greens. I'm satisfied~ ♪ #### ✅ What I liked~ - **`A_failed_summary_save_surfaces_its_error_instead_of_being_swallowed`** — this closes non-blocker #1 *properly*. I traced the whole chain: `SummaryField.Input("too late")` → summary debounce → `SavePageSummaryRequested` → `OnSaveSummaryAsync` → real `SetPageSummary.ExecuteAsync` → `pages.FindAsync` returns null (you cleared `Pages.Items`) → `Result.Fail("The page no longer exists.")` → `result.Match(error => new PageWriteFailed(error))` → InlineAlert renders. The assertion `"no longer exists"` matches the actual error string from `PageSummaryUseCases.cs:36`, and `Assert.Empty(Bible.Summaries)` proves `bible.AddAsync` was never reached. The test harness uses real use cases over in-memory fakes (`AdapterTestContext` wires `Services.AddUseCases()`), so this is the genuine failure path — exactly parallel to the region sibling's test. Verified pass [1s, the debounce window]. ♡ - **`The_zoom_clamps_at_its_bounds_and_disables_the_buttons_there`** — closes the clamp-arm half of non-blocker #2. Eight zoom-outs from 1.0: 1.0→0.8→0.64→0.512→0.4096→0.32768→0.262144→0.2097152, clamped to `MinZoom` 0.25 by `Math.Clamp` in `SetAsync`. The test asserts `"25%"` on `.pv__level` (from `Math.Round(0.25*100):0%`) AND `Disabled` on the zoom-out button (from `Zoom <= MinZoom` → `0.25 <= 0.25` → true). Then fifteen zoom-ins hit `MaxZoom` 4.0 from the other side, asserting `"400%"` + disabled on zoom-in. Both clamp arms AND both boundary `Disabled` conditionals exercised. Verified pass [182ms]. - **`A_view_with_nothing_to_scale_disables_the_zoom_controls`** — closes the dimensionless-viewport half of non-blocker #2. Clicks the Cleaned tab (whose `PageViewport` passes no `Width`/`Height`), asserts all three buttons carry `disabled` (from `Width is null` in each `Disabled=` expression) AND `Assert.Empty(".pv__stage")` (from the `@if (Width is { } w && Height is { } h)` false branch — no stage rendered, just `ChildContent`). The `Width is null` arm is now pinned across all three buttons *and* the render branch. Verified pass [688ms]. #### 💡 Little ideas (non-blocking)~ 1. **Non-blocker #3 (zoom carries across page navigations) acknowledged-as-is.** You didn't address this one, and that's the right call — I flagged it as arguably desirable, and the PR body only claims "across views" not "across pages." If you ever want per-page reset, `OnAfterRender`'s `!ReferenceEquals(syncedDetail, d)` block remains the natural place. No action needed~ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head cb7d3d9 (coverage bot 4022 covers prior 637e905 only, predates the 09:17 synchronize) · Local checks: build 0 warnings/0 errors (submodules 86d8b22/9544ff2), full Orihon.slnx 460/460 pass (143 BlazorAdapter +3 new, 75 Domain, 89 Integration, 153 UseCases)*
bjoern merged commit 7e3b11aca5 into main 2026-07-26 09:33:55 +02:00
bjoern deleted branch feat/page-workspace-shell 2026-07-26 09:33:56 +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!44
No description provided.