Skip to content

reconcile-highlight visualizer: pie slice composition opacity dims and persists until window resize #167

@codemonkeychris

Description

@codemonkeychris

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:

  1. Run step-08.cs (PieChart with LabelView rendering Component<ListItem, ItemProps> per slice) and toggle the visualizer on.
  2. Click +/- a few times — or just type into the unrelated name TextField.
  3. Some slice arcs now look pale; their labels are unaffected.
  4. Resize the window → slices snap back to full color.

The set of dimmed slices is non-deterministic across runs.

What's been ruled out

  1. 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.

  2. 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.

  3. Not ApplyModifiers writing through stale modifier values. Slice paths have Modifiers == null; the modifier-application path is never entered.

  4. Not the sprite-leak fixed in PR #XXX. That fade-out animation now correctly disposes sprites on batch.Completed. The persistent dimming is independent.

  5. 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

  1. Debug overlay that prints each slice path's Visual.Opacity value next to it. Confirm dimmed ones really do read < 1.0 in Composition.
  2. Bisect: temporarily disable ApplyImplicitTransitions / ApplyPropertyAnimation / ApplyLayoutAnimation in Reconciler.Update.cs. Does the dimming stop?
  3. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions