Skip to content

feat(ui): show a user icon in the profile menu trigger - #46

Open
a-effort wants to merge 1 commit into
fix/vendor-chunk-cyclefrom
userprofile-icon
Open

feat(ui): show a user icon in the profile menu trigger#46
a-effort wants to merge 1 commit into
fix/vendor-chunk-cyclefrom
userprofile-icon

Conversation

@a-effort

@a-effort a-effort commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

Add icon to user settings dropdown:
dark_profile
light_profile

before after
empty size-6 box + chevron user icon in the same box + chevron

Theming

Colors come from the muted / muted-foreground tokens, which index.css redefines under .dark. Lucide icons stroke with currentColor, so light and dark need no conditional logic and no dark: variants. They use the same pairing the neighboring GitHub and docs icons in the header already use.

A test asserts the fallback className contains no dark: prefix, so a future hardcoded override trips a test instead of shipping.

Structuring for profile pictures later

UserAvatar owns the fallback ladder rather than HeaderProfileMenu inlining an icon, and takes an optional src.

Worth knowing: there is no avatar/picture field anywhere in the API... checked openapi.json, src/generated/types/contextForge.ts and src/types/user.ts. So, profile pictures need a backend field first; this isn't a frontend-only follow-up. The seam is here because Radix Avatar is what handles the image load/error swap, which is the awkward part to retrofit later. An initials leg (from full_name) is the middle step that needs no backend at all.

The primitive comes from the radix-ui umbrella package, already a dependency (no new dependencies added). Sizing stays size-6 rounded-md with overflow-hidden, so an image later fills the same box and the header geometry doesn't move.

Accessibility / i18n

The trigger button already carries aria-label={displayName}, so the icon is decorative: aria-hidden, no second accessible name, and no new i18n strings.

Tests

  • avatar.test.tsx, user-avatar.test.tsx
  • HeaderProfileMenu.test.tsx

Note UserAvatar's src path can't be exercised in jsdom: Radix builds an off-DOM Image and waits for load, which jsdom never fires, so the <img> never mounts. The test asserts what is meaningful there: passing a src neither crashes nor blanks the frame and says why inline. Image rendering needs an e2e check once the backend field exists.

tsc -b / eslint / prettier   clean
vitest                       167 files, 2908 passed, 1 skipped

⚠️ Merge order

Based on fix/vendor-chunk-cycle (#45)... do not merge this first.

This change shifts chunk composition enough to trip a latent rollup chunk cycle that #45 fixes. Without #45 the app serves a blank page (Cannot set properties of undefined (setting 'Activity')). I verified both directions: this commit without #45 fails a module-eval smoke test, with #45 it passes.

No CI job runs npm run build, and E2E drives the Vite dev server where manualChunks doesn't apply, so this PR goes green either way. The ordering has to be enforced by hand. Retarget to main once #45 merges; no rebase needed.

The header profile trigger rendered an empty rounded square, since no
avatar data exists to put in it. Fill it with a lucide UserRound behind
an Avatar primitive, so the frame that already reserved the space reads
as a person rather than a gap.

Colors come from the muted / muted-foreground tokens, which index.css
redefines under .dark, so light and dark need no conditional logic here
and no dark: variants. A test asserts the fallback carries no dark:
prefix, to keep a future hardcoded override from creeping back in.

UserAvatar owns the fallback ladder rather than HeaderProfileMenu
inlining an icon, and takes an optional src. The API exposes no avatar
field today, so that leg is unused — but Radix Avatar is what handles
the image load/error swap, which is the part that gets ugly to retrofit
once profile pictures land. The primitive comes from the radix-ui
umbrella package, already a dependency, so this adds none.

Sizing stays size-6 rounded-md with overflow-hidden: an image later
fills the same box and the header geometry does not move. The trigger
button already carries aria-label={displayName}, so the icon is
decorative and adds no second accessible name and no new i18n strings.

Signed-off-by: Anna Effort <anna.effort@ibm.com>
@a-effort
a-effort requested review from gandhipratik203 and gcgoncalves and a lite review from Copilot and removed request for Copilot August 19, 2026 04:26
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.

1 participant