fix(practice): handle loading phase in activeProblem effect
The useInteractionPhase effect now handles two cases: 1. activeProblem.key changes (redo mode, navigation, session advances) 2. Phase becomes 'loading' (after incorrect answer or part transition) Previously, only key changes triggered problem loading, which caused "Loading next problem..." to persist after incorrect answers since clearToLoading() sets phase to 'loading' without changing the key. Also adds debug logging for part transitions and game breaks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4733149497
commit
cecd1e93e2
|
|
@ -582,6 +582,7 @@ export function PracticeClient({ studentId, player, initialSession }: PracticeCl
|
|||
// Handle part transition complete - called when transition screen finishes
|
||||
// This is where we trigger game break (after "put away abacus" message is shown)
|
||||
const handlePartTransitionComplete = useCallback(() => {
|
||||
console.log('[PracticeClient] handlePartTransitionComplete called, pendingGameBreak:', pendingGameBreak)
|
||||
// First, broadcast to observers
|
||||
sendPartTransitionComplete()
|
||||
|
||||
|
|
@ -591,6 +592,8 @@ export function PracticeClient({ studentId, player, initialSession }: PracticeCl
|
|||
setGameBreakStartTime(Date.now())
|
||||
setShowGameBreak(true)
|
||||
setPendingGameBreak(false)
|
||||
} else {
|
||||
console.log('[PracticeClient] No pending game break, continuing to practice')
|
||||
}
|
||||
}, [sendPartTransitionComplete, pendingGameBreak])
|
||||
|
||||
|
|
|
|||
|
|
@ -1361,11 +1361,23 @@ export function ActiveSession({
|
|||
useEffect(() => {
|
||||
const prevIndex = prevPartIndexRef.current
|
||||
|
||||
console.log('[ActiveSession] Part transition effect:', {
|
||||
prevIndex,
|
||||
currentPartIndex,
|
||||
partsLength: parts.length,
|
||||
willTriggerTransition: currentPartIndex !== prevIndex && currentPartIndex < parts.length,
|
||||
})
|
||||
|
||||
// If part index changed and we have a valid next part
|
||||
if (currentPartIndex !== prevIndex && currentPartIndex < parts.length) {
|
||||
const prevPart = prevIndex < parts.length ? parts[prevIndex] : null
|
||||
const nextPart = parts[currentPartIndex]
|
||||
|
||||
console.log('[ActiveSession] Triggering part transition screen:', {
|
||||
previousPartType: prevPart?.type ?? null,
|
||||
nextPartType: nextPart.type,
|
||||
})
|
||||
|
||||
// Trigger transition screen
|
||||
const startTime = Date.now()
|
||||
setTransitionData({
|
||||
|
|
@ -1385,6 +1397,7 @@ export function ActiveSession({
|
|||
|
||||
// Handle transition screen completion (countdown finished or user skipped)
|
||||
const handleTransitionComplete = useCallback(() => {
|
||||
console.log('[ActiveSession] Part transition complete, calling onPartTransitionComplete')
|
||||
setIsInPartTransition(false)
|
||||
setTransitionData(null)
|
||||
// Broadcast transition complete to observers
|
||||
|
|
@ -1591,6 +1604,15 @@ export function ActiveSession({
|
|||
const nextSlotIndex = currentSlotIndex + 1
|
||||
const nextSlot = currentPart?.slots[nextSlotIndex]
|
||||
|
||||
console.log('[ActiveSession] Post-feedback timeout fired:', {
|
||||
isCorrect,
|
||||
currentSlotIndex,
|
||||
nextSlotIndex,
|
||||
hasNextSlot: !!nextSlot,
|
||||
hasCurrentPart: !!currentPart,
|
||||
willTransition: !!(nextSlot && currentPart && isCorrect),
|
||||
})
|
||||
|
||||
if (nextSlot && currentPart && isCorrect) {
|
||||
// Has next problem - animate transition
|
||||
if (!nextSlot.problem) {
|
||||
|
|
@ -1602,9 +1624,11 @@ export function ActiveSession({
|
|||
// Mark that we need to apply centering offset in useLayoutEffect
|
||||
needsCenteringOffsetRef.current = true
|
||||
|
||||
console.log('[ActiveSession] Starting transition to next problem')
|
||||
startTransition(nextSlot.problem, nextSlotIndex)
|
||||
} else {
|
||||
// End of part or incorrect - clear to loading
|
||||
console.log('[ActiveSession] Calling clearToLoading (end of part or incorrect)')
|
||||
clearToLoading()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -662,8 +662,9 @@ export function useInteractionPhase(
|
|||
// ==========================================================================
|
||||
// React to activeProblem changes (single source of truth)
|
||||
// ==========================================================================
|
||||
// This effect watches for activeProblem.key changes and loads the new problem.
|
||||
// This eliminates the need for synchronization effects in the parent component.
|
||||
// This effect handles two cases:
|
||||
// 1. activeProblem.key changes (redo mode, direct navigation, session plan advances)
|
||||
// 2. Phase becomes 'loading' (need to reload current problem after incorrect answer or part transition)
|
||||
const prevActiveProblemKeyRef = useRef<string | null>(null)
|
||||
const hasMountedRef = useRef(false)
|
||||
|
||||
|
|
@ -673,35 +674,78 @@ export function useInteractionPhase(
|
|||
hasMountedRef.current = true
|
||||
// Initialize the ref with current key
|
||||
prevActiveProblemKeyRef.current = activeProblem?.key ?? null
|
||||
console.log('[useInteractionPhase] Initial mount, key:', activeProblem?.key)
|
||||
return
|
||||
}
|
||||
|
||||
// If no active problem, nothing to do
|
||||
if (!activeProblem) {
|
||||
console.log('[useInteractionPhase] No active problem - cannot load. Current phase:', phase.phase)
|
||||
prevActiveProblemKeyRef.current = null
|
||||
return
|
||||
}
|
||||
|
||||
const prevKey = prevActiveProblemKeyRef.current
|
||||
const currentKey = activeProblem.key
|
||||
const keyChanged = prevKey !== currentKey
|
||||
|
||||
// Key hasn't changed - no action needed
|
||||
if (prevKey === currentKey) {
|
||||
console.log('[useInteractionPhase] Effect running:', {
|
||||
prevKey,
|
||||
currentKey,
|
||||
keyChanged,
|
||||
currentPhase: phase.phase,
|
||||
})
|
||||
|
||||
// Case 1: Phase is 'loading' - need to load the problem
|
||||
// This happens after incorrect answers or when returning from part transitions
|
||||
if (phase.phase === 'loading') {
|
||||
console.log('[useInteractionPhase] Phase is loading - loading problem:', {
|
||||
problemAnswer: activeProblem.problem.answer,
|
||||
slotIndex: activeProblem.slotIndex,
|
||||
partIndex: activeProblem.partIndex,
|
||||
key: activeProblem.key,
|
||||
})
|
||||
const newAttempt = createAttemptInput(
|
||||
activeProblem.problem,
|
||||
activeProblem.slotIndex,
|
||||
activeProblem.partIndex
|
||||
)
|
||||
setPhase({ phase: 'inputting', attempt: newAttempt })
|
||||
prevActiveProblemKeyRef.current = currentKey
|
||||
console.log('[useInteractionPhase] Successfully loaded problem, phase now inputting')
|
||||
return
|
||||
}
|
||||
|
||||
// Key changed - load the new problem immediately
|
||||
// Note: We don't check phase here because this is for redo mode / direct navigation
|
||||
// which should immediately switch problems (no animation)
|
||||
const newAttempt = createAttemptInput(
|
||||
activeProblem.problem,
|
||||
activeProblem.slotIndex,
|
||||
activeProblem.partIndex
|
||||
)
|
||||
setPhase({ phase: 'inputting', attempt: newAttempt })
|
||||
// Case 2: Key changed - handle redo mode or session advancement
|
||||
if (keyChanged) {
|
||||
console.log('[useInteractionPhase] Key changed:', { prevKey, currentKey })
|
||||
|
||||
prevActiveProblemKeyRef.current = currentKey
|
||||
}, [activeProblem])
|
||||
// CRITICAL: Don't interrupt normal progression flow
|
||||
// If we're in showingFeedback, submitting, or transitioning, the normal flow
|
||||
// (startTransition → completeTransition) handles advancing to the next problem.
|
||||
const isInProgressionFlow =
|
||||
phase.phase === 'showingFeedback' ||
|
||||
phase.phase === 'submitting' ||
|
||||
phase.phase === 'transitioning'
|
||||
|
||||
if (isInProgressionFlow) {
|
||||
console.log('[useInteractionPhase] Key changed but in progression flow, deferring')
|
||||
// Still update the ref so we track the change
|
||||
prevActiveProblemKeyRef.current = currentKey
|
||||
return
|
||||
}
|
||||
|
||||
// Key changed and we're not in progression flow - load the new problem immediately
|
||||
console.log('[useInteractionPhase] Loading new problem from key change')
|
||||
const newAttempt = createAttemptInput(
|
||||
activeProblem.problem,
|
||||
activeProblem.slotIndex,
|
||||
activeProblem.partIndex
|
||||
)
|
||||
setPhase({ phase: 'inputting', attempt: newAttempt })
|
||||
prevActiveProblemKeyRef.current = currentKey
|
||||
}
|
||||
}, [activeProblem, phase.phase])
|
||||
|
||||
const handleDigit = useCallback(
|
||||
(digit: string) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue