Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
297927401c | ||
|
|
b45139b588 | ||
|
|
a57ebdf142 | ||
|
|
98a3a2573d |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **arcade:** delete old session when room game changes ([98a3a25](https://github.com/antialias/soroban-abacus-flashcards/commit/98a3a2573db51899c41ba02796895d676c4e16ef))
|
||||
|
||||
## [3.14.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.1...v3.14.2) (2025-10-15)
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,13 @@ export async function PATCH(req: NextRequest, context: RouteContext) {
|
||||
updateData.gameConfig = body.gameConfig
|
||||
}
|
||||
|
||||
// If game is being changed (or cleared), delete the existing arcade session
|
||||
// This ensures a fresh session will be created with the new game settings
|
||||
if (body.gameName !== undefined) {
|
||||
console.log(`[Settings API] Deleting existing arcade session for room ${roomId}`)
|
||||
await db.delete(schema.arcadeSessions).where(eq(schema.arcadeSessions.roomId, roomId))
|
||||
}
|
||||
|
||||
// Update room settings
|
||||
const [updatedRoom] = await db
|
||||
.update(schema.arcadeRooms)
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -87,13 +87,32 @@ export default function RoomPage() {
|
||||
// Show game selection if no game is set
|
||||
if (!roomData.gameName) {
|
||||
const handleGameSelect = (gameType: GameType) => {
|
||||
console.log('[RoomPage] handleGameSelect called with gameType:', gameType)
|
||||
|
||||
const gameConfig = GAMES_CONFIG[gameType]
|
||||
console.log('[RoomPage] Game config:', {
|
||||
name: gameConfig.name,
|
||||
available: gameConfig.available,
|
||||
})
|
||||
|
||||
if (gameConfig.available === false) {
|
||||
console.log('[RoomPage] Game not available, blocking selection')
|
||||
return // Don't allow selecting unavailable games
|
||||
}
|
||||
|
||||
// Map GameType to internal game name
|
||||
const internalGameName = GAME_TYPE_TO_NAME[gameType]
|
||||
console.log('[RoomPage] Mapping:', {
|
||||
gameType,
|
||||
internalGameName,
|
||||
mappingExists: !!internalGameName,
|
||||
})
|
||||
|
||||
console.log('[RoomPage] Calling setRoomGame with:', {
|
||||
roomId: roomData.id,
|
||||
gameName: internalGameName,
|
||||
gameConfig: {},
|
||||
})
|
||||
|
||||
setRoomGame({
|
||||
roomId: roomData.id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user