Files
GearBox/.planning/phases/33-currency-system/33-06-PLAN.md
Jean-Luc Makiola 7a696f39a5 docs(33): create phase plans for currency system
6 plans across 3 waves covering market-aware pricing, exchange rates,
community price data, and currency-normalized display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 17:58:37 +02:00

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
33-currency-system 06 execute 3
03
04
05
src/client/routes/global-items/$globalItemId.tsx
src/client/components/ComparisonTable.tsx
src/client/components/SetupCard.tsx
src/client/hooks/useGlobalItems.ts
src/server/mcp/tools/index.ts
true
D-17
D-18
D-19
D-20
D-21
truths artifacts key_links
Global item detail page shows market prices section with user's market MSRP prominent
Global item detail page shows community price stats for user's market
Global item detail has collapsible 'Other Markets' section
Comparison table normalizes candidate prices to user's currency
Converted prices in comparison table marked with ~ prefix
SetupCard displays prices with correct currency symbol
MCP tools include currency context in price responses
path provides contains
src/client/routes/global-items/$globalItemId.tsx Market prices section on catalog detail page marketPrices
path provides contains
src/client/components/ComparisonTable.tsx Currency-normalized comparison with conversion labels convertClientPrice
from to via pattern
src/client/routes/global-items/$globalItemId.tsx /api/market-prices/global-items/:id/prices React Query fetch for market prices market-prices
from to via pattern
src/client/components/ComparisonTable.tsx src/client/hooks/useExchangeRates.ts useExchangeRates + convertClientPrice useExchangeRates|convertClientPrice
Integrate market-aware pricing into catalog detail pages, comparison tables, setup cards, and MCP tools.

Purpose: User-facing display of market prices, community data, and currency-normalized comparisons — the visible payoff of the currency system. Output: Updated global item detail with market prices, comparison table with conversion, setup card with currency, MCP tools with currency context.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/33-currency-system/33-CONTEXT.md @.planning/phases/33-currency-system/33-UI-SPEC.md @.planning/phases/33-currency-system/33-05-SUMMARY.md From src/client/hooks/useExchangeRates.ts (Plan 05): ```typescript export function useExchangeRates(): UseQueryResult; export function convertClientPrice(cents: number, from: string, to: string, rates: Record): number; ```

From src/client/hooks/useCurrency.ts (Plan 05):

export interface CurrencyContext {
  currency: Currency;
  market: string;
  showConversions: boolean;
}
export function useCurrency(): CurrencyContext;

From src/client/lib/formatters.ts (Plan 05):

export function formatDualPrice(options: DualPriceOptions): { source: string; converted: string };

From src/client/hooks/useGlobalItems.ts (existing):

export function useGlobalItem(id: number): UseQueryResult<GlobalItem>;

From src/client/components/ComparisonTable.tsx (existing):

interface ComparisonTableProps {
  candidates: CandidateWithCategory[];
  resolvedCandidateId: number | null;
  deltas?: Record<number, CandidateDelta>;
}
Task 1: Add market prices section to global item detail page src/client/routes/global-items/$globalItemId.tsx, src/client/hooks/useGlobalItems.ts src/client/routes/global-items/$globalItemId.tsx, src/client/hooks/useGlobalItems.ts, src/client/lib/api.ts Per D-17: Add a "Price" section to the global item detail page.

First, add a new hook in src/client/hooks/useGlobalItems.ts:

export function useGlobalItemPrices(globalItemId: number) {
  return useQuery({
    queryKey: ["global-item-prices", globalItemId],
    queryFn: () => apiGet<{
      marketPrices: Array<{ market: string; currency: string; priceCents: number; source: string | null }>;
    }>(`/api/market-prices/global-items/${globalItemId}/prices`),
    enabled: globalItemId > 0,
  });
}

export function useGlobalItemCommunityStats(globalItemId: number) {
  return useQuery({
    queryKey: ["global-item-community-stats", globalItemId],
    queryFn: () => apiGet<Array<{ market: string; currency: string; medianPrice: number; reportCount: number }>>(`/api/community-prices/${globalItemId}`),
    enabled: globalItemId > 0,
  });
}

Then update src/client/routes/global-items/$globalItemId.tsx:

Add a MarketPricesSection component within the detail page:

  • Uses useCurrency() to get { currency, market }
  • Uses useGlobalItemPrices(id) and useGlobalItemCommunityStats(id)
  • Uses useExchangeRates() for conversion when needed

Layout per UI-SPEC section 4:

  1. Section heading: "Price" (text-sm font-medium text-gray-900)
  2. User's market MSRP shown prominently: find marketPrice where market matches user's market
    • If found: text-lg font-semibold text-gray-900 + "MSRP ({MARKET})" label in text-xs text-gray-500 ml-2
    • If not found but other markets exist: show converted price from nearest market with dual display format using formatDualPrice
  3. Community stats for user's market: filter communityStats where market matches
    • Per D-21: "Community ({MARKET}): {SYMBOL}{median} median ({N} reports)" in text-sm text-gray-700 with report count in text-xs text-gray-400
    • Only show if reportCount >= 3 (server already filters, but handle empty gracefully)
  4. Collapsible "Other Markets" section:
    • Use useState for expanded state, default collapsed
    • Toggle: "Other Markets" text with Lucide chevron-right/chevron-down icon (14px)
    • Style: text-sm text-gray-500 cursor-pointer hover:text-gray-700
    • Inner rows: same price/label styling, indented with pl-4
    • Show all market prices except user's market
    • Show community stats for other markets

Place this section below the existing weight/price display area in the detail page. <acceptance_criteria> - src/client/hooks/useGlobalItems.ts exports useGlobalItemPrices and useGlobalItemCommunityStats - src/client/routes/global-items/$globalItemId.tsx contains a MarketPricesSection component - User's market MSRP shown prominently with market label - Community stats displayed as "Community ({MARKET}): {median} median ({N} reports)" - "Other Markets" section is collapsible and collapsed by default - bun run build succeeds </acceptance_criteria> cd /home/jlmak/Projects/jlmak/GearBox && bun run build && grep -c "MarketPricesSection|useGlobalItemPrices|useGlobalItemCommunityStats" src/client/routes/global-items/$globalItemId.tsx src/client/hooks/useGlobalItems.ts Global item detail page shows market prices with user's market MSRP, community stats, and collapsible other markets

Task 2: Update ComparisonTable with currency normalization and update MCP tools src/client/components/ComparisonTable.tsx, src/client/components/SetupCard.tsx, src/server/mcp/tools/index.ts src/client/components/ComparisonTable.tsx, src/client/components/SetupCard.tsx, src/server/mcp/tools/index.ts Per D-20: Update `src/client/components/ComparisonTable.tsx`: - Import `useCurrency` (for user's preferred currency), `useExchangeRates`, `convertClientPrice` from hooks - In the price rendering section: 1. Check if candidate has a different currency than user's preference (via `priceCurrency` field on candidate if available, otherwise assume same currency) 2. If different currency: convert using `convertClientPrice(candidate.priceCents, candidate.priceCurrency, userCurrency, rates)` 3. Display converted price with `~` prefix in `text-gray-400`: e.g., `~$2,160` instead of plain `$2,160` 4. Best-price highlighting (`bg-green-50`) should apply based on converted amounts for apples-to-apples comparison - Add a new "Found Price" row (per D-06) in the ATTRIBUTE_ROWS array: - Key: "foundPrice", Label: "Found Price" - Render: show candidate.foundPriceCents formatted with candidate.foundPriceCurrency if available, else "—" - Include date if available: `text-xs text-gray-400` below the price - Note: The CandidateWithCategory interface may need extending. If the API doesn't yet return foundPriceCents/foundPriceCurrency on candidates, check the thread service response and update the interface to match.

Per D-18: Update src/client/components/SetupCard.tsx:

  • If SetupCard shows a price total, ensure it uses useFormatters().price() which now uses the correct currency
  • This should already work if the component uses useFormatters() — verify and adjust if it uses hardcoded "$" or similar

Per MCP tool updates: Update src/server/mcp/tools/index.ts:

  • In list_items and get_item tool responses: include priceCurrency field alongside priceCents
  • In get_setup tool response: include currency info with totals
  • Add a new tool get_exchange_rates:
    • Description: "Get current exchange rates for currency conversion"
    • No parameters required
    • Returns: { base, date, rates } from getExchangeRates()
  • In create_item and update_item tools: accept optional priceCurrency parameter
  • In add_candidate and update_candidate tools: accept optional foundPriceCents, foundPriceCurrency, foundPriceDate parameters
  • Follow existing MCP tool patterns for parameter/response structure <acceptance_criteria>
    • ComparisonTable.tsx imports useExchangeRates and convertClientPrice
    • ComparisonTable price cells show ~ prefix when price is converted from different currency
    • ComparisonTable has "Found Price" row for candidate research prices
    • SetupCard uses useFormatters().price() for currency-aware display
    • MCP tools/index.ts contains get_exchange_rates tool definition
    • MCP list_items and get_item responses include priceCurrency
    • bun run build succeeds </acceptance_criteria> cd /home/jlmak/Projects/jlmak/GearBox && bun run build && grep -c "convertClientPrice|foundPrice|get_exchange_rates|priceCurrency" src/client/components/ComparisonTable.tsx src/server/mcp/tools/index.ts Comparison table normalizes currencies, MCP tools include currency context, setup cards display correct currency

<threat_model>

Trust Boundaries

Boundary Description
server→client Market prices and exchange rates served to public clients
MCP client→server MCP tool invocations with currency parameters

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-33-15 Tampering ComparisonTable conversion accept Client-side conversion uses server-provided rates — worst case is stale rates, not a security issue
T-33-16 Information Disclosure market prices display accept Market prices are intentionally public data — MSRP is not sensitive
T-33-17 Tampering MCP priceCurrency param mitigate MCP tools validate priceCurrency against known currency list before persisting
</threat_model>
- `bun run build` succeeds (no TypeScript errors) - Global item detail shows market prices section - ComparisonTable normalizes prices to user's currency - MCP get_exchange_rates tool returns rates - All existing tests pass: `bun test`

<success_criteria>

  • Catalog detail page shows market prices + community data
  • Comparison table normalizes and labels converted prices
  • Setup cards show correct currency
  • MCP tools expose currency data and exchange rates
  • Full build succeeds </success_criteria>
After completion, create `.planning/phases/33-currency-system/33-06-SUMMARY.md`