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

16 KiB

Tasks: Player Character Management

Input: Design documents from /specs/005-player-characters/ Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/ui-contracts.md, quickstart.md

Tests: Included — pnpm check merge gate requires passing tests with coverage.

Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies)
  • [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
  • Include exact file paths in descriptions

Phase 1: Setup

Purpose: No new project setup needed — existing monorepo structure is used. This phase handles shared type foundations.

(No tasks — the project is already set up. Foundational tasks cover all shared work.)


Phase 2: Foundational (Blocking Prerequisites)

Purpose: Domain types, events, and port interface that ALL user stories depend on.

⚠️ CRITICAL: No user story work can begin until this phase is complete.

  • T001 [P] Define PlayerCharacterId branded type, PlayerCharacter interface, PlayerColor constrained type (10 colors), PlayerIcon constrained type (~15 Lucide icon identifiers), validation sets (VALID_PLAYER_COLORS, VALID_PLAYER_ICONS), and PlayerCharacterList aggregate type ({ readonly characters: readonly PlayerCharacter[] }) in packages/domain/src/player-character-types.ts
  • T002 [P] Add optional color?: string, icon?: string, and playerCharacterId?: PlayerCharacterId fields to the Combatant interface in packages/domain/src/types.ts. Import PlayerCharacterId from player-character-types.js.
  • T003 [P] Add PlayerCharacterCreated, PlayerCharacterUpdated, PlayerCharacterDeleted event types to the DomainEvent union in packages/domain/src/events.ts (see data-model.md for field definitions)
  • T004 [P] Add PlayerCharacterStore port interface (getAll(): PlayerCharacter[], save(characters: PlayerCharacter[]): void) to packages/application/src/ports.ts
  • T005 Re-export all new types and functions from packages/domain/src/index.ts and packages/application/src/index.ts
  • T006 Update encounter storage rehydration in apps/web/src/persistence/encounter-storage.ts to handle the new optional color, icon, and playerCharacterId fields on Combatant (validate as optional strings, discard invalid values)

Checkpoint: Foundation ready — domain types defined, Combatant extended, events added, port declared.


Phase 3: User Story 1 — Create & Persist Player Characters (Priority: P1) 🎯 MVP

Goal: Users can create player characters via a modal (name, AC, max HP, color, icon) and they persist across page reloads.

Independent Test: Create a player character, reload the page, verify it still exists with all attributes intact.

Maps to: Spec stories PC-1 (Create) and PC-2 (Persistence)

Domain

  • T007 [P] [US1] Implement createPlayerCharacter pure function in packages/domain/src/create-player-character.ts — accepts PlayerCharacter[], PlayerCharacterId, name, ac, maxHp, color, icon; returns updated list + PlayerCharacterCreated event or DomainError. Validate: name non-empty after trim, ac >= 0 integer, maxHp > 0 integer, color in VALID_PLAYER_COLORS, icon in VALID_PLAYER_ICONS.
  • T008 [P] [US1] Write unit tests for createPlayerCharacter in packages/domain/src/__tests__/create-player-character.test.ts — cover: valid creation, empty name, whitespace name, invalid ac, invalid maxHp, invalid color, invalid icon, event emission.

Application

  • T009 [US1] Implement createPlayerCharacterUseCase in packages/application/src/create-player-character-use-case.ts — accepts PlayerCharacterStore, id, name, ac, maxHp, color, icon; calls domain function and store.save().

Persistence Adapter

  • T010 [P] [US1] Implement savePlayerCharacters(characters: PlayerCharacter[]): void and loadPlayerCharacters(): PlayerCharacter[] in apps/web/src/persistence/player-character-storage.ts — localStorage key "initiative:player-characters", silent catch on save errors, return empty array on corrupt/missing data. Per-character rehydration with field validation (discard invalid characters).
  • T011 [P] [US1] Write tests for player character storage in apps/web/src/persistence/__tests__/player-character-storage.test.ts — cover: round-trip save/load, corrupt JSON, missing fields, invalid color/icon values, empty storage, storage errors.

React Hook

  • T012 [US1] Implement usePlayerCharacters hook in apps/web/src/hooks/use-player-characters.tsuseState initialized from loadPlayerCharacters(), useEffect to persist on change, expose characters, createCharacter(name, ac, maxHp, color, icon), and a makeStore() callback returning PlayerCharacterStore. Follow the useEncounter ref + effect pattern.

UI Components

  • T013 [P] [US1] Create ColorPalette component in apps/web/src/components/color-palette.tsx — renders a grid of color swatches from VALID_PLAYER_COLORS, highlights selected color with a ring/border, accepts value and onChange props.
  • T014 [P] [US1] Create IconGrid component in apps/web/src/components/icon-grid.tsx — renders a grid of Lucide icons from VALID_PLAYER_ICONS, highlights selected icon, accepts value and onChange props. Map icon identifiers to Lucide components.
  • T015 [US1] Create CreatePlayerModal component in apps/web/src/components/create-player-modal.tsx — modal with Name (text), AC (number), Max HP (number) fields, ColorPalette, IconGrid, Save and Cancel buttons. Name validation error display. Truncate/ellipsize long names in preview. Props: open, onClose, onSave(name, ac, maxHp, color, icon).
  • T016 [US1] Add "Create Player" icon button to the bottom bar in apps/web/src/components/action-bar.tsx (e.g., Users Lucide icon). Wire it to open the CreatePlayerModal.
  • T017 [US1] Wire usePlayerCharacters hook in apps/web/src/App.tsx — call hook at app level, pass createCharacter to the CreatePlayerModal via ActionBar.

Checkpoint: Users can create player characters with all attributes, and they persist across page reloads. This is the MVP.


Phase 4: User Story 2 — Search & Add to Encounter (Priority: P1)

Goal: Player characters appear in the combatant search dropdown and can be added to an encounter with stats pre-filled.

Independent Test: Create a player character, type its name in the search field, select it, verify combatant is added with correct stats, color, and icon.

Maps to: Spec story PC-3

Implementation

  • T018 [US2] Add player character search to ActionBar in apps/web/src/components/action-bar.tsx — accept a playerCharacters prop (or search function), filter by name substring, render a "Players" group above bestiary results in the dropdown. Hide section when no matches.
  • T019 [US2] Implement addFromPlayerCharacter callback in apps/web/src/hooks/use-encounter.ts — accepts a PlayerCharacter, creates a combatant with name, ac, maxHp, currentHp=maxHp, color, icon, and playerCharacterId. Use resolveCreatureName for name conflict resolution (same pattern as addFromBestiary).
  • T020 [US2] Wire search and add in apps/web/src/App.tsx — pass playerCharacters list and addFromPlayerCharacter handler to ActionBar. On player character selection, call addFromPlayerCharacter.

Checkpoint: Player characters are searchable and addable to encounters with pre-filled stats.


Phase 5: User Story 3 — Visual Distinction in Combatant Row (Priority: P2)

Goal: Combatants from player characters display their color and icon in the initiative tracker.

Independent Test: Add a player character to an encounter, verify the combatant row shows the chosen icon and color accent.

Maps to: Spec story PC-4

Implementation

  • T021 [US3] Update CombatantRow in apps/web/src/components/combatant-row.tsx — if combatant has color and icon fields, render the Lucide icon (small, tinted with color) to the left of the name. Apply a subtle color accent (e.g., left border or background tint). Ensure long names truncate with ellipsis. Map icon string identifiers to Lucide components (reuse mapping from IconGrid).

Checkpoint: Player character combatants are visually distinct from bestiary/custom combatants.


Phase 6: User Story 4 — Management View (Priority: P2)

Goal: Users can view, edit, and delete saved player characters from a dedicated management panel.

Independent Test: Open management view, verify all characters listed, edit one's name and AC, delete another, verify changes persisted.

Maps to: Spec stories PC-5 (View), PC-6 (Edit), PC-7 (Delete)

Domain

  • T022 [P] [US4] Implement editPlayerCharacter pure function in packages/domain/src/edit-player-character.ts — accepts PlayerCharacter[], id, and partial update fields; returns updated list + PlayerCharacterUpdated event or DomainError. Validate changed fields same as create. Return DomainError if no fields actually change (no-op guard).
  • T023 [P] [US4] Implement deletePlayerCharacter pure function in packages/domain/src/delete-player-character.ts — accepts PlayerCharacter[] and id; returns updated list + PlayerCharacterDeleted event or DomainError. Error if id not found.
  • T024 [P] [US4] Write unit tests for editPlayerCharacter in packages/domain/src/__tests__/edit-player-character.test.ts — cover: valid edit, not-found, invalid fields, no-op edit (no fields changed), event emission.
  • T025 [P] [US4] Write unit tests for deletePlayerCharacter in packages/domain/src/__tests__/delete-player-character.test.ts — cover: valid delete, not-found, event emission.

Application

  • T026 [P] [US4] Implement editPlayerCharacterUseCase in packages/application/src/edit-player-character-use-case.ts
  • T027 [P] [US4] Implement deletePlayerCharacterUseCase in packages/application/src/delete-player-character-use-case.ts

React Hook

  • T028 [US4] Extend usePlayerCharacters hook in apps/web/src/hooks/use-player-characters.ts — add editCharacter(id, updates) and deleteCharacter(id) methods using the new use cases.

UI Components

  • T029 [US4] Extend CreatePlayerModal in apps/web/src/components/create-player-modal.tsx to support edit mode — accept optional playerCharacter prop to pre-fill fields, change title to "Edit Player", save calls editCharacter instead of createCharacter.
  • T030 [US4] Create PlayerManagement component in apps/web/src/components/player-management.tsx — modal/panel listing all player characters (name, AC, max HP, color icon). Truncate/ellipsize long names. Each row has an edit button (opens modal in edit mode) and a delete button (ConfirmButton pattern from spec 001). Empty state with "Create your first player character" CTA.
  • T031 [US4] Add management view trigger to the UI — icon button in bottom bar or within ActionBar that opens PlayerManagement. Wire in apps/web/src/App.tsx with editCharacter and deleteCharacter handlers.

Checkpoint: Full CRUD for player characters — create, view, edit, delete all working and persisted.


Phase 7: Polish & Cross-Cutting Concerns

Purpose: Final integration validation and cleanup.

  • T032 Extract shared icon identifier → Lucide component mapping into a utility (used by IconGrid, CombatantRow, and PlayerManagement) to avoid duplication, e.g., apps/web/src/components/player-icon-map.ts
  • T033 Run pnpm check (audit + knip + biome + typecheck + test/coverage + jscpd) and fix any issues
  • T034 Update CLAUDE.md to add specs/005-player-characters/ to the current feature specs list and document the PlayerCharacterStore port
  • T035 Update README.md if it documents user-facing features (per constitution: features that alter what the product does must be reflected)

Dependencies & Execution Order

Phase Dependencies

  • Foundational (Phase 2): No dependencies — can start immediately
  • US1 Create & Persist (Phase 3): Depends on Phase 2 completion
  • US2 Search & Add (Phase 4): Depends on Phase 3 (needs characters to exist and hook to be wired)
  • US3 Visual Distinction (Phase 5): Depends on Phase 4 (needs combatants with color/icon to exist)
  • US4 Management (Phase 6): Depends on Phase 3 (needs create flow and hook, but NOT on US2/US3)
  • Polish (Phase 7): Depends on all story phases complete

User Story Dependencies

  • US1 (Create & Persist): Depends only on Foundational — can start immediately after Phase 2
  • US2 (Search & Add): Depends on US1 (needs usePlayerCharacters hook and characters to search)
  • US3 (Visual Distinction): Depends on US2 (needs combatants with color/icon fields populated)
  • US4 (Management): Depends on US1 only (needs hook and create flow). Can run in parallel with US2/US3.

Within Each User Story

  • Domain functions before application use cases
  • Application use cases before React hooks
  • React hooks before UI components
  • Tests can run in parallel with their domain functions (written to same-phase files)

Parallel Opportunities

Within Phase 2: T001, T002, T003, T004 can all run in parallel (different files) Within US1: T007+T008 parallel with T010+T011, parallel with T013+T014 Within US4: T022-T027 can all run in parallel (different domain/application files)


Parallel Example: Phase 2 (Foundational)

Parallel group 1:
  T001: PlayerCharacter types in packages/domain/src/player-character-types.ts
  T002: Combatant extension in packages/domain/src/types.ts
  T003: Domain events in packages/domain/src/events.ts
  T004: Port interface in packages/application/src/ports.ts

Sequential after group 1:
  T005: Re-export from index files (depends on T001-T004)
  T006: Encounter storage rehydration (depends on T002)

Parallel Example: User Story 1

Parallel group 1 (after Phase 2):
  T007+T008: Domain function + tests in packages/domain/src/
  T010+T011: Storage adapter + tests in apps/web/src/persistence/
  T013: ColorPalette component
  T014: IconGrid component

Sequential after group 1:
  T009: Application use case (depends on T007)
  T012: React hook (depends on T009, T010)
  T015: CreatePlayerModal (depends on T013, T014)
  T016: ActionBar button (depends on T015)
  T017: App.tsx wiring (depends on T012, T016)

Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 2: Foundational types and ports
  2. Complete Phase 3: US1 — Create + Persist
  3. STOP and VALIDATE: Create a player character, reload page, verify persistence
  4. This alone delivers value — users can save their party for reuse

Incremental Delivery

  1. Phase 2 → Foundation ready
  2. US1 → Create & persist player characters (MVP!)
  3. US2 → Search and add to encounters (core value unlocked)
  4. US3 → Visual distinction in rows (UX polish)
  5. US4 → Edit and delete (full CRUD) — can happen in parallel with US2/US3
  6. Phase 7 → Polish and validation

Notes

  • [P] tasks = different files, no dependencies
  • [Story] label maps task to specific user story for traceability
  • Commit after each phase or logical group of tasks
  • Run pnpm check at each checkpoint to catch regressions early
  • The icon identifier → Lucide component mapping will be needed in 3 places (T014, T021, T030) — T032 extracts it to avoid duplication, but initial implementations can inline it