Compare commits

...

2 Commits

Author SHA1 Message Date
semantic-release-bot
297927401c chore(release): 3.14.4 [skip ci]
## [3.14.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.3...v3.14.4) (2025-10-15)

### Bug Fixes

* **memory-quiz:** prevent input lag during rapid typing in room mode ([b45139b](b45139b588))
2025-10-15 00:31:10 +00:00
Thomas Hallock
b45139b588 fix(memory-quiz): prevent input lag during rapid typing in room mode
When typing rapidly in room mode, users had to type each digit
8+ times before it registered. This was caused by reading stale
state.currentInput values during rapid keypresses before React
could re-render with the optimistically updated state.

Solution: Use a ref to track the current input value and update
it immediately when keys are pressed, before waiting for the
network round-trip and React re-render.

Changes:
- Add currentInputRef to track input value immediately
- Update ref in useEffect to stay in sync with state
- Use ref instead of state.currentInput in keyboard handlers
- Clear ref immediately when accepting/rejecting numbers

This fixes the async network validation issue where local state
updates were too slow for rapid user input.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 19:30:12 -05:00
3 changed files with 28 additions and 7 deletions

View File

@@ -1,3 +1,10 @@
## [3.14.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.3...v3.14.4) (2025-10-15)
### Bug Fixes
* **memory-quiz:** prevent input lag during rapid typing in room mode ([b45139b](https://github.com/antialias/soroban-abacus-flashcards/commit/b45139b588d0ab6df4d6c1003c1b65b634e2b041))
## [3.14.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.2...v3.14.3) (2025-10-15)

View File

@@ -10,6 +10,15 @@ export function InputPhase() {
'neutral'
)
// Use a ref to track the current input to avoid stale reads during rapid typing
// This prevents the "type 8 times" issue where state.currentInput hasn't updated yet
const currentInputRef = useRef(state.currentInput)
// Keep ref in sync with state
useEffect(() => {
currentInputRef.current = state.currentInput
}, [state.currentInput])
// Use keyboard state from parent state instead of local state
const { hasPhysicalKeyboard, testingMode, showOnScreenKeyboard } = state
@@ -107,6 +116,7 @@ export function InputPhase() {
const acceptCorrectNumber = useCallback(
(number: number) => {
acceptNumber?.(number)
currentInputRef.current = '' // Clear ref immediately
setInput?.('')
setDisplayFeedback('correct')
@@ -121,7 +131,7 @@ export function InputPhase() {
)
const handleIncorrectGuess = useCallback(() => {
const wrongNumber = parseInt(state.currentInput, 10)
const wrongNumber = parseInt(currentInputRef.current, 10)
if (!Number.isNaN(wrongNumber)) {
dispatch({ type: 'ADD_WRONG_GUESS_ANIMATION', number: wrongNumber })
// Clear wrong guess animations after explosion
@@ -131,6 +141,7 @@ export function InputPhase() {
}
rejectNumber?.()
currentInputRef.current = '' // Clear ref immediately
setInput?.('')
setDisplayFeedback('incorrect')
@@ -140,7 +151,7 @@ export function InputPhase() {
if (state.guessesRemaining - 1 === 0) {
setTimeout(() => showResults?.(), 1000)
}
}, [dispatch, rejectNumber, setInput, showResults, state.guessesRemaining, state.currentInput])
}, [dispatch, rejectNumber, setInput, showResults, state.guessesRemaining])
// Simple keyboard event handlers that will be defined after callbacks
const handleKeyboardInput = useCallback(
@@ -150,7 +161,9 @@ export function InputPhase() {
// Only handle if input phase is active and guesses remain
if (state.guessesRemaining === 0) return
const newInput = state.currentInput + key
// Use ref for immediate value, update ref right away to prevent stale reads
const newInput = currentInputRef.current + key
currentInputRef.current = newInput // Update ref immediately for next keypress
setInput?.(newInput)
// Clear any existing timeout
@@ -202,8 +215,9 @@ export function InputPhase() {
)
const handleKeyboardBackspace = useCallback(() => {
if (state.currentInput.length > 0) {
const newInput = state.currentInput.slice(0, -1)
if (currentInputRef.current.length > 0) {
const newInput = currentInputRef.current.slice(0, -1)
currentInputRef.current = newInput // Update ref immediately
setInput?.(newInput)
// Clear any existing timeout
@@ -214,7 +228,7 @@ export function InputPhase() {
setDisplayFeedback('neutral')
}
}, [state.currentInput, state.prefixAcceptanceTimeout, dispatch, setInput])
}, [state.prefixAcceptanceTimeout, dispatch, setInput])
// Set up global keyboard listeners
useEffect(() => {

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "3.14.3",
"version": "3.14.4",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [