diff --git a/backend/app/socketio/game_namespace.py b/backend/app/socketio/game_namespace.py index 20d27f3..8cd37b8 100644 --- a/backend/app/socketio/game_namespace.py +++ b/backend/app/socketio/game_namespace.py @@ -177,6 +177,20 @@ class GameNamespaceHandler: response["turn_timeout_seconds"] = result.turn_timeout_seconds response["turn_deadline"] = result.turn_deadline + # Emit game:state event to the client + if result.visible_state: + await sio.emit( + "game:state", + { + "game_id": game_id, + "state": response["state"], + "is_your_turn": response["is_your_turn"], + "game_over": response["game_over"], + }, + to=sid, + namespace="/game", + ) + logger.info(f"Player {user_id} joined game {game_id}") return response diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index a219526..16bef31 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -43,7 +43,20 @@ async function parseErrorResponse(response: Response): Promise { try { const data: ErrorResponse = await response.json() - detail = data.detail || data.message + + // Handle Pydantic validation errors (422) - detail is an array + if (Array.isArray(data.detail)) { + // Extract error messages from validation error array + detail = data.detail + .map((err: any) => { + const field = err.loc ? err.loc.join('.') : 'unknown' + return `${field}: ${err.msg || 'Invalid value'}` + }) + .join('; ') + } else { + detail = data.detail || data.message + } + code = data.code } catch { // Response body is not JSON or empty diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 607d98e..57ca69a 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -92,9 +92,10 @@ export interface PaginatedResponse { /** * Backend error response shape. + * For validation errors (422), detail is an array of validation error objects. */ export interface ErrorResponse { - detail?: string + detail?: string | Array<{ loc: string[]; msg: string; type: string }> message?: string code?: string }