Skip to content

fix(avatar): consistent avataaars rendering (2/4) - #435

Merged
motirebuma merged 2 commits into
profile-biofrom
avatar-consistency
Jul 24, 2026
Merged

fix(avatar): consistent avataaars rendering (2/4)#435
motirebuma merged 2 commits into
profile-biofrom
avatar-consistency

Conversation

@niamao

@niamao niamao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Stack

Part 2 of 4 — merge after Part 1 (#434 / profile-bio).

Order Branch PR
1 profile-bio #434 (merge first)
2 avatar-consistency this PR (#435; base = Part 1)
3 presence-persist #436
4 api-wiring #437

After Part 1 merges to main, retarget this PR’s base to main (or rebase onto main).

Related issue: #362

Summary

  • Avatar parses qualities objects vs URL strings
  • Chat / comments / activity / search pass raw avatar values
  • useSyncCurrentUserProfile keeps nav/account avatar in sync with GET_USER
  • Store + login avatar typing for object avatars

Test plan

  • Profile, nav, comments, and chat show the same custom avatar
  • Hard reload still shows correct nav avatar when logged in
  • Avatar editor save updates profile and nav

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quotevote Ready Ready Preview, Comment Jul 24, 2026 9:22am

@motirebuma motirebuma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @niamao - nice cleanup! This simplifies a lot of ad hoc avatar handling code into a consistent pattern.

What's good

  1. Avatar component refactor - accepting src as string | Record | null and resolving it via parseAvatarToUrl essentially makes all of its usesites pass the raw avatar value, which is nice and clean. Extracting AvatarMedia as a subcomponent with key={resolvedSrc} correctly resets the loading/error state when the avatar changes. Good job!

  2. useSyncCurrentUserProfile is basically a hack to make sure that the nav bar avatar doesn't flicker when we log in - historically, the login response didn't include the avatar, so until we fetched it separately (in the profile page) we'd show a default avatar in the nav bar, but the profile page would show the real one. This hook basically syncs the avatar (as well as profile name and bio, email, and contributorBadge) from the server query to the Zustand store, which is exactly what we need. Using JSON.stringify to check if it changed is arguably a hack, but it's totally acceptable for a simple object like avatar.

  3. The presence restoration logic correctly favors preferredStatus over status and translates offlineonline for the current user (since they're obviously not actually offline if they're viewing the app). The test covers all four cases (default, set explicitly, offline → online, preferredStatus override).

  4. Updating all usesites of avatar from string to string | Record is thorough - I don't see any lingering typeof avatar === 'string' ? avatar : undefined coercions in activity item, buddy list, chat types, component props, or post chat.

Items to address

  1. The useSyncCurrentUserProfile hook will run on every dashboard page (since it's called in the DashboardClient layout), which means that every time you visit a dashboard page, it'll run a GET_USER query to update the profile. Since it's using cache-and-network, the first time it'll use the cached value, but subsequent times it'll have to make a network request anyway. Not a huge problem, but it's not ideal - ideally we'd use cache-first if we don't care about freshness, or implement some sort of debounce with a longer cache TTL.

  2. The BuddyItemList avatar coercion is a type assertion, not a runtime check - if item.user.avatar is something unexpected (a number, say), it would silently coerce to null. Probably not a huge problem, but worth noting.

Nits

The AvatarMedia function component inside Avatar could be a named export if we wanted to test it, but it's probably fine as is since it's pretty simple and testing it would require testing Avatar anyway. ParseAvatarToUrl isn't in the diffs, but I assume that's a real function in @/lib/avatar that takes the qualities object and turns it into an avataaars.io URL.

Thank you @niamao

@flyblackbox @niamao

Parse avatar qualities vs URL in Avatar, pass raw avatar values through
chat/comments/activity, sync login/nav avatar from GET_USER, and keep
store avatar updates typed for objects.

Co-authored-by: Cursor <[email protected]>
Use cache-first for useSyncCurrentUserProfile to avoid re-fetching GET_USER
on every dashboard navigation, and coerce BuddyItemList avatars at runtime
instead of type assertions. Cover coerceAvatarValue and resolveOwnStatus in tests.

Co-authored-by: Cursor <[email protected]>
@niamao

niamao commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review suggestions:

  1. useSyncCurrentUserProfile fetch policy — switched from cache-and-network to cache-first so dashboard remounts reuse the Apollo cache instead of hitting the network on every page. Cache misses (post-login / hard reload) still fetch once; avatar/settings mutations continue to update Apollo cache + Zustand.

  2. BuddyItemList avatar coercion — replaced the type assertion with a runtime coerceAvatarValue() helper in @/lib/avatar that accepts string | Record and returns null for unexpected shapes (numbers, arrays, etc.).

Also tightened presence tests: preferredStatus isn’t on GET_USER / Presence yet, so that case is unit-tested via exported resolveOwnStatus rather than a MockedProvider integration that couldn’t see those fields.

@motirebuma
@flyblackbox

@motirebuma motirebuma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @niamao - all review items addressed cleanly.

Previously raised items - now resolved

  1. useSyncCurrentUserProfile fetch policy - FIXED. Changed from cache-and-network to cache-first. The hook now shares Apollo cache between dashboard mounts rather than fetching fresh profile on every page navigation. Nice comment explains the thinking.

  2. BuddyItemList avatar coercion - FIXED. Now uses coerceAvatarValue(item.user.avatar) which is a proper run-time check that returns null for non-expected types (numbers, arrays, booleans) rather than type assertion. Added coerceAvatarValue unit test which covers strings, objects, null, undefined, numbers, arrays and booleans.

  3. resolveOwnStatus exported for testing - the presence resolution logic is now directly testable. Test covers default, explicit, offline->online and preferredStatus override cases.

What's good (unchanged from prior review)

  • Avatar component refactor with parseAvatarToUrl and keyed AvatarMedia subcomponent

  • useSyncCurrentUserProfile syncing avatar/name/bio/presence from GET_USER to Zustand

  • Type unification across all avatar call sites (activity, buddy list, chat, components)

Verdict: approved - ready to merge

Thank you @niamao

@flyblackbox @niamao

@motirebuma
motirebuma merged commit 77aa77e into profile-bio Jul 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants