fix(card-sorting): prevent infinite loop when all cards are correct
Root cause: When all cards are in the correct order, both prefix AND suffix contain all cards. The auto-arrange logic tried to position all 15 cards in a row, resulting in positions > 100% (e.g., card 14 at 229%). Server rejected these invalid positions, causing the effect to loop infinitely. Fix: 1. Detect when prefix/suffix overlap (all cards correct) and skip auto-arrange entirely 2. Add viewport boundary checks - only arrange cards if they fit within 0-100% range 3. Add detailed logging to show when/why auto-arrange is skipped The logs revealed: - "Move rejected: x must be between 0 and 100" errors - Loop count hitting 230+ iterations - All 15 cards being marked as both prefix AND suffix - Position calculations exceeding 229% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1264,6 +1264,13 @@ export function PlayingPhaseDrag() {
|
||||
suffixCards.unshift(inferredSequence[i])
|
||||
}
|
||||
|
||||
// Check if prefix and suffix overlap (all cards are correct)
|
||||
// In this case, prefix and suffix cards are the same, so skip auto-arrange
|
||||
const prefixAndSuffixOverlap =
|
||||
prefixCards.length > 0 &&
|
||||
suffixCards.length > 0 &&
|
||||
prefixCards[prefixCards.length - 1].id === suffixCards[0].id
|
||||
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Loop count:', autoArrangeLoopCount.current)
|
||||
console.log(
|
||||
@@ -1273,9 +1280,18 @@ export function PlayingPhaseDrag() {
|
||||
suffixCards.length
|
||||
)
|
||||
console.log('[AutoArrange] Total cards:', inferredSequence.length)
|
||||
console.log('[AutoArrange] Prefix/suffix overlap:', prefixAndSuffixOverlap)
|
||||
lastLogTime.current = now
|
||||
}
|
||||
|
||||
// Skip auto-arrange if all cards are correct (prefix and suffix are the same cards)
|
||||
if (prefixAndSuffixOverlap) {
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Skipping auto-arrange: all cards are correct')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Tolerance for position comparison (0.1%)
|
||||
const TOLERANCE = 0.1
|
||||
|
||||
@@ -1292,47 +1308,68 @@ export function PlayingPhaseDrag() {
|
||||
}
|
||||
|
||||
// Arrange prefix cards at top-left, side by side
|
||||
prefixCards.forEach((card, index) => {
|
||||
const x = 5 + index * (CARD_WIDTH_PCT + SPACING) // Start at 5% margin
|
||||
const y = 5 // Top margin
|
||||
const rotation = 0 // No rotation for organized cards
|
||||
const zIndex = 1000 + index // Higher z-index so they're on top
|
||||
// But only if they fit within the viewport
|
||||
const lastPrefixX = 5 + (prefixCards.length - 1) * (CARD_WIDTH_PCT + SPACING) + CARD_WIDTH_PCT
|
||||
if (lastPrefixX <= 100) {
|
||||
prefixCards.forEach((card, index) => {
|
||||
const x = 5 + index * (CARD_WIDTH_PCT + SPACING) // Start at 5% margin
|
||||
const y = 5 // Top margin
|
||||
const rotation = 0 // No rotation for organized cards
|
||||
const zIndex = 1000 + index // Higher z-index so they're on top
|
||||
|
||||
const currentState = newStates.get(card.id)
|
||||
if (currentState && positionsDiffer(currentState, { x, y, rotation })) {
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Prefix card needs update:', {
|
||||
cardId: card.id.slice(0, 8),
|
||||
current: currentState,
|
||||
target: { x, y, rotation },
|
||||
})
|
||||
const currentState = newStates.get(card.id)
|
||||
if (currentState && positionsDiffer(currentState, { x, y, rotation })) {
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Prefix card needs update:', {
|
||||
cardId: card.id.slice(0, 8),
|
||||
current: currentState,
|
||||
target: { x, y, rotation },
|
||||
})
|
||||
}
|
||||
newStates.set(card.id, { x, y, rotation, zIndex })
|
||||
hasChanges = true
|
||||
}
|
||||
newStates.set(card.id, { x, y, rotation, zIndex })
|
||||
hasChanges = true
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (shouldLog) {
|
||||
console.log(
|
||||
'[AutoArrange] Skipping prefix arrange: would exceed viewport (',
|
||||
lastPrefixX,
|
||||
'% > 100%)'
|
||||
)
|
||||
}
|
||||
|
||||
// Arrange suffix cards at bottom-right, side by side (right to left)
|
||||
suffixCards.forEach((card, index) => {
|
||||
const fromRight = suffixCards.length - 1 - index
|
||||
const x = 100 - CARD_WIDTH_PCT - 5 - fromRight * (CARD_WIDTH_PCT + SPACING) // From right edge
|
||||
const y = 100 - CARD_HEIGHT_PCT - 5 // Bottom margin
|
||||
const rotation = 0 // No rotation for organized cards
|
||||
const zIndex = 1000 + index // Higher z-index so they're on top
|
||||
// But only if they fit within the viewport
|
||||
const firstSuffixX =
|
||||
100 - CARD_WIDTH_PCT - 5 - (suffixCards.length - 1) * (CARD_WIDTH_PCT + SPACING)
|
||||
if (firstSuffixX >= 0) {
|
||||
suffixCards.forEach((card, index) => {
|
||||
const fromRight = suffixCards.length - 1 - index
|
||||
const x = 100 - CARD_WIDTH_PCT - 5 - fromRight * (CARD_WIDTH_PCT + SPACING) // From right edge
|
||||
const y = 100 - CARD_HEIGHT_PCT - 5 // Bottom margin
|
||||
const rotation = 0 // No rotation for organized cards
|
||||
const zIndex = 1000 + index // Higher z-index so they're on top
|
||||
|
||||
const currentState = newStates.get(card.id)
|
||||
if (currentState && positionsDiffer(currentState, { x, y, rotation })) {
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Suffix card needs update:', {
|
||||
cardId: card.id.slice(0, 8),
|
||||
current: currentState,
|
||||
target: { x, y, rotation },
|
||||
})
|
||||
const currentState = newStates.get(card.id)
|
||||
if (currentState && positionsDiffer(currentState, { x, y, rotation })) {
|
||||
if (shouldLog) {
|
||||
console.log('[AutoArrange] Suffix card needs update:', {
|
||||
cardId: card.id.slice(0, 8),
|
||||
current: currentState,
|
||||
target: { x, y, rotation },
|
||||
})
|
||||
}
|
||||
newStates.set(card.id, { x, y, rotation, zIndex })
|
||||
hasChanges = true
|
||||
}
|
||||
newStates.set(card.id, { x, y, rotation, zIndex })
|
||||
hasChanges = true
|
||||
}
|
||||
})
|
||||
})
|
||||
} else if (shouldLog) {
|
||||
console.log(
|
||||
'[AutoArrange] Skipping suffix arrange: would exceed viewport (',
|
||||
firstSuffixX,
|
||||
'% < 0%)'
|
||||
)
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
if (shouldLog) {
|
||||
|
||||
Reference in New Issue
Block a user