# RpgRoller RpgRoller is an ASP.NET Core and Blazor Server app for lightweight tabletop campaign play, character sheets, and dice workflows. - `RpgRoller/`: web app, API endpoints, domain model, EF Core persistence, Blazor components, and static assets - `RpgRoller.Tests/`: xUnit coverage for API behavior, services, hosting, payload budgets, and persistence and migration paths - `RpgRoller.sln`: solution used by local development and repo scripts - `POSTMORTEM.md`: architecture analysis of the May 2026 Firefox and RoboForm failure in the authenticated workspace - `TASKS.md`: the current execution plan for the approved frontend routing rewrite Test layout: - `RpgRoller.Tests/Api/`: endpoint and host-facing integration tests - `RpgRoller.Tests/Services/`: service and rules-engine tests - `RpgRoller.Tests/Support/`: shared harnesses, builders, and test host helpers ## Code Organization Backend: - `RpgRoller/Program.cs`: app bootstrap, JSON options, compression, API and component mapping, and optional `PathBase` - `RpgRoller/Hosting/`: service registration, startup initialization, SQLite path resolution, and schema upgrades - `RpgRoller/Api/`: minimal API endpoint groups, request mappings, cookie and session helpers, and result mapping - `RpgRoller/Services/`: gameplay and account workflows behind `IGameService` - `RpgRoller/Services/GameService.cs`: facade over composed domain services - `RpgRoller/Services/GameAuthService.cs`: registration, login, logout, session lookup, and `GetMe` - `RpgRoller/Services/GameCampaignService.cs`: campaign creation, listing, roster reads, campaign options, and deletion - `RpgRoller/Services/GameCharacterService.cs`: character creation, updates, activation, deletion, transfer, and owner-scoped reads - `RpgRoller/Services/GameSkillService.cs`: skill-group CRUD, skill CRUD, sheet shaping, and ruleset validation - `RpgRoller/Services/GameRollService.cs`: skill and custom rolls, compact log pages, roll detail, and campaign state snapshots - `RpgRoller/Services/GameUserAdministrationService.cs`: username reads, admin user listing, role updates, and account deletion - `RpgRoller/Services/GameStateStore.cs`, `GameStateCloneFactory.cs`, and `GamePersistenceService.cs`: in-memory runtime state, campaign-state version tracking, and SQLite load and save boundaries - `RpgRoller/Services/GameAuthorization.cs`, `GameContextResolver.cs`, and `GameDtoMapper.cs`: shared authorization, session and campaign resolution, and backend read-model mapping - `RpgRoller/Services/RollEngine.cs`, `StandardRollEngine.cs`, `D6RollEngine.cs`, `RolemasterRollEngine.cs`, `RollBreakdownFormatter.cs`, and `CampaignLogSummaryBuilder.cs`: ruleset-specific dice execution, breakdown formatting, and compact campaign-log summaries - `RpgRoller/Services/SkillDefinitionValidator.cs`, `RoleSerializer.cs`, `RollVisibilityParser.cs`, and `CustomRollOptionsResolver.cs`: shared rules and parsing helpers Frontend: - `RpgRoller/Components/App.razor`: current HTML shell and the request-time branch that decides whether `/` serves the static auth page or the interactive app - `RpgRoller/Components/Routes.razor`: Blazor router and layout hookup - `RpgRoller/Components/Layout/MainLayout.razor`: default layout - `RpgRoller/Components/Pages/Home.razor`: current root route component for `/`; it only renders `Workspace` - `RpgRoller/Components/Pages/Home.razor.cs`: logout navigation helper that force-loads `/` and carries auth status query text - `RpgRoller/Components/Pages/Workspace.razor`: authenticated workspace UI with play, campaign management, admin, toasts, and modals - `RpgRoller/Components/Pages/Workspace.razor.cs`: workspace composition root, lifecycle, coordinator wiring, JS-invokable entry points, and menu item construction - `RpgRoller/Components/Pages/WorkspaceState.cs`: workspace UI state plus pure computed and formatting projections used directly by the Razor view - `RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs`, `WorkspaceCampaignCoordinator.cs`, `WorkspaceCampaignScopeCoordinator.cs`, `WorkspacePlayCoordinator.cs`, `WorkspaceAdminCoordinator.cs`, `WorkspaceLiveStateController.cs`, `WorkspaceFeedbackService.cs`, and `WorkspaceToast.cs`: session bootstrap, campaign scope, play and log, admin, live update, and toast concerns used by `Workspace` - `RpgRoller/Components/Pages/HomeControls/StaticAuthPage.razor`: plain HTML login and registration page used when `/` is requested without a valid session - `RpgRoller/Components/Pages/HomeControls/`: workspace child components, forms, header, panels, and modal controls - `RpgRoller/Components/RpgRollerApiClient.cs`: browser API client for write actions - `RpgRoller/Components/WorkspaceQueryService.cs`: browser-facing read client for workspace data - `RpgRoller/wwwroot/js/rpgroller-api.js`: browser interop for auth forms, session storage, SSE wiring, and DOM helpers - `RpgRoller/wwwroot/styles.css`: app styling and responsive layout Current repo note: - `POSTMORTEM.md` documents why the current authenticated workspace architecture is fragile and why the next major frontend change is a route-first rewrite of the authenticated shell. - `TASKS.md` is the authoritative execution plan for that rewrite and must be kept current while the work proceeds. ## Runtime and Persistence - Persistence uses EF Core with SQLite (`Microsoft.EntityFrameworkCore.Sqlite`). - The default database file is `RpgRoller/App_Data/rpgroller.db`. - `ConnectionStrings__RpgRoller` overrides the SQLite path for local runs, tests, or temporary environments. - Startup applies pending EF Core migrations through `Database.Migrate()`. - The app loads runtime state into memory during startup and persists successful state changes back to SQLite. - `RpgRoller/App_Data/rpgroller.development.db` is a checked-in migration coverage fixture used by hosting tests that copy it to a temporary file before validation. ## Product Capabilities - Supported campaign rulesets: D6 System, D&D 5e, and Rolemaster - Account registration, login, session-based auth, and role-aware authorization - Admin tools for user listing, role updates, account deletion, and direct SQLite database download - Campaign creation, roster reads, participant-scoped visibility, and owner and admin deletion - Character creation, activation, owner transfer, campaign reassignment or unlinking, and owner and admin deletion - Skill groups with reusable defaults plus skill and skill-group create, edit, reassign, and delete flows - Owner-scoped play workspace that lists only the current user's characters while preserving GM and admin management capabilities - Campaign log paging, lazy-loaded roll detail, compact summaries, and live state refresh through SSE - Custom roll submission from the play screen without creating a persisted skill - Instant skill filtering in the character panel - Campaign management owner labels based on display names Rolemaster support: - Standard expressions such as `d10`, `15d10`, `2d10+48`, and `d100-15` - Open-ended percentile expressions such as `d100!+85` - Conditional `FumbleRange` handling for open-ended percentile skills and skill-group defaults - Persisted and validated automatic retry toggle for open-ended percentile skills; only eligible Rolemaster skills can enable it - Rolemaster skill rolls open a modal prompt before rolling so the player can apply a one-shot situational modifier; the prompt autofocuses, supports Enter and Escape, and closes when clicking outside it - One-shot situational modifiers are transient Rolemaster-only roll inputs; the temporary modifier is applied to both the first attempt and any automatic retry attempt - Automatic retry windows for eligible open-ended skills: results `76-90` retry once with `+5`, and results `91-110` retry once with `+10` - Open-ended high chaining and low-end subtraction with ordered die metadata in roll detail - Compact log badges and summaries for open-ended, retry, and fumble-related events, including `Retry +5` and `Retry +10` ## Current Frontend Architecture The current frontend is in an intermediate state that was created while mitigating the Firefox and RoboForm failure documented in `POSTMORTEM.md`. Today, `/` is dual-purpose: - when the request has no valid session cookie, `RpgRoller/Components/App.razor` renders `StaticAuthPage.razor` as plain HTML and `RpgRoller/wwwroot/js/rpgroller-api.js` handles login and registration through `fetch` - when the request has a valid session cookie, `App.razor` renders the interactive Blazor app and `Home.razor` loads the authenticated `Workspace` Inside the authenticated app, the hamburger menu does not navigate to different URLs. Instead, `WorkspaceSessionCoordinator.cs` stores a `screen` preference in `sessionStorage`, and `Workspace.razor` conditionally swaps between play, campaign management, and admin screens inside one large component tree. This architecture works functionally but remains structurally fragile because: - the root shell still branches on request-time `HttpContext` - the authenticated workspace still performs staged startup in `OnAfterRenderAsync` - the app coordinates state across Blazor component state, browser `sessionStorage`, `fetch`, and SSE during early startup - the route URL does not represent the authenticated screen the user is actually viewing ## Approved Rewrite Direction The approved remediation direction is a route-first authenticated shell: - `/` becomes an auth-aware entry point that redirects to `/login` or `/play` - `/login` hosts the anonymous auth experience - `/play`, `/campaigns`, and `/admin` become real authenticated routes - the hamburger menu becomes route navigation instead of in-memory screen switching - SSE and heavy play bootstrap stay scoped to `/play` - the large `Workspace` component is split so each route owns a smaller, more stable subtree This rewrite is not complete yet. Follow `TASKS.md` for the execution plan. ## Local Development Prerequisites: - .NET SDK 10.0+ - Node.js 22+ - Firefox - geckodriver Initial setup: ```bash dotnet tool restore npm ci ``` Run locally: 1. Start the app: ```bash dotnet run --project RpgRoller/RpgRoller.csproj ``` 2. Open `http://localhost:5000` or the URL printed in the console. 3. Expect `/` to redirect to `/login` when anonymous and to `/play` when a valid session cookie already exists. Browser smoke helpers: - Run the checked-in smoke suite against an isolated temporary SQLite database: ```bash node ./scripts/run-selenium.js ``` - Run the Selenium smoke suite directly when the app is already running: ```bash npm run e2e:smoke ``` VS Code launch profiles in `.vscode/launch.json`: - `RpgRoller: Server` - `RpgRoller: Server + Edge (F5)` - `RpgRoller: Server + Firefox (F5)` Environment overrides: - Set `ConnectionStrings__RpgRoller` to point at a custom SQLite database. - Set `PathBase` to host the app under a sub-path such as `/rpgroller`. Migration authoring: ```powershell dotnet dotnet-ef migrations add --project RpgRoller/RpgRoller.csproj --startup-project RpgRoller/RpgRoller.csproj ``` SQLite migration rule: - Keep table-rebuild operations separate from unrelated schema or data changes so EF Core does not emit non-transactional migration warnings. ## Frontend Runtime - The UI currently runs as Blazor Server with interactive components for the authenticated workspace and a plain HTML plus JavaScript auth page for anonymous users at `/`. - Static assets are linked through Blazor's `@Assets[...]` pipeline for fingerprinted cache-busting URLs. - Workspace reads are resolved through API requests in `WorkspaceQueryService`; browser interop stays focused on auth forms, session storage, SSE wiring, and small DOM helpers. - 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. - Newly appended local rolls auto-expand in the play workspace and reuse the roll response as the initial detail payload. - Custom roll submission uses the selected character context; D6 uses baseline wild-die and fumble behavior, while D&D 5e and Rolemaster use the submitted expression directly. - API JSON contracts use the source-generated `RpgRollerJsonSerializerContext`. - HTTP JSON responses are gzip-compressed when the client advertises support. - The OpenAPI contract source lives at `openapi/RpgRoller.json`. ## Test and Coverage - Test command: ```powershell dotnet test RpgRoller.Tests/RpgRoller.Tests.csproj --collect:"XPlat Code Coverage" --settings RpgRoller.Tests/coverlet.runsettings ``` - Coverage gate: ```powershell pwsh ./scripts/check-coverage.ps1 -MinLineRate 0.90 -MinBranchRate 0.70 ``` - Local parity script: ```powershell pwsh ./scripts/ci-local.ps1 ``` - `scripts/ci-local.ps1` writes coverage collector output to a unique temporary results directory outside the repo, reads coverage from there, removes that directory at the end of the run, and sweeps stray `coverage.cobertura.xml` files from `RpgRoller.Tests/TestResults`. - Regression tests enforce payload budgets for character sheet reads, initial and incremental campaign log loads, roll mutation responses, and lazy-loaded Rolemaster roll detail payloads. - `RpgRoller.Tests/coverlet.runsettings` measures the full `RpgRoller` backend assembly.