- Backend: validate display.currency against 10 supported ISO 4217 codes
(USD, GBP, EUR, CAD, AUD, JPY, CHF, MXN, BRL, INR); return 400 on
unsupported code with a clear message listing accepted values
- Frontend: useCurrency composable fetches rates from open.er-api.com
with 1-hour module-level cache and in-flight deduplication; falls back
to USD display on network failure
- Preferences store: adds display.currency with localStorage fallback for
anonymous users and localStorage-to-DB migration for newly logged-in users
- ListingCard: price and market price now convert from USD using live rates,
showing USD synchronously while rates load then updating reactively
- Settings UI: currency selector dropdown in Appearance section using
theme-aware CSS classes; available to all users (anon via localStorage,
logged-in via DB preference)
- Tests: 6 Python tests for the PATCH /api/preferences currency endpoint
(including ordering-safe fixture using patch.object on _LOCAL_SNIPE_DB);
14 Vitest tests for convertFromUSD, formatPrice, and formatPriceUSD
Phase 2 (snipe#4): after bulk-reporting sellers to eBay T&S, Snipe now
persists which sellers were reported so cards show a muted "Reported to
eBay" badge and users aren't prompted to re-report the same seller.
- migration 012 adds reported_sellers table (user DB, UNIQUE on seller)
- Store.mark_reported / list_reported methods
- POST /api/reported + GET /api/reported endpoints
- reported store (frontend) with optimistic update + server persistence
- reportSelected wires into store after opening eBay tabs
Phase 3 prep (snipe#4): community blocklist share toggle
- Settings > Community section: "Share blocklist with community" toggle
(visible only to signed-in cloud users, default OFF)
- Persisted as community.blocklist_share user preference
- Backend community signal publish now gated on opt-in preference;
privacy-by-architecture: sharing is explicit, never implicit
Both stores hardcoded /api/* paths without reading VITE_API_BASE, causing
/api/session to hit the menagerie root (no /snipe prefix) and receive a
302 redirect to circuitforge.tech/login instead of a session response.
Every other store already read VITE_API_BASE correctly.