diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index cf6e884b7b0..b0265713971 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -1438,25 +1438,14 @@ function ReviewChipAvatar({ } function GitHubAssigneeAvatar({ assignee }: { assignee: GitHubAssignableUser }): React.JSX.Element { - if (assignee.avatarUrl) { - return ( - {assignee.login} - ) - } return ( - - {assignee.login.slice(0, 1).toUpperCase()} - + ) } @@ -1896,13 +1885,12 @@ function GHAssigneesCell({ ) : null} - {user.avatarUrl ? ( - - ) : ( - - {user.login.slice(0, 1).toUpperCase()} - - )} + {user.login} {user.name ? ( @@ -2388,13 +2376,12 @@ function PRReviewCell({ {selected ? : null} - {reviewer.avatarUrl ? ( - - ) : ( - - {reviewer.login.slice(0, 1).toUpperCase()} - - )} + {reviewer.login} diff --git a/src/renderer/src/components/github/repro-8784-ghe-avatar-fallback.test.ts b/src/renderer/src/components/github/repro-8784-ghe-avatar-fallback.test.ts index f30da28d603..533274b2a95 100644 --- a/src/renderer/src/components/github/repro-8784-ghe-avatar-fallback.test.ts +++ b/src/renderer/src/components/github/repro-8784-ghe-avatar-fallback.test.ts @@ -51,4 +51,68 @@ describe('issue #8784 GHE avatar fallback (regression)', () => { // Why: list chip must not hardcode github.com/{login}.png. expect(taskPage).not.toMatch(/github\.com\/\$\{reviewer\.login\}\.png/) }) + + // GHES URLs can exist but fail unauthenticated; target slots need onError fallbacks. + // Scope checks because TaskPage also renders non-GitHub provider avatars. + const GITHUB_AVATAR_SLOTS = [ + { + file: 'pull-request-page/reviewers/picker-row.tsx', + fn: 'ReviewerPickerRow', + login: 'reviewer.login', + displayName: 'reviewer.name' + }, + { + file: 'pull-request-page/conversation/comment-card.tsx', + fn: 'ConversationCommentCard', + login: 'comment.author', + displayName: null + }, + { + file: 'pull-request-page/mentions/textarea.tsx', + fn: 'MentionTextarea', + login: 'option.login', + displayName: 'option.name' + }, + { + file: 'TaskPage.tsx', + fn: 'GitHubAssigneeAvatar', + login: 'assignee.login', + displayName: 'assignee.name' + }, + { file: 'TaskPage.tsx', fn: 'GHAssigneesCell', login: 'user.login', displayName: 'user.name' }, + { + file: 'TaskPage.tsx', + fn: 'PRReviewCell', + login: 'reviewer.login', + displayName: 'reviewer.name' + } + ] as const + + function componentBody(file: string, fn: string): string { + const source = readFileSync(join(__dirname, '..', file), 'utf8') + const start = source.indexOf(`function ${fn}`) + expect(start, `${file}: function ${fn} not found`).toBeGreaterThanOrEqual(0) + const next = source.indexOf('\nfunction ', start + 1) + return source.slice(start, next === -1 ? undefined : next) + } + + it.each(GITHUB_AVATAR_SLOTS)( + 'renders the $fn avatar through GitHubUserAvatar (#13976)', + ({ file, fn, login }) => { + const body = componentBody(file, fn) + + // Reject aliases and resolver expressions as well as direct avatarUrl fields. + expect(body, `${fn} still renders a bare img`).not.toMatch(/]/) + expect(body, `${fn} does not use GitHubUserAvatar`).toContain(' slot.displayName !== null))( + 'passes the $fn display name so initials are not reduced to one letter (#13976)', + ({ file, fn, displayName }) => { + expect(componentBody(file, fn)).toContain(`name={${displayName}}`) + } + ) }) diff --git a/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx b/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx index 66ed253a6c3..598a23ad6ca 100644 --- a/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/comment-card.tsx @@ -12,6 +12,7 @@ import { import { formatRelativeTime } from '@/components/github/work-item-state-presentation' import { CommentCodeContext } from '@/components/github/CommentCodeContext' import { CommentReactions } from '@/components/github/CommentReactions' +import { GitHubUserAvatar } from '@/components/github/github-user-avatar' import { translate } from '@/i18n/i18n' import type { GitHubOwnerRepo, GitHubPRFile } from '../../../../../shared/github/pull-request-types' import type { PRComment } from '../../../../../shared/github/comment-types' @@ -62,15 +63,12 @@ export function ConversationCommentCard({ )} >
- {comment.authorAvatarUrl ? ( - {comment.author} - ) : ( -
- )} + - {option.avatarUrl ? ( - - ) : ( -
- {option.login.slice(0, 1).toUpperCase()} -
- )} + @{option.login} {option.name && ( diff --git a/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx b/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx index a8b347765f2..5e24fffdbbb 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx @@ -2,6 +2,7 @@ import React from 'react' import { Check } from 'lucide-react' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' +import { GitHubUserAvatar } from '@/components/github/github-user-avatar' import type { GitHubAssignableUser } from '../../../../../shared/github/pull-request-types' export function ReviewerPickerRow({ @@ -59,13 +60,12 @@ export function ReviewerPickerRow({ {selected ? : null} - {reviewer.avatarUrl ? ( - - ) : ( - - {reviewer.login.slice(0, 1).toUpperCase()} - - )} + {reviewer.login}