# 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.