Files
keydr/docs/plans/2026-02-10-improvement.md
2026-02-10 23:32:57 -05:00

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::Settings in the Enter match arm
  • Add KeyCode::Char('c') => app.screen = AppScreen::Settings handler

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_settings in main.rs and add handle_settings_key logic
  • Add settings_selected: usize and settings_editing: bool fields to App

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:92 default: #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:

  1. Order-4 Markov chain trained on top 10,000 words from a real frequency dictionary
  2. Pre-built binary model (~47KB for English)
  3. Real word dictionary — the naturalWords mode (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:
    1. Reads an English word frequency list (we'll embed a curated 10K-word list as assets/wordfreq-en.csv)
    2. Uses order-4 chain (matching keybr)
    3. Appends each word weighted by frequency (like keybr's builder.append() loop)
    4. Outputs binary .data file matching keybr's format
  • 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 TransitionTable to support variable-order chains
  • Change the key from (char, char) → a Vec<char> prefix of length order - 1
  • Implement segment(prefix: &[char]) matching keybr's approach

Step C: Add a word dictionary for "natural words" mode

  • Create src/generator/dictionary.rs with a Dictionary struct
  • 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 naturalWords is enabled (default):
    1. Get real words matching the filter from Dictionary
    2. If >= 15 real words available, randomly pick from them
    3. 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-4
  • src/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's clones/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, K to switch tabs, or Tab to cycle
  • Render tabs as a header row with active tab highlighted

Dashboard Tab

  1. Summary stats row: Total lessons, Avg WPM, Best WPM, Avg Accuracy, Total time, Streak
  2. Progress bars (3 columns): WPM vs goal, Accuracy vs 100%, Level progress
  3. WPM over time chart (line chart, last 50 lessons) — already exists, add axis labels
  4. Accuracy over time chart (line chart, last 50 lessons) — new chart

History Tab

  1. Recent tests table: Last 20 lessons with columns: #, WPM, Raw WPM, Accuracy, Time, Date
  2. Per-key average speed chart: Bar chart of all 26 letters by avg typing time

Keystrokes Tab

  1. Keyboard accuracy heatmap: Render keyboard layout with per-key accuracy coloring (green=100%, yellow=90-100%, red=<90%)
  2. Slowest/Fastest keys tables: Top 5 each with average time in ms
  3. Word/Character stats: Total correct/wrong counts

Key files to modify/create:

  • src/ui/components/stats_dashboard.rs — complete rewrite with tabs
  • src/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 persisted
  • src/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) to Length(5) in main.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 fetching
  • src/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

  1. Issue 1 (Settings menu fix) — quick fix, unblocks testing
  2. Issue 2 (Text contrast) — quick theme change
  3. Issue 5 (Keyboard size) — quick layout fix
  4. Issue 3 (Word generation) — medium complexity, core improvement
  5. Issue 4 (Stats dashboard) — large UI rewrite
  6. Issue 6 (Internet content) — medium complexity, requires new dependency

Verification

  1. cargo build — compiles without errors
  2. cargo test — all tests pass
  3. 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)