76b1f99277942ae756bbd57c43fc5cc6d2f386ba
`draw_inner` read `needs_redraw` without consuming it, and used it to skip the whole `if let Some(active)` block -- including the `remove(id, false)` that frees a redrawn widget's previous primitives. So a widget that was both already active and marked dirty, and was reached by an *ancestor's* draw rather than by `redraw_updates` picking it first, drew a second full set of primitives and then had `active.insert` overwrite the only handles that could ever have freed the first set. Those primitives stay in the layer's instance buffer for the life of the process, with a leaked move slot and leaked mask refs, drawn every frame at whatever region they last had -- and `List` sets no mask, so a row measured at `GENEROUS_PADDING` leaves its ghost outside the list's own box. That is the doubled `Compacted:` row in docs/bench/iris-phone-v2-2026-09-06.md: overlapping copies inside the transcript and one more below the composer. Fixed by consuming the mark (`needs_redraw.remove`) at the top of `draw_inner` -- this call *is* the redraw it asked for -- and freeing the old primitives on the dirty path too. Guarded so it cannot come back silently: `UiRenderState::orphaned_primitives` walks every layer's live instances and names any whose owner is no longer active or no longer holds a handle to them, and `update` `debug_assert!`s it empty every frame (debug builds only). New regression test `an_ancestor_redrawing_a_dirty_row_leaves_no_stale_copy` in list.rs fails on the pre-fix code with "1 primitive(s) survived their own widget's redraw". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Languages
Rust
53%
Kotlin
44.4%
Shell
2.6%