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 `<div data-impeccable-variant="original">`. 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 <noreply@anthropic.com>
This commit is contained in:
Finde Xumara
2026-09-16 17:57:54 +05:00
committed by Abdul Wahab
co-authored by Claude Fable 5.1
parent 0a4e72a254
commit c0a0f8a9ad
+65 -8
View File
@@ -1093,12 +1093,26 @@ fn extract_inner_by_attr(text: &str, attr: &str) -> Option<String> {
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<String>) -> Vec<String> {
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<String> {
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<String> = 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<String>
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 = [
" </div>",
" <div data-impeccable-variants=\"ab12cd34\" data-impeccable-variant-count=\"3\" style={{ display: \"contents\" }}>",
" {/* impeccable-variants-start ab12cd34 */}",
" <style data-impeccable-css=\"ab12cd34\">{`",
" @scope ([data-impeccable-variant=\"1\"]) { :scope > .x { color: red; } }",
" `}</style>",
" {/* Original */}",
" <div data-impeccable-variant=\"original\">",
" <Bar title=\"Log in\" />",
" </div>",
" {/* Variants: insert below this line */}",
" <div data-impeccable-variant=\"1\">",
" <Bar title=\"Log in\" />",
" </div>",
" {/* impeccable-variants-end ab12cd34 */}",
" </div>",
" </AuthPage>",
];
let lines: Vec<String> = 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,
" </div>\n <Bar title=\"Log in\" />\n </AuthPage>"
);
}
}
#[cfg(test)]
mod bake_tests {
use super::*;