diff --git a/RpgRoller/Components/Pages/AdminWorkspaceContent.razor b/RpgRoller/Components/Pages/AdminWorkspaceContent.razor index e494198..96eda03 100644 --- a/RpgRoller/Components/Pages/AdminWorkspaceContent.razor +++ b/RpgRoller/Components/Pages/AdminWorkspaceContent.razor @@ -17,7 +17,7 @@

User Management

- @if (Workspace.State.IsAdminDataLoading) + @if (IsAdminDataLoading) {

Loading users...

} @@ -65,4 +65,6 @@ @code { [Parameter, EditorRequired] public WorkspacePageContext Workspace { get; set; } = null!; + + private bool IsAdminDataLoading => !Workspace.HasSessionInitialized || Workspace.State.IsAdminDataLoading; } diff --git a/RpgRoller/Components/Pages/PlayWorkspaceContent.razor b/RpgRoller/Components/Pages/PlayWorkspaceContent.razor index 2e0754a..80482a2 100644 --- a/RpgRoller/Components/Pages/PlayWorkspaceContent.razor +++ b/RpgRoller/Components/Pages/PlayWorkspaceContent.razor @@ -3,7 +3,7 @@
!Workspace.HasSessionInitialized || Workspace.State.IsCampaignDataLoading; } diff --git a/RpgRoller/Components/Pages/Workspace.razor b/RpgRoller/Components/Pages/Workspace.razor index 5a96fdc..0065a4c 100644 --- a/RpgRoller/Components/Pages/Workspace.razor +++ b/RpgRoller/Components/Pages/Workspace.razor @@ -14,30 +14,19 @@ }
- @if (HasSessionInitialized) - { - - } - else - { -
-
-

Loading workspace...

-
-
- } + @if (ChildContent is not null) { diff --git a/RpgRoller/Components/Pages/Workspace.razor.cs b/RpgRoller/Components/Pages/Workspace.razor.cs index a721f9d..acbc12f 100644 --- a/RpgRoller/Components/Pages/Workspace.razor.cs +++ b/RpgRoller/Components/Pages/Workspace.razor.cs @@ -12,6 +12,10 @@ public partial class Workspace : IAsyncDisposable protected override void OnParametersSet() { State.IsScreenMenuOpen = false; + if (PreviousRoute.HasValue && PreviousRoute.Value != Route && HasSessionInitialized) + _ = InvokeAsync(HandleRouteChangedAsync); + + PreviousRoute = Route; } [JSInvokable] @@ -114,6 +118,24 @@ public partial class Workspace : IAsyncDisposable await RequestRefreshAsync(); } + private async Task HandleRouteChangedAsync() + { + if (!HasSessionInitialized) + return; + + if (IsAdminRoute) + { + await Live.SyncStateEventsAsync(); + await EnsureAdminUsersLoadedAsync(); + await RequestRefreshAsync(); + return; + } + + await Scope.RefreshCampaignScopeAsync(); + await Live.SyncStateEventsAsync(); + await RequestRefreshAsync(); + } + private static bool IsStaticRenderInteropException(InvalidOperationException exception) { return exception.Message.Contains("statically rendered", StringComparison.OrdinalIgnoreCase); @@ -220,4 +242,5 @@ public partial class Workspace : IAsyncDisposable private WorkspaceCampaignScopeCoordinator? m_Scope; private WorkspaceSessionCoordinator? m_Session; private Task? InitializationTask { get; set; } -} \ No newline at end of file + private WorkspaceRoute? PreviousRoute { get; set; } +} diff --git a/RpgRoller/Components/Pages/WorkspaceRouteView.razor b/RpgRoller/Components/Pages/WorkspaceRouteView.razor index 80d024c..25a7d63 100644 --- a/RpgRoller/Components/Pages/WorkspaceRouteView.razor +++ b/RpgRoller/Components/Pages/WorkspaceRouteView.razor @@ -1,9 +1,6 @@ @using Microsoft.AspNetCore.Components -@if (Workspace.HasSessionInitialized) -{ - @ChildContent(Workspace) -} +@ChildContent(Workspace) @code { protected override async Task OnAfterRenderAsync(bool firstRender) diff --git a/TASKS.md b/TASKS.md index ab8cc4a..7f391ec 100644 --- a/TASKS.md +++ b/TASKS.md @@ -92,6 +92,8 @@ After Milestone 4, authenticated startup is now triggered by a route-owned wrapp Follow-up: the first pass at Milestone 4 removed shell-level `OnAfterRenderAsync`, but did not yet split `Session.InitializeAsync()` by route. The final follow-up fix made startup genuinely route-scoped by keeping `/admin` off play-only campaign scope and SSE startup, gating the full shell behind authenticated initialization, and adding direct `/admin` smoke coverage so this regression path stays visible. +Follow-up 2: gating the entire authenticated shell behind `HasSessionInitialized` produced another large first-batch subtree swap and broke the `/campaigns` to `/play` refresh path. The final stabilization renders a consistent route skeleton from batch 1, derives loading UI from `HasSessionInitialized` instead of mutating shared loading state in `WorkspaceRouteView`, and refreshes route-specific scope explicitly when the same `Workspace` instance changes from one authenticated route to another. + This section must be updated after each major milestone. When the implementation is complete, summarize which parts of the old workspace architecture were fully removed, which compatibility constraints remain, and whether the final startup path still depends on any multi-batch structural rendering. ## Context and Orientation