Files
the-last-startyper/AGENTS.md
T

6.4 KiB

Agent Development Guidelines - The Last StarTyper

React 19 Best Practices

⚠️ CRITICAL RULES - NO EXCEPTIONS

  1. NO useCallback - React 19's compiler handles optimization automatically
  2. NO useMemo - React 19's compiler handles memoization automatically
  3. NO functions as useEffect dependencies - Only use primitive values or state values

Example - What NOT to do:

// ❌ BAD - Uses useCallback
const handleClick = useCallback(() => {
  doSomething();
}, [dependency]);

// ❌ BAD - Function as dependency
useEffect(() => {
  handleClick();
}, [handleClick]); // Never use functions as dependencies!

Example - What to do instead:

// ✅ GOOD - Plain function
const handleClick = () => {
  doSomething();
};

// ✅ GOOD - Only value dependencies
useEffect(() => {
  handleClick();
}, [someStateValue]); // Only primitive/state values

Event Handler Best Practices

Avoid Stale Closures

When passing objects with methods to hooks, access properties directly inside handlers to avoid stale closures:

// ❌ BAD - Destructuring creates stale references
const { gameStatus, startGame } = gameManager;
useEffect(() => {
  if (gameStatus === 'ready') startGame();
}, [gameStatus]);

// ✅ GOOD - Direct access ensures fresh values
useEffect(() => {
  if (gameManager.gameStatus === 'ready') {
    gameManager.startGame();
  }
}, [gameManager.gameStatus]);

Keyboard Event Handling

Always prevent default behavior when handling game controls:

const handler = (ev: KeyboardEvent) => {
  if (ev.code === "Space") {
    ev.preventDefault(); // Prevent button activation
    // handle space key
  }
  if (ev.key === "Escape") {
    ev.preventDefault(); // Prevent dialog close
    // handle escape key
  }
};

UI/UX Considerations

Button Focus Management

Buttons retain focus after clicking, which can interfere with keyboard controls:

// Always blur buttons after clicking to prevent Space key from re-triggering
onClick={(e) => {
  handleAction();
  e.currentTarget.blur(); // Remove focus
}}

Hook Architecture

State Management

  • Use refs for values that need persistence without re-renders
  • Use state only for values that trigger UI updates
  • Keep game loop data in refs to avoid unnecessary renders
// Game loop data - use refs
const lettersRef = useRef<FallingLetter[]>([]);
const playerXRef = useRef<number>(400);

// UI state - use useState
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
const [score, setScore] = useState(0);

Effect Dependencies

Only include values that actually trigger the effect logic:

// ✅ GOOD - Only necessary dependencies
useEffect(() => {
  gameManager.setGameMode(gameMode);
}, [gameMode]); // Only the value that changes

// ❌ BAD - Including objects/functions
useEffect(() => {
  gameManager.setGameMode(gameMode);
}, [gameMode, gameManager]); // gameManager is unnecessary

Common Pitfalls & Solutions

Problem: Game state resets unexpectedly

Cause: Multiple state updates or function calls in wrong order Solution: Check for race conditions, ensure proper state checks before updates

Problem: Keyboard controls not working

Cause: DOM element has focus (usually buttons) Solution: Blur active elements, use preventDefault()

Problem: Stale values in event handlers

Cause: Closure over old values when using destructuring Solution: Access object properties directly inside handlers

Problem: Infinite re-renders

Cause: Functions as dependencies or state updates in render Solution: Remove function dependencies, move state updates to effects

Game-Specific Patterns

Game Mode Changes

  • Only quit active games when changing modes
  • Don't reset state unnecessarily
  • Preserve user context when possible
const changeGameMode = (mode: GameMode) => {
  setGameModeState(mode);
  // Only quit if actively playing
  if (gameStatus === 'started') {
    quitGame();
  }
};

Animation Frame Loops

  • Store animation frame IDs in refs
  • Always cleanup on unmount
  • Use refs for loop data to avoid re-renders
const gameLoopRef = useRef<number | null>(null);

const startLoop = () => {
  const loop = (time: number) => {
    // Update game state
    gameLoopRef.current = requestAnimationFrame(loop);
  };
  gameLoopRef.current = requestAnimationFrame(loop);
};

const stopLoop = () => {
  if (gameLoopRef.current) {
    cancelAnimationFrame(gameLoopRef.current);
    gameLoopRef.current = null;
  }
};

Testing & Debugging

Browser Focus Issues

When debugging keyboard input issues:

  1. Check if any DOM element has focus (inspect element, check document.activeElement)
  2. Test with blur() on suspected elements
  3. Try unfocusing/refocusing browser window - if this fixes it, it's a focus issue

State Synchronization

When game state seems out of sync:

  1. Check for multiple state sources (refs vs state)
  2. Verify effect dependencies are correct
  3. Look for race conditions in async operations
  4. Add console logs at state transitions

Code Quality Standards

Type Safety

  • Always use TypeScript types
  • Avoid any type
  • Define explicit return types for hooks

Naming Conventions

  • Hooks: use[Feature] (e.g., useGame, useBoard)
  • Event handlers: handle[Event] (e.g., handleTypedLetter)
  • Refs: [name]Ref (e.g., gameLoopRef, containerRef)
  • Managers: [feature]Manager (e.g., gameStatusManager)

File Organization

src/
├── use[Feature]/
│   ├── index.ts        # Public exports
│   └── use[Feature].ts # Hook implementation
├── types.ts            # All TypeScript types
└── index.ts            # Package exports

Performance Considerations

Minimize Re-renders

  • Use refs for non-UI state
  • Keep component trees shallow
  • Avoid passing new objects/arrays as props

Canvas Rendering

  • Only redraw when necessary
  • Use requestAnimationFrame for smooth animations
  • Clear only changed regions when possible

Remember

  1. React 19 = No useCallback/useMemo
  2. No functions as dependencies
  3. Blur buttons after clicking
  4. preventDefault on game controls
  5. Access object properties directly in handlers
  6. Use refs for game loop data
  7. Test focus issues by unfocusing browser

When in doubt, keep it simple. React 19's compiler will optimize for you.