Files
vim-sudoku/IMPLEMENTATION.md
T
pastilhas bc8eeaf400 feat: implement digit entry in edit mode (Phase 40.3.3)
- Add setCell method to useBoard to update board state
- Handle digit keys 1-9 in edit mode in useCursor
- Move boardRendered counter to useBoard for proper cursor re-rendering
- Fix golden glow disappearing after digit entry
- Digits display with .user-entered class via renderer
2025-10-22 19:49:12 +01:00

891 lines
26 KiB
Markdown

# vim-sudoku Unified API Implementation Log
This document tracks the step-by-step implementation of the unified API architecture for vim-sudoku.
## Overview
We are porting vim-sudoku from the old architecture (src_old) to follow the unified API patterns established by vim-maze. This enables consistent architecture across all VIMazing games and better platform integration.
---
## Agent Rules
These are the key principles and constraints that guide this implementation:
### React 19 Optimization
- **No `useCallback` or `useMemo`**: React 19's compiler automatically optimizes, making these hooks unnecessary
- Use plain functions instead of wrapped callbacks
- React will handle memoization automatically
- Cleaner, more readable code
### Code Conventions
- **No comments**: Code should be self-documenting unless explicitly requested
- **Type-first approach**: Define types before implementation
- **Explicit imports**: Always import types with `import type`
- **Return type annotations**: Use `ReturnType<typeof hookName>` for hook return types
### Architecture Patterns
- **Hook composition**: Small, focused hooks composed together
- **Ref-based state**: Use refs for non-reactive state, useState for reactive
- **Module organization**: Each hook module has types.ts, implementation, and index.ts
- **Follow vim-maze patterns**: Maintain consistency across games
### Documentation
- **Update IMPLEMENTATION.md**: After every iteration
- **Clear commit messages**: Describe what, why, and how
- **Track issues and solutions**: Document problems encountered and fixes
---
## Iteration 1: Preparation & Planning
**Date**: 2025-01-22
### Goals
- Review existing implementations (vim-maze, src_old, src_failed)
- Create implementation plan
- Prepare example app for new API
### Tasks Completed
#### 1. Reviewed Architecture
- Analyzed vim-maze unified API implementation
- Examined src_old working implementation
- Identified src_failed attempt issues
- Documented key differences between vim-maze and vim-sudoku
#### 2. Created Implementation Plan
- Created `IMPLEMENTATION_PLAN.md` with 7 phases (numbered x10 for flexibility)
- Defined file structure matching vim-maze patterns
- Documented key design decisions
- Established type mappings between vim-maze and vim-sudoku
- Listed success criteria
#### 3. Prepared Example App (Phase 5)
- Updated `example/src/App.tsx` to match vim-maze structure
- Simplified to desktop-only (no mobile support)
- Added score metrics display (time, keystrokes, final score)
- Updated game status messages for sudoku context
- Changed navigation instructions (hjkl + i for edit mode)
- Verified `useKeyBindings` already matches vim-maze
### File Changes
```
vim-sudoku/
├── IMPLEMENTATION_PLAN.md [CREATED] - Detailed implementation roadmap
├── IMPLEMENTATION.md [CREATED] - This file, tracking progress
├── example/src/App.tsx [MODIFIED] - Simplified, sudoku-focused UI
└── src/ → src_failed/ [RENAMED] - Previous failed attempt archived
```
### Key Decisions
1. **No Mobile Support**: Sudoku with vim bindings requires keyboard, mobile not practical
2. **Simple Metrics**: Start with time, keystrokes, and final score (no efficiency yet)
3. **Phase Numbering**: Use x10 numbering (10, 20, 30...) to allow insertions
### Next Steps
- Phase 10: Setup & Core Types
- Create clean src/ directory structure
- Define all type definitions
- Set up module exports
---
## Iteration 2: Phase 10 - Minimal Skeleton
**Date**: 2025-01-22
### Goals
- Create clean src/ directory structure
- Implement minimal types
- Build empty 9x9 grid rendering
- Mock GameManager API for example app
- Get visual feedback without errors
### Tasks Completed
#### 1. Created Core Structure
```
src/
├── game.css [COPIED] - From src_old
├── index.ts [CREATED] - Main exports
├── types.ts [CREATED] - Basic type definitions
├── useGame.ts [CREATED] - Orchestrator with mocks
└── useBoard/
└── useBoard.ts [CREATED] - Empty grid rendering
```
#### 2. Implemented Types (src/types.ts)
- `Difficulty` - Puzzle difficulty levels
- `GameStatus` - Game state machine states
- `GameOptions` - useGame configuration
- `GameManager` - Full API interface (mocked for now)
- `KeyLogEntry` - Keystroke tracking
#### 3. Implemented useBoard (src/useBoard/useBoard.ts)
- `containerRef` - DOM reference
- `renderBoard()` - Renders empty 9x9 sudoku grid
- Creates proper HTML structure with `sudoku-grid` and `sudoku-cell` classes
- Sets data attributes (data-r, data-c) for cell identification
#### 4. Implemented useGame (src/useGame.ts)
- Calls `useBoard()` for grid rendering
- Returns full `GameManager` interface
- Real implementations:
- `gameStatus` state management
- `startGame`, `togglePause`, `quitGame`
- `keyLog` tracking with `clearKeyLog`, `getKeyLog`
- Mocked implementations:
- `cursor` (empty object)
- `scoreManager` (zeros and null)
- Supports optional `platformHook` parameter
#### 5. Verified Build
- ✅ TypeScript compilation successful
- ✅ Example app builds without errors
- ✅ No runtime errors expected
### Technical Details
**Empty Grid Rendering**:
- 81 cells (9x9) created with `sudoku-cell` class
- Each cell has `data-r` and `data-c` attributes
- Wrapped in `sudoku-grid` div inside `#sudoku` container
- Uses CSS from src_old for styling
**Mocking Strategy**:
- Return full GameManager interface to satisfy example app
- Mock methods do nothing (empty functions)
- Mock values return defaults (0, null, empty arrays)
- Real state management only for critical path (gameStatus)
### Next Steps
- Phase 20: Implement full useBoard with SudokuGenerator
- Port puzzle generation logic
- Add board state management
- Implement cell validation
---
## Iteration 3: Phase 20 - Puzzle Generation & Rendering
**Date**: 2025-01-22
### Goals
- Port SudokuGenerator from src_old
- Generate valid sudoku puzzles
- Render given numbers in correct positions
- Test startGame/quitGame integration via platformHook
### Tasks Completed
#### 1. Ported SudokuGenerator
- Copied `src_old/lib/Sudoku.ts` → `src/useBoard/SudokuGenerator.ts`
- Full puzzle generation algorithm from robatron/sudoku.js
- Supports all difficulty levels (easy, medium, hard, very-hard, insane, inhuman)
- Returns 81-character string representation
#### 2. Enhanced useBoard
- Added `boardRef` - Current board state (81-char string)
- Added `initialBoardRef` - Initial puzzle state for reset
- Added `generateBoard(difficulty)` - Creates new puzzle
- Updated `renderBoard()` - Displays numbers from board state
- Cells show given digits, empty for '.' characters
#### 3. Updated useGame
- Reads `difficulty` from GameOptions (defaults to 'easy')
- Calls `generateBoard()` when gameStatus becomes 'started'
- Re-renders board on game status changes
- startGame → generates puzzle and displays it
- quitGame → clears to empty board
#### 4. Game Flow Integration
- **Waiting state**: Empty 9x9 grid displayed
- **Press Space**: Triggers startGame via platformHook
- **Started state**: New puzzle generated with ~62 givens (easy mode)
- **Press Q**: Triggers quitGame, returns to empty grid
### Technical Details
**Board Representation**:
```typescript
boardRef.current = "52...8.4....7...4......6.....8.....3.9....8..1...2.......2....6...3....7..8...9."
// 81 characters: '1'-'9' for givens, '.' for empty cells
```
**Rendering Logic**:
- Iterate through 81 characters
- Create cell with data-r and data-c attributes
- If character is digit → display in cell
- If character is '.' → leave cell empty
- Uses existing CSS classes from game.css
**Game Status Flow**:
```
waiting (empty grid)
↓ [space pressed]
started (puzzle generated & rendered)
↓ [q pressed]
waiting (empty grid)
```
### Testing Notes
- ✅ TypeScript compiles cleanly
- ✅ Example app builds successfully
- ✅ Easy difficulty generates ~62 given numbers
- ✅ platformHook integration working (startGame/quitGame)
- Ready for browser testing with actual puzzle display
### Next Steps
- Phase 30: Implement useGameStatus hook
- Proper state machine with transitions
- Handle paused state
- Integrate with scoring
---
## Iteration 4: Phase 21 - Better Generation Algorithm
**Date**: 2025-01-22
### Goals
- Replace Robatron's algorithm with pattern-based generation
- Separate generator and renderer into clean classes
- Always generate valid, solvable puzzles
- Store both puzzle and solution references
- Port unit tests for validation
### Tasks Completed
#### 1. Replaced SudokuGenerator
- **Old**: Robatron's backtracking algorithm (372 lines, complex)
- **New**: Pattern-based generation from src_old/sudoku/ (~130 lines, simple)
- Algorithm:
1. Generate fully solved board using base pattern + shuffles
2. Validate complete board (sum checks for rows/cols/boxes)
3. Remove X cells based on difficulty to create puzzle
4. Keep solution reference (no solving needed!)
#### 2. Implemented Class-Based Architecture
**SudokuGenerator class**:
- `generate(difficulty)` - Creates puzzle + solution
- `getPuzzle()` - Returns puzzle string (with '.' for empty)
- `getSolution()` - Returns complete solution
- `validateFullBoard()` - Private validation (defensive)
- Configurable difficulty at top of file
**SudokuRenderer class**:
- `render(board, initialBoard)` - Renders current state
- `renderEmpty()` - Renders empty 9x9 grid
- Distinguishes given cells from user-entered cells
- Adds CSS classes: 'given', 'user-entered'
#### 3. Updated useBoard
- Stores refs: `generatorRef`, `rendererRef`
- Stores state: `boardRef`, `solutionRef`, `initialBoardRef`
- Uses class instances for generation and rendering
- Clean separation of concerns
#### 4. Difficulty Configuration
```typescript
const CELLS_TO_REMOVE = {
easy: 40, // ~41 givens
medium: 50, // ~31 givens
hard: 60, // ~21 givens
} as const;
```
#### 5. Ported Unit Tests
- SudokuGenerator.test.ts using bun:test
- Tests: valid board generation (100x)
- Tests: correct cell removal counts
- Tests: puzzle/solution consistency
### Technical Details
**Why This Algorithm is Better**:
1. **Always valid**: Starts with mathematically valid complete board
2. **Always solvable**: We keep the solution, don't need to solve
3. **Fast**: No backtracking, just pattern + shuffle
4. **Simple**: Easy to understand and maintain
5. **Predictable difficulty**: Just cell count, not algorithmic complexity
**Pattern-Based Generation**:
```typescript
// Base pattern ensures valid structure
pattern(r, c) = (3 * (r % 3) + floor(r / 3) + c) % 9 + 1
// Then shuffle:
// - Rows within bands (0-2, 3-5, 6-8)
// - Cols within stacks (0-2, 3-5, 6-8)
// - Band order
// - Stack order
// - Digit mapping (1-9)
```
**Rendering Enhancement**:
- Given cells: `<div class="sudoku-cell given">5</div>`
- User-entered: `<div class="sudoku-cell user-entered">7</div>`
- Empty: `<div class="sudoku-cell"></div>`
### Build Status
- ✅ TypeScript compiles cleanly
- ✅ Example app builds successfully
- ✅ Unit tests ready (bun test)
- ✅ Algorithm validated with 100+ board generations
### Next Steps
- Phase 30: Implement useGameStatus hook
- Proper state machine
- Handle paused state
- Win condition detection
---
## Iteration 5: Phase 30 - useGameStatus Hook
**Date**: 2025-01-22
### Goals
- Extract game status logic into dedicated hook
- Follow vim-maze architecture pattern
- Clean separation of concerns
- Reusable state machine
### Tasks Completed
#### 1. Created useGameStatus Module
**File Structure**:
```
src/useGameStatus/
├── index.ts # Public exports
├── types.ts # GameStatus, GameStatusManager
└── useGameStatus.ts # Hook implementation
```
#### 2. Implemented State Machine
**States**:
- `waiting` - Initial state, awaiting start
- `started` - Game in progress
- `paused` - Game temporarily paused
- `game-over` - Game lost
- `game-won` - Game completed
**State Transitions**:
```
waiting ──startGame()──> started
▲ │
│ │ togglePause()
│ ▼
quitGame() paused
│
│ togglePause()
▼
started
```
#### 3. Hook API
```typescript
const gameStatusManager = useGameStatus();
const {
gameStatus, // Current state
setGameStatus, // Direct setter (for special cases)
startGame, // waiting → started
togglePause, // started ↔ paused
quitGame, // * → waiting
} = gameStatusManager;
```
#### 4. Updated useGame
- Removed inline `useState<GameStatus>`
- Uses `useGameStatus()` hook
- Spreads `gameStatusManager` into `GameManager`
- Cleaner, more maintainable code
#### 5. Updated Type System
**types.ts**:
- Imports `GameStatus` and `GameStatusManager` from useGameStatus
- Re-exports for public API
- `GameManager` extends `GameStatusManager` (intersection type)
- Avoids duplication, single source of truth
### Technical Details
**Simpler than vim-maze**:
- No intermediate states (no 'has-key' equivalent)
- Straightforward: waiting → started → won/over
- Pause is optional toggle, not a separate flow
**Hook Composition**:
```typescript
// In useGame
const gameStatusManager = useGameStatus();
const { gameStatus } = gameStatusManager;
// Compose into GameManager
const gameManager = {
...gameStatusManager, // gameStatus, startGame, etc.
// ... other managers
};
```
**Type Safety**:
- `GameManager` extends `GameStatusManager`
- All status methods available on gameManager
- Full TypeScript inference throughout
### Build Status
- ✅ TypeScript compiles cleanly
- ✅ Example app builds successfully
- ✅ No breaking changes to public API
### Next Steps
- Phase 40: useCursor Hook Composition
- useCellEditor (analog to useHero)
- useVimMotions
- useGameKeys
- useCursorRender
---
## Iteration 6: Phase 40.1 - Basic Cursor with hjkl Movement
**Date**: 2025-01-22
### Goals
- Implement basic cursor navigation
- Add hjkl vim keys
- Visual cursor highlight
- Only active during gameplay
### Tasks Completed
#### 1. Created useCursor Module
- `useCursor.ts` - Position tracking and movement logic
- `useCursorRender.ts` - Visual cursor highlight on DOM
- `types.ts` - Coord and CursorManager types
#### 2. Cursor Implementation
- Position tracking with useState (row/col 0-8)
- Movement functions: moveLeft, moveRight, moveUp, moveDown
- Bounds checking prevents moving outside 9x9 grid
- Keyboard listener for h/j/k/l keys
- Only active when gameStatus === 'started'
#### 3. Visual Feedback
- Blue highlight with border on current cell
- CSS class 'cursor' applied to active cell
- Removed from previous cell on movement
#### 4. Fixed Initial Cursor Display (Issue 1)
**Problem**: Cursor not showing on game start before first move
**Root Cause**: renderBoard() rebuilds DOM, wiping cursor class
**Solution**: Added boardRendered counter
- Increments after each renderBoard()
- useCursorRender depends on boardRendered
- Forces cursor re-application after DOM rebuild
**Flow**:
1. Press Space → startGame()
2. renderBoard() rebuilds DOM
3. boardRendered increments
4. useCursorRender triggers
5. Cursor class applied to (0,0)
### Technical Details
**useState vs useRef**:
- Initially used useRef (no re-renders)
- Changed to useState to trigger useCursorRender
- Position changes now properly update DOM
**Timing Issue Resolution**:
- renderBoard() is synchronous
- useEffect runs after render
- boardRendered ensures proper ordering
#### 5. Fixed Cursor Border Conflicts (Issue 2)
**Problem**: Cursor border conflicting with 3x3 box separators
- Cells have borders for grid structure
- Cursor border overlapped/clipped at edges
- Screenshots showed visual conflicts
**Solution**: Use outline instead of border
- `outline: 3px solid` (sits above borders)
- `outline-offset: -3px` (draws inside cell)
- `z-index: 10 + position: relative` (layers above)
- Outline doesn't affect layout or conflict with borders
#### 6. Added Red Flash on Invalid Movement (Issue 3)
**Feature**: Visual feedback when attempting invalid moves
**Implementation**:
- CSS: `.invalid-move` class with red outline and background
- Animation: 500ms ease-out fade from bright to transparent
- Logic: `flashInvalidMove()` triggers on bounds violations
- Automatically removes class after 500ms
**User Experience**:
- Press h at left edge → Red flash
- Press j at bottom → Red flash
- Press k at top → Red flash
- Press l at right → Red flash
**Cleanup**:
- Removed all console.log debugging statements
- Production-ready code
### Summary
Phase 40.1 complete with all issues resolved:
- ✅ Basic cursor with hjkl movement
- ✅ Visual cursor highlight (blue outline)
- ✅ Shows immediately on game start
- ✅ No border conflicts with grid
- ✅ Red flash on invalid movement
- ✅ Clean, production-ready code
---
## Iteration 7: Phase 40.2 - Complete Vim Motion System
**Date**: 2025-01-22
### Goals
- Implement anchor moves (0, ^, $, gg, G)
- Add count prefixes (5j, 10l, etc.)
- Add repeat motion (.)
- Handle the "0 caveat" properly
### Tasks Completed
#### 1. Created useVimMotions Hook
Complete vim motion processor with state management:
- Count buffer tracking (countRef)
- Last motion tracking for repeat
- Pending g state for gg sequence
#### 2. Implemented Anchor Moves
**Horizontal (Row-based):**
- `0` or `^` → Start of row (col 0)
- `$` → End of row (col 8)
**Vertical (Column-based):**
- `gg` → Top of column (row 0)
- `G` → Bottom of column (row 8)
#### 3. Count Prefix Support
- Works with all directional moves: h, j, k, l
- Examples: `5j`, `10l`, `3k`, `2h`
- Bounded to grid limits (0-8)
- Red flash on invalid moves
#### 4. Repeat Motion
- `.` → Repeats last motion with same count
- Tracks motion direction and step count
- Works with both direct moves and counted moves
#### 5. Solved the "0 Caveat"
**Problem**: 0 needs to work as both anchor AND count digit
- `0` alone → anchor to start
- `10j` → count of 10
**Solution** (from vim-maze):
```typescript
if (key === '0' && countRef.current === '') {
return { type: 'anchor', data: 'start' }; // Anchor
}
countRef.current += key; // Count digit
```
**Logic**: Check if count buffer is empty
- Empty → 0 is anchor
- Not empty → 0 is count digit
#### 6. Fixed gg Two-Key Sequence
**Bug**: Single `g` was triggering immediately
**Solution**: Added `pendingGRef` state
- First `g` → Set flag, wait for second key
- Second `g` → Trigger "top" anchor
- Any other key → Clear flag, no action
### Technical Implementation
**processKey Return Types:**
```typescript
{ type: 'move', data: Motion } // h/j/k/l with count
{ type: 'anchor', data: string } // 0/^/$/gg/G
{ type: 'repeat', data: Motion } // .
{ type: 'count', data: string } // 0-9
null // Ignored key or pending state
```
**Motion Tracking:**
```typescript
type Motion = { dr: number; dc: number; steps: number };
// Example: 5j → { dr: 1, dc: 0, steps: 5 }
```
**State Management:**
- `countRef` → Accumulates count digits
- `lastMotionRef` → Stores last motion for repeat
- `pendingGRef` → Tracks first g press for gg
### Testing Results
✅ All anchor moves working
✅ Count prefixes working (5j, 10l, etc.)
✅ Repeat motion (.) working
✅ 0 works as both anchor and count
✅ gg requires two presses
✅ All moves bounded with red flash feedback
### Next Steps
- Phase 40.3: Implement cell editing (i/r modes)
- Add digit entry (1-9)
- Add erase (x, Backspace)
- Mode indicators
---
## Iteration 8: Phase 40.3 - Edit Mode Implementation
**Date**: 2025-01-22
### Goals
- Implement edit mode state machine
- Add rules for editable vs given cells
- Use existing CSS classes for visual feedback
- No digit entry yet - just mode switching
### Tasks Completed
#### 1. Mode State Machine (Phase 40.3.1)
**Added CursorMode type:**
- `'normal'` - Navigation mode (vim motions active)
- `'edit'` - Edit mode (ready for digit entry)
**Key Bindings:**
- `i` - Enter edit mode (insert)
- `Escape` - Exit edit mode to normal
**State Management:**
- Mode tracked with useState in useCursor
- Starts in 'normal' mode on game start
- Mode passed to useCursorRender for visual feedback
#### 2. Edit Mode Rules (Phase 40.3.2)
**Cell Editability:**
- Given cells (`.given` class): Cannot be edited
- Empty cells: Can be edited
- User-entered cells (`.user-entered` class): Can be edited
**Validation Logic:**
```typescript
if (key === 'i') {
const targetCell = cells[index];
if (targetCell && targetCell.classList.contains('given')) {
flashInvalidMove(); // Red flash
return; // Block edit mode
}
setMode('edit'); // Allow edit mode
}
```
**Visual Feedback:**
- Attempt to edit given cell → Red flash (500ms)
- Successfully enter edit mode → Yellow/amber glow
#### 3. Visual Indicators
**Using Existing CSS Classes:**
- Normal mode: `.active` class (blue glow)
- Edit mode: `.editing` class (yellow/amber pulsing glow)
- Invalid attempt: `.invalid-move` class (red flash)
**Removed Custom CSS:**
- Removed `.cursor` and `.cursor-edit` classes
- Using original CSS design from src_old
- Consistent with existing game aesthetics
#### 4. Mode Behavior
**Normal Mode:**
- All vim motions work (hjkl, anchors, counts, repeat)
- Blue glow on current cell
- Can navigate freely
**Edit Mode:**
- All vim motions blocked
- Yellow/amber pulsing glow
- Only Escape key active (exit to normal)
- Ready for digit entry (not implemented yet)
### Technical Details
**Cell Classification:**
- Given cells: Have digits from puzzle generation
- Renderer adds `.given` class to these cells
- These cells are immutable (cannot be edited)
**Flash Mechanism:**
- `flashInvalidMove()` adds `.invalid-move` class
- CSS animation shows red glow for 500ms
- Class auto-removed after timeout
- Same mechanism used for boundary violations
**Mode Transition Flow:**
```
Normal (blue)
↓ [press i on empty/user cell]
Edit (yellow)
↓ [press Escape]
Normal (blue)
Normal (blue)
↓ [press i on given cell]
Red flash → Stay in Normal
```
### Testing Results
✅ Edit mode state machine working
✅ i and Escape toggle modes correctly
✅ Given cells block edit mode with red flash
✅ Empty cells allow edit mode
✅ User-entered cells allow edit mode
✅ Vim motions blocked in edit mode
✅ Visual indicators clear and distinct
### Next Steps
- Phase 40.3.3: Add digit entry (1-9)
- Phase 40.3.4: Add erase functionality (x, Backspace)
- Phase 40.3.5: Add replace mode (r)
- Integration with board state management
---
## 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...*