Repository navigation
Conversation
Expose session window and attempt floors in settings so unlocks, focus, and heatmap respond to tunable confidence inputs; round scores to two decimals so weak/mastered decisions match what practice displays. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Remove session window and attempt floors from Settings so routine saves do not overwrite advanced adaptive-engine values; document settings.toml. Co-authored-by: Cursor <cursoragent@cursor.com>
…in comments. Also fixes a pyright error in _snapshot_unlock_state (list invariance on Iterator vs list) that CI never reached because ruff failed first.
FOCUS_CHAR_BOOST weighting (added in b0f130e) changed the generated lesson text but the checked-in snapshot was never refreshed to match.
Confidence uses min(speed, accuracy) with recency-weighted session aggregates; stats trends unified on one widget; practice gets word-wrap, typing colors, underline cursor, idle learn timer, and HUD Last deltas. Co-authored-by: Cursor <cursoragent@cursor.com>
Keep dev 1.2.3 release changes when resolving conflicts with main (1.2.2). Co-authored-by: Cursor <cursoragent@cursor.com>
Ruff import order, line length, unused imports, and aggregate int casts. Co-authored-by: Cursor <cursoragent@cursor.com>
Run `uv run pre-commit install` once per clone to enable lint on commit. Co-authored-by: Cursor <cursoragent@cursor.com>
Matches CI: uv run pyright with pass_filenames false so pyproject.toml scope applies. Co-authored-by: Cursor <cursoragent@cursor.com>
Use explicit grid/spark_width kwargs in format_metric_trend_block and TypedDict session fixtures in tests instead of untyped dict unpacking. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Baselines were stale after 1.2.3 presentation changes; README now lists what pre-commit covers vs the macOS snapshot job. Co-authored-by: Cursor <cursoragent@cursor.com>
…ut dropdown when TOML layouts change without restart. Bump to 1.2.3 and expand layout TOML/repo tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Word-list sampling now scores dictionary words with transition weights so adaptive lessons practice measured weak pairs, not just weak keys. Co-authored-by: Cursor <cursoragent@cursor.com>
…a settings.toml. New keys stay locked until unlocked bigrams with data meet the confidence threshold; lesson generation boosts weak focus keys and transitions using configurable multipliers (not exposed in the Settings UI). Co-authored-by: Cursor <cursoragent@cursor.com>
Same-letter pairs no longer enter transition stats or unlock gates; lesson building favors letter drill while any unlocked key is below mastery. Leading skip keys also apply at word boundaries for accidental spaces. Co-authored-by: Cursor <cursoragent@cursor.com>
Weak-focus lessons now guarantee a configurable share of focus-matching words, sample the wordlist without replacement, and fall back to Markov when the pool is exhausted. Adds lesson_word_count, focus_word_min_fraction, and max_word_repeats to settings.toml, fixes empty-transition rebuild loops, and hardens edge cases around invalid settings and tiny alphabets. Co-authored-by: Cursor <cursoragent@cursor.com>
Users can set min/max generated word length in settings; the generator, Markov fallback padding, and WPM↔CPM conversion use those bounds so target speed matches the text they actually type. Co-authored-by: Cursor <cursoragent@cursor.com>
Dictionary words in lessons now honor generated_word_min/max (same as Markov), defaults tighten to 2-4 for shorter practice text, with integration tests for settings.toml and WPM conversion paths. Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts: # .pre-commit-config.yaml # README.md # docs/CHANGELOG.md # pyproject.toml # src/keystrike/app.py # src/keystrike/application/build_lesson.py # src/keystrike/application/session_use_cases.py # src/keystrike/application/stats_use_cases.py # src/keystrike/domain/aggregate.py # src/keystrike/domain/confidence.py # src/keystrike/domain/version.py # src/keystrike/infrastructure/session_repo_jsonl.py # src/keystrike/presentation/screens/practice.py # src/keystrike/presentation/screens/stats.py # src/keystrike/presentation/textual_app.py # src/keystrike/presentation/theme.py # src/keystrike/presentation/widgets/hud.py # src/keystrike/presentation/widgets/kb_heatmap.py # tests/application/test_session_use_cases.py # tests/application/test_stats_use_cases.py # tests/domain/test_aggregate.py # tests/infrastructure/test_session_repo_jsonl.py # tests/presentation/__snapshots__/test_snapshots/test_practice_screen_snapshot.svg # tests/presentation/test_hud.py # tests/presentation/test_kb_heatmap.py # tests/presentation/test_practice_screen.py # tests/presentation/test_stats_screen.py # tests/presentation/test_typing_area.py # uv.lock
Remove duplicated session timer helpers, refresh practice/settings snapshots, and skip Unix permission tests on Windows where chmod semantics differ. Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve conflicts: keep deduped session timer helpers, Windows permission test skips, and dev practice-screen snapshot baseline. Co-authored-by: Cursor <cursoragent@cursor.com>
Heatmap and HUD used attempt-ramped confidence, so calibrating keys could look mastered while lessons still treated them as uncertain. skill_of keeps display honest without changing scheduling; calibrating is first-class in the UI with shorter HUD copy and Focus-States wiki docs. Co-authored-by: Cursor <cursoragent@cursor.com>
Unlock the next key from per-key skill plus attempt floor, not ramped confidence or bigram thresholds. Transition focus uses skill-only gating so calibrating keys no longer block bigram focus; lesson sampling adds coverage-deficit weight for under-sampled keys. Co-authored-by: Cursor <cursoragent@cursor.com>
Wordlist sampling was using generated_word_min/max, so valid imported words were dropped or capped incorrectly while Markov fill should stay on its own 2–4 range. Co-authored-by: Cursor <cursoragent@cursor.com>
Unpracticed auto-unlocked keys no longer block transition focus; two-key drills get an unmeasured-pair fallback and sparse transition weighting. Co-authored-by: Cursor <cursoragent@cursor.com>
…on it Give a just-mastered key's bigrams sampling weight and focus priority right away instead of waiting for them to appear by chance (newest_key_unmeasured_pairs, shared by focus selection and lesson weighting). Extend compute_unlocked with an optional transition gate so the next letter also waits on the newest key's single weakest measured bigram, bounded to one pair with a stall-attempts safety valve so a stuck pair can't block progression forever. Fix session_use_cases' unlock snapshot to pass transitions/gate settings into compute_unlocked too, so alphabet_size can't ratchet past what the gate actually cleared. Move the gate's shared "newest practiced key" primitives into a neutral domain/newest_key module so focus.py and unlock.py depend on it instead of each other. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Keep unlock and focus on measured skill while confidence drives remedial pacing; extract session_queries and refactor BuildLesson gating/focus resolution. Persist word-length bounds on SessionResult (schema v4) for remedial WPM gates and split domain tests with doc updates. Co-authored-by: Cursor <cursoragent@cursor.com>
Resample dictionary words for focus bigrams instead of splicing chars into real words. Co-authored-by: Cursor <cursoragent@cursor.com>
Skill/confidence split for unlocks and focus, bigram progression gating, dictionary vs Markov word bounds, and word-list focus drill fixes. Refresh README, wiki, CHANGELOG, and demo GIF. Co-authored-by: Cursor <cursoragent@cursor.com>
BuildLesson could unlock more keys than settings.alphabet_size once compute_unlocked's mastery-driven growth kicked in, without ever writing that growth back to Settings -- only FinishSession did, and only after the fact. Extract the sync into a shared, contract-documented sync_alphabet_size() and call it from BuildLesson before generating lesson text, so the setting always leads the lesson's letter count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Breaking: settings.toml gains [unlock]/[focus]/[word_gen] tables (old flat keys are dropped, falling back to defaults) and the practice HUD's accuracy/daily-learn segments freeze at lesson start instead of ticking live. Also: alphabet-size sync moves to lesson build to avoid a mid-flow status jump, plus fixes for duplicate-word sampling, settings.toml control- character escaping, bool/int coercion, and aggregates-cache error handling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
main's PR #36 squash-merged Release 1.3.1 as a new commit with a tree identical to dev's b1052ce, so dev and main share no direct ancestor after that point. main has no content dev lacks (git diff b1052ce a807932 is empty) -- resolving every conflict in dev's favor. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The HUD no longer renders a dim idle style, dropping one CSS class from the practice screen's terminal snapshot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntion window A finished session no longer writes sessions/<YYYY-MM>/<ulid>.jsonl. Its keystrokes are reduced at finish time to per-key and per-bigram tallies (SessionResult.stats, [samples, time_ns, errors, attempts]) stored on the index.jsonl row (schema 5). combine_sessions merges those tallies, so heatmap, unlock, focus and trend numbers are unchanged. app.startup() (called by the CLI before build()/build_sync()) folds every existing keystroke log into its index row, deletes the month directories, and drops tallies from sessions older than 2 * confidence_session_window - 1 per layout — the furthest the trend replay reads. Git sync appends index rows only; a legacy remote row is upgraded from the clone's keystroke log on import. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Session history now stores per-key/per-bigram tallies on each index.jsonl row instead of raw keystroke logs (schema 5); existing logs are folded in and removed on first start, and tallies outside the stats retention window (2 * confidence_session_window - 1 sessions per layout) are pruned at startup. Git sync exchanges index rows only. Docs, wiki pages and the demo GIF refreshed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The GIF was rendered from the regression snapshots, which start from an empty store: no sessions, a blank heatmap, 100% accuracy. The generator now drives the real app with the test fakes through ~16 simulated lessons (a typist who is quick on the home row, slow and error-prone on a few keys, and improves over time), then captures Home, a lesson in progress, Stats with trends and a coloured heatmap, one letter's detail, and Settings. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FinishSession now stores SessionResult.weakest_pair: the slowest cross-key pair typed in the session (enough attempts, skill < 1.0). The next lesson's remedial focus starts from it instead of a calibrating pair with only a few presses. The optional field keeps schema 5; rows without it read back as None. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Count attempt floors and the stall cap on real presses in the window, not recency-weighted ones (aggregates cache schema 2). - Score accuracy against a 95% target and keep weighted errors fractional, so one old typo no longer blocks a well-practiced key. - Compare lesson speed in keystrokes per minute, spaces included, so the remedial WPM gate stops firing at exactly target speed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- select_lesson_focus picks the first rule with candidates: keys not cleared, the newest key's unlock-gate pairs, weak pairs ranked by (1 - confidence) x language frequency, then review of the most overdue key or pair. Each rule keeps the last lesson's focus until it clears or stalls at 3x its attempt floor. - Save focus_pair on the session result (only for pair focus) so the next lesson can keep it; drop weakest_pair. - Remove the remedial WPM gate, the saved weakest pair, the newest-key unmeasured-pair fallback and the review penalty in focus scoring. - Move the stall cap to domain.confidence so focus and unlock share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Focus now follows one ordered ladder: keys not cleared, unlock-gate pairs, weak pairs by weakness x language frequency, then review; the last focus is kept until it clears or stalls, and sessions save focus_pair for that. Attempt floors count real presses, and accuracy is scored against 95%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lowering alphabet_size now records an AlphabetCut (time, new size) in Settings and rebuilds the aggregates. Lesson and unlock logic drop the closed keys' tallies, and every bigram that touches them, from sessions before the cut, so a reopened key goes through key focus and its gate pairs again. compute_unlocked also opens at most one key per call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lowering the letter count now makes the closed keys learned again, one at a time, instead of reopening them at once from their old stats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UpdateSettingsrecords anAlphabetCut(time, new size) inSettings.alphabet_cutswhenalphabet_sizegoes down, then rebuilds every layout's aggregates.RebuildAggregatesandFinishSessiondrop the closed keys' tallies, and every bigram that touches them, from sessions before the cut. A reopened key goes through key focus and its gate pairs again before the next key opens.compute_unlockedopens at most one key pastalphabet_sizeper call. Before, 10 → 8 and then one unlock opened both keys 9 and 10.Test plan
uv run pytest -q— 627 passeduv run pytest -q -m snapshot— 4 passeduv run ruff check && uv run ruff format --check && uv run pyright— clean🤖 Generated with Claude Code