9.1 KiB
keydr Improvement Plan
Context
The app was built in a single first-pass implementation. Six issues need addressing: a broken settings menu, low-contrast pending text, poor phonetic word quality, a bare-bones stats dashboard, an undersized keyboard visualization, and hardcoded passage/code content.
Issue 1: Settings Menu Not Working
Root cause: In main.rs:handle_menu_key, the Enter match handles 0..=3 but Settings is menu item index 4 — it falls through to _ => {}. Also no KeyCode::Char('c') shortcut handler exists.
Fix (src/main.rs:124-158):
- Add
4 => app.screen = AppScreen::Settingsin the Enter match arm - Add
KeyCode::Char('c') => app.screen = AppScreen::Settingshandler
Additionally, make the Settings screen functional instead of a stub:
- Make it an interactive form with arrow keys to select fields, Enter to cycle values
- Fields: Target WPM (adjustable ±5), Theme (cycle through available), Word Count, Code Languages
- Save config on ESC via existing
Config::save() - New file: no new files needed; extend
render_settingsinmain.rsand addhandle_settings_keylogic - Add
settings_selected: usizeandsettings_editing: boolfields toApp
Issue 2: Low Contrast Pending Text
Root cause: text_pending = #585b70 (Catppuccin overlay0) on bg #1e1e2e is too dim for readable upcoming text.
Fix: Change text_pending in the default theme and all bundled theme files:
src/ui/theme.rs:92default:#585b70→#a6adc8(Catppuccin subtext0, much brighter)- Update all 8 theme TOML files in
assets/themes/with appropriate brighter pending text colors for each theme
Issue 3: Better Phonetic Word Generation
Root cause: Our current approach uses a hand-built order-2 trigram table from ~50 English patterns. keybr.com uses:
- Order-4 Markov chain trained on top 10,000 words from a real frequency dictionary
- Pre-built binary model (~47KB for English)
- Real word dictionary — the
naturalWordsmode (keybr's default) primarily uses real English words filtered by unlocked letters, falling back to phonetic pseudo-words only when <15 words match
Implementation plan:
Step A: Build a proper transition table from a word frequency list
- Create
tools/build_model.rs(a build-time binary) that:- Reads an English word frequency list (we'll embed a curated 10K-word list as
assets/wordfreq-en.csv) - Uses order-4 chain (matching keybr)
- Appends each word weighted by frequency (like keybr's
builder.append()loop) - Outputs binary
.datafile matching keybr's format
- Reads an English word frequency list (we'll embed a curated 10K-word list as
- OR simpler approach: Embed the word list directly and build the table at startup (it's fast enough)
Step B: Upgrade TransitionTable to order-4
- Modify
TransitionTableto support variable-order chains - Change the key from
(char, char)→ aVec<char>prefix of lengthorder - 1 - Implement
segment(prefix: &[char])matching keybr's approach
Step C: Add a word dictionary for "natural words" mode
- Create
src/generator/dictionary.rswith aDictionarystruct - Embed a 10K English word list (JSON or plain text) via rust-embed
Dictionary::find(filter: &CharFilter, focused: Option<char>) -> Vec<&str>returns real words where all characters are in the allowed set- If focused letter exists, prefer words containing it
Step D: Update PhoneticGenerator to use combined approach (like keybr's GuidedLesson)
- When
naturalWordsis enabled (default):- Get real words matching the filter from Dictionary
- If >= 15 real words available, randomly pick from them
- Otherwise, supplement with phonetic pseudo-words from the Markov chain
- This is what makes keybr's output "feel like real words" — because they mostly ARE real words
Key files to modify:
src/generator/transition_table.rs— upgrade to order-4src/generator/phonetic.rs— update word generation loop- New:
src/generator/dictionary.rs— real word dictionary - New:
assets/words-en.json— embedded 10K word list (we can extract from keybr'sclones/keybr.com/packages/keybr-content-words/lib/data/words-en.json) src/app.rs— wire up dictionary
Issue 4: Comprehensive Statistics Dashboard
Current state: Single screen with 4 summary numbers and 1 unlabeled WPM line chart.
Target (inspired by typr's three-tab layout):
Tab navigation
- Add tab state to the stats dashboard:
Dashboard | History | Keystrokes - Keyboard:
D,H,Kto switch tabs, orTabto cycle - Render tabs as a header row with active tab highlighted
Dashboard Tab
- Summary stats row: Total lessons, Avg WPM, Best WPM, Avg Accuracy, Total time, Streak
- Progress bars (3 columns): WPM vs goal, Accuracy vs 100%, Level progress
- WPM over time chart (line chart, last 50 lessons) — already exists, add axis labels
- Accuracy over time chart (line chart, last 50 lessons) — new chart
History Tab
- Recent tests table: Last 20 lessons with columns: #, WPM, Raw WPM, Accuracy, Time, Date
- Per-key average speed chart: Bar chart of all 26 letters by avg typing time
Keystrokes Tab
- Keyboard accuracy heatmap: Render keyboard layout with per-key accuracy coloring (green=100%, yellow=90-100%, red=<90%)
- Slowest/Fastest keys tables: Top 5 each with average time in ms
- Word/Character stats: Total correct/wrong counts
Key files to modify/create:
src/ui/components/stats_dashboard.rs— complete rewrite with tabssrc/ui/components/chart.rs— add AccuracyChart, BarChart widgets- New:
src/ui/components/keyboard_heatmap.rs— per-key accuracy visualization src/engine/key_stats.rs— ensure per-key accuracy tracking exists (not just timing)src/session/result.rs— ensure per-key accuracy data is persistedsrc/store/schema.rs— may need to add per-key accuracy to KeyStatsData
Issue 5: Keyboard Visualization Too Small
Current state: Keyboard diagram IS rendered in render_lesson (main.rs:330-335) but given only Constraint::Length(4) — with borders that's 2 inner rows, but QWERTY needs 3 rows.
Fix:
- Change keyboard constraint from
Length(4)toLength(5)inmain.rs:316 - Improve the keyboard rendering in
keyboard_diagram.rs:- Use wider keys (5 chars instead of 4) for readability
- Add finger-color coding (reuse existing
keyboard/finger.rs) - Show the next key to type highlighted (pass current target char)
- Improve spacing/centering
Files: src/main.rs:311-318, src/ui/components/keyboard_diagram.rs
Issue 6: Embedded + Internet Content (Both Approaches)
Embedded Baseline (always available, no network)
- Bundle ~50 passages from public domain literature directly in binary (via rust-embed)
- Bundle ~100 code snippets per language (Rust, Python, JS, Go) in embedded assets
- These replace the current ~15 hardcoded passages and ~12 code snippets per language
Internet Fetching (on top of embedded, with caching)
Passages: Project Gutenberg
- Fetch from
https://www.gutenberg.org/cache/epub/{id}/pg{id}.txt - Curate ~20 popular book IDs (Pride and Prejudice, Alice in Wonderland, etc.)
- Extract random paragraphs (skip Gutenberg header/footer boilerplate)
- Cache fetched books to
~/.local/share/keydr/passages/ - Gracefully fall back to embedded passages on network failure
Code: GitHub Raw Files
- Fetch raw files from curated popular repos (e.g.,
tokio-rs/tokio,python/cpython) - Use direct raw.githubusercontent.com URLs for specific files (no API auth needed)
- Extract function-length snippets (20-50 lines)
- Cache to
~/.local/share/keydr/code_cache/ - Gracefully fall back to embedded snippets on failure
New dependency: reqwest = { version = "0.12", features = ["json", "blocking"] }
Files to modify:
src/generator/passage.rs— expand embedded + add Gutenberg fetchingsrc/generator/code_syntax.rs— expand embedded + add GitHub fetching- New:
src/generator/cache.rs— shared disk caching logic - New:
assets/passages/*.txt— embedded passage files - New:
assets/code/*.rs,*.py, etc. — embedded code snippet files Cargo.toml— add reqwest dependency
Implementation Order
- Issue 1 (Settings menu fix) — quick fix, unblocks testing
- Issue 2 (Text contrast) — quick theme change
- Issue 5 (Keyboard size) — quick layout fix
- Issue 3 (Word generation) — medium complexity, core improvement
- Issue 4 (Stats dashboard) — large UI rewrite
- Issue 6 (Internet content) — medium complexity, requires new dependency
Verification
cargo build— compiles without errorscargo test— all tests pass- Manual testing for each issue:
- Settings: navigate to Settings in menu, change target WPM, verify it saves/loads
- Contrast: verify pending text is readable in the typing area
- Keyboard: verify all 3 QWERTY rows visible during lesson
- Words: start adaptive mode, verify words look like real English
- Stats: complete 2-3 lessons, check all three stats tabs render correctly
- Passages: start passage mode, verify it fetches new content (with network), and falls back gracefully (without)