Persist ranked-only mastery and use it consistently across progression, milestones, counts, and localized UI. Preserve mastery during history rebuilds, replay errors accurately, and generate replay-derived test profiles. Document the follow-up plan to remove the drill history cap.
Follow-up cleanup to the previous commit:
- rustfmt src/run.rs. The mechanical crate:: qualification pushed lines
past 100 chars and left 'use crate::i18n::t' out of sort order; the
original main.rs had been rustfmt-clean. Verified whitespace-only:
the token stream is identical modulo trailing commas and that one
import moving to its sorted position.
- Drop #![allow(dead_code)] from lib.rs. It existed because the library
was a benches-only shim exposing modules nothing called. Now that the
lib IS the application, it masks nothing -- cargo check is warning-free
without it, so removing it restores real dead-code detection.
- Collapse the duplicated explanatory comments. The rationale lived in
both lib.rs and main.rs; state it once as a lib.rs doc comment and
leave main.rs as the three lines it should be. Full history is in
docs/BUILDING.md.
Replace the 'known remaining inefficiency' section with the layout that
now exists, plus the measured before/after (clean release build: 2m14s
-> 1m46s wall, keydr units 61.0s -> 32.4s, peak ~2.0 GB unchanged).
Add a maintenance note: new modules go in src/lib.rs, not src/main.rs.
Re-declaring them in the binary would silently restore the double
compile this refactor removed.
src/main.rs re-declared the entire module tree (`mod app; mod config;
...`), which made the binary a second independent crate. Every module --
and the whole rust-i18n translation table generated by `i18n!` -- was
compiled twice, and 281 unit tests were executed twice.
Move all application code to src/run.rs inside the library, exposed as
`keydr::run()`. main.rs is now a 10-line entry point.
Mechanical changes only; no logic was altered:
- src/run.rs is src/main.rs minus the crate-root preamble
- `fn main()` -> `pub fn run()`; `struct Cli` -> `pub struct Cli`
- bare `use app::...` / inline `ui::theme::Theme` references are now
qualified with `crate::` (19 imports, 342 inline references), since
they are no longer resolving from the crate root
Clean release build, measured with --timings:
before after
keydr (bin) 33.7s 0.3s
keydr (lib) 26.0s 31.0s
keydr total 61.0s 32.4s (-28.6s)
total unit-seconds 292.2s 264.5s
wall clock 2m14s 1m46s (-28s, -21%)
Test coverage is unchanged: 341 unique tests before and after (verified
by diffing `--list` output, not just counts). The apparent drop from
622 to 340 executions is the duplicate run disappearing -- 281 tests
previously ran once per crate copy.
Verified the binary still works: --help/--version intact, TUI renders
its welcome screen under tmux, and translations resolve (checked both
English and German).
Note: `cargo check --benches` fails with 3 errors, but those are
pre-existing on main (NgramStat field renames) and unrelated.
The previous text claimed restructuring main.rs to use the lib crate
would 'roughly halve' build time. Measured with --timings on a clean
release build, that was wrong:
33.7s keydr (bin)
26.0s keydr (lib)
1.4s generate_test_profiles (bin)
61.0s of 292.2s total unit-seconds (21%)
The duplicated units are indeed the two slowest in the graph, but the
saving is ~26s of unit-work (less in wall time, since they overlap with
dependency compilation across 3 jobs), not half the build. The other
~79% is one-time dependency compilation that incremental builds skip.
Replace the estimate with the measurement.
systemd-oomd is desktop-only on Ubuntu (reverse-deps are ubuntu-desktop*);
'systemctl is-enabled systemd-oomd' returns not-found on this server, so
the previous advice to enable it would simply have failed. Recommend
earlyoom instead, with --avoid/--prefer tuned to protect the shell and
target rustc.
Also document the machine-wide alternatives to per-project tuning:
- sudo systemctl set-property user-1000.slice MemoryHigh=/MemoryMax=
(covers every shell and build; sshd lives in system.slice so it can
never lock you out)
- global ~/.cargo/config.toml, which does support [profile.*]
Clarify that the MemoryHigh warning applies to short-lived build scopes,
not to slice-level limits where gradual throttling is appropriate.
Release builds consumed 5+ GB and never completed on a 4-core / 7.6 GB
VM, thrashing the machine badly enough to lose SSH access.
Root cause was a codegen bug in rust-i18n v3, not machine size. Its
macro emitted one HashMap::from([...]) and one add_translations() call
per translation string, all in a single function body -- 8,652 of each
for our 21 locales / ~9,000 keys. LLVM scales superlinearly on function
size, so optimising that one initialiser took ~4.5 GB.
Verified via cargo expand (lib target):
v3.1.5 v4.2.1
HashMap::from 8,652 0
add_translations 8,652 21 (one per locale)
expanded lines 93,026 73,444
Fixed upstream in v4.0.0.
Clean release builds, same machine:
v3, opt-level=3, thin LTO 5.2 GB stalled >18m, never finished
v3, opt-level=1, no LTO 1.8 GB 15m30s (previous workaround)
v4, opt-level=3, thin LTO 2.08 GB 3m25s
v4, stock cargo defaults 1.92 GB 2m17s
Also:
- Remove the opt-level=1 / lto=false workaround; cargo's release
defaults are correct now. No shipped code is de-optimised.
- Keep only genuine small-machine hygiene in .cargo/config.toml:
jobs=3 (keeps the box interactive), lld linker, and trimmed debug
info on dev/test profiles.
- Document the diagnosis, the cargo expand / llvm-lines workflow that
found it, and the systemd-based safety nets in docs/BUILDING.md.
- Note in main.rs why i18n! must appear in both crate roots (t! expands
to crate::_rust_i18n_t, and main.rs re-declares the module tree
rather than depending on the lib target).
Test suite passes; release binary verified working.
Adds rust-i18n and refactors all of the text copy in the app to use the
translation function so that the UI language can be dynamically updated
in the settings.