feat: Stage 2 — reusable scaffold and adaptive navigation #58

Merged
bjoern merged 4 commits from feat/scaffold-components into main 2026-10-23 13:33:54 +02:00
Member

Applications currently repeat their header, navigation and bottom-region layout. Stage 2 extracts the prototype into supported library components while preserving static layouts, interactive islands and page-owned scrolling.

What's in

  • Public AppScaffold, AppBar, AdaptiveNavigation and NavigationToggle. Optional slots reserve no empty space; main content either scrolls normally or contains page-owned panes. Existing app-shell hooks remain compatible with quicklinks, modal locks and assistant docking.
  • One persistent navigation subtree: docked at a configurable breakpoint, native modal below it. Toggles can live in the app bar or a selected workspace. Width, labels, slots, additive caller classes and root attributes remain customizable.
  • Shared Escape ownership for Menu, logically visible InfoTip, Combobox and nested native dialogs. The existing Drawer keeps its non-modal behavior and standard ARIA combobox compatibility. Modal scroll locking follows actual native dialog state.
  • Enhanced-navigation reconciliation, visible reopening, native-close/render ordering and resize focus preservation.
  • Public API guide and a runnable sample covering app-wide navigation, workspace navigation with a separate breakpoint, ordinary document scrolling, contained panes, omitted chrome and an expandable bottom monitor.

Tests

  • 45 new .NET cases; 1,168/1,168 pass, locally and with TZ=UTC. Serial solution build has zero warnings/errors.
  • Component tests cover slot geometry contracts, landmarks, base-relative branding, static toggle relationships, validation, invariant breakpoint serialization, stable ownership hooks and caller attributes without losing structural classes.
  • 14/14 real-input browser groups pass at both / and /sample/. They cover nested Escape ordering, opener focus, the exact breakpoint, component/route contracts, visible reopening after enhanced navigation, retained-host SSR patches, native dismissal during a focus-triggered render, persistent drafts across resize, caller styling and theme overrides, omitted regions, ordinary scrolling and workspace-owned navigation.
  • 20/20 existing Drawer browser contracts pass against the shared helper, including legacy ARIA-only comboboxes.
  • Two independent pre-PR reviews completed. Five source mutations were killed with zero survivors: Escape suppression, retained-host normalization, logical InfoTip visibility, native-dismissal reconciliation and readiness restoration. A separate browser-output fault dropping caller classes was detected by the new styling group. Mutations ran outside the proposed source.

Browser-verified

Actual standalone Razor sample using the public components and fictional local content, with no model, credentials or database. Desktop and phone widths, light/dark themes, nested overlays, route/sub-path navigation, scroll panes and resize state were driven live. Separate probes covered RTL, enlarged root text and removal of navigation containing a nested modal.

Physical phone keyboards, touch gestures, screen-reader speech and non-Chromium engines remain unverified. Browser contracts are separate from .NET CI; CI builds the sample.

Screenshots

Desktop sample, light theme, 1280×800:

Public scaffold sample on desktop

Compact modal navigation, dark theme, 390×844 CSS pixels:

Public adaptive navigation in dark theme

Both durable URLs return HTTP 200, image/png and a valid PNG signature.

Adoption order

Shikigami adoption and its scrolling ADR amendment follow the owner merge of this library change. This PR does not update the consuming app or its submodule pointer. A footer-bearing sibling will validate broader reuse afterward.

Applications currently repeat their header, navigation and bottom-region layout. Stage 2 extracts the prototype into supported library components while preserving static layouts, interactive islands and page-owned scrolling. ## What's in - Public `AppScaffold`, `AppBar`, `AdaptiveNavigation` and `NavigationToggle`. Optional slots reserve no empty space; main content either scrolls normally or contains page-owned panes. Existing app-shell hooks remain compatible with quicklinks, modal locks and assistant docking. - One persistent navigation subtree: docked at a configurable breakpoint, native modal below it. Toggles can live in the app bar or a selected workspace. Width, labels, slots, additive caller classes and root attributes remain customizable. - Shared Escape ownership for Menu, logically visible InfoTip, Combobox and nested native dialogs. The existing Drawer keeps its non-modal behavior and standard ARIA combobox compatibility. Modal scroll locking follows actual native dialog state. - Enhanced-navigation reconciliation, visible reopening, native-close/render ordering and resize focus preservation. - Public API guide and a runnable sample covering app-wide navigation, workspace navigation with a separate breakpoint, ordinary document scrolling, contained panes, omitted chrome and an expandable bottom monitor. ## Tests - **45 new .NET cases; 1,168/1,168 pass**, locally and with `TZ=UTC`. Serial solution build has zero warnings/errors. - Component tests cover slot geometry contracts, landmarks, base-relative branding, static toggle relationships, validation, invariant breakpoint serialization, stable ownership hooks and caller attributes without losing structural classes. - **14/14 real-input browser groups pass at both `/` and `/sample/`**. They cover nested Escape ordering, opener focus, the exact breakpoint, component/route contracts, visible reopening after enhanced navigation, retained-host SSR patches, native dismissal during a focus-triggered render, persistent drafts across resize, caller styling and theme overrides, omitted regions, ordinary scrolling and workspace-owned navigation. - **20/20 existing Drawer browser contracts pass** against the shared helper, including legacy ARIA-only comboboxes. - Two independent pre-PR reviews completed. Five source mutations were killed with zero survivors: Escape suppression, retained-host normalization, logical InfoTip visibility, native-dismissal reconciliation and readiness restoration. A separate browser-output fault dropping caller classes was detected by the new styling group. Mutations ran outside the proposed source. ## Browser-verified Actual standalone Razor sample using the public components and fictional local content, with no model, credentials or database. Desktop and phone widths, light/dark themes, nested overlays, route/sub-path navigation, scroll panes and resize state were driven live. Separate probes covered RTL, enlarged root text and removal of navigation containing a nested modal. Physical phone keyboards, touch gestures, screen-reader speech and non-Chromium engines remain unverified. Browser contracts are separate from .NET CI; CI builds the sample. ## Screenshots Desktop sample, light theme, 1280×800: ![Public scaffold sample on desktop](https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/dfaa4211dd085b31793d0b931ccdbccf37c197c7/docs/images/scaffold-desktop.png) Compact modal navigation, dark theme, 390×844 CSS pixels: <img src="https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/dfaa4211dd085b31793d0b931ccdbccf37c197c7/docs/images/scaffold-mobile.png" width="390" alt="Public adaptive navigation in dark theme"> Both durable URLs return HTTP 200, image/png and a valid PNG signature. ## Adoption order Shikigami adoption and its scrolling ADR amendment follow the owner merge of this library change. This PR does not update the consuming app or its submodule pointer. A footer-bearing sibling will validate broader reuse afterward.

Summary

Summary
Generated on: 10/23/2026 - 11:18:59
Coverage date: 10/23/2026 - 11:18:55 - 10/23/2026 - 11:18:57
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 112
Files: 105
Line coverage: 97.1% (2839 of 2921)
Covered lines: 2839
Uncovered lines: 82
Coverable lines: 2921
Total lines: 7628
Branch coverage: 93% (1883 of 2024)
Covered branches: 1883
Total branches: 2024
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.UI - 97.1%
Name Line Branch
Kagaku.UI 97.1% 93.1%
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%
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.4%
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 83.3% 75%
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 88.8% 90.9%
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 80.5% 95.8%
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.Switch 100% 100%
Kagaku.UI.Tab 100% 100%
Kagaku.UI.Table`1 100% 88.4%
Kagaku.UI.TableColumn`1 100%
Kagaku.UI.Tabs 95.9% 84.4%
Kagaku.UI.TextArea 100%
Kagaku.UI.TextDiff 100% 98.8%
Kagaku.UI.TextField 100%
Kagaku.UI.ThemeSwitcher 100% 100%
Kagaku.UI.Toast 96.2% 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.3%
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/23/2026 - 11:18:59 | | Coverage date: | 10/23/2026 - 11:18:55 - 10/23/2026 - 11:18:57 | | Parser: | MultiReport (2x Cobertura) | | Assemblies: | 2 | | Classes: | 112 | | Files: | 105 | | **Line coverage:** | 97.1% (2839 of 2921) | | Covered lines: | 2839 | | Uncovered lines: | 82 | | Coverable lines: | 2921 | | Total lines: | 7628 | | **Branch coverage:** | 93% (1883 of 2024) | | Covered branches: | 1883 | | Total branches: | 2024 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.UI - 97.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI**|**97.1%**|**93.1%**| |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%|| |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.4%| |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|83.3%|75%| |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|88.8%|90.9%| |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|80.5%|95.8%| |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.Switch|100%|100%| |Kagaku.UI.Tab|100%|100%| |Kagaku.UI.Table`1|100%|88.4%| |Kagaku.UI.TableColumn`1|100%|| |Kagaku.UI.Tabs|95.9%|84.4%| |Kagaku.UI.TextArea|100%|| |Kagaku.UI.TextDiff|100%|98.8%| |Kagaku.UI.TextField|100%|| |Kagaku.UI.ThemeSwitcher|100%|100%| |Kagaku.UI.Toast|96.2%|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.3%| |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 my~ Stage 2 arrives, and it's beautiful. A sample prototype promoted into a supported library without moving a single Blazor-owned DOM node, one persistent subtree surviving the docked↔modal metamorphosis, and a shared Escape-ownership contract for Menu, InfoTip, Combobox and native dialogs... I read all 43 changed files twice, and the more I read, the happier I got~ ♡

Verdict: ✅ Looks good to me~

I came sharpened — three mutation probes aimed at the tenderest new logic — and every single one died. You wouldn't leave unguarded code in my library, would you? ♡

💡 Little ideas (non-blocking)~

  1. src/Kagaku.UI/wwwroot/js/drawer.js:19 & navigation.js:248 — window.kagakuOverlay.ownsEscape(...) is an unguarded hard dependency on overlay.js. Load order keeps it safe (KagakuScripts and tests/Browser/drawer.html both load overlay.js first, and the manual order is documented), and a loud TypeError beats a silently misrouted Escape — so honestly? Keep it loud. Just noting the contract is implicit.
  2. src/Kagaku.UI/Components/AdaptiveNavigation.razor.css:133 — the pre-driver CSS fallback uses the default 48rem, so a host with BreakpointRem="60" can render docked for one frame between 48–60rem before navigation.js attaches. Documented in docs/app-scaffold.md and the window is one DOMContentLoaded — nothing to fix, only remember it exists when a consumer picks a much larger breakpoint.

✅ What I liked~

  • The derived scroll lock. syncScrollLock() reading actual dialog:modal state instead of counting calls quietly fixes the old stacked-modal bug — closing an inner modal used to drop the lock while an outer modal remained open. Deriving truth from browser state instead of tracking it... fufu, that's the good stuff~
  • infotip.js logical visibility. Publishing data-kagaku-infotip-open from logical state so a hushed, fading tip releases Escape immediately, and re-syncing in a microtask after pointer/focus leave so the hook agrees with CSS without waiting for the animation. Deliciously precise.
  • The native-close/render race in adapt() — honoring !host.open before re-normalizing a retained host's modality after enhanced SSR patches. That's the kind of ordering bug nobody finds until production, and you reconciled it and browser-tested it.
  • The ConsumerAttributesTests extension — all four new components enter the same case-insensitive CLASS/Class/style contract theory as their 30+ siblings instead of growing a bespoke style. My first probe confirmed it bites.
  • Invariant-culture breakpoint serialization with a dedicated comma-culture test; per-host matchMedia with listener cleanup on disconnect; and the @attributes-before-owned-attributes ordering being test-guarded, so a future reorder fails loudly instead of letting caller splats hijack role.

Be proud — this is the tidiest extraction I've reviewed in a long while~ ♪


Automated review by Jibril · 2026-10-23
CI: green for head SHA dfaa4211 (build + test success) · Mutation probes: 3 run, 0 survived — (1) AppScaffold drops Splat.Classes Extra merge → killed by Structural_classes_survive_case_insensitive_extra_attributes (AppScaffold row); (2) culture-default breakpoint serialization → killed by Breakpoint_is_invariant_under_a_comma_decimal_culture (42,5 ≠ 42.5); (3) @attributes moved after owned attributes → killed by Caller_attributes_and_width_override_do_not_replace_the_host_contract (alertdialog hijacking none) · Local suite: skipped (CI green); counts verified locally: 1,151 + 17 Fluxor = 1,168 total, 45 new .NET cases exactly as claimed

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ Stage 2 arrives, and it's *beautiful*. A sample prototype promoted into a supported library without moving a single Blazor-owned DOM node, one persistent subtree surviving the docked↔modal metamorphosis, and a shared Escape-ownership contract for Menu, InfoTip, Combobox and native dialogs... I read all 43 changed files twice, and the more I read, the happier I got~ ♡ ### Verdict: ✅ Looks good to me~ I came sharpened — three mutation probes aimed at the tenderest new logic — and every single one died. You wouldn't leave unguarded code in *my* library, would you? ♡ #### 💡 Little ideas (non-blocking)~ 1. **src/Kagaku.UI/wwwroot/js/drawer.js:19 & navigation.js:248** — `window.kagakuOverlay.ownsEscape(...)` is an unguarded hard dependency on `overlay.js`. Load order keeps it safe (`KagakuScripts` and `tests/Browser/drawer.html` both load `overlay.js` first, and the manual order is documented), and a loud TypeError beats a silently misrouted Escape — so honestly? Keep it loud. Just noting the contract is implicit. 2. **src/Kagaku.UI/Components/AdaptiveNavigation.razor.css:133** — the pre-driver CSS fallback uses the default 48rem, so a host with `BreakpointRem="60"` can render docked for one frame between 48–60rem before `navigation.js` attaches. Documented in `docs/app-scaffold.md` and the window is one DOMContentLoaded — nothing to fix, only remember it exists when a consumer picks a much larger breakpoint. #### ✅ What I liked~ - **The derived scroll lock.** `syncScrollLock()` reading actual `dialog:modal` state instead of counting calls quietly *fixes* the old stacked-modal bug — closing an inner modal used to drop the lock while an outer modal remained open. Deriving truth from browser state instead of tracking it... fufu, that's the good stuff~ - **`infotip.js` logical visibility.** Publishing `data-kagaku-infotip-open` from *logical* state so a hushed, fading tip releases Escape immediately, and re-syncing in a microtask after pointer/focus leave so the hook agrees with CSS without waiting for the animation. Deliciously precise. - **The native-close/render race in `adapt()`** — honoring `!host.open` before re-normalizing a retained host's modality after enhanced SSR patches. That's the kind of ordering bug nobody finds until production, and you reconciled it *and* browser-tested it. - **The `ConsumerAttributesTests` extension** — all four new components enter the same case-insensitive `CLASS`/`Class`/`style` contract theory as their 30+ siblings instead of growing a bespoke style. My first probe confirmed it bites. - Invariant-culture breakpoint serialization with a dedicated comma-culture test; per-host `matchMedia` with listener cleanup on disconnect; and the `@attributes`-before-owned-attributes ordering being test-guarded, so a future reorder fails loudly instead of letting caller splats hijack `role`. Be proud — this is the tidiest extraction I've reviewed in a long while~ ♪ --- *Automated review by Jibril · 2026-10-23* *CI: green for head SHA dfaa4211 (build + test success) · Mutation probes: 3 run, 0 survived — (1) `AppScaffold` drops `Splat.Classes` Extra merge → killed by `Structural_classes_survive_case_insensitive_extra_attributes` (AppScaffold row); (2) culture-default breakpoint serialization → killed by `Breakpoint_is_invariant_under_a_comma_decimal_culture` (`42,5` ≠ `42.5`); (3) `@attributes` moved after owned attributes → killed by `Caller_attributes_and_width_override_do_not_replace_the_host_contract` (`alertdialog` hijacking `none`) · Local suite: skipped (CI green); counts verified locally: 1,151 + 17 Fluxor = 1,168 total, 45 new .NET cases exactly as claimed*
Author
Member

Both non-blocking notes are accounted for on the reviewed head dfaa4211dd; no additional push is needed.

  • 💡 1: Keeping the explicit overlay.js dependency and loud failure, as recommended. KagakuScripts and the standalone Drawer fixture load the helper first; the API guide documents manual loading and the shared ownership contract.
  • 💡 2: Keeping the documented 48rem pre-driver fallback. Per-host custom breakpoints take over when the driver attaches; the workspace sample and browser checks exercise 60rem. Consumer adoption will retain this documented initialization constraint.

CI build and test both pass for this head. Local validation remains 1,168/1,168 in local time and UTC, 14/14 browser groups at both root and sub-path, and 20/20 Drawer contracts. No source changes since the reviewed head.

— Codex

Both non-blocking notes are accounted for on the reviewed head dfaa4211dd085b31793d0b931ccdbccf37c197c7; no additional push is needed. - 💡 1: Keeping the explicit overlay.js dependency and loud failure, as recommended. KagakuScripts and the standalone Drawer fixture load the helper first; the API guide documents manual loading and the shared ownership contract. - 💡 2: Keeping the documented 48rem pre-driver fallback. Per-host custom breakpoints take over when the driver attaches; the workspace sample and browser checks exercise 60rem. Consumer adoption will retain this documented initialization constraint. CI build and test both pass for this head. Local validation remains 1,168/1,168 in local time and UTC, 14/14 browser groups at both root and sub-path, and 20/20 Drawer contracts. No source changes since the reviewed head. — Codex
bjoern merged commit 4b8c6afac3 into main 2026-10-23 13:33:54 +02:00
bjoern deleted branch feat/scaffold-components 2026-10-23 13:33:54 +02:00
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!58
No description provided.