feat: add framed surfaces and vertical settings navigation #65
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/surface-frame-vertical-tabs"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
SurfaceFramecomposes optional header/body/footer slots, caller attributes, body padding and caller-bounded scrolling using shared tokens.Tabsadds 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.Modalforwards 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.These are the reusable primitives needed by Shikigami's immersive Lacquer surfaces under its UI/scaffold decisions.
Validation
Screenshots
Actual MaterialDemo components; these palettes remain sample-owned.
Jade Day, desktop frame:
Amber Night, narrow settings dialog with enlarged root text:
Summary
Summary
Coverage
Kagaku.UI - 97.3%
Kagaku.UI.Fluxor - 97.7%
🔮 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→divswap, the@keyidentity work, theSplatdiscipline… 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~
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 wholeTabsTestsclass: 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.cjsis 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 andArrowUp→ 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.src/Kagaku.UI/wwwroot/js/tabs.js:7) — the new orientation-awarehandles()gating is only substring-checked. Probe: I swapped the ternary branches (vertical → Left/Right, horizontal → Up/Down) and ranKagakuScriptsTests: 6/6 passed, because every asserted fragment ("ArrowUp" || "ArrowDown","ArrowLeft" || "ArrowRight", thearia-orientationcomparison, the keydown regex) still appears verbatim — just wired to the wrong axis. Consequence of that mutant: horizontal tabs losepreventDefaulton 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
Containsspanning=== "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)~
aria-labelledbywhileTitleis also set, the dialog renders two naming sources (aria-labelledbywins in AX). Consider suppressing the generatedaria-labelwhenExtracarriesaria-labelledby. Only a nicety — the documented override precedence itself is solidly guarded.✅ What I liked~
Rows→plain-divroot swap is faithful to the pixel:Spacing.Largemaps to exactlyvar(--space-4)inLayoutCss, and stretch/flex-start are the flex defaults — I checked, not guessed~@keyon buttons and panels, mirrored bySetKeyin the sibling-removal test — the retained-editor identity claim is real and proven. This is the strongest part of the PR ♪@attributesin front ofaria-labelinModalto break the documented override contract — andModalTestskilled my mutant instantly. Delightful~KeepPanelsMountedsemantics are textbook:hidden+tabindex="-1", roving tabindex preserved, and the hidden panel'saria-controlswiring asserted explicitly.material-contracts.cjsswitched 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)Addressed this round in
f851e1df4f; current head4e4b2eadacalso refreshes the real sample screenshots.aria-labelledbysuppresses the generated title label, using a case-insensitive attribute-name check. Both attribute casings are tested; explicit calleraria-labelremains preserved.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
🔮 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~
Arrows_move_in_their_direction_and_wrap_with_three_tabstheory (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.tabs.jsorientation gating —KagakuScriptsTestsnow spans the complete ternary with oneAssert.Matches, andsurface-layouts.cjsdispatches all four arrows + Home/End + an unrelated key through the shipped listener in both orientations, asserting thepreventDefaulttruth table. Probe: I re-applied the axis-swap that survived last time: KagakuScriptsTests failed immediately. The substring loophole is sealed.aria-labelledbysuppression (Modal.razor:31, case-insensitiveHasLabelledBy) that answers my old non-blocking nudge. Probe: I removed the suppression (back to unconditionalaria-label="@Title"): both casing theories failed. Calleraria-labelledbywins, explicit calleraria-labelstill preserved — the test asserts that second transition too. Lovely.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 horizontalTabsinside 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~
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)4e4b2eadac301900babbRebased 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
🔮 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~
Tabs.razor:41) — main made the active panel a focusable stop (tabindex="0"); you had conditional tab order +hiddenfor retained panels. The merged line keeps both promises: active0, 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, andTabs.razor.css:43adopts main'soutline-offsetinset focus rule byte-for-byte (same shape as theDiffView/Tablesiblings).tabindexternary:Active ? -1 : 0): 2 tests failed — the rebase-strengthenedTabs_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.hidden(active panel hidden instead): 1 test failed (TabsTests.cs:254).Both mutants died — 2 probes, 0 survivors. The seam is barbed~
tabs.jsorientation ternary and the ModalHasLabelledBysuppression are unchanged since I killed mutants against them this morning (state notes agree); those results stand.✅ What I liked~
ScopedRuleSurvivalTestsdidn't merely survive the rebase — it got stronger:HasAttribute("tabindex")becameEqual("0")plus thearia-labelledby/aria-controlsround-trip. Tightening the very pin that guards the conflict you just resolved is the discipline I adore ♪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)