fix(vt): list column fix round 3/11 — per-column row-bound click guard

A short column (fewer bound rows than the list's own max-across-columns
row count) already draws nothing past its own row — DrawCheckCell/
DrawIconCell/DrawTextCell all no-op once index >= their own array's
Count. OnEventColumns's click routing didn't share that bound: a click
landing on a row the OVERALL list considers valid (row < the longest
column) but past a SHORTER column's own data would still invoke that
column's callback with a row index it never bound anything for.

Add the same per-column bound (index < columns[c].RowCount()) to the
check-column, icon-column, and text-column-with-onclick click paths —
text-without-onclick's plain SelectionChanged fallback is unaffected,
since row selection is a list-level concept already bounded by the
outer rowCount check.

New tests: a click past a short check/icon/onclick-bearing-text
column's own row count fires nothing (each shown to fail first — the
callback fired with the out-of-bound row index before this guard).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-06 20:56:44 +02:00
parent 96ee7b3214
commit ed94acb862
2 changed files with 105 additions and 5 deletions

View file

@ -418,19 +418,37 @@ public sealed class UiMarkupList : UiElement
// VTank's eight lists actually uses row selection, every
// real text cell is an action target. A text column
// without onclick keeps the original select-the-row
// behavior (the list's own selected/onchange).
// behavior (the list's own selected/onchange), which is
// NOT subject to the per-column row-bound guard below —
// selection is a list-level concept, already bounded by
// the overall rowCount check above.
if (columns[c].TextClicked is { } onTextClick)
onTextClick(index);
{
// Fix round finding 3: a per-column row-bound guard
// — this column's own bound row count can be
// SHORTER than the overall (max-across-columns) row
// count the outer index check above allows, so a
// click past THIS column's own data must still fire
// nothing (matches the draw side, which already
// skips drawing a cell past its own column's rows).
if (index < columns[c].RowCount())
onTextClick(index);
}
else
{
SelectionChanged?.Invoke(index);
}
break;
case UiMarkupListColumnKind.Check:
// A click in a check/icon column fires that column's own
// callback and does NOT change selection.
columns[c].CheckChanged?.Invoke(index);
// callback and does NOT change selection. Same
// per-column row-bound guard as the text-onclick case.
if (index < columns[c].RowCount())
columns[c].CheckChanged?.Invoke(index);
break;
case UiMarkupListColumnKind.Icon:
columns[c].IconClicked?.Invoke(index);
if (index < columns[c].RowCount())
columns[c].IconClicked?.Invoke(index);
break;
}
break;