Skip to content

fix: retain child state through insertions and reorders#361

Open
mayakwd wants to merge 1 commit into
pop-os:masterfrom
mayakwd:fix/retain-child-state
Open

fix: retain child state through insertions and reorders#361
mayakwd wants to merge 1 commit into
pop-os:masterfrom
mayakwd:fix/retain-child-state

Conversation

@mayakwd

@mayakwd mayakwd commented Jul 6, 2026

Copy link
Copy Markdown

Context
I'm working on a library for simple UI animations (crossfades, smooth scrolling, animations for UI elements appearing and disappearing, and so on). In the process, I've found a few bugs in the current implementation of the diff between the old and new widget states.

Problem
Tree::children stores the state of the widgets, and in each pass (layout, draw, etc), it pairs the new and old child states based on index. So, reconciliation must assign each surviving child's state to its new index.

Tree::diff_children_custom breaks this contract, which can cause several issues:

  • Insert before a named child - the new child is written into the named child's old slot, overwriting it. Its state is lost, and self.children ends up one shorter than the widget list, so the last widget renders with no state.
  • Remove before a named child - truncate drops the named child from the tail before it is ever matched by name, so its state doesn't survive and resets as a result.
  • Reorder two named children - both diffed in place at their old index and never moved, so the states stay put while the widgets swap - resulting in a state ending up on the wrong child.

Summing up: the surviving widget state, such as scroll offset, text cursor, selection, focus, and in-flight animation, lands on the wrong child or resets whenever a sibling is inserted, removed, or reordered.

Fix
Take the old subtrees and split them into named and positional buckets. Rebuild them, matching the following rules:

  • named children are matched by name
  • the rest by index
  • unmatched slot gets a fresh state
  • extras are dropped

What I've tested

  • Added several unit tests that check where the state ends up: insertion, removal-shift, and reorder tests fail on the current implementation, and pass after the fix.
  • Reproduced with a small app: a column of named cards, each scrolled to different offsets. Inserting a card at the top scrambled the other cards' scroll offsets, causing one to drop before the fix. After the fix, every card retained its offset.
  • (Less relevant, but still would like to mention). Checked on my animation library in a real app - state discrepancy was fully fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant