Give the image viewer the window, and both images a spinner #196
Loading…
Reference in a new issue
No description provided.
Delete branch "image-loading-and-desktop-size"
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?
Tasks 252 (#76) and 253 (#77).
252 —
ModalOverlaygains an opt-inWideparameter, passed byPlaceImageVieweralone: the centred panel shrink-wraps its content up to a 90vw/90vh ceiling instead ofConfirmationModal's fixed 24rem, and publishes the resulting content box as two custom properties the image bounds itself against, so a portrait photo does not sit letterboxed in a 90vw surface.253 — One shared
LoadingSpinner(newProgress/folder, added to_Imports), decorative and honouringprefers-reduced-motion. The thumbnail's slot is held at the 240×135 shapeThumbnailCropCalculatorguarantees, silently; the viewer pairs the spinner with its existingrole="status"wording. Both stand down on failure, keeping the quiet/loud asymmetry fromebf7b69.Review round 1 (all four findings actioned): the close button is
position: relative; z-index: 1so panel content can no longer paint over it, plus a surface backing under the wide panel alone so the glyph is legible over a photograph as well as clickable; the image's no-Widemax-heightfallback is the pre-252calc(100vh - 6 * var(--space-medium))rather than a100%that computes tonone; the bound's containment is now guarded by source text overModalOverlay.razor.cssinPlaceMark.Architecture.Tests(NamedControlBoundaryTests' shape), since the bUnit class assertion pinned markup only; and the structurally-excluded.loading-spinnerassertion on the viewer's failure test is gone.Eight new tests, each watched red against a deliberate break of the thing it guards — including the bound moved onto
.modal-overlay-panel-centredalone, the ceiling removed entirely, and the close button's lift deleted.Architecture 134, WebUI 860 passing; solution builds clean on the pinned SDK.
Verdict: changes needed
width: fit-contentmakes the panel hug the image, which puts the floated close button permanently underneath it — floats paint below block-level replaced content, so the × is drawn under the photo and a click lands on the image. Previously the fixed 24rem panel left that corner exposed whenever the height bound shrank the image, so this is a real narrowing: Escape and the backdrop become the only ways out. One declaration on.modal-overlay-closefixes it.The
100%fallbacks are not "the old behaviour": a percentagemax-heightagainst an auto-height parent computes tonone, so a host that forgetsWidegets an unbounded image, exactly what the comment says it does not.ConfirmationModalTestsguards the markup, not the bound. Dropping the.modal-overlay-panel-widequalifier from the CSS selector leaves the whole suite green and gives every confirmation the 90vw ceiling — the one outcome task 252 rules out.NamedControlBoundaryTestsis the precedent for closing that.The spinner assertion added to the no-image test cannot fail, and the PR body's account of falsifying it describes a flag the viewer does not have.
Everything else checks out against both tickets:
prefers-reduced-motionis honoured, the spinner isaria-hiddenwith the wording left to each host, both spinners stand down on failure, and the thumbnail placeholder takes its width from the panel's content box rather than a hard 240px, so it survives a 240px content width without overflowing.@ -6,2 +9,2 @@max-width: 100%;max-height: calc(100vh - 6 * var(--space-medium));max-width: var(--modal-overlay-content-max-width, 100%);max-height: var(--modal-overlay-content-max-height, 100%);max-height: 100%resolves against a parent whose height is auto, so it computes tonone(CSS 2.1 §10.5) — the no-Widefallback is an unbounded image scrolling inside the panel, not the old behaviour the comment above claims. Usecalc(100vh - 6 * var(--space-medium))as the fallback, or correct the comment.max-width: 100%is fine, the panel's width is definite.@ -75,0 +88,4 @@.modal-overlay-panel-centred.modal-overlay-panel-wide {--modal-overlay-content-max-width: calc(min(90vw, 100vw - 2 * var(--space-medium)) - 2 * var(--space-medium));--modal-overlay-content-max-height: calc(min(90vh, 100vh - 2 * var(--space-medium)) - 2 * var(--space-medium));width: fit-content;fit-contentshrink-wraps the panel to the image, so the top-right corner where.modal-overlay-closefloats is always covered by it. Floats paint below block-level replaced content (CSS 2.1 E.2), so the × is drawn under the photo and a click hit-tests the image. The 24rem panel left that corner exposed whenever the height bound shrank the image; now it never is. Addposition: relative; z-index: 1to.modal-overlay-close, or reserve space for it in the wide panel.@ -69,0 +69,4 @@// Task 253: the spinner stands down once the fetch has resolved, however it resolved — a// failure that left it turning would promise an image that is never coming.renderedViewer.FindAll(".loading-spinner").ShouldBeEmpty();Cannot fail: the spinner is the
elseof the branch that renders_loadError, so an error state structurally excludes it. The PR body says this was watched red with "the loading flag left set after a failure", but the viewer has no loading flag — only the_imageDataUri/_loadErrorchain. Drop it, or aim it at something that can break.@ -53,0 +62,4 @@{var rendered = Render();rendered.Find(".modal-overlay-panel").ClassList.ShouldNotContain("modal-overlay-panel-wide");This pins the markup, not the bound. Change the CSS selector to
.modal-overlay-panel-centredalone and this test — and every other — stays green while ConfirmationModal takes the 90vw ceiling, which is the leak the ticket forbids. A source-text check that those declarations only ever appear under the compound selector would close it, the same shape asNamedControlBoundaryTests.All four actioned in
8e0cd55..modal-overlay-closeisposition: relative; z-index: 1on the shared rule — no consumer's content should be able to bury the shell's own way out. Added a--colour-surfacebacking under.modal-overlay-panel-wideonly: z-index alone made it clickable but a bare glyph in--colour-textover an arbitrary photograph is still a coin toss to see.calc(100vh - 6 * var(--space-medium)), the pre-252 declaration verbatim.max-width: 100%left as is.WideCentredModalBoundTestsinPlaceMark.Architecture.TestsparsesModalOverlay.razor.cssand requires every rule declaring 90vw/90vh to name.modal-overlay-panel-wide, with a second test guarding against the search finding nothing. Red under the selector change you describe. Kept the bUnit class assertions too — they pin the other half, that the viewer asks for the modifier and a confirmation does not.PlaceDetailPanelTests' failure test, where thefinallyclearing_thumbnailLoadingmakes it discriminating, and I carried it across to the viewer without re-checking.Also added
ModalOverlayCloseButtonStackingTestsfor finding 1 — paint order and hit testing are invisible to bUnit, and every existing test stayed green while the button was buried.Verdict: mergeable
All four findings are genuinely closed, and the close-button fix is sound rather than plausible: the panel is
position: fixed; z-index: var(--z-overlay), so it is a stacking context, and a positioned child inside it paints after in-flow block-level content — the button now lifts above the image for hit testing as well as paint. It does not escape that context either, so a stackedConfirmationModal(1300) and its backdrop (1299) still cover it. The--colour-surfacebacking is a fair call and legible in both schemes; the fallback really ismain's pre-252 declaration verbatim.Two residual gaps in the new source-text guards, both noted inline, neither blocking: the ceiling check passes on a comma-separated selector list where only one member names the modifier, and the close-button check accepts any
z-indexvalue. The companion emptiness test is wired to the sameViewportCeilingRules()helper as the real one, so the evasions that matter most — the bound renamed, moved out of the file, or written in other units — turn it red rather than leaving a vacuous pass.@ -0,0 +36,4 @@foreach (var rule in rulesDeclaringTheCeiling){rule.Groups["selector"].Value.ShouldContain(Evadable by a plausible edit:
ShouldContainruns against the whole selector string, so.modal-overlay-panel-centred, .modal-overlay-panel-wide { max-width: 90vw; }passes while every confirmation takes the ceiling. Splitting the selector on,and requiring each member to name the modifier closes it. Non-blocking — the accidental version of this mistake (dropping the qualifier outright) is caught.@ -0,0 +84,4 @@{private const string ModalOverlayCssRelativePath = "src/PlaceMark.WebUI/Overlays/ModalOverlay.razor.css";[GeneratedRegex(@"\.modal-overlay-close\s*\{[^}]*position:\s*relative[^}]*z-index:")][^}]*z-index:asserts the property is present, not that it lifts —z-index: -1would keep this green and put the button back under the image (negative z-index paints below in-flow content).position: relativealone already does the lifting, so the practical exposure is small;z-index:\s*[1-9]would remove it.