Implement the 029-on-demand-bestiary feature that replaces the bundled XMM bestiary JSON with a compact search index (~350KB) and on-demand source loading, where users explicitly provide a URL or upload a JSON file to fetch full stat block data per source, which is then normalized and cached in IndexedDB (with in-memory fallback) so creature stat blocks load instantly on subsequent visits while keeping the app bundle small and never auto-fetching copyrighted content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-10 22:46:13 +01:00
parent 99d1ba1bcd
commit 91120d7c82
31 changed files with 38321 additions and 63422 deletions

View File

@@ -1,4 +1,3 @@
import type { Creature } from "@initiative/domain";
import { Search, X } from "lucide-react";
import {
type KeyboardEvent,
@@ -7,12 +6,13 @@ import {
useRef,
useState,
} from "react";
import type { SearchResult } from "../hooks/use-bestiary.js";
import { Input } from "./ui/input.js";
interface BestiarySearchProps {
onSelectCreature: (creature: Creature) => void;
onSelectCreature: (result: SearchResult) => void;
onClose: () => void;
searchFn: (query: string) => Creature[];
searchFn: (query: string) => SearchResult[];
}
export function BestiarySearch({
@@ -101,8 +101,8 @@ export function BestiarySearch({
</div>
) : (
<ul className="max-h-60 overflow-y-auto py-1">
{results.map((creature, i) => (
<li key={creature.id}>
{results.map((result, i) => (
<li key={`${result.source}:${result.name}`}>
<button
type="button"
className={`flex w-full items-center justify-between px-3 py-1.5 text-left text-sm ${
@@ -110,12 +110,12 @@ export function BestiarySearch({
? "bg-accent/20 text-foreground"
: "text-foreground hover:bg-hover-neutral-bg"
}`}
onClick={() => onSelectCreature(creature)}
onClick={() => onSelectCreature(result)}
onMouseEnter={() => setHighlightIndex(i)}
>
<span>{creature.name}</span>
<span>{result.name}</span>
<span className="text-xs text-muted-foreground">
{creature.sourceDisplayName}
{result.sourceDisplayName}
</span>
</button>
</li>