Add a vanilla-creature filter to card and library search

Filtering for creatures with an empty rules box can't be done from
`oracle_cards.oracle_text`: that column joins every face together, so an
Adventure card always carries its spell half's text and a naive `oracle_text
= ''` test would exclude exactly the cards worth including.

Derive it at ingest instead, from the front face's own text — the front is the
half that resolves as the creature, and adventure/prepared/transform cards are
all cast from it. Exposed as `vanilla_only` behind its own `has_vanilla`
capability check rather than folded into `has_universes_beyond`, since it
lands in a later reference-schema revision and a db can have one column
without the other. Needs a card-data re-download before it does anything.
This commit is contained in:
2026-08-14 01:02:29 -05:00
parent d014609244
commit e3725cee55
7 changed files with 133 additions and 5 deletions

View File

@@ -1449,6 +1449,13 @@ const ICON_CROWN = icon(
<line x1="5" y1="21" x2="19" y2="21" />
</>,
);
/** An empty rules box — a vanilla creature, all body and no text. */
const ICON_BLANK_BOX = icon(
<>
<rect x="3" y="4" width="18" height="16" rx="2" />
<line x1="3" y1="9" x2="21" y2="9" />
</>,
);
/**
* Card-origin and numeric criteria shared by the Library and Card Search
@@ -1467,6 +1474,7 @@ export type CardCriteria = {
priceMax: string;
legalIn: string;
canBeCommander: boolean;
vanillaOnly: boolean;
powerMin: string;
powerMax: string;
toughMin: string;
@@ -1481,6 +1489,7 @@ export const EMPTY_CRITERIA: CardCriteria = {
priceMax: "",
legalIn: "",
canBeCommander: false,
vanillaOnly: false,
powerMin: "",
powerMax: "",
toughMin: "",
@@ -1511,6 +1520,7 @@ export function applyCardCriteria(c: CardCriteria): Partial<LibraryFilter> {
price_max: num(c.priceMax),
legal_in: c.legalIn || null,
can_be_commander: c.canBeCommander,
vanilla_only: c.vanillaOnly,
power_min: num(c.powerMin),
power_max: num(c.powerMax),
toughness_min: num(c.toughMin),
@@ -1529,6 +1539,7 @@ export function applyCardCriteria(c: CardCriteria): Partial<LibraryFilter> {
export function criteriaActive(c: CardCriteria): boolean {
return (
c.canBeCommander ||
c.vanillaOnly ||
c.legalIn !== "" ||
[c.priceMin, c.priceMax, c.powerMin, c.powerMax, c.toughMin, c.toughMax].some(
(v) => v.trim() !== "",
@@ -1663,6 +1674,13 @@ export function CardCriteriaFields({
on={c.canBeCommander}
onToggle={() => onChange({ canBeCommander: !c.canBeCommander })}
/>
<IconToggle
icon={ICON_BLANK_BOX}
label="Only vanilla creatures"
hint="Creatures with an empty rules box. Includes Adventure and Prepared cards whose creature half has no abilities."
on={c.vanillaOnly}
onToggle={() => onChange({ vanillaOnly: !c.vanillaOnly })}
/>
</div>
</div>
</>