Add refactor blueprint tasks
This commit is contained in:
907
TASKS.md
Normal file
907
TASKS.md
Normal file
@@ -0,0 +1,907 @@
|
||||
# Refactor Blueprint: GameService and Workspace
|
||||
|
||||
## Purpose
|
||||
|
||||
This document is the implementation blueprint for splitting two oversized classes without changing product behavior:
|
||||
|
||||
- `RpgRoller/Services/GameService.cs`
|
||||
- `RpgRoller/Components/Pages/Workspace.razor.cs`
|
||||
|
||||
The goal is to reduce future churn, make responsibilities explicit, improve testability, and create stable seams for future features.
|
||||
|
||||
This is a planning artifact only. It does not authorize behavior changes. During implementation, preserve the existing public API contracts and current user-visible behavior unless a separate task explicitly requests functional changes.
|
||||
|
||||
## Primary Constraints
|
||||
|
||||
- Do not use partial classes as the main decomposition strategy.
|
||||
- Prefer composition of small sealed classes with clear ownership.
|
||||
- Keep `IGameService` stable during the refactor unless a separate change requests an API redesign.
|
||||
- Preserve current endpoint contracts and `WorkspaceQueryService` behavior.
|
||||
- Preserve current workspace UX and screen flow.
|
||||
- Do not revert unrelated local changes already present in the repo.
|
||||
- Keep changes incremental and validation-heavy.
|
||||
|
||||
## Desired End State
|
||||
|
||||
### Backend
|
||||
|
||||
- `GameService` becomes a thin façade/coordinator.
|
||||
- Stateful concerns are centralized in a shared state owner instead of spread across one giant class.
|
||||
- Domain workflows are split into small sealed collaborators.
|
||||
- Pure logic helpers move into small focused files.
|
||||
- File boundaries reflect change frequency and business ownership.
|
||||
|
||||
### Frontend
|
||||
|
||||
- `Workspace` becomes a thin Blazor component coordinator.
|
||||
- State, orchestration, admin actions, play/log actions, and live event synchronization live in separate sealed collaborators.
|
||||
- Razor bindings reference composed state/actions objects instead of one monolithic code-behind surface.
|
||||
- Existing child component boundaries remain intact unless a very small binding adjustment is needed.
|
||||
|
||||
## Current Problems To Solve
|
||||
|
||||
### GameService
|
||||
|
||||
Observed concerns mixed together today:
|
||||
|
||||
- authentication and session lifecycle
|
||||
- admin user management
|
||||
- campaign lifecycle
|
||||
- character lifecycle and ownership transfer
|
||||
- skill and skill-group lifecycle
|
||||
- dice validation and roll execution
|
||||
- campaign log shaping and roll-detail formatting
|
||||
- DTO mapping
|
||||
- in-memory state tracking
|
||||
- persistence load/save/cloning
|
||||
|
||||
This creates the wrong coupling pattern:
|
||||
|
||||
- changing one workflow risks merge conflicts with unrelated work
|
||||
- high-level service methods depend on low-level helper implementation details
|
||||
- lock/state/persistence concerns are interleaved with domain logic
|
||||
- roll engine logic is too close to CRUD flows
|
||||
- pure helpers are harder to discover and test in isolation
|
||||
|
||||
### Workspace
|
||||
|
||||
Observed concerns mixed together today:
|
||||
|
||||
- initial bootstrap
|
||||
- session reload/logout
|
||||
- screen selection and persistence
|
||||
- campaign selection and scope refresh
|
||||
- management actions
|
||||
- play-screen actions
|
||||
- campaign log detail caching
|
||||
- admin actions
|
||||
- live state event synchronization
|
||||
- toast/status handling
|
||||
- UI state storage and computed properties
|
||||
|
||||
This creates the wrong coupling pattern:
|
||||
|
||||
- every change lands in one file
|
||||
- state mutation rules are difficult to audit
|
||||
- JS interop ownership is spread across unrelated methods
|
||||
- event-driven refresh logic is coupled to screen and toast logic
|
||||
- the Razor file binds against too many members on one type
|
||||
|
||||
## Architecture Direction
|
||||
|
||||
## 1. Backend Composition Strategy
|
||||
|
||||
### 1.1 Keep a thin GameService façade
|
||||
|
||||
`GameService` should stay as the application-facing implementation of `IGameService`, but it should stop owning every workflow directly.
|
||||
|
||||
Target shape:
|
||||
|
||||
- constructor wires shared collaborators
|
||||
- each public `IGameService` method delegates to one domain-oriented sealed service
|
||||
- the façade owns no domain-heavy private logic
|
||||
- only lightweight delegation and composition remain in `GameService`
|
||||
|
||||
This preserves integration points while still removing the monolith.
|
||||
|
||||
### 1.2 Introduce a shared state owner
|
||||
|
||||
Create a single shared state owner to encapsulate the in-memory model, synchronization gate, and persistence coordination used by the composed backend services.
|
||||
|
||||
Candidate responsibility set:
|
||||
|
||||
- lock object
|
||||
- dictionaries/lists currently stored in `GameService`
|
||||
- lookup helpers for common state access
|
||||
- campaign-state tracker storage
|
||||
- database load/save orchestration hooks
|
||||
|
||||
Recommended candidate names:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GameRuntimeState`
|
||||
- `GameStateCoordinator`
|
||||
|
||||
Preferred choice: `GameStateStore`
|
||||
|
||||
Why:
|
||||
|
||||
- "Store" matches the fact that it owns mutable in-memory state
|
||||
- it avoids implying EF persistence semantics
|
||||
- it gives the rest of the services a stable dependency name
|
||||
|
||||
This shared state owner must be the only place that exposes the mutable collections and the synchronization gate needed by the domain services.
|
||||
|
||||
### 1.3 Split backend workflows into domain services
|
||||
|
||||
Create sealed services grouped by real business seams, not arbitrary line-count slices.
|
||||
|
||||
Recommended services:
|
||||
|
||||
1. `GameAuthService`
|
||||
2. `GameUserAdministrationService`
|
||||
3. `GameCampaignService`
|
||||
4. `GameCharacterService`
|
||||
5. `GameSkillService`
|
||||
6. `GameRollService`
|
||||
7. `GamePersistenceService`
|
||||
|
||||
Each service should be small enough to own one family of change, but large enough that one feature does not have to hop across many tiny orchestration layers.
|
||||
|
||||
### 1.4 Extract pure helper classes into separate files
|
||||
|
||||
Pure or near-pure helpers should not stay hidden inside the coordinator or domain services.
|
||||
|
||||
Recommended helper extraction:
|
||||
|
||||
- `SkillDefinitionValidator`
|
||||
- `RollVisibilityParser`
|
||||
- `CustomRollOptionsResolver`
|
||||
- `RollBreakdownFormatter`
|
||||
- `CampaignLogSummaryBuilder`
|
||||
- `GameDtoMapper`
|
||||
- `GameStateCloneFactory`
|
||||
- `RoleSerializer`
|
||||
|
||||
If a helper remains too broad after extraction, split again. Small helper files are desirable here because they are stable, discoverable, and easy to test.
|
||||
|
||||
## 2. Backend Responsibility Map
|
||||
|
||||
### 2.1 GameService
|
||||
|
||||
`GameService` should delegate the following methods:
|
||||
|
||||
- auth/session
|
||||
- `Register`
|
||||
- `Login`
|
||||
- `Logout`
|
||||
- `GetUserBySession`
|
||||
- `GetMe`
|
||||
- campaigns
|
||||
- `CreateCampaign`
|
||||
- `GetCampaigns`
|
||||
- `GetCharacterCampaignOptions`
|
||||
- `GetCampaign`
|
||||
- `DeleteCampaign`
|
||||
- users/admin
|
||||
- `GetUsernames`
|
||||
- `GetUsers`
|
||||
- `UpdateUserRoles`
|
||||
- `DeleteUser`
|
||||
- characters
|
||||
- `CreateCharacter`
|
||||
- `UpdateCharacter`
|
||||
- `DeleteCharacter`
|
||||
- `ActivateCharacter`
|
||||
- `GetOwnCharacters`
|
||||
- skills
|
||||
- `CreateSkillGroup`
|
||||
- `UpdateSkillGroup`
|
||||
- `DeleteSkillGroup`
|
||||
- `CreateSkill`
|
||||
- `UpdateSkill`
|
||||
- `DeleteSkill`
|
||||
- `GetCharacterSheet`
|
||||
- rolls/log
|
||||
- `RollSkill`
|
||||
- `RollCustom`
|
||||
- `GetCampaignLog`
|
||||
- `GetCampaignLogPage`
|
||||
- `GetRollDetail`
|
||||
- `GetCampaignStateSnapshot`
|
||||
- rulesets
|
||||
- `GetRulesets`
|
||||
|
||||
### 2.2 GameAuthService
|
||||
|
||||
Own:
|
||||
|
||||
- register/login/logout/session lookup
|
||||
- me-response shaping tied to session and active-character resolution
|
||||
- username normalization usage in auth flows
|
||||
- session creation
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `IPasswordHasher<UserAccount>`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
|
||||
### 2.3 GameUserAdministrationService
|
||||
|
||||
Own:
|
||||
|
||||
- `GetUsernames`
|
||||
- `GetUsers`
|
||||
- `UpdateUserRoles`
|
||||
- `DeleteUser`
|
||||
- role checks used by admin flows
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
- `RoleSerializer`
|
||||
- common authorization helpers
|
||||
|
||||
### 2.4 GameCampaignService
|
||||
|
||||
Own:
|
||||
|
||||
- create/list/get/delete campaign flows
|
||||
- campaign visibility rules
|
||||
- campaign context resolution for viewable campaigns
|
||||
- campaign roster shaping
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
- shared authorization/context helpers
|
||||
|
||||
### 2.5 GameCharacterService
|
||||
|
||||
Own:
|
||||
|
||||
- create/update/delete/activate character flows
|
||||
- owner transfer rules
|
||||
- active-character consistency rules
|
||||
- own-character listing
|
||||
- character-to-campaign state tracker updates
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
- shared authorization/context helpers
|
||||
|
||||
### 2.6 GameSkillService
|
||||
|
||||
Own:
|
||||
|
||||
- skill-group CRUD
|
||||
- skill CRUD
|
||||
- character sheet read model
|
||||
- skill-group assignment rules
|
||||
- skill definition validation orchestration
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
- `SkillDefinitionValidator`
|
||||
- shared authorization/context helpers
|
||||
|
||||
### 2.7 GameRollService
|
||||
|
||||
Own:
|
||||
|
||||
- `RollSkill`
|
||||
- `RollCustom`
|
||||
- campaign log reads
|
||||
- log page reads
|
||||
- roll detail reads
|
||||
- campaign state snapshot reads
|
||||
- roll recording
|
||||
- log visibility checks
|
||||
- compact log summary generation
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `GameStateStore`
|
||||
- `GamePersistenceService`
|
||||
- `GameDtoMapper`
|
||||
- `SkillDefinitionValidator` only if needed for custom roll flows
|
||||
- `RollVisibilityParser`
|
||||
- `RollEngine`
|
||||
- `RollBreakdownFormatter`
|
||||
- `CampaignLogSummaryBuilder`
|
||||
|
||||
### 2.8 GamePersistenceService
|
||||
|
||||
Own:
|
||||
|
||||
- load state from database at startup
|
||||
- persist current runtime state
|
||||
- clone/snapshot helpers used for persistence boundaries
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `IDbContextFactory<RpgRollerDbContext>`
|
||||
- `GameStateStore`
|
||||
- `GameStateCloneFactory`
|
||||
|
||||
Implementation note:
|
||||
|
||||
- keep EF persistence concerns here
|
||||
- do not let other domain services talk directly to EF unless there is a deliberate redesign later
|
||||
|
||||
## 3. Backend Cross-Cutting Helpers
|
||||
|
||||
Some logic is shared across multiple domain services but should still remain explicit instead of hidden in the state store.
|
||||
|
||||
Recommended small helper files:
|
||||
|
||||
### Authorization and context
|
||||
|
||||
- `GameAuthorization`
|
||||
- user role check helpers
|
||||
- can-view campaign
|
||||
- can-edit character
|
||||
- can-view roll
|
||||
|
||||
- `GameContextResolver`
|
||||
- resolve user from session
|
||||
- resolve campaign context
|
||||
- resolve character campaign
|
||||
- resolve current campaign id
|
||||
|
||||
These can be static helper classes if they remain pure over store inputs, or sealed services if constructor-injected collaborators make the code cleaner. Prefer the simpler shape once implementation starts.
|
||||
|
||||
### Mapping
|
||||
|
||||
- `GameDtoMapper`
|
||||
- `ToUserSummary`
|
||||
- `ToAdminUserSummary`
|
||||
- `ToCampaignOption`
|
||||
- `ToCampaignSummary`
|
||||
- `ToCampaignRoster`
|
||||
- `ToCharacterSheet`
|
||||
- `ToCharacterSummary`
|
||||
- `ToCampaignStateSnapshot`
|
||||
- `ToSkillGroupSummary`
|
||||
- `ToSkillSummary`
|
||||
- `ToRollResult`
|
||||
- `ToLogEntry`
|
||||
- `ToLogListEntry`
|
||||
|
||||
### Persistence and clone helpers
|
||||
|
||||
- `GameStateCloneFactory`
|
||||
- clone user/session/campaign/character/skill/skill-group/roll-log entry
|
||||
|
||||
- `RoleSerializer`
|
||||
- parse/serialize/normalize roles
|
||||
|
||||
### Roll engine helpers
|
||||
|
||||
- `SkillDefinitionValidator`
|
||||
- expression parse/option validation
|
||||
|
||||
- `RollEngine`
|
||||
- top-level roll dispatch by ruleset
|
||||
|
||||
- `StandardRollEngine`
|
||||
- `D6RollEngine`
|
||||
- `RolemasterRollEngine`
|
||||
|
||||
- `RollBreakdownFormatter`
|
||||
- shared textual breakdown formatting
|
||||
|
||||
- `CampaignLogSummaryBuilder`
|
||||
- compact summary + badges + expression extraction helpers
|
||||
|
||||
The roll-engine area is a strong candidate for several very small files because the behavior is algorithmic and changes independently from CRUD flows.
|
||||
|
||||
## 4. Recommended Backend File Layout
|
||||
|
||||
One reasonable target layout:
|
||||
|
||||
```text
|
||||
RpgRoller/Services/
|
||||
GameService.cs
|
||||
GameStateStore.cs
|
||||
GamePersistenceService.cs
|
||||
GameAuthService.cs
|
||||
GameUserAdministrationService.cs
|
||||
GameCampaignService.cs
|
||||
GameCharacterService.cs
|
||||
GameSkillService.cs
|
||||
GameRollService.cs
|
||||
GameAuthorization.cs
|
||||
GameContextResolver.cs
|
||||
GameDtoMapper.cs
|
||||
GameStateCloneFactory.cs
|
||||
RoleSerializer.cs
|
||||
SkillDefinitionValidator.cs
|
||||
RollVisibilityParser.cs
|
||||
CustomRollOptionsResolver.cs
|
||||
RollEngine.cs
|
||||
StandardRollEngine.cs
|
||||
D6RollEngine.cs
|
||||
RolemasterRollEngine.cs
|
||||
RollBreakdownFormatter.cs
|
||||
CampaignLogSummaryBuilder.cs
|
||||
```
|
||||
|
||||
Exact filenames can be adjusted during implementation if a nearby convention in the repo suggests better naming. The important part is the separation of responsibilities, not the precise suffix.
|
||||
|
||||
## 5. Frontend Composition Strategy
|
||||
|
||||
### 5.1 Keep Workspace as the component boundary
|
||||
|
||||
`Workspace` should remain the Blazor component type used by `Workspace.razor`, but it should stop directly owning all workflow code.
|
||||
|
||||
Target shape:
|
||||
|
||||
- `Workspace` keeps injected dependencies and lifecycle entry points
|
||||
- composed sealed collaborators handle most behavior
|
||||
- `Workspace` exposes only the state/actions needed by the Razor file
|
||||
|
||||
This avoids breaking the component boundary while still removing the monolith.
|
||||
|
||||
### 5.2 Introduce a dedicated state holder
|
||||
|
||||
Create a single state holder for workspace UI state and view-model data.
|
||||
|
||||
Recommended candidate name:
|
||||
|
||||
- `WorkspaceState`
|
||||
|
||||
Own:
|
||||
|
||||
- authenticated user/session-derived state
|
||||
- selected campaign and character state
|
||||
- campaign collections
|
||||
- rule set collections
|
||||
- admin user collection
|
||||
- log detail cache
|
||||
- toast state
|
||||
- screen/mobile-panel state
|
||||
- live connection state
|
||||
- modal state
|
||||
- computed helpers that are pure projections over stored state
|
||||
|
||||
This state holder should be easy to inspect and should reduce the risk of hidden cross-method mutations.
|
||||
|
||||
### 5.3 Split workspace orchestration into sealed collaborators
|
||||
|
||||
Recommended services:
|
||||
|
||||
1. `WorkspaceSessionCoordinator`
|
||||
2. `WorkspaceCampaignCoordinator`
|
||||
3. `WorkspacePlayCoordinator`
|
||||
4. `WorkspaceAdminCoordinator`
|
||||
5. `WorkspaceLiveStateController`
|
||||
6. `WorkspaceFeedbackService`
|
||||
|
||||
These names can be adjusted, but the responsibilities should stay distinct.
|
||||
|
||||
## 6. Frontend Responsibility Map
|
||||
|
||||
### 6.1 Workspace component
|
||||
|
||||
Own:
|
||||
|
||||
- injected dependencies
|
||||
- collaborator construction/wiring
|
||||
- lifecycle delegation
|
||||
- JS-invokable entry points delegating to collaborators
|
||||
- final binding surface consumed by Razor
|
||||
|
||||
Keep thin. Do not let it become a second coordinator monolith with only renamed methods.
|
||||
|
||||
### 6.2 WorkspaceState
|
||||
|
||||
Own:
|
||||
|
||||
- scalar UI state
|
||||
- collections
|
||||
- selected entities
|
||||
- modal visibility and form bootstrap models
|
||||
- log detail cache and loading/error maps
|
||||
- toast list
|
||||
- computed view properties
|
||||
|
||||
Recommended computed properties to live here if kept pure:
|
||||
|
||||
- selected campaign name
|
||||
- selected character
|
||||
- play-selected campaign
|
||||
- play-selected character
|
||||
- play-selected character id
|
||||
- filtered play skill and skill-group views if still direct projections
|
||||
- current-user role flags
|
||||
- campaign delete permission
|
||||
- screen flags
|
||||
- connection-state label/css
|
||||
- app CSS class
|
||||
|
||||
### 6.3 WorkspaceSessionCoordinator
|
||||
|
||||
Own:
|
||||
|
||||
- initial bootstrap sequence
|
||||
- session reload
|
||||
- logout flow
|
||||
- health retry flow
|
||||
- persisted session-storage reads/writes for screen, panel, campaign, roll visibility
|
||||
- clearing authenticated state
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- `WorkspaceQueryService`
|
||||
- `RpgRollerApiClient`
|
||||
- `IJSRuntime`
|
||||
- `WorkspaceLiveStateController`
|
||||
- `WorkspaceFeedbackService`
|
||||
|
||||
### 6.4 WorkspaceCampaignCoordinator
|
||||
|
||||
Own:
|
||||
|
||||
- campaign reload and selection
|
||||
- campaign scope refresh
|
||||
- campaign roster refresh
|
||||
- character campaign options reload
|
||||
- management-screen campaign and character mutations
|
||||
- character modal open/close and bootstrap
|
||||
- selected-character synchronization
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- `WorkspaceQueryService`
|
||||
- `RpgRollerApiClient`
|
||||
- `IJSRuntime`
|
||||
- `WorkspaceLiveStateController`
|
||||
- `WorkspaceFeedbackService`
|
||||
|
||||
### 6.5 WorkspacePlayCoordinator
|
||||
|
||||
Own:
|
||||
|
||||
- selected character activation
|
||||
- selected character sheet refresh
|
||||
- skill roll submission
|
||||
- custom roll handling
|
||||
- campaign log page refresh
|
||||
- roll detail expansion/loading/cache trimming
|
||||
- roll visibility changes
|
||||
- play-panel errors
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- `WorkspaceQueryService`
|
||||
- `RpgRollerApiClient`
|
||||
- `IJSRuntime`
|
||||
- `WorkspaceFeedbackService`
|
||||
|
||||
### 6.6 WorkspaceAdminCoordinator
|
||||
|
||||
Own:
|
||||
|
||||
- admin screen access enforcement
|
||||
- admin user load
|
||||
- toggle admin role
|
||||
- delete user
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- `WorkspaceQueryService`
|
||||
- `RpgRollerApiClient`
|
||||
- `IJSRuntime`
|
||||
- `WorkspaceLiveStateController`
|
||||
- `WorkspaceFeedbackService`
|
||||
|
||||
### 6.7 WorkspaceLiveStateController
|
||||
|
||||
Own:
|
||||
|
||||
- start/stop SSE state events
|
||||
- JS invokable state snapshot handling
|
||||
- JS invokable connection-state updates
|
||||
- current campaign-state refresh logic
|
||||
- campaign-state tracking reset logic
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- `WorkspaceCampaignCoordinator`
|
||||
- `WorkspacePlayCoordinator`
|
||||
- `IJSRuntime`
|
||||
|
||||
Critical rule:
|
||||
|
||||
- this class should own the live-update reconciliation logic
|
||||
- do not let campaign reload logic and live event logic drift into different locations again
|
||||
|
||||
### 6.8 WorkspaceFeedbackService
|
||||
|
||||
Own:
|
||||
|
||||
- status-to-toast behavior
|
||||
- live announcement updates
|
||||
- delayed toast dismissal
|
||||
|
||||
Dependencies:
|
||||
|
||||
- `WorkspaceState`
|
||||
- component refresh callback or dispatcher mechanism
|
||||
|
||||
Implementation note:
|
||||
|
||||
- if callback wiring becomes awkward, this service can remain a helper owned by `Workspace` rather than a DI service
|
||||
- the important part is isolating feedback behavior from campaign/admin/play workflows
|
||||
|
||||
## 7. Frontend Binding Strategy
|
||||
|
||||
If composition is used, `Workspace.razor` bindings will need to reference state and actions through the new composed surface.
|
||||
|
||||
Example direction:
|
||||
|
||||
- state data from `State`
|
||||
- management actions from `Campaigns`
|
||||
- play actions from `Play`
|
||||
- admin actions from `Admin`
|
||||
- session/bootstrap actions from `Session`
|
||||
|
||||
Illustrative pattern only:
|
||||
|
||||
```razor
|
||||
<AppHeader
|
||||
User="State.User"
|
||||
CampaignName="@State.SelectedCampaignName"
|
||||
LogoutRequested="Session.LogoutAsync" />
|
||||
```
|
||||
|
||||
Another example:
|
||||
|
||||
```razor
|
||||
<CampaignManagementPanel
|
||||
Campaigns="State.Campaigns"
|
||||
SelectedCampaignId="State.SelectedCampaignId"
|
||||
CampaignSelectionChanged="Campaigns.OnCampaignSelectionChangedAsync"
|
||||
DeleteCampaignRequested="Campaigns.DeleteSelectedCampaignAsync" />
|
||||
```
|
||||
|
||||
Important guardrail:
|
||||
|
||||
- avoid replacing one huge component surface with many deeply chained bindings that are hard to read
|
||||
- prefer a small number of clearly named composed properties on `Workspace`
|
||||
|
||||
For example:
|
||||
|
||||
- `State`
|
||||
- `Session`
|
||||
- `Campaigns`
|
||||
- `Play`
|
||||
- `Admin`
|
||||
- `Live`
|
||||
|
||||
## 8. Recommended Frontend File Layout
|
||||
|
||||
One reasonable target layout:
|
||||
|
||||
```text
|
||||
RpgRoller/Components/Pages/
|
||||
Workspace.razor
|
||||
Workspace.razor.cs
|
||||
WorkspaceState.cs
|
||||
WorkspaceSessionCoordinator.cs
|
||||
WorkspaceCampaignCoordinator.cs
|
||||
WorkspacePlayCoordinator.cs
|
||||
WorkspaceAdminCoordinator.cs
|
||||
WorkspaceLiveStateController.cs
|
||||
WorkspaceFeedbackService.cs
|
||||
WorkspaceToast.cs
|
||||
```
|
||||
|
||||
If some files become too small, merge only where the change patterns are clearly the same. Do not merge simply to reduce file count.
|
||||
|
||||
## 9. Implementation Order
|
||||
|
||||
The implementation phase should proceed in small safe steps.
|
||||
|
||||
### Phase 1: Backend enabling extractions
|
||||
|
||||
1. Extract pure helper classes from `GameService` first.
|
||||
2. Keep `GameService` behavior identical while moving algorithmic helpers out.
|
||||
3. Add or update tests around the extracted helpers if coverage or confidence drops.
|
||||
|
||||
Why first:
|
||||
|
||||
- lowest integration risk
|
||||
- creates stable dependencies for later service extraction
|
||||
|
||||
### Phase 2: Introduce GameStateStore and GamePersistenceService
|
||||
|
||||
1. Move runtime collections, gate, and persistence primitives behind the shared state owner.
|
||||
2. Move database load/save logic into persistence service.
|
||||
3. Keep `GameService` still acting as the main orchestrator while the shared dependencies settle.
|
||||
|
||||
Why second:
|
||||
|
||||
- makes later service extraction mechanical instead of risky
|
||||
|
||||
### Phase 3: Extract backend domain services
|
||||
|
||||
Suggested order:
|
||||
|
||||
1. auth
|
||||
2. campaigns
|
||||
3. characters
|
||||
4. skills
|
||||
5. rolls/log
|
||||
6. admin/users
|
||||
|
||||
This order keeps the highest-risk workflow area, rolls/logs, until the shared helpers and state seams are already stable.
|
||||
|
||||
### Phase 4: Thin GameService façade
|
||||
|
||||
1. Replace remaining logic in `GameService` with delegation only.
|
||||
2. Confirm constructor wiring stays readable.
|
||||
3. Re-run tests and local CI.
|
||||
|
||||
### Phase 5: Frontend state extraction
|
||||
|
||||
1. Introduce `WorkspaceState`.
|
||||
2. Move raw state and pure computed projections first.
|
||||
3. Keep method behavior temporarily in `Workspace` until state references are stabilized.
|
||||
|
||||
Why first:
|
||||
|
||||
- it shrinks the cognitive load before moving orchestration
|
||||
|
||||
### Phase 6: Frontend coordinator extraction
|
||||
|
||||
Suggested order:
|
||||
|
||||
1. feedback/toasts
|
||||
2. session/bootstrap
|
||||
3. campaign management
|
||||
4. play/log
|
||||
5. admin
|
||||
6. live-state controller
|
||||
|
||||
This order reduces the chance that live event logic is extracted before its dependent refresh flows are already stable.
|
||||
|
||||
### Phase 7: Razor binding cleanup
|
||||
|
||||
1. Update bindings to the new composed surface.
|
||||
2. Keep markup structure stable.
|
||||
3. Avoid stylistic churn unrelated to composition.
|
||||
|
||||
### Phase 8: Documentation and verification
|
||||
|
||||
1. Update `README.md` code-organization notes if file layout changed materially.
|
||||
2. Run local CI.
|
||||
3. Run Playwright smoke flow.
|
||||
4. Re-check coverage expectations.
|
||||
|
||||
## 10. Testing and Validation Expectations
|
||||
|
||||
During implementation, after each meaningful iteration:
|
||||
|
||||
- run `pwsh ./scripts/ci-local.ps1`
|
||||
- verify test coverage remains acceptable relative to the repo rules
|
||||
- add tests where extraction creates untested seams
|
||||
- after frontend-affecting iterations, run the Playwright smoke flow
|
||||
|
||||
Specific test focus areas:
|
||||
|
||||
### Backend
|
||||
|
||||
- auth/session regression
|
||||
- campaign visibility rules
|
||||
- character ownership transfer
|
||||
- skill-group assignment and validation
|
||||
- D6 roll behavior
|
||||
- Rolemaster roll behavior
|
||||
- custom roll behavior
|
||||
- log paging and detail visibility
|
||||
- persistence reload behavior
|
||||
- admin role mutation and user deletion side effects
|
||||
|
||||
### Frontend
|
||||
|
||||
- workspace bootstrap still restores screen/campaign/panel/visibility preferences
|
||||
- admin screen still enforces role access
|
||||
- play screen still refreshes log/roster/sheet correctly
|
||||
- newly recorded roll still appears and auto-expands as before
|
||||
- live connection state still updates announcements and fallback state
|
||||
|
||||
## 11. Risks and Guardrails
|
||||
|
||||
### Risk: Hidden behavior drift during backend extraction
|
||||
|
||||
Guardrail:
|
||||
|
||||
- keep `IGameService` contract fixed
|
||||
- extract pure helpers before orchestration changes
|
||||
- preserve method-level tests and add new coverage around moved logic
|
||||
|
||||
### Risk: Locking and persistence bugs after splitting GameService
|
||||
|
||||
Guardrail:
|
||||
|
||||
- centralize mutable runtime state in one store
|
||||
- centralize persistence writes in one service
|
||||
- do not let each domain service invent its own lock pattern
|
||||
|
||||
### Risk: Workspace composition creates binding sprawl
|
||||
|
||||
Guardrail:
|
||||
|
||||
- expose a small composed surface from `Workspace`
|
||||
- keep naming direct and role-based
|
||||
- avoid deep object chains in Razor
|
||||
|
||||
### Risk: Live-state refresh logic breaks after movement
|
||||
|
||||
Guardrail:
|
||||
|
||||
- move refresh logic together with live event handling ownership
|
||||
- keep campaign roster, selected sheet, and log refresh paths explicit
|
||||
- verify with Playwright after frontend extraction
|
||||
|
||||
### Risk: Over-fragmentation
|
||||
|
||||
Guardrail:
|
||||
|
||||
- many small helper files are good for pure logic
|
||||
- domain orchestration services should remain coarse enough to own meaningful workflows
|
||||
- do not create tiny services that only forward one call with no real ownership
|
||||
|
||||
## 12. Non-Goals
|
||||
|
||||
The implementation phase described by this blueprint should not, by default:
|
||||
|
||||
- redesign API contracts
|
||||
- redesign the workspace UX
|
||||
- replace Blazor patterns with a different UI architecture
|
||||
- change persistence strategy away from the current runtime-state-plus-SQLite model
|
||||
- introduce unrelated feature work
|
||||
|
||||
## 13. Definition of Done For The Refactor
|
||||
|
||||
The refactor is complete when all of the following are true:
|
||||
|
||||
- `GameService` is a thin coordinator, not a monolith
|
||||
- backend workflows are owned by composed sealed classes with clear responsibility boundaries
|
||||
- algorithmic and mapping helpers live in small focused files
|
||||
- `Workspace` is a thin component coordinator, not a monolithic code-behind
|
||||
- workspace state and workflow orchestration are separated cleanly
|
||||
- `Workspace.razor` binds against a readable composed surface
|
||||
- tests still pass
|
||||
- local CI still passes
|
||||
- frontend smoke verification still passes
|
||||
- documentation matches the resulting structure
|
||||
|
||||
## 14. Implementation Reminder
|
||||
|
||||
During the actual edit phase:
|
||||
|
||||
- keep changes small
|
||||
- validate often
|
||||
- do not revert unrelated user changes
|
||||
- preserve behavior first
|
||||
- optimize for reducing future churn, not merely reducing line count
|
||||
Reference in New Issue
Block a user