--- 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 After completion, create `.planning/phases/33-currency-system/33-06-SUMMARY.md`