60 lines
1.6 KiB
Markdown
60 lines
1.6 KiB
Markdown
# Data Model: Edit Combatant
|
|
|
|
**Feature**: 004-edit-combatant
|
|
**Date**: 2026-03-03
|
|
|
|
## Entities
|
|
|
|
### Combatant (unchanged)
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| id | CombatantId (branded string) | Immutable identity |
|
|
| name | string | Mutable — this feature updates it |
|
|
|
|
### Encounter (unchanged structure)
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| combatants | readonly Combatant[] | Edit replaces name in-place by mapping |
|
|
| activeIndex | number | Preserved during edit |
|
|
| roundNumber | number | Preserved during edit |
|
|
|
|
## Events
|
|
|
|
### CombatantUpdated (new)
|
|
|
|
| Field | Type | Notes |
|
|
|-------|------|-------|
|
|
| type | "CombatantUpdated" | Discriminant |
|
|
| combatantId | CombatantId | Which combatant was renamed |
|
|
| oldName | string | Name before edit |
|
|
| newName | string | Name after edit |
|
|
|
|
Added to the `DomainEvent` union type.
|
|
|
|
## State Transitions
|
|
|
|
### editCombatant(encounter, id, newName)
|
|
|
|
**Preconditions**:
|
|
- `newName` is non-empty and not whitespace-only
|
|
- `id` matches a combatant in `encounter.combatants`
|
|
|
|
**Postconditions**:
|
|
- The combatant with matching `id` has `name` set to `newName`
|
|
- `activeIndex` and `roundNumber` unchanged
|
|
- Combatant list order unchanged
|
|
- Exactly one `CombatantUpdated` event emitted
|
|
|
|
**Error cases**:
|
|
- `id` not found → `DomainError { code: "combatant-not-found" }`
|
|
- `newName` empty/whitespace → `DomainError { code: "invalid-name" }`
|
|
|
|
## Validation Rules
|
|
|
|
| Rule | Condition | Error Code |
|
|
|------|-----------|------------|
|
|
| Name must be non-empty | `newName.trim().length === 0` | `"invalid-name"` |
|
|
| Combatant must exist | No combatant with matching `id` | `"combatant-not-found"` |
|