iris: masking is opt in, and a LazySpan only culls

Iris, correcting the previous commit: "Why does the mask matter at all.
If you want a mask then you add .masked(). It should just prevent rows
that aren't in its region at all from drawing ... Just like the opt in
scrollable, masking should be opt in."

So `LazySpan` sets no mask. It culls -- a row entirely outside the box it
was offered is never drawn, which `intersects_viewport` already did -- and
draws a straddling row in full, because virtualisation decides which rows
and never how much of one. Cutting off that overhang is `.masked()`, added
by whoever wants it.

The transcript wants it (it is a list under a header bar) and opts back
in; the benchmark does not and needs no ceremony. `top_edge.rs` goes back
to reading the mask the list *inherited*, which is now also the test that
the transcript is still asking for one.

The previous commit had the span mask itself, which fixes the panic and is
still the widget deciding what is not its to decide.
This commit is contained in:
iris committed 2026-09-08 23:08:13 -04:00
1 parent afbc2ad132
commit e9a6562dc6
4 files changed
+58 -47

No files matched your search

+16 -10
View File
@@ -16,18 +16,24 @@ it helps judge the change without the session that made it. Newest first.
Four things you asked for, in one change.
**A lazy span no longer cares about masks.** It used to assert that
**A lazy span knows nothing about masks.** It used to assert that
something around it had called `.masked()` and refuse to draw otherwise,
which is why a plain full-screen list -- the benchmark, any simple app --
panicked on its second line. Your question was the right one: it cared
only because it draws a row straddling an edge *in full* (virtualisation
decides which rows, never how much of one) and relied on somebody else to
cut off the overhang. It clips itself to the box it was offered now, so a
caller places it like any other widget. That is also strictly stronger
than the assert was: a mask *larger* than the list's box satisfied
`is_masked` and let the overhang through anyway, which is the fault the
assert was written for. The transcript's own `.masked()` wrapper is gone
with it.
panicked on its second line. What it does now is only the part that is
its own: it *culls*, so a row entirely outside the box it was offered is
never drawn, and a row straddling an edge is still drawn in full, because
virtualisation decides which rows and never how much of one. Whoever
wants that overhang cut off adds `.masked()`, exactly as whoever wants
scrolling adds `.scrollable()` -- your words: "masking should be opt in".
The transcript opts in, because it is a list under a header bar; a
full-screen list does not, and the widget has no business assuming either.
I got this wrong once on the way: the first version had the span set a
mask of *itself*. That fixes the panic and is still the widget deciding
something that is not its to decide -- a caller already clipped by
something bigger ends up double-masked, and one that wants the overhang
has no way to say so. Worth recording as the shape to avoid, since it
looks like the tidy answer.
**Everything on the transcript screen is capped now.** One rule in one
place -- `client_core::text_cap`, mirrored as `TextCap.kt` with the same