Compare commits

...

4 Commits

Author SHA1 Message Date
semantic-release-bot
5bbb212da9 chore(release): 2.7.2 [skip ci]
## [2.7.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.1...v2.7.2) (2025-10-08)

### Bug Fixes

* add hasAttemptedFetch flag to prevent premature redirect ([c30f585](c30f585810))
2025-10-08 15:40:52 +00:00
Thomas Hallock
c30f585810 fix: add hasAttemptedFetch flag to prevent premature redirect
The previous fix didn't fully resolve the race condition. When userId
finished loading, there was a brief moment where:
- isUserIdPending = false (userId loaded)
- isLoading = false (fetch hasn't started yet)
- roomData = null

This triggered the redirect before the room fetch even began.

Solution:
- Added hasAttemptedFetch flag to track fetch attempt state
- Updated isLoading to include: isUserIdPending || isLoading || (!!userId && !hasAttemptedFetch)
- Now the page stays in loading state until we've both loaded userId AND attempted the room fetch

This ensures we never redirect while a fetch is pending.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 10:39:54 -05:00
semantic-release-bot
0a768c65fb chore(release): 2.7.1 [skip ci]
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.0...v2.7.1) (2025-10-08)

### Bug Fixes

* resolve race condition in /arcade/room redirect ([5ed2ab2](5ed2ab21ca))
2025-10-08 15:26:51 +00:00
Thomas Hallock
5ed2ab21ca fix: resolve race condition in /arcade/room redirect
The /arcade/room page was redirecting to /arcade before userId loaded,
causing a race condition where the page would redirect even when the user
was in a valid room.

Root cause:
- useViewerId() loads asynchronously
- useRoomData depended on userId but didn't expose userId loading state
- Page checked !isLoading && !roomData and redirected immediately
- By the time userId loaded and room data fetched, redirect already happened

Fix:
- Track isPending from useViewerId in useRoomData
- Combine isUserIdPending with room data loading state
- Page now waits for both userId and room data before redirecting

Added debug logging to help diagnose future issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 10:25:53 -05:00
6 changed files with 48 additions and 27 deletions

View File

@@ -1,3 +1,17 @@
## [2.7.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.1...v2.7.2) (2025-10-08)
### Bug Fixes
* add hasAttemptedFetch flag to prevent premature redirect ([c30f585](https://github.com/antialias/soroban-abacus-flashcards/commit/c30f58581028878350282cad5231d614590d9f2b))
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.7.0...v2.7.1) (2025-10-08)
### Bug Fixes
* resolve race condition in /arcade/room redirect ([5ed2ab2](https://github.com/antialias/soroban-abacus-flashcards/commit/5ed2ab21cab408147081a493c8dd6b1de48b2d01))
## [2.7.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v2.6.0...v2.7.0) (2025-10-08)

View File

@@ -12,11 +12,14 @@ import { getViewerId } from '@/lib/viewer'
export async function GET() {
try {
const userId = await getViewerId()
console.log('[Current Room API] Fetching for user:', userId)
// Get all rooms user is in (should be at most 1 due to modal room enforcement)
const roomIds = await getUserRooms(userId)
console.log('[Current Room API] User rooms:', roomIds)
if (roomIds.length === 0) {
console.log('[Current Room API] User is not in any room')
return NextResponse.json({ room: null }, { status: 200 })
}
@@ -25,6 +28,7 @@ export async function GET() {
// Get room data
const room = await getRoomById(roomId)
if (!room) {
console.log('[Current Room API] Room not found:', roomId)
return NextResponse.json({ error: 'Room not found' }, { status: 404 })
}
@@ -40,6 +44,12 @@ export async function GET() {
memberPlayersObj[uid] = players
}
console.log('[Current Room API] Returning room:', {
roomId: room.id,
roomName: room.name,
memberCount: members.length,
})
return NextResponse.json({
room,
members,

View File

@@ -15,6 +15,11 @@ export default function RoomPage() {
const router = useRouter()
const { roomData, isLoading } = useRoomData()
// Debug logging
useEffect(() => {
console.log('[RoomPage] State:', { isLoading, hasRoomData: !!roomData, roomData })
}, [isLoading, roomData])
// Redirect to arcade if no room
useEffect(() => {
if (!isLoading && !roomData) {

View File

@@ -1,7 +1,6 @@
'use client'
import { createContext, type ReactNode, useContext, useEffect, useMemo, useState } from 'react'
import { io } from 'socket.io-client'
import type { Player as DBPlayer } from '@/db/schema/players'
import {
useCreatePlayer,
@@ -158,26 +157,6 @@ export function GameModeProvider({ children }: { children: ReactNode }) {
}
}, [dbPlayers, isLoading, isInitialized, createPlayer])
// When in a room, broadcast player updates to other members
useEffect(() => {
if (!roomData || !viewerId || !isInitialized) return
const socket = io({ path: '/api/socket' })
// Wait for connection before emitting
socket.on('connect', () => {
console.log('[GameModeContext] Emitting players-updated for room:', roomData.id)
socket.emit('players-updated', {
roomId: roomData.id,
userId: viewerId,
})
})
return () => {
socket.disconnect()
}
}, [dbPlayers, roomData, viewerId, isInitialized])
const addPlayer = (playerData?: Partial<Player>) => {
const playerList = Array.from(players.values())

View File

@@ -31,45 +31,57 @@ export interface RoomData {
* Returns null if user is not in any room
*/
export function useRoomData() {
const { data: userId } = useViewerId()
const { data: userId, isPending: isUserIdPending } = useViewerId()
const [socket, setSocket] = useState<Socket | null>(null)
const [roomData, setRoomData] = useState<RoomData | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [hasAttemptedFetch, setHasAttemptedFetch] = useState(false)
// Fetch the user's current room
useEffect(() => {
if (!userId) {
console.log('[useRoomData] No userId, clearing room data')
setRoomData(null)
setHasAttemptedFetch(false)
return
}
console.log('[useRoomData] Fetching current room for user:', userId)
setIsLoading(true)
setHasAttemptedFetch(false)
// Fetch current room data
fetch('/api/arcade/rooms/current')
.then((res) => {
console.log('[useRoomData] API response status:', res.status)
if (!res.ok) throw new Error('Failed to fetch current room')
return res.json()
})
.then((data) => {
console.log('[useRoomData] API response data:', data)
if (data.room) {
setRoomData({
const roomData = {
id: data.room.id,
name: data.room.name,
code: data.room.code,
gameName: data.room.gameName,
members: data.members || [],
memberPlayers: data.memberPlayers || {},
})
}
console.log('[useRoomData] Setting room data:', roomData)
setRoomData(roomData)
} else {
console.log('[useRoomData] No room in response, clearing room data')
setRoomData(null)
}
setIsLoading(false)
setHasAttemptedFetch(true)
})
.catch((error) => {
console.error('Failed to fetch room data:', error)
console.error('[useRoomData] Failed to fetch room data:', error)
setRoomData(null)
setIsLoading(false)
setHasAttemptedFetch(true)
})
}, [userId])
@@ -197,7 +209,8 @@ export function useRoomData() {
return {
roomData,
isLoading,
// Loading if: userId is pending, currently fetching, or have userId but haven't tried fetching yet
isLoading: isUserIdPending || isLoading || (!!userId && !hasAttemptedFetch),
isInRoom: !!roomData,
}
}

View File

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