# 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. - [x] 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` - [x] 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`. - [x] 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) - [x] T004 [P] Add `PlayerCharacterStore` port interface (`getAll(): PlayerCharacter[]`, `save(characters: PlayerCharacter[]): void`) to `packages/application/src/ports.ts` - [x] T005 Re-export all new types and functions from `packages/domain/src/index.ts` and `packages/application/src/index.ts` - [x] 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 - [x] 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`. - [x] 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 - [x] 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 - [x] 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). - [x] 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 - [x] T012 [US1] Implement `usePlayerCharacters` hook in `apps/web/src/hooks/use-player-characters.ts` — `useState` 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 - [x] 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. - [x] 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. - [x] 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)`. - [x] 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`. - [x] 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 - [x] 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. - [x] 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`). - [x] 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 - [x] 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 - [x] 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). - [x] 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. - [x] 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. - [x] 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 - [x] T026 [P] [US4] Implement `editPlayerCharacterUseCase` in `packages/application/src/edit-player-character-use-case.ts` - [x] T027 [P] [US4] Implement `deletePlayerCharacterUseCase` in `packages/application/src/delete-player-character-use-case.ts` ### React Hook - [x] 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 - [x] 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`. - [x] 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. - [x] 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. - [x] 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` - [x] T033 Run `pnpm check` (audit + knip + biome + typecheck + test/coverage + jscpd) and fix any issues - [x] T034 Update `CLAUDE.md` to add `specs/005-player-characters/` to the current feature specs list and document the `PlayerCharacterStore` port - [x] 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