diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 21599d7..c54a7f5 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -762,6 +762,129 @@ Red flash → Stay in Normal --- -## Iteration 9: [Pending] +## Iteration 9: Phase 40.3.3 - Digit Entry + +**Date**: 2025-01-22 + +### Goals +- Allow user to type digits 1-9 when in edit mode +- Update board state with entered digits +- Cells show digits with `.user-entered` class +- Stay in edit mode after entering digit + +### Tasks Completed + +#### 1. Added setCell Method to BoardManager +**Updated useBoard.ts:** +- Added `setCell(row, col, value)` function +- Updates board string at calculated index +- Triggers re-render to display change + +**Implementation:** +```typescript +function setCell(row: number, col: number, value: string): void { + const board = boardRef.current.split(''); + const index = row * 9 + col; + board[index] = value; + boardRef.current = board.join(''); + renderBoard(); +} +``` + +#### 2. Updated BoardManager Type +- Added `setCell: (row: number, col: number, value: string) => void;` to type definition + +#### 3. Enhanced useCursor for Digit Entry +**Updated useCursor.ts:** +- Added `boardManager` parameter (BoardManager) +- In edit mode, detect digit keys (1-9) +- Call `boardManager.setCell()` to update board +- Stay in edit mode (no auto-exit) + +**Key Handling:** +```typescript +if (mode === 'edit') { + if (key === 'Escape') { + setMode('normal'); + return; + } + + if (key >= '1' && key <= '9') { + boardManager.setCell(position.row, position.col, key); + return; + } + + return; +} +``` + +#### 4. Updated useGame Integration +- Pass `board` (BoardManager) to `useCursor()` +- Three-parameter call: `useCursor(gameStatusManager, containerRef, board)` + +### Technical Details + +**Board State Flow:** +``` +User presses '5' in edit mode + ↓ +useCursor detects digit key + ↓ +Calls boardManager.setCell(row, col, '5') + ↓ +Updates boardRef.current string + ↓ +Calls renderBoard() + ↓ +SudokuRenderer.render() displays digit with .user-entered class + ↓ +User sees '5' in the cell +``` + +**Cell Classification:** +- Given cells: Have `.given` class (from initial puzzle) +- User-entered cells: Have `.user-entered` class (from renderer logic) +- Empty cells: No value displayed + +**Mode Behavior:** +- Edit mode entered with `i` on empty/user-entered cells +- Typing 1-9 places digit, stays in edit mode +- `Escape` exits to normal mode +- Can navigate to other cells and repeat + +### Build Status +- ✅ TypeScript compiles cleanly +- ✅ No type errors +- ✅ Integration complete + +### Issue 1: Golden Glow Disappearing After Digit Entry + +**Problem**: When typing a digit in edit mode, the `.editing` class (golden glow) disappeared even though we remained in edit mode. + +**Root Cause**: `setCell()` calls `renderBoard()` which rebuilds the entire DOM, wiping out the cursor highlight classes. + +**Solution**: Moved `boardRendered` counter from `useGame` to `useBoard` +- Added `boardRendered` state to `useBoard` hook +- Increment counter at end of `renderBoard()` function +- Export `boardRendered` from `useBoard` +- `useCursorRender` depends on `boardRendered` and re-applies cursor classes after DOM rebuild + +**Changes**: +- `useBoard.ts`: Added `useState` for `boardRendered`, increment in `renderBoard()` +- `useBoard/types.ts`: Added `boardRendered: number` to `BoardManager` +- `useGame.ts`: Removed local `boardRendered` state, use board's `boardRendered` instead +- Removed manual `setBoardRendered` calls from `useGame` (automatic now) + +**Result**: ✅ Golden glow persists when typing digits in edit mode + +### Next Steps +- Phase 40.3.4: Add erase functionality (x, Backspace, Delete) +- Phase 40.3.5: Add replace mode (r) +- Validation and conflict detection +- Win condition checking + +--- + +## Iteration 10: [Pending] *To be filled as implementation progresses...* diff --git a/src/useBoard/types.ts b/src/useBoard/types.ts index 643fde9..6fa42a2 100644 --- a/src/useBoard/types.ts +++ b/src/useBoard/types.ts @@ -3,6 +3,8 @@ import type { Difficulty } from '../types'; export type BoardManager = { containerRef: RefObject; + boardRendered: number; generateBoard: (difficulty: Difficulty) => void; renderBoard: () => void; + setCell: (row: number, col: number, value: string) => void; }; diff --git a/src/useBoard/useBoard.ts b/src/useBoard/useBoard.ts index 50952eb..81f5df7 100644 --- a/src/useBoard/useBoard.ts +++ b/src/useBoard/useBoard.ts @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { SudokuGenerator } from './SudokuGenerator'; import { SudokuRenderer } from './SudokuRenderer'; import type { Difficulty } from './SudokuGenerator'; @@ -10,6 +10,7 @@ export function useBoard() { const boardRef = useRef(''); const solutionRef = useRef(''); const initialBoardRef = useRef(''); + const [boardRendered, setBoardRendered] = useState(0); function generateBoard(difficulty: Difficulty = 'easy') { if (!generatorRef.current) { @@ -41,6 +42,16 @@ export function useBoard() { } else { rendererRef.current.renderEmpty(); } + + setBoardRendered(prev => prev + 1); + } + + function setCell(row: number, col: number, value: string): void { + const board = boardRef.current.split(''); + const index = row * 9 + col; + board[index] = value; + boardRef.current = board.join(''); + renderBoard(); } return { @@ -50,7 +61,9 @@ export function useBoard() { boardRef, solutionRef, initialBoardRef, + boardRendered, generateBoard, renderBoard, + setCell, }; } diff --git a/src/useCursor/useCursor.ts b/src/useCursor/useCursor.ts index 51af15f..fba60f8 100644 --- a/src/useCursor/useCursor.ts +++ b/src/useCursor/useCursor.ts @@ -1,9 +1,10 @@ import { useState, useEffect } from 'react'; import type { Coord, CursorManager, CursorMode } from './types'; import type { GameStatusManager } from '../useGameStatus/types'; +import type { BoardManager } from '../useBoard/types'; import { useVimMotions } from './useVimMotions'; -export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject) { +export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject, boardManager: BoardManager) { const [position, setPosition] = useState({ row: 0, col: 0 }); const [mode, setMode] = useState('normal'); const { gameStatus } = gameStatusManager; @@ -97,7 +98,14 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re if (mode === 'edit') { if (key === 'Escape') { setMode('normal'); + return; } + + if (key >= '1' && key <= '9') { + boardManager.setCell(position.row, position.col, key); + return; + } + return; } diff --git a/src/useGame.ts b/src/useGame.ts index 9f576a7..f694519 100644 --- a/src/useGame.ts +++ b/src/useGame.ts @@ -8,15 +8,14 @@ import { useCursorRender } from './useCursor/useCursorRender'; export function useGame(options: GameOptions = {}, platformHook?: unknown): GameManager { const { difficulty = 'easy' } = options; const board = useBoard(); - const { containerRef, renderBoard, generateBoard } = board; + const { containerRef, renderBoard, generateBoard, boardRendered } = board; const gameStatusManager = useGameStatus(); const { gameStatus } = gameStatusManager; const [keyLog, setKeyLog] = useState<{ key: string; timestamp: number }[]>([]); - const [boardRendered, setBoardRendered] = useState(0); - const cursor = useCursor(gameStatusManager, containerRef); + const cursor = useCursor(gameStatusManager, containerRef, board); const cursorPosition = cursor.position(); const cursorMode = cursor.mode(); @@ -24,17 +23,14 @@ export function useGame(options: GameOptions = {}, platformHook?: unknown): Game useEffect(() => { renderBoard(); - setBoardRendered(prev => prev + 1); }, []); useEffect(() => { if (gameStatus === 'started') { generateBoard(difficulty); renderBoard(); - setBoardRendered(prev => prev + 1); } else if (gameStatus === 'waiting') { renderBoard(); - setBoardRendered(prev => prev + 1); } }, [gameStatus, difficulty]);