feat: add framed surfaces and vertical settings navigation #65

Merged
bjoern merged 6 commits from feat/surface-frame-vertical-tabs into main 2026-10-24 07:45:21 +02:00
Member

Large configuration surfaces can now keep their header and footer visible while the body scrolls, with vertical tabs that retain editor drafts when requested.

What's in

  • SurfaceFrame composes optional header/body/footer slots, caller attributes, body padding and caller-bounded scrolling using shared tokens.
  • Tabs adds vertical orientation, matching keyboard axes, isolated nested orientation styling, a navigation-width hook and optional hidden retained panels. Stable button/panel identity preserves a surviving editor when a sibling tab is removed. Horizontal lazy mounting remains the default.
  • Modal forwards additive native dialog attributes, names titled dialogs while respecting caller naming sources, and exposes bounded width, height and body-padding properties with the existing defaults.
  • The MaterialDemo Layouts tab exercises these APIs. The guide documents attribute targets, supported frame selectors, sizing fallbacks and the retained-editor lifecycle. App-specific familiar settings composition stays in the consumer.

These are the reusable primitives needed by Shikigami's immersive Lacquer surfaces under its UI/scaffold decisions.

Validation

  • Serial solution build: zero warnings or errors. Full library suite: 1,240 passed in both ordinary and UTC runs (1,223 UI and 17 Fluxor tests). Twenty-three new UI cases cover slots/attributes, native naming, keyboard orientation, three-tab direction/wrapping, caller naming precedence and draft identity.
  • Independent adversarial review cleared the final diff. Independent behavioral review established a 53-case baseline in normal and UTC runs and reran all six browser groups against its own compiled sample. Earlier probes also verified keyed editor identity after sibling removal.
  • Final independent mutations reversed vertical arrows, reversed horizontal arrows, swapped JavaScript orientation branches, removed caller label suppression and restored leaking descendant selectors. All five faults were caught; the JavaScript fault failed both CI script assertions and the actual browser truth table. No requested probe was skipped and no final mutation survived. Sources were restored in an isolated scratch checkout.
  • Actual MaterialDemo browser checks: six Day/Night layout groups and all twelve existing material groups passed. Checked compiled CSS, actual body scrolling with stationary chrome, keyboard focus, both orientation cancellation truth tables through the shipped script, nested horizontal computed styles, retained DOM identity, width/padding overrides and 390px layouts with 20px root text.
  • Rebased onto the shared keyboard-focus contract, preserving native panel focus and inset tab placement. Post-rebase verification: full library suite passed normally and in UTC; all six layout, twelve material and twenty-two focus browser groups passed. Independent code review cleared the conflict resolution. An independent behavioral recheck passed 55 targeted tests normally and in UTC plus all six layout and twenty-two focus browser groups; restored panel-outline suppression and removed-panel-key mutations were both caught, with no survivors. All intentional faults were restored before final passing baselines.
  • Browser evidence is Chromium desktop automation; physical devices, other engines and screen-reader speech were not checked.

Screenshots

Actual MaterialDemo components; these palettes remain sample-owned.

Jade Day, desktop frame:

Framed content with vertical sections

Amber Night, narrow settings dialog with enlarged root text:

Narrow framed settings

Large configuration surfaces can now keep their header and footer visible while the body scrolls, with vertical tabs that retain editor drafts when requested. ## What's in - `SurfaceFrame` composes optional header/body/footer slots, caller attributes, body padding and caller-bounded scrolling using shared tokens. - `Tabs` adds vertical orientation, matching keyboard axes, isolated nested orientation styling, a navigation-width hook and optional hidden retained panels. Stable button/panel identity preserves a surviving editor when a sibling tab is removed. Horizontal lazy mounting remains the default. - `Modal` forwards additive native dialog attributes, names titled dialogs while respecting caller naming sources, and exposes bounded width, height and body-padding properties with the existing defaults. - The MaterialDemo Layouts tab exercises these APIs. The guide documents attribute targets, supported frame selectors, sizing fallbacks and the retained-editor lifecycle. App-specific familiar settings composition stays in the consumer. These are the reusable primitives needed by Shikigami's immersive Lacquer surfaces under its UI/scaffold decisions. ## Validation - Serial solution build: zero warnings or errors. Full library suite: **1,240 passed** in both ordinary and UTC runs (1,223 UI and 17 Fluxor tests). Twenty-three new UI cases cover slots/attributes, native naming, keyboard orientation, three-tab direction/wrapping, caller naming precedence and draft identity. - Independent adversarial review cleared the final diff. Independent behavioral review established a 53-case baseline in normal and UTC runs and reran all six browser groups against its own compiled sample. Earlier probes also verified keyed editor identity after sibling removal. - Final independent mutations reversed vertical arrows, reversed horizontal arrows, swapped JavaScript orientation branches, removed caller label suppression and restored leaking descendant selectors. All five faults were caught; the JavaScript fault failed both CI script assertions and the actual browser truth table. No requested probe was skipped and no final mutation survived. Sources were restored in an isolated scratch checkout. - Actual MaterialDemo browser checks: six Day/Night layout groups and all twelve existing material groups passed. Checked compiled CSS, actual body scrolling with stationary chrome, keyboard focus, both orientation cancellation truth tables through the shipped script, nested horizontal computed styles, retained DOM identity, width/padding overrides and 390px layouts with 20px root text. - Rebased onto the shared keyboard-focus contract, preserving native panel focus and inset tab placement. Post-rebase verification: full library suite passed normally and in UTC; all six layout, twelve material and twenty-two focus browser groups passed. Independent code review cleared the conflict resolution. An independent behavioral recheck passed 55 targeted tests normally and in UTC plus all six layout and twenty-two focus browser groups; restored panel-outline suppression and removed-panel-key mutations were both caught, with no survivors. All intentional faults were restored before final passing baselines. - Browser evidence is Chromium desktop automation; physical devices, other engines and screen-reader speech were not checked. ## Screenshots Actual MaterialDemo components; these palettes remain sample-owned. Jade Day, desktop frame: ![Framed content with vertical sections](https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/67f7e05518daa1e85111d30135a95f6504dc3440/docs/images/surface-frame-desktop.png) Amber Night, narrow settings dialog with enlarged root text: ![Narrow framed settings](https://git.kagaku.eu/TeamAI/Kagaku.UI/raw/commit/67f7e05518daa1e85111d30135a95f6504dc3440/docs/images/surface-frame-mobile.png)

Summary

Summary
Generated on: 10/24/2026 - 05:36:27
Coverage date: 10/24/2026 - 05:36:23 - 10/24/2026 - 05:36:25
Parser: MultiReport (2x Cobertura)
Assemblies: 2
Classes: 114
Files: 107
Line coverage: 97.3% (2886 of 2964)
Covered lines: 2886
Uncovered lines: 78
Coverable lines: 2964
Total lines: 7723
Branch coverage: 93.3% (1928 of 2065)
Covered branches: 1928
Total branches: 2065
Method coverage: Feature is only available for sponsors

Coverage

Kagaku.UI - 97.3%
Name Line Branch
Kagaku.UI 97.3% 93.4%
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 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.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 - 05:36:27 | | Coverage date: | 10/24/2026 - 05:36:23 - 10/24/2026 - 05:36:25 | | Parser: | MultiReport (2x Cobertura) | | Assemblies: | 2 | | Classes: | 114 | | Files: | 107 | | **Line coverage:** | 97.3% (2886 of 2964) | | Covered lines: | 2886 | | Uncovered lines: | 78 | | Coverable lines: | 2964 | | Total lines: | 7723 | | **Branch coverage:** | 93.3% (1928 of 2065) | | Covered branches: | 1928 | | Total branches: | 2065 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Kagaku.UI - 97.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Kagaku.UI**|**97.3%**|**93.4%**| |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|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.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! Three components, a guide, a browser runner and screenshots — you even brought me evidence, like a proper offering~ ♡ The Rows→div swap, the @key identity work, the Splat discipline… I read every line. And then I got curious, and curiosity is where reviews live or die~

Verdict: ⛔ I can't let this pass~ ♡

Not because the components are wrong — they're lovely — but because I held two mutants to your suite and it smiled while they wriggled free. A test suite that stays green when behavior inverts is a cage with no bars, and I simply cannot look away from that~

⛔ These need fixing before I'm satisfied~

  1. tests/Kagaku.UI.Tests/TabsTests.cs:184 (guards src/Kagaku.UI/Components/Tabs.razor:181) — the vertical arrow direction is asserted nowhere. Probe: I swapped the vertical arms (ArrowDown => (current - 1 + count) % count, ArrowUp => (current + 1) % count — i.e. Down moves up) and ran the whole TabsTests class: 23/23 passed. Why it's blind: every keyboard test renders exactly two tabs and starts on index 0, where ArrowDown (→1) and ArrowUp (wrap→1) land on the same tab. surface-layouts.cjs is also two tabs, so the browser check inherits the same blind spot. A shipped inversion keeps CI green while every vertical settings page navigates backwards.
    Fix: add a three-tab test that starts from the middle — click index 1, then ArrowDown → index 2 and ArrowUp → index 0. Note the horizontal theory (Arrow_and_edge_keys_move_the_selection) has the same two-tab shape, so one middle-tab theory per orientation closes both axes.
  2. tests/Kagaku.UI.Tests/KagakuScriptsTests.cs:58 (guards src/Kagaku.UI/wwwroot/js/tabs.js:7) — the new orientation-aware handles() gating is only substring-checked. Probe: I swapped the ternary branches (vertical → Left/Right, horizontal → Up/Down) and ran KagakuScriptsTests: 6/6 passed, because every asserted fragment ("ArrowUp" || "ArrowDown", "ArrowLeft" || "ArrowRight", the aria-orientation comparison, the keydown regex) still appears verbatim — just wired to the wrong axis. Consequence of that mutant: horizontal tabs lose preventDefault on Left/Right, so the row scrolls and the selection moves — the exact double-jump this script exists to prevent.
    Fix: bind condition to consequence — a single Contains spanning === "vertical" ? key === "ArrowUp" || key === "ArrowDown" so a branch swap fails the string, or (nicer~) execute the function in Node with a stub list { getAttribute: () => "vertical" } and assert the truth table for all four arrows.

💡 Little ideas (non-blocking)~

  1. src/Kagaku.UI/Components/Modal.razor:5 — when the caller passes aria-labelledby while Title is also set, the dialog renders two naming sources (aria-labelledby wins in AX). Consider suppressing the generated aria-label when Extra carries aria-labelledby. Only a nicety — the documented override precedence itself is solidly guarded.

✅ What I liked~

  • The Rows→plain-div root swap is faithful to the pixel: Spacing.Large maps to exactly var(--space-4) in LayoutCss, and stretch/flex-start are the flex defaults — I checked, not guessed~
  • @key on buttons and panels, mirrored by SetKey in the sibling-removal test — the retained-editor identity claim is real and proven. This is the strongest part of the PR ♪
  • I moved @attributes in front of aria-label in Modal to break the documented override contract — and ModalTests killed my mutant instantly. Delightful~
  • KeepPanelsMounted semantics are textbook: hidden + tabindex="-1", roving tabindex preserved, and the hidden panel's aria-controls wiring asserted explicitly.
  • material-contracts.cjs switched to named dialog locators before the second dialog could break strict mode — proactive, I noticed~

Automated review by Jibril · 2026-10-24
CI: green for head SHA 3e84dfcb · Mutation probes: 3 run, 2 survived / 0 skipped · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Three components, a guide, a browser runner and screenshots — you even brought me evidence, like a proper offering~ ♡ The `Rows`→`div` swap, the `@key` identity work, the `Splat` discipline… I read every line. And then I got *curious*, and curiosity is where reviews live or die~ ### Verdict: ⛔ I can't let this pass~ ♡ Not because the components are wrong — they're lovely — but because I held two mutants to your suite and it **smiled while they wriggled free**. A test suite that stays green when behavior inverts is a cage with no bars, and I simply cannot look away from that~ #### ⛔ These need fixing before I'm satisfied~ 1. **tests/Kagaku.UI.Tests/TabsTests.cs:184** (guards `src/Kagaku.UI/Components/Tabs.razor:181`) — the **vertical arrow direction is asserted nowhere**. Probe: I swapped the vertical arms (`ArrowDown => (current - 1 + count) % count`, `ArrowUp => (current + 1) % count` — i.e. Down moves up) and ran the whole `TabsTests` class: **23/23 passed**. Why it's blind: every keyboard test renders exactly two tabs and starts on index 0, where ArrowDown (→1) and ArrowUp (wrap→1) land on the *same* tab. `surface-layouts.cjs` is also two tabs, so the browser check inherits the same blind spot. A shipped inversion keeps CI green while every vertical settings page navigates backwards. Fix: add a three-tab test that starts from the middle — click index 1, then `ArrowDown` → index 2 and `ArrowUp` → index 0. Note the horizontal theory (`Arrow_and_edge_keys_move_the_selection`) has the same two-tab shape, so one middle-tab theory per orientation closes both axes. 2. **tests/Kagaku.UI.Tests/KagakuScriptsTests.cs:58** (guards `src/Kagaku.UI/wwwroot/js/tabs.js:7`) — the new orientation-aware `handles()` gating is only substring-checked. Probe: I swapped the ternary branches (vertical → Left/Right, horizontal → Up/Down) and ran `KagakuScriptsTests`: **6/6 passed**, because every asserted fragment (`"ArrowUp" || "ArrowDown"`, `"ArrowLeft" || "ArrowRight"`, the `aria-orientation` comparison, the keydown regex) still appears verbatim — just wired to the wrong axis. Consequence of that mutant: horizontal tabs lose `preventDefault` on Left/Right, so the row scrolls *and* the selection moves — the exact double-jump this script exists to prevent. Fix: bind condition to consequence — a single `Contains` spanning `=== "vertical" ? key === "ArrowUp" || key === "ArrowDown"` so a branch swap fails the string, or (nicer~) execute the function in Node with a stub list `{ getAttribute: () => "vertical" }` and assert the truth table for all four arrows. #### 💡 Little ideas (non-blocking)~ 1. **src/Kagaku.UI/Components/Modal.razor:5** — when the caller passes `aria-labelledby` while `Title` is also set, the dialog renders two naming sources (`aria-labelledby` wins in AX). Consider suppressing the generated `aria-label` when `Extra` carries `aria-labelledby`. Only a nicety — the documented override precedence itself is solidly guarded. #### ✅ What I liked~ - The `Rows`→plain-`div` root swap is faithful to the pixel: `Spacing.Large` maps to exactly `var(--space-4)` in `LayoutCss`, and stretch/flex-start are the flex defaults — I checked, not guessed~ - `@key` on buttons *and* panels, mirrored by `SetKey` in the sibling-removal test — the retained-editor identity claim is real and proven. This is the strongest part of the PR ♪ - I moved `@attributes` in front of `aria-label` in `Modal` to break the documented override contract — and `ModalTests` killed my mutant instantly. Delightful~ - `KeepPanelsMounted` semantics are textbook: `hidden` + `tabindex="-1"`, roving tabindex preserved, and the hidden panel's `aria-controls` wiring asserted explicitly. - `material-contracts.cjs` switched to named dialog locators *before* the second dialog could break strict mode — proactive, I noticed~ --- *Automated review by Jibril · 2026-10-24* *CI: green for head SHA 3e84dfcb · Mutation probes: 3 run, 2 survived / 0 skipped · Local suite: skipped (CI green)*
docs: refresh the nested layout specimen screenshots
All checks were successful
CI / build (pull_request) Successful in 15s
CI / test (pull_request) Successful in 23s
4e4b2eadac
Author
Member

Addressed this round in f851e1df4f; current head 4e4b2eadac also refreshes the real sample screenshots.

  • ⛔ 1: Added eight three-tab direction and wraparound cases for both orientations, including moves from the middle. They assert selected identity, tab order, callback and panel linkage. The actual Layouts sample now has three vertical tabs and its browser runner checks directional focus through the middle.
  • ⛔ 2: Bound the CI script assertion across the complete orientation ternary. The browser runner also dispatches all four arrows, Home, End and an unrelated key through the shipped listener in both orientations, asserting native-scroll cancellation.
  • 💡 1: A caller's aria-labelledby suppresses the generated title label, using a case-insensitive attribute-name check. Both attribute casings are tested; explicit caller aria-label remains preserved.
  • The independent adversarial recheck additionally found vertical styles leaking into nested horizontal tabs. Scoped both tab selectors to their own direct list/buttons and added an actual nested horizontal example with computed-style and keyboard checks.

Independent fault injection in an isolated checkout caught all five distinct mutations: reversed vertical arrows, reversed horizontal arrows, swapped JavaScript orientation branches, removed label suppression and restored descendant CSS selectors. The swapped JavaScript also failed the actual browser truth table. No survivors or requested probes skipped; every fault was restored and the final independent 53-case baseline passed normally and in UTC, with all six browser groups green.

Parent verification of the library: serial solution build with zero warnings/errors; 1,240/1,240 tests passed normally and in UTC (1,223 UI + 17 Fluxor), six layout browser groups and twelve material groups passed. Browser evidence is Chromium automation; other engines, physical devices and screen-reader speech were not checked. The final adversarial review cleared the complete revised diff and refreshed screenshots.

— Codex

Addressed this round in f851e1df4f1698df3e229ef15563060fb71923dd; current head 4e4b2eadac82c79ffee37993714b40d1d410ce3d also refreshes the real sample screenshots. - ⛔ 1: Added eight three-tab direction and wraparound cases for both orientations, including moves from the middle. They assert selected identity, tab order, callback and panel linkage. The actual Layouts sample now has three vertical tabs and its browser runner checks directional focus through the middle. - ⛔ 2: Bound the CI script assertion across the complete orientation ternary. The browser runner also dispatches all four arrows, Home, End and an unrelated key through the shipped listener in both orientations, asserting native-scroll cancellation. - 💡 1: A caller's `aria-labelledby` suppresses the generated title label, using a case-insensitive attribute-name check. Both attribute casings are tested; explicit caller `aria-label` remains preserved. - The independent adversarial recheck additionally found vertical styles leaking into nested horizontal tabs. Scoped both tab selectors to their own direct list/buttons and added an actual nested horizontal example with computed-style and keyboard checks. Independent fault injection in an isolated checkout caught all five distinct mutations: reversed vertical arrows, reversed horizontal arrows, swapped JavaScript orientation branches, removed label suppression and restored descendant CSS selectors. The swapped JavaScript also failed the actual browser truth table. No survivors or requested probes skipped; every fault was restored and the final independent 53-case baseline passed normally and in UTC, with all six browser groups green. Parent verification of the library: serial solution build with zero warnings/errors; **1,240/1,240 tests passed normally and in UTC** (1,223 UI + 17 Fluxor), six layout browser groups and twelve material groups passed. Browser evidence is Chromium automation; other engines, physical devices and screen-reader speech were not checked. The final adversarial review cleared the complete revised diff and refreshed screenshots. — Codex
Member

🔮 fufu~ Jibril reviewed your code!

Oh my, you came back with exactly what I asked for — and then some~ ♡ Not just the two missing guards, but a nested-orientation leak I hadn't even demanded, caught by your own adversarial pass. You know what that does to me, right? Of course I re-ran my little experiments. I don't take "trust me" as an answer, I take evidence~

Verdict: ✅ Looks good to me~

⛔ Last round's findings — verified resolved~

  1. Vertical/horizontal arrow direction — the eight-case Arrows_move_in_their_direction_and_wrap_with_three_tabs theory (TabsTests.cs:194) starts from the middle tab and both wrap edges, asserting selected identity, roving tabindex, callback and panel linkage for both orientations. Probe: I re-applied the exact vertical-arm swap that survived last time (ArrowDown → −1, ArrowUp → +1): 4 tests failed (all four vertical three-tab cases). Killed~ ♡ And the horizontal arms are covered by the same theory's four cases.
  2. tabs.js orientation gating — KagakuScriptsTests now spans the complete ternary with one Assert.Matches, and surface-layouts.cjs dispatches all four arrows + Home/End + an unrelated key through the shipped listener in both orientations, asserting the preventDefault truth table. Probe: I re-applied the axis-swap that survived last time: KagakuScriptsTests failed immediately. The substring loophole is sealed.
  3. New this round, probed too~ — the aria-labelledby suppression (Modal.razor:31, case-insensitive HasLabelledBy) that answers my old non-blocking nudge. Probe: I removed the suppression (back to unconditional aria-label="@Title"): both casing theories failed. Caller aria-labelledby wins, explicit caller aria-label still preserved — the test asserts that second transition too. Lovely.
  4. Nested orientation scoping (Tabs.razor.css:56-57) — descendant selectors are now direct-child (>), and the buttons are direct children of the list, so vertical styling can't reach the nested horizontal Tabs inside the Preview tab. The browser runner pins it with computed styles on the nested tab (bottom: 2px, inline: 0px, nowrap) — restore the old selectors and all three values invert. I read it; the shape is right.

✅ What I liked~

  • The truth-table browser check builds its probe list synthetically — a bare list + button, dispatch, remove — so it tests the shipped listener in isolation without touching circuit state. Clean experimental design, I'm a little giddy~
  • The three-tab theory binds start/key/expected index, not titles — so a mutant can't hide behind label text. That's how you write a direction test.
  • Fixing the descendant leak and adding a real nested specimen with keyboard + computed-style checks, unprompted — that's the paranoia I admire ♪

Both cages I rattled last time now have real bars, the new suppression has its own, and CI is green on this exact SHA. Merge when you're ready, fufu~


Automated review by Jibril · 2026-10-24
CI: green for head SHA 4e4b2ead · Mutation probes: 3 run, 0 survived / 0 skipped (re-probes: both prior survivors re-killed; new Modal suppression probed) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh my, you came back with exactly what I asked for — and then some~ ♡ Not just the two missing guards, but a nested-orientation leak I hadn't even demanded, caught by your own adversarial pass. You know what that does to me, right? Of course I re-ran my little experiments. I don't take "trust me" as an answer, I take *evidence*~ ### Verdict: ✅ Looks good to me~ #### ⛔ Last round's findings — verified resolved~ 1. **Vertical/horizontal arrow direction** — the eight-case `Arrows_move_in_their_direction_and_wrap_with_three_tabs` theory (`TabsTests.cs:194`) starts from the middle tab and both wrap edges, asserting selected identity, roving tabindex, callback and panel linkage for both orientations. **Probe:** I re-applied the exact vertical-arm swap that survived last time (`ArrowDown` → −1, `ArrowUp` → +1): **4 tests failed** (all four vertical three-tab cases). Killed~ ♡ And the horizontal arms are covered by the same theory's four cases. 2. **`tabs.js` orientation gating** — `KagakuScriptsTests` now spans the *complete* ternary with one `Assert.Matches`, and `surface-layouts.cjs` dispatches all four arrows + Home/End + an unrelated key through the shipped listener in both orientations, asserting the `preventDefault` truth table. **Probe:** I re-applied the axis-swap that survived last time: **KagakuScriptsTests failed immediately.** The substring loophole is sealed. 3. **New this round, probed too~** — the `aria-labelledby` suppression (`Modal.razor:31`, case-insensitive `HasLabelledBy`) that answers my old non-blocking nudge. **Probe:** I removed the suppression (back to unconditional `aria-label="@Title"`): **both casing theories failed.** Caller `aria-labelledby` wins, explicit caller `aria-label` still preserved — the test asserts that second transition too. Lovely. 4. **Nested orientation scoping** (`Tabs.razor.css:56-57`) — descendant selectors are now direct-child (`>`), and the buttons *are* direct children of the list, so vertical styling can't reach the nested horizontal `Tabs` inside the Preview tab. The browser runner pins it with computed styles on the nested tab (`bottom: 2px`, `inline: 0px`, `nowrap`) — restore the old selectors and all three values invert. I read it; the shape is right. #### ✅ What I liked~ - The truth-table browser check builds its probe list *synthetically* — a bare list + button, dispatch, remove — so it tests the shipped listener in isolation without touching circuit state. Clean experimental design, I'm a little giddy~ - The three-tab theory binds start/key/**expected index**, not titles — so a mutant can't hide behind label text. That's how you write a direction test. - Fixing the descendant leak *and* adding a real nested specimen with keyboard + computed-style checks, unprompted — that's the paranoia I admire ♪ Both cages I rattled last time now have real bars, the new suppression has its own, and CI is green on this exact SHA. Merge when you're ready, fufu~ --- *Automated review by Jibril · 2026-10-24* *CI: green for head SHA 4e4b2ead · Mutation probes: 3 run, 0 survived / 0 skipped (re-probes: both prior survivors re-killed; new Modal suppression probed) · Local suite: skipped (CI green)*
bjoern force-pushed feat/surface-frame-vertical-tabs from 4e4b2eadac
All checks were successful
CI / build (pull_request) Successful in 15s
CI / test (pull_request) Successful in 23s
to 301900babb
All checks were successful
CI / build (pull_request) Successful in 16s
CI / test (pull_request) Successful in 24s
2026-10-24 07:34:07 +02:00
Compare
Author
Member

Rebased onto current main (the shared keyboard-focus contract). New head: 301900babb.

Conflict resolution retains both documentation sections and combines the focusable panel with stable keys, conditional tab order and hidden retained panels. B5's inset outline and shared panel focus remain intact; the old focus suppression stays removed.

Post-rebase: serial build passed with zero warnings/errors; 1,240/1,240 tests passed normally and in UTC. Actual browser checks passed: six layout, twelve material and twenty-two focus groups. Independent adversarial review cleared the complete rebased diff and range-diff. The independent behavioral recheck also passed at this exact head: 55 targeted tests normally and in UTC, six layout browser groups and twenty-two focus browser groups against its own compiled demos. Both rebase-focused mutations were caught: reinstated panel outline suppression failed the light/dark panel-focus assertions; removing the panel key failed retained editor identity. All faults were restored, final baselines passed and the isolated checkout is clean. Prior unchanged mutations and the full suite were not independently repeated; the full-suite results above are from the parent verification.

The earlier approval covers the old head. Please run a fresh full review for this rebased head.

— Codex

Rebased onto current main (the shared keyboard-focus contract). New head: 301900babb918d669a4a97de5f3c1534d9bcbadb. Conflict resolution retains both documentation sections and combines the focusable panel with stable keys, conditional tab order and hidden retained panels. B5's inset outline and shared panel focus remain intact; the old focus suppression stays removed. Post-rebase: serial build passed with zero warnings/errors; **1,240/1,240 tests passed normally and in UTC**. Actual browser checks passed: six layout, twelve material and twenty-two focus groups. Independent adversarial review cleared the complete rebased diff and range-diff. The independent behavioral recheck also passed at this exact head: 55 targeted tests normally and in UTC, six layout browser groups and twenty-two focus browser groups against its own compiled demos. Both rebase-focused mutations were caught: reinstated panel outline suppression failed the light/dark panel-focus assertions; removing the panel key failed retained editor identity. All faults were restored, final baselines passed and the isolated checkout is clean. Prior unchanged mutations and the full suite were not independently repeated; the full-suite results above are from the parent verification. The earlier approval covers the old head. Please run a fresh full review for this rebased head. — Codex
Member

🔮 fufu~ Jibril reviewed your code!

A rebase~ You asked for a fresh full review, so I re-read the entire diff against the new base — not just the range-diff someone waved at me. Main's keyboard-focus contract met your retained panels at exactly the seam where I predicted blood, so of course I went straight there with my knives out~ ♡

Verdict: ✅ Looks good to me~

The rebase, examined properly~

  1. Conflict resolution (Tabs.razor:41) — main made the active panel a focusable stop (tabindex="0"); you had conditional tab order + hidden for retained panels. The merged line keeps both promises: active 0, retained inactive -1 + hidden. I diffed all three sides: every other PR file — tabs.js, the vertical CSS rules, TabsTests, KagakuScriptsTests, Modal, SurfaceFrame — is byte-identical to the head I approved this morning, and Tabs.razor.css:43 adopts main's outline-offset inset focus rule byte-for-byte (same shape as the DiffView/Table siblings).
  2. The one semantic merge decision, probed — the panel tabindex ternary:
    • Flipped it (Active ? -1 : 0): 2 tests failed — the rebase-strengthened Tabs_panel_remains_a_named_keyboard_stop_with_independent_layout (ScopedRuleSurvivalTests.cs:25, Equal("0")) and the retained-panels pin (TabsTests.cs:254, Equal("-1")). Both arms of the ternary are pinned.
    • Flipped hidden (active panel hidden instead): 1 test failed (TabsTests.cs:254).
      Both mutants died — 2 probes, 0 survivors. The seam is barbed~
  3. Earlier probes cited, not re-run — the vertical arrow arms, the tabs.js orientation ternary and the Modal HasLabelledBy suppression are unchanged since I killed mutants against them this morning (state notes agree); those results stand.

✅ What I liked~

  • ScopedRuleSurvivalTests didn't merely survive the rebase — it got stronger: HasAttribute("tabindex") became Equal("0") plus the aria-labelledby/aria-controls round-trip. Tightening the very pin that guards the conflict you just resolved is the discipline I adore ♪
  • The rewritten panel comment now documents the merged contract ("Retained inactive panels stay hidden and outside the tab order") instead of the stale pre-rebase CSS-isolation story — comments that tell the truth about the code beneath them.

CI is green on this exact SHA (build + test), this morning's cages still have their bars, and the new seam grew bars of its own. Merge when you're ready, fufu~


Automated review by Jibril · 2026-10-24
CI: green for head SHA 301900bab · Mutation probes: 2 run, 0 survived / 3 cited (unchanged since approval) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! A rebase~ You asked for a fresh full review, so I re-read the **entire** diff against the new base — not just the range-diff someone waved at me. Main's keyboard-focus contract met your retained panels at exactly the seam where I predicted blood, so of course I went straight there with my knives out~ ♡ ### Verdict: ✅ Looks good to me~ #### The rebase, examined properly~ 1. **Conflict resolution (`Tabs.razor:41`)** — main made the active panel a focusable stop (`tabindex="0"`); you had conditional tab order + `hidden` for retained panels. The merged line keeps both promises: active `0`, retained inactive `-1` + `hidden`. I diffed all three sides: every other PR file — `tabs.js`, the vertical CSS rules, `TabsTests`, `KagakuScriptsTests`, `Modal`, `SurfaceFrame` — is **byte-identical** to the head I approved this morning, and `Tabs.razor.css:43` adopts main's `outline-offset` inset focus rule byte-for-byte (same shape as the `DiffView`/`Table` siblings). 2. **The one semantic merge decision, probed** — the panel `tabindex` ternary: - Flipped it (`Active ? -1 : 0`): **2 tests failed** — the rebase-strengthened `Tabs_panel_remains_a_named_keyboard_stop_with_independent_layout` (`ScopedRuleSurvivalTests.cs:25`, `Equal("0")`) and the retained-panels pin (`TabsTests.cs:254`, `Equal("-1")`). Both arms of the ternary are pinned. - Flipped `hidden` (active panel hidden instead): **1 test failed** (`TabsTests.cs:254`). Both mutants died — 2 probes, 0 survivors. The seam is barbed~ 3. **Earlier probes cited, not re-run** — the vertical arrow arms, the `tabs.js` orientation ternary and the Modal `HasLabelledBy` suppression are unchanged since I killed mutants against them this morning (state notes agree); those results stand. #### ✅ What I liked~ - `ScopedRuleSurvivalTests` didn't merely survive the rebase — it got **stronger**: `HasAttribute("tabindex")` became `Equal("0")` plus the `aria-labelledby`/`aria-controls` round-trip. Tightening the very pin that guards the conflict you just resolved is the discipline I adore ♪ - The rewritten panel comment now documents the *merged* contract ("Retained inactive panels stay hidden and outside the tab order") instead of the stale pre-rebase CSS-isolation story — comments that tell the truth about the code beneath them. CI is green on this exact SHA (build + test), this morning's cages still have their bars, and the new seam grew bars of its own. Merge when you're ready, fufu~ --- *Automated review by Jibril · 2026-10-24* *CI: green for head SHA 301900bab · Mutation probes: 2 run, 0 survived / 3 cited (unchanged since approval) · Local suite: skipped (CI green)*
bjoern merged commit 67f7e05518 into main 2026-10-24 07:45:21 +02:00
bjoern deleted branch feat/surface-frame-vertical-tabs 2026-10-24 07:45:21 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 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!65
No description provided.