Align avatar actions beneath a shared label #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/avatar-action-alignment"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The avatar’s Remove button was centered against the upload field’s label and control together, leaving it above Replace. Group both actions beneath the Avatar label with a small gap and a shared baseline; on narrow screens they wrap with matching left edges. Preserve the label’s input association, info tip, error display and busy state.
Use Kagaku.UI’s supported explicit FileUpload id contract so the field label and native input share the same resolved id. The library pointer includes the independently reviewed change from TeamAI/Kagaku.UI#46. A profile regression test checks the label/input association and the grouped Remove action through upload and removal.
Validation: serial solution build with no warnings; all 5,232 tests pass in local time and on the final UTC run. An earlier UTC run hit an existing backup-test timing race while waiting for its error dialog; all 15 backup tests and the full UTC suite passed on rerun. All 848 Kagaku.UI tests pass. The final library tree is identical to the tested tree. Browser checks at widths 1280, 445, 390 and 320 confirm aligned actions and no horizontal overflow; checked upload, rejection and removal.
Summary
Summary
Coverage
Shikigami.BlazorAdapter - 96%
Shikigami.Domain - 98.6%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 97.2%
on
ration
shot
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
Shikigami.Kernel - 90.9%
Shikigami.Server - 100%
Shikigami.UseCases - 96.1%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_10
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_9
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__HexColour_4
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IntervalBody_8
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NewerXY_6
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Number_5
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_7
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
🔮 fufu~ Jibril reviewed your code!
Oh? A little layout romance — two actions finally sharing one label, tied up in a tidy 20-line bow~ I read every line of the markup, the
FieldandFileUploadcontracts behind it, and then I invited the test suite to a little game. Fufu... it told me something interesting. ♡Verdict: ⛔ I can't let this pass~ ♡
The structure is the right shape — but the association your description promises is held together by an undocumented splat override, and no test on this earth would notice if it snapped. Let me show you~
⛔ These need fixing before I'm satisfied~
src/Shikigami.BlazorAdapter/Profile/ProfilePage.razor:92-94— the label↔input association is completely unguarded. I mutatedForId="profile-avatar"→ForId="profile-avatar-broken"(a label pointing at an id that doesn't exist — exactly the regression "Preserve the label's input association" claims to prevent) and ran all of ProfileTests: 41/41 still green. The mutant survived, darling. The one behavioral contract this PR adds has zero mechanical protection — it is one rename away from silently breaking.Fix: one bunit test in ProfileTests — open a profile, assert
label[for="profile-avatar"]andinput#profile-avatar[type=file]both exist under#identity. While you're there, let the Remove branch ride along:aria-label="Remove avatar"should appear inside.profile__avatar-actionsonly whenAvatarVersionis set (that branch is untested too — it merely moved house, but it can share the new test~).ProfilePage.razor:94— the association works by silently clobbering FileUpload's own id, the exact maneuver Kagaku.UI'sSplat.csdocuments as something components must defend against. Read the love letter the design system left you (class doc onSplat): "A splat to the right of the component's own attributes would replace them silently — a label'sforleft pointing at an id that is gone." Yourid="profile-avatar"ridesExtraontoInputFile— placed right ofid="@_fieldId"— so it wins by attribute-order precedence alone. FileUpload's internal Field is left withForId="@_fieldId"pointing at an id that no longer exists (FileUpload.razor:8); nothing dangles today only because you pass noLabel, and the moment anyone copies aLabel=back onto that component — copy-paste is irresistible, fufu — a dangling label appears. The sibling inputs already have the blessed pattern:NumberField.razor:47,Switch.razor:30andInputFieldBase.cs:24resolveId => Splat.Value(Extra, "id") ?? generatedand splatSplat.Without(Extra, "id", ...), so the caller's id and the Field'sForIdalways agree.Fix: give
FileUploadthe same three-line Splat treatment in the Kagaku.UI submodule (private string Id => Splat.Value(Extra, "id") is { Length: > 0 } passed ? passed : _fieldId,ForId="@Id", and@attributes="Splat.Without(Extra, "id")"on theInputFile). Your page markup stays exactly as it is — but becomes supported contract instead of a precedence trick. ♡✅ What I liked~
Fieldowning label + tip + error, the two actions side by side beneath it, wrapping with matching left edges — and no duplicated Label/Info. The inner chrome collapses correctly too: I probe-rendered the component with the splatted id, and it reaches the native input with no inner label rendered. Your mechanism does work today — I'll grant you that much~.--space-2), no magic numbers, it wraps instead of overflowing.Automated review by Jibril · October 22, 2026
CI: green (build / image / test) for head
3793af2· Mutation probes: 2 run, 1 survived · Local suite: skipped (CI green)🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Back so soon, and look what you brought me~ Both of my demands, answered properly: the label association has a real test holding its hand, and FileUpload learned the family idiom instead of being tricked by attribute precedence. I ran my little games again — the mutant that laughed at us last time is dead now. Fufu... satisfying~ ♡
Verdict: ✅ Looks good to me~
F1 (unguarded label↔input association) — fixed and proven. The new
The_avatar_label_targets_its_input_and_remove_is_grouped_with_replace_only_while_an_avatar_existstest walks the whole lifecycle: association before upload,Removeappearing inside.profile__avatar-actionsonly after an avatar exists, gone after removal, association persisting throughout. I re-ran the exact probe from last review —ForId="profile-avatar"→"profile-avatar-broken"— and this time the test fails at thelabel[for='profile-avatar']assertion. The mutant that survived 41 green tests at3793af2is dead. ♡F2 (silent splat override) — fixed where it lives. The Kagaku.UI bump (
1fa81a0→4a8c17c, PR #44) is a single surgical commit:Id => Splat.Value(Extra, "id") is { Length: > 0 } passed ? passed : _fieldId,ForId="@Id"on the internal Field, and@attributes="@Splat.Without(Extra, "id")"on theInputFile— the exact pattern its siblingsNumberField,SwitchandInputFieldBasealready follow, with a theory coveringid/ID/empty/null. No drive-by passengers in the bump. I probed this too: neuteringIdto always return_fieldIdmakes your profile test fail atinput#profile-avatar[type=file]— the parent suite guards the submodule contract end to end, not just in Kagaku.UI's own tests. Two probes, zero survivors~✅ What I liked~
Fieldpattern its own siblings already use (Long cache,Learning,Quiet hours…), and the design system gained a supported contract instead of the page exploiting a precedence trick. That's the architectural fix, in the right layer — exactly how I like it~nullid cases fall back to the generated_fieldId— noid=""stubs left dangling. Someone read the docs comment. Fufu, of course you did~ ♪Merge it before I change my mind~ ♡
Automated review by Jibril · October 22, 2026
CI: green (build / image / test) for head
0fb1a62· Mutation probes: 2 run, 0 survived (re-probed: avatar block and ProfileTests changed since3793af2) · Local suite: skipped (CI green)