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>
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
PlayerCharacterIdbranded type,PlayerCharacterinterface,PlayerColorconstrained type (10 colors),PlayerIconconstrained type (~15 Lucide icon identifiers), validation sets (VALID_PLAYER_COLORS,VALID_PLAYER_ICONS), andPlayerCharacterListaggregate type ({ readonly characters: readonly PlayerCharacter[] }) inpackages/domain/src/player-character-types.ts - T002 [P] Add optional
color?: string,icon?: string, andplayerCharacterId?: PlayerCharacterIdfields to theCombatantinterface inpackages/domain/src/types.ts. ImportPlayerCharacterIdfromplayer-character-types.js. - T003 [P] Add
PlayerCharacterCreated,PlayerCharacterUpdated,PlayerCharacterDeletedevent types to theDomainEventunion inpackages/domain/src/events.ts(see data-model.md for field definitions) - T004 [P] Add
PlayerCharacterStoreport interface (getAll(): PlayerCharacter[],save(characters: PlayerCharacter[]): void) topackages/application/src/ports.ts - T005 Re-export all new types and functions from
packages/domain/src/index.tsandpackages/application/src/index.ts - T006 Update encounter storage rehydration in
apps/web/src/persistence/encounter-storage.tsto handle the new optionalcolor,icon, andplayerCharacterIdfields onCombatant(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
createPlayerCharacterpure function inpackages/domain/src/create-player-character.ts— acceptsPlayerCharacter[],PlayerCharacterId, name, ac, maxHp, color, icon; returns updated list +PlayerCharacterCreatedevent orDomainError. Validate: name non-empty after trim, ac >= 0 integer, maxHp > 0 integer, color inVALID_PLAYER_COLORS, icon inVALID_PLAYER_ICONS. - T008 [P] [US1] Write unit tests for
createPlayerCharacterinpackages/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
createPlayerCharacterUseCaseinpackages/application/src/create-player-character-use-case.ts— acceptsPlayerCharacterStore, id, name, ac, maxHp, color, icon; calls domain function andstore.save().
Persistence Adapter
- T010 [P] [US1] Implement
savePlayerCharacters(characters: PlayerCharacter[]): voidandloadPlayerCharacters(): PlayerCharacter[]inapps/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
usePlayerCharactershook inapps/web/src/hooks/use-player-characters.ts—useStateinitialized fromloadPlayerCharacters(),useEffectto persist on change, exposecharacters,createCharacter(name, ac, maxHp, color, icon), and amakeStore()callback returningPlayerCharacterStore. Follow theuseEncounterref + effect pattern.
UI Components
- T013 [P] [US1] Create
ColorPalettecomponent inapps/web/src/components/color-palette.tsx— renders a grid of color swatches fromVALID_PLAYER_COLORS, highlights selected color with a ring/border, acceptsvalueandonChangeprops. - T014 [P] [US1] Create
IconGridcomponent inapps/web/src/components/icon-grid.tsx— renders a grid of Lucide icons fromVALID_PLAYER_ICONS, highlights selected icon, acceptsvalueandonChangeprops. Map icon identifiers to Lucide components. - T015 [US1] Create
CreatePlayerModalcomponent inapps/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.,UsersLucide icon). Wire it to open theCreatePlayerModal. - T017 [US1] Wire
usePlayerCharactershook inapps/web/src/App.tsx— call hook at app level, passcreateCharacterto theCreatePlayerModalviaActionBar.
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
ActionBarinapps/web/src/components/action-bar.tsx— accept aplayerCharactersprop (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
addFromPlayerCharactercallback inapps/web/src/hooks/use-encounter.ts— accepts aPlayerCharacter, creates a combatant with name, ac, maxHp, currentHp=maxHp, color, icon, and playerCharacterId. UseresolveCreatureNamefor name conflict resolution (same pattern asaddFromBestiary). - T020 [US2] Wire search and add in
apps/web/src/App.tsx— passplayerCharacterslist andaddFromPlayerCharacterhandler toActionBar. On player character selection, calladdFromPlayerCharacter.
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
CombatantRowinapps/web/src/components/combatant-row.tsx— if combatant hascolorandiconfields, 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 fromIconGrid).
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
editPlayerCharacterpure function inpackages/domain/src/edit-player-character.ts— acceptsPlayerCharacter[], id, and partial update fields; returns updated list +PlayerCharacterUpdatedevent orDomainError. Validate changed fields same as create. ReturnDomainErrorif no fields actually change (no-op guard). - T023 [P] [US4] Implement
deletePlayerCharacterpure function inpackages/domain/src/delete-player-character.ts— acceptsPlayerCharacter[]and id; returns updated list +PlayerCharacterDeletedevent orDomainError. Error if id not found. - T024 [P] [US4] Write unit tests for
editPlayerCharacterinpackages/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
deletePlayerCharacterinpackages/domain/src/__tests__/delete-player-character.test.ts— cover: valid delete, not-found, event emission.
Application
- T026 [P] [US4] Implement
editPlayerCharacterUseCaseinpackages/application/src/edit-player-character-use-case.ts - T027 [P] [US4] Implement
deletePlayerCharacterUseCaseinpackages/application/src/delete-player-character-use-case.ts
React Hook
- T028 [US4] Extend
usePlayerCharactershook inapps/web/src/hooks/use-player-characters.ts— addeditCharacter(id, updates)anddeleteCharacter(id)methods using the new use cases.
UI Components
- T029 [US4] Extend
CreatePlayerModalinapps/web/src/components/create-player-modal.tsxto support edit mode — accept optionalplayerCharacterprop to pre-fill fields, change title to "Edit Player", save callseditCharacterinstead ofcreateCharacter. - T030 [US4] Create
PlayerManagementcomponent inapps/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 (ConfirmButtonpattern 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
ActionBarthat opensPlayerManagement. Wire inapps/web/src/App.tsxwitheditCharacteranddeleteCharacterhandlers.
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, andPlayerManagement) 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.mdto addspecs/005-player-characters/to the current feature specs list and document thePlayerCharacterStoreport - T035 Update
README.mdif 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
usePlayerCharactershook 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)
- Complete Phase 2: Foundational types and ports
- Complete Phase 3: US1 — Create + Persist
- STOP and VALIDATE: Create a player character, reload page, verify persistence
- This alone delivers value — users can save their party for reuse
Incremental Delivery
- Phase 2 → Foundation ready
- US1 → Create & persist player characters (MVP!)
- US2 → Search and add to encounters (core value unlocked)
- US3 → Visual distinction in rows (UX polish)
- US4 → Edit and delete (full CRUD) — can happen in parallel with US2/US3
- 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 checkat 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