From ce810d059c5271f411074fc17c131f2e05da8722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:20:47 +0100 Subject: [PATCH 1/6] Tests: an avatar shows two initials for a one-word user name Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/lib/userName.test.ts | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/lib/userName.test.ts b/src/renderer/src/lib/userName.test.ts index ef1e627e7..6aa47dfec 100644 --- a/src/renderer/src/lib/userName.test.ts +++ b/src/renderer/src/lib/userName.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { displayName, userFilterTexts } from './userName'; +import { displayName, initials, userFilterTexts } from './userName'; describe('displayName', () => { it('reads an email as a name', () => { @@ -7,6 +7,33 @@ describe('displayName', () => { }); }); +describe('initials', () => { + it('takes the first letter of the first two words of a name', () => { + expect(initials('jane.doe@unity3d.com')).toBe('JD'); + expect(initials('jane-doe')).toBe('JD'); + expect(initials('jane_doe')).toBe('JD'); + expect(initials('jane.mary.doe')).toBe('JM'); + }); + + it('takes the first two letters of a one-word name, as people know it from the other clients', () => { + expect(initials('danipen@unity3d.com')).toBe('DA'); + expect(initials('jd')).toBe('JD'); + }); + + it('shows a one-letter name whole', () => { + expect(initials('j@unity3d.com')).toBe('J'); + }); + + it('reads letters beyond ASCII', () => { + expect(initials('ñandú')).toBe('ÑA'); + }); + + it('shows a question mark for an empty name', () => { + expect(initials('')).toBe('?'); + expect(initials('@unity3d.com')).toBe('?'); + }); +}); + describe('userFilterTexts', () => { it('matches the name shown before the user as stored', () => { expect(userFilterTexts('jane.doe@unity3d.com')).toEqual(['Jane Doe', 'jane.doe@unity3d.com']); From b64aea4d6b2548edd90a7722346788dd61631084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:21:23 +0100 Subject: [PATCH 2/6] Avatars show two initials for a one-word user name, as the other UVCS clients do Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/lib/userName.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/lib/userName.ts b/src/renderer/src/lib/userName.ts index 6eabc871a..6ba87809d 100644 --- a/src/renderer/src/lib/userName.ts +++ b/src/renderer/src/lib/userName.ts @@ -8,9 +8,11 @@ export function displayName(user: string): string { .join(' '); } +/** `jane.doe` → `JD`; a one-word name gives its first two letters (`danipen` → `DA`), as the other UVCS clients show it. */ export function initials(user: string): string { - const words = displayName(user).split(' '); - return ((words[0]?.[0] ?? '') + (words[1]?.[0] ?? '')).toUpperCase() || '?'; + const [first = [], second] = displayName(user).split(' ').map((word) => Array.from(word)); + const letters = second ? [first[0], second[0]] : first.slice(0, 2); + return letters.join('').toUpperCase() || '?'; } /** What a filter matches a user by: the name shown (which `UserLabel` highlights), and the user as stored. */ From 4dd5ff3d9a4c6a8f323d486400c5428201756fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:21:23 +0100 Subject: [PATCH 3/6] Docs: how avatars pick their initials Co-Authored-By: Claude Opus 5.5 --- docs/ARCHITECTURE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c25e1fa11..fee1f5e8c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -659,9 +659,10 @@ for these names, and holds each module allowed to name them to exactly the file ## Profile pictures (Gravatar) -People show as their initials on their own color (`ui/Avatar`, `avatarColorOf`) until the user chooses to see their -Gravatar pictures. Nothing goes to gravatar.com before that choice: it's a hash of each email address shown, and the -user decides whether it leaves the machine. +People show as their initials (`initials`: the first letters of the first two words of the name, or the first two +letters of a one-word name, as the other UVCS clients show them) on their own color (`ui/Avatar`, `avatarColorOf`) +until the user chooses to see their Gravatar pictures. Nothing goes to gravatar.com before that choice: it's a hash of +each email address shown, and the user decides whether it leaves the machine. - **Off until chosen**: `showGravatar` and `gravatarChoiceAsked` both start false (`shared/domain/gravatarChoice.ts`). Pictures show only when both are true (`showsGravatar`), checked in one place, the main process's `system.gravatar` From 3fa03800513542d83385d7eb0d569f74c62da333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:23:31 +0100 Subject: [PATCH 4/6] Tests: initials keep a separately written accent and read names with extra spaces Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/lib/userName.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/renderer/src/lib/userName.test.ts b/src/renderer/src/lib/userName.test.ts index 6aa47dfec..2e43d0bcd 100644 --- a/src/renderer/src/lib/userName.test.ts +++ b/src/renderer/src/lib/userName.test.ts @@ -26,6 +26,18 @@ describe('initials', () => { it('reads letters beyond ASCII', () => { expect(initials('ñandú')).toBe('ÑA'); + expect(initials('\u{1D49C}bc')).toBe('\u{1D49C}B'); + expect(initials('\u{1D49C}x.bob')).toBe('\u{1D49C}B'); + }); + + it('keeps an accent written as a separate mark with its letter', () => { + expect(initials('n\u0303andu\u0301')).toBe('ÑA'); + expect(initials('n\u0303o.bob')).toBe('ÑB'); + }); + + it('reads a name with extra spaces as its words', () => { + expect(initials('Jane Doe')).toBe('JD'); + expect(initials(' jane')).toBe('JA'); }); it('shows a question mark for an empty name', () => { From 81d2fdeafa5cd7a88f72f68e148c7533cdfc3574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:23:50 +0100 Subject: [PATCH 5/6] Avatars keep a separately written accent in their initials and read names with extra spaces Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/lib/userName.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/lib/userName.ts b/src/renderer/src/lib/userName.ts index 6ba87809d..98a754fc7 100644 --- a/src/renderer/src/lib/userName.ts +++ b/src/renderer/src/lib/userName.ts @@ -10,7 +10,9 @@ export function displayName(user: string): string { /** `jane.doe` → `JD`; a one-word name gives its first two letters (`danipen` → `DA`), as the other UVCS clients show it. */ export function initials(user: string): string { - const [first = [], second] = displayName(user).split(' ').map((word) => Array.from(word)); + // NFC joins a letter written with a separate accent mark (`n` and a combining tilde) into one, so the two letters shown are letters. + const words = displayName(user).normalize('NFC').split(/\s+/).filter(Boolean); + const [first = [], second] = words.map((word) => Array.from(word)); const letters = second ? [first[0], second[0]] : first.slice(0, 2); return letters.join('').toUpperCase() || '?'; } From 7b045e2a6cde837e10ae548db7b91926756698ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Pen=CC=83alba?= Date: Thu, 29 Oct 2026 15:23:59 +0100 Subject: [PATCH 6/6] Wrap a comment in initials Co-Authored-By: Claude Opus 5.5 --- src/renderer/src/lib/userName.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/lib/userName.ts b/src/renderer/src/lib/userName.ts index 98a754fc7..0a092a219 100644 --- a/src/renderer/src/lib/userName.ts +++ b/src/renderer/src/lib/userName.ts @@ -10,7 +10,8 @@ export function displayName(user: string): string { /** `jane.doe` → `JD`; a one-word name gives its first two letters (`danipen` → `DA`), as the other UVCS clients show it. */ export function initials(user: string): string { - // NFC joins a letter written with a separate accent mark (`n` and a combining tilde) into one, so the two letters shown are letters. + // NFC joins a letter written with a separate accent mark (`n` and a combining tilde) into one, so the two letters + // shown are letters. const words = displayName(user).normalize('NFC').split(/\s+/).filter(Boolean); const [first = [], second] = words.map((word) => Array.from(word)); const letters = second ? [first[0], second[0]] : first.slice(0, 2);