User avatar never renders in `Header` — reads `authenticatedUser.avatar`, a field the platform has never provided
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 68/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 冷清
- 技术栈
- javascript
- 领域
- frontend
调研方向
从 src/Header.jsx 开始,将其 avatar prop 的处理方式与 src/studio-header/StudioHeader.tsx 中已修正的模式进行比较。通过 DesktopUserMenuToggle.jsx 和 MobileUserMenuToggle.jsx 追踪该 prop,然后验证只有在已获取到经过 hydration 的个人资料图片数据时才使用这些数据。完成标准是桌面端和移动端的两个 toggle 都能显示已上传的头像,而没有图片的用户仍保留占位符;为消费者记录 hydration 要求。
由索引模型根据 Issue 内容生成。
描述
[!IMPORTANT]
🤖 Generated by Claude
This issue — the investigation, evidence, and write-up — was produced by Claude (Claude Code), reviewed by a human before posting. Commit references, dates, and file paths were gathered programmatically from git history and the source repos; please still sanity-check specifics before acting on them.
Summary
The user avatar in the desktop and mobile user-menu toggles (DesktopUserMenuToggle / MobileUserMenuToggle) never displays a real image — it always falls back to the placeholder AvatarIcon.
The root cause is a single field-name mismatch: src/Header.jsx reads authenticatedUser.avatar, but no layer of the Open edX stack has ever populated an avatar field on the authenticated user. The profile image has always lived at authenticatedUser.profileImage.imageUrlMedium (populated by hydrateAuthenticatedUser()).
The Studio header hit the identical bug and was fixed in commit 38a68e5 ("fix: show hydrated profile avatars in Studio header"). The same fix was never applied to the main desktop/mobile Header.
Note: this issue is a write-up of the investigation only. It intentionally does not include a fix PR yet.
Symptom & scope
| Header | Avatar source | Status |
|---|---|---|
Header → DesktopHeader → DesktopUserMenuToggle |
authenticatedUser.avatar |
❌ broken |
Header → MobileHeader → MobileUserMenuToggle |
authenticatedUser.avatar |
❌ broken |
StudioHeader → UserMenu |
authenticatedUser.avatar || profileImage.imageUrlMedium |
✅ fixed (38a68e5) |
LearningHeader |
renders no avatar (username only) | — n/a |
Root cause
The plumbing inside this repo is correct end-to-end:
Header.jsx → DesktopHeaderSlot → DesktopHeader → DesktopUserMenuToggleSlot → DesktopUserMenuToggle → Avatar src={avatar}
The break is the source value at the top:
// src/Header.jsx
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
authenticatedUser.avatar is effectively always undefined, so Avatar renders the placeholder icon.
Why avatar is never set — evidence across the whole stack
| Layer | What it actually provides | avatar? |
|---|---|---|
edx-platform accounts API /api/user/v1/accounts/{username} |
profile_image: { has_image, image_url_full/large/medium/small } — shape stable since March 2015 (46b63164a) |
❌ never |
@edx/frontend-platform hydrateAuthenticatedUser() |
camelCases the above → profileImage: { hasImage, imageUrlMedium, … } via setAuthenticatedUser({ ...user, ...camelCaseObject(response.data) }) |
❌ never |
@edx/frontend-platform JWT-decoded user |
{ email, userId, username, roles, administrator, name } |
❌ never |
@edx/frontend-auth (legacy) userAccount model |
profileImage: { imageUrlMedium, imageUrlLarge } |
❌ never |
@edx/frontend-base v1.1.0 / v2.0.0 (the versions this repo pinned in 2019) |
getAuthenticatedUser returns exactly { userId, username, roles, administrator } (explicit field pick — no spread, so a JWT claim couldn't leak through either) |
❌ never |
A live sample of the accounts endpoint today confirms it — profile_image.image_url_medium present, has_image: true, and no avatar key.
The correct pattern
Mirror the Studio header fix (src/studio-header/StudioHeader.tsx):
const authenticatedUserAvatar = authenticatedUser?.avatar
|| (profileImage?.hasImage ? profileImage.imageUrlMedium : null);
Note the profileImage.hasImage guard — when the user has no uploaded image the API still returns profile_image with has_image: false and default-image URLs, so the guard matters.
Two failure modes across consumers
Profile-image data is only present if the MFE opts into hydration — initialize({ hydrateAuthenticatedUser: true }), which defaults to false. So consumers fail in one of two ways:
- Wrong field — MFEs that do hydrate (e.g.
frontend-app-account,frontend-app-profile) haveauthenticatedUser.profileImageavailable, but the header reads the nonexistent.avatar. - Not hydrated — MFEs that don't opt in (e.g.
frontend-app-learner-dashboard) never fetchprofileImageat all, so even a corrected field read would need hydration enabled.
Historical provenance (why the code looks like this)
afe6705(2019-09-13, initial commit): originalSiteHeader.jsxdestructuredavatarfrom@edx/frontend-base'sAuthenticationContext. That context never suppliedavatar— and the author's own example/test mocks only set{ userId, username, administrator }.f9d1734(2019-09-18, "upgrading to frontend-base 2.0.0"): the frontend-base 2.0 migration converted the destructuredavatarintoavatar: authenticatedUser.avatar— this is where the current form was introduced.57e05c4(2019-11-04, #26): added the!== nullguard only.- Likely origin of the field name:
frontend-app-learningonce contained a design docdocs/api-strawperson.js(since deleted) sketching a proposeduser: { …, avatar: String }API shape that was never implemented. The header appears to have been written against that aspirational contract, while the shipped data always lived underprofileImage.
So the avatar read has been dead on arrival since the repo's first commit; no dependency bump ever made it live.
Ecosystem audit (for completeness)
A full git-history pickaxe (git log --all -S avatar) across all 13 frontend-app-* consumers of this package found that no MFE has ever injected an avatar field onto authenticatedUser (no manual AppContext.Provider, no custom auth/hydration handler) — in current code or anywhere in history. Every avatar occurrence is incidental (Paragon <Avatar>, CSS class names, forum/API profile-image data, package-lock noise).
Two notable findings:
frontend-app-accountandfrontend-app-profilepreviously rendered their own vendored headers that read the image correctly fromstate.userAccount.profileImage— and regressed to a missing avatar when they adopted@edx/frontend-component-header.- The real image data is consistently sourced from
profileImage.imageUrlMediumeverywhere in the ecosystem (e.g.frontend-app-learner-portal-enterprise'sAvatarDropdown), making this component'savatarread the lone outlier.
Suggested direction (fix deferred)
- In
src/Header.jsx, fall back to the hydrated profile image, mirroring the Studio fix and guarding onhasImage:avatar: authenticatedUser ? (authenticatedUser.avatar || (authenticatedUser.profileImage?.hasImage ? authenticatedUser.profileImage.imageUrlMedium : null)) : null, - Document that consuming MFEs must call
initialize({ hydrateAuthenticatedUser: true })for the image to be available (relevant tofrontend-app-learner-dashboardand any other full-Headerconsumer that doesn't hydrate today).
Affected files
src/Header.jsx— the buggy read (feeds both desktop and mobile).src/desktop-header/DesktopUserMenuToggle.jsx,src/mobile-header/MobileUserMenuToggle.jsx— consume theavatarprop.- Reference for the correct pattern:
src/studio-header/StudioHeader.tsx(fixed in38a68e5).
- 主要语言
- JavaScript
- 星标
- 10
- 派生
- 194
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
openedx/frontend-component-header 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
-
[DEPR]: Learning Header Actions Slot V1可能重新可做 @ChrisChV 于 68 天前认领,目前没有进行中的 PR。 未关闭depr
openedx/frontend-component-header#681 · 4 条评论 · 1 个 reaction · 已指派 1 人 ·
-
[a11y]: The tab and arrow keys are not trapped in the header dropdown once activated.可能重新可做 @peterkulko 于 220 天前认领,目前没有进行中的 PR。 未关闭a11y
openedx/frontend-component-header#664 · 已指派 1 人 ·
-
难度 4/5 3-5 天 新手友好度 30/100
openedx/frontend-component-header#639 · 2 条评论 ·
-
难度 3/5 1-2 天 新手友好度 52/100
openedx/frontend-component-header#579 · 1 个 reaction ·
查看 openedx/frontend-component-header 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 85/100
pigeon189/zcode-fox-widget#17 · 1 条评论 ·
-
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 1 天内回复
-
Let TypeScript accept an initial object in ReactiveDict可能已有人在做 @yograj-vijay-yadav 今天认领。 未关闭bug good first issue help wanted workshop
难度 2/5 1-3 小时 新手友好度 92/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 65/100
akitaonrails/FrankMD#268 ·
维护者通常 1 天内回复
-
good first issue
难度 2/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复