278 lines
28 KiB
Markdown
278 lines
28 KiB
Markdown
# Finish GameService and Workspace Decomposition
|
|
|
|
This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.
|
|
|
|
`PLANS.md` is checked into the repository root. This document must be maintained in accordance with `PLANS.md`.
|
|
|
|
## Purpose / Big Picture
|
|
|
|
This work finishes a refactor that is already partly complete in the current tree. After the remaining changes are done, the application should behave exactly as it does now for login, campaign management, character management, rolling, live updates, and admin actions, but the code should be organized around small, obvious ownership boundaries instead of two oversized coordinators.
|
|
|
|
The user-visible proof is intentionally boring: after starting the app, logging in, selecting a campaign, rolling skills, opening the admin screen, and letting live updates reconnect, everything should still work the same. The win is for future implementation work. A novice contributor should be able to open a focused file such as `RpgRoller/Services/GameSkillService.cs` or `RpgRoller/Components/Pages/WorkspacePlayCoordinator.cs`, make one change, and not risk unrelated behavior.
|
|
|
|
## Progress
|
|
|
|
- [x] (2026-04-04 22:46Z) Re-read `AGENTS.md` and `PLANS.md`, then converted this file from a blueprint into a self-contained ExecPlan.
|
|
- [x] (2026-04-04 22:46Z) Inspected the current backend state. `RpgRoller/Services/GameService.cs`, `GameStateStore.cs`, `GamePersistenceService.cs`, `GameAuthService.cs`, `GameCampaignService.cs`, `GameCharacterService.cs`, `GameSkillService.cs`, `GameRollService.cs`, and `GameUserAdministrationService.cs` already exist.
|
|
- [x] (2026-04-04 22:46Z) Inspected the current frontend state. `RpgRoller/Components/Pages/WorkspaceState.cs`, `WorkspaceSessionCoordinator.cs`, `WorkspaceCampaignCoordinator.cs`, `WorkspaceCampaignScopeCoordinator.cs`, `WorkspacePlayCoordinator.cs`, `WorkspaceAdminCoordinator.cs`, `WorkspaceLiveStateController.cs`, `WorkspaceFeedbackService.cs`, and `WorkspaceToast.cs` already exist.
|
|
- [x] (2026-04-04 22:46Z) Marked the large structural extractions as already done in this plan instead of treating the repository as pre-refactor.
|
|
- [x] (2026-04-04 23:03Z) Completed backend shared-helper consolidation. `GameStateStore` now owns campaign-state version mutations, `GameAuthorization`, `GameContextResolver`, and `GameDtoMapper` now own the shared helper seams, and the domain services delegate to them instead of keeping private copies.
|
|
- [x] (2026-04-04 23:20Z) Completed backend roll decomposition. Dice execution now lives in `RollEngine`, `StandardRollEngine`, `D6RollEngine`, and `RolemasterRollEngine`, while `RollBreakdownFormatter` and `CampaignLogSummaryBuilder` own the extracted formatting and compact-log helpers.
|
|
- [x] (2026-04-04 23:03Z) Finished thinning `RpgRoller/Services/GameService.cs` for startup and campaign-state bootstrap. The constructor now loads persistence and rebuilds campaign-state versions through `GameStateStore` without keeping private helper methods.
|
|
- [ ] Finish thinning `RpgRoller/Components/Pages/Workspace.razor.cs`. Remaining work: remove the large mirror of `WorkspaceState` properties and the excess pass-through wrappers so the file acts as a composition root plus lifecycle and JS-invokable bridge.
|
|
- [ ] Update `README.md` and this ExecPlan after the remaining code changes land so the documentation reflects the final, not intermediate, structure. Completed in this iteration: backend helper descriptions and current remaining scope.
|
|
|
|
## Surprises & Discoveries
|
|
|
|
- Observation: backend helper consolidation was lower risk than it first looked because most duplicated code already matched line-for-line semantics.
|
|
Evidence: after moving authorization, session resolution, and mapping into `GameAuthorization`, `GameContextResolver`, and `GameDtoMapper`, the surrounding service tests passed without behavioral updates.
|
|
|
|
- Observation: `GameRollService` remained the only backend file with broad mixed ownership after the shared helpers moved out.
|
|
Evidence: the service now delegates authorization, context, state-snapshot mapping, and roll/log DTO mapping, but it still contains dice algorithms, compact log summary formatting, event badge generation, and dice serialization in one file.
|
|
|
|
- Observation: `GameRollService` is now the main backend monolith.
|
|
Evidence: `RpgRoller/Services/GameRollService.cs` still owns D6 logic, Rolemaster logic, log summary formatting, event badge generation, and JSON dice serialization in one file.
|
|
|
|
- Observation: the roll split preserved behavior cleanly because the extracted helper boundaries were already pure and ruleset-scoped.
|
|
Evidence: after moving dice execution into ruleset engines and moving summary text into `CampaignLogSummaryBuilder`, the existing D6, Rolemaster, log paging, detail, custom-roll, and Playwright smoke tests passed without contract changes.
|
|
|
|
- Observation: the frontend refactor introduced one extra collaborator that was not named in the original blueprint, and that collaborator is worth keeping.
|
|
Evidence: `RpgRoller/Components/Pages/WorkspaceCampaignScopeCoordinator.cs` now owns selected-campaign reload, selected-character synchronization, log reset, and unauthorized-session handling. Those behaviors are cohesive and should not be pushed back into `Workspace.razor.cs`.
|
|
|
|
- Observation: `Workspace.razor.cs` is structurally better than before but still too wide because it mirrors state into local aliases.
|
|
Evidence: `RpgRoller/Components/Pages/Workspace.razor.cs` contains a large block of properties such as `private UserSummary? User { get => State.User; set => State.User = value; }` and many one-line delegates such as `private Task RollSkillAsync(Guid skillId) => Play.RollSkillAsync(skillId);`.
|
|
|
|
- Observation: `README.md` already describes the repository as partially refactored.
|
|
Evidence: the current `README.md` names the extracted service and coordinator files directly, so the final implementation must update that description in place rather than add a historical change log.
|
|
|
|
## Decision Log
|
|
|
|
- Decision: Treat the repository as partially completed work, not as a blank implementation target.
|
|
Rationale: the current tree already contains the major backend and frontend file extractions, and an accurate ExecPlan must let a novice resume from the current state without redoing finished work.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
- Decision: Keep `RpgRoller/Components/Pages/WorkspaceCampaignScopeCoordinator.cs` as a first-class collaborator in the plan.
|
|
Rationale: it already owns coherent campaign-scope behavior and reduces the chance that unauthorized-session handling and selection refresh logic drift back into `Workspace.razor.cs`.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
- Decision: Use the remaining work to consolidate shared backend helpers instead of creating a second round of large, nearly duplicated services.
|
|
Rationale: most of the benefit of the first extraction is already present. The missing value now is shared helper ownership, not more top-level service files.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
- Decision: Keep the new backend helper seams static for now instead of introducing injected utility services.
|
|
Rationale: the extracted logic is pure over `GameStateStore` plus method inputs, so static helpers keep wiring simple while still removing the duplication that was obscuring the domain services.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
- Decision: Instantiate the extracted roll engines inside `GameRollService` instead of pushing more constructor parameters through `GameService`.
|
|
Rationale: the new engines depend only on `IDiceRoller`, so local construction keeps the facade wiring small while still carving the algorithmic work out of the service orchestration path.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
- Decision: Keep validation instructions in this ExecPlan even though this revision is documentation-only.
|
|
Rationale: `PLANS.md` requires executable validation guidance, but the user explicitly requested no CI or test work for this pass. The commands remain here for the implementation pass that follows later.
|
|
Date/Author: 2026-04-04 / Codex
|
|
|
|
## Outcomes & Retrospective
|
|
|
|
The repository now has the shared backend seams that the earlier rewrite described as missing. `GameStateStore` owns campaign-state version mutation, `GameAuthorization` owns shared access checks, `GameContextResolver` owns session and campaign resolution, and `GameDtoMapper` owns the backend read-model construction that had been repeated across services.
|
|
|
|
The remaining work is narrower than before. The repository now needs the final `Workspace` binding cleanup. `GameService` is already at the intended facade shape, and `GameRollService` is now primarily orchestration plus persistence-facing log record handling.
|
|
|
|
## Context and Orientation
|
|
|
|
This repository is an ASP.NET Core and Blazor Server application. The backend gameplay API is centered on `RpgRoller/Services/IGameService.cs` and its concrete implementation `RpgRoller/Services/GameService.cs`. The frontend authenticated workspace is centered on `RpgRoller/Components/Pages/Workspace.razor` and `RpgRoller/Components/Pages/Workspace.razor.cs`.
|
|
|
|
A "thin facade" in this plan means a class that mainly wires collaborators and forwards calls. It does not own substantial business logic. A "composition root" means the place where collaborators are constructed and connected together. In this repository, `GameService` and `Workspace.razor.cs` should be composition roots. A "campaign-state tracker" means the in-memory version counters used to decide whether a roster, character sheet, or log changed. Those counters currently live in `RpgRoller/Services/GameStateStore.cs` as `GameCampaignStateTracker`.
|
|
|
|
The current backend state is better than the old monolith. `RpgRoller/Services/GameService.cs` already delegates its public methods to `GameAuthService`, `GameCampaignService`, `GameCharacterService`, `GameSkillService`, `GameRollService`, and `GameUserAdministrationService`. `RpgRoller/Services/GamePersistenceService.cs` already owns SQLite loading and saving. `RpgRoller/Services/SkillDefinitionValidator.cs`, `RoleSerializer.cs`, `RollVisibilityParser.cs`, `CustomRollOptionsResolver.cs`, and `GameStateCloneFactory.cs` already exist as helper files.
|
|
|
|
The backend shared-helper duplication is now resolved. `RpgRoller/Services/GameStateStore.cs` owns campaign-state version mutations. `RpgRoller/Services/GameAuthorization.cs` owns shared access checks. `RpgRoller/Services/GameContextResolver.cs` owns session-token and campaign resolution. `RpgRoller/Services/GameDtoMapper.cs` owns the backend read models returned by the services. Roll execution is now split across `RpgRoller/Services/RollEngine.cs`, `StandardRollEngine.cs`, `D6RollEngine.cs`, `RolemasterRollEngine.cs`, `RollBreakdownFormatter.cs`, and `CampaignLogSummaryBuilder.cs`, leaving `RpgRoller/Services/GameRollService.cs` as a smaller workflow coordinator.
|
|
|
|
The current frontend state is also better than the old monolith. `RpgRoller/Components/Pages/WorkspaceState.cs` holds most UI state and many computed projections. Session/bootstrap behavior lives in `WorkspaceSessionCoordinator.cs`. Campaign management and modal flows live in `WorkspaceCampaignCoordinator.cs`. Selected campaign scope refresh lives in `WorkspaceCampaignScopeCoordinator.cs`. Play/log behavior lives in `WorkspacePlayCoordinator.cs`. Admin behavior lives in `WorkspaceAdminCoordinator.cs`. Live event reconciliation lives in `WorkspaceLiveStateController.cs`. Toast and announcement behavior lives in `WorkspaceFeedbackService.cs`.
|
|
|
|
The remaining frontend problem is that `RpgRoller/Components/Pages/Workspace.razor.cs` still mirrors a large amount of `WorkspaceState` into local alias properties and exposes many single-line wrapper methods only because the Razor file has not been fully retargeted to the composed surface. The next pass should delete those mirrors rather than add more wrappers.
|
|
|
|
## Plan of Work
|
|
|
|
### Milestone 1: Consolidate backend shared state and helper ownership
|
|
|
|
Begin by removing duplication that is now spread across the backend service files. Extend `RpgRoller/Services/GameStateStore.cs` so it owns the campaign-state tracker operations that are currently repeated in several places. Specifically, move `GetOrCreateCampaignStateLocked`, `AddCharacterStateLocked`, `RemoveCharacterStateLocked`, `TouchRosterLocked`, `TouchCharacterLocked`, `TouchLogLocked`, and `RebuildCampaignStateLocked` into `GameStateStore`. Once those methods exist there, delete the repeated private copies from `GameService`, `GameCharacterService`, `GameSkillService`, `GameRollService`, and `GameUserAdministrationService`.
|
|
|
|
In parallel, create `RpgRoller/Services/GameAuthorization.cs`, `RpgRoller/Services/GameContextResolver.cs`, and `RpgRoller/Services/GameDtoMapper.cs`. Keep them as static helper classes unless an implementation detail forces constructor injection. `GameAuthorization` should own role checks and "can view campaign", "can edit character", and "can view roll" decisions. `GameContextResolver` should own session-token-to-user lookup, campaign-context lookup, and character-to-campaign resolution. `GameDtoMapper` should own summary and response mapping such as user summaries, campaign summaries, campaign rosters, character summaries, character sheets, roll results, and campaign-state snapshots. Update each backend domain service to call these helpers instead of carrying its own copies. The end of this milestone should leave each domain service focused on one workflow family instead of common glue code.
|
|
|
|
### Milestone 2: Break up the roll engine and compact log formatting
|
|
|
|
After the shared helpers are in place, split `RpgRoller/Services/GameRollService.cs` into a smaller orchestration file plus algorithmic helpers. Create `RpgRoller/Services/RollEngine.cs` as the dispatcher for ruleset-specific rolling, and create `StandardRollEngine.cs`, `D6RollEngine.cs`, and `RolemasterRollEngine.cs` for the actual dice logic. Move human-readable breakdown formatting into `RpgRoller/Services/RollBreakdownFormatter.cs`. Move compact log summary text, badge generation, and custom-roll expression extraction into `RpgRoller/Services/CampaignLogSummaryBuilder.cs`.
|
|
|
|
Keep `GameRollService` responsible for the public workflow: authorize the request, look up the skill or character, ask the roll engine to compute the dice, record the log entry, and return the mapped response. Do not change JSON contracts, roll totals, badge text, breakdown text, or log paging behavior. The easiest way to stay safe is to move logic in place first, preserve existing method names where possible, and only then simplify call sites.
|
|
|
|
### Milestone 3: Finish thinning GameService
|
|
|
|
Once state-version helpers and backend shared helpers are centralized, return to `RpgRoller/Services/GameService.cs`. Remove every remaining private helper there. The constructor should create the shared collaborators, call persistence bootstrap, and stop. Startup loading should happen through `GamePersistenceService.LoadStateFromDatabase()` plus `GameStateStore.RebuildCampaignStateLocked()` called from one place only. `GameService` should retain public `IGameService` methods and nothing more than lightweight delegation and ruleset enumeration.
|
|
|
|
If constructor readability suffers, create small private factory methods inside `GameService` only for collaborator construction. Do not reintroduce domain behavior there. The acceptance standard for this milestone is that a reader can scan the entire file quickly and understand all wiring without scrolling through business logic.
|
|
|
|
### Milestone 4: Finish thinning Workspace and clean up Razor bindings
|
|
|
|
With the backend stable, finish the frontend cleanup. Keep `RpgRoller/Components/Pages/WorkspaceCampaignScopeCoordinator.cs` as the owner of selected-campaign scope refresh. In `RpgRoller/Components/Pages/Workspace.razor.cs`, delete the mirror block that copies `WorkspaceState` into local aliases such as `User`, `Campaigns`, `SelectedCampaign`, and the log-detail dictionaries. Keep `State` public to the Razor file and bind directly through `State` wherever the values are plain data or pure computed projections.
|
|
|
|
To support direct binding, move the remaining pure presentation helpers into `RpgRoller/Components/Pages/WorkspaceState.cs`. That includes owner labels and skill-definition labels, because those are pure projections over the selected campaign, selected user, and skill data. Keep imperative flows in the coordinators. The file `Workspace.razor.cs` should still own dependency injection, collaborator construction, lifecycle methods, `JSInvokable` entry points, `DisposeAsync`, and any tiny wrappers needed to satisfy component callback signatures. It should stop looking like a second state bag.
|
|
|
|
Then update `RpgRoller/Components/Pages/Workspace.razor` so it uses the composed surface consistently. Data should come from `State`, management actions from `Campaigns`, play actions from `Play`, admin actions from `Admin`, and session actions from `Session`. `Scope` may remain an internal collaborator if it simplifies orchestration and keeps the markup readable. Avoid creating deep object chains; clear names matter more than ideological purity.
|
|
|
|
### Milestone 5: Final documentation alignment
|
|
|
|
After the code refactor is complete, update `README.md` so its code-organization section describes the final structure plainly and completely. Update this ExecPlan in place. Mark completed progress items with timestamps, add any implementation surprises, record final decisions, and write a closing retrospective. Do not add a historical mini-changelog. The documentation should describe the repository as it exists after the refactor lands.
|
|
|
|
## Concrete Steps
|
|
|
|
Work from `D:\Code\RpgRoller`.
|
|
|
|
Start every future implementation pass by re-reading the plan and checking the current tree:
|
|
|
|
Get-Content PLANS.md
|
|
Get-Content TASKS.md
|
|
git status --short
|
|
rg --files RpgRoller/Services RpgRoller/Components/Pages
|
|
|
|
The remaining implementation pass is frontend-focused. Begin by inspecting the `Workspace` composition surface before editing:
|
|
|
|
Get-Content RpgRoller\Components\Pages\Workspace.razor.cs
|
|
Get-Content RpgRoller\Components\Pages\Workspace.razor
|
|
Get-Content RpgRoller\Components\Pages\WorkspaceState.cs
|
|
Get-Content RpgRoller\Components\Pages\WorkspaceCampaignScopeCoordinator.cs
|
|
Get-Content RpgRoller\Components\Pages\WorkspacePlayCoordinator.cs
|
|
|
|
Keep the next extraction small. Remove one block of mirrored state or pass-through wrappers at a time so component behavior can stay stable and the Playwright smoke flow can keep proving the result.
|
|
|
|
When beginning frontend cleanup, inspect the current composition surface before editing:
|
|
|
|
Get-Content RpgRoller\Components\Pages\Workspace.razor.cs
|
|
Get-Content RpgRoller\Components\Pages\Workspace.razor
|
|
Get-Content RpgRoller\Components\Pages\WorkspaceState.cs
|
|
Get-Content RpgRoller\Components\Pages\WorkspaceCampaignScopeCoordinator.cs
|
|
Get-Content RpgRoller\Components\Pages\WorkspacePlayCoordinator.cs
|
|
|
|
Move pure projections into `WorkspaceState`, simplify the composition root, and then retarget the Razor file to the composed surface. Keep the child components in `RpgRoller/Components/Pages/HomeControls/` stable unless a binding signature must change to support the cleanup.
|
|
|
|
When code work resumes later, validate after each meaningful iteration with the repo-standard commands:
|
|
|
|
pwsh ./scripts/ci-local.ps1
|
|
pwsh ./scripts/run-playwright.ps1
|
|
|
|
The expected result is simple: no failing tests, no coverage regression, and the Playwright smoke flow completes successfully against a temporary database.
|
|
|
|
## Validation and Acceptance
|
|
|
|
This implementation revision ran targeted helper tests during extraction and later full repo validation through `pwsh ./scripts/ci-local.ps1`. The same full validation remains mandatory after the frontend pass.
|
|
|
|
The backend is accepted when `RpgRoller/Services/GameService.cs` contains only collaborator wiring, ruleset enumeration, and public delegation; when shared authorization, context, mapping, and campaign-state helper logic each live in one place; and when `RpgRoller/Services/GameRollService.cs` no longer embeds the dice engines or compact log summary builders.
|
|
|
|
The frontend is accepted when `RpgRoller/Components/Pages/Workspace.razor.cs` reads like a composition root instead of a state mirror, and when `RpgRoller/Components/Pages/Workspace.razor` binds primarily through `State`, `Session`, `Campaigns`, `Play`, and `Admin`.
|
|
|
|
The user-visible acceptance flow is:
|
|
|
|
Log in through the home page, land in the authenticated workspace, switch between Play and Campaign Management, create or edit a character, roll a skill, expand a roll detail row, and open the Admin screen as an admin user. Confirm that live connection status still updates, the selected campaign still persists, and newly recorded rolls still appear and auto-expand as before.
|
|
|
|
The repo-level acceptance flow is:
|
|
|
|
Run `pwsh ./scripts/ci-local.ps1` from `D:\Code\RpgRoller` and expect the script to finish without failed tests or coverage failures. After any frontend-affecting iteration, run `pwsh ./scripts/run-playwright.ps1` and expect the smoke test to complete successfully.
|
|
|
|
## Idempotence and Recovery
|
|
|
|
This refactor should stay additive and repeatable. Re-reading the repo and re-running the inspection commands is always safe. Re-applying small helper extractions is safe as long as the public contracts remain unchanged.
|
|
|
|
No database schema change is planned here. If implementation work needs a running app for manual verification, prefer using a temporary SQLite database through `ConnectionStrings__RpgRoller` rather than reusing a valuable local database file. If a partial edit leaves the repository uncompilable, fix forward by finishing the helper extraction or by restoring the affected file to the last committed content through a normal edit, not through destructive git reset commands.
|
|
|
|
Keep commits small. If the work splits into multiple commits, pause after each commit and update the `Progress` section before continuing, so the plan remains restartable from the current tree.
|
|
|
|
## Artifacts and Notes
|
|
|
|
The current tree already shows the intended direction. These excerpts are the key evidence that the refactor is partway done but not finished:
|
|
|
|
public sealed class GameService : IGameService
|
|
{
|
|
public GameService(...)
|
|
{
|
|
m_StateStore = new();
|
|
m_PersistenceService = new(dbContextFactory, m_StateStore);
|
|
m_AuthService = new(m_StateStore, passwordHasher, m_PersistenceService);
|
|
...
|
|
LoadStateFromDatabase();
|
|
}
|
|
}
|
|
|
|
That excerpt shows the facade wiring is already present, but the startup helper still sits in `GameService`.
|
|
|
|
private UserSummary? User { get => State.User; set => State.User = value; }
|
|
private Task RollSkillAsync(Guid skillId) => Play.RollSkillAsync(skillId);
|
|
|
|
Those excerpts from `Workspace.razor.cs` show the remaining mirror and pass-through pattern that should be deleted in the final cleanup.
|
|
|
|
public sealed class WorkspaceCampaignScopeCoordinator
|
|
{
|
|
public async Task RefreshCampaignScopeAsync()
|
|
{
|
|
...
|
|
await RefreshCampaignRosterAsync();
|
|
await m_RefreshSelectedCharacterSheetAsync();
|
|
await m_RefreshCampaignLogAsync(null);
|
|
...
|
|
}
|
|
}
|
|
|
|
That excerpt shows the frontend already has a real collaborator worth preserving rather than folding back into `Workspace`.
|
|
|
|
## Interfaces and Dependencies
|
|
|
|
The implementation should end with the following stable helper surfaces.
|
|
|
|
In `RpgRoller/Services/GameStateStore.cs`, define instance methods that own campaign-state tracker mutation:
|
|
|
|
public GameCampaignStateTracker GetOrCreateCampaignStateLocked(Guid campaignId)
|
|
public void RebuildCampaignStateLocked()
|
|
public void AddCharacterStateLocked(Guid? campaignId, Guid characterId)
|
|
public void RemoveCharacterStateLocked(Guid? campaignId, Guid characterId)
|
|
public void TouchRosterLocked(Guid? campaignId)
|
|
public void TouchCharacterLocked(Guid? campaignId, Guid characterId)
|
|
public void TouchLogLocked(Guid? campaignId)
|
|
|
|
In `RpgRoller/Services/GameAuthorization.cs`, define static helpers for shared access rules:
|
|
|
|
public static bool HasRole(UserAccount user, string role)
|
|
public static bool CanViewCampaign(GameStateStore stateStore, Guid actorUserId, Guid campaignId)
|
|
public static bool CanEditCharacter(Guid actorUserId, Character character, Campaign campaign)
|
|
public static bool CanViewRoll(Guid actorUserId, Campaign campaign, RollLogEntry entry)
|
|
|
|
In `RpgRoller/Services/GameContextResolver.cs`, define static helpers for shared lookups:
|
|
|
|
public static UserAccount? ResolveUserLocked(GameStateStore stateStore, string sessionToken)
|
|
public static ServiceResult<(UserAccount User, Campaign Campaign)> ResolveCampaignContextLocked(GameStateStore stateStore, string sessionToken, Guid campaignId)
|
|
public static bool TryResolveCharacterCampaignLocked(GameStateStore stateStore, Character character, out Campaign campaign, out ServiceError? error)
|
|
|
|
In `RpgRoller/Services/GameDtoMapper.cs`, define static mapping helpers for the backend read models returned by the services. At minimum, this file must own user summaries, admin user summaries, campaign options, campaign summaries, campaign rosters, character summaries, character sheets, roll results, campaign log entries, campaign log list entries, and campaign-state snapshots.
|
|
|
|
In `RpgRoller/Services/RollEngine.cs`, define a small dispatcher class with one public method that receives a `RulesetKind`, a parsed `DiceExpression`, the D6 wild-die settings, and the optional Rolemaster fumble range, then returns `(int Total, string Breakdown, IReadOnlyList<RollDieResult> Dice)`. The ruleset-specific implementations should live in `StandardRollEngine.cs`, `D6RollEngine.cs`, and `RolemasterRollEngine.cs`.
|
|
|
|
In `RpgRoller/Services/RollBreakdownFormatter.cs`, keep only pure string-formatting helpers. In `RpgRoller/Services/CampaignLogSummaryBuilder.cs`, keep only pure compact-log helpers such as summary text, event badges, and custom-roll expression extraction.
|
|
|
|
In `RpgRoller/Components/Pages/Workspace.razor.cs`, the final composed surface should be limited to:
|
|
|
|
private WorkspaceState State { get; }
|
|
private WorkspaceSessionCoordinator Session { get; }
|
|
private WorkspaceCampaignCoordinator Campaigns { get; }
|
|
private WorkspaceCampaignScopeCoordinator Scope { get; }
|
|
private WorkspacePlayCoordinator Play { get; }
|
|
private WorkspaceAdminCoordinator Admin { get; }
|
|
private WorkspaceLiveStateController Live { get; }
|
|
private WorkspaceFeedbackService Feedback { get; }
|
|
|
|
The component may keep tiny wrapper methods for lifecycle, `JSInvokable` entry points, disposal, and menu callbacks, but it should not keep a second copy of the state model.
|
|
|
|
In `RpgRoller/Components/Pages/WorkspaceState.cs`, keep all plain state plus pure computed and formatting helpers needed directly by the Razor file. That includes selected campaign name, selected play character projections, screen flags, connection-state label and CSS class, app CSS class, owner labels, and skill-definition labels.
|
|
|
|
Revision note (2026-04-04): Replaced the old blueprint with an ExecPlan, reconciled it against the code already present in the repository, and marked completed versus remaining refactor work after direct file inspection. The reason for this rewrite is that `AGENTS.md` now requires complex refactors to be tracked as ExecPlans maintained under `PLANS.md`.
|
|
|
|
Revision note (2026-04-04 23:03Z): Marked backend shared-helper consolidation and `GameService` facade thinning as complete after implementing `GameAuthorization`, `GameContextResolver`, `GameDtoMapper`, and `GameStateStore` tracker methods. Updated the remaining scope so the next pass starts with `GameRollService` decomposition and later `Workspace` cleanup.
|
|
|
|
Revision note (2026-04-04 23:20Z): Marked backend roll decomposition as complete after extracting `RollEngine`, the ruleset-specific engines, `RollBreakdownFormatter`, and `CampaignLogSummaryBuilder`. Updated the remaining scope so the next pass can focus entirely on `Workspace` cleanup.
|