# Quickstart: On-Demand Bestiary with Pre-Indexed Search **Feature**: 029-on-demand-bestiary **Date**: 2026-03-10 ## What This Feature Does Replaces the bundled full bestiary file (one source, ~1.3 MB of copyrighted content) with: 1. A pre-shipped lightweight index (102 sources, 3,312 creatures, ~52 KB gzipped) for instant search and combatant creation 2. On-demand fetching of full stat block data per source, cached in IndexedDB ## Key Changes ### Removed - `data/bestiary/xmm.json` — no longer shipped with the app ### New Files - `apps/web/src/adapters/bestiary-index-adapter.ts` — loads and parses the shipped index, converts compact format to domain types - `apps/web/src/adapters/bestiary-cache.ts` — IndexedDB cache adapter for fetched source data - `apps/web/src/components/source-fetch-prompt.tsx` — dialog prompting user to fetch/upload source data - `apps/web/src/components/source-manager.tsx` — UI for viewing and clearing cached sources ### Modified Files - `apps/web/src/hooks/use-bestiary.ts` — rewritten to search from index and look up creatures from cache - `apps/web/src/hooks/use-encounter.ts` — `addFromBestiary` accepts index entries (no fetch needed to add) - `apps/web/src/components/bestiary-search.tsx` — shows source display name in results - `apps/web/src/components/stat-block-panel.tsx` — triggers source fetch prompt when creature not cached - `packages/domain/src/creature-types.ts` — new `BestiaryIndexEntry` and `BestiaryIndex` types ### Unchanged - `apps/web/src/adapters/bestiary-adapter.ts` — normalization pipeline processes fetched data exactly as before - `apps/web/src/adapters/strip-tags.ts` — tag stripping unchanged - `apps/web/src/components/stat-block.tsx` — stat block rendering unchanged - `apps/web/src/persistence/encounter-storage.ts` — encounter persistence unchanged ## Architecture Overview ``` index.json (shipped, static) ↓ Vite JSON import bestiary-index-adapter.ts ↓ BestiaryIndexEntry[] use-bestiary.ts (search, add) ↓ bestiary-search.tsx → use-encounter.ts (addFromBestiary) ↓ stat-block-panel.tsx ↓ creatureId → source not cached? source-fetch-prompt.tsx ↓ fetch URL or upload file bestiary-adapter.ts (normalizeBestiary — unchanged) ↓ Creature[] bestiary-cache.ts (IndexedDB) ↓ stat-block.tsx (renders full stat block) ``` ## Development Commands ```bash pnpm check # Must pass before every commit pnpm test # Run all tests pnpm typecheck # TypeScript type checking pnpm --filter web dev # Dev server at localhost:5173 ``` ## Testing Strategy - **Domain tests**: Pure function tests for new `BestiaryIndexEntry` type and any utility functions - **Adapter tests**: Test index parsing (compact → readable format), test IndexedDB cache operations (mock IndexedDB via fake-indexeddb) - **Component tests**: Not in scope (existing pattern — components tested via manual verification) - **Integration**: Verify search returns multi-source results, verify add-from-index flow, verify fetch→cache→display flow ## New Dependency - `idb` — Promise-based IndexedDB wrapper (~1.5 KB gzipped). Used only in `bestiary-cache.ts`.