ADR-003: Branded types for compile-time identity safety at zero runtime cost. ADR-004: On-demand bestiary via compact index + IndexedDB cache, avoiding distribution of copyrighted content. ADR-005: All quality gates at pre-commit for tight agent feedback loops, with analysis of per-change hooks as a future option. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4.1 KiB
ADR-004: On-Demand Bestiary Loading via Compact Index and IndexedDB Cache
Date: 2026-03-25 Status: accepted
Context
The application integrates a D&D creature bestiary containing 3,300+ creatures from the 5etools dataset. The full bestiary data (stat blocks, traits, actions, spellcasting) is several megabytes of JSON. Bundling it directly into the application would create two problems: a large initial download for every user, and the distribution of copyrighted game content as part of the application bundle.
Decision
The bestiary is split into two tiers:
-
Compact search index (
data/bestiary/index.json, ~350KB) — shipped with the application bundle. Contains only the fields needed for search and display in the autocomplete dropdown: name, source, AC, HP, DEX, CR, initiative proficiency, size, and type. Field names are abbreviated (n,s,ac,hp,dx,cr,ip,sz,tp) to minimize file size. Generated offline byscripts/generate-bestiary-index.mjsfrom a local clone of the 5etools repository. -
On-demand source data — full creature stat blocks are fetched per-source when a user first needs them (e.g., when viewing a stat block or adding a creature with HP/AC pre-fill). Fetched data is cached in IndexedDB (
initiative-bestiarydatabase) via theidblibrary, with an in-memory Map fallback when IndexedDB is unavailable. Users can also upload source files directly or bulk-import all sources.
The application never bundles or redistributes the full creature data. Users fetch it themselves from their own configured source URLs.
Alternatives Considered
Bundle all bestiary data — simplest approach, used during early development. Eliminated because it would distribute copyrighted content in the application bundle and inflate the initial download by several megabytes. Most users only need a fraction of the available sources.
Server-side API — a backend service could serve creature data on demand. This would keep the client lightweight and solve the bundle size concern, but the copyright issue remains — we would still be distributing copyrighted content, just from a server instead of a bundle. It also contradicts the project's local-first, single-user, no-backend architecture and would require hosting infrastructure and a network dependency for basic functionality.
Service Worker with lazy caching — fetch and cache bestiary data transparently via a Service Worker. More complex to implement and debug than explicit IndexedDB caching. The explicit approach gives users visibility and control over which sources are cached (via the source manager UI).
localStorage for caching — simpler API than IndexedDB, but localStorage has a ~5MB limit per origin, which is insufficient for multiple bestiary sources. IndexedDB has no practical storage limit.
Consequences
Positive:
- The application does not distribute copyrighted game content. Users fetch data from their own sources.
- Initial bundle stays small (~350KB for the search index). The full bestiary data is only downloaded when needed and then cached locally.
- Offline capability: once sources are cached in IndexedDB, creature data is available without network access.
- Users have explicit control over cached sources (import, clear, manage via UI).
Negative:
- First-time use requires fetching source data before full stat blocks are available. The bulk import feature mitigates this but requires an initial download.
- The search index must be regenerated manually when the upstream 5etools dataset changes. In practice this is infrequent (new D&D source books release a few times per year), so a manual process triggered by a new book release is sufficient at this scale.
- Two separate data representations (compact index vs full source) must be kept conceptually in sync. A creature that appears in the index but whose source hasn't been fetched will show limited information until the source is cached.
- IndexedDB adds adapter complexity (async API, database versioning, migration handling) compared to the synchronous localStorage used for encounter persistence.