Files
GearBox/.planning/phases/11-candidate-ranking/11-CONTEXT.md

5.7 KiB

Phase 11: Candidate Ranking - Context

Gathered: 2026-03-16 Status: Ready for planning

## Phase Boundary

Users can drag candidates into a priority order within a thread. The rank persists across sessions and is visually communicated with medal badges on the top 3. Drag handles and reordering are disabled on resolved threads. Comparison view, impact preview, and pros/cons editing are separate phases.

## Implementation Decisions

Card layout and view toggle

  • Add a grid/list view toggle in the thread header (list view is default)
  • List view: vertical stack of horizontal cards (image thumbnail on left, name + badges on right) — enables drag-to-reorder
  • Grid view: current 3-column responsive card layout preserved
  • Both views render candidates in rank order (sort_order ascending)
  • Rank badges visible in both views

Drag handle design

  • Always-visible GripVertical icon (Lucide) on the left side of each list-view card
  • Grip icon color: muted gray (text-gray-300), darkens to text-gray-500 on hover
  • Cursor changes to 'grab' on hover, 'grabbing' during drag
  • Drag feedback: elevated card with shadow + scale-up effect; other cards animate to show drop target gap (standard framer-motion Reorder behavior)
  • On resolved threads: grip icon disappears entirely (not disabled/grayed)
  • Drag only available in list view (grid view has no drag handles)

Rank badge style

  • Medal icons (Lucide 'medal' or 'trophy') in gold (#D4AF37), silver (#C0C0C0), bronze (#CD7F32) for top 3 candidates
  • Positioned inline before the candidate name text
  • Candidates ranked 4th and below show no rank indicator — position implied by list order
  • On resolved threads: rank badges remain visible (static, read-only) — overrides roadmap success criteria #4 which said "rank badges absent on resolved thread"; user prefers retrospective visibility

Sort order and persistence

  • Schema migration adds sort_order REAL NOT NULL DEFAULT 0 to thread_candidates
  • Migration initializes existing candidates with spaced values (1000, 2000, 3000...) ordered by created_at — ensures immediate correct ordering
  • Fractional indexing: only the moved item gets a single UPDATE (midpoint between neighbors)
  • New candidates added to a thread get the highest sort_order (appended to bottom of rank)
  • Auto-save on drop — no "Save order" button; reorder persists immediately via PATCH /api/threads/:id/candidates/reorder
  • tempItems local state pattern: render from tempItems ?? queryData.candidates; clear on mutation onSettled — prevents React Query flicker

Claude's Discretion

  • Exact horizontal card dimensions and spacing in list view
  • Grid/list toggle icon style and placement
  • Drag animation timing and spring config
  • Image thumbnail size in list view cards
  • How action buttons (Winner, Delete, Link) adapt to horizontal card layout
  • Keyboard accessibility for reordering (arrow keys to move)

<code_context>

Existing Code Insights

Reusable Assets

  • CandidateCard (src/client/components/CandidateCard.tsx): Current card component — needs horizontal variant for list view, or a new CandidateListItem component
  • StatusBadge (src/client/components/StatusBadge.tsx): Click-to-cycle pattern reusable in list view
  • useCandidates.ts hooks: useCreateCandidate, useUpdateCandidate, useDeleteCandidate — need new useReorderCandidates mutation
  • useThread hook: Returns thread with candidates[] array — already has all data needed, just needs sort_order ordering
  • formatWeight/formatPrice formatters: Reuse in list view card badges
  • useWeightUnit/useCurrency hooks: Already used by CandidateCard
  • LucideIcon helper: For GripVertical drag handle and medal rank badges
  • uiStore (Zustand): Add candidateViewMode: 'list' | 'grid' for view toggle persistence

Established Patterns

  • framer-motion@12.37.0 already installed — Reorder.Group/Reorder.Item for drag ordering
  • React Query for server data, Zustand for UI-only state
  • Pill badges: blue for weight, green for price, gray for category, purple for pros/cons
  • Services accept db as first param (DI pattern for testability)
  • API validation via @hono/zod-validator with Zod schemas
  • Hover-reveal action buttons on CandidateCard (Winner, Delete, External link)

Integration Points

  • src/db/schema.ts: Add sortOrder: real("sort_order").notNull().default(0) to threadCandidates
  • src/server/services/thread.service.ts: New reorderCandidates() function (transactional); update getCandidates to ORDER BY sort_order
  • src/server/routes/threads.ts: New PATCH /:id/candidates/reorder endpoint; reject if thread is resolved
  • src/shared/schemas.ts: New reorderCandidatesSchema (z.object with orderedIds array)
  • src/client/routes/threads/$threadId.tsx: Wrap candidates in Reorder.Group, add view toggle, use tempItems pattern
  • src/client/hooks/useCandidates.ts: New useReorderCandidates mutation hook
  • tests/helpers/db.ts: Update CREATE TABLE for thread_candidates to include sort_order column
  • Drizzle migration: sort_order REAL NOT NULL DEFAULT 0 + data migration to space existing rows

</code_context>

## Specific Ideas
  • List view cards should feel like Trello or Linear cards — horizontal layout, grip handle on the left, compact but informative
  • Drag feedback should use standard framer-motion spring animations (elevated + gap), not custom physics
  • Medal badges should use actual metallic-feeling colors (gold #D4AF37, silver #C0C0C0, bronze #CD7F32), not generic highlight colors
## Deferred Ideas

None — discussion stayed within phase scope


Phase: 11-candidate-ranking Context gathered: 2026-03-16