fix: four Copilot findings on the grid frame

All four were right; two more were right and are declined on the threads.

**A frame's pre-window headers were O(offset).** `axis_frame` accumulated
every group header above the window and flushed the run at the first
in-window item, so a deep scroll past many groups serialized headers that
`body_from` discards on arrival. Only the *nearest* one can ever be answered
with, so only that one is kept.

**`web/app.js` captured the row counter, not its value.** `r` is one binding
shared by every click-handler closure, so after the loop every editable cell
would have called `edit` with the last data row. The group-aware counter was
right; capturing it was not. It is a bug this PR introduced — the old code
took `r` as a `forEach` parameter, which is per-call.

**The agreement suite drove one arm.** It only read `rows`, and its fixture
had no grouped column axis, so `col_levels` and `col_groups` could regress
behind a green suite
(`doctrine(one_arm_of_a_symmetric_rule_is_evidence_about_that_arm_only)`).
The fixture now groups two of three months under `Q1` and leaves `Sep`
outside, with `_Measure` giving a second column level, and
`the_two_front_ends_draw_the_same_column_header` checks the levels against
the painted header lines and the spans against the column count. One span
per column, and a dropped level suppression, were each injected and watched
to fail.

**The group branch did not compare the label.** It asserted only that the
painted text began with an indicator, so any group name passed. It now
compares the whole painted header against the browser's glyph-plus-label.

The column test also *demonstrated* the finding this PR declines: `Q1` spans
all six columns including the two under the ungrouped `Sep`, because
`expand_category` emits a header on entering a group and no boundary on
leaving one. That is `GridLayout`'s rule and the TUI paints the same span.
It is asserted as it is, with the defect and the corrected expectation
written next to it, and stated on `AxisFrame::group_of`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-09-19 22:52:18 -07:00
parent 39008595e2
commit 7c4b90c537
4 changed files with 204 additions and 66 deletions
+5 -1
View File
@@ -300,6 +300,9 @@ function gridTable(model) {
continue;
}
r += 1;
// `r` is one binding shared by every closure below, so a click handler
// that captured it would fire with the LAST row's index. Snapshot it.
const dataRow = r;
for (const label of row.labels) {
const header = document.createElement("th");
@@ -326,7 +329,8 @@ function gridTable(model) {
// cell. An aggregate is a drill target and drill is not on the wire
// yet, so it is simply not offered here.
td.classList.toggle("editable", cell.editable);
if (cell.editable) td.addEventListener("click", () => edit(td, r, c, cell.edit_text));
if (cell.editable)
td.addEventListener("click", () => edit(td, dataRow, c, cell.edit_text));
});
}
return table;