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

4.4 KiB

Data Model: Player Character Management

Branch: 005-player-characters | Date: 2026-03-12

New Entities

PlayerCharacterId (branded type)

PlayerCharacterId = string & { __brand: "PlayerCharacterId" }

Unique identifier for player characters. Generated by the application layer (same pattern as CombatantId).

PlayerCharacter

Field Type Required Constraints
id PlayerCharacterId yes Unique, immutable after creation
name string yes Non-empty after trimming
ac number yes Non-negative integer
maxHp number yes Positive integer
color PlayerColor yes One of predefined color values
icon PlayerIcon yes One of predefined icon identifiers

PlayerColor (constrained string)

Predefined set of ~10 distinguishable color identifiers: "red" | "blue" | "green" | "purple" | "orange" | "pink" | "cyan" | "yellow" | "emerald" | "indigo"

Each maps to a specific hex/tailwind value at the adapter layer.

PlayerIcon (constrained string)

Predefined set of ~15 Lucide icon identifiers: "sword" | "shield" | "skull" | "heart" | "wand" | "flame" | "crown" | "star" | "moon" | "sun" | "axe" | "crosshair" | "eye" | "feather" | "zap"

PlayerCharacterList (aggregate)

Field Type Constraints
characters readonly PlayerCharacter[] May be empty. IDs unique within list.

Modified Entities

Combatant (extended)

Three new optional fields added:

Field Type Required Constraints
color string no Copied from PlayerCharacter at add-time
icon string no Copied from PlayerCharacter at add-time
playerCharacterId PlayerCharacterId no Reference to source player character (informational only)

These fields are set when a combatant is created from a player character. They are immutable snapshots — editing the source player character does not update existing combatants.

State Transitions

createPlayerCharacter

  • Input: PlayerCharacterList + name + ac + maxHp + color + icon + id
  • Output: Updated PlayerCharacterList + PlayerCharacterCreated event | DomainError
  • Validation: name non-empty after trim, ac >= 0 integer, maxHp > 0 integer, color in set, icon in set
  • Errors: invalid-name, invalid-ac, invalid-max-hp, invalid-color, invalid-icon

editPlayerCharacter

  • Input: PlayerCharacterList + id + partial fields (name?, ac?, maxHp?, color?, icon?)
  • Output: Updated PlayerCharacterList + PlayerCharacterUpdated event | DomainError
  • Validation: Same as create for any provided field. At least one field must change.
  • Errors: player-character-not-found, invalid-name, invalid-ac, invalid-max-hp, invalid-color, invalid-icon

deletePlayerCharacter

  • Input: PlayerCharacterList + id
  • Output: Updated PlayerCharacterList + PlayerCharacterDeleted event | DomainError
  • Validation: ID must exist in list
  • Errors: player-character-not-found

Domain Events

PlayerCharacterCreated

Field Type
type "PlayerCharacterCreated"
playerCharacterId PlayerCharacterId
name string

PlayerCharacterUpdated

Field Type
type "PlayerCharacterUpdated"
playerCharacterId PlayerCharacterId
oldName string
newName string

PlayerCharacterDeleted

Field Type
type "PlayerCharacterDeleted"
playerCharacterId PlayerCharacterId
name string

Persistence Schema

localStorage key: "initiative:player-characters"

JSON array of serialized PlayerCharacter objects:

[
  {
    "id": "pc_abc123",
    "name": "Aragorn",
    "ac": 16,
    "maxHp": 120,
    "color": "green",
    "icon": "sword"
  }
]

Rehydration rules

  • Parse JSON array; discard entire store on parse failure (return empty list)
  • Per-character validation: discard individual characters that fail validation
  • Required string fields: must be non-empty strings
  • Required number fields: must match domain constraints (ac >= 0, maxHp > 0)
  • Color/icon: must be members of the predefined sets; discard character if invalid

Port Interface

PlayerCharacterStore

getAll(): PlayerCharacter[]
save(characters: PlayerCharacter[]): void

Synchronous, matching the EncounterStore pattern. Implementation: localStorage adapter.