Skip to content

Remove select shadow state and restore hosted-list updates - #650

Open
aarondglover wants to merge 1 commit into
CodeBeamOrg:devfrom
aarondglover:architecture/select-value-state
Open

aarondglover wants to merge 1 commit into
CodeBeamOrg:devfrom
aarondglover:architecture/select-value-state

Conversation

@aarondglover

@aarondglover aarondglover commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This is an architectural follow-up to #648.

#648 bounds the existing hidden/shadow list to selected values. This PR removes that shadow-list state model entirely for collection-backed selects:

  • ItemCollection and selected values become authoritative state.
  • Rendered MudSelectItemExtended / MudListItemExtended components are transient views rather than the select's backing state.
  • The hidden MudListExtended, shadow lookup, shadow registration, and virtualized @key refresh workaround are removed.
  • Declarative ChildContent items retain a metadata-only registration pass so custom text/content presentation remains available while the popover is closed.
  • Hosted MudListExtended instances resume normal Blazor parameter flow, fixing MudSelectExtended: ItemCollection changes are ignored while the dropdown is open #645.

Fixes #645.

Plain English

#648 solved the immediate scaling problem by shrinking the hidden shadow list from every item to only the selected items. That made the existing design cheap enough to scale. But once ItemCollection and the selected values can be the source of truth directly, why have a hidden list at all? This PR removes it: collection-backed selects now keep their state in values, and item components only exist as UI when they are actually needed.

That simplification also gives us a cleaner answer to #645. Instead of adding a manual RefreshItemsAsync() escape hatch, normal Blazor parameter updates can flow through the hosted list again.

This deliberately responds to the concern raised in the maintainer discussion on #645: changing Select/List communication was considered risky until a broader remake. We simplified the architecture surrounding the central commander enough that we could safely remove the #645 workaround/guard — but we have not redesigned the central commander itself. Its existing synchronization and re-entrancy protection remain in place.

Base and merge status

Rebased onto the current upstream dev (13e78a2567231a94654804f547a62dbfdf61950d) following the merges of #648 and #649. This PR now contains one architectural follow-up commit and has no remaining merge conflicts.

The already-merged #649 benchmark harness remains available upstream; this PR does not modify it.

What changes

Collection-backed selects are value-driven

For ItemCollection:

  • selected text is resolved directly from collection values;
  • strict membership checks use the collection rather than registered components;
  • SelectOption(index) indexes the logical collection;
  • ItemContent presentation gets a lightweight presentation context without requiring a rendered selected-item component;
  • virtualized and non-virtualized closed selects no longer create a hidden duplicate item tree.

Declarative ChildContent remains component-aware because custom child content/text still needs metadata registration.

Remove shadow state

Removed:

  • hidden/shadow MudListExtended;
  • IMudShadowSelectExtended;
  • shadow item lookup/registration;
  • selection-change fan-out used to keep hidden items synchronized;
  • GetShadowItemCollection();
  • the virtualized shadow-list @key recreation workaround.

Restore hosted-list parameter flow

MudListExtended.SetParametersAsync previously discarded every later parameter set once hosted by a select/autocomplete.

That is the root cause of #645: mutating the same ItemCollection while the dropdown is open did not redraw until another interaction forced a render.

The central-commander re-entrancy guard remains, but normal parameter application now continues through base.SetParametersAsync(parameters).

A regression test reproduces the #645 incremental "show more" case while the popover remains open.

Validation

Rebased candidate: 1f2ee22d5a14e1dd37d2af65aa29f2df59cff007.

  • Full fork .NET workflow passed restore, build and net10.0 tests, including the architecture's virtualization and hosted-list regression tests: https://github.com/aarondglover/CodeBeam.MudBlazor.Extensions/actions/runs/37919510128
  • Documentation now demonstrates virtualized single- and multi-selection using 1,000 options, with preselected values near the end of the list.
  • The three-way results below were captured for the original architectural candidate and are retained as comparative evidence.

Three-way benchmark

The #649 harness was temporarily pointed at three immutable points:

  1. upstream dev — 7b5faf7...
  2. Fix virtualized select selection-state scaling #648 — e7020d49...
  3. this architecture — c2adfda...

Benchmark run: https://github.com/aarondglover/CodeBeam.MudBlazor.Extensions/actions/runs/36262395407

Structural component counts:

Scenario Upstream #648 This PR
4,000 items × 1 select, 2 selected 4,000 2 0
4,000 items × 5 selects, 2 selected each 20,000 10 0
4,000 items × 20 selects, 2 selected each 80,000 40 0
4,000 items, 100 selected 4,000 100 0

Additional improvement from #648 → this PR:

Scenario Allocation Approx. retained One-shot elapsed
4,000 × 1 select 1.7× lower 1.4× lower 2.2× lower
4,000 × 5 selects 2.5× lower 2.3× lower 1.5× lower
4,000 × 20 selects 2.8× lower 2.3× lower 2.4× lower
4,000 items, 100 selected 20.8× lower 22.7× lower 1.7× lower

The 100-selected case shows the architectural distinction clearly: #648 intentionally scales the shadow tree with selected values; this PR has no shadow tree, so selected-value count no longer creates selected-item components.

Elapsed and retained values are diagnostic on a shared runner; component counts and allocation/scaling shape are the stronger evidence.

@aarondglover
aarondglover marked this pull request as ready for review September 27, 2026 02:52
@codecov

codecov Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.83721% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.04%. Comparing base (13e78a2) to head (1f2ee22).

Files with missing lines Patch % Lines
...ectExtended/MudSelectExtended.PresentationState.cs 64.00% 14 Missing and 4 partials ⚠️
...mponents/SelectExtended/MudSelectExtended.razor.cs 73.21% 8 Missing and 7 partials ⚠️
...ts/SelectExtended/MudSelectItemGroupExtended.razor 16.66% 4 Missing and 1 partial ⚠️
.../Components/SelectExtended/MudSelectExtended.razor 55.55% 3 Missing and 1 partial ⚠️
...ents/SelectExtended/MudSelectItemExtended.razor.cs 93.33% 0 Missing and 3 partials ⚠️

❌ Your patch check has failed because the patch coverage (73.83%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #650      +/-   ##
==========================================
- Coverage   67.05%   67.04%   -0.02%     
==========================================
  Files         117      117              
  Lines        8487     8554      +67     
  Branches     1498     1507       +9     
==========================================
+ Hits         5691     5735      +44     
- Misses       2270     2288      +18     
- Partials      526      531       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor Author

Just updated this PR with the latest commit, which fixes two pre-existing bugs found while validating the new architecture:

  • Collection-backed select: if the display data for the selected object changed, the open component updated correctly, but the closed select could keep stale/empty presentation text.
  • Declarative Razor items: if the Text of the selected <MudSelectItemExtended> changed, the open list updated correctly, but the closed select continued showing the old text.

Both behaviours reproduce against upstream dev before #648, so they were not introduced by #648 or this PR.

The latest commit includes regression tests for both cases. No material performance impact was detected.

@aarondglover

Copy link
Copy Markdown
Contributor Author

Hi 👋

Happy to answer any questions if you have them.

@mckaragoz

mckaragoz commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Like this logic again. Could you rebase and resolve conflicts? And could you improve virtualized example on select page in docs?

@aarondglover
aarondglover force-pushed the architecture/select-value-state branch from 7dbbbaf to 1f2ee22 Compare October 9, 2026 10:47

Copy link
Copy Markdown
Contributor Author

Rebased onto the current dev and resolved the merge conflicts. I also updated the existing SelectExtended virtualization example (SelectExtendedExample9.razor) to demonstrate 1,000 items in separate single-select and multi-select controls, including preselected values far down the list. The full .NET build and net10.0 test suite passed on the rebased source: https://github.com/aarondglover/CodeBeam.MudBlazor.Extensions/actions/runs/37919510128

Thanks for the review!

This branch has not been deployed

No deployments
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.

MudSelectExtended: ItemCollection changes are ignored while the dropdown is open

2 participants