# Phase 12: Comparison View - Context **Gathered:** 2026-03-17 **Status:** Ready for planning ## Phase Boundary Users can view all candidates for a thread side-by-side in a tabular comparison layout with relative weight and price deltas. The table scrolls horizontally on narrow viewports with a sticky label column. Resolved threads display the comparison in read-only mode with the winning candidate visually marked. Impact preview (setup deltas) is a separate phase (13). ## Implementation Decisions ### Compare mode entry point - Add a third icon to the existing list/grid toggle bar, making it list | grid | compare (three-way toggle) - Use `candidateViewMode: 'list' | 'grid' | 'compare'` in uiStore — extends the existing Zustand state - Compare icon only appears when 2+ candidates exist in the thread (hidden otherwise) - "Add Candidate" button visibility in compare mode is Claude's discretion ### Table orientation and layout - Candidates as columns, attribute labels as rows (classic product-comparison pattern — Amazon/Wirecutter style) - Sticky left column for attribute labels; table scrolls horizontally on narrow viewports - Attribute row order: Image → Name → Rank → Weight (with delta) → Price (with delta) → Status → Product Link → Notes → Pros → Cons - Image row: sizing is Claude's discretion (balance compactness with product visibility) - Multi-line text (notes, pros, cons): rendering approach is Claude's discretion (keep table scannable) ### Delta highlighting style - Lightest candidate's weight cell gets a subtle colored background tint (e.g., bg-green-50); cheapest similarly - Non-best cells show delta text in neutral gray — no colored badges for deltas, only the "best" cell gets color - Missing weight/price data: Claude's discretion on indicator style (must satisfy COMP-04 — no misleading zeroes) - Delta format (absolute + delta, or delta only): Claude's discretion based on readability ### Resolved thread presentation - Winner column highlight and trophy/banner approach: Claude's discretion (existing resolution banner + column tint are both available patterns) - Interactive elements in resolved comparison (links clickable vs everything static): Claude's discretion, following the existing Phase 11 pattern where resolved threads disable mutation actions but keep read-only indicators - Existing resolution banner above the comparison table: Claude's discretion on whether to keep it, remove it, or adapt it ### Claude's Discretion - "Add Candidate" button visibility when in compare view - Image thumbnail sizing in comparison cells (square crop vs wider aspect) - Multi-line text rendering strategy (clamped with expand vs full text) - Missing data indicator style (dash with label, empty cell, etc.) - Delta format: absolute value + delta underneath, or delta only for non-best cells - Winner column marking approach (column tint, trophy icon, or both) - Resolved thread interactivity (links clickable vs all read-only) - Resolution banner behavior in compare view - View mode persistence (already in Zustand — whether compare resets on navigation or persists) - Compare toggle icon choice (e.g., Lucide `columns-3`, `table-2`, or similar) - Table cell padding, border styling, and overall table chrome - Column minimum/maximum widths - Keyboard accessibility for horizontal scrolling ## Existing Code Insights ### Reusable Assets - `candidateViewMode` in `uiStore` (`stores/uiStore.ts`): Already stores `'list' | 'grid'` — extend to include `'compare'` - `CandidateCard` / `CandidateListItem`: Data shape reference for what fields are available per candidate - `formatWeight()` / `formatPrice()` in `lib/formatters.ts`: Unit-aware formatting for table cells and deltas - `useWeightUnit()` / `useCurrency()` hooks: Current unit/currency for display - `RankBadge` (`CandidateListItem.tsx`): Exported component for gold/silver/bronze medals — reuse in compare table name row - `StatusBadge` (`StatusBadge.tsx`): Click-to-cycle status — render as static text in compare view (no interaction needed) - `LucideIcon` helper: For compare toggle icon and any icons in the table - `useThread(threadId)` hook: Returns `thread.candidates[]` with all fields needed (name, weightGrams, priceCents, status, pros, cons, notes, productUrl, imageFilename, categoryName, categoryIcon) ### Established Patterns - Three-way toggle: Extend existing `bg-gray-100 rounded-lg p-0.5` toggle bar pattern from thread toolbar - Pill badges: blue=weight, green=price, gray=category, purple=pros/cons — table can reference these colors for consistency - framer-motion already installed — AnimatePresence for view transitions if desired - React Query for server data, Zustand for UI-only state - Resolution banner: amber-50 bg with amber-200 border in resolved thread header — reusable pattern for winner column ### Integration Points - `src/client/routes/threads/$threadId.tsx`: Add compare view branch to the existing list/grid conditional rendering - `src/client/stores/uiStore.ts`: Extend `candidateViewMode` union type to include `'compare'` - New component: `ComparisonTable.tsx` (or similar) — receives candidates array, renders the tabular comparison - No backend changes needed — all data already available from `useThread` hook - No schema changes — this is a pure frontend/UI phase ## Specific Ideas - Classic product-comparison table like Amazon or Wirecutter — candidates as columns, attributes as rows - Subtle green tint on the "best" cell rather than heavy badges or bold formatting — keeps the minimalist feel - Gray delta text for non-best values — visual hierarchy: best stands out, others recede ## Deferred Ideas None — discussion stayed within phase scope --- *Phase: 12-comparison-view* *Context gathered: 2026-03-17*