d507ae4c96df40a9002a07ab59fe8ea2155f2c6c
`Painter::set_mask` refused a widget any mask of its own once an ancestor had set one -- `assertion failed: self.mask == MaskIdx::NONE` -- so clipping was one level deep wherever it was used at all. That is what stopped the transcript's `List` from being clipped to its own box: its rows already use `.masked()` themselves (a code fence, a tool card's one-line title), and giving the list one aborted on the first fence drawn. A mask now carries the mask it was set inside (`Mask::parent`) and the fragment stage walks that chain, so a pixel has to be inside every mask on it. Chained rather than intersected on the CPU because each mask moves with its own widget: a fence inside a transcript row carries the row's scroll and the list's box does not, and one region resolved when the fence was last drawn gets the second of those wrong as soon as the row is moved rather than redrawn -- which is every scroll frame. The child holds one ref on its parent's slot, released where the child's own slot is, so a chain cannot outlive what it points at. The old assert survives as the case that is still wrong: the same widget setting two masks, which since a mask now chains would be a clip loop. Also `Painter::draw_again`, for a layout that can only discover a correction to itself by laying out once -- `List::clamp_to_content`, in the commit after this -- and `Painter::is_masked`, which is how a widget that draws outside its own box can require something to be clipping it.
Languages
Rust
53%
Kotlin
44.4%
Shell
2.6%