fix(email-viewer, markdown): handle unreadable emails without manipulating their content#4066
fix(email-viewer, markdown): handle unreadable emails without manipulating their content#4066
Conversation
…wlist Inline color values from email content can collide with the surrounding theme — black text on a dark surface, or white text on a light one — making feed snippets unreadable. <limel-markdown> is a generic renderer used throughout the app and shouldn't carry email-specific fidelity concerns. Sender emphasis colors remain available when the user opens the original email through <limel-email-viewer>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… theme The email body now picks its background based on the app theme by default, using contrast tokens that auto-invert with light/dark mode. A small "Hard to read?" toggle, with a tooltip explaining the why, sits sticky at the top of the body and flips the background to the opposite tone for emails whose hardcoded text colors collide with the default surface. The email content is never modified — only the surface behind it changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds a light/dark surface-background toggle to the email viewer for readability when sender-defined text colors clash with the app theme, while removing inline color preservation from markdown sanitization to keep feed snippets readable in all themes. ChangesEmail Readability Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-4066/ |
The email viewer now renders a <limel-icon> inside its surface toggle. That triggers an icon-cache fetch for the SVG, which calls `response.text()`. The existing test mock unconditionally returned a response object that only implemented `arrayBuffer()`, so the icon fetch threw an unhandled "response.text is not a function" error and failed the test run. The mock now routes the email URL to its EML payload and returns a benign empty response (with both `text()` and `arrayBuffer()`) for any other fetch the email viewer's render tree may make. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/markdown/sanitize-style.spec.ts (1)
156-192: 🧹 Nitpick | 🔵 Trivial | 💤 Low value
normalizeBackgroundColorbackground-color conversion branch is a pipeline no-op — consider simplifying the implementation.
normalizeBackgroundColorconvertsbackground: <valid-color>→background-color: <color>, but sincebackground-coloris not in theallowedCssPropertieslist, the converted value is immediately filtered out downstream. The function provides security value by always removing thebackgroundproperty (preventing image injection), but thebackground-coloraddition for valid colors has no observable effect on the rendered output.The tests correctly verify the function contract in isolation. Consider simplifying
sanitize-style.tsto unconditionally stripbackgroundrather than conditionally re-addingbackground-color, which would avoid confusion for future maintainers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/markdown/sanitize-style.spec.ts` around lines 156 - 192, normalizeBackgroundColor currently converts valid background values to background-color then relies on downstream filtering, which is a no-op; simplify by making normalizeBackgroundColor always remove the background property (prevent image injection) and never add a background-color key. Update the function (normalizeBackgroundColor) to return a shallow copy of the input without the background property, preserve other properties unchanged, and maintain immutability (do not mutate the original object); leave tests as-is or adjust expectations if they assert background-color presence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/email-viewer/email-viewer.scss`:
- Around line 203-212: The hardcoded color-scheme values in the email viewer
rules mismatch the auto-inverting contrast tokens; update the rules so
color-scheme follows the actual surface tone by using prefers-color-scheme media
queries and mirroring the toggled state: keep the base selector (the block with
padding/overflow/background-color) with color-scheme: light by default and set
.toggled-surface-background to color-scheme: dark, then add `@media`
(prefers-color-scheme: dark) that flips those values (base -> color-scheme:
dark, .toggled-surface-background -> color-scheme: light) so browser-native UI
and system color keywords resolve correctly in both app themes.
---
Outside diff comments:
In `@src/components/markdown/sanitize-style.spec.ts`:
- Around line 156-192: normalizeBackgroundColor currently converts valid
background values to background-color then relies on downstream filtering, which
is a no-op; simplify by making normalizeBackgroundColor always remove the
background property (prevent image injection) and never add a background-color
key. Update the function (normalizeBackgroundColor) to return a shallow copy of
the input without the background property, preserve other properties unchanged,
and maintain immutability (do not mutate the original object); leave tests as-is
or adjust expectations if they assert background-color presence.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 833b6f94-3aeb-44de-9adc-78d6de7eed28
📒 Files selected for processing (15)
src/components/email-viewer/email-viewer.scsssrc/components/email-viewer/email-viewer.tsxsrc/components/file-viewer/file-viewer.spec.tsxsrc/components/markdown/allowed-css-properties.tssrc/components/markdown/examples/markdown-html.tsxsrc/components/markdown/markdown-parser.spec.tssrc/components/markdown/sanitize-style.spec.tssrc/translations/da.tssrc/translations/de.tssrc/translations/en.tssrc/translations/fi.tssrc/translations/fr.tssrc/translations/nl.tssrc/translations/no.tssrc/translations/sv.ts
💤 Files with no reviewable changes (1)
- src/components/markdown/allowed-css-properties.ts
|
Hey Kia, jumping in on the design direction here. The email-viewer half of this PR (theme-following surface + "Hard to read?" toggle) feels right to me, and I'd accept it independently of where the markdown discussion lands. What I want to question is part 1 — dropping The framing of part 1 as "removing email-shaped responsibility from a generic component" is what I want to question. Inline The other part of the framing I'd question is treating color use as a deliberate, flagged thing — "see my responses in red." That's one shape of color use, but plenty of it isn't deliberate at all. People highlight a name in blue, color a date red, paste an action item in green, use a colored heading from a template — without thinking of it as "color emphasis." Senders don't consciously decide "I'm using color here," they just write that way, and recipients read it that way. That's what #590 was actually about, even if the phrasing made it sound niche — it's not really one feature request, it's a pattern in how people compose emails. When we strip color universally, we lose all of that. For most messages the loss is invisible. For some it's load-bearing in a way that only becomes obvious after it's gone. The "View original email" promoted action is a fine fallback when someone consciously wants to see the rendered original, but it doesn't help in the case where someone is just glancing through their feed and would naturally read the colored phrase inline if it were there. Separate point — the writeup undersells how slimmed-down the contrast approach got. The cost it lists (DOM walk, contrast calc, What I find compelling about the contrast approach is that it intervenes only in cases where leaving the content alone would already produce something unreadable. For the organic color use we've both seen — colored names, dates, highlights, headings — those mostly meet 3:1 against a normal CRM surface, so they pass through untouched. The walk kicks in only for the genuinely-broken combinations: a sender composing in dark mode whose So I'd reframe the part 1 trade-off: it's not "preserve sender colors at the cost of architectural purity vs. drop them and keep the renderer clean." It's "drop a generic markdown feature from a public component (breaking external consumers as collateral) and lose the organic-color reading that #590 was actually about, vs. keep the public API as-is and add ~50 lines of CRM-specific logic in |
|
Thanks @FredrikWallstrom Regarding dropping or keeping colors in markdown. You're right that we have consumers that might be using it as is. I'm not sure how serious these use cases are, but perhaps that's a bad idea to suddenly remove the functionality. So I'm not insisting on doing so. But if I was gonna redesign this component again, I'd probably decide to exclude colors via sanitizer again 😄 It feels, for a markdown renderer component which is going to appear on Web Application UIs (as a natural part of the UI itself), that's an overkill. Imagine if Slack, Teams, Github, etc… all rendered text with color (with any user-chosen color) everywhere. That would open the Pandora's box, specially with dark mode / light mode support of most app UIs these days (as it has already done in our case). I think it would have been better to keep that door shut from the beginning. But if we drop this, and instead go with the contrast solution that you proposed, then the other commit in the current PR will also become meaningless. We should rather use your solution in both components in that case. That'll give a much better out of the box UX at the end. I'm not sure how much does it cost when it comes to performance. Probably nothing noticable if it is only one instance doing it. But should we consider to add it under a |
fix: #4065
Summary
Two atomic fixes that together solve the readability problem described in #4065 without ever modifying email content:
<limel-markdown>— dropscolorandbackground-colorfrom the inline-style allowlist. The component goes back to being a clean, theme-neutral renderer; feed snippets stay readable in any theme. The "preserve sender emphasis colors in the feed" use case fromlimepkg-email#590will be solved at the product level — a "See the original email" promoted action on the feed item (inlimeb-feed).<limel-email-viewer>— sets a theme-following default background on the email body (using--contrast-300/--contrast-1500tokens that auto-invert with the app theme), and adds a small "Hard to read?" toggle that flips the surface to the opposite tone for emails whose hardcoded colors collide with the default. Alimel-tooltipon the button explains the why. The email content itself is never modified — only the surface behind it changes.See #4065 for the full reasoning, including the approaches we considered and rejected (auto-detection of color-scheme hints, contrast-aware DOM walking from #4060, etc.).
Test plan
.emlfile via<limel-file-viewer>while the app is in light mode. Verify the email body has a light off-white surface.pointer-events: noneon the wrapper).Browsers tested
Windows:
macOS:
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Style
Localization