Purpose: Outlines file organization, naming conventions, import patterns, and architectural decisions Type: Steering Document - How the codebase is organized Related: See
product.mdfor product goals,tech.mdfor technology choices
typer-cli/
βββ src/
β βββ main.rs # Entry point, terminal initialization
β βββ app.rs # Application state machine, two-level navigation, event loop
β βββ ui/
β β βββ mod.rs # UI module exports
β β βββ render.rs # Category menu, lesson menu, TUI rendering
β β βββ keyboard.rs # Visual keyboard display with highlighting
β βββ engine/
β β βββ mod.rs # Engine module exports
β β βββ types.rs # Core types: TypingSession, CharInput, SessionResult
β β βββ scoring.rs # WPM and accuracy calculation algorithms
β β βββ analytics.rs # Per-key/bigram statistics tracking
β β βββ adaptive.rs # Weakness detection, spaced repetition
β βββ content/
β β βββ mod.rs # Content module exports
β β βββ category.rs # Lesson categories, filtering logic
β β βββ lesson.rs # Lesson type definitions and enums
β β βββ generator.rs # Home row content generation
β β βββ bigram_generator.rs # Bigram practice
β β βββ trigram_generator.rs # Trigram practice
β β βββ code_generator.rs # Code symbols
β β βββ finger_generator.rs # Finger-based drills
β β βββ common_word_generator.rs # Common words practice
β β βββ adaptive_generator.rs # Personalized content
β βββ data/
β β βββ mod.rs # Data module exports
β β βββ stats.rs # Stats and SessionRecord structures (with analytics)
β β βββ storage.rs # JSON persistence (load/save)
β βββ keyboard/
β βββ mod.rs # Keyboard module exports
β βββ azerty.rs # AZERTY layout definition with finger mapping
βββ docs/
β βββ README.md # Documentation index
β βββ features/ # Feature-based documentation
β β βββ two-level-menu/
β β β βββ requirements.md # EARS format requirements
β β β βββ design.md # Technical architecture
β β β βββ tasks.md # Implementation tracking
β β βββ [other features]/
β βββ steering/ # Persistent knowledge
β βββ product.md # Product vision and goals
β βββ tech.md # Technology stack decisions
β βββ structure.md # This file
βββ Cargo.toml # Dependencies and project metadata
βββ CLAUDE.md # Lightweight AI assistant context
βββ README.md # User-facing documentation
main.rs
ββ> app.rs
ββ> ui/
β ββ> render.rs (category menu, lesson menu, session rendering)
β β ββ> engine/types.rs
β β ββ> content/category.rs
β ββ> keyboard.rs (visual keyboard display)
β ββ> keyboard/azerty.rs
ββ> engine/
β ββ> types.rs (TypingSession, CharInput, SessionResult)
β ββ> scoring.rs (uses types.rs)
β ββ> analytics.rs (key/bigram statistics)
β ββ> adaptive.rs (weakness detection, spaced repetition)
ββ> content/
β ββ> category.rs (LessonCategory, filtering)
β ββ> lesson.rs (LessonType enum, definitions)
β ββ> generator.rs (home row generation)
β ββ> bigram_generator.rs (uses lesson.rs)
β ββ> trigram_generator.rs
β ββ> code_generator.rs
β ββ> finger_generator.rs (uses keyboard/azerty.rs)
β ββ> common_word_generator.rs
β ββ> adaptive_generator.rs (uses analytics.rs)
ββ> data/
β ββ> stats.rs (SessionRecord, AdaptiveAnalytics)
β ββ> storage.rs (uses stats.rs)
ββ> keyboard/
ββ> azerty.rs (layout definition, finger mapping)
Responsibility: Application entry point
What it does:
- Initializes terminal (raw mode, alternate screen)
- Creates App instance
- Starts main event loop
- Handles terminal cleanup on exit
What it doesn't do:
- Business logic
- UI rendering
- Event handling (delegated to App)
Responsibility: Application state machine and event orchestration
Core struct:
pub struct App {
session: Option<TypingSession>,
state: AppState, // LessonTypeMenu, LessonMenu, DurationMenu, Running, Completed, Quit
selected_category: usize,
categories: Vec<LessonCategory>,
current_category: Option<LessonCategoryType>,
selected_lesson: usize,
lessons: Vec<Lesson>,
// ... stats, storage, keyboard config
}State Machine:
LessonTypeMenu β LessonMenu (filtered) β DurationMenu β Running β Completed
β β β
ββ ESC βββββββββββββββββ ESC βββββββββββ
What it does:
- Manages two-level navigation (category β lesson)
- Filters lessons by selected category
- Routes keyboard events to appropriate handlers
- Converts relative lesson indices to absolute for execution
- Manages application lifecycle and state transitions
- Preserves category context after sessions
What it doesn't do:
- Rendering (delegates to ui/render.rs)
- Scoring calculations (delegates to engine/scoring.rs)
- Content generation (delegates to content generators)
Responsibility: Terminal UI rendering
What it does:
- Renders category selection menu with descriptions and colors
- Renders filtered lesson menu for selected category
- Renders duration selection menu
- Renders active typing session layout
- Renders results screen
- Applies color coding (green/red/gray, category colors)
- Formats statistics display
- Creates TUI widgets (blocks, paragraphs, spans, lists)
What it doesn't do:
- State management
- Event handling
- Scoring calculations
- Lesson filtering (receives filtered list from app)
Key functions:
render_lesson_type_menu(): Category selection screenrender_menu(): Filtered lesson selection (accepts category name)render_duration_menu(): Duration selectionrender(): Main typing interfacerender_results(): End-of-session results screen- Color scheme: Green (correct), Red (incorrect), Gray (pending), category-specific colors
Responsibility: Visual keyboard display
What it does:
- Renders full AZERTY keyboard layout (5 rows)
- Highlights next key to press (cyan background)
- Indicates shift state on both shift keys
- Displays finger color hints (toggle with Ctrl+F)
- Shows accuracy heatmap overlay (toggle with Ctrl+H)
- Supports compact and full keyboard modes
What it doesn't do:
- Input handling
- Key mapping logic (uses keyboard/azerty.rs)
- State management
Responsibility: Core typing session domain model
Key types:
pub struct TypingSession {
content: String,
current_index: usize,
inputs: Vec<CharInput>,
start_time: Instant,
end_time: Option<Instant>,
}
pub struct CharInput {
expected: char,
typed: char,
timestamp: Duration,
is_correct: bool,
}
pub struct SessionResult {
wpm: f64,
accuracy: f64,
duration: Duration,
char_count: usize,
error_count: usize,
}What it does:
- Manages typing session lifecycle
- Records each character input
- Determines session completion
- Validates character input
- Provides session results
Design decisions:
- Immutable session content (set at creation)
- Append-only inputs (no backspace in Phase 1)
- Lazy result calculation (only when requested)
Responsibility: Metric calculations
What it does:
- Calculates WPM:
(char_count / 5) / (duration_seconds / 60) - Calculates accuracy:
(correct_chars / total_chars) Γ 100 - Provides real-time and final metrics
Design decisions:
- Pure functions (no side effects)
- Independent of UI
- Well-tested with edge cases
Responsibility: Lesson categorization and filtering
Key types:
pub enum LessonCategoryType {
Adaptive,
FingerTraining,
RowTraining,
Languages,
Code,
Custom,
}
pub struct LessonCategory {
pub category_type: LessonCategoryType,
pub name: &'static str,
pub description: &'static str,
pub color: Color,
}What it does:
- Defines 6 lesson categories with metadata
- Provides filtering logic via
contains_lesson() - Generates category list with
all(has_adaptive: bool) - Maps lesson types to categories
Category Filtering:
- Adaptive:
LessonType::Adaptive - FingerTraining:
LessonType::FingerPair { .. } - RowTraining:
LessonType::RowProgression { .. } - Languages:
BigramType::Natural | Trigram | CommonWords - Code:
CodeSymbols | BigramType::Code - Custom:
LessonType::Custom { .. }
Responsibility: Lesson type definitions
Key types:
pub enum LessonType {
KeyPair { lesson_id: u8 },
KeyPairGroup { group_id: u8, with_shift: bool },
Bigram { bigram_type: BigramType, language: Option<Language>, level: u8 },
Trigram { language: Language, level: u8 },
CommonWords { language: Language, level: u8 },
CodeSymbols { language: ProgrammingLanguage, level: u8 },
FingerPair { finger_pair: FingerPair, level: u8, with_shift: bool },
Adaptive,
}
pub struct Lesson {
pub lesson_type: LessonType,
pub title: String,
}What it does:
- Defines all available lesson types
- Provides lesson metadata (title, type)
- Factory methods for creating lesson collections
- Implements ContentGenerator trait for lesson execution
Responsibility: Practice content generation
What it does:
- Generates character sequences for lessons
- Implements progressive difficulty
- Uses keyboard layout for key selection
Lesson generation strategies:
- Level 1: Alternating f/j patterns
- Level 2-4: Progressive finger addition
- Level 5: All home row keys
- Level 6: French words using home row
Design decisions:
- Deterministic generation (same lesson = same content)
- Appropriate length (~50-100 chars per lesson)
- Space-separated for WPM calculation
Responsibility: Statistics data structures
Key types:
pub struct Stats {
pub sessions: Vec<SessionRecord>,
}
pub struct SessionRecord {
pub timestamp: DateTime<Utc>,
pub lesson_type: String,
pub wpm: f64,
pub accuracy: f64,
pub duration: u64,
}What it does:
- Defines serializable stats format
- Provides helper methods for stats access
Responsibility: File system persistence
What it does:
- Loads stats from
~/.config/typer-cli/stats.json - Saves stats to JSON file
- Creates config directory if needed
- Handles file I/O errors gracefully
Design decisions:
- XDG Base Directory compliance
- Human-readable JSON format
- Create directory on first run
- Fail gracefully if stats can't be loaded
Responsibility: Keyboard layout definitions
What it does:
- Defines AZERTY home row:
qsdfghjklm - Provides key grouping by finger
Design decisions:
- Extensible to full keyboard layout
- Separated from lesson logic
- Easy to add BΓPO, Dvorak, etc. in future
1. Application Start
ββ> main.rs initializes terminal
ββ> app.rs creates TypingSession
ββ> content/generator.rs generates lesson content
ββ> keyboard/azerty.rs provides key set
2. User Types Character
ββ> main.rs captures keyboard event (crossterm)
ββ> app.rs handles_key_event()
ββ> TypingSession.process_input()
ββ> Validates character
ββ> Records CharInput
ββ> Checks if complete
3. Render Frame
ββ> main.rs triggers render
ββ> app.rs provides current state
ββ> ui/render.rs renders UI
ββ> engine/scoring.rs calculates metrics
ββ> Displayed to user
4. Session Complete
ββ> TypingSession marks complete
ββ> app.rs sets show_results = true
ββ> ui/render.rs renders results screen
ββ> engine/scoring.rs provides final metrics
5. Save Stats
ββ> app.rs creates SessionRecord
ββ> data/storage.rs saves to JSON
ββ> data/stats.rs serializes
6. Restart or Quit
ββ> User presses 'r' (restart) or 'q' (quit)
ββ> app.rs resets state or sets should_quit
Pattern: Clear boundaries between modules
- UI: Only rendering, no business logic
- Engine: Business logic, no I/O or rendering
- Data: Persistence only, no business logic
- Content: Generation only, no session management
Benefit: Testability, maintainability, extensibility
Pattern: Types model the problem domain
TypingSessionrepresents a practice sessionLessonrepresents lesson typesSessionResultrepresents outcomes
Benefit: Code reads like the problem domain
Pattern: High-level modules don't depend on low-level details
- App doesn't know about JSON storage
- Engine doesn't know about ratatui
- Content generator receives keyboard layout
Benefit: Easy to swap implementations, test with mocks
Pattern: Scoring functions have no side effects
pub fn calculate_wpm(char_count: usize, duration: Duration) -> f64Benefit: Predictable, testable, parallelizable
mod.rs: Module exports and public API- Descriptive names:
render.rs,scoring.rs,generator.rs
- PascalCase for structs/enums:
TypingSession,Lesson - snake_case for functions/variables:
calculate_wpm,current_index
- Tests in same file as implementation (below
#[cfg(test)]) - Test function names:
test_<what_is_tested>
Phase 1 has no user-configurable settings
Potential ~/.config/typer-cli/config.toml:
[display]
theme = "default" # or "high-contrast"
[lessons]
default_length = 100 # characters
[keyboard]
layout = "azerty" # future: "bepo", "qwerty"- Terminal errors: Propagate to main, clean exit
- Stats I/O errors: Log and continue (graceful degradation)
- User input: Validate and ignore invalid input
- Terminal initialization failures: Fatal (can't run without terminal)
- Stats file issues: Non-fatal (can run without stats)
- Invalid lesson: Panic (programmer error, not user error)
- Add variant to
Lessonenum incontent/lesson.rs - Implement generation logic in
content/generator.rs - Update UI to show new lesson type
- Create new file in
keyboard/(e.g.,bepo.rs) - Implement layout struct with key definitions
- Update generator to accept layout parameter
- Add field to
SessionResultinengine/types.rs - Implement calculation in
engine/scoring.rs - Update UI rendering in
ui/render.rs - Update
SessionRecordfor persistence
- Create theme struct in
ui/theme.rs - Define color schemes
- Pass theme to render functions
- Add theme selection to config file
- Location: Same file as implementation (
#[cfg(test)]module) - Coverage: All business logic in engine/, content/, data/
- Strategy: Test public API, mock dependencies where needed
- Location:
tests/directory (future) - Coverage: End-to-end session flows
- Strategy: Simulate user input, verify state changes
- Location: Inline in test functions or const data
- Strategy: Deterministic, edge cases, happy paths
- Target: <50ms latency from keypress to visual feedback
- Approach:
- Minimal processing per keystroke
- Pre-allocated data structures
- Efficient ratatui rendering
- Target: <10MB resident memory
- Approach:
- No large in-memory buffers
- Streaming stats to disk
- Bounded session history
- Target: <100ms cold start
- Achieved: ~50ms (measured with hyperfine)
- Approach:
- Minimal dependencies
- Lazy loading where possible
- Dynamic lesson loading from
~/.config/typer-cli/lessons/ - JSON lesson definitions
- Custom content generators
- Separate language packs
- i18n for UI strings
- Language-specific content generators
- Per-key tracking
- Heat map generation
- Trend analysis
- Weak point detection
- Export stats to CSV
- Import custom word lists
- Backup/restore functionality