Files
GearBox/.planning/phases/33-currency-system/33-CONTEXT.md

8.0 KiB

Phase 33: Currency System - Context

Gathered: 2026-04-13 Status: Ready for planning

## Phase Boundary

Replace the placeholder currency symbol swap with a real market-aware pricing system. Users select their market (tied to currency), see market-specific UVP/MSRP prices, community "what I paid" data filtered by locale, and approximate conversions as a labeled fallback when local prices don't exist. Includes community price submissions, candidate research prices, and purchase date tracking.

## Implementation Decisions

Data Model & Source Currency

  • D-01: Prices are market-specific, NOT simple exchange rate conversions. A €2,000 bike in Germany may be £2,200 in the UK and $3,100 in the US — these are independent market prices
  • D-02: Catalog items (globalItems) can have multiple market prices — UVP/MSRP stored per market/currency. Start with EU/DE prices as the primary market
  • D-03: Personal items store "what I paid" in the user's currency, auto-tagged with their market
  • D-04: Community price data is locale-tagged — German users see aggregate data from German submissions, UK users from UK submissions
  • D-05: Price submissions tied to collection ownership — you can only report a price for items you have in your collection (you actually bought it). Captured automatically when adding to collection
  • D-06: Candidate items in research threads can also have a "price I found it for" field — research-quality price data during comparison, valuable even before purchase
  • D-07: Both "what I paid" and "price I found it for" include a date field (when bought / when found) for temporal context and data aging

Conversion Strategy

  • D-08: Exchange rates sourced from ECB via frankfurter.app — free, daily updates, no API key, covers EUR/USD/GBP/JPY/CAD/AUD and ~30 more
  • D-09: Server-side conversion — server fetches rates daily, caches them, returns converted prices in API responses. MCP/API consumers also get conversion
  • D-10: Conversion is a FALLBACK, not the default — when a local market price exists, show that. Only convert when no local price is available
  • D-11: Converted prices are always clearly labeled as approximate — never presented as real market prices

User Experience & Display

  • D-12: Currency picker = market picker. Selecting EUR implies EU market, GBP implies UK market, USD implies US market. Simplifies settings — one choice drives both currency display and market data filtering
  • D-13: Auto-suggestion on first visit based on browser locale and IP geolocation. User can change anytime in settings
  • D-14: Converted prices use dual display format: €2,000 (~£1,720) — source price prominent, converted in parentheses. Makes it clear what's real vs. approximate
  • D-15: Global setting to auto-activate conversion (show converted prices by default) OR per-price toggle. User controls whether they see conversions automatically
  • D-16: Existing currency picker in settings page evolves to be the market/currency selector with the auto-suggestion behavior

Catalog & Sharing Implications

  • D-17: Catalog detail page: user's market UVP shown prominently + community average for their market. Collapsible "Other markets" section shows prices from other regions
  • D-18: Shared setups (card level): show viewer's market MSRP if available, otherwise converted price
  • D-19: Shared setups (detail level): full breakdown — owner's actual price, MSRP per market, community averages, conversion info
  • D-20: Comparison tables (thread candidates): normalize all candidates to user's currency for apples-to-apples comparison. Converted prices marked with ~. Users can add their own researched price via "price I found it for"
  • D-21: Community price aggregation shows per-market stats: "Users in DE typically pay €1,600 (12 reports)"

Claude's Discretion

  • Rate caching strategy (how long to cache, fallback when ECB is unreachable)
  • Schema design for market prices table (separate table vs. JSONB on globalItems)
  • Aggregation queries for community price stats (median vs. average, minimum report count threshold)
  • How to handle the transition from the current simple priceCents integer to the richer model

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

No external specs — requirements fully captured in decisions above.

Existing Implementation (to be replaced/extended)

  • src/client/lib/formatters.ts — Current formatPrice() with symbol-only swap, Currency type (lines 24-45)
  • src/client/hooks/useCurrency.ts — Current useCurrency() hook reading from settings (6 currencies)
  • src/client/hooks/useFormatters.tsuseFormatters() hook composing weight + price formatters
  • src/client/routes/settings.tsx — Currency pill picker UI (lines 254-280)
  • src/db/schema.tspriceCents: integer on items, candidates, globalItems
  • src/shared/schemas.ts — Zod schemas with priceCents fields
  • src/server/services/setup.service.ts — Setup totals computed via SQL SUM on price_cents
  • src/server/services/discovery.service.ts — Discovery feed price queries
  • src/client/components/ComparisonTable.tsx — Candidate price comparison display
  • src/client/lib/impactDeltas.ts — Price delta calculations for setup impact preview

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • useFormatters() hook: Central price formatting — all price displays go through this. Extend rather than replace
  • useCurrency() hook: Already reads currency preference from settings DB — extend to also imply market
  • Settings page currency picker: Existing UI to evolve into market/currency selector
  • formatPrice(): Needs to support dual display format and conversion annotations

Established Patterns

  • Prices stored as integer cents (priceCents) throughout the codebase (items, candidates, globalItems, setup aggregates)
  • COALESCE merge for reference items — global base + personal overlay. Currency data needs to work with this pattern
  • SQL aggregates for setup totals — computed on read, not stored. Currency conversion needs to integrate with these queries
  • Settings stored via useSetting() hook / settings table — currency/market preference fits this pattern

Integration Points

  • src/db/schema.ts: New market prices table, modify items/candidates for source currency + date fields
  • src/server/services/: New currency service for rate fetching, caching, conversion
  • src/server/services/setup.service.ts: Setup total queries need currency-aware aggregation
  • src/server/services/discovery.service.ts: Feed prices need market-awareness
  • src/client/lib/formatters.ts: Dual display format, conversion labeling
  • src/client/hooks/useCurrency.ts: Evolve to market-aware hook
  • src/client/routes/settings.tsx: Market/currency selector with auto-suggestion
  • src/server/mcp/: MCP tools need currency-aware price responses
  • src/client/components/ComparisonTable.tsx: Normalized currency display
  • src/client/routes/global-items/$globalItemId.tsx: Market prices + community data display

</code_context>

## Specific Ideas
  • The existing currency picker (pill toggle in settings) becomes the market selector — same UI pattern but with market implications
  • Auto-suggestion uses browser locale first, IP geolocation as fallback — suggest on first visit, respect manual override
  • Community price display: "Users in DE typically pay €1,600 (12 reports)" — locale-filtered aggregation
  • Candidate "price I found it for" is research-quality data — valuable even pre-purchase, should be treated as community data too
  • Purchase date on price submissions enables data aging — prices from years ago are less relevant
  • Primary market is EU/DE — start seeding UVP data for European manufacturers
## Deferred Ideas

None — discussion stayed within phase scope.


Phase: 33-currency-system Context gathered: 2026-04-13