Summary
When the reconcile-highlight visualizer (ReactorFeatureFlags.HighlightReconcileChanges = true) is on, individual pie-chart slices visibly lose color saturation — the colored arc behind the slice goes pale/washed-out — while the text labels rendered on top of those slices remain at full opacity. The dimming persists across subsequent renders and only snaps back to full color when the window is resized.
Repro on main with step-08.cs from the Reactor demos:
- Run
step-08.cs (PieChart with LabelView rendering Component<ListItem, ItemProps> per slice) and toggle the visualizer on.
- Click
+/- a few times — or just type into the unrelated name TextField.
- Some slice arcs now look pale; their labels are unaffected.
- Resize the window → slices snap back to full color.
The set of dimmed slices is non-deterministic across runs.
What's been ruled out
-
Not the highlight sprite painting on top of the slice. Sprites are short-lived (600ms fade, properly disposed after PR #XXX's cleanup fix). The dimming persists, which a transient overlay cannot do. Labels overlapping the dimmed slice are at full color, which a translucent yellow stripe overlay covering both would not produce.
-
Not a ShallowEquals miss on the slice paths. The pie-slice PathElements have identical PathDataString, identical Fill (compared via BrushesEqual), identical RenderTransform translation (TransformsEqual), no modifiers, no attached props. They take the Update skip fast-path — UpdatePath is never entered for them.
-
Not ApplyModifiers writing through stale modifier values. Slice paths have Modifiers == null; the modifier-application path is never entered.
-
Not the sprite-leak fixed in PR #XXX. That fade-out animation now correctly disposes sprites on batch.Completed. The persistent dimming is independent.
-
Not the LabelView-supplied Component re-rendering. Those re-renders only touch the inner [count: N] TextBlock. The outer Component/HStack are not flagged as modified after the ModifiersEqual accessibility fix in PR #XXX.
Why "snap back on resize" is the headline clue
Window resize forces a full XAML render pass + recomposition of every Visual. Whatever is dimming the slice is held in the Composition layer (not in WinUI XAML properties), and a fresh composition pass clears it. The labels stay crisp because they're separate FrameworkElements with their own Composition Visuals — only the slice's Path Visual is dimmed.
Hypotheses (ranked)
H1 (most likely): a Composition Opacity animation is being installed on the slice path's Visual and orphaned
ApplyImplicitTransitions / ApplyLayoutAnimation / ApplyPropertyAnimation in Reconciler.cs all call ElementCompositionPreview.GetElementVisual(uie) and then visual.StartAnimation(\"Opacity\", anim) or attach an ImplicitAnimationCollection. If one is being applied to a slice path Visual unintentionally (inherited / propagated config from an ancestor wrapper, or a leak across reconciles), the slice could be mid-animation toward < 1.0 opacity when the parent re-renders, take the skip path on the next Update, and stay stuck — until a layout pass reasserts.
This matches: snap-back-on-resize, labels-unaffected, different-slices-each-run.
Suspects:
Reconciler.cs:1636 and surrounding (ApplyPropertyAnimation, ApplyLayoutAnimation)
- New Canvas-anchor positioning code in
Reconciler.cs:386–442 (only writes Canvas.SetLeft/SetTop though — probably not it)
H2: ApplyModifiers not clearing a previously-set Opacity on element-pool reuse
Core/ElementPool.cs:15 tracks elements that had GetElementVisual() called and refuses to pool them. If a Path was pooled and got reused without resetting Visual.Opacity, that's the symptom we'd see.
H3: highlight overlay grabbing the slice path's Visual
OverlayHostWiring.EnsureRootContainer only calls GetElementVisual on the overlay canvas, but if TransformToVisual(host) lazily creates a Visual on the target that's then mutated elsewhere, it could leave the slice path with an unintended Visual. Less likely.
Next steps
- Debug overlay that prints each slice path's
Visual.Opacity value next to it. Confirm dimmed ones really do read < 1.0 in Composition.
- Bisect: temporarily disable
ApplyImplicitTransitions / ApplyPropertyAnimation / ApplyLayoutAnimation in Reconciler.Update.cs. Does the dimming stop?
- If H1 confirmed: which ancestor has a
LayoutAnimation / ImplicitTransitions / AnimationConfig set, and why is it being applied to the slice Visual instead of (only) the configured element?
Pointers
- Demo:
C:\Users\andersonch\Code\ReactorDemo\step-08.cs
- Highlight overlay:
src/Reactor/Hosting/ReconcileHighlightOverlay.cs
- Modifier application:
src/Reactor/Core/Reconciler.cs:1636, :1845
- Pie slice construction:
src/Reactor/Charting/D3Charts.cs (D3PathTranslated, D3Pie)
Summary
When the reconcile-highlight visualizer (
ReactorFeatureFlags.HighlightReconcileChanges = true) is on, individual pie-chart slices visibly lose color saturation — the colored arc behind the slice goes pale/washed-out — while the text labels rendered on top of those slices remain at full opacity. The dimming persists across subsequent renders and only snaps back to full color when the window is resized.Repro on
mainwithstep-08.csfrom the Reactor demos:step-08.cs(PieChart withLabelViewrenderingComponent<ListItem, ItemProps>per slice) and toggle the visualizer on.+/-a few times — or just type into the unrelated name TextField.The set of dimmed slices is non-deterministic across runs.
What's been ruled out
Not the highlight sprite painting on top of the slice. Sprites are short-lived (600ms fade, properly disposed after PR #XXX's cleanup fix). The dimming persists, which a transient overlay cannot do. Labels overlapping the dimmed slice are at full color, which a translucent yellow stripe overlay covering both would not produce.
Not a
ShallowEqualsmiss on the slice paths. The pie-slicePathElements have identicalPathDataString, identicalFill(compared viaBrushesEqual), identicalRenderTransformtranslation (TransformsEqual), no modifiers, no attached props. They take theUpdateskip fast-path —UpdatePathis never entered for them.Not
ApplyModifierswriting through stale modifier values. Slice paths haveModifiers == null; the modifier-application path is never entered.Not the sprite-leak fixed in PR #XXX. That fade-out animation now correctly disposes sprites on
batch.Completed. The persistent dimming is independent.Not the
LabelView-supplied Component re-rendering. Those re-renders only touch the inner[count: N]TextBlock. The outerComponent/HStackare not flagged as modified after theModifiersEqualaccessibility fix in PR #XXX.Why "snap back on resize" is the headline clue
Window resize forces a full XAML render pass + recomposition of every Visual. Whatever is dimming the slice is held in the Composition layer (not in WinUI XAML properties), and a fresh composition pass clears it. The labels stay crisp because they're separate FrameworkElements with their own Composition Visuals — only the slice's
PathVisual is dimmed.Hypotheses (ranked)
H1 (most likely): a Composition
Opacityanimation is being installed on the slice path's Visual and orphanedApplyImplicitTransitions/ApplyLayoutAnimation/ApplyPropertyAnimationinReconciler.csall callElementCompositionPreview.GetElementVisual(uie)and thenvisual.StartAnimation(\"Opacity\", anim)or attach anImplicitAnimationCollection. If one is being applied to a slice path Visual unintentionally (inherited / propagated config from an ancestor wrapper, or a leak across reconciles), the slice could be mid-animation toward < 1.0 opacity when the parent re-renders, take the skip path on the next Update, and stay stuck — until a layout pass reasserts.This matches: snap-back-on-resize, labels-unaffected, different-slices-each-run.
Suspects:
Reconciler.cs:1636and surrounding (ApplyPropertyAnimation,ApplyLayoutAnimation)Reconciler.cs:386–442(only writesCanvas.SetLeft/SetTopthough — probably not it)H2:
ApplyModifiersnot clearing a previously-set Opacity on element-pool reuseCore/ElementPool.cs:15tracks elements that hadGetElementVisual()called and refuses to pool them. If aPathwas pooled and got reused without resettingVisual.Opacity, that's the symptom we'd see.H3: highlight overlay grabbing the slice path's Visual
OverlayHostWiring.EnsureRootContaineronly callsGetElementVisualon the overlay canvas, but ifTransformToVisual(host)lazily creates a Visual on the target that's then mutated elsewhere, it could leave the slice path with an unintended Visual. Less likely.Next steps
Visual.Opacityvalue next to it. Confirm dimmed ones really do read < 1.0 in Composition.ApplyImplicitTransitions/ApplyPropertyAnimation/ApplyLayoutAnimationinReconciler.Update.cs. Does the dimming stop?LayoutAnimation/ImplicitTransitions/AnimationConfigset, and why is it being applied to the slice Visual instead of (only) the configured element?Pointers
C:\Users\andersonch\Code\ReactorDemo\step-08.cssrc/Reactor/Hosting/ReconcileHighlightOverlay.cssrc/Reactor/Core/Reconciler.cs:1636,:1845src/Reactor/Charting/D3Charts.cs(D3PathTranslated,D3Pie)