50 lines
3.7 KiB
Markdown
50 lines
3.7 KiB
Markdown
# 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 `direction` field: 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.
|