---
phase: 11-candidate-ranking
plan: "02"
subsystem: client-ui
tags: [drag-reorder, framer-motion, rank-badges, view-toggle, list-view]
dependency_graph:
  requires: [11-01]
  provides: [drag-to-reorder-ui, rank-badges, list-grid-toggle]
  affects: [threads/$threadId.tsx, CandidateCard, CandidateListItem, uiStore, useCandidates]
tech_stack:
  added: []
  patterns:
    - framer-motion Reorder.Group + Reorder.Item with useDragControls for drag handle
    - tempItems pattern to prevent React Query flicker during optimistic drag
    - RankBadge exported from CandidateListItem for reuse across views
    - candidateViewMode in uiStore for list/grid toggle state
key_files:
  created:
    - src/client/components/CandidateListItem.tsx
  modified:
    - src/client/hooks/useCandidates.ts
    - src/client/stores/uiStore.ts
    - src/client/components/CandidateCard.tsx
    - src/client/routes/threads/$threadId.tsx
    - src/client/lib/iconData.tsx
    - src/client/hooks/useThreads.ts
decisions:
  - Resolved thread list view uses plain div (not Reorder.Group) — no drag, rank badges visible
  - handleDragEnd fires on Reorder.Group onPointerUp to debounce reorder API call
  - biome-ignore applied to useExhaustiveDependencies for thread?.candidates dep — intentional trigger
metrics:
  duration: 4min
  completed: 2026-03-16T21:29:07Z
  tasks_completed: 3
  files_changed: 7
---

# Phase 11 Plan 02: Drag-to-Reorder UI Summary

Drag-to-reorder candidate ranking with list/grid view toggle, gold/silver/bronze rank badges, and framer-motion Reorder.Group with tempItems flicker prevention.

## What Was Built

### Task 1: useReorderCandidates hook + uiStore view mode + CandidateListItem component
- Added `useReorderCandidates` mutation hook to `useCandidates.ts` using `apiPatch` to hit `PATCH /api/threads/:id/candidates/reorder`
- Added `candidateViewMode: "list" | "grid"` and `setCandidateViewMode` to `uiStore.ts`
- Created `CandidateListItem.tsx` — horizontal card for list view with drag handle (GripVertical), RankBadge (medal icon), 48x48 image thumbnail, badge row (weight/price/category/status/pros-cons), and hover-reveal action buttons
- Exported `RankBadge` component (gold `#D4AF37`, silver `#C0C0C0`, bronze `#CD7F32`) for reuse
- Added `style` prop support to `LucideIcon` for colored medal icons
- Added `pros` and `cons` fields to `CandidateWithCategory` in `useThreads.ts` (Rule 2 auto-fix — missing after Phase 10)

### Task 2: Thread detail page with view toggle, Reorder.Group, rank badges in grid view
- Updated `CandidateCard.tsx`: added `rank?: number` prop and renders `<RankBadge rank={rank} />` in badge row
- Updated `threads/$threadId.tsx`:
  - List/grid view toggle (LayoutList / LayoutGrid icons) using `candidateViewMode` from uiStore
  - Active list view: `<Reorder.Group>` wrapping `<CandidateListItem>` instances for drag-to-reorder
  - Resolved list view: plain `<div>` with `<CandidateListItem isActive={false}>` — rank badges visible, drag handles hidden
  - Grid view: existing `<CandidateCard>` grid with `rank={index + 1}` passed to each card
  - `tempItems` state: holds in-progress drag order, falls back to `thread.candidates` when null
  - `handleDragEnd`: fires `reorderMutation.mutate({ orderedIds })` and clears tempItems on settled
  - `useEffect` clears tempItems when `thread?.candidates` reference changes (fresh server data)

### Task 3: Human verification (auto-approved — auto_chain_active)

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing] Added pros/cons fields to CandidateWithCategory in useThreads.ts**
- **Found during:** Task 1 (needed by CandidateListItem)
- **Issue:** `CandidateWithCategory` interface in `useThreads.ts` was missing `pros` and `cons` fields added in Phase 10. CandidateListItem needed these to render the "+/- Notes" badge.
- **Fix:** Added `pros: string | null` and `cons: string | null` to the interface
- **Files modified:** `src/client/hooks/useThreads.ts`
- **Commit:** acfa995

**2. [Rule 2 - Missing] Added style prop to LucideIcon component**
- **Found during:** Task 1 (needed by RankBadge for medal colors)
- **Issue:** LucideIcon only accepted `className` for styling; RankBadge needed inline `style={{ color }}` for gold/silver/bronze hex colors not achievable via Tailwind
- **Fix:** Added optional `style?: React.CSSProperties` prop to LucideIcon and passed through to icon component
- **Files modified:** `src/client/lib/iconData.tsx`
- **Commit:** acfa995

## Self-Check: PASSED

All created/modified files exist. Both task commits (acfa995, 94c07e7) confirmed in git log.
