---
phase: 33-currency-system
plan: 06
type: execute
wave: 3
depends_on: [03, 04, 05]
files_modified:
  - 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
autonomous: true
requirements: [D-17, D-18, D-19, D-20, D-21]

must_haves:
  truths:
    - "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"
  artifacts:
    - path: "src/client/routes/global-items/$globalItemId.tsx"
      provides: "Market prices section on catalog detail page"
      contains: "marketPrices"
    - path: "src/client/components/ComparisonTable.tsx"
      provides: "Currency-normalized comparison with conversion labels"
      contains: "convertClientPrice"
  key_links:
    - from: "src/client/routes/global-items/$globalItemId.tsx"
      to: "/api/market-prices/global-items/:id/prices"
      via: "React Query fetch for market prices"
      pattern: "market-prices"
    - from: "src/client/components/ComparisonTable.tsx"
      to: "src/client/hooks/useExchangeRates.ts"
      via: "useExchangeRates + convertClientPrice"
      pattern: "useExchangeRates|convertClientPrice"
---

<objective>
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.
</objective>

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

<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

<interfaces>
From src/client/hooks/useExchangeRates.ts (Plan 05):
```typescript
export function useExchangeRates(): UseQueryResult<ExchangeRates>;
export function convertClientPrice(cents: number, from: string, to: string, rates: Record<string, number>): number;
```

From src/client/hooks/useCurrency.ts (Plan 05):
```typescript
export interface CurrencyContext {
  currency: Currency;
  market: string;
  showConversions: boolean;
}
export function useCurrency(): CurrencyContext;
```

From src/client/lib/formatters.ts (Plan 05):
```typescript
export function formatDualPrice(options: DualPriceOptions): { source: string; converted: string };
```

From src/client/hooks/useGlobalItems.ts (existing):
```typescript
export function useGlobalItem(id: number): UseQueryResult<GlobalItem>;
```

From src/client/components/ComparisonTable.tsx (existing):
```typescript
interface ComparisonTableProps {
  candidates: CandidateWithCategory[];
  resolvedCandidateId: number | null;
  deltas?: Record<number, CandidateDelta>;
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add market prices section to global item detail page</name>
  <files>src/client/routes/global-items/$globalItemId.tsx, src/client/hooks/useGlobalItems.ts</files>
  <read_first>src/client/routes/global-items/$globalItemId.tsx, src/client/hooks/useGlobalItems.ts, src/client/lib/api.ts</read_first>
  <action>
Per D-17: Add a "Price" section to the global item detail page.

First, add a new hook in `src/client/hooks/useGlobalItems.ts`:
```typescript
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.
  </action>
  <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>
  <verify>
    <automated>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</automated>
  </verify>
  <done>Global item detail page shows market prices with user's market MSRP, community stats, and collapsible other markets</done>
</task>

<task type="auto">
  <name>Task 2: Update ComparisonTable with currency normalization and update MCP tools</name>
  <files>src/client/components/ComparisonTable.tsx, src/client/components/SetupCard.tsx, src/server/mcp/tools/index.ts</files>
  <read_first>src/client/components/ComparisonTable.tsx, src/client/components/SetupCard.tsx, src/server/mcp/tools/index.ts</read_first>
  <action>
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
  </action>
  <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>
  <verify>
    <automated>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</automated>
  </verify>
  <done>Comparison table normalizes currencies, MCP tools include currency context, setup cards display correct currency</done>
</task>

</tasks>

<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>

<verification>
- `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`
</verification>

<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>

<output>
After completion, create `.planning/phases/33-currency-system/33-06-SUMMARY.md`
</output>
