---
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"
---
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.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.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):
```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;
```
From src/client/components/ComparisonTable.tsx (existing):
```typescript
interface ComparisonTableProps {
candidates: CandidateWithCategory[];
resolvedCandidateId: number | null;
deltas?: Record;
}
```
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`:
```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>(`/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.
- 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
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
- 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
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
## 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 |
- `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`
- 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