Implement the 021-bestiary-statblock feature that adds a searchable D&D 2024 Monster Manual creature library with inline autocomplete suggestions, full stat block display in a fixed side panel, auto-numbering of duplicate creature names, HP/AC pre-fill from bestiary data, and automatic stat block display on turn change for wide viewports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-09 11:01:07 +01:00
parent 04a4f18f98
commit fa078be2f9
30 changed files with 66221 additions and 56 deletions

View File

@@ -0,0 +1,140 @@
# UI Contracts: Bestiary Search & Stat Block Display
**Branch**: `021-bestiary-statblock` | **Date**: 2026-03-06
## Component Contracts
### BestiarySearch
**Purpose**: Search input with autocomplete dropdown for creature selection.
**Props**:
- `onSelectCreature: (creature: Creature) => void` — Called when user selects a creature from results
- `onClose: () => void` — Called when search is dismissed (Escape, click outside)
**Behavior**:
- Opens focused on the search input
- Filters creatures by case-insensitive substring match on name
- Minimum 2 characters before showing results
- Maximum 10 results shown at a time
- Keyboard navigation: ArrowUp/ArrowDown to navigate, Enter to select, Escape to close
- Shows "No creatures found" for zero-match queries
- Shows source tag next to creature name (e.g., "Goblin (MM 2024)")
### StatBlock
**Purpose**: Renders a complete creature stat block.
**Props**:
- `creature: Creature` — The creature data to display
**Sections rendered** (in order, omitted if data absent):
1. Header: name, size, type, alignment
2. Stats bar: AC, HP (average + formula), Speed
3. Ability scores: STR/DEX/CON/INT/WIS/CHA with modifiers
4. Properties: Saving Throws, Skills, Damage Vulnerabilities, Damage Resistances, Damage Immunities, Condition Immunities, Senses, Languages, CR + Proficiency Bonus
5. Traits
6. Spellcasting (rendered as a separate section after traits)
7. Actions
8. Bonus Actions
9. Reactions
10. Legendary Actions (with preamble text)
### StatBlockPanel
**Purpose**: Responsive wrapper — side panel on desktop, drawer on mobile.
**Props**:
- `creature: Creature | null` — Currently displayed creature (null = closed)
- `onClose: () => void` — Close the panel/drawer
**Behavior**:
- Desktop (>= 1024px): Renders as a right-side panel, tracker shifts left
- Mobile (< 1024px): Renders as a slide-over drawer from right with backdrop
- Close button always visible
- Panel scrolls independently of the main content
### ActionBar (modified)
**Extended Props**:
- `onAddCombatant: (name: string) => void` — Existing: add plain combatant
- `onAddFromBestiary: (creature: Creature) => void` — New: add creature with stat pre-fill
- `suggestions: Creature[]` — Matching creatures for current name input
- `onSearchChange: (query: string) => void` — Notify parent of input changes for suggestion filtering
**New Elements**:
- Magnifying glass icon button next to input → opens dedicated BestiarySearch
- Autocomplete suggestion list below input when typing (>= 2 chars and matches exist)
## Hook Contracts
### useBestiary
**Purpose**: Provides creature search and lookup from the bundled bestiary.
**Returns**:
- `search: (query: string) => Creature[]` — Filter creatures by name substring
- `getCreature: (id: CreatureId) => Creature | undefined` — Look up creature by ID
- `allCreatures: Creature[]` — Full list (for potential future use)
**Behavior**:
- Loads and normalizes bestiary data once on initialization (lazy, memoized)
- Search is synchronous (in-memory filter)
- Returns results sorted alphabetically by name
### useEncounter (extended)
**Extended return**:
- `addFromBestiary: (creature: Creature) => void` — New: adds combatant with auto-numbered name, pre-filled HP/AC, and creatureId link
**Auto-numbering behavior**:
- Checks existing combatant names for conflicts with the creature's base name
- If no conflict: uses creature name as-is
- If one existing match: renames existing to "Name 1", new one becomes "Name 2"
- If N existing matches: new one becomes "Name N+1"
- Names remain editable after auto-numbering
## Layout Contract
### App (modified)
**Desktop layout (>= 1024px, stat block open)**:
```
+--------------------------------------------------+
| Header |
+-------------------------+------------------------+
| Tracker (flex-1) | Stat Block (~400px) |
| - Turn Navigation | - Scrollable |
| - Combatant List | - Close button |
| - Action Bar | |
+-------------------------+------------------------+
```
**Desktop layout (stat block closed)**:
```
+--------------------------------------------------+
| Header |
| Tracker (max-w-2xl, centered) |
| - Turn Navigation |
| - Combatant List |
| - Action Bar |
+--------------------------------------------------+
```
**Mobile layout (< 1024px, stat block open)**:
```
+--------------------------------------------------+
| Header |
| Tracker (full width) |
| - Turn Navigation |
| - Combatant List |
| - Action Bar |
| |
| +----------------------------------------------+ |
| | Drawer (slide from right, 85% width) | |
| | - Close button | |
| | - Stat Block (scrollable) | |
| +----------------------------------------------+ |
| | Backdrop (click to close) | |
+--------------------------------------------------+
```