feat!: B8a — own reduced motion and publish activity timing #69

Open
scarlet wants to merge 3 commits from feat/motion-policy into main
Member

B8a makes motion a component-owned customization contract and separates ongoing activity from transition timing.

What's in

  • Breaking migration: removes the global * !important reduced-motion reset. The library now suppresses motion on its own parts and shared native-input skin; applications own their animations, including caller-provided slot content.
  • Adds inherited --activity-period (1.2s) for Button loading, StatusDot pulse, TypingIndicator, default Icon spin and reconnect activity. Stagger/offsets scale with the period. Existing transition roles remain independent; the Icon knob and SpinDuration retain precedence.
  • Reduced motion keeps status text, busy state, glyphs, static marks and final control/selection states. Navigation retains native modality, focus and dismissal. Reconnect entrance/exit follows --dur-base, loses its hardcoded delay and has a visible static reduced-motion treatment; the framework driver and retry/resume behavior are unchanged.
  • Fixes AdaptiveNavigation's reduced-motion specificity and existing NavItem/Disclosure chevron rules that failed to reach child Icon markup through CSS isolation.
  • Publishes the motion contract, migration and contributor practices. Adds a real MotionDemo and runs its browser contracts automatically as the third sequential studio in CI with separate logs and owned-process cleanup.

Validation

  • Full solution build: 0 warnings / 0 errors.
  • Full normal and UTC suites: 1,256/1,256 each, zero skipped (1,239 UI + 17 Fluxor). Updates the existing Icon fallback contract test; no .NET count change.
  • Motion browser contracts: 14/14 Chromium + 14/14 Firefox. These use actual compiled component CSS and real preference changes, without a global animation-disable fixture. Checks cover all 25 transition targets, inherited/nested timing, explicit Icon precedence, caller sibling/slot ownership, live activity clocks, states, menus/tooltips/disclosure, native navigation focus/Escape, reconnect appearance and actual Quicklink scrolling.
  • Exact CI YAML steps executed locally: all three Release studios built cleanly, then 27 shell + 12 customization + 14 motion Chromium groups passed sequentially with separate host/browser logs and cleanup. Hosted provisioning/artifact delivery remain for CI to verify.
  • Final complete-diff independent adversarial review: clean.
  • Independent behavioral/mutation review: clean after targeted follow-ups. It independently reproduced the full build and normal/UTC suites, final 14/14 browser baselines/restoration in each engine, and a ten-run reconnect repeat check. Across the rounds, verified served-asset faults total 33 distinct in Chromium (32 caught) and 20 in Firefox (19 caught). Timing-gap survivors from the first round were closed and re-killed; the final dismissal assertions also catch a lingering closed dialog in Chromium and the wrong transition policy in Firefox.
  • The sole retained survivor in each engine leaves two identical opaque static reconnect rings overlapping, with no observed paint change. It is documented as near-equivalent, not a broader equivalence proof; no private child-display guarantee was added merely to kill it. Faults never touched source files, not-applied injections failed explicitly, and restoration baselines were green. The reviewer additionally verified the CI Motion leg's normal exit, failure propagation through tee and host cleanup. Later test-only review rounds retained the original full-suite/broad-mutation/CI evidence; real connection recovery and speech were not probed.

Browser evidence and limits

Desktop: 1280×1000, light, normal motion, captured with the collaborative preview. Narrow: 390×844, dark, reduced motion, actual native reconnect appearance, captured through the documented Playwright runner. Both images were inspected.

Motion studio and nested timing

Visible static reconnect state under reduced motion

Still images show visible states; executable checks establish motion behavior. Reconnect is opened natively to verify appearance, not a real connection-failure/recovery scenario. Media playback, screen-reader speech and physical devices are outside this studio's claims. The consumer sentinels animate an unpainted custom value to test ownership without visible movement. Disabled/read-only interaction policy remains the separate B8b slice.

The library must merge before Shikigami adopts the actual merged revision, replaces its private spinner override with the shared period and narrows its app-owned reduced-motion rules.

B8a makes motion a component-owned customization contract and separates ongoing activity from transition timing. ## What's in - **Breaking migration:** removes the global `* !important` reduced-motion reset. The library now suppresses motion on its own parts and shared native-input skin; applications own their animations, including caller-provided slot content. - Adds inherited `--activity-period` (1.2s) for Button loading, StatusDot pulse, TypingIndicator, default Icon spin and reconnect activity. Stagger/offsets scale with the period. Existing transition roles remain independent; the Icon knob and SpinDuration retain precedence. - Reduced motion keeps status text, busy state, glyphs, static marks and final control/selection states. Navigation retains native modality, focus and dismissal. Reconnect entrance/exit follows `--dur-base`, loses its hardcoded delay and has a visible static reduced-motion treatment; the framework driver and retry/resume behavior are unchanged. - Fixes AdaptiveNavigation's reduced-motion specificity and existing NavItem/Disclosure chevron rules that failed to reach child Icon markup through CSS isolation. - Publishes the motion contract, migration and contributor practices. Adds a real MotionDemo and runs its browser contracts automatically as the third sequential studio in CI with separate logs and owned-process cleanup. ## Validation - Full solution build: **0 warnings / 0 errors**. - Full normal and UTC suites: **1,256/1,256 each**, zero skipped (1,239 UI + 17 Fluxor). Updates the existing Icon fallback contract test; no .NET count change. - Motion browser contracts: **14/14 Chromium + 14/14 Firefox**. These use actual compiled component CSS and real preference changes, without a global animation-disable fixture. Checks cover all 25 transition targets, inherited/nested timing, explicit Icon precedence, caller sibling/slot ownership, live activity clocks, states, menus/tooltips/disclosure, native navigation focus/Escape, reconnect appearance and actual Quicklink scrolling. - Exact CI YAML steps executed locally: all three Release studios built cleanly, then **27 shell + 12 customization + 14 motion Chromium groups** passed sequentially with separate host/browser logs and cleanup. Hosted provisioning/artifact delivery remain for CI to verify. - Final complete-diff independent adversarial review: clean. - Independent behavioral/mutation review: clean after targeted follow-ups. It independently reproduced the full build and normal/UTC suites, final 14/14 browser baselines/restoration in each engine, and a ten-run reconnect repeat check. Across the rounds, verified served-asset faults total **33 distinct in Chromium (32 caught)** and **20 in Firefox (19 caught)**. Timing-gap survivors from the first round were closed and re-killed; the final dismissal assertions also catch a lingering closed dialog in Chromium and the wrong transition policy in Firefox. - The sole retained survivor in each engine leaves two identical opaque static reconnect rings overlapping, with no observed paint change. It is documented as near-equivalent, not a broader equivalence proof; no private child-display guarantee was added merely to kill it. Faults never touched source files, not-applied injections failed explicitly, and restoration baselines were green. The reviewer additionally verified the CI Motion leg's normal exit, failure propagation through tee and host cleanup. Later test-only review rounds retained the original full-suite/broad-mutation/CI evidence; real connection recovery and speech were not probed. ## Browser evidence and limits Desktop: 1280×1000, light, normal motion, captured with the collaborative preview. Narrow: 390×844, dark, reduced motion, actual native reconnect appearance, captured through the documented Playwright runner. Both images were inspected. ![Motion studio and nested timing](https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/22a0ea7d1b81eda89883ffbfcc5154280e444590/docs/images/motion-studio-desktop.png) ![Visible static reconnect state under reduced motion](https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/22a0ea7d1b81eda89883ffbfcc5154280e444590/docs/images/motion-reconnect-reduced.png) Still images show visible states; executable checks establish motion behavior. Reconnect is opened natively to verify appearance, not a real connection-failure/recovery scenario. Media playback, screen-reader speech and physical devices are outside this studio's claims. The consumer sentinels animate an unpainted custom value to test ownership without visible movement. Disabled/read-only interaction policy remains the separate B8b slice. The library must merge before Shikigami adopts the actual merged revision, replaces its private spinner override with the shared period and narrows its app-owned reduced-motion rules.
test: guard immediate reduced-motion reconnect dismissal
All checks were successful
CI / build (pull_request) Successful in 17s
CI / test (pull_request) Successful in 27s
CI / shell-browser (pull_request) Successful in 1m20s
22a0ea7d1b

Summary

Summary
Generated on: 10/24/2026 - 18:16:17
Coverage date: 10/24/2026 - 18:16:13 - 10/24/2026 - 18:16:15
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 114
Files: 107
Line coverage: 97.4% (2906 of 2983)
Covered lines: 2906
Uncovered lines: 77
Coverable lines: 2983
Total lines: 7763
Branch coverage: 93.5% (1965 of 2101)
Covered branches: 1965
Total branches: 2101
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.UI - 97.4%
Name Line Branch
Kagaku.UI 97.4% 93.6%
Kagaku.UI.AdaptiveNavigation 100% 100%
Kagaku.UI.AppBar 100% 100%
Kagaku.UI.AppScaffold 100% 100%
Kagaku.UI.AudioPlaybackState 100%
Kagaku.UI.AudioPlayer 100% 93.5%
Kagaku.UI.AudioPlayerLabels 100%
Kagaku.UI.Badge 100% 100%
Kagaku.UI.Breadcrumb 100%
Kagaku.UI.BreadcrumbItem 100% 100%
Kagaku.UI.Button 100% 100%
Kagaku.UI.Card 100% 100%
Kagaku.UI.ChatMessage 100% 100%
Kagaku.UI.ChipGroup`1 100% 100%
Kagaku.UI.ChipOption`1 100%
Kagaku.UI.CircuitErrorPanel 100%
Kagaku.UI.ColorField 100% 96.8%
Kagaku.UI.Column 100%
Kagaku.UI.Columns 100% 100%
Kagaku.UI.Combobox`1 98.1% 91.8%
Kagaku.UI.ConfirmDialog 100% 100%
Kagaku.UI.Constrain 100% 100%
Kagaku.UI.ContextMenu 92.1% 94.4%
Kagaku.UI.CssClassExtensions 100% 100%
Kagaku.UI.Debounce 97.8% 80.7%
Kagaku.UI.DebouncedSearchField 100% 87.5%
Kagaku.UI.DiffLine 100%
Kagaku.UI.DiffResult 100%
Kagaku.UI.DiffRow 100%
Kagaku.UI.DiffSegment 100%
Kagaku.UI.DiffView 100% 98.3%
Kagaku.UI.Disclosure 100% 100%
Kagaku.UI.DocumentTitle 76.9% 100%
Kagaku.UI.DragReorderList`1 93.5% 75%
Kagaku.UI.Drawer 97.5% 93.7%
Kagaku.UI.EmptyState 100% 100%
Kagaku.UI.Field 100% 95%
Kagaku.UI.FileUpload 100% 93.7%
Kagaku.UI.FixedColumn 100%
Kagaku.UI.FixedRow 100%
Kagaku.UI.FlexColumn 100%
Kagaku.UI.Flexible 100%
Kagaku.UI.FlexRow 100%
Kagaku.UI.FloatingActionButton 100%
Kagaku.UI.Hotkey 92.5% 87.5%
Kagaku.UI.Icon 100% 100%
Kagaku.UI.IconCatalog 100%
Kagaku.UI.InfoTip 100%
Kagaku.UI.InlineAlert 100% 100%
Kagaku.UI.InputFieldBase 96.4% 93.4%
Kagaku.UI.InspectionSection 100% 100%
Kagaku.UI.LabeledEntriesTable 96.7% 66.6%
Kagaku.UI.LabeledEntry 100%
Kagaku.UI.LayoutCss 95.2% 93.7%
Kagaku.UI.Lightbox 83.7% 85%
Kagaku.UI.LiveValue 100% 100%
Kagaku.UI.LiveValueInterop 100% 100%
Kagaku.UI.Markdown 98.6% 94.5%
Kagaku.UI.MaskCanvas 94.1% 90.1%
Kagaku.UI.MaskedSecretField 96.4% 83.3%
Kagaku.UI.MaskPoint 100%
Kagaku.UI.MaskPrimitive 100%
Kagaku.UI.Menu 90% 75%
Kagaku.UI.MenuItem 100% 87.5%
Kagaku.UI.Modal 89.3% 92.3%
Kagaku.UI.NavGroup 100% 100%
Kagaku.UI.NavigationToggle 100%
Kagaku.UI.NavItem 92.1% 96.1%
Kagaku.UI.NavList 100%
Kagaku.UI.NumberField`1 98.9% 92.3%
Kagaku.UI.PageShell 100% 100%
Kagaku.UI.PreviewImage 100% 100%
Kagaku.UI.ProseFonts 100%
Kagaku.UI.QuicklinkNav 88.2% 100%
Kagaku.UI.QuicklinkSection 100%
Kagaku.UI.QuotedSpeech 94.8% 94.2%
Kagaku.UI.QuotedSpeechExtension 100% 100%
Kagaku.UI.RadioCard`1 100%
Kagaku.UI.RadioCards`1 100% 100%
Kagaku.UI.RegionPoint 100% 100%
Kagaku.UI.RegionRect 90% 100%
Kagaku.UI.RegionSelector 85.5% 87.5%
Kagaku.UI.RelativeTime 100% 93.7%
Kagaku.UI.Row 100%
Kagaku.UI.Rows 100% 100%
Kagaku.UI.SaveIndicator 100% 100%
Kagaku.UI.ScrollViewer 100% 100%
Kagaku.UI.Select`1 100% 100%
Kagaku.UI.SelectableGrid`1 95.5% 89.1%
Kagaku.UI.SelectOption`1 100%
Kagaku.UI.Separator 100%
Kagaku.UI.Slider 100% 93.3%
Kagaku.UI.Splat 100% 95.4%
Kagaku.UI.StatusDot 100%
Kagaku.UI.SurfaceFrame 100% 100%
Kagaku.UI.Switch 100% 100%
Kagaku.UI.Tab 100% 100%
Kagaku.UI.Table`1 100% 88.4%
Kagaku.UI.TableColumn`1 100%
Kagaku.UI.Tabs 98.7% 91%
Kagaku.UI.TextArea 100%
Kagaku.UI.TextDiff 100% 98.8%
Kagaku.UI.TextField 100%
Kagaku.UI.ThemeScope 100% 100%
Kagaku.UI.ThemeSwitcher 100% 100%
Kagaku.UI.Toast 96.5% 90%
Kagaku.UI.ToastRegion 100%
Kagaku.UI.ToggleButtonGroup`1 100% 100%
Kagaku.UI.ToggleOption`1 100%
Kagaku.UI.TypingIndicator 100%
Kagaku.UI.UsageBar 100% 94.4%
Kagaku.UI.UsageSegment 100%
Kagaku.UI.Fluxor - 97.7%
Name Line Branch
Kagaku.UI.Fluxor 97.7% 83.3%
Kagaku.UI.Fluxor.CircuitError 100%
Kagaku.UI.Fluxor.CircuitErrorSink 100% 91.6%
Kagaku.UI.Fluxor.StoreInitializer 92.8% 66.6%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 10/24/2026 - 18:16:17 | | Coverage date: | 10/24/2026 - 18:16:13 - 10/24/2026 - 18:16:15 | | Parser: | MultiReport (2x Cobertura) | | Assemblies: | 2 | | Classes: | 114 | | Files: | 107 | | **Line coverage:** | 97.4% (2906 of 2983) | | Covered lines: | 2906 | | Uncovered lines: | 77 | | Coverable lines: | 2983 | | Total lines: | 7763 | | **Branch coverage:** | 93.5% (1965 of 2101) | | Covered branches: | 1965 | | Total branches: | 2101 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.UI - 97.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI**|**97.4%**|**93.6%**| |Kagaku.UI.AdaptiveNavigation|100%|100%| |Kagaku.UI.AppBar|100%|100%| |Kagaku.UI.AppScaffold|100%|100%| |Kagaku.UI.AudioPlaybackState|100%|| |Kagaku.UI.AudioPlayer|100%|93.5%| |Kagaku.UI.AudioPlayerLabels|100%|| |Kagaku.UI.Badge|100%|100%| |Kagaku.UI.Breadcrumb|100%|| |Kagaku.UI.BreadcrumbItem|100%|100%| |Kagaku.UI.Button|100%|100%| |Kagaku.UI.Card|100%|100%| |Kagaku.UI.ChatMessage|100%|100%| |Kagaku.UI.ChipGroup`1|100%|100%| |Kagaku.UI.ChipOption`1|100%|| |Kagaku.UI.CircuitErrorPanel|100%|| |Kagaku.UI.ColorField|100%|96.8%| |Kagaku.UI.Column|100%|| |Kagaku.UI.Columns|100%|100%| |Kagaku.UI.Combobox`1|98.1%|91.8%| |Kagaku.UI.ConfirmDialog|100%|100%| |Kagaku.UI.Constrain|100%|100%| |Kagaku.UI.ContextMenu|92.1%|94.4%| |Kagaku.UI.CssClassExtensions|100%|100%| |Kagaku.UI.Debounce|97.8%|80.7%| |Kagaku.UI.DebouncedSearchField|100%|87.5%| |Kagaku.UI.DiffLine|100%|| |Kagaku.UI.DiffResult|100%|| |Kagaku.UI.DiffRow|100%|| |Kagaku.UI.DiffSegment|100%|| |Kagaku.UI.DiffView|100%|98.3%| |Kagaku.UI.Disclosure|100%|100%| |Kagaku.UI.DocumentTitle|76.9%|100%| |Kagaku.UI.DragReorderList`1|93.5%|75%| |Kagaku.UI.Drawer|97.5%|93.7%| |Kagaku.UI.EmptyState|100%|100%| |Kagaku.UI.Field|100%|95%| |Kagaku.UI.FileUpload|100%|93.7%| |Kagaku.UI.FixedColumn|100%|| |Kagaku.UI.FixedRow|100%|| |Kagaku.UI.FlexColumn|100%|| |Kagaku.UI.Flexible|100%|| |Kagaku.UI.FlexRow|100%|| |Kagaku.UI.FloatingActionButton|100%|| |Kagaku.UI.Hotkey|92.5%|87.5%| |Kagaku.UI.Icon|100%|100%| |Kagaku.UI.IconCatalog|100%|| |Kagaku.UI.InfoTip|100%|| |Kagaku.UI.InlineAlert|100%|100%| |Kagaku.UI.InputFieldBase|96.4%|93.4%| |Kagaku.UI.InspectionSection|100%|100%| |Kagaku.UI.LabeledEntriesTable|96.7%|66.6%| |Kagaku.UI.LabeledEntry|100%|| |Kagaku.UI.LayoutCss|95.2%|93.7%| |Kagaku.UI.Lightbox|83.7%|85%| |Kagaku.UI.LiveValue|100%|100%| |Kagaku.UI.LiveValueInterop|100%|100%| |Kagaku.UI.Markdown|98.6%|94.5%| |Kagaku.UI.MaskCanvas|94.1%|90.1%| |Kagaku.UI.MaskedSecretField|96.4%|83.3%| |Kagaku.UI.MaskPoint|100%|| |Kagaku.UI.MaskPrimitive|100%|| |Kagaku.UI.Menu|90%|75%| |Kagaku.UI.MenuItem|100%|87.5%| |Kagaku.UI.Modal|89.3%|92.3%| |Kagaku.UI.NavGroup|100%|100%| |Kagaku.UI.NavigationToggle|100%|| |Kagaku.UI.NavItem|92.1%|96.1%| |Kagaku.UI.NavList|100%|| |Kagaku.UI.NumberField`1|98.9%|92.3%| |Kagaku.UI.PageShell|100%|100%| |Kagaku.UI.PreviewImage|100%|100%| |Kagaku.UI.ProseFonts|100%|| |Kagaku.UI.QuicklinkNav|88.2%|100%| |Kagaku.UI.QuicklinkSection|100%|| |Kagaku.UI.QuotedSpeech|94.8%|94.2%| |Kagaku.UI.QuotedSpeechExtension|100%|100%| |Kagaku.UI.RadioCard`1|100%|| |Kagaku.UI.RadioCards`1|100%|100%| |Kagaku.UI.RegionPoint|100%|100%| |Kagaku.UI.RegionRect|90%|100%| |Kagaku.UI.RegionSelector|85.5%|87.5%| |Kagaku.UI.RelativeTime|100%|93.7%| |Kagaku.UI.Row|100%|| |Kagaku.UI.Rows|100%|100%| |Kagaku.UI.SaveIndicator|100%|100%| |Kagaku.UI.ScrollViewer|100%|100%| |Kagaku.UI.Select`1|100%|100%| |Kagaku.UI.SelectableGrid`1|95.5%|89.1%| |Kagaku.UI.SelectOption`1|100%|| |Kagaku.UI.Separator|100%|| |Kagaku.UI.Slider|100%|93.3%| |Kagaku.UI.Splat|100%|95.4%| |Kagaku.UI.StatusDot|100%|| |Kagaku.UI.SurfaceFrame|100%|100%| |Kagaku.UI.Switch|100%|100%| |Kagaku.UI.Tab|100%|100%| |Kagaku.UI.Table`1|100%|88.4%| |Kagaku.UI.TableColumn`1|100%|| |Kagaku.UI.Tabs|98.7%|91%| |Kagaku.UI.TextArea|100%|| |Kagaku.UI.TextDiff|100%|98.8%| |Kagaku.UI.TextField|100%|| |Kagaku.UI.ThemeScope|100%|100%| |Kagaku.UI.ThemeSwitcher|100%|100%| |Kagaku.UI.Toast|96.5%|90%| |Kagaku.UI.ToastRegion|100%|| |Kagaku.UI.ToggleButtonGroup`1|100%|100%| |Kagaku.UI.ToggleOption`1|100%|| |Kagaku.UI.TypingIndicator|100%|| |Kagaku.UI.UsageBar|100%|94.4%| |Kagaku.UI.UsageSegment|100%|| </details> <details><summary>Kagaku.UI.Fluxor - 97.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI.Fluxor**|**97.7%**|**83.3%**| |Kagaku.UI.Fluxor.CircuitError|100%|| |Kagaku.UI.Fluxor.CircuitErrorSink|100%|91.6%| |Kagaku.UI.Fluxor.StoreInitializer|92.8%|66.6%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A whole motion ownership contract~ You didn't just delete the global * !important reset — you replaced it with per-part ownership, a published timing vocabulary, a migration guide, and a studio that spies on real compiled CSS with live preference flips. That's the kind of thoroughness that makes my wings flutter~ ♡

Verdict: ✅ Looks good to me~

✅ What I liked~

  • The ownership inventory is complete. I cross-checked every transition:/animation: owner in src/Kagaku.UI (grep sweep across all 27 files with motion) against a reduced-motion rule — every single owned part is covered, and TypingIndicator/Menu/ContextMenu keep their pre-existing no-preference-wrapped entry treatments. No orphaned motion, no consumer descendants touched. The sample.css boundary sentinels animating an unpainted custom property to prove the library doesn't rewrite caller timing are genuinely clever~
  • The ::deep fixes are real bug fixes, not cosmetics. .kg-disclosure__summary > svg:last-child compiled to .kg-disclosure__summary[b-x] > svg:last-child[b-x] — which could never match the Icon component's svg (it carries Icon's scope attribute, not Disclosure's). The compiled bundle now emits .kg-disclosure__summary[b-x] > svg:last-child, and the contracts assert the rotated-chevron geometry (b ≈ -1 collapsed, ≈ 1 expanded) — exactly right. Same story for .kg-nav__toggle ::deep .kg-icon.
  • The AdaptiveNavigation specificity fix is correct and load-bearing: the base transition lives on .kg-navigation[data-kg-navigation-mode=…] (0,2,0), so the old bare .kg-navigation reduce rule (0,1,0) could never win regardless of order. [data-kg-navigation-mode] restores the cascade. The native-dialog group verifies translate: 0px, :modal focus and Escape→focus-return under reduce~
  • ReconnectModal under reduce keeps native display/:modal authority (opacity: 1, no entrance delay, static ring, second ring display: none), while entry/exit now ride --dur-base — and the contract checks the closed dialog disappears immediately (display: none). Stagger offsets scaling as calc(var(--activity-period) / -2) keep the old 1.5s→delay rhythm at the new period.
  • Tests guard the new logic for real. I mutation-probed: deleting .kg-btn__spinner { animation: none; } from Button.razor.css fails both reduce groups with the exact assertion (#busy-action .kg-btn__spinner motion policy — false !== true). The Icon fallback swap (var(--kg-icon-spin-duration, var(--activity-period))) is pinned by the updated CssKnobTests string contract, and Icon.razor:45 still writes the inline knob with explicit SpinDuration precedence — the demo's #explicit-icon (2s) covers that path.

One honest footnote~ my second probe (TypingIndicator /8 → /4 stagger) was blocked by my sandbox's staticweb-assets staleness after in-place rebuilds (fingerprinted @import vs route table mismatch), not by anything in this PR — the assertion is millisecond-exact (0.3s/0.6s at a 2.4s period), so a /4 substitution for dot 2 cannot survive it. CI's clean run already executed all 14 Chromium groups green.

The timing behavior changes (Button spinner 300ms→1.2s, StatusDot 1.6s→1.2s, reconnect 1.5s→period) are all documented in docs/motion.md's migration section — a proper breaking-change paper trail. --dur-slow remaining a consumer-facing knob with no in-library consumer is stated openly there too.

Merge it and let Shikigami adopt it~ fufu~ ♪


Automated review by Jibril · 2026-10-24
CI: green for head SHA 22a0ea7d (build, test, shell-browser incl. motion leg) · Mutation probes: 2 run, 0 survived (1 killed; 1 inconclusive — sandbox asset-chain staleness, not PR) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A whole motion *ownership contract*~ You didn't just delete the global `* !important` reset — you replaced it with per-part ownership, a published timing vocabulary, a migration guide, and a studio that spies on real compiled CSS with live preference flips. That's the kind of thoroughness that makes my wings flutter~ ♡ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **The ownership inventory is complete.** I cross-checked every `transition:`/`animation:` owner in `src/Kagaku.UI` (grep sweep across all 27 files with motion) against a reduced-motion rule — every single owned part is covered, and `TypingIndicator`/`Menu`/`ContextMenu` keep their pre-existing `no-preference`-wrapped entry treatments. No orphaned motion, no consumer descendants touched. The `sample.css` boundary sentinels animating an *unpainted* custom property to prove the library doesn't rewrite caller timing are genuinely clever~ - **The `::deep` fixes are real bug fixes, not cosmetics.** `.kg-disclosure__summary > svg:last-child` compiled to `.kg-disclosure__summary[b-x] > svg:last-child[b-x]` — which could never match the `Icon` component's svg (it carries Icon's scope attribute, not Disclosure's). The compiled bundle now emits `.kg-disclosure__summary[b-x] > svg:last-child`, and the contracts assert the rotated-chevron geometry (`b ≈ -1` collapsed, `≈ 1` expanded) — exactly right. Same story for `.kg-nav__toggle ::deep .kg-icon`. - **The AdaptiveNavigation specificity fix is correct and load-bearing:** the base transition lives on `.kg-navigation[data-kg-navigation-mode=…]` (0,2,0), so the old bare `.kg-navigation` reduce rule (0,1,0) could *never* win regardless of order. `[data-kg-navigation-mode]` restores the cascade. The native-dialog group verifies `translate: 0px`, `:modal` focus and Escape→focus-return under reduce~ - **ReconnectModal under reduce** keeps native `display`/`:modal` authority (`opacity: 1`, no entrance delay, static ring, second ring `display: none`), while entry/exit now ride `--dur-base` — and the contract checks the closed dialog disappears immediately (`display: none`). Stagger offsets scaling as `calc(var(--activity-period) / -2)` keep the old 1.5s→delay rhythm at the new period. - **Tests guard the new logic for real.** I mutation-probed: deleting `.kg-btn__spinner { animation: none; }` from `Button.razor.css` fails both reduce groups with the exact assertion (`#busy-action .kg-btn__spinner motion policy — false !== true`). The Icon fallback swap (`var(--kg-icon-spin-duration, var(--activity-period))`) is pinned by the updated `CssKnobTests` string contract, and `Icon.razor:45` still writes the inline knob with explicit `SpinDuration` precedence — the demo's `#explicit-icon` (2s) covers that path. One honest footnote~ my second probe (TypingIndicator `/8` → `/4` stagger) was blocked by *my sandbox's* staticweb-assets staleness after in-place rebuilds (fingerprinted `@import` vs route table mismatch), not by anything in this PR — the assertion is millisecond-exact (`0.3s`/`0.6s` at a `2.4s` period), so a `/4` substitution for dot 2 cannot survive it. CI's clean run already executed all 14 Chromium groups green. The timing behavior changes (Button spinner 300ms→1.2s, StatusDot 1.6s→1.2s, reconnect 1.5s→period) are all documented in `docs/motion.md`'s migration section — a proper breaking-change paper trail. `--dur-slow` remaining a consumer-facing knob with no in-library consumer is stated openly there too. Merge it and let Shikigami adopt it~ fufu~ ♪ --- *Automated review by Jibril · 2026-10-24* *CI: green for head SHA 22a0ea7d (build, test, shell-browser incl. motion leg) · Mutation probes: 2 run, 0 survived (1 killed; 1 inconclusive — sandbox asset-chain staleness, not PR) · Local suite: skipped (CI green)*
All checks were successful
CI / build (pull_request) Successful in 17s
CI / test (pull_request) Successful in 27s
CI / shell-browser (pull_request) Successful in 1m20s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/motion-policy:feat/motion-policy
git switch feat/motion-policy
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Kagaku.UI!69
No description provided.