docs(spec): inventory search spell filters + jewelry-type selection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5f7b4dcd55
commit
aa3139b586
1 changed files with 106 additions and 0 deletions
|
|
@ -0,0 +1,106 @@
|
|||
# Inventory search: spell filters + jewelry-type selection
|
||||
|
||||
**Date:** 2026-07-14
|
||||
**Status:** Approved
|
||||
|
||||
## Problem
|
||||
|
||||
The inventory search UI (`static/inventory.html`) has a Legendary Cantrips
|
||||
checkbox grid and a "Spell name contains..." text box, and `inventory.js`
|
||||
sends them as `legendary_cantrips` / `spell_contains` query params — but the
|
||||
Go inventory service (`go-services/inventory-go/search.go`) never implemented
|
||||
these filters, so they are silently ignored. The legacy Python service
|
||||
(`inventory-service/main.py:3345-3452`) supported them.
|
||||
|
||||
Additionally there is no way to narrow a jewelry search to a specific type
|
||||
(ring / bracelet / necklace / trinket), even though the backend already
|
||||
supports this via the `slot_names` param (used by the suitbuilder).
|
||||
|
||||
Goal: check e.g. Invulnerability + Summoning, pick jewelry type Ring, and get
|
||||
back only rings carrying BOTH cantrips.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **AND semantics** for multiple checked cantrips (matches legacy intent):
|
||||
an item must carry every checked cantrip.
|
||||
- **Multi-select checkboxes** for jewelry type (Ring, Bracelet, Necklace,
|
||||
Trinket), shown only when the equipment-type selector is on Jewelry.
|
||||
None checked = all jewelry.
|
||||
- **Also wire `spell_contains`** (the dead free-text box) and `has_spell`
|
||||
(exact-name variant referenced by the agent tools) in the same pass — same
|
||||
mechanism, nearly free.
|
||||
|
||||
## Backend design (`go-services/inventory-go/search.go`)
|
||||
|
||||
Three new query params handled in `runSearch`, emitted as ordinary `WHERE`
|
||||
conditions on the `items_with_slots` CTE so they compose with all existing
|
||||
filters, sorting, pagination, and the count query:
|
||||
|
||||
1. **`legendary_cantrips`** — CSV of display names (e.g.
|
||||
`Legendary Invulnerability,Legendary Summoning Prowess`). For each name,
|
||||
case-insensitive substring match (both directions, mirroring Python's
|
||||
flexible matching) against the in-memory spell enum map (`s.spells`,
|
||||
loaded from `comprehensive_enum_database_v2.json`) to collect that
|
||||
cantrip's spell IDs. Each cantrip emits:
|
||||
|
||||
```sql
|
||||
EXISTS (SELECT 1 FROM item_spells sp
|
||||
WHERE sp.item_id = db_item_id AND sp.spell_id IN ($n, ...))
|
||||
```
|
||||
|
||||
Cantrips are ANDed. A cantrip matching zero spells contributes `1 = 0`.
|
||||
|
||||
Note: this deliberately does NOT replicate Python's
|
||||
`COUNT(DISTINCT spell_id) >= N` trick, which can false-positive when one
|
||||
cantrip name matches two spell IDs on the same item while another matches
|
||||
zero. Per-cantrip EXISTS is strictly correct AND semantics.
|
||||
|
||||
2. **`spell_contains`** — free text; collect all spell IDs whose name
|
||||
contains the text (case-insensitive); a single `EXISTS ... IN (ids)`.
|
||||
No matching spell → `1 = 0`.
|
||||
|
||||
3. **`has_spell`** — exact name match (case-insensitive) to one spell ID; a
|
||||
single `EXISTS`. Unknown name → `1 = 0`.
|
||||
|
||||
Spell IDs originate from our own enum map, not user input, but are still
|
||||
bound via the existing `argBuilder` positional params for consistency.
|
||||
|
||||
## Frontend design (`static/inventory.html` + `static/inventory.js`)
|
||||
|
||||
- New "Jewelry Type" filter card with checkboxes **Ring, Bracelet, Necklace,
|
||||
Trinket**, shown only when equipment type = Jewelry (same show/hide pattern
|
||||
as the weapon-type selector).
|
||||
- `inventory.js` maps checked boxes to `slot_names` CSV using the backend's
|
||||
slot vocabulary: Ring→`Ring`, Bracelet→`Bracelet`, Necklace→`Neck`,
|
||||
Trinket→`Trinket`. None checked → param omitted.
|
||||
- The cantrip grid and spell text box already send the correct params — no
|
||||
JS changes needed for those.
|
||||
- The existing `slotNameClause` per-type OR approaches (including the
|
||||
Trinket clause's `%bracelet%` exclusion) are reused untouched.
|
||||
|
||||
## Testing
|
||||
|
||||
TDD in `go-services/inventory-go/search_test.go` (new file):
|
||||
|
||||
- Name→spell-ID matcher: substring both-directions semantics, case
|
||||
insensitivity, zero-match behavior.
|
||||
- Condition builder: correct EXISTS SQL shape, one clause per cantrip ANDed,
|
||||
`1 = 0` on no-match, arg binding via argBuilder.
|
||||
|
||||
Query execution against the real DB is validated manually after deploy
|
||||
(consistent with the rest of search.go, which has no DB-backed tests).
|
||||
The Dockerfile's `RUN go test ./...` gates the image build.
|
||||
|
||||
## Deploy
|
||||
|
||||
- Backend: standard inventory-go sync + build + recreate (cutover override).
|
||||
- Frontend: `inventory.html`/`inventory.js` are plain static files on the
|
||||
bind mount — `git pull` on the server picks them up; `deploy-frontend.sh`
|
||||
(React build) is NOT involved.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Cantrip tiers other than Legendary in the checkbox grid (the free-text box
|
||||
covers Epic/Major/etc. searches).
|
||||
- React frontend inventory window changes.
|
||||
- Suitbuilder behavior (unchanged; it passes its own slot_names).
|
||||
Loading…
Add table
Add a link
Reference in a new issue