Compare commits

...

6 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
semantic-release-bot
a57ebdf142 chore(release): 3.14.3 [skip ci]
## [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](98a3a2573d))
2025-10-15 00:17:53 +00:00
Thomas Hallock
98a3a2573d fix(arcade): delete old session when room game changes
When changing a room's game via the settings API, the old arcade
session was persisting with the previous game's state. This caused
users to still see the old game after selecting a new one.

Changes:
- Delete existing arcade session when gameName is updated in room settings
- Add debug logging to room page game selection handler
- Ensure fresh session is created with new game settings

This fixes the issue where clicking Memory Lightning would not
properly switch the game from Battle Arena.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 19:17:02 -05:00
semantic-release-bot
0fd680396c chore(release): 3.14.2 [skip ci]
## [3.14.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.1...v3.14.2) (2025-10-15)

### Bug Fixes

* **room:** update GAME_TYPE_TO_NAME mapping for memory-quiz ([4afa171](4afa171af2))
2025-10-15 00:06:54 +00:00
Thomas Hallock
4afa171af2 fix(room): update GAME_TYPE_TO_NAME mapping for memory-quiz
The GAMES_CONFIG was changed from 'memory-lightning' to 'memory-quiz'
but the GAME_TYPE_TO_NAME mapping in room/page.tsx still used the old key.

This caused the handleGameSelect function to fail silently when users
clicked on Memory Lightning in the "Change Game" screen, as it couldn't
find the mapping for 'memory-quiz'.

Also added debug logging to GameCard component to help diagnose issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 19:06:00 -05:00
7 changed files with 84 additions and 9 deletions

View File

@@ -1,3 +1,24 @@
## [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)
### Bug Fixes
* **room:** update GAME_TYPE_TO_NAME mapping for memory-quiz ([4afa171](https://github.com/antialias/soroban-abacus-flashcards/commit/4afa171af212902120599b3d68f58cfbdf7820b0))
## [3.14.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.14.0...v3.14.1) (2025-10-14)

View File

@@ -68,7 +68,8 @@
"Bash(python3:*)",
"Bash(git reset:*)",
"Bash(lsof:*)",
"Bash(killall:*)"
"Bash(killall:*)",
"Bash(echo:*)"
],
"deny": [],
"ask": []

View File

@@ -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)

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

@@ -14,7 +14,7 @@ import { css } from '../../../../styled-system/css'
// Map GameType keys to internal game names
const GAME_TYPE_TO_NAME: Record<GameType, string> = {
'battle-arena': 'matching',
'memory-lightning': 'memory-quiz',
'memory-quiz': 'memory-quiz',
'complement-race': 'complement-race',
'master-organizer': 'master-organizer',
}
@@ -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,

View File

@@ -23,10 +23,23 @@ export function GameCard({ gameType, config, variant = 'detailed', className }:
}
const handleGameClick = () => {
console.log(`[GameCard] Clicked on ${config.name}:`, {
activePlayerCount,
maxPlayers: config.maxPlayers,
isGameAvailable: isGameAvailable(),
configAvailable: config.available,
willNavigate: isGameAvailable() && config.available !== false,
url: config.url,
})
if (isGameAvailable() && config.available !== false) {
console.log('🔄 GameCard: Navigating with Next.js router (no page reload)')
// Use Next.js router for client-side navigation - this preserves fullscreen!
router.push(config.url)
} else {
console.warn('❌ GameCard: Navigation blocked', {
reason: !isGameAvailable() ? 'Player count mismatch' : 'Game not available',
})
}
}

View File

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