From 68341a0b69edc187cd1deab561b7259117137757 Mon Sep 17 00:00:00 2001 From: Shuki Vaknin Date: Mon, 14 Sep 2026 19:31:12 +0300 Subject: [PATCH] fix(viewer): stop stretching images that carry their own max-width The viewer forces `img { max-width: 100% !important }` onto every image. That !important also beats a sender's *inline* max-width, which breaks the common newsletter idiom of sizing an image with max-width + max-height + width:100%: With the sender's max-width:200px overridden, width:100% grows the image to the full pane, while the max-height:134px it left alone still clamps the height - so the picture renders squashed flat. Measured on a real Namecheap renewal notice (hero asset 300x200, natural ratio 1.50) rendered with the viewer's own stylesheet: the hero laid out 540x134, an aspect ratio of 4.03 - stretched 2.7x. Identical at a 390px pane. With this change it lays out 200x133, ratio 1.50. Only force the cap on images that declare no width of their own, exactly as the table rule alongside it already does for the same reason (#790). height keeps its !important, so an image with a fixed inline height (the far more common `style="width:600px;height:400px"`) is unaffected - verified unchanged at 600x400. Images with no inline max-width are still capped at 100%, still with !important, so an email's own stylesheet cannot escape the pane - an email carrying `img { max-width: none }` still measures 710px, not 2000px. An image whose inline max-width exceeds the pane now overflows into the body's existing horizontal scroll rather than being silently distorted: the same trade-off #790 accepted for tables, and fitEmailBodyWidth() still scales it to fit on a phone. Applied to all three paths that build an email document: the reading pane, the thread conversation cards, and the print view. Claude-Session: https://claude.ai/code/session_019KwXZEwxijHRbfobfXmcK2 --- components/email/email-viewer.tsx | 20 +++++++++++++++++-- components/email/thread-conversation-view.tsx | 10 +++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index cd770e915..3d9a7e4cb 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -2298,7 +2298,15 @@ export function EmailViewer({ [style*="height:100%"], [style*="height: 100%"] { height: auto !important; } body { margin: 0; padding: ${bodyPadding}; overflow-x: auto; overflow-y: hidden; height: auto !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; line-height: 1.6; color: #1a1a1a; background: #ffffff; word-wrap: break-word; overflow-wrap: break-word; } @media (max-width: 640px) { body { padding-left: ${mobileBodyPaddingX}; padding-right: ${mobileBodyPaddingX}; } } - img { max-width: 100% !important; height: auto !important; } + /* Only force the cap on images that set no width of their own: an + !important 100% also overrides a sender's inline max-width, and an + image sized by max-width + max-height + width:100% then grows to + the pane width while its max-height still clamps the height - the + picture renders stretched. Same reasoning as the table rule below (#790). + height stays !important so a fixed inline height cannot squash it. */ + img:not([style*="max-width"]) { max-width: 100% !important; } + img[style*="max-width"] { max-width: 100%; } + img { height: auto !important; } a { color: #1a73e8; } /* Only force the cap on tables that set no width of their own: an !important 100% would also override a newsletter's inline @@ -2685,7 +2693,15 @@ export function EmailViewer({ .meta { font-size: 13px; color: #555; line-height: 1.6; } .meta strong { color: #000; } .body { font-size: 14px; line-height: 1.6; } - .body img { max-width: 100% !important; height: auto !important; } + /* Only force the cap on images that set no width of their own: an + !important 100% also overrides a sender's inline max-width, and an + image sized by max-width + max-height + width:100% then grows to + the pane width while its max-height still clamps the height - the + picture renders stretched. Matches the viewer's own image rule. + height stays !important so a fixed inline height cannot squash it. */ + .body img:not([style*="max-width"]) { max-width: 100% !important; } + .body img[style*="max-width"] { max-width: 100%; } + .body img { height: auto !important; } @media print { body { margin: 20px; } }
diff --git a/components/email/thread-conversation-view.tsx b/components/email/thread-conversation-view.tsx index 3374d706b..867d4b055 100644 --- a/components/email/thread-conversation-view.tsx +++ b/components/email/thread-conversation-view.tsx @@ -466,7 +466,15 @@ function EmailCard({ only left for content too wide to stay legible when scaled. */ html { overflow: hidden; } body { overflow-x: auto; overflow-y: hidden; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; line-height: 1.6; color: #1a1a1a; background: #ffffff; word-wrap: break-word; overflow-wrap: break-word; } - img { max-width: 100% !important; height: auto !important; } + /* Only force the cap on images that set no width of their own: an + !important 100% also overrides a sender's inline max-width, and an + image sized by max-width + max-height + width:100% then grows to + the pane width while its max-height still clamps the height - the + picture renders stretched. Same reasoning as the table rule below (#790). + height stays !important so a fixed inline height cannot squash it. */ + img:not([style*="max-width"]) { max-width: 100% !important; } + img[style*="max-width"] { max-width: 100%; } + img { height: auto !important; } a { color: #1a73e8; } /* Only force the cap on tables that set no width of their own: an !important 100% would also override a newsletter's inline