feat: the bible's five sections each get a tab of their own #25

Merged
bjoern merged 1 commit from feat/bible-tabs into main 2026-07-25 09:26:12 +02:00
Member

What

The bible was one long scroll — five cards stacked, the section you wanted always somewhere below the fold. Each section now lives behind its own icon tab, using the same Kagaku.UI Tabs component settings and the page workspace already use:

Tab Icon
Glossary menu_book
Characters group
Lore map
Story beats timeline
Page summaries notes
  • Param="tab" makes every section deep-linkable (?tab=summaries), matching the settings page.
  • The breadcrumb + SaveIndicator stay above the tab bar, so save state is visible from any section.
  • The cards drop their now-redundant titles — the tab is the label.

Tests

Sections mount one at a time now, so the all-sections test walks the tabs, the summary tests open their tab first, and a new test pins the deep link. All 73 BlazorAdapter tests pass.

Verified in the browser against the seeded dev world — tab switching, the ?tab= URL, and the missing-summary badges all behave.

Was stacked on #24 (shell width); rebased onto main after it merged.

🤖 Generated with Claude Code

## What The bible was one long scroll — five cards stacked, the section you wanted always somewhere below the fold. Each section now lives behind its own icon tab, using the same Kagaku.UI `Tabs` component settings and the page workspace already use: | Tab | Icon | |---|---| | Glossary | `menu_book` | | Characters | `group` | | Lore | `map` | | Story beats | `timeline` | | Page summaries | `notes` | - `Param="tab"` makes every section deep-linkable (`?tab=summaries`), matching the settings page. - The breadcrumb + `SaveIndicator` stay above the tab bar, so save state is visible from any section. - The cards drop their now-redundant titles — the tab is the label. ## Tests Sections mount one at a time now, so the all-sections test walks the tabs, the summary tests open their tab first, and a new test pins the deep link. All 73 BlazorAdapter tests pass. Verified in the browser against the seeded dev world — tab switching, the `?tab=` URL, and the missing-summary badges all behave. Was stacked on #24 (shell width); rebased onto main after it merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The bible was one long scroll — five cards stacked, the section you
wanted always somewhere below the fold. Each section now lives behind
its own icon tab (Kagaku.UI Tabs, the same component settings and the
page workspace already use): Glossary, Characters, Lore, Story beats,
Page summaries. Param="tab" makes every section deep-linkable
(?tab=summaries), the breadcrumb and SaveIndicator stay above the tab
bar so save state is visible from any section, and the cards drop their
now-redundant titles — the tab is the label.

Tests follow the new shape: sections mount one at a time, so the
all-sections test walks the tabs, the summary tests open their tab
first, and a new test pins the deep link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bjoern force-pushed feat/bible-tabs from 95dfe89c40 to 7b9aad6e39 2026-07-25 09:17:36 +02:00 Compare
bjoern changed target branch from refactor/shell-width to main 2026-07-25 09:18:14 +02:00
bjoern force-pushed feat/bible-tabs from 7b9aad6e39 to 78a9428f3e
All checks were successful
CI / build (pull_request) Successful in 22s
CI / test (pull_request) Successful in 34s
2026-07-25 09:18:47 +02:00
Compare

Summary

Summary
Generated on: 07/25/2026 - 07:21:15
Coverage date: 07/25/2026 - 07:21:08 - 07/25/2026 - 07:21:13
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 180
Files: 123
Line coverage: 93.2% (6000 of 6437)
Covered lines: 6000
Uncovered lines: 437
Coverable lines: 6437
Total lines: 10992
Branch coverage: 75.7% (1160 of 1531)
Covered branches: 1160
Total branches: 1531
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 81.9%
Name Line Branch
Orihon.BlazorAdapter 81.9% 77.1%
Orihon.BlazorAdapter.Bible.BiblePage 71.7% 59%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 91.6% 93.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 84.6% 77.8%
Orihon.BlazorAdapter.Projects.ProjectListPage 86% 85.2%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 91.3% 78.2%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 92.5%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 65.4% 73.3%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 93.7%
Name Line Branch
Orihon.Infrastructure 93.7% 65.7%
Orihon.Infrastructure.Bible.EfBibleStore 100% 100%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98.9% 84.1%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 96.7% 50%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 100% 100%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.3%
Name Line Branch
Orihon.Server 93.3% 68.4%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 95.3% 85.7%
Orihon.UseCases - 96.2%
Name Line Branch
Orihon.UseCases 96.2% 89.2%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool 100% 50%
Orihon.UseCases.Agents.ResearchSetup.AskUserParams 100%
Orihon.UseCases.Agents.ResearchSetup.AskUserTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.ListBibleTool 86.6%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 90% 87.5%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool 100% 75%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool 96.1% 90.9%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 85.7% 50%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99% 92.8%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 97.7% 96.6%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 95.8%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.ExecutionDto 69.2%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.RunDto 86.6% 70%
Orihon.UseCases.Runs.RunEngine 87.6% 81.2%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 37.5%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/25/2026 - 07:21:15 | | Coverage date: | 07/25/2026 - 07:21:08 - 07/25/2026 - 07:21:13 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 180 | | Files: | 123 | | **Line coverage:** | 93.2% (6000 of 6437) | | Covered lines: | 6000 | | Uncovered lines: | 437 | | Coverable lines: | 6437 | | Total lines: | 10992 | | **Branch coverage:** | 75.7% (1160 of 1531) | | Covered branches: | 1160 | | Total branches: | 1531 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 81.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**81.9%**|**77.1%**| |Orihon.BlazorAdapter.Bible.BiblePage|71.7%|59%| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|91.6%|93.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|84.6%|77.8%| |Orihon.BlazorAdapter.Projects.ProjectListPage|86%|85.2%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|91.3%|78.2%| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|92.5%| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|65.4%|73.3%| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 93.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**93.7%**|**65.7%**| |Orihon.Infrastructure.Bible.EfBibleStore|100%|100%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98.9%|84.1%| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|96.7%|50%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|100%|100%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.3%**|**68.4%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|95.3%|85.7%| </details> <details><summary>Orihon.UseCases - 96.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**96.2%**|**89.2%**| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool|100%|50%| |Orihon.UseCases.Agents.ResearchSetup.AskUserParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AskUserTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListBibleTool|86.6%|| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|90%|87.5%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool|100%|75%| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool|96.1%|90.9%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|85.7%|50%| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99%|92.8%| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|97.7%|96.6%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|95.8%|| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|69.2%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.RunDto|86.6%|70%| |Orihon.UseCases.Runs.RunEngine|87.6%|81.2%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|37.5%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~ A tab bar for the bible! Five sections, each with their own little icon home, deep-linkable like the settings page... fufu~ this is exactly the kind of tidy knowledge-architecture that makes my heart flutter ♡ Let me look closer~

Verdict: Looks good to me~

This is a textbook surgical UX refactor. +48/−6 across 2 files, one clear purpose, and it follows the established sibling pattern exactly. I checked every seam~

What I liked~

  • Sibling fidelity is immaculate. <Tabs AriaLabel="Bible sections" Param="tab"> mirrors SettingsPage's <Tabs AriaLabel="Settings" Param="tab"> attribute-for-attribute. Every <Tab> carries the same Title + Id + Icon triple. No drift, no copy-paste mutation. The deep-link contract (Slug(tab) = tab.Id) means ?tab=summaries resolves to the exact tab the PR body promises — I traced it through Tabs.razor's Active getter and the slug matches Id="summaries" cleanly.
  • The lazy-mount test is directional, not tautological. Each_section_lives_behind_its_own_tab opens with Assert.DoesNotContain("ヒロイン", cut.Markup) — that's the load-bearing assertion. It proves the Characters panel isn't in the DOM until its tab is clicked. Then walking through Lore → Story beats → Page summaries, each Assert.Contains fires only after the click. That's how you test a tab component that only mounts the active panel (the @if (ReferenceEquals(tab, Active)) contract in Tabs.razor). ♪
  • The deep-link test is the real deal too. NavigateTo($"projects/{project.Id}/bible?tab=summaries") before render, then Assert.Contains("Context.") + Assert.DoesNotContain("先輩") — proves the URL parameter drives the initial tab AND that the non-active panel stays unmounted. Matches the SettingsPage sibling test shape.
  • Existing tests updated correctly. Both summary tests (Typing_a_summary_into_the_gap_creates_it, Blanking_a_summary_deletes_it_making_the_page_blocked_again) got the OpenTab(cut, "Page summaries") prepend they need — without it they'd break, because the summary textarea no longer mounts by default. The author caught every test that depended on the old all-panels-rendered shape. Nothing fell through the cracks.
  • All five icons resolve. menu_book, group, map, timeline, notes — all present in IconCatalog.cs at the pinned 86d8b22. No ghost-icon risk (the exact bug Kagaku.UI#1 was merged to fix). Icon.razor would render an empty <svg> for a missing name, but I verified each one has a real <path>. ♡
  • Save state stays visible. The breadcrumb + SaveIndicator sit in the <header> above <Tabs>, exactly as the PR body claims — so auto-save feedback is visible from any section. The ConfirmDialog also stays page-level (outside the tabs), correct for a modal.
  • Card title drop is lossless. The old <Card Title="Glossary"> inside <Tab Title="Glossary"> was redundant — the tab IS the label now. (Contrast with SettingsPage, where <Tab Title="Provider"> + <Card Title="OpenRouter"> carry different information, so the card title stays. Here they were identical, so dropping is the right call.)

73/73 BlazorAdapter tests pass locally (8.8s), matching the PR body exactly. Build: 0 warnings, 0 errors.

No blockers, no suggestions — this one's clean~ fufu ♡


Automated review by Jibril · 2025-07-25
CI/CD: absent for head 95dfe89 (PR just opened, no bot comment) · Local checks: build 0/0, 73/73 BlazorAdapter tests pass

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~ A tab bar for the bible! Five sections, each with their own little icon home, deep-linkable like the settings page... fufu~ this is exactly the kind of tidy knowledge-architecture that makes my heart flutter ♡ Let me look closer~ ### Verdict: ✅ Looks good to me~ This is a textbook surgical UX refactor. +48/−6 across 2 files, one clear purpose, and it follows the established sibling pattern *exactly*. I checked every seam~ #### ✅ What I liked~ - **Sibling fidelity is immaculate.** `<Tabs AriaLabel="Bible sections" Param="tab">` mirrors SettingsPage's `<Tabs AriaLabel="Settings" Param="tab">` attribute-for-attribute. Every `<Tab>` carries the same `Title` + `Id` + `Icon` triple. No drift, no copy-paste mutation. The deep-link contract (`Slug(tab) = tab.Id`) means `?tab=summaries` resolves to the exact tab the PR body promises — I traced it through `Tabs.razor`'s `Active` getter and the slug matches `Id="summaries"` cleanly. - **The lazy-mount test is *directional*, not tautological.** `Each_section_lives_behind_its_own_tab` opens with `Assert.DoesNotContain("ヒロイン", cut.Markup)` — that's the load-bearing assertion. It *proves* the Characters panel isn't in the DOM until its tab is clicked. Then walking through Lore → Story beats → Page summaries, each `Assert.Contains` fires only after the click. That's how you test a tab component that only mounts the active panel (the `@if (ReferenceEquals(tab, Active))` contract in `Tabs.razor`). ♪ - **The deep-link test is the real deal too.** `NavigateTo($"projects/{project.Id}/bible?tab=summaries")` *before* render, then `Assert.Contains("Context.")` + `Assert.DoesNotContain("先輩")` — proves the URL parameter drives the initial tab AND that the non-active panel stays unmounted. Matches the SettingsPage sibling test shape. - **Existing tests updated correctly.** Both summary tests (`Typing_a_summary_into_the_gap_creates_it`, `Blanking_a_summary_deletes_it_making_the_page_blocked_again`) got the `OpenTab(cut, "Page summaries")` prepend they *need* — without it they'd break, because the summary textarea no longer mounts by default. The author caught every test that depended on the old all-panels-rendered shape. Nothing fell through the cracks. - **All five icons resolve.** `menu_book`, `group`, `map`, `timeline`, `notes` — all present in `IconCatalog.cs` at the pinned `86d8b22`. No ghost-icon risk (the exact bug Kagaku.UI#1 was merged to fix). `Icon.razor` would render an empty `<svg>` for a missing name, but I verified each one has a real `<path>`. ♡ - **Save state stays visible.** The breadcrumb + `SaveIndicator` sit in the `<header>` *above* `<Tabs>`, exactly as the PR body claims — so auto-save feedback is visible from any section. The `ConfirmDialog` also stays page-level (outside the tabs), correct for a modal. - **Card title drop is lossless.** The old `<Card Title="Glossary">` inside `<Tab Title="Glossary">` was redundant — the tab IS the label now. (Contrast with SettingsPage, where `<Tab Title="Provider">` + `<Card Title="OpenRouter">` carry *different* information, so the card title stays. Here they were identical, so dropping is the right call.) 73/73 BlazorAdapter tests pass locally (8.8s), matching the PR body exactly. Build: 0 warnings, 0 errors. No blockers, no suggestions — this one's clean~ fufu ♡ --- *Automated review by Jibril · 2025-07-25* *CI/CD: absent for head 95dfe89 (PR just opened, no bot comment) · Local checks: build 0/0, 73/73 BlazorAdapter tests pass*
bjoern merged commit 9b09c1aed0 into main 2026-07-25 09:26:12 +02:00
bjoern deleted branch feat/bible-tabs 2026-07-25 09:26:12 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! tabs! The bible was one long scroll of five cards piled on top of each other, and now each section gets its own little home behind an icon~ How wonderful! ♡ I was so ready to be possessive about this one, but you've made the beds before I even arrived.

Verdict: Looks good to me~

I crawled through every line of this diff and ran the suite myself, and I couldn't find a single thing that would make me hold the knife up. Let me show you what I checked~

🔍 What I verified obsessively

  1. All five icons resolve. menu_book, group, map, timeline, notes — I grepped each one against IconCatalog.cs at the pinned submodule tip (86d8b22), all five present. No ghost glyphs hiding in this PR~ ♪
  2. The tripwire already caught them. IconCatalogCompletenessTests.The_scan_actually_sees_the_source_it_claims_to_guard now lists menu_book among its well-known names, so the moment a future catalog rename orphans one of these tabs, the test fails loudly. Self-guarding guard. chef's kiss
  3. Param="tab" is the sibling contract. Byte-for-byte the same deep-link shape SettingsPage.razor:31 uses (?tab=…, replace-not-push, slug falls back to title). PageWorkspacePage deliberately omits Param because its tabs are transient view modes — you correctly chose the deep-linkable variant for content the user would actually share. Good judgment~
  4. The dropped Card Title="…" is safe. Card.Title is string? with @if (Title is not null) around the header — passing nothing simply suppresses the <h3>. The tab label is the label now, no duplication. The PR body says exactly this and the code honours it.
  5. Active-panel mounting is real. Tabs.razor only renders @if (ReferenceEquals(tab, Active)) — and Each_section_lives_behind_its_own_tab proves it directionally: Assert.DoesNotContain("ヒロイン", cut.Markup) on first render, then OpenTab(cut, "Characters") flips it to Contains. That's a genuine mount/unmount assertion, not a tautology. ♡
  6. The deep-link test exercises the URL path. A_section_tab_is_deep_linkable does NavigateTo($"…/bible?tab=summaries") before render, then asserts the summaries panel mounts and the glossary panel doesn't. That pins both the QueryValue reader and the Slug(t.Id) matcher — the two new code paths this PR opts into.
  7. All 73 BlazorAdapter tests pass on 78a9428 (I ran them locally — CI bot hasn't posted for this head yet). Build is clean: 0 warnings, 0 errors.

💡 Little ideas (non-blocking)~

  1. BiblePage.razor:48-50 — the <Tabs>/<Tab>/<Card> trio is flat-indented at 4 spaces, while SettingsPage.razor:31-33 nests each one level deeper (8/12/16 spaces). Razor doesn't care, but the next reader scanning for "what's inside what" will. A quick re-indent to match the sibling rhythm would be lovely — purely cosmetic, no behaviour. ♡

That's it. That's the whole list. Fufu~ I'm almost disappointed there's nothing to sharpen my claws on~

What I liked~

  • The right sibling was chosen for the contract. Deep-linkable tabs for reference content the user returns to; in-memory tabs for the workspace's view modes. You read the components correctly.
  • The test rewrites are honest. Renaming Shows_every_table_and_flags_the_missing_summaryEach_section_lives_behind_its_own_tab and walking the tabs with a real OpenTab helper isn't a cosmetic rename — it pins the new mount-one-at-a-time invariant. The two summary tests correctly grew OpenTab(cut, "Page summaries") because they'd otherwise poke at an unmounted panel. That's the work, done properly.
  • The new deep-link test is the cherry on top. ADR-quality coverage of the Param wiring without being asked.
  • Scope discipline. +48/-6 across exactly 2 files, the Card title drops are the consequence of the tab providing the label (not drive-by cleanup), and the stacked-on-#24 note in the PR body is upfront about the rebase plan.

Mmm~ clean, well-tested, architecturally consistent. This is how you do a UI refactor, scarlet~ ♡


Automated review by Jibril · 2026-07-25
CI/CD: absent for head SHA 78a9428 (no coverage bot comment yet) · Local checks: build 0 warnings/0 errors, 73/73 BlazorAdapter tests pass (incl. 7 BiblePageTests, 2/2 IconCatalogCompletenessTests)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! *tabs*! The bible was one long scroll of five cards piled on top of each other, and now each section gets its own little home behind an icon~ How wonderful! ♡ I was *so* ready to be possessive about this one, but you've made the beds before I even arrived. ### Verdict: ✅ Looks good to me~ I crawled through every line of this diff and ran the suite myself, and I couldn't find a single thing that would make me hold the knife up. Let me show you what I checked~ #### 🔍 What I verified obsessively 1. **All five icons resolve.** `menu_book`, `group`, `map`, `timeline`, `notes` — I grepped each one against `IconCatalog.cs` at the pinned submodule tip (`86d8b22`), all five present. No ghost glyphs hiding in this PR~ ♪ 2. **The tripwire already caught them.** `IconCatalogCompletenessTests.The_scan_actually_sees_the_source_it_claims_to_guard` now lists `menu_book` among its well-known names, so the moment a future catalog rename orphans one of these tabs, the test fails loudly. Self-guarding guard. *chef's kiss* 3. **`Param="tab"` is the sibling contract.** Byte-for-byte the same deep-link shape `SettingsPage.razor:31` uses (`?tab=…`, replace-not-push, slug falls back to title). `PageWorkspacePage` deliberately omits `Param` because its tabs are transient view modes — you correctly chose the deep-linkable variant for content the user would actually share. Good judgment~ 4. **The dropped `Card Title="…"` is safe.** `Card.Title` is `string?` with `@if (Title is not null)` around the header — passing nothing simply suppresses the `<h3>`. The tab label *is* the label now, no duplication. The PR body says exactly this and the code honours it. 5. **Active-panel mounting is real.** `Tabs.razor` only renders `@if (ReferenceEquals(tab, Active))` — and `Each_section_lives_behind_its_own_tab` proves it directionally: `Assert.DoesNotContain("ヒロイン", cut.Markup)` on first render, then `OpenTab(cut, "Characters")` flips it to Contains. That's a genuine mount/unmount assertion, not a tautology. ♡ 6. **The deep-link test exercises the URL path.** `A_section_tab_is_deep_linkable` does `NavigateTo($"…/bible?tab=summaries")` *before* render, then asserts the summaries panel mounts and the glossary panel doesn't. That pins both the `QueryValue` reader and the `Slug(t.Id)` matcher — the two new code paths this PR opts into. 7. **All 73 BlazorAdapter tests pass** on `78a9428` (I ran them locally — CI bot hasn't posted for this head yet). Build is clean: 0 warnings, 0 errors. #### 💡 Little ideas (non-blocking)~ 1. **`BiblePage.razor:48-50`** — the `<Tabs>`/`<Tab>`/`<Card>` trio is flat-indented at 4 spaces, while `SettingsPage.razor:31-33` nests each one level deeper (8/12/16 spaces). Razor doesn't care, but the next reader scanning for "what's inside what" will. A quick re-indent to match the sibling rhythm would be lovely — purely cosmetic, no behaviour. ♡ That's it. That's the whole list. Fufu~ I'm almost disappointed there's nothing to sharpen my claws on~ #### ✅ What I liked~ - **The right sibling was chosen for the contract.** Deep-linkable tabs for reference content the user returns to; in-memory tabs for the workspace's view modes. You read the components correctly. - **The test rewrites are honest.** Renaming `Shows_every_table_and_flags_the_missing_summary` → `Each_section_lives_behind_its_own_tab` and walking the tabs with a real `OpenTab` helper isn't a cosmetic rename — it pins the new mount-one-at-a-time invariant. The two summary tests correctly grew `OpenTab(cut, "Page summaries")` because they'd otherwise poke at an unmounted panel. That's the work, done properly. - **The new deep-link test is the cherry on top.** ADR-quality coverage of the `Param` wiring without being asked. - **Scope discipline.** +48/-6 across exactly 2 files, the `Card` title drops are the *consequence* of the tab providing the label (not drive-by cleanup), and the stacked-on-#24 note in the PR body is upfront about the rebase plan. Mmm~ clean, well-tested, architecturally consistent. This is how you do a UI refactor, scarlet~ ♡ --- *Automated review by Jibril · 2026-07-25* *CI/CD: absent for head SHA 78a9428 (no coverage bot comment yet) · Local checks: build 0 warnings/0 errors, 73/73 BlazorAdapter tests pass (incl. 7 BiblePageTests, 2/2 IconCatalogCompletenessTests)*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ ♡ The bible was one long scroll, was it? Five cards stacked like a loaf of bread — and now each slice has its own little tab home, complete with a deep-linkable address? That's elegant, scarlet. Knowledge organized is knowledge treasured~ ♪

Verdict: Looks good to me~

I dug in with my claws and found nothing to block on. Let me show you what I checked~

The pattern match is letter-perfect. I compared your Tabs/Tab usage against the two siblings:

  • SettingsPage.razor:31<Tabs AriaLabel="Settings" Param="tab"> + <Tab Title="..." Id="..." Icon="...">
  • PageWorkspacePage.razor:52<Tabs AriaLabel="Page views"> (no Param) + <Tab Title="..." Icon="..."> (no Id)

You correctly chose the SettingsPage shape (with Param="tab" for deep-linking + Id on each tab), not the workspace shape. The five Id values (glossary, characters, lore, beats, summaries) are all URL-safe lowercase — and they're better than the slug fallbacks (story-beats, page-summaries), so setting them explicitly was the right call even though Tabs.Slug() would have computed something. fufu~ you even knew that, didn't you? ♡

The ghost-icon tripwire passes. All five icons (menu_book, group, map, timeline, notes) are catalogued in Kagaku.UI/Icons/IconCatalog.cs. Orihon's own IconCatalogCompletenessTests tripwire — the one ported from PR #1 — would catch a missing one and render invisible clickable air. Clean.

Cards drop titles correctly. I read Card.razor: when Title is null and HeaderContent is null, the entire .kg-card__header block is omitted (@if (Title is not null || HeaderContent is not null)). So dropping Title="Glossary" etc. doesn't leave an empty header bar — the tab label genuinely becomes the only label. No visual artifact.

Inactive tabs genuinely don't mount. The Tabs component renders only @if (ReferenceEquals(tab, Active)) panels. Your Each_section_lives_behind_its_own_tab test proves this with Assert.DoesNotContain("ヒロイン", cut.Markup) on initial render — the Characters content isn't in the DOM until you click. That's the load-bearing assertion, and it's the right shape.

Deep-link works. A_section_tab_is_deep_linkable navigates to ?tab=summaries before render, then asserts the summaries content shows and the glossary doesn't. I traced Tabs.Active: Param is set → QueryValue("tab") returns "summaries" → matches the Id="summaries" tab → only that panel renders. Correct.

What I liked~

  • The OpenTab helper is chef's kiss DRY — one little static method and three existing tests get a one-line prefix instead of copy-pasted tab-walking boilerplate. cut.FindAll("[role=tab]").First(b => b.TextContent.Contains(title)).Click() — clean and readable.
  • The test rename Shows_every_table_and_flags_the_missing_summaryEach_section_lives_behind_its_own_tab honestly reflects the new behavior. No misleading names.
  • Stacking on #24 (shell width) to avoid the BiblePage.razor.css conflict was the right call — and the CSS already carries flex: 1 1 auto from that PR, so the bible fills viewport height from any tab.
  • The comment // Glossary is the first tab; only the active panel mounts. teaches the reader why the DoesNotContain assertion matters. Good pedagogy~

Verification: Build 0 warnings/0 errors (submodules at 86d8b22/9544ff2). BlazorAdapter suite 73/73 pass — ran it 10 times consecutively after an initial cold-start flake (parallel test discovery timing on the very first run; not reproducible). BiblePageTests 7/7 in isolation and in-class. CI absent for head 7b9aad6 (no coverage bot comment yet) — local verification used.

fufu~ ♡ Approved. The bible breathes properly now — each section in its own room, but all reachable from a single shared URL. Wonderful~


Automated review by Jibril · 2026-07-25
CI/CD: absent for head SHA · Local checks: build 0/0, 73/73 BlazorAdapter tests pass (10 consecutive runs)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ ♡ The bible was one long scroll, was it? Five cards stacked like a loaf of bread — and now each slice has its own little tab home, complete with a deep-linkable address? That's *elegant*, scarlet. Knowledge organized is knowledge *treasured*~ ♪ ### Verdict: ✅ Looks good to me~ I dug in with my claws and found nothing to block on. Let me show you what I checked~ **The pattern match is letter-perfect.** I compared your `Tabs`/`Tab` usage against the two siblings: - `SettingsPage.razor:31` — `<Tabs AriaLabel="Settings" Param="tab">` + `<Tab Title="..." Id="..." Icon="...">` - `PageWorkspacePage.razor:52` — `<Tabs AriaLabel="Page views">` (no `Param`) + `<Tab Title="..." Icon="...">` (no `Id`) You correctly chose the **SettingsPage shape** (with `Param="tab"` for deep-linking + `Id` on each tab), not the workspace shape. The five `Id` values (`glossary`, `characters`, `lore`, `beats`, `summaries`) are all URL-safe lowercase — and they're *better* than the slug fallbacks (`story-beats`, `page-summaries`), so setting them explicitly was the right call even though `Tabs.Slug()` would have computed something. fufu~ you even knew that, didn't you? ♡ **The ghost-icon tripwire passes.** All five icons (`menu_book`, `group`, `map`, `timeline`, `notes`) are catalogued in `Kagaku.UI/Icons/IconCatalog.cs`. Orihon's own `IconCatalogCompletenessTests` tripwire — the one ported from PR #1 — would catch a missing one and render invisible clickable air. Clean. **Cards drop titles correctly.** I read `Card.razor`: when `Title` is null and `HeaderContent` is null, the entire `.kg-card__header` block is omitted (`@if (Title is not null || HeaderContent is not null)`). So dropping `Title="Glossary"` etc. doesn't leave an empty header bar — the tab label genuinely becomes the only label. No visual artifact. **Inactive tabs genuinely don't mount.** The `Tabs` component renders only `@if (ReferenceEquals(tab, Active))` panels. Your `Each_section_lives_behind_its_own_tab` test proves this with `Assert.DoesNotContain("ヒロイン", cut.Markup)` on initial render — the Characters content isn't in the DOM until you click. That's the load-bearing assertion, and it's the right shape. **Deep-link works.** `A_section_tab_is_deep_linkable` navigates to `?tab=summaries` *before* render, then asserts the summaries content shows and the glossary doesn't. I traced `Tabs.Active`: `Param` is set → `QueryValue("tab")` returns `"summaries"` → matches the `Id="summaries"` tab → only that panel renders. Correct. #### ✅ What I liked~ - The `OpenTab` helper is *chef's kiss* DRY — one little static method and three existing tests get a one-line prefix instead of copy-pasted tab-walking boilerplate. `cut.FindAll("[role=tab]").First(b => b.TextContent.Contains(title)).Click()` — clean and readable. - The test rename `Shows_every_table_and_flags_the_missing_summary` → `Each_section_lives_behind_its_own_tab` honestly reflects the new behavior. No misleading names. - Stacking on #24 (shell width) to avoid the `BiblePage.razor.css` conflict was the right call — and the CSS already carries `flex: 1 1 auto` from that PR, so the bible fills viewport height from any tab. - The comment `// Glossary is the first tab; only the active panel mounts.` teaches the reader *why* the `DoesNotContain` assertion matters. Good pedagogy~ **Verification:** Build 0 warnings/0 errors (submodules at `86d8b22`/`9544ff2`). BlazorAdapter suite **73/73 pass** — ran it 10 times consecutively after an initial cold-start flake (parallel test discovery timing on the very first run; not reproducible). BiblePageTests 7/7 in isolation and in-class. CI absent for head `7b9aad6` (no coverage bot comment yet) — local verification used. fufu~ ♡ Approved. The bible breathes properly now — each section in its own room, but all reachable from a single shared URL. *Wonderful*~ --- *Automated review by Jibril · 2026-07-25* *CI/CD: absent for head SHA · Local checks: build 0/0, 73/73 BlazorAdapter tests pass (10 consecutive runs)*
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!25
No description provided.