|
| 1 | +# Finger Training - Design |
| 2 | + |
| 3 | +## Architecture |
| 4 | + |
| 5 | +### Data Model |
| 6 | + |
| 7 | +#### New Types (src/content/lesson.rs) |
| 8 | + |
| 9 | +```rust |
| 10 | +/// Finger pair combinations for bilateral training |
| 11 | +#[derive(Debug, Clone, Copy, PartialEq)] |
| 12 | +pub enum FingerPairType { |
| 13 | + Pinky, // Left pinky + Right pinky |
| 14 | + Ring, // Left ring + Right ring |
| 15 | + Middle, // Left middle + Right middle |
| 16 | + Index, // Left index + Right index |
| 17 | +} |
| 18 | + |
| 19 | +/// Extended LessonType enum |
| 20 | +pub enum LessonType { |
| 21 | + // ... existing variants ... |
| 22 | + FingerPair { |
| 23 | + finger_pair: FingerPairType, |
| 24 | + level: u8, // 1=Home Row, 2=Extended, 3=All Keys |
| 25 | + with_shift: bool, // false=base chars, true=mixed case+symbols |
| 26 | + }, |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +### Content Generation |
| 31 | + |
| 32 | +#### Key Extraction Algorithm (src/content/finger_generator.rs) |
| 33 | + |
| 34 | +**Function**: `get_finger_pair_keys(layout, finger_pair, level, with_shift) -> Vec<char>` |
| 35 | + |
| 36 | +**Input**: |
| 37 | +- `layout`: AzertyLayout reference |
| 38 | +- `finger_pair`: Which finger pair (Pinky/Ring/Middle/Index) |
| 39 | +- `level`: Difficulty (1/2/3) |
| 40 | +- `with_shift`: Include shift variants (true/false) |
| 41 | + |
| 42 | +**Process**: |
| 43 | +1. Map finger pair to (left_finger, right_finger) tuple |
| 44 | +2. Determine allowed rows based on level: |
| 45 | + - Level 1: Home row only |
| 46 | + - Level 2: Home + Top + Bottom |
| 47 | + - Level 3: All rows including Number |
| 48 | +3. Iterate through layout rows and keys |
| 49 | +4. For each key matching either finger: |
| 50 | + - Add base character |
| 51 | + - If with_shift=true, add shift variant |
| 52 | +5. Filter out placeholders (\0, \n) |
| 53 | +6. Sort and deduplicate |
| 54 | + |
| 55 | +**Output**: Vector of characters assigned to the finger pair |
| 56 | + |
| 57 | +#### Drill Generation Algorithm |
| 58 | + |
| 59 | +**Base Drills** (3-phase pattern): |
| 60 | + |
| 61 | +**Phase 1**: Single key repetitions |
| 62 | +``` |
| 63 | +ff dd jj kk |
| 64 | +``` |
| 65 | + |
| 66 | +**Phase 2**: Adjacent pairs and reversals |
| 67 | +``` |
| 68 | +fd df fj jf dk kd |
| 69 | +``` |
| 70 | + |
| 71 | +**Phase 3**: Triplets with permutations (if ≥3 keys) |
| 72 | +``` |
| 73 | +fdk dfk kfd dkf fkd kdf |
| 74 | +``` |
| 75 | + |
| 76 | +**Shift Drills** (weighted random): |
| 77 | + |
| 78 | +**Distribution**: |
| 79 | +- 50% lowercase characters |
| 80 | +- 40% uppercase characters |
| 81 | +- 10% symbols |
| 82 | + |
| 83 | +**Pool Building**: |
| 84 | +1. Separate keys into: lowercase, uppercase, symbols |
| 85 | +2. Create weighted pool: |
| 86 | + - Add lowercase × 50 |
| 87 | + - Add uppercase × 40 |
| 88 | + - Add symbols × 10 |
| 89 | + |
| 90 | +**Pattern Generation**: |
| 91 | +- Phase 1: 20 repetitions (random selection) |
| 92 | +- Phase 2: 30 pairs (random combinations) |
| 93 | +- Phase 3: 50 triplets (random combinations) |
| 94 | + |
| 95 | +**Content Assembly**: |
| 96 | +- Cycle through patterns until length reached |
| 97 | +- Check length before adding each pattern to prevent overflow |
| 98 | + |
| 99 | +### French AZERTY Corrections |
| 100 | + |
| 101 | +#### Number Row Mappings (src/keyboard/azerty.rs) |
| 102 | + |
| 103 | +**Corrected mappings** (symbols are base, numbers are shift): |
| 104 | + |
| 105 | +| Key | Base | Shift | Finger | |
| 106 | +|-----|------|-------|--------| |
| 107 | +| 1 | & | 1 | LeftPinky | |
| 108 | +| 2 | é | 2 | LeftPinky | |
| 109 | +| 3 | " | 3 | LeftRing | |
| 110 | +| 4 | ' | 4 | LeftMiddle | |
| 111 | +| 5 | ( | 5 | LeftIndex | |
| 112 | +| 6 | - | 6 | LeftIndex | |
| 113 | +| 7 | è | 7 | RightIndex | |
| 114 | +| 8 | _ | 8 | RightMiddle | |
| 115 | +| 9 | ç | 9 | RightRing | |
| 116 | + |
| 117 | +#### Bottom Row Corrections |
| 118 | + |
| 119 | +| Key | Was | Now | |
| 120 | +|-----|-----|-----| |
| 121 | +| w | LeftRing | LeftPinky | |
| 122 | +| x | LeftMiddle | LeftRing | |
| 123 | +| c | LeftIndex | LeftMiddle | |
| 124 | +| b | RightIndex | LeftIndex | |
| 125 | +| , | RightMiddle | RightIndex | |
| 126 | +| ; | RightRing | RightMiddle | |
| 127 | +| : | RightPinky | RightRing | |
| 128 | + |
| 129 | +**Total**: 16 finger mapping errors corrected |
| 130 | + |
| 131 | +### Menu Integration |
| 132 | + |
| 133 | +#### Lesson Ordering (src/app.rs) |
| 134 | + |
| 135 | +**Build sequence**: |
| 136 | +1. ADAPTIVE (1 lesson, if ≥10 sessions) |
| 137 | +2. FINGER TRAINING (24 lessons) |
| 138 | +3. PRIMARY (25 lessons) |
| 139 | +4. SECONDARY (27 lessons) |
| 140 | + |
| 141 | +#### Dynamic Separator Positioning (src/ui/render.rs) |
| 142 | + |
| 143 | +**Logic**: |
| 144 | +```rust |
| 145 | +let has_adaptive = first lesson is Adaptive type |
| 146 | +let finger_index = if has_adaptive { 1 } else { 0 } |
| 147 | +let primary_index = if has_adaptive { 25 } else { 24 } |
| 148 | +let secondary_index = if has_adaptive { 50 } else { 49 } |
| 149 | +``` |
| 150 | + |
| 151 | +**Separators**: |
| 152 | +- ADAPTIVE (cyan) - if present, before lesson 1 |
| 153 | +- FINGER TRAINING (green) - before finger_index |
| 154 | +- PRIMARY (cyan) - before primary_index |
| 155 | +- SECONDARY (cyan) - before secondary_index |
| 156 | + |
| 157 | +### Lesson Manifest |
| 158 | + |
| 159 | +#### Complete Set (24 lessons) |
| 160 | + |
| 161 | +**Pinky Fingers** (6 lessons): |
| 162 | +- L1: q, m, ù, * (4 keys) |
| 163 | +- L1+Shift: + Q, M, Ù, %, µ |
| 164 | +- L2: + a, <, w, p, ^, $, ! (11 keys) |
| 165 | +- L2+Shift: + uppercase + symbols |
| 166 | +- L3: + &, é, à, ), = (16 keys) |
| 167 | +- L3+Shift: + 1, 2, 0, °, + |
| 168 | + |
| 169 | +**Ring Fingers** (6 lessons): |
| 170 | +- L1: s, l (2 keys) |
| 171 | +- L2: + z, o, x, ; (6 keys) |
| 172 | +- L3: + ", ç (8 keys) + shift: 3, 9 |
| 173 | + |
| 174 | +**Middle Fingers** (6 lessons): |
| 175 | +- L1: d, k (2 keys) |
| 176 | +- L2: + e, i, c, ; (6 keys) |
| 177 | +- L3: + ', _ (8 keys) + shift: 4, 8 |
| 178 | + |
| 179 | +**Index Fingers** (6 lessons): |
| 180 | +- L1: f, g, h, j (4 keys) |
| 181 | +- L2: + r, t, y, u, v, c, b, n (12 keys) |
| 182 | +- L3: + (, -, è (16 keys) + shift: 5, 6, 7 |
| 183 | + |
| 184 | +### Configuration |
| 185 | + |
| 186 | +#### Keyboard Display Defaults (src/ui/keyboard.rs) |
| 187 | + |
| 188 | +```rust |
| 189 | +impl Default for KeyboardConfig { |
| 190 | + fn default() -> Self { |
| 191 | + Self { |
| 192 | + _show_shift_indicators: true, |
| 193 | + show_heatmap: false, // Disabled by default |
| 194 | + show_finger_colors: true, // Enabled by default |
| 195 | + _compact_mode: false, |
| 196 | + } |
| 197 | + } |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +**Rationale**: Finger colors more useful for learning; heatmap available on demand (Ctrl+H) |
| 202 | + |
| 203 | +## Implementation Files |
| 204 | + |
| 205 | +### Modified Files |
| 206 | +1. `src/keyboard/azerty.rs` - Corrected finger mappings (16 fixes) |
| 207 | +2. `src/content/lesson.rs` - Added FingerPairType enum and FingerPair variant |
| 208 | +3. `src/content/generator.rs` - Added FingerPair content generation |
| 209 | +4. `src/content/mod.rs` - Exported finger_generator module |
| 210 | +5. `src/app.rs` - Reordered lesson building |
| 211 | +6. `src/ui/render.rs` - Dynamic separator positioning |
| 212 | +7. `src/ui/keyboard.rs` - Disabled heatmap by default |
| 213 | + |
| 214 | +### New Files |
| 215 | +1. `src/content/finger_generator.rs` - Key extraction and drill generation (12 unit tests) |
| 216 | + |
| 217 | +## Testing Strategy |
| 218 | + |
| 219 | +### Unit Tests (finger_generator.rs) |
| 220 | +- Key extraction correctness (middle fingers home row, extended, all keys) |
| 221 | +- Shift variant inclusion |
| 222 | +- Level progression (L3 > L2 > L1 key count) |
| 223 | +- Placeholder filtering (\0, \n excluded) |
| 224 | +- Content generation length limits |
| 225 | +- All 24 lessons generate valid content |
| 226 | + |
| 227 | +### Updated Tests |
| 228 | +- `keyboard::azerty::tests::test_get_base_key` - French AZERTY expectations |
| 229 | +- `keyboard::azerty::tests::test_number_row_has_13_keys` - Corrected base/shift |
| 230 | + |
| 231 | +### Integration Testing |
| 232 | +- Menu displays correct separators with/without adaptive |
| 233 | +- Lesson numbering sequential |
| 234 | +- Content generation for all 24 lessons |
| 235 | +- Keyboard shortcuts work (Ctrl+F, Ctrl+H, Tab) |
| 236 | + |
| 237 | +## Performance Considerations |
| 238 | + |
| 239 | +- Lesson generation: O(n) where n = number of keys in layout |
| 240 | +- Content generation: O(m) where m = target content length |
| 241 | +- No runtime overhead during typing session (content pre-generated) |
| 242 | +- Memory: ~24KB for all finger lesson metadata |
| 243 | + |
| 244 | +## Future Enhancements |
| 245 | + |
| 246 | +1. **Per-finger analytics**: Track accuracy/speed by finger |
| 247 | +2. **Adaptive finger recommendations**: Suggest finger lessons based on weak keys |
| 248 | +3. **Cross-hand patterns**: Explicit left-right alternation drills |
| 249 | +4. **Finger strength visualization**: Heatmap overlay by finger performance |
| 250 | +5. **Thumb timing drills**: Spacebar rhythm and spacing practice |
0 commit comments