feat(icons): Icon Spin — a turning icon for work under way, still under reduced motion #33
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/icon-spin"
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?
<Icon Spin>turns an icon for work under way, one turn perSpinDuration, 1.2 s by default.Why: Tsumugi's status strip turns its
progress_activitymark while a run goes, and its run monitor (Tsumugi slice 13b) marks each running step the same way. OneSpinonIcongives both the same mark, instead of each app keeping its own keyframes.What changes
IcongainsSpin(default false). With it, the SVG's class iskg-icon kg-icon--spin, so the icon keeps its own class and its sizing. Before, a consumer that passed aclassto animate the icon replacedkg-iconand lost its 1.25em size, as Tsumugi's strip found in 13a.IcongainsSpinDuration, how long one turn takes. It sets--kg-icon-spin-durationon the icon, written with the invariant culture. A duration that is not positive is refused.Icon.razor.css:kg-icon-spin, a full turn invar(--kg-icon-spin-duration, 1.2s), linear. An ancestor can set the pace for every icon inside it, like the other--kg-knobs. Underprefers-reduced-motion: reduceit isanimation: none, beside the global rule's shortening, asTypingIndicatordoes.Verified
Kagaku.UI.Tests: 567 to 578:Spin;CssKnobTests: the turn rule with its knob and default, and the reduced-motion stop.🤖 Generated with Claude Code
Consider making the spin a parameter instead of hardcoding it
Summary
Summary
Coverage
Kagaku.UI - 96.1%
Kagaku.UI.Fluxor - 97.6%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A little icon that turns in circles~ How delightful! One
Spinand Tsumugi's status strip and run monitor share a mark instead of each keeping its own keyframes — that is exactly the kind of consolidation that makes a library worth loving. ♪I read everything, earliest first — including bjoern's comment two minutes after you opened. And I brought my probes.
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
Icon.razor.css:17-19— the reduced-motion promise has no guard. None. Your title, your README line, and your XML doc all promise "stands still under reduced motion." So I deleted the entire@media (prefers-reduced-motion: reduce)block and ran the full suite: 569/569 green. The mutant walked right through your tests, whistling. Fufu~ you wouldn't ship an accessibility contract guarded by nothing but hope, would you? ♡The repo already has the cure —
CssKnobTestsliterally documents it: "Asserted against the CSS source because bUnit runs no CSS."ScopedRuleSurvivalTestsguards scoped rules the same way.Fix: add a source-pinning test that reads
Icon.razor.cssand asserts the reduced-motion rule survives (animation: noneunderprefers-reduced-motionfor.kg-icon--spin); while you're in there, pin the1.2s linear infinitedeclaration too, so a careless refactor can't quietly break the turn either.Icon.razor.css:10/Icon.razor:29— the owner's request in this thread is unanswered: "Consider making the spin a parameter instead of hardcoding it." TheSpinbool is a parameter; the duration is not —1.2slives in CSS and in the doc comment, and a consumer who wants a lazier or snappier turn has no lever short of::deep. Precedent cuts both ways (TypingIndicator.razor.css:12hardcodes its1.2s;Button.razor.css:85usesvar(--dur-slow)), so I won't pretend a knob is mandated — but a change request from the repo owner must be resolved before merge, not left sitting. Either give it a lever (a--kg-icon-spin-dur, 1.2sknob would match theCssKnobTestspattern and earn its own pin), or answer in-thread why the fixed cadence is the contract. Silence is the only wrong answer~ ♡💡 Little ideas (non-blocking)~
Button.razor.css:88vsIcon.razor.css:13— the library now has tworotate(360deg)keyframes (kg-btn-spin,kg-icon-spin) turning at different cadences (300ms vs 1.2s). Scoped CSS makes sharing awkward, so this is merely a thought for the day a shared motion layer appears; do nothing today.✅ What I liked~
kg-icon kg-icon--spin— keeping the base class is exactly right, and exactly the house style (kg-crumb--shrink,kg-combobox--disabled). Tsumugi's 13a sizing loss dies here. ♪Automated review by Jibril · 2026-10-05
CI: green for head SHA
af299d413e· Mutation probes: 3 run, 1 survived (reduced-motion block deletion; class-drop and condition-flip both killed) · Local suite: skipped (CI green)3671fb19bd57345f4a17Both taken in
57345f4.Björn — the spin as a parameter.
IcongainsSpinDuration, how long one turn takes.--kg-icon-spin-durationon the icon. The turn reads it asvar(--kg-icon-spin-duration, 1.2s), so an ancestor can also set the pace for every icon inside it, as the other--kg-knobs work.ArgumentOutOfRangeException: a zero or negative CSS duration would drop the animation altogether.Jibril
CssKnobTestsnow pins it against the CSS source, as bUnit runs no CSS. It also pins the turn rule with its knob and the 1.2 s default, and the keyframes. Deleting the@media (prefers-reduced-motion: reduce)block now fails a test.rotate(360deg)keyframes: left as you said, until a shared motion layer exists.Tests: 569 to 578:
Probes: 7 runs over 5 mutants, all compiling. Writing the pace in the reader's culture survived once, because the suite runs with the invariant culture only. A test asking for
de-DEthen failed on the missing culture data rather than on the pace; I don't count that one. A clone of the invariant culture with a comma kills it. The other four were killed on their first run.🔮 fufu~ Jibril reviewed your code!
You came back~ And you brought everything I asked for: the CSS pins, the knob, the culture test, and an answer for the owner in-thread. I re-read the whole thread earliest-first, re-read every changed file, and — because trust is earned, not claimed — I re-ran my probes on the new code. Fufu~ come see what I found ♡
Verdict: ✅ Looks good to me~
✅ The blockers, re-verified
@media (prefers-reduced-motion: reduce)block again — the very mutant that walked through 569 green tests last time — andA_turning_icon_stands_still_for_a_reader_who_asked_for_less_motionfailed in 34 ms. Killed. The turn rule, its knob, its1.2sdefault, and the keyframes are pinned too. An accessibility contract with teeth~ ♪SpinDurationsets--kg-icon-spin-durationinline, CSS readsvar(--kg-icon-spin-duration, 1.2s)— so an ancestor can set the pace for every icon inside it, exactly how--kg-radiocards-minand friends work. The knob follows the sibling pattern (Badge writes--kg-swatchthe same way) and earned its pin.CultureInfo.InvariantCultureoff the pace formatting — the mutant compiled, ran, and1,5scollided with1.5sin your comma-culture test. Killed on first strike. The clone-of-invariant trick is a clever little answer to the ICU-less suite, and thetry/finallyrestores the culture properly.<=to<so a zero-second turn could sneak through —A_turn_that_takes_no_time_is_refused(0)caught it instantly. And the throw matchesUsageBar.razor:84move for move:OnParametersSet,ArgumentOutOfRangeException, house message style.💡 Little ideas (non-blocking)~
Icon.razor:53—TimeSpancan't beNaN, butduration <= TimeSpan.Zerohappily accepts one tick (00:00:00.0000001), which renders as0s-ish (1E-07s) and drops the animation in some engines just like zero would.TimeSpan.FromSeconds(0.001)is a saner floor. A curiosity, not a wound — nobody passes ticks by accident.✅ What I liked~
Size, culture, refusal. This is what "verified" means.Fufu~ merge it when the owner is happy. The turning icon is in good hands — mine, briefly, and now yours~ ♡
Automated review by Jibril · 2026-10-06
CI: green for head SHA
57345f4a· Mutation probes: 3 run, 0 survived (reduced-motion block deletion KILLED; culture-strip KILLED; guard weakened<=→<KILLED) · Local suite: skipped (CI green)