From b7d04009a2e0f13f65060fb07b2ae308f1ac7f49 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:17:50 +0100 Subject: [PATCH] fix(ui): refresh font-size text when selection becomes mixed Include the mixed-size flag in the font-size draft cache key. Selecting a mixed block can preserve the previous representative number, so a value-only key left the field showing 24 instead of Mixed. Unchanged formatting still preserves partially typed input across frames. The headless regression covers uniform-to-mixed and mixed-to-uniform transitions with the same number, draft preservation and a single committed edit. Linux scoped clippy, tests, wasm, formatting, layering and assets pass; reverting the cache fix reproduces the stale display. Co-Authored-By: Codex GPT-6.1 --- crates/ui-egui/src/panels/format_text_ui.rs | 52 +++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/panels/format_text_ui.rs b/crates/ui-egui/src/panels/format_text_ui.rs index e3d4463607..3a3fa7d517 100644 --- a/crates/ui-egui/src/panels/format_text_ui.rs +++ b/crates/ui-egui/src/panels/format_text_ui.rs @@ -465,8 +465,10 @@ fn font_size_input(ui: &mut egui::Ui, size: &mut f64, mixed: bool) -> bool { let initial_text = || { if mixed || *size <= 0.0 { tl!("Mixed").to_string() } else { size.to_string() } }; - let (previous, mut text) = ui.data(|d| d.get_temp::<(u64, String)>(id)).unwrap_or_else(|| (size.to_bits(), initial_text())); - if previous != size.to_bits() { + // A selection can become mixed without changing its representative numeric size. + let (previous, previous_mixed, mut text) = + ui.data(|d| d.get_temp::<(u64, bool, String)>(id)).unwrap_or_else(|| (size.to_bits(), mixed, initial_text())); + if previous != size.to_bits() || previous_mixed != mixed { text = initial_text(); } let field = egui::TextEdit::singleline(&mut text).id(id).desired_width(40.0); @@ -502,7 +504,7 @@ fn font_size_input(ui: &mut egui::Ui, size: &mut f64, mixed: bool) -> bool { if *size != before { text = size.to_string(); } - ui.data_mut(|d| d.insert_temp(id, (size.to_bits(), text))); + ui.data_mut(|d| d.insert_temp(id, (size.to_bits(), mixed, text))); }); committed } @@ -539,6 +541,50 @@ mod typed_size_tests { } } + #[test] + fn mixed_size_changes_refresh_the_field_without_discarding_a_draft() { + let mut h = egui_kittest::Harness::new_ui_state( + |ui, state: &mut (f64, bool, usize)| { + if font_size_input(ui, &mut state.0, state.1) { + state.1 = false; + state.2 += 1; + } + }, + (12.0, false, 0), + ); + h.run(); + let field = + |h: &egui_kittest::Harness<'_, (f64, bool, usize)>| h.get_by_role_and_label(egui::accesskit::Role::TextInput, "Font size:").value(); + assert_eq!(field(&h).as_deref(), Some("12")); + h.state_mut().0 = 24.0; + h.run_steps(2); + assert_eq!(field(&h).as_deref(), Some("24"), "the heading selection updates the size"); + h.state_mut().1 = true; + h.run_steps(2); + assert_eq!(field(&h).as_deref(), Some("Mixed"), "collapsing to a mixed block must refresh even when its representative size stays 24"); + if let Ok(path) = std::env::var("PDFCRAFT_MIXED_SIZE_SHOT") { + h.render().unwrap().save(path).unwrap(); + } + h.state_mut().1 = false; + h.run_steps(2); + assert_eq!(field(&h).as_deref(), Some("24"), "selecting the heading again clears Mixed without a numeric change"); + h.state_mut().1 = true; + h.run_steps(2); + h.get_by_role_and_label(egui::accesskit::Role::TextInput, "Font size:").click(); + h.run(); + h.event(egui::Event::Key { key: egui::Key::A, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::COMMAND }); + h.event(egui::Event::Text("1".into())); + h.run_steps(3); + assert_eq!(field(&h).as_deref(), Some("1"), "an unchanged mixed style must preserve the draft"); + h.event(egui::Event::Text("8".into())); + h.run_steps(3); + assert_eq!(field(&h).as_deref(), Some("18")); + assert_eq!(*h.state(), (24.0, true, 0), "typing does not apply a formatting change"); + h.event(egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }); + h.run_steps(2); + assert_eq!(*h.state(), (18.0, false, 1)); + } + #[test] fn typed_size_commits_once_and_invalid_input_preserves_the_style() { let mut h = egui_kittest::Harness::new_ui_state(