**Problem:** - player-ownership.ts imported drizzle-orm and @/db at top level - When RoomMemoryPairsProvider imported client-safe utilities, Webpack bundled ALL imports including database code - This caused hydration error: "The 'original' argument must be of type Function" - Node.js util.promisify was being called in browser context **Solution:** 1. Created player-ownership.client.ts with ONLY client-safe utilities - No database imports - Safe to import from 'use client' components - Contains: buildPlayerOwnershipFromRoomData(), buildPlayerMetadata(), helper functions 2. Updated player-ownership.ts to re-export client utilities and add server-only functions - Re-exports everything from .client.ts - Adds buildPlayerOwnershipMap() (async, database-backed) - Safe to import from server components/API routes 3. Updated RoomMemoryPairsProvider to import from .client.ts **Result:** - No more hydration errors on /arcade/room - Client bundle doesn't include database code - Server code can still use both client and server utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
4.7 KiB
4.7 KiB
Current Implementation vs Correct Design - Inconsistencies
❌ Inconsistency 1: Room Join Doesn't Fetch Active Players
Current Code (/api/arcade/rooms/:roomId/join):
// Only creates room_member record with userId
const member = await addRoomMember({
roomId,
userId: viewerId, // ✅ Correct: USER ID
displayName,
isCreator: false,
});
// ❌ Missing: Does not fetch user's active players
Should Be:
// 1. Create room member
const member = await addRoomMember({ ... })
// 2. Fetch user's active players
const activePlayers = await db.query.players.findMany({
where: and(
eq(players.userId, viewerId),
eq(players.isActive, true)
)
})
// 3. Return both member and their active players
return { member, activePlayers }
❌ Inconsistency 2: Socket Events Use USER ID Instead of PLAYER ID
Current Code (socket-server.ts):
socket.on("join-room", ({ roomId, userId }) => {
// Uses USER ID for presence
await setMemberOnline(roomId, userId, true);
socket.emit("room-joined", { members });
});
socket.on("room-game-move", ({ roomId, userId, move }) => {
// ❌ Wrong: Uses USER ID for game moves
// Should use PLAYER ID
});
Should Be:
socket.on("join-room", ({ roomId, userId }) => {
// ✅ Correct: Use USER ID for room presence
await setMemberOnline(roomId, userId, true);
// ❌ Missing: Should also fetch and broadcast active players
const activePlayers = await getActivePlayers(userId);
socket.emit("room-joined", { members, activePlayers });
});
socket.on("room-game-move", ({ roomId, playerId, move }) => {
// ✅ Correct: Use PLAYER ID for game actions
// Validate that playerId belongs to a member in this room
});
❌ Inconsistency 3: Room Member Interface Missing Player Association
Current Code (room_members table):
interface RoomMember {
id: string;
roomId: string;
userId: string; // ✅ Correct: USER ID
displayName: string;
isCreator: boolean;
// ❌ Missing: No link to user's players
}
Need to Add (runtime association, not DB schema):
interface RoomMemberWithPlayers {
member: RoomMember;
activePlayers: Player[]; // The user's active players
}
❌ Inconsistency 4: Client UI Shows Room Members, Not Players
Current Code (/arcade/rooms/[roomId]/page.tsx):
// Shows room members (users)
{members.map((member) => (
<div key={member.id}>
{member.displayName} {/* USER's display name */}
</div>
))}
// ❌ Missing: Should show the PLAYERS that will participate
Should Show:
{members.map((member) => (
<div key={member.id}>
<div>{member.displayName} (Room Member)</div>
<div>Players:
{member.activePlayers.map(player => (
<span key={player.id}>{player.emoji} {player.name}</span>
))}
</div>
</div>
))}
Summary of Required Changes
Phase 1: Backend - Player Fetching
- ✅
room_memberstable correctly uses USER ID (no change needed) - ❌
/api/arcade/rooms/:roomId/join- Fetch and return active players - ❌
/api/arcade/rooms/:roomIdGET - Include active players in response - ❌ Create helper:
getActivePlayers(userId) => Player[]
Phase 2: Socket Layer - Player Association
- ❌
join-roomevent - Broadcast active players to room - ❌
room-game-moveevent - Accept PLAYER ID, not USER ID - ❌ Validate PLAYER ID belongs to a room member
Phase 3: Frontend - Player Display
- ❌ Room lobby - Show each member's active players
- ❌ Game setup - Use PLAYER IDs for
activePlayersarray - ❌ Move/action events - Send PLAYER ID
Phase 4: Game Integration
- ❌ When room game starts, collect all PLAYER IDs from all members
- ❌ Arcade session
activePlayersshould contain all room PLAYER IDs - ❌ Game state tracks scores/moves by PLAYER ID, not USER ID
Test Scenarios
Scenario 1: Single Player Per User
USER Jane (guest_123)
└─ PLAYER Alice (active)
Joins room → Room shows "Jane: Alice 👧"
Game starts → activePlayers: ["alice_id"]
Scenario 2: Multiple Players Per User
USER Jane (guest_123)
├─ PLAYER Alice (active)
└─ PLAYER Bob (active)
Joins room → Room shows "Jane: Alice 👧, Bob 👦"
Game starts → activePlayers: ["alice_id", "bob_id"]
Scenario 3: Multi-User Room
USER Jane
└─ PLAYER Alice, Bob (active)
USER Mark
└─ PLAYER Mario (active)
USER Sara
└─ PLAYER Luna, Nova, Star (active)
Room shows:
- Jane: Alice 👧, Bob 👦
- Mark: Mario 🍄
- Sara: Luna 🌙, Nova ✨, Star ⭐
Game starts → activePlayers: [alice, bob, mario, luna, nova, star]
Total: 6 players across 3 users