Files
im_webapp/web/src/lib/avatar.ts
T
Your NameandClaude Sonnet 5 0b48346d02 fix(web): avatar background color was invisible everywhere (transparent)
avatarBgClass() built its class name via template-literal interpolation
(`bg-avatar-${i + 1}`). Tailwind's build finds utility classes by statically
scanning source text for complete class-name strings -- a dynamically
assembled name is invisible to that scan, so `.bg-avatar-1` through
`.bg-avatar-11` were never generated in the compiled CSS at all. Every
avatar in the app (chat list, conversation header, message bubbles, detail
panel) rendered with a fully transparent background instead of its color --
the element's className looked correct in the DOM, which is why this
shipped across several commits before being caught by checking computed
style rather than the class name itself.

Fixed by writing the 11 class names out as a literal array Tailwind's
scanner can actually see.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 02:23:20 +08:00

53 lines
1.9 KiB
TypeScript

/**
* Deterministic avatar background color per user/topic ID. Algorithm (Java-style String#hashCode,
* rolling `hash = hash*31 + charCode`) ported from the old app's `letterTileColorId` -- but the old
* app's palette had 16 colors (`% 16`) where this design's token palette (index.css's --color-avatar-1
* through -11) has 11, so the modulus is adjusted to match; the hash function itself is unchanged.
*/
function stringToColorHash(value: string): number {
let hash = 0;
for (let i = 0; i < value.length; i++) {
hash = (hash << 5) - hash + value.charCodeAt(i);
hash = hash & hash;
}
return hash;
}
const AVATAR_COLOR_COUNT = 11;
/** 1-based index matching the --color-avatar-1..11 tokens in index.css. */
export function avatarColorIndex(id: string): number {
return (Math.abs(stringToColorHash(id)) % AVATAR_COLOR_COUNT) + 1;
}
/**
* Written out as literal strings, NOT template-generated (`bg-avatar-${i}`) -- Tailwind's build
* finds utility classes by statically scanning source text for complete class-name strings, so a
* dynamically-assembled name is invisible to it and never gets its CSS generated at all. That exact
* bug shipped here once already: every avatar rendered with a fully transparent background instead
* of its color, discovered by checking computed style, not by reading the class name off the DOM
* (which looked correct).
*/
const AVATAR_BG_CLASSES = [
'bg-avatar-1',
'bg-avatar-2',
'bg-avatar-3',
'bg-avatar-4',
'bg-avatar-5',
'bg-avatar-6',
'bg-avatar-7',
'bg-avatar-8',
'bg-avatar-9',
'bg-avatar-10',
'bg-avatar-11',
];
export function avatarBgClass(id: string): string {
return AVATAR_BG_CLASSES[avatarColorIndex(id) - 1];
}
/** First character of a display name, matching the old app's letter-tile convention. */
export function avatarLetter(name: string | undefined): string {
return (name || '').trim().slice(0, 1) || '?';
}