From b71f9c95c6fadd2e12feaee425ee814bed797e81 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Thu, 24 Sep 2026 22:33:23 -0400 Subject: [PATCH] fix(emoji): flags render on Windows; "Twitter emoji" works again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows' emoji font has no flag glyphs, so πŸ‡ΊπŸ‡Έ (:flag_us:) showed as the letters "US". Two fixes: - A TwemojiFlags @font-face over the bundled Twemoji file, limited by unicode-range to the regional-indicator letters (U+1F1E6–1F1FF), is put in the font stack on Windows only. Every other emoji stays native, other platforms keep their own flags, and the file is fetched only when a flag is on screen. - The Appearance font setting overwrote --font-secondary with a stack that had no emoji families at all, so neither this nor the existing "Twitter emoji" switch reached message text. The font map now keeps var(--font-flags), var(--font-emoji) before the generic family. Verified in Chromium via CDP platform fonts: with a Windows user agent πŸ‡ΊπŸ‡Έ is drawn by "Twemoji Mozilla" while πŸ˜€ stays on the system emoji font; with a Linux user agent both stay native. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/pages/App.tsx | 12 ++++++++---- src/app/pages/client/ClientNonUIFeatures.tsx | 13 +++++++++++++ src/index.css | 16 +++++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index db1fbca96..7e27a9798 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -37,11 +37,15 @@ import { zIndices } from '../styles/zIndex'; import { OIDC_CALLBACK_PATH } from './paths'; import { OidcCallback } from './auth/oidc/OidcCallback'; +// The emoji families (Twemoji when "Twitter emoji" is on, Twemoji flags on +// Windows β€” see SystemEmojiFeature) must sit before the generic family, or the +// chosen font's stack silently drops them. +const EMOJI_FONTS = 'var(--font-flags), var(--font-emoji)'; const FONT_MAP: Record = { - system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", - inter: "'InterVariable', sans-serif", - 'jetbrains-mono': "'JetBrains Mono', monospace", - 'fira-code': "'Fira Code', monospace", + system: `system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', ${EMOJI_FONTS}, sans-serif`, + inter: `'InterVariable', ${EMOJI_FONTS}, sans-serif`, + 'jetbrains-mono': `'JetBrains Mono', ${EMOJI_FONTS}, monospace`, + 'fira-code': `'Fira Code', ${EMOJI_FONTS}, monospace`, }; function AppearanceEffects() { diff --git a/src/app/pages/client/ClientNonUIFeatures.tsx b/src/app/pages/client/ClientNonUIFeatures.tsx index 59f71c790..a2a7d7ddd 100644 --- a/src/app/pages/client/ClientNonUIFeatures.tsx +++ b/src/app/pages/client/ClientNonUIFeatures.tsx @@ -91,6 +91,13 @@ const LogoHighlightSVG = withOriginBaseUrl(getOriginBaseUrl(), '/public/res/lotu const INVITE_NOTIFY_ARM_DELAY_MS = 3000; +const IS_WINDOWS = + typeof navigator !== 'undefined' && + /Windows/i.test( + (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ?? + navigator.userAgent, + ); + function SystemEmojiFeature() { const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji'); @@ -99,6 +106,12 @@ function SystemEmojiFeature() { } else { document.documentElement.style.setProperty('--font-emoji', 'Twemoji_DISABLED'); } + // Windows has no flag emoji glyphs (πŸ‡ΊπŸ‡Έ shows as "US"): use Twemoji for the + // flag letters only, keeping every other emoji native. + document.documentElement.style.setProperty( + '--font-flags', + IS_WINDOWS ? 'TwemojiFlags' : 'TwemojiFlags_DISABLED', + ); return null; } diff --git a/src/index.css b/src/index.css index 2c128418d..db038d112 100644 --- a/src/index.css +++ b/src/index.css @@ -6,6 +6,19 @@ font-display: swap; } +/* Country flags only (regional indicator letters U+1F1E6–1F1FF), from the same + Twemoji file. Windows' emoji font has no flag glyphs and draws πŸ‡ΊπŸ‡Έ as the + letters "US"; SystemEmojiFeature enables this family on Windows. The + unicode-range means the file is only fetched when a flag is on screen. */ +@font-face { + font-family: TwemojiFlags; + src: + url('../public/font/Twemoji.Mozilla.v15.1.0.woff2'), + url('../public/font/Twemoji.Mozilla.v15.1.0.ttf'); + unicode-range: U+1F1E6-1F1FF; + font-display: swap; +} + :root { --tc-link: hsl(213deg 100% 45%); @@ -28,7 +41,8 @@ --mx-uc-8: hsl(94, 100%, 35%); --font-emoji: 'Twemoji_DISABLED'; - --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; + --font-flags: 'TwemojiFlags_DISABLED'; + --font-secondary: 'InterVariable', var(--font-flags), var(--font-emoji), sans-serif; } .dark-theme,