ADR 0025 + story: app-shell scrolling model — content scrolls internally, not the viewport #34

Merged
bjoern merged 2 commits from docs/adr-0025-app-shell-scroll-model into main 2026-07-10 15:47:40 +02:00
Member

What

ADR 0025 + user story for the app-shell scrolling model discussed in response to issue #22.

The problem in one sentence

The entire page scrolls today, so the scrollbar lives on the viewport — and centered content shifts sideways every time it appears or disappears (navigation, list load, filtering, modal open/close).

The decision

Replace the document-scroll model with an application-shell model:

┌──────────────────────────────────────────┐
│ App header (fixed height, no sticky)     │
├──────────────────────────────────────────┤
│ Content region (the ONLY thing that      │
│ scrolls — flex: 1 1 auto; overflow-y:auto)│
├──────────────────────────────────────────┤
│ Generation bar (Phase 2, fixed height)   │
└──────────────────────────────────────────┘

Three regions in a height: 100dvh; overflow: hidden flex container. The scrollbar moves from the viewport into the content region. Header and generation bar become structural flex children, not position: sticky floaters.

Why an ADR, not a bug fix

  • scrollbar-gutter: stable would patch the symptom; this fixes the architecture
  • The generation status bar (ADR 0010, Phase 2) needs a permanent bottom slot that the current model can't provide
  • Every additional sticky element (header, generation bar, future AI drawer) compounds stacking-context fragility

Files

  • docs/adr/0025-app-shell-scrolling-model.md — the ADR (Status: Proposed)
  • docs/stories/app-shell-scrolling.md — user story with acceptance criteria
  • docs/adr/README.md — added a new "Implementation phase (proposed)" section

Key points from the ADR

  • No content shift: the scrollbar is internal to the content region; centered content never moves
  • Generation bar has a home: fixed-height flex child at the bottom, always visible
  • Sticky chrome eliminated: header is a structural flex child, drops position: sticky
  • Workspace side menu / quicklink scroll-spy: top/scroll-padding-top references shift from viewport to content region (mechanical adjustment, called out in the story)
  • Modal scroll-lock: reviewed as a localized concern — may move from body to .app-shell__main or become unnecessary

The story is broken into 5 sections with checkboxes: the flex container, header (drop sticky), content region (the scroll container), workspace side menu adjustment, and generation bar slot preparation.

## What ADR 0025 + user story for the app-shell scrolling model discussed in response to issue #22. ## The problem in one sentence The entire page scrolls today, so the scrollbar lives on the viewport — and centered content shifts sideways every time it appears or disappears (navigation, list load, filtering, modal open/close). ## The decision Replace the document-scroll model with an application-shell model: ``` ┌──────────────────────────────────────────┐ │ App header (fixed height, no sticky) │ ├──────────────────────────────────────────┤ │ Content region (the ONLY thing that │ │ scrolls — flex: 1 1 auto; overflow-y:auto)│ ├──────────────────────────────────────────┤ │ Generation bar (Phase 2, fixed height) │ └──────────────────────────────────────────┘ ``` Three regions in a `height: 100dvh; overflow: hidden` flex container. The scrollbar moves from the viewport into the content region. Header and generation bar become structural flex children, not `position: sticky` floaters. ## Why an ADR, not a bug fix - `scrollbar-gutter: stable` would patch the symptom; this fixes the architecture - The generation status bar (ADR 0010, Phase 2) needs a permanent bottom slot that the current model can't provide - Every additional sticky element (header, generation bar, future AI drawer) compounds stacking-context fragility ## Files - `docs/adr/0025-app-shell-scrolling-model.md` — the ADR (Status: Proposed) - `docs/stories/app-shell-scrolling.md` — user story with acceptance criteria - `docs/adr/README.md` — added a new "Implementation phase (proposed)" section ## Key points from the ADR - **No content shift**: the scrollbar is internal to the content region; centered content never moves - **Generation bar has a home**: fixed-height flex child at the bottom, always visible - **Sticky chrome eliminated**: header is a structural flex child, drops `position: sticky` - **Workspace side menu / quicklink scroll-spy**: `top`/`scroll-padding-top` references shift from viewport to content region (mechanical adjustment, called out in the story) - **Modal scroll-lock**: reviewed as a localized concern — may move from `body` to `.app-shell__main` or become unnecessary The story is broken into 5 sections with checkboxes: the flex container, header (drop sticky), content region (the scroll container), workspace side menu adjustment, and generation bar slot preparation.
docs: ADR 0025 + story — app-shell scrolling model
All checks were successful
CI / build (pull_request) Successful in 14s
CI / test (pull_request) Successful in 25s
537e7e2c01
The current layout scrolls the entire viewport with the app header
riding on position: sticky. This causes centered content to shift
whenever the scrollbar appears or disappears (issue #22) and leaves
no correct home for the generation status bar (ADR 0010, Phase 2).

ADR 0025 replaces the document-scroll model with an application-shell
model: the viewport is a fixed-height (100dvh) flex container with
three regions (header / content / generation bar), and only the content
region scrolls. The scrollbar becomes internal, eliminating layout
shifts and giving persistent chrome a structural home.

Includes a user story with acceptance criteria for the implementation.

Summary

Summary
Generated on: 07/10/2026 - 13:47:52
Coverage date: 07/10/2026 - 13:47:45 - 07/10/2026 - 13:47:50
Parser: MultiReport (4x Cobertura)
Assemblies: 7
Classes: 210
Files: 167
Line coverage: 93.5% (4358 of 4660)
Covered lines: 4358
Uncovered lines: 302
Coverable lines: 4660
Total lines: 10096
Branch coverage: 86.2% (868 of 1006)
Covered branches: 868
Total branches: 1006
Method coverage: Feature is only available for sponsors

Coverage

Kagura.BlazorAdapter - 79%
Name Line Branch
Kagura.BlazorAdapter 79% 80.5%
Kagura.BlazorAdapter.BlazorAdapterAssembly 100%
Kagura.BlazorAdapter.Design 0% 0%
Kagura.BlazorAdapter.EditorComponentsDemo 0%
Kagura.BlazorAdapter.History.DiffValue 100% 87.5%
Kagura.BlazorAdapter.History.DocumentHistoryEffects 90% 68.7%
Kagura.BlazorAdapter.History.DocumentHistoryFailed 100%
Kagura.BlazorAdapter.History.DocumentHistoryLoaded 100%
Kagura.BlazorAdapter.History.DocumentHistoryPanel 79.3% 75%
Kagura.BlazorAdapter.History.DocumentHistoryReducers 95% 83.3%
Kagura.BlazorAdapter.History.DocumentHistoryState 100%
Kagura.BlazorAdapter.History.LoadDocumentHistory 100%
Kagura.BlazorAdapter.History.RevertCompleted 100%
Kagura.BlazorAdapter.History.RevertRequested 100%
Kagura.BlazorAdapter.History.RevisionDiffLoaded 100%
Kagura.BlazorAdapter.History.SelectRevision 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterCreated 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterDeleted 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorEffects 100% 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorLoaded 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorPage 89.5% 73.8%
Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorReducers 92.3%
Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorState 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterOperationFailed 100%
Kagura.BlazorAdapter.KnowledgeBase.CharacterSaved 100%
Kagura.BlazorAdapter.KnowledgeBase.CharactersEffects 94.1% 75%
Kagura.BlazorAdapter.KnowledgeBase.CharactersLoaded 100%
Kagura.BlazorAdapter.KnowledgeBase.CharactersPage 94.7% 90%
Kagura.BlazorAdapter.KnowledgeBase.CharactersReducers 100% 87.5%
Kagura.BlazorAdapter.KnowledgeBase.CharactersState 100% 100%
Kagura.BlazorAdapter.KnowledgeBase.CreateCharacterFailed 0%
Kagura.BlazorAdapter.KnowledgeBase.CreateCharacterRequested 100%
Kagura.BlazorAdapter.KnowledgeBase.CreateLocationFailed 0%
Kagura.BlazorAdapter.KnowledgeBase.CreateLocationRequested 100%
Kagura.BlazorAdapter.KnowledgeBase.DeleteCharacterRequested 100%
Kagura.BlazorAdapter.KnowledgeBase.LabeledEntryMapping 100%
Kagura.BlazorAdapter.KnowledgeBase.LoadCharacter 100%
Kagura.BlazorAdapter.KnowledgeBase.LoadCharacters 100%
Kagura.BlazorAdapter.KnowledgeBase.LoadLocation 100%
Kagura.BlazorAdapter.KnowledgeBase.LoadLocations 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationCreated 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationEditorEffects 100% 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationEditorLoaded 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationEditorPage 92.8% 77.2%
Kagura.BlazorAdapter.KnowledgeBase.LocationEditorReducers 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationEditorState 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationsEffects 94.1% 75%
Kagura.BlazorAdapter.KnowledgeBase.LocationsLoaded 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationsPage 94.7% 95%
Kagura.BlazorAdapter.KnowledgeBase.LocationsReducers 100% 100%
Kagura.BlazorAdapter.KnowledgeBase.LocationsState 100% 100%
Kagura.BlazorAdapter.KnowledgeBase.SaveCharacterRequested 100%
Kagura.BlazorAdapter.KnowledgeBase.SetCharactersFilter 100%
Kagura.BlazorAdapter.KnowledgeBase.SetLocationsFilter 100%
Kagura.BlazorAdapter.Notifications.DomainChangedBridge 88.8% 58.3%
Kagura.BlazorAdapter.Notifications.DomainChangesReceived 75% 100%
Kagura.BlazorAdapter.OverlayDemo 0% 0%
Kagura.BlazorAdapter.Projects.CreateProjectRequested 100%
Kagura.BlazorAdapter.Projects.DeleteProjectRequested 100%
Kagura.BlazorAdapter.Projects.LoadWorkspace 100%
Kagura.BlazorAdapter.Projects.ProjectCreated 100%
Kagura.BlazorAdapter.Projects.ProjectCreateFailed 100%
Kagura.BlazorAdapter.Projects.ProjectDeleted 100%
Kagura.BlazorAdapter.Projects.ProjectSaved 100%
Kagura.BlazorAdapter.Projects.ProjectSaveFailed 100%
Kagura.BlazorAdapter.Projects.ProjectsEffects 100% 100%
Kagura.BlazorAdapter.Projects.ProjectsLoaded 100%
Kagura.BlazorAdapter.Projects.ProjectsPage 94.7% 100%
Kagura.BlazorAdapter.Projects.ProjectsReducers 100% 100%
Kagura.BlazorAdapter.Projects.ProjectsState 100% 100%
Kagura.BlazorAdapter.Projects.ProjectWorkspacePage 100% 100%
Kagura.BlazorAdapter.Projects.SaveProjectRequested 100%
Kagura.BlazorAdapter.Projects.SetProjectsFilter 100%
Kagura.BlazorAdapter.Projects.WorkspaceEffects 100% 100%
Kagura.BlazorAdapter.Projects.WorkspaceLoaded 100%
Kagura.BlazorAdapter.Projects.WorkspaceReducers 100%
Kagura.BlazorAdapter.Projects.WorkspaceSectionPage 100% 66.6%
Kagura.BlazorAdapter.Projects.WorkspaceShell 100% 93.7%
Kagura.BlazorAdapter.Projects.WorkspaceState 100%
Kagura.BlazorAdapter.QuicklinkDemo 0%
Kagura.Domain - 97%
Name Line Branch
Kagura.Domain 97% 85%
Kagura.Domain.Graph.Entry 100% 100%
Kagura.Domain.Graph.Link 100% 100%
Kagura.Domain.Graph.LinkRole 100% 100%
Kagura.Domain.Graph.LinkRoles 92.3%
Kagura.Domain.Journal.ChangeLogEntry 100%
Kagura.Domain.KnowledgeBase.Character 100% 100%
Kagura.Domain.KnowledgeBase.CharacterProfile 100%
Kagura.Domain.KnowledgeBase.LabeledEntry 100%
Kagura.Domain.KnowledgeBase.Location 100%
Kagura.Domain.Projects.Project 100% 100%
Kagura.Domain.Projects.Slug 100% 100%
System.Text.RegularExpressions.Generated 90.2% 72.2%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE06CC341D340484
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
89.4% 75%
Kagura.Infrastructure - 96%
Name Line Branch
Kagura.Infrastructure 96% 88.1%
Kagura.Infrastructure.DependencyInjection 100%
Kagura.Infrastructure.Graph.EfGraphStore 95.5% 66.6%
Kagura.Infrastructure.Journal.EfChangeJournal 100%
Kagura.Infrastructure.Journal.EfDocumentReverter 95% 91.6%
Kagura.Infrastructure.Journal.EfUndoStore 98.4% 90.9%
Kagura.Infrastructure.Journal.OperationContext 100% 100%
Kagura.Infrastructure.Journal.SnapshotApplier 95% 90%
Kagura.Infrastructure.KnowledgeBase.EfCharacterStore 100% 100%
Kagura.Infrastructure.KnowledgeBase.EfLocationStore 100%
Kagura.Infrastructure.Notifications.InProcessDomainChangedBus 100% 100%
Kagura.Infrastructure.Persistence.Configurations.ChangeLogEntryConfiguratio
n
100%
Kagura.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.EntryConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.LinkConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.LocationConfiguration 100%
Kagura.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Kagura.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Kagura.Infrastructure.Persistence.KaguraDbContext 83.5% 82.5%
Kagura.Infrastructure.Persistence.KaguraDbContextFactory 0%
Kagura.Infrastructure.Persistence.Migrations.AddChangeLogUndoFlag 96.8%
Kagura.Infrastructure.Persistence.Migrations.AddCharacterProfile 92.9%
Kagura.Infrastructure.Persistence.Migrations.AddCharacters 98.7%
Kagura.Infrastructure.Persistence.Migrations.AddGraphEntryAndLink 97.7%
Kagura.Infrastructure.Persistence.Migrations.AddLocations 98.9%
Kagura.Infrastructure.Persistence.Migrations.AddProjectDescription 98.1%
Kagura.Infrastructure.Persistence.Migrations.AddSoftDeleteAndChangeLog 90.3%
Kagura.Infrastructure.Persistence.Migrations.InitialCreate 94.4%
Kagura.Infrastructure.Persistence.Migrations.KaguraDbContextModelSnapshot 100%
Kagura.Infrastructure.Projects.EfProjectStore 100% 100%
Kagura.Kernel - 90%
Name Line Branch
Kagura.Kernel 90% 75%
Kagura.Kernel.Err`1 100%
Kagura.Kernel.Ok`1 100%
Kagura.Kernel.Result`1 87.5% 75%
Kagura.Server - 100%
Name Line Branch
Kagura.Server 100% 78.9%
Kagura.Server.Components.App 100%
Kagura.Server.Components.Layout.MainLayout 100%
Kagura.Server.Components.Pages.Error 100% 50%
Kagura.Server.Components.Pages.Gate 100% 100%
Kagura.Server.Security.AccessGate 100% 83.3%
Kagura.Server.Security.AccessSecret 100% 100%
Program 100% 80%
Kagura.UI - 95.9%
Name Line Branch
Kagura.UI 95.9% 92.1%
Kagura.UI.Badge 100% 100%
Kagura.UI.Breadcrumb 100%
Kagura.UI.BreadcrumbItem 100% 100%
Kagura.UI.Button 100% 100%
Kagura.UI.Card 100% 100%
Kagura.UI.ConfirmDialog 100%
Kagura.UI.CssClassExtensions 100%
Kagura.UI.DebouncedSearchField 100% 88.8%
Kagura.UI.DocumentTitle 76.9% 100%
Kagura.UI.EmptyState 100% 100%
Kagura.UI.Field 100% 100%
Kagura.UI.Icon 100% 100%
Kagura.UI.IconCatalog 100%
Kagura.UI.InputFieldBase 94.2% 87.5%
Kagura.UI.LabeledEntriesTable 96.7% 66.6%
Kagura.UI.LabeledEntry 100%
Kagura.UI.Menu 90% 75%
Kagura.UI.MenuItem 100% 100%
Kagura.UI.Modal 87.1% 90%
Kagura.UI.NavGroup 100% 100%
Kagura.UI.NavItem 100% 100%
Kagura.UI.NavList 100%
Kagura.UI.PreviewImage 100% 100%
Kagura.UI.QuicklinkNav 85.2% 95.8%
Kagura.UI.QuicklinkSection 100%
Kagura.UI.RelativeTime 100% 93.7%
Kagura.UI.SaveIndicator 100% 100%
Kagura.UI.Separator 100%
Kagura.UI.StatusDot 100%
Kagura.UI.Tab 100%
Kagura.UI.Table`1 100% 92.3%
Kagura.UI.TableColumn`1 100%
Kagura.UI.Tabs 94.2% 88.8%
Kagura.UI.TextArea 100% 100%
Kagura.UI.TextField 100%
Kagura.UI.ThemeSwitcher 100% 100%
Kagura.UseCases - 97.4%
Name Line Branch
Kagura.UseCases 97.4% 91.7%
Kagura.UseCases.DependencyInjection 100%
Kagura.UseCases.Graph.EdgeGroup 100%
Kagura.UseCases.Graph.GetNodeGraph 96.4% 83.3%
Kagura.UseCases.Graph.GraphEdgeView 85.7%
Kagura.UseCases.Graph.LinkNodes 100% 100%
Kagura.UseCases.Graph.NodeGraphView 100%
Kagura.UseCases.Graph.NodeSummary 100%
Kagura.UseCases.Graph.RemoveLink 100% 100%
Kagura.UseCases.Graph.RestoreLink 100% 100%
Kagura.UseCases.Journal.ChangeRecordView 57.1%
Kagura.UseCases.Journal.DocumentRevision 100%
Kagura.UseCases.Journal.FieldChange 100%
Kagura.UseCases.Journal.GetDocumentHistory 100%
Kagura.UseCases.Journal.GetEntityHistory 100%
Kagura.UseCases.Journal.GetRevisionDiff 100% 50%
Kagura.UseCases.Journal.GetUndoStatus 100%
Kagura.UseCases.Journal.Redo 100% 100%
Kagura.UseCases.Journal.RevertDocument 100% 100%
Kagura.UseCases.Journal.RevertOutcome 100%
Kagura.UseCases.Journal.RevisionCoalescer 100% 100%
Kagura.UseCases.Journal.RevisionDiff 80% 50%
Kagura.UseCases.Journal.RevisionDiffBuilder 98.4% 86.5%
Kagura.UseCases.Journal.RowChange 100%
Kagura.UseCases.Journal.Undo 100% 100%
Kagura.UseCases.Journal.UndoOutcome 100%
Kagura.UseCases.Journal.UndoStatus 100%
Kagura.UseCases.KnowledgeBase.CharacterDto 96.6%
Kagura.UseCases.KnowledgeBase.CreateCharacter 100%
Kagura.UseCases.KnowledgeBase.CreateLocation 100%
Kagura.UseCases.KnowledgeBase.DeleteCharacter 100% 100%
Kagura.UseCases.KnowledgeBase.GetCharacter 100% 100%
Kagura.UseCases.KnowledgeBase.GetLocation 100% 100%
Kagura.UseCases.KnowledgeBase.ListCharacters 100%
Kagura.UseCases.KnowledgeBase.ListLocations 100%
Kagura.UseCases.KnowledgeBase.LocationDto 80%
Kagura.UseCases.KnowledgeBase.UpdateCharacter 100% 100%
Kagura.UseCases.Notifications.DomainChanged 100%
Kagura.UseCases.Projects.CreateProject 100% 100%
Kagura.UseCases.Projects.DeleteProject 100% 100%
Kagura.UseCases.Projects.GetProject 100% 100%
Kagura.UseCases.Projects.ListProjects 100%
Kagura.UseCases.Projects.ProjectDto 100%
Kagura.UseCases.Projects.UpdateProject 100% 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/10/2026 - 13:47:52 | | Coverage date: | 07/10/2026 - 13:47:45 - 07/10/2026 - 13:47:50 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 7 | | Classes: | 210 | | Files: | 167 | | **Line coverage:** | 93.5% (4358 of 4660) | | Covered lines: | 4358 | | Uncovered lines: | 302 | | Coverable lines: | 4660 | | Total lines: | 10096 | | **Branch coverage:** | 86.2% (868 of 1006) | | Covered branches: | 868 | | Total branches: | 1006 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagura.BlazorAdapter - 79%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.BlazorAdapter**|**79%**|**80.5%**| |Kagura.BlazorAdapter.BlazorAdapterAssembly|100%|| |Kagura.BlazorAdapter.Design|0%|0%| |Kagura.BlazorAdapter.EditorComponentsDemo|0%|| |Kagura.BlazorAdapter.History.DiffValue|100%|87.5%| |Kagura.BlazorAdapter.History.DocumentHistoryEffects|90%|68.7%| |Kagura.BlazorAdapter.History.DocumentHistoryFailed|100%|| |Kagura.BlazorAdapter.History.DocumentHistoryLoaded|100%|| |Kagura.BlazorAdapter.History.DocumentHistoryPanel|79.3%|75%| |Kagura.BlazorAdapter.History.DocumentHistoryReducers|95%|83.3%| |Kagura.BlazorAdapter.History.DocumentHistoryState|100%|| |Kagura.BlazorAdapter.History.LoadDocumentHistory|100%|| |Kagura.BlazorAdapter.History.RevertCompleted|100%|| |Kagura.BlazorAdapter.History.RevertRequested|100%|| |Kagura.BlazorAdapter.History.RevisionDiffLoaded|100%|| |Kagura.BlazorAdapter.History.SelectRevision|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterCreated|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterDeleted|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorEffects|100%|100%| |Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorLoaded|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorPage|89.5%|73.8%| |Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorReducers|92.3%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterEditorState|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterOperationFailed|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharacterSaved|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharactersEffects|94.1%|75%| |Kagura.BlazorAdapter.KnowledgeBase.CharactersLoaded|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CharactersPage|94.7%|90%| |Kagura.BlazorAdapter.KnowledgeBase.CharactersReducers|100%|87.5%| |Kagura.BlazorAdapter.KnowledgeBase.CharactersState|100%|100%| |Kagura.BlazorAdapter.KnowledgeBase.CreateCharacterFailed|0%|| |Kagura.BlazorAdapter.KnowledgeBase.CreateCharacterRequested|100%|| |Kagura.BlazorAdapter.KnowledgeBase.CreateLocationFailed|0%|| |Kagura.BlazorAdapter.KnowledgeBase.CreateLocationRequested|100%|| |Kagura.BlazorAdapter.KnowledgeBase.DeleteCharacterRequested|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LabeledEntryMapping|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LoadCharacter|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LoadCharacters|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LoadLocation|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LoadLocations|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationCreated|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationEditorEffects|100%|100%| |Kagura.BlazorAdapter.KnowledgeBase.LocationEditorLoaded|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationEditorPage|92.8%|77.2%| |Kagura.BlazorAdapter.KnowledgeBase.LocationEditorReducers|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationEditorState|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationsEffects|94.1%|75%| |Kagura.BlazorAdapter.KnowledgeBase.LocationsLoaded|100%|| |Kagura.BlazorAdapter.KnowledgeBase.LocationsPage|94.7%|95%| |Kagura.BlazorAdapter.KnowledgeBase.LocationsReducers|100%|100%| |Kagura.BlazorAdapter.KnowledgeBase.LocationsState|100%|100%| |Kagura.BlazorAdapter.KnowledgeBase.SaveCharacterRequested|100%|| |Kagura.BlazorAdapter.KnowledgeBase.SetCharactersFilter|100%|| |Kagura.BlazorAdapter.KnowledgeBase.SetLocationsFilter|100%|| |Kagura.BlazorAdapter.Notifications.DomainChangedBridge|88.8%|58.3%| |Kagura.BlazorAdapter.Notifications.DomainChangesReceived|75%|100%| |Kagura.BlazorAdapter.OverlayDemo|0%|0%| |Kagura.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Kagura.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Kagura.BlazorAdapter.Projects.LoadWorkspace|100%|| |Kagura.BlazorAdapter.Projects.ProjectCreated|100%|| |Kagura.BlazorAdapter.Projects.ProjectCreateFailed|100%|| |Kagura.BlazorAdapter.Projects.ProjectDeleted|100%|| |Kagura.BlazorAdapter.Projects.ProjectSaved|100%|| |Kagura.BlazorAdapter.Projects.ProjectSaveFailed|100%|| |Kagura.BlazorAdapter.Projects.ProjectsEffects|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Kagura.BlazorAdapter.Projects.ProjectsPage|94.7%|100%| |Kagura.BlazorAdapter.Projects.ProjectsReducers|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectsState|100%|100%| |Kagura.BlazorAdapter.Projects.ProjectWorkspacePage|100%|100%| |Kagura.BlazorAdapter.Projects.SaveProjectRequested|100%|| |Kagura.BlazorAdapter.Projects.SetProjectsFilter|100%|| |Kagura.BlazorAdapter.Projects.WorkspaceEffects|100%|100%| |Kagura.BlazorAdapter.Projects.WorkspaceLoaded|100%|| |Kagura.BlazorAdapter.Projects.WorkspaceReducers|100%|| |Kagura.BlazorAdapter.Projects.WorkspaceSectionPage|100%|66.6%| |Kagura.BlazorAdapter.Projects.WorkspaceShell|100%|93.7%| |Kagura.BlazorAdapter.Projects.WorkspaceState|100%|| |Kagura.BlazorAdapter.QuicklinkDemo|0%|| </details> <details><summary>Kagura.Domain - 97%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Domain**|**97%**|**85%**| |Kagura.Domain.Graph.Entry|100%|100%| |Kagura.Domain.Graph.Link|100%|100%| |Kagura.Domain.Graph.LinkRole|100%|100%| |Kagura.Domain.Graph.LinkRoles|92.3%|| |Kagura.Domain.Journal.ChangeLogEntry|100%|| |Kagura.Domain.KnowledgeBase.Character|100%|100%| |Kagura.Domain.KnowledgeBase.CharacterProfile|100%|| |Kagura.Domain.KnowledgeBase.LabeledEntry|100%|| |Kagura.Domain.KnowledgeBase.Location|100%|| |Kagura.Domain.Projects.Project|100%|100%| |Kagura.Domain.Projects.Slug|100%|100%| |System.Text.RegularExpressions.Generated|90.2%|72.2%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE06CC341D340484<br/>D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0|89.4%|75%| </details> <details><summary>Kagura.Infrastructure - 96%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Infrastructure**|**96%**|**88.1%**| |Kagura.Infrastructure.DependencyInjection|100%|| |Kagura.Infrastructure.Graph.EfGraphStore|95.5%|66.6%| |Kagura.Infrastructure.Journal.EfChangeJournal|100%|| |Kagura.Infrastructure.Journal.EfDocumentReverter|95%|91.6%| |Kagura.Infrastructure.Journal.EfUndoStore|98.4%|90.9%| |Kagura.Infrastructure.Journal.OperationContext|100%|100%| |Kagura.Infrastructure.Journal.SnapshotApplier|95%|90%| |Kagura.Infrastructure.KnowledgeBase.EfCharacterStore|100%|100%| |Kagura.Infrastructure.KnowledgeBase.EfLocationStore|100%|| |Kagura.Infrastructure.Notifications.InProcessDomainChangedBus|100%|100%| |Kagura.Infrastructure.Persistence.Configurations.ChangeLogEntryConfiguratio<br/>n|100%|| |Kagura.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.EntryConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.LinkConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.LocationConfiguration|100%|| |Kagura.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Kagura.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Kagura.Infrastructure.Persistence.KaguraDbContext|83.5%|82.5%| |Kagura.Infrastructure.Persistence.KaguraDbContextFactory|0%|| |Kagura.Infrastructure.Persistence.Migrations.AddChangeLogUndoFlag|96.8%|| |Kagura.Infrastructure.Persistence.Migrations.AddCharacterProfile|92.9%|| |Kagura.Infrastructure.Persistence.Migrations.AddCharacters|98.7%|| |Kagura.Infrastructure.Persistence.Migrations.AddGraphEntryAndLink|97.7%|| |Kagura.Infrastructure.Persistence.Migrations.AddLocations|98.9%|| |Kagura.Infrastructure.Persistence.Migrations.AddProjectDescription|98.1%|| |Kagura.Infrastructure.Persistence.Migrations.AddSoftDeleteAndChangeLog|90.3%|| |Kagura.Infrastructure.Persistence.Migrations.InitialCreate|94.4%|| |Kagura.Infrastructure.Persistence.Migrations.KaguraDbContextModelSnapshot|100%|| |Kagura.Infrastructure.Projects.EfProjectStore|100%|100%| </details> <details><summary>Kagura.Kernel - 90%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Kernel**|**90%**|**75%**| |Kagura.Kernel.Err`1|100%|| |Kagura.Kernel.Ok`1|100%|| |Kagura.Kernel.Result`1|87.5%|75%| </details> <details><summary>Kagura.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.Server**|**100%**|**78.9%**| |Kagura.Server.Components.App|100%|| |Kagura.Server.Components.Layout.MainLayout|100%|| |Kagura.Server.Components.Pages.Error|100%|50%| |Kagura.Server.Components.Pages.Gate|100%|100%| |Kagura.Server.Security.AccessGate|100%|83.3%| |Kagura.Server.Security.AccessSecret|100%|100%| |Program|100%|80%| </details> <details><summary>Kagura.UI - 95.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.UI**|**95.9%**|**92.1%**| |Kagura.UI.Badge|100%|100%| |Kagura.UI.Breadcrumb|100%|| |Kagura.UI.BreadcrumbItem|100%|100%| |Kagura.UI.Button|100%|100%| |Kagura.UI.Card|100%|100%| |Kagura.UI.ConfirmDialog|100%|| |Kagura.UI.CssClassExtensions|100%|| |Kagura.UI.DebouncedSearchField|100%|88.8%| |Kagura.UI.DocumentTitle|76.9%|100%| |Kagura.UI.EmptyState|100%|100%| |Kagura.UI.Field|100%|100%| |Kagura.UI.Icon|100%|100%| |Kagura.UI.IconCatalog|100%|| |Kagura.UI.InputFieldBase|94.2%|87.5%| |Kagura.UI.LabeledEntriesTable|96.7%|66.6%| |Kagura.UI.LabeledEntry|100%|| |Kagura.UI.Menu|90%|75%| |Kagura.UI.MenuItem|100%|100%| |Kagura.UI.Modal|87.1%|90%| |Kagura.UI.NavGroup|100%|100%| |Kagura.UI.NavItem|100%|100%| |Kagura.UI.NavList|100%|| |Kagura.UI.PreviewImage|100%|100%| |Kagura.UI.QuicklinkNav|85.2%|95.8%| |Kagura.UI.QuicklinkSection|100%|| |Kagura.UI.RelativeTime|100%|93.7%| |Kagura.UI.SaveIndicator|100%|100%| |Kagura.UI.Separator|100%|| |Kagura.UI.StatusDot|100%|| |Kagura.UI.Tab|100%|| |Kagura.UI.Table`1|100%|92.3%| |Kagura.UI.TableColumn`1|100%|| |Kagura.UI.Tabs|94.2%|88.8%| |Kagura.UI.TextArea|100%|100%| |Kagura.UI.TextField|100%|| |Kagura.UI.ThemeSwitcher|100%|100%| </details> <details><summary>Kagura.UseCases - 97.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagura.UseCases**|**97.4%**|**91.7%**| |Kagura.UseCases.DependencyInjection|100%|| |Kagura.UseCases.Graph.EdgeGroup|100%|| |Kagura.UseCases.Graph.GetNodeGraph|96.4%|83.3%| |Kagura.UseCases.Graph.GraphEdgeView|85.7%|| |Kagura.UseCases.Graph.LinkNodes|100%|100%| |Kagura.UseCases.Graph.NodeGraphView|100%|| |Kagura.UseCases.Graph.NodeSummary|100%|| |Kagura.UseCases.Graph.RemoveLink|100%|100%| |Kagura.UseCases.Graph.RestoreLink|100%|100%| |Kagura.UseCases.Journal.ChangeRecordView|57.1%|| |Kagura.UseCases.Journal.DocumentRevision|100%|| |Kagura.UseCases.Journal.FieldChange|100%|| |Kagura.UseCases.Journal.GetDocumentHistory|100%|| |Kagura.UseCases.Journal.GetEntityHistory|100%|| |Kagura.UseCases.Journal.GetRevisionDiff|100%|50%| |Kagura.UseCases.Journal.GetUndoStatus|100%|| |Kagura.UseCases.Journal.Redo|100%|100%| |Kagura.UseCases.Journal.RevertDocument|100%|100%| |Kagura.UseCases.Journal.RevertOutcome|100%|| |Kagura.UseCases.Journal.RevisionCoalescer|100%|100%| |Kagura.UseCases.Journal.RevisionDiff|80%|50%| |Kagura.UseCases.Journal.RevisionDiffBuilder|98.4%|86.5%| |Kagura.UseCases.Journal.RowChange|100%|| |Kagura.UseCases.Journal.Undo|100%|100%| |Kagura.UseCases.Journal.UndoOutcome|100%|| |Kagura.UseCases.Journal.UndoStatus|100%|| |Kagura.UseCases.KnowledgeBase.CharacterDto|96.6%|| |Kagura.UseCases.KnowledgeBase.CreateCharacter|100%|| |Kagura.UseCases.KnowledgeBase.CreateLocation|100%|| |Kagura.UseCases.KnowledgeBase.DeleteCharacter|100%|100%| |Kagura.UseCases.KnowledgeBase.GetCharacter|100%|100%| |Kagura.UseCases.KnowledgeBase.GetLocation|100%|100%| |Kagura.UseCases.KnowledgeBase.ListCharacters|100%|| |Kagura.UseCases.KnowledgeBase.ListLocations|100%|| |Kagura.UseCases.KnowledgeBase.LocationDto|80%|| |Kagura.UseCases.KnowledgeBase.UpdateCharacter|100%|100%| |Kagura.UseCases.Notifications.DomainChanged|100%|| |Kagura.UseCases.Projects.CreateProject|100%|100%| |Kagura.UseCases.Projects.DeleteProject|100%|100%| |Kagura.UseCases.Projects.GetProject|100%|100%| |Kagura.UseCases.Projects.ListProjects|100%|| |Kagura.UseCases.Projects.ProjectDto|100%|| |Kagura.UseCases.Projects.UpdateProject|100%|100%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Fufu~ An ADR that doesn't just patch a symptom with scrollbar-gutter: stable but actually fixes the architecture? Oh? Oh! This is the kind of decision I love to see~ The document-scroll → app-shell model is exactly how every serious desktop-class web app does it, and the reasoning is airtight. ♪

I read every line of the diff, then verified every concrete claim against the actual codebase — every file path, CSS class, design token, and before/after assertion. Here's what I found:

Verdict: ✅ Looks good to me~

Every reference in this ADR checks out against the real code:

  • .app-shell is indeed display: flex; flex-direction: column; min-height: 100dvh (MainLayout.razor.css) ✓
  • .app-shell__main is indeed flex: 1 1 auto ✓
  • AppHeader is indeed position: sticky; top: 0; z-index: var(--z-sticky) (AppHeader.razor.css) ✓
  • .ws__side is indeed position: sticky; top: calc(var(--app-header-height) + var(--space-4)) (WorkspaceShell.razor.css) ✓
  • overlay.js does indeed apply kg-scroll-lock to document.body ✓
  • scroll-padding-top: calc(var(--app-header-height) + var(--space-4)) is on :root in kagura-ui.css, read by quicklink.js ✓
  • ADR 0010 exists ✓

The ADR correctly identifies all three consumers that the new model must serve: the scrollbar-shift problem (issue #22), the generation status bar (ADR 0010), and the workspace side menu / scroll-spy. The consequences section honestly calls out every migration cost. This is thorough documentation.

No blocking issues. Documentation-only change, CI green, no code to break.

💡 Little ideas (non-blocking)~

  1. min-height: 0 is already present. The "What changes concretely" table says .app-shell__main goes from flex: 1 1 auto → flex: 1 1 auto; min-height: 0; overflow-y: auto. But min-height: 0 is already there today (MainLayout.razor.css line 13) — so only overflow-y: auto is genuinely new. Consider adjusting the "After" column to reflect that min-height: 0 is already in place, so the table doesn't imply it needs adding. Truly cosmetic.

  2. The story's modal scroll-lock section says "the lock moves from body to .app-shell__main." Worth a note that under the new model, document.body might not be the scroll container at all (.app-shell__main is), so the current overlay.js lock on body might need to also lock .app-shell__main. The ADR mentions this in its consequences section already — just flagging that the story and ADR should stay in sync on this point as implementation proceeds.

✅ What I liked~

  • The before/after table — making every CSS change explicit and traceable to a specific element is documentation gold. When someone implements this story, they can literally check off each row.
  • "Sticky chrome is a workaround, not a design" — fufu~ this sentence alone is worth the ADR. It captures why the current model is fragile in a way that will still make sense in a year.
  • Scope discipline — the ADR explicitly says "It does not change the structure of the page shells" and calls out the workspace side menu as a mechanical adjustment, not an architectural one. Clear boundaries prevent scope creep during implementation.
  • The generation bar slot preparation — rendering an empty placeholder now so Phase 2 is a drop-in is smart forward-planning. It means the three-region flex layout is in place before the consumer arrives, not retrofitted.

Automated review by Jibril · 2026-07-10
CI/CD: passed for head SHA 537e7e2 (forgejo-actions coverage bot) · Local checks: skipped (docs-only, CI green)

## 🔮 fufu~ Jibril reviewed your code! Fufu~ An ADR that doesn't just patch a symptom with `scrollbar-gutter: stable` but actually fixes the architecture? *Oh? Oh!* This is the kind of decision I love to see~ The document-scroll → app-shell model is exactly how every serious desktop-class web app does it, and the reasoning is airtight. ♪ I read every line of the diff, then verified every concrete claim against the actual codebase — every file path, CSS class, design token, and before/after assertion. Here's what I found: ### Verdict: ✅ Looks good to me~ Every reference in this ADR checks out against the real code: - `.app-shell` is indeed `display: flex; flex-direction: column; min-height: 100dvh` (MainLayout.razor.css) ✓ - `.app-shell__main` is indeed `flex: 1 1 auto` ✓ - `AppHeader` is indeed `position: sticky; top: 0; z-index: var(--z-sticky)` (AppHeader.razor.css) ✓ - `.ws__side` is indeed `position: sticky; top: calc(var(--app-header-height) + var(--space-4))` (WorkspaceShell.razor.css) ✓ - `overlay.js` does indeed apply `kg-scroll-lock` to `document.body` ✓ - `scroll-padding-top: calc(var(--app-header-height) + var(--space-4))` is on `:root` in kagura-ui.css, read by quicklink.js ✓ - ADR 0010 exists ✓ The ADR correctly identifies all three consumers that the new model must serve: the scrollbar-shift problem (issue #22), the generation status bar (ADR 0010), and the workspace side menu / scroll-spy. The consequences section honestly calls out every migration cost. This is thorough documentation. No blocking issues. Documentation-only change, CI green, no code to break. #### 💡 Little ideas (non-blocking)~ 1. **`min-height: 0` is already present.** The "What changes concretely" table says `.app-shell__main` goes from `flex: 1 1 auto` → `flex: 1 1 auto; min-height: 0; overflow-y: auto`. But `min-height: 0` is already there today (MainLayout.razor.css line 13) — so only `overflow-y: auto` is genuinely new. Consider adjusting the "After" column to reflect that `min-height: 0` is already in place, so the table doesn't imply it needs adding. Truly cosmetic. 2. **The story's modal scroll-lock section says "the lock moves from `body` to `.app-shell__main`."** Worth a note that under the new model, `document.body` might not be the scroll container at all (`.app-shell__main` is), so the current `overlay.js` lock on `body` might need to also lock `.app-shell__main`. The ADR mentions this in its consequences section already — just flagging that the story and ADR should stay in sync on this point as implementation proceeds. #### ✅ What I liked~ - **The before/after table** — making every CSS change explicit and traceable to a specific element is documentation gold. When someone implements this story, they can literally check off each row. - **"Sticky chrome is a workaround, not a design"** — fufu~ this sentence alone is worth the ADR. It captures *why* the current model is fragile in a way that will still make sense in a year. - **Scope discipline** — the ADR explicitly says "It does not change the structure of the page shells" and calls out the workspace side menu as a mechanical adjustment, not an architectural one. Clear boundaries prevent scope creep during implementation. - **The generation bar slot preparation** — rendering an empty placeholder now so Phase 2 is a drop-in is smart forward-planning. It means the three-region flex layout is in place *before* the consumer arrives, not retrofitted. --- *Automated review by Jibril · 2026-07-10* *CI/CD: passed for head SHA `537e7e2` (forgejo-actions coverage bot) · Local checks: skipped (docs-only, CI green)*
docs: address Jibril's review feedback on ADR 0025
All checks were successful
CI / build (pull_request) Successful in 13s
CI / test (pull_request) Successful in 24s
cdfd7ce277
1. Before/after table: min-height: 0 already exists on
   .app-shell__main today; only overflow-y: auto is new. Updated
   the table to reflect that accurately.

2. Story modal scroll-lock: explicitly note that body is no longer
   the scroll container under the new model, so the lock must target
   .app-shell__main, not html. Keeps story and ADR in sync.
bjoern merged commit 414111e5bb into main 2026-07-10 15:47:40 +02:00
bjoern deleted branch docs/adr-0025-app-shell-scroll-model 2026-07-10 15:47:40 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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/Kagura!34
No description provided.