soroban-abacus-flashcards/apps/web/.claude/ARCADE_ROUTING_ARCHITECTURE.md

16 KiB

Arcade Routing Architecture - Complete Overview

1. Current /arcade Page

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/app/arcade/page.tsx (lines 1-129)

Purpose: The main arcade landing page - displays the "Champion Arena"

Key Components:

  • ArcadeContent() - Renders the main arcade interface
    • Uses EnhancedChampionArena component which contains GameSelector
    • The GameSelector displays all available games as cards
    • GameSelector includes both legacy games and registry games

Current Flow:

  1. User navigates to /arcade
  2. Page renders FullscreenProvider wrapper
  3. Displays PageWithNav with title "🏟️ Champion Arena"
  4. Content area shows EnhancedChampionArenaGameSelector
  5. GameSelector renders GameCard components for each game
  6. When user clicks a game card, GameCard calls router.push(config.url)
  7. For registry games, config.url is /arcade/room?game={gameName}
  8. For legacy games, URL would be direct to their page

State Management:

  • GameModeContext provides player selection (emoji, name, color)
  • PageWithNav wraps content and provides mini-nav with:
    • Active player list
    • Add player button
    • Game mode indicator (single/battle/tournament)
    • Exit session handler

2. Current /arcade/room Page

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/app/arcade/room/page.tsx (lines 1-359)

Purpose: "Magical place" that shows either a game OR the game chooser, driven by room state

Three States:

State 1: Loading

  • Shows "Loading room..." message
  • Waits for useRoomData() hook to resolve

State 2: Game Selection UI (when !roomData.gameName)

  • Shows large game selection buttons
  • User clicks to select a game
  • Calls setRoomGame() mutation to save selection to room
  • Invokes handleGameSelect() which:
    1. Checks if game exists in registry via hasGame(gameType)
    2. If registry game: calls setRoomGame({roomId, gameName: gameType})
    3. If legacy game: maps to internal name via GAME_TYPE_TO_NAME, then calls setRoomGame()
    4. Game selection is persisted to the room database

State 3: Game Display (when roomData.gameName is set)

  • Checks game registry first via hasGame(roomData.gameName)
  • If registry game:
    • Gets game definition via getGame(roomData.gameName)
    • Renders: <Provider><GameComponent /></Provider>
    • Provider and GameComponent come from game registry definition
  • If legacy game:
    • Switch statement with TODO for individual games
    • Currently only shows "Game not yet supported"

Key Hook:

  • useRoomData() - Fetches current room from API and subscribes to socket updates
    • Returns roomData with fields: id, name, code, gameName, gameConfig, members, memberPlayers
    • Also returns isLoading boolean

Navigation Flow:

  1. User navigates to /arcade
  2. GameCard onClick calls router.push('/arcade/room?game={gameName}')
  3. User arrives at /arcade/room
  4. If NOT in a room yet: Shows error with link back to /arcade
  5. If in room but no game selected: Shows game selection UI
  6. If game selected: Loads and displays game

3. The "Mini App Nav" - GameContextNav Component

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/nav/GameContextNav.tsx (lines 1-372)

What It Is: The "mini app nav" is actually a sophisticated component within the PageWithNav wrapper that intelligently shows different UI based on context:

Components & Props:

  • navTitle - Current page title (e.g., "Champion Arena", "Choose Game", "Speed Complement Race")
  • navEmoji - Icon emoji for current page
  • gameMode - Computed from active player count: 'none' | 'single' | 'battle' | 'tournament'
  • activePlayers - Array of selected players
  • inactivePlayers - Array of available but unselected players
  • shouldEmphasize - Boolean to emphasize player selection
  • showFullscreenSelection - Boolean to show fullscreen mode for player selection
  • roomInfo - Optional arcade room data (roomId, roomName, gameName, playerCount, joinCode)
  • networkPlayers - Remote players from room members

Three Display Modes:

Mode 1: Fullscreen Player Selection

  • When showFullscreenSelection === true
  • Displays:
    • Large title with emoji
    • Game mode indicator
    • Fullscreen player selection UI
    • Shows all inactive players for selection

Mode 2: Solo Mode (NOT in room)

  • When roomInfo is undefined
  • Shows:
    • Game Title Section (left side):
      • GameTitleMenu with game title and emoji
      • Menu options: Setup, New Game, Quit
      • GameModeIndicator
    • Player Section (right side):
      • ActivePlayersList - shows selected players
      • AddPlayerButton - add more players

Mode 3: Room Mode (IN a room)

  • When roomInfo is defined
  • Shows:
    • Hidden: Game title section (display: none)
    • Room Info Pane (left side):
      • RoomInfo component with room details
      • Game mode indicator with color/emoji
      • Room name, player count, join code
      • NetworkPlayerIndicator components for remote players
    • Player Section (may be hidden):
      • Shows local active players
      • Add player button (for local players only)

Key Sub-Components:

  • GameTitleMenu - Menu for game options (setup, new game, quit)
  • GameModeIndicator - Shows 🎯 Solo, ⚔️ Battle, 🏆 Tournament, 👥 Select
  • RoomInfo - Displays room metadata
  • NetworkPlayerIndicator - Shows remote players with scores/streaks
  • ActivePlayersList - List of selected players
  • AddPlayerButton - Button to add more players with popover
  • FullscreenPlayerSelection - Large player picker for fullscreen mode
  • PendingInvitations - Banner for room invitations

State Management:

  • Lifted from PageWithNav to preserve state across remounts:
    • showPopover / setShowPopover - AddPlayerButton popover state
    • activeTab / setActiveTab - 'add' or 'invite' tab selection

4. Navigation Flow

Flow 1: Solo Player → Game Selection → Room Creation → Game Start

/arcade (Champion Arena)
  ↓ [Select players - updates GameModeContext]
  ↓ [Click game card - GameCard.onClick → router.push]
/arcade/room (if not in room, shows game selector)
  ↓ [Select game - calls setRoomGame mutation]
  ↓ [Room created, gameName saved to roomData]
  ↓ [useRoomData refetch updates roomData.gameName]
/arcade/room (now displays the game)
  ↓ [Game Provider and Component render]

Flow 2: Multiplayer - Room Invitation

User A: Creates room via Champion Arena
User B: Receives invitation
User B: Joins room via /arcade/room
User B: Sees same game selection (if set) or game selector (if not set)

Flow 3: Exit Game

/arcade/room (in-game)
  ↓ [Click "Quit" or "Exit Session" in GameContextNav]
  ↓ [onExitSession callback → router.push('/arcade')]
/arcade (back to champion arena)
  ↓ Player selection reset by GameModeContext

5. Game Chooser / Game Selection System

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameSelector.tsx (lines 1-112)

How It Works:

  1. GameSelector component gets all games from both sources:

    • Legacy GAMES_CONFIG (currently empty)
    • Registry games via getAllGames()
  2. For each game, creates GameCard component with configuration including url field

  3. Game Cards rendered in 2-column grid (responsive)

  4. When card clicked:

    • GameCard checks activePlayerCount against game's maxPlayers
    • If valid: calls router.push(config.url) - client-side navigation via Next.js
    • If invalid: blocks navigation with warning

Two Game Systems:

Registry Games (NEW - Modular)

  • Location: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/arcade-games/
  • File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/lib/arcade/game-registry.ts
  • Examples: complement-race, memory-quiz, matching
  • Each game has: manifest (metadata), Provider (context), GameComponent (UI)
  • Games registered globally via registerGame() function

Legacy Games (OLD)

  • Location: Directly in /app/arcade/ directory
  • Examples: /app/arcade/complement-race/page.tsx
  • Currently, only complement-race is partially migrated
  • Direct URL structure: /arcade/{gameName}/page.tsx

Game Config Structure (for display):

{
  name: string,              // Display name
  fullName?: string,         // Longer name for detailed view
  description: string,       // Short description
  longDescription?: string,  // Detailed description
  icon: emoji,              // Game icon emoji
  gradient: css gradient,   // Background gradient
  borderColor: css color,   // Border color for availability
  maxPlayers: number,       // Player limit for validation
  chips?: string[],         // Feature labels
  color?: 'green'|'purple'|'blue', // Color theme
  difficulty?: string,      // Difficulty level
  available: boolean,       // Is game available
}

6. Key Components Summary

PageWithNav - Main Layout Wrapper

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/PageWithNav.tsx (lines 1-192)

Responsibilities:

  • Wraps all game/arcade pages
  • Manages GameContextNav state (mini-nav)
  • Handles player configuration dialog
  • Shows moderation notifications
  • Renders top navigation bar via AppNavBar

Key Props:

  • navTitle - Passed to GameContextNav
  • navEmoji - Passed to GameContextNav
  • gameName - Internal game name for API
  • emphasizePlayerSelection - Highlight player controls
  • onExitSession - Callback when user exits
  • onSetup, onNewGame - Game-specific callbacks
  • children - Page content

AppNavBar - Top Navigation Bar

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/AppNavBar.tsx (lines 1-625)

Variants:

  • full - Standard navigation (default for non-game pages)
  • minimal - Game navigation (auto-selected for /arcade and /games)

Minimal Nav Features:

  • Hamburger menu (left) with:
    • Site navigation (Home, Create, Guide, Games)
    • Controls (Fullscreen, Exit Arcade)
    • Abacus style dropdown
  • Centered game context (navSlot)
  • Fullscreen indicator badge

EnhancedChampionArena - Main Arcade Display

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/EnhancedChampionArena.tsx (lines 1-40)

Responsibilities:

  • Container for game selector
  • Full-height flex layout
  • Passes configuration to GameSelector

GameSelector - Game Grid

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameSelector.tsx (lines 1-112)

Responsibilities:

  • Fetches all games from registry
  • Arranges in responsive grid
  • Shows header "🎮 Available Games"
  • Renders GameCard for each game

GameCard - Individual Game Button

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/components/GameCard.tsx (lines 1-241)

Responsibilities:

  • Displays game with icon, name, description
  • Shows feature chips and player count indicator
  • Validates player count against game requirements
  • Handles click to navigate to game
  • Two variants: compact and detailed

7. State Management

GameModeContext

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/contexts/GameModeContext.tsx (lines 1-325)

Manages:

  • Local players (Map<string, Player>)
  • Active players (Set)
  • Game mode (computed from active player count)
  • Player CRUD operations (add, update, remove)

Key Features:

  • Fetches players from user's local DB via useUserPlayers()
  • Creates 4 default players if none exist
  • When in room: merges room members' players (marked as isLocal: false)
  • Syncs to room members via notifyRoomOfPlayerUpdate()

Computed Values:

  • activePlayerCount - Size of activePlayers set
  • gameMode:
    • 1 player → 'single'
    • 2 players → 'battle'
    • 3+ players → 'tournament'

useRoomData Hook

File: /Users/antialias/projects/soroban-abacus-flashcards/apps/web/src/hooks/useRoomData.ts (lines 1-450+)

Manages:

  • Current room fetching via TanStack Query
  • Socket.io real-time updates
  • Room state (members, players, game name)
  • Moderation events (kicked, banned, invitations)

Key Operations:

  • fetchCurrentRoom() - GET /api/arcade/rooms/current
  • createRoomApi() - POST /api/arcade/rooms
  • joinRoomApi() - POST /api/arcade/rooms/{id}/join
  • leaveRoomApi() - POST /api/arcade/rooms/{id}/leave
  • setRoomGame() - Updates room's gameName and gameConfig

Socket Events:

  • join-user-channel - Personal notifications
  • join-room - Subscribe to room updates
  • room-joined - Refresh when entering room
  • member-joined - When player joins
  • member-left - When player leaves
  • room-players-updated - When players change
  • Moderation events (kicked, banned, etc.)

8. Routing Summary

Current URL Structure:

/                          → Home page (Soroban Generator)
/create                    → Create flashcards
/guide                     → Tutorial guide
/games                     → Games library (external game pages)
/arcade                    → Champion Arena (main landing with game selector)
/arcade/room               → Active game display or game selection UI
/arcade/room?game={name}   → Query param for game selection (optional)
/arcade/complement-race    → OLD: Direct complement-race page (legacy)
/arcade/complement-race/practice  → Complement-race practice mode
/arcade/complement-race/sprint    → Complement-race sprint mode
/arcade/complement-race/survival  → Complement-race survival mode
/arcade/memory-quiz        → Memory quiz game page (legacy structure)

Query Parameters:

  • /arcade/room?game={gameName} - Optional game selection (parsed by GameCard)

9. Key Differences: /arcade vs /arcade/room

Aspect /arcade /arcade/room
Purpose Game selection hub Active game display or selection within room
Displays GameSelector with all games Selected game OR game selector if no game in room
Room Context Optional (can start solo) Usually in a room (fetches via useRoomData)
Navigation Click game → /arcade/room Click game → Saves to room → Displays game
GameContextNav Shows player selector Shows room info when joined
Player State Local only Local + remote (room members)
Exit Button Usually hidden Shows "Exit Session" to return to /arcade
Socket Connection Optional Always connected (in room)
Page Transition User controls Driven by room state updates

10. Planning the Merge (/arcade/room → /arcade)

Challenges to Consider:

  1. URL Consolidation:

    • /arcade/room would become a sub-path or handled by /arcade with state
    • Query param ?game={name} could drive game selection
    • Current: /arcade/room?game=complement-race
    • Could become: /arcade?game=complement-race&mode=play
  2. Route Disambiguation:

    • /arcade needs to handle: game selection display, game display, game loading
    • Same page different modes based on state
    • Or: Sub-routes like /arcade/select, /arcade/play
  3. State Layering:

    • Local game mode (solo player, GameModeContext)
    • Room state (multiplayer, useRoomData)
    • Both need to coexist
  4. Navigation Preservation:

    • Currently: GameCardrouter.push('/arcade/room?game=X')
    • After merge: Would need new logic
    • Fullscreen state must persist (uses Next.js router, not reload)
  5. PageWithNav Behavior:

    • Mini-nav shows game selection UI vs room info
    • Currently determined by roomInfo presence
    • After merge: Need same logic but one route
  6. Game Display:

    • Currently: /arcade/room fetches game from registry
    • New: /arcade would need same game registry lookup
    • Game Provider/Component rendering must work identically

Merge Strategy Options:

Option A: Single Route with Modes

/arcade
├── Mode: browse (default, show GameSelector)
├── Mode: select (game selected, show GameSelector for confirmation)
└── Mode: play (in-game, show game display)

Option B: Sub-routes

/arcade
├── /arcade (selector)
├── /arcade/play (game display)
└── /arcade/configure (player config)

Option C: Query-Parameter Driven

/arcade
├── /arcade (default - selector)
├── /arcade?game=X (game loading)
└── /arcade?game=X&playing=true (in-game)

Recommendation: Option C (Query-driven) is closest to current architecture and requires minimal changes to existing logic.