Files
Lukas 91703ddebc
All checks were successful
CI / check (push) Successful in 45s
CI / build-image (push) Successful in 18s
Add player character management feature
Persistent player character templates (name, AC, HP, color, icon) with
full CRUD, bestiary-style search to add PCs to encounters with pre-filled
stats, and color/icon visual distinction in combatant rows. Also stops
the stat block panel from auto-opening when adding a creature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:11:08 +01:00

67 lines
4.7 KiB
Markdown

# Research: Player Character Management
**Branch**: `005-player-characters` | **Date**: 2026-03-12
## Key Decisions
### 1. PlayerCharacter as a separate domain entity
- **Decision**: `PlayerCharacter` is a new type in the domain layer, distinct from `Combatant`. When added to an encounter, a snapshot is copied into a `Combatant`.
- **Rationale**: Player characters are persistent templates reused across encounters; combatants are ephemeral per-encounter instances. Mixing them would violate the single-responsibility of the `Encounter` aggregate.
- **Alternatives considered**: Extending `Combatant` with a `isPlayerCharacter` flag — rejected because combatants belong to encounters and are cleared with them, while player characters must survive encounter clears.
### 2. Combatant type extension for color and icon
- **Decision**: Add optional `color?: string` and `icon?: string` fields to the existing `Combatant` interface, plus `playerCharacterId?: PlayerCharacterId` to track origin.
- **Rationale**: The combatant row needs to render color/icon. Storing these on the combatant (copied from the player character at add-time) keeps the combatant self-contained and avoids runtime lookups against the player character store.
- **Alternatives considered**: Looking up color/icon from the player character store at render time — rejected because the player character might be deleted or edited after the combatant was added, and the spec says combatants are independent copies.
### 3. Storage: separate localStorage key
- **Decision**: Use `localStorage` with key `"initiative:player-characters"`, separate from encounter storage (`"initiative:encounter"`).
- **Rationale**: Follows existing pattern (encounter uses its own key). Player characters must survive encounter clears. IndexedDB is overkill for a small list of player characters.
- **Alternatives considered**: IndexedDB (like bestiary cache) — rejected as overly complex for simple JSON list. Shared key with encounter — rejected because clearing encounter would wipe player characters.
### 4. Search integration approach
- **Decision**: The `useBestiary` search hook or a new `usePlayerCharacters` hook provides player character search results. The `ActionBar` dropdown renders a "Players" group above bestiary results.
- **Rationale**: Player character search is a simple substring match on a small in-memory list — no index needed. Keeping it separate from bestiary search maintains separation of concerns.
- **Alternatives considered**: Merging into the bestiary index — rejected because player characters are user-created, not part of the pre-built index.
### 5. Color palette and icon set
- **Decision**: Use a fixed set of 10 distinguishable colors and ~15 Lucide icons already available in the project.
- **Rationale**: Lucide React is already a dependency. A fixed palette ensures visual consistency and simplifies the domain model (color is a string enum, not arbitrary hex).
- **Alternatives considered**: Arbitrary hex color picker — rejected for MVP as it complicates UX and validation.
### 6. Domain operations pattern
- **Decision**: Player character CRUD follows the same pattern as encounter operations: pure functions returning `{result, events} | DomainError`. New domain events: `PlayerCharacterCreated`, `PlayerCharacterUpdated`, `PlayerCharacterDeleted`.
- **Rationale**: Consistency with existing domain patterns. Events enable future features (undo, audit).
- **Alternatives considered**: Simpler CRUD without events — rejected for consistency with the project's event-driven domain.
### 7. Management view location
- **Decision**: A new icon button in the bottom bar (alongside the existing search) opens a player character management panel/modal.
- **Rationale**: The bottom bar already serves as the primary action area. A modal keeps the management view accessible without adding routing complexity.
- **Alternatives considered**: A separate route/page — rejected because the app is currently a single-page encounter tracker with no routing.
## Cross-feature impacts
### Spec 001 (Combatant Management)
- `Combatant` type gains three optional fields: `color`, `icon`, `playerCharacterId`
- `encounter-storage.ts` rehydration needs to handle new optional fields
- Combatant row component needs to render color/icon when present
### Spec 003 (Combatant State)
- No changes needed. AC and HP management already works on optional fields that player characters pre-fill.
### Spec 004 (Bestiary)
- ActionBar dropdown gains a "Players" section above bestiary results
- `addFromBestiary` pattern informs the new `addFromPlayerCharacter` flow
- No changes to bestiary search itself
## Unresolved items
None. All technical decisions are resolved.