Files
initiative/specs/005-player-characters/research.md
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

4.7 KiB

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.