Skip to content

[No QA] Avatar component decomposition - #95050

Merged
roryabraham merged 22 commits into
Expensify:mainfrom
software-mansion-labs:chore/decomposition-of-avatar-component
Jul 26, 2026
Merged

roryabraham merged 22 commits into
Expensify:mainfrom
software-mansion-labs:chore/decomposition-of-avatar-component

Conversation

@jmusial

@jmusial jmusial commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

This PR decomposes the monolithic Avatar component into focused UserAvatar, WorkspaceAvatar, constructed from image, icon, and initials avatar primitives while preserving the existing public API.

It also keeps Avatar source 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.

  1. Open the app
  2. Navigate through pages, verify that avatar sizes are the same as on prod/staging.

Offline tests

N/A

QA Steps

N/A

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

MacOS: Chrome / Safari
Screen.Recording.2026-07-09.at.13.41.52.mov

@jmusial

jmusial commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 5630d21512

ℹ️ 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".

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/components/Avatar/UserAvatar.tsx 100.00% <100.00%> (ø)
src/components/Avatar/WorkspaceAvatar.tsx 100.00% <100.00%> (ø)
src/components/Avatar/hooks/useAvatarLoadError.ts 100.00% <100.00%> (ø)
src/components/Avatar/index.tsx 100.00% <100.00%> (ø)
...c/components/Avatar/primitives/AvatarContainer.tsx 100.00% <100.00%> (ø)
src/components/Avatar/primitives/AvatarIcon.tsx 100.00% <100.00%> (ø)
src/components/Avatar/primitives/AvatarImage.tsx 100.00% <100.00%> (ø)
src/components/Avatar/primitives/AvatarLetter.tsx 100.00% <100.00%> (ø)
src/libs/UserAvatarUtils.ts 97.20% <100.00%> (+0.03%) ⬆️
src/styles/utils/index.ts 55.47% <100.00%> (+0.24%) ⬆️
... and 147 files with indirect coverage changes

@jmusial jmusial changed the title Avatar component decomposition poc Avatar component decomposition Jul 7, 2026
@jmusial

jmusial commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: acd871a794

ℹ️ 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".

@jmusial jmusial changed the title Avatar component decomposition [No QA] Avatar component decomposition Jul 8, 2026
@jmusial

jmusial commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 88b1ae1551

ℹ️ 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".

@jmusial
jmusial marked this pull request as ready for review July 9, 2026 11:45
@jmusial
jmusial requested review from a team as code owners July 9, 2026 11:45
@melvin-bot
melvin-bot Bot requested a review from abdulrahuman5196 July 9, 2026 11:45
Comment thread src/components/Avatar/hooks/useUserAvatarSource.ts Outdated
Comment thread src/components/Avatar/hooks/useUserAvatarSource.ts Outdated
Comment thread src/components/Avatar/hooks/useWorkspaceAvatarSource.ts Outdated
Comment thread src/components/Avatar/primitives/AvatarBody.tsx Outdated
/** Renders a user or workspace avatar depending on the `type` prop.
* This wrapper exists for backward compatibility. If possible use WorkspaceAvatar and UserAvatar directly.
*/
function Avatar({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I presume we'll have a follow-up PR to update the callsites to use the correct level-two components directly? i.e: consuming components will use UserAvatar and WorkspaceAvatar directly, and we'll get rid of this wrapper/barrel entirely, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, #95599 and #95600

roryabraham
roryabraham previously approved these changes Jul 22, 2026
@abdulrahuman5196

Copy link
Copy Markdown
Contributor

checking now

@abdulrahuman5196 abdulrahuman5196 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@jmusial I had only minor comments in the code. Seems most comments are already addressed.
Other than that,
Why is the PR marked as No QA since this has obviously changed the code path of the Avatar component? And without the migration PRs if this moves to staging it needs QA runthrough. If we have different QA plan please mention it in PR.
And the Author's checklist only have screen recordings of mac, we should check in all platforms.

const letterAvatar = parseLetterAvatarURL(resolvedSource);
if (letterAvatar) {
return (
<AvatarContainer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the parent

        <AvatarContainer
            containerStyles={containerStyles}
            testID={testID}
        >

code is duplicated couple of times. If possible we should think of avoiding it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In theory we could, but with each Avatar primitive having slightly different logic I think this is more readable (avoiding conditionals in the code or pushing it into primitives)

import React from 'react';
import {View} from 'react-native';

type UserLetterAvatarProps = AvatarPrimitivesCommonProps & {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The component name is AvatarLetter but why do we name its props UserLetterAvatarProps? which is little odd.

@jmusial

jmusial commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@jmusial I had only minor comments in the code. Seems most comments are already addressed. Other than that, Why is the PR marked as No QA since this has obviously changed the code path of the Avatar component? And without the migration PRs if this moves to staging it needs QA runthrough. If we have different QA plan please mention it in PR. And the Author's checklist only have screen recordings of mac, we should check in all platforms.

@abdulrahuman5196 No QA because it's a refactor not changing any user facing behaviour, which is also reflected in test steps. Just checking if avatars display the same as on prod.

Same reason for just recording web.

If you feel like more reboust test steps & recordings are needed let me know I can add them

@abdulrahuman5196

Copy link
Copy Markdown
Contributor

No QA because it's a refactor not changing any user facing behaviour, which is also reflected in test steps. Just checking if avatars display the same as on prod.

I think adding QA would be good to check on avatars, regardless the code is good. I will start to check on different platforms and approve in couple of hours if no issues are found.

@abdulrahuman5196

abdulrahuman5196 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Screen.Recording.2026-07-27.at.1.15.54.AM.mov
Android: mWeb Chrome
Screen.Recording.2026-07-27.at.1.17.35.AM.mov
iOS: HybridApp
Screen.Recording.2026-07-27.at.12.57.13.AM.mov
iOS: mWeb Safari
Screen.Recording.2026-07-27.at.12.50.15.AM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-07-27.at.12.45.23.AM.mov

@abdulrahuman5196 abdulrahuman5196 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes looks good and works well. Reviewers checklist is also complete.

All yours. @roryabraham

🎀 👀 🎀
C+ Reviewed

@melvin-bot
melvin-bot Bot requested a review from roryabraham July 26, 2026 19:56
@roryabraham
roryabraham merged commit 5cda539 into Expensify:main Jul 26, 2026
35 of 37 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🚧 roryabraham has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/roryabraham in version: 9.4.45-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor

Help site review — no changes required

I reviewed the changes in this PR against Expensify's help site content under App/docs/articles.

Conclusion: No help site updates are required.

Why:

  • This is a pure internal refactor — it decomposes the monolithic Avatar component into UserAvatar, WorkspaceAvatar, and image/icon/initials primitives while preserving the existing public API. It's labeled [No QA] and described as "should be unnoticeable to user."
  • Every changed file lives in src/components/Avatar/, src/libs/UserAvatarUtils.ts, src/styles/utils/index.ts, or tests/ — none of them add or change product features, UI copy, tab/settings labels, buttons, or user-facing flows.
  • I checked the diff for any added or modified user-facing strings (text, labels, translations, copy). The only matches were internal component props and test assertions — no customer-visible wording changed.

Since the help site documents product behavior and this PR changes none, there is nothing to add or update in App/docs/articles.


@jmusial, no linked help site PR was created because this refactor doesn't change any documented, user-facing behavior. If you believe a specific article should still be updated (e.g. the change surfaces differently than expected), let me know which article and behavior and I'll draft it.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/marcaaron in version: 9.4.45-14 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

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.

6 participants