You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MudFileUpload renders its own file list in addition to the CustomContent drop zone FormCraft
supplies, so every selected file appears twice in the multiple-file upload — once as FormCraft's
chip and once as MudBlazor's, each with its own close button.
Measured with two files selected, rendering MudBlazorMultipleFileUploadComponent standalone:
.mud-chip count: 4 ← for TWO files
.mud-chip-close-button count: 4
all buttons: 6 ← the 4 close buttons, plus Browse and Clear All
Ancestry tells the two sets apart:
Chips
Rendered under
Text
FormCraft's
div.mud-file-upload-custom-content
a.png (1024 Bytes) — name and formatted size, from the MudChip loop at MudBlazorMultipleFileUploadComponent.razor:51
There are two removal paths for the same file, and they are not equivalent. MudBlazor's close
button runs MudFileUpload's internal removal, not FormCraft's RemoveFile — so it does not get Five more controls drop keyboard focus when they unmount or disable themselves #318's focus move, and it does not go through FormCraft's own value handling. Two buttons that look
the same and behave differently is worse than either alone.
It makes DOM assertions ambiguous, and has already cost time.fix(mudblazor): move focus deliberately after a self-unmounting control (#318) #324's chip tests had to scope to .mud-file-upload-custom-content .mud-chip-close-button precisely because a bare .mud-chip-close-button matches twice per file. Anyone writing upload tests later walks into the
same trap — and a test that silently drives MudBlazor's copy asserts nothing about FormCraft.
This is pre-existing and the model stays correct either way, which is why it is low priority rather
than a defect in behaviour. But it is very unlikely to be intended: the component goes to real trouble
to render a custom drop zone with its own chips, and then MudBlazor lists the files again underneath.
Proposed solution
Render the file list once. Establish first — by measurement, not by reading the docs — whether MudFileUpload can be told not to render its built-in list when CustomContent is supplied.
If it cannot: decide deliberately between dropping FormCraft's own chips in favour of MudBlazor's
(losing the size text and the RemoveFile route, so probably not) and accepting the duplication with
a documented reason. Do not leave it undecided — the point of this issue is to stop it being
accidental.
Check the single-file component for the same thing rather than assuming: it binds Files one-way
with an explicit FilesChanged handler where the multiple-file component uses @bind-Files, so its
behaviour here may genuinely differ.
Alternatives considered
Leave it, and only document the selector trap. Cheapest, and it addresses the test-ambiguity half.
Rejected as the whole answer: it leaves two visible remove buttons per file with different behaviour,
which is a real if minor user-facing defect, not just a testing inconvenience.
Drop FormCraft's chips and keep MudBlazor's. Simplest markup. Rejected as the default: it loses the
formatted size, and it routes removal away from RemoveFile — which means away from Five more controls drop keyboard focus when they unmount or disable themselves #318's focus move
and FormCraft's own value handling. Only worth revisiting if MudBlazor's list proves unsuppressable.
Hide MudBlazor's list with CSS. Rejected outright: the buttons stay in the DOM and in the
accessibility tree, so screen-reader and keyboard users still meet a duplicate remove control that
bypasses FormCraft. It would hide the symptom from sighted users only.
Area
FormCraft.ForMudBlazor — field rendering (file upload)
FormCraft supplies MudFileUpload with <CustomContent> — a drop zone containing an icon, a count, and
a chip per file. What was not appreciated is that CustomContentreplaces the drop target, not the
file list: MudFileUpload still renders its own .mud-file-upload-filelist beneath. The duplication
has presumably been visible since the component was written; it surfaced now only because #324 needed a
selector that matched exactly one close button per file and found two.
The removal asymmetry is the part that makes this more than cosmetic. FormCraft's chip close calls RemoveFile, which since #324 also moves focus when the last file goes. MudBlazor's calls its own
removal, which reaches FormCraft only through the @bind-Files writeback — the same writeback #319 is
about to change. Two paths into one value, one of which FormCraft does not control.
Approaches
A. Suppress MudBlazor's list, keep FormCraft's chips.Pros: one list, the richer one; removal
stays on RemoveFile so #318's focus handling and FormCraft's notification both apply; the test
selector becomes unambiguous. Cons: depends on MudFileUpload exposing a way to do it — unknown
until measured.
B. Drop FormCraft's chips, keep MudBlazor's.Pros: less markup to own; guaranteed available. Cons: loses the formatted size; moves removal off RemoveFile, which would have to be re-plumbed
through FilesChanged to keep #318's behaviour — more work than it looks, and in the wrong direction.
C. Document and leave.Pros: free. Cons: leaves two differently-behaved remove buttons per file.
Recommendation
A, with B as the fallback if and only if measurement shows MudBlazor's list cannot be
suppressed. Step 1 of the plan is that measurement, deliberately before any decision — this issue exists
because something was assumed about CustomContent once already.
📋 Spec
Goal
Each selected file appears exactly once in the multiple-file upload, with exactly one remove control,
and that control routes through FormCraft's RemoveFile.
Scope
MudBlazorMultipleFileUploadComponent — the duplicate list.
The single-file component, checked for the same duplication and fixed if present.
A test pinning the count, so the duplication cannot return unnoticed.
More than three files — FormCraft's loop renders CurrentValue.Take(3) plus a +N more chip; if
MudBlazor's list is unsuppressable, the two lists disagree about how many files are shown, which is
worth asserting either way.
Zero files — neither list should render a stray control.
The single-file component — different binding, so measure rather than infer.
Assumptions
MudFileUpload renders its file list independently of CustomContent — measured, and the thing Step
1 re-confirms before acting.
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: one list, one remove control per file, routed through RemoveFile.
Architecture: all changes in FormCraft.ForMudBlazor; the UI-agnostic FormCraft core is untouched.
Base branch dev; commit as Philippe Matray <phmatray@gmail.com>; conventional commits.
TreatWarningsAsErrors=true — the build fails on any warning.
Per-class test filter: dotnet test <proj>.csproj -c Release -- --filter-class <FQN>.
⛔ dotnet test --filter is inert here (MTP0001); run the full suite before claiming done.
Step 1: Write a characterisation test asserting today's behaviour — two files render four .mud-chip-close-button elements — and label it clearly as pinning the defect, to be inverted in Task 2.
Step 2: Add the same characterisation for the single-file component, establishing on evidence whether it duplicates too rather than assuming from the multiple-file case.
Step 3: Determine whether MudFileUpload can be told not to render its built-in list — inspect its parameters, and prove the finding with a throwaway test rather than from documentation.
Step 4: Record the outcome in a comment on the characterisation test: suppressible (approach A) or not (approach B), and which parameter does it.
Step 5: Run the suite → PASS (these describe what happens today).
Step 6: Commit: test(mudblazor): pin the duplicated upload file list.
Task 2: Render each file once
Files: modify MudBlazorMultipleFileUploadComponent.razor (and the single-file component if Task 1 found it affected); invert the Task 1 characterisations.
Interfaces: none new.
Step 1: Invert the characterisation to the intended behaviour — N files render exactly N close buttons.
Step 2: Run the suite → FAIL.
Step 3: Apply the approach Task 1 established: suppress MudBlazor's list if possible, otherwise drop FormCraft's chips and re-route removal through FilesChanged so RemoveFile still runs.
Step 1: Simplify FormCraftChipCloseSelector now that .mud-chip-close-button is unambiguous, and rewrite its remarks to record what was true and what changed — do not silently delete the explanation.
Step 2: Add a CLAUDE.md note under the file-upload bullets: MudFileUpload renders its own file list independently of CustomContent, and what this issue did about it.
Step 3: Add a README ## 🎉 Unreleased entry, calling out that duplicate remove buttons are gone.
Step 4: Run dotnet build -c Release and dotnet test -c Release → both green.
Step 5: Commit: docs(mudblazor): record the upload file-list behaviour.
Problem / motivation
MudFileUploadrenders its own file list in addition to theCustomContentdrop zone FormCraftsupplies, so every selected file appears twice in the multiple-file upload — once as FormCraft's
chip and once as MudBlazor's, each with its own close button.
Measured with two files selected, rendering
MudBlazorMultipleFileUploadComponentstandalone:Ancestry tells the two sets apart:
div.mud-file-upload-custom-contenta.png (1024 Bytes)— name and formatted size, from theMudChiploop atMudBlazorMultipleFileUploadComponent.razor:51span.mud-file-upload-filelist/div.mud-file-upload-filesa.png— name onlyWhy it is worth fixing, beyond looking wrong
button runs
MudFileUpload's internal removal, not FormCraft'sRemoveFile— so it does not getFive more controls drop keyboard focus when they unmount or disable themselves #318's focus move, and it does not go through FormCraft's own value handling. Two buttons that look
the same and behave differently is worse than either alone.
.mud-file-upload-custom-content .mud-chip-close-buttonprecisely because a bare.mud-chip-close-buttonmatches twice per file. Anyone writing upload tests later walks into thesame trap — and a test that silently drives MudBlazor's copy asserts nothing about FormCraft.
This is pre-existing and the model stays correct either way, which is why it is low priority rather
than a defect in behaviour. But it is very unlikely to be intended: the component goes to real trouble
to render a custom drop zone with its own chips, and then MudBlazor lists the files again underneath.
Proposed solution
Render the file list once. Establish first — by measurement, not by reading the docs — whether
MudFileUploadcan be told not to render its built-in list whenCustomContentis supplied.and, critically, the close button that routes through
RemoveFileand therefore through Five more controls drop keyboard focus when they unmount or disable themselves #318's focushandling and FormCraft's value notification.
(losing the size text and the
RemoveFileroute, so probably not) and accepting the duplication witha documented reason. Do not leave it undecided — the point of this issue is to stop it being
accidental.
Check the single-file component for the same thing rather than assuming: it binds
Filesone-waywith an explicit
FilesChangedhandler where the multiple-file component uses@bind-Files, so itsbehaviour here may genuinely differ.
Alternatives considered
Rejected as the whole answer: it leaves two visible remove buttons per file with different behaviour,
which is a real if minor user-facing defect, not just a testing inconvenience.
formatted size, and it routes removal away from
RemoveFile— which means away from Five more controls drop keyboard focus when they unmount or disable themselves #318's focus moveand FormCraft's own value handling. Only worth revisiting if MudBlazor's list proves unsuppressable.
accessibility tree, so screen-reader and keyboard users still meet a duplicate remove control that
bypasses FormCraft. It would hide the symptom from sighted users only.
Area
FormCraft.ForMudBlazor — field rendering (file upload)
Follow-up from #318 (landed as #324). Related: #319, #262
🧠 Brainstorm
Problem / context
FormCraft supplies
MudFileUploadwith<CustomContent>— a drop zone containing an icon, a count, anda chip per file. What was not appreciated is that
CustomContentreplaces the drop target, not thefile list:
MudFileUploadstill renders its own.mud-file-upload-filelistbeneath. The duplicationhas presumably been visible since the component was written; it surfaced now only because #324 needed a
selector that matched exactly one close button per file and found two.
The removal asymmetry is the part that makes this more than cosmetic. FormCraft's chip close calls
RemoveFile, which since #324 also moves focus when the last file goes. MudBlazor's calls its ownremoval, which reaches FormCraft only through the
@bind-Fileswriteback — the same writeback #319 isabout to change. Two paths into one value, one of which FormCraft does not control.
Approaches
A. Suppress MudBlazor's list, keep FormCraft's chips. Pros: one list, the richer one; removal
stays on
RemoveFileso #318's focus handling and FormCraft's notification both apply; the testselector becomes unambiguous. Cons: depends on
MudFileUploadexposing a way to do it — unknownuntil measured.
B. Drop FormCraft's chips, keep MudBlazor's. Pros: less markup to own; guaranteed available.
Cons: loses the formatted size; moves removal off
RemoveFile, which would have to be re-plumbedthrough
FilesChangedto keep #318's behaviour — more work than it looks, and in the wrong direction.C. Document and leave. Pros: free. Cons: leaves two differently-behaved remove buttons per file.
Recommendation
A, with B as the fallback if and only if measurement shows MudBlazor's list cannot be
suppressed. Step 1 of the plan is that measurement, deliberately before any decision — this issue exists
because something was assumed about
CustomContentonce already.📋 Spec
Goal
Each selected file appears exactly once in the multiple-file upload, with exactly one remove control,
and that control routes through FormCraft's
RemoveFile.Scope
MudBlazorMultipleFileUploadComponent— the duplicate list.Non-goals
tabindex/opacity(Mark required file-upload fields — the one field type #199 left unannounced #262 pinned these), orFive more controls drop keyboard focus when they unmount or disable themselves #318's focus targets.
Key files
FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorMultipleFileUploadComponent.razor(the chip loop at :51, theMudFileUploadbinding at :17)FormCraft.ForMudBlazor/Fields/FileUploadField/MudBlazorFileUploadFieldComponent.razor(the single-file counterpart)FormCraft.ForMudBlazor.UnitTests/Fields/FileUploadClearFocusTests.cs(whoseFormCraftChipCloseSelectordocuments the current trap)Validation rules
.mud-chip-close-buttonelements are rendered.RemoveFile— assert by the observable consequence: removingthe last file moves focus to Browse (Five more controls drop keyboard focus when they unmount or disable themselves #318).
Edge cases
CurrentValue.Take(3)plus a+N morechip; ifMudBlazor's list is unsuppressable, the two lists disagree about how many files are shown, which is
worth asserting either way.
Assumptions
MudFileUploadrenders its file list independently ofCustomContent— measured, and the thing Step1 re-confirms before acting.
Files; whichever of the two lands second should re-measurethe counts rather than trusting this issue's numbers.
dev.🛠️ Implementation plan
Goal: one list, one remove control per file, routed through
RemoveFile.Architecture: all changes in
FormCraft.ForMudBlazor; the UI-agnosticFormCraftcore is untouched.Tech stack: .NET 8 / 10 multi-target, Blazor, MudBlazor 9.8.0, xUnit + bUnit + Shouldly
(
MudBlazorTestBase).Global constraints:
dev; commit asPhilippe Matray <phmatray@gmail.com>; conventional commits.TreatWarningsAsErrors=true— the build fails on any warning.dotnet test <proj>.csproj -c Release -- --filter-class <FQN>.⛔
dotnet test --filteris inert here (MTP0001); run the full suite before claiming done.tabindex="-1"/opacity-0, or Five more controls drop keyboard focus when they unmount or disable themselves #318'sfocus behaviour — Mark required file-upload fields — the one field type #199 left unannounced #262 and Five more controls drop keyboard focus when they unmount or disable themselves #318 pinned all of these.
Task 1: Pin the duplication, and establish whether it can be suppressed
Files: create
FormCraft.ForMudBlazor.UnitTests/Fields/FileUploadFileListTests.cs.Interfaces: none.
.mud-chip-close-buttonelements — and label it clearly as pinning the defect, to be inverted in Task 2.MudFileUploadcan be told not to render its built-in list — inspect its parameters, and prove the finding with a throwaway test rather than from documentation.test(mudblazor): pin the duplicated upload file list.Task 2: Render each file once
Files: modify
MudBlazorMultipleFileUploadComponent.razor(and the single-file component if Task 1 found it affected); invert the Task 1 characterisations.Interfaces: none new.
FilesChangedsoRemoveFilestill runs.fix(mudblazor): render each uploaded file once.Task 3: Simplify the now-unambiguous test selector, and document
Files: modify
FormCraft.ForMudBlazor.UnitTests/Fields/FileUploadClearFocusTests.cs; modifyREADME.md; modifyCLAUDE.md.Interfaces: none new.
FormCraftChipCloseSelectornow that.mud-chip-close-buttonis unambiguous, and rewrite its remarks to record what was true and what changed — do not silently delete the explanation.CLAUDE.mdnote under the file-upload bullets:MudFileUploadrenders its own file list independently ofCustomContent, and what this issue did about it.## 🎉 Unreleasedentry, calling out that duplicate remove buttons are gone.dotnet build -c Releaseanddotnet test -c Release→ both green.docs(mudblazor): record the upload file-list behaviour.