Skip to content

Release 2.2.5 - #44

Merged
egno merged 70 commits into
mainfrom
dev
Oct 14, 2026
Merged

egno merged 70 commits into
mainfrom
dev

Conversation

@egno

@egno egno commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fewer saved pairs — FinishSession saves pair tallies only for the newest key's gate cohort and the lesson's focus pair, so a session row stays small as the alphabet grows. Pairs with a space are never tallied (is_excluded_transition replaces is_same_key_transition). Focus selection drops the weak-pair rule and pair review: a pair is the focus only through the unlock gate. Lesson pair weights cover the gate cohort only.
  • Shorter tallies — session rows store key and pair tallies as stats.keys_avg / stats.pairs_avg, [samples, avg_ms, errors, attempts] with the mean time in whole ms. Older stats.keys / stats.pairs rows still read.
  • Version bumped to 2.2.5.

Test plan

  • uv run pytest -q — 654 passed
  • uv run pytest -q -m snapshot — 4 passed
  • uv run ruff check && uv run ruff format --check && uv run pyright — clean

🤖 Generated with Claude Code

Egno and others added 30 commits July 29, 2026 11:05
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>
Egno and others added 29 commits August 21, 2026 14:04
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>
The stats screen speed line now shows wpm or cpm, as set in Settings,
instead of the ratio to the goal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stats screen shows speed in the target speed unit (wpm or cpm) from
Settings, instead of the ratio to the goal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each session saves its real CPM and WPM. The WPM <-> CPM rate is the
measured keystrokes per word (spaces included) over the recent session
window, with the word-length midpoint + 1 as the fallback. The target
speed is saved in its own unit; a WPM goal becomes CPM through the
measured rate. Older settings files migrate at load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sessions save their real CPM and WPM, the WPM <-> CPM rate is measured
from recent sessions, and the target speed stays fixed in its unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Startup deletes older index rows (PruneSessionHistory replaces
PruneSessionStats). Git sync prunes each merged index with the same
monotone rule, so a device with old rows cannot bring them back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session history keeps the newest 20 sessions per layout plus today's,
at startup and in git sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A session saves pair tallies only for the newest key's gate cohort and the
lesson's focus pair; pairs with a space are never tallied. A pair is the
focus only through the unlock gate. Key and pair tallies are stored as
[samples, avg_ms, errors, attempts]; older rows still read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sessions save pair tallies only for the unlock gate and the focus pair,
never pairs with a space, and store tallies with the mean time in ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@egno
egno merged commit c3d883c into main Oct 14, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants