Files
soroban-abacus-flashcards/apps/web/docs/MATCHING_PAIRS_MIGRATION_PLAN.md
Thomas Hallock 7c33d0246f fix: prevent undefined displayRules error in worksheet generator
Fixes production error "Cannot read properties of undefined (reading 'carryBoxes')"
that occurred when users tried to adjust difficulty settings.

Root cause: displayRules was undefined for new users or users with old V1 config
in database. Difficulty adjustment buttons accessed displayRules.carryBoxes without
checking if displayRules existed first.

Changes:
- AdditionWorksheetClient: Initialize displayRules with defaults when missing
- ConfigPanel: Use null-coalescing operators instead of non-null assertions
- ConfigPanel: Add error logging when required fields are missing
- NEW: WorksheetErrorBoundary component to catch all errors in worksheet page
- page.tsx: Wrap client component with error boundary

This ensures users see helpful error messages instead of blank pages,
and never need to open the browser console to understand what went wrong.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 13:01:54 -06:00

17 KiB

Matching Pairs Battle - Migration to Modular Game System

Status: Planning Phase Target Version: v4.2.0 Created: 2025-01-16 Game Name: matching


Executive Summary

This document outlines the migration plan for Matching Pairs Battle (aka Memory Pairs Challenge) from the legacy dual-location architecture to the modern modular game system using the Game SDK.

Key Complexity Factors:

  • Dual Location: Game exists in BOTH /src/app/arcade/matching/ AND /src/app/games/matching/
  • Partial Migration: RoomMemoryPairsProvider already uses useArcadeSession but not in modular format
  • Turn-Based Multiplayer: More complex than memory-quiz (requires turn validation, player ownership)
  • Rich UI State: Hover state, animations, mismatch feedback, pause/resume
  • Existing Tests: Has playerMetadata test that must continue to pass

Current File Structure Analysis

Location 1: /src/app/arcade/matching/

Components (4 files):

  • components/GameCard.tsx
  • components/PlayerStatusBar.tsx
  • components/ResultsPhase.tsx
  • components/SetupPhase.tsx
  • components/EmojiPicker.tsx
  • components/GamePhase.tsx
  • components/MemoryPairsGame.tsx
  • components/__tests__/EmojiPicker.test.tsx

Context (4 files):

  • context/MemoryPairsContext.tsx - Context definition and hook
  • context/LocalMemoryPairsProvider.tsx - Local mode provider (DEPRECATED)
  • context/RoomMemoryPairsProvider.tsx - Room mode provider (PARTIALLY MIGRATED)
  • context/types.ts - Type definitions
  • context/index.ts - Re-exports
  • context/__tests__/playerMetadata-userId.test.ts - Test for player ownership

Utils (3 files):

  • utils/cardGeneration.ts - Card generation logic
  • utils/gameScoring.ts - Scoring calculations
  • utils/matchValidation.ts - Match validation logic

Page:

  • page.tsx - Route handler for /arcade/matching

Location 2: /src/app/games/matching/

Components (6 files - DUPLICATES):

  • components/GameCard.tsx
  • components/PlayerStatusBar.tsx
  • components/ResultsPhase.tsx
  • components/SetupPhase.tsx
  • components/EmojiPicker.tsx
  • components/GamePhase.tsx
  • components/MemoryPairsGame.tsx
  • components/__tests__/EmojiPicker.test.tsx
  • components/PlayerStatusBar.stories.tsx - Storybook story

Context (2 files):

  • context/MemoryPairsContext.tsx
  • context/types.ts

Utils (3 files - DUPLICATES):

  • utils/cardGeneration.ts
  • utils/gameScoring.ts
  • utils/matchValidation.ts

Page:

  • page.tsx - Route handler for /games/matching (legacy?)

Shared Components

  • /src/components/matching/HoverAvatar.tsx - Networked presence component
  • /src/components/matching/MemoryGrid.tsx - Grid layout component

Validator

  • /src/lib/arcade/validation/MatchingGameValidator.ts - Already exists and comprehensive (570 lines)

Configuration

  • Already in GAMES_CONFIG as 'battle-arena' (maps to internal name 'matching')
  • Config type: MatchingGameConfig in /src/lib/arcade/game-configs.ts

Migration Complexity Assessment

Complexity: HIGH (8/10)

Reasons:

  1. Dual Locations: Must consolidate two separate implementations
  2. Partial Migration: RoomMemoryPairsProvider uses useArcadeSession but not in modular format
  3. Turn-Based Logic: Player ownership validation, turn switching
  4. Rich State: Hover state, animations, pause/resume, mismatch feedback
  5. Large Validator: 570 lines (vs 350 for memory-quiz)
  6. More Components: 7 components + 2 shared (vs 7 for memory-quiz)
  7. Tests: Must maintain playerMetadata test coverage

Similar To: Memory Quiz migration (same pattern)

Unique Challenges:

  • Consolidating duplicate files from two locations
  • Deciding which version of duplicates is canonical
  • Handling /games/matching/ route (deprecate or redirect?)
  • More complex multiplayer state (turn order, player ownership)

Phase 1: Pre-Migration Audit

Goal: Understand current state and identify discrepancies

Tasks:

  • Map all files in both locations
  • Compare duplicate files to identify differences (e.g., diff /src/app/arcade/matching/components/GameCard.tsx /src/app/games/matching/components/GameCard.tsx)
  • Identify which location is canonical (likely /src/app/arcade/matching/ based on RoomProvider)
  • Verify validator completeness (already done - looks comprehensive)
  • Check for references to /games/matching/ route

Deliverables:

  • File comparison report
  • Decision: Which duplicate files to keep
  • List of files to delete

Phase 2: Create Modular Game Definition

Goal: Define game in registry following SDK pattern

Tasks:

  1. Create /src/arcade-games/matching/index.ts with defineGame()
  2. Register in /src/lib/arcade/game-registry.ts
  3. Update /src/lib/arcade/validators.ts to import from new location
  4. Add type inference to /src/lib/arcade/game-configs.ts

Template:

// /src/arcade-games/matching/index.ts
import type { GameManifest, GameConfig } from "@/lib/arcade/game-sdk/types";
import { defineGame } from "@/lib/arcade/game-sdk";
import { MatchingProvider } from "./Provider";
import { MemoryPairsGame } from "./components/MemoryPairsGame";
import { matchingGameValidator } from "./Validator";
import { validateMatchingConfig } from "./config-validation";
import type { MatchingConfig, MatchingState, MatchingMove } from "./types";

const manifest: GameManifest = {
  name: "matching",
  displayName: "Matching Pairs Battle",
  icon: "⚔️",
  description: "Multiplayer memory battle with friends",
  longDescription:
    "Battle friends in epic memory challenges. Match pairs faster than your opponents in this exciting multiplayer experience.",
  maxPlayers: 4,
  difficulty: "Intermediate",
  chips: ["👥 Multiplayer", "🎯 Strategic", "🏆 Competitive"],
  color: "purple",
  gradient: "linear-gradient(135deg, #e9d5ff, #ddd6fe)",
  borderColor: "purple.200",
  available: true,
};

const defaultConfig: MatchingConfig = {
  gameType: "abacus-numeral",
  difficulty: 6,
  turnTimer: 30,
};

export const matchingGame = defineGame<
  MatchingConfig,
  MatchingState,
  MatchingMove
>({
  manifest,
  Provider: MatchingProvider,
  GameComponent: MemoryPairsGame,
  validator: matchingGameValidator,
  defaultConfig,
  validateConfig: validateMatchingConfig,
});

Files Modified:

  • /src/arcade-games/matching/index.ts (new)
  • /src/lib/arcade/game-registry.ts (add import + register)
  • /src/lib/arcade/validators.ts (update import path)
  • /src/lib/arcade/game-configs.ts (add type inference)

Phase 3: Move and Update Validator

Goal: Move validator to modular game directory

Tasks:

  1. Move /src/lib/arcade/validation/MatchingGameValidator.ts/src/arcade-games/matching/Validator.ts
  2. Update imports to use local types from ./types instead of importing from game-configs (avoid circular deps)
  3. Verify all move types are handled
  4. Check getInitialState() accepts all config fields

Note: Validator looks comprehensive already - likely minimal changes needed

Files Modified:

  • /src/arcade-games/matching/Validator.ts (moved)
  • Update imports in validator

Phase 4: Consolidate and Move Types

Goal: Create SDK-compatible type definitions in modular location

Tasks:

  1. Compare types from both locations:
    • /src/app/arcade/matching/context/types.ts
    • /src/app/games/matching/context/types.ts
  2. Create /src/arcade-games/matching/types.ts with:
    • MatchingConfig extends GameConfig
    • MatchingState (from MemoryPairsState)
    • MatchingMove union type (7 move types: FLIP_CARD, START_GAME, CLEAR_MISMATCH, GO_TO_SETUP, SET_CONFIG, RESUME_GAME, HOVER_CARD)
  3. Ensure compatibility with validator expectations
  4. Fix any {}Record<string, never> warnings

Move Types:

export interface MatchingConfig extends GameConfig {
  gameType: "abacus-numeral" | "complement-pairs";
  difficulty: 6 | 8 | 12 | 15;
  turnTimer: number;
}

export interface MatchingState {
  // Core game data
  cards: GameCard[];
  gameCards: GameCard[];
  flippedCards: GameCard[];

  // Config
  gameType: "abacus-numeral" | "complement-pairs";
  difficulty: 6 | 8 | 12 | 15;
  turnTimer: number;

  // Progression
  gamePhase: "setup" | "playing" | "results";
  currentPlayer: string;
  matchedPairs: number;
  totalPairs: number;
  moves: number;
  scores: Record<string, number>;
  activePlayers: string[];
  playerMetadata: Record<string, PlayerMetadata>;
  consecutiveMatches: Record<string, number>;

  // Timing
  gameStartTime: number | null;
  gameEndTime: number | null;
  currentMoveStartTime: number | null;
  timerInterval: NodeJS.Timeout | null;

  // UI state
  celebrationAnimations: CelebrationAnimation[];
  isProcessingMove: boolean;
  showMismatchFeedback: boolean;
  lastMatchedPair: [string, string] | null;

  // Pause/Resume
  originalConfig?: {
    gameType: "abacus-numeral" | "complement-pairs";
    difficulty: 6 | 8 | 12 | 15;
    turnTimer: number;
  };
  pausedGamePhase?: "setup" | "playing" | "results";
  pausedGameState?: PausedGameState;

  // Hover state
  playerHovers: Record<string, string | null>;
}

export type MatchingMove =
  | {
      type: "FLIP_CARD";
      playerId: string;
      userId: string;
      data: { cardId: string };
    }
  | {
      type: "START_GAME";
      playerId: string;
      userId: string;
      data: {
        cards: GameCard[];
        activePlayers: string[];
        playerMetadata: Record<string, PlayerMetadata>;
      };
    }
  | {
      type: "CLEAR_MISMATCH";
      playerId: string;
      userId: string;
      data: Record<string, never>;
    }
  | {
      type: "GO_TO_SETUP";
      playerId: string;
      userId: string;
      data: Record<string, never>;
    }
  | {
      type: "SET_CONFIG";
      playerId: string;
      userId: string;
      data: { field: "gameType" | "difficulty" | "turnTimer"; value: any };
    }
  | {
      type: "RESUME_GAME";
      playerId: string;
      userId: string;
      data: Record<string, never>;
    }
  | {
      type: "HOVER_CARD";
      playerId: string;
      userId: string;
      data: { cardId: string | null };
    };

Files Created:

  • /src/arcade-games/matching/types.ts

Phase 5: Create Unified Provider

Goal: Convert RoomMemoryPairsProvider to modular Provider using SDK

Tasks:

  1. Copy RoomMemoryPairsProvider as starting point (already uses useArcadeSession)
  2. Create /src/arcade-games/matching/Provider.tsx
  3. Remove dependency on MemoryPairsContext (will export its own hook)
  4. Update imports to use local types
  5. Ensure all action creators are present:
    • startGame
    • flipCard
    • resetGame
    • setGameType
    • setDifficulty
    • setTurnTimer
    • goToSetup
    • resumeGame
    • hoverCard
  6. Verify config persistence (nested under gameConfig.matching)
  7. Export useMatching hook

Key Changes:

  • Import types from ./types not from context
  • Export hook: export function useMatching() { return useContext(MatchingContext) }
  • Ensure hooks called before early returns (React rules)

Files Created:

  • /src/arcade-games/matching/Provider.tsx

Phase 6: Consolidate and Move Components

Goal: Move components to modular location, choosing canonical versions

Decision Process (for each component):

  1. If files are identical → pick either (prefer /src/app/arcade/matching/)
  2. If files differ → manually merge, keeping best of both
  3. Update imports to use new Provider: from '@/arcade-games/matching/Provider'
  4. Fix styled-system import paths (4 levels: ../../../../styled-system/css)

Components to Move:

  • GameCard.tsx
  • PlayerStatusBar.tsx
  • ResultsPhase.tsx
  • SetupPhase.tsx
  • EmojiPicker.tsx
  • GamePhase.tsx
  • MemoryPairsGame.tsx

Shared Components (leave in place):

  • /src/components/matching/HoverAvatar.tsx
  • /src/components/matching/MemoryGrid.tsx

Tests:

  • Move test to /src/arcade-games/matching/components/__tests__/EmojiPicker.test.tsx

Files Created:

  • /src/arcade-games/matching/components/*.tsx (7 files)
  • /src/arcade-games/matching/components/__tests__/EmojiPicker.test.tsx

Phase 7: Move Utility Functions

Goal: Consolidate utils in modular location

Tasks:

  1. Compare utils from both locations (likely identical)
  2. Move to /src/arcade-games/matching/utils/
    • cardGeneration.ts
    • gameScoring.ts
    • matchValidation.ts
  3. Update imports in components and validator

Files Created:

  • /src/arcade-games/matching/utils/*.ts (3 files)

Phase 8: Update Routes and Clean Up

Goal: Update page routes and delete legacy files

Tasks:

Route Updates:

  1. /src/app/arcade/matching/page.tsx - Replace with redirect to /arcade (local mode deprecated)
  2. /src/app/games/matching/page.tsx - Replace with redirect to /arcade (legacy route)
  3. Remove from GAMES_CONFIG in /src/components/GameSelector.tsx
  4. Remove from GAME_TYPE_TO_NAME in /src/app/arcade/room/page.tsx
  5. Update /src/lib/arcade/validation/types.ts imports (if referencing old types)

Delete Legacy Files (~30 files):

  • /src/app/arcade/matching/components/ (7 files + 1 test)
  • /src/app/arcade/matching/context/ (5 files + 1 test)
  • /src/app/arcade/matching/utils/ (3 files)
  • /src/app/games/matching/components/ (7 files + 1 test + 1 story)
  • /src/app/games/matching/context/ (2 files)
  • /src/app/games/matching/utils/ (3 files)
  • /src/lib/arcade/validation/MatchingGameValidator.ts (moved)

Files Modified:

  • /src/app/arcade/matching/page.tsx (redirect)
  • /src/app/games/matching/page.tsx (redirect)
  • /src/components/GameSelector.tsx (remove from GAMES_CONFIG)
  • /src/app/arcade/room/page.tsx (remove from GAME_TYPE_TO_NAME)

Testing Checklist

After migration, verify:

  • Type checking passes (npm run type-check)
  • Format/lint passes (npm run pre-commit)
  • EmojiPicker test passes
  • PlayerMetadata test passes
  • Game loads in room mode
  • Game selector shows one "Matching Pairs Battle" button
  • Settings persist when changed in setup
  • Turn-based gameplay works (only current player can flip)
  • Card matching works (both abacus-numeral and complement-pairs)
  • Pause/Resume works
  • Hover state shows for other players
  • Mismatch feedback displays correctly
  • Results phase calculates scores correctly

Migration Steps Summary

8 Phases:

  1. Pre-Migration Audit - Compare duplicate files
  2. Create Modular Game Definition - Registry + types
  3. Move and Update Validator - Move to new location
  4. Consolidate and Move Types - SDK-compatible types
  5. Create Unified Provider - Room-only provider
  6. Consolidate and Move Components - Choose canonical versions
  7. Move Utility Functions - Consolidate utils
  8. Update Routes and Clean Up - Delete legacy files

Estimated Effort: 4-6 hours (larger than memory-quiz due to dual locations and more complexity)


Key Differences from Memory Quiz Migration

  1. Dual Locations: Must consolidate two separate implementations
  2. More Complex: Turn-based multiplayer vs cooperative team play
  3. Partial Migration: RoomProvider already uses useArcadeSession
  4. More Components: 7 game components + 2 shared
  5. Existing Tests: Must maintain test coverage
  6. Two Routes: Both /arcade/matching and /games/matching exist

Risks and Mitigation

Risk 1: File Divergence

Risk: Duplicate files may have different features/fixes Mitigation: Manually diff each duplicate pair, merge best of both

Risk 2: Test Breakage

Risk: PlayerMetadata test may break during migration Mitigation: Run tests frequently, update test if needed

Risk 3: Turn Logic Complexity

Risk: Player ownership and turn validation is complex Mitigation: Validator already handles this - trust existing logic

Risk 4: Unknown Dependencies

Risk: Other parts of codebase may depend on /games/matching/ Mitigation: Search for imports before deletion: grep -r "from.*games/matching" src/


Post-Migration Verification

After completing all phases:

  1. Run full test suite
  2. Manual testing:
    • Create room
    • Select "Matching Pairs Battle"
    • Configure settings (verify persistence)
    • Start game with multiple players
    • Play several turns (verify turn order)
    • Pause and resume
    • Complete game (verify results)
  3. Verify no duplicate game buttons
  4. Check browser console for errors
  5. Verify settings load correctly on page refresh

References

  • Memory Quiz Migration Plan: docs/MEMORY_QUIZ_MIGRATION_PLAN.md
  • Game Migration Playbook: docs/GAME_MIGRATION_PLAYBOOK.md
  • Game SDK Documentation: .claude/GAME_SDK_DOCUMENTATION.md
  • Settings Persistence: .claude/GAME_SETTINGS_PERSISTENCE.md