4.5 KiB
Tasks: Add Combatant
Input: Design documents from /specs/002-add-combatant/
Prerequisites: plan.md, spec.md, research.md, data-model.md, quickstart.md
Tests: Included — spec success criteria SC-001 and SC-002 require all acceptance scenarios and invariants to be verified by tests.
Organization: Single user story (P1). Tasks follow the established advanceTurn pattern across all three layers.
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)
- Include exact file paths in descriptions
Phase 1: Foundational (Domain Event)
Purpose: Add the CombatantAdded event type that all layers depend on
- T001 Add CombatantAdded event interface and extend DomainEvent union in packages/domain/src/events.ts
Checkpoint: CombatantAdded event type available for import
Phase 2: User Story 1 - Add Combatant to Encounter (Priority: P1) 🎯 MVP
Goal: A game master can add a new combatant to an existing encounter. The combatant is appended to the end of the initiative list without changing the active turn or round.
Independent Test: Call addCombatant with an Encounter, a CombatantId, and a name. Assert the returned Encounter has the new combatant at the end, activeIndex and roundNumber unchanged, and a CombatantAdded event emitted.
Domain Layer
- T002 [US1] Create addCombatant pure function in packages/domain/src/add-combatant.ts
- T003 [US1] Export addCombatant and AddCombatantSuccess from packages/domain/src/index.ts
Domain Tests
- T004 [US1] Create acceptance tests (6 scenarios) and invariant tests (INV-1 through INV-7) in packages/domain/src/tests/add-combatant.test.ts
Application Layer
- T005 [P] [US1] Create addCombatantUseCase in packages/application/src/add-combatant-use-case.ts
- T006 [US1] Export addCombatantUseCase from packages/application/src/index.ts
Web Adapter
- T007 [US1] Add addCombatant callback to useEncounter hook in apps/web/src/hooks/use-encounter.ts
- T008 [US1] Add combatant name input and add button to apps/web/src/App.tsx
Checkpoint: All 6 acceptance scenarios pass. User can type a name and add a combatant via the UI. pnpm check passes.
Phase 3: Polish & Cross-Cutting Concerns
- T009 Run pnpm check (format + lint + typecheck + test) and fix any issues
- T010 Verify layer boundary compliance (domain has no outer-layer imports)
Dependencies & Execution Order
Phase Dependencies
- Foundational (Phase 1): No dependencies — start immediately
- User Story 1 (Phase 2): Depends on T001 (CombatantAdded event type)
- Polish (Phase 3): Depends on all Phase 2 tasks
Within User Story 1
T001 (event type)
├── T002 (domain function) → T003 (domain exports) → T004 (domain tests)
└── T005 (use case) ──────→ T006 (app exports) → T007 (hook) → T008 (UI)
- T002 depends on T001 (needs CombatantAdded type)
- T003 depends on T002 (exports the new function)
- T004 depends on T003 (tests import from index)
- T005 depends on T003 (use case imports domain function) — can run in parallel with T004
- T006 depends on T005
- T007 depends on T006
- T008 depends on T007
Parallel Opportunities
- T004 (domain tests) and T005 (use case) can run in parallel after T003
- T009 and T010 can run in parallel
Parallel Example: After T003
# These two tasks touch different packages and can run in parallel:
T004: "Acceptance + invariant tests in packages/domain/src/__tests__/add-combatant.test.ts"
T005: "Use case in packages/application/src/add-combatant-use-case.ts"
Implementation Strategy
MVP (This Feature)
- T001: Add event type (foundation)
- T002–T003: Domain function + exports
- T004 + T005 in parallel: Tests + use case
- T006–T008: Application exports → hook → UI
- T009–T010: Verify everything passes
Validation
After T004: All 6 acceptance scenarios pass as pure-function tests
After T008: UI allows adding combatants by name
After T009: pnpm check passes clean (merge gate)
Notes
- Follow the
advanceTurnpattern for function signature, result type, and error handling - CombatantId is passed in as input (generated by caller), not created inside domain
- Name is trimmed then validated; empty after trim returns DomainError with code "invalid-name"
- Commit after each task or logical group
- Total: 10 tasks (1 foundational + 7 US1 + 2 polish)