[No QA] Avatar component decomposition#95050
Conversation
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@abdulrahuman5196 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@roryabraham - this is the first PR from |
| const {hasImageError, onImageError} = useAvatarLoadError(originalSource); | ||
|
|
||
| const source = originalSource; | ||
| const optimizedSource = optimizeAvatarSource(source); |
There was a problem hiding this comment.
❌ CONSISTENCY-3 (docs)
The source-to-renderable-model resolution logic here is duplicated almost verbatim in useUserAvatarSource.ts. Both hooks compute optimizeAvatarSource(source), derive useFallBackAvatar (hasImageError || !source || source === defaultAvatars.FallbackAvatar), compute avatarSource = useFallBackAvatar ? fallbackAvatar : (optimizedSource ?? fallbackAvatar), and then return the identical {variant: 'image', ...} / {variant: 'icon', ...} shapes. Duplicating this raises maintenance cost and bug risk if the resolution rules change.
Extract the shared logic into a helper that both hooks call, keeping only the user/workspace-specific inputs (fallback avatar, testID, icon colors) at each call site:
// e.g. resolveAvatarBody.ts
function resolveAvatarBody({source, hasImageError, onImageError, defaultAvatars, fallbackAvatar, fallbackAvatarTestID, iconColors}): ResolvedImageAvatar | ResolvedIconAvatar {
const optimizedSource = optimizeAvatarSource(source);
const useFallBackAvatar = hasImageError || !source || source === defaultAvatars.FallbackAvatar;
const avatarSource = useFallBackAvatar ? fallbackAvatar : (optimizedSource ?? fallbackAvatar);
if (typeof avatarSource === 'string') {
return {avatarSource, variant: 'image', hasImageError, fallbackAvatarTestID, onImageError};
}
return {avatarSource, variant: 'icon', hasImageError, iconColors, fallbackAvatarTestID, onImageError};
}Then useUserAvatarSource and useWorkspaceAvatarSource each only compute their own fallbackAvatar/fallbackAvatarTestID/iconColors and delegate to it.
Reviewed at: 88b1ae1 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Not sure about this one. I personally like verbosity of current implementation even if it means 1 repetition of code
|
No product review needed |
| import useAvatarLoadError from './useAvatarLoadError'; | ||
|
|
||
| type UseUserAvatarSourceParams = { | ||
| source?: AvatarSource; |
There was a problem hiding this comment.
Some comments explaining source and avatarID would be helpful. It is ever valid to have both, or would a discriminated union be more precise?
If we could make some of these fields non-optional it might ripple some simplifications through the hook.
| import useAvatarLoadError from './useAvatarLoadError'; | ||
|
|
||
| type UseWorkspaceAvatarSourceParams = { | ||
| source?: AvatarSource; |
There was a problem hiding this comment.
Does it really make sense for all of these to be optional?
|
Thanks for making those changes @jmusial, but I remain skeptical that both |
I see your point that maybe this may be split down further. Unfortunately I need to run for today, I will pick it up from here next week. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77f2fd7407
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b53c297188
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| size, | ||
| fill, | ||
| testID, | ||
| avatarID = CONST.DEFAULT_NUMBER_ID, |
There was a problem hiding this comment.
Preserve omitted workspace avatar IDs
When the compatibility Avatar is used for a workspace without avatarID (the prop is still optional), this default converts the missing value to 0; WorkspaceAvatar then hashes '0' for the default workspace icon colors. Before the refactor, the workspace path used avatarID?.toString() ?? '', so omitted IDs hashed the empty string. This silently changes the background/fill for legal calls such as a workspace avatar with only type and name, especially on fallback after an image error; pass undefined through for workspaces or keep the old empty-string fallback.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is deliberate and I think acceptable.
|
@roryabraham I updated the PR so now for I explored some options with splitting out fallback logic out of the hooks but it overcomplicated the code IMO. let me know what you think, thx. And FYI I'll be OOO for Thu and Fri. |
Explanation of Change
This PR decomposes the monolithic
Avatarcomponent into focusedUserAvatar,WorkspaceAvatar, constructed fromimage,icon, andinitialsavatar primitives while preserving the existing public API.It also keeps
Avatarsource resolution and load-error handling in dedicated hooks, making the rendering paths easier to understand, test, and extend.Fixed Issues
$ #95597
PROPOSAL:
Tests
Pure refactor, should be unnoticable to user.
Offline tests
N/A
QA Steps
N/A
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
MacOS: Chrome / Safari
Screen.Recording.2026-07-09.at.13.41.52.mov