fix(card-sorting): lock correctly positioned prefix/suffix cards

Cards in the correct prefix or suffix are now locked and cannot be
dragged, preventing state thrashing when trying to move them.

**Changes:**

1. **Added isCardLocked() helper:**
   - Checks if a card is part of the correct prefix or suffix
   - Uses same logic as visual feedback (prefix/suffix detection)

2. **Updated handlePointerDown:**
   - Early return if isCardLocked(cardId) is true
   - Prevents drag initialization for locked cards

3. **Updated AnimatedCard component:**
   - Added isLocked prop
   - Cursor changes to 'not-allowed' for locked cards
   - Visual indication that cards cannot be moved

**User experience:**
- Locked cards show 'not-allowed' cursor on hover
- Attempting to drag does nothing (no state changes)
- Clear feedback that these cards are finalized
- Prevents accidental disruption of correctly placed cards
- No more state thrashing when clicking on arranged cards

**Example:**
With sequence [10, 20, 30, 50, 40] and correct order [10, 20, 30, 40, 50]:
- Cards 10, 20, 30 are locked (correct prefix)
- Only cards 50 and 40 can be dragged
- Trying to drag card 10 shows 'not-allowed' cursor and does nothing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-10-23 21:56:46 -05:00
parent 4ba7f24717
commit 170abed231

View File

@@ -732,6 +732,7 @@ function AnimatedCard({
isSpectating,
isCorrect,
isCorrectPosition,
isLocked,
draggedByPlayerId,
localPlayerId,
players,
@@ -748,6 +749,7 @@ function AnimatedCard({
isSpectating: boolean
isCorrect: boolean
isCorrectPosition: boolean
isLocked: boolean
draggedByPlayerId?: string
localPlayerId?: string
players: Map<string, { id: string; name: string; emoji: string }>
@@ -795,7 +797,7 @@ function AnimatedCard({
position: 'absolute',
width: '140px',
height: '180px',
cursor: isSpectating ? 'default' : 'grab',
cursor: isLocked ? 'not-allowed' : isSpectating ? 'default' : 'grab',
touchAction: 'none',
userSelect: 'none',
transition: 'box-shadow 0.2s ease',
@@ -1310,9 +1312,39 @@ export function PlayingPhaseDrag() {
return `${m}:${s.toString().padStart(2, '0')}`
}
// Check if a card is locked (in correct prefix or suffix and thus not draggable)
const isCardLocked = (cardId: string): boolean => {
const cardIndex = inferredSequence.findIndex((c) => c.id === cardId)
if (cardIndex < 0) return false
// Check if card is in correct prefix
let isInPrefix = true
for (let i = 0; i <= cardIndex; i++) {
if (inferredSequence[i]?.id !== state.correctOrder[i]?.id) {
isInPrefix = false
break
}
}
if (isInPrefix) return true
// Check if card is in correct suffix
let isInSuffix = true
const offsetFromEnd = inferredSequence.length - 1 - cardIndex
for (let i = 0; i <= offsetFromEnd; i++) {
const seqIdx = inferredSequence.length - 1 - i
const correctIdx = state.correctOrder.length - 1 - i
if (inferredSequence[seqIdx]?.id !== state.correctOrder[correctIdx]?.id) {
isInSuffix = false
break
}
}
return isInSuffix
}
// Handle pointer down (start drag)
const handlePointerDown = (e: React.PointerEvent, cardId: string) => {
if (isSpectating) return
if (isCardLocked(cardId)) return // Don't allow dragging locked cards
const target = e.currentTarget as HTMLElement
target.setPointerCapture(e.pointerId)
@@ -2021,6 +2053,7 @@ export function PlayingPhaseDrag() {
isSpectating={isSpectating}
isCorrect={isCorrect}
isCorrectPosition={isInCorrectPrefixOrSuffix}
isLocked={isInCorrectPrefixOrSuffix}
draggedByPlayerId={draggedByPlayerId}
localPlayerId={localPlayerId}
players={players}