fix: skipped oversized spans while continuing BFS summary unfolding

- Changed `select_folded_spans` to skip an unfold candidate that exceeded `unfold_limit` and continue processing the remaining BFS queue instead of breaking.
- Added a regression test in `summary.rs` proving a large `<style>` raw-text span no longer starves sibling HTML sections from unfolding.
- Updated the summary unfold-ceiling setting description to document that overflowed candidates stay folded while BFS continues.
This commit is contained in:
can1357
2026-06-17 09:24:19 +02:00
parent d5b3c78132
commit 2ccbd0bbea
3 changed files with 64 additions and 6 deletions
+59 -5
View File
@@ -108,10 +108,13 @@ impl ElidableForest {
/// BFS unfold. Start with every root span folded (matches the legacy
/// outermost-only behavior) and progressively replace folded spans with
/// their elidable children, breadth-first, until the visible line count
/// reaches `unfold_until`. A candidate unfold that would push the visible
/// count past `unfold_limit` is rejected and aborts the loop ("revert the
/// last unfold because it overflew"). `unfold_until == 0` short-circuits
/// to the legacy behavior.
/// reaches `unfold_until`. A candidate unfold whose revealed lines would
/// push the visible count past `unfold_limit` is skipped — that span stays
/// folded and its subtree is not explored — while the BFS keeps unfolding
/// the remaining queued siblings. A single oversized, un-unfoldable leaf
/// (e.g. a `<style>` raw-text block) therefore no longer aborts the whole
/// pass and starve its siblings. `unfold_until == 0` short-circuits to the
/// legacy behavior.
fn select_folded_spans(
forest: &ElidableForest,
total_lines: u32,
@@ -145,7 +148,7 @@ fn select_folded_spans(
let revealed = node.span.lines().saturating_sub(child_line_total);
let new_visible = visible.saturating_add(revealed);
if new_visible > unfold_limit {
break;
continue;
}
folded.remove(&idx);
for &c in &node.children {
@@ -1311,4 +1314,55 @@ mod tests {
1
);
}
#[test]
fn bfs_unfold_skips_unfoldable_leaf_and_continues_to_siblings() {
// Repro for the "useless HTML summary" report: a big <style> raw-text
// block (no elidable children) sits before a structured <body>. The
// style block's only unfold candidate is its entire 120-line body,
// which overflows unfold_limit. The BFS must SKIP it (leave it folded)
// and keep unfolding the sibling <body>/<div> subtree, instead of
// aborting the whole pass and collapsing the body into one dead `...`.
let css = (0..120)
.map(|i| format!("\t.rule{i} {{ color: #{i:06x}; }}"))
.collect::<Vec<_>>()
.join("\n");
let sections = (0..6)
.map(|i| {
format!(
"<section class=\"sec{i}\">\n<h2>Heading {i}</h2>\n<p>para a {i}</p>\n<p>para b \
{i}</p>\n<p>para c {i}</p>\n</section>"
)
})
.collect::<Vec<_>>()
.join("\n");
let code = format!(
"<!doctype html>\n<html \
lang=\"en\">\n<head>\n<title>T</title>\n<style>\n{css}\n</style>\n</head>\n<body>\n<div \
class=\"page\">\n{sections}\n</div>\n</body>\n</html>\n"
);
let result = summarize_with_unfold(&code, "page.html", 50, 100);
assert!(result.parsed);
assert!(result.elided);
let kept_text = result
.segments
.iter()
.filter(|s| s.kind == "kept")
.filter_map(|s| s.text.as_deref())
.collect::<Vec<_>>()
.join("\n");
// The body div was unfolded: its child <section> structure is visible.
assert!(
kept_text.contains("<section class=\"sec0\">"),
"body div should unfold to show sections"
);
assert!(
kept_text.contains("<section class=\"sec5\">"),
"all sibling sections should surface"
);
// The <style> raw text stays folded as one elided span — no CSS interior leaks
// into kept content.
assert!(!kept_text.contains(".rule0 {"), "oversized style body must stay folded");
}
}
@@ -2658,7 +2658,7 @@ export const SETTINGS_SCHEMA = {
group: "Read Summaries",
label: "Read Summary Unfold Ceiling",
description:
"Hard ceiling on summary size while BFS-unfolding. An unfold that would exceed this is reverted and unfolding stops.",
"Hard ceiling on summary size while BFS-unfolding. An unfold whose revealed lines would exceed this is skipped (that span stays folded) and unfolding continues with the remaining spans.",
},
},
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed `summarizeCode` BFS unfold aborting the entire pass when it hit an oversized, un-unfoldable leaf span (e.g. an HTML `<style>` raw-text block, an embedded blob, or a minified line) whose only unfold candidate is its whole body. The overflow check used to `break` the breadth-first loop, so any large leaf encountered before its siblings starved the rest of the tree — an HTML page summarized to `<style> ... </style>` plus `<div class="page"> ... </div>`, collapsing the document body into one dead `...`. An overflowing span is now skipped (left folded, its subtree unexplored) and the BFS keeps unfolding the remaining queued siblings, so structured siblings like the `<body>` DOM are revealed up to `unfoldLimit` while the oversized leaf stays folded.
## [16.0.2] - 2026-06-16
### Added