ff351ee014
Copilot's fifth pass; both correct. **Inline group rows are back in `frameCost`.** Omitting them was the same failure I had just fixed for the header block: they take real table height, so any viewport containing one asked for more data rows than `#grid` could show, and the DOM takes its own scroll position while the client's cursor offsets come from the larger window. Copilot's own suggestion answers the oscillation objection — the reservation is a **high-water mark**, which cannot oscillate because it only rises, and it rises in the safe direction: over-reserving leaves blank space, under-reserving is the bug. It resets on a view change so one dense document does not make every later view pessimistic. **`same_prefix` is replaced by `AxisFrame::shares_ancestry`**, the bulk form, for the same reason `header_labels` and `groups` are bulk: called per (column, level) it scanned the frame twice apiece, quadratic in the columns drawn and growing with the horizontal offset, where the pre-anr9 widget's materialized lookup was O(1). `same_prefix` is **deleted** rather than kept beside it — two answers to one question is the thing this PR exists to remove, and keeping a per-position form that nothing calls would be an invitation. `shared_ancestry_widens_as_ the_level_rises` checks both arms, because a version comparing whole tuples passes the leaf arm and a version comparing only the outermost label passes the outer one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>