From c0a0f8a9ad3a536f80736311b364416e2bfcdead Mon Sep 17 00:00:00 2001 From: Finde Xumara Date: Wed, 9 Sep 2026 16:28:53 +0200 Subject: [PATCH] fix(live): discard restores the original without a blank line above and below it `extract_original` split the wrapper's inner text on newlines and kept the first and last pieces, which are the line breaks around the original element inside `
`. Every discard therefore left one empty line before and one after the restored element. `extract_variant` already trims those; the original now gets the same trim. Test writes a JSX wrapper to disk, discards, and checks the file byte for byte. Co-Authored-By: Claude Fable 5.1 --- crates/live/src/live_accept.rs | 73 ++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/crates/live/src/live_accept.rs b/crates/live/src/live_accept.rs index d197ed404..7844af059 100644 --- a/crates/live/src/live_accept.rs +++ b/crates/live/src/live_accept.rs @@ -1093,12 +1093,26 @@ fn extract_inner_by_attr(text: &str, attr: &str) -> Option { None } +/// Drop leading and trailing blank lines while keeping a single empty line +/// when the inner text is only whitespace. +fn trim_surrounding_blank_lines(lines: Vec) -> Vec { + let mut start = 0usize; + let mut end = lines.len(); + while end - start > 1 && trim(&lines[start]).is_empty() { + start += 1; + } + while end - start > 1 && trim(&lines[end - 1]).is_empty() { + end -= 1; + } + lines[start..end].to_vec() +} + /// JS: extractOriginal(lines, block) fn extract_original(lines: &[String], block: &MarkerBlock) -> Vec { let text = strip_style_and_join(lines, block); match extract_inner_by_attr(&text, "data-impeccable-variant=\"original\"") { None => Vec::new(), - Some(inner) => inner.split('\n').map(String::from).collect(), + Some(inner) => trim_surrounding_blank_lines(inner.split('\n').map(String::from).collect()), } } @@ -1113,13 +1127,7 @@ fn extract_variant( &text, &format!("data-impeccable-variant=\"{}\"", variant_num), )?; - let mut result: Vec = inner.split('\n').map(String::from).collect(); - while result.len() > 1 && trim(&result[0]).is_empty() { - result.remove(0); - } - while result.len() > 1 && trim(result.last().unwrap()).is_empty() { - result.pop(); - } + let result = trim_surrounding_blank_lines(inner.split('\n').map(String::from).collect()); if result.is_empty() { None } else { @@ -1249,6 +1257,55 @@ fn find_session_file(id: &str, cwd: &str) -> Option<(String, String, Vec Some((file, content, lines)) } +#[cfg(test)] +mod discard_tests { + use super::*; + + #[test] + fn discard_restores_the_original_without_blank_lines_around_it() { + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or(0); + let dir = std::env::temp_dir().join(format!( + "impeccable-discard-{}-{nanos}", + std::process::id() + )); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let file = dir.join("Login.tsx"); + let src = [ + "
", + "
", + " {/* impeccable-variants-start ab12cd34 */}", + " ", + " {/* Original */}", + "
", + " ", + "
", + " {/* Variants: insert below this line */}", + "
", + " ", + "
", + " {/* impeccable-variants-end ab12cd34 */}", + "
", + " ", + ]; + let lines: Vec = src.iter().map(|s| s.to_string()).collect(); + let path = file.to_string_lossy().into_owned(); + std::fs::write(&file, lines.join("\n")).unwrap(); + handle_discard_unlocked("ab12cd34", &lines, &path).unwrap(); + let out = std::fs::read_to_string(&file).unwrap(); + let _ = std::fs::remove_dir_all(&dir); + assert_eq!( + out, + " \n \n " + ); + } +} + #[cfg(test)] mod bake_tests { use super::*;