# Implementation Plan: Bestiary Search & Stat Block Display **Branch**: `021-bestiary-statblock` | **Date**: 2026-03-06 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/021-bestiary-statblock/spec.md` ## Summary Add a searchable creature library (2024 Monster Manual, 503 creatures) to the initiative tracker. Users search by name, view full stat blocks in a responsive side panel, and add creatures as combatants with HP/AC/name pre-filled. Data is bundled as static JSON, normalized at load time via an adapter. Auto-numbering handles duplicate creature names. Layout adapts between side-by-side (desktop) and drawer (mobile). ## Technical Context **Language/Version**: TypeScript 5.8 (strict mode, verbatimModuleSyntax) **Primary Dependencies**: React 19, Vite 6, Tailwind CSS v4, Lucide React (icons) **Storage**: Browser localStorage (existing adapter, extended for creatureId) **Testing**: Vitest (unit tests for domain functions, adapter normalization, tag stripping) **Target Platform**: Modern browsers (desktop + mobile) **Project Type**: Web application (local-first, single-user) **Performance Goals**: Search results in <200ms for 500 creatures (trivially met with in-memory filter) **Constraints**: Offline-capable, no backend, bundle size increase ~300-500KB gzipped **Scale/Scope**: 503 creatures (2024 MM), extensible to multiple bestiary sources ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* | Principle | Status | Notes | |-----------|--------|-------| | I. Deterministic Domain Core | PASS | Creature types and auto-numbering are pure. No I/O in domain. Normalization adapter lives in web layer. | | II. Layered Architecture | PASS | Domain: types + auto-numbering. Application: N/A (no new use case needed — addCombatant reused). Web: adapter, hooks, components. | | III. Agent Boundary | N/A | No agent layer involvement. | | IV. Clarification-First | PASS | All ambiguities resolved in spec discussion and research phase. | | V. Escalation Gates | PASS | Feature fully specified before planning. | | VI. MVP Baseline Language | PASS | Custom creature editor, fuzzy search, multiple bestiary sources described as future iterations. | | VII. No Gameplay Rules | PASS | Stat block display is data rendering, not gameplay mechanics. | **Post-Phase 1 Re-check**: All gates still pass. The `Creature` type in domain contains no I/O. The bestiary adapter in the web layer handles all data transformation. The `creatureId` extension to `Combatant` is a minimal, optional field. ## Project Structure ### Documentation (this feature) ```text specs/021-bestiary-statblock/ ├── spec.md ├── plan.md # This file ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── ui-contracts.md ├── checklists/ │ └── requirements.md └── tasks.md # Generated by /speckit.tasks ``` ### Source Code (repository root) ```text data/ └── bestiary/ └── xmm.json # Raw 5etools bestiary (2024 MM) packages/domain/src/ ├── types.ts # MODIFIED: add creatureId to Combatant ├── creature-types.ts # NEW: Creature, CreatureId, TraitBlock, etc. ├── auto-number.ts # NEW: resolveCreatureName() ├── index.ts # MODIFIED: export new types └── __tests__/ ├── auto-number.test.ts # NEW └── layer-boundaries.test.ts # EXISTING (unchanged) packages/application/src/ └── (no changes — reuses existing addCombatant use case) apps/web/src/ ├── App.tsx # MODIFIED: two-column layout, stat block state ├── adapters/ │ ├── bestiary-adapter.ts # NEW: raw JSON → Creature[] │ ├── strip-tags.ts # NEW: {@tag} → plain text │ └── __tests__/ │ ├── bestiary-adapter.test.ts # NEW │ └── strip-tags.test.ts # NEW ├── hooks/ │ ├── use-encounter.ts # MODIFIED: addFromBestiary, auto-numbering │ └── use-bestiary.ts # NEW: search + lookup ├── components/ │ ├── action-bar.tsx # MODIFIED: search icon, suggestions │ ├── bestiary-search.tsx # NEW: search input + dropdown │ ├── stat-block.tsx # NEW: creature stat block renderer │ └── stat-block-panel.tsx # NEW: responsive panel/drawer wrapper ├── persistence/ │ └── encounter-storage.ts # MODIFIED: persist/rehydrate creatureId └── index.css # MODIFIED: stat block styling ``` **Structure Decision**: Follows existing monorepo layout. New domain types in `packages/domain`. Bestiary adapter and UI components in `apps/web`. Raw data in top-level `data/` directory (not inside any package, as it's a static asset consumed by the web app's build).