5.1 KiB
Implementation Plan: Bestiary Search & Stat Block Display
Branch: 021-bestiary-statblock | Date: 2026-03-06 | Spec: 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)
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)
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).