Creates migration 0011 to:
- Create room_game_configs table with proper schema
- Add unique index on (room_id, game_name)
- Migrate existing game_config data from arcade_rooms table
Migration is idempotent and safe to run on any database state:
- Uses IF NOT EXISTS for table and index creation
- Uses INSERT OR IGNORE to avoid duplicate data
- Will work on both fresh databases and existing production
This ensures production will automatically get the new table structure
when the migration runs on deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Manual migration applied on 2025-10-15:
- Created room_game_configs table via sqlite3 CLI
- Migrated 6000 existing configs from arcade_rooms.game_config
- 5991 matching configs + 9 memory-quiz configs
- Table created directly instead of through drizzle migration system
The manually created drizzle migration SQL file has been removed since
the migration was applied directly to the database. See
.claude/MANUAL_MIGRATION_0011.md for complete details on the migration
process, verification steps, and rollback plan.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
### Schema Changes
- Create `room_game_configs` table with one row per game per room
- Migrate existing gameConfig data from arcade_rooms.game_config JSON column
- Add unique index on (roomId, gameName) for efficient queries
### Benefits
- ✅ Type-safe config access with shared types
- ✅ Smaller rows (only configs for used games)
- ✅ Easier updates (single row vs entire JSON blob)
- ✅ Better concurrency (no lock contention between games)
- ✅ Foundation for per-game audit trail
### Core Changes
1. **Shared Config Types** (`game-configs.ts`)
- `MatchingGameConfig`, `MemoryQuizGameConfig` interfaces
- Default configs for each game
- Single source of truth for all settings
2. **Helper Functions** (`game-config-helpers.ts`)
- `getGameConfig<T>()` - type-safe config retrieval with defaults
- `setGameConfig()` - upsert game config
- `getAllGameConfigs()` - aggregate all game configs for a room
- `validateGameConfig()` - runtime validation
3. **API Routes**
- `/api/arcade/rooms/current`: Aggregates configs from new table
- `/api/arcade/rooms/[roomId]/settings`: Writes to new table
4. **Socket Server** (`socket-server.ts`)
- Uses `getGameConfig()` helper for session creation
- Eliminates manual config extraction and defaults
5. **Validators**
- `MemoryQuizGameValidator.getInitialState(config: MemoryQuizGameConfig)`
- `MatchingGameValidator.getInitialState(config: MatchingGameConfig)`
- Type signatures enforce consistency
### Migration Path
- Existing data migrated automatically (SQL in migration file)
- Old `gameConfig` column preserved temporarily
- Client-side providers unchanged (read from aggregated response)
Next steps:
- Test settings persistence thoroughly
- Drop old `gameConfig` column after validation
- Update documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 1: Database and API updates
- Create migration 0010 to make game_name and game_config nullable
- Update arcade_rooms schema to support rooms without games
- Update RoomData interface to make gameName optional
- Update CreateRoomParams to make gameName optional
- Update room creation API to allow null gameName
- Update all room data parsing to handle null gameName
This allows rooms to be created without a game selected, enabling
users to choose a game inside the room itself. The URL remains
/arcade/room regardless of selection, setup, or gameplay state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create 0009_add_display_password.sql migration
- Add entry to drizzle journal
- This adds the display_password column that was missing in production
The plan is to nuke the production database and let all migrations
run from scratch on container restart.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add success/error message UI component to ModerationPanel
- Replace all browser alert() calls with inline React-based feedback
- Add displayPassword field to arcade_rooms schema for plain text storage
- Create migration to add display_password column
- Update settings PATCH route to store both hashed and display passwords
- Update room GET route to return displayPassword only to room creator
- Update ModerationPanel to populate password field when loading settings
- Fix room-manager test to include displayPassword field
Password field now persists and displays correctly when reloading the page
for room owners in password-protected rooms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update placeholder text in room creation forms to show auto-generated format
- Make room.name nullable in database schema (migration 0008)
- Add accessMode field to RoomData interface
- Implement password prompt UI for password-protected rooms via share links
- Add password support to room browser join flow
- Remove autoFocus attribute for accessibility compliance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive access control system for arcade rooms with 6 modes:
- open: Anyone can join (default)
- locked: Only current members allowed
- retired: Room no longer functions
- password: Requires password to join
- restricted: Only users with pending invitations can join
- approval-only: Requires host approval via join request system
Database Changes:
- Add accessMode field to arcade_rooms (replaces isLocked boolean with enum)
- Add password field to arcade_rooms (hashed with bcrypt)
- Create room_join_requests table for approval-only mode
New API Endpoints:
- PATCH /api/arcade/rooms/:roomId/settings - Update room access mode and password (host only)
- POST /api/arcade/rooms/:roomId/transfer-ownership - Transfer ownership to another member (host only)
- POST /api/arcade/rooms/:roomId/join-request - Request to join approval-only room
- GET /api/arcade/rooms/:roomId/join-requests - Get pending join requests (host only)
- POST /api/arcade/rooms/:roomId/join-requests/:requestId/approve - Approve join request (host only)
- POST /api/arcade/rooms/:roomId/join-requests/:requestId/deny - Deny join request (host only)
Updated Endpoints:
- POST /api/arcade/rooms/:roomId/join - Now validates access modes before allowing join:
* locked: Rejects all joins
* retired: Rejects all joins (410 Gone)
* password: Requires password validation
* restricted: Requires valid pending invitation
* approval-only: Requires approved join request
* open: Allows anyone (existing behavior)
Libraries:
- Add room-join-requests.ts for managing join request lifecycle
- Ownership transfer updates room.createdBy and member.isCreator flags
- Socket.io events for join request notifications and ownership transfers
Migration: 0007_access_modes.sql
Next Steps (UI not included in this commit):
- RoomSettingsModal for configuring access mode and password
- Join request approval UI in ModerationPanel
- Ownership transfer UI in ModerationPanel
- Password input in join flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive database schema to support:
- Room bans: Track banned users with reasons and timestamps
- Room reports: Allow players to report others for misconduct
- Room invitations: Send and track room invitations
- Room member history: Track all users who have ever been in a room
This foundational schema enables the complete moderation system
including banning, kicking, reporting, and invitation features.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Problem:**
When users joined rooms, the arcade session was created with empty
activePlayers array, causing the game to start in single-player mode
even though multiple users had joined the room.
**Root Cause:**
Initial session creation in `join-arcade-session` handler set
`activePlayers: []` without fetching the actual room members' players.
**Solution:**
1. **Session Creation**: When creating initial session, fetch all room
members' active players using `getRoomPlayerIds()` and populate
`activePlayers` field.
2. **Dynamic Updates**: When members join room (`join-room` event) or
toggle players (`players-updated` event), update session's
`activePlayers` dynamically using new `updateSessionActivePlayers()`
function.
3. **Protection**: Only update `activePlayers` if game is in 'setup'
phase - prevents disrupting games in progress.
**Key Changes:**
- `socket-server.ts`: Import `getRoomPlayerIds`, use it to populate
activePlayers on session creation, update session when room membership
changes
- `session-manager.ts`: Add `updateSessionActivePlayers()` function to
safely update activePlayers during setup phase
- Test fixes: Updated test files to match new schema (roomId PRIMARY KEY)
**Testing:**
- Session correctly populated with all room members' players on creation
- New members' players added to session when they join (if in setup)
- Player toggle updates session activePlayers in real-time
- Games in progress protected from activePlayers modifications
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Root Cause:**
Database schema had `user_id` as PRIMARY KEY for `arcade_sessions`,
allowing multiple sessions for the same `roomId`. When two users joined
the same room, each created their own session, causing them to see
completely different games instead of sharing one session.
**Database Changes:**
- Changed PRIMARY KEY from `user_id` to `room_id`
- Now enforces one session per room at database level
- Updated all queries to use `room_id` as primary lookup key
**Code Changes:**
- Updated `createArcadeSession()` to check for existing room session
- Added error handling for race conditions (UNIQUE constraint failures)
- Modified `applyGameMove()`, `deleteArcadeSession()`, and `updateSessionActivity()` to use `room_id`
- Cleaned up orphaned sessions and duplicates before migration
**Migration:**
- Generated migration: `drizzle/0005_jazzy_mimic.sql`
- Cleaned up 58 orphaned sessions (NULL room_id)
- Removed duplicate sessions for same room (kept highest version)
- Migration successfully applied to dev database
**Testing Required:**
- Verify two users in same room now share the same game state
- Confirm session updates broadcast correctly to all room members
- Test that PRIMARY KEY constraint prevents duplicate creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement hybrid database + application-level enforcement to ensure users
can only be in one room at a time, with graceful auto-leave behavior and
clear error messaging.
## Changes
### Database Layer
- Add unique index on `room_members.user_id` to enforce one room per user
- Migration includes cleanup of any existing duplicate memberships
- Constraint provides safety net if application logic fails
### Application Layer
- Auto-leave logic: when joining a room, automatically remove user from
all other rooms first
- Return `AutoLeaveResult` with metadata about rooms that were left
- Idempotent rejoining: rejoining the same room just updates status
### API Layer
- Join route returns auto-leave information in response
- Catches and handles constraint violations with 409 Conflict
- User-friendly error messages when conflicts occur
### Frontend
- Room list and detail pages handle ROOM_MEMBERSHIP_CONFLICT errors
- Show alerts when user needs to leave current room
- Refresh room list after conflicts to show current state
### Testing
- 7 integration tests for modal room behavior
- Tests cover: first join, auto-leave, rejoining, multi-user scenarios,
constraint enforcement, and metadata accuracy
- Updated existing unit tests for new return signature
## Technical Details
- `addRoomMember()` now returns `{ member, autoLeaveResult? }`
- Auto-leave happens before new room join, preventing race conditions
- Database unique constraint as ultimate safety net
- Socket events remain status-only (joining goes through API)
## Testing
- ✅ All modal room tests pass (7/7)
- ✅ All room API e2e tests pass (12/12)
- ✅ Format and lint checks pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Run Biome formatter on all files to ensure consistent code style:
- Single quotes for JS/TS
- Double quotes for JSX
- 2-space indentation
- 100 character line width
- Semicolons as needed
- ES5 trailing commas
This is the result of running: npx @biomejs/biome format . --write
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement foundational infrastructure for multi-room arcade system:
Database:
- Add arcade_rooms table for room metadata and lifecycle
- Add room_members table for membership tracking
- Add nullable roomId field to arcade_sessions for room association
- Create migration 0003_naive_reptil.sql
Managers:
- Implement room-manager.ts with full CRUD operations
- Implement room-membership.ts for member management
- Add room-code.ts utility for unique room code generation
- Include TTL-based room cleanup functionality
Documentation:
- Add arcade-rooms-technical-plan.md with complete system design
- Add arcade-rooms-implementation-tasks.md with 62-task breakdown
This establishes the foundation for public multiplayer rooms with:
- URL-addressable rooms with unique codes
- Guest user support
- Configurable TTL for automatic cleanup
- Room creator moderation controls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Changed CreateSessionOptions.activePlayers from number[] to string[]
- Updated socket-server.ts fallback from [1] to [data.userId]
- Added debug logging to validateFlipCard to diagnose turn validation issues
This ensures that when a session is created without explicit activePlayers,
it uses the actual UUID of the requesting player instead of the numeric value 1.
- Add abacus_settings table with all display configuration fields
- Create API routes (GET/PATCH) for abacus settings
- Add React Query hooks with optimistic updates
- Create AbacusSettingsSync component to bridge localStorage and API
- Settings now persist server-side per guest/user session
- Maintains backward compatibility with existing localStorage pattern
Migration includes:
- Database schema for 12 abacus display settings
- Automatic migration generation and application
- API-driven persistence with guest session support
- Sync component loads from API on mount and saves changes automatically
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>