Compare commits

...

5 Commits

Author SHA1 Message Date
semantic-release-bot
5b4cb7d35a chore(release): 3.17.5 [skip ci]
## [3.17.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.4...v3.17.5) (2025-10-15)

### Bug Fixes

* **arcade:** implement settings persistence for matching game ([08fe432](08fe4326a6))
2025-10-15 16:04:05 +00:00
Thomas Hallock
eacbafb1ea debug(arcade): add detailed logging for settings persistence
Add comprehensive console logging to trace the settings persistence flow:
- Load settings from database on initialization
- Save settings to database when changed (gameType, difficulty, turnTimer)
- API calls to server with full request/response logging

This will help diagnose if settings are being persisted correctly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 11:03:07 -05:00
Thomas Hallock
08fe4326a6 fix(arcade): implement settings persistence for matching game
- Add useUpdateGameConfig hook and database saves to RoomMemoryPairsProvider
- Load saved settings from gameConfig['matching'] on init
- Save gameType, difficulty, and turnTimer changes to database
- Apply lint fixes: use dot notation instead of bracket notation

Matching game now persists settings when switching between games,
matching the behavior of memory-quiz.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 11:03:07 -05:00
semantic-release-bot
fabb33252c chore(release): 3.17.4 [skip ci]
## [3.17.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.3...v3.17.4) (2025-10-15)

### Bug Fixes

* **matching:** add settings persistence to matching game ([00dcb87](00dcb872b7))
2025-10-15 15:16:36 +00:00
Thomas Hallock
00dcb872b7 fix(matching): add settings persistence to matching game
The matching game was not saving settings to the database at all.
When you changed gameType, difficulty, or turnTimer, it only sent
a move to the arcade session but never saved to the database.

This adds the same persistence logic that memory-quiz uses:

**On Load:**
- Reads settings from gameConfig['matching'] in the database
- Merges with initialState
- Passes to useArcadeSession

**On Change:**
- Sends SET_CONFIG move (for real-time sync)
- Saves to gameConfig['matching'] via updateGameConfig
- Updates TanStack Query cache

Changes:
- Import useUpdateGameConfig hook
- Add mergedInitialState with settings from database
- Save settings in setGameType, setDifficulty, setTurnTimer

Now settings persist when switching between games!

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 10:15:42 -05:00
4 changed files with 145 additions and 8 deletions

View File

@@ -1,3 +1,17 @@
## [3.17.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.4...v3.17.5) (2025-10-15)
### Bug Fixes
* **arcade:** implement settings persistence for matching game ([08fe432](https://github.com/antialias/soroban-abacus-flashcards/commit/08fe4326a6a7c484b9058a241f4ff79b3fb5125f))
## [3.17.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.3...v3.17.4) (2025-10-15)
### Bug Fixes
* **matching:** add settings persistence to matching game ([00dcb87](https://github.com/antialias/soroban-abacus-flashcards/commit/00dcb872b7e70bdb7de301b56fe42195e6ee923f))
## [3.17.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.2...v3.17.3) (2025-10-15)

View File

@@ -2,7 +2,7 @@
import { type ReactNode, useCallback, useEffect, useMemo } from 'react'
import { useArcadeSession } from '@/hooks/useArcadeSession'
import { useRoomData } from '@/hooks/useRoomData'
import { useRoomData, useUpdateGameConfig } from '@/hooks/useRoomData'
import { useViewerId } from '@/hooks/useViewerId'
import {
buildPlayerMetadata as buildPlayerMetadataUtil,
@@ -240,6 +240,7 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
const { data: viewerId } = useViewerId()
const { roomData } = useRoomData() // Fetch room data for room-based play
const { activePlayerCount, activePlayers: activePlayerIds, players } = useGameMode()
const { mutate: updateGameConfig } = useUpdateGameConfig()
// Get active player IDs directly as strings (UUIDs)
const activePlayers = Array.from(activePlayerIds)
@@ -247,8 +248,44 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
// Derive game mode from active player count
const gameMode = activePlayerCount > 1 ? 'multiplayer' : 'single'
// NO LOCAL STATE - Configuration lives in session state
// Changes are sent as moves and synchronized across all room members
// Merge saved game config from room with initialState
// Settings are scoped by game name to preserve settings when switching games
const mergedInitialState = useMemo(() => {
const gameConfig = roomData?.gameConfig as Record<string, any> | null | undefined
console.log('[RoomMemoryPairsProvider] Loading settings from database:', {
gameConfig,
roomId: roomData?.id,
})
if (!gameConfig) {
console.log('[RoomMemoryPairsProvider] No gameConfig, using initialState')
return initialState
}
// Get settings for this specific game (matching)
const savedConfig = gameConfig.matching as Record<string, any> | null | undefined
console.log('[RoomMemoryPairsProvider] Saved config for matching:', savedConfig)
if (!savedConfig) {
console.log('[RoomMemoryPairsProvider] No saved config for matching, using initialState')
return initialState
}
const merged = {
...initialState,
// Restore settings from saved config
gameType: savedConfig.gameType ?? initialState.gameType,
difficulty: savedConfig.difficulty ?? initialState.difficulty,
turnTimer: savedConfig.turnTimer ?? initialState.turnTimer,
}
console.log('[RoomMemoryPairsProvider] Merged state:', {
gameType: merged.gameType,
difficulty: merged.difficulty,
turnTimer: merged.turnTimer,
})
return merged
}, [roomData?.gameConfig])
// Arcade session integration WITH room sync
const {
@@ -259,7 +296,7 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
} = useArcadeSession<MemoryPairsState>({
userId: viewerId || '',
roomId: roomData?.id, // CRITICAL: Pass roomId for network sync across room members
initialState,
initialState: mergedInitialState,
applyMove: applyMoveOptimistically,
})
@@ -498,6 +535,8 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
const setGameType = useCallback(
(gameType: typeof state.gameType) => {
console.log('[RoomMemoryPairsProvider] setGameType called:', gameType)
// Use first active player as playerId, or empty string if none
const playerId = activePlayers[0] || ''
sendMove({
@@ -506,12 +545,38 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
userId: viewerId || '',
data: { field: 'gameType', value: gameType },
})
// Save setting to room's gameConfig for persistence
if (roomData?.id) {
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
const currentMatchingConfig = (currentGameConfig.matching as Record<string, any>) || {}
const updatedConfig = {
...currentGameConfig,
matching: {
...currentMatchingConfig,
gameType,
},
}
console.log('[RoomMemoryPairsProvider] Saving gameType to database:', {
roomId: roomData.id,
updatedConfig,
})
updateGameConfig({
roomId: roomData.id,
gameConfig: updatedConfig,
})
} else {
console.warn('[RoomMemoryPairsProvider] Cannot save gameType - no roomData.id')
}
},
[activePlayers, sendMove, viewerId]
[activePlayers, sendMove, viewerId, roomData?.id, roomData?.gameConfig, updateGameConfig]
)
const setDifficulty = useCallback(
(difficulty: typeof state.difficulty) => {
console.log('[RoomMemoryPairsProvider] setDifficulty called:', difficulty)
const playerId = activePlayers[0] || ''
sendMove({
type: 'SET_CONFIG',
@@ -519,12 +584,38 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
userId: viewerId || '',
data: { field: 'difficulty', value: difficulty },
})
// Save setting to room's gameConfig for persistence
if (roomData?.id) {
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
const currentMatchingConfig = (currentGameConfig.matching as Record<string, any>) || {}
const updatedConfig = {
...currentGameConfig,
matching: {
...currentMatchingConfig,
difficulty,
},
}
console.log('[RoomMemoryPairsProvider] Saving difficulty to database:', {
roomId: roomData.id,
updatedConfig,
})
updateGameConfig({
roomId: roomData.id,
gameConfig: updatedConfig,
})
} else {
console.warn('[RoomMemoryPairsProvider] Cannot save difficulty - no roomData.id')
}
},
[activePlayers, sendMove, viewerId]
[activePlayers, sendMove, viewerId, roomData?.id, roomData?.gameConfig, updateGameConfig]
)
const setTurnTimer = useCallback(
(turnTimer: typeof state.turnTimer) => {
console.log('[RoomMemoryPairsProvider] setTurnTimer called:', turnTimer)
const playerId = activePlayers[0] || ''
sendMove({
type: 'SET_CONFIG',
@@ -532,8 +623,32 @@ export function RoomMemoryPairsProvider({ children }: { children: ReactNode }) {
userId: viewerId || '',
data: { field: 'turnTimer', value: turnTimer },
})
// Save setting to room's gameConfig for persistence
if (roomData?.id) {
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
const currentMatchingConfig = (currentGameConfig.matching as Record<string, any>) || {}
const updatedConfig = {
...currentGameConfig,
matching: {
...currentMatchingConfig,
turnTimer,
},
}
console.log('[RoomMemoryPairsProvider] Saving turnTimer to database:', {
roomId: roomData.id,
updatedConfig,
})
updateGameConfig({
roomId: roomData.id,
gameConfig: updatedConfig,
})
} else {
console.warn('[RoomMemoryPairsProvider] Cannot save turnTimer - no roomData.id')
}
},
[activePlayers, sendMove, viewerId]
[activePlayers, sendMove, viewerId, roomData?.id, roomData?.gameConfig, updateGameConfig]
)
const goToSetup = useCallback(() => {

View File

@@ -672,6 +672,11 @@ async function updateGameConfigApi(params: {
roomId: string
gameConfig: Record<string, unknown>
}): Promise<void> {
console.log('[updateGameConfigApi] Sending PATCH to server:', {
url: `/api/arcade/rooms/${params.roomId}/settings`,
gameConfig: params.gameConfig,
})
const response = await fetch(`/api/arcade/rooms/${params.roomId}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
@@ -682,8 +687,11 @@ async function updateGameConfigApi(params: {
if (!response.ok) {
const errorData = await response.json()
console.error('[updateGameConfigApi] Server error:', errorData)
throw new Error(errorData.error || 'Failed to update game config')
}
console.log('[updateGameConfigApi] Server responded OK')
}
/**

View File

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