Simplify workspace composition root

This commit is contained in:
2026-04-05 01:19:12 +02:00
parent 6cdd29ed93
commit b291d0531f
6 changed files with 319 additions and 394 deletions

View File

@@ -19,8 +19,8 @@ The user-visible proof is intentionally boring: after starting the app, logging
- [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.
- [x] (2026-04-04 23:17Z) Finished thinning `RpgRoller/Components/Pages/Workspace.razor.cs`. The mirror block is gone, the Razor file binds through `State` and coordinator surfaces directly, and `WorkspaceState` now owns the pure owner-label and skill-label projections.
- [x] (2026-04-04 23:17Z) Updated `README.md` and this ExecPlan so the documentation reflects the completed backend and frontend decomposition structure.
## Surprises & Discoveries
@@ -36,6 +36,9 @@ The user-visible proof is intentionally boring: after starting the app, logging
- 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 cleanup was safer once the Razor file bound straight to `State` because the mirror aliases were only indirection, not logic.
Evidence: removing the alias block from `Workspace.razor.cs` did not require coordinator behavior changes; the existing smoke tests still covered play, custom rolls, and Rolemaster UI flows successfully.
- 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`.
@@ -67,6 +70,10 @@ The user-visible proof is intentionally boring: after starting the app, logging
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 a few tiny wrapper methods in `Workspace.razor.cs` only where they break coordinator-construction cycles or support component-local lifecycle behavior.
Rationale: direct binding through `State`, `Session`, `Campaigns`, `Play`, `Admin`, `Scope`, and `Live` removed the noisy aliases, but a minimal set of wrapper methods still keeps lazy coordinator construction acyclic and the composition root readable.
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
@@ -75,7 +82,7 @@ The user-visible proof is intentionally boring: after starting the app, logging
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.
The planned decomposition work is now complete. `GameService` is at the intended facade shape, `GameRollService` is primarily orchestration plus persistence-facing log record handling, and `Workspace.razor.cs` now reads as a composition root instead of a duplicated state bag.
## Context and Orientation
@@ -87,9 +94,7 @@ The current backend state is better than the old monolith. `RpgRoller/Services/G
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.
The frontend state is now at the intended shape. `RpgRoller/Components/Pages/WorkspaceState.cs` holds plain UI state plus pure computed and formatting 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`. `RpgRoller/Components/Pages/Workspace.razor.cs` is now mainly the composition root that wires those collaborators together.
## Plan of Work
@@ -134,7 +139,7 @@ Start every future implementation pass by re-reading the plan and checking the c
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:
The implementation work is complete. If a future contributor needs to re-check the final frontend composition surface, start here:
Get-Content RpgRoller\Components\Pages\Workspace.razor.cs
Get-Content RpgRoller\Components\Pages\Workspace.razor
@@ -142,17 +147,7 @@ The remaining implementation pass is frontend-focused. Begin by inspecting the `
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.
The final frontend layout keeps child-component contracts stable while binding through `State` and the coordinator surfaces directly.
When code work resumes later, validate after each meaningful iteration with the repo-standard commands:
@@ -163,7 +158,7 @@ The expected result is simple: no failing tests, no coverage regression, and the
## 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.
This implementation revision ran targeted helper tests during extraction, added `WorkspaceState` tests for the new pure projections, and then ran full repo validation through `pwsh ./scripts/ci-local.ps1`.
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.
@@ -275,3 +270,5 @@ Revision note (2026-04-04): Replaced the old blueprint with an ExecPlan, reconci
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.
Revision note (2026-04-04 23:17Z): Marked `Workspace` cleanup as complete after deleting the state-mirroring alias block, moving pure display helpers into `WorkspaceState`, rebinding the Razor file through `State` and coordinator surfaces, and adding `WorkspaceState` coverage tests.