Implement milestone 2 route navigation

This commit is contained in:
2026-05-04 21:23:45 +02:00
parent c13a2ce7c7
commit def2a3f680
21 changed files with 334 additions and 193 deletions

View File

@@ -18,11 +18,11 @@ The change is complete when a human can run the app, open `/`, observe the corre
- [x] (2026-05-04 17:52Z) Updated `README.md` so it accurately describes the current architecture and the approved rewrite direction.
- [x] (2026-05-04 18:29Z) Implemented a host-level `/` redirect to `/login` or `/play`, moved the static auth document to `/login`, switched login/logout targets to `/play` and `/login`, and updated the root-path host and smoke coverage to the new contract.
- [x] (2026-05-04 19:26Z) Replaced the checked-in Playwright smoke coverage with a geckodriver+Selenium smoke runner, including a Firefox DOM-wrap addon for extension-like startup mutations, and updated repo scripts/docs to the new browser verification path.
- [ ] Introduce real authenticated routes for `/play`, `/campaigns`, and `/admin` while preserving current behavior.
- [ ] Remove `screen` as a `sessionStorage` routing mechanism and replace menu actions with URL navigation.
- [x] (2026-05-04) Introduced real authenticated routes for `/play`, `/campaigns`, and `/admin` while preserving the shared `Workspace` behavior behind those routes.
- [x] (2026-05-04) Removed `screen` as a `sessionStorage` routing mechanism and replaced menu actions with URL navigation.
- [ ] Split the large `Workspace` render tree so play, campaign management, and admin each own a smaller subtree.
- [ ] Reduce `OnAfterRenderAsync` to the smallest practical scope and keep staged startup out of the authenticated shell root.
- [ ] Update host tests, Selenium smoke tests, and docs so the new route model is the only documented and verified behavior.
- [x] (2026-05-04) Updated host tests, Selenium smoke tests, and docs so the real-route model is the documented and verified Milestone 2 behavior.
## Surprises & Discoveries
@@ -78,15 +78,17 @@ After Milestone 1, the dual-purpose `/` entry point is gone. Anonymous requests
After the Selenium migration iteration, the repositorys browser smoke coverage once again matches the documented verification path. The smoke suite now runs against Firefox through geckodriver, and the DOM-wrap regression coverage remains intact through a temporary test addon. The next risk is purely architectural again: the authenticated shell still uses in-memory screen switching, so Milestone 2 remains the next code change on the critical path.
After Milestone 2, the authenticated shell now has first-class `/play`, `/campaigns`, and `/admin` routes, and the menu navigates with URLs instead of `sessionStorage` screen names. The remaining risk is now narrower and more structural: `Workspace.razor` still owns mutually exclusive authenticated branches, and the root `OnAfterRenderAsync` path still stages page-specific startup work that should move into route-owned components in Milestones 3 and 4.
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
The current app serves both anonymous and authenticated experiences from the same root request path. In `RpgRoller/Components/App.razor`, the HTML shell checks the current request path and session cookie through `HttpContext`. If the request is for `/` and no valid session exists, it renders `RpgRoller/Components/Pages/HomeControls/StaticAuthPage.razor` as plain HTML and loads `RpgRoller/wwwroot/js/rpgroller-api.js`. That JavaScript file binds the login and registration forms and sends `fetch` requests to `/api/auth/register` and `/api/auth/login`.
The current app serves both anonymous and authenticated experiences from the same HTML shell. In `RpgRoller/Components/App.razor`, the shell checks the current request path through `HttpContext`. If the request is for `/login`, it renders `RpgRoller/Components/Pages/HomeControls/StaticAuthPage.razor` as plain HTML and loads `RpgRoller/wwwroot/js/rpgroller-api.js`. That JavaScript file binds the login and registration forms and sends `fetch` requests to `/api/auth/register` and `/api/auth/login`.
If a valid session cookie exists, `App.razor` instead renders the interactive Blazor router. The only current component route for the authenticated shell is `RpgRoller/Components/Pages/Home.razor`, which maps `@page "/"` and immediately renders `Workspace`. `RpgRoller/Components/Pages/Home.razor.cs` is only a logout redirect helper; it is not a real page controller anymore.
If a valid session cookie exists, `App.razor` instead renders the interactive Blazor router. The authenticated shell is now entered through `RpgRoller/Components/Pages/PlayPage.razor`, `CampaignsPage.razor`, and `AdminPage.razor`, which map `/play`, `/campaigns`, and `/admin` and forward into the shared `Workspace` component.
The authenticated workspace lives in `RpgRoller/Components/Pages/Workspace.razor` and `Workspace.razor.cs`. The Razor file contains the header, play screen, campaign management screen, admin screen, toasts, and modals. The code-behind wires several coordinator classes, and `OnAfterRenderAsync` drives session initialization and staged control enablement. The currently selected screen is stored in `WorkspaceState.CurrentScreen`, and `WorkspaceSessionCoordinator.cs` persists that screen name in browser `sessionStorage` under the key `rpgroller.screen`.
The authenticated workspace lives in `RpgRoller/Components/Pages/Workspace.razor` and `Workspace.razor.cs`. The Razor file still contains the header, play screen, campaign management screen, admin screen, toasts, and modals. The code-behind wires several coordinator classes, and `OnAfterRenderAsync` still drives session initialization and staged control enablement. The current route now comes from the page component parameter rather than `WorkspaceState.CurrentScreen`, and `WorkspaceSessionCoordinator.cs` no longer persists a screen name in browser `sessionStorage`.
In plain language, a “route-first authenticated shell” means that the browser path decides which authenticated page is being rendered. `/play` means the play page. `/campaigns` means the campaign management page. `/admin` means the admin page. The URL is not a decorative detail; it is the primary way the app chooses the screen. Menu clicks change the URL. Reloading the page preserves the same screen because the URL already says what the screen is.
@@ -249,12 +251,12 @@ Current evidence that explains the bootstrap constraint:
RpgRoller/Components/RpgRollerApiClient.cs
var response = await js.InvokeAsync<JsApiResponse>("rpgRollerApi.request", method, path, payload);
Current evidence that explains why route navigation must replace screen persistence:
Current evidence that Milestone 2 is intentionally transitional rather than final:
RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs
private const string ScreenSessionKey = "screen";
state.CurrentScreen = NormalizeRequestedScreen(storedScreen) ?? ScreenPlay;
await js.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, state.CurrentScreen);
RpgRoller/Components/Pages/Workspace.razor
@if (IsPlayRoute) { ... }
else if (IsCampaignsRoute) { ... }
else if (IsAdminRoute) { ... }
## Interfaces and Dependencies