Repository navigation
bug: explicit corrections can fail to learn against the visible reading #22
Description
Regression
PR #18 correctly made plain confirmation preserve the visible reading, but its explicit-correction learning branch still uses hidden final scoring as the baseline. An unchanged word can visibly be Japanese while final scoring prefers English. Choosing English then commits correctly but is not learned; cycling back to the original Japanese can instead create a false correction.
I introduced the retained final-scoring exception in #18. The display-preserving confirmation contract should stay; the correction baseline needs bounded provenance, not a rollback or blanket replacement of final scoring.
Reproduction
Use a fresh persistent synthetic store, with English, Korean and Japanese enabled and live conversion disabled:
- Type
siasia: the preview isしあしあ. - Use Esc to select the literal English
siasia, then Enter. The number of Esc presses depends on the configured language order. - Repeat in a new session using the same store.
- Reopen the engine/store and type it again.
Expected: first English correction becomes pending, the second is learned, and the reopened engine shows English.
Actual on v0.10.10: both confirmations insert English, but pending/learned remain empty and reopening still shows Japanese. ozzy (おっzy) has the same problem. A full preconfirmation cycle back to the original Japanese also creates an unnecessary pending Japanese correction.
Minimal Rust test
Save as crates/core/tests/public_origin_repro.rs, then run:
cargo test -p tacit-core --all-features --test public_origin_repro -- --nocaptureuse tacit_core::session::testing::FakeTextField;
use tacit_core::{make_engine, AppPolicies, InputSession, Key, Language, Languages};
#[test]
fn explicit_english_correction_learns_the_visible_japanese_origin() {
let path = std::env::temp_dir().join(format!("tacit-public-origin-{}.json", std::process::id()));
assert!(!path.exists(), "use a fresh synthetic store");
let engine = make_engine(Some(path.clone()), &Languages::legacy(true));
for round in 1..=2 {
let mut session = InputSession::new(engine.clone(), AppPolicies::default(), true);
let mut field = FakeTextField::new();
for ch in "siasia".chars() { session.handle(Key::Letter(ch), &mut field); }
assert_eq!(field.text(), "しあしあ");
// This legacy profile orders languages en, ko, ja; other orders can need more Esc presses.
session.handle(Key::Escape, &mut field);
assert_eq!(field.text(), "siasia");
session.handle(Key::Enter, &mut field);
assert_eq!(field.text(), "siasia");
if round == 1 {
assert_eq!(engine.pending().get("siasia"), Some(&Language::English));
} else {
assert_eq!(engine.learned().get("siasia"), Some(&Language::English));
}
}
drop(engine);
let reopened = make_engine(Some(path.clone()), &Languages::legacy(true));
assert_eq!(reopened.learned().get("siasia"), Some(&Language::English));
let mut session = InputSession::new(reopened.clone(), AppPolicies::default(), true);
let mut field = FakeTextField::new();
for ch in "siasia".chars() { session.handle(Key::Letter(ch), &mut field); }
assert_eq!(field.text(), "siasia");
drop(session);
drop(reopened);
std::fs::remove_file(path).unwrap();
}The snippet was executed against the exact v0.10.10 source and fails at the first pending-English assertion.
Revision controls and cause
- Before fix: make plain confirmation accept the visible reading #18:
ce7bd934eb784990cc6db86ac76a33d7f2e2bbfclearns the repeated English correction; returning to the original Japanese does not create one. - fix: make plain confirmation accept the visible reading #18 merge:
9c02a6d7ab68669cb02e00b971c7e1e10b4d680ereproduces both failures. - v0.10.10:
9a7a219a65414dc8f7ff69bee010e5828e24f2e4reproduces both failures. - Revision comparisons used separate source/build directories and unique persistent stores. Existing session/Japanese tests still passed before adding the regressions.
The forced branch in committed_language chooses decide(false) for learning. That can make the explicit English choice equal the hidden baseline, despite correcting a Japanese display.
The repair should retain the first actually shown language across unchanged cycles, invalidate it on key/context changes, and preserve current-token final scoring after edits/restoration. Before-digit confirmation must retain its existing confidence policy. Japanese Space's separate recording path also needs coverage, without changing learning delivery/dedup timing.
These are core/session and C ABI observations on macOS, not physical-key timing, representative error-frequency measurements, or independent Windows/Linux runtime results. No personal learning data is involved.
Activity
fix: learn corrections from the visible reading origin