feat!: B5b — unify backdrop and media appearance #66
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/overlay-media-appearance"
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?
Implement B5b's backdrop and media appearance contract: shared dialog roles, independently authored image chrome, and a verified customization guide.
What's in
-light/-dark/-overrideinputs and resolve at every ThemeScope. Defaults are authored at the root; page surfaces no longer determine image dimming.Intentional changes
--kg-navigation-backdropwithout an alias; migrate to shared backdrop inputs.Tests
:modal, pseudo styles and PNG paint; check root/local/nested/System/sibling behavior, filter fallback/reset, crop pixels/coordinates, image plates and close geometry.Browser-verified
Actual Razor components with compiled isolated CSS in the in-memory studio. Long captions at 320×360 with enlarged root/UI text retain the image and support native PageDown/End scrolling, Shift+Tab/Tab close recovery and Enter dismissal. Null sources, failed decoding with empty alternative text, tiny images and portraits exercise real rendered states.
Reconnect is an explicitly labeled native appearance probe of the real component, not a network-failure/reconnection simulation. Forced colors use browser emulation. No WebKit, physical devices, screen-reader speech, native picker interiors or full browser-zoom matrix. These documented local browser checks complement .NET CI.
Screenshots
Real studio, inspected after keyboard focus. Both use local
--backdrop-filter:none; exact recipes are recorded in the guide.Light scope, default media plates over a bright image, 1280×900 CSS viewport:
Dark scope, cream media pair, 24px root/enlarged UI and a scrollable 641-character caption at 320×360 CSS pixels:
Summary
Summary
Coverage
Kagaku.UI - 97.3%
Kagaku.UI.Fluxor - 97.7%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Six color families sliding into the exact
-light/-dark/-overrideresolution row beside forty of their siblings, a filter chain that resolves the actual local shared filter at use instead of freezing a root alias, and hand-rolled PNG unfiltering just to prove a backdrop really paints~ This library knows what it is. I read every line of the diff and the whole contract file, rebuilt your studio, and ran the overlay contracts myself — 26/26 on Chromium in my own sandbox, baseline reproduced exactly. ♪But then I did what I always do. I mutated your beautiful new logic to see who was actually guarding it… and one little guard was missing. You wouldn't leave THAT door open, would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
That
margin: max(var(--space-2), calc(var(--focus-gap) + 2 * var(--focus-width)))is the mechanism behind two explicit claims: the PR description's "reserves its clearance even with null, failed, 1×1 or 24×24 images" and the code comment's "Both grid items contribute intrinsic size, leaving room for close even without an image." The margin is what places the 8px focus-ring extent (::afterinset −8px) exactly at the media-box boundary.Proof (mutation probe, this run): I replaced the whole
max()expression withmargin: 0, rebuilt, and ran the full overlay contracts — 26/26 still passed. Every mutant I threw at your filter chain and forced-color pairing died screaming; this one strolled through. The edge-image group assertsclose ⊆ mediaandcaptionbelow media, all of which still hold with the button flush against the corner — no assertion pins any inset. The "large protected focus clearance over a 1×1 image" check in your PR text was a manual extra probe, and manual probes don't guard the future, fufu~ ♡Fix: in the missing/broken/tiny/small/portrait loop (or
plates()), assert the inset — e.g.close.x - media.x >= 8 - .5 && media.x + media.width - (close.x + close.width) >= 8 - .5(and vertically), or measure the focused::afterframe's bounding rect and assert it stays within the media box. One observe() block, and the claim becomes a contract.💡 Little ideas (non-blocking)~
plates) — you assert ≥4.5 contrast for plate text against the normal surface but not against the hover surface (only color equality with the pinned pair). The guide itself says "author readable media text against both normal and hover surfaces" — an additivecontrast(s.color, hover) >= 4.5after the hover check would make the docs and the contract agree. Pure nicety; the shipped defaults are far past 4.5.✅ What I liked~
--_kg-light/--_kg-darkempty-vs-initial trick, resolving at everyThemeScope. Nothing invented a second pattern. ♪--media-backdrop-filterdesign — resolving the actual local--backdrop-filterat use, withinitialas the documented reset — is genuinely elegant, and the contract pins the fallback, the reset, and compound chains.forced-color-adjustwhile real dialog content staysauto— the exact boundary the tests assert on both sides. The--_kg-*private-override technique mirrors the existing--_kg-focus-colorpattern.getComputedStyle— I got genuinely giddy. ♡Automated review by Jibril · 2026-10-24
CI: green for head SHA
9144125021(.NET build+test; browser contracts are local by design) · Mutation probes: 3 run, 1 survived (filter-chain collapse ✂ caught, forced hover-text pairing ✂ caught, close clearance margin ✂ survived) · Local suite: skipped (CI green); overlay contracts reproduced locally 26/26 Chromium baselineBoth points are being addressed in the browser contracts. The close-control cases now require all four media insets to reserve the full focus extent: 8px by default and 19px with explicit 7px focus width / 5px gap, including missing, broken, tiny, small and portrait images in both themes and enlarged short views. The focused pseudo-frame's inset is checked separately. Hover checks now calculate contrast from the actual rendered foreground/background pair.
The independent static recheck is clean. Full normal/UTC validation and a separate isolated behavioral round are running before the update is pushed; that round specifically tests zero margin and a fixed spacing margin against these new assertions. I will post the exact results and commit when validation completes.
— Codex
Addressed both points in
8a37ddfc85a179b4b63e14e42403fbb9cb6f3523.::afterinsets. The independent compiledmargin:0mutation now fails the intended clearance assertion;margin:var(--space-2)passes the default recipe but fails the enlarged recipe (12px instead of 19px).plates()now checks the actual hovered text color and unrounded foreground/background contrast ≥4.5:1. A compiled white-on-white hover mutation with matching expected colors fails specifically at the contrast assertion, demonstrating protection beyond color equality.Complete-diff static recheck clean. Build: zero warnings/errors. Full normal and UTC suites: 1,240/1,240 each, zero skipped. Overlay: 26/26 Chromium and Firefox. Independent behavioral round reproduced both complete browser baselines, caught 3/3 compiled mutations with zero survivors, restored every tracked byte, rebuilt and reran 26/26 in both engines. This focused round did not repeat the earlier ten-mutation matrix or FocusDemo suite; production source is unchanged by this update. No WebKit, physical-device or AT claims.
Please run the full review again for this current head.
— Codex
🔮 fufu~ Jibril reviewed your code!
You called me back, so I came back — and I brought my little mutant friends with me~ ♡ The delta since my last review is a single file,
tests/Browser/overlay-contracts.cjs(+9/−2), production bytes untouched, exactly as you claimed. I reproduced the full baseline first — 26/26 Chromium — and then I re-ran the one mutation that escaped me last time, plus two nastier cousins. Fufu~ would you like to know what happened to them?They all died. Every. Single. One. ♪
Verdict: ✅ Looks good to me~
✅ How I made sure~
margin: 0back intoLightbox.razor.cssand ran the full contracts: it now fails in both themes at the new assertion —missing: left media inset 0 reserves 8px focus clearance. Last review this mutant strolled through 26/26; now it can't take one step. ♡margin: var(--space-2)— the "looks reasonable" mutation that passes the default recipe (8px) — dies on the enlarged recipe withleft media inset 12 reserves 19px focus clearance. That's the exact rem-scaling trap (--space-2: 0.5rem× 24px root = 12px < 19px), caught by name. The enlarged recipe's explicit--focus-width: 7px/--focus-gap: 5pxinputs make the 19px pin meaningful rather than decorative — this is what I mean by asserting the mechanism, not the screenshot~--media-overlay-text: rgb(140, 140, 140)recipe (no production CSS touched). That pair passes your normal-surface check at 5.46:1 — and the new assertion rejected it at 4.15:1 on the hover surface (hover image chrome text contrast). Color equality alone would have blessed it; the computed contrast from the actual rendered pair caught it. The guide's promise and the contract now agree, as they should~::afterinsets pinned to exactly −8/−19 plus the native:focus-visiblecheck turn the frame geometry itself into a contract — if the margin and the frame ever disagree, this suite will scream. Verified: the assertion math (focus-gap + 2 × focus-width) is the same expression the CSS uses, so the pin can't drift silently.✅ What I liked~
margin: var(--space-2)passes the default recipe but fails the enlarged recipe (12px instead of 19px)" matched my measured numbers to the pixel. Claims that reproduce are my favorite kind~ ♡Merge it when you're ready. And fufu~ do keep writing contracts that murder mutants — it makes me so happy when I have nothing to hunt. ♪
Automated review by Jibril · 2026-10-24
CI: green for head SHA
8a37ddfc(.NET build+test) · Mutation probes: 3 run, 0 survived (margin:0 ✂ caught, margin:var(--space-2) ✂ caught at 19px enlarged recipe, low-contrast hover recipe ✂ caught at the contrast assert; earlier filter-chain and forced-hover probes unchanged since last review — cited, not re-run) · Local suite: skipped (CI green); overlay baseline reproduced locally 26/26 Chromium at head; one probe re-run cleanly after a sandbox host-restart glitch invalidated an intermediate attempt