From bc80aac9852b25eb5e1483dbee38ac24f2b6717f Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Sun, 14 Jun 2026 12:32:19 +0700 Subject: [PATCH] fix(webui): derive the account avatar initial by code point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (displayName).charAt(0) returns the first UTF-16 unit, so a non-BMP first character (emoji, rare CJK) rendered as a lone surrogate (οΏ½). Spread to iterate by code point. Co-Authored-By: Claude Fable 5 --- .../src/pages/settings/settings-account.test.tsx | 12 ++++++++++++ frontend/src/pages/settings/settings-account.tsx | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/settings/settings-account.test.tsx b/frontend/src/pages/settings/settings-account.test.tsx index deceba31..f2ffd274 100644 --- a/frontend/src/pages/settings/settings-account.test.tsx +++ b/frontend/src/pages/settings/settings-account.test.tsx @@ -32,6 +32,18 @@ describe('SettingsAccount gating', () => { expect(screen.getAllByRole('button', { name: 'Change' })).toHaveLength(3); }); + it.each([ + ['πŸ˜€ Team', 'πŸ˜€'], + ['δΈ­ζ–‡ η”¨ζˆ·', 'δΈ­'], + ['ν•œκ΅­ μ‚¬μš©μž', 'ν•œ'], + ['𠀋 Lin', '𠀋'], + ])('uses the first whole code point of %s as the avatar initial', (name, expected) => { + authState.value = { user: { mail: 'e@x.com', name, type: 'local' } }; + render(); + + expect(screen.getByText(expected)).toBeInTheDocument(); + }); + it('hides password and email editing for an OAuth account but keeps the name editable', () => { authState.value = { user: githubUser }; render(); diff --git a/frontend/src/pages/settings/settings-account.tsx b/frontend/src/pages/settings/settings-account.tsx index 973b3b3b..d65a92da 100644 --- a/frontend/src/pages/settings/settings-account.tsx +++ b/frontend/src/pages/settings/settings-account.tsx @@ -31,7 +31,7 @@ function SettingsAccount() { const stopEditing = () => setEditing(null); const displayName = user.name?.trim() || user.mail; - const initial = (displayName || '?').charAt(0).toUpperCase(); + const initial = ([...(displayName || '?')][0] ?? '?').toUpperCase(); const createdAt = user.created_at ? new Date(user.created_at) : null; const memberSince = createdAt && !Number.isNaN(createdAt.getTime()) ? format(createdAt, 'MMMM yyyy', { locale: enUS }) : null;