9169ac5c52
collect_boundaries walked every node in the file even though the answer is bounded by the visible window, which cost roughly twice the parse: on an 81KB source the walk was 8.95ms against a 4.32ms parse, and on 1MB it was 138.8ms against 87.6ms. A node contributes a boundary only when one of its own endpoint lines is visible, and both of those lines lie inside its raw row span. Every descendant's span is contained in its ancestor's, so a span holding no visible line rules out that node and everything beneath it. Skip such subtrees with a binary search over the merged visible ranges. The test is the raw span, not endpoint visibility: a node whose span merely straddles the window has both endpoints outside it yet can contain a child that opens exactly on a visible line. The raw span is also conservative relative to node_content_end_line, so the prune needs no reasoning about that newline adjustment. Equivalence is proven differentially rather than argued: the pre-prune walk is retained under cfg(test) and compared for exact Option<Vec<u32>> equality across 4827 .ts/.py/.rs files and 38,616 comparisons over eight window shapes, including whole-file-visible, past-EOF, disjoint ranges, empty range lists and files that fail to parse. Zero mismatches. root.has_error() is still evaluated on the whole tree before the walk, so pruning cannot change a None verdict. Measured on the built addon with a mid-file 40-line window, medians of 20, against the parse cache alone: 81KB 13.4ms -> 4.45ms cold and 9.04ms -> 0.149ms warm; 1.06MB 188.1ms -> 55.8ms cold and 131.7ms -> 0.440ms warm.