Repository navigation
Fix keyed child mapping while exit transitions are retained - #1429
Alexandre Zollinger Chohfi (azchohfi) merged 8 commits into
Conversation
Keep logical child identity separate from physical collection positions across keyed prefix/suffix updates, removals, insertions and LIS reorders. Add native regressions for in-flight exits and Grid placement. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep end-relative no-exit matching for transparent wrappers that mount no control. Add a reused self-triggered keyed child regression. Fix the current-head selftest/coverage failure by unmounting a tagged item view after WinUI clears its unlinked container's Content; preserve delete visuals and prove cleanup and stale-state rejection deterministically. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve the additive CHANGELOG conflict by retaining both the keyed-exit correction and main's Jump List Native AOT entry. Preserve the auto-merged native/packaged Jump List registrations alongside all keyed-exit and orphan-view regressions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The reconciler-wide exit flag can incorrectly enable mapping for a collection with no exiting controls, skipping or misapplying suffix updates and leaving controls stale.
1 open issue
What changed in this PR
This PR extends the earlier child-reconciliation fixes to preserve keyed controls while sibling exit transitions remain active. It also completes orphaned list-row cleanup when WinUI has already cleared the container’s item.
Changes:
- Map logical keyed children to physical controls during retained exits.
- Unmount cleared-item orphan views while preserving their transition visuals.
- Add and register seven native regression fixtures, and document the fixes.
| File | Description |
|---|---|
tests/Reactor.AppTests.Host/SelfTest/SelfTestFixtureRegistry.cs |
Registers the seven new fixtures. |
tests/Reactor.AppTests.Host/SelfTest/Fixtures/TransitionTests.cs |
Covers keyed identity, ordering, placement, empty Memo and dirty-child updates. |
tests/Reactor.AppTests.Host/SelfTest/Fixtures/TemplatedListOrphanContainerFixtures.cs |
Verifies cleared-item cleanup, rejected stale writes and idempotence. |
src/Reactor/Core/Reconciler.Update.cs |
Handles orphaned containers whose item is already null. |
src/Reactor/Core/ChildReconciler.cs |
Adds retained-exit index mapping for keyed reconciliation. |
CHANGELOG.md |
Documents keyed-exit and orphan-cleanup fixes. |
Review effort: Balanced
Preserve null-mounted logical slots and resolve insertion anchors against the next realized sibling. Scope exit detection to each collection and cover local and unrelated-panel exits with exact identity and order assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
The Docs build check is also failing because regeneration changes |
Reserve keyed siblings' controls while mapping mixed-key children, cover native reorder and insertion with local and unrelated exits, and regenerate the reconciliation guide. Preserve both changelog entries when integrating main. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fixed in d18edbc: regenerated the reconciliation guide with |
Integrate the perf-library workflow path update from main without changing reconciliation code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Alexandre Zollinger Chohfi (azchohfi)
left a comment
There was a problem hiding this comment.
Approving. A focused benchmark shows the new native child scans in keyed reconciliation cost about 0.5-1.5 us and 100 B per child per render when an exit is in flight or a child mounts nothing. I'll follow up separately with a perf fix and benchmarks.
90c9959
into
microsoft:main
…1445) * Avoid per-child control reads in keyed mapping around retained exits #1429 made the keyed pass read every child of a keyed panel whenever any exit was in flight on the reconciler, and read each child twice more to build the logical-to-physical map when a panel held an exit or a null-mounted Memo. Each UIElementCollection read is a WinRT call that allocates ~52 B, so a 500-child keyed panel paid +93% to +189% allocation per render. - Detect an exit in a collection by probing the collection once per in-flight exit (IndexOf) instead of reading every child. - Resolve the keyed map with a forward cursor on demand: a skipped child never advances it, each physical child is read at most once, and the control it claims is handed to the update so it is not read again. - Add KX1-KX4 keyed-exit micro benches to the control-model catalog (27 benches). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Address pr-review findings - Cache the control each keyed-map slot claims so suffix updates reuse the cursor's read instead of reading every suffix child again. - Decide the identity map lazily: an all-skip pass never probes for exits. - Seed ProbeEachChild dirty checks with the mapped control. - Raise ExitProbeLimit to 64 with its cost rationale. - Cover suffix read counts, both sides of the probe limit, and the context / callback skip arms under a non-identity map. - Correct the KX bench header (rows update, they do not skip) and narrow the CHANGELOG wording. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Summary
Keep keyed children paired with their own native controls while removed siblings remain for exit transitions, preserving content, identity, order, and Grid placement through updates, inserts, removals, and LIS reorders. Also finish orphan-view cleanup when WinUI clears an unlinked list container's item but leaves its template view mounted.
The logical-to-physical map skips retained exits and preserves absent transparent Memo slots, including unkeyed Memos in mixed-key lists. Exit detection is collection-local; an exit in another panel sharing the reconciler cannot misalign updates. Insertion anchors resolve to the next realized sibling, and null mounts do not advance physical insertion positions. Orphan cleanup preserves exit visuals while clearing lifecycle ownership, rejecting stale state writes and making later recycle idempotent.
Linked issue / spec
Contentwas already null.Test plan
[a, emptyMemo, b]reordered to[b, emptyMemo, a].mainthrough7f46468f3cb1307c7fbf108a6a575849a65084c9, preserving both changelog entries when resolving the merge conflict.Local validation of the merged changes at
d18edbc8(the subsequentcef684f7merge adds only main's perf-workflow path update):dotnet restore Reactor.slnx -v:qSystem.Security.Cryptography.Pkcs9.0.6.dotnet build tests\Reactor.AppTests.Host --no-restore -c Release -p:Platform=x64 -p:SkipSignaturesGen=true -m:1 -nodereuse:false -v:qdotnet run --project tests\Reactor.AppTests.Host --no-build -c Release -p:Platform=x64 -- --self-test --filter 'Transition_'dotnet run --project tests\Reactor.AppTests.Host --no-build -c Release -p:Platform=x64 -- --self-test --filter 'KeyedWrapper_'dotnet run --project tests\Reactor.AppTests.Host --no-build -c Release -p:Platform=x64 -- --self-test --filter 'ConditionalChildSlot_'dotnet build tests\Reactor.Tests --no-restore -c Release -p:Platform=x64 -p:SkipSignaturesGen=true -m:1 -nodereuse:false -v:qdotnet test tests\Reactor.Tests --no-build -c Release -p:Platform=x64 --filter-class '*ChildReconciler*'dotnet run --project tools\Reactor.DocPipeline -- compile --topic reconciliation --no-screenshotsdotnet build Reactor.slnx --no-restore -c Release -v:qcopilot-cli\1.0.98\win32-arm64\plugins\github-copilot-browser\scripts\nativeMessagingRegistration.mjsin untoucheddemo-script-tool; no sample workaround or suppression included.Current-head remote CI at
cef684f7298b0cb1e26f958abf83f58e04bfe5d8is green: 39 successful checks, 3 intentional path-filtered skips, no failures or pending checks. CI passed Release solution build, units, integration, both native/packaged/AOT/E2E shards, docs build and snippet gate, fuzz smoke, and AOT trim proof. Coverage, packaging, bootstrap, Node projects, and build metrics also passed. Skips are Devtools trim mstat, Audit ledger gate, and Docs tier-drift. The regenerated reconciliation guide clears the prior Docs build failure.Earlier local evidence: full units at the initial head passed 15,122 with 64 skipped. A later full local run had a single high-contrast-desktop-dependent theme test failure; it was not weakened, and remote Unit Tests passed. An isolated preview.12 backport reproduced the original hard casts and passed native ARM64 transition fixtures after correction using architecture-neutral local.4 packages; the consumer reported survival of the original reconnect sequence with matching payload hashes. Those packages, backport sources, feed pins, and consumer changes are not part of this PR, and local.4 does not include subsequent readiness fixes.
Risk / breaking changes
No public API change. Keyed reconciliation is a hot path: ordinary count-aligned, non-exiting collections retain the allocation-free prefix/suffix path. Sparse or retained-exit collections rent a logical-to-physical map and sibling-key dictionary, returned in
finally; survivor positions count actual immediate versus deferred removals rather than trusting an exiting duplicate key. Null-content orphan cleanup runs only after the caller establishes that the container is unlinked, preserves its visuals, and clears ownership before later recycle/remount.