Compare commits

..

7 Commits

Author SHA1 Message Date
semantic-release-bot
73c54a7ebc chore(release): 3.17.2 [skip ci]
## [3.17.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.1...v3.17.2) (2025-10-15)

### Bug Fixes

* **room-data:** update query cache when gameConfig changes ([7cea297](7cea297095))
2025-10-15 15:01:02 +00:00
Thomas Hallock
7cea297095 fix(room-data): update query cache when gameConfig changes
The issue was that useUpdateGameConfig was saving settings to the database
but not updating the TanStack Query cache. This meant that when components
re-mounted (e.g., when switching games), they would read stale data from
the cache instead of the newly saved settings.

Changes:
- Added onSuccess callback to useUpdateGameConfig to update the cache
- Added gameConfig field to RoomData interface
- Updated all API functions to include gameConfig in returned data:
  - fetchCurrentRoom
  - createRoomApi
  - joinRoomApi
  - getRoomByCodeApi

Now when settings are saved, the cache is immediately updated, so switching
games and returning shows the correct saved settings.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 10:00:05 -05:00
semantic-release-bot
019d36a0ab chore(release): 3.17.1 [skip ci]
## [3.17.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.0...v3.17.1) (2025-10-15)

### Bug Fixes

* **arcade-rooms:** navigate to invite link after room creation ([1922b21](1922b2122b))
* **memory-quiz:** scope game settings by game name for proper persistence ([3dfe54f](3dfe54f1cb))
2025-10-15 14:51:46 +00:00
Thomas Hallock
1922b2122b fix(arcade-rooms): navigate to invite link after room creation
Previously, when creating a new room, users were navigated to
/arcade-rooms/{roomId}, which is the direct room route.

Now users are navigated to /join/{code}, which is the invite link
format. This provides a better user experience as it follows the
same flow as joining via an invite link.

Changes:
- Changed router.push from /arcade-rooms/{id} to /join/{code}

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 09:50:53 -05:00
Thomas Hallock
3dfe54f1cb fix(memory-quiz): scope game settings by game name for proper persistence
Previously, settings were stored at the root of gameConfig, causing each
game to overwrite the other's settings when switching between games.

Now settings are stored under gameConfig['memory-quiz'], allowing each
game to maintain its own settings independently. When you switch from
memory-quiz to another game and back, the memory-quiz settings are
preserved exactly as you left them.

Changes:
- Load settings from gameConfig['memory-quiz'] instead of root gameConfig
- Save settings to gameConfig['memory-quiz'] to avoid overwriting other games
- Added comments explaining the scoping strategy

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 09:50:13 -05:00
semantic-release-bot
5f04a3b622 chore(release): 3.17.0 [skip ci]
## [3.17.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.16.0...v3.17.0) (2025-10-15)

### Features

* **memory-quiz:** persist game settings per-game across sessions ([05a8e0a](05a8e0a842))
2025-10-15 14:46:51 +00:00
Thomas Hallock
05a8e0a842 feat(memory-quiz): persist game settings per-game across sessions
Implement per-game settings persistence so that when users switch between
games and come back, their settings are restored. Settings are saved to
the room's gameConfig field in the database.

Changes:
- Add useUpdateGameConfig hook to save settings to room
- Load settings from roomData.gameConfig on provider initialization
- Merge saved config with initialState using useMemo
- Save settings to database when setConfig is called
- Settings persist across:
  - Game switches (memory-quiz -> matching -> memory-quiz)
  - Page refreshes
  - New arcade sessions

Settings saved: selectedCount, displayTime, selectedDifficulty, playMode

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-15 09:45:56 -05:00
5 changed files with 121 additions and 6 deletions

View File

@@ -1,3 +1,25 @@
## [3.17.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.1...v3.17.2) (2025-10-15)
### Bug Fixes
* **room-data:** update query cache when gameConfig changes ([7cea297](https://github.com/antialias/soroban-abacus-flashcards/commit/7cea297095b78d74f5b77ca83489ec1be684a486))
## [3.17.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.17.0...v3.17.1) (2025-10-15)
### Bug Fixes
* **arcade-rooms:** navigate to invite link after room creation ([1922b21](https://github.com/antialias/soroban-abacus-flashcards/commit/1922b2122bb1bc4aeada7526d8c46aa89024bb00))
* **memory-quiz:** scope game settings by game name for proper persistence ([3dfe54f](https://github.com/antialias/soroban-abacus-flashcards/commit/3dfe54f1cb89bd636e763e1c5acb03776f97c011))
## [3.17.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.16.0...v3.17.0) (2025-10-15)
### Features
* **memory-quiz:** persist game settings per-game across sessions ([05a8e0a](https://github.com/antialias/soroban-abacus-flashcards/commit/05a8e0a84272c6c45a4014413ee00726eb88b76a))
## [3.16.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v3.15.2...v3.16.0) (2025-10-15)

View File

@@ -70,7 +70,7 @@ export default function RoomBrowserPage() {
}
const data = await response.json()
router.push(`/arcade-rooms/${data.room.id}`)
router.push(`/join/${data.room.code}`)
} catch (err) {
console.error('Failed to create room:', err)
showError('Failed to create room', err instanceof Error ? err.message : undefined)

View File

@@ -1,10 +1,10 @@
'use client'
import type { ReactNode } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useGameMode } from '@/contexts/GameModeContext'
import { useArcadeSession } from '@/hooks/useArcadeSession'
import { useRoomData } from '@/hooks/useRoomData'
import { useRoomData, useUpdateGameConfig } from '@/hooks/useRoomData'
import { useViewerId } from '@/hooks/useViewerId'
import type { GameMove } from '@/lib/arcade/validation'
import { TEAM_MOVE } from '@/lib/arcade/validation/types'
@@ -238,6 +238,7 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
const { data: viewerId } = useViewerId()
const { roomData } = useRoomData()
const { activePlayers: activePlayerIds, players } = useGameMode()
const { mutate: updateGameConfig } = useUpdateGameConfig()
// Get active player IDs as array
const activePlayers = Array.from(activePlayerIds)
@@ -246,6 +247,28 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
// This prevents sending a network request for every keystroke
const [localCurrentInput, setLocalCurrentInput] = useState('')
// 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
if (!gameConfig) return initialState
// Get settings for this specific game (memory-quiz)
const savedConfig = gameConfig['memory-quiz'] as Record<string, any> | null | undefined
if (!savedConfig) return initialState
console.log('[RoomMemoryQuizProvider] Loading saved game config for memory-quiz:', savedConfig)
return {
...initialState,
// Restore settings from saved config
selectedCount: savedConfig.selectedCount ?? initialState.selectedCount,
displayTime: savedConfig.displayTime ?? initialState.displayTime,
selectedDifficulty: savedConfig.selectedDifficulty ?? initialState.selectedDifficulty,
playMode: savedConfig.playMode ?? initialState.playMode,
}
}, [roomData?.gameConfig])
// Arcade session integration WITH room sync
const {
state,
@@ -255,7 +278,7 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
} = useArcadeSession<SorobanQuizState>({
userId: viewerId || '',
roomId: roomData?.id, // CRITICAL: Pass roomId for network sync across room members
initialState,
initialState: mergedInitialState,
applyMove: applyMoveOptimistically,
})
@@ -421,8 +444,29 @@ export function RoomMemoryQuizProvider({ children }: { children: ReactNode }) {
userId: viewerId || '',
data: { field, value },
})
// Save setting to room's gameConfig for persistence
// Settings are scoped by game name to preserve settings when switching games
if (roomData?.id) {
const currentGameConfig = (roomData.gameConfig as Record<string, any>) || {}
const currentMemoryQuizConfig =
(currentGameConfig['memory-quiz'] as Record<string, any>) || {}
const updatedConfig = {
...currentGameConfig,
'memory-quiz': {
...currentMemoryQuizConfig,
[field]: value,
},
}
console.log('[RoomMemoryQuizProvider] Saving game config for memory-quiz:', updatedConfig)
updateGameConfig({
roomId: roomData.id,
gameConfig: updatedConfig,
})
}
},
[viewerId, sendMove]
[viewerId, sendMove, roomData?.id, roomData?.gameConfig, updateGameConfig]
)
// Merge network state with local input state

View File

@@ -23,6 +23,7 @@ export interface RoomData {
name: string
code: string
gameName: string | null // Nullable to support game selection in room
gameConfig?: Record<string, unknown> | null // Game-specific settings
accessMode: 'open' | 'password' | 'approval-only' | 'restricted' | 'locked' | 'retired'
members: RoomMember[]
memberPlayers: Record<string, RoomPlayer[]> // userId -> players
@@ -71,6 +72,7 @@ async function fetchCurrentRoom(): Promise<RoomData | null> {
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
gameConfig: data.room.gameConfig || null,
accessMode: data.room.accessMode || 'open',
members: data.members || [],
memberPlayers: data.memberPlayers || {},
@@ -105,6 +107,7 @@ async function createRoomApi(params: CreateRoomParams): Promise<RoomData> {
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
gameConfig: data.room.gameConfig || null,
accessMode: data.room.accessMode || 'open',
members: data.members || [],
memberPlayers: data.memberPlayers || {},
@@ -141,6 +144,7 @@ async function joinRoomApi(params: {
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
gameConfig: data.room.gameConfig || null,
accessMode: data.room.accessMode || 'open',
members: data.members || [],
memberPlayers: data.memberPlayers || {},
@@ -183,6 +187,7 @@ async function getRoomByCodeApi(code: string): Promise<RoomData> {
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
gameConfig: data.room.gameConfig || null,
accessMode: data.room.accessMode || 'open',
members: data.members || [],
memberPlayers: data.memberPlayers || {},
@@ -665,3 +670,47 @@ export function useClearRoomGame() {
},
})
}
/**
* Update game config for current room (game-specific settings)
*/
async function updateGameConfigApi(params: {
roomId: string
gameConfig: Record<string, unknown>
}): Promise<void> {
const response = await fetch(`/api/arcade/rooms/${params.roomId}/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
gameConfig: params.gameConfig,
}),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to update game config')
}
}
/**
* Hook: Update game config for current room
* This allows games to persist their settings (e.g., difficulty, card count)
*/
export function useUpdateGameConfig() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updateGameConfigApi,
onSuccess: (_, variables) => {
// Update the cache with the new gameConfig
queryClient.setQueryData<RoomData | null>(roomKeys.current(), (prev) => {
if (!prev) return null
return {
...prev,
gameConfig: variables.gameConfig,
}
})
console.log('[useUpdateGameConfig] Updated cache with new gameConfig:', variables.gameConfig)
},
})
}

View File

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