3.7 KiB
Research: Turn Navigation
R1: RetreatTurn Domain Pattern
Decision: Implement RetreatTurn as a mirror of the existing AdvanceTurn pattern -- a pure function that accepts an Encounter and returns the updated Encounter plus domain events, or a DomainError.
Rationale: The existing AdvanceTurn function (packages/domain/src/advance-turn.ts) establishes a clear pattern: pure function, value-based error handling via DomainError, domain events returned as data. RetreatTurn is its exact inverse, so following the same pattern maintains consistency and testability.
Alternatives considered:
- Generic "move turn" function with direction parameter: Rejected because AdvanceTurn already exists and changing its signature would break the existing API for no benefit. Two focused functions are simpler than one parameterized function.
- Undo/redo stack: Out of scope per spec assumptions. RetreatTurn is a positional operation, not a state-history undo.
R2: Boundary Guard (Cannot Retreat Before Start)
Decision: RetreatTurn returns a DomainError when roundNumber === 1 && activeIndex === 0. This is the earliest possible encounter state.
Rationale: There is no meaningful "previous turn" before the encounter starts. Allowing retreat past this point would require round 0 or negative rounds, which violates INV-3 (roundNumber >= 1). The spec explicitly requires this guard (FR-003, acceptance scenario 3).
Alternatives considered:
- Silently no-op: Rejected because domain operations should be explicit about failures (constitution principle I).
- Allow round 0 as "setup" round: Out of scope, would require spec amendment.
R3: New Domain Events (TurnRetreated, RoundRetreated)
Decision: Introduce two new domain event types: TurnRetreated and RoundRetreated, mirroring the existing TurnAdvanced and RoundAdvanced event shapes.
Rationale: The existing event system uses discriminated unions with a type field. Retreat events should be distinct from advance events so consumers can differentiate the direction of navigation. The shapes mirror their forward counterparts for consistency.
Alternatives considered:
- Reuse TurnAdvanced/RoundAdvanced with a
directionfield: Rejected because it changes the existing event shape (breaking change) and complicates event consumers that only care about forward progression.
R4: UI Placement -- Turn Navigation at Top
Decision: Create a new TurnNavigation component positioned between the header and the combatant list. Move the Next Turn button out of ActionBar into this new component. ActionBar retains only the Add Combatant form.
Rationale: The spec requires turn controls at the top of the tracker (FR-007). The current Next Turn button lives in ActionBar at the bottom. Splitting concerns (turn navigation vs. combatant management) improves the component structure and matches the spec's UX intent.
Alternatives considered:
- Keep Next Turn in ActionBar and duplicate at top: Rejected -- redundant controls create confusion.
- Move entire ActionBar to top: Rejected -- the Add Combatant form is secondary to turn navigation and should not compete for top-of-page prominence.
R5: Disabled State for Previous Turn Button
Decision: The Previous Turn button renders in a disabled state (using the existing Button component's disabled prop) when the encounter is at round 1, activeIndex 0, or when there are no combatants.
Rationale: The spec requires visual indication that Previous Turn is unavailable (FR-008, FR-009). Using the existing Button disabled styling from shadcn/ui-style components ensures visual consistency.
Alternatives considered:
- Hide the button entirely when unavailable: Rejected -- hiding causes layout shifts and makes the control harder to discover.