Files
initiative/specs/021-bestiary-statblock/plan.md

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).