fix(images): stop the lightbox resizing itself to the request width

Two defects from giving the lightbox a `width`, both reported as the layout moving
after opening an image from the feed.

EdgeImage turns a `width` prop into an inline `maxWidth`, which beats the
`max-w-full` class the lightbox relies on, so the image was pinned to the REQUEST
width and the surrounding layout jumped once it loaded. The width is a CDN parameter,
not a layout instruction; the inline value is now overridden.

And the width itself could be a guess. `imageWidth` carries a `?? 1200` fallback for
the aspect-ratio box, and plenty of rows have a null `Image.width` — the image this
was found on reports WIDTH 832 in its generation data while the column is null. Using
the fallback asked the CDN for 1200 and got a real 1200x1754 upscale of an 832x1216
file. With no known width we now fall through to `original`, which is what this
surface served before and cannot upscale.

Verified by driving the feed and opening a card: frames at 500ms and 4s are identical,
and a slide whose row does carry a width reports maxWidth 100% rather than 800px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
manuelurenah
2026-09-15 17:02:17 -04:00
parent caad1faf98
commit bfc2f1128d
@@ -265,7 +265,14 @@ function ImageContent({
const isVideo = image?.type === 'video';
// The SOURCE's own width, snapped DOWN and capped. Not the fit box above: `width` there is the
// on-screen box, and asking the cacher for more pixels than the original has only upscales.
const lightboxWidth = snapWidthDownToCommonSize(Math.min(imageWidth, MAX_EDGE_WIDTH));
//
// 🔴 `image.width` and NOT `imageWidth`, whose `?? 1200` fallback would be a GUESS. Plenty of
// rows have a null width, and guessing 1200 for an 832px file requests an upscale — measured,
// that returns a real 1200x1754 against an 832x1216 source. With no width we fall through to
// `original`, which is what this surface served before and cannot upscale.
const lightboxWidth = image?.width
? snapWidthDownToCommonSize(Math.min(image.width, MAX_EDGE_WIDTH))
: undefined;
// dragstart carries no pointerType, and android chrome fires it for a long-press
// drag — leaving that one to embla keeps the touch swipe as `watchTouchDrag` has it
@@ -311,6 +318,11 @@ function ImageContent({
type={image.type}
imageId={image.id}
className={`max-h-full w-auto max-w-full ${!safe ? 'invisible' : ''}`}
// 🔴 EdgeImage turns a `width` prop into an inline `maxWidth`, which beats the
// `max-w-full` class above and would pin the lightbox to the REQUEST width instead
// of letting it fill the viewport. The width here is a CDN parameter, not a layout
// instruction, so the inline value has to be overridden.
style={{ maxWidth: '100%' }}
wrapperProps={{
className: `flex items-center justify-center max-h-full w-auto max-w-full ${
!safe ? 'invisible' : ''