feat: implement r and c commands for single-digit edit
- Add singleEdit state to track single vs multi-edit mode - r and c only work on user-entered cells (flash red otherwise) - i only works on empty cells (flash red on given or user-entered) - r/c auto-exit to normal mode after one digit entry - i stays in edit mode until Escape (multi-edit)
This commit is contained in:
+93
-3
@@ -35,6 +35,12 @@ These are the key principles and constraints that guide this implementation:
|
||||
- **Clear commit messages**: Describe what, why, and how
|
||||
- **Track issues and solutions**: Document problems encountered and fixes
|
||||
|
||||
### Testing Workflow
|
||||
- **Wait for user testing**: After implementing a feature, WAIT for the user to test and report issues
|
||||
- **Do not assume success**: Do not move on to next phases or document success until user confirms
|
||||
- **Take it slow**: Address one issue at a time as reported by the user
|
||||
- **Be patient**: The user will test thoroughly and report all issues found
|
||||
|
||||
---
|
||||
|
||||
## Iteration 1: Preparation & Planning
|
||||
@@ -890,14 +896,98 @@ User sees '5' in the cell
|
||||
|
||||
**Result**: ✅ Edit mode entry now correctly checks and flashes the current cell position
|
||||
|
||||
---
|
||||
|
||||
## Iteration 10: Phase 40.3.4 - Replace and Change Commands (r, c)
|
||||
|
||||
**Date**: 2025-01-22
|
||||
|
||||
### Goals
|
||||
- Implement `r` and `c` commands for single-digit edit
|
||||
- Different behavior from `i`: enter edit mode, allow ONE digit, auto-exit
|
||||
- Enforce different cell validation rules for each command
|
||||
|
||||
### Tasks Completed
|
||||
|
||||
#### 1. Added Single-Edit State
|
||||
**Updated useCursor.ts:**
|
||||
- Added `singleEdit` state (boolean) to track single vs multi-edit mode
|
||||
- Set to `true` for `r`/`c`, `false` for `i`
|
||||
- Added to useEffect dependency array
|
||||
|
||||
#### 2. Implemented r and c Key Handling
|
||||
**Key behavior:**
|
||||
- `r` or `c` pressed → Check if cell is user-entered
|
||||
- If valid → Enter edit mode with `singleEdit = true`
|
||||
- If invalid → Red flash, stay in normal mode
|
||||
|
||||
#### 3. Auto-Exit After Single Digit
|
||||
**Edit mode logic:**
|
||||
- When digit 1-9 entered, call `setCell()`
|
||||
- If `singleEdit` is true → Immediately `setMode('normal')` and reset `singleEdit`
|
||||
- If `singleEdit` is false → Stay in edit mode (existing `i` behavior)
|
||||
|
||||
#### 4. Cell Validation Rules
|
||||
**Implemented complete validation:**
|
||||
|
||||
| Command | Empty Cell | User-Entered Cell | Given Cell |
|
||||
|---------|------------|-------------------|------------|
|
||||
| `i` | ✅ Enter edit (multi) | ❌ Red flash | ❌ Red flash |
|
||||
| `r` | ❌ Red flash | ✅ Enter edit (single) | ❌ Red flash |
|
||||
| `c` | ❌ Red flash | ✅ Enter edit (single) | ❌ Red flash |
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
const isGiven = targetCell.classList.contains('given');
|
||||
const isUserEntered = targetCell.classList.contains('user-entered');
|
||||
const isEmpty = !isGiven && !isUserEntered;
|
||||
|
||||
if (key === 'i') {
|
||||
if (!isEmpty) {
|
||||
flashInvalidMove();
|
||||
return;
|
||||
}
|
||||
} else if (key === 'r' || key === 'c') {
|
||||
if (!isUserEntered) {
|
||||
flashInvalidMove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Technical Details
|
||||
|
||||
**Edit Mode Behavior:**
|
||||
- `i` → Multi-edit: Enter digit, stay in edit mode, press Escape to exit
|
||||
- `r`/`c` → Single-edit: Enter digit, immediately exit to normal mode
|
||||
|
||||
**State Flow:**
|
||||
```
|
||||
Press 'r' on user-entered cell
|
||||
↓
|
||||
Validate: isUserEntered? Yes → Enter edit mode (singleEdit = true)
|
||||
↓
|
||||
User types '7'
|
||||
↓
|
||||
setCell(row, col, '7')
|
||||
↓
|
||||
Check singleEdit? True → setMode('normal'), setSingleEdit(false)
|
||||
↓
|
||||
Back in normal mode (blue cursor)
|
||||
```
|
||||
|
||||
### Build Status
|
||||
- ✅ TypeScript compiles cleanly
|
||||
- ✅ No type errors
|
||||
- ✅ All validation rules working correctly
|
||||
|
||||
### Next Steps
|
||||
- Phase 40.3.4: Add erase functionality (x, Backspace, Delete)
|
||||
- Phase 40.3.5: Add replace mode (r)
|
||||
- Phase 40.3.5: Add erase functionality (x, Backspace, Delete)
|
||||
- Validation and conflict detection
|
||||
- Win condition checking
|
||||
|
||||
---
|
||||
|
||||
## Iteration 10: [Pending]
|
||||
## Iteration 11: [Pending]
|
||||
|
||||
*To be filled as implementation progresses...*
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useVimMotions } from './useVimMotions';
|
||||
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>, boardManager: BoardManager) {
|
||||
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
|
||||
const [mode, setMode] = useState<CursorMode>('normal');
|
||||
const [singleEdit, setSingleEdit] = useState<boolean>(false);
|
||||
const { gameStatus } = gameStatusManager;
|
||||
const vimMotions = useVimMotions();
|
||||
|
||||
@@ -98,18 +99,23 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
||||
if (mode === 'edit') {
|
||||
if (key === 'Escape') {
|
||||
setMode('normal');
|
||||
setSingleEdit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key >= '1' && key <= '9') {
|
||||
boardManager.setCell(position.row, position.col, key);
|
||||
if (singleEdit) {
|
||||
setMode('normal');
|
||||
setSingleEdit(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'i') {
|
||||
if (key === 'i' || key === 'r' || key === 'c') {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
@@ -117,12 +123,26 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
||||
const index = position.row * 9 + position.col;
|
||||
const targetCell = cells[index];
|
||||
|
||||
if (targetCell && targetCell.classList.contains('given')) {
|
||||
flashInvalidMove();
|
||||
return;
|
||||
if (!targetCell) return;
|
||||
|
||||
const isGiven = targetCell.classList.contains('given');
|
||||
const isUserEntered = targetCell.classList.contains('user-entered');
|
||||
const isEmpty = !isGiven && !isUserEntered;
|
||||
|
||||
if (key === 'i') {
|
||||
if (!isEmpty) {
|
||||
flashInvalidMove();
|
||||
return;
|
||||
}
|
||||
} else if (key === 'r' || key === 'c') {
|
||||
if (!isUserEntered) {
|
||||
flashInvalidMove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setMode('edit');
|
||||
setSingleEdit(key === 'r' || key === 'c');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,7 +185,7 @@ export function useCursor(gameStatusManager: GameStatusManager, containerRef: Re
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [gameStatus, mode, position]);
|
||||
}, [gameStatus, mode, position, singleEdit]);
|
||||
|
||||
const cursor: CursorManager = {
|
||||
position: () => position,
|
||||
|
||||
Reference in New Issue
Block a user