fix(mobile): image/video aspect-ratio so media doesn't crop/letterbox (N2)
CI / Build & Quality Checks (push) Successful in 10m53s
CI / Trigger Desktop Build (push) Successful in 6s

MImage/MVideo pinned AttachmentBox to a fixed height computed for a 400px-wide
layout. The box width is responsive (maxWidth:100%) but the height was frozen,
so on a phone the box narrows below 400px while keeping desktop height ->
images crop (object-fit:cover) and videos letterbox (object-fit:contain).

Drive the box by `aspect-ratio: w/h` when intrinsic dimensions are known, so the
height tracks the responsive width. On desktop the box stays 400px wide, so the
aspect-ratio yields the identical height (algebraically 400*h/w =
scaleYDimension(w,400,h)) — pixel-identical. Falls back to the fixed height when
dimensions are unknown; the 48px floor and 600px cap are preserved.

Uses the same pattern already shipped in this codebase (TwitchThumbnailWrapper,
GalleryTile). Two review passes, one empirically measuring the rendered image in
Chromium: desktop unchanged, narrow widths keep correct aspect, no collapse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 23:28:20 -04:00
co-authored by Claude Opus 4.8
parent 154e35ef9f
commit 09f37f890f
+18 -10
View File
@@ -417,6 +417,22 @@ type RenderImageContentProps = {
markedAsSpoiler?: boolean;
spoilerReason?: string;
};
// Media frame sizing. When intrinsic width/height are known, drive the box by
// aspect-ratio so its height tracks the responsive (maxWidth:100%) width — a
// fixed pixel height computed for a 400px-wide layout otherwise crops (images,
// object-fit:cover) or letterboxes (videos, object-fit:contain) on phones where
// the box narrows below 400px. On desktop the box stays 400px wide, so the
// aspect-ratio yields the identical height. Falls back to the fixed height when
// dimensions are unknown.
const attachmentMediaStyle = (
w: number | undefined,
h: number | undefined,
fallbackHeight: number,
): CSSProperties =>
w && h
? { aspectRatio: `${w} / ${h}`, minHeight: toRem(48) }
: { height: toRem(fallbackHeight < 48 ? 48 : fallbackHeight) };
type MImageProps = {
content: IImageContent;
renderImageContent: (props: RenderImageContentProps) => ReactNode;
@@ -432,11 +448,7 @@ export function MImage({ content, renderImageContent, outlined }: MImageProps) {
return (
<Attachment outlined={outlined}>
<AttachmentBox
style={{
height: toRem(height < 48 ? 48 : height),
}}
>
<AttachmentBox style={attachmentMediaStyle(imgInfo?.w, imgInfo?.h, height)}>
{renderImageContent({
body: content.body || 'Image',
info: imgInfo,
@@ -498,11 +510,7 @@ export function MVideo({ content, renderAsFile, renderVideoContent, outlined }:
}
/>
</AttachmentHeader>
<AttachmentBox
style={{
height: toRem(height < 48 ? 48 : height),
}}
>
<AttachmentBox style={attachmentMediaStyle(videoInfo.w, videoInfo.h, height)}>
{renderVideoContent({
body: content.body || 'Video',
info: videoInfo,