SIGN IN SIGN UP

fix(react): clear removed snapshot prop refs (#2590)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Cleared transient snapshot child property references when snapshot
subtrees are removed, preventing deleted items from being retained.

* **Tests**
* Added tests covering cleanup behavior for removed children, including
nested arrays and cyclic reference scenarios.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/lynx-family/lynx-stack/pull/2590)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

## Summary

This fixes a main-thread snapshot retention path where removed child
subtrees could still be strongly referenced by compiler-generated
transient child props. Snapshot JSX uses `$*` props as staging
references for named children; when `SnapshotInstance.removeChild()`
detached a child, the structural tree and native elements were cleaned
up, but those `$*` props could still point at the removed snapshot
subtree.

The practical failure mode is that a deleted list holder or list item
subtree can remain reachable from the root props chain after it is
removed. That keeps old snapshot instances alive even though they are no
longer part of the rendered tree.

## Key points

- Clear transient `$*` owner props during removal. Direct refs such as
`{ $0: removedChild }` are deleted from `props`, so the owner no longer
keeps a hard reference to the removed subtree after `removeChild()`.
- Handle compiled array child shapes recursively. When a transient prop
stores children as arrays, only removed snapshot entries are cleared,
including nested arrays:

  ```js
  // before removal
  owner.props.$0 = [keptChild, [removedChild]];

  // after removeChild(removedChild)
  owner.props.$0 = [keptChild, [undefined]];
  ```

- Clean transient props inside the removed subtree as it is torn down.
This breaks references held by the deleted subtree itself, including
component/list structures that had their own compiled child staging
props.

## Runtime Contract

This only affects compiler-owned transient child props whose keys start
with `$`. Normal user props, public runtime APIs, serialized snapshot
shape, native patch operations, and list update protocols are unchanged.

The cleanup is tied to `SnapshotInstance.removeChild()`: removed
snapshot instances and descendants are collected into a removal set,
then `$*` prop values are cleared if they directly reference any removed
snapshot or contain one inside an array. Existing array identity is
preserved, and non-removed entries keep their current identity and
order.

## Checklist

- [x] Tests updated (or not required).
- [x] Documentation updated (or not required).
- [x] Changeset added, and when a BREAKING CHANGE occurs, it needs to be
clearly marked (or not required).
Y
Yradex committed
d182a06964c672d9484daa40dc152d38abc22ce0
Parent: ee79eff
Committed by GitHub <noreply@github.com> on 5/14/2026, 12:47:49 PM