# Tasks: Encounter Difficulty Indicator **Input**: Design documents from `/specs/008-encounter-difficulty/` **Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md **Organization**: Tasks are grouped by user story (ED-1 through ED-4) 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., ED-1, ED-3, ED-4) - Include exact file paths in descriptions --- ## Phase 1: Foundational (Level field on PlayerCharacter) **Purpose**: Add optional `level` field to `PlayerCharacter` — required by all user stories since the difficulty calculation depends on party levels. **⚠️ CRITICAL**: The difficulty indicator cannot function without PC levels. This must complete first. - [x] T001 Add `level?: number` to `PlayerCharacter` interface in `packages/domain/src/player-character-types.ts` - [x] T002 [P] Add level validation to `createPlayerCharacter()` in `packages/domain/src/create-player-character.ts` — validate if provided: integer, 1-20, error code `"invalid-level"` - [x] T003 [P] Add level validation to `validateFields()` and apply in `applyFields()` in `packages/domain/src/edit-player-character.ts` - [x] T004 [P] Add level tests to `packages/domain/src/__tests__/create-player-character.test.ts` — valid level, no level, out-of-range, non-integer - [x] T005 [P] Add level tests to `packages/domain/src/__tests__/edit-player-character.test.ts` — set level, clear level, invalid level - [x] T006 Update `CreatePlayerCharacterUseCase` to accept and pass `level` in `packages/application/src/create-player-character-use-case.ts` - [x] T007 [P] Update `EditPlayerCharacterUseCase` to accept and pass `level` in `packages/application/src/edit-player-character-use-case.ts` - [x] T008 Update player characters context to pass `level` in create/edit calls in `apps/web/src/contexts/player-characters-context.tsx` - [x] T009 Add level input field to create player modal in `apps/web/src/components/create-player-modal.tsx` — optional number input, 1-20 range - [x] T010 Add level display and edit support in player character manager in `apps/web/src/components/player-character-manager.tsx` - [x] T011 Export updated `PlayerCharacter` type from `packages/domain/src/index.ts` (verify re-export includes level) **Checkpoint**: Player characters can be created/edited with an optional level. Existing PCs without level continue to work. All quality gates pass. --- ## Phase 2: User Story 4 — XP Budget Calculation (Priority: P1) 🎯 MVP Core **Goal**: Implement the pure domain difficulty calculation with CR-to-XP and XP Budget tables. **Independent Test**: Verified with unit tests using known party/monster combinations from the 2024 DMG examples. ### Implementation for User Story 4 - [x] T012 Create `packages/domain/src/encounter-difficulty.ts` with `DifficultyTier` type (`"trivial" | "low" | "moderate" | "high"`), `DifficultyResult` interface, `CR_TO_XP` lookup table (Record mapping all CRs 0 through 30 including fractions to XP values), and `XP_BUDGET_PER_CHARACTER` lookup table (Record mapping levels 1-20 to `{ low, moderate, high }`) - [x] T013 Implement `crToXp(cr: string): number` in `packages/domain/src/encounter-difficulty.ts` — returns XP for given CR string, 0 for unknown CRs - [x] T014 Implement `calculateEncounterDifficulty(partyLevels: number[], monsterCrs: string[]): DifficultyResult` in `packages/domain/src/encounter-difficulty.ts` — sums party budget per level, sums monster XP per CR, determines tier by comparing total XP against thresholds - [x] T015 Export `DifficultyTier`, `DifficultyResult`, `crToXp`, `calculateEncounterDifficulty` from `packages/domain/src/index.ts` (same file as T011 — merge into one edit) - [x] T016 Create `packages/domain/src/__tests__/encounter-difficulty.test.ts` with tests for: all CR string formats (0, 1/8, 1/4, 1/2, integers 1-30), unknown CR returns 0, all difficulty tiers (trivial/low/moderate/high), DMG example encounters (4x level 1 vs Bugbear = Low, 5x level 3 vs 1125 XP = Moderate), mixed party levels, empty arrays, High as cap (XP far exceeding High threshold still returns "high") **Checkpoint**: Domain difficulty calculation is complete, tested, and exported. All quality gates pass. --- ## Phase 3: User Story 1 — See Encounter Difficulty at a Glance (Priority: P1) 🎯 MVP **Goal**: Display the 3-bar difficulty indicator in the top bar that updates live as combatants change. **Independent Test**: Add PC combatants with levels and bestiary-linked monsters — indicator appears with correct tier. Add/remove combatants — indicator updates. ### Implementation for User Story 1 - [x] T017 Create `apps/web/src/hooks/use-difficulty.ts` — hook that consumes `useEncounterContext()`, `usePlayerCharactersContext()`, and `useBestiary()` to derive party levels and monster CRs from current encounter, calls `calculateEncounterDifficulty()`, returns `DifficultyResult | null` (null when insufficient data) - [x] T018 Create `apps/web/src/components/difficulty-indicator.tsx` — renders 3 bars as small `div` elements with Tailwind classes: empty bars for trivial (all `bg-muted`), 1 green bar for low (`bg-green-500`), 2 yellow bars for moderate (`bg-yellow-500`), 3 red bars for high (`bg-red-500`). Add `title` attribute for tooltip (e.g., "Moderate encounter difficulty"). Accept `DifficultyResult` as prop. - [x] T019 Add `DifficultyIndicator` to `TurnNavigation` in `apps/web/src/components/turn-navigation.tsx` — position to the right of the active combatant name inside the center flex section. Use `useDifficulty()` hook; render indicator only when result is non-null. - [x] T019a Add tests for `useDifficulty` hook in `apps/web/src/hooks/__tests__/use-difficulty.test.ts` — verify correct `DifficultyResult` for known combatant/PC/creature combinations, returns null when data is insufficient, and updates when combatants change. Include tooltip text assertions for `DifficultyIndicator`. **Checkpoint**: Difficulty indicator appears in top bar for encounters with leveled PCs + bestiary monsters. Updates live. All quality gates pass. --- ## Phase 4: User Story 2 — Indicator Hidden When Data Insufficient (Priority: P1) **Goal**: Indicator is completely hidden when the encounter lacks PC combatants with levels or bestiary-linked monsters. **Independent Test**: Create encounters with only custom combatants, only monsters (no PCs), only PCs without levels — indicator should not appear in any case. ### Implementation for User Story 2 - [x] T020 Review and verify `useDifficulty()` null-return paths implemented in T017 cover all edge cases: no combatants with `playerCharacterId` that have a level, no combatants with `creatureId`, all PCs without levels, all custom combatants, empty encounter. Fix any missing cases. - [x] T021 Verify `TurnNavigation` in `apps/web/src/components/turn-navigation.tsx` renders nothing for the indicator when `useDifficulty()` returns null — confirm conditional rendering is correct. **Checkpoint**: Indicator hides correctly for all insufficient-data scenarios. No visual artifacts when hidden. --- ## Phase 5: Polish & Cross-Cutting Concerns **Purpose**: Final validation and documentation updates. - [x] T022 Run `pnpm check` to verify all quality gates pass (audit, knip, biome, oxlint, typecheck, test/coverage, jscpd) - [x] T023 Verify export compatibility — create a player character with level, export encounter JSON, re-import, confirm level is preserved. Verify old exports (without level) still import correctly. If the added `level` field causes old imports to fail, bump `ExportBundle` version and add migration logic in `validateImportBundle()` per CLAUDE.md convention. - [x] T024 Update `specs/005-player-characters/spec.md` to note that `PlayerCharacter` now supports an optional `level` field (added by spec 008) - [x] T025 Update `CLAUDE.md` to add spec 008 to the current feature specs list - [x] T026 Update `README.md` if encounter difficulty is a user-facing feature worth documenting --- ## Dependencies & Execution Order ### Phase Dependencies - **Phase 1 (Foundational)**: No dependencies — can start immediately - **Phase 2 (XP Calculation)**: T012-T014 depend on T001 (level type). T016 tests can be written in parallel with T012-T014. - **Phase 3 (Indicator UI)**: Depends on Phase 1 (level in forms) and Phase 2 (calculation function) - **Phase 4 (Visibility)**: Depends on Phase 3 (indicator exists to hide) - **Phase 5 (Polish)**: Depends on all previous phases ### User Story Dependencies - **ED-4 (Calculation)**: Depends on `level` field existing on `PlayerCharacter` (Phase 1, T001) - **ED-1 (Indicator)**: Depends on ED-4 (calculation) + Phase 1 (level in UI) - **ED-2 (Visibility)**: Depends on ED-1 (indicator rendering) — primarily a verification task - **ED-3 (Level field)**: Implemented in Phase 1 as foundational — all stories depend on it ### Within Each Phase - Tasks marked [P] can run in parallel - Domain tasks before application tasks before web tasks - Type definitions before functions using those types - Implementation before tests (unless TDD requested) ### Parallel Opportunities **Phase 1 parallel group**: ``` T002 (create validation) ‖ T003 (edit validation) ‖ T004 (create tests) ‖ T005 (edit tests) T006 (create use case) ‖ T007 (edit use case) ``` **Phase 2 parallel group**: ``` T012 (tables + types) → T013 (crToXp) ‖ T014 (calculateDifficulty) → T016 (tests) ``` **Phase 3 parallel group**: ``` T017 (hook) ‖ T018 (component) → T019 (integration into top bar) ``` --- ## Implementation Strategy ### MVP First (Phase 1 + Phase 2 + Phase 3) 1. Complete Phase 1: Level field on PlayerCharacter 2. Complete Phase 2: Domain difficulty calculation + tests 3. Complete Phase 3: Indicator in top bar 4. **STOP and VALIDATE**: Indicator shows correct difficulty for encounters with leveled PCs and bestiary monsters 5. Demo: add a party of level 3 PCs, add some goblins from bestiary, see bars change ### Incremental Delivery 1. Phase 1 → Level field works in PC forms → Can assign levels immediately 2. Phase 2 → Calculation is correct → Domain tests prove it 3. Phase 3 → Indicator visible → Feature is usable 4. Phase 4 → Edge cases verified → Feature is robust 5. Phase 5 → Docs updated → Feature is complete --- ## Notes - [P] tasks = different files, no dependencies - [Story] label maps task to specific user story for traceability - Story ED-3 (level field) is implemented in Phase 1 as it's foundational to all other stories - The `useDifficulty` hook is the key integration point — it bridges three contexts into one domain call - No new contexts or ports needed — existing patterns handle everything - Commit after each phase checkpoint