perf: debounce value change events during rapid gesture interactions

- Implement 150ms debounced value change handler to prevent tutorial
  system bottlenecks during rapid bead dragging
- Allow abacus to render smoothly by deferring expensive tutorial
  operations (validation, state updates, event logging) until
  gestures settle
- Add proper cleanup and ref synchronization for debounced values
- This should eliminate UI lag when dragging beads up and down rapidly

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-21 17:40:16 -05:00
parent 8e81d25f06
commit 82e15a1cd9

View File

@@ -243,7 +243,11 @@ export function TutorialPlayer({
}
}, [events, notifyEvent])
// Abacus event handlers
// Debounced value change handling for smooth gesture performance
const valueChangeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const lastValueRef = useRef<number>(currentValue)
const pendingValueRef = useRef<number | null>(null)
const handleValueChange = useCallback((newValue: number) => {
// Ignore programmatic changes to prevent feedback loops
if (isProgrammaticChange.current) {
@@ -251,13 +255,43 @@ export function TutorialPlayer({
return
}
dispatch({
type: 'USER_VALUE_CHANGE',
oldValue: currentValue,
newValue,
stepId: currentStep.id
})
}, [currentValue, currentStep])
// Store the pending value for immediate abacus updates
pendingValueRef.current = newValue
// Clear any existing timeout
if (valueChangeTimeoutRef.current) {
clearTimeout(valueChangeTimeoutRef.current)
}
// Debounce the tutorial system notification
valueChangeTimeoutRef.current = setTimeout(() => {
const finalValue = pendingValueRef.current
if (finalValue !== null && finalValue !== lastValueRef.current) {
dispatch({
type: 'USER_VALUE_CHANGE',
oldValue: lastValueRef.current,
newValue: finalValue,
stepId: currentStep.id
})
lastValueRef.current = finalValue
}
pendingValueRef.current = null
}, 150) // 150ms debounce - gestures settle quickly
}, [currentStep])
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (valueChangeTimeoutRef.current) {
clearTimeout(valueChangeTimeoutRef.current)
}
}
}, [])
// Keep lastValueRef in sync with currentValue changes from external sources
useEffect(() => {
lastValueRef.current = currentValue
}, [currentValue])
const handleBeadClick = useCallback((beadInfo: any) => {
dispatch({