From 56e0ec1e79f08b8f231b15fbe30b9c2f088eb82f Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 4 May 2026 23:02:39 +0200 Subject: [PATCH] fix: phase authenticated startup --- README.md | 1 + .../Pages/AdminWorkspaceContent.razor | 128 ++++++++------ .../Pages/CampaignsWorkspaceContent.razor | 52 ++++-- .../Pages/PlayWorkspaceContent.razor | 163 +++++++++++------- RpgRoller/Components/Pages/Workspace.razor | 72 ++++++-- RpgRoller/Components/Pages/Workspace.razor.cs | 34 +++- .../Components/Pages/WorkspacePageContext.cs | 7 + .../Components/Pages/WorkspaceRenderPhase.cs | 9 + .../Components/Pages/WorkspaceRouteView.razor | 5 +- TASKS.md | 4 + 10 files changed, 312 insertions(+), 163 deletions(-) create mode 100644 RpgRoller/Components/Pages/WorkspaceRenderPhase.cs diff --git a/README.md b/README.md index 7c4baa1..20f7cca 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ SQLite migration rule: - Interactive authenticated startup begins in `WorkspaceRouteView.razor` after first render because `RpgRollerApiClient` still depends on JS interop-backed `fetch`. - Workspace startup diagnostics now log route initialization, route-content render phases, and browser-side workspace mutation snapshots to help isolate the remaining Firefox startup crash documented in `POSTMORTEM.md`. - Pre-Blazor diagnostics also watch the static `#rr-interactive-host` container before `_framework/blazor.web.js` connects, so extension-driven DOM mutations can be compared against the first failing interactive batch. +- Authenticated routes now mount through phased interactive batches: minimal shell first, then a simple header placeholder, then route skeletons, and only then the real header and control-heavy route content after route initialization succeeds. - Live workspace refresh compares separate roster, per-character sheet, and log versions so unrelated changes do not trigger full reloads. - Campaign log data is loaded in bounded slices: campaign summaries, one selected roster, one selected character sheet, and a 25-row incremental log window from `/api/campaigns/{campaignId}/log/page`. - Log rows return compact summary data first and lazy-load full detail from `/api/rolls/{rollId}` when expanded. diff --git a/RpgRoller/Components/Pages/AdminWorkspaceContent.razor b/RpgRoller/Components/Pages/AdminWorkspaceContent.razor index 537e05d..3f282bc 100644 --- a/RpgRoller/Components/Pages/AdminWorkspaceContent.razor +++ b/RpgRoller/Components/Pages/AdminWorkspaceContent.razor @@ -1,67 +1,85 @@ @using Microsoft.AspNetCore.Components -
- @if (Workspace.State.IsCurrentUserAdmin) - { +@if (!Workspace.ShowLiveContent) +{ +
-

Database

+

Admin

-

Download the current SQLite file for backup or offline inspection.

-
- } -
-
-

User Management

-
- @if (IsAdminDataLoading) +
+} +else +{ +
+ @if (Workspace.State.IsCurrentUserAdmin) { -

Loading users...

+
+
+

Database

+
+

Download the current SQLite file for backup or offline inspection.

+ +
} - else if (!Workspace.State.IsCurrentUserAdmin) - { -

Admin role is required to manage users.

- } - else if (Workspace.State.AdminUsers.Count == 0) - { -

No users found.

- } - else - { -
    - @foreach (var user in Workspace.State.AdminUsers) - { -
  • -
    - @user.Username -

    @user.DisplayName

    -

    Roles: @(user.Roles.Count == 0 ? "none" : string.Join(", ", user.Roles))

    -
    -
    - - -
    -
  • - } -
- } - -
+
+
+

User Management

+
+ @if (IsAdminDataLoading) + { +

Loading users...

+ } + else if (!Workspace.State.IsCurrentUserAdmin) + { +

Admin role is required to manage users.

+ } + else if (Workspace.State.AdminUsers.Count == 0) + { +

No users found.

+ } + else + { +
    + @foreach (var user in Workspace.State.AdminUsers) + { +
  • +
    + @user.Username +

    @user.DisplayName

    +

    Roles: @(user.Roles.Count == 0 ? "none" : string.Join(", ", user.Roles))

    +
    +
    + + +
    +
  • + } +
+ } +
+
+} @code { private bool IsAdminDataLoading => !Workspace.HasSessionInitialized || Workspace.State.IsAdminDataLoading; diff --git a/RpgRoller/Components/Pages/CampaignsWorkspaceContent.razor b/RpgRoller/Components/Pages/CampaignsWorkspaceContent.razor index d1cd5a0..b322ca2 100644 --- a/RpgRoller/Components/Pages/CampaignsWorkspaceContent.razor +++ b/RpgRoller/Components/Pages/CampaignsWorkspaceContent.razor @@ -1,24 +1,42 @@ @using Microsoft.AspNetCore.Components @using RpgRoller.Components.Pages.HomeControls - +@if (!Workspace.ShowLiveContent) +{ +
+
+
+

Campaign Management

+
+
+
+
+
+
+
+
+} +else +{ + - + +} @code { private async Task OnCampaignSelectionChangedAsync(ChangeEventArgs args) diff --git a/RpgRoller/Components/Pages/PlayWorkspaceContent.razor b/RpgRoller/Components/Pages/PlayWorkspaceContent.razor index 59c2ab5..4d3b499 100644 --- a/RpgRoller/Components/Pages/PlayWorkspaceContent.razor +++ b/RpgRoller/Components/Pages/PlayWorkspaceContent.razor @@ -1,74 +1,103 @@ @using Microsoft.AspNetCore.Components @using RpgRoller.Components.Pages.HomeControls -
- +
+
+
+
+
+
+
+ +
+ +} +else +{ +
+ + + +
+ + + + + - - - - - - - - + IsSubmitting="Workspace.State.IsSubmittingRolemasterSkillRoll" + ConfirmRequested="Workspace.Play.SubmitRolemasterSkillRollAsync" + CancelRequested="Workspace.Play.CancelRolemasterSkillRollAsync"/> +} @code { private bool IsCampaignDataLoading => !Workspace.HasSessionInitialized || Workspace.State.IsCampaignDataLoading; diff --git a/RpgRoller/Components/Pages/Workspace.razor b/RpgRoller/Components/Pages/Workspace.razor index ef4d428..7f5d34a 100644 --- a/RpgRoller/Components/Pages/Workspace.razor +++ b/RpgRoller/Components/Pages/Workspace.razor @@ -2,13 +2,14 @@

@State.LiveAnnouncement

- @if (State.HasHealthIssue) + @if (PageContext.ShowLiveContent && State.HasHealthIssue) {