Backend:
- Fix session never being attached to ws.data at upgrade time
- Add GameServer class: connection registry, per-connection room tracking,
automatic room cleanup on disconnect via ws.data.rooms
- Replace ws-handler.ts with typed event-driven architecture using mitt
- Remove redundant subscription tracking from RoomManager
- Add JOIN_RESULT with player/spectator lists replacing error-as-control-flow
- Add SESSION_REPLACED for multi-tab same-account detection
- Add FILL_ROOM command for admin solo testing (fills empty slots with host)
- Fix dual-schema routing; remove game types from WsMessageSchema
- Per-player personalized views sent directly after each action
Chess plugin:
- Allow same-player (solo) mode: skip color/turn ownership checks
- Fix forfeit and disconnect handling in solo mode (winner: null)
Frontend:
- Click-to-move with legal move dots and last-move highlight
- Auto-scroll move history, forfeit confirmation, turn-reactive board border
- JOIN_RESULT initialises player/spectator lists immediately on join
- Contextual connecting state, player slot cards in waiting room
- Copy-invite button with Copied! flash, Back to Lobby CTA on finish
- Session-replaced warning banner with Rejoin here action
- Lobby passes preferAs intent through route state
- Admin waiting room shows Start Solo Test button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prevent same player from joining as both white and black
- Add validation to reject duplicate players in RoomManager
- Fix spectator status not resetting when joining as player
- Use ref to track latest chess state in ChessBoard for accurate move validation
- Add role field to JOIN_ROOM message schema
- Allow admin users to join rooms and be added as players
- Update panel to pass user role when joining game rooms
- Fix chess move coordinates in tests (algebraic notation)
- Ensure admin users can make moves for both sides
Bun's ws.publish() excludes the sender, so the player making a move never
received the GAME_UPDATE with the new FEN — causing pieces to snap back.
Added ctx.send() alongside ctx.publish() for GAME_UPDATE and GAME_ENDED.
Also redesigned the panel for mobile: hamburger drawer sidebar, responsive
chess board sizing via ResizeObserver, stacked layouts on small screens,
and touch-friendly modals/controls across lobby and game pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap chess.js move validation in try-catch for invalid moves. Fix admin
self-play by detecting when both players share the same ID and allowing
either color's pieces to be dragged on the current turn.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip the duplicate-player check when the joining user has the admin
role, so admins can test games solo by joining their own room as both
players.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Swap the custom move validation and Unicode piece grid for chess.js
(full rules engine with check/checkmate/castling/en passant/promotion)
and react-chessboard (drag-and-drop SVG board). Board styled to match
the purple dark theme and auto-orients to the player's color.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Disconnecting and reconnecting the WebSocket on every route change caused
a race condition: the old socket's async onclose handler would null out
globalWs after the new socket was created, causing JOIN_ROOM messages to
be silently dropped. The WebSocket is a global singleton — keep it alive
and let the built-in reconnection logic handle actual disconnects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bun's ws.publish() excludes the sender, so the joining client never
received the PLAYER_JOINED message and stayed stuck on the connecting
spinner. Now sends the message directly to the joiner via ctx.send
in addition to publishing to other room subscribers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The room creator was added as a player during createRoom, but when
GameRoom mounted it sent JOIN_ROOM which failed with "Already in room",
leaving the UI stuck on the loading spinner. Now treats duplicate joins
as successful rejoins, which also fixes reconnection after page refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/auth/me was returning enrolled: false for all unauthenticated requests,
causing the frontend to show the NotEnrolled page before login. Enrollment
is already enforced server-side during the OAuth callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace useState-based page switching with react-router-dom Routes.
Layout now renders admin or player nav items based on user.role.
Add stub pages for PlayerDashboard, GameLobby, and GameRoom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split ItemStudio (1863->388), Settings (1445->355), and Users
(1062->164) into focused sub-components under pages/components/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all hardcoded custom ID strings with module-level constants.
Each module now has *_CUSTOM_IDS in its types file, using functions
for dynamic IDs and PREFIX for startsWith matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix web/ -> api/, add missing panel/modules/graphics sections,
expand module and utility listings to match actual codebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add routing table mapping custom ID prefixes to handler files and
describe the ComponentInteractionHandler dispatch mechanism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
One-line JSDoc on 82 methods across 11 service files for quick
scanning without reading full implementations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provide non-obvious business rules and constraints for economy,
inventory, quest, moderation, trade, and trivia modules to reduce
context-gathering overhead for AI tools.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace setTimeout race in use-item flow with explicit Back button
- Fix collector end handler to re-render current view instead of blanking
- Add appendUseBackButton helper to attach navigation to use results
- Remove unused isInventoryInteraction import
- Fix rarity test type assertions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Viewing another user's inventory is read-only — Use and Discard
buttons only render when viewer is the inventory owner, with a
server-side guard in the interaction handler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a limit check to assignQuest that reads maxActiveQuests from game
settings and throws a UserError when the user has reached their active
quest limit. Completed quests are excluded from the count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These domain events were only connected to dashboard recording but never
called questService.handleEvent(), so quests with triggers TRANSFER_OUT,
DAILY_REWARD, TRIVIA_WIN, and EXAM_REWARD never tracked progress. Added
userId and tx to event payloads and switched from emit to emitAsync for
transaction atomicity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Discord counts all nested components (buttons inside action rows)
toward the message-level 40 component cap. 7 per page exceeded this
when pagination buttons were included.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>